Built-In Wait Mechanisms

Web applications do not respond to user requests immediately. Database queries take time to complete and components take time to render.

There are end-to-end testing frameworks that cannot account for this behavior. As a result, they execute actions before action targets appear on the page. This shortcoming is the leading cause of unstable tests that fail for unknown reasons.

To solve the problem, many frameworks ask their users to insert explicit “wait” statements between actions. This design decision complicates the test development process and prolongs test execution time.

TestCafe does things differently. It can intelligently anticipate delays in the course of a test’s execution.

This article describes the wait mechanisms that operate when TestCafe executes selectors, performs test actions, evaluates assertions, sends AJAX requests, and navigates to a new page.

Wait Mechanism for Selectors

When TestCafe executes a Selector query, it waits for the target element to appear in the DOM. The query fails if it cannot find the target within the Selector timeout. The target element doesn’t have to be visible for the Selector to succeed.

import { Selector } from 'testcafe';

fixture `My fixture`
    .page `https://devexpress.github.io/testcafe/example`;

const nameInput = Selector('#developer-name');

test('My test', async t => {

    // Waits for '#developer-name' to appear in the DOM.
    const nameInputElement = await nameInput();
});

If you enable the visibilityCheck Selector option, TestCafe limits the DOM search to visible elements.

import { Selector } from 'testcafe';

fixture `My fixture`
    .page `https://devexpress.github.io/testcafe/example`;

const nameInput = Selector('#developer-name');

test('My test', async t => {
    // Waits for '#developer-name' to appear in the DOM and become visible.
    const nameInputElement = await nameInput.with({ visibilityCheck: true })();
});

Wait Mechanism for Actions

Before TestCafe executes a page action, it executes the Selector query for that action’s target. If the action target does not become visible within the selector timeout, the test fails.

Note

The t.setFilesToUpload and t.clearUpload actions do not require a visible target element.

import { Selector } from 'testcafe';

fixture `My fixture`
    .page `https://devexpress.github.io/testcafe/example`;

const nameInput    = Selector('#developer-name');
const submitButton = Selector('#submit-button');

test('My test', async t => {
    await t
        .typeText(nameInput, 'Peter Parker') // Waits for `#developer-name`
        .click(submitButton);                // Waits for '#submit-button'
});

Treatment of Overlapping DOM Elements

  1. When TestCafe interacts with a page element, it places the cursor in the center of the action target.
  2. If another element overlaps the center of the action target, TestCafe initiates the selector timeout.
  3. TestCafe scans the target element for points that other elements do not overlap.
  4. If the selector timeout ends before TestCafe finds an unobstructed point, TestCafe interacts with the topmost element at the center of the original target.

Set a custom offset

Sometimes the offset option can help you resolve issues with overlapping elements. The offset option controls the precise position of the cursor during test actions. You can specify the cursor offset for following actions: click, hover, and drag.

  • A positive offset value moves the cursor in relation to the top border (the Y axis offset) or the left border (the X axis offset) of the element.
  • A negative offset value moves the cursor in relation to the bottom border (the Y axis offset) or the right border (the X axis offset) of the element.

The following .click() method targets the bottom-left corner of the box_behind element:

await t
    .click(box_behind, { offsetX: 1, offsetY: -1 });

Wait Mechanism for Assertions

Whenever an assertion evaluates a selector property or a client function, TestCafe activates the Smart Assertion Query Mechanism.

The Smart Assertion Query Mechanism does not wait for page elements to appear. It just repeats the evaluation process until the assertion yields a successful result, or the assertion timeout expires.

If you need to wait for an element before you execute an assertion, add another assertion that checks whether the element exists, or how many elements of particular kind there are.

import { Selector } from 'testcafe';

fixture `My fixture`
    .page `https://devexpress.github.io/testcafe/example`;

const nameInput = Selector('#developer-name');
const populate  = Selector('#populate');

test('My test', async t => {
    await t
        .setNativeDialogHandler(() => true)
        .click(populate)

        // Waits for the '#developer-name'
        // element to appear in DOM.
        .expect(nameInput.exists).ok()

        // Keeps trying to obtain nameInput.value
        // until it equals 'Peter Parker'
        // or the timeout passes.
        .expect(nameInput.value).eql('Peter Parker');
});

Wait Mechanism for XHR and Fetch Requests

TestCafe waits for XHR/fetch requests to resolve for the duration of the AJAX request timeout. The test continues even if the request doesn’t succeed within this timeout.

The --ajax-request-timeout option controls the global value of the AJAX request timeout.

If you want to control the timeout value on a case-by-case basis, add aselector or an assertion to your code, and adjust the timeout for that particular action.

// The page should print 'No Data' after the fetch request
const emptyLabel = Selector('p').withText('No Data').with({ visibilityCheck: true });

await t.click('#fetch-data');

// Wait with an assertion.
await t.expect(emptyLabel.exists).ok('', { timeout: 10000 });

// Wait with a selector.
const labelSnapshot = await emptyLabel.with({ timeout: 10000 });

Wait Mechanism for Redirects

When an action triggers a redirect, TestCafe automatically waits for the server to respond. The test continues if the server does not respond within 15 seconds.