
AAS Core 浏览器自动化指南用 Playwright 构建稳定、可观测、可复现的浏览器检查【免费下载链接】agentic-awesome-skillsAAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and planning, backed by 2,600 agentic skills. Includes CLI, local MCP, catalog, plugins, and Workbench.项目地址: https://gitcode.com/GitHub_Trending/an/agentic-awesome-skills本文以 AASAgentic Awesome Skills仓库中browser-automation技能的知识主体 详细指南 为骨架并结合其根定义 SKILL.md 及仓库索引数据系统讲解在 Agent 工作流中如何编写可靠的浏览器自动化以用户视角语义定位器、有界事件等待、测试隔离与显式结果验证为四大支柱覆盖从单测到 CI 失败诊断、从并行抓取到网络拦截的完整实战链路。读完本文你将掌握一套可直接落地的 Playwright 自动化规范、可复制的配置与代码模板以及一套能快速定位 CI 偶发失败的原因排查方法论。一、技能定位与适用范围browser-automation是 AAS 仓库中的一项critical 风险等级技能见 SKILL.md 元数据其核心定位是构建基于可观测 UI 状态的可靠浏览器检查强调语义化定位器、有界等待、隔离的测试数据与显式的结果验证。该技能声明的能力标签包括browser-automation、playwright、puppeteer、headless-browsersweb-scraping、browser-testing、e2e-testing、ui-automation、selenium-alternatives在仓库索引中该技能与research、chatgpt、decision-support等技能一样被编入自动化类目参见 catalog.json 中的风险分级与标签体系属于 Agent 在“需要验证真实浏览器工作流、诊断 UI 时序失败或收集被明确授权的页面数据”时的首选工具。明确的适用范围边界来自 SKILL.md该技能用于验证真实浏览器工作流、诊断 UI 时序失败、收集显式授权的页面数据。不属于其范畴的场景参见 详细指南API 测试 → 转交backend负载测试 → 转交performance-thinker无障碍测试 → 转交accessibility-specialist视觉回归测试 → 转交ui-design二、核心原则Principles详细指南开篇给出了六条贯穿全文的工程原则它们是后续所有模式与反模式判定的依据优先使用用户视角定位器getByRole、getByText而非 CSS/XPath——选择元素的方式应与用户看到它们的方式一致偏好可观测断言与有界事件等待固定延时不能证明页面就绪隔离自动化测试状态对于交互式任务保留用户已选择的已认证上下文截图与 Trace 是调试的生命线CI 用无头模式调试用有头模式将访问拒绝、CAPTCHA 与锁定的桌面视为明确的边界需要时使用经授权的 API 或用户交互而不是绕过。其中第 3 条在 SKILL.md 中被进一步强化不要把“对已有认证浏览器的交互”替换成全新的未认证会话也不要提取凭据来让自动化测试通过。这既是工程规范也是安全边界。三、框架选型先看现有 runner再谈对比详细指南的 Tooling 一节给出了务实的选型建议优先使用项目中已存在的 runner只要它能驱动所需浏览器与环境即可对比维度包括支持的浏览器、fixture、无障碍定位器、诊断能力与部署约束本技能不提供任何“框架成功率”的实测基准也不要求隐身插件或禁用浏览器沙箱。换言之Playwright、Puppeteer、Selenium 的集成方式各不相同应基于实际需求选择而不是基于未经证实的“成功率”宣称。这一立场也体现在 SKILL.md 的修订说明中“移除了未经证实的对比与绕过默认项澄清了等待与证据限制。”四、核心模式Patterns详细指南以代码片段形式给出了八个核心模式以下逐一展开并补齐参数说明。4.1 测试隔离模式Test Isolation Pattern适用场景所有需要可复现性的测试与自动化任务。每个测试获得自己独立的Browser contextcookie、storage全新 page干净的状态Playwright 测试运行器默认即为每个test注入全新的pagefixture天然满足隔离手动编写时则通过browser.newContext()显式创建隔离上下文并在结束后context.close()清理。4.2 共享认证模式Shared Authentication Pattern适用场景多个测试都需要登录态但不想在每次测试里重复走登录流程。核心做法使用专用且已授权的测试账号把登录状态保存为 storage-state 文件跨测试复用。安全要求指南强调storage-state 文件包含可复用的凭据必须不进入 Git / 公开产物限制访问权限设置过期时间。同时要清醒认识共享认证并不会在测试之间隔离后端数据变更。完整可运行示例保存登录态// setup.ts import { test as setup } from playwright/test; setup(authenticate, async ({ page }) { await page.goto(/login); await page.getByLabel(Email).fill(userexample.com); await page.getByLabel(Password).fill(password); await page.getByRole(button, { name: Sign in }).click(); // Wait for auth to complete await page.waitForURL(/dashboard); // Save authentication state await page.context().storageState({ path: ./playwright/.auth/user.json }); });在配置中通过 project 依赖与storageState复用// playwright.config.ts export default defineConfig({ projects: [ { name: setup, testMatch: /.*\.setup\.ts/ }, { name: tests, dependencies: [setup], use: { storageState: ./playwright/.auth/user.json, }, }, ], });4.3 用户视角定位器模式User-Facing Locator Pattern适用场景始终——这是选择器的默认方案。定位器优先级从最优到最次getByRole—— 最佳匹配无障碍树getByText—— 良好匹配可见内容getByLabel—— 良好匹配表单标签getByTestId—— 兜底显式测试契约CSS/XPath —— 最后手段脆弱避免。4.4 过滤与链式Filtering and Chaining在列表型 UI 中先按可见文本过滤到目标行再在行内定位操作按钮// Filter by containing text await page.getByRole(listitem) .filter({ hasText: Product A }) .getByRole(button, { name: Add to cart }) .click(); // Filter by NOT containing await page.getByRole(listitem) .filter({ hasNotText: Sold out }) .first() .click(); // Chain locators const row page.getByRole(row, { name: John Doe }); await row.getByRole(button, { name: Edit }).click();4.5 自动等待模式Auto-Wait Pattern适用场景使用 Playwright 时始终如此——永远不要手动添加等待。Playwright 会自动等待元素已附加到 DOM元素可见元素稳定无动画元素可接收事件元素已启用错误示范切勿如此// DONT DO THIS await page.goto(/dashboard); await page.waitForTimeout(2000); // NO! Arbitrary wait await page.click(.submit-button); // DONT DO THIS await page.waitForSelector(.loading-spinner, { state: hidden }); await page.waitForTimeout(500); // Just to be safe - NO!正确示范交给自动等待// Auto-waits for button to be clickable await page.getByRole(button, { name: Submit }).click(); // Auto-waits for text to appear await expect(page.getByText(Success!)).toBeVisible(); // Auto-waits for navigation to complete await page.goto(/dashboard); // Navigation finished; still assert the application state needed by the next action.确实需要等待时使用有界事件等待而非睡眠// Wait for specific network request const responsePromise page.waitForResponse( response response.url().includes(/api/data) ); await page.getByRole(button, { name: Load }).click(); const response await responsePromise; // Wait for URL change await Promise.all([ page.waitForURL(**/dashboard), page.getByRole(button, { name: Login }).click(), ]); // Wait for download const downloadPromise page.waitForEvent(download); await page.getByText(Export CSV).click(); const download await downloadPromise;注意 SKILL.md 的边界说明自动等待检查的是“可操作”而不是业务正确性或后端写入成功。导航完成后仍需对下一步所需的应用状态做断言。4.6 错误恢复模式Error Recovery Pattern适用场景任何生产级自动化。三个层次失败自动截图 Trace、try-catch 捕获调试信息、指数退避重试。自动截图配置CI 失败诊断基础// playwright.config.ts export default defineConfig({ use: { screenshot: only-on-failure, trace: retain-on-failure, video: retain-on-failure, }, retries: 2, // Retry failed tests });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) { // Capture debug info const screenshot await page.screenshot({ path: errors/${Date.now()}-error.png, fullPage: true }); // Capture full HTML only when specifically authorized; it can contain secrets. console.error({ pathname: new URL(url).pathname, errorType: error instanceof Error ? error.name : UnknownError, }); return { success: false, error: Product read failed }; } }指数退避重试带抖动与预算校验async function withRetryT( fn: () PromiseT, maxRetries 3, baseDelay 1000 ): PromiseT { if (!Number.isInteger(maxRetries) || maxRetries 1 || maxRetries 10) throw new Error(Invalid attempt budget); let lastError: unknown; 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 jitter)); } } } throw lastError; } // Usage const result await withRetry( async () { const result await scrapeProduct(page, url); if (!result.success) throw new Error(Read failed); return result; }, 3, 2000 );指南特别提醒重试“读”通常是安全的但重试“结账、删除、发消息”可能产生重复副作用参见 SKILL.md。重复此类操作前必须验证当前状态。4.7 并行执行模式Parallel Execution Pattern适用场景多个相互独立的页面或测试。Playwright Test 并行化// playwright.config.ts export default defineConfig({ fullyParallel: true, workers: process.env.CI ? 4 : undefined, // CI: 4 workers, local: CPU-based projects: [ { name: chromium, use: { ...devices[Desktop Chrome] } }, { name: firefox, use: { ...devices[Desktop Firefox] } }, { name: webkit, use: { ...devices[Desktop Safari] } }, ], });基于独立 Browser Context 的并行抓取每个 context 天然隔离const browser await chromium.launch(); const urls [url1, url2, url3, url4, url5]; // Create multiple contexts - each is isolated const results await Promise.all( urls.map(async (url) { const context await browser.newContext(); const page await context.newPage(); try { await page.goto(url); const data await extractData(page); return { url, data, success: true }; } catch (error) { return { url, error: error.message, success: false }; } finally { await context.close(); } }) ); await browser.close();限流并行处理配合p-limit控制并发上限import pLimit from p-limit; const limit pLimit(5); // Max 5 concurrent const results await Promise.all( urls.map(url limit(async () { const context await browser.newContext(); const page await context.newPage(); // Random delay between requests await new Promise(r setTimeout(r, Math.random() * 2000)); try { return await scrapePage(page, url); } finally { await context.close(); } })) );4.8 网络拦截模式Network Interception Pattern适用场景测试中 Mock/阻断/修改网络请求阻断广告与统计提升抓取速度。阻断非必要资源与跟踪脚本await page.route(**/*, (route) { const url route.request().url(); const resourceType route.request().resourceType(); // Block images, fonts, analytics for faster scraping if ([image, font, media].includes(resourceType)) { return route.abort(); } // Block tracking/analytics if (url.includes(google-analytics) || url.includes(facebook.com/tr)) { return route.abort(); } return route.continue(); });Mock API 响应测试await page.route(**/api/products, async (route) { await route.fulfill({ status: 200, contentType: application/json, body: JSON.stringify([ { id: 1, name: Mock Product, price: 99.99 }, ]), }); }); // Now page will receive mocked data await page.goto(/products);捕获 API 响应有界观测const apiResponses: any[] []; page.on(response, async (response) { if (response.url().includes(/api/)) { // Do not accumulate unbounded response bodies or raw query strings. if (apiResponses.length 100) apiResponses.push({ pathname: new URL(response.url()).pathname, status: response.status(), }); } }); await page.goto(/dashboard); // This bounded observation is incomplete until the required application event is observed.注意三处安全与正确性约束不无限累积响应体与原始查询串有界观测在“必需的应用事件被观察到之前”是不完整的资源阻断与 Mock 改变了环境不能据此断言未修改的生产行为SKILL.md。五、Sharp Edges八大高频坑与其修复详细指南用“严重度 症状 根因 修复”的结构剖析了八个反模式这是全文最实用的部分。5.1 用 waitForTimeout 代替正规等待严重度CRITICAL症状本地通过、CI 失败9 次通过第 10 次挂随机 “Element not found”本应 3 秒的测试跑 30 秒以上。根因waitForTimeout是固定延时。页面 500ms 加载完你仍等 2000msCI 慢到 2100ms 你就失败。不存在正确的固定值——它要么太短要么太长。修复删除所有waitForTimeout需要特定条件时用expect(...).toBeVisible()、waitForURL、waitForResponse动画场景交给自动等待“元素稳定”定时器只用于真实的 timer 行为/退避绝不用 sleep 证明 UI 就绪。5.2 CSS 选择器绑死样式类严重度HIGH根因CSS 类名是实现细节而非语义前端重构btn-primary→button--primary后行为未变测试却崩。修复改用用户视角定位器必须用 CSS 时退化为data-testid。// WRONG - Tied to CSS: await page.locator(.btn-primary.submit-form).click(); await page.locator(#sidebar div.menu ul li:nth-child(3)).click(); // CORRECT - User-facing: await page.getByRole(button, { name: Submit }).click(); await page.getByRole(menuitem, { name: Settings }).click(); // If you must use CSS, use>// Playwright Test (automatic isolation): test(first test, async ({ page }) { // Fresh context, fresh page }); test(second test, async ({ page }) { // Completely isolated from first test }); // Manual isolation: const context await browser.newContext(); // Fresh context const page await context.newPage(); // ... test code ... await context.close(); // Clean up共享认证的正确姿势文件级复用// 1. Save auth state to file await context.storageState({ path: ./auth.json }); // 2. Reuse in other tests const context await browser.newContext({ storageState: ./auth.json });两条铁律绝不在测试中修改全局状态绝不依赖前一个测试的动作。5.4 CI 失败不采集 Trace严重度MEDIUM根因CI 无头运行在异构硬件上时序与网络都不同没有 Trace 就看不到“实际发生了什么”——动作序列、网络请求、控制台日志。修复失败保留 Trace。// playwright.config.ts: export default defineConfig({ use: { trace: retain-on-failure, screenshot: only-on-failure, video: retain-on-failure, }, outputDir: ./test-results, });本地查看授权范围内的 Tracenpx playwright show-trace test-results/path/to/trace.zip。CI 中复用仓库已有的、固定到已审查 commit 的产物上传 Action。Trace 可能包含页面数据、请求与凭据上传前先设置访问与保留策略尽量使用合成测试数据。Trace 能提供动作时间线、每步截图、网络请求与响应、控制台日志、DOM 快照。5.5 有头通过、无头失败严重度MEDIUM根因无头浏览器没有显示设备影响部分 CSS可见性计算、视口尺寸与字体渲染部分动画行为不同弹窗可能不工作。修复设置一致的视口。const browser await chromium.launch({ headless: true, }); const context await browser.newContext({ viewport: { width: 1280, height: 720 }, }); // Or in config: export default defineConfig({ use: { viewport: { width: 1280, height: 720 }, }, });调试无头失败的四个步骤本地有头复现npx playwright test --headed放慢观察npx playwright test --debugCI 失败用npx playwright show-trace trace.zip顽固问题在失败点截图await page.screenshot({ path: debug.png, fullPage: true })。5.6 被限流封禁严重度HIGH症状前 50 页正常之后 429请求全部失败IP 被拉黑成功请求后开始出 CAPTCHA。根因站点监控请求模式单 IP 每秒 100 个请求显然是自动化。修复请求间加延时并尊重服务文档声明的限额与 Retry-After 指引。const randomDelay () new Promise(r setTimeout(r, 1000 Math.random() * 2000)); for (const url of urls) { await randomDelay(); // 1-3 second delay await page.goto(url); // ... scrape ... }使用有界并发与最大重试预算不要通过轮换代理或身份来绕过拒绝。5.7 未处理新窗口/弹窗严重度MEDIUM根因target_blank链接打开新窗口而你的 page 引用仍指向原页面新窗口存在但你没在监听。修复在触发动作之前先注册等待。// New window/tab: const pagePromise context.waitForEvent(page); await page.getByRole(link, { name: Open in new tab }).click(); const newPage await pagePromise; await newPage.waitForLoadState(); // Now interact with new page await expect(newPage.getByRole(heading)).toBeVisible(); // Close when done await newPage.close(); // Popup windows: const popupPromise page.waitForEvent(popup); await page.getByRole(button, { name: Open popup }).click(); const popup await popupPromise; await popup.waitForLoadState(); // Multiple windows: const pages context.pages(); // Get all open pages5.8 无法操作 iframe 内的元素严重度MEDIUM根因iframe 是独立文档page.locator只搜索主 frame。修复显式获取 frame / 使用frameLocator。// By frame name: const frame page.frame(payment-iframe); await frame.getByRole(textbox, { name: Card number }).fill(4242...); // By selector: const frame page.frameLocator(iframe#payment); await frame.getByRole(textbox, { name: Card number }).fill(4242...); // Nested iframes: const outer page.frameLocator(iframe#outer); const inner outer.frameLocator(iframe#inner); await inner.getByRole(button).click(); // Wait for iframe to load: await page.waitForSelector(iframe#payment); const frame page.frameLocator(iframe#payment); await frame.getByText(Secure Payment).waitFor();六、Validation Checks静态校验清单详细指南还提供了一套可机读的校验规则适合接入 Lint 或评审流程按严重度分级严重度检查项消息要点ERROR使用waitForTimeout移除它用waitForResponse、waitForURL或断言替代WARNING测试代码中使用setTimeout不可靠替换为expect(...).toBeVisible()或page.waitFor*WARNING自定义 sleep 函数表明等待策略不当使用 Playwright 内置等待WARNINGCSS 类选择器脆弱优先getByRole/getByText/getByLabel/getByTestIdWARNINGnth-child定位位置型选择器极脆弱DOM 顺序变化即失效WARNING框架自动生成选择器每次构建都可能变化改用data-testidWARNING无错误处理的抓取循环单页失败会拖垮整个抓取加 try/catchINFOXPath 选择器最后手段仅在复杂 DOM 遍历时使用这组规则与仓库根技能 SKILL.md 中的 Good/Bad Examples 一脉相承getByRole是“最好的选择”自动生成选择器如[data-v-12345]则被明确列入禁止清单参见 SKILL.md。七、协作与委派Collaboration浏览器自动化往往只是更大任务的一环详细指南给出了明确的委派触发条件需要浏览器之外的完整桌面控制 →computer-use-agents非浏览器应用的桌面自动化需要与浏览器测试并行的 API 测试 →backendAPI 集成与测试模式需要整体测试策略 →test-architect需要视觉回归测试 →ui-design视觉对比与设计验证需要在工作流中运行浏览器自动化 →workflow-automation浏览器任务的持久化执行为 Agent 构建浏览器工具 →agent-tool-builder面向 LLM Agent 的工具设计模式该技能配合良好agent-tool-builder、workflow-automation、computer-use-agents、test-architect。八、安全与局限性必须内化的边界最后是 SKILL.md 中不可忽略的边界声明自动等待只证明“可操作”不证明业务正确或后端写入成功截图、HTML、Trace 与 auth-state 文件可能含隐私信息——只采集被授权范围重试读可安全重试写操作结账/删除/发消息可能重复副作用——重复前先验证状态资源阻断与 Mock 改变环境不能代表未修改的生产行为示例依赖项目自身的 imports、runner 与 fixture 路由本技能不安装任何浏览器、服务或账号。一个值得复盘的端到端示例来自 SKILL.md 的 worked example从本地 Web 应用导出已审阅的 JSON 文件。前提是应用已在运行、目标浏览器可用、准备了合成表单值。正确流程是观察导出控件 →点击前注册 download 事件→ 检查下载的 JSON → 确认编辑输入会使旧预览失效。期望结果一个包含已审阅值的文件无隐藏项目数据、无网络提交。这个示例完整演示了“先注册事件再触发动作 显式验证产物 不越界”的规范。结语浏览器自动化失败的根源几乎总是同一个把实现细节CSS 类、固定延时、共享状态当作行为契约。AAS 的browser-automation技能给出的解药是——用用户视角定位语义用事件等待取代睡眠用隔离换取可复现用 Trace 换取可诊断用显式验证换取可信结论。将本文的八个模式与八大反模式对照落地即可把“本地过、CI 挂”的玄学调试变成有截图、有 Trace、有根因的确定性工程。该技能的完整源文件位于仓库的 技能定义 与 详细指南需要全文执行时请完整阅读后者。【免费下载链接】agentic-awesome-skillsAAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and planning, backed by 2,600 agentic skills. Includes CLI, local MCP, catalog, plugins, and Workbench.项目地址: https://gitcode.com/GitHub_Trending/an/agentic-awesome-skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考