Skip to content

Commit e0b4892

Browse files
authored
Code reorganization, Readme Update (#120)
* chore: first step in the code reorganization * feat: new way of importing step functions, updated readme and code reorganization * chore: tweaked readme and updated package-lock * Apply suggestions from code review Co-Authored-By: lgandecki <[email protected]>
1 parent 8bf932d commit e0b4892

16 files changed

+3340
-3144
lines changed

README.md

Lines changed: 71 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,72 @@
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-
Please take a look at an example here:
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
6+
)
67

7-
https://github.com/TheBrainFamily/cypress-cucumber-example
8+
## Setup
89

9-
10-
Important steps:
11-
12-
## Installation
10+
### Installation
1311
Install this plugin:
1412

1513
```shell
1614
npm install --save-dev cypress-cucumber-preprocessor
1715
```
1816

19-
## Step definitions
17+
### Cypress Configuration
18+
Add it to your plugins:
19+
20+
cypress/plugins/index.js
21+
```javascript
22+
const cucumber = require('cypress-cucumber-preprocessor').default
23+
module.exports = (on, config) => {
24+
on('file:preprocessor', cucumber())
25+
}
26+
```
27+
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
37+
### Feature files
38+
39+
Put your feature files in cypress/integration/
40+
41+
Example: cypress/integration/Google.feature
42+
```gherkin
43+
Feature: The Facebook
44+
45+
I want to open a social network page
46+
47+
Scenario: Opening a social network page
48+
Given I open Google page
49+
Then I see "google" in the title
50+
```
51+
52+
### Step definitions
2053

2154
Put your step definitions in cypress/support/step_definitions
2255

2356
Examples:
2457
cypress/support/step_definitions/google.js
2558
```javascript
26-
/* global Given */
27-
// you can have external state, and also require things!
28-
const url = 'https://google.com'
59+
import { Given } from "cypress-cucumber-preprocessor/steps";
2960

61+
const url = 'https://google.com'
3062
Given('I open Google page', () => {
3163
cy.visit(url)
3264
})
3365
```
3466

3567
cypress/support/step_definitions/shared.js
3668
```javascript
37-
/* global Then */
69+
import { Then } from "cypress-cucumber-preprocessor/steps";
70+
3871
Then(`I see {string} in the title`, (title) => {
3972
cy.title().should('include', title)
4073
})
@@ -46,7 +79,17 @@ Since Given/When/Then are on global scope please use
4679
```
4780
to make IDE/linter happy
4881

49-
We had a pattern to import those explicitly, but for some reason it was messing up the watch mode on Linux :-( (#10)
82+
or import them directly
83+
84+
### Running
85+
86+
Run your cypress the way you would usually do, for example:
87+
88+
```bash
89+
./node_modules/.bin/cypress open
90+
```
91+
92+
click on a .feature file on the list of specs, and see the magic happening!
5093

5194
### Background section
5295

@@ -55,20 +98,20 @@ Adding a background section to your feature will enable you to run steps before
5598
```javascript
5699
let counter = 0;
57100

58-
given("counter has been reset", () => {
101+
Given("counter has been reset", () => {
59102
counter = 0;
60103
});
61104

62-
when("counter is incremented", () => {
105+
When("counter is incremented", () => {
63106
counter += 1;
64107
});
65108

66-
then("counter equals {int}", value => {
109+
Then("counter equals {int}", value => {
67110
expect(counter).to.equal(value);
68111
});
69112
```
70113

71-
```
114+
```gherkin
72115
Feature: Background Section
73116
74117
Background:
@@ -90,73 +133,41 @@ You can share context between step definitions using `cy.as()` alias.
90133

91134
Example:
92135
```javascript
93-
given('I go to the add new item page', () => {
136+
Given('I go to the add new item page', () => {
94137
cy.visit('/addItem');
95138
});
96139

97-
when('I add a new item', () => {
140+
When('I add a new item', () => {
98141
cy.get('input[name="addNewItem"]').as('addNewItemInput');
99142
cy.get('@addNewItemInput').type('My item');
100143
cy.get('button[name="submitItem"]').click();
101144
})
102145

103-
then('I see new item added', () => {
146+
Then('I see new item added', () => {
104147
cy.get('td:contains("My item")');
105148
});
106149

107-
then('I can add another item', () => {
150+
Then('I can add another item', () => {
108151
expect(cy.get('@addNewItemInput').should('be.empty');
109152
});
110153

111154
```
112155
113156
For more information please visit: https://docs.cypress.io/api/commands/as.html
114157
115-
## Spec/Feature files
116-
Your feature file in cypress/integration:
117-
118-
Example: cypress/integration/Facebook.feature
119-
```gherkin
120-
Feature: The Facebook
121-
122-
I want to open a social network page
123158
124-
Scenario: Opening a social network page
125-
Given I open Facebook page
126-
Then I see "Facebook" in the title
127-
```
128-
129-
## Tagging tests
159+
### Tagging tests
130160
You can use tags to select which test should run using [cucumber's tag expressions](https://github.com/cucumber/cucumber/tree/master/tag-expressions).
131161
Keep in mind we are using newer syntax, eg. `'not @foo and (@bar or @zap)'`.
132162
In order to initialize tests using tags you will have to run cypress and pass TAGS environment variable.
133163
134164
Example:
135165
```cypress run -e TAGS='not @foo and (@bar or @zap)'```
136166
167+
## Custom Parameter Type Resolves
137168
138-
## Configuration
139-
Add it to your plugins:
140-
141-
cypress/plugins/index.js
142-
```javascript
143-
const cucumber = require('cypress-cucumber-preprocessor').default
144-
module.exports = (on, config) => {
145-
on('file:preprocessor', cucumber())
146-
}
147-
```
148-
149-
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 :
150-
151-
```javascript
152-
"cypress-cucumber-preprocessor": {
153-
"step_definitions": "cypress/support/step_definitions/"
154-
}
155-
```
156-
157-
## Running
158-
159-
Run your cypress the way you would normally do :) click on a .feature file on the list of specs, and see the magic happening!
169+
Thanks to @Oltodo we can now use Custom Parameter Type Resolves.
170+
Here is an [example](cypress/support/step_definitions/customParameterTypes.js) with related [.feature file](cypress/integration/CustomParameterTypes.feature)
160171
161172
## Cucumber Expressions
162173
@@ -165,30 +176,25 @@ We use https://docs.cucumber.io/cucumber/cucumber-expressions/ to parse your .fe
165176
## Development
166177
167178
Install all dependencies:
168-
```javascript
179+
```bash
169180
npm install
170181
```
171182
172183
Link the package:
173-
```javascript
184+
```bash
174185
npm link
175186
npm link cypress-cucumber-preprocessor
176187
```
177188
178189
Run tests:
179-
```javascript
190+
```bash
180191
npm test
181192
```
182193
183194
## Disclaimer
184195
185196
Please let me know if you find any issues or have suggestions for improvements.
186197
187-
## Custom Parameter Type Resolves
188-
189-
Thanks to @Oltodo we can know use Custom Parameter Type Resolves.
190-
Here is an [example](cypress/support/step_definitions/customParameterTypes.js) with related [.feature file](cypress/integration/CustomParameterTypes.feature)
191-
192198
## WebStorm Support
193199
194200
If you want WebStorm to resolve your steps, use the capitalized Given/When/Then function names (instead of the initial given/when/then).
@@ -241,7 +247,7 @@ Then in your .ts files you need to make sure you either require/import the funct
241247
```typescript
242248
declare const Given, When, Then;
243249
// OR
244-
const {given, when, then} = require('cypress-cucumber-preprocessor/resolveStepDefinition')
250+
import { Given, Then, When } from "cypress-cucumber-preprocessor/steps";
245251
```
246252
247253
## Using Webpack
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
/* global when then */
2-
when("I run one successful step", () => {
1+
// eslint-disable-next-line import/no-extraneous-dependencies
2+
import { Then, When } from "cypress-cucumber-preprocessor/steps";
3+
4+
When("I run one successful step", () => {
35
expect(true).to.equal(true);
46
});
57

6-
when("I run another that's unsuccessful", () => {
8+
When("I run another that's unsuccessful", () => {
79
expect(true).to.equal(false);
810
});
911

10-
then("I don't run the last step", () => {
12+
Then("I don't run the last step", () => {
1113
throw new Error("this test should be skipped");
1214
});

cypress/support/step_definitions/tags_implementation.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
const {
44
shouldProceedCurrentStep
5-
} = require("cypress-cucumber-preprocessor/tagsHelper"); // eslint-disable-line
5+
} = require("cypress-cucumber-preprocessor/lib/tagsHelper"); // eslint-disable-line
66

77
let parsedTags;
88

File renamed without changes.

loader.js renamed to lib/loader.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ const stepDefinitionPath = require("./stepDefinitionPath.js");
77
// feature file
88
const createCucumber = (spec, toRequire) =>
99
`
10-
const {resolveAndRunStepDefinition, defineParameterType, given, when, then} = require('cypress-cucumber-preprocessor/resolveStepDefinition');
10+
const {resolveAndRunStepDefinition, defineParameterType, given, when, then} = require('cypress-cucumber-preprocessor/lib/resolveStepDefinition');
1111
const Given = window.Given = window.given = given;
1212
const When = window.When = window.when = when;
1313
const Then = window.Then = window.then = then;
1414
window.defineParameterType = defineParameterType;
15-
const { createTestsFromFeature } = require('cypress-cucumber-preprocessor/createTestsFromFeature');
15+
const { createTestsFromFeature } = require('cypress-cucumber-preprocessor/lib/createTestsFromFeature');
1616
${eval(toRequire).join("\n")}
1717
const {Parser, Compiler} = require('gherkin');
1818
const spec = \`${spec}\`

lib/resolveStepDefinition.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
const DataTable = require("cucumber/lib/models/data_table").default;
2+
const {
3+
defineParameterType
4+
} = require("cucumber/lib/support_code_library_builder/define_helpers");
5+
const {
6+
CucumberExpression,
7+
RegularExpression,
8+
ParameterTypeRegistry
9+
} = require("cucumber-expressions");
10+
11+
class StepDefinitionRegistry {
12+
constructor() {
13+
this.definitions = {};
14+
this.runtime = {};
15+
this.options = {
16+
parameterTypeRegistry: new ParameterTypeRegistry()
17+
};
18+
19+
this.definitions = [];
20+
this.runtime = (matcher, implementation) => {
21+
let expression;
22+
if (matcher instanceof RegExp) {
23+
expression = new RegularExpression(
24+
matcher,
25+
this.options.parameterTypeRegistry
26+
);
27+
} else {
28+
expression = new CucumberExpression(
29+
matcher,
30+
this.options.parameterTypeRegistry
31+
);
32+
}
33+
this.definitions.push({ implementation, expression });
34+
};
35+
36+
this.resolve = (type, text) =>
37+
this.definitions.filter(({ expression }) => expression.match(text))[0];
38+
}
39+
}
40+
41+
const stepDefinitionRegistry = new StepDefinitionRegistry();
42+
43+
function resolveStepDefinition(step) {
44+
const stepDefinition = stepDefinitionRegistry.resolve(
45+
step.keyword.toLowerCase().trim(),
46+
step.text
47+
);
48+
return stepDefinition || {};
49+
}
50+
51+
module.exports = {
52+
// eslint-disable-next-line func-names
53+
resolveAndRunStepDefinition(step) {
54+
const { expression, implementation } = resolveStepDefinition(step);
55+
if (expression && implementation) {
56+
let argument;
57+
if (step.argument) {
58+
if (step.argument.type === "DataTable") {
59+
argument = new DataTable(step.argument);
60+
} else if (step.argument.type === "DocString") {
61+
argument = step.argument.content;
62+
}
63+
}
64+
return implementation.call(
65+
this,
66+
...expression.match(step.text).map(match => match.getValue()),
67+
argument
68+
);
69+
}
70+
throw new Error(`Step implementation missing for: ${step.text}`);
71+
},
72+
given: (expression, implementation) => {
73+
stepDefinitionRegistry.runtime(expression, implementation);
74+
},
75+
when: (expression, implementation) => {
76+
stepDefinitionRegistry.runtime(expression, implementation);
77+
},
78+
then: (expression, implementation) => {
79+
stepDefinitionRegistry.runtime(expression, implementation);
80+
},
81+
defineParameterType: defineParameterType(stepDefinitionRegistry)
82+
};

0 commit comments

Comments
 (0)