名称: playwright 描述: 使用Playwright进行浏览器自动化和端到端测试。自动检测开发服务器,编写干净的测试脚本。测试页面、填写表单、截图、检查响应式设计、验证用户体验、测试登录流程、检查链接、自动化任何浏览器任务。用于跨浏览器测试、视觉回归测试、API测试、TypeScript/JavaScript和Python项目中的组件测试。 允许工具: Bash, Read, Edit, Write, Grep, Glob, TodoWrite
Playwright - 浏览器自动化与端到端测试
精通Playwright的浏览器自动化和端到端测试知识——一个现代的跨浏览器测试框架。
重要 - 路径解析:
此技能可以安装在不同位置。在执行命令之前,根据加载此SKILL.md文件的位置确定技能目录,并在所有命令中使用该路径。用实际发现的路径替换$SKILL_DIR。
常见安装路径:
- 插件系统:
~/.claude/plugins/*/playwright/skills/playwright - 手动全局:
~/.claude/skills/playwright - 项目特定:
<project>/.claude/skills/playwright
关键工作流程 - 遵循这些步骤
当自动化浏览器任务时:
-
自动检测开发服务器 - 对于本地主机测试,始终先运行服务器检测:
cd $SKILL_DIR && node -e "require('./lib/helpers').detectDevServers().then(servers => console.log(JSON.stringify(servers)))"- 如果发现1个服务器:自动使用它,通知用户
- 如果发现多个服务器:询问用户要测试哪一个
- 如果未发现服务器:询问URL或帮助启动开发服务器
-
将脚本写入/tmp - 切勿将测试文件写入技能目录;始终使用
/tmp/playwright-test-*.js -
默认使用可见浏览器 - 除非用户特别请求无头模式,始终使用
headless: false -
参数化URL - 始终通过脚本顶部的常量使URL可配置
-
通过run.js执行 - 始终运行:
cd $SKILL_DIR && node run.js /tmp/playwright-test-*.js
快速开始
首次设置
# 导航到技能目录
cd $SKILL_DIR
# 使用bun安装(首选)
bun run setup
# 或使用npm
npm run setup:npm
这将安装Playwright和Chromium浏览器。只需执行一次。
安装(用于端到端测试项目)
# 使用Bun(首选)
bun add -d @playwright/test
bunx playwright install
# 使用npm
npm init playwright@latest
配置
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './tests',
fullyParallel: true,
reporter: 'html',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
webServer: {
command: 'bun run dev',
url: 'http://localhost:3000',
},
})
浏览器自动化模式
工作原理
- 您描述要测试/自动化的内容
- 我自动检测运行中的开发服务器(或询问URL)
- 我在
/tmp/playwright-test-*.js中编写自定义Playwright代码 - 我通过执行:
cd $SKILL_DIR && node run.js /tmp/playwright-test-*.js - 结果实时显示,浏览器窗口可见
测试页面(多视口)
// /tmp/playwright-test-responsive.js
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3001'; // 自动检测
(async () => {
const browser = await chromium.launch({ headless: false, slowMo: 100 });
const page = await browser.newPage();
// 桌面测试
await page.setViewportSize({ width: 1920, height: 1080 });
await page.goto(TARGET_URL);
console.log('桌面 - 标题:', await page.title());
await page.screenshot({ path: '/tmp/desktop.png', fullPage: true });
// 移动测试
await page.setViewportSize({ width: 375, height: 667 });
await page.screenshot({ path: '/tmp/mobile.png', fullPage: true });
await browser.close();
})();
执行:cd $SKILL_DIR && node run.js /tmp/playwright-test-responsive.js
测试登录流程
// /tmp/playwright-test-login.js
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3001'; // 自动检测
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto(`${TARGET_URL}/login`);
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
await page.waitForURL('**/dashboard');
console.log('✅ 登录成功,重定向到仪表板');
await browser.close();
})();
检查损坏链接
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto('http://localhost:3000');
const links = await page.locator('a[href^="http"]').all();
const results = { working: 0, broken: [] };
for (const link of links) {
const href = await link.getAttribute('href');
try {
const response = await page.request.head(href);
if (response.ok()) {
results.working++;
} else {
results.broken.push({ url: href, status: response.status() });
}
} catch (e) {
results.broken.push({ url: href, error: e.message });
}
}
console.log(`✅ 工作链接: ${results.working}`);
console.log(`❌ 损坏链接:`, results.broken);
await browser.close();
})();
端到端测试模式
运行测试
# 运行所有测试
bunx playwright test
# 有头模式(看到浏览器)
bunx playwright test --headed
# 特定文件
bunx playwright test tests/login.spec.ts
# 调试模式
bunx playwright test --debug
# UI模式(交互式)
bunx playwright test --ui
# 特定浏览器
bunx playwright test --project=chromium
# 生成报告
bunx playwright show-report
编写测试
import { test, expect } from '@playwright/test'
test.describe('登录流程', () => {
test('成功登录', async ({ page }) => {
await page.goto('/')
await page.getByRole('link', { name: '登录' }).click()
await page.getByLabel('电子邮件').fill('user@example.com')
await page.getByLabel('密码').fill('password123')
await page.getByRole('button', { name: '登录' }).click()
await expect(page.getByRole('heading', { name: '仪表板' })).toBeVisible()
})
test('显示无效凭据错误', async ({ page }) => {
await page.goto('/login')
await page.getByLabel('电子邮件').fill('wrong@example.com')
await page.getByLabel('密码').fill('wrongpassword')
await page.getByRole('button', { name: '登录' }).click()
await expect(page.getByText('无效凭据')).toBeVisible()
})
})
选择器(最佳实践)
// ✅ 基于角色(推荐)
await page.getByRole('button', { name: '提交' })
await page.getByRole('link', { name: '首页' })
// ✅ 文本/标签
await page.getByText('你好世界')
await page.getByLabel('电子邮件')
// ✅ 测试ID(后备)
await page.getByTestId('提交按钮')
// ❌ 避免CSS选择器(脆弱)
await page.locator('.btn-primary')
断言
// 可见性
await expect(page.getByText('成功')).toBeVisible()
await expect(page.getByRole('button')).toBeEnabled()
// 文本
await expect(page.getByRole('heading')).toHaveText('欢迎')
await expect(page.getByRole('alert')).toContainText('错误')
// 属性
await expect(page.getByRole('link')).toHaveAttribute('href', '/home')
// URL/标题
await expect(page).toHaveURL('/dashboard')
await expect(page).toHaveTitle('仪表板')
// 计数
await expect(page.getByRole('listitem')).toHaveCount(5)
操作
// 点击
await page.getByRole('button').click()
await page.getByText('文件').dblclick()
// 输入
await page.getByLabel('电子邮件').fill('user@example.com')
await page.getByLabel('搜索').press('Enter')
// 选择
await page.getByLabel('国家').selectOption('us')
// 文件上传
await page.getByLabel('上传').setInputFiles('path/to/file.pdf')
网络模拟
test('模拟API响应', async ({ page }) => {
await page.route('**/api/users', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([{ id: 1, name: '测试用户' }]),
})
})
await page.goto('/users')
await expect(page.getByText('测试用户')).toBeVisible()
})
视觉测试
test('捕获截图', async ({ page }) => {
await page.goto('/')
await page.screenshot({ path: 'screenshot.png', fullPage: true })
await expect(page).toHaveScreenshot('homepage.png')
})
认证状态
// 登录后保存状态
setup('认证', async ({ page }) => {
await page.goto('/login')
await page.getByLabel('电子邮件').fill('user@example.com')
await page.getByLabel('密码').fill('password123')
await page.getByRole('button', { name: '登录' }).click()
await page.context().storageState({ path: 'auth.json' })
})
// 在配置中重用
use: { storageState: 'auth.json' }
页面对象模型
// pages/LoginPage.ts
import { Page, Locator } from '@playwright/test'
export class LoginPage {
readonly emailInput: Locator
readonly passwordInput: Locator
readonly submitButton: Locator
constructor(page: Page) {
this.emailInput = page.getByLabel('电子邮件')
this.passwordInput = page.getByLabel('密码')
this.submitButton = page.getByRole('button', { name: '登录' })
}
async login(email: string, password: string) {
await this.emailInput.fill(email)
await this.passwordInput.fill(password)
await this.submitButton.click()
}
}
// 使用
const loginPage = new LoginPage(page)
await loginPage.login('user@example.com', 'password123')
可用帮助函数
可选实用函数在lib/helpers.js中:
const helpers = require('./lib/helpers');
// 检测运行中的开发服务器(关键 - 首先使用这个!)
const servers = await helpers.detectDevServers();
console.log('发现服务器:', servers);
// 带重试的安全点击
await helpers.safeClick(page, 'button.submit', { retries: 3 });
// 带清除的安全输入
await helpers.safeType(page, '#username', 'testuser');
// 带时间戳的截图
await helpers.takeScreenshot(page, 'test-result');
// 处理cookie横幅
await helpers.handleCookieBanner(page);
// 提取表格数据
const data = await helpers.extractTableData(page, 'table.results');
// 创建带有自定义头部的上下文
const context = await helpers.createContext(browser);
自定义HTTP头部
通过环境变量为所有HTTP请求配置自定义头部:
# 单个头部(常见情况)
PW_HEADER_NAME=X-Automated-By PW_HEADER_VALUE=playwright-skill \
cd $SKILL_DIR && node run.js /tmp/my-script.js
# 多个头部(JSON格式)
PW_EXTRA_HEADERS='{"X-Automated-By":"playwright-skill","X-Debug":"true"}' \
cd $SKILL_DIR && node run.js /tmp/my-script.js
使用helpers.createContext()时自动应用头部。
内联执行(简单任务)
对于快速一次性任务:
cd $SKILL_DIR && node run.js "
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto('http://localhost:3001');
await page.screenshot({ path: '/tmp/quick-screenshot.png', fullPage: true });
console.log('截图保存');
await browser.close();
"
何时使用:
- 内联:快速一次性任务(截图、元素检查)
- 文件:复杂测试、可重用自动化
最佳实践
- 关键:首先检测服务器 - 在编写测试代码之前始终运行
detectDevServers() - 使用/tmp存储测试文件 - 写入
/tmp/playwright-test-*.js,绝不写入技能目录 - 参数化URL - 将检测/提供的URL放入
TARGET_URL常量 - 默认:可见浏览器 - 除非明确请求,始终使用
headless: false - 优先基于角色的选择器 - 比CSS选择器更稳定
- 信任自动等待 - 不需要手动睡眠
- 每个测试获取新上下文 - 自动隔离
- 并行运行测试 - 默认行为
- 模拟外部依赖 - 使用
page.route() - 使用跟踪查看器 - 时间旅行调试
提示
- 放慢速度:使用
slowMo: 100使动作可见 - 等待策略:使用
waitForURL、waitForSelector、waitForLoadState而不是固定超时 - 错误处理:始终使用try-catch进行健壮自动化
- 控制台输出:使用
console.log()跟踪进度
故障排除
Playwright未安装:
cd $SKILL_DIR && bun run setup
模块未找到:
确保通过run.js包装器从技能目录运行
浏览器未打开:
检查headless: false并确保显示可用
元素未找到:
添加等待:await page.waitForSelector('.element', { timeout: 10000 })
另请参阅
vitest-testing- 单元和集成测试api-testing- HTTP API测试test-quality-analysis- 测试质量模式
何时加载参考
当需要时加载references/API_REFERENCE.md:
- 高级选择器模式和定位器策略
- 网络拦截和请求/响应模拟
- 认证模式和会话管理
- 视觉回归测试设置
- 移动设备模拟配置
- 性能测试和指标
- 调试技术(跟踪查看器、检查器)
- CI/CD管道集成
- 使用axe-core进行可访问性测试
- 数据驱动和参数化测试
- 页面对象模型高级模式
- 并行执行策略