1. 初识Playwright:现代Web测试的瑞士军刀

第一次听说Playwright时,我正在为一个电商项目焦头烂额地调试Selenium脚本。那会儿最让我头疼的就是不同浏览器间的兼容性问题,直到同事推荐了这个微软开源的测试工具。Playwright给我的第一印象就像是个"全能选手"——它不仅能同时支持Chromium、Firefox和WebKit三大浏览器引擎,还能用我熟悉的Python语言编写脚本。

这个工具最吸引我的地方在于它的"真实感"。比如在测试一个下拉菜单时,传统的工具可能需要手动添加等待时间,而Playwright会自动等待元素真正可交互时才执行操作。有次我测试一个懒加载的图片墙,脚本竟然自动等待所有图片加载完成才进行截图,这让我省去了大量调试时间。

实际项目中,我常用它来做这些事情:

  • 检查页面在不同浏览器下的渲染一致性
  • 自动化填写表单并验证提交结果
  • 捕获页面加载过程中的性能指标
  • 对单页应用(SPA)进行深度交互测试

2. 环境搭建:十分钟快速上手

记得第一次安装Playwright时,我特意记了时——从零开始到成功运行第一个测试只用了7分半钟。下面分享我最常用的Python环境配置方法,连终端命令都给你准备好了:

# 创建虚拟环境(可选但推荐)
python -m venv playwright-env
source playwright-env/bin/activate  # Linux/Mac
# playwright-env\Scripts\activate   # Windows

# 安装Playwright包
pip install playwright

# 安装浏览器二进制文件
playwright install

这里有个小技巧:如果你在国内,可能会遇到下载慢的问题。可以尝试设置环境变量来加速下载:

# 设置Playwright下载镜像源
set PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright
playwright install

安装完成后,我习惯用这个"健康检查"脚本来验证环境:

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://example.com")
    print(f"页面标题:{page.title()}")
    browser.close()

如果看到输出"页面标题:Example Domain",恭喜你,环境配置成功了!我第一次运行成功时,还特意截图发了朋友圈纪念。

3. 第一个测试脚本:从登录测试开始

让我们从一个真实的电商网站登录测试开始。我选择这个场景是因为它涵盖了:

  • 页面导航
  • 元素定位
  • 表单填写
  • 点击操作
  • 结果验证
import re
from playwright.sync_api import expect

def test_login(page):
    # 访问登录页面
    page.goto("https://demo.opencart.com/admin")
    
    # 断言页面标题
    expect(page).to_have_title(re.compile("Administration"))
    
    # 填写登录表单
    page.get_by_label("Username").fill("demo")
    page.get_by_label("Password").fill("demo")
    
    # 点击登录按钮
    page.get_by_role("button", name="Login").click()
    
    # 验证登录后跳转
    expect(page.get_by_role("heading", name="Dashboard")).to_be_visible()

这个脚本里我特别喜欢Playwright的智能等待机制。有次我在一个慢速网络环境下测试,发现它自动等待了足足15秒直到登录按钮变为可点击状态,而传统工具这时早就超时报错了。

元素定位方面,我推荐优先使用这些方法:

  1. get_by_role() - 通过ARIA角色定位
  2. get_by_label() - 关联label文本
  3. get_by_placeholder() - 通过占位文本
  4. get_by_text() - 通过可见文本

尽量避免使用xpath,就像上个月我维护的一个项目,前端改了DOM结构导致大量xpath定位失效,而使用语义化定位的脚本全都安然无恙。

4. 进阶技巧:处理常见疑难杂症

在实际项目中,我遇到过不少"坑",这里分享三个最有用的解决方案:

场景一:处理动态加载元素 上周测试一个使用无限滚动的页面时,我这样等待特定商品出现:

# 传统方式(不推荐)
page.wait_for_timeout(5000)  # 固定等待5秒

# Playwright推荐方式
product = page.get_by_text("限量版球鞋")
product.wait_for(state="visible")  # 最多等待30秒

场景二:文件上传测试 测试文件上传功能时,我发现Playwright的处理特别优雅:

# 单文件上传
page.get_by_label("上传头像").set_input_files("avatar.jpg")

# 多文件上传
page.get_by_label("上传作品集").set_input_files([
    "work1.jpg",
    "work2.pdf"
])

场景三:跨iframe操作 昨天刚解决的一个支付iframe问题:

# 定位到支付iframe
frame = page.frame_locator("iframe[name='payment']")

# 在iframe内操作
frame.get_by_placeholder("卡号").fill("4111111111111111")
frame.get_by_text("确认支付").click()

还有个特别实用的功能是网络请求拦截,我在测试错误处理时经常用:

# 模拟API返回500错误
page.route("**/api/checkout", lambda route: route.abort())

5. 跨浏览器测试实战

Playwright最强大的功能之一就是真正的跨浏览器测试。这是我上个月做兼容性测试的配置:

import pytest
from playwright.sync_api import Page

@pytest.mark.parametrize("browser_type", ["chromium", "firefox", "webkit"])
def test_search(browser_type, request):
    playwright = request.getfixturevalue("playwright")
    browser = getattr(playwright, browser_type).launch()
    page = browser.new_page()
    
    page.goto("https://example.com/search")
    page.get_by_placeholder("搜索").fill("Playwright")
    page.get_by_role("button", name="搜索").click()
    
    expect(page.locator("#results")).to_contain_text("Playwright")
    browser.close()

测试中发现一个有趣的差异:WebKit对CSS动画的处理与其他浏览器不同,导致我们的下拉菜单在Safari中显示异常。多亏了这个测试,我们提前发现了问题。

移动端测试也很简单:

iphone = playwright.devices["iPhone 13"]
browser = playwright.chromium.launch()
context = browser.new_context(**iphone)
page = context.new_page()

6. 调试与问题排查

调试是自动化测试中最耗时的环节,Playwright提供了几个神器:

1. Playwright Inspector 在终端运行测试时加上--debug参数:

pytest --debug

会启动一个GUI调试器,可以:

  • 单步执行
  • 查看实时DOM
  • 修改定位器
  • 查看执行日志

2. 追踪记录 在配置中添加:

context.tracing.start(screenshots=True, snapshots=True)
# 测试结束后
context.tracing.stop(path="trace.zip")

然后用playwright show-trace trace.zip查看完整的测试回放。

3. 视频录制 这是我上周排查一个偶发bug时的配置:

context = browser.new_context(record_video_dir="videos/")
# 测试结束后
page.close()
context.close()

7. 持续集成实践

在GitHub Actions中我是这样配置的:

name: Playwright Tests
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-python@v4
    - run: pip install playwright pytest-playwright
    - run: playwright install
    - run: pytest tests/ --browser=all
    - uses: actions/upload-artifact@v3
      if: always()
      with:
        name: playwright-report
        path: playwright-report/

有个小技巧:在CI中我通常会启用headless模式并设置超时更长:

browser = playwright.chromium.launch(
    headless=True,
    timeout=60000  # 60秒超时
)

8. 最佳实践与性能优化

经过十几个项目的实践,我总结了这些经验:

目录结构建议

tests/
├── fixtures/
├── page_objects/  # 页面对象模型
├── test_cases/
├── utils/         # 工具函数
└── conftest.py    # pytest配置

页面对象模式示例

class LoginPage:
    def __init__(self, page):
        self.page = page
        self.username = page.get_by_label("Username")
        self.password = page.get_by_label("Password")
        self.submit = page.get_by_role("button", name="Login")
    
    def navigate(self):
        self.page.goto("https://example.com/login")
    
    def login(self, user, pwd):
        self.username.fill(user)
        self.password.fill(pwd)
        self.submit.click()

性能优化技巧

  • 复用浏览器上下文:每个测试类只创建一次
  • 并行执行:pytest -n auto
  • 禁用不必要的资源加载:
    context.route("**/*.{png,jpg,jpeg}", lambda route: route.abort())
    

断言策略

# 不推荐
assert "成功" in page.text_content("#message")

# 推荐
expect(page.locator("#message")).to_contain_text("成功")
expect(page).to_have_url(re.compile(r"/dashboard"))

最近一个项目中,通过这些优化将测试套件执行时间从23分钟降到了4分钟,团队的小伙伴们都惊呆了。

更多推荐