playwright 最佳实践
playwright 最佳实践
Playwright 是由微软开发的现代化 E2E 自动化测试框架。
🎬 一、Playwright 简介
1. Playwright 是什么?
Playwright 是由微软开发的现代化 E2E 自动化测试框架,支持多浏览器(Chromium、Firefox、WebKit)和多语言(Node.js、Python、Java、C#),特别适合做 UI 自动化测试和爬虫。
示例代码
import { test, expect } from '@playwright/test';test('使用 Google 搜索', async ({ page }) => {// 进入 google 搜索页await page.goto('https://www.google.com');// 模拟手动输入 Playwrightawait page.fill('input[name="q"]', 'Playwright');// 模拟回车确认搜索await page.keyboard.press('Enter');// 校验await expect(page.locator('#search')).toContainText('Playwright');
});
2. Playwright 能做什么?
- 端到端自动化测试
- 登录、下单、支付、弹窗、表单交互等全流程模拟
- 浏览器爬虫
- 提取页面信息,模拟滚动、点击、登录等
- 回归测试
- CI/CD 中检测新版本是否破坏 UI 功能
- 可视化回归测试
- 截图、视频、trace,查看测试中实际发生了什么
3. Playwright 特性
特性 | 说明 |
---|---|
✅ 多浏览器支持 | 支持 Chromium (Chrome/Edge)、Firefox 和 WebKit (Safari) |
✅ 多语言支持 | 支持 JavaScript/TypeScript、Python、Java、C#/.NET |
✅ 自动等待机制 | 自动等待元素出现、可点击、导航完成等,不容易出 flaky(随机失败)测试 |
✅ 原生支持 iframe、多页签 | 轻松处理复杂页面中的 iframe、弹窗、多标签页 |
✅ 内置测试运行器 | 自带功能完整的 test runner(类似 Jest + Mocha + Chai + Puppeteer) |
✅ 网络拦截与 Mock | 可模拟后端接口,进行无网络依赖测试 |
✅ 跨平台运行 | 支持 Windows、macOS、Linux、本地/CI 环境运行 |
✅ 丰富调试工具 | 提供 UI trace viewer、codegen 工具、screenshot/video trace |
🚀 二、Playwright 实践
1. Playwright 安装依赖
tests/├── login.spec.ts├── dashboard.spec.ts└── utils/└── auth.ts
playwright.config.ts
官网参考
$ npm init playwright@latest
# 或手动安装
$ npm install -D @playwright/test
$ npx playwright install
2. 测试用例基础结构
如何编写测试用例
import { test, expect } from '@playwright/test';
// test.describe 理解为测试分组
test.describe('用户模块', () => {// test 理解为定义了一个测试用例test('登录', () => {// 登录相关测试});test('注册', () => {// 注册相关测试});
});
3. 常见 API 用法
✅ 页面导航 & 交互
await page.goto('https://example.com'); // 打开页面
await page.click('text=Sign in'); // 点击按钮或链接
await page.fill('#username', 'myname'); // 填写输入框
await page.press('#input', 'Enter'); // 模拟按键
await page.selectOption('select#role', 'admin'); // 选择下拉框选项
✅ 元素等待
await page.waitForSelector('#submit-btn'); // 等待出现并可见
await page.waitForSelector('.modal', { state: 'hidden' }); // 等待消失
await expect(page.locator('h1')).toHaveText('Hello'); // 推荐写法
✅ 截图 / 视频 / Trace
await page.screenshot({ path: 'screenshot.png' }); // 截图
await page.context().tracing.start({ screenshots: true, snapshots: true });
await page.context().tracing.stop({ path: 'trace.zip' }); // 查看 UI 行为
✅ 请求拦截 / mock 接口
await page.route('**/api/**', route =>route.fulfill({ status: 200, body: JSON.stringify({ data: [] }) })
);
4. 模块封装
封装一个登录模块
// login.page.ts
export class LoginPage {constructor(private page: Page) {}async login(username: string, password: string) {await this.page.fill('#user', username);await this.page.fill('#pass', password);await this.page.click('text=Login');}
}
5. 配置项
- 自定义配置项
// playwright.config.ts
import { defineConfig } from '@playwright/test';export default defineConfig({use: {// Emulates `'prefers-colors-scheme'` media feature.colorScheme: 'dark',// Context geolocation.geolocation: { longitude: 12.492507, latitude: 41.889938 },// Emulates the user locale.locale: 'en-GB',// Grants specified permissions to the browser context.permissions: ['geolocation'],// Emulates the user timezone.timezoneId: 'Europe/Paris',// Viewport used for all pages in the context.viewport: { width: 1280, height: 720 },},
});
5. demo 演示
- ✅ 示例 1:模拟登录
import { test, expect } from '@playwright/test';test.describe('登录功能', () => {test.beforeEach(async ({ page }) => {await page.goto('https://yourapp.com/login');});test('登录成功跳转到首页', async ({ page }) => {await page.fill('#username', 'robbie');await page.fill('#password', 'correct_password');await page.click('button[type="submit"]');await expect(page).toHaveURL(/dashboard/);await expect(page.locator('text=欢迎回来')).toBeVisible();});test('登录失败提示错误', async ({ page }) => {await page.fill('#username', 'robbie');await page.fill('#password', 'wrong_password');await page.click('button[type="submit"]');await expect(page.locator('.error')).toHaveText('用户名或密码错误');});
});
- ✅ 示例 2:等待 loading 消失再点击按钮
test('加载后点击按钮', async ({ page }) => {await page.goto('https://yourapp.com/profile');// 等待 loading 动画结束await page.waitForSelector('.loading-spinner', { state: 'hidden' });// 再点击保存按钮await page.click('button.save');await expect(page.locator('.toast')).toHaveText('保存成功');
});
- ✅ 示例 3:表单验证(空值、错误格式)
test('表单校验错误提示', async ({ page }) => {await page.goto('https://yourapp.com/register');await page.click('button[type="submit"]');await expect(page.locator('#email-error')).toHaveText('邮箱不能为空');await expect(page.locator('#password-error')).toHaveText('密码不能为空');
});
- ✅ 示例 4:打开弹窗并验证内容
test('打开弹窗并验证内容', async ({ page }) => {await page.goto('https://yourapp.com');await page.click('button.view-detail');await page.waitForSelector('.modal-content');await expect(page.locator('.modal-title')).toHaveText('详情信息');
});
- ✅ 示例 5:拦截并 Mock 接口返回数据
test('拦截接口并返回自定义数据', async ({ page }) => {await page.route('**/api/user/info', route =>route.fulfill({status: 200,contentType: 'application/json',body: JSON.stringify({ name: '测试用户', id: 1 }),}));await page.goto('https://yourapp.com/user');await expect(page.locator('.username')).toHaveText('测试用户');
});
- ✅ 示例 6:截图用于 UI 回归测试
test('页面截图', async ({ page }) => {await page.goto('https://yourapp.com/dashboard');await page.screenshot({ path: 'screenshots/dashboard.png', fullPage: true });
});
- ✅ 示例 7:使用 test.step 添加步骤标记(增强 Trace 可读性)
test('使用步骤结构化流程', async ({ page }) => {await test.step('打开登录页', async () => {await page.goto('https://yourapp.com/login');});await test.step('填写登录表单', async () => {await page.fill('#username', 'robbie');await page.fill('#password', '123456');});await test.step('点击登录按钮并跳转', async () => {await page.click('button[type="submit"]');await expect(page).toHaveURL(/dashboard/);});
});
- ✅ 示例 8:使用 test.use 指定不同登录状态
Playwright 支持通过 test.use() 给某些 describe 套件设置专属上下文(例如已登录用户):
test.describe.use({ storageState: 'logged-in-state.json' });test('已登录用户访问主页', async ({ page }) => {await page.goto('https://yourapp.com');await expect(page.locator('.username')).toHaveText('robbie');
});
参考文档
- playwright 官方网站
- playwright 指引文档
- playwright API
- playwright 发布记录