Playwright (Automation + MCP + Scraper)

Automates, tests, and debugs browsers with Playwright: locators, auto-waiting, traces, CI runs, and MCP browser control. Use when a test is flaky, times out, or fails only in CI or…

Iván

@ivangdavila

What This Skill Does

Browser automation tool using Playwright MCP. Navigates websites, clicks elements, fills forms, takes screenshots, extracts data, and debugs real browser workflows through MCP commands or direct Playwright scripts.

Replaces manual browser testing and static fetch tools by providing a real browser environment for JS-rendered pages, multi-step forms, and UI debugging.

When to Use It

  • Navigate to a JavaScript-rendered page and extract structured data
  • Fill and submit a multi-step web form automatically
  • Take a full-page screenshot or generate a PDF of a live website
  • Debug a UI bug by running a headed browser with trace and console inspection
  • Automate file downloads or uploads through a browser interface
  • Reproduce a user workflow step-by-step for testing or verification

Install

$ openclaw skills install @ivangdavila/playwright

User preferences and memory live in ~/Clawic/data/playwright/ (see setup.md on first use). If you have data at an old location (~/playwright/ or ~/clawic/playwright/), move it to ~/Clawic/data/playwright/. Everything a run produces — specs, traces, reports, snapshots, storageState files — stays inside the repository or its temp dir, never in the preferences folder: auth state is a credential and belongs in a gitignored path.

When To Use

  • Writing or repairing Playwright specs, fixtures, playwright.config.ts, or one-off automation scripts
  • A run fails: timeout, strict mode violation, flaky click, passes locally and fails in CI or headless
  • Login, session reuse, uploads, downloads, iframes, popups, dialogs, or device emulation are in the way
  • Visual snapshots, accessibility checks, trace capture, sharding, or cutting suite wall time
  • Driving a live browser through Playwright MCP, or extracting data a plain fetch cannot render
  • Porting a Cypress, Puppeteer, or Selenium suite to Playwright, including deciding which tests not to port
  • Not for maintaining an existing Cypress or Puppeteer suite (→ cypress, puppeteer) or for anything an HTTP request already answers (→ http)

Quick Reference

SituationPlay
strict mode violation: resolved to N elementsNarrow with .filter({ hasText }) or a parent chain — never .nth() unless position is the behavior under test → selectors.md
Timeout 30000ms exceeded waiting for locatorThe locator never matched. Trace it, do not raise the timeout → debugging.md
element is not visible / intercepts pointer eventsAn overlay, animation, or disabled state — force: true hides the bug → waiting.md
Test needs a waitForTimeout to passReplace with a web-first assertion on the state you were sleeping for → waiting.md
Green locally, red in CI (or only headless)Compare viewport, workers, timezone, animations, and browser build → flake.md, ci-cd.md
Passes alone, fails in the suiteShared account, shared server data, or leaked storage — isolate per worker → auth.md
Same test red 1 run in 20Measure before fixing: --repeat-each, then classify by cause → flake.md
Login repeated in every testSign in once in a setup project, reuse storageStateauth.md
A test passes while the feature is broken, or nobody knows what deserves an E2EWhat earns a test, outcome assertions, skip and fixme annotations → testing.md
Writing playwright.config.ts, adding a project, a setting that will not applyConfig anatomy, precedence, projects, webServer, tags → config.md
Setup repeated in every test, page objects, or where test data comes fromTest- and worker-scoped fixtures, page-object rules, data strategy → fixtures.md
Third party, payment sandbox, or slow API in the pathIntercept with page.route, or record and replay a HAR → network.md
Upload, download, PDF, or drag-and-dropEvent-first patterns (waitForEvent('download'), setInputFiles) → files.md
New tab, popup, window.alert, geolocation, dark mode, offlineContext-level control; dialogs auto-dismiss unless handled → contexts.md
Screenshot diff passes locally, fails on the runnerSnapshots are per-platform; generate them where CI runs → visual.md
WCAG or keyboard-navigation coverageaxe scan plus real keyboard assertions → accessibility.md
Suite takes too longMeasure the slowest tests first, then parallelize or seed via API → performance.md
Browser missing, wrong channel, WebKit-only failureVersion-locked browsers and engine differences → browsers.md
Agent should click through a live site nowSnapshot-driven MCP browser control → mcp.md
Pull data from a JS-rendered pageBounded extraction with throttling → scraping.md
Porting from Cypress, Puppeteer, or SeleniumTranslation table and the habits to drop → migration.md
Anything elseReproduce headed with a trace (npx playwright test <file> --headed --trace on), open the trace, and read the DOM snapshot at the failing step

CLI toolkit: commands.md. First-use preference loading: setup.md.

Core Rules

  1. Locate by what the user perceives. Order: role/label/text → test ID → CSS/XPath. getByRole('button', { name: 'Submit' }) survives a class rename and a CSS-in-JS rebuild; .css-1a2b3c does not. Test IDs are a contract you own — fine, but they assert nothing about accessibility.
  2. Never sleep; assert. page.waitForTimeout(500) is simultaneously too slow on a fast run and too short on a loaded runner. Web-first assertions re-check until the expect timeout (5 s default) and stop the moment the condition holds — strictly faster and strictly more reliable.
  3. Budget the timeouts, do not inflate them. Defaults: test 30 000 ms, expect 5 000 ms, action and navigation 0 (unbounded, so the test timeout is the real ceiling), webServer 60 000 ms. Sizing rule: sum the slowest legitimate path, measured not guessed, and keep the total inside the 30 000 ms default — login 4 s + dashboard render 8 s + one 5 s assertion = 17 s, comfortably inside 30 s. When one step is genuinely slow, raise that assertion's timeout, not the global test timeout: a global bump turns every real hang into a 2-minute wait.
  4. One test, one fresh context, one account it owns. Playwright gives each test a new browser context; that resets cookies and storage but not your database. With N workers, allocate N accounts and index them by parallelIndex — sharing one mutable user makes failures order-dependent, and order-dependent failures are unfixable by reading the diff.
  5. Capture before you rewrite. For any failure you did not cause in the last edit, get a trace first (trace: 'on-first-retry', or --trace on for one run). Rewriting a locator against a symptom you never observed is how a two-line bug becomes a rewritten suite.
  6. Retries buy green builds and hide the bill. A per-test failure probability p across N tests makes a full run red with probability 1 − (1 − p)^N: p = 0.005 across 300 tests → 78% of runs red. Two retries mask that, but the flake is still there and now costs 3× wall time on every occurrence. Keep retries, and track the flaky count as a number that must go down (flake.md).
  7. Mock what you do not own; seed through the API, click through the UI only for what is under test. Creating a user by API and starting the test on the target page removes minutes and removes the upstream service from your failure surface. Exception: the integration itself is the thing being verified.
  8. Treat production and destructive flows as opt-in. Automating a real payment, deletion, or email send needs the user to say so in this session; default to local or staging. storageState files are live credentials — gitignore them and never write one into a shared or synced folder.
  9. Pin the browsers to the Playwright version. Browser builds ship with the package, so mcr.microsoft.com/playwright:vX.Y.Z-* must match the installed @playwright/test exactly, and a CI cache key must include that version. A mismatch surfaces as Executable doesn't exist at .../ms-playwright/... or, worse, as a silent behavior difference.

Failure Signatures

Read the first line of the error before anything else; it names the subsystem.

MessageReal causeFirst move
strict mode violation: ... resolved to 3 elementsThe locator describes a category, not an elementFilter or chain (selectors.md); the error lists the matches — read them
Timeout 30000ms exceeded. waiting for locator(...)Never matched: wrong frame, wrong name, not renderedTrace → DOM snapshot at that step (debugging.md)
waiting for element to be visible, enabled and stableMatched but not actionable: animation, overlay, disabledwaiting.md actionability table — not force: true
element intercepts pointer eventsAnother node covers the target (cookie banner, toast, modal backdrop)Dismiss it in a fixture; the banner is real user-facing state
Test timeout of 30000ms exceeded while running "beforeEach" hookSetup, not the test, is slow or hangingMove it to a setup project or fixture with its own timeout
Target page, context or browser has been closedAction after close(), or the process crashed mid-runCheck for a crash in the worker log; in Docker suspect /dev/shm (ci-cd.md)
Executable doesn't exist at .../ms-playwright/...Browsers not installed or version-mismatchednpx playwright install --with-deps at the pinned version (rule 9)
net::ERR_CONNECTION_REFUSEDApp not up, wrong port, or webServer exitedwebServer block plus reuseExistingServer: !process.env.CI
net::ERR_CERT_AUTHORITY_INVALIDSelf-signed cert on staging or a corporate MITM proxyignoreHTTPSErrors: true scoped to that project only
Screenshot comparison failed, -linux.png missingSnapshots are keyed by platform and projectGenerate baselines on the CI platform (visual.md)
No error, test passes, feature is brokenAsserted on the command, not the outcomeAssert user-visible state (testing.md)

Isolation Model

What resets, and when — the mental model behind most "it only fails in the suite" bugs.

LayerCost to createResetsLeaks across tests
BrowserExpensive; one per worker, reusedNothing between testsYes, by design
ContextCheap; new one per test by defaultCookies, localStorage, sessionStorage, permissions, service workersNo
PageCheapest; many per contextNothing (shares the context jar)Within the test only
Worker processRestarted after a failure and on retryWorker fixtures re-runModule-level globals inside one worker
Server, database, mailboxNot managed by PlaywrightNothingYes — this is the real leak

Consequences: fullyParallel: true runs tests inside one file across workers, so file-level ordering guarantees vanish. test.describe.serial restores order but skips the remaining tests on the first failure and re-runs the whole group on retry. A retried test starts in a fresh worker — which is exactly why some flake "fixes itself" on retry and tells you the leak was in-process.

Output Gates

Before delivering a spec, script, or config:

  • Zero waitForTimeout used for synchronization and zero networkidle waits — each replaced by an assertion on the state being waited for (a politeness delay in extraction is the only exception, scraping.md)?
  • Every locator resolves to exactly one element, without .first()/.nth() standing in for a real filter?
  • Assertions check user-visible outcomes (text, URL, enabled state, download), not that a call returned?
  • No shared mutable account or shared seed data between tests that can run in parallel?
  • Trace configured (on-first-retry at minimum) and failure artifacts uploaded in CI?
  • No credential, token, or storageState path committed; production URLs absent unless the user asked for them?
  • Runs headless on a clean checkout with browsers installed at the pinned version?

Configuration

User-dependent variables. Defaults apply until the user states a preference; store them in ~/Clawic/data/playwright/config.yaml.

VariableTypeDefaultEffect
languagetypescript | javascripttypescriptLanguage of every emitted spec, config, and snippet; switches playwright.config.ts to .js and drops type imports
runner_modetest | script | mcptestWhich path Quick Reference proposes first: @playwright/test suite, standalone Playwright script, or MCP browser control
default_browserslist (chromium, firefox, webkit)chromiumProjects written into config and the engines assumed when diagnosing a failure (browsers.md)
cross_browser_cadencepr | nightly | releasenightlyWhich gate the non-default engines run at: every PR, a scheduled run, or release only — sets the matrix in browsers.md and the jobs written in ci-cd.md
headed_by_defaultboolfalseAdds --headed to suggested commands and headless: false to launch options during exploration
test_id_attributestringdata-testidValue written to testIdAttribute and the attribute getByTestId examples use
ci_providergithub | gitlab | othergithubWhich pipeline template ci-cd.md guidance is rendered for, including artifact and cache steps
scrape_delay_msnumber (0-60000)1000Minimum gap between page loads in multi-page extraction (scraping.md throttling)

Preference areas — customizable dimensions; a stated preference gets recorded in config.yaml and applied:

  • Tooling — package manager (npm/pnpm/yarn/bun) used in every command, UI mode vs CLI, codegen vs hand-written locators
  • Conventions — spec layout and naming, page-object vs inline fixtures, tag vocabulary (@smoke, @flaky), assertion style
  • Platform — browser channels (chrome, msedge), device emulation set, CI runner OS, official Docker image vs manual browser install
  • Safety posture — whether non-local base URLs may be automated at all, confirmation before destructive or paid flows, how loudly to flag production targets
  • Flake policy — retry count in CI, quarantine-and-file vs fix-before-merge, whether a flaky pass fails the build
  • Cadence — what runs at which gate (PR, nightly, release), how often the @integration spec hits the real third party, how long a @flaky quarantine may last before the test is fixed or deleted, when a recorded HAR and an accessibility scan are refreshed
  • Extraction posture — crawl breadth, robots and terms strictness, asset blocking, stop conditions
  • Output format — reporter set (list, html, blob, junit), how much trace and video to retain, verbosity of failure summaries

Traps

TrapWhy it failsDo instead
force: true to get past a failing clickBypasses the actionability checks that were reporting a real overlay or disabled control — the test goes green while the user stays blockedFind the blocker in the trace; dismiss banners in a fixture (waiting.md)
waitForLoadState('networkidle')Analytics beacons, polling, and websockets keep an SPA "busy" forever; the docs discourage itAssert on the element or response you actually need
.first() / .nth(0) to silence strict modeFreezes an accidental DOM order; a new promo card at index 0 silently retargets the test.filter({ hasText }), .filter({ has }), or scope to a parent
page.$$eval / locator.all() for listsNeither auto-waits; both snapshot whatever exists at call time, so a slow render yields an empty array and a passing testawait expect(list).toHaveCount(n) first, then read
locator.isVisible() in an ifReturns immediately without retrying — the classic "works on my fast machine" conditionalawait expect(locator).toBeVisible(), or toBeHidden() for the negative
Storing an ElementHandle across a re-renderThe handle points at a detached node; locators re-resolve at action time, handles do notKeep locators in variables; handles only when you need the raw node
One storageState for every test that mutates dataParallel workers fight over the same server-side rowsOne account per worker indexed by parallelIndex (auth.md)
Building the app's own page objects before any duplication existsAn abstraction layer nobody needed makes every future failure two files away from its causeInline first; extract on the third repeat (fixtures.md)
Locators reaching into an iframeLocators pierce open shadow DOM but never an iframe boundarypage.frameLocator(...) / locator.contentFrame() (selectors.md)
Registering a dialog handler and forgetting to respondThe page hangs; without any handler Playwright auto-dismisses, so adding one changes behaviorAlways accept() or dismiss() inside the handler (contexts.md)
Committing a suite that only passes headedHeadless differs in fonts, media codecs, and window size, and CI has no displayRun headless locally before handing off (browsers.md)
Driving a full browser to read static HTMLSlower, flakier, and a browser process per request for data a fetch returnsTry the HTTP path first (scraping.md, http)

Where Experts Disagree

  • Test IDs vs semantic locators. Role and label locators double as an accessibility smoke test and break when the UI genuinely changes for users; test IDs never break and never tell you anything. Default: semantic first, test IDs for elements with no accessible name (icon buttons, list rows, canvas widgets). The boundary is whether the string a locator uses is one a user could perceive.
  • Retries in CI. Two retries is the scaffolded default and keeps a large suite mergeable; the zero-retry camp argues every masked flake is a real race in the product. Workable line: retries on, flaky count reported and trending down, and no test allowed to stay flaky across two sprints.
  • How many E2E tests. Pyramid says a handful of journeys; the trophy argues browser tests are the only ones that check what ships. Decide by cost: an E2E test that takes 20 s and catches a class of bug unit tests structurally cannot is worth it; one that re-tests validation logic is not.
  • Agent-driven browsing vs committed automation. MCP is unbeatable for exploration and one-off tasks and leaves nothing reviewable behind. Anything that must run again next week gets written down as code (mcp.md).

Security & Privacy

EndpointData sentPurpose
Sites the user asks to automateNavigation, form input, cookies, uploaded files, page interactionsThe requested automation, testing, capture, or extraction
https://registry.npmjs.orgPackage metadata and tarballs on installInstall Playwright or Playwright MCP
Playwright browser CDN (during playwright install)Browser build downloadsFetch the version-locked browser binaries

This skill does NOT: persist sessions or credentials by default; write anything outside the repository except the preferences folder above; recommend fingerprint spoofing, CAPTCHA-solving services, or rotating exits; automate production, payment, or destructive flows without the user asking in the current session; or send page content anywhere beyond the sites involved in the task.

Guardrails: auth artifacts stay in a gitignored path and are never printed into logs or reports; traces and videos can contain tokens and personal data, so treat uploaded CI artifacts as sensitive and cap retention; extraction stays inside the scope the user named.

Related Skills

More Clawic skills, get them at https://clawic.com/skills/playwright (install if the user confirms):

  • cypress — the same job in a Cypress codebase
  • puppeteer — Chrome-only automation without the test runner
  • http — methods, status codes, headers, and caching when the answer needs no browser at all
  • scrape — robots, rate limits, and data-handling compliance for larger crawls
  • screenshot — capture craft beyond test assertions

Feedback

Part of Clawic, the verified skill library. Get this skill: https://clawic.com/skills/playwright.

Top skills in this category