diff --git a/.changeset/lucky-beers-complain.md b/.changeset/lucky-beers-complain.md new file mode 100644 index 0000000..67f5a2b --- /dev/null +++ b/.changeset/lucky-beers-complain.md @@ -0,0 +1,21 @@ +--- +"@quickpickle/playwright": patch +"quickpickle": patch +--- + +Added common variable should to BeforeAll and AfterAll hooks. +In your support files, you can get or set variables that should be +common (shared) across all the tests in the Feature file. + +In general it is not good testing practice to use this for anything +other than setup and teardown; for example, you might start a server +and save the port in BeforeAll, then tear it down in AfterAll. + +Here is a ridiculous example demonstrating the functionality: + +```ts +import { BeforeAll, AfterAll, AfterStep } from 'quickpickle' +BeforeAll(async (common) => { common.totalSteps = 0 }) +AfterStep(async (world) => { world.common.totalSteps++ }) +AfterAll(async(common) => { expect(common.totalSteps).not.toBeFalsy() }) +``` diff --git a/packages/main/gherkin-example/example.feature.js b/packages/main/gherkin-example/example.feature.js index beaa645..e7dc74f 100644 --- a/packages/main/gherkin-example/example.feature.js +++ b/packages/main/gherkin-example/example.feature.js @@ -23,7 +23,7 @@ const afterScenario = async(state) => { } const initScenario = async(context, scenario, tags, steps) => { - let state = new World(context, { feature:'Feature: QuickPickle\'s Comprehensive Gherkin Syntax Example', scenario, tags, steps, config:{"todoTags":["@todo","@wip"],"skipTags":["@skip"],"failTags":["@fail","@fails"],"softFailTags":["@soft","@softfail"],"concurrentTags":["@concurrent"],"sequentialTags":["@sequential"],"explodeTags":[["@1a","@1b"],["@2a","@2b"]],"worldConfig":{}}}, {}); + let state = new World(context, { feature:'Feature: QuickPickle\'s Comprehensive Gherkin Syntax Example', scenario, tags, steps, common, config:{"todoTags":["@todo","@wip"],"skipTags":["@skip"],"failTags":["@fail","@fails"],"softFailTags":["@soft","@softfail"],"concurrentTags":["@concurrent"],"sequentialTags":["@sequential"],"explodeTags":[["@1a","@1b"],["@2a","@2b"]],"worldConfig":{}}}, {}); await state.init(); state.common = common; state.info.feature = 'Feature: QuickPickle\'s Comprehensive Gherkin Syntax Example'; diff --git a/packages/main/src/hooks.ts b/packages/main/src/hooks.ts index 5b12145..c77bf95 100644 --- a/packages/main/src/hooks.ts +++ b/packages/main/src/hooks.ts @@ -59,14 +59,14 @@ const addHook = (hooksName: string, opts: string | Hook | ((state:any) => any), }; -export const BeforeAll = (opts: string | (() => any), f?: () => any): void => { addHook('beforeAll', opts, f) }; +export const BeforeAll = (opts: string | ((common:any) => void), f?: (common:any) => void): void => { addHook('beforeAll', opts, f) }; -export const Before = (opts: string | Hook | ((state:any) => any), f?: (state:any) => any): void => { addHook('before', opts, f) }; +export const Before = (opts: string | Hook | ((state:any) => void), f?: (state:any) => void): void => { addHook('before', opts, f) }; -export const BeforeStep = (opts: string | Hook | ((state:any) => any), f?: (state:any) => any): void => { addHook('beforeStep', opts, f) }; +export const BeforeStep = (opts: string | Hook | ((state:any) => void), f?: (state:any) => void): void => { addHook('beforeStep', opts, f) }; -export const AfterAll = (opts: string | (() => any), f?: () => any): void => { addHook('afterAll', opts, f) }; +export const AfterAll = (opts: string | ((common:any) => void), f?: (common:any) => void): void => { addHook('afterAll', opts, f) }; -export const After = (opts: string | Hook | ((state:any) => any), f?: (state:any) => any): void => { addHook('after', opts, f) }; +export const After = (opts: string | Hook | ((state:any) => void), f?: (state:any) => void): void => { addHook('after', opts, f) }; -export const AfterStep = (opts: string | Hook | ((state:any) => any), f?: (state:any) => any): void => { addHook('afterStep', opts, f) }; +export const AfterStep = (opts: string | Hook | ((state:any) => void), f?: (state:any) => void): void => { addHook('afterStep', opts, f) }; diff --git a/packages/main/src/render.ts b/packages/main/src/render.ts index eab33b2..123738b 100644 --- a/packages/main/src/render.ts +++ b/packages/main/src/render.ts @@ -62,7 +62,7 @@ export function renderFeature(feature:Feature, config:QuickPickleConfig) { // Render the initScenario function, which will be called at the beginning of each scenario return` const initScenario = async(context, scenario, tags, steps) => { - let state = new World(context, { feature:'${featureName}', scenario, tags, steps, config:${JSON.stringify(config)}}, ${JSON.stringify(config.worldConfig)}); + let state = new World(context, { feature:'${featureName}', scenario, tags, steps, common, config:${JSON.stringify(config)}}, ${JSON.stringify(config.worldConfig)}); await state.init(); state.common = common; state.info.feature = '${featureName}'; diff --git a/packages/main/src/world.ts b/packages/main/src/world.ts index 4dd144d..6a8f9f2 100644 --- a/packages/main/src/world.ts +++ b/packages/main/src/world.ts @@ -19,18 +19,21 @@ export interface QuickPickleWorldInterface { isComplete: boolean // (read only) whether the Scenario is on the last step config: QuickPickleConfig // (read only) configuration for QuickPickle worldConfig: QuickPickleConfig['worldConfig'] // (read only) configuration for the World - common: {[key: string]: any} // Common data shared across tests --- USE SPARINGLY + common: {[key: string]: any} // Common data shared across tests in one Feature file --- USE SPARINGLY init: () => Promise // function called by QuickPickle when the world is created tagsMatch(tags: string[]): string[]|null // function to check if the Scenario tags match the given tags } +export type InfoConstructor = Omit & { common:{[key:string]:any} } + export class QuickPickleWorld implements QuickPickleWorldInterface { info: QuickPickleWorldInterface['info'] - common: QuickPickleWorldInterface['common'] = {} + common: QuickPickleWorldInterface['common'] context: TestContext - constructor(context:TestContext, info:Omit) { + constructor(context:TestContext, info:InfoConstructor) { this.context = context - this.info = {...info, errors:[]} + this.common = info.common + this.info = { ...info, errors:[] } } async init() {} get config() { return this.info.config } @@ -52,7 +55,7 @@ export class QuickPickleWorld implements QuickPickleWorldInterface { export type WorldConstructor = new ( context: TestContext, - info: QuickPickleWorldInterface['info'], + info: InfoConstructor, ) => QuickPickleWorldInterface; let worldConstructor:WorldConstructor = QuickPickleWorld diff --git a/packages/main/tests/hooks/hooks.feature b/packages/main/tests/hooks/hooks.feature index 9e04b75..1723596 100644 --- a/packages/main/tests/hooks/hooks.feature +++ b/packages/main/tests/hooks/hooks.feature @@ -4,6 +4,9 @@ Feature: Testing hooks As a behavioral test writer I need a consistent way to run code before and after steps, scenarios, and test runs + Scenario: Hooks: The BeforeAll hook can set things in common + Then the variable "common.beforeAll" should be "beforeAll" + Scenario: Hooks: All hooks should work Given I run the tests Then the tests should pass diff --git a/packages/main/tests/hooks/hooks.steps.ts b/packages/main/tests/hooks/hooks.steps.ts index de708ad..152572f 100644 --- a/packages/main/tests/hooks/hooks.steps.ts +++ b/packages/main/tests/hooks/hooks.steps.ts @@ -3,11 +3,14 @@ import { Given, When, Then, BeforeAll, Before, BeforeStep, AfterStep, After, Aft const log:any = {} -BeforeAll(async () => { +BeforeAll(async (common) => { + common.beforeAll = 'beforeAll' + common.totalSteps = 0 log.tests = {} }) Before(async (state) => { + state.common.before = true log.tests[state.info.scenario] = [] log.tests[state.info.scenario].push('Before') state.hooks = {} @@ -23,6 +26,7 @@ AfterStep({ tags:'clearErrorsAfterStep' }, async (state) => { }) AfterStep(async (state) => { + state.common.totalSteps++ log.tests[state.info.scenario].push('errors: ' + state.info.errors.length) log.tests[state.info.scenario].push('AfterStep') }) @@ -56,8 +60,11 @@ const testWithError = [ 'After', ] -AfterAll(async () => { +AfterAll(async (common) => { console.log('AfterAll') + expect(common.beforeAll).toBe('beforeAll') + expect(common.before).toBe(true) + expect(common.totalSteps).not.toBeFalsy() expect(log.tests).toMatchObject({ 'Hooks: All hooks should work': testWithNoErrors, 'Hooks: Hooks also work on @soft tests': testWithNoErrors, diff --git a/packages/playwright/src/PlaywrightWorld.ts b/packages/playwright/src/PlaywrightWorld.ts index 78c4ba9..33bd565 100644 --- a/packages/playwright/src/PlaywrightWorld.ts +++ b/packages/playwright/src/PlaywrightWorld.ts @@ -3,6 +3,7 @@ import { normalizeTags, QuickPickleWorld, QuickPickleWorldInterface } from 'quic import { After } from 'quickpickle'; import type { TestContext } from 'vitest'; import { defaultsDeep } from 'lodash-es' +import { InfoConstructor } from 'quickpickle/dist/world'; const browsers = { chromium, firefox, webkit } @@ -53,7 +54,7 @@ export class PlaywrightWorld extends QuickPickleWorld { browserContext!: BrowserContext page!: Page - constructor(context:TestContext, info:QuickPickleWorldInterface['info']) { + constructor(context:TestContext, info:InfoConstructor) { super(context, info) this.setConfig(info.config.worldConfig) } diff --git a/vitest.workspace.ts b/vitest.workspace.ts index df28350..7beaabe 100644 --- a/vitest.workspace.ts +++ b/vitest.workspace.ts @@ -5,7 +5,8 @@ export default defineWorkspace([ root: './packages/main', extends: './packages/main/vite.config.ts', test: { - include : [ './tests/*.feature' ], + name: 'features', + include : [ 'tests/*.feature' ], setupFiles: [ 'tests/tests.steps.ts' ], }, }, @@ -13,12 +14,25 @@ export default defineWorkspace([ root: './packages/main', extends: './packages/main/vite.config.ts', test: { - include : [ 'tests/*.test.ts' ], + name: 'hooks', + include : [ 'tests/hooks/*.feature' ], + setupFiles: [ 'tests/tests.steps.ts','tests/hooks/hooks.steps.ts' ], + }, + }, + { + root: './packages/main', + extends: './packages/main/vite.config.ts', + test: { + name: 'unit', + include : [ './tests/*.test.ts' ], } }, { root: './packages/playwright', extends: './packages/playwright/vite.config.ts', + test: { + name: 'playwright', + }, // @ts-ignore quickpickle: { skipTags: ['@skip','@wip','@skip-ci'],