playwright-local by jezweb/claude-skills
npx skills add https://github.com/jezweb/claude-skills --skill playwright-local状态 : 生产就绪 ✅ 最后更新 : 2026-01-21 依赖项 : Node.js 20+ (Node.js 18 已弃用) 或 Python 3.9+ 最新版本 : playwright@1.57.0, playwright-stealth@0.0.1, puppeteer-extra-plugin-stealth@2.11.2 浏览器版本 : Chromium 143.0.7499.4 | Firefox 144.0.2 | WebKit 26.0
⚠️ v1.57 重大变更 : Playwright 现在使用 Chrome for Testing 构建版本,而不是 Chromium。这提供了更真实的浏览器行为,但改变了浏览器图标和标题栏。
Node.js :
npm install -D playwright
npx playwright install chromium
Python :
pip install playwright
playwright install chromium
为何重要:
playwright install 下载浏览器二进制文件(Chromium 约 400MB)广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
chromiumfirefoxwebkit~/.cache/ms-playwright/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() 关闭浏览器,避免僵尸进程waitUntil: 'networkidle'timeout: 60000 调整# Node.js
npx tsx scrape.ts
# Python
python scrape.py
| 功能 | Playwright 本地版 | Cloudflare 浏览器渲染 |
|---|---|---|
| IP 地址 | 住宅 IP(你的 ISP) | 数据中心 IP(易被检测) |
| 隐身插件 | 完全支持 | 不可用 |
| 速率限制 | 无 | 免费层 2,000 请求/天 |
| 成本 | 免费(你的 CPU) | 免费层后 $5/10k 请求 |
| 浏览器控制 | 所有 Playwright 功能 | 有限的 API |
| 并发性 | 你的硬件限制 | 基于账户的限制 |
| 会话持久性 | 完整的 Cookie/存储控制 | 有限的会话管理 |
| 使用场景 | 有机器人防护的网站,认证流程 | 简单抓取,无服务器环境 |
何时使用 Cloudflare : 无服务器环境,简单抓取,大规模时成本效益高 何时使用本地版 : 需要绕过反机器人检测,需要住宅 IP,复杂自动化
⚠️ 2025 年现实检查 : 隐身插件对基本的反机器人措施效果良好,但高级检测系统(Cloudflare Bot Management, PerimeterX, DataDome)已显著进化。当前的检测领域包括:
- 行为分析(鼠标模式,滚动时序,击键动态)
- TLS 指纹识别(JA3/JA4 签名)
- Canvas 和 WebGL 指纹识别
- HTTP/2 指纹识别
建议 :
- 隐身插件是一个好的起点,但不是完整的解决方案
- 结合真实的用户行为模拟(使用
steps选项)- 对于高度保护的网站,考虑使用住宅代理
- “今天有效的方法明天可能无效” - 定期测试
- 对于高级场景,研究替代方案如
nodriver或undetected-chromedriver
npm install playwright-extra playwright-stealth
为了 puppeteer-extra 兼容性 :
npm install puppeteer-extra puppeteer-extra-plugin-stealth
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 标志await page.addInitScript(() => {
// 移除 webdriver 属性
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined,
});
// 模拟插件
Object.defineProperty(navigator, 'plugins', {
get: () => [1, 2, 3, 4, 5],
});
// 模拟语言
Object.defineProperty(navigator, 'languages', {
get: () => ['en-US', 'en'],
});
// 一致的权限
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) => (
parameters.name === 'notifications' ?
Promise.resolve({ state: Notification.permission }) :
originalQuery(parameters)
);
});
// 模拟人类光标移动
async function humanClick(page, selector) {
const element = await page.locator(selector);
const box = await element.boundingBox();
if (box) {
// 移动到元素内的随机点
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 });
}
}
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,
});
import { chromium } from 'playwright';
import fs from 'fs/promises';
// 保存会话
const context = await browser.newContext();
const page = await context.newPage();
// ... 执行登录 ...
const cookies = await context.cookies();
await fs.writeFile('session.json', JSON.stringify(cookies, null, 2));
await context.close();
// 恢复会话
const savedCookies = JSON.parse(await fs.readFile('session.json', 'utf-8'));
const newContext = await browser.newContext();
await newContext.addCookies(savedCookies);
在此处测试你的设置: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)✅ 对 SPA(React, Vue, Angular)使用 waitUntil: 'networkidle' ✅ 使用 await browser.close() 关闭浏览器以防止内存泄漏 ✅ 将自动化包装在 try/catch/finally 块中 ✅ 为不可靠的网站设置显式超时 ✅ 出错时保存截图用于调试 ✅ 在与元素交互前使用 page.waitForSelector() ✅ 对于高流量抓取,轮换用户代理 ✅ 先用 headless: false 测试,然后切换到 headless: true
❌ 不等待元素就使用 page.click()(先使用 waitForSelector) ❌ 依赖固定的 setTimeout() 进行等待(使用 waitForSelector, waitForLoadState) ❌ 没有速率限制就进行抓取(在请求之间添加延迟) ❌ 对所有请求使用相同的用户代理(轮换代理) ❌ 忽略导航错误(捕获并使用退避策略重试) ❌ 不先测试有头模式就运行无头模式(视觉调试能发现问题) ❌ 在代码中存储凭据(使用环境变量)
Playwright v1.56 引入了无需设置事件监听器即可捕获调试信息的新方法:
import { test, expect } from '@playwright/test';
test('capture console output', async ({ page }) => {
await page.goto('https://example.com');
// 获取所有最近的控制台消息
const messages = page.consoleMessages();
// 按类型过滤
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');
// 获取所有页面错误(未捕获的异常)
const errors = page.pageErrors();
// 如果发生任何错误,则测试失败
expect(errors).toHaveLength(0);
});
test('inspect API calls', async ({ page }) => {
await page.goto('https://example.com');
// 获取所有最近的网络请求
const requests = page.requests();
// 过滤 API 调用
const apiCalls = requests.filter(r => r.url().includes('/api/'));
console.log('API calls made:', apiCalls.length);
// 检查失败的请求
const failed = requests.filter(r => r.failure());
expect(failed).toHaveLength(0);
});
何时使用 : 调试测试失败,验证没有控制台错误,审计网络活动。
steps 选项提供了对鼠标移动的细粒度控制,适用于:
// 通过 10 个中间步骤移动到元素(更平滑,更像人类)
await page.locator('button.submit').click({ steps: 10 });
// 快速点击(较少步骤)
await page.locator('button.cancel').click({ steps: 2 });
const source = page.locator('#draggable');
const target = page.locator('#dropzone');
// 平滑的拖放动画(20 步)
await source.dragTo(target, { steps: 20 });
// 快速拖放(5 步)
await source.dragTo(target, { steps: 5 });
反检测优势 : 许多机器人检测系统会寻找瞬时鼠标移动。使用 steps: 10 或更高值可以模拟真实的人类鼠标行为。
此技能可预防 10 个已记录的问题:
错误 : 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();
}
}
错误 : TimeoutError: waiting for selector "button" failed: timeout 30000ms exceeded 来源 : https://playwright.dev/docs/actionability 发生原因 : 元素不存在,选择器错误,或页面未加载 预防措施 :
// 使用带有显式超时的 waitForSelector
const button = await page.waitForSelector('button.submit', {
state: 'visible',
timeout: 10000,
});
await button.click();
// 或使用带有自动等待的定位器
await page.locator('button.submit').click();
错误 : TimeoutError: page.goto: Timeout 30000ms exceeded. 来源 : https://playwright.dev/docs/navigations 发生原因 : 页面加载缓慢,无限加载旋转器,被防火墙阻止 预防措施 :
try {
await page.goto(url, {
waitUntil: 'domcontentloaded', // 比 networkidle 宽松
timeout: 60000, // 为慢速网站增加超时
});
} 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');
}
}
}
错误 : Error: Execution context was destroyed, most likely because of a navigation. 来源 : https://github.com/microsoft/playwright/issues/3934 发生原因 : SPA 导航重新渲染了元素 预防措施 :
// 导航后重新查询元素
async function safeClick(page, selector) {
await page.waitForSelector(selector);
await page.click(selector);
await page.waitForLoadState('networkidle');
}
错误 : 页面返回 403 或显示验证码 来源 : https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth 发生原因 : 网站检测到 navigator.webdriver、数据中心 IP 或指纹不匹配 预防措施 : 使用隐身模式(上述步骤 2-7)+ 住宅 IP
错误 : 下载开始但从未完成 来源 : 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());
错误 : 滚动到底部但没有新内容加载 来源 : https://playwright.dev/docs/input#scrolling 发生原因 : 滚动事件未正确触发,或滚动太快 预防措施 :
let previousHeight = 0;
while (true) {
const currentHeight = await page.evaluate(() => document.body.scrollHeight);
if (currentHeight === previousHeight) {
break; // 没有更多内容
}
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await page.waitForTimeout(2000); // 等待新内容加载
previousHeight = currentHeight;
}
错误 : 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'],
});
错误 : 当存在 page.pause() 时,CI 中的测试无限期挂起 来源 : GitHub Issue #38754 发生原因 : page.pause() 在无头模式下被忽略,但禁用了测试超时,导致后续失败的断言永远挂起 预防措施 :
// 条件调试 - 仅在本地开发中暂停
if (!process.env.CI && !process.env.HEADLESS) {
await page.pause();
}
// 或使用环境变量
const shouldPause = process.env.DEBUG_MODE === 'true';
if (shouldPause) {
await page.pause();
}
影响 : 高 - 可能导致 CI 管道在失败的断言上无限期挂起
错误 : 测试浏览器扩展时,测试在权限提示处挂起 来源 : GitHub Issue #38670 发生原因 : 带有扩展的 launchPersistentContext 显示无法自动授予的非可关闭权限提示(剪贴板读取/写入,本地网络访问) 预防措施 :
// 在 CI 中不要对扩展使用持久上下文
// 改用常规上下文
const context = await browser.newContext({
permissions: ['clipboard-read', 'clipboard-write']
});
// 对于扩展,尽可能预先授予权限
const context = await browser.newContext({
permissions: ['notifications', 'geolocation']
});
影响 : 高 - 在 CI/CD 环境中阻止自动化扩展测试
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',
// 反检测设置
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 服务器输出:
import { defineConfig } from '@playwright/test';
export default defineConfig({
webServer: {
command: 'npm run dev',
// 等待服务器打印端口
wait: {
stdout: '/Server running on port (?<SERVER_PORT>\\d+)/'
},
},
use: {
// 在测试中使用捕获的端口
baseURL: `http://localhost:${process.env.SERVER_PORT ?? 3000}`
}
});
优势 :
何时使用 :
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();
// 登录
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 });
// 保存会话
const cookies = await context.cookies();
await fs.writeFile('session.json', JSON.stringify(cookies));
// 导航到受保护页面
await page.goto('https://example.com/protected-data');
const data = await page.locator('.data-table').textContent();
await browser.close();
return data;
}
何时使用 : 需要登录的网站,抓取用户特定内容
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 动态,产品列表,具有无限滚动的新闻网站
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;
}
何时使用 : 并发抓取多个页面,比较购物
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();
}
何时使用 : 视觉回归测试,页面存档,文档
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();
}
何时使用 : 报告生成,发票存档,内容保存
async function fillFormWithValidation(page) {
// 填写字段
await page.fill('input[name="firstName"]', 'John');
await page.fill('input[name="lastName"]', 'Doe');
await page.fill('input[name="email"]', 'john@example.com');
// 处理下拉菜单
await page.selectOption('select[name="country"]', 'US');
// 处理复选框
await page.check('input[name="terms"]');
// 等待验证
await page.waitForSelector('input[name="email"]:valid');
// 提交
await page.click('button[type="submit"]');
// 等待成功消息
await page.waitForSelector('.success-message', { timeout: 10000 });
}
何时使用 : 账户创建,表单测试,数据录入自动化
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));
}
}
}
// 用法
await retryWithBackoff(async () => {
await page.goto('https://unreliable-site.com');
});
何时使用 : 不稳定的网络,速率受限的 API,不可靠的网站
所有模板都是可直接使用的 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 导出使用示例:
# 复制模板
cp ~/.claude/skills/playwright-local/templates/stealth-mode.ts ./scrape.ts
# 为你的用例编辑
# 使用 tsx 运行
npx tsx scrape.ts
Claude 在需要时可以加载的文档:
references/stealth-techniques.md - 完整的反检测指南references/selector-strategies.md - CSS vs XPath vs 文本选择器references/common-blocks.md - 已知的阻止模式和绕过方法Claude 何时应加载这些 : 当排查机器人检测、选择器问题或特定网站阻止时
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 服务器 用于 AI 代理集成:
# 初始化 AI 代理配置
npx playwright init-agents
这会生成以下配置:
关键特性 :
MCP 服务器设置 :
# 全局安装
npm install -g @anthropic/mcp-playwright
# 或添加到 Claude Desktop 配置
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@anthropic/mcp-playwright"]
}
}
}
何时使用 : 构建需要浏览器自动化的 AI 代理,将 Playwright 与 Claude 或其他 LLM 集成。
Playwright v1.57 在 HTML 报告中引入了 Speedboard - 一个用于识别慢速测试和性能瓶颈的专用标签页。
在配置中启用 :
export default defineConfig({
reporter: 'html',
});
查看 Speedboard :
npx playwright test --reporter=html
npx playwright show-report
Speedboard 显示的内容 :
使用场景 :
waitForTimeout() 调用的测试官方 Docker 镜像提供一致、可重现的环境:
当前镜像 (v1.57.0):
FROM mcr.microsoft.com/playwright:v1.57.0-noble
# 为安全创建非 root 用户
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
安全注意事项 :
:latest)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 工作流程:
npx tsx scrape.ts https://example.com// 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 随后可以:
import { chromium } from 'playwright';
async function scrapeConcurrently(urls: string[]) {
const browser = await chromium.launch();
// 使用单独的上下文进行隔离
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;
}
**
Status : Production Ready ✅ Last Updated : 2026-01-21 Dependencies : Node.js 20+ (Node.js 18 deprecated) or Python 3.9+ Latest Versions : playwright@1.57.0, playwright-stealth@0.0.1, puppeteer-extra-plugin-stealth@2.11.2 Browser Versions : Chromium 143.0.7499.4 | Firefox 144.0.2 | WebKit 26.0
⚠️ v1.57 Breaking Change : Playwright now uses Chrome for Testing builds instead of Chromium. This provides more authentic browser behavior but changes the browser icon and title bar.
Node.js :
npm install -D playwright
npx playwright install chromium
Python :
pip install playwright
playwright install chromium
Why this matters:
playwright install downloads browser binaries (~400MB for Chromium)chromium, firefox, or webkit~/.cache/ms-playwright/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 });
CRITICAL:
await browser.close() to avoid zombie processeswaitUntil: 'networkidle' for dynamic content (SPAs)timeout: 60000 if needed# Node.js
npx tsx scrape.ts
# Python
python scrape.py
| Feature | Playwright Local | Cloudflare Browser Rendering |
|---|---|---|
| IP Address | Residential (your ISP) | Datacenter (easily detected) |
| Stealth Plugins | Full support | Not available |
| Rate Limits | None | 2,000 requests/day free tier |
| Cost | Free (your CPU) | $5/10k requests after free tier |
| Browser Control | All Playwright features | Limited API |
| Concurrency | Your hardware limit | Account-based limits |
| Session Persistence | Full cookie/storage control | Limited session management |
| Use Case |
When to use Cloudflare : Serverless environments, simple scraping, cost-efficient at scale When to use Local : Anti-bot bypass needed, residential IP required, complex automation
⚠️ 2025 Reality Check : Stealth plugins work well against basic anti-bot measures, but advanced detection systems (Cloudflare Bot Management, PerimeterX, DataDome) have evolved significantly. The detection landscape now includes:
- Behavioral analysis (mouse patterns, scroll timing, keystroke dynamics)
- TLS fingerprinting (JA3/JA4 signatures)
- Canvas and WebGL fingerprinting
- HTTP/2 fingerprinting
Recommendations :
- Stealth plugins are a good starting point, not a complete solution
- Combine with realistic user behavior simulation (use
stepsoption)- Consider residential proxies for heavily protected sites
- "What works today may not work tomorrow" - test regularly
- For advanced scenarios, research alternatives like
nodriverorundetected-chromedriver
npm install playwright-extra playwright-stealth
For puppeteer-extra compatibility :
npm install puppeteer-extra puppeteer-extra-plugin-stealth
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',
});
Key Points:
--disable-blink-features=AutomationControlled removes navigator.webdriver flagawait 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)
);
});
// 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 });
}
}
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,
});
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);
Test your setup at: 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 });
What to check:
navigator.webdriver should be undefined (not false)✅ Use waitUntil: 'networkidle' for SPAs (React, Vue, Angular) ✅ Close browsers with await browser.close() to prevent memory leaks ✅ Wrap automation in try/catch/finally blocks ✅ Set explicit timeouts for unreliable sites ✅ Save screenshots on errors for debugging ✅ Use page.waitForSelector() before interacting with elements ✅ Rotate user agents for high-volume scraping ✅ Test with headless: false first, then switch to headless: true
❌ Use page.click() without waiting for element (waitForSelector first) ❌ Rely on fixed setTimeout() for waits (use waitForSelector, waitForLoadState) ❌ Scrape without rate limiting (add delays between requests) ❌ Use same user agent for all requests (rotate agents) ❌ Ignore navigation errors (catch and retry with backoff) ❌ Run headless without testing headed mode first (visual debugging catches issues) ❌ Store credentials in code (use environment variables)
Playwright v1.56 introduced new methods for capturing debug information without setting up event listeners:
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);
});
When to use : Debugging test failures, verifying no console errors, auditing network activity.
The steps option provides fine-grained control over mouse movement, useful for:
// 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 });
Anti-detection benefit : Many bot detection systems look for instantaneous mouse movements. Using steps: 10 or higher simulates realistic human mouse behavior.
This skill prevents 10 documented issues:
Error : Protocol error (Target.sendMessageToTarget): Target closed. Source : https://github.com/microsoft/playwright/issues/2938 Why It Happens : Page was closed before action completed, or browser crashed Prevention :
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();
}
}
Error : TimeoutError: waiting for selector "button" failed: timeout 30000ms exceeded Source : https://playwright.dev/docs/actionability Why It Happens : Element doesn't exist, selector is wrong, or page hasn't loaded Prevention :
// 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();
Error : TimeoutError: page.goto: Timeout 30000ms exceeded. Source : https://playwright.dev/docs/navigations Why It Happens : Slow page load, infinite loading spinner, blocked by firewall Prevention :
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');
}
}
}
Error : Error: Execution context was destroyed, most likely because of a navigation. Source : https://github.com/microsoft/playwright/issues/3934 Why It Happens : SPA navigation re-rendered the element Prevention :
// Re-query element after navigation
async function safeClick(page, selector) {
await page.waitForSelector(selector);
await page.click(selector);
await page.waitForLoadState('networkidle');
}
Error : Page returns 403 or shows captcha Source : https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth Why It Happens : Site detects navigator.webdriver, datacenter IP, or fingerprint mismatch Prevention : Use stealth mode (Step 2-7 above) + residential IP
Error : Download starts but never finishes Source : https://playwright.dev/docs/downloads Why It Happens : Download event not awaited, file stream not closed Prevention :
const [download] = await Promise.all([
page.waitForEvent('download'),
page.click('a.download-link'),
]);
const path = await download.path();
await download.saveAs('./downloads/' + download.suggestedFilename());
Error : Scroll reaches bottom but no new content loads Source : https://playwright.dev/docs/input#scrolling Why It Happens : Scroll event not triggered correctly, or scroll too fast Prevention :
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;
}
Error : WebSocket connection to 'ws://...' failed Source : https://playwright.dev/docs/api/class-browser Why It Happens : Browser launched without --no-sandbox in restrictive environments Prevention :
const browser = await chromium.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
Error : Tests hang indefinitely in CI when page.pause() is present Source : GitHub Issue #38754 Why It Happens : page.pause() is ignored in headless mode but disables test timeout, causing subsequent failing assertions to hang forever Prevention :
// 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();
}
Impact : HIGH - Can cause CI pipelines to hang indefinitely on failing assertions
Error : Tests hang on permission prompts when testing browser extensions Source : GitHub Issue #38670 Why It Happens : launchPersistentContext with extensions shows non-dismissible permission prompts (clipboard-read/write, local-network-access) that cannot be auto-granted Prevention :
// 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']
});
Impact : HIGH - Blocks automated extension testing in CI/CD environments
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',
],
},
},
},
],
});
Why these settings:
trace: 'on-first-retry' - Captures full trace for debugging failed testsscreenshot: 'only-on-failure' - Saves disk spaceviewport: { width: 1920, height: 1080 } - Common desktop resolution--disable-blink-features=AutomationControlled - Removes webdriver flagWait for web server output before starting tests using regular expressions:
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}`
}
});
Benefits :
When to Use :
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;
}
When to use : Sites requiring login, scraping user-specific content
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);
}
When to use : Twitter feeds, product listings, news sites with infinite scroll
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;
}
When to use : Scraping multiple pages concurrently, comparison shopping
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();
}
When to use : Visual regression testing, page archiving, documentation
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();
}
When to use : Report generation, invoice archiving, content preservation
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"]', 'john@example.com');
// 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 });
}
When to use : Account creation, form testing, data entry automation
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');
});
When to use : Flaky networks, rate-limited APIs, unreliable sites
All templates are ready-to-use TypeScript files. Copy from ~/.claude/skills/playwright-local/templates/:
basic-scrape.ts - Simple page scrapingstealth-mode.ts - Full stealth configurationauthenticated-session.ts - Login + scrape patterninfinite-scroll.ts - Scroll until no new contentscreenshot-capture.ts - Full page screenshotspdf-generation.ts - PDF exportExample Usage:
# 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
Documentation Claude can load when needed:
references/stealth-techniques.md - Complete anti-detection guidereferences/selector-strategies.md - CSS vs XPath vs text selectorsreferences/common-blocks.md - Known blocking patterns and bypassesWhen Claude should load these : When troubleshooting bot detection, selector issues, or site-specific blocks
scripts/install-browsers.sh - Install all Playwright browsersUsage:
chmod +x ~/.claude/skills/playwright-local/scripts/install-browsers.sh
~/.claude/skills/playwright-local/scripts/install-browsers.sh
Microsoft provides an official Playwright MCP Server for AI agent integration:
# Initialize AI agent configurations
npx playwright init-agents
This generates configuration files for:
Key Features :
MCP Server Setup :
# Install globally
npm install -g @anthropic/mcp-playwright
# Or add to Claude Desktop config
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@anthropic/mcp-playwright"]
}
}
}
When to use : Building AI agents that need browser automation, integrating Playwright with Claude or other LLMs.
Playwright v1.57 introduced Speedboard in the HTML reporter - a dedicated tab for identifying slow tests and performance bottlenecks.
Enable in Config :
export default defineConfig({
reporter: 'html',
});
View Speedboard :
npx playwright test --reporter=html
npx playwright show-report
What Speedboard Shows :
Use Cases :
waitForTimeout() callsOfficial Docker images provide consistent, reproducible environments:
Current Image (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"]
Available Tags :
:v1.57.0-noble - Ubuntu 24.04 LTS (recommended):v1.57.0-jammy - Ubuntu 22.04 LTSRun with Recommended Flags :
docker run -it --init --ipc=host my-playwright-tests
| Flag | Purpose |
|---|---|
--init | Prevents zombie processes (handles PID=1) |
--ipc=host | Prevents Chromium memory exhaustion |
--cap-add=SYS_ADMIN | Only for local dev (enables sandbox) |
Python Image :
FROM mcr.microsoft.com/playwright/python:v1.57.0-noble
Security Notes :
:latest)Claude Code can orchestrate browser automation:
// 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 workflow:
npx tsx scrape.ts https://example.com// 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 can then:
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;
}
Performance gain : 10 URLs in parallel takes ~same time as 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;
}
| Strategy | Example | When to Use |
|---|---|---|
| CSS | page.click('button.submit') | Standard HTML elements |
| XPath | page.click('xpath=//button[text()="Submit"]') | Complex DOM queries |
| Text | page.click('text=Submit') | When text is unique |
| Data attributes | page.click('[data-testid="submit"]') | Test automation |
| Method | Use Case |
|---|---|
waitUntil: 'load' | All resources loaded (default) |
waitUntil: 'domcontentloaded' | DOM ready, faster for slow sites |
waitUntil: 'networkidle' | No network activity for 500ms (SPAs) |
page.waitForSelector(selector) | Element appears in DOM |
page.waitForLoadState('networkidle') | After navigation |
page.waitForTimeout(ms) |
| Option | Value | Purpose |
|---|---|---|
headless | true/false | Show browser UI |
slowMo | 100 (ms) | Slow down for debugging |
args | ['--no-sandbox'] | Disable sandbox (Docker) |
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',
]
Required :
Optional (Node.js stealth) :
Optional (Python stealth) :
Docker Images :
mcr.microsoft.com/playwright:v1.57.0-noble - Ubuntu 24.04, Node.js 22 LTSmcr.microsoft.com/playwright/python:v1.57.0-noble - Python variant{
"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
This skill is based on production web scraping systems:
Solution :
npx playwright install chromium
# Or for all browsers:
npx playwright install
Solution : Add shared memory size
# In Dockerfile
RUN playwright install --with-deps chromium
# Run with:
docker run --shm-size=2gb your-image
Solution : Wait for content to load
await page.goto(url, { waitUntil: 'networkidle' });
await page.waitForTimeout(1000); // Extra buffer
await page.screenshot({ path: 'output.png' });
Solution : Reduce concurrency or add memory
const browser = await chromium.launch({
args: ['--disable-dev-shm-usage'], // Use /tmp instead of /dev/shm
});
Solution :
Error : Unable to locate package libicu74, Package 'libxml2' has no installation candidate Source : GitHub Issue #38874 Solution :
# 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
Temporary workaround (if Docker not an option):
# Manually install compatible libraries
sudo apt-get update
sudo apt-get install libicu72 libxml2
Use this checklist to verify your setup:
npm list playwright or pip show playwright)npx playwright install chromium)headless: false firstheadless: trueQuestions? Issues?
references/common-blocks.md for site-specific blocksnpx playwright install chromiumLast verified : 2026-01-21 | Skill version : 3.1.0 | Changes : Added 2 critical CI issues (page.pause() timeout, extension permission prompts), v1.57 features (Speedboard, webServer wait config), Ubuntu 25.10 compatibility guidance
Weekly Installs
504
Repository
GitHub Stars
643
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubPassSocketFailSnykWarn
Installed on
claude-code395
opencode346
gemini-cli314
cursor293
antigravity283
codex276
Google Slides 演示文稿创建与共享自动化教程 - 使用 Google Workspace CLI
6,500 周安装
Vite Flare Starter:开箱即用的全栈Cloudflare应用模板,集成React 19、Hono、D1
375 周安装
VectorBT + OpenAlgo Python回测环境一键配置指南 | 量化交易开发
375 周安装
Turso数据库测试指南:SQL兼容性、Rust集成与模糊测试方法详解
375 周安装
LLM硬件模型匹配器:自动检测系统配置,推荐最佳LLM模型,支持GPU/CPU/量化
375 周安装
MySQL数据库管理、优化与开发实战指南 - 生产环境技能全解析
375 周安装
Cosmos dbt Core 集成指南:Airflow 中运行 dbt 任务的完整配置清单
376 周安装
| Bot-protected sites, auth flows |
| Simple scraping, serverless |
page.click('ul > li:nth-child(2)') |
| Position-based |
| Fixed delay (avoid if possible) |
executablePath |
/path/to/chrome |
| Use custom browser |
downloadsPath | ./downloads | Download location |