前端技术05-Selenium太慢?从手动测试到自动化:Playwright多浏览器并行测试实战,Playwright让E2E测试效率翻倍
目录
- 1. 开篇:测试工程师的至暗时刻
- 2. Playwright是什么?
- 3. 三强争霸:Playwright vs Cypress vs Selenium
- 4. 环境搭建:5分钟上手
- 5. 核心概念:Locator与Action
- 6. 实战:编写第一个E2E测试
- 7. Page Object模式最佳实践
- 8. 并行测试:效率提升5倍的秘密
- 9. CI/CD集成:GitHub Actions配置
- 10. 避坑指南:常见问题与解决方案
- 11. 文末三件套
开篇:测试工程师的至暗时刻
你是否遇到过每次上线都要手动测试一遍所有功能,重复劳动浪费大量时间的痛苦场景?Selenium配置复杂,跑个测试用例慢得想砸电脑。网上搜到的E2E方案要么代码写不动,要么浏览器兼容性差。本文将从原理到实战,给出一个生产级解决方案,包含完整代码和避坑指南。
💡效率技巧:据统计,一个中等规模的前端项目,回归测试通常需要2-3人天。而自动化E2E测试可以将这个时间压缩到30分钟以内,人力成本降低90%。
想象一下:周五晚上6点,你正准备下班,产品经理突然说"有个紧急需求要上线"。你看着那200多个测试用例,心里默默算了算——手动测完大概要到凌晨2点。这时候,一套靠谱的自动化测试方案就是你的救命稻草。
Playwright是什么?
Playwright是微软开源的端到端(E2E)测试框架,2020年发布以来迅速成为前端测试领域的新宠。它支持Chromium、Firefox、WebKit三大浏览器引擎,可以用同一套代码测试Chrome、Edge、Firefox、Safari。
┌─────────────────────────────────────────────────────────────┐ │ Playwright 架构图 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ Chrome │ │ Firefox │ │ Safari │ │ │ │ (Chromium) │ │ (Gecko) │ │ (WebKit) │ │ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ │ │ │ │ └──────────────────┼──────────────────┘ │ │ │ │ │ ┌────────┴────────┐ │ │ │ Playwright API │ │ │ │ (统一接口) │ │ │ └────────┬────────┘ │ │ │ │ │ ┌────────┴────────┐ │ │ │ 你的测试代码 │ │ │ │ (JS/TS/Python) │ │ │ └─────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘核心优势一览
| 特性 | Playwright | 说明 |
|---|---|---|
| 多浏览器 | ✅ Chromium/Firefox/WebKit | 真正的跨浏览器测试 |
| 并行执行 | ✅ 原生支持 | 多worker并行,效率翻倍 |
| 自动等待 | ✅ 内置智能等待 | 告别sleep(3000) |
| 录制回放 | ✅ Codegen工具 | 点点鼠标生成代码 |
| 移动端模拟 | ✅ 内置设备列表 | iPhone、Pixel一键模拟 |
| API测试 | ✅ 支持 | 同一框架测接口和UI |
| 可视化调试 | ✅ Trace Viewer | 慢动作回放测试过程 |
| CI/CD友好 | ✅ 一行命令跑完 | Docker、GitHub Actions无缝集成 |
⚠️避坑警告:Playwright的WebKit支持在Windows上需要额外安装依赖,Linux上需要安装系统库。建议用官方Docker镜像或在macOS上开发。
三强争霸:Playwright vs Cypress vs Selenium
选测试框架就像选对象——没有最好的,只有最适合的。我们来做个全面对比:
┌────────────────────────────────────────────────────────────────────┐ │ 三大框架对比矩阵 │ ├──────────────┬─────────────┬─────────────┬─────────────────────────┤ │ 特性 │ Playwright │ Cypress │ Selenium │ ├──────────────┼─────────────┼─────────────┼─────────────────────────┤ │ 浏览器支持 │ Chromium │ Chromium │ Chrome/Firefox/ │ │ │ Firefox │ (核心) │ Safari/Edge/IE... │ │ │ WebKit │ Firefox │ │ │ │ │ (实验性) │ │ ├──────────────┼─────────────┼─────────────┼─────────────────────────┤ │ 执行速度 │ ⚡⚡⚡ │ ⚡⚡⚡ │ ⚡ │ │ │ (并行快) │ (单线程快) │ (慢,尤其IE) │ ├──────────────┼─────────────┼─────────────┼─────────────────────────┤ │ 多标签页 │ ✅ │ ❌ │ ✅ │ ├──────────────┼─────────────┼─────────────┼─────────────────────────┤ │ iframe支持 │ ✅ │ ⚠️ │ ✅ │ ├──────────────┼─────────────┼─────────────┼─────────────────────────┤ │ 移动端测试 │ ✅ │ ❌ │ ⚠️ (需Appium) │ ├──────────────┼─────────────┼─────────────┼─────────────────────────┤ │ 跨域测试 │ ✅ │ ❌ │ ✅ │ ├──────────────┼─────────────┼─────────────┼─────────────────────────┤ │ 语言支持 │ JS/TS/Python│ JS │ Java/Python/C#/JS... │ │ │ /Java │ │ │ ├──────────────┼─────────────┼─────────────┼─────────────────────────┤ │ 学习曲线 │ 平缓 │ 平缓 │ 陡峭 │ ├──────────────┼─────────────┼─────────────┼─────────────────────────┤ │ 社区活跃度 │ 🔥🔥🔥 │ 🔥🔥🔥 │ 🔥🔥 │ ├──────────────┼─────────────┼─────────────┼─────────────────────────┤ │ 背后公司 │ 微软 │ Cypress.io │ Selenium项目(开源) │ └──────────────┴─────────────┴─────────────┴─────────────────────────┘一句话总结
- •Playwright:功能全面、速度快、适合大型项目,微软背书,未来可期
- •Cypress:开发体验好、调试方便,但多标签页和跨域是硬伤
- •Selenium:老牌框架、生态庞大,但配置复杂、速度慢,适合遗留项目维护
💡效率技巧:如果你的项目需要测试多标签页交互(比如支付回调、OAuth登录),直接选Playwright。Cypress在这块会让你怀疑人生。
环境搭建:5分钟上手
1. 安装Node.js
确保Node.js版本 >= 14:
node -v2. 初始化项目
mkdir playwright-demo && cd playwright-demo npm init -y3. 安装Playwright
npm init playwright@latest安装过程中会询问几个问题:
- • 选择TypeScript或JavaScript(推荐TypeScript)
- • 测试目录名(默认
tests) - • 是否添加GitHub Actions工作流(选Yes)
- • 是否安装浏览器(选Yes)
安装完成后,项目结构如下:
playwright-demo/ ├── tests/ │ └── example.spec.ts # 示例测试文件 ├── tests-examples/ │ └── demo-todo-app.spec.ts # TodoMVC完整示例 ├── playwright.config.ts # 配置文件 ├── package.json └── package-lock.json4. 运行示例测试
npx playwright test第一次运行会自动下载浏览器二进制文件(Chromium、Firefox、WebKit),大概需要几分钟。
⚠️避坑警告:如果下载浏览器卡住,可以设置镜像源:
# Windows PowerShell $env:PLAYWRIGHT_DOWNLOAD_HOST="https://npmmirror.com/mirrors/playwright" npx playwright install
5. 查看测试报告
npx playwright show-report核心概念:Locator与Action
Playwright的设计哲学是"以用户视角编写测试"。两个核心概念:
Locator(定位器)
Locator是用来找到页面元素的"指南针",支持多种定位策略:
// 1. 通过role定位(推荐,最符合无障碍标准) const button = page.getByRole('button', { name: '提交' }); // 2. 通过文本定位 const link = page.getByText('忘记密码?'); // 3. 通过label定位(表单元素) const input = page.getByLabel('用户名'); // 4. 通过placeholder定位 const search = page.getByPlaceholder('搜索...'); // 5. 通过test id定位(最稳定,不受文案变化影响) const card = page.getByTestId('product-card'); // 6. CSS选择器(不推荐,但有时候不得不用) const item = page.locator('.list-item:nth-child(3)'); // 7. XPath(万不得已再用) const xpath = page.locator('xpath=//div[@class="modal"]');Action(操作)
找到元素后,就可以对它进行操作:
// 点击 await page.getByRole('button').click(); // 双击 await page.getByText('打开').dblclick(); // 右键 await page.getByRole('button').click({ button: 'right' }); // 悬停 await page.getByText('菜单').hover(); // 输入文本 await page.getByLabel('用户名').fill('admin'); // 清空并输入 await page.getByLabel('搜索').clear(); await page.getByLabel('搜索').fill('Playwright'); // 键盘操作 await page.getByLabel('搜索').press('Enter'); await page.getByLabel('搜索').press('Control+a'); // 全选 await page.getByLabel('搜索').press('Control+c'); // 复制 // 选择下拉框 await page.getByLabel('省份').selectOption('北京市'); // 勾选复选框 await page.getByLabel('同意协议').check(); await page.getByLabel('同意协议').uncheck(); // 单选按钮 await page.getByLabel('男').check(); // 上传文件 await page.getByLabel('上传头像').setInputFiles('path/to/avatar.png'); // 拖拽 await page.getByText('拖拽我').dragTo(page.getByText('放这里'));💡效率技巧:优先使用
getByRole、getByLabel、getByText等语义化定位器,而不是CSS选择器。这样测试代码更健壮,页面样式调整也不容易挂。
实战:编写第一个E2E测试
假设我们要测试一个电商网站的登录和下单流程:
// tests/ecommerce.spec.ts import { test, expect } from '@playwright/test'; test.describe('电商网站E2E测试', () => { test.beforeEach(async ({ page }) => { // 每个测试用例开始前执行 await page.goto('https://demo.ecommerce.com'); }); test('用户登录成功', async ({ page }) => { // 1. 进入登录页 await page.getByRole('link', { name: '登录' }).click(); // 2. 填写表单 await page.getByLabel('邮箱').fill('test@example.com'); await page.getByLabel('密码').fill('password123'); // 3. 点击登录 await page.getByRole('button', { name: '登录' }).click(); // 4. 验证登录成功 await expect(page.getByText('欢迎回来')).toBeVisible(); await expect(page.getByText('test@example.com')).toBeVisible(); }); test('用户购买商品完整流程', async ({ page }) => { // 1. 搜索商品 await page.getByPlaceholder('搜索商品').fill('iPhone 15'); await page.getByRole('button', { name: '搜索' }).click(); // 2. 选择第一个商品 await page.getByTestId('product-card').first().click(); // 3. 加入购物车 await page.getByRole('button', { name: '加入购物车' }).click(); // 4. 验证提示 await expect(page.getByText('已成功加入购物车')).toBeVisible(); // 5. 进入购物车 await page.getByRole('link', { name: '购物车' }).click(); // 6. 结算 await page.getByRole('button', { name: '去结算' }).click(); // 7. 填写收货地址 await page.getByLabel('收件人').fill('张三'); await page.getByLabel('手机号').fill('13800138000'); await page.getByLabel('详细地址').fill('北京市朝阳区xxx街道'); // 8. 提交订单 await page.getByRole('button', { name: '提交订单' }).click(); // 9. 验证订单成功 await expect(page.getByText('订单提交成功')).toBeVisible(); await expect(page.getByTestId('order-id')).toBeVisible(); }); test('购物车数量计算正确', async ({ page }) => { // 添加3个商品到购物车 for (let i = 0; i < 3; i++) { await page.goto(`https://demo.ecommerce.com/product/${i + 1}`); await page.getByRole('button', { name: '加入购物车' }).click(); await page.waitForTimeout(500); // 等待提示消失 } // 验证购物车角标显示3 const cartBadge = page.getByTestId('cart-badge'); await expect(cartBadge).toHaveText('3'); }); });运行单个测试文件
npx playwright test tests/ecommerce.spec.ts带UI调试模式运行
npx playwright test --ui这会打开一个可视化的测试浏览器,你可以:
- • 单步执行测试
- • 查看每一步的DOM快照
- • 实时修改代码并重新运行
💡效率技巧:调试时加
--headed参数可以看到浏览器窗口,方便观察测试执行过程。CI环境默认是无头模式(headless)。
Page Object模式最佳实践
当测试用例多了,直接操作DOM会让代码难以维护。Page Object模式把页面封装成类,测试代码只和页面对象交互。
┌─────────────────────────────────────────────────────────────────┐ │ Page Object 架构 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────┐ ┌─────────────────────────────┐ │ │ │ 测试用例 │ ─────▶ │ Page Object 层 │ │ │ │ (test.spec.ts) │ │ (pages/login.page.ts) │ │ │ │ │ │ │ │ │ │ test('登录', │ │ class LoginPage { │ │ │ │ async () => { │ │ async login() { ... } │ │ │ │ await │ │ async goto() { ... } │ │ │ │ loginPage │ │ } │ │ │ │ .login() │ │ │ │ │ │ }) │ └─────────────────────────────┘ │ │ └─────────────────┘ │ │ │ ▼ │ │ ┌─────────────────────────────┐ │ │ │ Playwright API │ │ │ │ (page.locator/click/fill) │ │ │ └─────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────┐ │ │ │ 浏览器 │ │ │ └─────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘实现Page Object
// pages/base.page.ts import { Page, Locator } from '@playwright/test'; export abstract class BasePage { constructor(protected page: Page) {} async goto(url: string) { await this.page.goto(url); } async waitForLoad() { await this.page.waitForLoadState('networkidle'); } }// pages/login.page.ts import { Page, Locator, expect } from '@playwright/test'; import { BasePage } from './base.page'; export class LoginPage extends BasePage { // Locators readonly emailInput: Locator; readonly passwordInput: Locator; readonly loginButton: Locator; readonly errorMessage: Locator; readonly userMenu: Locator; constructor(page: Page) { super(page); this.emailInput = page.getByLabel('邮箱'); this.passwordInput = page.getByLabel('密码'); this.loginButton = page.getByRole('button', { name: '登录' }); this.errorMessage = page.getByTestId('error-message'); this.userMenu = page.getByTestId('user-menu'); } async goto() { await super.goto('https://demo.ecommerce.com/login'); } async login(email: string, password: string) { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.loginButton.click(); } async expectLoginSuccess() { await expect(this.userMenu).toBeVisible(); } async expectLoginFailure(errorText?: string) { await expect(this.errorMessage).toBeVisible(); if (errorText) { await expect(this.errorMessage).toHaveText(errorText); } } }// pages/product.page.ts import { Page, Locator } from '@playwright/test'; import { BasePage } from './base.page'; export class ProductPage extends BasePage { readonly addToCartButton: Locator; readonly productName: Locator; readonly productPrice: Locator; readonly quantityInput: Locator; constructor(page: Page) { super(page); this.addToCartButton = page.getByRole('button', { name: '加入购物车' }); this.productName = page.getByTestId('product-name'); this.productPrice = page.getByTestId('product-price'); this.quantityInput = page.getByLabel('数量'); } async goto(productId: string) { await super.goto(`https://demo.ecommerce.com/product/${productId}`); } async addToCart(quantity: number = 1) { if (quantity > 1) { await this.quantityInput.fill(quantity.toString()); } await this.addToCartButton.click(); } async getProductInfo() { return { name: await this.productName.textContent(), price: await this.productPrice.textContent(), }; } }// pages/cart.page.ts import { Page, Locator, expect } from '@playwright/test'; import { BasePage } from './base.page'; export class CartPage extends BasePage { readonly checkoutButton: Locator; readonly cartItems: Locator; readonly totalPrice: Locator; readonly emptyCartMessage: Locator; constructor(page: Page) { super(page); this.checkoutButton = page.getByRole('button', { name: '去结算' }); this.cartItems = page.getByTestId('cart-item'); this.totalPrice = page.getByTestId('total-price'); this.emptyCartMessage = page.getByText('购物车是空的'); } async goto() { await super.goto('https://demo.ecommerce.com/cart'); } async getItemCount() { return await this.cartItems.count(); } async removeItem(index: number) { const item = this.cartItems.nth(index); await item.getByRole('button', { name: '删除' }).click(); } async proceedToCheckout() { await this.checkoutButton.click(); } async expectEmptyCart() { await expect(this.emptyCartMessage).toBeVisible(); } }使用Page Object编写测试
// tests/pom-example.spec.ts import { test, expect } from '@playwright/test'; import { LoginPage } from '../pages/login.page'; import { ProductPage } from '../pages/product.page'; import { CartPage } from '../pages/cart.page'; test.describe('使用Page Object的测试', () => { test('完整购买流程', async ({ page }) => { // 1. 登录 const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.login('test@example.com', 'password123'); await loginPage.expectLoginSuccess(); // 2. 浏览商品并加入购物车 const productPage = new ProductPage(page); await productPage.goto('iphone-15'); const { name, price } = await productPage.getProductInfo(); await productPage.addToCart(2); // 3. 查看购物车 const cartPage = new CartPage(page); await cartPage.goto(); expect(await cartPage.getItemCount()).toBe(1); // 4. 结算 await cartPage.proceedToCheckout(); // ... 继续后续流程 }); test('未登录用户无法结算', async ({ page }) => { const cartPage = new CartPage(page); await cartPage.goto(); await cartPage.expectEmptyCart(); }); });💡效率技巧:Page Object只封装"做什么"(业务操作),不封装"怎么做"(具体定位)。如果一个按钮从"提交"改成"确认提交",只需要改Page Object,测试用例完全不用动。
并行测试:效率提升5倍的秘密
Playwright的并行能力是其最大杀手锏之一。默认配置下,测试是串行执行的,但开启并行后,效率能提升5倍以上。
工作原理
串行执行(默认) 并行执行(workers=4) Test 1 ──────────────────────▶ Worker 1: Test 1 ────▶ Test 2 ──────────────────────▶ Worker 2: Test 2 ────▶ Test 3 ──────────────────────▶ Worker 3: Test 3 ────▶ Test 4 ──────────────────────▶ Worker 4: Test 4 ────▶ Test 5 ──────────────────────▶ Worker 1: Test 5 ────▶ Test 6 ──────────────────────▶ Worker 2: Test 6 ────▶ 总时间 = 6 × T 总时间 ≈ 2 × T配置并行测试
// playwright.config.ts import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './tests', // 并行配置 fullyParallel: true, // 完全并行模式 workers: process.env.CI ? 4 : undefined, // CI环境4个worker,本地自动 // 每个worker的浏览器配置 projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, { name: 'firefox', use: { ...devices['Desktop Firefox'] }, }, { name: 'webkit', use: { ...devices['Desktop Safari'] }, }, // 移动端 { name: 'Mobile Chrome', use: { ...devices['Pixel 5'] }, }, { name: 'Mobile Safari', use: { ...devices['iPhone 12'] }, }, ], // 失败重试(CI环境) retries: process.env.CI ? 2 : 0, // 超时设置 timeout: 30 * 1000, // 30秒 expect: { timeout: 5000, // 断言超时5秒 }, });控制并行粒度
有时候你需要控制哪些测试可以并行,哪些必须串行:
// tests/parallel.spec.ts import { test, expect } from '@playwright/test'; // 默认并行执行 test('测试A', async ({ page }) => { // ... }); test('测试B', async ({ page }) => { // ... }); // 同一个文件内串行执行 test.describe.serial('订单相关测试(需要串行)', () => { test('创建订单', async ({ page }) => { // ... }); test('支付订单', async ({ page }) => { // 依赖上一个测试创建的订单 }); test('取消订单', async ({ page }) => { // 依赖上一个测试支付的订单 }); }); // 完全禁用并行(单个worker执行) test.describe.configure({ mode: 'serial' });数据隔离:每个测试独立环境
并行测试最大的挑战是数据隔离。Playwright推荐每个测试用独立的数据:
// 使用API创建测试数据 test('创建用户并测试', async ({ page, request }) => { // 1. 通过API创建测试用户 const uniqueEmail = `test-${Date.now()}@example.com`; await request.post('/api/users', { data: { email: uniqueEmail, password: 'test123' } }); // 2. 用新用户登录 await page.goto('/login'); await page.getByLabel('邮箱').fill(uniqueEmail); // ... // 3. 测试结束后清理(或用beforeEach统一清理) });⚠️避坑警告:并行测试时,多个worker可能同时操作同一数据,导致 flaky test(不稳定测试)。务必确保每个测试用独立的数据集,或使用事务回滚机制。
CI/CD集成:GitHub Actions配置
自动化测试的价值在CI/CD中才能最大化。Playwright官方提供了GitHub Actions模板:
# .github/workflows/playwright.yml name: Playwright Tests on: push: branches: [main, develop] pull_request: branches: [main, develop] jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: lts/* cache: 'npm' - name: Install dependencies run: npm ci - name: Install Playwright Browsers run: npx playwright install --with-deps - name: Run Playwright tests run: npx playwright test env: CI: true BASE_URL: ${{ secrets.TEST_BASE_URL }} TEST_USER: ${{ secrets.TEST_USER }} TEST_PASS: ${{ secrets.TEST_PASS }} - name: Upload test results uses: actions/upload-artifact@v4 if: always() with: name: playwright-report path: | playwright-report/ test-results/ retention-days: 30多浏览器矩阵测试
# .github/workflows/playwright-matrix.yml name: Cross-Browser Tests on: push: branches: [main] schedule: - cron: '0 2 * * 1' # 每周一凌晨2点运行 jobs: test: strategy: fail-fast: false matrix: project: [chromium, firefox, webkit] os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: lts/* - name: Install dependencies run: npm ci - name: Install Playwright run: npx playwright install --with-deps - name: Run tests (${{ matrix.project }} on ${{ matrix.os }}) run: npx playwright test --project=${{ matrix.project }} - name: Upload results uses: actions/upload-artifact@v4 if: failure() with: name: ${{ matrix.project }}-${{ matrix.os }}-results path: test-results/Docker环境运行
# Dockerfile.test FROM mcr.microsoft.com/playwright:v1.40.0-jammy WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . CMD ["npx", "playwright", "test"]# 构建并运行 docker build -f Dockerfile.test -t playwright-tests . docker run --rm -v $(pwd)/test-results:/app/test-results playwright-tests💡效率技巧:CI环境用
--reporter=html,line同时生成HTML报告和控制台输出,方便排查问题。HTML报告可以上传到GitHub Pages或S3做长期存档。
避坑指南:常见问题与解决方案
1. 测试不稳定(Flaky Tests)
症状:同样的测试,有时候过有时候不过
原因:
- • 网络请求未完成就开始断言
- • 动画/过渡效果导致元素位置变化
- • 测试数据被其他测试污染
解决方案:
// ❌ 错误:硬编码等待 await page.waitForTimeout(3000); await expect(page.getByText('加载完成')).toBeVisible(); // ✅ 正确:使用自动等待 await expect(page.getByText('加载完成')).toBeVisible({ timeout: 10000 }); // ✅ 更好:等待网络请求完成 await page.goto('/dashboard'); await page.waitForLoadState('networkidle'); await expect(page.getByText('数据已加载')).toBeVisible();2. 弹窗/对话框处理
// 处理alert/confirm/prompt test('处理确认对话框', async ({ page }) => { // 监听对话框事件 page.on('dialog', async dialog => { expect(dialog.type()).toBe('confirm'); expect(dialog.message()).toBe('确定要删除吗?'); await dialog.accept(); // 点击确定 // await dialog.dismiss(); // 点击取消 }); await page.getByRole('button', { name: '删除' }).click(); });3. 文件下载测试
test('下载文件', async ({ page }) => { const [download] = await Promise.all([ page.waitForEvent('download'), page.getByRole('button', { name: '导出' }).click(), ]); expect(download.suggestedFilename()).toBe('report.pdf'); await download.saveAs('/tmp/' + download.suggestedFilename()); });4. 多标签页/窗口测试
test('新窗口打开链接', async ({ page, context }) => { const [newPage] = await Promise.all([ context.waitForEvent('page'), page.getByRole('link', { name: '在新窗口打开' }).click(), ]); await newPage.waitForLoadState(); await expect(newPage).toHaveURL(/\/detail/); await expect(newPage.getByText('详情页')).toBeVisible(); });5. 认证状态复用
// playwright.config.ts export default defineConfig({ globalSetup: require.resolve('./global-setup'), }); // global-setup.ts import { chromium, FullConfig } from '@playwright/test'; async function globalSetup(config: FullConfig) { const browser = await chromium.launch(); const page = await browser.newPage(); // 登录并保存状态 await page.goto('https://demo.ecommerce.com/login'); await page.getByLabel('邮箱').fill('test@example.com'); await page.getByLabel('密码').fill('password123'); await page.getByRole('button', { name: '登录' }).click(); // 保存认证状态到文件 await page.context().storageState({ path: 'auth.json' }); await browser.close(); } export default globalSetup; // 测试文件中使用保存的状态 test.use({ storageState: 'auth.json' }); test('已登录用户测试', async ({ page }) => { // 直接进入已登录状态,无需重新登录 await page.goto('/dashboard'); await expect(page.getByText('欢迎回来')).toBeVisible(); });6. 截图和录屏
// 失败时自动截图 export default defineConfig({ use: { screenshot: 'only-on-failure', video: 'retain-on-failure', trace: 'on-first-retry', }, }); // 手动截图 test('手动截图', async ({ page }) => { await page.goto('/dashboard'); await page.screenshot({ path: 'dashboard.png', fullPage: true }); });⚠️避坑警告:不要在测试代码里用
console.log调试,Playwright有自己的日志系统。用DEBUG=pw:api npx playwright test可以看到详细的API调用日志。
文末三件套
1. 【源码获取】
关注此系列获取后续更新,后台回复'Playwright'获取完整源码和示例项目链接。
项目包含:
- • 完整的Page Object示例
- • GitHub Actions配置模板
- • Docker运行配置
- • 常见测试场景代码片段
2. 【思考题】
你的团队E2E测试覆盖率如何?
- • A. 还没有E2E测试,全靠手动
- • B. 有少量测试,覆盖率<30%
- • C. 核心流程已覆盖,覆盖率50%左右
- • D. 覆盖率>80%,每次发布都跑全量回归
欢迎在评论区分享你的答案和踩坑经历!
3. 【系列预告】
下一篇《GitHub Actions CI/CD实战》将深入讲解:
- • 如何配置多环境部署流水线
- • 自动化测试与代码审查的集成
- • 测试报告可视化与告警机制
- • 性能测试与E2E测试的结合
总结
Playwright作为新一代E2E测试框架,凭借多浏览器支持、原生并行能力、智能等待机制,正在快速取代Selenium和Cypress成为前端测试的首选。
核心要点回顾:
- 1.多浏览器:一套代码测Chrome、Firefox、Safari
- 2.并行执行:workers配置让测试效率提升5倍
- 3.Page Object:封装页面逻辑,测试代码更易维护
- 4.CI/CD集成:GitHub Actions一行配置,自动化回归
- 5.调试友好:Trace Viewer让问题定位事半功倍
如果你还在为手动测试烦恼,或者被Selenium的慢速度折磨,是时候试试Playwright了。5分钟上手,1小时见效,这才是现代前端工程师该有的效率工具。
CSDN标签:Playwright, E2E测试, 自动化测试, Selenium, Cypress, 前端测试, CI/CD
参考链接:
- • Playwright官方文档
- • Playwright GitHub
- • API参考
