cucumberjs-qase-reporter 2.0.0-beta.2 → 2.0.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
@@ -1,50 +1,69 @@
1
- > # Qase TMS Cucumber JS reporter
2
- >
3
- > Publish results simple and easy.
1
+ # Qase TMS Cucumber JS reporter
4
2
 
5
- ## How to integrate
3
+ Publish results simple and easy.
6
4
 
5
+ ## How to install
6
+
7
+ ```sh
8
+ npm install -D cucumberjs-qase-reporter
7
9
  ```
8
- npm install cucumberjs-qase-reporter
9
- ```
10
10
 
11
- ## Example of usage
11
+ ## Updating from v1
12
+
13
+ To update a test project using cucumberjs-qase-reporter@v1 to version 2:
14
+
15
+ 1. Update reporter configuration in `qase.config.json` and/or environment variables —
16
+ see the [configuration reference](#configuration) below.
12
17
 
13
- The Cucumber JS reporter has the ability to auto-generate test cases
18
+ ## Getting started
19
+
20
+ The Cucumber JS reporter can auto-generate test cases
14
21
  and suites from your test data.
22
+ Test results of subsequent test runs will match the same test cases
23
+ as long as their names and file paths don't change.
24
+
25
+ You can also annotate the tests with the IDs of existing test cases
26
+ from Qase.io before executing tests. It's a more reliable way to bind
27
+ autotests to test cases, that persists when you rename, move, or
28
+ parameterize your tests.
15
29
 
16
- But if necessary, you can independently register the ID of already
17
- existing test cases from TMS before the executing tests. You can decorate your scenarios with Qase TMS case IDs in format `Q-<case id>` or `Q123`, also `q` can be in any case:
30
+ For example:
18
31
 
19
32
  ```gherkin
20
33
  Feature: Cucumber documentation
21
- As a user of cucumber.js
22
- I want to have documentation on cucumber
23
- So I can write better applications
24
-
25
- @sections @Q-2
26
- Scenario: Usage documentation
27
- Given I am on the cucumber.js GitHub repository
28
- When I go to the README file
29
- Then I should see a "Cool" section
30
- When I go to the README file
31
-
32
- @ignore @q4
33
- Scenario: Status badges 2
34
- Given I am on the cucumber.js GitHub repository
35
- When I go to the README file
36
- Then I should see a "Build Status" badge
37
- And I should see a "Dependencies" badge
34
+ As a user of cucumber.js
35
+ I want to have documentation on cucumber
36
+ So I can write better applications
37
+
38
+ @QaseID=1
39
+ Scenario: Usage documentation
40
+ Given I am on the cucumber.js GitHub repository
41
+ When I go to the README file
42
+ Then I should see a "Cool" section
43
+
44
+ @QaseID=2
45
+ @QaseFields={'severity':'high'}
46
+ Scenario: Status badges 2
47
+ Given I am on the cucumber.js GitHub repository
48
+ When I go to the README file
49
+ Then I should see a "Build Status" badge
50
+ And I should see a "Dependencies" badge
38
51
  ```
39
- ---
40
- To run tests and create a test run, execute the command (for example from folder examples/cucumberjs):
52
+
53
+ To execute Cucumber JS tests and report them to Qase.io, run the command:
54
+
41
55
  ```bash
42
- QASE_MODE=testops cucumber-js -f cucumberjs-qase-reporter:/dev/null features -r zombie/support -r zombie/steps --publish-quiet
56
+ QASE_MODE=testops cucumber-js -f cucumberjs-qase-reporter features -r step_definitions --publish-quiet
43
57
  ```
58
+
44
59
  or
60
+
45
61
  ```bash
46
62
  npm test
47
63
  ```
64
+
65
+ You can try it with the example project at [`examples/cucumberjs`](../examples/cucumberjs/).
66
+
48
67
  <p align="center">
49
68
  <img width="65%" src="./screenshots/screenshot.png">
50
69
  </p>
@@ -61,34 +80,28 @@ https://app.qase.io/run/QASE_PROJECT_CODE
61
80
 
62
81
  ## Configuration
63
82
 
64
- Qase reporter supports passing parameters using two ways:
65
- using `.qaserc`/`qase.config.json` file and using ENV variables.
66
-
67
- `.qaserc` parameters, (* - required):
68
- - `mode` - `testops`/`off` Enables reporter, default - `off`
69
- - `debug` - Enables debug logging, defaule - `false`
70
- - `environment` - To execute with the sending of the envinroment information
71
- - *`testops.api.token` - Token for API access, you can find more information
72
- [here](https://developers.qase.io/#authentication)
73
- - *`testops.project` - Code of your project (can be extracted from main
74
- page of your project: `https://app.qase.io/project/DEMOTR` -
75
- `DEMOTR` is project code here)
76
- - `testops.run.id` - Pass Run ID
77
- - `testops.run.title` - Set custom Run name, when new run is created
78
- - `testops.run.description` - Set custom Run description, when new run is created
79
- - `testops.run.complete` - Whether the run should be completed
80
-
81
- Example configuration file:
83
+ Qase Cucumber JS reporter can be configured in multiple ways:
84
+
85
+ - using a separate config file `qase.config.json`,
86
+ - using environment variables (they override the values from the configuration files).
87
+
88
+ For a full list of configuration options, see
89
+ the [Configuration reference](../qase-javascript-commons/README.md#configuration).
90
+
91
+ Example `qase.config.json` file:
82
92
 
83
93
  ```json
84
94
  {
95
+ "mode": "testops",
85
96
  "debug": true,
86
- "environment": 1,
87
97
  "testops": {
88
98
  "api": {
89
99
  "token": "api_key"
90
100
  },
91
- "project": "project_code"
101
+ "project": "project_code",
102
+ "run": {
103
+ "complete": true
104
+ }
92
105
  }
93
106
  }
94
107
  ```
@@ -105,41 +118,17 @@ Supported ENV variables:
105
118
  - `QASE_TESTOPS_RUN_DESCRIPTION` - Same as `testops.run.description`
106
119
 
107
120
  To run using ENV you have to execute:
108
- ```bash
109
- cucumber-js -f cucumberjs-qase-reporter features
110
- ```
111
-
112
- ## Setup with Protractor
113
121
 
114
- Due to different configurations of protractor and cucumber itself you should install a bit more libraries:
115
122
  ```bash
116
- npm install cucumberjs-qase-reporter @cucumber/cucumber @cucumber/messages
117
- ```
118
-
119
- After that you will be able to use reporter like this (`protractor.conf.js`):
120
- ```js
121
- exports.config = {
122
- ...
123
- cucumberOpts: {
124
- require: [
125
- './tests/e2e/specs/*.js',
126
- ], // require step definition files before executing features
127
- tags: [],
128
- 'dry-run': false,
129
- compiler: [],
130
- format: ["node_modules/cucumberjs-qase-reporter"],
131
- },
132
- ...
133
- }
123
+ cucumber-js -f cucumberjs-qase-reporter features -r step_definitions --publish-quiet
134
124
  ```
135
125
 
136
- **Do not forget to add `.qaserc`/`qase.config.json` file!**
137
-
138
126
  ## Requirements
139
127
 
140
- We maintain the reporter on LTS versions of Node. You can find the current versions by following the [link](https://nodejs.org/en/about/releases/)
141
- <!-- references -->
128
+ We maintain the reporter on [LTS versions of Node](https://nodejs.org/en/about/releases/).
142
129
 
143
130
  `@cucumber/cucumber >= 7.0.0`
144
131
 
132
+ <!-- references -->
133
+
145
134
  [auth]: https://developers.qase.io/#authentication
package/changelog.md ADDED
@@ -0,0 +1,44 @@
1
+ # qase-cucumberjs@2.0.0
2
+
3
+ ## What's new
4
+
5
+ This is the first release in the 2.x series of the Cucumber JS reporter.
6
+ It brings a new annotation syntax with field values,
7
+ new and more flexible configs, uploading results in parallel with running tests,
8
+ and other powerful features.
9
+
10
+ This changelog entry will be updated soon.
11
+ For more information about the new features and a guide for migration from v1, refer to the
12
+ [reporter documentation](https://github.com/qase-tms/qase-javascript/tree/main/qase-cucumberjs#readme)
13
+
14
+ # qase-cucumberjs@2.0.0-beta.3
15
+
16
+ ## What's new
17
+
18
+ Added support new Qase tags.
19
+
20
+ ```diff
21
+ - @Q-1
22
+ + @QaseID=2
23
+ + @QaseTitle=Scenario_with_Qase_title_tag
24
+ + @QaseFields={"description":"Description","severity":"high"}
25
+ Scenario: simple test
26
+ ```
27
+
28
+ QaseID - is a unique identifier of the test case in the Qase TMS.
29
+ QaseTitle - is a title of the test case in the Qase TMS.
30
+ QaseFields - is a JSON object with additional fields for the test case in the Qase TMS.
31
+
32
+ # qase-cucumberjs@2.0.0-beta.2
33
+
34
+ ## What's new
35
+
36
+ Added support gherkin steps.
37
+ Before this version, the reporter was not able to parse the steps from the feature files.
38
+ Now, the reporter can parse the steps and send them to the Qase TMS.
39
+
40
+ # qase-cucumberjs@2.0.0-beta.1
41
+
42
+ ## What's new
43
+
44
+ First major beta release for the version 2 series of the Qase Cypress reporter.
@@ -0,0 +1,5 @@
1
+ interface TestMetadata {
2
+ ids: number[];
3
+ fields: Record<string, string>;
4
+ title: string | null;
5
+ }
package/dist/models.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";
@@ -1,6 +1,5 @@
1
- import { Formatter, IFormatterOptions, Status } from '@cucumber/cucumber';
2
- import { ConfigLoader, ConfigType, StepStatusEnum, TestStatusEnum } from 'qase-javascript-commons';
3
- type TestStepResultStatus = (typeof Status)[keyof typeof Status];
1
+ import { Formatter, IFormatterOptions } from '@cucumber/cucumber';
2
+ import { ConfigLoader, ConfigType } from 'qase-javascript-commons';
4
3
  export type CucumberQaseOptionsType = IFormatterOptions & {
5
4
  qase?: ConfigType;
6
5
  };
@@ -9,64 +8,11 @@ export type CucumberQaseOptionsType = IFormatterOptions & {
9
8
  * @extends Formatter
10
9
  */
11
10
  export declare class CucumberQaseReporter extends Formatter {
12
- /**
13
- * @type {Record<TestStepResultStatus, TestStatusEnum | null>}
14
- */
15
- static statusMap: Record<TestStepResultStatus, TestStatusEnum | null>;
16
- /**
17
- * @type {Record<TestStepResultStatus, StepStatusEnum>}
18
- */
19
- static stepStatusMap: Record<TestStepResultStatus, StepStatusEnum>;
20
- /**
21
- * @type {RegExp}
22
- */
23
- static qaseIdRegExp: RegExp;
24
- /**
25
- * @param {readonly PickleTag[]} tagsList
26
- * @returns {number[]}
27
- * @private
28
- */
29
- private static getCaseIds;
30
- /**
31
- * @type {Record<string, PickleInfoType>}
32
- * @private
33
- */
34
- private pickleInfo;
35
- /**
36
- * @type {Record<string, TestCaseStarted>}
37
- * @private
38
- */
39
- private testCaseStarts;
40
- /**
41
- * @type {Record<string, >}
42
- * @private
43
- */
44
- /**
45
- * @type {Record<string, TestStepFinished>}
46
- * @private
47
- */
48
- private testCaseStepsFinished;
49
- private testCaseStartedResult;
50
- /**
51
- * @type {Record<string, string[]>}
52
- * @private
53
- */
54
- private testCaseStartedErrors;
55
- /**
56
- * @type {Record<string, string>}
57
- * @private
58
- */
59
- private testCaseScenarioId;
60
- /**
61
- * @type {Record<string, string>}
62
- * @private
63
- */
64
- private scenarios;
65
11
  /**
66
12
  * @type {Record<string, string>}
67
13
  * @private
68
14
  */
69
- private attachments;
15
+ private storage;
70
16
  /**
71
17
  * @type {ReporterInterface}
72
18
  * @private
@@ -86,7 +32,6 @@ export declare class CucumberQaseReporter extends Formatter {
86
32
  * @private
87
33
  */
88
34
  private bindEventListeners;
89
- private convertSteps;
90
35
  /**
91
36
  * @returns {Promise<void>}
92
37
  * @private
@@ -98,4 +43,3 @@ export declare class CucumberQaseReporter extends Formatter {
98
43
  */
99
44
  private startTestRun;
100
45
  }
101
- export {};
package/dist/reporter.js CHANGED
@@ -3,25 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CucumberQaseReporter = void 0;
4
4
  const cucumber_1 = require("@cucumber/cucumber");
5
5
  const qase_javascript_commons_1 = require("qase-javascript-commons");
6
+ const storage_1 = require("./storage");
6
7
  /**
7
8
  * @class CucumberQaseReporter
8
9
  * @extends Formatter
9
10
  */
10
11
  class CucumberQaseReporter extends cucumber_1.Formatter {
11
- /**
12
- * @param {readonly PickleTag[]} tagsList
13
- * @returns {number[]}
14
- * @private
15
- */
16
- static getCaseIds(tagsList) {
17
- return tagsList.reduce((acc, tagInfo) => {
18
- const ids = Array.from(tagInfo.name.matchAll(CucumberQaseReporter.qaseIdRegExp))
19
- .map(([, id]) => Number(id))
20
- .filter((id) => id !== undefined);
21
- acc.push(...ids);
22
- return acc;
23
- }, []);
24
- }
25
12
  /**
26
13
  * @param {CucumberQaseOptionsType} options
27
14
  * @param {ConfigLoaderInterface} configLoader
@@ -30,46 +17,6 @@ class CucumberQaseReporter extends cucumber_1.Formatter {
30
17
  const { qase, ...formatterOptions } = options;
31
18
  const config = configLoader.load();
32
19
  super(formatterOptions);
33
- /**
34
- * @type {Record<string, PickleInfoType>}
35
- * @private
36
- */
37
- this.pickleInfo = {};
38
- /**
39
- * @type {Record<string, TestCaseStarted>}
40
- * @private
41
- */
42
- this.testCaseStarts = {};
43
- /**
44
- * @type {Record<string, >}
45
- * @private
46
- */
47
- /**
48
- * @type {Record<string, TestStepFinished>}
49
- * @private
50
- */
51
- this.testCaseStepsFinished = {};
52
- this.testCaseStartedResult = {};
53
- /**
54
- * @type {Record<string, string[]>}
55
- * @private
56
- */
57
- this.testCaseStartedErrors = {};
58
- /**
59
- * @type {Record<string, string>}
60
- * @private
61
- */
62
- this.testCaseScenarioId = {};
63
- /**
64
- * @type {Record<string, string>}
65
- * @private
66
- */
67
- this.scenarios = {};
68
- /**
69
- * @type {Record<string, string>}
70
- * @private
71
- */
72
- this.attachments = {};
73
20
  this.reporter = qase_javascript_commons_1.QaseReporter.getInstance({
74
21
  ...(0, qase_javascript_commons_1.composeOptions)(qase, config),
75
22
  frameworkPackage: '@cucumber/cucumber',
@@ -77,6 +24,7 @@ class CucumberQaseReporter extends cucumber_1.Formatter {
77
24
  reporterName: 'cucumberjs-qase-reporter',
78
25
  });
79
26
  this.eventBroadcaster = formatterOptions.eventBroadcaster;
27
+ this.storage = new storage_1.Storage();
80
28
  this.bindEventListeners();
81
29
  }
82
30
  /**
@@ -85,29 +33,13 @@ class CucumberQaseReporter extends cucumber_1.Formatter {
85
33
  bindEventListeners() {
86
34
  this.eventBroadcaster.on('envelope', (envelope) => {
87
35
  if (envelope.gherkinDocument) {
88
- if (envelope.gherkinDocument.feature) {
89
- const { children, name } = envelope.gherkinDocument.feature;
90
- children.forEach(({ scenario }) => {
91
- if (scenario) {
92
- this.scenarios[scenario.id] = name;
93
- }
94
- });
95
- }
36
+ this.storage.addScenario(envelope.gherkinDocument);
96
37
  }
97
38
  else if (envelope.pickle) {
98
- this.pickleInfo[envelope.pickle.id] = {
99
- caseIds: CucumberQaseReporter.getCaseIds(envelope.pickle.tags),
100
- name: envelope.pickle.name,
101
- lastAstNodeId: envelope.pickle.astNodeIds[envelope.pickle.astNodeIds.length - 1],
102
- steps: envelope.pickle.steps,
103
- };
39
+ this.storage.addPickle(envelope.pickle);
104
40
  }
105
41
  else if (envelope.attachment) {
106
- if (envelope.attachment.testCaseStartedId &&
107
- envelope.attachment.fileName) {
108
- this.attachments[envelope.attachment.testCaseStartedId] =
109
- envelope.attachment.fileName;
110
- }
42
+ this.storage.addAttachment(envelope.attachment);
111
43
  }
112
44
  else if (envelope.testRunStarted) {
113
45
  this.startTestRun();
@@ -116,131 +48,23 @@ class CucumberQaseReporter extends cucumber_1.Formatter {
116
48
  void this.publishResults();
117
49
  }
118
50
  else if (envelope.testCase) {
119
- this.testCaseScenarioId[envelope.testCase.id] =
120
- envelope.testCase;
51
+ this.storage.addTestCase(envelope.testCase);
121
52
  }
122
53
  else if (envelope.testCaseStarted) {
123
- this.testCaseStarts[envelope.testCaseStarted.id] =
124
- envelope.testCaseStarted;
125
- this.testCaseStartedResult[envelope.testCaseStarted.id] =
126
- qase_javascript_commons_1.TestStatusEnum.passed;
54
+ this.storage.addTestCaseStarted(envelope.testCaseStarted);
127
55
  }
128
56
  else if (envelope.testStepFinished) {
129
- const stepFin = envelope.testStepFinished;
130
- const oldStatus = this.testCaseStartedResult[stepFin.testCaseStartedId];
131
- const newStatus = CucumberQaseReporter.statusMap[stepFin.testStepResult.status];
132
- if (newStatus === null) {
133
- return;
134
- }
135
- this.testCaseStepsFinished[stepFin.testStepId] = stepFin;
136
- if (newStatus !== qase_javascript_commons_1.TestStatusEnum.passed) {
137
- if (stepFin.testStepResult.message) {
138
- const errors = this.testCaseStartedErrors[stepFin.testCaseStartedId] ?? [];
139
- if (!this.testCaseStartedErrors[stepFin.testCaseStartedId]) {
140
- this.testCaseStartedErrors[stepFin.testCaseStartedId] = errors;
141
- }
142
- errors.push(stepFin.testStepResult.message);
143
- }
144
- if (oldStatus) {
145
- if (oldStatus !== qase_javascript_commons_1.TestStatusEnum.failed) {
146
- this.testCaseStartedResult[stepFin.testCaseStartedId] = newStatus;
147
- }
148
- }
149
- else {
150
- this.testCaseStartedResult[stepFin.testCaseStartedId] = newStatus;
151
- }
152
- }
57
+ this.storage.addTestCaseStep(envelope.testStepFinished);
153
58
  }
154
59
  else if (envelope.testCaseFinished) {
155
- const tcs = this.testCaseStarts[envelope.testCaseFinished.testCaseStartedId];
156
- if (!tcs) {
157
- return;
158
- }
159
- const testCase = this.testCaseScenarioId[tcs.testCaseId];
160
- if (!testCase) {
161
- return;
162
- }
163
- const info = this.pickleInfo[testCase.pickleId];
164
- if (!info) {
60
+ const result = this.storage.convertTestCase(envelope.testCaseFinished);
61
+ if (!result) {
165
62
  return;
166
63
  }
167
- let error;
168
- if (this.testCaseStartedErrors[tcs.id]?.length) {
169
- error = new Error(this.testCaseStartedErrors[tcs.id]?.join('\n\n'));
170
- }
171
- let relations = null;
172
- if (info.lastAstNodeId != undefined && this.scenarios[info.lastAstNodeId] != undefined) {
173
- relations = {
174
- suite: {
175
- data: [
176
- {
177
- title: this.scenarios[info.lastAstNodeId] ?? '',
178
- public_id: null,
179
- },
180
- ],
181
- },
182
- };
183
- }
184
- void this.reporter.addTestResult({
185
- attachments: [],
186
- author: null,
187
- execution: {
188
- status: this.testCaseStartedResult[envelope.testCaseFinished.testCaseStartedId] ?? qase_javascript_commons_1.TestStatusEnum.passed,
189
- start_time: null,
190
- end_time: null,
191
- duration: Math.abs(envelope.testCaseFinished.timestamp.seconds - tcs.timestamp.seconds),
192
- stacktrace: error?.stack ?? null,
193
- thread: null,
194
- },
195
- fields: {},
196
- message: null,
197
- muted: false,
198
- params: {},
199
- relations: relations,
200
- run_id: null,
201
- signature: '',
202
- steps: this.convertSteps(info.steps, testCase),
203
- testops_id: info.caseIds.length > 0 ? info.caseIds : null,
204
- id: tcs.id,
205
- title: info.name,
206
- // suiteTitle: info.lastAstNodeId && this.scenarios[info.lastAstNodeId],
207
- });
64
+ void this.reporter.addTestResult(result);
208
65
  }
209
66
  });
210
67
  }
211
- convertSteps(steps, testCase) {
212
- const results = [];
213
- for (const s of testCase.testSteps) {
214
- const finished = this.testCaseStepsFinished[s.id];
215
- if (!finished) {
216
- continue;
217
- }
218
- const step = steps.find((step) => step.id === s.pickleStepId);
219
- if (!step) {
220
- continue;
221
- }
222
- const result = {
223
- id: s.id,
224
- step_type: qase_javascript_commons_1.StepType.GHERKIN,
225
- data: {
226
- keyword: step.text,
227
- name: step.text,
228
- line: 0,
229
- },
230
- execution: {
231
- status: CucumberQaseReporter.stepStatusMap[finished.testStepResult.status],
232
- start_time: null,
233
- end_time: null,
234
- duration: finished.testStepResult.duration.seconds,
235
- },
236
- attachments: [],
237
- steps: [],
238
- parent_id: null,
239
- };
240
- results.push(result);
241
- }
242
- return results;
243
- }
244
68
  /**
245
69
  * @returns {Promise<void>}
246
70
  * @private
@@ -257,31 +81,3 @@ class CucumberQaseReporter extends cucumber_1.Formatter {
257
81
  }
258
82
  }
259
83
  exports.CucumberQaseReporter = CucumberQaseReporter;
260
- /**
261
- * @type {Record<TestStepResultStatus, TestStatusEnum | null>}
262
- */
263
- CucumberQaseReporter.statusMap = {
264
- [cucumber_1.Status.PASSED]: qase_javascript_commons_1.TestStatusEnum.passed,
265
- [cucumber_1.Status.FAILED]: qase_javascript_commons_1.TestStatusEnum.failed,
266
- [cucumber_1.Status.SKIPPED]: qase_javascript_commons_1.TestStatusEnum.skipped,
267
- [cucumber_1.Status.AMBIGUOUS]: null,
268
- [cucumber_1.Status.PENDING]: qase_javascript_commons_1.TestStatusEnum.blocked,
269
- [cucumber_1.Status.UNDEFINED]: null,
270
- [cucumber_1.Status.UNKNOWN]: null,
271
- };
272
- /**
273
- * @type {Record<TestStepResultStatus, StepStatusEnum>}
274
- */
275
- CucumberQaseReporter.stepStatusMap = {
276
- [cucumber_1.Status.PASSED]: qase_javascript_commons_1.StepStatusEnum.passed,
277
- [cucumber_1.Status.FAILED]: qase_javascript_commons_1.StepStatusEnum.failed,
278
- [cucumber_1.Status.SKIPPED]: qase_javascript_commons_1.StepStatusEnum.blocked,
279
- [cucumber_1.Status.AMBIGUOUS]: qase_javascript_commons_1.StepStatusEnum.blocked,
280
- [cucumber_1.Status.PENDING]: qase_javascript_commons_1.StepStatusEnum.blocked,
281
- [cucumber_1.Status.UNDEFINED]: qase_javascript_commons_1.StepStatusEnum.blocked,
282
- [cucumber_1.Status.UNKNOWN]: qase_javascript_commons_1.StepStatusEnum.blocked,
283
- };
284
- /**
285
- * @type {RegExp}
286
- */
287
- CucumberQaseReporter.qaseIdRegExp = /^@[Qq]-?(\d+)$/g;
@@ -0,0 +1,101 @@
1
+ import { Attachment as Attach, GherkinDocument, Pickle, TestCaseFinished, TestCaseStarted, TestStepFinished } from '@cucumber/messages';
2
+ import { StepStatusEnum, TestResultType, TestStatusEnum } from 'qase-javascript-commons';
3
+ import { TestCase } from '@cucumber/messages/dist/esm/src/messages';
4
+ import { Status } from '@cucumber/cucumber';
5
+ type TestStepResultStatus = (typeof Status)[keyof typeof Status];
6
+ export declare class Storage {
7
+ /**
8
+ * @type {Record<string, Pickle>}
9
+ * @private
10
+ */
11
+ private pickles;
12
+ /**
13
+ * @type {Record<string, TestCaseStarted>}
14
+ * @private
15
+ */
16
+ private testCaseStarts;
17
+ /**
18
+ * @type {Record<string, TestStepFinished>}
19
+ * @private
20
+ */
21
+ private testCaseSteps;
22
+ /**
23
+ * @type {Record<string, TestStatusEnum>}
24
+ * @private
25
+ */
26
+ private testCaseStartedResult;
27
+ /**
28
+ * @type {Record<string, string[]>}
29
+ * @private
30
+ */
31
+ private testCaseStartedErrors;
32
+ /**
33
+ * @type {Record<string, string>}
34
+ * @private
35
+ */
36
+ private testCases;
37
+ /**
38
+ * @type {Record<string, string>}
39
+ * @private
40
+ */
41
+ private scenarios;
42
+ /**
43
+ * @type {Record<string, string>}
44
+ * @private
45
+ */
46
+ private attachments;
47
+ /**
48
+ * Add pickle to storage
49
+ * @param {Pickle} pickle
50
+ */
51
+ addPickle(pickle: Pickle): void;
52
+ /**
53
+ * Add scenario to storage
54
+ * @param {GherkinDocument} document
55
+ */
56
+ addScenario(document: GherkinDocument): void;
57
+ /**
58
+ * Add attachment to storage
59
+ * @param {Attach} attachment
60
+ */
61
+ addAttachment(attachment: Attach): void;
62
+ /**
63
+ * Add test case to storage
64
+ * @param {TestCase} testCase
65
+ */
66
+ addTestCase(testCase: TestCase): void;
67
+ /**
68
+ * Get all test cases
69
+ * @param {TestCaseStarted} testCaseStarted
70
+ */
71
+ addTestCaseStarted(testCaseStarted: TestCaseStarted): void;
72
+ /**
73
+ * Add test case step to storage
74
+ * @param {TestStepFinished} testCaseStep
75
+ */
76
+ addTestCaseStep(testCaseStep: TestStepFinished): void;
77
+ /**
78
+ * Convert test case to test result
79
+ * @param {TestCaseFinished} testCase
80
+ * @returns {undefined | TestResultType}
81
+ */
82
+ convertTestCase(testCase: TestCaseFinished): undefined | TestResultType;
83
+ /**
84
+ * Convert test steps to test result steps
85
+ * @param {readonly PickleStep[]} steps
86
+ * @param {TestCase} testCase
87
+ * @returns {TestStepType[]}
88
+ * @private
89
+ */
90
+ private convertSteps;
91
+ /**
92
+ * @type {Record<TestStepResultStatus, TestStatusEnum>}
93
+ */
94
+ static statusMap: Record<TestStepResultStatus, TestStatusEnum>;
95
+ /**
96
+ * @type {Record<TestStepResultStatus, StepStatusEnum>}
97
+ */
98
+ static stepStatusMap: Record<TestStepResultStatus, StepStatusEnum>;
99
+ private parseTags;
100
+ }
101
+ export {};
@@ -0,0 +1,294 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Storage = void 0;
4
+ const qase_javascript_commons_1 = require("qase-javascript-commons");
5
+ const cucumber_1 = require("@cucumber/cucumber");
6
+ const qaseIdRegExp = /^@[Qq]-?(\d+)$/g;
7
+ const newQaseIdRegExp = /^@[Qq]ase[Ii][Dd]=(\d+)$/g;
8
+ const qaseTitleRegExp = /^@[Qq]ase[Tt]itle=(.+)$/g;
9
+ const qaseFieldsRegExp = /^@[Qq]ase[Ff]ields:(.+?)=(.+)$/g;
10
+ class Storage {
11
+ constructor() {
12
+ /**
13
+ * @type {Record<string, Pickle>}
14
+ * @private
15
+ */
16
+ this.pickles = {};
17
+ /**
18
+ * @type {Record<string, TestCaseStarted>}
19
+ * @private
20
+ */
21
+ this.testCaseStarts = {};
22
+ /**
23
+ * @type {Record<string, TestStepFinished>}
24
+ * @private
25
+ */
26
+ this.testCaseSteps = {};
27
+ /**
28
+ * @type {Record<string, TestStatusEnum>}
29
+ * @private
30
+ */
31
+ this.testCaseStartedResult = {};
32
+ /**
33
+ * @type {Record<string, string[]>}
34
+ * @private
35
+ */
36
+ this.testCaseStartedErrors = {};
37
+ /**
38
+ * @type {Record<string, string>}
39
+ * @private
40
+ */
41
+ this.testCases = {};
42
+ /**
43
+ * @type {Record<string, string>}
44
+ * @private
45
+ */
46
+ this.scenarios = {};
47
+ /**
48
+ * @type {Record<string, string>}
49
+ * @private
50
+ */
51
+ this.attachments = {};
52
+ }
53
+ /**
54
+ * Add pickle to storage
55
+ * @param {Pickle} pickle
56
+ */
57
+ addPickle(pickle) {
58
+ this.pickles[pickle.id] = pickle;
59
+ }
60
+ /**
61
+ * Add scenario to storage
62
+ * @param {GherkinDocument} document
63
+ */
64
+ addScenario(document) {
65
+ if (document.feature) {
66
+ const { children, name } = document.feature;
67
+ children.forEach(({ scenario }) => {
68
+ if (scenario) {
69
+ this.scenarios[scenario.id] = name;
70
+ }
71
+ });
72
+ }
73
+ }
74
+ /**
75
+ * Add attachment to storage
76
+ * @param {Attach} attachment
77
+ */
78
+ addAttachment(attachment) {
79
+ if (attachment.testCaseStartedId && attachment.fileName) {
80
+ if (!this.attachments[attachment.testCaseStartedId]) {
81
+ this.attachments[attachment.testCaseStartedId] = [];
82
+ }
83
+ this.attachments[attachment.testCaseStartedId]?.push({
84
+ file_name: attachment.fileName,
85
+ mime_type: attachment.mediaType,
86
+ file_path: null,
87
+ content: attachment.body,
88
+ size: 0,
89
+ id: attachment.fileName,
90
+ });
91
+ }
92
+ }
93
+ /**
94
+ * Add test case to storage
95
+ * @param {TestCase} testCase
96
+ */
97
+ addTestCase(testCase) {
98
+ this.testCases[testCase.id] = testCase;
99
+ }
100
+ /**
101
+ * Get all test cases
102
+ * @param {TestCaseStarted} testCaseStarted
103
+ */
104
+ addTestCaseStarted(testCaseStarted) {
105
+ this.testCaseStarts[testCaseStarted.id] =
106
+ testCaseStarted;
107
+ this.testCaseStartedResult[testCaseStarted.id] =
108
+ qase_javascript_commons_1.TestStatusEnum.passed;
109
+ }
110
+ /**
111
+ * Add test case step to storage
112
+ * @param {TestStepFinished} testCaseStep
113
+ */
114
+ addTestCaseStep(testCaseStep) {
115
+ const oldStatus = this.testCaseStartedResult[testCaseStep.testCaseStartedId];
116
+ const newStatus = Storage.statusMap[testCaseStep.testStepResult.status];
117
+ this.testCaseSteps[testCaseStep.testStepId] = testCaseStep;
118
+ if (newStatus !== qase_javascript_commons_1.TestStatusEnum.passed) {
119
+ if (testCaseStep.testStepResult.message) {
120
+ if (!this.testCaseStartedErrors[testCaseStep.testCaseStartedId]) {
121
+ this.testCaseStartedErrors[testCaseStep.testCaseStartedId] = [];
122
+ }
123
+ this.testCaseStartedErrors[testCaseStep.testCaseStartedId]?.push(testCaseStep.testStepResult.message);
124
+ }
125
+ if (oldStatus) {
126
+ if (oldStatus !== qase_javascript_commons_1.TestStatusEnum.failed) {
127
+ this.testCaseStartedResult[testCaseStep.testCaseStartedId] = newStatus;
128
+ }
129
+ }
130
+ else {
131
+ this.testCaseStartedResult[testCaseStep.testCaseStartedId] = newStatus;
132
+ }
133
+ }
134
+ }
135
+ /**
136
+ * Convert test case to test result
137
+ * @param {TestCaseFinished} testCase
138
+ * @returns {undefined | TestResultType}
139
+ */
140
+ convertTestCase(testCase) {
141
+ const tcs = this.testCaseStarts[testCase.testCaseStartedId];
142
+ if (!tcs) {
143
+ return undefined;
144
+ }
145
+ const tc = this.testCases[tcs.testCaseId];
146
+ if (!tc) {
147
+ return undefined;
148
+ }
149
+ const pickle = this.pickles[tc.pickleId];
150
+ if (!pickle) {
151
+ return undefined;
152
+ }
153
+ let error;
154
+ if (this.testCaseStartedErrors[tcs.id]?.length) {
155
+ error = new Error(this.testCaseStartedErrors[tcs.id]?.join('\n\n'));
156
+ }
157
+ let relations = null;
158
+ const nodeId = pickle.astNodeIds[pickle.astNodeIds.length - 1];
159
+ if (nodeId != undefined && this.scenarios[nodeId] != undefined) {
160
+ relations = {
161
+ suite: {
162
+ data: [
163
+ {
164
+ title: this.scenarios[nodeId] ?? '',
165
+ public_id: null,
166
+ },
167
+ ],
168
+ },
169
+ };
170
+ }
171
+ const metadata = this.parseTags(pickle.tags);
172
+ return {
173
+ attachments: [],
174
+ author: null,
175
+ execution: {
176
+ status: this.testCaseStartedResult[testCase.testCaseStartedId] ?? qase_javascript_commons_1.TestStatusEnum.passed,
177
+ start_time: null,
178
+ end_time: null,
179
+ duration: Math.abs(testCase.timestamp.seconds - tcs.timestamp.seconds),
180
+ stacktrace: error?.stack ?? null,
181
+ thread: null,
182
+ },
183
+ fields: metadata.fields,
184
+ message: null,
185
+ muted: false,
186
+ params: {},
187
+ relations: relations,
188
+ run_id: null,
189
+ signature: '',
190
+ steps: this.convertSteps(pickle.steps, tc),
191
+ testops_id: metadata.ids.length > 0 ? metadata.ids : null,
192
+ id: tcs.id,
193
+ title: metadata.title ?? pickle.name,
194
+ };
195
+ }
196
+ /**
197
+ * Convert test steps to test result steps
198
+ * @param {readonly PickleStep[]} steps
199
+ * @param {TestCase} testCase
200
+ * @returns {TestStepType[]}
201
+ * @private
202
+ */
203
+ convertSteps(steps, testCase) {
204
+ const results = [];
205
+ for (const s of testCase.testSteps) {
206
+ const finished = this.testCaseSteps[s.id];
207
+ if (!finished) {
208
+ continue;
209
+ }
210
+ const step = steps.find((step) => step.id === s.pickleStepId);
211
+ if (!step) {
212
+ continue;
213
+ }
214
+ const result = {
215
+ id: s.id,
216
+ step_type: qase_javascript_commons_1.StepType.GHERKIN,
217
+ data: {
218
+ keyword: step.text,
219
+ name: step.text,
220
+ line: 0,
221
+ },
222
+ execution: {
223
+ status: Storage.stepStatusMap[finished.testStepResult.status],
224
+ start_time: null,
225
+ end_time: null,
226
+ duration: finished.testStepResult.duration.seconds,
227
+ },
228
+ attachments: [],
229
+ steps: [],
230
+ parent_id: null,
231
+ };
232
+ results.push(result);
233
+ }
234
+ return results;
235
+ }
236
+ parseTags(tags) {
237
+ const metadata = {
238
+ ids: [],
239
+ fields: {},
240
+ title: null,
241
+ };
242
+ for (const tag of tags) {
243
+ if (qaseIdRegExp.test(tag.name)) {
244
+ metadata.ids.push(Number(tag.name.replace(/^@[Qq]-?/, '')));
245
+ continue;
246
+ }
247
+ if (newQaseIdRegExp.test(tag.name)) {
248
+ metadata.ids.push(Number(tag.name.replace(/^@[Qq]ase[Ii][Dd]=/, '')));
249
+ continue;
250
+ }
251
+ if (qaseTitleRegExp.test(tag.name)) {
252
+ metadata.title = tag.name.replace(/^@[Qq]ase[Tt]itle=/, '');
253
+ continue;
254
+ }
255
+ if (qaseFieldsRegExp.test(tag.name)) {
256
+ const value = tag.name.replace(/^@[Qq]ase[Ff]ields:/, '');
257
+ try {
258
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
259
+ const record = JSON.parse(value);
260
+ metadata.fields = { ...metadata.fields, ...record };
261
+ }
262
+ catch (e) {
263
+ // do nothing
264
+ }
265
+ }
266
+ }
267
+ return metadata;
268
+ }
269
+ }
270
+ exports.Storage = Storage;
271
+ /**
272
+ * @type {Record<TestStepResultStatus, TestStatusEnum>}
273
+ */
274
+ Storage.statusMap = {
275
+ [cucumber_1.Status.PASSED]: qase_javascript_commons_1.TestStatusEnum.passed,
276
+ [cucumber_1.Status.FAILED]: qase_javascript_commons_1.TestStatusEnum.failed,
277
+ [cucumber_1.Status.SKIPPED]: qase_javascript_commons_1.TestStatusEnum.skipped,
278
+ [cucumber_1.Status.AMBIGUOUS]: qase_javascript_commons_1.TestStatusEnum.failed,
279
+ [cucumber_1.Status.PENDING]: qase_javascript_commons_1.TestStatusEnum.skipped,
280
+ [cucumber_1.Status.UNDEFINED]: qase_javascript_commons_1.TestStatusEnum.skipped,
281
+ [cucumber_1.Status.UNKNOWN]: qase_javascript_commons_1.TestStatusEnum.skipped,
282
+ };
283
+ /**
284
+ * @type {Record<TestStepResultStatus, StepStatusEnum>}
285
+ */
286
+ Storage.stepStatusMap = {
287
+ [cucumber_1.Status.PASSED]: qase_javascript_commons_1.StepStatusEnum.passed,
288
+ [cucumber_1.Status.FAILED]: qase_javascript_commons_1.StepStatusEnum.failed,
289
+ [cucumber_1.Status.SKIPPED]: qase_javascript_commons_1.StepStatusEnum.skipped,
290
+ [cucumber_1.Status.AMBIGUOUS]: qase_javascript_commons_1.StepStatusEnum.failed,
291
+ [cucumber_1.Status.PENDING]: qase_javascript_commons_1.StepStatusEnum.skipped,
292
+ [cucumber_1.Status.UNDEFINED]: qase_javascript_commons_1.StepStatusEnum.skipped,
293
+ [cucumber_1.Status.UNKNOWN]: qase_javascript_commons_1.StepStatusEnum.skipped,
294
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cucumberjs-qase-reporter",
3
- "version": "2.0.0-beta.2",
3
+ "version": "2.0.0",
4
4
  "description": "Qase TMS CucumberJS Reporter",
5
5
  "homepage": "https://github.com/qase-tms/qase-javascript",
6
6
  "main": "./dist/index.js",