Skip to content

Commit

Permalink
feat(globalhooks): add the ability to access a copy of the features m…
Browse files Browse the repository at this point in the history
…etadata (#68)

A global hook (BeforeAll and AfterAll) will run before or after every
single feature.
However they could only be used to add information to the context (which
is empty when the feature starts).
No information about the feature could be used in the hooks. The global
hooks are now capable of passing a copy of the metadata (name of the
feature and tags, at the moment) to the hook function, allowing for more
targeted behaviors.

Co-authored-by: Arthur Warnier <arthur.warnier@gaming1.com>
  • Loading branch information
Arthy000 and Arthur Warnier committed Feb 15, 2022
1 parent 17f9716 commit e87d6cc
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 48 deletions.
15 changes: 11 additions & 4 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ declare module 'gherkin-testcafe' {
}

interface GherkinTestCafeFactory extends TestCafeFactory {
(hostname?: string, port1?: number, port2?: number, sslOptions?: TlsOptions, developmentMode?: boolean): Promise<
GherkinTestCafe
>;
(
hostname?: string,
port1?: number,
port2?: number,
sslOptions?: TlsOptions,
developmentMode?: boolean
): Promise<GherkinTestCafe>;
}
}

Expand Down Expand Up @@ -57,7 +61,10 @@ declare module '@cucumber/cucumber' {
}

export type HookFunction = (testController: typeof t) => Promise<void>;
export type GlobalHookFunction = (fixtureContext: { [key: string]: any }) => Promise<void>;
export type GlobalHookFunction = (
fixtureContext: { [key: string]: any },
fixtureMeta: Record<string, string>
) => Promise<void>;

export function After(code: HookFunction): void;
export function After(options: string, code: HookFunction): void;
Expand Down
95 changes: 51 additions & 44 deletions src/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,23 @@ const chalk = require('chalk');
const AND_SEPARATOR = ' and ';

const getTags = () => {
const tagsIndex = process.argv.findIndex(val => val === '--tags');
const tagsIndex = process.argv.findIndex((val) => val === '--tags');
if (tagsIndex !== -1) {
return process.argv[tagsIndex + 1]
.split(',')
.map(tag => (tag.includes(AND_SEPARATOR) ? tag.split(AND_SEPARATOR) : tag));
.map((tag) => (tag.includes(AND_SEPARATOR) ? tag.split(AND_SEPARATOR) : tag));
}

return [];
};

const getParameterTypeRegistry = () => {
const parameterTypeRegistryIndex = process.argv.findIndex(val => val === '--param-type-registry-file');
const parameterTypeRegistryIndex = process.argv.findIndex((val) => val === '--param-type-registry-file');

if (parameterTypeRegistryIndex !== -1) {
const parameterTypeRegistryFilePath = process.argv[parameterTypeRegistryIndex + 1];
const absFilePath = require.resolve(parameterTypeRegistryFilePath, {
paths: [process.cwd()]
paths: [process.cwd()],
});
return require(absFilePath);
}
Expand All @@ -43,8 +43,8 @@ const getParameterTypeRegistry = () => {

module.exports = class GherkinTestcafeCompiler {
constructor(sources, compilerOptions) {
this.stepFiles = sources.filter(source => source.endsWith('.js') || source.endsWith('.ts'));
this.specFiles = sources.filter(source => source.endsWith('.feature'));
this.stepFiles = sources.filter((source) => source.endsWith('.js') || source.endsWith('.ts'));
this.specFiles = sources.filter((source) => source.endsWith('.feature'));

this.stepDefinitions = [];

Expand All @@ -57,7 +57,7 @@ module.exports = class GherkinTestcafeCompiler {
this.cucumberExpressionParamRegistry = getParameterTypeRegistry();
this.externalCompilers = [
new TestcafeESNextCompiler(),
new TestcafeTypescriptCompiler(compilerOptions[CustomizableCompilers.typescript])
new TestcafeTypescriptCompiler(compilerOptions[CustomizableCompilers.typescript]),
];
}

Expand All @@ -84,7 +84,7 @@ module.exports = class GherkinTestcafeCompiler {
'Failed to parse feature file ' + specFile,
...gherkinResult
.filter(({ attachment }) => Boolean(attachment))
.map(({ attachment }) => attachment.source.uri + attachment.data)
.map(({ attachment }) => attachment.source.uri + attachment.data),
].join('\n')
);
}
Expand All @@ -103,22 +103,22 @@ module.exports = class GherkinTestcafeCompiler {

async _dryRun() {
const featureStepsArray = await Promise.all(
this.specFiles.map(async specFile => {
this.specFiles.map(async (specFile) => {
const { gherkinResult, gherkinDocument } = await this._loadSpecs(specFile);

const featureTitle = `Feature: ${gherkinDocument.feature.name}`;
const featureSteps = [];
gherkinResult.forEach(({ pickle: scenario }) => {
if (scenario) {
scenario.steps.forEach(step => {
if (featureSteps.every(stepText => stepText !== step.text)) {
scenario.steps.forEach((step) => {
if (featureSteps.every((stepText) => stepText !== step.text)) {
featureSteps.push(step.text);
}
});
}
});

const missingFeatureSteps = featureSteps.filter(step => !this._findStepDefinition(step));
const missingFeatureSteps = featureSteps.filter((step) => !this._findStepDefinition(step));

return { featureTitle, featureSteps, missingFeatureSteps };
})
Expand All @@ -142,36 +142,38 @@ module.exports = class GherkinTestcafeCompiler {
async getTests() {
await this._loadStepDefinitions();

const dryRun = process.argv.findIndex(val => val === '--dry-run') !== -1;
const dryRun = process.argv.findIndex((val) => val === '--dry-run') !== -1;

if (dryRun) {
await this._dryRun();
process.exit(0);
}

let tests = await Promise.all(
this.specFiles.map(async specFile => {
this.specFiles.map(async (specFile) => {
const { gherkinResult, gherkinDocument, testFile, fixture } = await this._loadSpecs(specFile);

const meta = {
name: gherkinDocument.feature.name,
tags: `${
gherkinDocument.feature.tags.length > 0
? gherkinDocument.feature.tags.map((tag) => tag.name).reduce((acc, cur) => `${acc},${cur}`)
: ''
}`,
};

fixture(`Feature: ${gherkinDocument.feature.name}`)
.before(ctx => this._runFeatureHooks(ctx, this.beforeAllHooks))
.after(ctx => this._runFeatureHooks(ctx, this.afterAllHooks))
.meta(
'tags',
`${
gherkinDocument.feature.tags.length > 0
? gherkinDocument.feature.tags.map(tag => tag.name).reduce((acc, cur) => `${acc},${cur}`)
: ''
}`
);
.before((ctx) => this._runFeatureHooks(ctx, meta, this.beforeAllHooks))
.after((ctx) => this._runFeatureHooks(ctx, meta, this.afterAllHooks))
.meta(meta);

gherkinResult.forEach(({ pickle: scenario }) => {
if (!scenario || !this._shouldRunScenario(scenario)) {
return;
}

const test = new Test(testFile);
test(`Scenario: ${scenario.name}`, async t => {
test(`Scenario: ${scenario.name}`, async (t) => {
let error;

try {
Expand All @@ -187,11 +189,11 @@ module.exports = class GherkinTestcafeCompiler {
}
})
.page('about:blank')
.before(t => this._runHooks(t, this._findHook(scenario, this.beforeHooks)))
.after(t => this._runHooks(t, this._findHook(scenario, this.afterHooks)))
.before((t) => this._runHooks(t, this._findHook(scenario, this.beforeHooks)))
.after((t) => this._runHooks(t, this._findHook(scenario, this.afterHooks)))
.meta(
'tags',
scenario.tags.length > 0 ? scenario.tags.map(tag => tag.name).reduce((acc, cur) => `${acc},${cur}`) : ''
scenario.tags.length > 0 ? scenario.tags.map((tag) => tag.name).reduce((acc, cur) => `${acc},${cur}`) : ''
);
});

Expand All @@ -202,7 +204,7 @@ module.exports = class GherkinTestcafeCompiler {
tests = tests.reduce((agg, cur) => agg.concat(cur));

if (this.filter) {
tests = tests.filter(test => this.filter(test.name, test.fixture.name, test.fixture.path));
tests = tests.filter((test) => this.filter(test.name, test.fixture.name, test.fixture.path));
}

if (!tests.length) {
Expand All @@ -215,8 +217,8 @@ module.exports = class GherkinTestcafeCompiler {
async _loadStepDefinitions() {
supportCodeLibraryBuilder.reset(process.cwd(), IdGenerator.uuid());

const compilerResult = this.externalCompilers.map(async externalCompiler => {
const testFiles = this.stepFiles.filter(filename => {
const compilerResult = this.externalCompilers.map(async (externalCompiler) => {
const testFiles = this.stepFiles.filter((filename) => {
let supportedExtensions = externalCompiler.getSupportedExtension();

if (!Array.isArray(supportedExtensions)) {
Expand All @@ -233,7 +235,7 @@ module.exports = class GherkinTestcafeCompiler {
});

const compiledCode = await externalCompiler.precompile(
testFiles.map(filename => {
testFiles.map((filename) => {
const code = readFileSync(filename, 'utf-8');

return { code, filename };
Expand Down Expand Up @@ -276,7 +278,7 @@ module.exports = class GherkinTestcafeCompiler {
}

_findHook(scenario, hooks) {
return hooks.filter(hook => !hook.options.tags || scenario.tags.find(tag => tag.name === hook.options.tags));
return hooks.filter((hook) => !hook.options.tags || scenario.tags.find((tag) => tag.name === hook.options.tags));
}

async _runHooks(testController, hooks) {
Expand All @@ -285,9 +287,9 @@ module.exports = class GherkinTestcafeCompiler {
}
}

async _runFeatureHooks(fixtureCtx, hooks) {
async _runFeatureHooks(fixtureCtx, fixtureMeta, hooks) {
for (const hook of hooks) {
await hook.code(fixtureCtx);
await hook.code(fixtureCtx, fixtureMeta);
}
}

Expand Down Expand Up @@ -319,15 +321,20 @@ module.exports = class GherkinTestcafeCompiler {

const matchResult = cucumberExpression.match(step.text);
return matchResult
? [true, matchResult.map(r => r.getValue()), this._getCucumberDataTable(step), this._getCucumberDocString(step)]
? [
true,
matchResult.map((r) => r.getValue()),
this._getCucumberDataTable(step),
this._getCucumberDocString(step),
]
: [false, [], this._getCucumberDataTable(step), this._getCucumberDocString(step)];
} else if (stepDefinition.pattern instanceof RegExp) {
const match = stepDefinition.pattern.exec(step.text);
return [
Boolean(match),
match ? match.slice(1) : [],
this._getCucumberDataTable(step),
this._getCucumberDocString(step)
this._getCucumberDocString(step),
];
}

Expand All @@ -337,21 +344,21 @@ module.exports = class GherkinTestcafeCompiler {
}

_getIncludingTags(tags) {
return tags.filter(tag => (Array.isArray(tag) ? true : !tag.startsWith('~')));
return tags.filter((tag) => (Array.isArray(tag) ? true : !tag.startsWith('~')));
}

_getExcludingTags(tags) {
return tags
.filter(tag => (Array.isArray(tag) ? false : tag.startsWith('~')))
.map(tag => (!Array.isArray(tag) && tag.startsWith('~') ? tag.slice(1) : tag));
.filter((tag) => (Array.isArray(tag) ? false : tag.startsWith('~')))
.map((tag) => (!Array.isArray(tag) && tag.startsWith('~') ? tag.slice(1) : tag));
}

_scenarioHasAnyOfTheTags(scenario, tags) {
const scenarioTagsList = scenario.tags.map(tag => tag.name);
const scenarioTagsList = scenario.tags.map((tag) => tag.name);

return (
!tags.length ||
tags.some(tag => {
tags.some((tag) => {
return Array.isArray(tag) ? this._scenarioHasAllOfTheTags(scenario, tag) : scenarioTagsList.includes(tag);
})
);
Expand All @@ -362,9 +369,9 @@ module.exports = class GherkinTestcafeCompiler {
}

_scenarioHasAllOfTheTags(scenario, tags) {
const scenarioTagsList = scenario.tags.map(tag => tag.name);
const scenarioTagsList = scenario.tags.map((tag) => tag.name);

return tags.every(tag =>
return tags.every((tag) =>
tag.startsWith('~') ? !scenarioTagsList.includes(tag.slice(1)) : scenarioTagsList.includes(tag)
);
}
Expand Down

0 comments on commit e87d6cc

Please sign in to comment.