This guide shows you how to test WebMCP tools against both the native Chromium implementation and the polyfill runtime, and when to use each testing lane.
Understand the testing lanes
The monorepo provides several testing lanes organized by runtime:
| Runtime | Canonical caller | Command |
|---|
| Tab / global (polyfill) | SDK Client + TabClientTransport | pnpm --filter mcp-e2e-tests test:runtime-contract |
| Iframe | SDK Client + IframeParentTransport | pnpm --filter mcp-e2e-tests test:runtime-contract |
| Native Chromium (default) | navigator.modelContext / modelContextTesting | pnpm --filter mcp-e2e-tests test:native-contract:default |
| Native Chromium (Chrome Beta) | navigator.modelContext / modelContextTesting | pnpm --filter mcp-e2e-tests test:native-contract:beta |
| Local relay | SDK Client over stdio | pnpm --filter @mcp-b/webmcp-local-relay test:e2e |
| DevTools bridge | SDK Client + WebMCPClientTransport | pnpm --filter @mcp-b/chrome-devtools-mcp test:e2e |
Every canonical E2E suite proves the same six assertions against the real runtime:
- Initial discovery returns the expected tools
- A successful call returns the expected payload
- The runtime records the invocation
- Dynamic registration becomes discoverable without restart
- Unregistration removes the tool and later calls fail
- Runtime-thrown tool errors propagate to the caller
Run the canonical polyfill tests
The polyfill tests use Playwright with a real browser. They exercise @mcp-b/global running in a page, discovered and called through the SDK Client and TabClientTransport.
# Full canonical E2E umbrella (all runtimes, runs sequentially)
pnpm test:e2e
# Browser runtime contract only (tab/global + iframe + native)
pnpm --filter mcp-e2e-tests test:runtime-contract
These tests use the shared fixture in e2e/runtime-contract/ which registers a deterministic tool set: echo, sum, dynamic_tool, and always_fail.
Run the native Chromium tests
Native Chromium tests use the browser’s built-in navigator.modelContext and navigator.modelContextTesting APIs directly. This is the one exception to the SDK-client pattern: the browser API itself is the public boundary.
Default Chromium
pnpm --filter mcp-e2e-tests test:native-contract:default
Chrome Beta with WebMCP flags
Enable the WebMCP testing flag
Open Chrome Beta and navigate to chrome://flags/#enable-webmcp-testing. Enable WebMCP for testing and restart the browser.
Run the flagged native contract lane
pnpm --filter mcp-e2e-tests test:native-contract:beta
This uses the Playwright config that passes:
--enable-experimental-web-platform-features
--enable-features=WebMCPTesting
Detect native vs. polyfill in your own code
If you need runtime detection (for conditional test paths or feature flags):
// Check if the API exists
console.log('modelContext available:', !!navigator.modelContext);
console.log('modelContextTesting available:', !!navigator.modelContextTesting);
// Distinguish native from polyfill
if (navigator.modelContextTesting) {
const name = navigator.modelContextTesting.constructor.name;
const isNative = !name.includes('WebModelContext');
console.log('Is native:', isNative);
}
To launch Chromium with the native API enabled in your own Playwright tests:
export default defineConfig({
use: {
launchOptions: {
args: [
'--enable-experimental-web-platform-features',
'--enable-features=WebModelContext',
],
},
},
});
For headless CI environments, add --headless=new, --disable-gpu, and --no-sandbox:
chromium \
--headless=new \
--enable-experimental-web-platform-features \
--disable-gpu \
--no-sandbox
--no-sandbox disables critical security features. Use it only in trusted CI environments.
Run integration and framework tests
Beyond the canonical contract lanes, the monorepo has additional integration suites:
# Runtime API integration (direct page.evaluate, modelContextTesting probes)
pnpm --filter mcp-e2e-tests test:integration:runtime-api
# Framework integration (React hooks, validation matrices)
pnpm --filter mcp-e2e-tests test:integration:frameworks
These lanes are useful for broader compatibility checks but are not the canonical E2E gate.
Debug failing tests
# Headed mode (see the browser)
pnpm test:e2e:headed
# Playwright UI mode
pnpm test:e2e:ui
# Debug mode (step through)
pnpm test:e2e:debug
# Headed Chrome Beta run
cd e2e && pnpm test:chrome-beta:webmcp:headed
Troubleshoot common issues
| Problem | Fix |
|---|
navigator.modelContext is undefined | Verify the experimental flag is enabled. Check chrome://version for --enable-experimental-web-platform-features in the command line. |
| Polyfill detected instead of native | Remove @mcp-b/global imports from the test page. Launch in incognito mode to rule out extensions. |
| Port already in use | The tab/global lane defaults to port 4173. Kill the process: lsof -ti:4173 | xargs kill |
| Playwright browsers not installed | Run pnpm --filter mcp-e2e-tests exec playwright install chromium |
Choose the right testing approach
| If you are… | Use this lane |
|---|
Building tools with @mcp-b/global | test:runtime-contract (polyfill) |
| Validating against the native browser API | test:native-contract:default or test:native-contract:beta |
| Testing the local relay end-to-end | pnpm --filter @mcp-b/webmcp-local-relay test:e2e |
| Testing the DevTools bridge end-to-end | pnpm --filter @mcp-b/chrome-devtools-mcp test:e2e |
| Running the full canonical gate | pnpm test:e2e |
Related pages