必须用 pytest.fixture 管理 WebDriver 生命周期并采用 PO 模式封装页面操作,否则会导致浏览器残留、状态丢失、维护困难;需通过 yield 保证 driver.quit() 执行,并在失败时自动截图留存现场。

直接用 pytest 跑 Selenium 脚本是可行的,但不加约束会很快失控:浏览器没关、元素找不到就报错、失败没截图、用例一改全崩。核心问题不在“能不能跑”,而在于“怎么让每次运行都可预期、可追溯、可维护”。
为什么不能直接在test_*.py里写driver = webdriver.Chrome()
这不是语法错误,而是工程隐患。最典型的表现是:
- 每个测试函数都新建/关闭一次浏览器,执行慢、资源占用高
-
driver实例在函数间无法共享,登录态、cookie、页面跳转状态全断开 - 异常退出时
driver.quit()没被调用,残留进程卡死端口 - 所有定位逻辑(
find_element(By.ID, "xxx"))散落在用例里,UI一改就得全局搜索替换
解决思路是把浏览器生命周期交给 pytest 的 @pytest.fixture 管理,并把页面操作封装进独立类——不是“能不能”,而是“必须这么做”。
用fixture统一管理WebDriver生命周期
把浏览器启动、配置、关闭逻辑抽成一个作用域为 session 或 function 的 fixture,比手写 setUp/tearDown 更灵活、更可靠。
立即学习“Python免费学习笔记(深入)”;
示例(放在 conftest.py 中):
@pytest.fixture(scope="function")
def driver():
options = webdriver.ChromeOptions()
options.add_argument("--headless") # 无头模式
options.add_argument("--no-sandbox")
driver = webdriver.Chrome(options=options)
driver.implicitly_wait(5) # 全局隐式等待,慎用
yield driver
driver.quit() # 无论是否异常,都会执行
关键点:
- 用
yield而非return,确保driver.quit()必然执行 - 避免在 fixture 里设
time.sleep();显式等待(WebDriverWait)应放在页面对象或测试步骤中 - 如果多个用例需共享登录态,把 scope 改为
"class"或"module",但要同步处理 cookie 同步或页面重用逻辑
PO模式不是可选项,是必选项
当你看到测试用例里反复出现 driver.find_element(By.XPATH, "//button[@data-testid='submit']"),就该停手重构了。这不是代码风格问题,是维护成本爆炸的前兆。
inference.sh 的 Python SDK:运行 AI 应用、构建智能体,并集成 150 多个模型。包名:inferencesh (pip install inferencesh)。支持同步/异步……
一个最小可用的页面类示例:
class LoginPage:
def __init__(self, driver):
self.driver = driver
self.username_field = (By.ID, "username")
self.password_field = (By.ID, "password")
self.login_button = (By.CSS_SELECTOR, "button[type='submit']")
<pre class="brush:php;toolbar:false;">def input_username(self, text):
self.driver.find_element(*self.username_field).send_keys(text)
def click_login(self):
self.driver.find_element(*self.login_button).click()
对应测试用例就变成:
def test_login_success(driver):
page = LoginPage(driver)
page.input_username("testuser")
page.click_login()
assert "dashboard" in driver.current_url
好处很实在:
- UI 变动只改
LoginPage类里的定位器,不用动任何测试函数 - 方法名即行为语义(
input_username),比裸写send_keys更易读、易复用 - 后续加等待逻辑(如
WebDriverWait(driver, 10).until(...))只在此类中集中处理
失败时没截图 = 白测
UI 测试失败最头疼的不是报错,而是不知道当时页面长什么样。光靠日志和 URL 很难还原现场。
在 fixture 的异常清理阶段补上截图逻辑:
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
report = outcome.get_result()
if report.when == "call" and report.failed:
driver = item.funcargs.get("driver")
if driver:
screenshot_path = f"screenshots/{item.name}_{int(time.time())}.png"
os.makedirs("screenshots", exist_ok=True)
driver.save_screenshot(screenshot_path)
注意:
- 不要依赖
driver.get_screenshot_as_file()在测试函数里手动调,容易遗漏 - 截图路径要带时间戳或唯一ID,否则并发执行时会覆盖
- 如果用了 Allure,可进一步用
allure.attach.file()把截图嵌入报告,但基础截图逻辑必须先稳住
真正难的从来不是写出能跑的脚本,而是让脚本在 CI 上连续跑一周都不飘、换人接手三天就能上手改、UI 改版后只需动两行定位器。这些事,全靠 fixture 约束生命周期、PO 封装变化点、截图固化现场——缺一不可。

















