Selector.addCustomMethods Method

Adds custom client-side methods to the Selector object.

Use the addCustomMethods method to implement Selector methods specific to your web app.

Selector().addCustomMethods({
    method1: fn1,
    method2: fn2,
    /* ... */
}, options) → Selector
Parameter Type Description
method1, method2, … String Method name.
fn1, fn2, … Function Client-side function that calculates the method’s return value.
options Object Options.

Client functions (fn1, fn2, …) accept the following parameters:

Parameter Type Description
node Object | Array A DOM node or an array of DOM nodes. See returnDOMNodes.
param1, param2, … Any Additional parameters.

DOMNodeState objects include the values of custom user-defined methods.

Options

Option Type Description Default
returnDOMNodes Boolean true if the method returns a single DOM node; false if it returns a serializable object. false

Return single DOM nodes to define targets for actions and assertions, as well as examine DOM Elements.

If some of your custom methods return arrays, and some — single nodes, call the addCustomMethods method twice:

Example

import { Selector } from 'testcafe';

fixture`Selector.addCustomMethods`
    .page`https://js.devexpress.com/`;

test('Check table', async t => {
    const myTable = Selector('.dx-datagrid-table')
        .nth(1)
        .addCustomMethods({
            getExpandButtonCell: (table, rowIndex) => {
                return table[0].querySelectorAll('.dx-group-row')[rowIndex].cells[0];
            },
            // ...
            // Other methods that return DOM nodes.
        }, {
            returnDOMNodes: true,
        })
        .addCustomMethods({
            getCellText: (table, rowIndex, columnIndex) => {
                return table.rows[rowIndex].cells[columnIndex].innerText;
            },
            // ...
            // Other methods that return serializable objects.
        });

    await t
        .expect(myTable.getCellText(3, 1)).contains('Europe')
        .click(myTable.getExpandButtonCell(0))
        .expect(myTable.getCellText(1, 1)).contains('North America');
});

If you use TypeScript, extend the Selector interface with your custom methods:

import { Selector } from 'testcafe';

interface CustomSelector extends Selector {
    getCellText (rowIndex: number, columnIndex: number): Promise<any>;
    getExpandButtonCell (rowIndex: number): SelectorPromise;
}

fixture`Selector.addCustomMethods with TS`
    .page`https://js.devexpress.com/`;

test('Check table', async t => {
    const myTable = <CustomSelector>Selector('.dx-datagrid-table')
        .nth(1)
        .addCustomMethods({
            getExpandButtonCell: (table: HTMLTableElement, rowIndex: number) => {
                return table[0].querySelectorAll('.dx-group-row')[rowIndex].cells[0];
            },
            // ...
            // Other methods that return DOM nodes.
        }, {
            returnDOMNodes: true,
        })
        .addCustomMethods({
            getCellText: (table: HTMLTableElement, rowIndex: number, columnIndex: number) => {
                return table.rows[rowIndex].cells[columnIndex].innerText;
            },
            // ...
            // Other methods that return serializable objects.
        });

    await t
        .expect(myTable.getCellText(3, 1)).contains('Europe')
        .click(myTable.getExpandButtonCell(0))
        .expect(myTable.getCellText(1, 1)).contains('North America');
});

Propagation

TestCafe propagates all custom properties and methods down every selector chain.

import { Selector } from 'testcafe';

fixture`Selector.addCustomDOMProperties propagation`
    .page`https://devexpress.github.io/testcafe/example/`;

test('Propagate custom properties', async t => {
    const div = Selector('div').addCustomDOMProperties({
        innerHTML: el => el.innerHTML,
    });

    await t
        .expect(div.innerHTML).contains('<header>')
        .expect(div.nth(2).innerHTML).contains('<fieldset>')
        .expect(div.withText('Submit').innerHTML).contains('<button');
});

Additions to your Selector chain change the target of your custom method. You may unwittingly request a property that does not exist on the client. Make sure your code does not fail due to propagation:

import { Selector } from 'testcafe';

fixture`Selector.addCustomDOMProperties propagation in the chain`
    .page`https://devexpress.github.io/testcafe/example/`;

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

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

    // This line throws an error.
    try {
        await t.expect(fieldSet.nth(1).getLabel(3).textContent)
            .eql('Easy embedding into a Continuous integration system');
    }
    catch (err) {
        await t.expect(err.errMsg).eql('TypeError: Cannot read properties of null (reading \'innerText\')');
    }

    // When TestCafe evaluates "getLabel(3)", it also tries to propagate
    // the "legend" property to the result. So, it queries for a
    // <legend> inside the <label> element, which returns nothing.
});