1. 项目概述:为什么我们需要一个高可靠的Web自动化流程?

在Web应用开发迭代越来越快的今天,前端一个按钮的改动,后端一个接口的调整,都可能引发连锁反应,导致线上功能出现意想不到的故障。传统的单元测试和接口测试虽然能覆盖一部分逻辑,但对于用户实际操作的完整路径——从打开浏览器、输入网址、点击交互到最终看到结果——往往力有不逮。这就是端到端测试的价值所在:它模拟真实用户的行为,验证整个应用链路是否畅通无阻。

我见过太多团队,初期为了快速上线,手动点点点还能应付。但随着功能模块增多,回归测试的工作量呈指数级增长,测试人员疲于奔命,上线前依然提心吊胆。这时候,一个稳定、可靠的自动化测试流程就成了刚需。它不仅是质量保障的“守门员”,更是团队研发效率的“加速器”,能把人力从重复劳动中解放出来,投入到更有价值的探索性测试和用户体验优化中去。

这次要聊的,就是用 Python 搭配 Playwright 来搭建这样一套流程。你可能听说过 Selenium,但 Playwright 作为后起之秀,在稳定性、执行速度和现代化 Web 应用支持上优势明显。它由微软出品,原生支持 Chromium、Firefox 和 WebKit 三大浏览器引擎,对单页应用、Shadow DOM、网络拦截等场景的处理更加得心应手。用 Python 来驱动,则让整个框架的搭建和脚本编写对广大后端和测试工程师更加友好,能无缝集成到现有的 CI/CD 流水线中。

2. 核心工具选型:为什么是 Python + Playwright?

工欲善其事,必先利其器。在 Web 自动化领域,工具链的选择直接决定了后续开发和维护的成本。我们放弃老牌的 Selenium,选择 Python + Playwright 的组合,是基于以下几个核心考量。

2.1 Playwright 的压倒性优势

首先,Playwright 在设计之初就瞄准了现代 Web 开发的痛点。它与 Selenium 最大的不同在于其架构:Playwright 通过一个单一的 API 来控制浏览器,而不是通过 WebDriver 协议。这意味着更少的通信开销和更快的执行速度。在实际对比中,相同用例的脚本,Playwright 的执行耗时通常只有 Selenium 的 60%-70%。

其次,稳定性是自动化测试的生命线。Playwright 内置了强大的自动等待机制。它不仅能等待元素加载( page.wait_for_selector ),还能智能等待元素变得可交互(如可点击、可输入)。这从根本上解决了因网络延迟或前端框架渲染导致的“元素找不到”的经典难题,大大减少了脚本中需要手动添加 time.sleep 的情况。

再者,对于复杂场景的支持非常出色。例如:

  • 网络拦截与模拟 :可以轻松地拦截和修改网络请求,用于测试错误处理、模拟慢速网络或 mock 接口数据,而无需动后端代码。
  • 文件上传下载 :提供了简洁的 API 来处理文件选择对话框和监听下载事件,比传统的通过 input 元素上传要稳定得多。
  • 跨域与多页面 :原生支持多页面(Tab)和 iframe 的操作,上下文(Context)和页面(Page)的概念清晰,管理起来非常方便。

2.2 Python 作为粘合剂的灵活性

为什么用 Python 而不是 Playwright 官方也更推荐的 TypeScript/JavaScript?这取决于团队的技术栈。如果团队以 Python 技术栈为主(例如后端是 Django/Flask,数据分析用 Pandas),那么选择 Python 可以降低学习成本,并且能更好地与现有工具链集成。

Python 的 pytest 框架是测试领域的瑞士军刀,功能强大且插件生态丰富。我们可以用 pytest-playwright 插件来管理浏览器的启动和关闭,用 pytest-html 生成漂亮的测试报告,用 pytest-xdist 进行并行测试以提升效率。此外,Python 在数据处理(如测试数据准备)、调用外部服务(如数据库校验)等方面也更为方便。

一个常见的误区是认为 Playwright for Python 是“二等公民”。实际上,微软官方对 Python 版本的支持非常积极,API 与 Node.js 版本保持了高度一致,绝大多数新特性都会同步更新。

2.3 备选方案简析

当然,市场上还有其他选择。Cypress 也是一个优秀的端到端测试框架,它采用运行在浏览器内的独特架构,提供了极佳的调试体验。但它对浏览器外的操作(如访问多个域名、操作多个 Tab)支持较弱,且对 iframe 的支持 historically 有些问题。如果你的应用是纯粹的单页应用且测试范围限定在当前域名内,Cypress 值得考虑。

另一个是 Selenium 4,它引入了相对定位器、新的 Chrome DevTools Protocol 支持等改进,生态依然庞大。但它的核心架构决定了其在稳定性和执行速度上要追上 Playwright 仍有难度。对于已有庞大 Selenium 资产且升级成本高的团队,可以逐步迁移;对于新项目,我更推荐直接从 Playwright 开始。

注意 :工具选型没有绝对的好坏,只有适合与否。评估时一定要结合项目技术栈、团队技能和待测应用的特点(如是否是大量使用 Web Components 的现代应用)。

3. 环境搭建与核心配置详解

理论说再多,不如动手搭一遍。这里我会带你从零开始,搭建一个健壮的 Python + Playwright 测试环境,并分享一些让环境更“听话”的配置技巧。

3.1 Python 环境与依赖安装

首先确保你有一个干净的 Python 环境。我强烈建议使用 venv conda 创建虚拟环境,避免包依赖冲突。

# 创建并激活虚拟环境 (以 venv 为例)
python -m venv playwright-env
# Windows
playwright-env\Scripts\activate
# macOS/Linux
source playwright-env/bin/activate

接下来安装核心包。我们不仅需要 playwright ,还需要 pytest 作为测试运行器,以及连接两者的 pytest-playwright 插件。

pip install playwright pytest pytest-playwright

安装完成后,需要安装 Playwright 所需的浏览器二进制文件。这是 Playwright 的一大优点,它会自动下载指定版本的浏览器,确保环境一致性。

playwright install chromium firefox webkit

这条命令会下载 Chromium、Firefox 和 WebKit(Safari 的开源核心)的最新稳定版。如果你只需要 Chromium(对于大多数测试场景已经足够),可以只安装它,以节省磁盘空间和下载时间。

3.2 关键配置:让测试更稳定、更高效

默认配置能跑,但好的配置能让测试跑得又快又稳。我们需要在项目根目录创建一个 pytest.ini 配置文件。

[pytest]
# 指定测试文件的位置和命名模式
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*

# 添加命令行默认参数
addopts = 
    -v  # 详细输出
    --tb=short  # 发生错误时,缩短回溯信息,更清晰
    --strict-markers  # 严格检查标记,避免拼写错误
    --html=reports/report.html  # 生成HTML报告
    --self-contained-html  # 将CSS和JS内嵌到HTML中,报告单文件即可查看

# 定义自定义标记,用于分类测试,如 @pytest.mark.smoke
markers =
    smoke: 冒烟测试用例
    regression: 回归测试用例
    slow: 运行较慢的测试用例

# Playwright 特定配置
[pytest.playwright]
# 设置全局超时时间(毫秒)
timeout = 30000
# 是否在每次测试失败时自动截屏和保存视频(非常有用!)
screenshot = on
video = on
# 浏览器启动选项,例如无头模式、窗口大小
launch_persistent_context_options = 
    {"headless": true, "viewport": {"width": 1920, "height": 1080}}

配置解析与避坑指南:

  • --tb=short :当测试失败时,默认的 --tb=auto 会打印很长的堆栈信息,其中大部分是 Playwright 内部调用,对定位问题帮助不大。 short 模式只显示最相关的错误行,更清晰。
  • screenshot video :务必开启。当用例在 CI 环境失败时,一张截图或一段视频是分析问题的“第一现场证据”,价值远超日志文字。它们会自动保存在 test-results/ 目录下。
  • launch_persistent_context_options :这里配置的是浏览器启动的默认选项。 headless: true 表示无头模式(不显示UI),适合 CI 环境;本地调试时可以设为 false 。统一视口大小能保证测试的一致性,避免因窗口大小不同导致元素定位失败。
  • 浏览器版本 :对于追求绝对稳定的企业级项目,可以考虑锁定浏览器版本。可以在安装时指定: playwright install chromium@版本号 ,并在配置中通过 channel executable_path 指定。这能避免因浏览器自动升级导致的不兼容问题。

3.3 项目结构规划

一个清晰的项目结构是维护性的基础。建议采用如下结构:

your_project/
├── conftest.py          # pytest 共享 fixture 定义
├── pytest.ini           # 配置文件
├── requirements.txt     # Python 依赖清单
├── pages/               # 页面对象模型(Page Object Model)
│   ├── __init__.py
│   ├── login_page.py
│   └── home_page.py
├── tests/               # 测试用例
│   ├── __init__.py
│   ├── test_login.py
│   └── test_checkout.py
├── utils/               # 工具函数(如数据生成、API调用)
│   ├── __init__.py
│   └── data_helper.py
├── fixtures/            # 测试数据文件
│   └── users.json
└── reports/             # 测试报告输出目录(由pytest-html生成)

这个结构将页面逻辑、测试用例、工具和数据分离,符合“关注点分离”原则。 conftest.py 是这个结构的灵魂,我们接下来会重点讲。

4. 核心实战:从编写第一个测试到搭建完整框架

环境就绪,让我们开始编写真正的测试。我会遵循“简单到复杂”的原则,先写一个直白的测试,然后逐步重构,引入最佳实践,最终形成一个可维护的框架。

4.1 编写第一个“直白”的测试

我们先写一个最直接的测试脚本,访问百度首页并搜索一个关键词。新建 tests/test_first_demo.py

import re
from playwright.sync_api import Page, expect

def test_baidu_search(page: Page):
    # 1. 导航到百度
    page.goto("https://www.baidu.com")
    
    # 2. 定位搜索框并输入关键词
    search_box = page.locator("#kw")
    search_box.fill("Playwright")
    
    # 3. 定位搜索按钮并点击
    search_button = page.locator("#su")
    search_button.click()
    
    # 4. 等待结果页面加载,并断言结果中包含预期文本
    page.wait_for_load_state("networkidle") # 等待网络空闲
    first_result = page.locator("div.result h3 a").first
    expect(first_result).to_contain_text(re.compile(r"playwright", re.IGNORECASE))

运行测试: pytest tests/test_first_demo.py -v

这个测试能跑通,但它暴露了几个问题:

  1. 硬编码 :URL、选择器、测试数据都直接写在测试里,一旦页面改动,需要修改多处。
  2. 缺乏复用 :登录、导航到某个页面等操作会在多个测试中重复。
  3. 可读性差 :业务逻辑(在百度搜索)和底层操作(定位、点击)混杂在一起。

4.2 引入 Fixture 和页面对象模型

为了解决上述问题,我们需要引入两个核心概念:Pytest Fixture 和 页面对象模型。

首先,在 conftest.py 中定义共享的 Fixture。 Fixture 是 pytest 提供的用于准备和清理测试环境的强大机制。

import pytest
from playwright.sync_api import Browser, BrowserContext, Page

@pytest.fixture(scope="session")
def browser():
    """启动一个浏览器实例,整个测试会话只启动一次。"""
    # 使用 sync_playwright 上下文管理器
    from playwright.sync_api import sync_playwright
    with sync_playwright() as p:
        # 启动浏览器,使用配置中的选项
        browser = p.chromium.launch(headless=True, slow_mo=50) # slow_mo 可减慢操作,便于观察
        yield browser
        browser.close()

@pytest.fixture(scope="function")
def context(browser: Browser):
    """为每个测试函数创建一个新的浏览器上下文。"""
    # 上下文相当于一个独立的浏览器会话,隔离 cookies、localStorage 等
    context = browser.new_context(
        viewport={"width": 1920, "height": 1080},
        # 可以在这里模拟设备,如 'iPhone 11'
        # record_video_dir="videos/" # 如果需要为每个测试单独录视频
    )
    yield context
    context.close()

@pytest.fixture(scope="function")
def page(context: BrowserContext):
    """为每个测试函数创建一个新的页面。"""
    page = context.new_page()
    yield page
    page.close()

@pytest.fixture
def login_page(page: Page):
    """返回一个已初始化的登录页面对象。"""
    from pages.login_page import LoginPage
    return LoginPage(page)

关键点解析:

  • scope 参数 session 表示整个 pytest 执行过程只创建一次,适合重量级资源如浏览器; function 表示每个测试函数都会创建/销毁,适合需要隔离的测试,如 page context 。这平衡了执行效率和测试独立性。
  • slow_mo :在本地调试时,给 launch new_context 加上 slow_mo=500 (单位毫秒),可以让每个 Playwright 操作都慢下来,方便你观察测试执行过程。
  • 上下文隔离 :为每个测试使用独立的 context 是黄金法则。这确保了测试 A 设置的 cookie 不会影响测试 B,实现了真正的并行化和稳定性。

其次,创建页面对象(Page Object)。 pages/login_page.py 中:

from playwright.sync_api import Page, expect

class LoginPage:
    def __init__(self, page: Page):
        self.page = page
        self.username_input = page.locator("#username")
        self.password_input = page.locator("#password")
        self.submit_button = page.locator("button[type='submit']")
        self.error_message = page.locator(".alert-error")
    
    def navigate(self):
        """导航到登录页。"""
        self.page.goto("https://your-app.com/login")
        return self
    
    def fill_credentials(self, username: str, password: str):
        """填写用户名和密码。"""
        self.username_input.fill(username)
        self.password_input.fill(password)
        return self
    
    def submit(self):
        """点击登录按钮。"""
        self.submit_button.click()
        # 等待页面跳转或某个登录后元素出现
        self.page.wait_for_url("**/dashboard")
        # 或者 self.page.wait_for_selector(".user-avatar")
    
    def get_error_message(self):
        """获取错误提示文本。"""
        return self.error_message.text_content()
    
    def perform_login(self, username: str, password: str):
        """链式调用:导航、填写、提交。"""
        return self.navigate().fill_credentials(username, password).submit()

页面对象模型将页面的元素定位和基本操作封装起来。测试用例从此不再关心 #username 这个选择器是什么,它只调用 login_page.fill_credentials(“user”, “pass”) 。当页面元素 ID 变更时,你只需要修改这个页面对象类中的一个地方。

4.3 重构第一个测试,并编写一个完整的登录测试

现在,我们用 Fixture 和页面对象重写并扩展测试。假设我们有一个待测应用。

# tests/test_login.py
import pytest

def test_successful_login(login_page):
    """测试成功登录流程。"""
    # 使用页面对象的“组合方法”
    login_page.perform_login("valid_user", "valid_password")
    # 断言:登录后应跳转到仪表盘,且页面标题包含用户名
    expect(login_page.page).to_have_url("**/dashboard")
    expect(login_page.page.locator(".welcome-msg")).to_contain_text("valid_user")

def test_login_with_invalid_password(login_page):
    """测试使用错误密码登录。"""
    login_page.navigate().fill_credentials("valid_user", "wrong_pass").submit()
    # 断言:应显示错误信息
    error_text = login_page.get_error_message()
    assert "密码错误" in error_text
    # 或者使用 Playwright 的 expect
    expect(login_page.error_message).to_be_visible()
    expect(login_page.error_message).to_contain_text("密码错误")

def test_baidu_search_using_fixture(page):
    """使用 fixture 重写最初的百度测试。"""
    page.goto("https://www.baidu.com")
    page.locator("#kw").fill("Playwright Python")
    page.locator("#su").click()
    page.wait_for_load_state("networkidle")
    # 更健壮的断言:等待特定结果出现后再断言
    results = page.locator("div.result h3 a")
    expect(results.first).to_be_visible()
    # 检查所有结果中是否至少有一个包含关键词(不区分大小写)
    all_texts = results.all_text_contents()
    assert any("playwright" in text.lower() for text in all_texts)

可以看到,测试用例变得非常简洁和可读,纯粹在描述业务场景。所有技术细节都被隐藏在了 Fixture 和页面对象之后。

5. 高级技巧与稳定性提升实战

编写能跑的测试不难,编写能在 CI/CD 流水线中稳定运行、快速反馈的测试才是挑战。下面分享几个提升稳定性和效率的高级技巧。

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

不稳定的测试十有八九是“等待”问题。Playwright 提供了多种等待方式,关键在于正确使用。

1. 自动等待是默认的: page.click(selector) 这个操作内部已经包含了等待元素可交互(可见、未禁用、未被遮挡)的步骤。所以,在大多数情况下,你不需要在操作前手动等待。

2. 显式等待用于复杂条件: 当你的断言依赖于某个异步状态时,需要使用 expect 断言,它内置了重试和超时机制。

# 不推荐:使用固定的 sleep
import time
time.sleep(5) # 魔法数字,可能太长或太短

# 推荐:使用 expect 等待元素满足条件
expect(page.locator(".loading-spinner")).to_be_hidden(timeout=10000) # 等待加载动画消失,最多10秒
expect(page).to_have_url("**/order-success", timeout=15000) # 等待URL变化

3. 定位器策略: 优先使用面向用户的定位方式,如文本内容、角色属性,它们比脆弱的 CSS 选择器更稳定。

# 脆弱:依赖于具体的 CSS 结构
page.locator("#main > div.content > form > div:nth-child(2) > input")

# 稍好:但 ID 可能动态生成或变更
page.locator("#email-input")

# 更好:使用文本内容或 ARIA 角色(如果前端实现规范)
page.locator("button", has_text="提交订单")
page.locator("[aria-label='搜索']")
page.get_by_role("textbox", name="用户名") # Playwright 1.27+
page.get_by_text("欢迎回来") # Playwright 1.27+

# 最佳实践:组合使用,增加特异性
page.locator("form.login-form").locator("input[name='email']")

5.2 处理动态内容与复杂交互

现代 Web 应用大量使用动态加载、模态框、拖拽等交互。

处理动态下拉列表(非标准 select):

# 1. 点击触发下拉框
page.locator(".ant-select-selector").click()
# 2. 等待下拉选项出现
page.wait_for_selector(".ant-select-dropdown:visible")
# 3. 选择特定选项
page.locator(".ant-select-item", has_text="选项二").click()

文件上传:

# 传统 input[type=file] 元素
page.locator("input[type='file']").set_input_files("/path/to/file.pdf")

# 处理隐藏的 input 或自定义上传组件
# 有时需要先点击一个按钮触发文件选择对话框,但 Playwright 无法与系统对话框交互。
# 解决方案:直接监听页面上的 `filechooser` 事件。
with page.expect_file_chooser() as fc_info:
    page.locator(".upload-button").click() # 点击触发对话框的按钮
file_chooser = fc_info.value
file_chooser.set_files("/path/to/file.pdf")

网络拦截与 Mock: 这是 Playwright 的杀手锏,用于制造测试场景或提升速度。

# 拦截所有图片请求并阻止加载,加速测试
page.route("**/*.{png,jpg,jpeg,webp,gif}", lambda route: route.abort())

# 拦截特定 API 请求,并返回 Mock 数据
def handle_route(route):
    if "/api/user/profile" in route.request.url:
        # 返回模拟的 JSON 响应
        route.fulfill(
            status=200,
            content_type="application/json",
            body=json.dumps({"name": "Mock User", "age": 30})
        )
    else:
        # 继续原来的请求
        route.continue_()
page.route("**/api/**", handle_route)

# 在测试中,调用 API 的代码将收到我们 mock 的数据

5.3 测试数据管理与准备

测试数据不应该硬编码在脚本里。我常用的模式是使用 JSON 或 YAML 文件管理测试数据,并用 Fixture 在测试前准备、测试后清理。

# utils/data_helper.py
import json
import pytest

def load_test_data(file_name: str, key: str):
    with open(f"fixtures/{file_name}.json", "r", encoding="utf-8") as f:
        data = json.load(f)
    return data.get(key, [])

# conftest.py 中添加数据准备 Fixture
@pytest.fixture
def prepare_user_data():
    """测试前置:创建一个测试用户。"""
    from utils.api_client import create_user
    user_data = {"username": f"test_{int(time.time())}", "password": "123456"}
    user_id = create_user(user_data) # 调用后端 API 或操作数据库
    yield user_data # 将用户数据传递给测试用例
    # 测试后置:清理测试用户
    delete_user(user_id)

在测试用例中,直接使用这个 Fixture:

def test_user_dashboard(prepare_user_data, page):
    user = prepare_user_data # 拿到创建好的用户数据
    # 使用该用户登录并测试...

6. 集成到 CI/CD 与生成测试报告

自动化测试只有集成到持续集成流程中,才能发挥最大价值。这里以 GitHub Actions 为例。

6.1 编写 GitHub Actions 工作流

在项目根目录创建 .github/workflows/e2e-tests.yml

name: E2E Tests

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        browser: [chromium] # 可以扩展为 [chromium, firefox, webkit]
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.10'
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
        playwright install ${{ matrix.browser }} --with-deps
    
    - name: Run E2E Tests
      run: |
        # 设置环境变量,如测试服务器的地址
        export BASE_URL=https://staging.your-app.com
        # 运行测试,生成JUnit格式报告用于CI集成,HTML报告用于查看详情
        pytest tests/ \
          --browser ${{ matrix.browser }} \
          --junitxml=reports/junit-${{ matrix.browser }}.xml \
          --html=reports/report-${{ matrix.browser }}.html \
          --self-contained-html
    
    - name: Upload test results
      if: always() # 即使测试失败也上传报告
      uses: actions/upload-artifact@v3
      with:
        name: test-results-${{ matrix.browser }}
        path: |
          reports/
          test-results/ # 包含截图和视频

这个工作流会在每次推送到主分支或发起 Pull Request 时,在 Ubuntu 环境下用 Chromium 运行所有测试。它安装了依赖和浏览器,运行测试并生成两种报告,最后将报告和失败截图打包上传,供下载查看。

6.2 解读测试报告与问题排查

测试运行后, pytest-html 生成的 report.html 是最直观的。报告会清晰列出通过、失败、跳过的用例,点击失败用例可以查看详细的错误堆栈和日志。

当测试在 CI 中失败时,排查思路如下:

  1. 查看失败截图和视频 :这是第一步,也是最有效的一步。直接看失败时刻的页面状态,能立刻判断是页面没加载出来、元素错位,还是弹窗未关闭等问题。
  2. 检查 CI 日志 :查看 pytest 输出的完整日志,特别是失败前的最后几个操作。Playwright 的日志会记录它执行了哪些操作(如 click , fill )。
  3. 对比本地环境 :在本地用相同的命令(特别是相同的 headless 模式)复现。有时 CI 环境与本地环境(浏览器版本、屏幕分辨率、网络)的差异会导致问题。
  4. 检查选择器 :如果截图显示元素存在但测试说找不到,很可能是选择器问题。在 CI 的截图里,用浏览器的开发者工具(如果允许)检查元素的属性是否与脚本中的选择器匹配。
  5. 检查异步操作 :确认是否在正确的时机进行了断言。是否在某个网络请求完成或元素状态稳定后才进行下一步操作?适当增加 expect timeout 参数。
  6. 检查测试数据 :确认测试依赖的数据(如测试用户、订单状态)在 CI 环境中是否已正确准备。

一个常见的失败模式是“动态内容”。例如,一个列表的第一项每次加载可能不同。你的脚本断言第一项是“项目A”,但 CI 运行时第一项可能是“项目B”。解决方案是避免断言绝对顺序或内容,而是断言相对关系或使用更模糊的匹配。

# 脆弱的断言
expect(page.locator(".list-item").first).to_have_text("项目A")

# 更健壮的断言
all_items = page.locator(".list-item").all_text_contents()
assert "项目A" in all_items # 断言“项目A”在列表中即可
# 或者使用正则匹配
expect(page.locator(".list-item").first).to_have_text(re.compile(r"项目"))

7. 维护性与扩展性最佳实践

项目后期,测试套件可能增长到数百个用例。如何保持其可维护性?

1. 使用标记分类测试: 利用我们在 pytest.ini 中定义的 markers

import pytest

@pytest.mark.smoke
def test_login(login_page):
    ...

@pytest.mark.regression
@pytest.mark.slow
def test_complex_order_flow(order_page):
    ...

# 只运行冒烟测试
pytest -m smoke
# 运行除慢速测试外的所有测试
pytest -m "not slow"

2. 参数化测试: 用一组数据驱动同一个测试逻辑。

import pytest

@pytest.mark.parametrize("username, password, expected_error", [
    ("", "123456", "用户名不能为空"),
    ("admin", "", "密码不能为空"),
    ("wrong", "wrong", "用户名或密码错误"),
])
def test_login_validation(login_page, username, password, expected_error):
    login_page.navigate().fill_credentials(username, password).submit()
    expect(login_page.error_message).to_contain_text(expected_error)

3. 定期重构与清理:

  • 定期审查页面对象,合并重复的操作。
  • 将通用的工具函数(如随机生成字符串、日期)提取到 utils 模块。
  • 删除或注释掉长期不用的测试用例。

4. 视觉回归测试(进阶): 对于 UI 变化敏感的项目,可以集成视觉回归测试工具,如 playwright-screenshot percy 。在测试中截取关键页面或组件的截图,与基准图对比,自动检测像素级差异。这能有效捕捉到未预料到的样式改动。

搭建高可靠的 Web 自动化流程不是一蹴而就的,它始于一个简单的脚本,成长于持续的最佳实践迭代和稳定性打磨。从用 Playwright 写出第一个能跑的测试,到构建一个能在团队 CI 流水线中稳定运行、快速反馈的测试套件,这个过程本身也是对软件质量保障体系的深刻实践。最重要的不是追求 100% 的自动化覆盖率,而是让自动化测试成为团队值得信赖的、高效的反馈工具,真正为产品交付速度和质量保驾护航。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐