Skip to content

Latest commit

 

History

History
275 lines (214 loc) · 11.3 KB

events.md

File metadata and controls

275 lines (214 loc) · 11.3 KB

Testplane Events

Contents

Master process events

Event Description
INIT Will be triggered before any job start (run or readTests). If handler returns a promise then job will start only after the promise will be resolved. Emitted only once no matter how many times job will be performed.
BEFORE_FILE_READ Will be triggered on test files parsing before reading the file. The handler accepts data object with file, testplane (helper which will be available in test file) and testParser (TestParserAPI object) fields.
AFTER_FILE_READ Will be triggered on test files parsing right after reading the file. The handler accepts data object with file and testplane (helper which will be available in test file) fields.
AFTER_TESTS_READ Will be triggered right after tests read via readTests or run methods with TestCollection object.
RUNNER_START Will be triggered after worker farm initialization and before test execution. If a handler returns a promise, tests will be executed only after the promise is resolved. The handler accepts an instance of a runner as the first argument. You can use this instance to emit and subscribe to any other available events.
RUNNER_END Will be triggered after test execution and before worker farm ends. If a handler returns a promise, worker farm will be ended only after the promise is resolved. The handler accepts an object with tests execution statistics.
NEW_WORKER_PROCESS Will be triggered after new subprocess is spawned. The handler accepts a restricted process object with only send method.
SESSION_START Will be triggered after browser session initialization. If a handler returns a promise, tests will be executed only after the promise is resolved. The handler accepts an instance of webdriverIO as the first argument and an object with a browser identifier as the second.
SESSION_END Will be triggered after the browser session ends. If a handler returns a promise, tests will be executed only after the promise is resolved. The handler accepts an instance of webdriverIO as the first argument and an object with a browser identifier as the second.
BEGIN Will be triggered before test execution, but after all the runners are initialized.
END Will be triggered just before RUNNER_END event. The handler accepts a stats of tests execution.
SUITE_BEGIN Test suite is about to execute.
SUITE_END Test suite execution is finished.
TEST_BEGIN Test is about to execute.
TEST_END Test execution is finished.
TEST_PASS Test passed.
TEST_FAIL Test failed.
TEST_PENDING Test is skipped.
RETRY Test failed but went to retry.
ERROR Generic (no tests) errors.
INFO Reserved.
WARNING Reserved.
EXIT Will be triggered when SIGTERM is received (for example, Ctrl + C). The handler can return a promise.

Worker process events

Event Description
BEFORE_FILE_READ Will be triggered on test files parsing before reading the file. The handler accepts data object with file, testplane (helper which will be available in test file) and testParser (TestParserAPI object) fields.
AFTER_FILE_READ Will be triggered on test files parsing right after reading the file. The handler accepts data object with file and testplane (helper which will be available in test file) fields.
AFTER_TESTS_READ Will be triggered right after tests read each time some file is being reading during test run.
NEW_BROWSER Will be triggered after new browser instance created. The handler accepts an instance of webdriverIO as the first argument and an object with a browser identifier and version as the second.
UPDATE_REFERENCE Will be triggered after updating reference image.

Sharing data between master and worker processes

Events which are triggered in the main process and subprocesses can not share information between each other, for example:

module.exports = (testplane) => {
    let flag = false;

    testplane.on(testplane.events.RUNNER_START, () => {
        flag = true;
    });

    testplane.on(testplane.events.NEW_BROWSER, () => {
        // outputs `false`, because `NEW_BROWSER` event was triggered in a subprocess,
        // but `RUNNER_START` was not
        console.log(flag);
    });

    testplane.on(testplane.events.RUNNER_END, () => {
        // outputs `true`
        console.log(flag);
    });
};

But you can solve such problem this way:

module.exports = (testplane, opts) => {
    testplane.on(testplane.events.RUNNER_START, () => {
      opts.flag = true;
    });

    testplane.on(testplane.events.NEW_BROWSER, () => {
        // outputs `true`, because properties in a config (variable `opts` is a part of a config)
        // which have raw data types are passed to subprocesses after `RUNNER_START` event
        console.log(opts.flag);
    });
};

Intercepting events

You have the ability to intercept events in plugins and translate them to other events:

module.exports = (testplane) => {
    testplane.intercept(testplane.events.TEST_FAIL, ({event, data: test}) => {
        test.skip({reason: 'intercepted failure'});

        return {event: testplane.events.TEST_PENDING, test};
    });

    testplane.on(testplane.events.TEST_FAIL, (test) => {
        // this event handler will never be called
    });

    testplane.on(testplane.evenst.TEST_PENDING, (test) => {
        // this event handler will always be called instead of 'TEST_FAIL' one
    });
};

If for some reason interceptor should not translate passed event to another one you can return the same object or some falsey value:

module.exports = (testplane) => {
    testplane.intercept(testplane.events.TEST_FAIL, ({event, data}) => {
        return {event, data};
        // return;
        // return null;
        // return false;
    });

    testplane.on(testplane.events.TEST_FAIL, (test) => {
        // this event handler will be called as usual because interceptor does not change event
    });
};

If for some reason interceptor should ignore passed event and do not translate it to any other listeners you can return an empty object:

module.exports = (testplane) => {
    testplane.intercept(testplane.events.TEST_FAIL, ({event, data}) => {
        return {};
    });

    testplane.on(testplane.events.TEST_FAIL, (test) => {
        // this event handler will NEVER be called because interceptor ignores it
    });
};

The above feature can be used to delay triggering of some events, for example:

module.exports = (testplane) => {
  const intercepted = [];

  testplane.intercept(testplane.events.TEST_FAIL, ({event, data}) => {
        intercepted.push({event, data});
        return {};
    });

    testplane.on(testplane.events.END, () => {
        intercepted.forEach(({event, data}) => testplane.emit(event, data));
    });
};

Events that can be intercepted

Event
SUITE_BEGIN
SUITE_END
TEST_BEGIN
TEST_END
TEST_PASS
TEST_FAIL
RETRY

Passing information between event handlers {#passing-information-between-event-handlers}

Events that are triggered in the master process and in Testplane workers cannot exchange information via global variables.

For example, this approach will not work:

module.exports = (testplane) => {
    let flag = false;

    testplane.on(testplane.events.RUNNER_START, () => {
        flag = true;
    });

    testplane.on(testplane.events.NEW_BROWSER, () => {
        // false will be displayed because the NEW_BROWSER event
        // is triggered in the testplane worker, and RUNNER_START in the master process
        console.info(flag);
    });

    testplane.on(testplane.events.RUNNER_END, () => {
        // true will be output
        console.info(flag);
    });
};

But you can solve the issue like this:

module.exports = (testplane, opts) => {
    testplane.on(testplane.events.RUNNER_START, () => {
        opts.flag = true;
    });

    testplane.on(testplane.events.NEW_BROWSER, () => {
        // true will be output because the properties in the config,
        // which have a primitive type (and the "opts" variable is part of the config),
        // are automatically passed to workers during the RUNNER_START event
        console.info(opts.flag);
    });
};

Or like this: see example from the description of the NEW_WORKER_PROCESS event.

Notice that upon transferring between master and workers objects go through serialization/deserialization (in particular, objects methods will be lost).

Also

Parallel execution of plugin code {#parallel-execution-of-plugin-code}

The test runner has a method registerWorkers, which registers the plugin code for parallel execution in Testplane workers. The method accepts the following parameters:

Parameter Type Description
workerFilepath String Absolute path to the worker.
exportedMethods String[] List of exported methods.

Returns an object containing asynchronous functions with names from the exported methods.

The file with the path workerFilepath must export an object containing asynchronous functions with names from exportedMethods.

Example {#parallel-execution-of-plugin-code-example}

Plugin code: plugin.js

let workers;

module.exports = (testplane) => {
    testplane.on(testplane.events.RUNNER_START, async (runner) => {
        const workerFilepath = require.resolve('./worker');
        const exportedMethods = ['foo'];

        workers = runner.registerWorkers(workerFilepath, exportedMethods);

        // outputs FOO_RUNNER_START
        console.info(await workers.foo('RUNNER_START'));
    });

    testplane.on(testplane.events.RUNNER_END, async () => {
        // outputs FOO_RUNNER_END
    console.info(await workers.foo('RUNNER_END'));
    });
};

Worker code: worker.js

module.exports = {
    foo: async function(event) {
        return 'FOO_' + event;
    }
};