From 5ff63c6508f97e3aaa7ce631d37ff9b8ad5021ea Mon Sep 17 00:00:00 2001 From: Vitaliy Potapov Date: Mon, 28 Oct 2024 19:05:49 +0400 Subject: [PATCH] add name option to BeforeAll / AfterAll hooks --- CHANGELOG.md | 1 + src/gen/formatter.ts | 2 +- src/hooks/scenario.ts | 6 ++-- src/hooks/worker.ts | 47 +++++++++++++++++++++++----- test/reporter-data/features/steps.ts | 4 ++- test/reporter-data/test.mjs | 5 ++- 6 files changed, 51 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a248c1b..9eb9d3a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * support scoped step definitions ([#205](https://github.com/vitalets/playwright-bdd/issues/205)) * new config option `missingSteps` to setup different behavior when step definitions are missing ([#158](https://github.com/vitalets/playwright-bdd/issues/158)) * new config option `matchKeywords` to enable keyword matching when searching for step definitions ([#221](https://github.com/vitalets/playwright-bdd/issues/221)) +* add `name` option to `BeforeAll / AfterAll` hooks * make config option `quote` default to `"single"` to have less escapes in the generated files * config option `featuresRoot` serves as a default directory for both `features` and `steps`, if these options are not explicitly defined * feature: provide full localized step titles to Playwright HTML reporter ([#229](https://github.com/vitalets/playwright-bdd/issues/229), [#122](https://github.com/vitalets/playwright-bdd/issues/122)) diff --git a/src/gen/formatter.ts b/src/gen/formatter.ts index de166c53..4e027566 100644 --- a/src/gen/formatter.ts +++ b/src/gen/formatter.ts @@ -78,7 +78,7 @@ export class Formatter { const title = type === 'beforeAll' ? 'BeforeAll Hooks' : 'AfterAll Hooks'; return [ // eslint-disable-next-line max-len - `test.${type}(${this.quoted(title)}, ({ ${allFixturesStr} }) => ${runWorkerHooksFixture}(${this.quoted(type)}, { ${fixturesStr} }));`, + `test.${type}(${this.quoted(title)}, ({ ${allFixturesStr} }) => ${runWorkerHooksFixture}(test, ${this.quoted(type)}, { ${fixturesStr} }));`, ]; } diff --git a/src/hooks/scenario.ts b/src/hooks/scenario.ts index 82ce5dfa..346a3d95 100644 --- a/src/hooks/scenario.ts +++ b/src/hooks/scenario.ts @@ -95,9 +95,9 @@ export async function runScenarioHooks(type: ScenarioHookType, fixtures: Scenari } async function runScenarioHook(hook: GeneralScenarioHook, fixtures: ScenarioHookFixtures) { - const hookFn = wrapHookFn(hook, fixtures); + const fn = wrapHookFnWithTimeout(hook, fixtures); const stepTitle = getHookStepTitle(hook); - await runStepWithLocation(fixtures.$bddContext.test, stepTitle, hook.location, hookFn); + await runStepWithLocation(fixtures.$bddContext.test, stepTitle, hook.location, fn); } export function getScenarioHooksFixtureNames(hooks: GeneralScenarioHook[]) { @@ -120,7 +120,7 @@ export function getScenarioHooksToRun(type: ScenarioHookType, tags: string[] = [ /** * Wraps hook fn with timeout. */ -function wrapHookFn(hook: GeneralScenarioHook, fixtures: ScenarioHookFixtures) { +function wrapHookFnWithTimeout(hook: GeneralScenarioHook, fixtures: ScenarioHookFixtures) { const { timeout } = hook.options; const { $bddContext } = fixtures; const fixturesArg = { diff --git a/src/hooks/worker.ts b/src/hooks/worker.ts index 39e7926e..79265dcf 100644 --- a/src/hooks/worker.ts +++ b/src/hooks/worker.ts @@ -6,12 +6,15 @@ import { WorkerInfo } from '@playwright/test'; import { fixtureParameterNames } from '../playwright/fixtureParameterNames'; -import { KeyValue, TestTypeCommon } from '../playwright/types'; +import { KeyValue, PlaywrightLocation, TestTypeCommon } from '../playwright/types'; import { callWithTimeout } from '../utils'; +import { getLocationByOffset } from '../playwright/getLocationInFile'; +import { runStepWithLocation } from '../playwright/runStepWithLocation'; export type WorkerHookType = 'beforeAll' | 'afterAll'; type WorkerHookOptions = { + name?: string; timeout?: number; }; @@ -26,10 +29,11 @@ export type WorkerHook = { type: WorkerHookType; options: WorkerHookOptions; fn: WorkerHookFn; + location: PlaywrightLocation; + customTest?: TestTypeCommon; // 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. + // so we need to know if hook was executed to avoid double execution in every test file. executed?: boolean; - customTest?: TestTypeCommon; }; /** @@ -58,18 +62,24 @@ export function createBeforeAllAfterAll( type, options: getOptionsFromArgs(args) as WorkerHookOptions, fn: getFnFromArgs(args) as WorkerHook['fn'], + // offset = 3 b/c this call is 3 steps below the user's code + location: getLocationByOffset(3), customTest, }); }; } // eslint-disable-next-line visual/complexity -export async function runWorkerHooks(type: WorkerHookType, fixtures: WorkerHookFixtures) { +export async function runWorkerHooks( + test: TestTypeCommon, + type: WorkerHookType, + fixtures: WorkerHookFixtures, +) { const hooksToRun = getWorkerHooksToRun(type); let error; for (const hook of hooksToRun) { try { - await runWorkerHook(hook, fixtures); + await runWorkerHook(test, hook, fixtures); } catch (e) { if (type === 'beforeAll') throw e; if (!error) error = e; @@ -78,11 +88,12 @@ export async function runWorkerHooks(type: WorkerHookType, fixtures: WorkerHookF if (error) throw error; } -async function runWorkerHook(hook: WorkerHook, fixtures: WorkerHookFixtures) { +async function runWorkerHook(test: TestTypeCommon, hook: WorkerHook, fixtures: WorkerHookFixtures) { if (!hook.executed) { hook.executed = true; - const { timeout } = hook.options; - await callWithTimeout(() => hook.fn(fixtures), timeout, getTimeoutMessage(hook)); + const hookFn = wrapHookFnWithTimeout(hook, fixtures); + const stepTitle = getHookStepTitle(hook); + await runStepWithLocation(test, stepTitle, hook.location, hookFn); } } @@ -102,6 +113,22 @@ export function getWorkerHooksFixtureNames(hooks: WorkerHook[]) { return [...fixtureNames]; } +/** + * Wraps hook fn with timeout. + */ +function wrapHookFnWithTimeout(hook: WorkerHook, fixtures: WorkerHookFixtures) { + const { timeout } = hook.options; + + return async () => { + await callWithTimeout( + // call with null to avoid using 'this' inside worker hook + () => hook.fn.call(null, fixtures), + timeout, + getTimeoutMessage(hook), + ); + }; +} + function getOptionsFromArgs(args: unknown[]) { if (typeof args[0] === 'object') return args[0]; return {}; @@ -124,3 +151,7 @@ function getTimeoutMessage(hook: WorkerHook) { const { timeout } = hook.options; return `${hook.type} hook timeout (${timeout} ms)`; } + +function getHookStepTitle(hook: WorkerHook) { + return hook.options.name || (hook.type === 'beforeAll' ? 'BeforeAll hook' : 'AfterAll hook'); +} diff --git a/test/reporter-data/features/steps.ts b/test/reporter-data/features/steps.ts index b93f4b58..0a22d84f 100644 --- a/test/reporter-data/features/steps.ts +++ b/test/reporter-data/features/steps.ts @@ -2,7 +2,7 @@ import { createBdd } from 'playwright-bdd'; import { test } from './fixtures'; -const { Given, When, Before, BeforeAll } = createBdd(test); +const { Given, When, Before, BeforeAll, AfterAll } = createBdd(test); Given('background step', async () => {}); Given('I am on home page', async ({ myPage }) => { @@ -12,3 +12,5 @@ When('Action {int}', () => {}); Before({ name: 'hook 1' }, async () => {}); Before(async () => {}); BeforeAll(async () => {}); +BeforeAll({ name: 'named BeforeAll hook' }, async () => {}); +AfterAll({ name: 'named AfterAll hook' }, async () => {}); diff --git a/test/reporter-data/test.mjs b/test/reporter-data/test.mjs index 3794ecc2..377f7fed 100644 --- a/test/reporter-data/test.mjs +++ b/test/reporter-data/test.mjs @@ -22,8 +22,11 @@ function checkStepLocations(output) { ); expect(output).toContain(`Before Hooks :0:0`); - // todo: add named beforeAll hook expect(output).toContain(`BeforeAll Hooks`); + expect(output).toContain(`BeforeAll hook ${normalize('features/steps.ts')}:14:1`); + expect(output).toContain(`named BeforeAll hook ${normalize('features/steps.ts')}:15:1`); + expect(output).toContain(`named AfterAll hook ${normalize('features/steps.ts')}:16:1`); + expect(output).toContain(`BeforeEach Hooks`); expect(output).toContain(`hook 1 ${normalize('features/steps.ts')}:12:1`); expect(output).toContain(`BeforeEach hook ${normalize('features/steps.ts')}:13:1`);