@qavajs/cypress-runner-adapter 1.9.2 → 1.11.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/README.md CHANGED
@@ -30,6 +30,47 @@ module.exports = defineConfig({
30
30
  });
31
31
  ```
32
32
 
33
+ ### TypeScript
34
+
35
+ TypeScript support is built-in. Point `supportFile` at a `.ts` file and the adapter will compile it automatically:
36
+
37
+ ```javascript
38
+ const { defineConfig } = require('cypress');
39
+ const cucumber = require('@qavajs/cypress-runner-adapter/adapter');
40
+
41
+ module.exports = defineConfig({
42
+ e2e: {
43
+ specPattern: 'cypress/feature/**/*.feature',
44
+ supportFile: 'cypress/support/e2e.ts',
45
+ setupNodeEvents(on, config) {
46
+ on('file:preprocessor', cucumber);
47
+ },
48
+ },
49
+ });
50
+ ```
51
+
52
+ ```typescript
53
+ // cypress/support/e2e.ts
54
+ import { Given, When, Then, setWorldConstructor, World, WorldOptions } from '@qavajs/cypress-runner-adapter';
55
+
56
+ class CustomWorld extends World {
57
+ myValue: string | null;
58
+
59
+ constructor(options: WorldOptions) {
60
+ super(options);
61
+ this.myValue = null;
62
+ }
63
+ }
64
+
65
+ setWorldConstructor(CustomWorld);
66
+
67
+ Given('I navigate to {string}', function (this: CustomWorld, url: string) {
68
+ cy.visit(url);
69
+ });
70
+ ```
71
+
72
+ A `tsconfig.json` is not required but recommended. The adapter uses `transpileOnly: true` so type errors will not block the test run.
73
+
33
74
  ## Step Definitions
34
75
 
35
76
  Define steps in your support file (e.g. `cypress/support/e2e.js`):
package/adapter/index.js CHANGED
@@ -2,7 +2,26 @@ const { mkdirSync, writeFileSync, readFileSync } = require('node:fs');
2
2
  const { dirname } = require('node:path');
3
3
  const { randomUUID } = require('node:crypto');
4
4
  const { AstBuilder, compile, GherkinClassicTokenMatcher, Parser } = require('@cucumber/gherkin');
5
- const webpackPreprocessor = require('@cypress/webpack-preprocessor')();
5
+ const webpackPreprocessor = require('@cypress/webpack-preprocessor')({
6
+ webpackOptions: {
7
+ mode: 'development',
8
+ resolve: { extensions: ['.ts', '.js'] },
9
+ module: {
10
+ rules: [
11
+ {
12
+ test: /\.tsx?$/,
13
+ exclude: [/node_modules/],
14
+ use: [{ loader: 'ts-loader', options: { transpileOnly: true } }]
15
+ },
16
+ {
17
+ test: /\.jsx?$/,
18
+ exclude: [/node_modules/],
19
+ use: [{ loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } }]
20
+ }
21
+ ]
22
+ }
23
+ }
24
+ });
6
25
  const tagExpressionParser = require('@cucumber/tag-expressions').default;
7
26
  const makeMochaTestDescribe = require('./make_mocha_tests_describe');
8
27
  const makeMochaTestIt = require('./make_mocha_tests_it');
@@ -3,6 +3,21 @@ module.exports = function makeMochaTest(tests) {
3
3
  cy.log(data);
4
4
  }
5
5
 
6
+ function attach(data, options) {
7
+ const type = typeof options === 'string' ? options : options?.mediaType ? options.mediaType : 'text/plain';
8
+ const name = options?.name ?? 'attachment';
9
+ const src = typeof data === 'string' && data.startsWith('data:')
10
+ ? data
11
+ : `data:${type};base64,${btoa(unescape(encodeURIComponent(String(data))))}`;
12
+ cy.then(() => {
13
+ Cypress.log({
14
+ displayName: name,
15
+ message: type,
16
+ consoleProps: () => ({ name, src })
17
+ });
18
+ });
19
+ }
20
+
6
21
  function keyword(step) {
7
22
  switch (step.type) {
8
23
  case 'Context': return 'Given';
@@ -35,7 +50,7 @@ module.exports = function makeMochaTest(tests) {
35
50
  step: { text, argument },
36
51
  world: this
37
52
  });
38
- step.code.apply(this, parameters);
53
+ return step.code.apply(this, parameters);
39
54
  }
40
55
 
41
56
  function executeStep(pickle, world) {
@@ -45,7 +60,7 @@ module.exports = function makeMochaTest(tests) {
45
60
  if (pickle.argument && pickle.argument.docString) {
46
61
  Cypress.log({ displayName: 'Multiline', message: pickle.argument.docString.content });
47
62
  }
48
- executeStepByText.call(world, pickle.text, pickle.argument);
63
+ return executeStepByText.call(world, pickle.text, pickle.argument);
49
64
  }
50
65
 
51
66
  if (supportCodeLibrary.beforeTestRunHookDefinitions.length > 0) {
@@ -63,7 +78,7 @@ module.exports = function makeMochaTest(tests) {
63
78
  describe('Scenario: ' + test.name, { testIsolation: false }, function () {
64
79
  const world = new supportCodeLibrary.World({
65
80
  log,
66
- attach: log,
81
+ attach,
67
82
  link: log
68
83
  });
69
84
  world.executeStep = executeStepByText;
@@ -72,12 +87,14 @@ module.exports = function makeMochaTest(tests) {
72
87
  let duration = 0;
73
88
  let exception;
74
89
  let message;
90
+ let willBeRetried = false;
75
91
  afterEach(function () {
76
92
  if (this.currentTest.state !== 'passed') {
77
93
  skip = true;
78
94
  }
79
95
  duration += this.currentTest.duration;
80
96
  if (this.currentTest.state === 'failed') {
97
+ willBeRetried = this.currentTest.currentRetry() < this.currentTest._retries;
81
98
  status = this.currentTest.state;
82
99
  exception = this.currentTest.err;
83
100
  message = this.currentTest.err?.message;
@@ -133,7 +150,12 @@ module.exports = function makeMochaTest(tests) {
133
150
  }]);
134
151
  }
135
152
  }
136
- executeStep(step, world);
153
+ const stepResult = executeStep(step, world);
154
+ if (stepResult === 'pending' || stepResult === 'skipped') {
155
+ skip = true;
156
+ status = stepResult;
157
+ return this.skip();
158
+ }
137
159
  });
138
160
  }
139
161
  for (const afterTest of supportCodeLibrary.afterTestCaseHookDefinitions) {
@@ -150,7 +172,7 @@ module.exports = function makeMochaTest(tests) {
150
172
  message
151
173
  },
152
174
  gherkinDocument: tests,
153
- willBeRetried: false,
175
+ willBeRetried,
154
176
  testCaseStartedId: test.id,
155
177
  }]);
156
178
  });
@@ -3,6 +3,21 @@ module.exports = function makeMochaTest(tests) {
3
3
  cy.log(data);
4
4
  }
5
5
 
6
+ function attach(data, options) {
7
+ const type = typeof options === 'string' ? options : options?.mediaType ? options.mediaType : 'text/plain';
8
+ const name = options?.name ?? 'attachment';
9
+ const src = typeof data === 'string' && data.startsWith('data:')
10
+ ? data
11
+ : `data:${type};base64,${btoa(unescape(encodeURIComponent(String(data))))}`;
12
+ cy.then(() => {
13
+ Cypress.log({
14
+ displayName: name,
15
+ message: type,
16
+ consoleProps: () => ({ name, src })
17
+ });
18
+ });
19
+ }
20
+
6
21
  function keyword(step) {
7
22
  switch (step.type) {
8
23
  case 'Context':
@@ -43,7 +58,7 @@ module.exports = function makeMochaTest(tests) {
43
58
  step: { text, argument },
44
59
  world: this
45
60
  });
46
- step.code.apply(this, parameters);
61
+ return step.code.apply(this, parameters);
47
62
  }
48
63
 
49
64
  function executeStep(pickle, world) {
@@ -55,7 +70,7 @@ module.exports = function makeMochaTest(tests) {
55
70
  Cypress.log({ displayName: 'Multiline', message: pickle.argument.docString.content });
56
71
  }
57
72
  });
58
- executeStepByText.call(world, pickle.text, pickle.argument);
73
+ return executeStepByText.call(world, pickle.text, pickle.argument);
59
74
  }
60
75
 
61
76
  supportCodeLibrary.World.prototype.executeStep = executeStepByText;
@@ -100,7 +115,7 @@ module.exports = function makeMochaTest(tests) {
100
115
  this.currentTest.body = renderGherkinTest(test.steps);
101
116
  const world = this.world = new supportCodeLibrary.World({
102
117
  log,
103
- attach: log,
118
+ attach,
104
119
  link: log
105
120
  });
106
121
  for (const beforeTest of supportCodeLibrary.beforeTestCaseHookDefinitions) {
@@ -121,6 +136,7 @@ module.exports = function makeMochaTest(tests) {
121
136
  const test = findTest(tests, this.currentTest.title);
122
137
  const world = this.world;
123
138
  const result = getResult(this.currentTest);
139
+ const willBeRetried = result.status === 'failed' && this.currentTest.currentRetry() < this.currentTest._retries;
124
140
  // corner case to complete AfterStep if test is failed
125
141
  if (result.status === 'failed' && this.step) {
126
142
  for (const afterStep of supportCodeLibrary.afterTestStepHookDefinitions) {
@@ -144,7 +160,7 @@ module.exports = function makeMochaTest(tests) {
144
160
  result,
145
161
  gherkinDocument: tests,
146
162
  testCaseStartedId: test.id,
147
- willBeRetried: false
163
+ willBeRetried
148
164
  }]);
149
165
  });
150
166
  }
@@ -170,7 +186,11 @@ module.exports = function makeMochaTest(tests) {
170
186
  }]);
171
187
  }
172
188
  }
173
- executeStep(step, world);
189
+ const stepResult = executeStep(step, world);
190
+ if (stepResult === 'pending' || stepResult === 'skipped') {
191
+ result.status = stepResult;
192
+ return this.skip();
193
+ }
174
194
  for (const afterStep of supportCodeLibrary.afterTestStepHookDefinitions) {
175
195
  if (afterStep.appliesToTestCase(this.step)) {
176
196
  afterStep.code.apply(world, [{
package/index.d.ts CHANGED
@@ -1,42 +1 @@
1
- declare type Expression = string | RegExp;
2
- declare type TestHookOptions = {
3
- tags?: string,
4
- name?: string
5
- };
6
- declare type StepHookOptions = {
7
- tags?: string
8
- };
9
- declare type ParameterTypeOption = {
10
- name: string,
11
- preferForRegexpMatch?: boolean,
12
- regexp: RegExp,
13
- transformer?: Function,
14
- useForSnippets?: boolean
15
- }
16
- export function Given(expression: Expression, fn: Function): void;
17
- export function When(expression: Expression, fn: Function): void;
18
- export function Then(expression: Expression, fn: Function): void;
19
- export function Before(fn: Function): void;
20
- export function Before(options: TestHookOptions, fn: Function): void;
21
- export function After(fn: Function): void;
22
- export function After(options: TestHookOptions, fn: Function): void;
23
- export function BeforeStep(fn: Function): void;
24
- export function BeforeStep(options: StepHookOptions, fn: Function): void;
25
- export function AfterStep(fn: Function): void;
26
- export function AfterStep(options: StepHookOptions, fn: Function): void;
27
- export function BeforeAll(fn: Function): void;
28
- export function AfterAll(fn: Function): void;
29
- export function setWorldConstructor(world: IWorld): void;
30
- export function defineParameterType(option: ParameterTypeOption): void;
31
- export function Template(template: (...args: any[]) => string): () => void;
32
- export class World {
33
- log: (...args: any) => void;
34
- attach: (...args: any) => void;
35
- link: (...args: any) => void;
36
- executeStep(text: string, argument?: any): void;
37
- constructor(options: {
38
- log: (...args: any) => void;
39
- attach: (...args: any) => void;
40
- link: (...args: any) => void;
41
- });
42
- }
1
+ export * from './supportCodeLibrary/index';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qavajs/cypress-runner-adapter",
3
- "version": "1.9.2",
3
+ "version": "1.11.0",
4
4
  "main": "supportCodeLibrary/index.js",
5
5
  "exports": {
6
6
  ".": "./supportCodeLibrary/index.js",
@@ -16,7 +16,11 @@
16
16
  "debug": "cypress open --config-file test/cypress.config.js",
17
17
  "test": "cypress run --config-file test/cypress.config.js",
18
18
  "debug:it": "MODE=it cypress open --config-file test/cypress.config.js",
19
- "test:it": "MODE=it cypress run --config-file test/cypress.config.js"
19
+ "test:it": "MODE=it cypress run --config-file test/cypress.config.js",
20
+ "debug:ts": "cypress open --config-file test/cypress-ts.config.js",
21
+ "test:ts": "cypress run --config-file test/cypress-ts.config.js",
22
+ "debug:ts:it": "MODE=it cypress open --config-file test/cypress-ts.config.js",
23
+ "test:ts:it": "MODE=it cypress run --config-file test/cypress-ts.config.js"
20
24
  },
21
25
  "license": "MIT",
22
26
  "description": "feature file preprocessor",
@@ -33,12 +37,14 @@
33
37
  "homepage": "https://qavajs.github.io/docs/intro",
34
38
  "dependencies": {
35
39
  "@cucumber/cucumber-expressions": "^19.0.0",
36
- "@cucumber/gherkin": "^39.0.0",
40
+ "@cucumber/gherkin": "^39.1.0",
37
41
  "@cucumber/tag-expressions": "^9.1.0",
38
- "@cypress/webpack-preprocessor": "^7.1.0"
42
+ "@cypress/webpack-preprocessor": "^7.1.0",
43
+ "ts-loader": "^9.5.7"
39
44
  },
40
45
  "devDependencies": {
41
- "cypress": "^15.13.0"
46
+ "cypress": "^15.14.2",
47
+ "typescript": "^6.0.3"
42
48
  },
43
49
  "keywords": [
44
50
  "cypress",
@@ -50,4 +56,4 @@
50
56
  "test",
51
57
  "test-automation"
52
58
  ]
53
- }
59
+ }
@@ -0,0 +1,112 @@
1
+ export type Expression = string | RegExp;
2
+
3
+ export class DataTable {
4
+ constructor(sourceTable: string[][] | { rows: { cells: { value: string }[] }[] });
5
+ raw(): string[][];
6
+ rows(): string[][];
7
+ hashes(): Record<string, string>[];
8
+ rowsHash(): Record<string, string>;
9
+ transpose(): DataTable;
10
+ }
11
+
12
+ export interface GherkinDocument {
13
+ uri?: string;
14
+ feature?: {
15
+ name: string;
16
+ description: string;
17
+ tags: { name: string }[];
18
+ children: object[];
19
+ };
20
+ comments: object[];
21
+ }
22
+
23
+ export interface Pickle {
24
+ id: string;
25
+ uri: string;
26
+ name: string;
27
+ language: string;
28
+ steps: { id: string; text: string; argument?: object }[];
29
+ tags: { name: string; astNodeId: string }[];
30
+ astNodeIds: string[];
31
+ }
32
+
33
+ export interface TestStepResult {
34
+ status: string;
35
+ duration?: { seconds: number; nanos: number };
36
+ message?: string;
37
+ }
38
+
39
+ export interface TestCaseHookParams {
40
+ gherkinDocument: GherkinDocument;
41
+ pickle: Pickle;
42
+ testCaseStartedId: string;
43
+ willBeRetried: boolean;
44
+ result?: TestStepResult;
45
+ error?: Error;
46
+ }
47
+
48
+ export interface TestStepHookParams {
49
+ gherkinDocument: GherkinDocument;
50
+ pickle: Pickle;
51
+ testCaseStartedId: string;
52
+ result?: TestStepResult;
53
+ error?: Error;
54
+ }
55
+
56
+ export interface WorldOptions {
57
+ log: (...args: any[]) => void;
58
+ attach: (data: string, mediaType?: string) => void;
59
+ link: (url: string, text?: string) => void;
60
+ }
61
+
62
+ export class World {
63
+ log(...args: any[]): void;
64
+ attach(data: string, mediaType?: string): void;
65
+ link(url: string, text?: string): void;
66
+ executeStep(text: string, argument?: DataTable | string): void;
67
+ constructor(options: WorldOptions);
68
+ }
69
+
70
+ type StepFn<W extends World = World> = (this: W, ...args: any[]) => any;
71
+
72
+ export function defineStep<W extends World = World>(keyword: string, expression: Expression, fn: StepFn<W>): void;
73
+ export function Given<W extends World = World>(expression: Expression, fn: StepFn<W>): void;
74
+ export function When<W extends World = World>(expression: Expression, fn: StepFn<W>): void;
75
+ export function Then<W extends World = World>(expression: Expression, fn: StepFn<W>): void;
76
+
77
+ type TestCaseHookFn = (this: World, params: TestCaseHookParams) => any;
78
+ type TestStepHookFn = (this: World, params: TestStepHookParams) => any;
79
+ type TestRunHookFn = (this: World) => any;
80
+
81
+ export interface TestCaseHookOptions {
82
+ tags?: string;
83
+ name?: string;
84
+ }
85
+
86
+ export interface TestStepHookOptions {
87
+ tags?: string;
88
+ }
89
+
90
+ export function Before(fn: TestCaseHookFn): void;
91
+ export function Before(options: TestCaseHookOptions, fn: TestCaseHookFn): void;
92
+ export function After(fn: TestCaseHookFn): void;
93
+ export function After(options: TestCaseHookOptions, fn: TestCaseHookFn): void;
94
+ export function BeforeStep(fn: TestStepHookFn): void;
95
+ export function BeforeStep(options: TestStepHookOptions, fn: TestStepHookFn): void;
96
+ export function AfterStep(fn: TestStepHookFn): void;
97
+ export function AfterStep(options: TestStepHookOptions, fn: TestStepHookFn): void;
98
+ export function BeforeAll(fn: TestRunHookFn): void;
99
+ export function AfterAll(fn: TestRunHookFn): void;
100
+
101
+ export interface ParameterTypeOption {
102
+ name: string;
103
+ regexp: RegExp;
104
+ transformer?: (...args: string[]) => any;
105
+ useForSnippets?: boolean;
106
+ preferForRegexpMatch?: boolean;
107
+ }
108
+
109
+ export function defineParameterType(option: ParameterTypeOption): void;
110
+ export function setWorldConstructor(world: new (...args: any[]) => World): void;
111
+ export function setDefaultTimeout(milliseconds: number): void;
112
+ export function Template(template: (...args: any[]) => string): (...args: any[]) => void;
@@ -108,6 +108,10 @@ export function defineParameterType(options) {
108
108
  supportCodeLibrary.parameterTypeRegistry.defineSourcedParameterType(parameterType, {})
109
109
  }
110
110
 
111
+ export function setDefaultTimeout(milliseconds) {
112
+ Cypress.config('defaultCommandTimeout', milliseconds);
113
+ }
114
+
111
115
  /**
112
116
  * Define template step
113
117
  * @param {() => string} scenario - multiline string with steps