1. 项目概述从零到一构建UI自动化测试体系如果你是一名测试工程师或者正在向这个方向转型那么“UI自动化测试”这个词对你来说一定不陌生。它听起来很酷能解放双手但真正上手时很多人都会卡在第一步如何把Selenium的零散脚本变成一个稳定、可维护、能持续集成的自动化测试项目这正是“PytestSelenium UI自动化测试实战实例”这个标题背后要解决的核心痛点。它不是简单地教你写几个find_element和click而是分享一套经过实战检验的、从脚本到框架的完整工程化解决方案。我经历过从用unittest写几百行一个的测试用例到后来用Pytest重构整个测试套件效率提升了好几倍。这个组合之所以强大在于Pytest提供了极其灵活和强大的测试组织、发现、运行和报告能力而Selenium则是操控浏览器的“金手指”。两者结合你得到的不是一个玩具而是一个可以应对真实复杂Web应用、支持团队协作、并能融入CI/CD流水线的生产级测试工具链。无论你是想为个人项目增加自动化测试还是要在团队中推动测试左移和持续测试这套组合都是目前Python生态中最主流、最值得投入时间学习的技术栈。接下来我将以一个电商网站核心购物流程的测试为例带你一步步搭建这个框架并分享那些官方文档里不会写的“踩坑”经验和性能调优技巧。2. 整体框架设计与核心思路拆解在动手写代码之前我们先要搞清楚我们要建的是什么以及为什么选择Pytest和Selenium这个组合。很多新手会直接打开IDE开始录制脚本或抄写代码但缺乏顶层设计项目很快就会变得难以维护用例之间耦合严重定位问题耗时耗力。2.1 为什么是Pytest Selenium首先看Selenium它是Web UI自动化的行业标准支持所有主流浏览器提供了丰富的API来模拟用户操作。但Selenium本身只是一个“驱动浏览器”的工具库它不负责测试用例的管理、数据驱动、夹具Fixture生命周期、测试报告生成等。这些正是测试框架需要解决的问题。Pytest作为一个全功能的测试框架完美地弥补了这些缺口简易性用例就是普通的Python函数用assert语句进行断言学习成本极低。强大的Fixture这是Pytest的灵魂。你可以用Fixture来管理测试前置和后置操作比如启动/关闭浏览器、登录、准备测试数据。Fixture支持作用域函数、类、模块、会话可以极大地减少重复代码并保证资源如浏览器实例的高效利用。参数化轻松实现数据驱动测试。用pytest.mark.parametrize一个装饰器就能用多组数据运行同一个测试逻辑。丰富的插件生态pytest-html生成美观的HTML报告pytest-xdist支持分布式并行测试pytest-rerunfailures对失败用例进行重试这些都能直接提升自动化测试的效率和可靠性。灵活的钩子Hook允许你在测试生命周期的各个阶段插入自定义逻辑比如修改测试报告、处理失败截图等。因此我们的设计思路是用Pytest来组织和驱动测试流程用Selenium来执行具体的页面交互操作。同时我们会引入Page Object ModelPOM页面对象模型设计模式将页面元素定位和操作封装成独立的类让测试脚本用例只关注业务逻辑和测试断言从而实现业务与技术的分离提升代码的可读性和可维护性。2.2 项目目录结构规划一个清晰的目录结构是项目可维护性的基石。下面是我推荐并将在实例中使用的结构pytest_selenium_demo/ ├── conftest.py # Pytest全局配置文件定义核心Fixture如driver ├── pytest.ini # Pytest配置文件定义命令行默认参数、标记等 ├── requirements.txt # 项目依赖包列表 ├── common/ # 公共模块 │ ├── __init__.py │ ├── base_page.py # 所有Page类的基类封装公共方法 │ ├── logger.py # 自定义日志模块 │ └── config.py # 配置文件读取如URL、账号、超时时间 ├── pages/ # 页面对象层POM │ ├── __init__.py │ ├── login_page.py # 登录页面 │ ├── home_page.py # 首页 │ └── cart_page.py # 购物车页面 ├── test_cases/ # 测试用例层 │ ├── __init__.py │ ├── test_login.py # 登录功能测试 │ └── test_shopping_flow.py # 购物流程测试 ├── test_data/ # 测试数据层JSON/YAML/Excel │ └── user_data.json ├── reports/ # 测试报告输出目录.gitignore忽略 │ └── 20240527_103022.html └── screenshots/ # 失败用例截图目录.gitignore忽略 └── test_failed_....png这个结构体现了清晰的层次common放一些底层工具比如读写配置、记录日志的模块。pages每个页面对应一个类里面是这个页面的元素定位器和操作方法。test_cases这里才是真正的测试用例它们调用pages里的方法来完成操作和断言。test_data把测试数据特别是账号密码从代码里分离出来方便管理和参数化。reports/screenshots输出目录通常不纳入版本控制。注意conftest.py是Pytest的一个特殊文件它里面定义的Fixture可以被同一目录及子目录下的所有测试文件自动发现和使用。我们把最核心的driverFixture放在项目根目录的conftest.py里这样所有用例都能共用同一个浏览器会话管理逻辑。3. 环境搭建与核心工具链配置工欲善其事必先利其器。这一步我们会配置好整个项目运行所需的所有环境包括Python环境、浏览器驱动、以及Pytest的核心插件。3.1 基础环境安装与依赖管理首先确保你安装了Python建议3.8及以上版本。然后在项目根目录下创建requirements.txt文件并写入以下内容# 测试框架与运行器 pytest7.0.0 # 浏览器自动化 selenium4.0.0 # 生成HTML测试报告 pytest-html3.0.0 # 失败用例自动重试 pytest-rerunfailures10.0 # 控制用例执行顺序谨慎使用 pytest-ordering0.6 # 漂亮的终端报告 pytest-sugar0.9.0 # 数据驱动支持如果需要 pytest-datadir1.3.1在终端中进入项目目录运行以下命令安装所有依赖pip install -r requirements.txt这里解释几个关键依赖的选择Selenium 4.x相比3.x4.x提供了更符合W3C标准的API并且原生支持了相对定位器Relative Locators等新特性是未来的方向。我们直接使用最新稳定版。pytest-html这是生成可视化报告最常用的插件生成的HTML报告信息全面便于分享和归档。pytest-rerunfailuresUI自动化测试不稳定是常态可能因为网络延迟、元素加载慢等原因导致偶发性失败。这个插件允许你对失败用例进行重试可以有效过滤掉一些非代码缺陷导致的失败提升测试套件的稳定性。3.2 浏览器驱动管理告别手动下载最让人头疼的莫过于浏览器驱动如chromedriver的版本匹配问题。Chrome浏览器频繁更新手动下载和配置驱动非常麻烦。这里我强烈推荐使用webdriver-manager这个第三方库它可以自动检测你本地安装的浏览器版本并下载匹配的驱动。首先安装它pip install webdriver-manager然后在你的conftest.py中就可以这样优雅地初始化driver完全无需关心驱动路径# conftest.py import pytest from selenium import webdriver from selenium.webdriver.chrome.service import Service as ChromeService from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.options import Options pytest.fixture(scopefunction) # 每个测试函数一个独立的浏览器实例 def driver(): chrome_options Options() # 常用配置项 chrome_options.add_argument(--start-maximized) # 启动时最大化窗口 chrome_options.add_argument(--disable-gpu) # 禁用GPU加速某些环境下更稳定 # chrome_options.add_argument(--headless) # 无头模式不打开GUI常用于CI环境 chrome_options.add_experimental_option(excludeSwitches, [enable-logging]) # 禁止控制台日志 # 使用webdriver-manager自动管理驱动 service ChromeService(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice, optionschrome_options) # 设置隐式等待全局等待元素出现的超时时间 driver.implicitly_wait(10) yield driver # 将driver对象提供给测试用例 # 测试结束后执行清理工作 driver.quit()这个driverFixture的作用域是function意味着每个测试用例都会启动和关闭一次浏览器。这样做虽然耗时但保证了用例之间的绝对隔离避免了一个用例的状态污染另一个用例。如果你的用例是纯读操作且无状态依赖可以考虑使用scopeclass或scopemodule来提升执行速度。实操心得在团队协作中将webdriver-manager写入requirements.txt可以确保所有成员和CI服务器环境都能自动获取正确的驱动极大降低了环境配置成本。另外无头模式--headless在CI/CD流水线中非常有用因为它不需要图形界面。但在调试阶段建议关闭无头模式以便直观地观察测试执行过程。3.3 Pytest核心配置与插件调优接下来配置pytest.ini文件它相当于Pytest的“仪表盘”可以预设很多运行选项。# pytest.ini [pytest] # 指定测试文件的位置和命名规则 testpaths test_cases python_files test_*.py python_classes Test* python_functions test_* # 添加命令行默认参数 addopts -v # 详细输出 --htmlreports/report.html # 生成HTML报告 --self-contained-html # 生成独立的HTML报告内联CSS/JS --reruns 1 # 失败后重试1次 --reruns-delay 2 # 每次重试间隔2秒 # 自定义标记用于分类运行用例 markers smoke: 冒烟测试用例 regression: 回归测试用例 login: 与登录相关的测试addopts这里设置的参数会在每次运行pytest命令时自动生效。我们配置了生成HTML报告、失败重试等实用功能。markers你可以给测试用例打上不同的标签例如pytest.mark.smoke。之后就可以用pytest -m smoke只运行冒烟测试非常灵活。4. 页面对象模型POM的深度实现POM是UI自动化测试中最重要的设计模式没有之一。它的核心思想是将页面封装成对象测试脚本通过操作这些对象来完成业务流。下面我们以登录页面为例深入讲解如何实现一个健壮的POM。4.1 构建可复用的页面基类首先在common/base_page.py中创建一个所有页面类的基类。这个基类封装了Selenium最常用的操作并加入等待、日志等增强功能。# common/base_page.py import logging from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException, NoSuchElementException class BasePage: def __init__(self, driver): self.driver driver self.logger logging.getLogger(__name__) self.wait WebDriverWait(driver, 10) # 显式等待对象 def find_element(self, locator): 查找单个元素加入显式等待和日志 self.logger.info(f正在查找元素: {locator}) try: # 使用显式等待直到元素可见并可交互 element self.wait.until(EC.visibility_of_element_located(locator)) self.logger.info(f成功找到元素: {locator}) return element except TimeoutException: self.logger.error(f查找元素超时: {locator}) # 这里可以附加截图操作 raise def click(self, locator): 点击元素 element self.find_element(locator) self.logger.info(f点击元素: {locator}) element.click() def input_text(self, locator, text): 向输入框输入文本并清空原有内容 element self.find_element(locator) element.clear() self.logger.info(f向元素 {locator} 输入文本: {text}) element.send_keys(text) def get_text(self, locator): 获取元素的文本内容 element self.find_element(locator) text element.text self.logger.info(f获取元素 {locator} 的文本: {text}) return text def is_element_present(self, locator, timeout5): 判断元素是否存在不抛异常 try: WebDriverWait(self.driver, timeout).until( EC.presence_of_element_located(locator) ) return True except TimeoutException: return False这个基类的关键在于统一的元素查找所有查找都通过find_element方法内部集成了显式等待和日志记录。显式等待比隐式等待更精确它针对某个特定条件进行等待而不是一个全局的固定时间。日志集成每个关键操作都记录日志这在调试复杂用例时是无价之宝。常用操作封装将click,input_text等高频操作封装起来使页面类代码更简洁。4.2 实现具体的页面对象以登录页面为例有了基类我们就可以创建具体的页面类了。以pages/login_page.py为例# pages/login_page.py from selenium.webdriver.common.by import By from common.base_page import BasePage class LoginPage(BasePage): # 1. 元素定位器Locators集中管理 # 使用 (By.策略, 表达式) 的元组形式 USERNAME_INPUT (By.ID, username) PASSWORD_INPUT (By.ID, password) LOGIN_BUTTON (By.XPATH, //button[typesubmit]) ERROR_MESSAGE (By.CLASS_NAME, alert-error) SUCCESS_MESSAGE (By.CSS_SELECTOR, .welcome-msg) # 2. 页面URL可选 URL https://example.com/login def __init__(self, driver): super().__init__(driver) self.driver.get(self.URL) # 初始化时打开页面 # 3. 页面操作方法 def enter_username(self, username): self.input_text(self.USERNAME_INPUT, username) return self # 支持链式调用 def enter_password(self, password): self.input_text(self.PASSWORD_INPUT, password) return self def click_login(self): self.click(self.LOGIN_BUTTON) # 4. 业务场景组合方法 def login(self, username, password): 完整的登录业务流 self.logger.info(f执行登录操作用户名: {username}) self.enter_username(username) self.enter_password(password) self.click_login() # 5. 页面状态断言方法 def get_error_message(self): 获取登录错误提示信息 if self.is_element_present(self.ERROR_MESSAGE): return self.get_text(self.ERROR_MESSAGE) return def is_login_successful(self): 判断是否登录成功 return self.is_element_present(self.SUCCESS_MESSAGE)这个登录页面类体现了POM的精髓元素定位集中化所有定位器都定义为类属性放在文件顶部。如果前端页面元素ID或XPath变了你只需要修改这一个地方所有用到它的测试用例都会自动生效。操作原子化每个页面元素的操作都封装成一个独立的方法如enter_username。业务组合化login方法将多个原子操作组合成一个完整的业务流。测试用例只需要调用login(username, password)代码非常清晰。页面状态可查询提供了get_error_message和is_login_successful等方法供测试用例进行断言。避坑技巧关于元素定位策略的优先级我个人的经验法则是ID Name CSS Selector XPath。ID和Name通常最稳定且速度快。CSS Selector在大多数情况下比XPath性能更好语法也更简洁。XPath功能强大但性能相对较差且容易因页面结构微小变动而失效应作为最后的选择。对于动态ID可以考虑使用CSS Selector的部分匹配如[id*part_of_id]或XPath的contains函数。5. 测试用例的编写与组织艺术有了稳固的页面对象层编写测试用例就变成了一件愉快的事情。用例脚本将变得非常简洁只关注“测试什么”和“期望是什么”。5.1 编写第一个测试用例登录功能测试在test_cases/test_login.py中我们编写登录功能的测试。# test_cases/test_login.py import pytest import logging from pages.login_page import LoginPage # 获取日志记录器 logger logging.getLogger(__name__) class TestLogin: 登录功能测试集 pytest.mark.smoke pytest.mark.login def test_login_success(self, driver): 测试正常用户名密码登录成功 logger.info(开始执行测试: test_login_success) login_page LoginPage(driver) # 调用页面对象的业务方法 login_page.login(valid_user, valid_password) # 断言验证登录成功后的页面状态 assert login_page.is_login_successful() is True # 可以进一步断言跳转后的URL或页面标题 # assert dashboard in driver.current_url logger.info(测试通过: 登录成功) pytest.mark.regression pytest.mark.login pytest.mark.parametrize(username, password, expected_error, [ (, somepass, 用户名不能为空), (invalid_user, , 密码不能为空), (wrong_user, wrong_pass, 用户名或密码错误), ]) def test_login_failure(self, driver, username, password, expected_error): 参数化测试多种错误的登录场景 logger.info(f开始执行参数化测试: username{username}, password{password}) login_page LoginPage(driver) login_page.login(username, password) # 断言验证出现了预期的错误提示信息 actual_error login_page.get_error_message() # 这里使用 in 而不是 因为实际错误信息可能包含更多内容 assert expected_error in actual_error, f期望错误信息包含 {expected_error} 实际得到 {actual_error} logger.info(f测试通过: 错误场景 {expected_error} 验证成功)这个测试类展示了几个关键点用例即函数每个测试用例都是一个以test_开头的方法。使用Fixturedriver参数会自动注入我们在conftest.py中定义的driverFixture。使用标记我们用pytest.mark.smoke等装饰器给用例打标签方便分类运行。参数化测试pytest.mark.parametrize是数据驱动的利器。它允许你用多组数据运行同一个测试逻辑极大减少了重复代码。上面的例子用三组无效数据测试了登录失败的不同场景。清晰的断言断言语句明确表达了期望的结果。断言失败时Pytest会给出清晰的错误信息。5.2 组织复杂业务流程测试购物流程单个页面的测试是基础真正的价值在于模拟用户的端到端业务流程。我们来看一个购物流程的测试例子test_cases/test_shopping_flow.py。# test_cases/test_shopping_flow.py import pytest import logging from pages.login_page import LoginPage from pages.home_page import HomePage from pages.cart_page import CartPage from pages.product_page import ProductPage logger logging.getLogger(__name__) class TestShoppingFlow: 端到端购物流程测试 pytest.mark.smoke pytest.fixture(autouseTrue) # autouseTrue 表示这个fixture会自动被类中的所有用例使用 def pre_condition(self, driver): 前置条件用户已登录 logger.info(执行购物流程前置条件用户登录) login_page LoginPage(driver) login_page.login(standard_user, secret_sauce) yield # 如果需要后置清理可以写在这里 logger.info(购物流程测试结束) def test_add_item_to_cart_and_checkout(self, driver): 测试添加商品到购物车并结算 logger.info(开始测试添加商品到购物车并结算) home_page HomePage(driver) product_page ProductPage(driver) cart_page CartPage(driver) # 步骤1: 在首页点击第一个商品 home_page.click_first_product() # 步骤2: 在产品详情页将商品加入购物车 product_page.add_to_cart() # 步骤3: 前往购物车页面 product_page.go_to_cart() # 步骤4: 验证购物车中有该商品 assert cart_page.get_cart_item_count() 1 item_name cart_page.get_first_item_name() assert Sauce Labs Backpack in item_name # 假设商品名 # 步骤5: 点击结算 cart_page.click_checkout() # ... 后续填写地址、支付信息等步骤 logger.info(测试通过购物车添加与结算流程完整)这个测试用例展示了如何串联多个页面对象完成一个完整的用户旅程。pytest.fixture(autouseTrue)是一个很实用的技巧它让pre_condition这个Fixture自动应用于类中的每一个测试方法避免了在每个方法开头都写一遍登录代码。6. 高级技巧与实战问题深度排查当基础框架搭建起来后你会遇到各种实际挑战。下面分享几个提升测试稳定性、可维护性和效率的高级技巧。6.1 处理动态加载与元素等待策略现代Web应用大量使用Ajax和前端框架元素不会一次性全部加载出来。不恰当的等待是UI自动化失败的首要原因。1. 抛弃time.sleep()拥抱显式等待绝对不要在测试脚本中使用time.sleep(10)这种固定等待。它要么浪费大量时间元素提前出现了要么依然会失败元素加载比10秒还慢。始终使用Selenium提供的WebDriverWait配合expected_conditions。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By # 等待元素可见并可点击 wait WebDriverWait(driver, 10) # 最多等10秒 element wait.until(EC.element_to_be_clickable((By.ID, dynamic-button))) # 等待元素消失如加载动画 wait.until(EC.invisibility_of_element_located((By.CLASS_NAME, loading-spinner))) # 等待页面标题包含特定文字 wait.until(EC.title_contains(订单完成))2. 自定义等待条件有时内置的条件不够用你可以自定义一个等待函数。def wait_for_text_in_element(driver, locator, text, timeout10): 等待某个元素的文本包含指定的文字 def predicate(_driver): try: element _driver.find_element(*locator) return text in element.text except Exception: return False WebDriverWait(driver, timeout).until(predicate) # 使用 wait_for_text_in_element(driver, (By.ID, status), 支付成功)6.2 应对反爬与浏览器指纹识别一些网站会检测Selenium的自动化特征并进行屏蔽。如果你的脚本突然无法工作可能是触发了反爬机制。常见特征与规避方法window.navigator.webdriver属性在普通浏览器中为undefined或false在Selenium控制的浏览器中为true。chrome_options.add_argument(--disable-blink-featuresAutomationControlled) chrome_options.add_experimental_option(excludeSwitches, [enable-automation]) chrome_options.add_experimental_option(useAutomationExtension, False) # 执行CDP命令覆盖webdriver属性 driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, { get: () undefined }); })User-Agent有些Selenium使用的默认UA会被识别。可以设置为一个常见的浏览器UA。chrome_options.add_argument(user-agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...)行为模式过于机械化的操作如毫秒级精准点击、无鼠标移动轨迹可能被识别。可以引入随机延迟和模拟人类鼠标移动可使用ActionChains稍微复杂化操作。重要提示这些技术应仅用于对自己拥有或获得授权的网站进行测试。用于绕过他人网站的防护机制可能违反服务条款甚至法律。6.3 测试报告增强与失败分析生成的HTML报告是测试结果的最终呈现。我们可以通过Pytest的钩子函数来增强它比如自动附加失败截图。在conftest.py中添加以下代码# conftest.py (追加内容) import pytest from datetime import datetime import os pytest.hookimpl(tryfirstTrue, hookwrapperTrue) def pytest_runtest_makereport(item, call): Hook函数用于在测试报告生成时获取结果并截图 outcome yield report outcome.get_result() # 只关注测试用例setup/call/teardown的call阶段且是失败或错误的情况 if report.when call and report.failed: # 获取测试用例中的driver fixture for name, fixtureinfo in item._fixtureinfo.name2fixturedefs.items(): if name driver: driver item.funcargs[name] break else: # 如果没有找到driver则跳过 return # 创建截图目录 screenshot_dir screenshots os.makedirs(screenshot_dir, exist_okTrue) # 生成带时间戳的截图文件名 timestamp datetime.now().strftime(%Y%m%d_%H%M%S) test_name item.name screenshot_path os.path.join(screenshot_dir, f{test_name}_{timestamp}.png) # 截图并保存 try: driver.save_screenshot(screenshot_path) # 将截图路径附加到测试报告的extra信息中pytest-html插件会读取 if hasattr(report, extra): # 确保extra是列表 from pytest_html import extras report.extra.append(extras.image(screenshot_path)) except Exception as e: print(f截图失败: {e})现在每当有测试用例失败HTML报告中就会自动嵌入一张截图你可以直观地看到失败时浏览器页面的状态这对于远程调试和问题定位至关重要。6.4 并行测试与执行优化当测试用例成百上千时串行执行会非常耗时。使用pytest-xdist插件可以轻松实现并行测试。安装后只需在运行命令后加上-n参数pytest -n auto # auto会自动检测CPU核心数来分配进程 pytest -n 4 # 指定用4个进程并行运行并行测试的注意事项用例独立性并行执行要求测试用例之间没有依赖不能共享状态。这正是我们为每个用例使用独立driverFixturescopefunction的原因。资源竞争如果用例操作共享资源如同一个测试数据库的某条记录需要设计好数据隔离策略例如使用随机生成的数据。日志与报告并行执行的日志会混在一起建议使用pytest的--tbshort简化回溯信息并确保日志模块是进程安全的或者为每个进程生成独立的日志文件。7. 持续集成CI集成与团队协作自动化测试只有融入CI/CD流水线才能最大化其价值。这里以GitHub Actions为例展示如何将PytestSelenium项目集成进去。在项目根目录创建.github/workflows/test.ymlname: UI Automation Tests on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: test: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkoutv3 - name: Set up Python uses: actions/setup-pythonv4 with: python-version: 3.9 - name: Install system dependencies (for Chrome) run: | sudo apt-get update sudo apt-get install -y wget unzip wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - echo deb [archamd64] http://dl.google.com/linux/chrome/deb/ stable main | sudo tee /etc/apt/sources.list.d/google-chrome.list sudo apt-get update sudo apt-get install -y google-chrome-stable - name: Install Python dependencies run: | pip install --upgrade pip pip install -r requirements.txt - name: Run UI Tests with Pytest run: | # 在无头模式下运行测试并生成报告 pytest -v --headless --htmlreports/report.html --self-contained-html - name: Upload test report uses: actions/upload-artifactv3 if: always() # 即使测试失败也上传报告 with: name: pytest-html-report path: reports/这个工作流做了以下几件事在Ubuntu最新环境中启动一个虚拟机。安装Chrome浏览器因为我们要跑UI测试。安装Python依赖包括webdriver-manager它会自动下载chromedriver。以无头模式运行所有测试。将生成的HTML报告打包上传供后续查看。团队协作心得在团队中推行自动化测试技术选型只是第一步。更重要的是建立规范统一的代码风格如使用Black格式化、清晰的目录结构、定期的用例评审、以及将测试结果作为代码合并的准入门槛之一。将CI流水线配置好让每次提交都能自动运行测试并生成报告能极大地提升代码质量和团队信心。从单个的Selenium脚本到一个结构清晰、易于维护、支持团队协作和持续集成的Pytest自动化测试项目这条路我走过也踩过不少坑。核心在于理解Pytest的Fixture机制和POM设计模式的思想它们是将“脚本”提升为“工程”的关键。环境问题用webdriver-manager解决稳定性问题用显式等待和重试机制应对报告问题用钩子函数增强执行效率用并行测试提升。把这些点都串起来你就能构建出一个真正能在项目中发挥作用的UI自动化测试体系。剩下的就是在实际业务中不断打磨你的页面对象和测试用例让自动化测试成为研发流程中可靠的一环。
Pytest+Selenium UI自动化测试实战:从脚本到工程化框架搭建
发布时间:2026/6/29 20:48:17
1. 项目概述从零到一构建UI自动化测试体系如果你是一名测试工程师或者正在向这个方向转型那么“UI自动化测试”这个词对你来说一定不陌生。它听起来很酷能解放双手但真正上手时很多人都会卡在第一步如何把Selenium的零散脚本变成一个稳定、可维护、能持续集成的自动化测试项目这正是“PytestSelenium UI自动化测试实战实例”这个标题背后要解决的核心痛点。它不是简单地教你写几个find_element和click而是分享一套经过实战检验的、从脚本到框架的完整工程化解决方案。我经历过从用unittest写几百行一个的测试用例到后来用Pytest重构整个测试套件效率提升了好几倍。这个组合之所以强大在于Pytest提供了极其灵活和强大的测试组织、发现、运行和报告能力而Selenium则是操控浏览器的“金手指”。两者结合你得到的不是一个玩具而是一个可以应对真实复杂Web应用、支持团队协作、并能融入CI/CD流水线的生产级测试工具链。无论你是想为个人项目增加自动化测试还是要在团队中推动测试左移和持续测试这套组合都是目前Python生态中最主流、最值得投入时间学习的技术栈。接下来我将以一个电商网站核心购物流程的测试为例带你一步步搭建这个框架并分享那些官方文档里不会写的“踩坑”经验和性能调优技巧。2. 整体框架设计与核心思路拆解在动手写代码之前我们先要搞清楚我们要建的是什么以及为什么选择Pytest和Selenium这个组合。很多新手会直接打开IDE开始录制脚本或抄写代码但缺乏顶层设计项目很快就会变得难以维护用例之间耦合严重定位问题耗时耗力。2.1 为什么是Pytest Selenium首先看Selenium它是Web UI自动化的行业标准支持所有主流浏览器提供了丰富的API来模拟用户操作。但Selenium本身只是一个“驱动浏览器”的工具库它不负责测试用例的管理、数据驱动、夹具Fixture生命周期、测试报告生成等。这些正是测试框架需要解决的问题。Pytest作为一个全功能的测试框架完美地弥补了这些缺口简易性用例就是普通的Python函数用assert语句进行断言学习成本极低。强大的Fixture这是Pytest的灵魂。你可以用Fixture来管理测试前置和后置操作比如启动/关闭浏览器、登录、准备测试数据。Fixture支持作用域函数、类、模块、会话可以极大地减少重复代码并保证资源如浏览器实例的高效利用。参数化轻松实现数据驱动测试。用pytest.mark.parametrize一个装饰器就能用多组数据运行同一个测试逻辑。丰富的插件生态pytest-html生成美观的HTML报告pytest-xdist支持分布式并行测试pytest-rerunfailures对失败用例进行重试这些都能直接提升自动化测试的效率和可靠性。灵活的钩子Hook允许你在测试生命周期的各个阶段插入自定义逻辑比如修改测试报告、处理失败截图等。因此我们的设计思路是用Pytest来组织和驱动测试流程用Selenium来执行具体的页面交互操作。同时我们会引入Page Object ModelPOM页面对象模型设计模式将页面元素定位和操作封装成独立的类让测试脚本用例只关注业务逻辑和测试断言从而实现业务与技术的分离提升代码的可读性和可维护性。2.2 项目目录结构规划一个清晰的目录结构是项目可维护性的基石。下面是我推荐并将在实例中使用的结构pytest_selenium_demo/ ├── conftest.py # Pytest全局配置文件定义核心Fixture如driver ├── pytest.ini # Pytest配置文件定义命令行默认参数、标记等 ├── requirements.txt # 项目依赖包列表 ├── common/ # 公共模块 │ ├── __init__.py │ ├── base_page.py # 所有Page类的基类封装公共方法 │ ├── logger.py # 自定义日志模块 │ └── config.py # 配置文件读取如URL、账号、超时时间 ├── pages/ # 页面对象层POM │ ├── __init__.py │ ├── login_page.py # 登录页面 │ ├── home_page.py # 首页 │ └── cart_page.py # 购物车页面 ├── test_cases/ # 测试用例层 │ ├── __init__.py │ ├── test_login.py # 登录功能测试 │ └── test_shopping_flow.py # 购物流程测试 ├── test_data/ # 测试数据层JSON/YAML/Excel │ └── user_data.json ├── reports/ # 测试报告输出目录.gitignore忽略 │ └── 20240527_103022.html └── screenshots/ # 失败用例截图目录.gitignore忽略 └── test_failed_....png这个结构体现了清晰的层次common放一些底层工具比如读写配置、记录日志的模块。pages每个页面对应一个类里面是这个页面的元素定位器和操作方法。test_cases这里才是真正的测试用例它们调用pages里的方法来完成操作和断言。test_data把测试数据特别是账号密码从代码里分离出来方便管理和参数化。reports/screenshots输出目录通常不纳入版本控制。注意conftest.py是Pytest的一个特殊文件它里面定义的Fixture可以被同一目录及子目录下的所有测试文件自动发现和使用。我们把最核心的driverFixture放在项目根目录的conftest.py里这样所有用例都能共用同一个浏览器会话管理逻辑。3. 环境搭建与核心工具链配置工欲善其事必先利其器。这一步我们会配置好整个项目运行所需的所有环境包括Python环境、浏览器驱动、以及Pytest的核心插件。3.1 基础环境安装与依赖管理首先确保你安装了Python建议3.8及以上版本。然后在项目根目录下创建requirements.txt文件并写入以下内容# 测试框架与运行器 pytest7.0.0 # 浏览器自动化 selenium4.0.0 # 生成HTML测试报告 pytest-html3.0.0 # 失败用例自动重试 pytest-rerunfailures10.0 # 控制用例执行顺序谨慎使用 pytest-ordering0.6 # 漂亮的终端报告 pytest-sugar0.9.0 # 数据驱动支持如果需要 pytest-datadir1.3.1在终端中进入项目目录运行以下命令安装所有依赖pip install -r requirements.txt这里解释几个关键依赖的选择Selenium 4.x相比3.x4.x提供了更符合W3C标准的API并且原生支持了相对定位器Relative Locators等新特性是未来的方向。我们直接使用最新稳定版。pytest-html这是生成可视化报告最常用的插件生成的HTML报告信息全面便于分享和归档。pytest-rerunfailuresUI自动化测试不稳定是常态可能因为网络延迟、元素加载慢等原因导致偶发性失败。这个插件允许你对失败用例进行重试可以有效过滤掉一些非代码缺陷导致的失败提升测试套件的稳定性。3.2 浏览器驱动管理告别手动下载最让人头疼的莫过于浏览器驱动如chromedriver的版本匹配问题。Chrome浏览器频繁更新手动下载和配置驱动非常麻烦。这里我强烈推荐使用webdriver-manager这个第三方库它可以自动检测你本地安装的浏览器版本并下载匹配的驱动。首先安装它pip install webdriver-manager然后在你的conftest.py中就可以这样优雅地初始化driver完全无需关心驱动路径# conftest.py import pytest from selenium import webdriver from selenium.webdriver.chrome.service import Service as ChromeService from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.options import Options pytest.fixture(scopefunction) # 每个测试函数一个独立的浏览器实例 def driver(): chrome_options Options() # 常用配置项 chrome_options.add_argument(--start-maximized) # 启动时最大化窗口 chrome_options.add_argument(--disable-gpu) # 禁用GPU加速某些环境下更稳定 # chrome_options.add_argument(--headless) # 无头模式不打开GUI常用于CI环境 chrome_options.add_experimental_option(excludeSwitches, [enable-logging]) # 禁止控制台日志 # 使用webdriver-manager自动管理驱动 service ChromeService(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice, optionschrome_options) # 设置隐式等待全局等待元素出现的超时时间 driver.implicitly_wait(10) yield driver # 将driver对象提供给测试用例 # 测试结束后执行清理工作 driver.quit()这个driverFixture的作用域是function意味着每个测试用例都会启动和关闭一次浏览器。这样做虽然耗时但保证了用例之间的绝对隔离避免了一个用例的状态污染另一个用例。如果你的用例是纯读操作且无状态依赖可以考虑使用scopeclass或scopemodule来提升执行速度。实操心得在团队协作中将webdriver-manager写入requirements.txt可以确保所有成员和CI服务器环境都能自动获取正确的驱动极大降低了环境配置成本。另外无头模式--headless在CI/CD流水线中非常有用因为它不需要图形界面。但在调试阶段建议关闭无头模式以便直观地观察测试执行过程。3.3 Pytest核心配置与插件调优接下来配置pytest.ini文件它相当于Pytest的“仪表盘”可以预设很多运行选项。# pytest.ini [pytest] # 指定测试文件的位置和命名规则 testpaths test_cases python_files test_*.py python_classes Test* python_functions test_* # 添加命令行默认参数 addopts -v # 详细输出 --htmlreports/report.html # 生成HTML报告 --self-contained-html # 生成独立的HTML报告内联CSS/JS --reruns 1 # 失败后重试1次 --reruns-delay 2 # 每次重试间隔2秒 # 自定义标记用于分类运行用例 markers smoke: 冒烟测试用例 regression: 回归测试用例 login: 与登录相关的测试addopts这里设置的参数会在每次运行pytest命令时自动生效。我们配置了生成HTML报告、失败重试等实用功能。markers你可以给测试用例打上不同的标签例如pytest.mark.smoke。之后就可以用pytest -m smoke只运行冒烟测试非常灵活。4. 页面对象模型POM的深度实现POM是UI自动化测试中最重要的设计模式没有之一。它的核心思想是将页面封装成对象测试脚本通过操作这些对象来完成业务流。下面我们以登录页面为例深入讲解如何实现一个健壮的POM。4.1 构建可复用的页面基类首先在common/base_page.py中创建一个所有页面类的基类。这个基类封装了Selenium最常用的操作并加入等待、日志等增强功能。# common/base_page.py import logging from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException, NoSuchElementException class BasePage: def __init__(self, driver): self.driver driver self.logger logging.getLogger(__name__) self.wait WebDriverWait(driver, 10) # 显式等待对象 def find_element(self, locator): 查找单个元素加入显式等待和日志 self.logger.info(f正在查找元素: {locator}) try: # 使用显式等待直到元素可见并可交互 element self.wait.until(EC.visibility_of_element_located(locator)) self.logger.info(f成功找到元素: {locator}) return element except TimeoutException: self.logger.error(f查找元素超时: {locator}) # 这里可以附加截图操作 raise def click(self, locator): 点击元素 element self.find_element(locator) self.logger.info(f点击元素: {locator}) element.click() def input_text(self, locator, text): 向输入框输入文本并清空原有内容 element self.find_element(locator) element.clear() self.logger.info(f向元素 {locator} 输入文本: {text}) element.send_keys(text) def get_text(self, locator): 获取元素的文本内容 element self.find_element(locator) text element.text self.logger.info(f获取元素 {locator} 的文本: {text}) return text def is_element_present(self, locator, timeout5): 判断元素是否存在不抛异常 try: WebDriverWait(self.driver, timeout).until( EC.presence_of_element_located(locator) ) return True except TimeoutException: return False这个基类的关键在于统一的元素查找所有查找都通过find_element方法内部集成了显式等待和日志记录。显式等待比隐式等待更精确它针对某个特定条件进行等待而不是一个全局的固定时间。日志集成每个关键操作都记录日志这在调试复杂用例时是无价之宝。常用操作封装将click,input_text等高频操作封装起来使页面类代码更简洁。4.2 实现具体的页面对象以登录页面为例有了基类我们就可以创建具体的页面类了。以pages/login_page.py为例# pages/login_page.py from selenium.webdriver.common.by import By from common.base_page import BasePage class LoginPage(BasePage): # 1. 元素定位器Locators集中管理 # 使用 (By.策略, 表达式) 的元组形式 USERNAME_INPUT (By.ID, username) PASSWORD_INPUT (By.ID, password) LOGIN_BUTTON (By.XPATH, //button[typesubmit]) ERROR_MESSAGE (By.CLASS_NAME, alert-error) SUCCESS_MESSAGE (By.CSS_SELECTOR, .welcome-msg) # 2. 页面URL可选 URL https://example.com/login def __init__(self, driver): super().__init__(driver) self.driver.get(self.URL) # 初始化时打开页面 # 3. 页面操作方法 def enter_username(self, username): self.input_text(self.USERNAME_INPUT, username) return self # 支持链式调用 def enter_password(self, password): self.input_text(self.PASSWORD_INPUT, password) return self def click_login(self): self.click(self.LOGIN_BUTTON) # 4. 业务场景组合方法 def login(self, username, password): 完整的登录业务流 self.logger.info(f执行登录操作用户名: {username}) self.enter_username(username) self.enter_password(password) self.click_login() # 5. 页面状态断言方法 def get_error_message(self): 获取登录错误提示信息 if self.is_element_present(self.ERROR_MESSAGE): return self.get_text(self.ERROR_MESSAGE) return def is_login_successful(self): 判断是否登录成功 return self.is_element_present(self.SUCCESS_MESSAGE)这个登录页面类体现了POM的精髓元素定位集中化所有定位器都定义为类属性放在文件顶部。如果前端页面元素ID或XPath变了你只需要修改这一个地方所有用到它的测试用例都会自动生效。操作原子化每个页面元素的操作都封装成一个独立的方法如enter_username。业务组合化login方法将多个原子操作组合成一个完整的业务流。测试用例只需要调用login(username, password)代码非常清晰。页面状态可查询提供了get_error_message和is_login_successful等方法供测试用例进行断言。避坑技巧关于元素定位策略的优先级我个人的经验法则是ID Name CSS Selector XPath。ID和Name通常最稳定且速度快。CSS Selector在大多数情况下比XPath性能更好语法也更简洁。XPath功能强大但性能相对较差且容易因页面结构微小变动而失效应作为最后的选择。对于动态ID可以考虑使用CSS Selector的部分匹配如[id*part_of_id]或XPath的contains函数。5. 测试用例的编写与组织艺术有了稳固的页面对象层编写测试用例就变成了一件愉快的事情。用例脚本将变得非常简洁只关注“测试什么”和“期望是什么”。5.1 编写第一个测试用例登录功能测试在test_cases/test_login.py中我们编写登录功能的测试。# test_cases/test_login.py import pytest import logging from pages.login_page import LoginPage # 获取日志记录器 logger logging.getLogger(__name__) class TestLogin: 登录功能测试集 pytest.mark.smoke pytest.mark.login def test_login_success(self, driver): 测试正常用户名密码登录成功 logger.info(开始执行测试: test_login_success) login_page LoginPage(driver) # 调用页面对象的业务方法 login_page.login(valid_user, valid_password) # 断言验证登录成功后的页面状态 assert login_page.is_login_successful() is True # 可以进一步断言跳转后的URL或页面标题 # assert dashboard in driver.current_url logger.info(测试通过: 登录成功) pytest.mark.regression pytest.mark.login pytest.mark.parametrize(username, password, expected_error, [ (, somepass, 用户名不能为空), (invalid_user, , 密码不能为空), (wrong_user, wrong_pass, 用户名或密码错误), ]) def test_login_failure(self, driver, username, password, expected_error): 参数化测试多种错误的登录场景 logger.info(f开始执行参数化测试: username{username}, password{password}) login_page LoginPage(driver) login_page.login(username, password) # 断言验证出现了预期的错误提示信息 actual_error login_page.get_error_message() # 这里使用 in 而不是 因为实际错误信息可能包含更多内容 assert expected_error in actual_error, f期望错误信息包含 {expected_error} 实际得到 {actual_error} logger.info(f测试通过: 错误场景 {expected_error} 验证成功)这个测试类展示了几个关键点用例即函数每个测试用例都是一个以test_开头的方法。使用Fixturedriver参数会自动注入我们在conftest.py中定义的driverFixture。使用标记我们用pytest.mark.smoke等装饰器给用例打标签方便分类运行。参数化测试pytest.mark.parametrize是数据驱动的利器。它允许你用多组数据运行同一个测试逻辑极大减少了重复代码。上面的例子用三组无效数据测试了登录失败的不同场景。清晰的断言断言语句明确表达了期望的结果。断言失败时Pytest会给出清晰的错误信息。5.2 组织复杂业务流程测试购物流程单个页面的测试是基础真正的价值在于模拟用户的端到端业务流程。我们来看一个购物流程的测试例子test_cases/test_shopping_flow.py。# test_cases/test_shopping_flow.py import pytest import logging from pages.login_page import LoginPage from pages.home_page import HomePage from pages.cart_page import CartPage from pages.product_page import ProductPage logger logging.getLogger(__name__) class TestShoppingFlow: 端到端购物流程测试 pytest.mark.smoke pytest.fixture(autouseTrue) # autouseTrue 表示这个fixture会自动被类中的所有用例使用 def pre_condition(self, driver): 前置条件用户已登录 logger.info(执行购物流程前置条件用户登录) login_page LoginPage(driver) login_page.login(standard_user, secret_sauce) yield # 如果需要后置清理可以写在这里 logger.info(购物流程测试结束) def test_add_item_to_cart_and_checkout(self, driver): 测试添加商品到购物车并结算 logger.info(开始测试添加商品到购物车并结算) home_page HomePage(driver) product_page ProductPage(driver) cart_page CartPage(driver) # 步骤1: 在首页点击第一个商品 home_page.click_first_product() # 步骤2: 在产品详情页将商品加入购物车 product_page.add_to_cart() # 步骤3: 前往购物车页面 product_page.go_to_cart() # 步骤4: 验证购物车中有该商品 assert cart_page.get_cart_item_count() 1 item_name cart_page.get_first_item_name() assert Sauce Labs Backpack in item_name # 假设商品名 # 步骤5: 点击结算 cart_page.click_checkout() # ... 后续填写地址、支付信息等步骤 logger.info(测试通过购物车添加与结算流程完整)这个测试用例展示了如何串联多个页面对象完成一个完整的用户旅程。pytest.fixture(autouseTrue)是一个很实用的技巧它让pre_condition这个Fixture自动应用于类中的每一个测试方法避免了在每个方法开头都写一遍登录代码。6. 高级技巧与实战问题深度排查当基础框架搭建起来后你会遇到各种实际挑战。下面分享几个提升测试稳定性、可维护性和效率的高级技巧。6.1 处理动态加载与元素等待策略现代Web应用大量使用Ajax和前端框架元素不会一次性全部加载出来。不恰当的等待是UI自动化失败的首要原因。1. 抛弃time.sleep()拥抱显式等待绝对不要在测试脚本中使用time.sleep(10)这种固定等待。它要么浪费大量时间元素提前出现了要么依然会失败元素加载比10秒还慢。始终使用Selenium提供的WebDriverWait配合expected_conditions。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By # 等待元素可见并可点击 wait WebDriverWait(driver, 10) # 最多等10秒 element wait.until(EC.element_to_be_clickable((By.ID, dynamic-button))) # 等待元素消失如加载动画 wait.until(EC.invisibility_of_element_located((By.CLASS_NAME, loading-spinner))) # 等待页面标题包含特定文字 wait.until(EC.title_contains(订单完成))2. 自定义等待条件有时内置的条件不够用你可以自定义一个等待函数。def wait_for_text_in_element(driver, locator, text, timeout10): 等待某个元素的文本包含指定的文字 def predicate(_driver): try: element _driver.find_element(*locator) return text in element.text except Exception: return False WebDriverWait(driver, timeout).until(predicate) # 使用 wait_for_text_in_element(driver, (By.ID, status), 支付成功)6.2 应对反爬与浏览器指纹识别一些网站会检测Selenium的自动化特征并进行屏蔽。如果你的脚本突然无法工作可能是触发了反爬机制。常见特征与规避方法window.navigator.webdriver属性在普通浏览器中为undefined或false在Selenium控制的浏览器中为true。chrome_options.add_argument(--disable-blink-featuresAutomationControlled) chrome_options.add_experimental_option(excludeSwitches, [enable-automation]) chrome_options.add_experimental_option(useAutomationExtension, False) # 执行CDP命令覆盖webdriver属性 driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, { get: () undefined }); })User-Agent有些Selenium使用的默认UA会被识别。可以设置为一个常见的浏览器UA。chrome_options.add_argument(user-agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...)行为模式过于机械化的操作如毫秒级精准点击、无鼠标移动轨迹可能被识别。可以引入随机延迟和模拟人类鼠标移动可使用ActionChains稍微复杂化操作。重要提示这些技术应仅用于对自己拥有或获得授权的网站进行测试。用于绕过他人网站的防护机制可能违反服务条款甚至法律。6.3 测试报告增强与失败分析生成的HTML报告是测试结果的最终呈现。我们可以通过Pytest的钩子函数来增强它比如自动附加失败截图。在conftest.py中添加以下代码# conftest.py (追加内容) import pytest from datetime import datetime import os pytest.hookimpl(tryfirstTrue, hookwrapperTrue) def pytest_runtest_makereport(item, call): Hook函数用于在测试报告生成时获取结果并截图 outcome yield report outcome.get_result() # 只关注测试用例setup/call/teardown的call阶段且是失败或错误的情况 if report.when call and report.failed: # 获取测试用例中的driver fixture for name, fixtureinfo in item._fixtureinfo.name2fixturedefs.items(): if name driver: driver item.funcargs[name] break else: # 如果没有找到driver则跳过 return # 创建截图目录 screenshot_dir screenshots os.makedirs(screenshot_dir, exist_okTrue) # 生成带时间戳的截图文件名 timestamp datetime.now().strftime(%Y%m%d_%H%M%S) test_name item.name screenshot_path os.path.join(screenshot_dir, f{test_name}_{timestamp}.png) # 截图并保存 try: driver.save_screenshot(screenshot_path) # 将截图路径附加到测试报告的extra信息中pytest-html插件会读取 if hasattr(report, extra): # 确保extra是列表 from pytest_html import extras report.extra.append(extras.image(screenshot_path)) except Exception as e: print(f截图失败: {e})现在每当有测试用例失败HTML报告中就会自动嵌入一张截图你可以直观地看到失败时浏览器页面的状态这对于远程调试和问题定位至关重要。6.4 并行测试与执行优化当测试用例成百上千时串行执行会非常耗时。使用pytest-xdist插件可以轻松实现并行测试。安装后只需在运行命令后加上-n参数pytest -n auto # auto会自动检测CPU核心数来分配进程 pytest -n 4 # 指定用4个进程并行运行并行测试的注意事项用例独立性并行执行要求测试用例之间没有依赖不能共享状态。这正是我们为每个用例使用独立driverFixturescopefunction的原因。资源竞争如果用例操作共享资源如同一个测试数据库的某条记录需要设计好数据隔离策略例如使用随机生成的数据。日志与报告并行执行的日志会混在一起建议使用pytest的--tbshort简化回溯信息并确保日志模块是进程安全的或者为每个进程生成独立的日志文件。7. 持续集成CI集成与团队协作自动化测试只有融入CI/CD流水线才能最大化其价值。这里以GitHub Actions为例展示如何将PytestSelenium项目集成进去。在项目根目录创建.github/workflows/test.ymlname: UI Automation Tests on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: test: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkoutv3 - name: Set up Python uses: actions/setup-pythonv4 with: python-version: 3.9 - name: Install system dependencies (for Chrome) run: | sudo apt-get update sudo apt-get install -y wget unzip wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - echo deb [archamd64] http://dl.google.com/linux/chrome/deb/ stable main | sudo tee /etc/apt/sources.list.d/google-chrome.list sudo apt-get update sudo apt-get install -y google-chrome-stable - name: Install Python dependencies run: | pip install --upgrade pip pip install -r requirements.txt - name: Run UI Tests with Pytest run: | # 在无头模式下运行测试并生成报告 pytest -v --headless --htmlreports/report.html --self-contained-html - name: Upload test report uses: actions/upload-artifactv3 if: always() # 即使测试失败也上传报告 with: name: pytest-html-report path: reports/这个工作流做了以下几件事在Ubuntu最新环境中启动一个虚拟机。安装Chrome浏览器因为我们要跑UI测试。安装Python依赖包括webdriver-manager它会自动下载chromedriver。以无头模式运行所有测试。将生成的HTML报告打包上传供后续查看。团队协作心得在团队中推行自动化测试技术选型只是第一步。更重要的是建立规范统一的代码风格如使用Black格式化、清晰的目录结构、定期的用例评审、以及将测试结果作为代码合并的准入门槛之一。将CI流水线配置好让每次提交都能自动运行测试并生成报告能极大地提升代码质量和团队信心。从单个的Selenium脚本到一个结构清晰、易于维护、支持团队协作和持续集成的Pytest自动化测试项目这条路我走过也踩过不少坑。核心在于理解Pytest的Fixture机制和POM设计模式的思想它们是将“脚本”提升为“工程”的关键。环境问题用webdriver-manager解决稳定性问题用显式等待和重试机制应对报告问题用钩子函数增强执行效率用并行测试提升。把这些点都串起来你就能构建出一个真正能在项目中发挥作用的UI自动化测试体系。剩下的就是在实际业务中不断打磨你的页面对象和测试用例让自动化测试成为研发流程中可靠的一环。