From 6ad48f472167f8106f3fe307952581384f4eb58f Mon Sep 17 00:00:00 2001 From: Vitaliy Potapov Date: Fri, 25 Oct 2024 14:04:10 +0400 Subject: [PATCH] improve hooks: run worker hooks via beforeAll/afterAll and use custom test instances from hooks --- src/gen/formatter.ts | 22 ++-- src/gen/hooks.ts | 65 ----------- src/gen/testFile.ts | 9 +- src/gen/testFileHooks.ts | 103 ++++++++++++++++++ src/hooks/scenario.ts | 6 +- src/hooks/worker.ts | 70 +++++++----- src/run/bddFixtures/test.ts | 4 +- src/run/bddFixtures/worker.ts | 35 ++---- src/steps/createBdd.ts | 4 +- test/hooks-order/test.mjs | 9 +- test/hooks-tags/test.mjs | 2 +- test/hooks-worker/features/fixtures.ts | 7 ++ test/hooks-worker/features/sample1.feature | 4 + test/hooks-worker/features/sample2.feature | 4 + test/hooks-worker/features/steps.ts | 23 ++++ test/hooks-worker/package.json | 3 + test/hooks-worker/playwright.config.ts | 11 ++ test/hooks-worker/test.mjs | 16 +++ .../import-test-guess-hooks/steps/fixtures.ts | 11 +- test/import-test-guess-hooks/steps/steps.ts | 9 +- 20 files changed, 266 insertions(+), 151 deletions(-) delete mode 100644 src/gen/hooks.ts create mode 100644 src/gen/testFileHooks.ts create mode 100644 test/hooks-worker/features/fixtures.ts create mode 100644 test/hooks-worker/features/sample1.feature create mode 100644 test/hooks-worker/features/sample2.feature create mode 100644 test/hooks-worker/features/steps.ts create mode 100644 test/hooks-worker/package.json create mode 100644 test/hooks-worker/playwright.config.ts create mode 100644 test/hooks-worker/test.mjs diff --git a/src/gen/formatter.ts b/src/gen/formatter.ts index 7247c0cb..c96bdfd8 100644 --- a/src/gen/formatter.ts +++ b/src/gen/formatter.ts @@ -10,6 +10,7 @@ import { DescribeConfigureOptions } from '../playwright/types'; import { toPosixPath } from '../utils/paths'; import { BDDConfig } from '../config/types'; import { ScenarioHookType } from '../hooks/scenario'; +import { WorkerHookType } from '../hooks/worker'; const supportsTags = playwrightVersion >= '1.42.0'; @@ -68,7 +69,17 @@ export class Formatter { const allFixturesStr = [runScenarioHooksFixture, ...fixturesNames].join(', '); const method = type === 'before' ? 'beforeEach' : 'afterEach'; return [ - `test.${method}(({ ${allFixturesStr} }) => $runScenarioHooks(${this.quoted(type)}, { ${fixturesStr} }));`, + // eslint-disable-next-line max-len + `test.${method}(({ ${allFixturesStr} }) => ${runScenarioHooksFixture}(${this.quoted(type)}, { ${fixturesStr} }));`, + ]; + } + + workerHooksCall(type: WorkerHookType, fixturesNames: string[]) { + const runWorkerHooksFixture = '$runWorkerHooks'; + const fixturesStr = fixturesNames.join(', '); + const allFixturesStr = [runWorkerHooksFixture, ...fixturesNames].join(', '); + return [ + `test.${type}(({ ${allFixturesStr} }) => ${runWorkerHooksFixture}(${this.quoted(type)}, { ${fixturesStr} }));`, ]; } @@ -127,15 +138,6 @@ export class Formatter { ]; } - workerHookFixtures(fixtureNames: string[]) { - if (!fixtureNames.length) return []; - const fixtures = fixtureNames.join(', '); - const scope = this.quoted('worker'); - return [ - `$workerHookFixtures: [({ ${fixtures} }, use) => use({ ${fixtures} }), { scope: ${scope} }],`, - ]; - } - worldFixture(worldFixtureName: string) { return [`$world: ({ ${worldFixtureName} }, use) => use(${worldFixtureName}),`]; } diff --git a/src/gen/hooks.ts b/src/gen/hooks.ts deleted file mode 100644 index 7bb56d30..00000000 --- a/src/gen/hooks.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Collect hooks for test file. - */ - -import { - GeneralScenarioHook, - getScenarioHooksFixtureNames, - getScenarioHooksToRun, - ScenarioHookType, -} from '../hooks/scenario'; -import { toBoolean } from '../utils'; -import { Formatter } from './formatter'; -import { TestNode } from './testNode'; - -export class Hooks { - before = new TypedHooks('before', this.formatter); - after = new TypedHooks('after', this.formatter); - - constructor(private formatter: Formatter) {} - - registerHooksForTest(node: TestNode) { - this.before.registerHooksForTest(node); - this.after.registerHooksForTest(node); - } - - getCustomTests() { - return new Set([ - ...this.before.getCustomTests(), // prettier-ignore - ...this.after.getCustomTests(), - ]); - } - - getLines() { - const lines = [ - ...this.before.getLines(), // prettier-ignore - ...this.after.getLines(), - ]; - if (lines.length) lines.push(''); - return lines; - } -} - -class TypedHooks { - hooks = new Set(); - - constructor( - private type: T, - private formatter: Formatter, - ) {} - - registerHooksForTest(node: TestNode) { - if (node.isSkipped()) return; - getScenarioHooksToRun(this.type, node.tags).forEach((hook) => this.hooks.add(hook)); - } - - getCustomTests() { - return new Set([...this.hooks].map((hook) => hook.customTest).filter(toBoolean)); - } - - getLines() { - if (!this.hooks.size) return []; - const fixtureNames = getScenarioHooksFixtureNames([...this.hooks]); - return this.formatter.scenarioHooksCall(this.type, fixtureNames); - } -} diff --git a/src/gen/testFile.ts b/src/gen/testFile.ts index 91303231..44ad21f7 100644 --- a/src/gen/testFile.ts +++ b/src/gen/testFile.ts @@ -22,7 +22,6 @@ import { KeywordsMap, getKeywordsMap } from './i18n'; import { stringifyLocation, throwIf } from '../utils'; import parseTagsExpression from '@cucumber/tag-expressions'; import { TestNode } from './testNode'; -import { getWorkerHooksFixtures } from '../hooks/worker'; import { LANG_EN, isEnglish } from '../config/lang'; import { BddMetaBuilder } from './bddMetaBuilder'; import { GherkinDocumentWithPickles } from '../features/types'; @@ -39,7 +38,7 @@ import { getStepTextWithKeyword } from '../features/helpers'; import { formatDuplicateStepsMessage, StepFinder } from '../steps/finder'; import { exit } from '../utils/exit'; import { StepDefinition } from '../steps/stepDefinition'; -import { Hooks } from './hooks'; +import { TestFileHooks } from './testFileHooks'; type TestFileOptions = { gherkinDocument: GherkinDocumentWithPickles; @@ -56,7 +55,7 @@ export class TestFile { private gherkinDocumentQuery: GherkinDocumentQuery; private bddMetaBuilder: BddMetaBuilder; private stepFinder: StepFinder; - private hooks: Hooks; + private hooks: TestFileHooks; public missingSteps: MissingStep[] = []; public featureUri: string; @@ -68,7 +67,7 @@ export class TestFile { this.bddMetaBuilder = new BddMetaBuilder(this.gherkinDocumentQuery); this.featureUri = this.getFeatureUri(); this.stepFinder = new StepFinder(options.config); - this.hooks = new Hooks(this.formatter); + this.hooks = new TestFileHooks(this.formatter); } get gherkinDocument() { @@ -162,7 +161,7 @@ export class TestFile { ...this.formatter.testFixture(), ...this.formatter.uriFixture(this.featureUri), ...this.bddMetaBuilder.getFixture(), - ...this.formatter.workerHookFixtures(getWorkerHooksFixtures()), + // ...this.formatter.workerHookFixtures(getWorkerHooksFixtures()), ...(worldFixtureName ? this.formatter.worldFixture(worldFixtureName) : []), ]); diff --git a/src/gen/testFileHooks.ts b/src/gen/testFileHooks.ts new file mode 100644 index 00000000..db4a28c4 --- /dev/null +++ b/src/gen/testFileHooks.ts @@ -0,0 +1,103 @@ +/** + * Collect hooks for test file. + */ + +import { + GeneralScenarioHook, + getScenarioHooksFixtureNames, + getScenarioHooksToRun, + ScenarioHookType, +} from '../hooks/scenario'; +import { + getWorkerHooksFixtureNames, + getWorkerHooksToRun, + WorkerHook, + WorkerHookType, +} from '../hooks/worker'; +import { toBoolean } from '../utils'; +import { Formatter } from './formatter'; +import { TestNode } from './testNode'; + +export class TestFileHooks { + private beforeAll = new TestFileWorkerHooks('beforeAll', this.formatter); + private afterAll = new TestFileWorkerHooks('afterAll', this.formatter); + private before = new TestFileScenarioHooks('before', this.formatter); + private after = new TestFileScenarioHooks('after', this.formatter); + + constructor(private formatter: Formatter) {} + + registerHooksForTest(node: TestNode) { + if (node.isSkipped()) return; + this.beforeAll.registerHooksForTest(node); + this.afterAll.registerHooksForTest(node); + this.before.registerHooksForTest(node); + this.after.registerHooksForTest(node); + } + + getCustomTests() { + return new Set([ + ...this.beforeAll.getCustomTests(), // prettier-ignore + ...this.afterAll.getCustomTests(), + ...this.before.getCustomTests(), + ...this.after.getCustomTests(), + ]); + } + + getLines() { + const lines = [ + ...this.beforeAll.getLines(), // prettier-ignore + ...this.afterAll.getLines(), + ...this.before.getLines(), + ...this.after.getLines(), + ]; + if (lines.length) lines.push(''); + return lines; + } +} + +class TestFileScenarioHooks { + hooks = new Set(); + + constructor( + private type: T, + private formatter: Formatter, + ) {} + + registerHooksForTest(node: TestNode) { + getScenarioHooksToRun(this.type, node.tags).forEach((hook) => this.hooks.add(hook)); + } + + getCustomTests() { + return new Set([...this.hooks].map((hook) => hook.customTest).filter(toBoolean)); + } + + getLines() { + if (!this.hooks.size) return []; + const fixtureNames = getScenarioHooksFixtureNames([...this.hooks]); + return this.formatter.scenarioHooksCall(this.type, fixtureNames); + } +} + +class TestFileWorkerHooks { + hooks = new Set(); + + constructor( + private type: T, + private formatter: Formatter, + ) {} + + // todo: node is not used until we add tags to worker hooks + registerHooksForTest(_node: TestNode) { + getWorkerHooksToRun(this.type).forEach((hook) => this.hooks.add(hook)); + } + + getCustomTests() { + return new Set([...this.hooks].map((hook) => hook.customTest).filter(toBoolean)); + } + + getLines() { + if (!this.hooks.size) return []; + const fixtureNames = getWorkerHooksFixtureNames([...this.hooks]); + return this.formatter.workerHooksCall(this.type, fixtureNames); + } +} diff --git a/src/hooks/scenario.ts b/src/hooks/scenario.ts index ddc09d4b..4a7f237d 100644 --- a/src/hooks/scenario.ts +++ b/src/hooks/scenario.ts @@ -80,10 +80,10 @@ export function scenarioHookFactory< // eslint-disable-next-line visual/complexity export async function runScenarioHooks(type: ScenarioHookType, fixtures: ScenarioHookFixtures) { - const scenarioHooksToRun = getScenarioHooksToRun(type, fixtures.$bddContext.tags); + const hooksToRun = getScenarioHooksToRun(type, fixtures.$bddContext.tags); let error; - for (const hook of scenarioHooksToRun) { + for (const hook of hooksToRun) { try { await runScenarioHook(hook, fixtures); } catch (e) { @@ -122,7 +122,7 @@ export function getScenarioHooksToRun(type: ScenarioHookType, tags: string[] = [ } /** - * Wraps hook fn with timeout and waiting Cucumber attachments to fulfill. + * Wraps hook fn with timeout. */ function wrapHookFn(hook: GeneralScenarioHook, fixtures: ScenarioHookFixtures) { const { timeout } = hook.options; diff --git a/src/hooks/worker.ts b/src/hooks/worker.ts index 6527b808..c7e87a49 100644 --- a/src/hooks/worker.ts +++ b/src/hooks/worker.ts @@ -6,23 +6,30 @@ import { WorkerInfo } from '@playwright/test'; import { fixtureParameterNames } from '../playwright/fixtureParameterNames'; -import { KeyValue } from '../playwright/types'; +import { KeyValue, TestTypeCommon } from '../playwright/types'; import { callWithTimeout } from '../utils'; +export type WorkerHookType = 'beforeAll' | 'afterAll'; + type WorkerHookOptions = { timeout?: number; }; -type WorkerHookBddFixtures = { +type WorkerHookFixtures = { $workerInfo: WorkerInfo; + [key: string]: unknown; }; type WorkerHookFn = (fixtures: Fixtures) => unknown; -type WorkerHook = { - type: 'beforeAll' | 'afterAll'; +export type WorkerHook = { + type: WorkerHookType; options: WorkerHookOptions; fn: WorkerHookFn; + // Since playwright-bdd v8 we run worker hooks via test.beforeAll / test.afterAll in each test file, + // so we need to know if hook was executed to avoid double execution every test file. + executed?: boolean; + customTest?: TestTypeCommon; }; /** @@ -37,34 +44,32 @@ type WorkerHookDefinitionArgs = | [WorkerHookOptions, WorkerHookFn]; const workerHooks: WorkerHook[] = []; -let workerHooksFixtures: string[]; /** * Returns BeforeAll() / AfterAll() functions. */ -export function workerHookFactory(type: WorkerHook['type']) { - type Args = WorkerHookDefinitionArgs; +export function workerHookFactory( + type: WorkerHookType, + customTest: TestTypeCommon | undefined, +) { + type Args = WorkerHookDefinitionArgs; return (...args: Args) => { addHook({ type, options: getOptionsFromArgs(args) as WorkerHookOptions, fn: getFnFromArgs(args) as WorkerHook['fn'], + customTest, }); }; } // eslint-disable-next-line visual/complexity -export async function runWorkerHooks( - type: WorkerHook['type'], - fixtures: Fixtures, -) { +export async function runWorkerHooks(type: WorkerHookType, fixtures: WorkerHookFixtures) { + const hooksToRun = getWorkerHooksToRun(type); let error; - for (const hook of workerHooks) { - if (hook.type !== type) continue; - - const { timeout } = hook.options; + for (const hook of hooksToRun) { try { - await callWithTimeout(() => hook.fn(fixtures), timeout, getTimeoutMessage(hook)); + await runWorkerHook(hook, fixtures); } catch (e) { if (type === 'beforeAll') throw e; if (!error) error = e; @@ -73,21 +78,28 @@ export async function runWorkerHooks( if (error) throw error; } -export function getWorkerHooksFixtures() { - if (!workerHooksFixtures) { - const fixturesFakeObj: Record = { - $workerInfo: null, - }; - const set = new Set(); - workerHooks.forEach((hook) => { - fixtureParameterNames(hook.fn) - .filter((fixtureName) => !Object.hasOwn(fixturesFakeObj, fixtureName)) - .forEach((fixtureName) => set.add(fixtureName)); - }); - workerHooksFixtures = [...set]; +async function runWorkerHook(hook: WorkerHook, fixtures: WorkerHookFixtures) { + if (!hook.executed) { + hook.executed = true; + const { timeout } = hook.options; + await callWithTimeout(() => hook.fn(fixtures), timeout, getTimeoutMessage(hook)); } +} + +export function getWorkerHooksToRun(type: WorkerHookType) { + return workerHooks.filter((hook) => hook.type === type); + // todo: add tags + // .filter((hook) => !hook.tagsExpression || hook.tagsExpression.evaluate(tags)); +} + +export function getWorkerHooksFixtureNames(hooks: WorkerHook[]) { + const fixtureNames = new Set(); + + hooks.forEach((hook) => { + fixtureParameterNames(hook.fn).forEach((fixtureName) => fixtureNames.add(fixtureName)); + }); - return workerHooksFixtures; + return [...fixtureNames]; } function getOptionsFromArgs(args: unknown[]) { diff --git a/src/run/bddFixtures/test.ts b/src/run/bddFixtures/test.ts index b08c508f..d24f24f7 100644 --- a/src/run/bddFixtures/test.ts +++ b/src/run/bddFixtures/test.ts @@ -100,7 +100,7 @@ export const test = base.extend({ // See: https://github.com/vitalets/playwright-bdd/issues/166 Given: [ // todo: $beforeAll, $afterAll should go away - ({ $bddContext, $applySpecialTags, $beforeAll, $afterAll }, use) => + ({ $bddContext, $applySpecialTags /*, $beforeAll, $afterAll */ }, use) => use(createStepInvoker($bddContext)), fixtureOptions, ], @@ -147,7 +147,7 @@ export const test = base.extend({ // - $beforeAll / $afterAll: in pw < 1.39 worker-scoped auto-fixtures were called after test-scoped // todo: $beforeAll, $afterAll should go away $runScenarioHooks: [ - async ({ $bddContext, $beforeAll, $afterAll }, use) => { + async ({ $bddContext /*, $beforeAll, $afterAll */ }, use) => { const fn = (type: ScenarioHookType, fixtures: Record) => { return runScenarioHooks(type, { $bddContext, ...fixtures }); }; diff --git a/src/run/bddFixtures/worker.ts b/src/run/bddFixtures/worker.ts index d79aee27..863caeeb 100644 --- a/src/run/bddFixtures/worker.ts +++ b/src/run/bddFixtures/worker.ts @@ -2,10 +2,8 @@ * Worker-scoped fixtures added by playwright-bdd. */ -/* eslint-disable @typescript-eslint/no-unused-vars */ - import { BDDConfig } from '../../config/types'; -import { test as base } from '@playwright/test'; +import { test as base, WorkerInfo } from '@playwright/test'; import { getConfigFromEnv } from '../../config/env'; import { getPlaywrightConfigDir } from '../../config/configDir'; import { runWorkerHooks } from '../../hooks/worker'; @@ -19,13 +17,13 @@ import { loadSteps, resolveStepFiles } from '../../steps/loader'; const fixtureOptions = { scope: 'worker', box: true } as { scope: 'worker' }; export type BddFixturesWorker = { + $workerInfo: WorkerInfo; $bddConfig: BDDConfig; - $workerHookFixtures: Record; - $beforeAll: void; - $afterAll: void; + $runWorkerHooks: typeof runWorkerHooks; }; export const test = base.extend, BddFixturesWorker>({ + $workerInfo: [({}, use, $workerInfo) => use($workerInfo), fixtureOptions], $bddConfig: [ async ({}, use, workerInfo) => { const config = getConfigFromEnv(workerInfo.project.testDir); @@ -36,25 +34,12 @@ export const test = base.extend, BddFixturesWorker>({ }, fixtureOptions, ], - - // can be overwritten in test file if there are worker hooks - $workerHookFixtures: [({}, use) => use({}), fixtureOptions], - $beforeAll: [ - // Important unused dependencies: - // 1. $afterAll: in pw < 1.39 worker-scoped auto-fixtures are called in incorrect order - // 2. $bddConfig: to load hooks before this fixtures - async ({ $workerHookFixtures, $bddConfig }, use, $workerInfo) => { - await runWorkerHooks('beforeAll', { $workerInfo, ...$workerHookFixtures }); - await use(); - }, - fixtureOptions, - ], - $afterAll: [ - // Important unused dependencies: - // 1. $bddConfig: to load hooks before this fixtures - async ({ $workerHookFixtures, $bddConfig }, use, $workerInfo) => { - await use(); - await runWorkerHooks('afterAll', { $workerInfo, ...$workerHookFixtures }); + $runWorkerHooks: [ + // Important unused dependency: + // - $bddConfig: to load bdd config before hooks + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async ({ $bddConfig }, use) => { + await use(runWorkerHooks); }, fixtureOptions, ], diff --git a/src/steps/createBdd.ts b/src/steps/createBdd.ts index 2cb840a8..dca97e86 100644 --- a/src/steps/createBdd.ts +++ b/src/steps/createBdd.ts @@ -58,8 +58,8 @@ export function createBdd< if (customTest === (baseBddTest as TestTypeCommon)) customTest = undefined; if (customTest) assertTestHasBddFixtures(customTest); - const BeforeAll = workerHookFactory('beforeAll'); - const AfterAll = workerHookFactory('afterAll'); + const BeforeAll = workerHookFactory('beforeAll', customTest); + const AfterAll = workerHookFactory('afterAll', customTest); const Before = scenarioHookFactory('before', customTest); const After = scenarioHookFactory('after', customTest); diff --git a/test/hooks-order/test.mjs b/test/hooks-order/test.mjs index 1039b3d5..247d52ba 100644 --- a/test/hooks-order/test.mjs +++ b/test/hooks-order/test.mjs @@ -59,13 +59,16 @@ test('hooks order, 2 workers', () => { ]); }); -test('error in beforeAll: no other hooks called, process exit', () => { +test('error in beforeAll: no other beforeAll hooks called', () => { const stdout = execPlaywrightWithErrorInHook('BeforeAll 1'); expectHookCalls(stdout, ['BeforeAll 1 worker 0']); - expect(stdout).not.toContain('AfterAll'); + expect(stdout).not.toContain('BeforeAll 2'); + expect(stdout).not.toContain('After 1'); + expect(stdout).not.toContain('After 2'); + // AfterAll hooks can be called (depends on pw version) }); -test('error in before: no more before hooks called, but all after / afterAll hooks called', () => { +test('error in before: no other before hooks called, but all after / afterAll hooks called', () => { const stdout = execPlaywrightWithErrorInHook('Before 1'); expectHookCalls(stdout, [ 'BeforeAll 1 worker 0', diff --git a/test/hooks-tags/test.mjs b/test/hooks-tags/test.mjs index 9cc78ad5..10353dde 100644 --- a/test/hooks-tags/test.mjs +++ b/test/hooks-tags/test.mjs @@ -115,7 +115,7 @@ test(`${testDir.name} (gen not foo)`, () => { expectHookCalls(stdout, []); }); -test(`${testDir.name} (run not foo)`, () => { +test(`${testDir.name} (run not foo)`, { skip: !pwSupportsTags }, () => { const stdout = execPlaywrightTest( testDir.name, `${BDDGEN_CMD} && ${PLAYWRIGHT_CMD} --grep-invert "@foo"`, diff --git a/test/hooks-worker/features/fixtures.ts b/test/hooks-worker/features/fixtures.ts new file mode 100644 index 00000000..e3b1feb9 --- /dev/null +++ b/test/hooks-worker/features/fixtures.ts @@ -0,0 +1,7 @@ +import { test as base, createBdd } from 'playwright-bdd'; + +export const test = base.extend({ + // ... +}); + +export const { Given, BeforeAll, AfterAll } = createBdd(test); diff --git a/test/hooks-worker/features/sample1.feature b/test/hooks-worker/features/sample1.feature new file mode 100644 index 00000000..14f75d6e --- /dev/null +++ b/test/hooks-worker/features/sample1.feature @@ -0,0 +1,4 @@ +Feature: feature 1 + + Scenario: scenario 1 + Given a step diff --git a/test/hooks-worker/features/sample2.feature b/test/hooks-worker/features/sample2.feature new file mode 100644 index 00000000..8c75a7d7 --- /dev/null +++ b/test/hooks-worker/features/sample2.feature @@ -0,0 +1,4 @@ +Feature: feature 2 + + Scenario: scenario 2 + Given a step diff --git a/test/hooks-worker/features/steps.ts b/test/hooks-worker/features/steps.ts new file mode 100644 index 00000000..cefa72c3 --- /dev/null +++ b/test/hooks-worker/features/steps.ts @@ -0,0 +1,23 @@ +import { Given, BeforeAll, AfterAll } from './fixtures'; + +const logger = console; + +BeforeAll(({ $workerInfo }) => { + logger.log(`BeforeAll 1 worker ${$workerInfo.workerIndex}`); +}); + +BeforeAll(({ $workerInfo }) => { + logger.log(`BeforeAll 2 worker ${$workerInfo.workerIndex}`); +}); + +AfterAll(({ $workerInfo }) => { + logger.log(`AfterAll 1 worker ${$workerInfo.workerIndex}`); +}); + +AfterAll(({ $workerInfo }) => { + logger.log(`AfterAll 2 worker ${$workerInfo.workerIndex}`); +}); + +Given('a step', ({ $testInfo }) => { + logger.log(`a step of ${$testInfo.title}`); +}); diff --git a/test/hooks-worker/package.json b/test/hooks-worker/package.json new file mode 100644 index 00000000..de610253 --- /dev/null +++ b/test/hooks-worker/package.json @@ -0,0 +1,3 @@ +{ + "description": "This file is required for Playwright to consider this dir as a . It ensures to load 'playwright-bdd' from './test/node_modules/playwright-bdd' and output './test-results' here to avoid conflicts." +} diff --git a/test/hooks-worker/playwright.config.ts b/test/hooks-worker/playwright.config.ts new file mode 100644 index 00000000..ed874a7e --- /dev/null +++ b/test/hooks-worker/playwright.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from '@playwright/test'; +import { defineBddConfig } from 'playwright-bdd'; + +const testDir = defineBddConfig({ + featuresRoot: 'features', +}); + +export default defineConfig({ + testDir, + workers: 1, +}); diff --git a/test/hooks-worker/test.mjs b/test/hooks-worker/test.mjs new file mode 100644 index 00000000..7272c6f8 --- /dev/null +++ b/test/hooks-worker/test.mjs @@ -0,0 +1,16 @@ +import { test, expect, TestDir, execPlaywrightTest } from '../_helpers/index.mjs'; + +const testDir = new TestDir(import.meta); + +test(`${testDir.name} (run all tests)`, () => { + const stdout = execPlaywrightTest(testDir.name); + + expect(countOfSubstring(stdout, 'BeforeAll 1 worker 0')).toEqual(1); + expect(countOfSubstring(stdout, 'BeforeAll 2 worker 0')).toEqual(1); + expect(countOfSubstring(stdout, 'AfterAll 1 worker 0')).toEqual(1); + expect(countOfSubstring(stdout, 'AfterAll 2 worker 0')).toEqual(1); +}); + +function countOfSubstring(str, substr) { + return str.split(substr).length - 1; +} diff --git a/test/import-test-guess-hooks/steps/fixtures.ts b/test/import-test-guess-hooks/steps/fixtures.ts index d9636d0d..1a2a842d 100644 --- a/test/import-test-guess-hooks/steps/fixtures.ts +++ b/test/import-test-guess-hooks/steps/fixtures.ts @@ -1,13 +1,16 @@ import { test as base, createBdd } from 'playwright-bdd'; export const test = base.extend<{ option1: string }>({ - option1: ['foo', { option: true }], + option1: ({}, use) => use('foo'), }); - export const { Given } = createBdd(test); -export const testForScenarioHook = test.extend<{ option2: string }>({ - option2: ['bar', { option: true }], +export const testForWorkerHook = test.extend({ + option2: [({}, use) => use('bar'), { scope: 'worker' }], }); +export const { BeforeAll } = createBdd(testForWorkerHook); +export const testForScenarioHook = testForWorkerHook.extend<{ option3: string }>({ + option3: ({}, use) => use('baz'), +}); export const { Before } = createBdd(testForScenarioHook); diff --git a/test/import-test-guess-hooks/steps/steps.ts b/test/import-test-guess-hooks/steps/steps.ts index 65a6784f..44f4b7c0 100644 --- a/test/import-test-guess-hooks/steps/steps.ts +++ b/test/import-test-guess-hooks/steps/steps.ts @@ -1,11 +1,16 @@ import { expect } from '@playwright/test'; -import { Given, Before } from './fixtures'; +import { Given, BeforeAll, Before } from './fixtures'; Given('step {int}', async ({ option1 }) => { expect(option1).toEqual('foo'); }); -Before({ tags: '@foo' }, async ({ option1, option2 }) => { +BeforeAll(async ({ option2 }) => { + expect(option2).toEqual('bar'); +}); + +Before({ tags: '@foo' }, async ({ option1, option2, option3 }) => { expect(option1).toEqual('foo'); expect(option2).toEqual('bar'); + expect(option3).toEqual('baz'); });