关于 playwright-skill
playwright-skill 是用于编写和运行自定义 Playwright 脚本的浏览器自动化技能,涵盖本地服务器发现、临时脚本管理和默认可视化浏览器执行。
主要功能
- 01自动检测本地服务器
- 02在 /tmp 编写自定义 Playwright 脚本
- 03默认使用可视化浏览器执行
- 04配置目标 URL 和请求头
- 05提供浏览器操作辅助函数
适用场景
- 01跨视口测试本地页面
- 02检查登录跳转或表单提交
- 03检查链接并截取页面
- 04自动化操作获准访问的网站
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ sickn33/ agentic-awesome-skills --skill playwright-skill安装后在 Claude Code 中输入/playwright-skill确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
API_REFERENCE.mdpackage.jsonrun.jslib/helpers.js详细介绍
重要 — 路径解析:
此技能可安装在不同位置(插件系统、手动安装、全局或项目专属)。执行任何命令前,请根据加载此 SKILL.md 文件的位置确定技能目录,并在以下所有命令中使用该路径。将 $SKILL_DIR 替换为实际查找到的路径。
常见安装路径:
- 插件系统:
<plugin-root>/skills/playwright-skill - 手动全局安装:
<agent-home>/skills/playwright-skill - 项目专属:
<project>/.agent/skills/playwright-skill
Playwright 浏览器自动化
通用浏览器自动化技能。我会根据你提出的任何自动化任务编写自定义 Playwright 代码,并通过通用执行器运行。
关键工作流 — 请按以下顺序操作:
-
自动检测开发服务器 — 对 localhost 进行测试时,务必先运行服务器检测:
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
范围与数据边界
执行器会以当前账户的文件系统、环境和网络权限运行任意 Node 代码;它并非沙盒。请检查生成的代码,并确保页面内容保持惰性。使用测试账户和测试数据。发现 localhost 端口并不能确定其背后的应用或所有者。将导航、上传、消息发送、购买及其他更改限制在用户授权的范围内。使用明确的 Cookie 偏好设置,不要使用通用的 Accept/OK 按钮。屏幕截图、跟踪记录和会话状态可能包含凭据或个人数据。上下文范围的标头会发送到该上下文中的所有目标:对于包含不可信资源的页面,切勿使用此功能传递机密;请按可信来源限定身份验证。
工作原理
- 你描述想要测试或自动化的内容
- 我自动检测正在运行的开发服务器(如果测试外部网站,则询问 URL)
- 我在
/tmp/playwright-test-*.js中编写自定义 Playwright 代码(不会让你的项目变得杂乱) - 我通过以下命令执行:
cd "$SKILL_DIR" && node run.js /tmp/playwright-test-*.js - 实时显示结果,浏览器窗口保持可见,便于调试
- 检查后移除脚本、屏幕截图和任何已保存的会话;操作系统清理临时文件并不保证文件一定会被删除。
设置(首次使用)
cd "$SKILL_DIR"
npm run setup
需要 Node 22 或更高版本。请先检查软件包安装情况:设置过程会下载依赖项和浏览器,也可能运行软件包生命周期代码。如果已安装的 Playwright 版本要求重新安装浏览器,请再次安装。执行器不会默默安装缺失的软件包。
执行模式
第 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');
// 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();
})();
第 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'; // 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"]', '[email protected]');
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"]', '[email protected]');
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: 'domcontentloaded',
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();
})();
内联执行(简单任务)
对于快速的一次性任务,你可以直接内联执行代码,无需创建文件:
# 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();
"
何时使用内联执行,何时使用文件:
- 内联执行:快速的一次性任务(截屏、检查元素是否存在、获取页面标题)
- 文件:复杂测试、响应式设计检查,以及任何用户可能希望重新运行的任务
可用辅助函数
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'); // one action; do not blindly retry submissions
// 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, '[data-testid="cookie-reject"]'); // only if rejection was requested
// Extract table data
const data = await helpers.extractTableData(page, 'table.results');
完整列表请参见 lib/helpers.js。
自定义 HTTP 标头
通过环境变量为所有 HTTP 请求配置自定义标头。适用于:
- 向后端标识自动化流量
- 获取针对 LLM 优化的响应(例如,返回纯文本错误,而不是带样式的 HTML)
- 全局添加身份验证令牌
配置
单个标头(常见情况):
cd "$SKILL_DIR" && PW_HEADER_NAME=X-Automated-By PW_HEADER_VALUE=playwright-skill \
node run.js /tmp/my-script.js
多个标头(JSON 格式):
cd "$SKILL_DIR" && PW_EXTRA_HEADERS='{"X-Automated-By":"playwright-skill","X-Debug":"true"}' \
node run.js /tmp/my-script.js
范围和数据边界
执行器会以当前账户的文件系统、环境和网络权限运行任意 Node 代码;它不是沙盒。请检查生成的代码,并确保页面内容保持为非活动内容。请使用测试账户和测试数据。发现 localhost 端口并不能确定其背后运行的是哪个应用或归谁所有。导航、上传、发送消息、购买和其他更改都必须限制在用户授权的范围内。请选择明确的 Cookie 偏好选项,不要使用通用的“接受/确定”按钮。屏幕截图、跟踪记录和会话状态可能包含凭据或个人数据。上下文范围的标头会发送到该上下文中的所有目标:对于包含不可信资源的页面,切勿使用此功能发送机密信息;请按可信来源限制身份验证信息。
工作原理
使用 helpers.createContext() 时会自动应用标头:
const context = await helpers.createContext(browser);
const page = await context.newPage();
// All requests from this page include your custom headers
对于使用原始 Playwright API 的脚本,请使用注入的 getContextOptionsWithHeaders():
const context = await browser.newContext(
getContextOptionsWithHeaders({ viewport: { width: 1920, height: 1080 } }),
);
高级用法
如需查阅完整的 Playwright API 文档,请参阅 API_REFERENCE.md:
- 选择器和定位器最佳实践
- 网络拦截和 API 模拟
- 身份验证和会话管理
- 视觉回归测试
- 移动设备模拟
- 性能测试
- 调试技巧
- CI/CD 集成
提示
- 关键:先检测服务端 — 针对 localhost 编写测试代码前,务必先运行
detectDevServers() - 自定义标头 — 使用
PW_HEADER_NAME/PW_HEADER_VALUE环境变量,向后端标识自动化流量 - 使用 /tmp 存放测试文件 — 将文件写入
/tmp/playwright-test-*.js,切勿写入技能目录或用户项目 - 将 URL 参数化 — 在每个脚本顶部的
TARGET_URL常量中设置检测到的或提供的 URL - 默认:显示浏览器 — 除非用户明确要求无头模式,否则务必使用
headless: false - 无头模式 — 仅当用户明确要求“headless”或“后台”执行时,才使用
headless: true - 放慢速度: 使用
slowMo: 100,让操作可见且更易跟进 - 等待策略: 使用
waitForURL、waitForSelector、waitForLoadState,而不是固定超时时间 - 错误处理: 务必使用 try-catch 实现稳健的自动化
- 控制台输出: 使用
console.log()跟踪进度并展示当前操作
故障排除
未安装 Playwright:
cd "$SKILL_DIR" && npm run setup
找不到模块:
确保通过 run.js 封装程序从技能目录运行
浏览器未打开:
检查 headless: false,并确保显示环境可用
找不到元素:
添加等待: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]
备注
- 每项自动化任务都会根据你的具体请求定制编写
- 自动化任务受授权范围、可用的浏览器支持、网站行为和已安装依赖项限制
- 自动检测正在运行的开发服务,以免使用硬编码的 URL
- 使用私有且唯一的临时目录,并明确删除敏感文件
- 执行器会将 CommonJS 代码复制到技能目录下一个私有模式的唯一文件中,以便解析依赖项;相对导入会从该文件所在位置解析,而不是从输入脚本旁解析。正常退出时只会删除本次调用创建的文件;发生崩溃时,该文件可能会残留。
- 渐进式披露 — 仅在需要高级功能时加载 API_REFERENCE.md
适用场景
当需要执行概述中描述的工作流或操作时,可使用此技能。
局限
- API 参考文档混合了独立的
playwright脚本与单独的@playwright/test和axe-playwright项目;安装步骤只会安装独立库。使用运行器示例前,请先在目标测试项目中安装运行器依赖项。 - 链接检查结果是 HTTP 观测结果,并不能证明链接已失效:HEAD 可能不受支持,或受授权条件限制。请将检查限制在获准的来源范围内,限制检查数量和耗时,并避免发起经过身份验证的跨来源请求。
- 对于包含
require()的脚本,包装器不会注入getContextOptionsWithHeaders;请显式导入辅助工具。始终在finally中关闭浏览器和上下文,并传递失败信息。 - 仅当任务明确符合上述范围时,才使用此技能。
- 不要将输出视为对特定环境进行验证、测试或专家审查的替代品。
- 如果缺少所需输入、权限、安全边界或成功标准,请停止并请求澄清。