Skip to content

Commit a65cf6d

Browse files
committed
Added cucumber-js-style Before and After hooks.
1 parent 158ef02 commit a65cf6d

File tree

9 files changed

+177
-12
lines changed

9 files changed

+177
-12
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
Feature: Using Before and After
2+
3+
# After is implicitly tested by resetting the counter.
4+
5+
Scenario: With Before only
6+
Given I do something
7+
And Something else
8+
Then Before was called once
9+
10+
@withTaggedBefore
11+
Scenario: With tagged Before only
12+
Given I do something
13+
And Something else
14+
Then Before with tag was called once
15+
16+
Scenario: With After only
17+
Given I do something
18+
And Something else
19+
20+
@withTaggedAfter
21+
Scenario: With tagged After only
22+
Given I do something
23+
And Something else
24+
25+
Scenario: With Before and After
26+
Given I do something
27+
And Something else
28+
Then Before was called once
29+
30+
@withTaggedBefore
31+
@withTaggedAfter
32+
Scenario: With tagged After only
33+
Given I do something
34+
And Something else
35+
Then Before with tag was called once
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/* global given, when, then, testBefore, testAfter */
2+
/* eslint-env mocha */
3+
4+
let beforeCounter = 0;
5+
let beforeWithTagCounter = 0;
6+
7+
testBefore(() => {
8+
beforeCounter += 1;
9+
});
10+
11+
testBefore({ tags: "@withTaggedBefore" }, () => {
12+
beforeWithTagCounter += 1;
13+
});
14+
15+
testAfter(() => {
16+
beforeCounter = 0;
17+
});
18+
19+
testAfter({ tags: "@withTaggedAfter" }, () => {
20+
beforeWithTagCounter = 0;
21+
});
22+
23+
given("I do something", () => {});
24+
25+
when("Something else", () => {});
26+
27+
then("Before was called once", () => {
28+
expect(beforeCounter).to.equal(1);
29+
});
30+
31+
then("Before with tag was called once", () => {
32+
expect(beforeWithTagCounter).to.equal(1);
33+
});
34+
35+
then("Before was not called", () => {
36+
expect(beforeCounter).to.equal(1);
37+
});

lib/createTestFromScenario.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
/* eslint-disable prefer-template */
22
const statuses = require("cucumber").Status;
3-
const { resolveAndRunStepDefinition } = require("./resolveStepDefinition");
3+
const {
4+
resolveAndRunStepDefinition,
5+
resolveAndRunBeforeHooks,
6+
resolveAndRunAfterHooks
7+
} = require("./resolveStepDefinition");
48
const { generateCucumberJson } = require("./cukejson/generateCucumberJson");
59

610
const replaceParameterTags = (rowData, text) =>
@@ -33,9 +37,11 @@ const runTest = (scenario, stepsToRun, rowData) => {
3337
const state = window.testState;
3438
return cy
3539
.then(() => state.onStartScenario(scenario, indexedSteps))
40+
.then(() => resolveAndRunBeforeHooks.call(this, scenario.tags))
3641
.then(() =>
3742
indexedSteps.forEach(step => stepTest.call(this, state, step, rowData))
3843
)
44+
.then(() => resolveAndRunAfterHooks.call(this, scenario.tags))
3945
.then(() => state.onFinishScenario(scenario));
4046
});
4147
};
@@ -131,7 +137,7 @@ const createTestFromScenarios = (
131137
// eslint-disable-next-line func-names, prefer-arrow-callback
132138
after(function() {
133139
cy.then(() => testState.onFinishTest()).then(() => {
134-
if (window.cucumberJson.generate) {
140+
if (window.cucumberJson && window.cucumberJson.generate) {
135141
const json = generateCucumberJson(testState);
136142
writeCucumberJsonFile(json);
137143
}

lib/cukejson/cucumberDataCollector.test.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ const { generateCucumberJson } = require("./generateCucumberJson");
66
window.cucumberJson = { generate: true };
77

88
window.Cypress = {
9-
log: jest.fn()
9+
log: jest.fn(),
10+
Promise: { each: (iterator, iteree) => iterator.map(iteree) }
1011
};
1112

1213
const assertCucumberJson = (json, expectedResults) => {

lib/resolveStepDefinition.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const {
77
RegularExpression,
88
ParameterTypeRegistry
99
} = require("cucumber-expressions");
10+
const { shouldProceedCurrentStep } = require("./tagsHelper");
1011

1112
class StepDefinitionRegistry {
1213
constructor() {
@@ -38,7 +39,25 @@ class StepDefinitionRegistry {
3839
}
3940
}
4041

42+
class HookRegistry {
43+
constructor() {
44+
this.definitions = [];
45+
this.runtime = {};
46+
47+
this.runtime = (tags, implementation) => {
48+
this.definitions.push({ implementation, tags });
49+
};
50+
51+
this.resolve = scenarioTags =>
52+
this.definitions.filter(({ tags }) =>
53+
shouldProceedCurrentStep(scenarioTags, tags)
54+
);
55+
}
56+
}
57+
4158
const stepDefinitionRegistry = new StepDefinitionRegistry();
59+
const beforeHookRegistry = new HookRegistry();
60+
const afterHookRegistry = new HookRegistry();
4261

4362
function resolveStepDefinition(step) {
4463
const stepDefinition = stepDefinitionRegistry.resolve(
@@ -104,7 +123,47 @@ function resolveStepArgument(argument, exampleRowData, replaceParameterTags) {
104123
return argument;
105124
}
106125

126+
function resolveAndRunHooks(hookRegistry, scenarioTags) {
127+
return window.Cypress.Promise.each(
128+
hookRegistry.resolve(scenarioTags),
129+
({ implementation }) => implementation.call(this)
130+
);
131+
}
132+
133+
function parseHookArgs(args) {
134+
if (args.length === 2) {
135+
if (typeof args[0] !== "object" || typeof args[0].tags !== "string") {
136+
throw new Error(
137+
"Hook definitions with two arguments should have an object containing tags (string) as the first argument."
138+
);
139+
}
140+
if (typeof args[1] !== "function") {
141+
throw new Error(
142+
"Hook definitions with two arguments must have a function as the second argument."
143+
);
144+
}
145+
return {
146+
tags: args[0].tags,
147+
implementation: args[1]
148+
};
149+
}
150+
if (typeof args[0] !== "function") {
151+
throw new Error(
152+
"Hook definitions with one argument must have a function as the first argument."
153+
);
154+
}
155+
return {
156+
implementation: args[0]
157+
};
158+
}
159+
107160
module.exports = {
161+
resolveAndRunBeforeHooks(scenarioTags) {
162+
return resolveAndRunHooks(beforeHookRegistry, scenarioTags);
163+
},
164+
resolveAndRunAfterHooks(scenarioTags) {
165+
return resolveAndRunHooks(afterHookRegistry, scenarioTags);
166+
},
108167
// eslint-disable-next-line func-names
109168
resolveAndRunStepDefinition(step, replaceParameterTags, exampleRowData) {
110169
const { expression, implementation } = resolveStepDefinition(step);
@@ -138,6 +197,14 @@ module.exports = {
138197
but: (expression, implementation) => {
139198
stepDefinitionRegistry.runtime(expression, implementation);
140199
},
200+
before: (...args) => {
201+
const { tags, implementation } = parseHookArgs(args);
202+
beforeHookRegistry.runtime(tags, implementation);
203+
},
204+
after: (...args) => {
205+
const { tags, implementation } = parseHookArgs(args);
206+
afterHookRegistry.runtime(tags, implementation);
207+
},
141208
defineStep: (expression, implementation) => {
142209
stepDefinitionRegistry.runtime(expression, implementation);
143210
},

lib/resolveStepDefinition.test.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ describe("Tags with env TAGS set", () => {
4848
window.Cypress = {
4949
env: () => "@test-tag and not @ignore-tag",
5050
on: jest.fn(),
51-
off: jest.fn()
51+
off: jest.fn(),
52+
Promise: { each: (iterator, iteree) => iterator.map(iteree) }
5253
};
5354
require("../cypress/support/step_definitions/tags_implementation_with_env_set");
5455
resolveFeatureFromFile(
@@ -64,7 +65,8 @@ describe("Smart tagging", () => {
6465
window.Cypress = {
6566
env: () => "",
6667
on: jest.fn(),
67-
off: jest.fn()
68+
off: jest.fn(),
69+
Promise: { each: (iterator, iteree) => iterator.map(iteree) }
6870
};
6971
require("../cypress/support/step_definitions/smart_tagging");
7072
resolveFeatureFromFile("./cypress/integration/SmartTagging.feature");
@@ -79,3 +81,8 @@ describe("defineStep", () => {
7981
require("../cypress/support/step_definitions/usingDefineSteps");
8082
resolveFeatureFromFile("./cypress/integration/DefineStep.feature");
8183
});
84+
85+
describe("Before and After", () => {
86+
require("../cypress/support/step_definitions/before_and_after_steps");
87+
resolveFeatureFromFile("./cypress/integration/BeforeAndAfterSteps.feature");
88+
});

lib/setup.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
/* eslint-disable global-require */
22
/* global jest */
33
const fs = require("fs");
4+
5+
window.Cypress = {
6+
env: jest.fn(),
7+
on: jest.fn(),
8+
off: jest.fn(),
9+
Promise: { each: (iterator, iteree) => iterator.map(iteree) }
10+
};
11+
412
const { createTestsFromFeature } = require("./createTestsFromFeature");
513

614
const {
@@ -10,7 +18,9 @@ const {
1018
then,
1119
given,
1220
and,
13-
but
21+
but,
22+
before,
23+
after
1424
} = require("./resolveStepDefinition");
1525

1626
const mockedPromise = jest.fn().mockImplementation(() => Promise.resolve(true));
@@ -37,11 +47,8 @@ window.cy = {
3747
window.before = jest.fn();
3848
window.after = jest.fn();
3949

40-
window.Cypress = {
41-
env: jest.fn(),
42-
on: jest.fn(),
43-
off: jest.fn()
44-
};
50+
window.testBefore = before;
51+
window.testAfter = after;
4552

4653
const resolveFeatureFromFile = featureFile => {
4754
const spec = fs.readFileSync(featureFile);

lib/tagsInheritance.test.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ describe("Tags inheritance", () => {
66
window.Cypress = {
77
env: () => "@inherited-tag and @own-tag",
88
on: jest.fn(),
9-
off: jest.fn()
9+
off: jest.fn(),
10+
Promise: { each: (iterator, iteree) => iterator.map(iteree) }
1011
};
1112

1213
require("../cypress/support/step_definitions/tags_implementation_with_env_set");

steps.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const {
77
then,
88
and,
99
but,
10+
before,
11+
after,
1012
defineParameterType,
1113
defineStep
1214
} = require("./lib/resolveStepDefinition");
@@ -22,6 +24,8 @@ module.exports = {
2224
Then: then,
2325
And: and,
2426
But: but,
27+
Before: before,
28+
After: after,
2529
defineParameterType,
2630
defineStep
2731
};

0 commit comments

Comments
 (0)