playwright
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePlaywright Web Automation
Playwright 网页自动化
Browser automation via Chromium + playwright-core over CDP. No MCP server needed — scripts run as files.
.mjs通过Chromium + playwright-core基于CDP实现浏览器自动化。无需MCP服务器——脚本以文件形式运行。
.mjsPrerequisites
前置条件
bash
undefinedbash
undefinedInstall playwright-core (no browser download — uses existing Chromium)
安装playwright-core(无需下载浏览器——使用已有的Chromium)
npm install playwright-core
npm install playwright-core
Verify Chromium exists
验证Chromium是否存在
which chromium || ls /Applications/Chromium.app/Contents/MacOS/Chromium
undefinedwhich chromium || ls /Applications/Chromium.app/Contents/MacOS/Chromium
undefinedLaunch Chromium with CDP
通过CDP启动Chromium
bash
undefinedbash
undefinedKill any existing debug instance
终止所有已存在的调试实例
pkill -f "remote-debugging-port=9222" 2>/dev/null
pkill -f "remote-debugging-port=9222" 2>/dev/null
Launch headless (cron/CI) or headed (interactive)
启动无头模式(用于cron/CI)或有头模式(交互式)
chromium --remote-debugging-port=9222
--user-data-dir="/tmp/playwright-profile"
--disable-blink-features=AutomationControlled
--no-first-run --no-default-browser-check
--headless=new
"about:blank" > /tmp/chromium.log 2>&1 &
--user-data-dir="/tmp/playwright-profile"
--disable-blink-features=AutomationControlled
--no-first-run --no-default-browser-check
--headless=new
"about:blank" > /tmp/chromium.log 2>&1 &
chromium --remote-debugging-port=9222
--user-data-dir="/tmp/playwright-profile"
--disable-blink-features=AutomationControlled
--no-first-run --no-default-browser-check
--headless=new
"about:blank" > /tmp/chromium.log 2>&1 &
--user-data-dir="/tmp/playwright-profile"
--disable-blink-features=AutomationControlled
--no-first-run --no-default-browser-check
--headless=new
"about:blank" > /tmp/chromium.log 2>&1 &
macOS: use full path /Applications/Chromium.app/Contents/MacOS/Chromium
macOS:使用完整路径 /Applications/Chromium.app/Contents/MacOS/Chromium
Linux: chromium-browser or chromium
Linux:使用chromium-browser或chromium
Wait for CDP
等待CDP就绪
sleep 3 && curl -sf http://localhost:9222/json/version > /dev/null && echo "CDP ready"
undefinedsleep 3 && curl -sf http://localhost:9222/json/version > /dev/null && echo "CDP ready"
undefinedConnect via Playwright (ESM)
通过Playwright(ESM)连接
Write scripts as files:
.mjsjavascript
// run: node script.mjs
import { chromium } from 'playwright-core';
const browser = await chromium.connectOverCDP('http://localhost:9222');
const ctx = browser.contexts()[0];
const page = ctx.pages()[0] || await ctx.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded', timeout: 15000 });
await page.waitForTimeout(2000);
await page.screenshot({ path: '/tmp/screenshot.png' });
await browser.close(); // disconnects, doesn't kill ChromiumIMPORTANT: Always use extension — playwright-core is ESM-only.
.mjs将脚本编写为文件:
.mjsjavascript
// 运行:node script.mjs
import { chromium } from 'playwright-core';
const browser = await chromium.connectOverCDP('http://localhost:9222');
const ctx = browser.contexts()[0];
const page = ctx.pages()[0] || await ctx.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded', timeout: 15000 });
await page.waitForTimeout(2000);
await page.screenshot({ path: '/tmp/screenshot.png' });
await browser.close(); // 断开连接,不会终止Chromium重要提示: 务必使用扩展名——playwright-core仅支持ESM。
.mjsKey Patterns
核心模式
Screenshot & Inspect
截图与检查
javascript
await page.screenshot({ path: '/tmp/screen.png' });
// Read /tmp/screen.png in Claude to see it visuallyjavascript
await page.screenshot({ path: '/tmp/screen.png' });
// 在Claude中读取/tmp/screen.png以查看可视化内容Fill Forms & Click
填写表单与点击
javascript
// Inspect inputs first
const inputs = await page.evaluate(() =>
Array.from(document.querySelectorAll('input')).map(i => ({
name: i.name, type: i.type, placeholder: i.placeholder
}))
);
console.log(JSON.stringify(inputs, null, 2));
await page.fill('input[name="email"]', 'user@example.com');
await page.click('button:has-text("Submit")');
await page.waitForTimeout(2000);javascript
// 先检查输入框
const inputs = await page.evaluate(() =>
Array.from(document.querySelectorAll('input')).map(i => ({
name: i.name, type: i.type, placeholder: i.placeholder
}))
);
console.log(JSON.stringify(inputs, null, 2));
await page.fill('input[name="email"]', 'user@example.com');
await page.click('button:has-text("Submit")');
await page.waitForTimeout(2000);Extract Page Content
提取页面内容
javascript
const text = await page.evaluate(() => document.body.innerText);
const html = await page.content();
const title = await page.title();javascript
const text = await page.evaluate(() => document.body.innerText);
const html = await page.content();
const title = await page.title();Wait for Navigation
等待导航完成
javascript
await Promise.all([
page.waitForNavigation({ waitUntil: 'networkidle' }),
page.click('button:has-text("Login")')
]);javascript
await Promise.all([
page.waitForNavigation({ waitUntil: 'networkidle' }),
page.click('button:has-text("Login")')
]);SPA / Client-Side Rendered Data
SPA/客户端渲染数据
SPAs may not hydrate fully in headless. Use data endpoints if available:
javascript
const data = await page.evaluate(async () => {
const res = await fetch('/api/data', { credentials: 'include' });
return res.json();
});无头模式下SPA可能无法完全加载。如果有可用的数据端点,请使用:
javascript
const data = await page.evaluate(async () => {
const res = await fetch('/api/data', { credentials: 'include' });
return res.json();
});Multi-Page Flow
多页面流程
javascript
await page.goto('https://app.example.com/login');
await page.fill('#email', 'user@example.com');
await page.fill('#password', 'password');
await page.click('button[type="submit"]');
await page.waitForNavigation({ waitUntil: 'networkidle' });
await page.screenshot({ path: '/tmp/after-login.png' });
// Continue to next page
await page.click('a:has-text("Dashboard")');
await page.waitForLoadState('networkidle');
await page.screenshot({ path: '/tmp/dashboard.png' });javascript
await page.goto('https://app.example.com/login');
await page.fill('#email', 'user@example.com');
await page.fill('#password', 'password');
await page.click('button[type="submit"]');
await page.waitForNavigation({ waitUntil: 'networkidle' });
await page.screenshot({ path: '/tmp/after-login.png' });
// 继续跳转到下一页
await page.click('a:has-text("Dashboard")');
await page.waitForLoadState('networkidle');
await page.screenshot({ path: '/tmp/dashboard.png' });Record Video
录制视频
javascript
// Must create a NEW context for video recording
const ctx = await browser.newContext({
recordVideo: { dir: '/tmp/videos/', size: { width: 1280, height: 720 } }
});
const page = await ctx.newPage();
await page.goto('https://example.com');
// ... do things ...
await ctx.close(); // video saved to /tmp/videos/*.webmjavascript
// 必须创建新的上下文以录制视频
const ctx = await browser.newContext({
recordVideo: { dir: '/tmp/videos/', size: { width: 1280, height: 720 } }
});
const page = await ctx.newPage();
await page.goto('https://example.com');
// ... 执行操作 ...
await ctx.close(); // 视频将保存到/tmp/videos/*.webmConsole & Network Monitoring
控制台与网络监控
javascript
page.on('console', msg => console.log(`[${msg.type()}] ${msg.text()}`));
page.on('response', res => {
if (res.status() >= 400) console.log(`[${res.status()}] ${res.url()}`);
});javascript
page.on('console', msg => console.log(`[${msg.type()}] ${msg.text()}`));
page.on('response', res => {
if (res.status() >= 400) console.log(`[${res.status()}] ${res.url()}`);
});Selectors (prefer in this order)
选择器(优先顺序如下)
- —
data-testid="foo"page.click('[data-testid="foo"]') - Role —
page.getByRole('button', { name: 'Submit' }) - Text —
page.click('button:has-text("Submit")') - CSS — (fragile, last resort)
page.click('.submit-btn')
- ——
data-testid="foo"page.click('[data-testid="foo"]') - 角色 ——
page.getByRole('button', { name: 'Submit' }) - 文本 ——
page.click('button:has-text("Submit")') - CSS —— (易失效,最后选择)
page.click('.submit-btn')
Cleanup
清理
bash
pkill -f "remote-debugging-port=9222"bash
pkill -f "remote-debugging-port=9222"Troubleshooting
故障排除
| Problem | Solution |
|---|---|
| CDP not responding | Wait longer, check |
| Port 9222 in use | |
| ESM import error | Use |
| Page not loading | Increase timeout, use |
| Screenshots blank | Add |
| CSS not loaded | Use |
| Form submit fails | Check if element is visible: |
| 问题 | 解决方案 |
|---|---|
| CDP无响应 | 等待更长时间,检查 |
| 端口9222被占用 | 执行 |
| ESM导入错误 | 使用 |
| 页面无法加载 | 增加超时时间,使用 |
| 截图空白 | 在捕获前添加 |
| CSS未加载 | 使用 |
| 表单提交失败 | 检查元素是否可见: |