Heuristic:DevExpress Testcafe Assertion Retry Timing
| Knowledge Sources | |
|---|---|
| Domains | Testing, Optimization |
| Last Updated | 2026-02-12 03:30 GMT |
Overview
Assertion retry mechanism using 200ms polling intervals with configurable timeout (default 3 seconds) for smart assertions against dynamic DOM state.
Description
TestCafe's assertion executor implements a retry loop for "smart assertions" — assertions whose actual value comes from a re-executable promise (typically a Selector property). Instead of failing immediately on mismatch, the executor re-evaluates the selector and retries the assertion at 200ms intervals until the timeout expires. The default assertion timeout is 3000ms (3 seconds), the selector timeout is 10000ms (10 seconds), and the page load timeout is 3000ms. These three timeouts interact: the selector must resolve within its timeout, and then the assertion retries within its own timeout.
Usage
Apply this knowledge when tuning test stability for applications with dynamic content, animations, or async rendering. If tests fail intermittently on assertions, increase the assertion timeout. If selectors cannot find elements, increase the selector timeout. Understanding the 200ms retry interval helps estimate the maximum number of attempts within a given timeout.
The Insight (Rule of Thumb)
- Action: Configure assertion timeout via `t.expect(selector.value).eql(expected, { timeout: N })` or set the global default via `--assertion-timeout` CLI flag or `assertionTimeout` configuration option.
- Value: Default assertion timeout: 3000ms. Default selector timeout: 10000ms. Default page load timeout: 3000ms. Retry interval: 200ms (hardcoded).
- Trade-off: Higher timeouts increase test stability but slow down failure detection. A 3000ms assertion timeout allows up to ~15 retry attempts (3000 / 200). A 10000ms timeout allows ~50 attempts.
- Interaction: Selector timeout and assertion timeout are independent. A selector resolves first, then the assertion retries against its value. Both must be tuned together for optimal results.
Reasoning
Modern web applications update the DOM asynchronously. A naive assertion that checks DOM state at a single point in time would produce flaky tests due to race conditions between the test runner and the application. TestCafe solves this by re-executing the selector and retrying the assertion in a polling loop. The 200ms interval is a balance between responsiveness (detecting changes quickly) and CPU overhead (not spinning too fast). The 3-second default timeout is sufficient for most UI updates but may need increasing for slow API-dependent content or complex animations.
Retry math:
- 3000ms timeout / 200ms interval = up to 15 retries
- 10000ms timeout / 200ms interval = up to 50 retries
- 30000ms timeout / 200ms interval = up to 150 retries
Code Evidence
From `src/assertions/executor.ts:10`:
const ASSERTION_DELAY = 200;
From `src/configuration/default-values.ts:4-8`:
export const DEFAULT_TIMEOUT = {
selector: 10000,
assertion: 3000,
pageLoad: 3000,
};
Retry loop from `src/assertions/executor.ts:58-84`:
private _wrapFunction (fn: Function): Function {
return async () => {
const resultPromise = this.command.actual as ReExecutablePromise;
while (!this.passed) {
this.command.actual = await resultPromise._reExecute();
try {
fn();
this.passed = true;
this._onExecutionFinished();
}
catch (err) {
if (this._getTimeLeft() <= 0) {
this._onExecutionFinished();
throw err;
}
await delay(ASSERTION_DELAY);
this.inRetry = true;
this.emit('start-assertion-retries', this._getTimeLeft());
}
}
};
}