Playwright 速查表
1. 安装与运行
bash
# 初始化
npm init playwright@latest
# 安装依赖后补装浏览器
npx playwright install
# 运行全部测试
npx playwright test
# 跑指定文件
npx playwright test tests/demo.spec.ts
# 跑指定浏览器
npx playwright test --project=chromium
# 有头模式
npx playwright test --headed
# 调试模式
npx playwright test --debug
# 可视化 UI
npx playwright test --ui
# 录制脚本
npx playwright codegen https://example.com
2. 基础模板
ts
import { test, expect } from '@playwright/test';
test('example test', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveTitle(/Example/);
});
3. 常用对象
test:定义测试expect:断言page:当前页面browser:浏览器实例context:浏览器上下文,类似独立用户会话
4. 页面操作
打开页面
ts
await page.goto('https://example.com');
刷新
ts
await page.reload();
后退/前进
ts
await page.goBack();
await page.goForward();
获取标题/URL
ts
await page.title();
page.url();
5. 常用定位器
推荐写法
ts
page.getByRole('button', { name: '登录' })
page.getByLabel('用户名')
page.getByPlaceholder('请输入内容')
page.getByText('提交')
page.getByTestId('submit-btn')
通用定位
ts
page.locator('.class-name')
page.locator('#id')
page.locator('//button')
链式定位
ts
page.locator('.card').getByText('详情')
page.getByRole('dialog').getByRole('button', { name: '确认' })
第几个元素
ts
page.locator('.item').first()
page.locator('.item').last()
page.locator('.item').nth(2)
6. 常用交互
点击
ts
await page.getByRole('button', { name: '提交' }).click();
输入
ts
await page.getByLabel('用户名').fill('admin');
清空后输入
ts
await page.locator('#input').fill('');
await page.locator('#input').fill('new value');
按键
ts
await page.locator('#search').press('Enter');
await page.keyboard.press('Escape');
勾选/取消勾选
ts
await page.locator('#agree').check();
await page.locator('#agree').uncheck();
选择下拉框
ts
await page.locator('select').selectOption('option1');
await page.locator('select').selectOption({ label: '北京' });
悬停
ts
await page.locator('.menu').hover();
双击/右键
ts
await page.locator('.item').dblclick();
await page.locator('.item').click({ button: 'right' });
7. 常用断言
标题
ts
await expect(page).toHaveTitle(/Playwright/);
URL
ts
await expect(page).toHaveURL(/dashboard/);
文本可见
ts
await expect(page.getByText('登录成功')).toBeVisible();
元素隐藏
ts
await expect(page.locator('.loading')).toBeHidden();
输入框值
ts
await expect(page.locator('#username')).toHaveValue('admin');
文本内容
ts
await expect(page.locator('.title')).toHaveText('欢迎');
await expect(page.locator('.title')).toContainText('欢迎');
数量
ts
await expect(page.locator('.item')).toHaveCount(3);
勾选状态
ts
await expect(page.locator('#agree')).toBeChecked();
启用/禁用
ts
await expect(page.locator('button')).toBeEnabled();
await expect(page.locator('button')).toBeDisabled();
8. 获取数据
文本
ts
const text = await page.locator('.title').textContent();
const innerText = await page.locator('.title').innerText();
输入框值
ts
const value = await page.locator('input').inputValue();
属性
ts
const href = await page.locator('a').getAttribute('href');
全部文本
ts
const texts = await page.locator('.item').allTextContents();
9. 等待
等元素出现
ts
await page.locator('.result').waitFor();
等 URL
ts
await page.waitForURL('**/dashboard');
等响应
ts
await page.waitForResponse(resp => resp.url().includes('/api/user') && resp.status() === 200);
等请求
ts
await page.waitForRequest('**/api/login');
不推荐
ts
await page.waitForTimeout(3000);
10. Hooks
ts
test.beforeEach(async ({ page }) => {
await page.goto('https://example.com/login');
});
test.afterEach(async () => {
// 清理
});
test.beforeAll(async () => {
// 全局初始化
});
test.afterAll(async () => {
// 全局收尾
});
11. 分组与单测控制
分组
ts
test.describe('登录模块', () => {
test('正确登录', async ({ page }) => {});
test('错误密码提示', async ({ page }) => {});
});
只跑一个
ts
test.only('只跑这个测试', async ({ page }) => {});
跳过
ts
test.skip('先跳过', async ({ page }) => {});
标记慢测试
ts
test.slow();
12. 文件上传下载
上传
ts
await page.locator('input[type="file"]').setInputFiles('./a.png');
清空已上传文件
ts
await page.locator('input[type="file"]').setInputFiles([]);
下载
ts
const downloadPromise = page.waitForEvent('download');
await page.getByText('下载').click();
const download = await downloadPromise;
await download.saveAs('./downloads/report.xlsx');
13. 弹窗处理
alert / confirm / prompt
ts
page.on('dialog', async dialog => {
console.log(dialog.message());
await dialog.accept();
});
拒绝
ts
page.on('dialog', async dialog => {
await dialog.dismiss();
});
14. 新窗口 / 新标签页
ts
const [newPage] = await Promise.all([
page.waitForEvent('popup'),
page.getByText('打开新窗口').click(),
]);
await newPage.waitForLoadState();
await expect(newPage).toHaveURL(/target/);
15. iframe
ts
const frame = page.frameLocator('#my-frame');
await frame.getByRole('button', { name: '提交' }).click();
16. 截图 / 视频 / Trace
截图
ts
await page.screenshot({ path: 'page.png', fullPage: true });
配置里保留失败证据
ts
use: {
screenshot: 'only-on-failure',
video: 'retain-on-failure',
trace: 'on-first-retry',
}
查看 trace
bash
npx playwright show-trace trace.zip
17. 调试
暂停
ts
await page.pause();
运行调试
bash
npx playwright test --debug
18. 网络拦截 / Mock
Mock 接口
ts
await page.route('**/api/user', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ name: '老张' }),
});
});
放行请求
ts
await page.route('**/*', route => route.continue());
终止请求
ts
await page.route('**/ads/**', route => route.abort());
19. 登录态复用
保存状态
ts
await page.context().storageState({ path: 'playwright/.auth/user.json' });
使用状态
ts
use: {
storageState: 'playwright/.auth/user.json',
}
20. 常见配置
playwright.config.ts
ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30_000,
retries: 1,
use: {
baseURL: 'https://example.com',
headless: true,
screenshot: 'only-on-failure',
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});
使用 baseURL
ts
await page.goto('/login');
21. API 测试
ts
import { test, expect } from '@playwright/test';
test('api test', async ({ request }) => {
const resp = await request.get('https://example.com/api/user');
expect(resp.ok()).toBeTruthy();
const data = await resp.json();
expect(data.name).toBe('Tom');
});
22. POM 模式最小模板
ts
import { Page } from '@playwright/test';
export class LoginPage {
constructor(private page: Page) {}
async goto() {
await this.page.goto('/login');
}
async login(username: string, password: string) {
await this.page.getByLabel('用户名').fill(username);
await this.page.getByLabel('密码').fill(password);
await this.page.getByRole('button', { name: '登录' }).click();
}
}
23. 最常见坑
不要滥用 waitForTimeout
ts
await page.waitForTimeout(5000); // 尽量别这么写
优先语义定位
ts
page.getByRole(...)
page.getByLabel(...)
page.getByTestId(...)
一个测试只验证一个核心目标
别把整条业务链全塞一个 case 里。
测试尽量独立
不要强依赖上一个测试生成的数据。
24. 最实用模板
ts
import { test, expect } from '@playwright/test';
test('登录成功', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('用户名').fill('admin');
await page.getByLabel('密码').fill('123456');
await page.getByRole('button', { name: '登录' }).click();
await expect(page).toHaveURL(/dashboard/);
await expect(page.getByText('欢迎你')).toBeVisible();
});
25. 定位器优先级建议
推荐顺序:
getByRolegetByLabelgetByPlaceholdergetByTestIdlocator(css)xpath(最后再用)