Skip to content

test: migrate listExtensions test to unit test - #2704

Merged
OrKoN merged 2 commits into
ChromeDevTools:mainfrom
maheshsingh20:test/extensions-unit-tests
Sep 11, 2026
Merged

OrKoN merged 2 commits into
ChromeDevTools:mainfrom
maheshsingh20:test/extensions-unit-tests

Conversation

@maheshsingh20

Copy link
Copy Markdown
Contributor

This PR migrates the mock-eligible test in tests/tools/extensions.test.ts from a real-browser test (withMcpContext) to a mock-based unit test using the established Sinon mock infrastructure.

Architectural Principle (Category 1 vs. Category 2 Split)

This PR continues issue #2639 (item 1), applying the core architectural separation of concerns established across #2641, #2665, #2683, and #2698:

  • Category 1 (Handler wiring & response verification -> Mock unit tests):
    Tests that only verify tool parameter parsing and response manipulation should not launch real browsers. Launching Chrome just to assert that a tool invoked response.setListExtensions() introduces unnecessary startup latency and lifecycle flakiness without providing real integration coverage. These are converted to deterministic unit tests using createHandlerMocks().

  • Category 2 (Genuine browser, extension, and protocol behavior -> Retained real-browser tests):
    Tests that depend on genuine browser mechanics must remain in withMcpContext. Mocking them would require simulating Chrome's internal extension loader, V8 script execution, or Puppeteer's target lifecycle inside mocks, creating brittle and artificial tests that do not validate real behavior.

Summary of Changes

  • Converted to mocks (Category 1):
    • lists installed extensions: Converted to createHandlerMocks(). Asserts response.setListExtensions was invoked with sinon.assert.calledOnceWithExactly(response.setListExtensions).
  • Intentionally retained as real-browser tests (Category 2):
    • installs and uninstalls an extension and verifies it in chrome://extensions: Genuinely installs an unpacked extension into Chrome and verifies real extension lifecycle and management.
    • reloads an extension: Verifies Chrome's live extension reload mechanics and registry reappearance.
    • triggers an extension action: Validates asynchronous Puppeteer Target creation when an extension action fires.
    • verifies that content script console logs are received: Tests real content script execution in a live navigated page and CDP console capture.

Verification

  • powershell -Command "Remove-Item -Recurse -Force build -ErrorAction SilentlyContinue"; npm run build (exit code: 0)
  • node scripts/test.js tests/tools/extensions.test.ts (6 passed, 0 failed, exit code: 0)
  • npm run check-format (exit code: 0)
  • npm run docs:generate (exit code: 0)
  • git status (clean working tree, exit code: 0)

@maheshsingh20

Copy link
Copy Markdown
Contributor Author

Hi @OrKoN, following up on #2698 and continuing #2639 (item 1), I've opened PR #2704 to migrate tests/tools/extensions.test.ts.

Applying our established Category 1 vs. Category 2 architectural split:

  • Category 1 (Mock unit test): lists installed extensions now uses createHandlerMocks() with sinon.assert.calledOnceWithExactly(response.setListExtensions), removing real-browser launch overhead for pure response flag verification.
  • Category 2 (Retained real-browser tests): The other 4 tests intentionally remain in withMcpContext since they verify genuine Chrome extension installation, runtime reload mechanics, asynchronous Puppeteer Target creation, and live content script execution.

All verification steps (npm run test tests/tools/extensions.test.ts, npm run check-format, npm run docs:generate) passed cleanly with exit code 0. PTAL!

@OrKoN

OrKoN commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Hi @OrKoN, following up on #2698 and continuing #2639 (item 1), I've opened PR #2704 to migrate tests/tools/extensions.test.ts.

Applying our established Category 1 vs. Category 2 architectural split:

  • Category 1 (Mock unit test): lists installed extensions now uses createHandlerMocks() with sinon.assert.calledOnceWithExactly(response.setListExtensions), removing real-browser launch overhead for pure response flag verification.
  • Category 2 (Retained real-browser tests): The other 4 tests intentionally remain in withMcpContext since they verify genuine Chrome extension installation, runtime reload mechanics, asynchronous Puppeteer Target creation, and live content script execution.

All verification steps (npm run test tests/tools/extensions.test.ts, npm run check-format, npm run docs:generate) passed cleanly with exit code 0. PTAL!

Similar to other places, we should use the handlers in isolation using mocks and move e2e tests to McpContex.test.ts where the underlying logic is implemented. Perhaps we can combine all extension actions in a single test since this is also tested by Puppeteer.

@maheshsingh20
maheshsingh20 force-pushed the test/extensions-unit-tests branch from 66c7310 to 5bc3086 Compare September 9, 2026 15:49
@maheshsingh20

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback! I've updated the PR accordingly:

  1. Unit tests in tests/tools/extensions.test.ts:

    • Converted all extension tool handlers (install_extension, uninstall_extension, list_extensions, reload_extension, trigger_extension_action) to pure mock-based unit tests using createHandlerMocks().
    • Covered happy paths and error cases (e.g. reload_extension throwing when the extension is not found).
    • Removed all real-browser launches and withMcpContext calls from this file.
  2. E2E tests in tests/McpContext.test.ts:

    • Moved the real browser tests to tests/McpContext.test.ts where the underlying logic is implemented.
    • Combined all extension actions into a single lifecycle test (install -> list -> get -> reload -> triggerAction -> uninstall), and retained the content script console log verification test.
  3. Mock infrastructure:

    • Re-exported CdpExtension in src/third_party/index.ts.
    • Added createMockExtension helper in tests/mocks.ts using sinon.createStubInstance(CdpExtension).

All tests, builds, and format checks (npm run check-format) pass cleanly. PTAL!

@OrKoN OrKoN left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM thanks! could you please rebase?

@maheshsingh20
maheshsingh20 force-pushed the test/extensions-unit-tests branch from cb3ffbd to 7706114 Compare September 10, 2026 17:39
@maheshsingh20

Copy link
Copy Markdown
Contributor Author

Hi @OrKoN ,
Resolved all conflict issue PTAL!!

@OrKoN
OrKoN enabled auto-merge September 11, 2026 05:50
@maheshsingh20

maheshsingh20 commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor Author

Hi @OrKoN,

There was another rebase conflict, I've resolved it now. Please take another look when you get a chance.

@OrKoN

OrKoN commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Let's do https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/tests/tools/lighthouse.test.ts but the implementation for lighthouse is actually in the tool itself so we do need to keep e2e tests: what we should do is to use mocks for tests that do not test Lighthouse itself, e.g., tests like 'restores emulation' for which the implementation is in McpPage.

@maheshsingh20

Copy link
Copy Markdown
Contributor Author

Let's do https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/tests/tools/lighthouse.test.ts but the implementation for lighthouse is actually in the tool itself so we do need to keep e2e tests: what we should do is to use mocks for tests that do not test Lighthouse itself, e.g., tests like 'restores emulation' for which the implementation is in McpPage.

Hi @OrKoN, thanks for the pointer. Before I start on lighthouse.test.ts; confirming my understanding: since Lighthouse's audit logic is implemented directly in src/tools/lighthouse.ts (not delegated to McpPage/McpContext), tests verifying actual audit behavior (report content, scores, audit modes) stay real-browser, since there's no lower-level class to relocate that coverage to. Tests like 'restores emulation' that actually exercise McpPage's behavior via the lighthouse tool get converted to mocks at the handler level, with the underlying restoreEmulation() behavior verified directly in McpPage.test.ts if it isn't already. I'll go through the full file to check if any other tests fall into that second category beyond the one you named. Will open a PR once this is done.

@OrKoN

OrKoN commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Yes, sounds right!

@OrKoN
OrKoN added this pull request to the merge queue Sep 11, 2026
Merged via the queue into ChromeDevTools:main with commit 9c72209 Sep 11, 2026
20 checks passed
@maheshsingh20

Copy link
Copy Markdown
Contributor Author

Hi @OrKoN
One implementation note before I proceed: to stub Lighthouse's boundary call cleanly, ES module namespace imports can't be stubbed directly by sinon. I'd propose adding a small lighthouseRunner = {navigation, snapshot} object export in src/tools/lighthouse.ts itself as a stubbable seam, with the handler calling through it. This is a small production-code change (not just test-only) — does that approach work for you, or would you prefer a different pattern?

@OrKoN

OrKoN commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

yeah that sounds fine!

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.

2 participants