playwright-bdd
Version:
BDD Testing with Playwright runner
172 lines • 7.09 kB
JavaScript
"use strict";
/**
* Manage hooks for test file.
*
* For worker hooks we generate test.beforeAll() / test.afterAll() that call $runWorkerFixture
* and pass all needed fixtures to it.
*
* For scenario hooks we generate test.beforeEach() / test.afterEach()
* that call $runScenarioHooks and pass all needed fixtures to it.
*
* test.beforeEach('BeforeEach Hooks', ({ $runScenarioHooks, fixtureA, fixtureB }) => {
* return $runScenarioHooks('before', { fixtureA, fixtureB }));
* });
* test.afterEach('AfterEach Hooks', ({ $runScenarioHooks, fixtureC }) => {
* return $runScenarioHooks('after', { fixtureC });
* });
*
* It's important to generate these test.beforeEach() / test.afterEach() separately from Background,
* otherwise dependency fixtures will be nested under background in the report.
*
* Additionally, we generate test.use() code for $beforeEachFixtures/$afterEachFixtures,
* that collect all custom fixtures used in scenario hooks in this file.
*
* PREVIOUSLY:
* For scenario hooks we generate test.beforeEach() / test.afterEach()
* that just reference $beforeEach/$afterEach fixtures,
* to get them executed during fixtures setup and call scenario hooks:
*
* test.beforeEach('BeforeEach Hooks', ({ $beforeEach }) => {});
* test.afterEach('AfterEach Hooks', ({ $afterEach }) => {});
*
* The approach is different for beforeAll/afterAll.
* If we follow the same approach and call scenario hooks directly inside test.beforeEach,
* them in case of error in hook, Playwright will execute Background steps.
* See: https://github.com/microsoft/playwright/issues/33314
*
* We can solve it by checking testInfo.error in Background's beforeEach, and calling testInfo.skip() in case of error.
* But in this case in the Playwright report Background's beforeEach is still displayed and marked as failed:
* test.beforeEach('Background', async ({}, testInfo) => {
* if (testInfo.error) testInfo.skip();
* // ... bg steps
* });
*
* Another option is to wrap whole Background code into if (!testInfo.error) { ... },
* but in this case Background is also displayed in the report as passed,
* although actually it was not executed.
*
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.TestFileHooks = void 0;
const renderer_1 = require("../bddData/renderer");
const scenario_1 = require("../hooks/scenario");
const worker_1 = require("../hooks/worker");
const utils_1 = require("../utils");
const exit_1 = require("../utils/exit");
class TestFileHooks {
constructor(formatter) {
this.formatter = formatter;
this.beforeAll = new WorkerHooks('beforeAll', this.formatter);
this.afterAll = new WorkerHooks('afterAll', this.formatter);
this.before = new ScenarioHooks('before', this.formatter);
this.after = new ScenarioHooks('after', this.formatter);
}
fillFromTests(tests) {
tests
.filter((test) => !test.skipped)
.forEach((test) => {
this.beforeAll.registerHooksForTest(test);
this.afterAll.registerHooksForTest(test);
// Important to generate calls of test.beforeEach() / test.afterEach()
// that reference $beforeEach/$afterEach fixtures.
// This forces $beforeEach/$afterEach to be initialized before the background.
// Otherwise, Before/After hooks are called during background initialization
// and in the report results are placed inside Background parent.
this.before.registerHooksForTest(test);
this.after.registerHooksForTest(test);
});
}
getCustomTestInstances() {
return new Set([
...this.beforeAll.getCustomTestInstances(), // prettier-ignore
...this.afterAll.getCustomTestInstances(),
...this.before.getCustomTestInstances(),
...this.after.getCustomTestInstances(),
]);
}
getWorldFixtureNames() {
return new Set([
...this.before.getWorldFixtureNames(), // prettier-ignore
...this.after.getWorldFixtureNames(),
]);
}
render() {
const lines = [
...this.beforeAll.render(), // prettier-ignore
...this.afterAll.render(),
...this.before.render(),
...this.after.render(),
];
if (lines.length)
lines.push('');
return lines;
}
}
exports.TestFileHooks = TestFileHooks;
class ScenarioHooks {
constructor(type, formatter) {
this.type = type;
this.formatter = formatter;
this.hooks = new Set();
}
registerHooksForTest(test) {
(0, scenario_1.getScenarioHooksToRun)(this.type, test.tags).forEach((hook) => this.hooks.add(hook));
}
getCustomTestInstances() {
return new Set([...this.hooks].map((hook) => hook.customTest).filter(utils_1.toBoolean));
}
render() {
if (!this.hooks.size)
return [];
const fixtureNames = (0, scenario_1.getScenarioHooksFixtureNames)([...this.hooks]);
return this.formatter.scenarioHooksCall(this.type, new Set(fixtureNames));
}
getWorldFixtureNames() {
return new Set([...this.hooks].map((hook) => hook.worldFixture).filter(utils_1.toBoolean));
}
}
class WorkerHooks {
constructor(type, formatter) {
this.type = type;
this.formatter = formatter;
this.hooks = new Set();
this.tests = [];
}
registerHooksForTest(test) {
/**
* For worker hooks (beforeAll, afterAll) we require
* that each test match exactly the same set of hooks.
* Otherwise, in fully-parallel mode, we will run all worker hooks
* in each worker for each test, that actually makes test-level tags useless.
*/
const hooksForTest = new Set((0, worker_1.getWorkerHooksToRun)(this.type, test.tags));
if (this.tests.length === 0) {
this.hooks = hooksForTest;
}
else {
this.ensureHooksEqual(test, hooksForTest);
}
this.tests.push(test);
}
getCustomTestInstances() {
return new Set([...this.hooks].map((hook) => hook.customTest).filter(utils_1.toBoolean));
}
render() {
if (!this.hooks.size)
return [];
const fixtureNames = (0, worker_1.getWorkerHooksFixtureNames)([...this.hooks]);
return this.formatter.workerHooksCall(this.type, new Set(fixtureNames), renderer_1.BddDataRenderer.varName);
}
ensureHooksEqual(test, hooksForTest) {
if ((0, utils_1.areSetsEqual)(this.hooks, hooksForTest))
return;
const prevTest = this.tests.at(-1);
(0, exit_1.exit)([
`Tagged ${this.type} hooks can use only feature-level tags.`,
`Feature: ${test.featureUri}`,
` - ${this.hooks.size} hook(s): ${prevTest.testTitle} ${prevTest.tags.join(' ')}`,
` - ${hooksForTest.size} hook(s): ${test.testTitle} ${test.tags.join(' ')}`,
].join('\n'));
}
}
//# sourceMappingURL=hooks.js.map