Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions cypress/integration/BeforeAndAfterSteps.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Feature: Using Before and After

Scenario: With Untagged Before
Then Untagged Before was called once
And Tagged Before was not called

# After is tested in following scenario by verifying that the After ran at the end of the previous one
Scenario: With Untagged After part 1
Given I executed empty step

Scenario: With Untagged After part 2
Then Flag should be set by untagged After

# After is tested in following scenario by verifying that the After ran at the end of the previous one.
@withTaggedAfter
Scenario: With tagged After part 1
Given I executed empty step

Scenario: With tagged After part 2
Then Flag should be set by tagged After

@withTaggedBefore
Scenario: With tagged Before only
Then Tagged Before was called once
And Untagged Before was called once

@withTaggedBefore
@withAnotherTaggedBefore
Scenario: With multiple tags
Given I executed empty step
Then Tagged Before was called twice
And Untagged Before was called once
73 changes: 73 additions & 0 deletions cypress/support/step_definitions/before_and_after_steps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* eslint-env mocha */
/* eslint-disable import/no-extraneous-dependencies */
const {
Before,
After,
Given,
Then
} = require("cypress-cucumber-preprocessor/steps");

let beforeCounter = 0;
let beforeWithTagCounter = 0;
let flagSetByUntaggedAfter = false;
let flagSetByTaggedAfter = false;

Before(() => {
beforeCounter += 1;
beforeWithTagCounter = 0;
});

Before({ tags: "@withTaggedBefore" }, () => {
beforeWithTagCounter += 1;
});

Before({ tags: "@withAnotherTaggedBefore" }, () => {
beforeWithTagCounter += 1;
});

Before({ tags: "@willNeverRun" }, () => {
throw new Error("XXX: before hook unexpectedly called.");
});

After({ tags: "@willNeverRun" }, () => {
throw new Error("XXX: after hook unexpectedly called.");
});

After(() => {
beforeCounter = 0;
flagSetByUntaggedAfter = true;
});

After({ tags: "@withTaggedAfter" }, () => {
flagSetByTaggedAfter = true;
});

Given("I executed empty step", () => {});

Then("Untagged Before was called once", () => {
expect(beforeCounter).to.equal(1);
});

Then("Untagged Before was not called", () => {
expect(beforeCounter).to.equal(0);
});

Then("Tagged Before was called once", () => {
expect(beforeWithTagCounter).to.equal(1);
});

Then("Tagged Before was called twice", () => {
expect(beforeWithTagCounter).to.equal(2);
});

Then("Tagged Before was not called", () => {
expect(beforeWithTagCounter).to.equal(0);
});

Then("Flag should be set by untagged After", () => {
expect(flagSetByUntaggedAfter).to.equal(true);
});

Then("Flag should be set by tagged After", () => {
expect(flagSetByTaggedAfter).to.equal(true);
});
10 changes: 8 additions & 2 deletions lib/createTestFromScenario.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/* eslint-disable prefer-template */
const statuses = require("cucumber").Status;
const { resolveAndRunStepDefinition } = require("./resolveStepDefinition");
const {
resolveAndRunStepDefinition,
resolveAndRunBeforeHooks,
resolveAndRunAfterHooks
} = require("./resolveStepDefinition");
const { generateCucumberJson } = require("./cukejson/generateCucumberJson");

const replaceParameterTags = (rowData, text) =>
Expand Down Expand Up @@ -33,9 +37,11 @@ const runTest = (scenario, stepsToRun, rowData) => {
const state = window.testState;
return cy
.then(() => state.onStartScenario(scenario, indexedSteps))
.then(() => resolveAndRunBeforeHooks.call(this, scenario.tags))
.then(() =>
indexedSteps.forEach(step => stepTest.call(this, state, step, rowData))
)
.then(() => resolveAndRunAfterHooks.call(this, scenario.tags))
.then(() => state.onFinishScenario(scenario));
});
};
Expand Down Expand Up @@ -131,7 +137,7 @@ const createTestFromScenarios = (
// eslint-disable-next-line func-names, prefer-arrow-callback
after(function() {
cy.then(() => testState.onFinishTest()).then(() => {
if (window.cucumberJson.generate) {
if (window.cucumberJson && window.cucumberJson.generate) {
const json = generateCucumberJson(testState);
writeCucumberJsonFile(json);
}
Expand Down
4 changes: 0 additions & 4 deletions lib/cukejson/cucumberDataCollector.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ const { generateCucumberJson } = require("./generateCucumberJson");

window.cucumberJson = { generate: true };

window.Cypress = {
log: jest.fn()
};

const assertCucumberJson = (json, expectedResults) => {
expect(json).to.have.length(1);
expect(json[0].keyword).to.eql("Feature");
Expand Down
4 changes: 3 additions & 1 deletion lib/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ const { getStepDefinitionsPaths } = require("./getStepDefinitionsPaths");
// feature file
const createCucumber = (filePath, cucumberJson, spec, toRequire) =>
`
const {resolveAndRunStepDefinition, defineParameterType, given, when, then, and, but, defineStep} = require('cypress-cucumber-preprocessor/lib/resolveStepDefinition');
const {resolveAndRunStepDefinition, defineParameterType, given, when, then, and, but, before, after, defineStep} = require('cypress-cucumber-preprocessor/lib/resolveStepDefinition');
const Given = window.Given = window.given = given;
const When = window.When = window.when = when;
const Then = window.Then = window.then = then;
const And = window.And = window.and = and;
const But = window.But = window.but = but;
const Before = window.Before = window.before = before;
const After = window.After = window.after = after;
window.defineParameterType = defineParameterType;
window.defineStep = defineStep;
const { createTestsFromFeature } = require('cypress-cucumber-preprocessor/lib/createTestsFromFeature');
Expand Down
70 changes: 70 additions & 0 deletions lib/resolveStepDefinition.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const {
RegularExpression,
ParameterTypeRegistry
} = require("cucumber-expressions");
const { shouldProceedCurrentStep } = require("./tagsHelper");

class StepDefinitionRegistry {
constructor() {
Expand Down Expand Up @@ -38,7 +39,28 @@ class StepDefinitionRegistry {
}
}

class HookRegistry {
constructor() {
this.definitions = [];
this.runtime = {};

this.runtime = (tags, implementation) => {
this.definitions.push({ tags, implementation });
};

this.resolve = scenarioTags =>
this.definitions.filter(
({ tags }) =>
!tags ||
tags.length === 0 ||
shouldProceedCurrentStep(scenarioTags, tags)
);
}
}

const stepDefinitionRegistry = new StepDefinitionRegistry();
const beforeHookRegistry = new HookRegistry();
const afterHookRegistry = new HookRegistry();

function resolveStepDefinition(step) {
const stepDefinition = stepDefinitionRegistry.resolve(
Expand Down Expand Up @@ -104,7 +126,47 @@ function resolveStepArgument(argument, exampleRowData, replaceParameterTags) {
return argument;
}

function resolveAndRunHooks(hookRegistry, scenarioTags) {
return window.Cypress.Promise.each(
hookRegistry.resolve(scenarioTags),
({ implementation }) => implementation.call(this)
);
}

function parseHookArgs(args) {
if (args.length === 2) {
if (typeof args[0] !== "object" || typeof args[0].tags !== "string") {
throw new Error(
"Hook definitions with two arguments should have an object containing tags (string) as the first argument."
);
}
if (typeof args[1] !== "function") {
throw new Error(
"Hook definitions with two arguments must have a function as the second argument."
);
}
return {
tags: args[0].tags,
implementation: args[1]
};
}
if (typeof args[0] !== "function") {
throw new Error(
"Hook definitions with one argument must have a function as the first argument."
);
}
return {
implementation: args[0]
};
}

module.exports = {
resolveAndRunBeforeHooks(scenarioTags) {
return resolveAndRunHooks(beforeHookRegistry, scenarioTags);
},
resolveAndRunAfterHooks(scenarioTags) {
return resolveAndRunHooks(afterHookRegistry, scenarioTags);
},
// eslint-disable-next-line func-names
resolveAndRunStepDefinition(step, replaceParameterTags, exampleRowData) {
const { expression, implementation } = resolveStepDefinition(step);
Expand Down Expand Up @@ -138,6 +200,14 @@ module.exports = {
but: (expression, implementation) => {
stepDefinitionRegistry.runtime(expression, implementation);
},
before: (...args) => {
const { tags, implementation } = parseHookArgs(args);
beforeHookRegistry.runtime(tags, implementation);
},
after: (...args) => {
const { tags, implementation } = parseHookArgs(args);
afterHookRegistry.runtime(tags, implementation);
},
defineStep: (expression, implementation) => {
stepDefinitionRegistry.runtime(expression, implementation);
},
Expand Down
19 changes: 12 additions & 7 deletions lib/resolveStepDefinition.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* eslint-disable global-require */

const { resolveFeatureFromFile } = require("./setup.js");
const {
resolveFeatureFromFile
} = require("./testHelpers/resolveFeatureFromFile");

describe("Scenario Outline", () => {
require("../cypress/support/step_definitions/scenario_outline_integer");
Expand Down Expand Up @@ -46,9 +48,8 @@ describe("Tags implementation", () => {

describe("Tags with env TAGS set", () => {
window.Cypress = {
env: () => "@test-tag and not @ignore-tag",
on: jest.fn(),
off: jest.fn()
...window.Cypress,
env: () => "@test-tag and not @ignore-tag"
};
require("../cypress/support/step_definitions/tags_implementation_with_env_set");
resolveFeatureFromFile(
Expand All @@ -62,9 +63,8 @@ describe("Tags with env TAGS set", () => {

describe("Smart tagging", () => {
window.Cypress = {
env: () => "",
on: jest.fn(),
off: jest.fn()
...window.Cypress,
env: () => ""
};
require("../cypress/support/step_definitions/smart_tagging");
resolveFeatureFromFile("./cypress/integration/SmartTagging.feature");
Expand All @@ -79,3 +79,8 @@ describe("defineStep", () => {
require("../cypress/support/step_definitions/usingDefineSteps");
resolveFeatureFromFile("./cypress/integration/DefineStep.feature");
});

describe("Before and After", () => {
require("../cypress/support/step_definitions/before_and_after_steps");
resolveFeatureFromFile("./cypress/integration/BeforeAndAfterSteps.feature");
});
2 changes: 0 additions & 2 deletions lib/setupTestFramework.js

This file was deleted.

9 changes: 5 additions & 4 deletions lib/tagsInheritance.test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
/* eslint-disable global-require */
/* global jest */
const { resolveFeatureFromFile } = require("./setup");
const {
resolveFeatureFromFile
} = require("./testHelpers/resolveFeatureFromFile");

describe("Tags inheritance", () => {
window.Cypress = {
env: () => "@inherited-tag and @own-tag",
on: jest.fn(),
off: jest.fn()
...window.Cypress,
env: () => "@inherited-tag and @own-tag"
};

require("../cypress/support/step_definitions/tags_implementation_with_env_set");
Expand Down
14 changes: 14 additions & 0 deletions lib/testHelpers/resolveFeatureFromFile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* eslint-disable global-require */
/* global jest */
const fs = require("fs");

const { createTestsFromFeature } = require("../createTestsFromFeature");

const resolveFeatureFromFile = featureFile => {
const spec = fs.readFileSync(featureFile);
createTestsFromFeature(featureFile, spec);
};

module.exports = {
resolveFeatureFromFile
};
Loading