{"id":"e9d560a1-f9ce-443b-839c-7c1116245cda","shortId":"7vZf69","kind":"skill","title":"browser-automation","tagline":"Browser automation powers web testing, scraping, and AI agent","description":"# Browser Automation\n\nBrowser automation powers web testing, scraping, and AI agent interactions.\nThe difference between a flaky script and a reliable system comes down to\nunderstanding selectors, waiting strategies, and anti-detection patterns.\n\nThis skill covers Playwright (recommended) and Puppeteer, with patterns for\ntesting, scraping, and agentic browser control. Key insight: Playwright won\nthe framework war. Unless you need Puppeteer's stealth ecosystem or are\nChrome-only, Playwright is the better choice in 2025.\n\nCritical distinction: Testing automation (predictable apps you control) vs\nscraping/agent automation (unpredictable sites that fight back). Different\nproblems, different solutions.\n\n## Principles\n\n- Use user-facing locators (getByRole, getByText) over CSS/XPath\n- Never add manual waits - Playwright's auto-wait handles it\n- Each test/task should be fully isolated with fresh context\n- Screenshots and traces are your debugging lifeline\n- Headless for CI, headed for debugging\n- Anti-detection is cat-and-mouse - stay current or get blocked\n\n## Capabilities\n\n- browser-automation\n- playwright\n- puppeteer\n- headless-browsers\n- web-scraping\n- browser-testing\n- e2e-testing\n- ui-automation\n- selenium-alternatives\n\n## Scope\n\n- api-testing → backend\n- load-testing → performance-thinker\n- accessibility-testing → accessibility-specialist\n- visual-regression-testing → ui-design\n\n## Tooling\n\n### Frameworks\n\n- Playwright - When: Default choice - cross-browser, auto-waiting, best DX Note: 96% success rate, 4.5s avg execution, Microsoft-backed\n- Puppeteer - When: Chrome-only, need stealth plugins, existing codebase Note: 75% success rate at scale, but best stealth ecosystem\n- Selenium - When: Legacy systems, specific language bindings Note: Slower, more verbose, but widest browser support\n\n### Stealth_tools\n\n- puppeteer-extra-plugin-stealth - When: Need to bypass bot detection with Puppeteer Note: Gold standard for anti-detection\n- playwright-extra - When: Stealth plugins for Playwright Note: Port of puppeteer-extra ecosystem\n- undetected-chromedriver - When: Selenium anti-detection Note: Dynamic bypass of detection\n\n### Cloud_browsers\n\n- Browserbase - When: Managed headless infrastructure Note: Built-in stealth mode, session management\n- BrowserStack - When: Cross-browser testing at scale Note: Real devices, CI integration\n\n## Patterns\n\n### Test Isolation Pattern\n\nEach test runs in complete isolation with fresh state\n\n**When to use**: Testing, any automation that needs reproducibility\n\n# TEST ISOLATION:\n\n\"\"\"\nEach test gets its own:\n- Browser context (cookies, storage)\n- Fresh page\n- Clean state\n\"\"\"\n\n## Playwright Test Example\n\"\"\"\nimport { test, expect } from '@playwright/test';\n\n// Each test runs in isolated browser context\ntest('user can add item to cart', async ({ page }) => {\n  // Fresh context - no cookies, no storage from other tests\n  await page.goto('/products');\n  await page.getByRole('button', { name: 'Add to Cart' }).click();\n  await expect(page.getByTestId('cart-count')).toHaveText('1');\n});\n\ntest('user can remove item from cart', async ({ page }) => {\n  // Completely isolated - cart is empty\n  await page.goto('/cart');\n  await expect(page.getByText('Your cart is empty')).toBeVisible();\n});\n\"\"\"\n\n## Shared Authentication Pattern\n\"\"\"\n// Save auth state once, reuse across tests\n// setup.ts\nimport { test as setup } from '@playwright/test';\n\nsetup('authenticate', async ({ page }) => {\n  await page.goto('/login');\n  await page.getByLabel('Email').fill('user@example.com');\n  await page.getByLabel('Password').fill('password');\n  await page.getByRole('button', { name: 'Sign in' }).click();\n\n  // Wait for auth to complete\n  await page.waitForURL('/dashboard');\n\n  // Save authentication state\n  await page.context().storageState({\n    path: './playwright/.auth/user.json'\n  });\n});\n\n// playwright.config.ts\nexport default defineConfig({\n  projects: [\n    { name: 'setup', testMatch: /.*\\.setup\\.ts/ },\n    {\n      name: 'tests',\n      dependencies: ['setup'],\n      use: {\n        storageState: './playwright/.auth/user.json',\n      },\n    },\n  ],\n});\n\"\"\"\n\n### User-Facing Locator Pattern\n\nSelect elements the way users see them\n\n**When to use**: Always - the default approach for selectors\n\n# USER-FACING LOCATORS:\n\n\"\"\"\nPriority order:\n1. getByRole  - Best: matches accessibility tree\n2. getByText  - Good: matches visible content\n3. getByLabel - Good: matches form labels\n4. getByTestId - Fallback: explicit test contracts\n5. CSS/XPath - Last resort: fragile, avoid\n\"\"\"\n\n## Good Examples (User-Facing)\n\"\"\"\n// By role - THE BEST CHOICE\nawait page.getByRole('button', { name: 'Submit' }).click();\nawait page.getByRole('link', { name: 'Sign up' }).click();\nawait page.getByRole('heading', { name: 'Dashboard' }).isVisible();\nawait page.getByRole('textbox', { name: 'Search' }).fill('query');\n\n// By text content\nawait page.getByText('Welcome back').isVisible();\nawait page.getByText(/Order #\\d+/).click();  // Regex supported\n\n// By label (forms)\nawait page.getByLabel('Email address').fill('user@example.com');\nawait page.getByLabel('Password').fill('secret');\n\n// By placeholder\nawait page.getByPlaceholder('Search...').fill('query');\n\n// By test ID (when no user-facing option works)\nawait page.getByTestId('submit-button').click();\n\"\"\"\n\n## Bad Examples (Fragile)\n\"\"\"\n// DON'T - CSS selectors tied to structure\nawait page.locator('.btn-primary.submit-form').click();\nawait page.locator('#header > div > button:nth-child(2)').click();\n\n// DON'T - XPath tied to structure\nawait page.locator('//div[@class=\"form\"]/button[1]').click();\n\n// DON'T - Auto-generated selectors\nawait page.locator('[data-v-12345]').click();\n\"\"\"\n\n## Filtering and Chaining\n\"\"\"\n// Filter by containing text\nawait page.getByRole('listitem')\n  .filter({ hasText: 'Product A' })\n  .getByRole('button', { name: 'Add to cart' })\n  .click();\n\n// Filter by NOT containing\nawait page.getByRole('listitem')\n  .filter({ hasNotText: 'Sold out' })\n  .first()\n  .click();\n\n// Chain locators\nconst row = page.getByRole('row', { name: 'John Doe' });\nawait row.getByRole('button', { name: 'Edit' }).click();\n\"\"\"\n\n### Auto-Wait Pattern\n\nLet Playwright wait automatically, never add manual waits\n\n**When to use**: Always with Playwright\n\n# AUTO-WAIT PATTERN:\n\n\"\"\"\nPlaywright waits automatically for:\n- Element to be attached to DOM\n- Element to be visible\n- Element to be stable (not animating)\n- Element to receive events\n- Element to be enabled\n\nNEVER add manual waits!\n\"\"\"\n\n## Wrong - Manual Waits\n\"\"\"\n// DON'T DO THIS\nawait page.goto('/dashboard');\nawait page.waitForTimeout(2000);  // NO! Arbitrary wait\nawait page.click('.submit-button');\n\n// DON'T DO THIS\nawait page.waitForSelector('.loading-spinner', { state: 'hidden' });\nawait page.waitForTimeout(500);  // \"Just to be safe\" - NO!\n\"\"\"\n\n## Correct - Let Auto-Wait Work\n\"\"\"\n// Auto-waits for button to be clickable\nawait page.getByRole('button', { name: 'Submit' }).click();\n\n// Auto-waits for text to appear\nawait expect(page.getByText('Success!')).toBeVisible();\n\n// Auto-waits for navigation to complete\nawait page.goto('/dashboard');\n// Page is ready - no manual wait needed\n\"\"\"\n\n## When You DO Need to Wait\n\"\"\"\n// Wait for specific network request\nconst responsePromise = page.waitForResponse(\n  response => response.url().includes('/api/data')\n);\nawait page.getByRole('button', { name: 'Load' }).click();\nconst response = await responsePromise;\n\n// Wait for URL change\nawait Promise.all([\n  page.waitForURL('**/dashboard'),\n  page.getByRole('button', { name: 'Login' }).click(),\n]);\n\n// Wait for download\nconst downloadPromise = page.waitForEvent('download');\nawait page.getByText('Export CSV').click();\nconst download = await downloadPromise;\n\"\"\"\n\n### Stealth Browser Pattern\n\nAvoid bot detection for scraping\n\n**When to use**: Scraping sites with anti-bot protection\n\n# STEALTH BROWSER PATTERN:\n\n\"\"\"\nBot detection checks for:\n- navigator.webdriver property\n- Chrome DevTools protocol artifacts\n- Browser fingerprint inconsistencies\n- Behavioral patterns (perfect timing, no mouse movement)\n- Headless indicators\n\"\"\"\n\n## Puppeteer Stealth (Best Anti-Detection)\n\"\"\"\nimport puppeteer from 'puppeteer-extra';\nimport StealthPlugin from 'puppeteer-extra-plugin-stealth';\n\npuppeteer.use(StealthPlugin());\n\nconst browser = await puppeteer.launch({\n  headless: 'new',\n  args: [\n    '--no-sandbox',\n    '--disable-setuid-sandbox',\n    '--disable-blink-features=AutomationControlled',\n  ],\n});\n\nconst page = await browser.newPage();\n\n// Set realistic viewport\nawait page.setViewport({ width: 1920, height: 1080 });\n\n// Realistic user agent\nawait page.setUserAgent(\n  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' +\n  '(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'\n);\n\n// Navigate with human-like behavior\nawait page.goto('https://target-site.com', {\n  waitUntil: 'networkidle0',\n});\n\"\"\"\n\n## Playwright Stealth\n\"\"\"\nimport { chromium } from 'playwright-extra';\nimport stealth from 'puppeteer-extra-plugin-stealth';\n\nchromium.use(stealth());\n\nconst browser = await chromium.launch({ headless: true });\nconst context = await browser.newContext({\n  viewport: { width: 1920, height: 1080 },\n  userAgent: 'Mozilla/5.0 ...',\n  locale: 'en-US',\n  timezoneId: 'America/New_York',\n});\n\"\"\"\n\n## Human-Like Behavior\n\"\"\"\n// Random delays between actions\nconst randomDelay = (min: number, max: number) =>\n  new Promise(r => setTimeout(r, Math.random() * (max - min) + min));\n\nawait page.goto(url);\nawait randomDelay(500, 1500);\n\n// Mouse movement before click\nconst button = await page.$('button.submit');\nconst box = await button.boundingBox();\nawait page.mouse.move(\n  box.x + box.width / 2,\n  box.y + box.height / 2,\n  { steps: 10 }  // Move in steps like a human\n);\nawait randomDelay(100, 300);\nawait button.click();\n\n// Scroll naturally\nawait page.evaluate(() => {\n  window.scrollBy({\n    top: 300 + Math.random() * 200,\n    behavior: 'smooth'\n  });\n});\n\"\"\"\n\n### Error Recovery Pattern\n\nHandle failures gracefully with screenshots and retries\n\n**When to use**: Any production automation\n\n# ERROR RECOVERY PATTERN:\n\n## Automatic Screenshot on Failure\n\"\"\"\n// playwright.config.ts\nexport default defineConfig({\n  use: {\n    screenshot: 'only-on-failure',\n    trace: 'retain-on-failure',\n    video: 'retain-on-failure',\n  },\n  retries: 2,  // Retry failed tests\n});\n\"\"\"\n\n## Try-Catch with Debug Info\n\"\"\"\nasync function scrapeProduct(page: Page, url: string) {\n  try {\n    await page.goto(url, { timeout: 30000 });\n\n    const title = await page.getByRole('heading', { level: 1 }).textContent();\n    const price = await page.getByTestId('price').textContent();\n\n    return { title, price, success: true };\n\n  } catch (error) {\n    // Capture debug info\n    const screenshot = await page.screenshot({\n      path: `errors/${Date.now()}-error.png`,\n      fullPage: true\n    });\n\n    const html = await page.content();\n    await fs.writeFile(`errors/${Date.now()}-page.html`, html);\n\n    console.error({\n      url,\n      error: error.message,\n      currentUrl: page.url(),\n    });\n\n    return { success: false, error: error.message };\n  }\n}\n\"\"\"\n\n## Retry with Exponential Backoff\n\"\"\"\nasync function withRetry<T>(\n  fn: () => Promise<T>,\n  maxRetries = 3,\n  baseDelay = 1000\n): Promise<T> {\n  let lastError: Error;\n\n  for (let attempt = 0; attempt < maxRetries; attempt++) {\n    try {\n      return await fn();\n    } catch (error) {\n      lastError = error;\n\n      if (attempt < maxRetries - 1) {\n        const delay = baseDelay * Math.pow(2, attempt);\n        const jitter = delay * 0.1 * Math.random();\n        await new Promise(r => setTimeout(r, delay + jitter));\n      }\n    }\n  }\n\n  throw lastError;\n}\n\n// Usage\nconst result = await withRetry(\n  () => scrapeProduct(page, url),\n  3,\n  2000\n);\n\"\"\"\n\n### Parallel Execution Pattern\n\nRun tests/tasks in parallel for speed\n\n**When to use**: Multiple independent pages or tests\n\n# PARALLEL EXECUTION:\n\n## Playwright Test Parallelization\n\"\"\"\n// playwright.config.ts\nexport default defineConfig({\n  fullyParallel: true,\n  workers: process.env.CI ? 4 : undefined,  // CI: 4 workers, local: CPU-based\n\n  projects: [\n    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },\n    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },\n    { name: 'webkit', use: { ...devices['Desktop Safari'] } },\n  ],\n});\n\"\"\"\n\n## Browser Contexts for Parallel Scraping\n\"\"\"\nconst browser = await chromium.launch();\n\nconst urls = ['url1', 'url2', 'url3', 'url4', 'url5'];\n\n// Create multiple contexts - each is isolated\nconst results = await Promise.all(\n  urls.map(async (url) => {\n    const context = await browser.newContext();\n    const page = await context.newPage();\n\n    try {\n      await page.goto(url);\n      const data = await extractData(page);\n      return { url, data, success: true };\n    } catch (error) {\n      return { url, error: error.message, success: false };\n    } finally {\n      await context.close();\n    }\n  })\n);\n\nawait browser.close();\n\"\"\"\n\n## Rate-Limited Parallel Processing\n\"\"\"\nimport pLimit from 'p-limit';\n\nconst limit = pLimit(5);  // Max 5 concurrent\n\nconst results = await Promise.all(\n  urls.map(url => limit(async () => {\n    const context = await browser.newContext();\n    const page = await context.newPage();\n\n    // Random delay between requests\n    await new Promise(r => setTimeout(r, Math.random() * 2000));\n\n    try {\n      return await scrapePage(page, url);\n    } finally {\n      await context.close();\n    }\n  }))\n);\n\"\"\"\n\n### Network Interception Pattern\n\nMock, block, or modify network requests\n\n**When to use**: Testing, blocking ads/analytics, modifying responses\n\n# NETWORK INTERCEPTION:\n\n## Block Unnecessary Resources\n\"\"\"\nawait page.route('**/*', (route) => {\n  const url = route.request().url();\n  const resourceType = route.request().resourceType();\n\n  // Block images, fonts, analytics for faster scraping\n  if (['image', 'font', 'media'].includes(resourceType)) {\n    return route.abort();\n  }\n\n  // Block tracking/analytics\n  if (url.includes('google-analytics') ||\n      url.includes('facebook.com/tr')) {\n    return route.abort();\n  }\n\n  return route.continue();\n});\n\"\"\"\n\n## Mock API Responses (Testing)\n\"\"\"\nawait page.route('**/api/products', async (route) => {\n  await route.fulfill({\n    status: 200,\n    contentType: 'application/json',\n    body: JSON.stringify([\n      { id: 1, name: 'Mock Product', price: 99.99 },\n    ]),\n  });\n});\n\n// Now page will receive mocked data\nawait page.goto('/products');\n\"\"\"\n\n## Capture API Responses\n\"\"\"\nconst apiResponses: any[] = [];\n\npage.on('response', async (response) => {\n  if (response.url().includes('/api/')) {\n    const data = await response.json().catch(() => null);\n    apiResponses.push({\n      url: response.url(),\n      status: response.status(),\n      data,\n    });\n  }\n});\n\nawait page.goto('/dashboard');\n// apiResponses now contains all API calls\n\"\"\"\n\n## Sharp Edges\n\n### Using waitForTimeout Instead of Proper Waits\n\nSeverity: CRITICAL\n\nSituation: Waiting for elements or page state\n\nSymptoms:\nTests pass locally, fail in CI. Pass 9 times, fail on the 10th.\n\"Element not found\" errors that seem random. Tests take 30+ seconds\nwhen they should take 3.\n\nWhy this breaks:\nwaitForTimeout is a fixed delay. If the page loads in 500ms, you wait\n2000ms anyway. If the page takes 2100ms (CI is slower), you fail.\nThere's no correct value - it's always either too short or too long.\n\nRecommended fix:\n\n# REMOVE all waitForTimeout calls\n\n# WRONG:\nawait page.goto('/dashboard');\nawait page.waitForTimeout(2000);  # Arbitrary!\nawait page.click('.submit');\n\n# CORRECT - Auto-wait handles it:\nawait page.goto('/dashboard');\nawait page.getByRole('button', { name: 'Submit' }).click();\n\n# If you need to wait for specific condition:\nawait expect(page.getByText('Dashboard')).toBeVisible();\nawait page.waitForURL('**/dashboard');\nawait page.waitForResponse(resp => resp.url().includes('/api/data'));\n\n# For animations, wait for element to be stable:\nawait page.getByRole('button').click();  # Auto-waits for stable\n\n# NEVER use setTimeout or waitForTimeout in production code\n\n### CSS Selectors Tied to Styling Classes\n\nSeverity: HIGH\n\nSituation: Selecting elements for interaction\n\nSymptoms:\nTests break after CSS refactoring. Selectors like .btn-primary stop\nworking. Frontend redesign breaks all tests without changing behavior.\n\nWhy this breaks:\nCSS class names are implementation details for styling, not semantic\nmeaning. When designers change from .btn-primary to .button--primary,\nyour tests break even though behavior is identical.\n\nRecommended fix:\n\n# Use user-facing locators instead:\n\n# WRONG - Tied to CSS:\nawait page.locator('.btn-primary.submit-form').click();\nawait page.locator('#sidebar > div.menu > ul > li:nth-child(3)').click();\n\n# CORRECT - User-facing:\nawait page.getByRole('button', { name: 'Submit' }).click();\nawait page.getByRole('menuitem', { name: 'Settings' }).click();\n\n# If you must use CSS, use data-testid:\n<button data-testid=\"submit-order\">Submit</button>\n\nawait page.getByTestId('submit-order').click();\n\n# Locator priority:\n# 1. getByRole - matches accessibility\n# 2. getByText - matches visible content\n# 3. getByLabel - matches form labels\n# 4. getByTestId - explicit test contract\n# 5. CSS/XPath - last resort only\n\n### navigator.webdriver Exposes Automation\n\nSeverity: HIGH\n\nSituation: Scraping sites with bot detection\n\nSymptoms:\nImmediate 403 errors. CAPTCHA challenges. Empty pages. \"Access Denied\"\nmessages. Works for 1 request, then gets blocked.\n\nWhy this breaks:\nBy default, headless browsers set navigator.webdriver = true. This is\nthe first thing bot detection checks. It's a bright red flag that\nsays \"I'm automated.\"\n\nRecommended fix:\n\n# Use stealth plugins:\n\n## Puppeteer Stealth (best option):\nimport puppeteer from 'puppeteer-extra';\nimport StealthPlugin from 'puppeteer-extra-plugin-stealth';\n\npuppeteer.use(StealthPlugin());\n\nconst browser = await puppeteer.launch({\n  headless: 'new',\n  args: ['--disable-blink-features=AutomationControlled'],\n});\n\n## Playwright Stealth:\nimport { chromium } from 'playwright-extra';\nimport stealth from 'puppeteer-extra-plugin-stealth';\n\nchromium.use(stealth());\n\n## Manual (partial):\nawait page.evaluateOnNewDocument(() => {\n  Object.defineProperty(navigator, 'webdriver', {\n    get: () => undefined,\n  });\n});\n\n# Note: This is cat-and-mouse. Detection evolves.\n# For serious scraping, consider managed solutions like Browserbase.\n\n### Tests Share State and Affect Each Other\n\nSeverity: HIGH\n\nSituation: Running multiple tests in sequence\n\nSymptoms:\nTests pass individually but fail when run together. Order matters -\ntest B fails if test A runs first. Random failures that \"fix themselves\"\non rerun.\n\nWhy this breaks:\nShared browser context means shared cookies, localStorage, and session\nstate. Test A logs in, test B expects logged-out state. Test A adds\nitem to cart, test B's cart count is wrong.\n\nRecommended fix:\n\n# Each test must be fully isolated:\n\n## Playwright Test (automatic isolation):\ntest('first test', async ({ page }) => {\n  // Fresh context, fresh page\n});\n\ntest('second test', async ({ page }) => {\n  // Completely isolated from first test\n});\n\n## Manual isolation:\nconst context = await browser.newContext();  // Fresh context\nconst page = await context.newPage();\n// ... test code ...\nawait context.close();  // Clean up\n\n## Shared authentication (the right way):\n// 1. Save auth state to file\nawait context.storageState({ path: './auth.json' });\n\n// 2. Reuse in other tests\nconst context = await browser.newContext({\n  storageState: './auth.json'\n});\n\n# Never modify global state in tests\n# Never rely on previous test's actions\n\n### No Trace Capture for CI Failures\n\nSeverity: MEDIUM\n\nSituation: Debugging test failures in CI\n\nSymptoms:\n\"Test failed in CI\" with no useful information. Can't reproduce\nlocally. Screenshot shows page but not what went wrong. Guessing\nat root cause.\n\nWhy this breaks:\nCI runs headless on different hardware. Timing is different. Network\nis different. Without traces, you can't see what actually happened -\nthe sequence of actions, network requests, console logs.\n\nRecommended fix:\n\n# Enable traces for failures:\n\n## playwright.config.ts:\nexport default defineConfig({\n  use: {\n    trace: 'retain-on-failure',    # Keep trace on failure\n    screenshot: 'only-on-failure', # Screenshot on failure\n    video: 'retain-on-failure',    # Video on failure\n  },\n  outputDir: './test-results',\n});\n\n## View trace locally:\nnpx playwright show-trace test-results/path/to/trace.zip\n\n## In CI, upload test-results as artifact:\n# GitHub Actions:\n- uses: actions/upload-artifact@v3\n  if: failure()\n  with:\n    name: playwright-traces\n    path: test-results/\n\n# Trace shows:\n# - Timeline of actions\n# - Screenshots at each step\n# - Network requests and responses\n# - Console logs\n# - DOM snapshots\n\n### Tests Pass Headed but Fail Headless\n\nSeverity: MEDIUM\n\nSituation: Running tests in headless mode for CI\n\nSymptoms:\nWorks perfectly when you watch it. Fails mysteriously in CI.\n\"Element not visible\" in headless but visible in headed mode.\n\nWhy this breaks:\nHeadless browsers have no display, which affects some CSS (visibility\ncalculations), viewport sizing, and font rendering. Some animations\nbehave differently. Popup windows may not work.\n\nRecommended fix:\n\n# Set consistent viewport:\nconst browser = await chromium.launch({\n  headless: true,\n});\n\nconst context = await browser.newContext({\n  viewport: { width: 1280, height: 720 },\n});\n\n# Or in config:\nexport default defineConfig({\n  use: {\n    viewport: { width: 1280, height: 720 },\n  },\n});\n\n# Debug headless failures:\n# 1. Run with headed mode locally\nnpx playwright test --headed\n\n# 2. Slow down to watch\nnpx playwright test --headed --slowmo 100\n\n# 3. Use trace viewer for CI failures\nnpx playwright show-trace trace.zip\n\n# 4. For stubborn issues, screenshot at failure point:\nawait page.screenshot({ path: 'debug.png', fullPage: true });\n\n### Getting Blocked by Rate Limiting\n\nSeverity: HIGH\n\nSituation: Scraping multiple pages quickly\n\nSymptoms:\nWorks for first 50 pages, then 429 errors. Suddenly all requests fail.\nIP gets blocked. CAPTCHA starts appearing after successful requests.\n\nWhy this breaks:\nSites monitor request patterns. 100 requests per second from one IP\nis obviously automated. Rate limits protect servers and catch scrapers.\n\nRecommended fix:\n\n# Add delays between requests:\n\nconst randomDelay = () =>\n  new Promise(r => setTimeout(r, 1000 + Math.random() * 2000));\n\nfor (const url of urls) {\n  await randomDelay();  // 1-3 second delay\n  await page.goto(url);\n  // ... scrape ...\n}\n\n# Use rotating proxies:\nconst proxies = ['http://proxy1:8080', 'http://proxy2:8080'];\nlet proxyIndex = 0;\n\nconst getNextProxy = () => proxies[proxyIndex++ % proxies.length];\n\nconst context = await browser.newContext({\n  proxy: { server: getNextProxy() },\n});\n\n# Limit concurrent requests:\nimport pLimit from 'p-limit';\nconst limit = pLimit(3);  // Max 3 concurrent\n\nawait Promise.all(\n  urls.map(url => limit(() => scrapePage(url)))\n);\n\n# Rotate user agents:\nconst userAgents = [\n  'Mozilla/5.0 (Windows...',\n  'Mozilla/5.0 (Macintosh...',\n];\n\nawait page.setExtraHTTPHeaders({\n  'User-Agent': userAgents[Math.floor(Math.random() * userAgents.length)]\n});\n\n### New Windows/Popups Not Handled\n\nSeverity: MEDIUM\n\nSituation: Clicking links that open new windows\n\nSymptoms:\nClick button, nothing happens. Test hangs. \"Window not found\" errors.\nActions succeed but verification fails because you're on wrong page.\n\nWhy this breaks:\ntarget=\"_blank\" links open new windows. Your page reference still\npoints to the original page. The new window exists but you're not\nlistening for it.\n\nRecommended fix:\n\n# Wait for popup BEFORE triggering it:\n\n## New window/tab:\nconst pagePromise = context.waitForEvent('page');\nawait page.getByRole('link', { name: 'Open in new tab' }).click();\nconst newPage = await pagePromise;\nawait newPage.waitForLoadState();\n\n// Now interact with new page\nawait expect(newPage.getByRole('heading')).toBeVisible();\n\n// Close when done\nawait newPage.close();\n\n## Popup windows:\nconst popupPromise = page.waitForEvent('popup');\nawait page.getByRole('button', { name: 'Open popup' }).click();\nconst popup = await popupPromise;\nawait popup.waitForLoadState();\n\n## Multiple windows:\nconst pages = context.pages();  // Get all open pages\n\n### Can't Interact with Elements in iframes\n\nSeverity: MEDIUM\n\nSituation: Page contains embedded iframes\n\nSymptoms:\nElement clearly visible but \"not found\". Selector works in DevTools\nbut not in Playwright. Parent page selectors work, iframe content\ndoesn't.\n\nWhy this breaks:\niframes are separate documents. page.locator only searches the main\nframe. You need to explicitly get the iframe's frame to interact\nwith its contents.\n\nRecommended fix:\n\n# Get frame by name or selector:\n\n## By frame name:\nconst frame = page.frame('payment-iframe');\nawait frame.getByRole('textbox', { name: 'Card number' }).fill('4242...');\n\n## By selector:\nconst frame = page.frameLocator('iframe#payment');\nawait frame.getByRole('textbox', { name: 'Card number' }).fill('4242...');\n\n## Nested iframes:\nconst outer = page.frameLocator('iframe#outer');\nconst inner = outer.frameLocator('iframe#inner');\nawait inner.getByRole('button').click();\n\n## Wait for iframe to load:\nawait page.waitForSelector('iframe#payment');\nconst frame = page.frameLocator('iframe#payment');\nawait frame.getByText('Secure Payment').waitFor();\n\n## Validation Checks\n\n### Using waitForTimeout\n\nSeverity: ERROR\n\nwaitForTimeout causes flaky tests and slow execution\n\nMessage: Using waitForTimeout - remove it. Playwright auto-waits for elements. Use waitForResponse, waitForURL, or assertions instead.\n\n### Using setTimeout in Test Code\n\nSeverity: WARNING\n\nsetTimeout is unreliable for timing in tests\n\nMessage: Using setTimeout instead of Playwright waits. Replace with await expect(...).toBeVisible() or page.waitFor*.\n\n### Custom Sleep Function\n\nSeverity: WARNING\n\nSleep functions indicate improper waiting strategy\n\nMessage: Custom sleep function detected. Use Playwright's built-in waiting mechanisms instead.\n\n### CSS Class Selector Used\n\nSeverity: WARNING\n\nCSS class selectors are fragile\n\nMessage: Using CSS class selector. Prefer getByRole, getByText, getByLabel, or getByTestId for more stable selectors.\n\n### nth-child CSS Selector\n\nSeverity: WARNING\n\nPosition-based selectors are very fragile\n\nMessage: Using position-based selector. These break when DOM order changes. Use user-facing locators instead.\n\n### XPath Selector Used\n\nSeverity: INFO\n\nXPath should be last resort\n\nMessage: Using XPath selector. Consider getByRole, getByText first. XPath should be last resort for complex DOM traversal.\n\n### Auto-Generated Selector\n\nSeverity: WARNING\n\nFramework-generated selectors are extremely fragile\n\nMessage: Using auto-generated selector. These change on every build. Use data-testid instead.\n\n### Puppeteer Without Stealth Plugin\n\nSeverity: INFO\n\nScraping without stealth is easily detected\n\nMessage: Using Puppeteer without stealth plugin. Consider puppeteer-extra-plugin-stealth for anti-detection.\n\n### navigator.webdriver Not Hidden\n\nSeverity: INFO\n\nnavigator.webdriver exposes automation\n\nMessage: Launching browser without hiding automation flags. For scraping, add stealth measures.\n\n### Scraping Loop Without Error Handling\n\nSeverity: WARNING\n\nOne failure shouldn't crash entire scrape\n\nMessage: Scraping loop without try/catch. One page failure will crash the entire scrape. Add error handling.\n\n## Collaboration\n\n### Delegation Triggers\n\n- user needs full desktop control beyond browser -> computer-use-agents (Desktop automation for non-browser apps)\n- user needs API testing alongside browser tests -> backend (API integration and testing patterns)\n- user needs testing strategy -> test-architect (Overall test architecture decisions)\n- user needs visual regression testing -> ui-design (Visual comparison and design validation)\n- user needs browser automation in workflows -> workflow-automation (Durable execution for browser tasks)\n- user building browser tools for agents -> agent-tool-builder (Tool design patterns for LLM agents)\n\n## Related Skills\n\nWorks well with: `agent-tool-builder`, `workflow-automation`, `computer-use-agents`, `test-architect`\n\n## When to Use\n- User mentions or implies: playwright\n- User mentions or implies: puppeteer\n- User mentions or implies: browser automation\n- User mentions or implies: headless\n- User mentions or implies: web scraping\n- User mentions or implies: e2e test\n- User mentions or implies: end-to-end\n- User mentions or implies: selenium\n- User mentions or implies: chromium\n- User mentions or implies: browser test\n- User mentions or implies: page.click\n- User mentions or implies: locator\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.","tags":["browser","automation","antigravity","awesome","skills","sickn33","agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding","ai-workflows"],"capabilities":["skill","source-sickn33","skill-browser-automation","topic-agent-skills","topic-agentic-skills","topic-ai-agent-skills","topic-ai-agents","topic-ai-coding","topic-ai-workflows","topic-antigravity","topic-antigravity-skills","topic-claude-code","topic-claude-code-skills","topic-codex-cli","topic-codex-skills"],"categories":["antigravity-awesome-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/sickn33/antigravity-awesome-skills/browser-automation","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add sickn33/antigravity-awesome-skills","source_repo":"https://github.com/sickn33/antigravity-awesome-skills","install_from":"skills.sh"}},"qualityScore":"0.700","qualityRationale":"deterministic score 0.70 from registry signals: · indexed on github topic:agent-skills · 35034 github stars · SKILL.md body (29,108 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-04-25T12:50:31.367Z","embedding":null,"createdAt":"2026-04-18T20:27:36.825Z","updatedAt":"2026-04-25T12:50:31.367Z","lastSeenAt":"2026-04-25T12:50:31.367Z","tsv":"'-3':2741 '/api':1702 '/api/data':945,1866 '/api/products':1662 '/auth.json':2324,2335 '/button':720 '/cart':456 '/dashboard':513,848,920,963,1717,1822,1838,1860 '/div':717 '/login':488 '/order':642 '/path/to/trace.zip':2469 '/playwright/.auth/user.json':521,538 '/products':423,1688 '/test-results':2457 '/tr''))':1651 '0':1369,2759 '0.1':1394 '1':439,566,721,1300,1384,1674,2020,2068,2315,2611,2740 '10':1203 '10.0':1090 '100':1212,2631,2700 '1000':1361,2730 '1080':1081,1142 '10th':1754 '12345':734 '1280':2593,2605 '1500':1180 '1920':1079,1140 '2':572,707,1198,1201,1271,1389,2024,2325,2621 '200':1224,1668 '2000':851,1415,1583,1825,2732 '2000ms':1787 '2025':88 '2100ms':1793 '3':578,1359,1414,1770,1984,2029,2632,2784,2786 '30':1764 '300':1213,1222 '30000':1293 '4':584,1446,1449,2034,2645 '4.5':231 '403':2057 '4242':3037,3052 '429':2678 '5':590,1552,1554,2039 '50':2675 '500':873,1179 '500ms':1784 '720':2595,2607 '75':249 '8080':2754,2756 '9':1749 '96':228 '99.99':1679 'access':201,204,570,2023,2063 'accessibility-specialist':203 'accessibility-test':200 'across':473 'action':1158,2348,2415,2479,2498,2837 'actions/upload-artifact':2481 'actual':2410 'add':120,406,428,753,794,836,2250,2719,3330,3360 'address':653 'ads/analytics':1607 'affect':2187,2557 'agent':12,23,60,1084,2797,2808,3376,3440,3442,3450,3457,3466 'agent-tool-build':3441,3456 'ai':11,22 'alongsid':3388 'altern':188 'alway':554,800,1806 'america/new_york':1150 'analyt':1629,1647 'anim':826,1868,2568 'anti':44,153,293,316,1000,1032,3311 'anti-bot':999 'anti-detect':43,152,292,315,1031,3310 'anyway':1788 'api':191,1657,1690,1722,3386,3392 'api-test':190 'apirespons':1693,1718 'apiresponses.push':1709 'app':94,3383 'appear':905,2689 'applewebkit/537.36':1093 'application/json':1670 'approach':557 'arbitrari':853,1826 'architect':3403,3469 'architectur':3406 'arg':1056,2133 'artifact':1015,2477 'ask':3573 'assert':3116 'async':410,447,484,1281,1353,1501,1563,1663,1697,2276,2285 'attach':814 'attempt':1368,1370,1372,1382,1390 'auth':469,508,2317 'authent':466,483,515,2311 'auto':126,223,726,786,804,882,886,900,912,1832,1880,3108,3257,3272 'auto-gener':725,3256,3271 'auto-wait':125,222,785,803,881,885,899,911,1831,1879,3107 'autom':3,5,14,16,92,99,168,185,369,1242,2046,2101,2709,3320,3326,3378,3424,3429,3462,3488 'automat':792,809,1246,2271 'automationcontrol':1068,2138 'avg':233 'avoid':595,988 'await':421,424,432,454,457,486,489,494,499,511,517,606,612,619,625,635,640,650,656,663,678,694,699,715,729,743,761,779,846,849,855,864,871,893,906,918,946,954,960,976,983,1052,1071,1076,1085,1105,1130,1136,1174,1177,1187,1192,1194,1210,1214,1218,1289,1296,1304,1320,1330,1332,1375,1396,1409,1481,1498,1505,1509,1512,1517,1534,1536,1558,1566,1570,1576,1586,1591,1615,1660,1665,1686,1705,1715,1820,1823,1827,1836,1839,1853,1858,1861,1875,1970,1975,1990,1996,2012,2129,2159,2296,2302,2306,2321,2332,2583,2589,2653,2738,2744,2767,2788,2804,2891,2902,2904,2911,2919,2927,2936,2938,3030,3045,3065,3074,3083,3141 'b':2210,2242,2255 'back':104,237,638 'backend':193,3391 'backoff':1352 'bad':684 'base':1454,3206,3215 'basedelay':1360,1387 'behav':2569 'behavior':1019,1104,1154,1225,1925,1955 'best':225,255,568,604,1030,2109 'better':85 'beyond':3371 'bind':264 'blank':2852 'blink':1066,2136 'block':164,1597,1606,1612,1626,1641,2072,2660,2686 'bodi':1671 'bot':284,989,1001,1006,2053,2088 'boundari':3581 'box':1191 'box.height':1200 'box.width':1197 'box.x':1196 'box.y':1199 'break':1773,1907,1920,1928,1952,2075,2226,2390,2550,2695,2850,2988,3218 'bright':2094 'browser':2,4,13,15,61,167,173,178,221,271,324,342,380,401,986,1004,1016,1051,1129,1474,1480,2079,2128,2228,2552,2582,3323,3372,3382,3389,3423,3433,3437,3487,3528 'browser-autom':1,166 'browser-test':177 'browser.close':1537 'browser.newcontext':1137,1506,1567,2297,2333,2590,2768 'browser.newpage':1072 'browserbas':325,2182 'browserstack':338 'btn':1914,1945 'btn-primari':1913,1944 'btn-primary.submit':696,1972 'build':3279,3436 'builder':3444,3459 'built':332,3166 'built-in':331,3165 'button':426,501,608,682,703,751,781,859,889,895,948,965,1186,1841,1877,1948,1992,2828,2929,3067 'button.boundingbox':1193 'button.click':1215 'button.submit':1189 'bypass':283,320 'calcul':2561 'call':1723,1818 'capabl':165 'captcha':2059,2687 'captur':1315,1689,2351 'card':3034,3049 'cart':409,430,436,446,451,461,755,2253,2257 'cart-count':435 'cat':157,2170 'cat-and-mous':156,2169 'catch':1277,1313,1377,1525,1707,2715 'caus':2387,3095 'chain':738,770 'challeng':2060 'chang':959,1924,1942,3222,3276 'check':1008,2090,3089 'child':706,1983,3199 'choic':86,218,605 'chrome':80,241,1012,1461 'chrome-on':79,240 'chrome/120.0.0.0':1097 'chromedriv':312 'chromium':1113,1457,2142,3523 'chromium.launch':1131,1482,2584 'chromium.use':1126,2155 'ci':148,349,1448,1747,1794,2353,2362,2367,2391,2471,2526,2537,2637 'clarif':3575 'class':718,1897,1930,3172,3178,3185 'clean':386,2308 'clear':2965,3548 'click':431,505,611,618,644,683,698,708,722,735,756,769,784,898,951,968,980,1184,1844,1878,1974,1985,1995,2001,2017,2820,2827,2899,2933,3068 'clickabl':892 'close':2916 'cloud':323 'code':1891,2305,3122 'codebas':247 'collabor':3363 'come':35 'comparison':3417 'complet':359,449,510,917,2287 'complex':3253 'comput':3374,3464 'computer-use-ag':3373,3463 'concurr':1555,2773,2787 'condit':1852 'config':2598 'consid':2178,3243,3303 'consist':2579 'consol':2418,2507 'console.error':1338 'const':772,939,952,972,981,1050,1069,1128,1134,1159,1185,1190,1294,1302,1318,1328,1385,1391,1407,1479,1483,1496,1503,1507,1515,1549,1556,1564,1568,1618,1622,1692,1703,2127,2294,2300,2330,2581,2587,2723,2734,2751,2760,2765,2781,2798,2887,2900,2923,2934,2942,3024,3040,3055,3060,3078 'contain':741,760,1720,2960 'content':577,634,2028,2983,3012 'contenttyp':1669 'context':138,381,402,413,1135,1475,1492,1504,1565,2229,2279,2295,2299,2331,2588,2766 'context.close':1535,1592,2307 'context.newpage':1510,1571,2303 'context.pages':2944 'context.storagestate':2322 'context.waitforevent':2889 'contract':589,2038 'control':62,96,3370 'cooki':382,415,2232 'correct':879,1802,1830,1986 'count':437,2258 'cover':49 'cpu':1453 'cpu-bas':1452 'crash':3344,3356 'creat':1490 'criteria':3584 'critic':89,1733 'cross':220,341 'cross-brows':219,340 'css':689,1892,1909,1929,1969,2006,2559,3171,3177,3184,3200 'css/xpath':118,591,2040 'csv':979 'current':161 'currenturl':1342 'custom':3146,3158 'd':643 'dashboard':623,1856 'data':732,1516,1522,1685,1704,1714,2009,3282 'data-testid':2008,3281 'data-v':731 'date.now':1324,1335 'debug':144,151,1279,1316,2358,2608 'debug.png':2656 'decis':3407 'default':217,524,556,1252,1440,2077,2428,2600 'defineconfig':525,1253,1441,2429,2601 'delay':1156,1386,1393,1402,1573,1778,2720,2743 'deleg':3364 'deni':2064 'depend':534 'describ':3552 'design':212,1941,3415,3419,3446 'desktop':1460,1466,1472,3369,3377 'detail':1934 'detect':45,154,285,294,317,322,990,1007,1033,2054,2089,2173,3161,3296,3312 'devic':348,1459,1465,1471 'devtool':1013,2973 'differ':26,105,107,2395,2399,2402,2570 'disabl':1061,1065,2135 'disable-blink-featur':1064,2134 'disable-setuid-sandbox':1060 'display':2555 'distinct':90 'div':702 'div.menu':1978 'document':2992 'doe':778 'doesn':2984 'dom':816,2509,3220,3254 'done':2918 'download':971,975,982 'downloadpromis':973,984 'durabl':3430 'dx':226 'dynam':319 'e2e':181,3504 'e2e-testing':180 'easili':3295 'ecosystem':76,257,309 'edg':1725 'edit':783 'either':1807 'element':545,811,817,821,827,831,1737,1755,1871,1902,2538,2953,2964,3111 'email':491,652 'embed':2961 'empti':453,463,2061 'en':1147 'en-us':1146 'enabl':834,2422 'end':3511,3513 'end-to-end':3510 'entir':3345,3358 'environ':3564 'environment-specif':3563 'error':1227,1243,1314,1323,1334,1340,1347,1365,1378,1380,1526,1529,1758,2058,2679,2836,3093,3336,3361 'error.message':1341,1348,1530 'error.png':1325 'even':1953 'event':830 'everi':3278 'evolv':2174 'exampl':390,597,685 'execut':234,1417,1434,3100,3431 'exist':246,2869 'expect':393,433,458,907,1854,2243,2912,3142 'expert':3569 'explicit':587,2036,3002 'exponenti':1351 'export':523,978,1251,1439,2427,2599 'expos':2045,3319 'extra':277,297,308,1039,1045,1117,1123,2116,2122,2146,2152,3306 'extractdata':1518 'extrem':3267 'face':113,541,562,600,675,1963,1989,3226 'facebook.com':1650 'facebook.com/tr''))':1649 'fail':1273,1745,1751,1798,2203,2211,2365,2515,2534,2683,2841 'failur':1231,1249,1259,1264,1269,2218,2354,2360,2425,2435,2439,2444,2447,2452,2455,2484,2610,2638,2651,3341,3354 'fallback':586 'fals':1346,1532 'faster':1631 'featur':1067,2137 'fight':103 'file':2320 'fill':492,497,630,654,659,666,3036,3051 'filter':736,739,746,757,764 'final':1533,1590 'fingerprint':1017 'firefox':1463,1467 'first':768,2086,2216,2274,2290,2674,3246 'fix':1777,1814,1959,2103,2220,2262,2421,2577,2718,2878,3014 'flag':2096,3327 'flaki':29,3096 'fn':1356,1376 'font':1628,1635,2565 'form':582,649,697,719,1973,2032 'found':1757,2835,2969 'fragil':594,686,3181,3210,3268 'frame':2998,3007,3016,3022,3025,3041,3079 'frame.getbyrole':3031,3046 'frame.getbytext':3084 'framework':68,214,3263 'framework-gener':3262 'fresh':137,362,384,412,2278,2280,2298 'frontend':1918 'fs.writefile':1333 'full':3368 'fulli':134,2267 'fullpag':1326,2657 'fullyparallel':1442 'function':1282,1354,3148,3152,3160 'gecko':1096 'generat':727,3258,3264,3273 'get':163,377,2071,2164,2659,2685,2945,3003,3015 'getbylabel':579,2030,3190 'getbyrol':115,567,750,2021,3188,3244 'getbytestid':585,2035,3192 'getbytext':116,573,2025,3189,3245 'getnextproxi':2761,2771 'github':2478 'global':2338 'gold':289 'good':574,580,596 'googl':1646 'google-analyt':1645 'grace':1232 'guess':2384 'handl':128,1230,1834,2816,3337,3362 'hang':2832 'happen':2411,2830 'hardwar':2396 'hasnottext':765 'hastext':747 'head':149,621,1298,2513,2546,2614,2620,2629,2914 'header':701 'headless':146,172,328,1026,1054,1132,2078,2131,2393,2516,2523,2542,2551,2585,2609,3493 'headless-brows':171 'height':1080,1141,2594,2606 'hidden':870,3315 'hide':3325 'high':1899,2048,2191,2665 'html':1329,1337 'human':1102,1152,1209 'human-lik':1101,1151 'id':670,1673 'ident':1957 'ifram':2955,2962,2982,2989,3005,3029,3043,3054,3058,3063,3071,3076,3081 'imag':1627,1634 'immedi':2056 'implement':1933 'impli':3476,3481,3486,3492,3497,3503,3509,3517,3522,3527,3533,3538 'import':391,476,1034,1040,1112,1118,1543,2111,2117,2141,2147,2775 'improp':3154 'includ':944,1637,1701,1865 'inconsist':1018 'independ':1429 'indic':1027,3153 'individu':2201 'info':1280,1317,3233,3290,3317 'inform':2371 'infrastructur':329 'inner':3061,3064 'inner.getbyrole':3066 'input':3578 'insight':64 'instead':1728,1965,3117,3135,3170,3228,3284 'integr':350,3393 'interact':24,1904,2907,2951,3009 'intercept':1594,1611 'ip':2684,2706 'isol':135,353,360,374,400,450,1495,2268,2272,2288,2293 'issu':2648 'isvis':624,639 'item':407,444,2251 'jitter':1392,1403 'john':777 'json.stringify':1672 'keep':2436 'key':63 'khtml':1094 'label':583,648,2033 'languag':263 'last':592,2041,3237,3250 'lasterror':1364,1379,1405 'launch':3322 'legaci':260 'let':789,880,1363,1367,2757 'level':1299 'li':1980 'lifelin':145 'like':1095,1103,1153,1207,1912,2181 'limit':1540,1548,1550,1562,2663,2711,2772,2780,2782,2792,3540 'link':614,2821,2853,2893 'listen':2874 'listitem':745,763 'llm':3449 'load':195,867,950,1782,3073 'load-test':194 'loading-spinn':866 'local':1145,1451,1744,2375,2460,2616 'localstorag':2233 'locat':114,542,563,771,1964,2018,3227,3539 'log':2239,2245,2419,2508 'logged-out':2244 'login':967 'long':1812 'loop':3334,3349 'm':2100 'macintosh':2803 'main':2997 'manag':327,337,2179 'manual':121,795,837,840,925,2157,2292 'match':569,575,581,2022,2026,2031,3549 'math.floor':2810 'math.pow':1388 'math.random':1170,1223,1395,1582,2731,2811 'matter':2208 'max':1163,1171,1553,2785 'maxretri':1358,1371,1383 'may':2573 'mean':1939,2230 'measur':3332 'mechan':3169 'media':1636 'medium':2356,2518,2818,2957 'mention':3474,3479,3484,3490,3495,3501,3507,3515,3520,3525,3531,3536 'menuitem':1998 'messag':2065,3101,3132,3157,3182,3211,3239,3269,3297,3321,3347 'microsoft':236 'microsoft-back':235 'min':1161,1172,1173 'miss':3586 'mock':1596,1656,1676,1684 'mode':335,2524,2547,2615 'modifi':1599,1608,2337 'monitor':2697 'mous':159,1024,1181,2172 'move':1204 'movement':1025,1182 'mozilla/5.0':1087,1144,2800,2802 'multipl':1428,1491,2194,2668,2940 'must':2004,2265 'mysteri':2535 'name':427,502,527,532,609,615,622,628,752,776,782,896,949,966,1456,1462,1468,1675,1842,1931,1993,1999,2486,2894,2930,3018,3023,3033,3048 'natur':1217 'navig':915,1099,2162 'navigator.webdriver':1010,2044,2081,3313,3318 'need':72,243,281,371,927,931,1847,3000,3367,3385,3398,3409,3422 'nest':3053 'network':937,1593,1600,1610,2400,2416,2503 'networkidle0':1109 'never':119,793,835,1884,2336,2342 'new':1055,1165,1397,1577,2132,2725,2813,2824,2855,2867,2885,2897,2909 'newpag':2901 'newpage.close':2920 'newpage.getbyrole':2913 'newpage.waitforloadstate':2905 'no-sandbox':1057 'non':3381 'non-brows':3380 'note':227,248,265,288,303,318,330,346,2166 'noth':2829 'npx':2461,2617,2626,2639 'nt':1089 'nth':705,1982,3198 'nth-child':704,1981,3197 'null':1708 'number':1162,1164,3035,3050 'object.defineproperty':2161 'obvious':2708 'one':2705,3340,3352 'only-on-failur':1256,2441 'open':2823,2854,2895,2931,2947 'option':676,2110 'order':565,2016,2207,3221 'origin':2864 'outer':3056,3059 'outer.framelocator':3062 'output':3558 'outputdir':2456 'overal':3404 'p':1547,2779 'p-limit':1546,2778 'page':385,411,448,485,921,1070,1188,1284,1285,1412,1430,1508,1519,1569,1588,1681,1739,1781,1791,2062,2277,2281,2286,2301,2378,2669,2676,2847,2858,2865,2890,2910,2943,2948,2959,2979,3353 'page.click':856,1828,3534 'page.content':1331 'page.context':518 'page.evaluate':1219 'page.evaluateonnewdocument':2160 'page.frame':3026 'page.framelocator':3042,3057,3080 'page.getbylabel':490,495,651,657 'page.getbyplaceholder':664 'page.getbyrole':425,500,607,613,620,626,744,762,774,894,947,964,1297,1840,1876,1991,1997,2892,2928 'page.getbytestid':434,679,1305,2013 'page.getbytext':459,636,641,908,977,1855 'page.goto':422,455,487,847,919,1106,1175,1290,1513,1687,1716,1821,1837,2745 'page.html':1336 'page.locator':695,700,716,730,1971,1976,2993 'page.mouse.move':1195 'page.on':1695 'page.route':1616,1661 'page.screenshot':1321,2654 'page.setextrahttpheaders':2805 'page.setuseragent':1086 'page.setviewport':1077 'page.url':1343 'page.waitfor':3145 'page.waitforevent':974,2925 'page.waitforresponse':941,1862 'page.waitforselector':865,3075 'page.waitfortimeout':850,872,1824 'page.waitforurl':512,962,1859 'pagepromis':2888,2903 'parallel':1416,1422,1433,1437,1477,1541 'parent':2978 'partial':2158 'pass':1743,1748,2200,2512 'password':496,498,658 'path':520,1322,2323,2490,2655 'pattern':46,55,351,354,467,543,788,806,987,1005,1020,1229,1245,1418,1595,2699,3396,3447 'payment':3028,3044,3077,3082,3086 'payment-ifram':3027 'per':2702 'perfect':1021,2529 'perform':198 'performance-think':197 'permiss':3579 'placehold':662 'playwright':50,65,82,123,169,215,296,302,388,790,802,807,1110,1116,1435,2139,2145,2269,2462,2488,2618,2627,2640,2977,3106,3137,3163,3477 'playwright-extra':295,1115,2144 'playwright-trac':2487 'playwright.config.ts':522,1250,1438,2426 'playwright/test':395,481 'plimit':1544,1551,2776,2783 'plugin':245,278,300,1046,1124,2106,2123,2153,3288,3302,3307 'point':2652,2861 'popup':2571,2881,2921,2926,2932,2935 'popup.waitforloadstate':2939 'popuppromis':2924,2937 'port':304 'posit':3205,3214 'position-bas':3204,3213 'power':6,17 'predict':93 'prefer':3187 'previous':2345 'price':1303,1306,1310,1678 'primari':1915,1946,1949 'principl':109 'prioriti':564,2019 'problem':106 'process':1542 'process.env.ci':1445 'product':748,1241,1677,1890 'project':526,1455 'promis':1166,1357,1362,1398,1578,2726 'promise.all':961,1499,1559,2789 'proper':1730 'properti':1011 'protect':1002,2712 'protocol':1014 'proxi':2750,2752,2762,2769 'proxies.length':2764 'proxy1':2753 'proxy2':2755 'proxyindex':2758,2763 'puppet':53,73,170,238,276,287,307,1028,1035,1038,1044,1122,2107,2112,2115,2121,2151,3285,3299,3305,3482 'puppeteer-extra':306,1037,2114 'puppeteer-extra-plugin-stealth':275,1043,1121,2120,2150,3304 'puppeteer.launch':1053,2130 'puppeteer.use':1048,2125 'queri':631,667 'quick':2670 'r':1167,1169,1399,1401,1579,1581,2727,2729 'random':1155,1572,1761,2217 'randomdelay':1160,1178,1211,2724,2739 'rate':230,251,1539,2662,2710 'rate-limit':1538 're':2844,2872 'readi':923 'real':347 'realist':1074,1082 'receiv':829,1683 'recommend':51,1813,1958,2102,2261,2420,2576,2717,2877,3013 'recoveri':1228,1244 'red':2095 'redesign':1919 'refactor':1910 'refer':2859 'regex':645 'regress':208,3411 'relat':3451 'reli':2343 'reliabl':33 'remov':443,1815,3104 'render':2566 'replac':3139 'reproduc':372,2374 'request':938,1575,1601,2069,2417,2504,2682,2692,2698,2701,2722,2774 'requir':3577 'rerun':2223 'resort':593,2042,3238,3251 'resourc':1614 'resourcetyp':1623,1625,1638 'resp':1863 'resp.url':1864 'respons':942,953,1609,1658,1691,1696,1698,2506 'response.json':1706 'response.status':1713 'response.url':943,1700,1711 'responsepromis':940,955 'result':1408,1497,1557,2468,2475,2493 'retain':1262,1267,2433,2450 'retain-on-failur':1261,1266,2432,2449 'retri':1236,1270,1272,1349 'return':1308,1344,1374,1520,1527,1585,1639,1652,1654 'reus':472,2326 'review':3570 'right':2313 'role':602 'root':2386 'rotat':2749,2795 'rout':1617,1664 'route.abort':1640,1653 'route.continue':1655 'route.fulfill':1666 'route.request':1620,1624 'row':773,775 'row.getbyrole':780 'run':357,398,1419,2193,2205,2215,2392,2520,2612 'safari':1473 'safari/537.36':1098 'safe':877 'safeti':3580 'sandbox':1059,1063 'save':468,514,2316 'say':2098 'scale':253,345 'scope':189,3551 'scrape':9,20,58,176,992,996,1478,1632,2050,2177,2667,2747,3291,3329,3333,3346,3348,3359,3499 'scrapepag':1587,2793 'scrapeproduct':1283,1411 'scraper':2716 'scraping/agent':98 'screenshot':139,1234,1247,1255,1319,2376,2440,2445,2499,2649 'script':30 'scroll':1216 'search':629,665,2995 'second':1765,2283,2703,2742 'secret':660 'secur':3085 'see':549,2408 'seem':1760 'select':544,1901 'selector':39,559,690,728,1893,1911,2970,2980,3020,3039,3173,3179,3186,3196,3201,3207,3216,3230,3242,3259,3265,3274 'selenium':187,258,314,3518 'selenium-altern':186 'semant':1938 'separ':2991 'sequenc':2197,2413 'serious':2176 'server':2713,2770 'session':336,2235 'set':1073,2000,2080,2578 'settimeout':1168,1400,1580,1886,2728,3119,3125,3134 'setuid':1062 'setup':479,482,528,530,535 'setup.ts':475 'sever':1732,1898,2047,2190,2355,2517,2664,2817,2956,3092,3123,3149,3175,3202,3232,3260,3289,3316,3338 'share':465,2184,2227,2231,2310 'sharp':1724 'short':1809 'shouldn':3342 'show':2377,2464,2495,2642 'show-trac':2463,2641 'sidebar':1977 'sign':503,616 'site':101,997,2051,2696 'situat':1734,1900,2049,2192,2357,2519,2666,2819,2958 'size':2563 'skill':48,3452,3543 'skill-browser-automation' 'sleep':3147,3151,3159 'slow':2622,3099 'slower':266,1796 'slowmo':2630 'smooth':1226 'snapshot':2510 'sold':766 'solut':108,2180 'source-sickn33' 'specialist':205 'specif':262,936,1851,3565 'speed':1424 'spinner':868 'stabl':824,1874,1883,3195 'standard':290 'start':2688 'state':363,387,470,516,869,1740,2185,2236,2247,2318,2339 'status':1667,1712 'stay':160 'stealth':75,244,256,273,279,299,334,985,1003,1029,1047,1111,1119,1125,1127,2105,2108,2124,2140,2148,2154,2156,3287,3293,3301,3308,3331 'stealthplugin':1041,1049,2118,2126 'step':1202,1206,2502 'still':2860 'stop':1916,3571 'storag':383,417 'storagest':519,537,2334 'strategi':41,3156,3400 'string':1287 'structur':693,714 'stubborn':2647 'style':1896,1936 'submit':610,681,858,897,1829,1843,1994,2011,2015 'submit-button':680,857 'submit-ord':2014 'substitut':3561 'succeed':2838 'success':229,250,909,1311,1345,1523,1531,2691,3583 'sudden':2680 'support':272,646 'symptom':1741,1905,2055,2198,2363,2527,2671,2826,2963 'system':34,261 'tab':2898 'take':1763,1769,1792 'target':2851 'target-site.com':1107 'task':3434,3547 'test':8,19,57,91,179,182,192,196,202,209,343,352,356,367,373,376,389,392,397,403,420,440,474,477,533,588,669,1274,1432,1436,1605,1659,1742,1762,1906,1922,1951,2037,2183,2195,2199,2209,2213,2237,2241,2248,2254,2264,2270,2273,2275,2282,2284,2291,2304,2329,2341,2346,2359,2364,2467,2474,2492,2511,2521,2619,2628,2831,3097,3121,3131,3387,3390,3395,3399,3402,3405,3412,3468,3505,3529,3567 'test-architect':3401,3467 'test-result':2466,2473,2491 'test/task':131 'testid':2010,3283 'testmatch':529 'tests/tasks':1420 'text':633,742,903 'textbox':627,3032,3047 'textcont':1301,1307 'thing':2087 'thinker':199 'though':1954 'throw':1404 'tie':691,712,1894,1967 'time':1022,1750,2397,3129 'timelin':2496 'timeout':1292 'timezoneid':1149 'titl':1295,1309 'tobevis':464,910,1857,2915,3143 'togeth':2206 'tohavetext':438 'tool':213,274,3438,3443,3445,3458 'top':1221 'topic-agent-skills' 'topic-agentic-skills' 'topic-ai-agent-skills' 'topic-ai-agents' 'topic-ai-coding' 'topic-ai-workflows' 'topic-antigravity' 'topic-antigravity-skills' 'topic-claude-code' 'topic-claude-code-skills' 'topic-codex-cli' 'topic-codex-skills' 'trace':141,1260,2350,2404,2423,2431,2437,2459,2465,2489,2494,2634,2643 'trace.zip':2644 'tracking/analytics':1642 'travers':3255 'treat':3556 'tree':571 'tri':1276,1288,1373,1511,1584 'trigger':2883,3365 'true':1133,1312,1327,1443,1524,2082,2586,2658 'try-catch':1275 'try/catch':3351 'ts':531 'ui':184,211,3414 'ui-autom':183 'ui-design':210,3413 'ul':1979 'undefin':1447,2165 'understand':38 'undetect':311 'undetected-chromedriv':310 'unless':70 'unnecessari':1613 'unpredict':100 'unreli':3127 'upload':2472 'url':958,1176,1286,1291,1339,1413,1484,1502,1514,1521,1528,1561,1589,1619,1621,1710,2735,2737,2746,2791,2794 'url.includes':1644,1648 'url1':1485 'url2':1486 'url3':1487 'url4':1488 'url5':1489 'urls.map':1500,1560,2790 'us':1148 'usag':1406 'use':110,366,536,553,799,995,1239,1254,1427,1458,1464,1470,1604,1726,1885,1960,2005,2007,2104,2370,2430,2480,2602,2633,2748,3090,3102,3112,3118,3133,3162,3174,3183,3212,3223,3231,3240,3270,3280,3298,3375,3465,3472,3541 'user':112,404,441,540,548,561,599,674,1083,1962,1988,2796,2807,3225,3366,3384,3397,3408,3421,3435,3473,3478,3483,3489,3494,3500,3506,3514,3519,3524,3530,3535 'user-ag':2806 'user-fac':111,539,560,598,673,1961,1987,3224 'user@example.com':493,655 'userag':1143,2799,2809 'useragents.length':2812 'v':733 'v3':2482 'valid':3088,3420,3566 'valu':1803 'verbos':268 'verif':2840 'video':1265,2448,2453 'view':2458 'viewer':2635 'viewport':1075,1138,2562,2580,2591,2603 'visibl':576,820,2027,2540,2544,2560,2966 'visual':207,3410,3416 'visual-regression-test':206 'vs':97 'wait':40,122,127,224,506,787,791,796,805,808,838,841,854,883,887,901,913,926,933,934,956,969,1731,1735,1786,1833,1849,1869,1881,2879,3069,3109,3138,3155,3168 'waitfor':3087 'waitforrespons':3113 'waitfortimeout':1727,1774,1817,1888,3091,3094,3103 'waitforurl':3114 'waituntil':1108 'war':69 'warn':3124,3150,3176,3203,3261,3339 'watch':2532,2625 'way':547,2314 'web':7,18,175,3498 'web-scrap':174 'webdriv':2163 'webkit':1469 'welcom':637 'well':3454 'went':2382 'widest':270 'width':1078,1139,2592,2604 'win64':1091 'window':1088,2572,2801,2825,2833,2856,2868,2922,2941 'window.scrollby':1220 'window/tab':2886 'windows/popups':2814 'without':1923,2403,3286,3292,3300,3324,3335,3350 'withretri':1355,1410 'won':66 'work':677,884,1917,2066,2528,2575,2672,2971,2981,3453 'worker':1444,1450 'workflow':3426,3428,3461 'workflow-autom':3427,3460 'wrong':839,1819,1966,2260,2383,2846 'x64':1092 'xpath':711,3229,3234,3241,3247","prices":[{"id":"359245ac-4842-4192-bda8-2b9319faefb9","listingId":"e9d560a1-f9ce-443b-839c-7c1116245cda","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"sickn33","category":"antigravity-awesome-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T20:27:36.825Z"}],"sources":[{"listingId":"e9d560a1-f9ce-443b-839c-7c1116245cda","source":"github","sourceId":"sickn33/antigravity-awesome-skills/browser-automation","sourceUrl":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/browser-automation","isPrimary":false,"firstSeenAt":"2026-04-18T21:33:46.131Z","lastSeenAt":"2026-04-25T12:50:31.367Z"},{"listingId":"e9d560a1-f9ce-443b-839c-7c1116245cda","source":"skills_sh","sourceId":"sickn33/antigravity-awesome-skills/browser-automation","sourceUrl":"https://skills.sh/sickn33/antigravity-awesome-skills/browser-automation","isPrimary":true,"firstSeenAt":"2026-04-18T20:27:36.825Z","lastSeenAt":"2026-04-25T12:40:16.515Z"}],"details":{"listingId":"e9d560a1-f9ce-443b-839c-7c1116245cda","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"sickn33","slug":"browser-automation","github":{"repo":"sickn33/antigravity-awesome-skills","stars":35034,"topics":["agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding","ai-workflows","antigravity","antigravity-skills","claude-code","claude-code-skills","codex-cli","codex-skills","cursor","cursor-skills","developer-tools","gemini-cli","gemini-skills","kiro","mcp","skill-library"],"license":"mit","html_url":"https://github.com/sickn33/antigravity-awesome-skills","pushed_at":"2026-04-25T06:33:17Z","description":"Installable GitHub library of 1,400+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and more. Includes installer CLI, bundles, workflows, and official/community skill collections.","skill_md_sha":"9bf8245ba92f89d55fac7064ab11d760a2a6a0f4","skill_md_path":"skills/browser-automation/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/browser-automation"},"layout":"multi","source":"github","category":"antigravity-awesome-skills","frontmatter":{"name":"browser-automation","description":"Browser automation powers web testing, scraping, and AI agent"},"skills_sh_url":"https://skills.sh/sickn33/antigravity-awesome-skills/browser-automation"},"updatedAt":"2026-04-25T12:50:31.367Z"}}