COMMON CONCEPTS

This section provides information about common concepts behind organizing your test code.

Sharing Data Between Steps

The this keyword can be used to share data between test steps. Note that you cannot use it to store DOM elements or jQuery objects.

To share data, declare a property and assign a value to use. You can then access this property's value in successive steps.

this.propertyName = value;

The following example demonstrates the use of the this keyword in a test. (TestCafe Example Page)

'@test'['Handle Confirm'] = {
    'Click div "Populate Form"': function () {
        var populateFormButton = $(':containsExcludeChildren(Populate Form)');
        handleConfirm('OK');
        act.click(populateFormButton);
    },
    'Click submit button "Submit"': function () {
        this.autoGeneratedName = $('#Developer_Name').val();
        var submitButton = $('.button.blue.fix-width-180');
        act.click(submitButton);
    },
    'Check result': function () {
        var header = $('.article-header');
        var expectedResult = 'Thank You, ' + this.autoGeneratedName + '!'; 
        eq(header.html(), expectedResult);
    }
};

Test Run Metadata

There will be scenarios when you may need to get a worker name from test code. For example, the way a test executes can depend on the device type. Or you can test an account by using a worker name as a username.

You can obtain a worker name from test code by using the this.__workerName property. Names of Workers will be the same as those displayed in the WORKERS tab of the Control Panel (see the Connecting a Remote Device topic).

The code above illustrates an example when a worker name is used as a username to test a login form.

'@test'['Using a Worker Name'] = {
    'Type in input': function () {
        var input = $('#gaia_loginform').find('[name="Email"]');
        act.type(input, this.__workerName);
    },
    'Type in password input "Password"': function () {
        var passwordInput = $('#gaia_loginform').find('[name="Passwd"]');
        act.type(passwordInput, 'Pass1234');
    },
    'Click submit button "Sign in"': function () {
        var submitButton = $('#gaia_loginform').find('[name="signIn"]');
        act.click(submitButton);
    }
};

Using External JS Libraries

You can enrich TestCafe functionality and reduce repetitive code by including external JS libraries in a test. This way, the test code will become more organized and easier to read. Making changes in the external file once instead of editing each part of the duplicated code in a test will now be easier when needed.

External libraries can provide additional functionality to a test (for example, a module that helps you run the same test with a different set of data each time) or just keep some repetitive code, which can then be used from different tests (for example, code related to authorization; refer to the Mixins topic).

To include an individual external library in a test, use the @require directive.

'@require ../modules/underscore.js';

When the underscore.js library has been included, all functions declared in that library become available for use within the test. The library's each function implements iteration over an array of objects.

'@test'['Find customers'] = {
    'Specify a salesperson': function () {
        var input = $('[name="salesperson"]');
        act.type(input, 'Peter Parker');
    },
    'Check the customers list': function () {
        var customersTemplate = "<% _.each(customers, function(name) { %><li><%= name %></li><% }); %>",
            expectedHTML = _.template(customersTemplate, {
                customers: ['Moe Clark', 'John Evans', 'Larry Turner']
            });

        eq($("#custormers")[0].innerHTML, expectedHTML);
    }
};

A set of .js files can be referenced one after another from code via the @require directive or can be specified as a module via the TestCafe configuration file, and then loaded into test code as a module.

Here is the content of the test_config.json file.

"modules": {
    "screenshotMaker": [
        "../modules/html2canvas.js",
        "../modules/screenshot_maker.js"
    ]
}

A module is loaded into test code using the @require directive followed by the module name after the colon.

'@require :screenshotMaker';

In the How to run the same test with a different set of data each time knowledge base article, an external library is loaded to a test as an individual file and serves to generate random data each time when running a test.

Directory Configuration File

File name: test_config.json
Location: directories with test fixtures (regardless of the nesting level)

Each folder with test fixtures can contain a configuration file. TestCafe automatically locates these files and applies their settings to all fixtures in the current directory. The same settings apply to all nested directories, unless they have local configuration files.

Note that settings are inherited by child directories from their parent directories. If a parent file defines a module, it will be available in all child folders. URLs in nested folders can also be specified relative to the parent value.

The following is a sample file listing that explains the supported syntax of a configuration file.

{
    "baseUrl": "http://example.com/Products/",                                          
    "modules": {
        "my_module": [
            "./helpers/coordinateHelper.js",
            "./helpers/colorHelper.js"
        ]
    }
}

baseUrl - Defines the URL for all test fixtures in the current folder. Specifying the base URL in the configuration file might prove useful if a folder name on the tested website changes. So, you will only need to update the path once.

Important notes

  • In a top-level configuration file, you must specify an absolute base URL (including protocol). In configuration files of nested directories, you can specify either an absolute or relative base URL (if a parent folder contains its own configuration file).
  • If you use the base URL, each fixture in the corresponding folder must define a relative path to the tested page. Otherwise, the base URL will be ignored.

Suppose that you have created a test for the http://example.com/Products/Account/login.aspx web page. The test is located in the Products subdirectory of the root directory. This is how you can specify the URL of the tested page.

  1. In the configuration file of the root directory, specify the absolute base URL: "baseUrl": "http://example.com/".

  2. In the configuration file of the subdirectory, specify the relative base URL: "baseUrl": "./Products/".

  3. In the fixture containing the test, specify the relative URL: "@page ./Account/login.aspx".

modules - Defines modules - sets of JavaScript files that can be referenced in test fixture code.

To load a module to a fixture, use the @require directive as shown below. Note that individual JavaScript files will be loaded in the same order as defined in the configuration file.

'@fixture Features';

'@page ./Features.aspx';

'@require :my_module';
'@require ./helpers/test.js';

'@test'['Refresh'] = {
    'Click "Refresh" button': function () {
        this.prevImageSrc = getCaptcha().getImage().prop('src');
        act.click(getCaptcha().getRefreshButton());
    },
    'Check to see if the captchaImage url has been changed': function() {
        notEq(getCaptcha().getImage().prop('src'), this.prevImageSrc);
    }
};

Mixins

As it is often the case, every single test in a group of tests has to repeat a certain routine operation. With TestCafe, you can write code for this operation once and reuse it in multiple tests.

To do this, create a mixin, which is a piece of test code meant to be reused across your test base.

To define a mixin, use the @mixin directive as shown in the code below.

'@mixin'['Authentication'] = {
    'Type in input login': function () {
        var input = $('#login_input');
        act.type(input, 'Peter Parker');
    }
};

You can put a mixin either to a JS library file (an existing or a new one) or to a test fixture file.

Mixins defined in a library can be used in any fixture. To reference an external library from test code, use the @require directive.

'@require ./mixin.js';

Mixins embedded in a test file cannot be used outside of this file.

The placement of mixins in the fixture code is arbitrary, i.e., mixins can be placed before the tests, after or among them.

To call mixin code, put the @mixin directive followed by the mixin name after the test step name.

'@test'['View profile'] = {
    'Log In': '@mixin Authentication',

    'Click link "My Profile"': function () {
        var link = $(':containsExcludeChildren(My Profile)');
        act.click(link);
    }
};

Note that a call to a mixin will substitute for a test step. This means that you cannot combine a call to a mixin with some other code as a single step.

Important note

Mixins cannot be nested.

To share data between mixins and test steps (like between any test steps), use this keyword. For details, see Sharing Data Between Steps.

The following example illustrates how to use a mixin. It shows the "Authentication" mixin that logs into an account. This mixin contains three steps: enter login, enter password and click the "Submit" button. The mixin is embedded in a test file and used as the "Log In" step in two tests.

'@mixin'['Authentication'] = {
    'Type in input login': function () {
        var input = $('#login_input');
        act.type(input, 'Peter Parker');
    },
    'Type in input password': function () {
        var input = $('#password_input');
        act.type(input, '12345');
    },
    'Click submit button': function () {
        var submitButton = $('#submit_button');
        act.click(submitButton);
    }
};

'@test'['View profile'] = {
    'Log In': '@mixin Authentication',

    'Click link "My Profile"': function () {
        var link = $(':containsExcludeChildren(My Profile)');
        act.click(link);
    }
};

'@test'['Edit profile'] = {
    'Log In': '@mixin Authentication',

    'Click link "Edit Profile"': function () {
        var link = $(':containsExcludeChildren(Edit Profile)');
        act.click(link);
    }
};

Wrappers

Some target web pages may contain complicated controls, making it difficult to understand and manage test code generated by Visual Test Recorder. For example, the code can include very long expressions that are used to refer to web page elements. To simplify access to the elements and improve code readability, you can use TestCafe wrappers. These are sets of JavaScript functions that help you easily work with complex controls in TestCafe. You can use these functions instead of the recorded selector expressions to access the desired elements.

Another issue that the wrappers help to handle is dynamic indexes of web page elements. Sometimes the indexes can change from one test run to another. Thus, the recorded code may not work as expected. To make your test more stable, use wrapper functions.

DevExpress provides a set of wrappers for DevExpress ASP.NET controls. However, you can create wrappers for any other control on your own.

To use the wrappers in tests, follow these steps:

  1. Download and unpack the .zip archive with TestCafe wrappers to any folder on your machine.

  2. Move the wrappers folder to the directory that contains your TestCafe tests. So, the test and wrapper folder must be located at the same directory level.

  3. Define the modules section in the directory configuration file as shown below:

    "modules": {
         "dx": [
                "../wrappers/dx.js",
                "../wrappers/web/ASPxGridViewWrapper.js"
         ]
    }

After these steps, you are ready to use wrapper functions in your tests.

For an example of how to use TestCafe wrappers in tests, see How to Use TestCafe Wrappers in Tests.

Test Parameterization

Test parameterization is a testing method utilizing parameters rather than fixed values. So you can run the same test with different data sets (test cases) used as inputs or expected outputs that are stored separately from the test steps.

In the Control Panel, parameterized tests represent a group of tests. The number of tests corresponds with the number of test conditions. You can run parameterized tests either as a whole group or individually.

Test cases represent an array of parameters and values, and use the syntax as follows.

{'@name': 'test_case_name', parameter_name_1: 'parameter_value_1', parameter_name_2: 'parameter_value_2', ...}

The @name parameter specifies the name of the test case. This parameter is optional. If it isn't specified, the test case will have the auto-generated name "Test case at index N" where N is the index of the test case in the array.

The parameter_name and parameter_value specify the name and the value of the parameter that will be used in test steps.

To specify test cases within the test code, write them after the @testCases directive in the beginning of the test before the test steps.

'@test'['Data-driven testing'] = {
    '@testCases': [
        {'@name': 'Peter', login: 'Peter Parker', password: '12345', group: 'admin'},
        {'@name': 'John', login: 'John Smith', password: 'test', group: 'user'},
        {'@name': 'Olivia', login: 'Olivia Taylor', password: 'qwerty', group: 'moderator'}
    ],
    ...
};

If there are many test cases and you wish to keep your test code organized, you can save them to a separated JSON file. It is also useful if you are going to share these data sets with other tests.

For that, create a JSON file (for example, user-credentials-cases.json file) and save test cases to it.

[
    {"@name": "Peter", "login": "Peter Parker", "password": "12345", "group": "admin"},
    {"@name": "John", "login": "John Smith", "password": "test", "group": "user"},
    {"@name": "Olivia", "login": "Olivia Taylor", "password": "qwerty", "group": "moderator"}
]

To include the separated test cases in the test code, specify a relative path to that file by using the @testCases directive.

'@test'['Data-driven testing'] = {
    '@testCases': './user_credentials_cases.json',
    ...
};

To obtain the test case's parameters from the test steps, use the this keyword followed by the parameter name.

'@test'['Data-driven testing'] = {
    ...
    'Type in input login': function () {
        var input = $('#login_input');
        act.type(input, this.login);
    },
    'Type in input password': function () {
        var input = $('#password_input');
        act.type(input, this.password);
    },
    ...
};

The following example illustrates the test that will be run with different data parameters.

'@test'['Data-driven testing'] = {
    '@testCases': [
        {'@name': 'Peter', login: 'Peter Parker', password: '12345', group: 'admin'},
        {'@name': 'John', login: 'John Smith', password: 'test', group: 'user'},
        {'@name': 'Olivia', login: 'Olivia Taylor', password: 'qwerty', group: 'moderator'}
    ],
    'Type in input login': function () {
        var input = $('#login_input');
        act.type(input, this.login);
    },
    'Type in input password': function () {
        var input = $('#password_input');
        act.type(input, this.password);
    },
    'Click submit button': function () {
        var submitButton = $('#submit_button');
        act.click(submitButton);
    },
    'Check Result': function () {
        var header = $('#group_header');
        eq(header.text(), this.group);
    }
};

Http Authentication

TestCafe allows you to test web pages that are protected with Http Basic or Windows (NTLM) authentication.

User's login and password can be specified within a fixture creating dialog, during a test recording or manually via the @auth directive within a fixture code. Since TestCafe supports two-way synchronization, user credentials specified by any means mentioned before will appear both in the UI and code.

To specify user credentials within the code, write the @auth directive followed by a colon-separated pair of login and password.

'@auth login:password';

Once user's login and password are defined either within the UI or within the code, TestCafe won't ask for them again.

Important note

Note that in case of Windows authentication, TestCafe additionally requires domain and workstation (PC) names. By default, these names are automatically received from the machine where TestCafe is installed.

IFrame Support

TestCafe allows you to test content which is located within the same-domain and cross-domain IFrames.

To implement user actions, assertions and other scripts for elements within IFrames, use the inIFrame function. When you assign this function to a step, web elements selectors will be considered in the context of the specified IFrame.

'First step': inIFrame('#iFrame', function () {
    test step code
})

In the example above, the first parameter '#iframe' specifies the iframe's selector (user action target) on the web page. The second parameter is a function that will execute user actions, assertions or other scripts within the specified iframe.

The following example illustrates how to use the inIFrames function.

'Click submit button': inIFrame('#iframe', function() {
        act.click('#submitButton');
    }),
'Check if error message is appeared': inIFrame('#iframe', function() {
        ok($("#errormsg").is(":visible"));
    })