当前位置: 首页 > news >正文

前端技术05-Selenium太慢?从手动测试到自动化:Playwright多浏览器并行测试实战,Playwright让E2E测试效率翻倍

目录

  1. 1. 开篇:测试工程师的至暗时刻
  2. 2. Playwright是什么?
  3. 3. 三强争霸:Playwright vs Cypress vs Selenium
  4. 4. 环境搭建:5分钟上手
  5. 5. 核心概念:Locator与Action
  6. 6. 实战:编写第一个E2E测试
  7. 7. Page Object模式最佳实践
  8. 8. 并行测试:效率提升5倍的秘密
  9. 9. CI/CD集成:GitHub Actions配置
  10. 10. 避坑指南:常见问题与解决方案
  11. 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 -v

2. 初始化项目

mkdir playwright-demo && cd playwright-demo npm init -y

3. 安装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.json

4. 运行示例测试

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('放这里'));

💡效率技巧:优先使用getByRolegetByLabelgetByText等语义化定位器,而不是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. 1.多浏览器:一套代码测Chrome、Firefox、Safari
  2. 2.并行执行:workers配置让测试效率提升5倍
  3. 3.Page Object:封装页面逻辑,测试代码更易维护
  4. 4.CI/CD集成:GitHub Actions一行配置,自动化回归
  5. 5.调试友好:Trace Viewer让问题定位事半功倍

如果你还在为手动测试烦恼,或者被Selenium的慢速度折磨,是时候试试Playwright了。5分钟上手,1小时见效,这才是现代前端工程师该有的效率工具。


CSDN标签:Playwright, E2E测试, 自动化测试, Selenium, Cypress, 前端测试, CI/CD

参考链接

  • • Playwright官方文档
  • • Playwright GitHub
  • • API参考
http://www.jsqmd.com/news/951330/

相关文章:

  • 2026年6月无刷电机/无刷直流电机/无刷电机控制器/直流无刷驱动板/无刷驱动板厂家推荐榜单:精密调速与高效节能优选! - 企业推荐官【官方】
  • 多线程学习笔记
  • 普宁长期看电脑的人配眼镜找哪家好|防蓝光镜片真的有必要配吗 - 品牌观察
  • AI Agent实战入门:从ChatGPT到可执行数字员工的范式跃迁
  • 【HarmonyOS 6.0】Map Kit 流场图层:在基础地图上可视化动态流动数据
  • VASP 磁性结构可视化:一键生成 VESTA / MCIF
  • 【技术人职场避坑指南】当“权限不足”遇上“责任无限”,如何设计你的协作“防火墙”?
  • 1.2 原理图中的备用料如何一键导出?I 芯巧Cadence快问快答系列-操作锦囊
  • DIY锂电改造:从镍氢到锂离子电池的微型BMS实践指南
  • 做题记录5 —— 2026.6
  • GEO源头厂商主体杭州爱搜索:如何构建AI搜索优化长效竞争力 - 品牌报告
  • 优刻得GLM-5 Pro国产芯片推理实战指南
  • 千问 LeetCode 2935. 找出强数对的最大异或值 II JavaScript实现
  • LLM和Agent——专题5: LLM Ops 入门(4)
  • 单片机答辩
  • OpenCV findCirclesGrid实战:手把手教你搞定相机标定用的圆点棋盘检测
  • 0.1mm微裂纹实时闭环剔除技术揭秘
  • Arduino与光耦驱动辉光管:替代74141芯片的矩阵扫描方案
  • TVA闭环优化焊接参数
  • ECS 为什么最终会走向 Archetype
  • 2026年 广东铝型材厂家推荐:深圳工业铝型材/散热器铝型材/异型铝型材/精密6063铝型材定制开模与挤压源头实力榜单 - 品牌企业推荐师(官方)
  • es6新特性功能介绍(二)
  • 基于Arduino LilyPad的视觉暂留手套制作:从原理到可穿戴互动艺术
  • 超越本地智能:在快马平台借助ai大模型实现自然语言驱动python代码生成
  • 沐风老师3DMAX中式屋顶生成器ChineseRoof使用方法
  • HarmonyOS 6 ArkUI 像素单位使用文档
  • 2026年6月高频机厂家推荐排行榜:高周波塑料热合机、自动高频机设备、服装高频机模具及全自动高频机源头厂商精选 - 企业推荐官【官方】
  • 基于Arduino与ESP8266的宠物机器人球DIY:物联网与低功耗设计实践
  • DeepSeek-V4:长上下文与Agent协同驱动的工作流重构
  • 大疆无人机固件自由:3步掌握DankDroneDownloader终极指南