How Test Overrides and FakeEgo Doubles Enable Reliable Testing in ego-lite
Test overrides and the FakeEgo double work together to replace the live Chrome DevTools Protocol runtime with in-memory mocks, allowing fast, deterministic unit tests without launching a real browser.
The ego-lite project implements a lightweight testing harness centered on two core mechanisms: the __testing.setOverrides() API for injecting custom stubs and a FakeEgo object that mimics the full globalThis.ego runtime. Together they let tests simulate CDP interactions, error conditions, and browser events while exercising the same code paths used in production.
Understanding the Test Override System
The override system lives in src/helpers.test.mjs and provides a controlled way to swap implementations during test execution. It targets low-level helpers that normally communicate with the browser runtime.
How setOverrides Works
The __testing.setOverrides() function accepts an object mapping function names to replacement implementations. When invoked, it temporarily replaces the default behavior of helpers like ensureSession, cdp, js, and the CDP transport layer.
// src/some-feature.test.mjs
import { __testing } from './helpers.test.mjs';
import { navigate } from './helpers.js';
__testing.setOverrides({
// Force Page.navigate to reject with a custom error
sendCDPMessage: async (method, params) => {
if (method === 'Page.navigate') {
throw new Error('Navigation failed – simulated by test');
}
return await __testing.originalSendCDPMessage(method, params);
},
});
await t.rejects(
navigate('https://example.com'),
/Navigation failed – simulated by test/
);
Scope and Cleanup
Overrides are scoped to the current test file and automatically revert when the test finishes. For explicit cleanup, tests call __testing.restoreOriginals():
// In a test file's afterEach hook
afterEach(() => {
__testing.restoreOriginals(); // Restores real implementations
});
The FakeEgo Double Architecture
When the test environment initializes, src/helpers.test.mjs creates a FakeEgo object and assigns it to globalThis.ego. This double implements the complete public API surface—send, on, sendCDPMessage, and related methods—without requiring an actual CDP session.
Recording and Programmability
The FakeEgo instance records every sendCDPMessage call for later inspection. Tests can program predetermined responses and simulate asynchronous browser events through its event emitter interface.
// src/click.test.mjs
import { __testing } from './helpers.test.mjs';
import { click } from './helpers.js';
await click('@5'); // Element referenced by @5
const lastCall = __testing.fakeEgo.lastSend; // Stored by FakeEgo
t.same(lastCall.method, 'DOM.focus');
t.same(lastCall.params, { backendNodeId: 5 });
Simulating Browser Events
The on method allows code under test to register listeners for simulated CDP events such as Page.loadEventFired. This enables testing of async workflows that depend on browser lifecycle notifications.
Key Testing Capabilities
The combination of overrides and the FakeEgo double enables specific testing scenarios that would be difficult or slow with real browser automation.
Mocking Asynchronous CDP Interactions
Tests execute entirely in-process without browser startup overhead. Complex sequences of CDP calls—navigation, DOM queries, JavaScript execution—can be validated in milliseconds.
Simulating Error Conditions
The harness supports both transient and permanent failures by throwing ElementResolutionError with the appropriate transient flag set. This validates retry logic and error handling paths.
Exercising Production Code Paths
High-level helpers (click, navigate, waitForSelector) are invoked through the same modules used in production. The only difference is that src/helpers.ts detects and applies overrides when present, then delegates to src/browser-runtime.ts for real CDP transport or the fake implementation during tests.
State Isolation
The mutable runtime singleton defined in src/state.ts presents a testing challenge: shared state can leak between tests. The override system addresses this by ensuring each test receives a fresh copy of the state after restoreOriginals() completes, preventing cross-test contamination.
Source File Reference
| File | Purpose |
|---|---|
src/helpers.test.mjs |
Defines __testing, creates the FakeEgo double, and provides setOverrides / restoreOriginals helpers |
src/helpers.ts |
Public helper surface that reads current overrides when present |
src/browser-runtime.ts |
Real CDP transport implementation replaced by the fake during testing |
src/state.ts |
Mutable runtime singleton; isolated per test via override reset |
Summary
__testing.setOverrides()injects custom stubs for low-level CDP helpers, scoped per test file with automatic cleanup.FakeEgoreplacesglobalThis.egowith an in-memory double that records calls, programs responses, and emits simulated browser events.- Combined capabilities include fast async mocking, precise error simulation, and full production code path coverage without browser overhead.
- State isolation prevents cross-test pollution through fresh state initialization on each override reset.
Frequently Asked Questions
How do test overrides differ from traditional mocking libraries?
Test overrides in ego-lite operate at the module level rather than replacing individual functions. The __testing API swaps implementations for named helpers across the entire import graph, ensuring consistency when multiple modules depend on the same underlying function. This approach avoids the complexity of managing mock scopes across nested imports while preserving the ability to inspect and verify calls.
What happens if I forget to call restoreOriginals?
Overrides are automatically cleaned up when the test file finishes executing, as each test run receives an isolated override context. However, explicit restoreOriginals() calls in afterEach hooks remain a best practice for tests that make multiple override changes, ensuring predictable state between assertions within the same file.
Can FakeEgo simulate network latency or timeouts?
Yes. The FakeEgo implementation controls all promise resolution timing. Tests can return delayed promises from sendCDPMessage overrides or use the on emitter to dispatch events at scheduled intervals. This enables validation of timeout handling, debouncing logic, and race condition prevention without actual network delays.
Is the override system available in production builds?
No. The __testing export and FakeEgo construction are gated behind NODE_ENV=test or equivalent environment detection during the build process. Production bundles exclude src/helpers.test.mjs entirely, ensuring no test code ships to runtime environments.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →