Implementation:DevExpress Testcafe TestRunController
| Knowledge Sources | |
|---|---|
| Domains | Test Execution, Lifecycle Management |
| Last Updated | 2026-02-12 12:00 GMT |
Overview
TestRunController manages the full lifecycle of a single test's execution, including creation, quarantine retry logic, fixture hooks, disconnection handling, and native automation mode.
Description
TestRunController extends AsyncEventEmitter and acts as a wrapper around an individual TestRun instance. It is responsible for:
- Creating the
TestRun(orLegacyTestRun) with the appropriate constructor - Running fixture before/after hooks and test-run-level hooks
- Managing quarantine mode: tracking attempts, deciding whether to restart or finalize
- Handling browser disconnections with a threshold-based retry mechanism (
DISCONNECT_THRESHOLD = 3) - Blocking test execution while fixture hooks are in progress
- Registering and unregistering custom client script routes via the proxy
- Determining native automation mode support per browser connection
Usage
TestRunController is instantiated by BrowserJob._createTestRunController for each test in the queue. The BrowserJob calls start(connection, startRunExecutionTime) to kick off execution and listens for lifecycle events. This class is not used directly by test authors.
Code Reference
Source Location
- Repository: DevExpress_Testcafe
- File: src/runner/test-run-controller.ts
- Lines: 1-299
Signature
const DISCONNECT_THRESHOLD = 3;
export default class TestRunController extends AsyncEventEmitter {
private readonly _quarantine: null | Quarantine;
private _disconnectionCount: number;
private readonly _proxy: Proxy;
public readonly index: number;
public test: Test;
private readonly _opts: Dictionary<OptionValue>;
private _screenshots: Screenshots;
private readonly _warningLog: WarningLog;
private readonly _fixtureHookController: FixtureHookController;
private readonly _testRunCtor: LegacyTestRun['constructor'] | TestRun['constructor'];
public testRun: null | LegacyTestRun | TestRun;
public done: boolean;
private readonly _messageBus: MessageBus;
private readonly _testRunHook: TestRunHookController;
private clientScriptRoutes: string[];
private isNativeAutomation: boolean;
public readonly id: string;
public constructor({
test,
index,
proxy,
screenshots,
warningLog,
fixtureHookController,
opts,
testRunHook,
messageBus,
}: TestRunControllerInit);
public get blocked(): boolean;
public async start(connection: BrowserConnection, startRunExecutionTime?: Date): Promise<string | null>;
}
Import
import TestRunController from '../runner/test-run-controller';
I/O Contract
Inputs
| Name | Type | Required | Description |
|---|---|---|---|
| test | Test |
Yes | The test definition to execute |
| index | number |
Yes | 1-based index of this test in the queue (used for reporting) |
| proxy | Proxy |
Yes | Hammerhead proxy for URL rewriting and client script routing |
| screenshots | Screenshots |
Yes | Screenshot manager for creating a capturer bound to this test run |
| warningLog | WarningLog |
Yes | Warning log for non-fatal issues |
| fixtureHookController | FixtureHookController |
Yes | Manages fixture before/after hooks and test blocking |
| opts | Dictionary<OptionValue> |
Yes | Runner options including quarantineMode, disableNativeAutomation, TestRunCtor, etc. |
| testRunHook | TestRunHookController |
Yes | Manages test-run-level before/after hooks |
| messageBus | MessageBus |
Yes | Central event bus for cross-component communication |
Outputs
| Name | Type | Description |
|---|---|---|
| start return | null> | The session URL to navigate the browser to, or null if the test is skipped or a hook failed
|
| blocked | boolean |
Whether this controller's test is currently blocked by a fixture hook |
| done | boolean |
Whether this test run has fully completed |
| testRun | LegacyTestRun | null | The underlying test run instance (set after start is called)
|
| id | string |
Unique identifier for this controller instance |
Events Emitted
| Event | Payload | Description |
|---|---|---|
test-run-create |
{ testRun, legacy, test, index, quarantine } |
Fired when a new TestRun instance is created |
test-run-ready |
none | Fired when the test run is ready for execution (first quarantine attempt only) |
test-run-before-done |
none | Fired before the test run completes (for sequencing reporters) |
test-run-done |
none | Fired when the test run is fully done (after hooks and client script cleanup) |
test-run-restart |
none | Fired when the test needs to restart (quarantine retry or disconnection) |
test-action-done |
ActionEventArg |
Fired after each test action completes |
Usage Examples
// Internal usage within BrowserJob._createTestRunController
const testRunController = new TestRunController({
test,
index: index + 1,
proxy: this._proxy,
screenshots: this._screenshots,
warningLog: this.warningLog,
fixtureHookController: this.fixtureHookController,
opts: this._opts,
messageBus: this._messageBus,
testRunHook: this._testRunHook,
});
// Listen for lifecycle events
testRunController.on('test-run-create', async (testRunInfo) => {
await this.emit('test-run-create', testRunInfo);
});
testRunController.on('test-run-done', async () => {
await this._onTestRunDone(testRunController);
});
testRunController.on('test-run-restart', async () => {
await this._onTestRunRestart(testRunController);
});
// Start the test on a connection
const testRunUrl = await testRunController.start(connection, startTime);
if (testRunUrl) {
// Browser navigates to testRunUrl to begin the test
}
Internal Mechanics
Quarantine Mode
When opts.quarantineMode is enabled, the controller creates a Quarantine instance that tracks attempts and errors. After each test run completes:
- The result is pushed to
quarantine.attempts - If the threshold is not reached, the test restarts via
_keepInQuarantine - Once the threshold is reached (configurable
attemptLimitandsuccessThreshold), the test is finalized - If multiple attempts occurred, the test is marked as
unstableif any attempt passed
Disconnection Handling
Each disconnection increments _disconnectionCount. If the count reaches DISCONNECT_THRESHOLD (3), the connection's processDisconnection is called with true to indicate threshold exceeded. Otherwise, the test is restarted.
Native Automation
Before creating a test run, _handleNativeAutomationMode checks whether the browser supports native automation (e.g., CDP). If disableNativeAutomation is not set but the browser does not support it, a GeneralError is thrown.