browser-automation-framework
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseIMPORTANT - Path Resolution:
This skill can be installed in different locations (plugin system, manual installation, global, or project-specific). Before executing any commands, determine the skill directory based on where you loaded this SKILL.md file, and use that path in all commands below. Replace with the actual discovered path.
$SKILL_DIRCommon installation paths:
- Plugin system:
~/.claude/plugins/marketplaces/playwright-skill/skills/playwright-skill - Manual global:
~/.claude/skills/playwright-skill - Project-specific:
<project>/.claude/skills/playwright-skill
重要提示 - 路径解析:
本Skill可安装在不同位置(插件系统、手动安装、全局或项目专属)。执行任何命令前,请根据加载此SKILL.md文件的位置确定Skill目录,并在以下所有命令中使用该路径。将替换为实际找到的路径。
$SKILL_DIR常见安装路径:
- 插件系统:
~/.claude/plugins/marketplaces/playwright-skill/skills/playwright-skill - 全局手动安装:
~/.claude/skills/playwright-skill - 项目专属:
<project>/.claude/skills/playwright-skill
Playwright Browser Automation
Playwright浏览器自动化
General-purpose browser automation skill. I'll write custom Playwright code for any automation task you request and execute it via the universal executor.
CRITICAL WORKFLOW - Follow these steps in order:
-
Auto-detect dev servers - For localhost testing, ALWAYS run server detection FIRST:bash
cd $SKILL_DIR && node -e "require('./lib/helpers').detectDevServers().then(servers => console.log(JSON.stringify(servers)))"- If 1 server found: Use it automatically, inform user
- If multiple servers found: Ask user which one to test
- If no servers found: Ask for URL or offer to help start dev server
-
Write scripts to /tmp - NEVER write test files to skill directory; always use
/tmp/playwright-test-*.js -
Use visible browser by default - Always useunless user specifically requests headless mode
headless: false -
Parameterize URLs - Always make URLs configurable via environment variable or constant at top of script
通用型浏览器自动化Skill。我会根据你的自动化需求编写定制化Playwright代码,并通过通用执行器运行。
关键工作流程 - 请按以下顺序执行:
-
自动检测开发服务器 - 针对本地测试,务必先运行服务器检测:bash
cd $SKILL_DIR && node -e "require('./lib/helpers').detectDevServers().then(servers => console.log(JSON.stringify(servers)))"- 如果找到1个服务器:自动使用该服务器,并告知用户
- 如果找到多个服务器:询问用户要测试哪一个
- 如果未找到服务器:询问用户提供URL,或协助启动开发服务器
-
将脚本写入/tmp目录 - 切勿将测试文件写入Skill目录;请始终使用路径
/tmp/playwright-test-*.js -
默认使用可视浏览器 - 除非用户明确要求无头模式,否则始终使用
headless: false -
参数化URL - 始终通过环境变量或脚本顶部的常量配置URL
How It Works
工作原理
- You describe what you want to test/automate
- I auto-detect running dev servers (or ask for URL if testing external site)
- I write custom Playwright code in (won't clutter your project)
/tmp/playwright-test-*.js - I execute it via:
cd $SKILL_DIR && node run.js /tmp/playwright-test-*.js - Results displayed in real-time, browser window visible for debugging
- Test files auto-cleaned from /tmp by your OS
- 你描述想要测试/自动化的任务
- 我自动检测运行中的开发服务器(如果测试外部网站则询问URL)
- 我将定制化Playwright代码写入(不会占用你的项目空间)
/tmp/playwright-test-*.js - 通过以下命令执行:
cd $SKILL_DIR && node run.js /tmp/playwright-test-*.js - 实时显示结果,浏览器窗口可视以便调试
- 测试文件会由系统自动从/tmp目录清理
Setup (First Time)
首次设置
bash
cd $SKILL_DIR
npm run setupThis installs Playwright and Chromium browser. Only needed once.
bash
cd $SKILL_DIR
npm run setup此命令将安装Playwright和Chromium浏览器,仅需执行一次。
Execution Pattern
执行流程
Step 1: Detect dev servers (for localhost testing)
bash
cd $SKILL_DIR && node -e "require('./lib/helpers').detectDevServers().then(s => console.log(JSON.stringify(s)))"Step 2: Write test script to /tmp with URL parameter
javascript
// /tmp/playwright-test-page.js
const { chromium } = require('playwright');
// Parameterized URL (detected or user-provided)
const TARGET_URL = 'http://localhost:3001'; // <-- Auto-detected or from user
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto(TARGET_URL);
console.log('Page loaded:', await page.title());
await page.screenshot({ path: '/tmp/screenshot.png', fullPage: true });
console.log('📸 Screenshot saved to /tmp/screenshot.png');
await browser.close();
})();Step 3: Execute from skill directory
bash
cd $SKILL_DIR && node run.js /tmp/playwright-test-page.js步骤1:检测开发服务器(针对本地测试)
bash
cd $SKILL_DIR && node -e "require('./lib/helpers').detectDevServers().then(s => console.log(JSON.stringify(s)))"步骤2:将测试脚本写入/tmp目录并参数化URL
javascript
// /tmp/playwright-test-page.js
const { chromium } = require('playwright');
// 参数化URL(自动检测或用户提供)
const TARGET_URL = 'http://localhost:3001'; // <-- 自动检测或来自用户提供
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto(TARGET_URL);
console.log('页面已加载:', await page.title());
await page.screenshot({ path: '/tmp/screenshot.png', fullPage: true });
console.log('📸 截图已保存至/tmp/screenshot.png');
await browser.close();
})();步骤3:从Skill目录执行脚本
bash
cd $SKILL_DIR && node run.js /tmp/playwright-test-page.jsCommon Patterns
常见场景示例
Test a Page (Multiple Viewports)
多视口页面测试
javascript
// /tmp/playwright-test-responsive.js
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3001'; // Auto-detected
(async () => {
const browser = await chromium.launch({ headless: false, slowMo: 100 });
const page = await browser.newPage();
// Desktop test
await page.setViewportSize({ width: 1920, height: 1080 });
await page.goto(TARGET_URL);
console.log('Desktop - Title:', await page.title());
await page.screenshot({ path: '/tmp/desktop.png', fullPage: true });
// Mobile test
await page.setViewportSize({ width: 375, height: 667 });
await page.screenshot({ path: '/tmp/mobile.png', fullPage: true });
await browser.close();
})();javascript
// /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();
})();Test Login Flow
登录流程测试
javascript
// /tmp/playwright-test-login.js
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3001'; // Auto-detected
(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"]');
// Wait for redirect
await page.waitForURL('**/dashboard');
console.log('✅ Login successful, redirected to dashboard');
await browser.close();
})();javascript
// /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();
})();Fill and Submit Form
表单填写与提交测试
javascript
// /tmp/playwright-test-form.js
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3001'; // Auto-detected
(async () => {
const browser = await chromium.launch({ headless: false, slowMo: 50 });
const page = await browser.newPage();
await page.goto(`${TARGET_URL}/contact`);
await page.fill('input[name="name"]', 'John Doe');
await page.fill('input[name="email"]', 'john@example.com');
await page.fill('textarea[name="message"]', 'Test message');
await page.click('button[type="submit"]');
// Verify submission
await page.waitForSelector('.success-message');
console.log('✅ Form submitted successfully');
await browser.close();
})();javascript
// /tmp/playwright-test-form.js
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3001'; // 自动检测
(async () => {
const browser = await chromium.launch({ headless: false, slowMo: 50 });
const page = await browser.newPage();
await page.goto(`${TARGET_URL}/contact`);
await page.fill('input[name="name"]', 'John Doe');
await page.fill('input[name="email"]', 'john@example.com');
await page.fill('textarea[name="message"]', '测试消息');
await page.click('button[type="submit"]');
// 验证提交结果
await page.waitForSelector('.success-message');
console.log('✅ 表单提交成功');
await browser.close();
})();Check for Broken Links
失效链接检测
javascript
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(`✅ Working links: ${results.working}`);
console.log(`❌ Broken links:`, results.broken);
await browser.close();
})();javascript
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();
})();Take Screenshot with Error Handling
带错误处理的截图功能
javascript
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
try {
await page.goto('http://localhost:3000', {
waitUntil: 'networkidle',
timeout: 10000
});
await page.screenshot({
path: '/tmp/screenshot.png',
fullPage: true
});
console.log('📸 Screenshot saved to /tmp/screenshot.png');
} catch (error) {
console.error('❌ Error:', error.message);
} finally {
await browser.close();
}
})();javascript
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
try {
await page.goto('http://localhost:3000', {
waitUntil: 'networkidle',
timeout: 10000
});
await page.screenshot({
path: '/tmp/screenshot.png',
fullPage: true
});
console.log('📸 截图已保存至/tmp/screenshot.png');
} catch (error) {
console.error('❌ 错误信息:', error.message);
} finally {
await browser.close();
}
})();Test Responsive Design
响应式设计测试
javascript
// /tmp/playwright-test-responsive-full.js
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3001'; // Auto-detected
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
const viewports = [
{ name: 'Desktop', width: 1920, height: 1080 },
{ name: 'Tablet', width: 768, height: 1024 },
{ name: 'Mobile', width: 375, height: 667 }
];
for (const viewport of viewports) {
console.log(`Testing ${viewport.name} (${viewport.width}x${viewport.height})`);
await page.setViewportSize({
width: viewport.width,
height: viewport.height
});
await page.goto(TARGET_URL);
await page.waitForTimeout(1000);
await page.screenshot({
path: `/tmp/${viewport.name.toLowerCase()}.png`,
fullPage: true
});
}
console.log('✅ All viewports tested');
await browser.close();
})();javascript
// /tmp/playwright-test-responsive-full.js
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3001'; // 自动检测
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
const viewports = [
{ name: 'Desktop', width: 1920, height: 1080 },
{ name: 'Tablet', width: 768, height: 1024 },
{ name: 'Mobile', width: 375, height: 667 }
];
for (const viewport of viewports) {
console.log(`正在测试${viewport.name}(${viewport.width}x${viewport.height})`);
await page.setViewportSize({
width: viewport.width,
height: viewport.height
});
await page.goto(TARGET_URL);
await page.waitForTimeout(1000);
await page.screenshot({
path: `/tmp/${viewport.name.toLowerCase()}.png`,
fullPage: true
});
}
console.log('✅ 所有视口测试完成');
await browser.close();
})();Inline Execution (Simple Tasks)
内联执行(简单任务)
For quick one-off tasks, you can execute code inline without creating files:
bash
undefined对于快速一次性任务,你可以直接内联执行代码而无需创建文件:
bash
undefinedTake a quick screenshot
快速截图
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('Screenshot saved');
await browser.close();
"
**When to use inline vs files:**
- **Inline**: Quick one-off tasks (screenshot, check if element exists, get page title)
- **Files**: Complex tests, responsive design checks, anything user might want to re-runcd $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();
"
**内联执行与文件执行的适用场景:**
- **内联执行**:快速一次性任务(截图、检查元素是否存在、获取页面标题)
- **文件执行**:复杂测试、响应式设计检查、用户可能需要重新运行的任务Available Helpers
可用辅助工具
Optional utility functions in :
lib/helpers.jsjavascript
const helpers = require('./lib/helpers');
// Detect running dev servers (CRITICAL - use this first!)
const servers = await helpers.detectDevServers();
console.log('Found servers:', servers);
// Safe click with retry
await helpers.safeClick(page, 'button.submit', { retries: 3 });
// Safe type with clear
await helpers.safeType(page, '#username', 'testuser');
// Take timestamped screenshot
await helpers.takeScreenshot(page, 'test-result');
// Handle cookie banners
await helpers.handleCookieBanner(page);
// Extract table data
const data = await helpers.extractTableData(page, 'table.results');See for full list.
lib/helpers.jslib/helpers.jsjavascript
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');完整列表请查看。
lib/helpers.jsCustom HTTP Headers
自定义HTTP请求头
Configure custom headers for all HTTP requests via environment variables. Useful for:
- Identifying automated traffic to your backend
- Getting LLM-optimized responses (e.g., plain text errors instead of styled HTML)
- Adding authentication tokens globally
可通过环境变量为所有HTTP请求配置自定义请求头。适用于以下场景:
- 向后端标识自动化流量
- 获取针对LLM优化的响应(例如,纯文本错误信息而非样式化HTML)
- 全局添加认证令牌
Configuration
配置方式
Single header (common case):
bash
PW_HEADER_NAME=X-Automated-By PW_HEADER_VALUE=playwright-skill \
cd $SKILL_DIR && node run.js /tmp/my-script.jsMultiple headers (JSON format):
bash
PW_EXTRA_HEADERS='{"X-Automated-By":"playwright-skill","X-Debug":"true"}' \
cd $SKILL_DIR && node run.js /tmp/my-script.js单个请求头(常见场景):
bash
PW_HEADER_NAME=X-Automated-By PW_HEADER_VALUE=playwright-skill \
cd $SKILL_DIR && node run.js /tmp/my-script.js多个请求头(JSON格式):
bash
PW_EXTRA_HEADERS='{"X-Automated-By":"playwright-skill","X-Debug":"true"}' \
cd $SKILL_DIR && node run.js /tmp/my-script.jsHow It Works
工作机制
Headers are automatically applied when using :
helpers.createContext()javascript
const context = await helpers.createContext(browser);
const page = await context.newPage();
// All requests from this page include your custom headersFor scripts using raw Playwright API, use the injected :
getContextOptionsWithHeaders()javascript
const context = await browser.newContext(
getContextOptionsWithHeaders({ viewport: { width: 1920, height: 1080 } })
);使用时会自动应用请求头:
helpers.createContext()javascript
const context = await helpers.createContext(browser);
const page = await context.newPage();
// 此页面的所有请求都会包含你配置的自定义请求头对于使用原生Playwright API的脚本,请使用注入的:
getContextOptionsWithHeaders()javascript
const context = await browser.newContext(
getContextOptionsWithHeaders({ viewport: { width: 1920, height: 1080 } })
);Advanced Usage
高级用法
For comprehensive Playwright API documentation, see API_REFERENCE.md:
- Selectors & Locators best practices
- Network interception & API mocking
- Authentication & session management
- Visual regression testing
- Mobile device emulation
- Performance testing
- Debugging techniques
- CI/CD integration
如需完整的Playwright API文档,请查看API_REFERENCE.md:
- 选择器与定位器最佳实践
- 网络拦截与API模拟
- 认证与会话管理
- 视觉回归测试
- 移动设备模拟
- 性能测试
- 调试技巧
- CI/CD集成
Tips
提示
- CRITICAL: Detect servers FIRST - Always run before writing test code for localhost testing
detectDevServers() - Custom headers - Use /
PW_HEADER_NAMEenv vars to identify automated traffic to your backendPW_HEADER_VALUE - Use /tmp for test files - Write to , never to skill directory or user's project
/tmp/playwright-test-*.js - Parameterize URLs - Put detected/provided URL in a constant at the top of every script
TARGET_URL - DEFAULT: Visible browser - Always use unless user explicitly asks for headless mode
headless: false - Headless mode - Only use when user specifically requests "headless" or "background" execution
headless: true - Slow down: Use to make actions visible and easier to follow
slowMo: 100 - Wait strategies: Use ,
waitForURL,waitForSelectorinstead of fixed timeoutswaitForLoadState - Error handling: Always use try-catch for robust automation
- Console output: Use to track progress and show what's happening
console.log()
- 关键提示:先检测服务器 - 针对本地测试,务必先运行再编写测试代码
detectDevServers() - 自定义请求头 - 使用/
PW_HEADER_NAME环境变量向后端标识自动化流量PW_HEADER_VALUE - 使用/tmp目录存储测试文件 - 将文件写入,切勿写入Skill目录或用户项目
/tmp/playwright-test-*.js - 参数化URL - 将检测到的/用户提供的URL放在脚本顶部的常量中
TARGET_URL - 默认使用可视浏览器 - 除非用户明确要求,否则始终使用
headless: false - 无头模式 - 仅当用户明确要求“headless”或“后台执行”时使用
headless: true - 放慢执行速度:使用让操作更易观察
slowMo: 100 - 等待策略:使用、
waitForURL、waitForSelector替代固定超时waitForLoadState - 错误处理:始终使用try-catch实现健壮的自动化
- 控制台输出:使用跟踪进度并展示执行状态
console.log()
Troubleshooting
故障排除
Playwright not installed:
bash
cd $SKILL_DIR && npm run setupModule not found:
Ensure running from skill directory via wrapper
run.jsBrowser doesn't open:
Check and ensure display available
headless: falseElement not found:
Add wait:
await page.waitForSelector('.element', { timeout: 10000 })Playwright未安装:
bash
cd $SKILL_DIR && npm run setup模块未找到:
确保通过包装器从Skill目录运行
run.js浏览器无法打开:
检查是否设置了,并确保有可用的显示设备
headless: false元素未找到:
添加等待操作:
await page.waitForSelector('.element', { timeout: 10000 })Example Usage
使用示例
User: "Test if the marketing page looks good"
Claude: I'll test the marketing page across multiple viewports. Let me first detect running servers...
[Runs: detectDevServers()]
[Output: Found server on port 3001]
I found your dev server running on http://localhost:3001
[Writes custom automation script to /tmp/playwright-test-marketing.js with URL parameterized]
[Runs: cd $SKILL_DIR && node run.js /tmp/playwright-test-marketing.js]
[Shows results with screenshots from /tmp/]User: "Check if login redirects correctly"
Claude: I'll test the login flow. First, let me check for running servers...
[Runs: detectDevServers()]
[Output: Found servers on ports 3000 and 3001]
I found 2 dev servers. Which one should I test?
- http://localhost:3000
- http://localhost:3001
User: "Use 3001"
[Writes login automation to /tmp/playwright-test-login.js]
[Runs: cd $SKILL_DIR && node run.js /tmp/playwright-test-login.js]
[Reports: ✅ Login successful, redirected to /dashboard]用户: "测试营销页面的显示效果"
Claude: 我将在多个视口下测试营销页面。首先让我检测运行中的服务器...
[执行: detectDevServers()]
[输出: 在端口3001找到服务器]
我找到你的开发服务器运行在http://localhost:3001
[将定制化自动化脚本写入/tmp/playwright-test-marketing.js并参数化URL]
[执行: cd $SKILL_DIR && node run.js /tmp/playwright-test-marketing.js]
[展示/tmp目录中的截图及测试结果]用户: "检查登录是否正确重定向"
Claude: 我将测试登录流程。首先,让我检查运行中的服务器...
[执行: detectDevServers()]
[输出: 在端口3000和3001找到服务器]
我找到2个开发服务器。请问要测试哪一个?
- http://localhost:3000
- http://localhost:3001
用户: "使用3001"
[将登录自动化脚本写入/tmp/playwright-test-login.js]
[执行: cd $SKILL_DIR && node run.js /tmp/playwright-test-login.js]
[报告结果: ✅ 登录成功,已重定向至/dashboard]Notes
注意事项
- Each automation is custom-written for your specific request
- Not limited to pre-built scripts - any browser task possible
- Auto-detects running dev servers to eliminate hardcoded URLs
- Test scripts written to for automatic cleanup (no clutter)
/tmp - Code executes reliably with proper module resolution via
run.js - Progressive disclosure - API_REFERENCE.md loaded only when advanced features needed
- 每个自动化脚本都是根据你的具体需求定制编写的
- 不限于预构建脚本 - 任何浏览器任务都可实现
- 自动检测运行中的开发服务器,无需硬编码URL
- 测试脚本写入/tmp目录,系统会自动清理(不会产生冗余文件)
- 通过确保代码模块解析正确,运行可靠
run.js - 渐进式披露 - 仅在需要高级功能时加载API_REFERENCE.md