1. 为什么选择Pytest+Selenium做UI自动化测试
在软件测试领域,UI自动化测试一直是个既重要又让人头疼的话题。我见过太多团队投入大量时间搭建UI自动化框架,最终却因为维护成本高、用例脆弱而放弃。经过多年实践,我发现Pytest+Selenium的组合是目前最经济高效的解决方案。
Pytest作为Python生态中最主流的测试框架,相比unittest等传统框架有几大杀手锏:
- 更简洁的断言语法(assert直接使用Python原生语法)
- 强大的fixture机制(测试前置后置处理)
- 丰富的插件生态(allure报告、并行测试等)
- 参数化测试支持(一套代码测多组数据)
而Selenium作为浏览器自动化的事实标准,其优势在于:
- 跨浏览器支持(Chrome/Firefox/Edge等)
- 多语言绑定(Python/Java/C#等)
- 活跃的社区支持
- 与各种云测试平台的无缝集成
当这两个工具强强联合时,就能构建出既灵活又稳定的UI自动化测试体系。下面这个真实案例来自我最近参与的电商项目:我们只用200行代码就实现了核心购物流程的自动化验证,而且这些用例已经稳定运行了6个月,捕获了12次线上问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境搭建与基础配置
2.1 开发环境准备
我强烈推荐使用Anaconda来管理Python环境,它能完美解决不同项目间的依赖冲突问题。以下是具体步骤:
bash复制# 创建专用环境
conda create -n ui_auto python=3.8
conda activate ui_auto
# 安装核心依赖
pip install pytest selenium webdriver-manager
webdriver-manager是个神器,它能自动下载和管理各浏览器的driver二进制文件,省去了手动配置的麻烦。以前我们需要:
- 访问浏览器厂商官网
- 找到对应版本的driver
- 设置PATH环境变量
现在一行代码就能搞定:
python复制from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
2.2 Pytest基础配置
在项目根目录创建pytest.ini文件,这是我的推荐配置:
ini复制[pytest]
testpaths = tests
python_files = test_*.py
python_functions = test_*
addopts = -v --tb=short --strict-markers
markers =
smoke: 冒烟测试
regression: 回归测试
关键参数说明:
--tb=short让错误堆栈更简洁--strict-markers强制标记声明,避免拼写错误- 自定义markers可以灵活组织测试套件
3. 实战:电商登录测试案例
3.1 页面对象模型(POM)实现
好的UI自动化一定要遵循POM设计模式,把页面元素定位与业务操作分离。下面是我们电商登录页的实现:
python复制# pages/login_page.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class LoginPage:
def __init__(self, driver):
self.driver = driver
self.wait = WebDriverWait(driver, 10)
@property
def username_field(self):
return self.wait.until(EC.presence_of_element_located(
(By.CSS_SELECTOR, "#username")))
@property
def password_field(self):
return self.driver.find_element(By.ID, "password")
@property
def submit_button(self):
return self.driver.find_element(By.XPATH, "//button[@type='submit']")
def login(self, username, password):
self.username_field.send_keys(username)
self.password_field.send_keys(password)
self.submit_button.click()
return HomePage(self.driver)
这里有几个值得注意的技巧:
- 对关键元素使用WebDriverWait显式等待
- 使用@property装饰器实现懒加载
- 方法返回新页面对象实现流程串联
3.2 测试用例编写
对应测试用例应该这样写:
python复制# tests/test_login.py
import pytest
from pages.login_page import LoginPage
@pytest.mark.smoke
class TestLogin:
@pytest.fixture(autouse=True)
def setup(self, browser):
self.browser = browser
self.browser.get("https://shop.example.com/login")
self.login_page = LoginPage(browser)
def test_valid_login(self):
home_page = self.login_page.login("valid_user", "valid_pass")
assert "我的账户" in home_page.title
@pytest.mark.parametrize("username,password,expected", [
("", "pass123", "用户名不能为空"),
("test", "", "密码不能为空"),
("wrong", "wrong", "用户名或密码错误")
])
def test_invalid_login(self, username, password, expected):
self.login_page.login(username, password)
assert expected in self.login_page.error_message
这个案例展示了:
- 使用class组织相关测试
- autouse fixture实现测试前置
- 参数化测试覆盖多种边界情况
- 合理的断言验证点
4. 高级技巧与最佳实践
4.1 智能等待策略
UI自动化最大的痛点就是元素加载时机问题。我的经验是:
python复制# 在BasePage中定义智能等待方法
def wait_for(self, locator, timeout=10, poll_frequency=0.5):
try:
return WebDriverWait(self.driver, timeout, poll_frequency).until(
EC.presence_of_element_located(locator))
except TimeoutException:
self._highlight(locator) # 调试时高亮显示
raise
同时要避免以下反模式:
- 硬性sleep(浪费执行时间)
- 过度使用隐式等待(影响全局)
- 不处理StaleElementReferenceException
4.2 失败自动截图
通过pytest钩子函数实现失败自动截图:
python复制# conftest.py
import pytest
from datetime import datetime
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
report = outcome.get_result()
if report.when == "call" and report.failed:
driver = item.funcargs["browser"]
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
screenshot_path = f"screenshots/failure_{item.name}_{timestamp}.png"
driver.save_screenshot(screenshot_path)
report.extra = [("image", screenshot_path)]
4.3 跨浏览器测试配置
通过pytest命令行参数实现浏览器切换:
python复制# conftest.py
def pytest_addoption(parser):
parser.addoption("--browser", action="store", default="chrome")
@pytest.fixture
def browser(request):
browser_name = request.config.getoption("--browser")
if browser_name == "firefox":
driver = webdriver.Firefox()
elif browser_name == "edge":
driver = webdriver.Edge()
else:
options = webdriver.ChromeOptions()
options.add_argument("--headless") # 无头模式
driver = webdriver.Chrome(options=options)
driver.implicitly_wait(5)
yield driver
driver.quit()
使用时只需:
bash复制pytest --browser=firefox
5. 常见问题排查指南
5.1 ElementNotInteractableException问题
这是新手最常遇到的错误之一,通常有以下几种原因:
-
元素被遮挡(如弹窗、广告)
- 解决方案:先关闭遮挡元素或滚动到目标位置
python复制self.driver.execute_script("arguments[0].scrollIntoView();", element) -
元素在iframe中
- 必须显式切换到iframe:
python复制self.driver.switch_to.frame("iframe_id") # 操作元素... self.driver.switch_to.default_content() -
元素状态不正确(如disabled)
- 需要先检查元素状态:
python复制assert element.is_enabled(), "元素不可用"
5.2 自动化测试稳定性提升
根据我的经验,提升稳定性需要:
-
使用可靠的定位策略优先级:
- ID > CSS Selector > XPath(尽量避免完整路径)
-
实现重试机制:
python复制@pytest.mark.flaky(reruns=2, reruns_delay=1) def test_unstable_feature(): ... -
定期清理测试数据:
python复制@pytest.fixture def clean_test_data(): yield # 测试后清理逻辑
6. 测试报告与持续集成
6.1 Allure报告集成
安装依赖:
bash复制pip install allure-pytest
运行测试:
bash复制pytest --alluredir=./allure-results
allure serve ./allure-results
在用例中添加步骤说明:
python复制import allure
@allure.step("登录操作")
def login(username, password):
...
@allure.title("测试有效登录")
def test_login():
with allure.step("输入凭证"):
login("user", "pass")
6.2 Jenkins集成配置
在Jenkinsfile中添加:
groovy复制pipeline {
agent any
stages {
stage('Test') {
steps {
sh 'pytest --alluredir=allure-results'
}
post {
always {
allure includeProperties: false,
jdk: '',
results: [[path: 'allure-results']]
}
}
}
}
}
7. 从Selenium迁移到Playwright
最近我开始将部分项目迁移到Playwright,它有几个明显优势:
- 自动等待机制更智能
- 内置截图和录屏功能
- 支持移动端模拟
- 更快的执行速度
迁移示例:
python复制# 原Selenium代码
driver.find_element(By.ID, "submit").click()
# Playwright等效代码
page.click("#submit")
不过对于已有成熟Selenium套件的团队,我的建议是:
- 新项目可以直接用Playwright
- 老项目逐步迁移
- 关键路径保持双套实现直到Playwright稳定
