-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(testcontroller): add new properties to TestController typing (#113)
* feat(testcontroller): add new properties to TestController typing No actual code changes for TestController: - Added the property testRun which itself has properties. - Added the test property that gives access to name, meta and fixture name. - Added fixtureCtx property. - Added many examples of hook usage (global hooks and tagg hooks) Co-authored-by: Arthur Warnier <arthur.warnier@gmail.com>
- Loading branch information
Showing
9 changed files
with
280 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export const BEFORE_PREFIX = 'before'; | ||
export const AFTER_PREFIX = 'after'; | ||
export const BEFORE_AND_AFTER_PREFIX = 'beforeafter'; | ||
|
||
export const beforeTags = ['tag1', 'tag2'].map((tagSuffix) => `${BEFORE_PREFIX}${tagSuffix}`); | ||
export const afterTags = ['tag'].map((tagSuffix) => `${AFTER_PREFIX}${tagSuffix}`); | ||
export const bothTags = ['tag'].map((tagSuffix) => `${BEFORE_AND_AFTER_PREFIX}${tagSuffix}`); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { AfterAll, BeforeAll } from '@cucumber/cucumber'; | ||
|
||
let finishedFeaturesCount = 0; | ||
|
||
BeforeAll(async (ctx, meta) => { | ||
console.log('Preparing feature:', meta.name); | ||
// set the fixtureCtx for all the scenarios included in the feature | ||
ctx.featureName = meta.name; | ||
ctx.afterHooksCounter = 0; | ||
ctx.finishedFeaturesCount = finishedFeaturesCount; | ||
}); | ||
|
||
AfterAll(async (ctx, meta) => { | ||
const expectedCount = 3; | ||
if (meta.name === 'Hooks feature 2' && ctx.afterHooksCounter !== expectedCount) { | ||
throw new Error(`The After hooks ran ${ctx.afterHooksCounter} times instead of ${expectedCount}`); | ||
} | ||
finishedFeaturesCount += 1; | ||
console.log('Cleaning after feature:', meta.name); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import { Given, Then } from '@cucumber/cucumber'; | ||
|
||
import { AFTER_PREFIX, BEFORE_PREFIX, beforeTags, afterTags, bothTags } from './hooks-const'; | ||
|
||
/* PREREQUISITES */ | ||
|
||
Given(/a (BeforeAll|AfterAll) hook is defined/, async (t: TestController) => { | ||
// NOTE: nothing to do here as there is no way to check for global hooks being implemented | ||
}); | ||
|
||
Given(/the scenario has no tags/, async (t: TestController) => { | ||
const testTags: string[] = getTagsFromMeta(t); | ||
// fails if any tag is added to the scenario | ||
await t.expect(testTags.length).eql(0, `At least one tag (${testTags}) is attached to the scenario`); | ||
}); | ||
|
||
Given(/the scenario has a tag linked to a Before hook/, async (t: TestController) => { | ||
// fails in any case except a single tag that contains "before" | ||
const testTags: string[] = getTagsFromMeta(t); | ||
await t | ||
.expect(testTags.length) | ||
.eql(1, `Tag count is ${testTags.length}`) | ||
.expect(beforeTags) | ||
.contains(testTags[0], `${testTags[0]} doesn't exist in the hooks-tag file`); | ||
}); | ||
|
||
Given(/the scenario has one tag linked to a Before hook/, async (t: TestController) => { | ||
// fails in any case except a single tag that contains "before" | ||
const testTags: string[] = getTagsFromMeta(t).filter((tag) => tag.includes(BEFORE_PREFIX)); | ||
await t | ||
.expect(testTags.length) | ||
.eql(1, `Tag count is ${testTags.length}`) | ||
.expect(beforeTags) | ||
.contains(testTags[0], `${testTags[0]} doesn't exist in the hooks-tag file`); | ||
}); | ||
|
||
Given(/the scenario has one tag linked to an After hook/, async (t: TestController) => { | ||
// fails in any case except a single tag that contains "before" | ||
const testTags: string[] = getTagsFromMeta(t).filter((tag) => tag.includes(AFTER_PREFIX)); | ||
await t | ||
.expect(testTags.length) | ||
.eql(1, `Tag count is ${testTags.length}`) | ||
.expect(afterTags) | ||
.contains(testTags[0], `${testTags[0]} doesn't exist in the hooks-tag file`); | ||
}); | ||
|
||
Given(/the scenario has a tag linked to an After hook/, async (t: TestController) => { | ||
// fails in any case except a single tag that contains "after" | ||
const testTags: string[] = getTagsFromMeta(t); | ||
await t | ||
.expect(testTags.length) | ||
.eql(1, `Tag count is ${testTags.length}`) | ||
.expect(afterTags) | ||
.contains(testTags[0], `${testTags[0]} doesn't exist in the hooks-tag file`); | ||
}); | ||
|
||
Given(/the scenario has several tags linked to Before hooks/, async (t: TestController) => { | ||
// Cause the test to fail if the number of tags is less than 2 | ||
const testTags: string[] = getTagsFromMeta(t); | ||
await t.expect(testTags.length).gte(2, `Tag count is ${testTags.length}`); | ||
|
||
await Promise.all( | ||
testTags.map(async (tag) => t.expect(beforeTags).contains(tag, `${tag} doesn't exist in the hooks-tag file`)) | ||
); | ||
}); | ||
|
||
Given(/the scenario has a tag linked to a Before and an After hook/, async (t: TestController) => { | ||
// fails in any case except a single tag that contains "both" | ||
const testTags: string[] = getTagsFromMeta(t); | ||
await t | ||
.expect(testTags.length) | ||
.eql(1, `Tag count is ${testTags.length}`) | ||
.expect(bothTags) | ||
.contains(testTags[0], `${testTags[0]} doesn't exist in the hooks-tag file`); | ||
}); | ||
|
||
/* ASSERTIONS */ | ||
|
||
Then(/the BeforeAll hook should have run before any scenario/, async (t: TestController) => { | ||
const fixtureName = t.testRun.test.fixture.name; | ||
// fixtureCtx.featureName is set by the BeforeAll hook (see hooks-global.ts) | ||
// Will cause the test to fail if the BeforeAll hook hasn't run | ||
await t | ||
.expect(fixtureName) | ||
.ok("fixtureName doesn't exist in the test object") | ||
.expect(fixtureName) | ||
.contains(t.fixtureCtx.featureName, `featureName is not properly set to the current feature name`); | ||
}); | ||
|
||
Then(/the AfterAll hook should have run after the previous feature/, async (t: TestController) => { | ||
// cause the test to fail if the featureCounter hasn't been initialized yet | ||
const { finishedFeaturesCount } = t.testRun.fixtureCtx; | ||
await t | ||
.expect(typeof finishedFeaturesCount) | ||
.eql('number', `The finishedFeatureCount is of type ${typeof finishedFeaturesCount} instead of number`) | ||
.expect(finishedFeaturesCount) | ||
.gt(0, "No feature was finished or AfterAll didn't run"); | ||
}); | ||
|
||
Then(/no tagged hook should run/, async (t: TestController) => { | ||
const contextKeys = Object.keys(t.ctx); | ||
const beforeHookTrackers = contextKeys.filter((contextKey) => contextKey.includes(BEFORE_PREFIX)); | ||
const afterHookTrackers = contextKeys.filter((contextKey) => contextKey.includes(AFTER_PREFIX)); | ||
await t.expect(beforeHookTrackers.length).eql(0, `${beforeHookTrackers} hooks have run`); | ||
await t.expect(afterHookTrackers.length).eql(0, `${afterHookTrackers} hooks have run`); | ||
}); | ||
|
||
Then(/the linked Before hook should have run/, async (t: TestController) => { | ||
// Cause the test to fail if no before hook ran or if the wrong one did | ||
const selectedHook = Object.keys(t.ctx).filter((contextKey) => contextKey.includes(BEFORE_PREFIX)); | ||
const testTags = getTagsFromMeta(t); | ||
await t | ||
.expect(selectedHook.length) | ||
.eql( | ||
1, | ||
`${selectedHook.length || 'No'} "${BEFORE_PREFIX}" key ${ | ||
selectedHook.length > 1 ? 'were' : 'was' | ||
} found in the context` | ||
) | ||
.expect(testTags) | ||
.contains(selectedHook[0], `${selectedHook[0]} is not associated to the current scenario`); | ||
}); | ||
|
||
Then(/the linked After hook should run/, async (t: TestController) => { | ||
// NOTE: cannot be tested in the current test because the hooks runs after it | ||
// Instead the AfterAll hook checks if the After hook ran the correct amount of times | ||
}); | ||
|
||
Then(/the linked Before hooks should have run/, async (t: TestController) => { | ||
// Cause the test to fail if no before hook ran | ||
const selectedHooks = Object.keys(t.ctx).filter((contextKey) => contextKey.includes(BEFORE_PREFIX)); | ||
await t.expect(selectedHooks.length).gt(0, `No key containing ${BEFORE_PREFIX} was found in the context`); | ||
|
||
const testTags: string[] = getTagsFromMeta(t); | ||
|
||
await Promise.all( | ||
testTags.map(async (tag) => t.expect(selectedHooks).contains(tag, `The hook associated to ${tag} hasn't run`)) | ||
); | ||
}); | ||
|
||
/* HELPERS */ | ||
const getTagsFromMeta = (t: TestController): string[] => { | ||
return t.testRun.test.meta.tags | ||
.split(',') | ||
.filter((tag) => tag) | ||
.map((tag) => tag.replace('@', '')); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { After, Before } from '@cucumber/cucumber'; | ||
import { afterTags, beforeTags, bothTags } from './hooks-const'; | ||
|
||
const allBeforeTags = [...beforeTags, ...bothTags]; | ||
const allAfterTags = [...afterTags, ...bothTags]; | ||
|
||
allBeforeTags.forEach((fullTag) => | ||
Before(`@${fullTag}`, async (t: TestController) => { | ||
t.ctx[fullTag] = true; | ||
}) | ||
); | ||
|
||
allAfterTags.forEach((fulltag) => { | ||
After(`@${fulltag}`, async (t: TestController) => { | ||
t.testRun.fixtureCtx.afterHooksCounter += 1; | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
Feature: Hooks feature 1 | ||
I want to be able to use hooks to link specific behaviors to my scenarios | ||
|
||
Scenario: Global hooks run before or after each feature | ||
Given a BeforeAll hook is defined | ||
Then the BeforeAll hook should have run before any scenario | ||
|
||
Scenario: No tag | ||
Given the scenario has no tags | ||
Then no tagged hook should run | ||
|
||
@beforetag1 | ||
Scenario: One tag - before | ||
Given the scenario has a tag linked to a Before hook | ||
Then the linked Before hook should have run | ||
|
||
@beforetag1 @beforetag2 | ||
Scenario: Several tags | ||
Given the scenario has several tags linked to Before hooks | ||
Then the linked Before hooks should have run |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
Feature: Hooks feature 2 | ||
I want to be able to use hooks to link specific behaviors to my scenarios | ||
|
||
Scenario: Global hooks run before or after each feature | ||
Given a AfterAll hook is defined | ||
Then the AfterAll hook should have run after the previous feature | ||
|
||
@aftertag | ||
Scenario: One tag - after | ||
Given the scenario has a tag linked to an After hook | ||
Then the linked After hook should run | ||
|
||
@beforeaftertag | ||
Scenario: One tag - before and after | ||
Given the scenario has a tag linked to a Before and an After hook | ||
Then the linked Before hook should have run | ||
And the linked After hook should run | ||
|
||
@beforetag1 @aftertag | ||
Scenario: Several tags - before and after | ||
Given the scenario has one tag linked to a Before hook | ||
And the scenario has one tag linked to an After hook | ||
Then the linked Before hook should have run | ||
And the linked After hook should run |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters