Overview
TestDriver uses EventEmitter2 for its event system. Events use a colon-delimited namespace pattern and support wildcard listeners.
Access the emitter through testdriver.emitter:
testdriver.emitter.on('command:start', (data) => {
console.log(`Running: ${data.command}`);
});
Configuration
The internal emitter is created with:
new EventEmitter2({
wildcard: true,
delimiter: ':',
maxListeners: 20,
verboseMemoryLeak: false,
ignoreErrors: false,
});
Wildcard Listeners
Use * to match a single level or ** to match multiple levels:
// Match all log events
testdriver.emitter.on('log:*', (message) => {
console.log(message);
});
// Match all events in any namespace
testdriver.emitter.on('**', (...args) => {
console.log('Event:', this.event, args);
});
Event Reference
Command Events
Emitted during the execution of SDK commands (click, type, find, etc.).
| Event |
Payload |
command:start |
{ command, depth, data, timestamp, sourcePosition } |
command:success |
{ command, depth, data, duration, response, timestamp, sourcePosition } |
command:error |
{ command, depth, data, error, duration, timestamp, sourcePosition } |
command:status |
{ command, status: "executing", data, depth, timestamp } |
command:progress |
{ command, status: "completed", timing, data, depth, timestamp } |
testdriver.emitter.on('command:start', ({ command, data }) => {
console.log(`Starting ${command}`, data);
});
testdriver.emitter.on('command:error', ({ command, error, duration }) => {
console.error(`${command} failed after ${duration}ms: ${error}`);
});
Step Events
Emitted for each AI reasoning step within a command.
| Event |
Payload |
step:start |
{ stepIndex, prompt, commandCount, timestamp, sourcePosition } |
step:success |
{ stepIndex, prompt, commandCount, duration, timestamp, sourcePosition } |
step:error |
{ stepIndex, prompt, error, duration?, timestamp, sourcePosition? } |
testdriver.emitter.on('step:start', ({ stepIndex, prompt }) => {
console.log(`Step ${stepIndex}: ${prompt}`);
});
Test Events
Emitted when a test file execution starts.
| Event |
Payload |
test:start |
{ filePath, timestamp } |
test:success |
Emitted on test completion |
test:error |
Emitted on test failure |
Log Events
Emitted for all log output from the SDK.
| Event |
Payload |
log:log |
(message: string) — general log message |
log:warn |
(message: string) — warning message |
log:debug |
(message: string) — debug output (only when VERBOSE/DEBUG/TD_DEBUG env set) |
log:info |
(message: string) — informational message |
log:error |
(message: string) — error message |
log:narration |
(message: string, overwrite?: boolean) — in-place status line |
log:markdown |
(markdown: string) — full static markdown content |
log:markdown:start |
(streamId: string) — begin streaming markdown |
log:markdown:chunk |
(streamId: string, chunk: string) — incremental chunk |
log:markdown:end |
(streamId: string) — end streaming markdown |
// Capture all logs
testdriver.emitter.on('log:*', function (message) {
console.log(`[${this.event}]`, message);
});
Screen Capture Events
Emitted during screenshot capture.
| Event |
Payload |
screen-capture:start |
{ scale, silent, display } |
screen-capture:end |
{ scale, silent, display } |
screen-capture:error |
{ error, scale, silent, display } |
Sandbox Events
Emitted for sandbox WebSocket lifecycle and communication.
| Event |
Payload |
sandbox:connected |
No payload — WebSocket connection established |
sandbox:authenticated |
{ traceId } — authentication successful |
sandbox:error |
(err: Error | string) — connection or sandbox error |
sandbox:sent |
(message: object) — WebSocket message sent |
sandbox:received |
No payload — successful message reply received |
sandbox:progress |
{ step, message } — sandbox setup progress |
testdriver.emitter.on('sandbox:connected', () => {
console.log('Connected to sandbox');
});
testdriver.emitter.on('sandbox:progress', ({ step, message }) => {
console.log(`Sandbox: [${step}] ${message}`);
});
Redraw Events
Emitted during screen stability detection. See Redraw for more details.
| Event |
Payload |
redraw:status |
{ redraw: { enabled, settled, hasChangedFromInitial, consecutiveFramesStable, diffFromInitial, diffFromLast, text }, network: { enabled, settled, rxBytes, txBytes, text }, timeout: { isTimeout, elapsed, max, text } } |
redraw:complete |
{ screenSettled, hasChangedFromInitial, consecutiveFramesStable, networkSettled, isTimeout, timeElapsed } |
testdriver.emitter.on('redraw:complete', (result) => {
if (result.isTimeout) {
console.warn('Redraw timed out after', result.timeElapsed, 'ms');
}
});
File Events
Emitted during file load/save operations in the agent.
| Event |
Payload |
file:start |
{ operation: "load" | "save" | "run", filePath, timestamp } |
file:stop |
{ operation, filePath, duration, success, sourceMap?, reason?, timestamp } |
file:load |
{ filePath, size, timestamp } |
file:save |
{ filePath, size, timestamp } |
file:diff |
{ filePath, diff: { patches, sourceMaps, summary: { additions, deletions, modifications } }, timestamp } |
file:error |
{ operation, filePath, error, duration?, timestamp } |
Error Events
Emitted for errors at various severity levels.
| Event |
Payload |
error:fatal |
(error: string | Error) — terminates the process |
error:general |
(message: string) — non-fatal error |
error:sandbox |
(err: Error | string) — sandbox/WebSocket error |
testdriver.emitter.on('error:*', function (err) {
console.error(`[${this.event}]`, err);
});
SDK Events
Emitted for API request lifecycle.
| Event |
Payload |
sdk:request |
{ path } — outgoing API request |
sdk:response |
{ path } — API response received |
sdk:retry |
{ path, attempt, error, delayMs } — request retry |
Other Events
| Event |
Payload |
exit |
(exitCode: number) — 0 for success, 1 for failure |
status |
(message: string) — general status updates |
mouse-click |
{ x, y, button, click, double } — mouse click performed |
terminal:stdout |
Terminal stdout output |
terminal:stderr |
Terminal stderr output |
Practical Examples
Custom Test Reporter
const results = [];
testdriver.emitter.on('command:success', ({ command, duration }) => {
results.push({ command, duration, status: 'pass' });
});
testdriver.emitter.on('command:error', ({ command, duration, error }) => {
results.push({ command, duration, status: 'fail', error });
});
// After test completes
afterAll(() => {
console.table(results);
});
Progress Monitoring
testdriver.emitter.on('step:start', ({ stepIndex, prompt }) => {
process.stdout.write(`\r Step ${stepIndex}: ${prompt}`);
});
testdriver.emitter.on('command:progress', ({ command, timing }) => {
process.stdout.write(`\r ${command} completed in ${timing}ms`);
});
Debug Logging
// Log every event (verbose)
testdriver.emitter.on('**', function (...args) {
console.debug(`[EVENT] ${this.event}`, ...args);
});
Types
interface CommandStartEvent {
command: string;
depth: number;
data: Record<string, any>;
timestamp: number;
sourcePosition: SourcePosition;
}
interface CommandSuccessEvent {
command: string;
depth: number;
data: Record<string, any>;
duration: number;
response: any;
timestamp: number;
sourcePosition: SourcePosition;
}
interface CommandErrorEvent {
command: string;
depth: number;
data: Record<string, any>;
error: string;
duration: number;
timestamp: number;
sourcePosition: SourcePosition;
}
interface StepStartEvent {
stepIndex: number;
prompt: string;
commandCount: number;
timestamp: number;
sourcePosition: SourcePosition;
}
interface StepSuccessEvent {
stepIndex: number;
prompt: string;
commandCount: number;
duration: number;
timestamp: number;
sourcePosition: SourcePosition;
}
interface RedrawStatusEvent {
redraw: {
enabled: boolean;
settled: boolean;
hasChangedFromInitial: boolean;
consecutiveFramesStable: number;
diffFromInitial: number;
diffFromLast: number;
text: string;
};
network: {
enabled: boolean;
settled: boolean;
rxBytes: number;
txBytes: number;
text: string;
};
timeout: {
isTimeout: boolean;
elapsed: number;
max: number;
text: string;
};
}
interface RedrawCompleteEvent {
screenSettled: boolean;
hasChangedFromInitial: boolean;
consecutiveFramesStable: number;
networkSettled: boolean;
isTimeout: boolean;
timeElapsed: number;
}
interface SandboxProgressEvent {
step: string;
message: string;
}
interface SourcePosition {
file: string;
line: number;
column: number;
}
1---2name: testdriver-events3description: Listen to SDK lifecycle events with wildcard support4---5<!-- Generated from events.mdx. DO NOT EDIT. -->67## Overview89TestDriver uses [EventEmitter2](https://github.com/EventEmitter2/EventEmitter2) for its event system. Events use a colon-delimited namespace pattern and support wildcard listeners.1011Access the emitter through `testdriver.emitter`:1213```javascript14testdriver.emitter.on('command:start', (data) => {15 console.log(`Running: ${data.command}`);16});17```1819### Configuration2021The internal emitter is created with:2223```javascript24new EventEmitter2({25 wildcard: true,26 delimiter: ':',27 maxListeners: 20,28 verboseMemoryLeak: false,29 ignoreErrors: false,30});31```3233### Wildcard Listeners3435Use `*` to match a single level or `**` to match multiple levels:3637```javascript38// Match all log events39testdriver.emitter.on('log:*', (message) => {40 console.log(message);41});4243// Match all events in any namespace44testdriver.emitter.on('**', (...args) => {45 console.log('Event:', this.event, args);46});47```4849## Event Reference5051### Command Events5253Emitted during the execution of SDK commands (`click`, `type`, `find`, etc.).5455| Event | Payload |56|---|---|57| `command:start` | `{ command, depth, data, timestamp, sourcePosition }` |58| `command:success` | `{ command, depth, data, duration, response, timestamp, sourcePosition }` |59| `command:error` | `{ command, depth, data, error, duration, timestamp, sourcePosition }` |60| `command:status` | `{ command, status: "executing", data, depth, timestamp }` |61| `command:progress` | `{ command, status: "completed", timing, data, depth, timestamp }` |6263```javascript64testdriver.emitter.on('command:start', ({ command, data }) => {65 console.log(`Starting ${command}`, data);66});6768testdriver.emitter.on('command:error', ({ command, error, duration }) => {69 console.error(`${command} failed after ${duration}ms: ${error}`);70});71```7273### Step Events7475Emitted for each AI reasoning step within a command.7677| Event | Payload |78|---|---|79| `step:start` | `{ stepIndex, prompt, commandCount, timestamp, sourcePosition }` |80| `step:success` | `{ stepIndex, prompt, commandCount, duration, timestamp, sourcePosition }` |81| `step:error` | `{ stepIndex, prompt, error, duration?, timestamp, sourcePosition? }` |8283```javascript84testdriver.emitter.on('step:start', ({ stepIndex, prompt }) => {85 console.log(`Step ${stepIndex}: ${prompt}`);86});87```8889### Test Events9091Emitted when a test file execution starts.9293| Event | Payload |94|---|---|95| `test:start` | `{ filePath, timestamp }` |96| `test:success` | *Emitted on test completion* |97| `test:error` | *Emitted on test failure* |9899### Log Events100101Emitted for all log output from the SDK.102103| Event | Payload |104|---|---|105| `log:log` | `(message: string)` — general log message |106| `log:warn` | `(message: string)` — warning message |107| `log:debug` | `(message: string)` — debug output (only when `VERBOSE`/`DEBUG`/`TD_DEBUG` env set) |108| `log:info` | `(message: string)` — informational message |109| `log:error` | `(message: string)` — error message |110| `log:narration` | `(message: string, overwrite?: boolean)` — in-place status line |111| `log:markdown` | `(markdown: string)` — full static markdown content |112| `log:markdown:start` | `(streamId: string)` — begin streaming markdown |113| `log:markdown:chunk` | `(streamId: string, chunk: string)` — incremental chunk |114| `log:markdown:end` | `(streamId: string)` — end streaming markdown |115116```javascript117// Capture all logs118testdriver.emitter.on('log:*', function (message) {119 console.log(`[${this.event}]`, message);120});121```122123### Screen Capture Events124125Emitted during screenshot capture.126127| Event | Payload |128|---|---|129| `screen-capture:start` | `{ scale, silent, display }` |130| `screen-capture:end` | `{ scale, silent, display }` |131| `screen-capture:error` | `{ error, scale, silent, display }` |132133### Sandbox Events134135Emitted for sandbox WebSocket lifecycle and communication.136137| Event | Payload |138|---|---|139| `sandbox:connected` | *No payload* — WebSocket connection established |140| `sandbox:authenticated` | `{ traceId }` — authentication successful |141| `sandbox:error` | `(err: Error \| string)` — connection or sandbox error |142| `sandbox:sent` | `(message: object)` — WebSocket message sent |143| `sandbox:received` | *No payload* — successful message reply received |144| `sandbox:progress` | `{ step, message }` — sandbox setup progress |145146```javascript147testdriver.emitter.on('sandbox:connected', () => {148 console.log('Connected to sandbox');149});150151testdriver.emitter.on('sandbox:progress', ({ step, message }) => {152 console.log(`Sandbox: [${step}] ${message}`);153});154```155156### Redraw Events157158Emitted during screen stability detection. See [Redraw](/redraw) for more details.159160| Event | Payload |161|---|---|162| `redraw:status` | `{ redraw: { enabled, settled, hasChangedFromInitial, consecutiveFramesStable, diffFromInitial, diffFromLast, text }, network: { enabled, settled, rxBytes, txBytes, text }, timeout: { isTimeout, elapsed, max, text } }` |163| `redraw:complete` | `{ screenSettled, hasChangedFromInitial, consecutiveFramesStable, networkSettled, isTimeout, timeElapsed }` |164165```javascript166testdriver.emitter.on('redraw:complete', (result) => {167 if (result.isTimeout) {168 console.warn('Redraw timed out after', result.timeElapsed, 'ms');169 }170});171```172173### File Events174175Emitted during file load/save operations in the agent.176177| Event | Payload |178|---|---|179| `file:start` | `{ operation: "load" \| "save" \| "run", filePath, timestamp }` |180| `file:stop` | `{ operation, filePath, duration, success, sourceMap?, reason?, timestamp }` |181| `file:load` | `{ filePath, size, timestamp }` |182| `file:save` | `{ filePath, size, timestamp }` |183| `file:diff` | `{ filePath, diff: { patches, sourceMaps, summary: { additions, deletions, modifications } }, timestamp }` |184| `file:error` | `{ operation, filePath, error, duration?, timestamp }` |185186### Error Events187188Emitted for errors at various severity levels.189190| Event | Payload |191|---|---|192| `error:fatal` | `(error: string \| Error)` — terminates the process |193| `error:general` | `(message: string)` — non-fatal error |194| `error:sandbox` | `(err: Error \| string)` — sandbox/WebSocket error |195196```javascript197testdriver.emitter.on('error:*', function (err) {198 console.error(`[${this.event}]`, err);199});200```201202### SDK Events203204Emitted for API request lifecycle.205206| Event | Payload |207|---|---|208| `sdk:request` | `{ path }` — outgoing API request |209| `sdk:response` | `{ path }` — API response received |210| `sdk:retry` | `{ path, attempt, error, delayMs }` — request retry |211212### Other Events213214| Event | Payload |215|---|---|216| `exit` | `(exitCode: number)` — `0` for success, `1` for failure |217| `status` | `(message: string)` — general status updates |218| `mouse-click` | `{ x, y, button, click, double }` — mouse click performed |219| `terminal:stdout` | Terminal stdout output |220| `terminal:stderr` | Terminal stderr output |221222## Practical Examples223224### Custom Test Reporter225226```javascript227const results = [];228229testdriver.emitter.on('command:success', ({ command, duration }) => {230 results.push({ command, duration, status: 'pass' });231});232233testdriver.emitter.on('command:error', ({ command, duration, error }) => {234 results.push({ command, duration, status: 'fail', error });235});236237// After test completes238afterAll(() => {239 console.table(results);240});241```242243### Progress Monitoring244245```javascript246testdriver.emitter.on('step:start', ({ stepIndex, prompt }) => {247 process.stdout.write(`\r Step ${stepIndex}: ${prompt}`);248});249250testdriver.emitter.on('command:progress', ({ command, timing }) => {251 process.stdout.write(`\r ${command} completed in ${timing}ms`);252});253```254255### Debug Logging256257```javascript258// Log every event (verbose)259testdriver.emitter.on('**', function (...args) {260 console.debug(`[EVENT] ${this.event}`, ...args);261});262```263264## Types265266```typescript267interface CommandStartEvent {268 command: string;269 depth: number;270 data: Record<string, any>;271 timestamp: number;272 sourcePosition: SourcePosition;273}274275interface CommandSuccessEvent {276 command: string;277 depth: number;278 data: Record<string, any>;279 duration: number;280 response: any;281 timestamp: number;282 sourcePosition: SourcePosition;283}284285interface CommandErrorEvent {286 command: string;287 depth: number;288 data: Record<string, any>;289 error: string;290 duration: number;291 timestamp: number;292 sourcePosition: SourcePosition;293}294295interface StepStartEvent {296 stepIndex: number;297 prompt: string;298 commandCount: number;299 timestamp: number;300 sourcePosition: SourcePosition;301}302303interface StepSuccessEvent {304 stepIndex: number;305 prompt: string;306 commandCount: number;307 duration: number;308 timestamp: number;309 sourcePosition: SourcePosition;310}311312interface RedrawStatusEvent {313 redraw: {314 enabled: boolean;315 settled: boolean;316 hasChangedFromInitial: boolean;317 consecutiveFramesStable: number;318 diffFromInitial: number;319 diffFromLast: number;320 text: string;321 };322 network: {323 enabled: boolean;324 settled: boolean;325 rxBytes: number;326 txBytes: number;327 text: string;328 };329 timeout: {330 isTimeout: boolean;331 elapsed: number;332 max: number;333 text: string;334 };335}336337interface RedrawCompleteEvent {338 screenSettled: boolean;339 hasChangedFromInitial: boolean;340 consecutiveFramesStable: number;341 networkSettled: boolean;342 isTimeout: boolean;343 timeElapsed: number;344}345346interface SandboxProgressEvent {347 step: string;348 message: string;349}350351interface SourcePosition {352 file: string;353 line: number;354 column: number;355}356```