SuperAPI AI Directory

playwright-verify

Testing & quality

View repository

Updated 10-16 01:07

About this skill

Skill: playwright-verify

Distilled from GH#96 — a CHEAP verification tool (< 5s per run, no LLM) and the ONLY way to interact with localhost/file:// when claude-in-chrome is blocked for these 2 schemes on the machine.

WHAT

Purpose and context

  • Purpose: verify code quickly, one-off, using Playwright run via a standalone .mjs + node (no test runner): capture screenshots localhost/file://, catch console/pageerror/http/requestfailed errors, measure real elements, auth bypass; for multi-step work use a persistent session connectOverCDP.
  • Trigger (when to use):
  • claude-in-chrome reports Frame with ID 0 is showing error page or Can't interact with browser-internal or unparseable URLs on localhost:* / file://.
  • Need to diagnose a UI element that is "not visible" / "not clickable" — a screenshot is not enough, real measurements are needed (getBoundingClientRect, getComputedStyle).
  • Need to verify 1 code hypothesis and then throw it away (not worth creating *.spec.ts + test runner).
  • Need to test via dev-login/fake session instead of real SSO login every time.
    • User says 'verify with playwright', 'screenshot the page', 'quickly test UI with a script', 'verify multiple steps', 'keep the session between runs', 'cannot click / cannot see the element', or /playwright-verify.
  • Non-goals: does not create *.spec.ts + test runner (npx playwright test), is not a UAT test case suite (→ uat-nonit-testcase), does not replace claude-in-chrome for normal external websites, does not commit scripts into the target repo.

Mental model

giả thuyết cần verify → (đã cài?) → script .mjs một-phát: launch → gắn 4 kênh lỗi → goto (load + wait) → screenshot/đo → close → đọc log + ảnh → vứt. Multi-step branch: một Chrome CDP riêng (profile tạm) ← mỗi lượt step-N.mjs bám vào, state sống giữa các lượt → pkill khi xong.

Input and output contract

Field Required? Meaning
In URL (localhost:* / file:// / other) yes the page to verify
In project has @playwright/test in node_modules yes the script must be run FROM that directory (or NODE_PATH)
In cookie/localStorage dev-login no for auth bypass
Out screenshot (e.g. /tmp/check.png) + log [console]/[pageerror]/[http]/[reqfail] yes as evidence; a clean screenshot alone is NOT enough
Out measurements of getBoundingClientRect/getComputedStyle when diagnosing an element real numbers instead of guessing from the screenshot
Out "done" — hypothesis confirmed/refuted by log + screenshot; script cleaned up; Chrome CDP (if any) has pkill

Rules and capabilities

  • RULE-01 (MUST): import { chromium } from "@playwright/test" must be run FROM WITHIN the project directory that has @playwright/test in node_modules — ESM resolves by cwd, not by the script file location. A script in another directory (e.g. /tmp) → cp into the project before running, or point NODE_PATH.
  • RULE-02 (MUST): waitUntil: "networkidle" HANGS INDEFINITELY on Next.js dev server — HMR/webpack dev keeps a long-poll/websocket connection alive continuously so the network never "idle". Use waitUntil: "load" + page.waitForTimeout(N) fixed.
  • RULE-03 (MUST): Auth bypass for dev-login: context.addCookies([{url, name, value}]) (prefer url instead of domain+path individually — avoid cookies not matching) + context.addInitScript(...) set localStorage — set state BEFORE calling newPage(), not after.
  • RULE-04 (MUST): 1 context for each independent route if the app has logic "encounter 401 at secondary API → automatically clear global session" — reusing 1 context for multiple navigations causes route N's error to contaminate route N+1.
  • RULE-05 (MUST): Diagnose "not seen/not clickable": don't just look at screenshots — call page.evaluate(() => el.getBoundingClientRect()) + getComputedStyle(el) to get real measurements; locator.click() reports clear errors itself ("element is not visible") with retry step logs, read that log before guessing the cause.
  • RULE-06 (MUST): A clean screenshot proves nothing — always extract 4 error channels, not just 2: console (type error) · pageerror · response has status() >= 400 · requestfailed. Analytics crash, API 5xx, promise rejection show up in no pixel. Attach to page BEFORE goto:
    page.on("response", r => { if (r.status() >= 400) console.log("[http]", r.status(), r.url()); });
    page.on("requestfailed", r => console.log("[reqfail]", r.failure()?.errorText, r.url()));
    
  • RULE-07 (MUST): Session persistence mode: a separate Chrome, a separate profile (--user-data-dir temp). Never --remote-debugging-port the user's personal Chrome — macOS raises a permission dialog and the script will step on the user's active tabs.
  • RULE-08 (MUST): Standalone script files do not go into git — run from scratchpad, do not commit into the target repo.
  • RULE-09 (MUST): Touch only what the task requires — no opportunistic changes.
  • Capabilities: run a local headless browser + read/write temp files; external network only when installing a binary (CDN), with timeout.

Failure boundaries

  • npx playwright install is blocked by network/hangs → blocked, report a clear error (with timeout), do not let the command hang indefinitely.
  • ESM does not resolve @playwright/test (running from wrong cwd) → cp the script into the project or NODE_PATH, then rerun.
  • networkidle hangs on Next.js dev → switch to load + waitForTimeout (not an app bug).
  • Element not seen/not clickable → measure real numbers + read log locator.click() before concluding; do not guess from screenshots.

HOW

Main workflow

Step Type Inputs Action Outputs/exit Failure/next
W01 deterministic machine Check if installed (ls ~/Library/Caches/ms-playwright); if not, install binary ready CDN blocked → blocked
W02 judgment hypothesis + URL Write 1 file .mjs run directly with node, attach 4 error channels before goto script —
W03 effect script Run from the correct directory (node check.mjs), read log + images; clean up script log + images resolve error → cp/NODE_PATH, repeat W03
W04 effect multi-step work Persistent session mode connectOverCDP (B01) state kept between turns done → pkill

Details of each step (source of truth for W01–W04):

  1. Check whether it's already installed before reinstalling: ls ~/Library/Caches/ms-playwright (macOS) — the binary cache is shared across all projects, no need to reinstall per-repo.
    • Not present: pnpm add -D @playwright/test (skip if the project already has it in devDependencies) then npx playwright install chromium.
    • npx playwright install downloads the binary from an external CDN (cdn.playwright.dev) — a sandbox/CI with a network allowlist will hang or fail. Set a timeout when calling automatically; if blocked, report a clear error instead of letting the command hang indefinitely.
  2. Write 1 file .mjs run directly with node (not via npx playwright test) — heavier for verify-then-discard:
    import { chromium } from "@playwright/test"; // export chromium dùng được như package "playwright"
    
    const browser = await chromium.launch();
    const page = await browser.newPage({ viewport: { width: 1400, height: 900 } });
    page.on("console", m => console.log("[console]", m.type(), m.text()));
    page.on("pageerror", e => console.log("[pageerror]", e.message));
    
    await page.goto("http://localhost:3000/some-page", { waitUntil: "load" }); // KHÔNG "networkidle" — xem Rules #2
    await page.waitForTimeout(1500);
    await page.screenshot({ path: "/tmp/check.png", fullPage: true });
    
    await browser.close();
    
  3. Run from the correct directory (see Rules #1), node check.mjs, read console/pageerror log + screenshots. Clean up file .mjs afterward if running in 1 specific project (no need to delete anything from git — the script isn't in the repo if run from a scratch dir).
  4. MULTI-STEP work with conditions → persistent session mode (learned from browser-use/browser-harness, 09/2026). One-shot scripts force the agent to blindly write the whole scenario; one wrong step means rerunning from login. Instead: set up ONE Chrome with a CDP port, each turn is a small script attaching to it — tab, cookie, localStorage, scroll are preserved between turns, the agent looks before deciding the next step.
    # một lần — Chrome riêng, profile riêng; KHÔNG bám vào Chrome cá nhân của user
    CH="$HOME/Library/Caches/ms-playwright/chromium-1243/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"
    "$CH" --remote-debugging-port=9333 --user-data-dir=/tmp/pw-prof --headless=new --no-first-run about:blank &
    
    // mỗi lượt — step-N.mjs
    import { chromium } from "@playwright/test";
    const browser = await chromium.connectOverCDP("http://localhost:9333");
    const ctx = browser.contexts()[0];
    const page = ctx.pages()[0] ?? await ctx.newPage();   // bám tab cũ, KHÔNG launch mới
    // ... một hành động + quan sát; state sống sang lượt sau ...
    await browser.close();   // chỉ ngắt kết nối, Chrome vẫn chạy
    
    When done: pkill -f "remote-debugging-port=9333". Measured 09/09/2026 (python-playwright, same page file://): fresh launch 1.1–1.9s/turn, loses state; connectOverCDP 0.65–0.80s/turn, window.__marker survives across a new process (3/3). The .mjs version above hasn't been run with node in the measurement session — the API is identical to the python version that was run.

Branches

ID Kind Guard Behavior Skip / failure Rejoin
B01 conditional_required MULTI-STEP work with conditions (need to look before deciding the next step) a separate Chrome --remote-debugging-port=9333 + temp profile; each turn step-N.mjs connectOverCDP one-shot → skip; never stick to personal Chrome ends with pkill -f "remote-debugging-port=9333"
B02 user_optional page needs dev-login/fake session context.addCookies + context.addInitScript BEFORE newPage(); 1 context/route if the app self-clears session on 401 — W03
B03 recovery element "not visible/not clickable" page.evaluate(() => el.getBoundingClientRect()) + getComputedStyle(el), read the retry log of locator.click() — W03

Validation and stopping

Conclude based on 4 error channels + screenshots + measurements, not just screenshots. Each run < 5s, no LLM. Stop when the hypothesis has evidence; clean up scripts + pkill Chrome CDP.

Examples

  • Positive: claude-in-chrome reports Frame with ID 0 is showing error page on http://localhost:3000/some-page → cp check.mjs into project → node check.mjs → /tmp/check.png + clean log for both [console]/[pageerror]/[http]/[reqfail] → confirm the page renders, delete script.
  • Boundary/failure: script uses waitUntil: "networkidle" on Next.js dev → hangs indefinitely; fix to waitUntil: "load" + page.waitForTimeout(1500) → run completes. Screenshot clean but log has [http] 500 /api/x → do NOT report pass.