@qavajs/cypress-runner-adapter 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -14,6 +14,16 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
14
14
 
15
15
  :microscope: - experimental
16
16
 
17
+ ## [1.3.0]
18
+ - :rocket: added tags support
19
+ - :rocket: added alternative 'it mode' to translate gherkin tests to mocha `it` instead of `describe`
20
+ ```bash
21
+ MODE=it npx cypress open
22
+ ```
23
+
24
+ ## [1.2.1]
25
+ - :pencil: updated to cypress 15.3
26
+
17
27
  ## [1.2.0]
18
28
  - :pencil: updated to cypress 15
19
29
 
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # @qavajs/cypress-runner-adapter
2
- Adapter to run cucumberjs tests via cypress test runner
2
+ Adapter to run Gherkin tests via cypress test runner
3
3
 
4
4
  ## Installation
5
5
 
@@ -39,3 +39,18 @@ When('open {string} url', function (url) {
39
39
  cy.visit(url);
40
40
  });
41
41
  ```
42
+
43
+ ## Tags
44
+ Test can be filtered using Cucumber tag expressions provided via environment variable `TAGS`
45
+ ```
46
+ TAGS='@first and @second' npx cypress run
47
+ ```
48
+
49
+ ## Translation Mode
50
+ Gherkin tests can be translated in different modes
51
+ - `describe` - default mode. Scenario will be translated as `describe`, each step will be translated as `it`
52
+ - `it` - Scenario will be translated as `it`
53
+
54
+ ```bash
55
+ MODE=it npx cypress open
56
+ ```
package/adapter/index.js CHANGED
@@ -1,149 +1,32 @@
1
- const {
2
- ensureFileSync,
3
- writeFileSync,
4
- readFileSync,
5
- } = require('fs-extra');
1
+ const { ensureFileSync, writeFileSync, readFileSync } = require('fs-extra');
6
2
  const { randomUUID } = require('node:crypto');
7
3
  const { AstBuilder, compile, GherkinClassicTokenMatcher, Parser } = require('@cucumber/gherkin');
8
4
  const webpackPreprocessor = require('@cypress/webpack-preprocessor')();
5
+ const tagExpressionParser = require('@cucumber/tag-expressions').default;
6
+ const makeMochaTestDescribe = require('./make_mocha_tests_describe');
7
+ const makeMochaTestIt = require('./make_mocha_tests_it');
9
8
 
10
9
  const uuidFn = () => randomUUID();
11
- const builder = new AstBuilder(uuidFn)
10
+ const builder = new AstBuilder(uuidFn);
12
11
  const matcher = new GherkinClassicTokenMatcher();
13
- const parser = new Parser(builder, matcher)
12
+ const parser = new Parser(builder, matcher);
14
13
 
15
- function adapter(testCases) {
16
- return `
17
- const tests = ${JSON.stringify(testCases)};
14
+ const makeMochaTest = process.env.MODE === 'it' ? makeMochaTestIt : makeMochaTestDescribe;
18
15
 
19
- function keyword(step) {
20
- switch (step.type) {
21
- case 'Context': return 'Given';
22
- case 'Action': return 'When';
23
- case 'Outcome': return 'Then';
24
- default: return 'Step';
25
- }
26
- }
27
-
28
- function executeStepByText(text, argument) {
29
- const steps = supportCodeLibrary.stepDefinitions
30
- .filter(stepDefinition => stepDefinition.matchesStepName(text));
31
- if (steps.length === 0) throw new Error(\`Step '\${text}' is not defined\`);
32
- if (steps.length > 1) throw new Error(\`Step '\${text}' matches multiple step definitions\`);
33
- const [step] = steps;
34
- const { parameters } = step.getInvocationParameters({
35
- step: { text, argument },
36
- world: this
37
- });
38
- step.code.apply(this, parameters);
39
- }
40
-
41
- function executeStep(pickle, world) {
42
- if (pickle.argument && pickle.argument.dataTable) {
43
- Cypress.log({ displayName: 'DataTable', message: pickle.argument.dataTable })
44
- }
45
- if (pickle.argument && pickle.argument.docString) {
46
- Cypress.log({ displayName: 'Multiline', message: pickle.argument.docString.content })
47
- }
48
- executeStepByText.call(world, pickle.text, pickle.argument);
49
- }
50
-
51
- if (supportCodeLibrary.beforeTestRunHookDefinitions.length > 0) {
52
- describe('Before All', function () {
53
- for (const beforeRun of supportCodeLibrary.beforeTestRunHookDefinitions) {
54
- it('Before All', function () {
55
- beforeRun.code.apply();
56
- });
57
- }
58
- })
59
- }
60
- for (const test of tests) {
61
- describe('Scenario: ' + test.name, { testIsolation: false }, function () {
62
- const world = new supportCodeLibrary.World();
63
- world.executeStep = executeStepByText;
64
- let skip = false;
65
- let result = 'passed';
66
- afterEach(function() {
67
- if (this.step) {
68
- for (const afterStep of supportCodeLibrary.afterTestStepHookDefinitions) {
69
- if (afterStep.appliesToTestCase(this.step)) {
70
- afterStep.code.apply(world, [{
71
- pickle: test,
72
- pickleStep: this.step,
73
- gherkinDocument: tests,
74
- result: this.currentTest.state
75
- }]);
76
- }
77
- }
78
- }
79
- if (this.currentTest.state !== 'passed') {
80
- skip = true;
81
- }
82
- result = this.currentTest.state;
83
- });
84
- for (const beforeTest of supportCodeLibrary.beforeTestCaseHookDefinitions) {
85
- if (beforeTest.appliesToTestCase(test)) {
86
- it(beforeTest.name, function () {
87
- if (skip) return this.skip();
88
- beforeTest.code.apply(world, [{
89
- pickle: test,
90
- gherkinDocument: tests,
91
- willBeRetried: false
92
- }]);
93
- });
94
- }
95
- }
96
- for (const step of test.steps) {
97
- it(keyword(step) + ': ' + step.text, function () {
98
- this.step = step;
99
- if (skip) return this.skip();
100
- for (const beforeStep of supportCodeLibrary.beforeTestStepHookDefinitions) {
101
- if (beforeStep.appliesToTestCase(step)) {
102
- beforeStep.code.apply(world, [{
103
- pickle: test,
104
- pickleStep: step,
105
- gherkinDocument: tests
106
- }]);
107
- }
108
- }
109
- executeStep(step, world);
110
- })
111
- }
112
- for (const afterTest of supportCodeLibrary.afterTestCaseHookDefinitions) {
113
- if (afterTest.appliesToTestCase(test)) {
114
- it(afterTest.name, function () {
115
- afterTest.code.apply(world, [{
116
- pickle: test,
117
- result,
118
- gherkinDocument: tests,
119
- willBeRetried: false
120
- }]);
121
- });
122
- }
123
- }
124
- });
125
- }
126
- if (supportCodeLibrary.afterTestRunHookDefinitions.length > 0) {
127
- describe('After All', function () {
128
- for (const afterRun of supportCodeLibrary.afterTestRunHookDefinitions) {
129
- it('After All', function () {
130
- afterRun.code.apply();
131
- });
132
- }
133
- })
134
- }
135
- `;
16
+ function adapter(testCases) {
17
+ return `(${makeMochaTest.toString()})(${JSON.stringify(testCases)});`;
136
18
  }
137
19
 
138
20
  module.exports = async function cucumber(file) {
139
21
  const { filePath, outputPath, shouldWatch } = file;
140
22
  if (!filePath.endsWith('.feature')) {
141
- return webpackPreprocessor(file)
23
+ return webpackPreprocessor(file);
142
24
  }
143
25
  const gherkinDocument = parser.parse(readFileSync(filePath, 'utf-8'));
144
- const testCases = compile(gherkinDocument, filePath, uuidFn);
26
+ const tagExpression = tagExpressionParser(process.env.TAGS || '');
27
+ const testCases = compile(gherkinDocument, filePath, uuidFn)
28
+ .filter(test => tagExpression.evaluate(test.tags.map(tag => tag.name)));
145
29
  ensureFileSync(outputPath);
146
30
  writeFileSync(outputPath, adapter(testCases), 'utf-8');
147
-
148
- return outputPath
31
+ return outputPath;
149
32
  }
@@ -0,0 +1,119 @@
1
+ module.exports = function makeMochaTest(tests) {
2
+ function keyword(step) {
3
+ switch (step.type) {
4
+ case 'Context': return 'Given';
5
+ case 'Action': return 'When';
6
+ case 'Outcome': return 'Then';
7
+ default: return 'Step';
8
+ }
9
+ }
10
+
11
+ function executeStepByText(text, argument) {
12
+ const steps = supportCodeLibrary.stepDefinitions
13
+ .filter(stepDefinition => stepDefinition.matchesStepName(text));
14
+ if (steps.length === 0) throw new Error(`Step '${text}' is not defined`);
15
+ if (steps.length > 1) throw new Error(`Step '${text}' matches multiple step definitions`);
16
+ const [step] = steps;
17
+ const { parameters } = step.getInvocationParameters({
18
+ step: { text, argument },
19
+ world: this
20
+ });
21
+ step.code.apply(this, parameters);
22
+ }
23
+
24
+ function executeStep(pickle, world) {
25
+ if (pickle.argument && pickle.argument.dataTable) {
26
+ Cypress.log({ displayName: 'DataTable', message: pickle.argument.dataTable });
27
+ }
28
+ if (pickle.argument && pickle.argument.docString) {
29
+ Cypress.log({ displayName: 'Multiline', message: pickle.argument.docString.content });
30
+ }
31
+ executeStepByText.call(world, pickle.text, pickle.argument);
32
+ }
33
+
34
+ if (supportCodeLibrary.beforeTestRunHookDefinitions.length > 0) {
35
+ describe('Before All', function () {
36
+ for (const beforeRun of supportCodeLibrary.beforeTestRunHookDefinitions) {
37
+ it('Before All', function () {
38
+ beforeRun.code.apply();
39
+ });
40
+ }
41
+ })
42
+ }
43
+
44
+ for (const test of tests) {
45
+ describe('Scenario: ' + test.name, { testIsolation: false }, function () {
46
+ const world = new supportCodeLibrary.World();
47
+ world.executeStep = executeStepByText;
48
+ let skip = false;
49
+ let result = 'passed';
50
+ afterEach(function () {
51
+ if (this.step) {
52
+ for (const afterStep of supportCodeLibrary.afterTestStepHookDefinitions) {
53
+ if (afterStep.appliesToTestCase(this.step)) {
54
+ afterStep.code.apply(world, [{
55
+ pickle: test,
56
+ pickleStep: this.step,
57
+ gherkinDocument: tests,
58
+ result: this.currentTest.state
59
+ }]);
60
+ }
61
+ }
62
+ }
63
+ if (this.currentTest.state !== 'passed') {
64
+ skip = true;
65
+ }
66
+ result = this.currentTest.state;
67
+ });
68
+ for (const beforeTest of supportCodeLibrary.beforeTestCaseHookDefinitions) {
69
+ if (beforeTest.appliesToTestCase(test)) {
70
+ it(beforeTest.name, function () {
71
+ if (skip) return this.skip();
72
+ beforeTest.code.apply(world, [{
73
+ pickle: test,
74
+ gherkinDocument: tests,
75
+ willBeRetried: false
76
+ }]);
77
+ });
78
+ }
79
+ }
80
+ for (const step of test.steps) {
81
+ it(keyword(step) + ': ' + step.text, function () {
82
+ this.step = step;
83
+ if (skip) return this.skip();
84
+ for (const beforeStep of supportCodeLibrary.beforeTestStepHookDefinitions) {
85
+ if (beforeStep.appliesToTestCase(step)) {
86
+ beforeStep.code.apply(world, [{
87
+ pickle: test,
88
+ pickleStep: step,
89
+ gherkinDocument: tests
90
+ }]);
91
+ }
92
+ }
93
+ executeStep(step, world);
94
+ });
95
+ }
96
+ for (const afterTest of supportCodeLibrary.afterTestCaseHookDefinitions) {
97
+ if (afterTest.appliesToTestCase(test)) {
98
+ it(afterTest.name, function () {
99
+ afterTest.code.apply(world, [{
100
+ pickle: test,
101
+ result,
102
+ gherkinDocument: tests,
103
+ willBeRetried: false
104
+ }]);
105
+ });
106
+ }
107
+ }
108
+ });
109
+ }
110
+ if (supportCodeLibrary.afterTestRunHookDefinitions.length > 0) {
111
+ describe('After All', function () {
112
+ for (const afterRun of supportCodeLibrary.afterTestRunHookDefinitions) {
113
+ it('After All', function () {
114
+ afterRun.code.apply();
115
+ });
116
+ }
117
+ });
118
+ }
119
+ }
@@ -0,0 +1,135 @@
1
+ module.exports = function makeMochaTest(tests) {
2
+ function keyword(step) {
3
+ switch (step.type) {
4
+ case 'Context':
5
+ return 'Given';
6
+ case 'Action':
7
+ return 'When';
8
+ case 'Outcome':
9
+ return 'Then';
10
+ default:
11
+ return 'Step';
12
+ }
13
+ }
14
+
15
+ function executeStepByText(text, argument) {
16
+ const steps = supportCodeLibrary.stepDefinitions
17
+ .filter(stepDefinition => stepDefinition.matchesStepName(text));
18
+ if (steps.length === 0) throw new Error(`Step '${text}' is not defined`);
19
+ if (steps.length > 1) throw new Error(`Step '${text}' matches multiple step definitions`);
20
+ const [ step ] = steps;
21
+ const { parameters } = step.getInvocationParameters({
22
+ step: {text, argument},
23
+ world: this
24
+ });
25
+ step.code.apply(this, parameters);
26
+ }
27
+
28
+ function executeStep(pickle, world) {
29
+ cy.then(() => {
30
+ if (pickle.argument && pickle.argument.dataTable) {
31
+ Cypress.log({displayName: 'DataTable', message: pickle.argument.dataTable});
32
+ }
33
+ if (pickle.argument && pickle.argument.docString) {
34
+ Cypress.log({displayName: 'Multiline', message: pickle.argument.docString.content});
35
+ }
36
+ });
37
+ executeStepByText.call(world, pickle.text, pickle.argument);
38
+ }
39
+
40
+ supportCodeLibrary.World.prototype.executeStep = executeStepByText;
41
+
42
+ function runStep(name, callback) {
43
+ cy.then(() => {
44
+ Cypress.log({displayName: name, message: ''});
45
+ })
46
+ callback();
47
+ }
48
+
49
+ function findTest(tests, name) {
50
+ const testName = name.replace(/Scenario:\s+/, '');
51
+ return tests.find(test => test.name === testName);
52
+ }
53
+
54
+ if (supportCodeLibrary.beforeTestRunHookDefinitions.length > 0) {
55
+ before(function () {
56
+ for (const beforeRun of supportCodeLibrary.beforeTestRunHookDefinitions) {
57
+ beforeRun.code.apply();
58
+ }
59
+ })
60
+ }
61
+
62
+ beforeEach(function () {
63
+ const test = findTest(tests, this.currentTest.title);
64
+ const world = this.world = new supportCodeLibrary.World();
65
+ for (const beforeTest of supportCodeLibrary.beforeTestCaseHookDefinitions) {
66
+ if (beforeTest.appliesToTestCase(test)) {
67
+ runStep(beforeTest.name, function () {
68
+ beforeTest.code.apply(world, [{
69
+ pickle: test,
70
+ gherkinDocument: tests,
71
+ willBeRetried: false
72
+ }]);
73
+ });
74
+ }
75
+ }
76
+ });
77
+
78
+ afterEach( function () {
79
+ const test = findTest(tests, this.currentTest.title);
80
+ const world = this.world;
81
+ for (const afterTest of supportCodeLibrary.afterTestCaseHookDefinitions) {
82
+ if (afterTest.appliesToTestCase(test)) {
83
+ runStep(afterTest.name, function () {
84
+ afterTest.code.apply(world, [{
85
+ pickle: test,
86
+ result: null,
87
+ gherkinDocument: tests,
88
+ willBeRetried: false
89
+ }]);
90
+ });
91
+ }
92
+ }
93
+ });
94
+
95
+ for (const test of tests) {
96
+ it('Scenario: ' + test.name, function () {
97
+ const world = this.world;
98
+ for (const step of test.steps) {
99
+ const stepName = keyword(step) + ': ' + step.text;
100
+ runStep(stepName, function () {
101
+ this.step = step;
102
+ this.currentTest = { state: 'passed' };
103
+ for (const beforeStep of supportCodeLibrary.beforeTestStepHookDefinitions) {
104
+ if (beforeStep.appliesToTestCase(step)) {
105
+ beforeStep.code.apply(world, [{
106
+ pickle: test,
107
+ pickleStep: step,
108
+ gherkinDocument: tests
109
+ }]);
110
+ }
111
+ }
112
+ executeStep(step, world);
113
+ for (const afterStep of supportCodeLibrary.afterTestStepHookDefinitions) {
114
+ if (afterStep.appliesToTestCase(this.step)) {
115
+ afterStep.code.apply(world, [{
116
+ pickle: test,
117
+ pickleStep: this.step,
118
+ gherkinDocument: tests,
119
+ result: this.currentTest.state
120
+ }]);
121
+ }
122
+ }
123
+ });
124
+ }
125
+ });
126
+ }
127
+
128
+ if (supportCodeLibrary.afterTestRunHookDefinitions.length > 0) {
129
+ after(function () {
130
+ for (const afterRun of supportCodeLibrary.afterTestRunHookDefinitions) {
131
+ afterRun.code.apply();
132
+ }
133
+ });
134
+ }
135
+ }
package/package.json CHANGED
@@ -1,22 +1,44 @@
1
1
  {
2
2
  "name": "@qavajs/cypress-runner-adapter",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "debug": "cypress open --config-file test/cypress.config.js",
7
- "test": "cypress run --config-file test/cypress.config.js"
7
+ "test": "cypress run --config-file test/cypress.config.js",
8
+ "debug:it": "MODE=it cypress open --config-file test/cypress.config.js",
9
+ "test:it": "MODE=it cypress run --config-file test/cypress.config.js"
8
10
  },
9
- "author": "Alexandr Galichenko",
10
11
  "license": "MIT",
11
12
  "description": "feature file preprocessor",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/qavajs/cypress-runner-adapter.git"
16
+ },
17
+ "authors": [
18
+ "Alexandr Galichenko"
19
+ ],
20
+ "bugs": {
21
+ "url": "https://github.com/qavajs/cypress-runner-adapter/issues"
22
+ },
23
+ "homepage": "https://github.com/qavajs/cypress-runner-adaptert#readme",
12
24
  "dependencies": {
13
25
  "@cucumber/cucumber-expressions": "^18.0.1",
14
- "@cucumber/gherkin": "^34.0.0",
15
- "@cucumber/tag-expressions": "^6.2.0",
26
+ "@cucumber/gherkin": "^35.1.0",
27
+ "@cucumber/tag-expressions": "^7.0.0",
16
28
  "@cypress/webpack-preprocessor": "^7.0.1",
17
- "fs-extra": "^11.3.1"
29
+ "fs-extra": "^11.3.2"
18
30
  },
19
31
  "devDependencies": {
20
- "cypress": "^15.1.0"
21
- }
32
+ "cypress": "^15.3.0"
33
+ },
34
+ "keywords": [
35
+ "cypress",
36
+ "cypress-plugin",
37
+ "cypress-preprocessor",
38
+ "qavajs",
39
+ "cucumber",
40
+ "gherkin",
41
+ "test",
42
+ "test-automation"
43
+ ]
22
44
  }