onClicked fires. Extension.js lets you trigger those handlers directly — the toolbar action and keyboard-shortcut commands — so you can exercise them headlessly and assert on what they did.
This works because Extension.js captures your chrome.action.onClicked and chrome.commands.onCommand listeners at build time (the dev runtime loads before your background code) and replays them on demand. No window, no mouse, no flake.
Requires a dev session started with
--allow-control — see Debugging
overview.Trigger the toolbar action
default_popup, this opens it. If it doesn’t, it replays the onClicked listeners:
Trigger a keyboard command
Replay anychrome.commands.onCommand handler by name — the shortcut itself is never pressed:
The one caveat: no user gesture
Replay invokes your handler, but it is not a real user click, so the browser does not attach a user gesture. The practical consequence: Extension.js tells you when this matters: the result reportsgesture: false, and adds a warning when your manifest declares activeTab:
Need a genuine-gesture click?
A real action click grantsactiveTab — confirmed on Chrome 149. The replay deliberately does not carry that gesture (so activeTab-dependent handlers behave differently). For the rare case where you need true gesture semantics, use chrome-devtools-mcp’s trigger_extension_action (Chromium only), which drives a real invocation through Puppeteer over the supported pipe transport.
Consider running it alongside Extension.js anyway: let it own genuine-gesture browser driving, while Extension.js owns the build-aware, cross-browser, headless replay you use for everything else.
In CI: assert your action handler ran
Boot a session, fire the action, and assert on the side effect — no browser UI, no Playwright. This is the whole loop in a GitHub Actions job:smoke:open-action check, which fires the action and a command, then reads chrome.storage back to prove each handler actually executed.
See CI templates for the full pipeline.
Cross-browser
open action and open command are built on the in-browser companion, so they run on both Chrome and Firefox — they touch only addListener, nothing engine-specific, and are verified on both. A single background.service_worker source works on Firefox too: Extension.js translates it to a background.scripts event page for the Firefox build. (A genuine-gesture click is Chromium-only and lives in chrome-devtools-mcp — see above.)
Next steps
- Debugging overview — the full control surface.
- CI templates — wire this into a pull-request gate.
- Run both MCP servers — fidelity vs. portability, side by side.

