Playwright UI 自动化测试实战教程

基于目标 Web 项目真实落地经验编写的实战教程,涵盖从入门到企业级框架搭建的完整路径。


目录

  • [第一篇:Playwright 介绍与试用](#第一篇:Playwright 介绍与试用)
  • [第二篇:快速上手 --- 一条测试用例的核心组成](#第二篇:快速上手 — 一条测试用例的核心组成)
  • 第三篇:落地项目实战
  • 附录

第一篇:Playwright 介绍与使用

1.1 什么是 Playwright

核心定位 :Playwright Test 是端到端(E2E)Web 应用自动化测试框架,支持在 Windows、Linux 和 macOS 上运行 Chromium、WebKit 和 Firefox,无论是本地还是 CI 环境,无论是无头模式还是有头模式,并提供 Chrome (Android) 和 Mobile Safari 的原生移动仿真。

支持语言 :Python、JavaScript/TypeScript、Java、.NET C#

1.2 与 Selenium / Cypress 对比

维度 Playwright Selenium Cypress
架构 直接通过浏览器协议通信 通过 WebDriver 中间层 浏览器内嵌执行
浏览器支持 Chromium / Firefox / WebKit 全部主流浏览器 仅 Chromium
语言支持 Python / JS / Java / C# Java / Python / C# / Ruby 等 仅 JavaScript
自动等待 内置,所有操作自动等待 需手动 WebDriverWait 内置
并行执行 原生支持 需 TestNG / pytest-xdist 有限支持
执行速度 快(无中间层) 中(WebDriver 开销) 中(浏览器内执行)
网络拦截 原生支持 需额外代理 原生支持
社区生态 成长中(Microsoft 维护) 最成熟 活跃但局限于 JS

选型建议:

  • 新项目、需跨浏览器、Python 技术栈 → Playwright
  • 已有 Selenium 资产、需支持 Safari IE → Selenium
  • 纯前端团队、仅测 Chromium → Cypress

1.3 环境准备与安装

系统要求

  • Python ≥ 3.8
  • Windows / macOS / Linux

安装步骤

bash 复制代码
# 1. 创建虚拟环境
python -m venv .venv

# Windows 激活
.venv\Scripts\activate
# macOS/Linux 激活
source .venv/bin/activate

# 2. 安装 Playwright 及 pytest 插件
pip install playwright pytest-playwright pytest-html

# 3. 安装浏览器二进制文件(关键步骤)
python -m playwright install

# 安装完成后会下载三个浏览器:
# - Chromium (~150MB)
# - Firefox (~100MB)
# - WebKit (~100MB)

验证安装

bash 复制代码
python -c "from playwright.sync_api import sync_playwright; print('OK')"

1.4 第一个 Playwright 脚本

python 复制代码
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()

    page.goto("https://www.example.com")
    print(f"页面标题: {page.title()}")
    page.screenshot(path="first_screenshot.png")

    browser.close()

录制工具(codegen)

bash 复制代码
# 启动录制工具,自动生成代码
python -m playwright codegen https://www.example.com

录制时浏览器打开目标页面,你在页面上的所有操作会被实时转化为 Python 代码。适合快速了解页面元素的选择器和操作流程。

知识点小结

概念 说明
sync_playwright() 同步上下文管理器,返回 Playwright 实例
p.chromium.launch() 启动浏览器,headless=False 显示界面
browser.new_page() 创建新标签页
page.goto() 导航到指定 URL
page.screenshot() 截图,full_page=True 截取完整页面

第二篇:快速上手 --- 一条测试用例的核心组成

2.1 核心组成:四个角色

一条完整的 UI 自动化测试用例,无论项目多复杂,本质上由四个角色协作完成:

复制代码
┌─────────────────────────────────────────────────────┐
│                  测试用例 (Test)                      │
│  · 描述"验证什么",不关心"怎么操作"                     │
│  · 包含步骤编排 + 断言                                │
└───────────────┬─────────────────────────────────────┘
                │ 调用
                ▼
┌─────────────────────────────────────────────────────┐
│              页面对象 (Page Object)                   │
│  · 描述"怎么操作",封装元素定位与业务动作               │
│  · 继承自 BasePage,复用通用操作原语                   │
└───────────────┬─────────────────────────────────────┘
                │ 继承
                ▼
┌─────────────────────────────────────────────────────┐
│              基础页面类 (BasePage)                    │
│  · 封装 Playwright 底层 API:click / fill / wait 等   │
│  · 所有页面对象共享同一套操作原语                      │
└───────────────┬─────────────────────────────────────┘
                │ 底层调用
                ▼
┌─────────────────────────────────────────────────────┐
│           Playwright Page (浏览器页面)                │
│  · 由 pytest-playwright 插件自动注入                   │
│  · 真实操作浏览器,执行 DOM 交互                       │
└─────────────────────────────────────────────────────┘

一句话总结:测试用例调页面对象 → 页面对象继承基础页面类 → 基础页面类调 Playwright Page → Playwright 操作浏览器。

2.2 四个角色的职责边界

角色 关注什么 不关心什么
测试用例 验证什么(断言)、步骤顺序 元素怎么定位、按钮怎么点
页面对象 元素定位器、业务动作组合 断言逻辑、测试数据来源
基础页面类 Playwright API 封装、等待策略 具体页面的业务逻辑
Playwright Page 浏览器协议通信、DOM 操作 测试框架、业务逻辑

设计原则:上层只依赖下层接口,不跨层调用。测试用例不直接操作 Playwright Page,页面对象不包含断言。

2.3 最小骨架示例

用最少的代码展示四个角色如何串联:

python 复制代码
# === 角色三:BasePage(只贴核心方法,省略完整实现) ===
class BasePage:
    def __init__(self, page):
        self.page = page
        self.url = ""

    def navigate(self):
        self.page.goto(self.url)

    def click(self, selector):
        self.page.locator(selector).click()

    def fill(self, selector, text):
        self.page.locator(selector).fill(text)

    def is_visible(self, selector):
        return self.page.locator(selector).is_visible()

# === 角色二:Page Object ===
class LoginPage(BasePage):
    def __init__(self, page):
        super().__init__(page)
        self.url = "https://example.com/login"
        self.email_input = "input[name='email']"
        self.password_input = "input[name='password']"
        self.submit_btn = "button[type='submit']"

    def login(self, email, password):
        self.fill(self.email_input, email)
        self.fill(self.password_input, password)
        self.click(self.submit_btn)

# === 角色一:Test Case ===
def test_login_success(page):           # ← 角色四:page 由 pytest-playwright 自动注入
    login_page = LoginPage(page)        # 1. 创建页面对象
    login_page.navigate()               # 2. 导航到登录页
    login_page.login("user@test.com", "pass123")  # 3. 执行业务动作
    assert "/login" not in page.url     # 4. 断言结果

关键点:

  • page 参数无需手动创建 --- pytest-playwright 插件自动注入浏览器实例
  • 测试函数只调 login_page.login(),不直接写 page.fill() --- 这就是 POM 的核心
  • 断言写在测试函数中,不写在页面对象中 --- 职责分离

2.4 企业级框架搭建

上面的骨架能跑,但在企业项目中还不够。需要补充的基础设施:

基础设施 解决什么问题 详见
Config 配置管理 多环境 URL 切换、超时/浏览器参数化 第三篇 3.2
TestDataManager 测试数据与代码分离(JSON/CSV) 第三篇 3.3
Logger 日志 执行过程可追溯、失败可定位 第三篇 3.4
conftest.py Fixture 编排、失败截图、用例日志 第三篇 3.6
测试标记 smoke/regression 分类、选择性执行 第三篇 3.8
CLI 脚本 统一运行入口、参数化执行 第三篇 3.9
报告体系 HTML/Allure 可视化报告 第三篇 3.11

2.5 运行测试

bash 复制代码
# 最简运行
python -m pytest tests/ -v
# 指定浏览器 + 有头模式(调试用)
python -m pytest --browser chromium --headed
# 按标记过滤 + 生成报告
python -m pytest -m smoke --html=reports/report.html --self-contained-html

第三篇:落地项目实战

目标 Web 项目 UI 自动化测试框架:Playwright + Python + pytest

3.1 项目结构总览

复制代码
ui_test/
├── config/                 # 配置层:环境、浏览器、超时
│   └── config.py
├── pages/                  # 页面对象层:每页一个类
│   ├── home_page.py
│   └── login_page.py
├── tests/                  # 测试用例层:断言逻辑
│   └── test_login.py
├── utils/                  # 工具层:BasePage、日志、数据、浏览器
│   ├── base_page.py
│   ├── browser_manager.py
│   ├── logger.py
│   └── test_data_manager.py
├── test_data/              # 测试数据(JSON/CSV)
├── screenshots/            # 失败截图输出
├── reports/                # 测试报告输出
├── conftest.py             # pytest Fixture 与 Hook
├── pytest.ini              # pytest 配置
├── run_tests.py            # CLI 运行入口
└── .env                    # 环境变量(不入版本控制)

分层依赖方向:配置层 ← 工具层 ← 页面对象层 ← 测试用例层。上层只依赖直接下层,不跨层调用。

3.2 多环境配置体系

设计思路

用 .env 文件管理环境特定配置(URL、凭据),用 Config 类统一读取,支持 dev / test / prod 多环境切换。

.env 文件

ini 复制代码
BASE_URL=https://test.your-web-app.com/en
TEST_ENV=test
BROWSER_HEADLESS=true
BROWSER_TIMEOUT=30000

# 敏感凭据(不入版本控制)
TEST_USERNAME=testuser@example.com
TEST_PASSWORD=your_password_here

Config 类核心设计

Config 类通过 os.getenv() 从 .env 读取配置,有默认值兜底。关键设计点:

  1. 环境变量优先:所有配置可通过环境变量覆盖,CI/CD 中直接注入即可
  2. 多环境字典 :ENVIRONMENT_CONFIG 维护 dev / test / prod 三套环境
  3. 自动目录创建 :create_directories() 在启动时创建 screenshots / reports / test\_data 目录
  4. 敏感信息隔离:凭据通过环境变量注入,不硬编码
bash 复制代码
# 环境切换方式
# 方式 1:修改 .env 文件
# 方式 2:CI/CD 中注入环境变量
BROWSER_HEADLESS=true TEST_ENV=test python -m pytest tests/

3.3 测试数据管理

设计思路

测试数据与测试代码分离,支持 JSON(结构化凭据)和 CSV(参数化批量数据)两种格式。

数据文件示例

users.json(按用户类型组织):

json 复制代码
{
  "valid": { "email": "testuser@example.com", "password": "TestPassword123!" },
  "invalid": { "email": "invalid@test.com", "password": "wrong_password" }
}

**test\_**cases.csv(参数化测试数据):

csv 复制代码
test_id,email,password,expected_result
TC001,testuser@example.com,TestPassword123!,登录成功
TC002,invalid@test.com,wrong,登录失败

TestDataManager 核心能力

  • load_json_data() / load_csv_data() --- 加载两种格式数据
  • get_user_data('valid') --- 按类型获取用户凭据
  • get_random_data('email') --- 生成随机测试数据(邮箱/用户名/密码/手机号)

pytest 参数化测试

python 复制代码
@pytest.mark.parametrize("email,password,expected", [
    ("testuser@example.com", "TestPassword123!", "成功"),
    ("invalid@test.com", "wrong", "失败"),
])
def test_login_scenarios(page, email, password, expected):
    login_page = LoginPage(page)
    login_page.navigate()
    login_page.login(email, password)
    if expected == "成功":
        assert "/login" not in page.url
    else:
        assert login_page.is_error_displayed()

3.4 日志系统

设计思路

双通道输出:文件(DEBUG 级,完整记录)+ 控制台(INFO 级,关键信息)。日志文件按天命名,存放在 reports/logs/ 目录。

核心能力

  • logger.info() / logger.error() --- 标准日志级别
  • logger.step("描述") --- 测试步骤标记,方便定位失败发生在哪一步
  • logger.test_case_start() / logger.test_case_end() --- 用例边界标记

日志输出示例

复制代码
2026-11-04 16:49:40 - test_login - INFO - 步骤: 步骤 1: 导航到首页
2026-11-04 16:49:42 - test_login - INFO - 步骤: 步骤 2: 点击 Sign In
2026-11-04 16:49:42 - test_login - INFO - 步骤: 步骤 3: 输入凭据
2026-11-04 16:49:42 - test_login - INFO - 步骤: 步骤 4: 执行登录
2026-11-04 16:49:42 - test_login - INFO - 步骤: 步骤 5: 验证结果

3.5 浏览器管理器

BrowserManager 封装 Playwright 浏览器完整生命周期(启动 → 上下文 → 页面 → 关闭),适用于脱离 pytest 独立运行脚本的场景。

注意:使用 pytest-playwright 时,page fixture 由插件自动管理,不需要 BrowserManager。它仅在编写独立运行脚本(如调试脚本、数据采集脚本)时使用。

生命周期:sync_playwright().start() → browser.launch() → browser.new_context() → context.new_page() → ... → 逐一 close()

3.6 conftest.py Fixture 编排

conftest.py 是 pytest 的全局配置文件,定义所有测试共享的 Fixture 和 Hook。

Fixture 体系

Fixture Scope 作用
setup_test_environment session, autouse 启动时创建目录、打印环境信息
test_data session 初始化 TestDataManager
test_case_logger function, autouse 每个用例前后记录开始/结束日志
screenshot_on_failure function, autouse 失败时自动全页截图

Hook 机制

  • pytest_runtest_makereport --- 捕获测试结果(通过/失败),挂到 item 上供 screenshot fixture 判断
  • pytest_configure --- 注册自定义 marker(smoke / regression 等)
  • pytest_collection_modifyitems --- 为所有测试自动添加 ui 标记

Fixture Scope 说明

Scope 生命周期 适用场景
session 整个测试会话 环境初始化、数据管理器
function 每个测试函数(默认) 用例日志、截图
class 每个测试类 类级共享数据

3.7 失败自动截图机制

失败截图是 UI 自动化的关键诊断手段,实现分三步:

  1. Hook 捕获结果 :pytest_runtest_makereport 将测试结果挂到 item.rep_call
  2. Fixture 判断并截图 :screenshot_on_failure 在 teardown 阶段检查 rep_call.failed,若失败则调 page.screenshot(full_page=True)
  3. 命名规范 :failure_{用例名}_{浏览器}_{时间戳}.png,便于关联到具体用例

3.8 测试标记与用例分类

pytest.ini 注册标记

ini 复制代码
[tool:pytest]
markers =
    smoke: 冒烟测试标记
    regression: 回归测试标记
    slow: 慢速测试标记
    login: 登录相关测试标记
    ui: UI界面测试标记

标记使用与执行

python 复制代码
@pytest.mark.smoke
@pytest.mark.login
def test_sign_in_visible(self, page):
    ...
bash 复制代码
python -m pytest -m smoke              # 只跑冒烟
python -m pytest -m "login and regression"  # 登录回归
python -m pytest -m "not slow"         # 排除慢速

3.9 CLI 运行脚本

run_tests.py 封装 pytest 命令组装逻辑,提供统一入口:

bash 复制代码
python run_tests.py --setup            # 安装浏览器
python run_tests.py                    # 运行全部测试
python run_tests.py -m smoke -b firefox  # 冒烟 + Firefox
python run_tests.py --headed -v        # 有头模式调试
python run_tests.py -w 4               # 4 进程并行
python run_tests.py --allure           # 生成 Allure 报告

核心逻辑:解析 argparse 参数 → 设置环境变量(浏览器/headless)→ 组装 pytest 命令 → subprocess 执行。

3.10 完整登录流程实战

被测网站分析

页面 元素 选择器
首页 Sign In 链接 a.btn-ghost[href='/en/login']
登录页 邮箱输入框 input[name='email']
登录页 密码输入框 input[name='password']
登录页 提交按钮 button.auth-submit[type='submit']

测试流程

复制代码
首页 → 点击 Sign In → 登录表单出现 → 填写凭据 → 点击提交 → 验证登录成功

断言策略:双重判断

登录成功的验证采用双重判断,兼容传统多页应用和 SPA 应用:

  1. URL 变化检查 (快速):登录成功后 URL 不再包含 /login
  2. 表单消失检查(补充):如果 URL 未变化(SPA 可能不改变 URL),检查登录表单是否消失
python 复制代码
# 先检查 URL(快速判断),再检查表单消失
current_url = page.url
url_changed = '/login' not in current_url

if not url_changed:
    login_form_gone = not login_page.is_login_form_visible(timeout=3000)
else:
    login_form_gone = True

assert login_form_gone or url_changed

3.11 测试报告体系

HTML 报告(pytest-html)

bash 复制代码
# 注意:使用 3.2.0 版本,4.x 有 JS 渲染兼容问题(JS 动态渲染表格,内嵌预览面板不执行 JS 导致空白)
pip install pytest-html==3.2.0
python -m pytest --html=reports/report.html --self-contained-html

Allure 报告

bash 复制代码
pip install allure-pytest
# 生成数据
python -m pytest --alluredir=reports/allure-results
# 生成可视化报告(需 allure CLI)
allure generate reports/allure-results -o reports/allure-report --clean
allure open reports/allure-report

Allure 提供趋势图、历史对比、用例分类树、步骤详情等可视化能力。

附录

A. 完整项目依赖清单

复制代码
pytest==7.4.3
pytest-asyncio==0.21.1
pytest-html==3.2.0
pytest-xdist==3.3.1
pytest-playwright==0.4.3
playwright==1.40.0
allure-pytest==2.13.2
python-dotenv==1.0.0

B. 常用命令速查表

命令 说明
python -m playwright install 安装浏览器二进制
python -m playwright codegen <url> 录制脚本
python -m pytest tests/ -v 运行所有测试
python -m pytest -m smoke 运行冒烟测试
python -m pytest --browser firefox 指定浏览器
python -m pytest --headed 有头模式
python -m pytest -n 4 4 进程并行
python -m pytest --html=report.html --self-contained-html 生成 HTML 报告
allure generate allure-results -o allure-report --clean 生成 Allure 报告

C. Playwright 选择器速查

类型 语法 稳定性
测试 ID page.get_by_test_id('login-btn') ★★★★★
角色+名称 page.get_by_role('button', name='Sign In') ★★★★
CSS 属性 input[name='email'] ★★★
文本 text='Sign In' ★★
XPath xpath=//button[@type="submit"] ★

相关推荐
AI源码1 小时前
基于 Python 的汽车市场数据分析与可视化平台
python·数据分析·汽车
会飞锦鲤1 小时前
基于 SVM + HOG 的铸造缺陷检测系统
python·opencv·机器学习·计算机视觉·缺陷检测·图像分类·svm
starrysky8102 小时前
zipfile 中文乱码修复:把已知 GBK 旧 ZIP 解压到正确中文目录
python
用户91294046848422 小时前
Codex 从零到能交付(09):让 Codex 写有用的测试,而不是只测成功路径
python
2601_967987372 小时前
Debian如何配置Python调试工具
python·debian·pdb·调试工具·ipdb
MoRay2 小时前
放弃云端 Computer Use,我用 Python 手搓了一个带“物理防呆”的本地 GUI Agent 底座
python·github
青少儿编程课堂4 小时前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:Bellman-Ford与SPFA含负权最短路及负环检测全解析
c++·python·算法·bfs·信息学竞赛
小小张说故事4 小时前
Python 的 __init__ 到底是不是构造函数?拆开 __new__ 先造对象、那次被跳过的初始化和必须返回 None 的规矩
python
拖累斯4 小时前
Jenkins + Python 全自动跑通「禅道任务/BUG 导出 → 月度开发考核表」
python·jenkins·bug