test(browser-extension): fake-chrome harness + background unit tests (phase 2)#50
Conversation
…(4% → 14%) Phase 2 (post-merge follow-up to #49): a dependency-free fake of the chrome.* surface the background uses, wired as a vitest setupFile, so the extension's background logic is unit-testable under the node environment. - test/helpers/fake-chrome.ts: stable singleton fake (runtime message bus, action/tabs/windows/scripting/sidePanel/cookies) with inspectable state + configurable failure modes; identity never changes so module-level captures (const sidePanelApi = chrome.sidePanel) stay valid across resets. - feedback.test: startFeedback orchestration — result correlation, id filtering, attention badge/clear, cancel, overlay→side-panel fallback, capture-fail error, and timeout (fake timers). - feedback-side-panel.test: session store — capture+enable, screenshot-fail, clear restores popup behavior + retires the panel, non-matching-id no-op. - command-router.test: registry/executor-backed actions, page-action bridges (snapshot/get_text/get_html via the fake scripting), cookies, unknown-action, and the cancel registry (db mocked). request_feedback drives end to end. Extension coverage floor raised 3% → 13%. Remaining (still climbing): bridge-client, group-registry, page-actions (jsdom), and the React panels. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive unit-testing suite for the browser extension's background modules using Vitest, including tests for the command router, feedback side-panel session store, and feedback orchestration. It also adds a stable fake implementation of the chrome.* API surface and raises test coverage thresholds. The review feedback suggests enhancing the chrome.tabs.get fake implementation to include properties like status, url, and title to prevent future test hangs, and recommends centralizing the duplicated flush helper function into the shared fake-chrome.ts helper.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| get: (tabId: number): Promise<{ id: number; windowId: number }> => | ||
| Promise.resolve({ id: tabId, windowId: 1 }), |
There was a problem hiding this comment.
The current fake implementation of chrome.tabs.get only returns id and windowId. However, GroupRegistry (which is planned to be tested in the next phase) uses waitForComplete which polls chrome.tabs.get and waits for tab.status === 'complete'. If status is missing or not 'complete', waitForComplete will loop and block for the full 15-second timeout (LOAD_TIMEOUT_MS), causing tests to hang or fail.
Additionally, GroupRegistry.list expects url and title to be present on the tab object.
Updating the fake to return status: "complete", url, and title will prevent these issues.
| get: (tabId: number): Promise<{ id: number; windowId: number }> => | |
| Promise.resolve({ id: tabId, windowId: 1 }), | |
| get: (tabId: number): Promise<{ id: number; windowId: number; status: string; url: string; title: string }> => | |
| Promise.resolve({ id: tabId, windowId: 1, status: "complete", url: "about:blank", title: "Test Tab" }), |
| for (const listener of [...chromeState.messageListeners]) { | ||
| listener(message, {}, () => {}); | ||
| } | ||
| } |
There was a problem hiding this comment.
To avoid duplicating the flush helper function across multiple test files (e.g., command-router.test.ts and feedback.test.ts), let's export it from this shared fake-chrome helper.
}
/** Let queued microtasks (chrome fakes resolve via promises) flush. */
export const flush = () => new Promise((r) => setTimeout(r, 0));| import { chromeState, emitMessage } from "./helpers/fake-chrome"; | ||
|
|
||
| const flush = () => new Promise((r) => setTimeout(r, 0)); |
There was a problem hiding this comment.
Import the shared flush helper from ./helpers/fake-chrome instead of defining it locally to reduce code duplication.
| import { chromeState, emitMessage } from "./helpers/fake-chrome"; | |
| const flush = () => new Promise((r) => setTimeout(r, 0)); | |
| import { chromeState, emitMessage, flush } from "./helpers/fake-chrome"; |
| import { chromeState, emitMessage } from "./helpers/fake-chrome"; | ||
|
|
||
| /** Let queued microtasks (chrome fakes resolve via promises) flush. */ | ||
| const flush = () => new Promise((r) => setTimeout(r, 0)); |
There was a problem hiding this comment.
Import the shared flush helper from ./helpers/fake-chrome instead of defining it locally to reduce code duplication.
| import { chromeState, emitMessage } from "./helpers/fake-chrome"; | |
| /** Let queued microtasks (chrome fakes resolve via promises) flush. */ | |
| const flush = () => new Promise((r) => setTimeout(r, 0)); | |
| import { chromeState, emitMessage, flush } from "./helpers/fake-chrome"; |
…4% → 25%) Continue phase 2 on the fake-chrome harness (no new deps): - fake-chrome: enrich tabs (create/remove/goBack/goForward/reload; get now returns a `complete` tab so group-registry's waitForComplete doesn't spin). - bridge-client.test: fake WebSocket + mocked setStatus — hello handshake with role/identity, ready→connected, command→correlated result, handler-throw→error result, cancel→onCancel, ping→pong, disconnect, and the no-token guard. - group-registry.test: mocked Dexie table + fake chrome.tabs — resolveTab isolation guard, open/navigate/list/close/forget lifecycle, foreign-tab rejection, and hydrate from the db. Extension coverage floor raised 13% → 23%. Remaining: page-actions (jsdom) and the React panels (jsdom + testing-library). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Phase 2 follow-up to #49: a dependency-free fake-chrome harness that makes the extension's background logic unit-testable under Vitest's node environment, plus tests across the background modules.
Coverage
browser-extension: 4% → 25% statements (floor raised 3 → 23, enforced bypnpm coverage).Added
test/helpers/fake-chrome.ts— a stable singleton fake of thechrome.*surface (runtime message bus,action/tabs/windows/scripting/sidePanel/cookies), inspectable state + configurable failure modes. Object identity never changes, so module-level captures likeconst sidePanelApi = chrome.sidePanelsurvive per-test resets. Wired viasetupFiles.feedback.test—startFeedback: result correlation, id filtering, attention badge, cancel, overlay→side-panel fallback, capture-fail error, timeout.feedback-side-panel.test— session store: capture+enable, screenshot-fail, clear (restores popup + retires panel), non-matching-id.command-router.test— registry/executor actions, page-action bridges via fakescripting, cookies, unknown-action, cancel registry,request_feedbackend to end.bridge-client.test— fake WebSocket + mockedsetStatus: hello handshake, ready→connected, command→result, throw→error, cancel→onCancel, ping→pong, disconnect, no-token guard.group-registry.test— mocked Dexie + fakechrome.tabs: resolveTab isolation, open/navigate/list/close/forget lifecycle, foreign-tab rejection, hydrate.Remaining (needs new dev-deps — separate decision)
page-actionspageDispatchand the React panels need jsdom + @testing-library/react; the CDP/content executors needchrome.debuggerfaking. Out of scope for this no-dep pass.Gate green: build, typecheck,
pnpm coverage(thresholds met), lint, format:check.🤖 Generated with Claude Code