关于 playwright-local
使用 Playwright 进行本地浏览器自动化的技能,适用于抓取、测试和浏览器工作流。内容包括 Node.js 与 Python 配置、隐身设置、会话管理、调试和问题排查。
主要功能
- 01Node.js 与 Python 配置指南
- 02Chromium、Firefox 和 WebKit 用法
- 03隐身模式配置指南
- 04Cookie 与会话持久化
- 05检查控制台、页面错误和请求
- 06常见浏览器问题排查
适用场景
- 01使用本地浏览器自动化抓取页面
- 02测试登录与身份验证流程
- 03排查浏览器测试失败
- 04检查页面错误和网络活动
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jezweb/ claude-skills --skill playwright-local安装后在 Claude Code 中输入/playwright-local确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.md.claude-plugin/plugin.jsonassets/example-template.txtreferences/common-blocks.mdselector-strategies.mdstealth-techniques.mdscripts/install-browsers.shtemplates/authenticated-session.tsbasic-scrape.tsinfinite-scroll.tspdf-generation.tsscreenshot-capture.tsstealth-mode.ts详细介绍
Playwright 本地浏览器自动化
状态:已可用于生产 ✅ 上次更新:2026-01-21 依赖项:Node.js 20+(不再支持 Node.js 18)或 Python 3.9+ 最新版本:[email protected]、[email protected]、[email protected] 浏览器版本:Chromium 143.0.7499.4 | Firefox 144.0.2 | WebKit 26.0
⚠️ v1.57 重大变更:Playwright 现在使用 Chrome for Testing 构建版本,而不是 Chromium。这能提供更真实的浏览器行为,但会更改浏览器图标和标题栏。
快速开始(5 分钟)
1. 安装 Playwright
Node.js:
npm install -D playwright
npx playwright install chromium
Python:
pip install playwright
playwright install chromium
为什么这很重要:
playwright install会下载浏览器二进制文件(Chromium 约 400MB)- 只安装需要的浏览器:
chromium、firefox或webkit - 二进制文件存储在
~/.cache/ms-playwright/
2. 抓取基本页面
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle' });
const title = await page.title();
const content = await page.textContent('body');
await browser.close();
console.log({ title, content });
重要:
- 始终使用
await browser.close()关闭浏览器,以避免产生僵尸进程 - 对动态内容(SPA)使用
waitUntil: 'networkidle' - 默认超时时间为 30 秒;如有需要,可使用
timeout: 60000进行调整
3. 在本地测试
# Node.js
npx tsx scrape.ts
# Python
python scrape.py
为什么选择 Playwright 本地运行,而不是 Cloudflare Browser Rendering
| 功能 | Playwright 本地运行 | Cloudflare Browser Rendering |
|---|---|---|
| IP 地址 | 家庭宽带(你的 ISP) | 数据中心(容易被检测) |
| 隐身插件 | 完全支持 | 不可用 |
| 速率限制 | 无 | 免费套餐每天 2,000 个请求 |
| 费用 | 免费(使用你的 CPU) | 免费套餐用完后,每 10,000 个请求 $5 |
| 浏览器控制 | 支持所有 Playwright 功能 | API 有限 |
| 并发量 | 受你的硬件限制 | 受账户限制 |
| 会话持久化 | 可完全控制 Cookie 和存储 | 会话管理有限 |
| 适用场景 | 受机器人保护的网站、身份验证流程 | 简单抓取、无服务器环境 |
何时使用 Cloudflare:无服务器环境、简单抓取、大规模使用时更节省成本
何时使用本地运行:需要绕过反机器人措施、需要家庭宽带 IP、复杂自动化
隐身设置的 7 个步骤
⚠️ 2025 年的现实情况:隐身插件可以很好地应对基础反机器人措施,但高级检测系统(Cloudflare Bot Management、PerimeterX、DataDome)已显著演进。目前的检测手段包括:
- 行为分析(鼠标轨迹、滚动时机、击键动态)
- TLS 指纹识别(JA3/JA4 特征)
- Canvas 和 WebGL 指纹识别
- HTTP/2 指纹识别
建议:
- 隐身插件是一个不错的起点,但并非完整解决方案
- 结合真实的用户行为模拟(使用
steps选项)- 对于防护严密的网站,可考虑使用住宅代理
- “今天有效的方法,明天未必有效”——请定期测试
- 对于高级场景,可研究
nodriver或undetected-chromedriver等替代方案
步骤 1:安装隐身插件(Node.js)
npm install playwright-extra playwright-stealth
兼容 puppeteer-extra:
npm install puppeteer-extra puppeteer-extra-plugin-stealth
步骤 2:配置隐身模式
playwright-extra:
import { chromium } from 'playwright-extra';
import stealth from 'puppeteer-extra-plugin-stealth';
chromium.use(stealth());
const browser = await chromium.launch({
headless: true,
args: [
'--disable-blink-features=AutomationControlled',
'--no-sandbox',
'--disable-setuid-sandbox',
],
});
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
viewport: { width: 1920, height: 1080 },
locale: 'en-US',
timezoneId: 'America/New_York',
});
要点:
--disable-blink-features=AutomationControlled会移除navigator.webdriver标志- 随机化视口尺寸,以避免被识别为指纹
- 使用户代理与浏览器版本匹配(上面的示例使用 Chrome 120)
步骤 3:屏蔽 WebDriver 检测
await page.addInitScript(() => {
// Remove webdriver property
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined,
});
// Mock plugins
Object.defineProperty(navigator, 'plugins', {
get: () => [1, 2, 3, 4, 5],
});
// Mock languages
Object.defineProperty(navigator, 'languages', {
get: () => ['en-US', 'en'],
});
// Consistent permissions
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) => (
parameters.name === 'notifications' ?
Promise.resolve({ state: Notification.permission }) :
originalQuery(parameters)
);
});
步骤 4:模拟真人鼠标移动
// Simulate human cursor movement
async function humanClick(page, selector) {
const element = await page.locator(selector);
const box = await element.boundingBox();
if (box) {
// Move to random point within element
const x = box.x + box.width * Math.random();
const y = box.y + box.height * Math.random();
await page.mouse.move(x, y, { steps: 10 });
await page.mouse.click(x, y, { delay: 100 });
}
}
步骤 5:轮换用户代理
const userAgents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
];
const randomUA = userAgents[Math.floor(Math.random() * userAgents.length)];
const context = await browser.newContext({
userAgent: randomUA,
});
步骤 6:Cookie 和会话持久化
import { chromium } from 'playwright';
import fs from 'fs/promises';
// Save session
const context = await browser.newContext();
const page = await context.newPage();
// ... perform login ...
const cookies = await context.cookies();
await fs.writeFile('session.json', JSON.stringify(cookies, null, 2));
await context.close();
// Restore session
const savedCookies = JSON.parse(await fs.readFile('session.json', 'utf-8'));
const newContext = await browser.newContext();
await newContext.addCookies(savedCookies);
步骤 7:验证隐身效果
在以下网址测试你的设置:https://bot.sannysoft.com/
const page = await context.newPage();
await page.goto('https://bot.sannysoft.com/', { waitUntil: 'networkidle' });
await page.screenshot({ path: 'stealth-test.png', fullPage: true });
检查内容:
navigator.webdriver应为undefined(而不是false)- 应能检测到 Chrome
- 应已填充插件信息
- 页面上不应有红色警示
重要规则
务必做到
✅ 对单页应用(React、Vue、Angular)使用 waitUntil: 'networkidle'
✅ 使用 await browser.close() 关闭浏览器,防止内存泄漏
✅ 将自动化操作包裹在 try/catch/finally 块中
✅ 为不稳定的网站设置明确的超时时间
✅ 出错时保存截图以便调试
✅ 与元素交互前使用 page.waitForSelector()
✅ 高流量抓取时轮换用户代理
✅ 先使用 headless: false 测试,再切换到 headless: true
绝不要做
❌ 不等待元素就使用 page.click()(先调用 waitForSelector)
❌ 依赖固定的 setTimeout() 等待(使用 waitForSelector、waitForLoadState)
❌ 抓取时不限制速率(在请求之间添加延迟)
❌ 所有请求都使用相同的用户代理(轮换用户代理)
❌ 忽略导航错误(捕获错误并通过退避重试)
❌ 未先测试有头模式就运行无头模式(视觉调试可以发现问题)
❌ 将凭据存储在代码中(使用环境变量)
调试方法(v1.56+)
Playwright v1.56 引入了无需设置事件监听器即可捕获调试信息的新方法:
控制台消息
import { test, expect } from '@playwright/test';
test('capture console output', async ({ page }) => {
await page.goto('https://example.com');
// Get all recent console messages
const messages = page.consoleMessages();
// Filter by type
const errors = messages.filter(m => m.type() === 'error');
const logs = messages.filter(m => m.type() === 'log');
console.log('Console errors:', errors.map(m => m.text()));
});
页面错误
test('check for JavaScript errors', async ({ page }) => {
await page.goto('https://example.com');
// Get all page errors (uncaught exceptions)
const errors = page.pageErrors();
// Fail test if any errors occurred
expect(errors).toHaveLength(0);
});
网络请求
test('inspect API calls', async ({ page }) => {
await page.goto('https://example.com');
// Get all recent network requests
const requests = page.requests();
// Filter for API calls
const apiCalls = requests.filter(r => r.url().includes('/api/'));
console.log('API calls made:', apiCalls.length);
// Check for failed requests
const failed = requests.filter(r => r.failure());
expect(failed).toHaveLength(0);
});
适用场景:调试测试失败、验证是否存在控制台错误、审查网络活动。
高级鼠标控制(v1.57+)
steps 选项可精细控制鼠标移动,适用于:
- 让行为更像真人,以应对反机器人检测
- 测试带平滑动画的拖放操作
- 调试视觉交互
带步骤的点击
// Move to element in 10 intermediate steps (smoother, more human-like)
await page.locator('button.submit').click({ steps: 10 });
// Fast click (fewer steps)
await page.locator('button.cancel').click({ steps: 2 });
带步骤的拖动
const source = page.locator('#draggable');
const target = page.locator('#dropzone');
// Smooth drag animation (20 steps)
await source.dragTo(target, { steps: 20 });
// Quick drag (5 steps)
await source.dragTo(target, { steps: 5 });
反检测优势:许多机器人检测系统会检查瞬时鼠标移动。使用 steps: 10 或更高值可模拟真实的人类鼠标行为。
已知问题预防
此技能可预防 10 个有记录的问题:
问题 #1:目标已关闭错误
错误:Protocol error (Target.sendMessageToTarget): Target closed.
来源:https://github.com/microsoft/playwright/issues/2938
发生原因:操作完成前页面已关闭,或浏览器崩溃
预防方法:
try {
await page.goto(url, { timeout: 30000 });
} catch (error) {
if (error.message.includes('Target closed')) {
console.log('Browser crashed, restarting...');
await browser.close();
browser = await chromium.launch();
}
}
问题 #2:未找到元素
错误:TimeoutError: waiting for selector "button" failed: timeout 30000ms exceeded
来源:https://playwright.dev/docs/actionability
发生原因:元素不存在、选择器错误,或页面尚未加载
预防方法:
// Use waitForSelector with explicit timeout
const button = await page.waitForSelector('button.submit', {
state: 'visible',
timeout: 10000,
});
await button.click();
// Or use locator with auto-wait
await page.locator('button.submit').click();
问题 #3:导航超时
错误:TimeoutError: page.goto: Timeout 30000ms exceeded.
来源:https://playwright.dev/docs/navigations
发生原因:页面加载缓慢、加载指示器无限转动,或被防火墙拦截
预防方法:
try {
await page.goto(url, {
waitUntil: 'domcontentloaded', // Less strict than networkidle
timeout: 60000, // Increase for slow sites
});
} catch (error) {
if (error.name === 'TimeoutError') {
console.log('Navigation timeout, checking if page loaded...');
const title = await page.title();
if (title) {
console.log('Page loaded despite timeout');
}
}
}
问题 #4:框架已分离错误
错误:Error: Execution context was destroyed, most likely because of a navigation.
来源:https://github.com/microsoft/playwright/issues/3934
发生原因:单页应用导航重新渲染了元素
预防方法:
// Re-query element after navigation
async function safeClick(page, selector) {
await page.waitForSelector(selector);
await page.click(selector);
await page.waitForLoadState('networkidle');
}
问题 #5:机器人检测(403/验证码)
错误:页面返回 403 或显示验证码
来源:https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth
发生原因:网站检测到 navigator.webdriver、数据中心 IP,或指纹不匹配
预防方法:使用隐身模式(上述步骤 2-7)+ 住宅 IP
问题 #6:文件下载未完成
错误:下载已开始但始终未完成 来源:https://playwright.dev/docs/downloads 发生原因:未等待下载事件,或文件流未关闭 预防方法:
const [download] = await Promise.all([
page.waitForEvent('download'),
page.click('a.download-link'),
]);
const path = await download.path();
await download.saveAs('./downloads/' + download.suggestedFilename());
问题 #7:无限滚动未加载更多内容
错误:滚动到底部,但没有加载新内容 来源:https://playwright.dev/docs/input#scrolling 发生原因:滚动事件未正确触发,或滚动速度过快 预防方法:
let previousHeight = 0;
while (true) {
const currentHeight = await page.evaluate(() => document.body.scrollHeight);
if (currentHeight === previousHeight) {
break; // No more content
}
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await page.waitForTimeout(2000); // Wait for new content to load
previousHeight = currentHeight;
}
问题 #8:WebSocket 连接失败
错误:WebSocket connection to 'ws://...' failed
来源:https://playwright.dev/docs/api/class-browser
发生原因:在受限环境中启动浏览器时未使用 --no-sandbox
预防措施:
const browser = await chromium.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
问题 #9:page.pause() 在无头模式下会禁用超时
错误:存在 page.pause() 时,CI 中的测试会无限期挂起
来源:GitHub 问题 #38754
发生原因:page.pause() 在无头模式下会被忽略,但会禁用测试超时,导致后续失败的断言永远挂起
预防措施:
// Conditional debugging - only pause in local development
if (!process.env.CI && !process.env.HEADLESS) {
await page.pause();
}
// Or use environment variable
const shouldPause = process.env.DEBUG_MODE === 'true';
if (shouldPause) {
await page.pause();
}
影响:高 — 可能导致 CI 流水线因断言失败而无限期挂起
问题 #10:权限提示会阻碍 CI 中的扩展测试
错误:测试浏览器扩展时,测试会卡在权限提示上
来源:GitHub 问题 #38670
发生原因:launchPersistentContext 搭配扩展时会显示无法关闭的权限提示(clipboard-read/write、local-network-access),而且无法自动授予权限
预防措施:
// Don't use persistent context for extensions in CI
// Use regular context instead
const context = await browser.newContext({
permissions: ['clipboard-read', 'clipboard-write']
});
// For extensions, pre-grant permissions where possible
const context = await browser.newContext({
permissions: ['notifications', 'geolocation']
});
影响:高 — 会阻碍 CI/CD 环境中的自动化扩展测试
配置文件参考
playwright.config.ts(完整示例)
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30000,
expect: {
timeout: 5000,
},
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
// Anti-detection settings
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
viewport: { width: 1920, height: 1080 },
locale: 'en-US',
timezoneId: 'America/New_York',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'stealth',
use: {
...devices['Desktop Chrome'],
launchOptions: {
args: [
'--disable-blink-features=AutomationControlled',
'--no-sandbox',
],
},
},
},
],
});
这些设置的作用:
trace: 'on-first-retry'- 捕获完整跟踪信息,以便调试失败的测试screenshot: 'only-on-failure'- 节省磁盘空间viewport: { width: 1920, height: 1080 }- 常见的桌面分辨率--disable-blink-features=AutomationControlled- 移除 webdriver 标志
动态 Web 服务配置(v1.57+)
使用正则表达式等待 Web 服务输出,然后再启动测试:
import { defineConfig } from '@playwright/test';
export default defineConfig({
webServer: {
command: 'npm run dev',
// Wait for server to print port
wait: {
stdout: '/Server running on port (?<SERVER_PORT>\\d+)/'
},
},
use: {
// Use captured port in tests
baseURL: `http://localhost:${process.env.SERVER_PORT ?? 3000}`
}
});
优势:
- 处理开发服务器(Vite、Next.js 开发模式)分配的动态端口
- 无需进行 HTTP 就绪检查
- 命名捕获组会成为环境变量
- 适用于只记录就绪消息的服务
适用场景:
- 使用随机端口的开发服务器
- 没有 HTTP 端点的服务
- 使用端口映射的容器化环境
常见模式
模式 1:抓取已通过身份验证的会话
import { chromium } from 'playwright';
import fs from 'fs/promises';
async function scrapeWithAuth() {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();
// Login
await page.goto('https://example.com/login');
await page.fill('input[name="email"]', process.env.EMAIL);
await page.fill('input[name="password"]', process.env.PASSWORD);
await page.click('button[type="submit"]');
await page.waitForURL('**/dashboard', { timeout: 10000 });
// Save session
const cookies = await context.cookies();
await fs.writeFile('session.json', JSON.stringify(cookies));
// Navigate to protected page
await page.goto('https://example.com/protected-data');
const data = await page.locator('.data-table').textContent();
await browser.close();
return data;
}
适用场景:需要登录的网站、抓取用户专属内容
模式 2:带去重功能的无限滚动
async function scrapeInfiniteScroll(page, selector) {
const items = new Set();
let previousCount = 0;
let noChangeCount = 0;
while (noChangeCount < 3) {
const elements = await page.locator(selector).all();
for (const el of elements) {
const text = await el.textContent();
items.add(text);
}
if (items.size === previousCount) {
noChangeCount++;
} else {
noChangeCount = 0;
}
previousCount = items.size;
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await page.waitForTimeout(1500);
}
return Array.from(items);
}
适用场景:Twitter 信息流、商品列表、支持无限滚动的新闻网站
模式 3:多标签页编排
async function scrapeMultipleTabs(urls) {
const browser = await chromium.launch();
const context = await browser.newContext();
const results = await Promise.all(
urls.map(async (url) => {
const page = await context.newPage();
await page.goto(url);
const title = await page.title();
await page.close();
return { url, title };
})
);
await browser.close();
return results;
}
适用场景:并发抓取多个页面、比价购物
模式 4:截取整页屏幕截图
async function captureFullPage(url, outputPath) {
const browser = await chromium.launch();
const page = await browser.newPage({
viewport: { width: 1920, height: 1080 },
});
await page.goto(url, { waitUntil: 'networkidle' });
await page.screenshot({
path: outputPath,
fullPage: true,
type: 'png',
});
await browser.close();
}
适用场景:视觉回归测试、页面存档、文档编制
模式 5:生成 PDF
async function generatePDF(url, outputPath) {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle' });
await page.pdf({
path: outputPath,
format: 'A4',
printBackground: true,
margin: {
top: '1cm',
right: '1cm',
bottom: '1cm',
left: '1cm',
},
});
await browser.close();
}
适用场景:生成报告、归档发票、保存内容
模式 6:带验证的表单自动化
async function fillFormWithValidation(page) {
// Fill fields
await page.fill('input[name="firstName"]', 'John');
await page.fill('input[name="lastName"]', 'Doe');
await page.fill('input[name="email"]', '[email protected]');
// Handle dropdowns
await page.selectOption('select[name="country"]', 'US');
// Handle checkboxes
await page.check('input[name="terms"]');
// Wait for validation
await page.waitForSelector('input[name="email"]:valid');
// Submit
await page.click('button[type="submit"]');
// Wait for success message
await page.waitForSelector('.success-message', { timeout: 10000 });
}
适用场景:创建账户、测试表单、自动录入数据
模式 7:使用指数退避重试
async function retryWithBackoff(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s
console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// Usage
await retryWithBackoff(async () => {
await page.goto('https://unreliable-site.com');
});
适用场景:不稳定的网络、受速率限制的 API、不可靠的网站
使用内置资源
模板(templates/)
所有模板均为可直接使用的 TypeScript 文件。请从 ~/.claude/skills/playwright-local/templates/ 复制:
basic-scrape.ts- 简单的页面抓取stealth-mode.ts- 完整的隐身配置authenticated-session.ts- 登录 + 抓取模式infinite-scroll.ts- 持续滚动,直到没有新内容screenshot-capture.ts- 整页屏幕截图pdf-generation.ts- 导出 PDF
用法示例:
# Copy template
cp ~/.claude/skills/playwright-local/templates/stealth-mode.ts ./scrape.ts
# Edit for your use case
# Run with tsx
npx tsx scrape.ts
参考资料(references/)
Claude 可按需加载的文档:
references/stealth-techniques.md- 完整的反检测指南references/selector-strategies.md- CSS、XPath 和文本选择器references/common-blocks.md- 已知的拦截模式及绕过方法
Claude 应在以下情况下加载这些资料:排查机器人检测、选择器问题或特定网站的拦截时
脚本(scripts/)
scripts/install-browsers.sh- 安装所有 Playwright 浏览器
用法:
chmod +x ~/.claude/skills/playwright-local/scripts/install-browsers.sh
~/.claude/skills/playwright-local/scripts/install-browsers.sh
高级主题
Playwright MCP 服务(v1.56+)
Microsoft 提供了官方的 Playwright MCP 服务,用于 AI Agent 集成:
# Initialize AI agent configurations
npx playwright init-agents
这会为以下工具生成配置文件:
- VS Code - Copilot 集成
- Claude Desktop - Claude MCP 客户端
- opencode - 开源 AI 编码工具
主要功能:
- 使用无障碍树代替屏幕截图(速度更快,也更可靠)
- 适合大模型的结构化数据格式
- 与 GitHub Copilot 的 Coding Agent 集成
- 符合 Model Context Protocol(MCP)规范
MCP 服务设置:
# Install globally
npm install -g @anthropic/mcp-playwright
# Or add to Claude Desktop config
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@anthropic/mcp-playwright"]
}
}
}
适用场景:构建需要浏览器自动化的 AI Agent,将 Playwright 与 Claude 或其他大模型集成。
使用 Speedboard 进行性能分析(v1.57+)
Playwright v1.57 在 HTML 报告器中引入了 Speedboard,这是一个专门用于识别运行缓慢的测试和性能瓶颈的选项卡。
在配置中启用:
export default defineConfig({
reporter: 'html',
});
查看 Speedboard:
npx playwright test --reporter=html
npx playwright show-report
Speedboard 显示内容:
- 所有测试按执行时间排序(最慢的排在最前)
- 等待时间明细
- 网络请求耗时
- 帮助识别低效的选择器和不必要的等待
适用场景:
- 优化测试套件的运行时间
- 查找包含过多
waitForTimeout()调用的测试 - 识别影响测试的慢速 API 响应
- 优先重构运行最慢的测试
Docker 部署
官方 Docker 镜像提供一致且可复现的环境:
当前镜像(v1.57.0):
FROM mcr.microsoft.com/playwright:v1.57.0-noble
# Create non-root user for security
RUN groupadd -r pwuser && useradd -r -g pwuser pwuser
USER pwuser
WORKDIR /app
COPY --chown=pwuser:pwuser . .
RUN npm ci
CMD ["npx", "playwright", "test"]
可用标签:
:v1.57.0-noble- Ubuntu 24.04 LTS(推荐):v1.57.0-jammy- Ubuntu 22.04 LTS
使用推荐标志运行:
docker run -it --init --ipc=host my-playwright-tests
| 标志 | 用途 |
|---|---|
--init | 防止僵尸进程(处理 PID=1) |
--ipc=host | 防止 Chromium 内存耗尽 |
--cap-add=SYS_ADMIN | 仅用于本地开发(启用沙盒) |
Python 镜像:
FROM mcr.microsoft.com/playwright/python:v1.57.0-noble
安全注意事项:
- 始终在容器内创建非 root 用户
- root 用户会禁用 Chromium 沙盒(存在安全风险)
- 对不可信网站使用 seccomp 配置文件
- 固定到特定版本标签(避免使用
:latest)
通过 Bash 工具在 Claude Code 中运行 Playwright
Claude Code 可以编排浏览器自动化:
// scrape.ts
import { chromium } from 'playwright';
async function scrape(url: string) {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto(url);
const data = await page.evaluate(() => {
return {
title: document.title,
headings: Array.from(document.querySelectorAll('h1, h2'))
.map(el => el.textContent),
};
});
await browser.close();
console.log(JSON.stringify(data, null, 2));
}
scrape(process.argv[2]);
Claude Code 工作流:
- 使用 Bash 工具编写脚本:
npx tsx scrape.ts https://example.com - 捕获 JSON 输出
- 解析并分析结果
- 生成摘要或后续操作
屏幕截图审查工作流
// screenshot-review.ts
import { chromium } from 'playwright';
async function captureForReview(url: string) {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(url);
await page.screenshot({ path: '/tmp/review.png', fullPage: true });
await browser.close();
console.log('Screenshot saved to /tmp/review.png');
}
captureForReview(process.argv[2]);
Claude Code 随后可以:
- 通过 Bash 工具运行脚本
- 使用 Read 工具读取屏幕截图
- 分析视觉布局
- 提出改进建议
使用并行浏览器上下文提升速度
import { chromium } from 'playwright';
async function scrapeConcurrently(urls: string[]) {
const browser = await chromium.launch();
// Use separate contexts for isolation
const results = await Promise.all(
urls.map(async (url) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(url);
const title = await page.title();
await context.close();
return { url, title };
})
);
await browser.close();
return results;
}
性能提升:并行处理 10 个 URL 所需时间约等于处理 1 个 URL
浏览器指纹识别防护
async function setupStealthContext(browser) {
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
viewport: { width: 1920, height: 1080 },
locale: 'en-US',
timezoneId: 'America/New_York',
// WebGL fingerprinting defense
screen: {
width: 1920,
height: 1080,
},
// Geolocation (if needed)
geolocation: { longitude: -74.006, latitude: 40.7128 },
permissions: ['geolocation'],
});
return context;
}
处理动态内容加载
async function waitForDynamicContent(page, selector) {
// Wait for initial element
await page.waitForSelector(selector);
// Wait for content to stabilize (no changes for 2s)
let previousContent = '';
let stableCount = 0;
while (stableCount < 4) {
await page.waitForTimeout(500);
const currentContent = await page.locator(selector).textContent();
if (currentContent === previousContent) {
stableCount++;
} else {
stableCount = 0;
}
previousContent = currentContent;
}
return previousContent;
}
快速参考
选择器策略
| 策略 | 示例 | 适用场景 |
|---|---|---|
| CSS | page.click('button.submit') | 标准 HTML 元素 |
| XPath | page.click('xpath=//button[text()="Submit"]') | 复杂 DOM 查询 |
| 文本 | page.click('text=Submit') | 文本唯一时 |
| 数据属性 | page.click('[data-testid="submit"]') | 测试自动化 |
| 第 N 个子元素 | page.click('ul > li:nth-child(2)') | 基于位置选择 |
等待策略
| 方法 | 使用场景 |
|---|---|
waitUntil: 'load' | 所有资源均已加载(默认) |
waitUntil: 'domcontentloaded' | DOM 已就绪,适合访问速度较慢的网站 |
waitUntil: 'networkidle' | 500ms 内没有网络活动(适用于 SPA) |
page.waitForSelector(selector) | 等待元素出现在 DOM 中 |
page.waitForLoadState('networkidle') | 导航后等待 |
page.waitForTimeout(ms) | 固定延迟(尽可能避免) |
浏览器启动选项
| 选项 | 值 | 用途 |
|---|---|---|
headless | true/false | 显示浏览器界面 |
slowMo | 100 (ms) | 放慢速度以便调试 |
args | ['--no-sandbox'] | 禁用沙盒(Docker) |
executablePath | /path/to/chrome | 使用自定义浏览器 |
downloadsPath | ./downloads | 下载位置 |
用于隐身的常见启动参数
args: [
'--disable-blink-features=AutomationControlled',
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--no-first-run',
'--no-zygote',
'--single-process',
'--disable-gpu',
]
依赖项
必需:
- [email protected] - 核心浏览器自动化库
- Node.js 20+(不再支持 Node.js 18)或 Python 3.9+ - 运行时
可选(Node.js 隐身):
- [email protected] - Playwright 插件系统
- [email protected] - 反检测插件
可选(Python 隐身):
- [email protected] - Python 隐身库
Docker 镜像:
mcr.microsoft.com/playwright:v1.57.0-noble- Ubuntu 24.04、Node.js 22 LTSmcr.microsoft.com/playwright/python:v1.57.0-noble- Python 版本
官方文档
- Playwright:https://playwright.dev/docs/intro
- Playwright API:https://playwright.dev/docs/api/class-playwright
- 隐身插件:https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth
- Context7 库 ID:/microsoft/playwright
软件包版本(2026-01-10 验证)
{
"devDependencies": {
"playwright": "^1.57.0",
"@playwright/test": "^1.57.0",
"playwright-extra": "^4.3.6",
"puppeteer-extra-plugin-stealth": "^2.11.2"
}
}
Python:
playwright==1.57.0
playwright-stealth==0.0.1
生产环境示例
此技能基于生产环境中的网页抓取系统:
- 每日抓取量:10,000+ 个页面/天
- 成功率:98%+(含重试)
- 绕过机器人检测:在受保护网站上的成功率达 95%+
- 错误:通过上述模式预防了所有 8 个已知问题
- 验证:✅ 已测试住宅 IP、隐身模式和会话持久化
故障排除
问题:“Executable doesn't exist”错误
解决方案:
npx playwright install chromium
# Or for all browsers:
npx playwright install
问题:Docker 中性能缓慢
解决方案:增加共享内存大小
# In Dockerfile
RUN playwright install --with-deps chromium
# Run with:
docker run --shm-size=2gb your-image
问题:截图为空白
解决方案:等待内容加载
await page.goto(url, { waitUntil: 'networkidle' });
await page.waitForTimeout(1000); // Extra buffer
await page.screenshot({ path: 'output.png' });
问题:“Page crashed”错误
解决方案:降低并发量或增加内存
const browser = await chromium.launch({
args: ['--disable-dev-shm-usage'], // Use /tmp instead of /dev/shm
});
问题:总是出现验证码
解决方案:
- 确认隐身模式已启用(检查 bot.sannysoft.com)
- 轮换用户代理
- 在操作之间加入随机延迟
- 如有需要,使用住宅代理
问题:Ubuntu 25.10 安装失败
错误:Unable to locate package libicu74、Package 'libxml2' has no installation candidate
来源:GitHub Issue #38874
解决方案:
# Use Ubuntu 24.04 Docker image (officially supported)
docker pull mcr.microsoft.com/playwright:v1.57.0-noble
# Or wait for Ubuntu 25.10 support in future releases
# Track issue: https://github.com/microsoft/playwright/issues/38874
临时解决方法(如果无法使用 Docker):
# Manually install compatible libraries
sudo apt-get update
sudo apt-get install libicu72 libxml2
完整设置检查清单
使用此清单验证设置:
- 已安装 Playwright(
npm list playwright或pip show playwright) - 已下载浏览器(
npx playwright install chromium) - 基础脚本运行成功
- 已配置隐身模式(如有需要)
- 会话持久化正常
- 截图已正确保存
- 错误处理包含重试
- 浏览器已正确关闭(没有僵尸进程)
- 首先使用
headless: false进行了测试 - 生产脚本使用
headless: true
有疑问?遇到问题?
- 查看
references/common-blocks.md,了解特定网站的拦截 - 在 https://bot.sannysoft.com/ 验证隐身设置
- 查看官方文档:https://playwright.dev/docs/intro
- 确保已安装浏览器二进制文件:
npx playwright install chromium
最后验证时间:2026-01-21 | 技能版本:3.1.0 | 变更:新增 2 个关键 CI 问题(page.pause() 超时、扩展程序权限提示),v1.57 功能(Speedboard、webServer 等待配置),Ubuntu 25.10 兼容性指南