Skip to content

test(browser-extension): fake-chrome harness + background unit tests (phase 2)#50

Merged
stephane-segning merged 2 commits into
mainfrom
claude/extension-test-harness
Jun 14, 2026
Merged

test(browser-extension): fake-chrome harness + background unit tests (phase 2)#50
stephane-segning merged 2 commits into
mainfrom
claude/extension-test-harness

Conversation

@stephane-segning

@stephane-segning stephane-segning commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

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 by pnpm coverage).

Added

  • test/helpers/fake-chrome.ts — a stable singleton fake of the chrome.* surface (runtime message bus, action/tabs/windows/scripting/sidePanel/cookies), inspectable state + configurable failure modes. Object identity never changes, so module-level captures like const sidePanelApi = chrome.sidePanel survive per-test resets. Wired via setupFiles.
  • feedback.teststartFeedback: 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 fake scripting, cookies, unknown-action, cancel registry, request_feedback end to end.
  • bridge-client.test — fake WebSocket + mocked setStatus: hello handshake, ready→connected, command→result, throw→error, cancel→onCancel, ping→pong, disconnect, no-token guard.
  • group-registry.test — mocked Dexie + fake chrome.tabs: resolveTab isolation, open/navigate/list/close/forget lifecycle, foreign-tab rejection, hydrate.

Remaining (needs new dev-deps — separate decision)

page-actions pageDispatch and the React panels need jsdom + @testing-library/react; the CDP/content executors need chrome.debugger faking. Out of scope for this no-dep pass.

Gate green: build, typecheck, pnpm coverage (thresholds met), lint, format:check.

🤖 Generated with Claude Code

…(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>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +82 to +83
get: (tabId: number): Promise<{ id: number; windowId: number }> =>
Promise.resolve({ id: tabId, windowId: 1 }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
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, {}, () => {});
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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));

Comment on lines +13 to +15
import { chromeState, emitMessage } from "./helpers/fake-chrome";

const flush = () => new Promise((r) => setTimeout(r, 0));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Import the shared flush helper from ./helpers/fake-chrome instead of defining it locally to reduce code duplication.

Suggested change
import { chromeState, emitMessage } from "./helpers/fake-chrome";
const flush = () => new Promise((r) => setTimeout(r, 0));
import { chromeState, emitMessage, flush } from "./helpers/fake-chrome";

Comment on lines +5 to +8
import { chromeState, emitMessage } from "./helpers/fake-chrome";

/** Let queued microtasks (chrome fakes resolve via promises) flush. */
const flush = () => new Promise((r) => setTimeout(r, 0));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Import the shared flush helper from ./helpers/fake-chrome instead of defining it locally to reduce code duplication.

Suggested change
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>
@stephane-segning stephane-segning merged commit d339c03 into main Jun 14, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant