Advanced Selector Techniques

Extend the Selector class

You can extend the Selector class with custom properties and methods. This is necessary to implement framework-specific API and retrieve DOM element properties unavailable out of the box.

To add a new method, pass a client function to the addCustomMethods method. To add a new property, pass a client function to the addCustomDOMProperties method.

import { Selector } from 'testcafe';

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

test('Check Label HTML', async t => {
    let fieldSet = Selector('fieldset').addCustomMethods({
        getLabel: (el, idx) => {
            return el[0].elements[idx].labels[0];
        }
    }, {
        returnDOMNodes: true
    });

    await t.expect(fieldSet.nth(1).getLabel(3).textContent).eql('Easy embedding into a Continuous integration system');

    fieldSet = fieldSet.addCustomDOMProperties({
        legend: el => el.querySelector('legend').innerText
    });

    await t.expect(fieldSet.nth(1).legend).eql('Which features are important to you:');
});

Custom properties and methods propagate through the selector chain. If you define selector.myProperty, you can access it further down the chain: selector.nth(2).myProperty, selector.withText('ABC').myProperty.

Execute Selectors inside Node.js Callbacks

Selectors need access to the test controller object. Selectors inside the test function implicitly obtain this access.

If you want to execute a Selector from a Node.js callback, you have to manually bind the query to the test controller with the boundTestRun option.

import { http } from 'http';
import { Selector } from 'testcafe';

fixture `My fixture`
    .page `http://www.example.com/`;

const elementWithId = Selector(id => document.getElementById(id));

test('Title changed', async t => {
    const boundSelector = elementWithId.with({ boundTestRun: t });

    // Performs an HTTP request that changes the article title.
    // Resolves to a value indicating whether the title has been changed.
    const match = await new Promise(resolve => {
        const req = http.request(/* request options */, res => {
            if(res.statusCode === 200) {
                boundSelector('article-title').then(titleEl => {
                    resolve(titleEl.textContent === 'New title');
                });
            }
        });

        req.write(title)
        req.end();
    });

    await t.expect(match).ok();
});

To ensure that TestCafe triggers the callback function before the test ends, suspend the test until then. For instance, you can introduce a Promise and synchronously wait until it completes, as shown in the example above.

Note

The boundTestRun option requires the same test controller instance that is passed to the function used in the test declaration. It cannot work with imported test controllers.

Select Elements With Dynamic IDs

Well-written Selectors use element identifiers that persist between test runs. While the id attribute may seem suitable for this purpose, many JavaScript frameworks generate dynamic element IDs. If your project contains dynamic IDs, write Selectors that reference class, content, tag name, or position instead.

Below is the list of Selector methods that can locate action targets without referencing their IDs:

Example

<html>
    <body>
        <div id="j9dk399sd304" class="container">
            <div id="dsf054k45o3e">Item 1</div>
            <div id="lk94km904wfv">Item 2</div>
        </div>
    </body>
</html>
import { Selector } from 'testcafe';

fixture `My fixture`
    .page `http://localhost/`;

test('My test', async t => {
    const container = Selector('div').withAttribute('class', 'container');
    const item1     = Selector('div').withText('Item 1');
    const item2     = container.child(1);
});

If a part of ID remains static, you can still target it with the help of the following workarounds:

Example

<html>
    <body>
        <div id="9fgk309d3-wrapper-9f">
            <div id="g99dsf99sdfg-container">
                <div id="item-df9f9sfd9fd9">Item</div>
            </div>
        </div>
    </body>
</html>
import { Selector } from 'testcafe';

fixture `My fixture`
    .page `http://localhost/`;

test('My test', async t => {
    const wrapper   = Selector('div').withAttribute('id', /\w+-wrapper-\w+/);
    const container = Selector('[id$="container"]');
    const item      = Selector('[id|="item"]');
});