1. 项目概述:为什么选择 Pytest + Playwright 这套组合拳?
如果你正在寻找一套能打、稳定、且面向未来的 UI 自动化测试方案,那么Pytest加Playwright的组合,绝对值得你投入时间深入研究。我接触过 Selenium、Cypress 等一众工具,最终在团队的新项目中全面转向了这套技术栈,原因很简单:它解决了传统 UI 自动化中几个最让人头疼的痛点。
首先,Playwright 的“稳”是出了名的。它由微软团队开发,原生支持 Chromium、Firefox 和 WebKit 三大浏览器引擎,这意味着你的脚本在 Chrome、Edge、Safari 上都能获得高度一致的执行效果。更关键的是,它内置了智能等待机制,能自动等待元素可操作、网络请求完成,大大减少了因页面加载或异步操作导致的“元素找不到”的随机失败。其次,Pytest 的“强”在于其强大的测试组织能力和丰富的插件生态。用 Pytest 来组织你的 UI 测试用例,就像用专业的乐高积木搭建城堡,夹具(Fixture)管理测试前后的资源(如浏览器实例),参数化轻松实现数据驱动,丰富的断言方式让结果验证更直观,再加上 Allure 等插件生成美观的报告,整个测试流程的专业度和可维护性直接上了一个台阶。
这个系列的第二篇,我们就来扎扎实实地搞定环境搭建和基础框架的雏形。我会带你一步步安装所有必需的工具,并搭建一个清晰、可扩展的目录结构。这个基础打好了,后续编写用例、管理页面对象、生成报告都会事半功倍。无论你是刚接触 UI 自动化的新手,还是从其他框架(如 Selenium + unittest)迁移过来的老手,跟着这篇指南走,都能快速上手。
2. 核心工具安装与环境配置详解
工欲善其事,必先利其器。搭建环境看似简单,但细节决定成败。一个干净、隔离且版本匹配的 Python 环境,是避免后续各种诡异依赖冲突的前提。我会分步讲解,并解释每一步背后的原因。
2.1 Python 环境与包管理工具准备
我强烈建议使用Conda或venv来创建独立的虚拟环境。这能确保你的项目依赖不会污染系统级的 Python 包,也方便在不同项目间切换。这里以venv为例,因为它轻量且是 Python 3 自带的。
# 1. 创建项目目录并进入 mkdir playwright-ui-automation cd playwright-ui-automation # 2. 创建虚拟环境(假设你的 Python 3 命令是 python3) python3 -m venv venv # 3. 激活虚拟环境 # 在 Windows 上: venv\Scripts\activate # 在 macOS/Linux 上: source venv/bin/activate激活后,你的命令行提示符前通常会显示(venv),表示你已进入该虚拟环境。后续所有pip install操作都只影响这个环境。
注意:有些系统(尤其是 Windows)在安装 Python 时可能没有将
python和pip命令添加到系统路径,或者存在多个 Python 版本。如果遇到python命令未找到,可以尝试使用py -3或python3。确认版本可以用python --version。我个人的习惯是始终使用python -m pip来代替直接的pip命令,这样可以精确指定使用当前 Python 解释器下的 pip,避免混淆。
2.2 核心依赖包安装:Pytest 与 Playwright
接下来安装两位主角。我们将使用pip进行安装。为了确保版本兼容性和可复现性,最好将依赖记录在requirements.txt文件中。
首先,直接安装:
pip install pytest playwright安装playwright包的同时,它会包含核心的 Python 客户端库。但请注意,这还没有安装实际的浏览器运行时。这是 Playwright 设计上的一个特点,将客户端库和浏览器二进制文件分离管理。
安装完成后,你需要安装 Playwright 所需的浏览器。这是至关重要的一步。
playwright install这条命令会下载 Chromium、Firefox 和 WebKit 的预备版本。这些浏览器是专门为 Playwright 优化过的,并非你系统上安装的普通 Chrome 或 Firefox。下载可能会花费一些时间,取决于你的网络速度。
实操心得:
playwright install默认会安装所有三个浏览器。如果你的测试场景只针对 Chromium(覆盖 Chrome 和 Edge),可以指定安装以节省时间和磁盘空间:playwright install chromium。另外,如果遇到下载速度极慢或失败的情况(这在某些网络环境下常见),可以尝试设置环境变量来使用国内镜像,例如在命令前添加PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright/。我在公司内网就经常需要这样配置。
2.3 验证安装与初步试运行
安装完成后,做个快速验证,确保一切就绪。
验证 Pytest:
pytest --version这应该会输出 Pytest 的版本号,例如
pytest 8.x.x。验证 Playwright: 创建一个简单的 Python 脚本
test_verify.py:import playwright.sync_api def test_browser_launch(): with playwright.sync_api.sync_playwright() as p: # 尝试启动 Chromium 浏览器 browser = p.chromium.launch(headless=False) # headless=False 让我们能看到浏览器 page = browser.new_page() page.goto("https://www.example.com") title = page.title() print(f"Page title: {title}") browser.close() assert "Example" in title在项目根目录下运行:
pytest test_verify.py -v如果一切正常,你应该会看到一个浏览器窗口打开,访问 example.com,然后在控制台看到测试通过的信息。这证明了 Playwright 的浏览器驱动和 Python 绑定都工作正常。
踩过的坑:第一次运行时,可能会报错提示缺少某些依赖库(特别是在 Linux 系统上)。例如,Playwright 可能需要
libenchant等库来处理字体。错误信息通常会明确告诉你需要安装什么包。在 Ubuntu/Debian 上,你可能需要运行sudo apt-get install一系列开发库。请仔细阅读错误日志,并根据 Playwright 官方文档的 系统依赖 部分进行安装。
3. 项目目录结构与基础框架搭建
一个清晰的目录结构是维护大型自动化测试项目的基石。它让代码组织有序,职责分离,新人也能快速上手。下面是我在多个项目中总结并优化后的一套结构,你可以直接作为模板。
playwright-ui-automation/ ├── requirements.txt # 项目依赖包列表 ├── pytest.ini # Pytest 配置文件 ├── conftest.py # Pytest 共享夹具(Fixture)定义 ├── common/ # 公共模块 │ ├── __init__.py │ ├── config.py # 配置文件读取(环境、URL、账号等) │ ├── logger.py # 日志记录模块 │ └── utils.py # 通用工具函数(如数据生成、文件操作) ├── pages/ # 页面对象模型(Page Object)目录 │ ├── __init__.py │ ├── base_page.py # 所有页面对象的基类 │ └── login_page.py # 示例:登录页面对象 ├── tests/ # 测试用例目录 │ ├── __init__.py │ ├── test_login.py # 示例:登录功能测试用例 │ └── test_search.py # 示例:搜索功能测试用例 ├── fixtures/ # 自定义 Pytest 夹具(可选,复杂时可独立) │ └── browser_fixture.py ├── data/ # 测试数据文件(JSON, YAML, CSV) │ └── test_users.json ├── reports/ # 测试报告输出目录(由插件生成) │ └── allure-results/ # Allure 原始结果 └── screenshots/ # 失败截图保存目录现在,我们来逐一创建这些核心文件并填充基础内容。
3.1 依赖管理文件:requirements.txt
在项目根目录创建requirements.txt,记录精确的版本号有助于团队协作和环境复现。
pytest==8.0.0 playwright==1.40.0 allure-pytest==2.13.2 pytest-html==4.1.1 pytest-xdist==3.5.0 python-dotenv==1.0.0pytest-html:生成 HTML 格式的测试报告。allure-pytest:集成 Allure 报告,生成更美观、交互性更强的报告。pytest-xdist:支持测试并行运行,大幅提升执行速度。python-dotenv:用于从.env文件加载环境变量,管理敏感配置。
安装所有依赖:pip install -r requirements.txt
3.2 Pytest 配置文件:pytest.ini
pytest.ini文件用于配置 Pytest 的默认行为,非常有用。
[pytest] # 指定测试文件的位置和命名模式 testpaths = tests python_files = test_*.py python_classes = Test* python_functions = test_* # 添加命令行默认选项 addopts = -v # 详细输出 --strict-markers # 严格检查标记 --html=reports/pytest_report.html # 生成HTML报告 --self-contained-html # 生成独立的HTML文件 --alluredir=reports/allure-results # 生成Allure结果数据 # 定义自定义标记(markers),用于分类运行测试 markers = smoke: 冒烟测试用例 regression: 回归测试用例 login: 与登录相关的测试这个配置意味着,你直接在项目根目录运行pytest命令时,它会自动在tests/目录下查找以test_开头的文件,并执行其中以Test开头的类里的test_开头的方法。同时会自动生成 HTML 和 Allure 报告。
3.3 共享夹具定义:conftest.py
conftest.py是 Pytest 的魔力所在,其中定义的夹具(Fixture)可以被同一目录及子目录下的所有测试文件自动发现和使用。我们将在这里定义最核心的浏览器夹具。
import pytest from playwright.sync_api import Page, BrowserContext, Browser, Playwright import logging # 配置日志 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @pytest.fixture(scope="session") def playwright_instance() -> Playwright: """在整个测试会话中只启动一次 Playwright 实例。""" from playwright.sync_api import sync_playwright with sync_playwright() as playwright: logger.info("Playwright instance started.") yield playwright logger.info("Playwright instance closed.") @pytest.fixture(scope="function") # 默认是 function 级别,每个测试函数一个浏览器上下文 def browser_context(playwright_instance: Playwright, request) -> BrowserContext: """为每个测试函数创建一个浏览器上下文(Context)。 上下文相当于一个独立的浏览器会话,隔离了 cookies、localStorage 等。 """ # 可以从命令行参数或标记中获取浏览器类型,默认为 chromium browser_type = getattr(request.config.option, "browser", "chromium") browser = getattr(playwright_instance, browser_type) # 启动浏览器,可配置 headless 模式 headless = not request.config.getoption("--headed") # 默认无头,加 --headed 参数则显示 browser_instance = browser.launch(headless=headless, args=["--start-maximized"]) # 创建上下文,可以在此统一设置视口、用户代理、忽略 HTTPS 错误等 context = browser_instance.new_context( viewport={'width': 1920, 'height': 1080}, ignore_https_errors=True, # 忽略 HTTPS 证书错误,用于测试环境 # record_video_dir="videos/" if request.config.getoption("--video") else None # 可选:录制视频 ) # 在测试失败时自动截图并保存到上下文,方便夹具在清理前获取 context._test_failed = False def on_page(page): page.on("pageerror", lambda exception: logger.error(f"Page error: {exception}")) context.on("page", on_page) yield context # 测试结束后,关闭上下文和浏览器 context.close() browser_instance.close() @pytest.fixture(scope="function") def page(browser_context: BrowserContext) -> Page: """为每个测试函数创建一个新的页面(Tab)。 这是最常用的夹具,测试用例直接使用这个 `page` 对象来操作。 """ page = browser_context.new_page() yield page page.close() # 添加一个自定义命令行选项,用于指定浏览器 def pytest_addoption(parser): parser.addoption( "--browser", action="store", default="chromium", help="指定浏览器: chromium, firefox 或 webkit" ) parser.addoption( "--headed", action="store_true", default=False, help="以有头模式运行(显示浏览器界面)" ) parser.addoption( "--video", action="store_true", default=False, help="录制测试视频" )这个conftest.py做了几件关键事:
playwright_instance:会话级夹具,整个测试运行期间只初始化一次 Playwright,效率高。browser_context:函数级夹具,为每个测试用例创建一个干净的浏览器上下文。这是实现测试隔离的关键,确保用例之间 cookies、缓存不互相干扰。这里还集成了命令行参数,可以动态选择浏览器和运行模式。page:函数级夹具,每个用例获得一个全新的页面对象。测试脚本主要与这个page交互。pytest_addoption:添加了自定义命令行参数,让运行测试时更灵活。
注意事项:夹具的
scope(作用域)选择很重要。session级别效率最高,但要注意资源清理。function级别隔离性最好,但创建开销稍大。对于浏览器实例,通常用session;对于上下文和页面,用function是更安全的选择。yield之前的代码是“设置”,之后的代码是“清理”,确保了资源即使测试失败也能被正确关闭。
4. 编写第一个页面对象与测试用例
有了稳固的基础设施,我们来创建第一个页面对象和测试用例,感受一下这套框架的流畅度。
4.1 创建页面对象基类:common/base_page.py
页面对象模型(Page Object Model, POM)的核心思想是将页面的元素定位和操作封装成类,让测试用例更关注业务逻辑,而非底层实现。一个设计良好的基类能减少大量重复代码。
from playwright.sync_api import Page, Locator, expect import logging import time from typing import Tuple, Optional class BasePage: """所有页面对象的基类,封装通用操作和等待逻辑。""" def __init__(self, page: Page): self.page = page self.logger = logging.getLogger(self.__class__.__name__) self.timeout = 30000 # 默认超时时间 30 秒 def navigate(self, url: str) -> None: """导航到指定URL,并等待页面加载到网络空闲状态。""" self.logger.info(f"Navigating to: {url}") self.page.goto(url, wait_until="networkidle") # wait_until 是关键参数 # 可以在这里添加额外的页面加载完成检查 def find_element(self, selector: str, timeout: Optional[int] = None) -> Locator: """查找单个元素,并等待其可见。""" timeout = timeout or self.timeout self.logger.debug(f"Finding element: {selector}") element = self.page.locator(selector).first # .first 确保获取第一个匹配项 element.wait_for(state="visible", timeout=timeout) return element def click(self, selector: str, timeout: Optional[int] = None) -> None: """点击元素,并等待点击操作完成。""" element = self.find_element(selector, timeout) self.logger.info(f"Clicking element: {selector}") element.click() # Playwright 的 click 本身会进行可操作性检查,这里通常不需要额外等待 def fill(self, selector: str, text: str, timeout: Optional[int] = None) -> None: """在输入框内填充文本,并清空原有内容。""" element = self.find_element(selector, timeout) self.logger.info(f"Filling '{text}' into element: {selector}") element.fill(text) def get_text(self, selector: str, timeout: Optional[int] = None) -> str: """获取元素的内部文本。""" element = self.find_element(selector, timeout) text = element.inner_text() self.logger.debug(f"Got text '{text}' from element: {selector}") return text def wait_for_selector(self, selector: str, state: str = "visible", timeout: Optional[int] = None) -> Locator: """等待特定选择器的元素达到指定状态(visible, hidden, attached, detached)。""" timeout = timeout or self.timeout self.logger.debug(f"Waiting for selector '{selector}' to be {state}") return self.page.locator(selector).first.wait_for(state=state, timeout=timeout) def take_screenshot(self, name: str = None) -> None: """截取当前页面全屏截图,用于调试或报告。""" if name is None: name = f"screenshot_{int(time.time())}.png" path = f"screenshots/{name}" self.page.screenshot(path=path, full_page=True) self.logger.info(f"Screenshot saved to: {path}") def assert_element_visible(self, selector: str, timeout: Optional[int] = None) -> None: """使用 Playwright 的 expect 断言元素可见,集成更友好的错误信息。""" timeout = timeout or self.timeout locator = self.page.locator(selector).first expect(locator).to_be_visible(timeout=timeout)这个基类提供了最常用的方法。注意wait_until="networkidle"和wait_for(state="visible")的使用,它们是 Playwright 稳定性的保障。expect断言提供了比普通assert更丰富的等待和错误信息。
4.2 实现具体页面对象:pages/login_page.py
以登录页面为例,展示如何继承基类。
from .base_page import BasePage class LoginPage(BasePage): """登录页面对象,封装登录相关的所有元素和操作。""" # 元素定位器(Selector),集中管理,便于维护 USERNAME_INPUT = "#username" PASSWORD_INPUT = "#password" LOGIN_BUTTON = "button[type='submit']" ERROR_MESSAGE = ".alert-error" SUCCESS_MESSAGE = ".welcome-message" def __init__(self, page): super().__init__(page) self.url = "/login" # 相对于基础URL的路径 def load(self, base_url: str) -> None: """加载登录页面。""" full_url = f"{base_url}{self.url}" self.navigate(full_url) self.page.wait_for_url(f"**{self.url}") # 等待URL跳转完成 def login(self, username: str, password: str) -> None: """执行登录操作。""" self.logger.info(f"Attempting login with user: {username}") self.fill(self.USERNAME_INPUT, username) self.fill(self.PASSWORD_INPUT, password) self.click(self.LOGIN_BUTTON) # 可以在这里添加登录后的通用等待,比如等待页面跳转或某个元素出现 def get_error_message(self) -> str: """获取登录错误提示信息。""" return self.get_text(self.ERROR_MESSAGE) def is_login_successful(self) -> bool: """判断登录是否成功(通过检查成功元素是否存在)。""" try: # 快速检查元素是否存在且可见,不等待太久 self.wait_for_selector(self.SUCCESS_MESSAGE, timeout=5000) return True except Exception: return False4.3 编写第一个测试用例:tests/test_login.py
现在,使用我们创建的夹具和页面对象来编写测试。
import pytest from pages.login_page import LoginPage from common.config import Config # 假设我们有一个配置模块 class TestLogin: """登录功能测试集。""" @pytest.fixture(autouse=True) def setup(self, page): """每个测试方法执行前的准备工作。""" self.page = page self.config = Config() # 读取配置 self.login_page = LoginPage(page) self.login_page.load(self.config.base_url) def test_successful_login(self): """测试使用正确凭据登录成功。""" # 从配置或数据文件读取测试数据 username = self.config.valid_username password = self.config.valid_password self.login_page.login(username, password) # 使用基类的断言方法 self.login_page.assert_element_visible(LoginPage.SUCCESS_MESSAGE) # 或者使用 Playwright 的 expect 直接断言 # from playwright.sync_api import expect # expect(self.page.locator(LoginPage.SUCCESS_MESSAGE)).to_be_visible() # 也可以断言 URL 跳转 # expect(self.page).to_have_url("**/dashboard") @pytest.mark.parametrize("username, password, expected_error", [ ("wrong_user", "admin123", "Invalid username or password"), ("admin", "wrong_pass", "Invalid username or password"), ("", "admin123", "Username is required"), ("admin", "", "Password is required"), ]) def test_failed_login(self, username, password, expected_error): """参数化测试:测试各种错误的登录凭据。""" self.login_page.login(username, password) # 断言出现了错误提示,并且提示信息正确 actual_error = self.login_page.get_error_message() assert expected_error in actual_error, f"Expected error '{expected_error}' not found in '{actual_error}'" @pytest.mark.smoke # 使用自定义标记,可以通过 `pytest -m smoke` 只运行冒烟测试 def test_login_page_elements(self): """冒烟测试:验证登录页面关键元素存在。""" self.login_page.assert_element_visible(LoginPage.USERNAME_INPUT) self.login_page.assert_element_visible(LoginPage.PASSWORD_INPUT) self.login_page.assert_element_visible(LoginPage.LOGIN_BUTTON)这个测试类展示了几个关键点:
- 使用
autouse=True的夹具:setup方法在每个测试方法前自动运行,初始化页面对象。 - 数据驱动测试:使用
@pytest.mark.parametrize装饰器,用多组数据运行同一个测试逻辑,非常高效。 - 标记(Markers):使用
@pytest.mark.smoke给测试打标签,方便分类运行。 - 清晰的断言:使用封装好的断言方法或 Playwright 原生的
expect,使测试意图明确。
5. 运行测试与报告生成
框架搭建好了,用例也写了,现在是验收成果的时候。我们将学习如何运行测试并生成直观的报告。
5.1 运行测试的多种方式
在项目根目录下,打开终端(确保虚拟环境已激活),你可以尝试以下命令:
运行所有测试:
pytest这会按照
pytest.ini的配置,运行tests/目录下所有测试。运行特定测试文件或类:
pytest tests/test_login.py # 运行单个文件 pytest tests/test_login.py::TestLogin # 运行单个测试类 pytest tests/test_login.py::TestLogin::test_successful_login # 运行单个测试方法使用标记运行:
pytest -m smoke # 只运行标记为 smoke 的测试 pytest -m "not smoke" # 运行除了 smoke 之外的所有测试并行运行测试(利用 pytest-xdist):
pytest -n auto # 自动检测 CPU 核心数并行运行 pytest -n 2 # 指定 2 个 worker 并行运行并行能极大缩短测试套件的总执行时间,特别适合用例多的场景。
带界面运行(调试时非常有用):
pytest --headed # 以有头模式运行,你会看到浏览器窗口弹出 pytest --browser=firefox --headed # 指定用 Firefox 浏览器并显示界面
5.2 生成与查看测试报告
测试结果不能只看控制台,一份美观的报告对于结果分析、历史追溯和团队分享至关重要。
1. HTML 报告(pytest-html)我们已经配置了pytest.ini,每次运行pytest都会在reports/目录下生成一个pytest_report.html文件。直接用浏览器打开这个文件,可以看到一个包含通过率、失败用例、执行时长等信息的静态 HTML 报告。虽然简单,但足够清晰。
2. Allure 报告(更推荐)Allure 报告功能强大,交互性好,能展示测试步骤、截图、附件等。
- 生成 Allure 结果数据:运行测试时,
--alluredir=reports/allure-results参数已经帮我们生成了原始数据(一堆.json文件)。 - 生成并打开 Allure 报告:首先,你需要安装 Allure 命令行工具。可以参考 Allure 官方安装指南 。安装后,运行:
第一条命令根据结果数据生成可浏览的 HTML 报告。第二条命令会在你的默认浏览器中打开这份报告。allure generate reports/allure-results -o reports/allure-report --clean allure open reports/allure-report
Allure 报告里,你可以看到清晰的用例层级、通过/失败状态、每个测试步骤的详细日志(如果使用了allure.attach或 Pytest 的caplog夹具)、以及我们在夹具或用例中自动附加的截图(需要在conftest.py或用例中实现截图逻辑并附加到 Allure)。这是向非技术人员展示测试结果的最佳方式。
实操心得:为了在 Allure 报告中自动附加失败用例的截图,可以在
conftest.py的browser_context或page夹具中添加逻辑。一种常见做法是使用 Pytest 的pytest_runtest_makereport钩子函数,在测试失败时获取当前的page对象并截图,然后使用allure.attach将图片附加到报告中。这需要一些额外的配置,但一旦设置好,调试失败用例时会非常方便。
6. 常见问题与排查技巧实录
在实际搭建和运行过程中,你肯定会遇到各种各样的问题。这里我总结了一些高频问题和解决方法。
6.1 安装与环境问题
问题1:playwright install下载浏览器极慢或失败。
- 原因:Playwright 默认从 Google 的存储库下载浏览器,国内网络可能访问不畅。
- 解决:
- 使用环境变量指定镜像源(推荐):
# Linux/macOS export PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright/ playwright install # Windows (PowerShell) $env:PLAYWRIGHT_DOWNLOAD_HOST="https://npmmirror.com/mirrors/playwright/" playwright install - 使用
--with-deps参数并手动设置 npm 镜像(如果上述方法不行):npm config set registry https://registry.npmmirror.com/ playwright install --with-deps chromium
- 使用环境变量指定镜像源(推荐):
问题2:运行时报错关于GLIBCXX或libxxx找不到。
- 原因:系统缺少 Playwright 浏览器运行所需的底层动态库。
- 解决:根据错误信息安装对应的系统包。在 Ubuntu/Debian 上,通常需要:
请始终以 Playwright 官方文档的系统要求章节为准。sudo apt-get update sudo apt-get install -y libnss3 libatk-bridge2.0-0 libdrm-dev libxkbcommon-dev libgbm-dev libasound-dev libpangocairo-1.0-0 libx11-xcb1 libxcomposite1 libxcursor1 libxdamage1 libxi6 libxtst6 libxrandr2 libgtk-3-0 libxss1
6.2 元素定位与等待问题
问题3:测试不稳定,有时找不到元素(TimeoutError)。
- 原因:这是 UI 自动化最常见的问题。页面加载速度、网络请求、动态渲染都可能导致元素出现时机不确定。
- 解决:
- 优先使用 Playwright 的自动等待:
page.click(),page.fill(),locator.click()等方法内部已经包含了等待元素可操作(可见、启用、稳定)的逻辑。尽量避免使用time.sleep()。 - 使用明确的等待:
page.wait_for_selector(),locator.wait_for()。wait_for_selector的state参数很实用(visible,hidden,attached)。 - 等待网络请求:
page.goto(url, wait_until="networkidle")或page.wait_for_load_state("networkidle")。 - 检查选择器:使用浏览器开发者工具(F12)的 Playwright 插件或
playwright codegen命令来生成可靠的选择器。确保选择器能唯一标识目标元素。
- 优先使用 Playwright 的自动等待:
问题4:如何定位动态生成的元素(如ID是随机字符串)?
- 解决:
- 使用其他稳定属性:如
># CSS 选择器:属性以...开头 page.locator("[data-id^='user-']") # XPath:包含特定文本 page.locator("xpath=//button[contains(text(), 'Submit')]") - 使用 Playwright 的
get_by_*系列方法:这是 Playwright 非常强大的特性,语义清晰,抗动态变化能力强。page.get_by_role("button", name="Sign in") # 通过角色和可访问名 page.get_by_text("Click me") # 通过文本 page.get_by_placeholder("Enter your email") # 通过占位符 page.get_by_test_id("login-submit") # 通过>import os from dotenv import load_dotenv load_dotenv() # 加载 .env 文件中的变量到环境变量 class Config: def __init__(self): self.base_url = os.getenv("BASE_URL", "https://default.example.com") self.valid_username = os.getenv("TEST_USERNAME", "admin") self.valid_password = os.getenv("TEST_PASSWORD", "admin123") - 在测试中通过
Config类获取配置。可以为不同环境创建不同的.env文件(如.env.test,.env.staging),在运行测试前通过环境变量指定加载哪个。
- 使用其他稳定属性:如
问题7:Allure 报告中没有截图或日志。
- 解决:需要主动将附件添加到 Allure。在
conftest.py中实现一个钩子:
这段代码会在测试调用(import allure import pytest @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: # 假设 page 夹具在测试项中可用 page = item.funcargs.get("page") if page: # 截图并附加到 Allure 报告 screenshot = page.screenshot(full_page=True) allure.attach(screenshot, name="失败截图", attachment_type=allure.attachment_type.PNG) # 也可以附加页面源代码 html = page.content() allure.attach(html, name="页面源码", attachment_type=allure.attachment_type.HTML)call)阶段失败时,尝试获取当前测试用例的page对象,进行截图和获取源码,并附加到 Allure 报告中。
搭建过程就像盖房子,地基(环境与目录)打得牢,主体结构(POM 与夹具)设计得好,后续的“装修”(编写更多页面对象和用例)和“入住”(集成到 CI/CD)才会顺畅。这套Pytest + Playwright的组合,提供了从编写、运行到报告的全套现代化解决方案,其稳定性和开发体验远超许多传统方案。当你熟悉了这套流程后,你会发现编写 UI 自动化测试不再是令人畏惧的苦差事,而是一种高效、可靠的保障手段。