playwright-verify
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSkill: playwright-verify
Skill: playwright-verify
Đúc kết từ GH#96 — công cụ verify RẺ (< 5s/lần, không LLM) và là cách DUY NHẤT tương tác
được với / khi bị chặn 2 scheme này trên máy.
localhostfile://claude-in-chrome提炼自 GH#96——低成本验证工具(每次 < 5 秒,不使用 LLM),并且是在本机 阻止访问这两个 scheme 时,唯一能够与 / 交互的方式。
claude-in-chromelocalhostfile://WHAT
WHAT
Purpose và context
Purpose 与 context
- Purpose: verify code nhanh, một lần, bằng Playwright chạy qua standalone +
.mjs(không test runner): chụp ảnhnode/localhost, moi lỗi console/pageerror/http/requestfailed, đo phần tử thật, auth bypass; việc nhiều bước thì phiên bềnfile://.connectOverCDP - Trigger (when to use):
- báo
claude-in-chromehoặcFrame with ID 0 is showing error pagetrênCan't interact with browser-internal or unparseable URLs/localhost:*.file:// - Cần chẩn đoán phần tử UI "không thấy" / "không bấm được" — ảnh chụp không đủ, cần số đo
thật (,
getBoundingClientRect).getComputedStyle - Cần verify 1 giả thuyết code rồi vứt (không xứng tạo + test runner).
*.spec.ts - Cần test qua dev-login/session giả thay vì đăng nhập SSO thật mỗi lần.
- User nói 'verify bằng playwright', 'chụp ảnh trang', 'test nhanh UI bằng script', 'verify nhiều bước', 'giữ session giữa các lần chạy', 'không click được / không thấy phần tử', hoặc .
/playwright-verify
- User nói 'verify bằng playwright', 'chụp ảnh trang', 'test nhanh UI bằng script', 'verify nhiều bước', 'giữ session giữa các lần chạy', 'không click được / không thấy phần tử', hoặc
- Non-goals: không tạo + test runner (
*.spec.ts), không phải bộ test case UAT (→ uat-nonit-testcase), không thay claude-in-chrome cho trang web ngoài bình thường, không commit script vào repo đích.npx playwright test
- Purpose: 快速、一次性验证代码,使用通过 standalone +
.mjs运行的 Playwright(不使用 test runner):截取node/localhost截图,收集 console/pageerror/http/requestfailed 错误,测量真实元素,绕过 auth;多步骤任务则使用持久file://session。connectOverCDP - Trigger(何时使用):
- 在
claude-in-chrome/localhost:*上报告file://或Frame with ID 0 is showing error page。Can't interact with browser-internal or unparseable URLs - 需要诊断 UI 元素“看不到”或“无法点击”的问题——仅凭截图不够,需要真实测量值(、
getBoundingClientRect)。getComputedStyle - 需要验证一个代码假设,然后将其丢弃(无需创建 + test runner)。
*.spec.ts - 需要通过模拟 dev-login/session 进行测试,而不是每次都登录真实 SSO。
- 用户说“用 Playwright 验证”“截取页面截图”“用脚本快速测试 UI”“进行多步骤验证”“在多次运行之间保留 session”“无法点击或看不到元素”,或使用 。
/playwright-verify - Non-goals: 不创建 + test runner(
*.spec.ts),不用于 UAT 测试用例集(→npx playwright test),不用于替代普通外部网站上的 claude-in-chrome,也不会将脚本提交到目标 repo。uat-nonit-testcase
Mental model
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ứtmộ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需要验证的假设 →(已安装?)→ 一次性 .mjs 脚本:launch → 挂接 4 个错误通道 → goto(load + wait)→ screenshot/测量 → close → 读取 log + 图像 → 丢弃一个独立的 Chrome CDP(临时 profile)← 每轮 step-N.mjs 接入,state 在各轮之间保持存活 → 完成后 pkillInput và output contract
Input 与 output contract
| Field | Required? | Ý nghĩa | |
|---|---|---|---|
| In | URL ( | có | trang cần verify |
| In | project có | có | script phải chạy TỪ thư mục đó (hoặc |
| In | cookie/localStorage dev-login | không | cho auth bypass |
| Out | ảnh chụp (vd | có | bằng chứng; ảnh sạch một mình KHÔNG đủ |
| Out | số đo | khi chẩn đoán phần tử | số thật thay vì đoán từ ảnh |
| Out | "xong" | — | giả thuyết xác nhận/bác bỏ bằng log + ảnh; script đã dọn; Chrome CDP (nếu có) đã |
| 字段 | 必填? | 含义 | |
|---|---|---|---|
| In | URL( | 是 | 需要验证的页面 |
| In | project 的 | 是 | 脚本必须从该目录运行(或设置 |
| In | cookie/localStorage dev-login | 否 | 用于绕过 auth |
| Out | 截图(例如 | 是 | 证据;仅有干净截图不足以证明结果 |
| Out | | 诊断元素时 | 用真实数值代替根据截图猜测 |
| Out | “完成” | — | 通过 log + 图像确认或否定假设;脚本已清理;Chrome CDP(如有)已执行 |
Rules và capabilities
Rules 与 capabilities
- RULE-01 (MUST): phải chạy TỪ TRONG thư mục project có
import { chromium } from "@playwright/test"trong@playwright/test— ESM resolve theonode_modules, không theo vị trí file script. Script ở thư mục khác (vdcwd) →/tmpvào project trước khi chạy, hoặc trỏcp.NODE_PATH - RULE-02 (MUST): TREO VÔ THỜI HẠN trên Next.js dev server — HMR/webpack dev giữ 1 kết nối long-poll/websocket sống liên tục nên network không bao giờ "idle". Dùng
waitUntil: "networkidle"+waitUntil: "load"cố định.page.waitForTimeout(N) - RULE-03 (MUST): Auth bypass cho dev-login: (ưu tiên
context.addCookies([{url, name, value}])thay vìurl+domainriêng lẻ — tránh cookie không match) +pathsetcontext.addInitScript(...)— set state TRƯỚC khi gọilocalStorage, không phải sau.newPage() - RULE-04 (MUST): 1 cho mỗi route độc lập nếu app có logic "gặp 401 ở API phụ → tự xoá session toàn cục" — dùng lại 1 context cho nhiều navigation khiến lỗi route N hỏng lây route N+1.
context - RULE-05 (MUST): Chẩn đoán "không thấy/không bấm được": đừng chỉ nhìn ảnh chụp — gọi
+
page.evaluate(() => el.getBoundingClientRect())lấy số đo thật;getComputedStyle(el)tự báo lỗi rõ ("element is not visible") kèm log các bước thử lại, đọc log đó trước khi đoán nguyên nhân.locator.click() - RULE-06 (MUST): Ảnh chụp sạch không chứng minh gì — luôn moi 4 kênh lỗi, không chỉ 2:
(type
console) ·error·pageerrorcóresponse·status() >= 400. Analytics crash, API 5xx, promise rejection không hiện lên pixel nào. Gắn vàorequestfailedTRƯỚCpage:gotojspage.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): Mode phiên bền: một Chrome riêng, một profile riêng (tạm). Không bao giờ
--user-data-dirlên Chrome cá nhân của user — macOS bật hộp thoại xin phép và script sẽ giẫm lên tab user đang làm.--remote-debugging-port - RULE-08 (MUST): File script standalone không vào git — chạy từ scratchpad, không commit vào repo đích.
- RULE-09 (MUST): Touch only what the task requires — no opportunistic changes.
- Capabilities: chạy trình duyệt headless cục bộ + đọc/ghi file tạm; mạng ngoài chỉ khi cài binary (CDN), có timeout.
- RULE-01 (MUST):必须从包含
import { chromium } from "@playwright/test"且已安装于@playwright/test的 project 目录运行——ESM 根据node_modules解析,而不是根据脚本文件所在位置解析。如果脚本位于其他目录(例如cwd),应先将其/tmp到 project 后再运行,或者设置cp。NODE_PATH - RULE-02 (MUST):在 Next.js dev server 上使用 会无限挂起——HMR/webpack dev 会持续保持一个 long-poll/websocket 连接,因此网络永远不会进入“idle”状态。应改用
waitUntil: "networkidle"+ 固定时长的waitUntil: "load"。page.waitForTimeout(N) - RULE-03 (MUST):dev-login 的 auth bypass:使用 (优先使用
context.addCookies([{url, name, value}]),而不是分别设置url+domain,以避免 cookie 无法匹配)+path设置context.addInitScript(...)——必须在调用localStorage之前设置 state,而不是之后。newPage() - RULE-04 (MUST):如果 app 存在“辅助 API 遇到 401 → 自动清除全局 session”的逻辑,则每个独立 route 使用 1 个 ——对多个 navigation 复用同一个 context,会导致 route N 的错误破坏 route N+1。
context - RULE-05 (MUST):诊断“看不到或无法点击”时:不要只查看截图——调用 +
page.evaluate(() => el.getBoundingClientRect())获取真实测量值;getComputedStyle(el)会给出明确的错误信息(“element is not visible”)以及重试步骤日志,应先读取这些日志,再判断原因。locator.click() - RULE-06 (MUST):干净截图无法证明任何事情——始终收集 4 个错误通道,而不只是 2 个:(type 为
console)·error·pageerror的response·status() >= 400。Analytics crash、API 5xx、promise rejection 都不会显示在任何像素上。必须在requestfailed之前挂接到goto:pagejspage.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 模式:使用一个独立 Chrome 和一个独立 profile(临时 )。绝不能对用户个人 Chrome 使用
--user-data-dir——macOS 会弹出权限请求对话框,而且脚本会干扰用户正在使用的 tab。--remote-debugging-port - RULE-08 (MUST):standalone 脚本不得进入 git——从 scratchpad 运行,不要提交到目标 repo。
- RULE-09 (MUST):仅修改任务要求的范围——不做顺手修改。
- Capabilities:可运行本地 headless 浏览器并读取/写入临时文件;仅在安装 binary(CDN)时访问外部网络,并设置 timeout。
Failure boundaries
Failure boundaries
- bị chặn mạng/treo → blocked, báo lỗi rõ (có timeout), không để lệnh treo vô hạn.
npx playwright install - ESM không resolve (chạy sai cwd) → cp script vào project hoặc
@playwright/test, rồi chạy lại.NODE_PATH - treo trên Next.js dev → đổi sang
networkidle+load(không phải lỗi app).waitForTimeout - Phần tử không thấy/không bấm → đo số thật + đọc log trước khi kết luận; không đoán từ ảnh.
locator.click()
- 被网络阻断或挂起 → blocked,明确报告错误并设置 timeout,不能让命令无限挂起。
npx playwright install - ESM 无法解析 (
@playwright/test错误)→ 将脚本cwd到 project 或设置cp,然后重新运行。NODE_PATH - 在 Next.js dev 上挂起 → 改为
networkidle+load(这不是 app 错误)。waitForTimeout - 元素看不到或无法点击 → 在下结论前测量真实数值并读取 日志;不要根据截图猜测。
locator.click()
HOW
HOW
Main workflow
Main workflow
| Step | Type | Inputs | Action | Outputs/exit | Failure/next |
|---|---|---|---|---|---|
| W01 | deterministic | máy | Kiểm đã cài ( | binary sẵn | CDN bị chặn → blocked |
| W02 | judgment | giả thuyết + URL | Viết 1 file | script | — |
| W03 | effect | script | Chạy từ đúng thư mục ( | log + ảnh | resolve lỗi → cp/NODE_PATH, lặp W03 |
| W04 | effect | việc nhiều bước | Mode phiên bền | state giữ giữa lượt | xong → |
Chi tiết từng bước (nguồn chân lý cho W01–W04):
- Kiểm tra đã cài chưa trước khi cài lại: (macOS) — binary cache dùng chung mọi project, không cần cài lại per-repo.
ls ~/Library/Caches/ms-playwright- Chưa có: (bỏ qua nếu project đã có sẵn trong
pnpm add -D @playwright/test) rồidevDependencies.npx playwright install chromium - tải binary từ CDN ngoài (
npx playwright install) — sandbox/CI có allowlist mạng sẽ treo hoặc fail. Set timeout khi gọi tự động; nếu bị chặn, báo lỗi rõ thay vì để lệnh treo vô thời hạn.cdn.playwright.dev
- Chưa có:
- Viết 1 file chạy thẳng bằng
.mjs(không quanode) — nặng hơn cho việc verify-rồi-vứt:npx playwright testjsimport { 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(); - Chạy từ đúng thư mục (xem Rules #1), , đọc console/pageerror log + ảnh chụp. Dọn file
node check.mjssau khi xong nếu chạy trong 1 project cụ thể (không cần xoá gì khỏi git — script không nằm trong repo nếu chạy từ scratch dir)..mjs - Việc NHIỀU BƯỚC có điều kiện → mode phiên bền (học từ browser-use/browser-harness,
09/2026). Script một-phát bắt agent viết mù cả kịch bản; sai một bước là chạy lại từ
đăng nhập. Thay vào đó: dựng MỘT Chrome có cổng CDP, mỗi lượt là một script nhỏ bám vào —
tab, cookie, , scroll giữ nguyên giữa các lượt, agent nhìn rồi mới quyết bước kế.
localStoragebash# 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 &Xong việc:js// 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. Đo 09/09/2026 (python-playwright, cùng trangpkill -f "remote-debugging-port=9333"): launch mới 1.1–1.9s/lượt, mất state;file://0.65–0.80s/lượt,connectOverCDPsống qua tiến trình mới (3/3). Bảnwindow.__markertrên chưa chạy bằng node trong phiên đo — API đồng nhất với bản python đã chạy..mjs
| 步骤 | 类型 | 输入 | 操作 | 输出/退出 | 失败/下一步 |
|---|---|---|---|---|---|
| W01 | 确定性 | 机器 | 检查是否已安装( | binary 就绪 | CDN 被阻断 → blocked |
| W02 | 判断 | 假设 + URL | 编写 1 个直接通过 | 脚本 | — |
| W03 | 执行 | 脚本 | 从正确目录运行( | log + 图像 | 解决错误 → cp/NODE_PATH,重复 W03 |
| W04 | 执行 | 多步骤任务 | 持久 session 模式 | state 在各轮之间保留 | 完成后 → pkill |
各步骤详情(W01–W04 的事实来源):
- 重新安装前先检查是否已经安装(,macOS)——binary cache 为所有 project 共享,不需要针对每个 repo 重复安装。
ls ~/Library/Caches/ms-playwright- 尚未安装:执行 (如果 project 的
pnpm add -D @playwright/test中已经存在则跳过),然后执行devDependencies。npx playwright install chromium - 会从外部 CDN(
npx playwright install)下载 binary——sandbox/CI 的网络 allowlist 可能导致其挂起或失败。自动调用时应设置 timeout;如果被阻断,应明确报告错误,而不是让命令无限挂起。cdn.playwright.dev
- 尚未安装:执行
- 编写 1 个直接通过 运行的
node文件(不经过.mjs)——对于“验证后即丢弃”的场景,这种方式更轻量:npx playwright testjsimport { 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(); - 从正确目录运行(参见 Rules #1),执行 ,读取 console/pageerror log + 截图。如果在特定 project 中运行,完成后清理
node check.mjs文件(如果从 scratch 目录运行,则无需从 git 中删除任何内容,因为脚本不在 repo 中)。.mjs - 有条件的多步骤任务 → 持久 session 模式(借鉴自 browser-use/browser-harness,09/2026)。一次性脚本会迫使 agent 盲目编写整个场景;只要某个步骤出错,就必须从登录步骤重新运行。替代方案是:启动一个带 CDP 端口的 Chrome,每轮运行一个小型脚本进行连接——tab、cookie、、scroll 状态会在各轮之间保留,agent 观察当前状态后再决定下一步。
localStoragebash# 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 &工作完成后:执行js// 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。 09/09/2026 的测量结果(python-playwright,同一个pkill -f "remote-debugging-port=9333"页面):每次新 launch 需要 1.1–1.9 秒且会丢失 state;file://每次需要 0.65–0.80 秒,connectOverCDP可跨新进程存活(3/3)。测量期间,上面的window.__marker版本尚未通过 node 运行——其 API 与已经运行过的 python 版本一致。.mjs
Branches
Branches
| ID | Kind | Guard | Hành vi | Skip / failure | Rejoin |
|---|---|---|---|---|---|
| B01 | conditional_required | việc NHIỀU BƯỚC có điều kiện (cần nhìn rồi mới quyết bước kế) | một Chrome riêng | việc một-phát → skip; không bao giờ bám Chrome cá nhân | kết thúc bằng |
| B02 | user_optional | trang cần dev-login/session giả | | — | W03 |
| B03 | recovery | phần tử "không thấy/không bấm được" | | — | W03 |
| ID | 类型 | Guard | 行为 | 跳过/失败 | 汇合 |
|---|---|---|---|---|---|
| B01 | conditional_required | 有条件的多步骤任务(需要先观察,再决定下一步) | 一个独立 Chrome, | 一次性任务 → skip;绝不能连接用户的个人 Chrome | 结束时执行 |
| B02 | user_optional | 页面需要模拟 dev-login/session | 在 | — | W03 |
| B03 | recovery | 元素“看不到或无法点击” | | — | W03 |
Validation và stopping
Validation 与 stopping
Kết luận dựa trên 4 kênh lỗi + ảnh + số đo, không chỉ ảnh. Mỗi lượt chạy < 5s, không LLM. Dừng khi giả thuyết đã có bằng chứng; dọn script + Chrome CDP.
pkill结论必须基于 4 个错误通道 + 图像 + 测量值,而不能只依赖图像。每次运行 < 5 秒,不使用 LLM。当假设已经有证据时停止;清理脚本并对 Chrome CDP 执行 。
pkillExamples
Examples
- Positive: claude-in-chrome báo trên
Frame with ID 0 is showing error page→ cphttp://localhost:3000/some-pagevào project →check.mjs→node check.mjs+ log sạch cả/tmp/check.png/[console]/[pageerror]/[http]→ xác nhận trang render, xoá script.[reqfail] - Boundary/failure: script dùng trên Next.js dev → treo vô hạn; sửa thành
waitUntil: "networkidle"+waitUntil: "load"→ chạy xong. Ảnh sạch nhưng log cópage.waitForTimeout(1500)→ KHÔNG báo pass.[http] 500 /api/x
- Positive: claude-in-chrome 在 上报告
http://localhost:3000/some-page→ 将Frame with ID 0 is showing error pagecheck.mjs到 project → 执行cp→ 得到node check.mjs,且/tmp/check.png/[console]/[pageerror]/[http]日志均干净 → 确认页面正常渲染,删除脚本。[reqfail] - Boundary/failure: 脚本在 Next.js dev 上使用 → 无限挂起;改为
waitUntil: "networkidle"+waitUntil: "load"→ 顺利运行。图像干净但日志包含page.waitForTimeout(1500)→ 不得报告为 pass。[http] 500 /api/x