Skip to content

Commit e0d09ca

Browse files
authored
feat: step definitions related to the feature file (#121)
1 parent e0b4892 commit e0d09ca

File tree

9 files changed

+141
-32
lines changed

9 files changed

+141
-32
lines changed

README.md

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
[![CircleCI](https://circleci.com/gh/TheBrainFamily/cypress-cucumber-preprocessor.svg?style=shield)](https://circleci.com/gh/TheBrainFamily/cypress-cucumber-preprocessor)
33
# Run cucumber/gherkin-syntaxed specs with cypress.io
44

5-
Follow the Setup steps, or if you prefer to hack on a working example, take a look at [https://github.com/TheBrainFamily/cypress-cucumber-example](https://github.com/TheBrainFamily/cypress-cucumber-example
5+
Follow the Setup steps, or if you prefer to hack on a working example, take a look [https://github.com/TheBrainFamily/cypress-cucumber-example](https://github.com/TheBrainFamily/cypress-cucumber-example
66
)
77

88
## Setup
@@ -25,15 +25,6 @@ module.exports = (on, config) => {
2525
}
2626
```
2727

28-
Step definition files are by default in: cypress/support/step_definitions. If you want to put them somewhere please use [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) format. For example, add to your package.json :
29-
30-
```javascript
31-
"cypress-cucumber-preprocessor": {
32-
"step_definitions": "cypress/support/step_definitions/"
33-
}
34-
```
35-
36-
## Usage
3728
### Feature files
3829

3930
Put your feature files in cypress/integration/
@@ -51,7 +42,73 @@ Feature: The Facebook
5142

5243
### Step definitions
5344

54-
Put your step definitions in cypress/support/step_definitions
45+
#### Cypress Cucumber Preprocessor Style (recommended!)
46+
47+
##### Step definitions unique for the feature
48+
49+
###### Configuration
50+
First please use [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) to create a configuration for the plugin, for example put this section:
51+
52+
```json
53+
"cypress-cucumber-preprocessor": {
54+
"nonGlobalStepDefinitions": true
55+
}
56+
```
57+
inside your package.json. (this will become the default option in a future version)
58+
59+
###### Step definitions creation
60+
Then put your step definitions in cypress/integration with the folder name matching the .feature filename.
61+
Easier to show than to explain, so, assuming the feature file is in cypress/integration/Google.feature , as proposed above, the preprocessor will read all the files inside cypress/integration/Google/, so:
62+
63+
cypress/integration/Google/google.js (or any other .js file in the same path)
64+
```javascript
65+
import { Given } from "cypress-cucumber-preprocessor/steps";
66+
67+
const url = 'https://google.com'
68+
Given('I open Google page', () => {
69+
cy.visit(url)
70+
})
71+
```
72+
73+
This is a good place to put before/beforeEach/after/afterEach hooks related to THAT PARTICULAR FEATURE. This is incredibly hard to get right with pure cucumber.
74+
75+
##### Reusable step definitions
76+
77+
We also have a way to create reusable step definitions. Put them in cypress/integration/common/
78+
79+
Example:
80+
cypress/integration/common/i_see_string_in_the_title.js
81+
```javascript
82+
import { Then } from "cypress-cucumber-preprocessor/steps";
83+
84+
Then(`I see {string} in the title`, (title) => {
85+
cy.title().should('include', title)
86+
})
87+
```
88+
89+
This is a good place to put global before/beforeEach/after/afterEach hooks.
90+
91+
##### Why a new pattern?
92+
The problem with the legacy structure is that everything is global. This is problematic for multiple reasons.
93+
- It makes it harder to create .feature files that read nicely - you have to make sure you are not stepping on toes of already existing step definitions. You should be able to write your tests without worrying about reusability, complex regexp matches, or anything like that. Just write a story. Explain what you want to see without getting into the details. Reuse in the .js files, not in something you should consider an always up-to-date, human-readable documentation.
94+
- The startup times get much worse - because we have to analyze all the different step definitions so we can match the .feature files to the test.
95+
- Hooks are problematic. If you put before() in a step definition file, you might think that it will run only for the .feature file related to that step definition. You try the feature you work on, everything seems fine and you push the code. Here comes a surprise - it will run for ALL .feature files in your whole project. Very unintuitive. And good luck debugging problems caused by that! This problem was not unique to this plugin, bo to the way cucumberjs operates.
96+
Let's look how this differs with the proposed structure. Assuming you want to have a hook before ./Google.feature file, just create a ./Google/before.js and put the hook there. This should take care of long requested feature - (https://github.com/TheBrainFamily/cypress-cucumber-preprocessor/issues/25)[#25]
97+
98+
If you have a few tests the "oldschool" style is completely fine. But for a large enterprise-grade application, with hundreds or sometimes thousands of .feature files, the fact that everything is global becomes a maintainability nightmare.
99+
100+
#### Oldschool/Legacy Cucumber style (please let us know if you decide to use it!)
101+
102+
##### Step Definition location configuration
103+
Step definition files are by default in: cypress/support/step_definitions. If you want to put them somewhere please use [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) format. For example, add to your package.json :
104+
105+
```javascript
106+
"cypress-cucumber-preprocessor": {
107+
"step_definitions": "cypress/support/step_definitions/"
108+
}
109+
```
110+
111+
Follow your configuration or use the defaults and put your step definitions in cypress/support/step_definitions
55112

56113
Examples:
57114
cypress/support/step_definitions/google.js
@@ -73,13 +130,16 @@ Then(`I see {string} in the title`, (title) => {
73130
})
74131
```
75132

133+
134+
#### Given/When/Then functions
135+
76136
Since Given/When/Then are on global scope please use
77137
```javascript
78138
/* global Given, When, Then */
79139
```
80140
to make IDE/linter happy
81141

82-
or import them directly
142+
or import them directly as shown in the above examples
83143

84144
### Running
85145

lib/getStepDefinitionPathsFrom.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const path = require("path");
2+
3+
module.exports = {
4+
getStepDefinitionPathsFrom: filePath =>
5+
filePath.replace(path.extname(filePath), "")
6+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const { getStepDefinitionPathsFrom } = require("./getStepDefinitionPathsFrom");
2+
3+
test("getStepDefinitionPathsFrom", () => {
4+
expect(
5+
getStepDefinitionPathsFrom("/home/lgandecki/someComplex_.feature")
6+
).equal("/home/lgandecki/someComplex_");
7+
});

lib/getStepDefinitionsPaths.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const glob = require("glob");
2+
const cosmiconfig = require("cosmiconfig");
3+
const stepDefinitionPath = require("./stepDefinitionPath.js");
4+
const { getStepDefinitionPathsFrom } = require("./getStepDefinitionPathsFrom");
5+
6+
const getStepDefinitionsPaths = filePath => {
7+
let paths = [];
8+
const explorer = cosmiconfig("cypress-cucumber-preprocessor", { sync: true });
9+
const loaded = explorer.load();
10+
if (loaded && loaded.config && loaded.config.nonGlobalStepDefinitions) {
11+
const nonGlobalPattern = `${getStepDefinitionPathsFrom(
12+
filePath
13+
)}/**/*.+(js|ts)`;
14+
const commonDefinitionsPattern = `${stepDefinitionPath()}/common/**/*.+(js|ts)`;
15+
paths = paths.concat(glob.sync(nonGlobalPattern));
16+
paths = paths.concat(glob.sync(commonDefinitionsPattern));
17+
} else {
18+
const pattern = `${stepDefinitionPath()}/**/*.+(js|ts)`;
19+
paths = paths.concat(glob.sync(pattern));
20+
}
21+
return paths;
22+
};
23+
24+
module.exports = { getStepDefinitionsPaths };

lib/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const transform = file => {
1717
function end() {
1818
if (file.match(".feature$")) {
1919
log("compiling feature ", file);
20-
this.queue(compile(data));
20+
this.queue(compile(data, file));
2121
} else {
2222
this.queue(data);
2323
}

lib/loader.js

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/* eslint-disable no-eval */
22
const log = require("debug")("cypress:cucumber");
3-
const glob = require("glob");
4-
const stepDefinitionPath = require("./stepDefinitionPath.js");
3+
const { getStepDefinitionsPaths } = require("./getStepDefinitionsPaths");
54

65
// This is the template for the file that we will send back to cypress instead of the text of a
76
// feature file
@@ -21,15 +20,9 @@ const createCucumber = (spec, toRequire) =>
2120
createTestsFromFeature(gherkinAst);
2221
`;
2322

24-
const createPattern = () => `${stepDefinitionPath()}/**/*.+(js|ts)`;
25-
26-
const pattern = createPattern();
27-
28-
const getStepDefinitionsPaths = () => [].concat(glob.sync(pattern));
29-
30-
module.exports = spec => {
23+
module.exports = function(spec, filePath = this.resourcePath) {
3124
log("compiling", spec);
32-
const stepDefinitionsToRequire = getStepDefinitionsPaths().map(
25+
const stepDefinitionsToRequire = getStepDefinitionsPaths(filePath).map(
3326
sdPath => `require('${sdPath}')`
3427
);
3528
return createCucumber(spec, stepDefinitionsToRequire);

lib/stepDefinitionPath.js

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,32 @@ const cosmiconfig = require("cosmiconfig");
55
module.exports = () => {
66
const appRoot = process.cwd();
77

8+
const cypressOptions = JSON.parse(
9+
fs.readFileSync(`${appRoot}/cypress.json`, "utf-8")
10+
);
11+
812
const explorer = cosmiconfig("cypress-cucumber-preprocessor", { sync: true });
913
const loaded = explorer.load();
10-
if (loaded && loaded.config && loaded.config.step_definitions) {
11-
return path.resolve(appRoot, loaded.config.step_definitions);
14+
if (loaded && loaded.config) {
15+
const { config } = loaded;
16+
if (config.nonGlobalStepDefinitions && config.step_definitions) {
17+
throw new Error(
18+
"Error! You can't have both step_definitions folder and nonGlobalStepDefinitions setup in cypress-cucumber-preprocessor configuration"
19+
);
20+
}
21+
if (config.nonGlobalStepDefinitions) {
22+
return path.resolve(
23+
appRoot,
24+
cypressOptions.integrationFolder || "cypress/integration"
25+
);
26+
}
27+
if (config.step_definitions) {
28+
return path.resolve(appRoot, config.step_definitions);
29+
}
1230
}
1331

1432
// XXX Deprecated, left here for backward compability
15-
const cypressOptions = JSON.parse(
16-
fs.readFileSync(`${appRoot}/cypress.json`, "utf-8")
17-
);
33+
1834
if (cypressOptions && cypressOptions.fileServerFolder) {
1935
return `${cypressOptions.fileServerFolder}/support/step_definitions`;
2036
}

loader.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const loader = require("./lib/loader");
2+
3+
module.exports = loader;

wallaby.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
module.exports = function (wallaby) {
44
return {
55
files: [
6-
{pattern: './*.+(js|jsx|json|snap|css|less|sass|scss|jpg|jpeg|gif|png|svg)', load: false},
7-
{pattern: '!./*.test.js?(x)', load: false},
8-
'./*.snap',
6+
{pattern: './lib/*.+(js|jsx|json|snap|css|less|sass|scss|jpg|jpeg|gif|png|svg)', load: false},
7+
{pattern: '!./lib/*.test.js?(x)', load: false},
8+
'./lib/*.snap',
99
{pattern: '.eslintrc', load: false},
1010
'./cypress/support/step_definitions/*.js',
1111
'./cypress/integration/*.feature',
1212
],
1313

1414
tests: [
15-
{pattern: './*.test.js?(x)', load: true}
15+
{pattern: './lib/*.test.js?(x)', load: true}
1616
],
1717

1818

0 commit comments

Comments
 (0)