尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Python UI自动化测试实战:pytest与Selenium黄金组合搭建企业级框架

Python UI自动化测试实战:pytest与Selenium黄金组合搭建企业级框架
📅 发布时间:2026/6/19 5:14:20

1. 项目概述:为什么选择 pytest + selenium 这套组合拳?

如果你正在为网页应用的功能回归测试而头疼,每次上线前都要手动点一遍按钮、填一遍表单,那 UI 自动化测试就是你必须要掌握的一项技能。而pytest加selenium,可以说是 Python 生态里做这件事的“黄金搭档”。我用了这套组合好几年,从简单的登录测试到复杂的电商下单流程,它都能稳稳地扛下来。

简单来说,selenium负责“动手”,它就像一个虚拟的、不知疲倦的用户,能按照你的指令去打开浏览器、点击元素、输入文字。而pytest负责“动脑”和“管理”,它提供了强大的测试发现、执行、断言和报告机制,让一堆零散的自动化操作变成结构清晰、可维护的测试用例集。市面上也有其他框架,比如 unittest,但 pytest 的简洁语法(比如直接用assert)、丰富的插件生态(如生成漂亮的 HTML 报告)以及强大的 fixture 机制,让它成为更现代、更高效的选择。这套组合特别适合测试、开发以及 DevOps 工程师,用于构建可靠的前端功能回归防线,尤其是在敏捷开发、持续集成(CI)的流程中,能极大解放人力。

2. 环境搭建与核心工具选型

开始写脚本之前,得先把“战场”布置好。这里面的坑,新手最容易踩。

2.1 Python 环境与包管理

首先确保你有一个干净的 Python 环境(建议 3.7 及以上版本)。我强烈推荐使用virtualenv或venv创建虚拟环境,这是避免包冲突的黄金法则。在项目根目录下执行:

python -m venv venv # Windows 激活 venv\Scripts\activate # Linux/Mac 激活 source venv/bin/activate

激活后,命令行提示符前会出现(venv)标识。接着,用 pip 安装核心包:

pip install pytest selenium

这里有个实操心得:网络问题可能导致 pip 安装缓慢或失败。可以配置国内镜像源,例如使用阿里云镜像:pip install -i https://mirrors.aliyun.com/pypi/simple/ pytest selenium。安装后,用pytest --version和python -c “import selenium; print(selenium.__version__)”验证安装是否成功。

2.2 浏览器驱动的选择与配置

Selenium 本身不能直接控制浏览器,它需要通过一个名为“WebDriver”的桥梁。你需要下载与你本地浏览器版本匹配的驱动。

  1. 确定浏览器版本:打开你的 Chrome 或 Firefox,在设置里查看精确版本号。
  2. 下载对应驱动:
    • Chrome:访问 ChromeDriver 官网 或国内镜像站,下载对应版本。
    • Firefox:访问 GeckoDriver 发布页 。
  3. 配置驱动路径:有三种常用方法,我推荐第三种,最省事。
    • 方法一(不推荐):将下载的驱动文件(如chromedriver.exe)放在系统 PATH 环境变量包含的目录里(如 Windows 的C:\Windows)。这容易造成版本管理混乱。
    • 方法二(灵活):在代码中指定驱动路径。
      from selenium import webdriver driver = webdriver.Chrome(executable_path=‘/你的路径/chromedriver’)
    • 方法三(推荐,使用webdriver-manager):安装这个包,它可以自动下载和管理匹配的浏览器驱动,彻底告别手动下载和版本匹配的烦恼。
      pip install webdriver-manager
      使用时:
      from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service)

注意:如果你在公司内网或代理环境下,webdriver-manager可能下载失败。这时需要回退到方法二,并确保团队内部共享统一版本的驱动文件。

2.3 IDE 与项目结构规划

用什么写代码都行,但 PyCharm 或 VS Code 对 Python 和 pytest 的支持更好。更重要的是规划一个清晰的项目结构,这对后续维护至关重要。一个典型的结构如下:

your_ui_auto_project/ ├── conftest.py # pytest 共享 fixture 配置,如驱动初始化 ├── requirements.txt # 项目依赖包列表 ├── test_cases/ # 存放测试用例文件 │ ├── __init__.py │ ├── test_login.py │ └── test_search.py ├── page_objects/ # 页面对象模型(PO)目录 │ ├── __init__.py │ ├── base_page.py │ ├── login_page.py │ └── home_page.py ├── test_data/ # 测试数据文件(如 JSON, YAML, Excel) │ └── users.json ├── reports/ # 测试报告输出目录(由插件生成) └── utils/ # 工具函数,如截图、日志、数据读取 ├── __init__.py └── logger.py

先建立这个骨架,后面的代码往里填,思路会清晰很多。

3. 从零编写第一个自动化脚本与用例

让我们从一个最简单的例子开始:打开百度,搜索一个关键词,并验证搜索结果标题。

3.1 最简单的线性脚本

创建一个文件test_first_script.py,先不用任何框架,就用最原始的 Selenium 脚本:

from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys import time # 1. 启动浏览器(这里使用自动管理驱动的方式) from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service) try: # 2. 打开网页 driver.get(“https://www.baidu.com”) time.sleep(2) # 等待页面加载,这是初级做法,后面会改进 # 3. 定位元素并操作 search_box = driver.find_element(By.ID, “kw”) # 百度搜索框的ID是‘kw’ search_box.send_keys(“pytest selenium”) search_box.send_keys(Keys.RETURN) # 模拟回车键 time.sleep(3) # 等待搜索结果加载 # 4. 进行断言验证 assert “pytest_selenium” in driver.title.lower() # 粗略验证标题 print(“测试通过!”) finally: # 5. 关闭浏览器 driver.quit()

运行这个脚本:python test_first_script.py。你会看到浏览器自动打开、搜索、然后关闭。这就是自动化的魔力。但这段代码问题很多:硬编码的等待time.sleep、断言过于简单、没有错误报告、浏览器窗口一闪而过不利于调试。

3.2 用 pytest 改造为正式测试用例

现在,我们用 pytest 的规则重写它。pytest 会自动发现以test_开头或_test结尾的文件和函数。

创建test_cases/test_baidu_search.py:

import pytest from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class TestBaiduSearch: """百度搜索测试类""" # 每个测试方法开始前执行 def setup_method(self): from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service service = Service(ChromeDriverManager().install()) self.driver = webdriver.Chrome(service=service) self.driver.implicitly_wait(10) # 设置隐式等待,全局生效 self.wait = WebDriverWait(self.driver, 10) # 显式等待对象 # 每个测试方法结束后执行 def teardown_method(self): self.driver.quit() def test_search_pytest_selenium(self): """测试搜索 pytest selenium 关键字""" driver = self.driver wait = self.wait driver.get(“https://www.baidu.com”) # 使用显式等待,更智能地等待元素出现 search_input = wait.until( EC.presence_of_element_located((By.ID, “kw”)) ) search_input.send_keys(“pytest selenium” + Keys.RETURN) # 等待搜索结果区域出现,并断言其中包含特定文本 first_result = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, “#content_left .result”)) ) assert “pytest” in first_result.text.lower() assert “selenium” in first_result.text.lower() # 你可以添加更多测试方法 def test_search_weather(self): """测试搜索天气""" self.driver.get(“https://www.baidu.com”) # ... 类似的操作和断言

现在,在项目根目录下运行pytest test_cases/test_baidu_search.py -v。-v参数表示详细输出,你会看到每个测试方法的执行结果(PASSED 或 FAILED)。pytest 会自动执行setup_method和teardown_method,管理浏览器的生命周期。这里的核心改进是用了等待机制,替代了不稳定的time.sleep。

4. 构建可维护的自动化测试框架

当用例越来越多,直接在每个测试方法里写find_element和send_keys会变得难以维护。这时需要引入设计模式。

4.1 页面对象模型(PO)实战

PO 模式的核心思想是将页面封装成类,页面的元素定位和操作作为类的方法,测试用例只调用这些方法,不关心具体定位细节。这样,前端页面改了,你只需要改对应的 Page 类,测试用例基本不用动。

首先,在page_objects目录下创建base_page.py,这是一个所有页面类的基类,封装公共操作:

from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class BasePage: def __init__(self, driver): self.driver = driver self.wait = WebDriverWait(driver, 10) def find_element(self, locator): """查找单个元素(显式等待)""" return self.wait.until(EC.presence_of_element_located(locator)) def find_elements(self, locator): """查找多个元素""" return self.wait.until(EC.presence_of_all_elements_located(locator)) def click(self, locator): """点击元素""" element = self.find_element(locator) element.click() def input_text(self, locator, text): """输入文本""" element = self.find_element(locator) element.clear() element.send_keys(text) def get_text(self, locator): """获取元素文本""" return self.find_element(locator).text

然后,创建具体的页面类,例如login_page.py:

from selenium.webdriver.common.by import By from .base_page import BasePage class LoginPage(BasePage): # 定位器:将元素定位方式集中管理 USERNAME_INPUT = (By.ID, “username”) PASSWORD_INPUT = (By.ID, “password”) LOGIN_BUTTON = (By.ID, “loginBtn”) ERROR_MSG = (By.CLASS_NAME, “error-message”) def __init__(self, driver): super().__init__(driver) self.driver = driver def open(self, url): self.driver.get(url) return self def login(self, username, password): """登录操作""" self.input_text(self.USERNAME_INPUT, username) self.input_text(self.PASSWORD_INPUT, password) self.click(self.LOGIN_BUTTON) return self # 链式调用,可返回结果页对象 def get_error_message(self): """获取错误提示信息""" return self.get_text(self.ERROR_MSG)

最后,测试用例变得非常简洁清晰。创建test_cases/test_login.py:

import pytest from page_objects.login_page import LoginPage class TestLogin: def test_login_success(self, browser): # 使用了 fixture ‘browser‘ login_page = LoginPage(browser) login_page.open(“https://your-app.com/login”) # 假设登录成功会跳转到首页,首页标题包含‘Dashboard’ login_page.login(“valid_user”, “valid_pass”) assert “Dashboard” in browser.title def test_login_failed_with_wrong_password(self, browser): login_page = LoginPage(browser) login_page.open(“https://your-app.com/login”) login_page.login(“valid_user”, “wrong_pass”) error_text = login_page.get_error_message() assert “密码错误” in error_text

你看,测试用例里已经没有find_element和By.ID了,全是业务语义的操作。这就是 PO 模式带来的可读性和可维护性提升。

4.2 使用 pytest fixture 管理测试生命周期

上面的例子中,TestLogin类需要一个browser参数。这个browser就是一个fixture,它负责创建和销毁 WebDriver 实例。我们把fixture定义在conftest.py文件中,这个文件里的fixture可以被整个项目共享。

在项目根目录创建conftest.py:

import pytest from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service @pytest.fixture(scope=“class”) def browser(): """提供 WebDriver 实例的 fixture,作用域为类级别(一个测试类共用同一个浏览器实例)""" service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service) driver.implicitly_wait(5) driver.maximize_window() # 默认最大化窗口 yield driver # 测试执行时使用 driver driver.quit() # 测试结束后退出 @pytest.fixture def login_page(browser): """直接提供一个登录页面的 fixture""" from page_objects.login_page import LoginPage return LoginPage(browser)

现在,测试用例可以直接使用这些fixture。scope=“class”表示这个browser在一个测试类中只初始化一次,所有方法共用,可以加快执行速度(如果用例间没有状态依赖)。你也可以用scope=“function”(默认值)让每个测试方法都重启浏览器。

4.3 测试数据分离与管理

不要把测试数据硬编码在用例里。将数据分离出来,便于管理和参数化测试。pytest 的@pytest.mark.parametrize装饰器是神器。

首先,可以简单地在用例中参数化:

import pytest class TestLoginWithData: @pytest.mark.parametrize(“username, password, expected”, [ (“admin”, “admin123”, True), # 成功用例 (“admin”, “wrong”, False), # 失败用例 (“”, “admin123”, False), # 空用户名 (“admin”, “”, False), # 空密码 ]) def test_login_params(self, browser, username, password, expected): login_page = LoginPage(browser) login_page.open(“...”) login_page.login(username, password) if expected: assert “Dashboard” in browser.title else: assert login_page.get_error_message() != “”

对于更复杂的数据,可以放在外部文件里,如 JSON 或 YAML。创建test_data/login_data.json:

[ { “case_name”: “正确账号密码登录成功”, “username”: “test_user”, “password”: “123456”, “expected”: “success” }, { “case_name”: “错误密码登录失败”, “username”: “test_user”, “password”: “wrong”, “expected”: “fail” } ]

然后在conftest.py或工具函数中读取数据:

import json import pytest def load_login_data(): with open(‘test_data/login_data.json’, ‘r’, encoding=‘utf-8’) as f: return json.load(f) @pytest.fixture(params=load_login_data()) def login_data(request): return request.param # 在用例中使用 def test_login_with_json_data(browser, login_data): # login_data 就是 JSON 数组中的每一个字典对象 pass

5. 高级技巧与最佳实践

掌握了基础框架后,这些技巧能让你的自动化脚本更健壮、更专业。

5.1 智能等待与元素定位策略

永远不要用time.sleep,这是 UI 自动化测试的第一条军规。要用 Selenium 提供的等待机制。

  • 隐式等待 (Implicit Wait):driver.implicitly_wait(10)设置一个全局等待时间,在查找任何元素时,如果元素没有立即出现,会轮询等待最多10秒。它是一次性设置,对整个 driver 生命周期有效。但它不适用于元素的状态(如可点击、可见)。
  • 显式等待 (Explicit Wait):针对特定元素和条件进行等待,更灵活、更精确。这是推荐的主要方式。
    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, poll_frequency=0.5, ignored_exceptions=[NoSuchElementException]) # 等待元素可见并可点击 button = wait.until(EC.element_to_be_clickable((By.ID, “submitBtn”))) button.click() # 等待元素包含特定文本 element = wait.until(EC.text_to_be_present_in_element((By.TAG_NAME, “h1”), “Welcome”))

元素定位优先级建议:ID>Name>CSS Selector>XPath。ID 和 Name 通常最稳定。CSS Selector 性能好,语法简洁。XPath 功能强大但性能稍差,且容易因页面结构微小变动而失效,谨慎使用。对于动态ID(包含变化部分),可以使用CSS Selector的模糊匹配,如input[id*=‘username’]。

5.2 测试报告与日志记录

跑完测试,你需要知道结果。pytest 有很多插件可以生成漂亮的报告。

  • pytest-html:生成 HTML 报告。
    pip install pytest-html pytest --html=reports/report.html --self-contained-html
  • allure-pytest:生成非常美观、交互性强的 Allure 报告。
    pip install allure-pytest pytest --alluredir=./allure-results # 生成报告 allure serve ./allure-results

同时,加入日志记录,方便调试。在conftest.py或一个工具模块中配置:

import logging import sys def get_logger(name): logger = logging.getLogger(name) logger.setLevel(logging.INFO) if not logger.handlers: ch = logging.StreamHandler(sys.stdout) formatter = logging.Formatter(‘%(asctime)s - %(name)s - %(levelname)s - %(message)s’) ch.setFormatter(formatter) logger.addHandler(ch) return logger # 在 fixture 或 Page 类中使用 logger = get_logger(__name__) logger.info(“正在打开登录页面...”)

5.3 失败截图与重试机制

测试失败时,一张截图抵得上千行日志。我们可以通过修改conftest.py中的 fixture 或使用 hook 函数来实现自动截图。

import pytest from datetime import datetime @pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_runtest_makereport(item, call): """获取测试用例执行结果的钩子函数""" outcome = yield rep = outcome.get_result() if rep.when == “call” and rep.failed: # 只有测试执行阶段失败才截图 driver = item.funcargs.get(“browser”, None) if driver is not None: timestamp = datetime.now().strftime(“%Y%m%d_%H%M%S”) screenshot_name = f”screenshot_failure_{item.name}_{timestamp}.png” driver.save_screenshot(f”reports/{screenshot_name}”) print(f”\n测试失败,截图已保存至:reports/{screenshot_name}”)

对于不稳定的测试(如网络波动),可以使用pytest-rerunfailures插件进行重试。

pip install pytest-rerunfailures pytest --reruns 3 --reruns-delay 2 # 失败后重试3次,每次间隔2秒

6. 常见问题排查与避坑指南

在实际操作中,你肯定会遇到各种奇怪的问题。这里记录了一些高频坑点和解决思路。

6.1 元素定位不到(NoSuchElementException)

这是最常见的问题。

  • 可能原因1:等待时间不足。页面还没加载完就去定位元素。
    • 解决:使用显式等待WebDriverWait配合EC.presence_of_element_located或EC.visibility_of_element_located。
  • 可能原因2:元素在 iframe 或 shadow DOM 内。
    • 解决:先切换到对应的 iframe:driver.switch_to.frame(“frame_name_or_id”),操作完再切回来:driver.switch_to.default_content()。Shadow DOM 需要使用driver.execute_script执行 JavaScript 来定位。
  • 可能原因3:元素属性是动态生成的。每次刷新页面 ID 或 Class 会变。
    • 解决:使用相对定位,如通过父元素的稳定属性结合 XPath 轴(如following-sibling::,parent::)或 CSS Selector 的其他属性(如^=开头,$=结尾,*=包含)。
  • 可能原因4:页面有多个相同属性的元素。find_element只返回第一个。
    • 解决:使用find_elements获取列表,然后按索引或遍历查找;或者使用更精确的定位器。

6.2 脚本在 CI/CD 环境(如 Jenkins)中运行失败

本地跑得好好的,一上 Jenkins 就挂。

  • 可能原因1:无头模式或缺少显示服务器。Linux 服务器通常没有图形界面。
    • 解决:使用无头模式运行 Chrome。
      from selenium.webdriver.chrome.options import Options chrome_options = Options() chrome_options.add_argument(“--headless”) # 无头模式 chrome_options.add_argument(“--no-sandbox”) # 在 Docker 或某些 CI 环境中需要 chrome_options.add_argument(“--disable-dev-shm-usage”) # 解决共享内存问题 driver = webdriver.Chrome(options=chrome_options)
  • 可能原因2:环境路径问题。Jenkins 节点上可能没有 Chrome 浏览器或驱动。
    • 解决:确保 CI 环境中安装了 Chrome(或使用webdriver-manager自动处理驱动),或者使用 Docker 镜像来提供一致的环境。
  • 可能原因3:权限或防火墙。脚本无法访问目标测试环境。
    • 解决:检查 Jenkins 节点的网络配置,确保能连通测试服务器。

6.3 自动化测试不稳定(Flaky Tests)

有时成功有时失败,最让人抓狂。

  • 对策1:强化等待。检查所有关键操作前后是否都有合适的显式等待,特别是对于 Ajax 加载的内容。
  • 对策2:避免依赖固定等待时间。彻底抛弃time.sleep。
  • 对策3:优化定位器。使用更稳定、唯一的元素定位方式,避免使用绝对 XPath。
  • 对策4:引入重试机制。如上文所述,使用pytest-rerunfailures对不稳定用例进行重试。
  • 对策5:隔离测试环境与数据。确保测试用例是独立的,不依赖前一个用例产生的数据。每次测试前可以清理或重置测试数据。

6.4 如何选择 UI 自动化还是接口自动化?

这也是一个常见困惑。我的经验是:

  • UI 自动化:适合验证端到端的用户业务流程和前端交互。例如,完整的用户注册-登录-下单流程。它更贴近真实用户,但运行慢、稳定性相对差、维护成本高。
  • 接口自动化:适合验证业务逻辑和数据一致性。它运行极快、稳定性高、维护成本低。例如,验证提交订单接口是否成功扣减库存、生成订单号。

最佳实践是结合使用:用接口自动化覆盖大部分业务逻辑和核心数据流测试,构建快速反馈的测试层;用 UI 自动化覆盖关键的用户场景和核心业务流程,作为最终的用户验收层。不要试图用 UI 自动化覆盖所有测试点,那会是一场维护噩梦。

我个人在搭建自动化体系时,通常会遵循“金字塔模型”:底层是大量的单元测试和接口测试(快速稳定),顶层是少量的、关键的 UI 自动化测试(覆盖核心场景)。pytest + selenium 正是打造这顶层关键测试的利器。记住,UI 自动化的目标不是发现大量 bug,而是保障核心流程的畅通无阻,为持续交付提供信心。

相关新闻

  • qwen3.6超大杯:面向macOS桌面的白盒化大模型实践
  • 多模态AI推理:Qwen3-VL-4B-Instruct在边缘计算中的架构创新与实践
  • Gemma 4:面向边缘部署的字节效率多模态模型

最新新闻

  • LLM前摄干扰缺陷:为什么大模型无法准确追踪最新数据
  • Narou.rb:日本网络小说下载与管理的终极解决方案
  • 2026专业奢侈品回收综合实力榜 透明报价与口碑双优 - 工业品牌热点
  • Apkmod安全注意事项:合法使用APK逆向工程工具的道德和法律边界
  • HDPE双壁波纹管行业实力风云榜,2026口碑供应商横评 - mypinpai
  • Wox终极指南:如何用跨平台启动器提升10倍工作效率

日新闻

  • 5分钟掌握Python进化算法:Geatpy高性能优化工具完全指南
  • Microchip 24AA044 EEPROM选型与应用全指南:从参数解析到实战编程
  • 华为的鸿蒙到底有多牛?为什么称作遥遥领先?

周新闻

  • 3步解锁iOS设备:applera1n激活锁绕过完全指南
  • 39 2026 人工智能证书终极盘点,普通人选 AI 证书可以从这些方向入手
  • Redis 暴露公网有多危险?从端口检查到补救步骤

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号