From 758c26a448875f628e559cbaf8de0d93a80a6d4b Mon Sep 17 00:00:00 2001 From: Lyon <88232613+pikasTech@users.noreply.github.com> Date: Thu, 4 Jun 2026 20:55:21 +0800 Subject: [PATCH] fix: surface playwright fallback remediation (#852) Co-authored-by: Codex Agent --- docs/reference/spec-v02-hwlab-cloud-web.md | 4 +++- scripts/dev-cloud-workbench-smoke.test.mjs | 7 ++++++ scripts/src/browser-launcher.mjs | 26 +++++++++++++++++----- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/docs/reference/spec-v02-hwlab-cloud-web.md b/docs/reference/spec-v02-hwlab-cloud-web.md index 3d44141d..fca872ed 100644 --- a/docs/reference/spec-v02-hwlab-cloud-web.md +++ b/docs/reference/spec-v02-hwlab-cloud-web.md @@ -102,7 +102,9 @@ Cloud Web 的默认校验入口是 `bun run --cwd web/hwlab-cloud-web check`。该入口必须在 v0.2 CI 的 `hwlab-cloud-web` 镜像发布前执行,且保持秒级或低十秒级,不引入浏览器、Playwright、公网或真实 provider 依赖。 -Browser/layout/live smoke 属于显式专项诊断,不进入默认 Cloud Web check。所有 repo-owned Playwright smoke、layout smoke 和专项截图脚本必须通过 `scripts/src/browser-launcher.mjs` 启动 Chromium;不得直接 `chromium.launch()` 绑定到偶然存在的 Playwright browser cache。launcher 的浏览器来源判定顺序是显式 env(`HWLAB_PLAYWRIGHT_CHROMIUM` / `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` / `CHROMIUM_PATH`)、G14 系统 Chromium(优先 `/snap/bin/chromium`)、最后才是 Playwright cache。诊断入口是 `npm run web:browser:doctor`,输出必须包含 `playwrightPackageSource`、`browser.browserSource`、`browser.executablePath`、`browser.fallbackUsed`、`failureCode` 和 remediation;浏览器不可用、auth bootstrap 未完成和真实 layout 失败不得再统一表现成 `#command-input hidden` 超时。 +Browser/layout/live smoke 属于显式专项诊断,不进入默认 Cloud Web check。所有 repo-owned Playwright smoke、layout smoke 和专项截图脚本必须通过 `scripts/src/browser-launcher.mjs` 启动 Chromium;不得直接 `chromium.launch()` 绑定到偶然存在的 Playwright browser cache。launcher 的浏览器来源判定顺序是显式 env(`HWLAB_PLAYWRIGHT_CHROMIUM` / `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` / `CHROMIUM_PATH`)、G14 系统 Chromium(优先 `/snap/bin/chromium`)、最后才是 Playwright cache。诊断入口是 `npm run web:browser:doctor`,输出必须包含 `playwrightPackageSource`、`platform`、`managedBrowser.cacheStatus`、`installCheck.status`、`browser.browserSource`、`browser.executablePath`、`browser.fallbackUsed`、`browser.candidatePaths`、`failureCode` 和 remediation;浏览器不可用、auth bootstrap 未完成和真实 layout 失败不得再统一表现成 `#command-input hidden` 超时。 + +临时 live DOM closeout probe 也必须使用 repo-owned 入口,不得在 `/tmp` 或 issue 验收脚本里手写 Playwright import/launch。标准入口是 `npm run web:dom-probe -- start --url http://74.48.78.17:19666/ --fresh-session`,它会从 workspace 解析依赖、复用统一 launcher、把 PID/stdout/stderr/result/screenshot 写入 `.state/web-live-dom-probe/`,并立即返回 `status` 短查询命令;`npm run web:dom-probe -- status ` 用于轮询结果。前端-only DOM/renderer/scroll 验收默认不提交 Code Agent;只有显式 `--message` 才经 UI 提交一条真实消息,并按默认策略尝试取消运行中的 turn。`npm run web:browser:guard` 是最小 grep guard,用于确认 `scripts/`、`tools/`、`web/` 下没有新增绕过 launcher 的直接 Chromium launch 调用,唯一允许文件是 `scripts/src/browser-launcher.mjs`。 Live smoke 登录前必须等待前端 auth bootstrap 结束(`body[data-auth-state]` 不再是 `checking`,且 login submit 已可见/可用)再填表;登录后必须断言 URL query 不含 `username` 或 `password`,防止原生 form submit 泄漏凭据并伪装成 layout 超时。 diff --git a/scripts/dev-cloud-workbench-smoke.test.mjs b/scripts/dev-cloud-workbench-smoke.test.mjs index c91cf91a..878e5e2a 100644 --- a/scripts/dev-cloud-workbench-smoke.test.mjs +++ b/scripts/dev-cloud-workbench-smoke.test.mjs @@ -153,6 +153,7 @@ test("workbench smoke CLI treats skipped browser evidence as blocked and refuses test("repo-owned browser launcher classifies cache misses and exposes system Chromium fallback metadata", () => { assert.equal(classifyBrowserLaunchFailure(new Error("Executable doesn't exist at /root/.cache/ms-playwright/chromium_headless_shell-1217")), "browser-cache-missing"); + assert.equal(classifyBrowserLaunchFailure(new Error("ERROR: Playwright does not support chromium on ubuntu26.04-x64")), "browser-install-unsupported-platform"); assert.equal(classifyBrowserLaunchFailure(new Error("spawn /missing/chromium ENOENT")), "browser-executable-unavailable"); const plan = chromiumLaunchPlan({ HWLAB_PLAYWRIGHT_CHROMIUM: "/definitely/missing/chromium" }); assert.ok(["system-chromium", "playwright-cache"].includes(plan.browserSource)); @@ -161,6 +162,12 @@ test("repo-owned browser launcher classifies cache misses and exposes system Chr const managed = playwrightManagedBrowserPlan({ executablePath: () => "/definitely/missing/playwright-cache" }); assert.equal(managed.cacheStatus, "missing"); assert.equal(managed.exists, false); + const fallbackRemediation = browserLaunchRemediation({ executablePath: "/snap/bin/chromium" }, { + managedBrowser: managed, + installCheck: { status: "unsupported" } + }); + assert.ok(fallbackRemediation.some((line) => line.includes("Playwright-managed browser cache is missing"))); + assert.ok(fallbackRemediation.some((line) => line.includes("status=unsupported"))); const platform = browserRuntimePlatform(); assert.equal(platform.platform, process.platform); assert.equal(platform.arch, process.arch); diff --git a/scripts/src/browser-launcher.mjs b/scripts/src/browser-launcher.mjs index 984cc68a..5076f378 100644 --- a/scripts/src/browser-launcher.mjs +++ b/scripts/src/browser-launcher.mjs @@ -110,7 +110,7 @@ export async function browserDoctor() { managedBrowser, installCheck, browser: browser.hwlabLaunchPlan ?? chromiumLaunchPlan(), - remediation: [] + remediation: browserLaunchRemediation(browser.hwlabLaunchPlan ?? chromiumLaunchPlan(), { managedBrowser, installCheck }) }; } @@ -196,17 +196,31 @@ export function chromiumLaunchPlan(env = process.env) { export function classifyBrowserLaunchFailure(error) { const message = error instanceof Error ? error.message : String(error); + if (/does not support chromium on/iu.test(message)) return "browser-install-unsupported-platform"; if (/Executable doesn't exist|download new browsers|playwright install/iu.test(message)) return "browser-cache-missing"; if (/spawn|ENOENT|permission denied|EACCES/iu.test(message)) return "browser-executable-unavailable"; return "browser-launch-failed"; } -export function browserLaunchRemediation(plan = chromiumLaunchPlan()) { - if (plan.executablePath) return [`using ${plan.executablePath}`]; - return [ +export function browserLaunchRemediation(plan = chromiumLaunchPlan(), context = {}) { + const remediation = []; + if (plan.executablePath) { + remediation.push(`using ${plan.executablePath}`); + if (context.managedBrowser?.cacheStatus === "missing") { + remediation.push("Playwright-managed browser cache is missing; the repo launcher selected system Chromium fallback."); + } + if (context.installCheck?.status) { + remediation.push(`Playwright install dry-run status=${context.installCheck.status}; keep using the repo launcher/system Chromium fallback when managed browser install is unavailable or unnecessary.`); + } + return remediation; + } + remediation.push( "Set HWLAB_PLAYWRIGHT_CHROMIUM=/snap/bin/chromium when system Chromium exists.", - "Install Playwright browsers with npx playwright install when no system Chromium is available." - ]; + context.installCheck?.status === "unsupported" + ? "Playwright install is unsupported on this platform; install a system Chromium package or use a runner image that already contains Chromium." + : "Install Playwright browsers with npx playwright install when no system Chromium is available." + ); + return remediation; } function normalizePlaywrightModule(module) {