diff options
| author | Tianren Dong <tianrendong.dtr@gmail.com> | 2026-09-28 20:18:03 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-28 20:18:03 -0400 |
| commit | 0192da77e06b6ee3d0ce04e9d43ea387372281ca (patch) | |
| tree | 01c7619101e05e819dbc90eef1e906ed96c276d1 /test-suite | |
| parent | 104d7f442e7e3596781f32d8f42df39fc1c41aa0 (diff) | |
Report hidden Pi tab in /chrome doctor (#26)
Chrome ignores clicks and typing in hidden pages. Doctor now asks the worker
whether this session's own tab is visible (automation.status includeVisibility,
which never creates or activates a tab) and reports the reason and fix when it
is hidden.
Diffstat (limited to 'test-suite')
| -rw-r--r-- | test-suite/unit/chrome-command.test.mjs | 30 | ||||
| -rw-r--r-- | test-suite/unit/hidden-input.test.mjs | 20 |
2 files changed, 48 insertions, 2 deletions
diff --git a/test-suite/unit/chrome-command.test.mjs b/test-suite/unit/chrome-command.test.mjs index 3fb4462..dac719a 100644 --- a/test-suite/unit/chrome-command.test.mjs +++ b/test-suite/unit/chrome-command.test.mjs @@ -24,6 +24,7 @@ function healthyResponse(action) { case "tab.version": return { extensionVersion: version }; case "page.evaluate": return 2; case "page.probe": return { arithmetic: 2, location: "https://fixture.test/", webdriver: false }; + case "automation.status": return { windowId: 5, tabId: 6, visibility: "visible" }; default: throw new Error(`Unexpected bridge action: ${action}`); } } @@ -44,6 +45,7 @@ function harness({ until, background = true, mode = "server", choices = [], send Date: { now: () => now }, PI_CHROME_VERSION: version, chromeAuthorizedUntil: until, backgroundEnabled: background, hostnameOf: (url) => new URL(url).hostname, + sessionKeyFor: () => "session:alpha", bridge: { status: () => ({ mode }), async send(action, params, timeout) { @@ -128,8 +130,10 @@ test("Doctor includes locked, timed, indefinite, and expired authorization plus assert.match(report, /can run code/); assert.match(report, /fixture\.test/); assert.deepEqual(h.calls.map(({ action, timeout }) => [action, timeout]), [ - ["tab.version", 35_000], ["page.evaluate", 10_000], ["page.probe", 10_000], + ["tab.version", 35_000], ["page.evaluate", 10_000], ["page.probe", 10_000], ["automation.status", 10_000], ]); + assert.deepEqual(h.calls.at(-1).params, { sessionKey: "session:alpha", includeVisibility: true }); + assert.match(report, /Pi's tab is visible/); assert.ok(h.calls.filter((call) => call.action.startsWith("page.")).every((call) => call.params.foreground === false)); assert.equal(h.sandbox.chromeAuthorizedUntil, until, "diagnostics do not grant or change authorization"); assert.equal(h.sandbox.backgroundEnabled, background); @@ -157,7 +161,29 @@ test("Doctor retains local state and repair hints when connection/version checks test("choosing Doctor explicitly from the dashboard runs full diagnostics", async () => { const h = harness({ choices: ["Doctor / troubleshoot"] }); await h.run(); - assert.deepEqual(h.calls.map((call) => call.action), ["tab.version", "tab.version", "page.evaluate", "page.probe"]); + assert.deepEqual(h.calls.map((call) => call.action), ["tab.version", "tab.version", "page.evaluate", "page.probe", "automation.status"]); assert.match(h.notices.at(-1)[0], /Authorization: locked/); assert.match(h.notices.at(-1)[0], /Background: on \(hard\)/); }); + +test("Doctor warns when this session's Pi tab is hidden, and stays quiet when it cannot tell", async () => { + const withStatus = (status) => (action, ...rest) => action === "automation.status" + ? (typeof status === "function" ? status() : status) + : healthyResponse(action, ...rest); + const hidden = harness({ send: withStatus({ tabId: 6, visibility: "hidden", hiddenReason: "its window is covered by another window" }) }); + await hidden.run("doctor"); + const report = hidden.notices.at(-1)[0]; + assert.match(report, /⚠ Pi's tab is hidden because its window is covered by another window/); + assert.match(report, /Chrome ignores clicks and typing in hidden tabs/); + assert.match(report, /\/chrome background off/); + + const none = harness({ send: withStatus({ windowId: null, tabId: null }) }); + await none.run("doctor"); + assert.match(none.notices.at(-1)[0], /hasn't opened its own tab in this session yet/); + + for (const status of [{ windowId: 5, tabId: 6 }, () => { throw new Error("old extension"); }]) { + const quiet = harness({ send: withStatus(status) }); + await quiet.run("doctor"); + assert.doesNotMatch(quiet.notices.at(-1)[0], /Pi's tab|own tab/); + } +}); diff --git a/test-suite/unit/hidden-input.test.mjs b/test-suite/unit/hidden-input.test.mjs index 376db69..4b45a49 100644 --- a/test-suite/unit/hidden-input.test.mjs +++ b/test-suite/unit/hidden-input.test.mjs @@ -277,3 +277,23 @@ test("pruneScreenshotDir deletes only eligible files and never throws", async () assert.equal(await prune.pruneScreenshotDir(path.join(dir, "missing")), 0); fs.rmSync(dir, { recursive: true, force: true }); }); + +// ---- /chrome doctor visibility report ---- +test("automation.status reports whether this session's own tab is visible, without creating one", async () => { + const h = worker({ tab: { id: 2, windowId: 1, active: false, url: "https://fixture.test/" }, win: { id: 1, state: "normal" } }); + h.chrome.scripting.executeScript = async () => [{ result: "hidden" }]; + const none = await h.w.dispatch("automation.status", { sessionKey: "s", includeVisibility: true }); + assert.deepEqual(clone(none), { windowId: null, tabId: null }); + assert.equal(h.calls.some((c) => c.method === "tabs.create" || c.method === "windows.create"), false); + + vm.runInContext("automationTargets.set('s', { tabId: 2, windowId: undefined })", h.w); + const hidden = await h.w.dispatch("automation.status", { sessionKey: "s", includeVisibility: true }); + assert.equal(hidden.visibility, "hidden"); + assert.match(hidden.hiddenReason, /inactive tab/); + + h.chrome.scripting.executeScript = async () => [{ result: "visible" }]; + const visible = await h.w.dispatch("automation.status", { sessionKey: "s", includeVisibility: true }); + assert.equal(visible.visibility, "visible"); + assert.equal(visible.hiddenReason, undefined); + assert.deepEqual(clone(await h.w.dispatch("automation.status", { sessionKey: "s" })), { windowId: null, tabId: 2 }, "plain status is unchanged"); +}); |
