Hooks

Note

This article describes test hooks. Read the Request Interception Guide for more information on request hooks.

Hooks are functions that run immediately before or immediately after other test entities. You can attach hooks to the following test entities: tests, fixtures, and test runs.

“Before” hooks often prepare the testing environment (e.g. authenticate the user) for future tests. Likewise, “after” hooks often ‘reset’ the testing environment (e.g. remove a database object) after the end of a test.

To re-use setup and teardown code, attach a single hook to multiple entities of the same kind.

Local Hooks and Global Hooks

Local hooks reside inside individual test files. These hooks do not affect the rest of the test suite.

Global hooks apply to your entire test suite. You can only define them in a JavaScript configuration file.

Test and fixture hooks can be local or global. Test run hooks are only global because they affect the entirety of your test suite.

Hooks before and after test runs

Each unique launch of TestCafe constitutes a single test run. The before test run hook launches as soon as you launch TestCafe. TestCafe executes the after test run hook just before the TestCafe process terminates.

Test run hooks cannot access the browser. Use test run hooks to execute server-side tasks (e.g., launch or shut down a web application).

Test run hooks are global. You can only define global hooks in a JavaScript configuration file.

import { utils } from './my-utils.js';

module.exports = {
    hooks: {
        testRun: {
            before: async ctx => {
                ctx.serverId = 123;

                utils.launchServer(ctx.serverId);
            },
            after: async ctx => {
                utils.terminateServer(ctx.serverId);
            },
        },
    },
};

Hooks before and after fixtures

Fixture hooks run in between tests. They cannot access the browser and execute page actions. Use fixture hooks to perform server-side operations (e.g. manage database entries).

Global fixture hooks

Global fixture hooks run before/after each of the fixtures in your test suite.

As with all global hooks, you can only declare global fixture hooks in a JavaScript configuration file.

import { utils } from './my-utils.js';

module.exports = {
    hooks: {
        fixture: {
           before: async ctx => {
                ctx.dbName= 'users';

                utils.populateDb(ctx.dbName);
            },
            after: async ctx => {
                utils.dropDb(ctx.dbName);
            },    

        },
    },
};

Hooks for individual fixtures

Fixture hooks run before/after an individual fixture.

Declare hooks for individual fixtures with the fixture.before and fixture.after methods.

import { utils } from './my-utils.js';

fixture `My fixture`
    .page `http://example.com`
    .before( async ctx => {
        ctx.dbName= 'users';

        utils.populateDb(ctx.dbName);
    })
    .after( async ctx => {
        utils.dropDb(ctx.dbName);
    });

Hooks before and after tests

Hooks that run before and after tests can access the browser. Feel free to interact with the test page and include page actions in your hook.

Global test hooks

Global test hooks run before/after each of the tests in your entire test suite. They do not interfere with the execution of other test hooks.

As with all global hooks, you can only define global test hooks in a JavaScript configuration file.

import { admin } from 'roles';

module.exports = {
    hooks: {
        test: {
            before: async t => {
                t.ctx = 'test data';

                await t.useRole(admin); 
            },
            after: async t => {
                await t.click('#delete-data');
                console.log(t.ctx); // > test data
            }
        },
    },
};

Fixture-wide test hooks

Fixture-wide test hooks run before/after each of the tests in a particular fixture.

Specify these hooks with the fixture.beforeEach and fixture.afterEach methods.

Individual test hooks override fixture-wide beforeEach and afterEach hooks.

import { admin } from 'roles';

fixture `My fixture`
    .page `http://example.com`
    .beforeEach( async t => {
        t.ctx = 'test data';

        await t.useRole(admin);
    })
    ('MyTest', async t => { /* ... */ })
    .afterEach( async t => {
        await t.click('#delete-data');
        console.log(t.ctx); // > test data
    });

Hooks for individual tests

Warning

Test-specific hooks are an emergency measure. If your test suite is well-organized, test-specific hooks offer no benefit. The only legitimate reason to use a test-specific hook is to override an existing fixture-wide hook.

TestCafe conceived hooks as a tool to help developers re-use code and maintain a cleaner codebase. Test-specific hooks do not run multiple times, and allow for the same kind of code as the test body.

Before you attach a hook to an individual test, ask yourself: “Can I move this code to the test body?”.

Test hooks run before/after individual tests. Hooks for individual tests override fixture-wide hooks of the same kind.

Specify hooks for individual tests with the test.before and test.after methods.

test
    .before( async t => {
        t.ctx = 'test data';

        await t.useRole(admin);
    })
    ('MyTest', async t => { /* ... */ })
    .after( async t => {
        await t.click('#delete-data');
        console.log(t.ctx); // > test data
    });

Hook Order and Priority

Global hooks do not override local test and fixture hooks. Global before hooks precede local before hooks. Global after hooks follow local after hooks.

Test-specific before and after hooks override fixture-wide test hooks.

The chart below shows the order in which TestCafe executes different components of the test suite.

Test flow chart

Error Handling

If any before hook yields an error, TestCafe skips the remainder of the current fixture and resumes execution at the fixture.after hook.

Share Data Between Hooks and Test Code

Every test and fixture contains a unique context (ctx) object. You can use that object to store and access data across different test entities.

Users can access an entity’s context object within the test entity itself, as well as within its hooks. If you want to share data across the entirety of your test suite, check out global user variables. Note that global user variables are read-only.

Fixture context

You can access the fixture context object within tests, test hooks, and fixture hooks.

The ctx keyword lets you reference the object in fixture hook code.

The fixtureCtx keyword lets you reference the object in test code and test hook code.

Example 1: Local Hooks

fixture `Fixture1`
    .before(async ctx  => {
        ctx.someProp = 123;
    })
    .after(async ctx  => {
        console.log(ctx.newProp); // > abc
    });

test('Test1', async t => {
    console.log(t.fixtureCtx.someProp); // > 123
});

test('Test2', async t => {
    t.fixtureCtx.newProp = 'abc';
});

Example 2: Global Hooks

Configuration file (.testcaferc.js):

module.exports = {
    hooks: {
        fixture: {
            before: async ctx => {
                ctx.someProp  = 123;
                ctx.newProp = 'abc';
            }
        }
    }
};

Test file:

fixture('Fixture 1')
    .before(async ctx => {
        console.log(ctx); // > {someProp: 123, newProp: 'abc'}
    });

test('Test 1', async t => {
    console.log(t.fixtureCtx); // > { someProp: 123, newProp: 'abc' }
});

Test Context

You can access test context objects (t.ctx) within tests and their hooks.

Example 1: Local Hooks

fixture `Fixture1`
    .beforeEach(async t  => {
        t.ctx.someProp = 123;
    });

test
    ('Test1', async t => {
        console.log(t.ctx.someProp); // > 123
    })
    .after(async t => {
         console.log(t.ctx.someProp); // > 123
    });

Example 2: Global Hooks

Configuration file (.testcaferc.js):

module.exports = {
    hooks: {
        test: {
            before: async t => {
                t.ctx.someProp = 123;
                t.ctx.newProp = 'abc';
            },
            after:  async t => {
                console.log(t.ctx); // > {someProp: 123, newProp: 'abc'}
            },
        },
    },
};

Test file:

fixture('Fixture')
    .beforeEach(async t => {
        console.log(t.ctx.someProp); // > 123
    });

test('Test', async t => {
    console.log(t.ctx.newProp); // >abc
});

Test Run Context

Use the test run context object (ctx) to share data between global test run hooks.

module.exports = {
    hooks: {
        testRun: {
            before: async ctx => {
                ctx.someProp = 123;
            },
            after:  async ctx => {
                console.log(ctx.someProp); // > 123
            },
        },
    },
};