Selector.addCustomMethods Method

Adds custom methods executed on the client side to the selector.

Use addCustomMethods to implement selector methods specific to the web app architecture or framework.

Selector().addCustomMethods({
    method1: fn1,
    method2: fn2,
    /* ... */
}, options) → Selector
Parameter Type Description
method1, method2, ... String Method names.
fn1, fn2, ... Function Functions that contain method code. Executed on the client side in the browser.
options Object Custom method options.

Functions that contain method code (fn1, fn2, ...) take the following parameters.

Parameter Type Description
node Object | Array The DOM node if the returnDOMNodes option is set to false; an array of DOM nodes if returnDOMNodes is set to true.
param1, param2, ... Any Method parameters.

The addCustomMethods function also adds the specified methods to the element state returned by the selector.

Options

Option Type Description Default
returnDOMNodes Boolean true if custom methods return DOM nodes; false if they return serializable objects. false

Enable returnDOMNodes to use custom method results as selectors to define action targets or assertion's actual values, obtain the element state, etc.

The following example shows how to add both types of methods to a selector. addCustomMethods is called twice: the first call adds a method that returns a DOM node (returnDOMNodes is set to true), the second call defines a method that returns a string.

Example

import { Selector } from 'testcafe';

fixture `My fixture`
    .page `https://js.devexpress.com/`;

test('My test', async t => {
    const myTable = Selector('.dx-datagrid-table')
        .nth(1)
        .addCustomMethods({
            getExpandButtonCell: (elements, rowIndex) => {
                return elements[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, declare a Selector interface extension with your custom methods:

import { Selector } from 'testcafe';

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

fixture `My fixture`
    .page `https://js.devexpress.com/`;

test('My test', async t => {
    const myTable = <CustomSelector>Selector('#customers').addCustomMethods({
        getExpandButtonCell: (elements: HTMLCollection, rowIndex: number) => {
            return elements[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

Custom methods propagate through the selector chain. You can call them for any subsequent selector:

import { Selector } from 'testcafe';

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

test('Propagate custom properties', async t => {
    const fieldSet = Selector('fieldset').addCustomMethods({
        getInput: (el, idx) => {
            return el[0].querySelectorAll('input')[idx];
        }
    }, {
        returnDOMNodes: true
    });

    await t
        .typeText(fieldSet.getInput(0), 'Peter Parker')
        .click(fieldSet.withText('Operating System').getInput(2))
        .click(fieldSet.withAttribute('id', 'tried-section').getInput(0));
});

Note that TestCafe propagates all custom properties and methods down every selector chain. Ensure that custom code does not fail during propagation:

import { Selector } from 'testcafe';

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

test('Check Label HTML', async t => {
    let 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.
    await t.expect(fieldSet.nth(1).getLabel(3).textContent).eql('Easy embedding into a Continuous integration system');

    // 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.
});