playwright-skill by lackeyjb/playwright-skill
npx skills add https://github.com/lackeyjb/playwright-skill --skill playwright-skill重要提示 - 路径解析: 此技能可以安装在不同位置(插件系统、手动安装、全局安装或项目特定安装)。在执行任何命令之前,请根据加载此 SKILL.md 文件的位置确定技能目录,并在以下所有命令中使用该路径。请将 $SKILL_DIR 替换为实际发现的路径。
常见安装路径:
~/.claude/plugins/marketplaces/playwright-skill/skills/playwright-skill~/.claude/skills/playwright-skill<project>/.claude/skills/playwright-skill通用浏览器自动化技能。我将为您请求的任何自动化任务编写自定义的 Playwright 代码,并通过通用执行器执行。
关键工作流程 - 请按顺序遵循以下步骤:
自动检测开发服务器 - 对于 localhost 测试,请务必首先运行服务器检测:
cd $SKILL_DIR && node -e "require('./lib/helpers').detectDevServers().then(servers => console.log(JSON.stringify(servers)))"
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
将脚本写入 /tmp - 切勿将测试文件写入技能目录;始终使用 /tmp/playwright-test-*.js
默认使用可见浏览器 - 除非用户明确请求无头模式,否则始终使用 headless: false
参数化 URL - 始终通过环境变量或脚本顶部的常量使 URL 可配置
/tmp/playwright-test-*.js 中编写自定义的 Playwright 代码(不会弄乱您的项目)cd $SKILL_DIR && node run.js /tmp/playwright-test-*.jscd $SKILL_DIR
npm run setup
这将安装 Playwright 和 Chromium 浏览器。只需执行一次。
步骤 1:检测开发服务器(用于 localhost 测试)
cd $SKILL_DIR && node -e "require('./lib/helpers').detectDevServers().then(s => console.log(JSON.stringify(s)))"
步骤 2:将测试脚本写入 /tmp 并包含 URL 参数
// /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:从技能目录执行
cd $SKILL_DIR && node run.js /tmp/playwright-test-page.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();
})();
// /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();
})();
// /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"]', 'Test message');
await page.click('button[type="submit"]');
// 验证提交
await page.waitForSelector('.success-message');
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();
})();
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();
}
})();
// /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();
})();
对于快速的一次性任务,您可以内联执行代码而无需创建文件:
# 快速截图
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();
"
何时使用内联 vs 文件:
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');
完整列表请参见 lib/helpers.js。
通过环境变量为所有 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() 时,头部会自动应用:
const context = await helpers.createContext(browser);
const page = await context.newPage();
// 此页面的所有请求都将包含您的自定义头部
对于使用原始 Playwright API 的脚本,请使用注入的 getContextOptionsWithHeaders():
const context = await browser.newContext(
getContextOptionsWithHeaders({ viewport: { width: 1920, height: 1080 } }),
);
有关全面的 Playwright API 文档,请参阅 API_REFERENCE.md:
detectDevServers()PW_HEADER_NAME/PW_HEADER_VALUE 环境变量向后端标识自动化流量/tmp/playwright-test-*.js,切勿写入技能目录或用户项目TARGET_URL 常量中headless: falseheadless: trueslowMo: 100 使操作可见且更易于跟踪waitForURL、waitForSelector、waitForLoadState 而非固定超时console.log() 跟踪进度并显示正在发生的情况Playwright 未安装:
cd $SKILL_DIR && npm run setup
模块未找到: 确保通过 run.js 包装器从技能目录运行
浏览器未打开: 检查 headless: false 并确保显示可用
元素未找到: 添加等待:await page.waitForSelector('.element', { timeout: 10000 })
用户:"测试营销页面是否看起来不错"
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]
/tmp 以便自动清理(无杂乱)run.js 实现适当的模块解析,代码可靠执行每周安装数
916
仓库
GitHub 星标数
2.1K
首次出现
2026年1月20日
安全审计
安装于
opencode732
codex693
gemini-cli686
cursor684
github-copilot670
amp577
IMPORTANT - 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 $SKILL_DIR with the actual discovered path.
Common installation paths:
~/.claude/plugins/marketplaces/playwright-skill/skills/playwright-skill~/.claude/skills/playwright-skill<project>/.claude/skills/playwright-skillGeneral-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:
cd $SKILL_DIR && node -e "require('./lib/helpers').detectDevServers().then(servers => console.log(JSON.stringify(servers)))"
Write scripts to /tmp - NEVER write test files to skill directory; always use /tmp/playwright-test-*.js
Use visible browser by default - Always use headless: false unless user specifically requests headless mode
Parameterize URLs - Always make URLs configurable via environment variable or constant at top of script
/tmp/playwright-test-*.js (won't clutter your project)cd $SKILL_DIR && node run.js /tmp/playwright-test-*.jscd $SKILL_DIR
npm run setup
This installs Playwright and Chromium browser. Only needed once.
Step 1: Detect dev servers (for localhost testing)
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
// /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
cd $SKILL_DIR && node run.js /tmp/playwright-test-page.js
// /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();
})();
// /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();
})();
// /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();
})();
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();
})();
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();
}
})();
// /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();
})();
For quick one-off tasks, you can execute code inline without creating files:
# Take 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:
Optional utility functions in lib/helpers.js:
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 lib/helpers.js for full list.
Configure custom headers for all HTTP requests via environment variables. Useful for:
Single header (common case):
PW_HEADER_NAME=X-Automated-By PW_HEADER_VALUE=playwright-skill \
cd $SKILL_DIR && node run.js /tmp/my-script.js
Multiple headers (JSON format):
PW_EXTRA_HEADERS='{"X-Automated-By":"playwright-skill","X-Debug":"true"}' \
cd $SKILL_DIR && node run.js /tmp/my-script.js
Headers are automatically applied when using helpers.createContext():
const context = await helpers.createContext(browser);
const page = await context.newPage();
// All requests from this page include your custom headers
For scripts using raw Playwright API, use the injected getContextOptionsWithHeaders():
const context = await browser.newContext(
getContextOptionsWithHeaders({ viewport: { width: 1920, height: 1080 } }),
);
For comprehensive Playwright API documentation, see API_REFERENCE.md:
detectDevServers() before writing test code for localhost testingPW_HEADER_NAME/PW_HEADER_VALUE env vars to identify automated traffic to your backend/tmp/playwright-test-*.js, never to skill directory or user's projectTARGET_URL constant at the top of every scriptheadless: false unless user explicitly asks for headless modeheadless: true when user specifically requests "headless" or "background" executionPlaywright not installed:
cd $SKILL_DIR && npm run setup
Module not found: Ensure running from skill directory via run.js wrapper
Browser doesn't open: Check headless: false and ensure display available
Element not found: Add wait: await page.waitForSelector('.element', { timeout: 10000 })
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]
/tmp for automatic cleanup (no clutter)run.jsWeekly Installs
916
Repository
GitHub Stars
2.1K
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubFailSocketWarnSnykWarn
Installed on
opencode732
codex693
gemini-cli686
cursor684
github-copilot670
amp577
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装
slowMo: 100waitForURL, waitForSelector, waitForLoadState instead of fixed timeoutsconsole.log() to track progress and show what's happening