Skip to content

Commit

Permalink
Added cucumber-js-style Before and After hooks.
Browse files Browse the repository at this point in the history
  • Loading branch information
visusnet committed Jul 29, 2019
1 parent 158ef02 commit 1ee34c5
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 15 deletions.
35 changes: 35 additions & 0 deletions cypress/integration/BeforeAndAfterSteps.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Feature: Using Before and After

# After is implicitly tested by resetting the counter.

Scenario: With Before only
Given I do something
And Something else
Then Before was called once

@withTaggedBefore
Scenario: With tagged Before only
Given I do something
And Something else
Then Before with tag was called once

Scenario: With After only
Given I do something
And Something else

@withTaggedAfter
Scenario: With tagged After only
Given I do something
And Something else

Scenario: With Before and After
Given I do something
And Something else
Then Before was called once

@withTaggedBefore
@withTaggedAfter
Scenario: With tagged After only
Given I do something
And Something else
Then Before with tag was called once
37 changes: 37 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,37 @@
/* global given, when, then, before, after */
/* eslint-env mocha */

let beforeCounter = 0;
let beforeWithTagCounter = 0;

before(() => {
beforeCounter += 1;
});

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

after(() => {
beforeCounter = 0;
});

after({ tags: "@withTaggedAfter" }, () => {
beforeWithTagCounter = 0;
});

given("I do something", () => {});

when("Something else", () => {});

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

then("Before with tag was called once", () => {
expect(beforeWithTagCounter).to.equal(1);
});

then("Before was not called", () => {
expect(beforeCounter).to.equal(1);
});
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
3 changes: 2 additions & 1 deletion lib/cukejson/cucumberDataCollector.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ const { generateCucumberJson } = require("./generateCucumberJson");
window.cucumberJson = { generate: true };

window.Cypress = {
log: jest.fn()
log: jest.fn(),
Promise: { each: (iterator, iteree) => iterator.map(iteree) }
};

const assertCucumberJson = (json, expectedResults) => {
Expand Down
67 changes: 67 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,25 @@ class StepDefinitionRegistry {
}
}

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

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

this.resolve = scenarioTags =>
this.definitions.filter(({ tags }) =>
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 +123,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 +197,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
11 changes: 9 additions & 2 deletions lib/resolveStepDefinition.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ describe("Tags with env TAGS set", () => {
window.Cypress = {
env: () => "@test-tag and not @ignore-tag",
on: jest.fn(),
off: jest.fn()
off: jest.fn(),
Promise: { each: (iterator, iteree) => iterator.map(iteree) }
};
require("../cypress/support/step_definitions/tags_implementation_with_env_set");
resolveFeatureFromFile(
Expand All @@ -64,7 +65,8 @@ describe("Smart tagging", () => {
window.Cypress = {
env: () => "",
on: jest.fn(),
off: jest.fn()
off: jest.fn(),
Promise: { each: (iterator, iteree) => iterator.map(iteree) }
};
require("../cypress/support/step_definitions/smart_tagging");
resolveFeatureFromFile("./cypress/integration/SmartTagging.feature");
Expand All @@ -79,3 +81,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");
});
22 changes: 13 additions & 9 deletions lib/setup.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
/* eslint-disable global-require */
/* global jest */
const fs = require("fs");

window.Cypress = {
env: jest.fn(),
on: jest.fn(),
off: jest.fn(),
Promise: { each: (iterator, iteree) => iterator.map(iteree) }
};

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

const {
Expand All @@ -10,7 +18,9 @@ const {
then,
given,
and,
but
but,
before,
after
} = require("./resolveStepDefinition");

const mockedPromise = jest.fn().mockImplementation(() => Promise.resolve(true));
Expand All @@ -34,14 +44,8 @@ window.cy = {
end: mockedPromise
};

window.before = jest.fn();
window.after = jest.fn();

window.Cypress = {
env: jest.fn(),
on: jest.fn(),
off: jest.fn()
};
window.before = before;
window.after = after;

const resolveFeatureFromFile = featureFile => {
const spec = fs.readFileSync(featureFile);
Expand Down
3 changes: 2 additions & 1 deletion lib/tagsInheritance.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ describe("Tags inheritance", () => {
window.Cypress = {
env: () => "@inherited-tag and @own-tag",
on: jest.fn(),
off: jest.fn()
off: jest.fn(),
Promise: { each: (iterator, iteree) => iterator.map(iteree) }
};

require("../cypress/support/step_definitions/tags_implementation_with_env_set");
Expand Down
4 changes: 4 additions & 0 deletions steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const {
then,
and,
but,
before,
after,
defineParameterType,
defineStep
} = require("./lib/resolveStepDefinition");
Expand All @@ -22,6 +24,8 @@ module.exports = {
Then: then,
And: and,
But: but,
Before: before,
After: after,
defineParameterType,
defineStep
};

0 comments on commit 1ee34c5

Please sign in to comment.