Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
Browser automation powers web testing, scraping, and AI agent interactions.
The difference between a flaky script and a reliable system comes down to
understanding selectors, waiting strategies, and anti-detection patterns.
This skill covers Playwright (recommended) and Puppeteer, with patterns for
testing, scraping, and agentic browser control. Key insight: Playwright won
the framework war. Unless you need Puppeteer's stealth ecosystem or are
Chrome-only, Playwright is the better choice in 2025.
Critical distinction: Testing automation (predictable apps you control) vs
scraping/agent automation (unpredictable sites that fight back). Different
problems, different solutions.
Principles
Use user-facing locators (getByRole, getByText) over CSS/XPath
Never add manual waits - Playwright's auto-wait handles it
Each test/task should be fully isolated with fresh context
Screenshots and traces are your debugging lifeline
Headless for CI, headed for debugging
Anti-detection is cat-and-mouse - stay current or get blocked
BrowserStack - When: Cross-browser testing at scale Note: Real devices, CI integration
Patterns
Test Isolation Pattern
Each test runs in complete isolation with fresh state
When to use: Testing, any automation that needs reproducibility
TEST ISOLATION:
"""
Each test gets its own:
Browser context (cookies, storage)
Fresh page
Clean state
"""
Playwright Test Example
"""
import { test, expect } from '@playwright/test';
// Each test runs in isolated browser context
test('user can add item to cart', async ({ page }) => {
// Fresh context - no cookies, no storage from other tests
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 }) => {
// Completely isolated - cart is empty
await page.goto('/cart');
await expect(page.getByText('Your cart is empty')).toBeVisible();
});
"""
Shared Authentication Pattern
"""
// Save auth state once, reuse across tests
// setup.ts
import { test as setup } from '@playwright/test';
await page.goto('/dashboard');
// apiResponses now contains all API calls
"""
Sharp Edges
Using waitForTimeout Instead of Proper Waits
Severity: CRITICAL
Situation: Waiting for elements or page state
Symptoms:
Tests pass locally, fail in CI. Pass 9 times, fail on the 10th.
"Element not found" errors that seem random. Tests take 30+ seconds
when they should take 3.
Why this breaks:
waitForTimeout is a fixed delay. If the page loads in 500ms, you wait
2000ms anyway. If the page takes 2100ms (CI is slower), you fail.
There's no correct value - it's always either too short or too long.
await page.getByRole('button').click(); # Auto-waits for stable
NEVER use setTimeout or waitForTimeout in production code
CSS Selectors Tied to Styling Classes
Severity: HIGH
Situation: Selecting elements for interaction
Symptoms:
Tests break after CSS refactoring. Selectors like .btn-primary stop
working. Frontend redesign breaks all tests without changing behavior.
Why this breaks:
CSS class names are implementation details for styling, not semantic
meaning. When designers change from .btn-primary to .button--primary,
your tests break even though behavior is identical.
Recommended fix:
Use user-facing locators instead:
WRONG - Tied to CSS:
await page.locator('.btn-primary.submit-form').click();
await page.locator('#sidebar > div.menu > ul > li:nth-child(3)').click();
Symptoms:
Immediate 403 errors. CAPTCHA challenges. Empty pages. "Access Denied"
messages. Works for 1 request, then gets blocked.
Why this breaks:
By default, headless browsers set navigator.webdriver = true. This is
the first thing bot detection checks. It's a bright red flag that
says "I'm automated."
Recommended fix:
Use stealth plugins:
Puppeteer Stealth (best option):
import puppeteer from 'puppeteer-extra';
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
For serious scraping, consider managed solutions like Browserbase.
Tests Share State and Affect Each Other
Severity: HIGH
Situation: Running multiple tests in sequence
Symptoms:
Tests pass individually but fail when run together. Order matters -
test B fails if test A runs first. Random failures that "fix themselves"
on rerun.
Why this breaks:
Shared browser context means shared cookies, localStorage, and session
state. Test A logs in, test B expects logged-out state. Test A adds
item to cart, test B's cart count is wrong.
// 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'
});
Never modify global state in tests
Never rely on previous test's actions
No Trace Capture for CI Failures
Severity: MEDIUM
Situation: Debugging test failures in CI
Symptoms:
"Test failed in CI" with no useful information. Can't reproduce
locally. Screenshot shows page but not what went wrong. Guessing
at root cause.
Why this breaks:
CI runs headless on different hardware. Timing is different. Network
is different. Without traces, you can't see what actually happened -
the sequence of actions, network requests, console logs.
Recommended fix:
Enable traces for failures:
playwright.config.ts:
export default defineConfig({
use: {
trace: 'retain-on-failure', # Keep trace on failure
screenshot: 'only-on-failure', # Screenshot on failure
video: 'retain-on-failure', # Video on failure
},
outputDir: './test-results',
});
Symptoms:
Works perfectly when you watch it. Fails mysteriously in CI.
"Element not visible" in headless but visible in headed mode.
Why this breaks:
Headless browsers have no display, which affects some CSS (visibility
calculations), viewport sizing, and font rendering. Some animations
behave differently. Popup windows may not work.
Symptoms:
Works for first 50 pages, then 429 errors. Suddenly all requests fail.
IP gets blocked. CAPTCHA starts appearing after successful requests.
Why this breaks:
Sites monitor request patterns. 100 requests per second from one IP
is obviously automated. Rate limits protect servers and catch scrapers.
Symptoms:
Click button, nothing happens. Test hangs. "Window not found" errors.
Actions succeed but verification fails because you're on wrong page.
Why this breaks:
target="_blank" links open new windows. Your page reference still
points to the original page. The new window exists but you're not
listening for it.
Recommended fix:
Wait for popup BEFORE triggering it:
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();
const pages = context.pages(); // Get all open pages
Can't Interact with Elements in iframes
Severity: MEDIUM
Situation: Page contains embedded iframes
Symptoms:
Element clearly visible but "not found". Selector works in DevTools
but not in Playwright. Parent page selectors work, iframe content
doesn't.
Why this breaks:
iframes are separate documents. page.locator only searches the main
frame. You need to explicitly get the iframe's frame to interact
with its contents.