browser-automation

browser-automation

热门

浏览器自动化为 Web 测试、数据抓取与 AI Agent 交互提供核心动力。

4.3万Star
6676Fork
更新于 2026/7/15
SKILL.md
只读
名称
browser-automation
描述

浏览器自动化为 Web 测试、数据抓取与 AI Agent 交互提供核心动力。

浏览器自动化

浏览器自动化为 Web 测试、数据抓取与 AI Agent 交互提供核心动力。
一个脚本是动不动就崩还是稳定可靠,关键在于你是否真正理解定位选择器、等待策略以及反检测模式。

本 Skill 涵盖 Playwright(推荐)和 Puppeteer,并针对测试、数据抓取与 Agent 级浏览器控制总结了常用模式。核心洞察:Playwright 已经赢下了自动化框架之争。除非你需要 Puppeteer 的反检测生态,或者限定纯 Chrome 环境,否则在 2025 年 Playwright 都是更优选。

关键区别:测试自动化(针对你可控、行为可预测的 App)vs 抓取/Agent 自动化(针对会防范堵截的不可控网站)。问题不同,解法也完全不同。

原则

  • 优先使用面向用户的定位器(getByRole、getByText),而不是 CSS/XPath
  • 绝不手动添加硬等待——Playwright 的自动等待(auto-wait)机制会妥善处理
  • 每个测试/任务必须在全新的上下文(context)中做到彻底隔离
  • 截图与 Trace 是你的调试救命稻草
  • CI 环境用 Headless(无头)模式,本地调试用 Headed(有头)模式
  • 反检测是一场猫鼠游戏——时刻保持更新,否则就会被封禁

能力

  • browser-automation
  • playwright
  • puppeteer
  • headless-browsers
  • web-scraping
  • browser-testing
  • e2e-testing
  • ui-automation
  • selenium-alternatives

范围划分

  • api-testing → backend
  • load-testing → performance-thinker
  • accessibility-testing → accessibility-specialist
  • visual-regression-testing → ui-design

工具栈

Frameworks

  • Playwright - 适用场景:默认首选——跨浏览器支持、内置自动等待、开发体验(DX)极佳 说明:大规模下成功率 96%,平均执行耗时 4.5s,微软背书
  • Puppeteer - 适用场景:仅限 Chrome、需要 Stealth(防检测)插件、基于已有旧代码库 说明:大规模下成功率 75%,但拥有一流的反检测生态
  • Selenium - 适用场景:遗留系统、需要特定语言绑定 说明:速度较慢、代码较繁琐,但浏览器兼容性最广

Stealth_tools

  • puppeteer-extra-plugin-stealth - 适用场景:结合 Puppeteer 绕过 Bot 检测 说明:反检测领域的行业标杆
  • playwright-extra - 适用场景:Playwright 的 Stealth 插件集 说明:puppeteer-extra 生态在 Playwright 上的移植版
  • undetected-chromedriver - 适用场景:Selenium 的反检测方案 说明:动态绕过检测机制

Cloud_browsers

  • Browserbase - 适用场景:托管型 Headless 基础设施 说明:内置 Stealth 模式与会话管理
  • BrowserStack - 适用场景:大规模跨浏览器测试 说明:真机测试、CI/CD 集成

设计模式

测试隔离模式 (Test Isolation Pattern)

每个测试均在独立且状态全新的环境里运行

适用场景:测试、任何需要结果可复现的自动化任务

TEST ISOLATION:

"""
每个测试各自拥有:

  • 独立的浏览器上下文(Cookies、LocalStorage)
  • 全新的页面
  • 干净的状态
    """

Playwright 测试示例

"""
import { test, expect } from '@playwright/test';

// 每个测试都在独立的浏览器上下文中运行
test('user can add item to cart', async ({ page }) => {
// 全新的上下文——没有任何来自其他测试的 Cookie 或本地存储
await page.goto('/products');
await page.getByRole('button', { name: 'Add to Cart' }).click();
await expect(page.getByTestId('cart-count')).toHaveText('1');
});

test('user can remove item from cart', async ({ page }) => {
// 彻底隔离——购物车初始状态为空
await page.goto('/cart');
await expect(page.getByText('Your cart is empty')).toBeVisible();
});
"""

共享身份验证模式 (Shared Authentication Pattern)

"""
// 一次性保存登录状态,供多个测试复用
// setup.ts
import { test as setup } from '@playwright/test';

setup('authenticate', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('password');
await page.getByRole('button', { name: 'Sign in' }).click();

// 等待身份验证完成
await page.waitForURL('/dashboard');

// 保存身份验证状态
await page.context().storageState({
path: './playwright/.auth/user.json'
});
});

// playwright.config.ts
export default defineConfig({
projects: [
{ name: 'setup', testMatch: /.*.setup.ts/ },
{
name: 'tests',
dependencies: ['setup'],
use: {
storageState: './playwright/.auth/user.json',
},
},
],
});
"""

面向用户的定位器模式 (User-Facing Locator Pattern)

以用户在界面上看到的方式寻找元素

适用场景:无脑首选——元素选择器的默认推荐写法

USER-FACING LOCATORS:

"""
优先顺序:

  1. getByRole - 首选:精准匹配无障碍树 (Accessibility Tree)
  2. getByText - 次选:匹配可见文本内容
  3. getByLabel - 良好:匹配表单 Label 标签
  4. getByTestId - 托底:明确定义的测试专属 ID 契约
  5. CSS/XPath - 极其脆弱,尽量避免使用
    """

正确示范(面向用户)

"""
// 基于 Role 定位 - 绝佳选择
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByRole('link', { name: 'Sign up' }).click();
await page.getByRole('heading', { name: 'Dashboard' }).isVisible();
await page.getByRole('textbox', { name: 'Search' }).fill('query');

// 基于文本内容定位
await page.getByText('Welcome back').isVisible();
await page.getByText(/Order #\d+/).click(); // 支持正则表达式

// 基于表单 Label 定位
await page.getByLabel('Email address').fill('user@example.com');
await page.getByLabel('Password').fill('secret');

// 基于占位符定位
await page.getByPlaceholder('Search...').fill('query');

// 基于 Test ID 定位(面向用户的定位方案均失效时的备选)
await page.getByTestId('submit-button').click();
"""

错误示范(脆弱代码)

"""
// 切勿使用——深度绑定页面结构的 CSS 选择器
await page.locator('.btn-primary.submit-form').click();
await page.locator('#header > div > button:nth-child(2)').click();

// 切勿使用——深度绑定结构的 XPath
await page.locator('//div[@class="form"]/button[1]').click();

// 切勿使用——自动生成的动态选择器
await page.locator('[data-v-12345]').click();
"""

过滤与链式调用

"""
// 根据包含的文本进行过滤
await page.getByRole('listitem')
.filter({ hasText: 'Product A' })
.getByRole('button', { name: 'Add to cart' })
.click();

// 根据排除文本进行过滤
await page.getByRole('listitem')
.filter({ hasNotText: 'Sold out' })
.first()
.click();

// 链式结合定位
const row = page.getByRole('row', { name: 'John Doe' });
await row.getByRole('button', { name: 'Edit' }).click();
"""

自动等待模式 (Auto-Wait Pattern)

充分利用 Playwright 的自动等待机制,切勿手动加等待

适用场景:使用 Playwright 时的铁律

AUTO-WAIT PATTERN:

"""
Playwright 会自动等待:

  • 元素挂载至 DOM
  • 元素处于可见状态
  • 元素状态稳定(无过渡动画)
  • 元素正常接收事件
  • 元素处于启用状态

绝对不要手动加硬等待!
"""

错误示范 - 手动硬等待

"""
// 千万别这么写
await page.goto('/dashboard');
await page.waitForTimeout(2000); // 错误!随意的固定超时等待
await page.click('.submit-button');

// 千万别这么写
await page.waitForSelector('.loading-spinner', { state: 'hidden' });
await page.waitForTimeout(500); // “保险起见加一下”——不行!
"""

正确示范 - 让自动等待发挥作用

"""
// 自动等待按钮变为可点击状态
await page.getByRole('button', { name: 'Submit' }).click();

// 自动等待文本出现
await expect(page.getByText('Success!')).toBeVisible();

// 自动等待页面跳转完成
await page.goto('/dashboard');
// 页面就绪——无需任何手动等待
"""

确实需要等待的特例场景

"""
// 等待特定的网络请求
const responsePromise = page.waitForResponse(
response => response.url().includes('/api/data')
);
await page.getByRole('button', { name: 'Load' }).click();
const response = await responsePromise;

// 等待 URL 变化
await Promise.all([
page.waitForURL('**/dashboard'),
page.getByRole('button', { name: 'Login' }).click(),
]);

// 等待文件下载
const downloadPromise = page.waitForEvent('download');
await page.getByText('Export CSV').click();
const download = await downloadPromise;
"""

隐身浏览器模式 (Stealth Browser Pattern)

在爬虫或数据抓取时绕过 Bot 自动化检测

适用场景:抓取带有防爬/反自动化防护的目标网站

STEALTH BROWSER PATTERN:

"""
Bot 检测通常会检查:

  • navigator.webdriver 属性标志
  • Chrome DevTools Protocol (CDP) 痕迹
  • 浏览器指纹一致性 (Browser fingerprint)
  • 行为模式(极其精准的时间间隔、没有任何鼠标轨迹)
  • 无头模式 (Headless) 特征标记
    """

Puppeteer Stealth(最佳反检测方案)

"""
import puppeteer from 'puppeteer-extra';
import StealthPlugin from 'puppeteer-extra-plugin-stealth';

puppeteer.use(StealthPlugin());

const browser = await puppeteer.launch({
headless: 'new',
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-blink-features=AutomationControlled',
],
});

const page = await browser.newPage();

// 设置真实的视口尺寸
await page.setViewport({ width: 1920, height: 1080 });

// 模拟真实的 User-Agent
await page.setUserAgent(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' +
'(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
);

// 模拟人类行为进行页面导航
await page.goto('https://target-site.com', {
waitUntil: 'networkidle0',
});
"""

Playwright Stealth

"""
import { chromium } from 'playwright-extra';
import stealth from 'puppeteer-extra-plugin-stealth';

chromium.use(stealth());

const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
userAgent: 'Mozilla/5.0 ...',
locale: 'en-US',
timezoneId: 'America/New_York',
});
"""

拟人化行为操作 (Human-Like Behavior)

"""
// 操作之间加入随机延迟
const randomDelay = (min: number, max: number) =>
new Promise(r => setTimeout(r, Math.random() * (max - min) + min));

await page.goto(url);
await randomDelay(500, 1500);

// 点击前先模拟真实鼠标移动
const button = await page.$('button.submit');
const box = await button.boundingBox();
await page.mouse.move(
box.x + box.width / 2,
box.y + box.height / 2,
{ steps: 10 } // 像真人一样分步移动
);
await randomDelay(100, 300);
await button.click();

// 滚动页面时保持自然节奏
await page.evaluate(() => {
window.scrollBy({
top: 300 + Math.random() * 200,
behavior: 'smooth'
});
});
"""

容错复原模式 (Error Recovery Pattern)

通过截图与重试机制优雅处理异常失败

适用场景:任何生产级别的自动化系统

ERROR RECOVERY PATTERN:

失败时自动截图与 Trace

"""
// playwright.config.ts
export default defineConfig({
use: {
screenshot: 'only-on-failure',
trace: 'retain-on-failure',
video: 'retain-on-failure',
},
retries: 2, // 重试失败的测试
});
"""

附带调试信息的 Try-Catch

"""
async function scrapeProduct(page: Page, url: string) {
try {
await page.goto(url, { timeout: 30000 });

const title = await page.getByRole('heading', { level: 1 }).textContent();
const price = await page.getByTestId('price').textContent();

return { title, price, success: true };

} catch (error) {
// 捕获调试信息
const screenshot = await page.screenshot({
path: errors/${Date.now()}-error.png,
fullPage: true
});

const html = await page.content();
await fs.writeFile(`errors/${Date.now()}-page.html`, html);

console.error({
  url,
  error: error.message,
  currentUrl: page.url(),
});

return { success: false, error: error.message };

}
}
"""

指数退避重试 (Retry with Exponential Backoff)

"""
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries = 3,
baseDelay = 1000
): Promise<T> {
let lastError: Error;

for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;

  if (attempt < maxRetries - 1) {
    const delay = baseDelay * Math.pow(2, attempt);
    const jitter = delay * 0.1 * Math.random();
    await new Promise(r => setTimeout(r, delay + jitt