cypress-qase-reporter 2.0.0-beta.0 → 2.0.0-beta.2

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,65 +1,64 @@
1
- > # Qase TMS Cypress reporter
2
- >
3
- > Publish results simple and easy.
1
+ # Qase TMS Cypress reporter
4
2
 
5
- ## How to integrate
3
+ Publish results simple and easy.
4
+
5
+ ## How to install
6
6
 
7
7
  ```
8
- npm install cypress-qase-reporter
8
+ npm install -D cypress-qase-reporter@beta
9
9
  ```
10
10
 
11
- ## Example of usage
11
+ ## Getting started
12
+
13
+ The Cypress reporter can auto-generate test cases
14
+ and suites from your test data.
15
+ Test results of subsequent test runs will match the same test cases
16
+ as long as their names and file paths don't change.
12
17
 
13
- If you want to decorate come test with Qase Case ID you could use qase function:
18
+ You can also annotate the tests with the IDs of existing test cases
19
+ from Qase.io before executing tests. It's a more reliable way to bind
20
+ autotests to test cases, that persists when you rename, move, or
21
+ parameterize your tests.
22
+
23
+ For example:
14
24
 
15
25
  ```typescript
16
- import { qase } from 'cypress-qase-reporter';
26
+ import { qase } from 'cypress-qase-reporter/mocha';
17
27
 
18
28
  describe('My First Test', () => {
19
- qase([1,2],
20
- it('Several ids', () => {
29
+ qase(1,
30
+ it('Several ids', () => {
21
31
  expect(true).to.equal(true);
22
- })
23
- );
24
- qase(3,
25
- it('Correct test', () => {
32
+ })
33
+ );
34
+ // a test can check multiple test cases
35
+ qase([2, 3],
36
+ it('Correct test', () => {
26
37
  expect(true).to.equal(true);
27
- })
28
- );
29
- qase(4,
30
- it.skip('Skipped test', () => {
38
+ })
39
+ );
40
+ qase(4,
41
+ it.skip('Skipped test', () => {
31
42
  expect(true).to.equal(true);
32
- })
33
- );
34
- qase(5,
35
- it('Failed test', () => {
36
- expect(true).to.equal(false);
37
- })
38
- );
43
+ })
44
+ );
39
45
  });
40
-
41
- ```
42
- If you are going to use several specifications for execution and you have in config
43
- ```json
44
- "testops": {
45
- "run": {
46
- "complete": true
47
- }
48
- }
49
- ```
50
- then it is necessary to additionally set in the project settings
51
- ```
52
- Allow to add results for cases in closed runs.
53
46
  ```
54
47
 
55
- To run tests and create a test run, execute the command (for example from folder examples):
48
+ To execute Cypress tests and report them to Qase.io, run the command:
49
+
56
50
  ```bash
57
51
  QASE_MODE=testops npx cypress run
58
52
  ```
53
+
59
54
  or
55
+
60
56
  ```bash
61
57
  npm test
62
58
  ```
59
+
60
+ You can try it with the example project at [`examples/cypress`](../examples/cypress/).
61
+
63
62
  <p align="center">
64
63
  <img width="65%" src="./screenshots/screenshot.png">
65
64
  </p>
@@ -72,39 +71,65 @@ https://app.qase.io/run/QASE_PROJECT_CODE
72
71
 
73
72
  ## Configuration
74
73
 
75
- Reporter options (* - required):
76
-
77
- - `mode` - `testops`/`off` Enables reporter, default - `off`
78
- - `debug` - Enables debug logging, defaule - `false`
79
- - `environment` - To execute with the sending of the envinroment information
80
- - *`testops.api.token` - Token for API access, you can find more information
81
- [here](https://developers.qase.io/#authentication)
82
- - *`testops.project` - Code of your project (can be extracted from main
83
- page of your project: `https://app.qase.io/project/DEMOTR` -
84
- `DEMOTR` is project code here)
85
- - `testops.uploadAttachments` - Permission to send screenshots to Qase TMS
86
- - `testops.run.id` - Pass Run ID
87
- - `testops.run.title` - Set custom Run name, when new run is created
88
- - `testops.run.description` - Set custom Run description, when new run is created
89
- - `testops.run.complete` - Whether the run should be completed
90
- - `framework.cypress.screenshotFolder` - Folder for save screenshot cypress
91
-
92
- #### You can check example configuration with multiple reporters in [demo project](../examples/cypress/cypress.config.js).
93
-
94
- Supported ENV variables:
95
-
96
- - `QASE_MODE` - Same as `mode`
97
- - `QASE_DEBUG` - Same as `debug`
98
- - `QASE_ENVIRONMENT` - Same as `environment`
99
- - `QASE_TESTOPS_API_TOKEN` - Same as `testops.api.token`
100
- - `QASE_TESTOPS_PROJECT` - Same as `testops.project`
101
- - `QASE_TESTOPS_RUN_ID` - Pass Run ID from ENV and override reporter option `testops.run.id`
102
- - `QASE_TESTOPS_RUN_TITLE` - Same as `testops.run.title`
103
- - `QASE_TESTOPS_RUN_DESCRIPTION` - Same as `testops.run.description`
74
+ Qase Cypress reporter can be configured in multiple ways:
75
+ - by adding configuration block in `cypress.config.js`,
76
+ - using a separate config file `qase.config.json`,
77
+ - using environment variables (they override the values from the configuration files).
78
+
79
+ For a full list of configuration options, see the [Configuration reference](../qase-javascript-commons/README.md#configuration).
80
+
81
+ Example `cypress.config.js` config:
82
+
83
+ ```js
84
+ import cypress from 'cypress';
85
+
86
+ import plugins from './cypress/plugins/index.js';
87
+
88
+ module.exports = cypress.defineConfig({
89
+ reporter: 'cypress-multi-reporters',
90
+ reporterOptions: {
91
+ reporterEnabled: 'cypress-mochawesome-reporter, cypress-qase-reporter',
92
+ cypressMochawesomeReporterReporterOptions: {
93
+ charts: true,
94
+ },
95
+ cypressQaseReporterReporterOptions: {
96
+ debug: true,
97
+
98
+ testops: {
99
+ api: {
100
+ token: 'api_key',
101
+ },
102
+
103
+ project: 'project_code',
104
+ uploadAttachments: true,
105
+
106
+ run: {
107
+ complete: true,
108
+ },
109
+ },
110
+
111
+ framework: {
112
+ cypress: {
113
+ screenshotsFolder: 'cypress/screenshots',
114
+ }
115
+ }
116
+ },
117
+ },
118
+ video: false,
119
+ e2e: {
120
+ setupNodeEvents(on, config) {
121
+ return plugins(on, config);
122
+ },
123
+ },
124
+ });
125
+ ```
126
+
127
+ Check out the example of configuration for multiple reporters in the
128
+ [demo project](../examples/cypress/cypress.config.js).
104
129
 
105
130
  ## Requirements
106
131
 
107
- 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/)
132
+ We maintain the reporter on [LTS versions of Node](https://nodejs.org/en/about/releases/).
108
133
 
109
134
  `cypress >= 8.0.0`
110
135
 
@@ -0,0 +1,4 @@
1
+ import { JSONSchemaType } from 'ajv';
2
+ import { FrameworkOptionsType } from 'qase-javascript-commons';
3
+ import { ReporterOptionsType } from './options';
4
+ export declare const configSchema: JSONSchemaType<FrameworkOptionsType<'cypress', ReporterOptionsType>>;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.configSchema = void 0;
4
+ exports.configSchema = {
5
+ type: 'object',
6
+ nullable: true,
7
+ properties: {
8
+ framework: {
9
+ type: 'object',
10
+ nullable: true,
11
+ properties: {
12
+ cypress: {
13
+ type: 'object',
14
+ nullable: true,
15
+ properties: {
16
+ screenshotsFolder: {
17
+ type: 'string',
18
+ nullable: true,
19
+ }
20
+ }
21
+ }
22
+ }
23
+ }
24
+ }
25
+ };
@@ -0,0 +1 @@
1
+ export { qase } from './mocha';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.qase = void 0;
4
+ var mocha_1 = require("./mocha");
5
+ Object.defineProperty(exports, "qase", { enumerable: true, get: function () { return mocha_1.qase; } });
@@ -0,0 +1,2 @@
1
+ import { CypressQaseReporter } from './reporter';
2
+ export = CypressQaseReporter;
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ const reporter_1 = require("./reporter");
3
+ module.exports = reporter_1.CypressQaseReporter;
@@ -0,0 +1,2 @@
1
+ import { Test } from 'mocha';
2
+ export declare const qase: (caseId: number | string | number[] | string[], test: Test) => Test;
package/dist/mocha.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.qase = void 0;
4
+ const qase = (caseId, test) => {
5
+ const caseIds = Array.isArray(caseId) ? caseId : [caseId];
6
+ test.title = `${test.title} (Qase ID: ${caseIds.join(',')})`;
7
+ return test;
8
+ };
9
+ exports.qase = qase;
@@ -0,0 +1,3 @@
1
+ export type ReporterOptionsType = {
2
+ screenshotsFolder?: string;
3
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,65 @@
1
+ import { MochaOptions, reporters, Runner } from 'mocha';
2
+ import { ConfigLoader, TestStatusEnum } from 'qase-javascript-commons';
3
+ import { ReporterOptionsType } from './options';
4
+ type CypressState = 'failed' | 'passed' | 'pending';
5
+ export type CypressQaseOptionsType = Omit<MochaOptions, 'reporterOptions'> & {
6
+ reporterOptions: ReporterOptionsType;
7
+ };
8
+ /**
9
+ * @class CypressQaseReporter
10
+ * @extends reporters.Base
11
+ */
12
+ export declare class CypressQaseReporter extends reporters.Base {
13
+ /**
14
+ * @type {RegExp}
15
+ */
16
+ static qaseIdRegExp: RegExp;
17
+ /**
18
+ * @type {Record<CypressState, TestStatusEnum>}
19
+ */
20
+ static statusMap: Record<CypressState, TestStatusEnum>;
21
+ /**
22
+ * @param {string} title
23
+ * @returns {number[]}
24
+ * @private
25
+ */
26
+ private static getCaseId;
27
+ /**
28
+ * @param {number[]} ids
29
+ * @param {string} dir
30
+ * @returns {Attachment[]}
31
+ * @private
32
+ */
33
+ private static findAttachments;
34
+ /**
35
+ * @type {string | undefined}
36
+ * @private
37
+ */
38
+ private screenshotsFolder;
39
+ /**
40
+ * @type {ReporterInterface}
41
+ * @private
42
+ */
43
+ private reporter;
44
+ /**
45
+ * @param {Runner} runner
46
+ * @param {CypressQaseOptionsType} options
47
+ * @param {ConfigLoaderInterface} configLoader
48
+ */
49
+ constructor(runner: Runner, options: CypressQaseOptionsType, configLoader?: ConfigLoader<import("qase-javascript-commons").FrameworkOptionsType<"cypress", ReporterOptionsType>>);
50
+ /**
51
+ * @param {Runner} runner
52
+ * @private
53
+ */
54
+ private addRunnerListeners;
55
+ /**
56
+ * @param {Test} test
57
+ * @private
58
+ */
59
+ private addTestResult;
60
+ /**
61
+ * @private
62
+ */
63
+ private preventExit;
64
+ }
65
+ export {};
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CypressQaseReporter = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const uuid_1 = require("uuid");
9
+ const mocha_1 = require("mocha");
10
+ const qase_javascript_commons_1 = require("qase-javascript-commons");
11
+ const traverse_dir_1 = require("./utils/traverse-dir");
12
+ const configSchema_1 = require("./configSchema");
13
+ const { EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_TEST_PENDING, EVENT_RUN_END, EVENT_RUN_BEGIN, } = mocha_1.Runner.constants;
14
+ /**
15
+ * @class CypressQaseReporter
16
+ * @extends reporters.Base
17
+ */
18
+ class CypressQaseReporter extends mocha_1.reporters.Base {
19
+ /**
20
+ * @param {string} title
21
+ * @returns {number[]}
22
+ * @private
23
+ */
24
+ static getCaseId(title) {
25
+ const [, ids] = title.match(CypressQaseReporter.qaseIdRegExp) ?? [];
26
+ return ids ? ids.split(',').map((id) => Number(id)) : [];
27
+ }
28
+ /**
29
+ * @param {number[]} ids
30
+ * @param {string} dir
31
+ * @returns {Attachment[]}
32
+ * @private
33
+ */
34
+ static findAttachments(ids, dir) {
35
+ const idSet = new Set(ids);
36
+ const attachments = [];
37
+ try {
38
+ (0, traverse_dir_1.traverseDir)(path_1.default.join(process.cwd(), dir), (filePath) => {
39
+ if (CypressQaseReporter.getCaseId(filePath).some((item) => idSet.has(item))) {
40
+ attachments.push({
41
+ content: '',
42
+ id: (0, uuid_1.v4)(),
43
+ mime_type: '', size: 0,
44
+ file_name: path_1.default.basename(filePath),
45
+ file_path: filePath,
46
+ });
47
+ }
48
+ });
49
+ }
50
+ catch (error) { /* ignore */
51
+ }
52
+ return attachments;
53
+ }
54
+ /**
55
+ * @param {Runner} runner
56
+ * @param {CypressQaseOptionsType} options
57
+ * @param {ConfigLoaderInterface} configLoader
58
+ */
59
+ constructor(runner, options, configLoader = new qase_javascript_commons_1.ConfigLoader(configSchema_1.configSchema)) {
60
+ super(runner, options);
61
+ const { reporterOptions } = options;
62
+ const config = configLoader.load();
63
+ const { framework, ...composedOptions } = (0, qase_javascript_commons_1.composeOptions)(reporterOptions, config);
64
+ this.screenshotsFolder = framework?.cypress?.screenshotsFolder;
65
+ this.reporter = new qase_javascript_commons_1.QaseReporter({
66
+ ...composedOptions,
67
+ frameworkPackage: 'cypress',
68
+ frameworkName: 'cypress',
69
+ reporterName: 'cypress-qase-reporter',
70
+ });
71
+ this.addRunnerListeners(runner);
72
+ }
73
+ /**
74
+ * @param {Runner} runner
75
+ * @private
76
+ */
77
+ addRunnerListeners(runner) {
78
+ runner.on(EVENT_TEST_PASS, (test) => this.addTestResult(test));
79
+ runner.on(EVENT_TEST_PENDING, (test) => this.addTestResult(test));
80
+ runner.on(EVENT_TEST_FAIL, (test) => this.addTestResult(test));
81
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
82
+ runner.on(EVENT_RUN_BEGIN, () => this.reporter.startTestRun());
83
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
84
+ runner.once(EVENT_RUN_END, async () => {
85
+ this.preventExit();
86
+ await this.reporter.publish();
87
+ if (process.exitCode !== undefined) {
88
+ process.exit(process.exitCode);
89
+ }
90
+ });
91
+ }
92
+ /**
93
+ * @param {Test} test
94
+ * @private
95
+ */
96
+ addTestResult(test) {
97
+ const ids = CypressQaseReporter.getCaseId(test.title);
98
+ const attachments = this.screenshotsFolder
99
+ ? CypressQaseReporter.findAttachments(ids, this.screenshotsFolder)
100
+ : undefined;
101
+ const result = {
102
+ attachments: attachments ?? [],
103
+ author: null,
104
+ fields: {},
105
+ message: test.err?.message ?? null,
106
+ muted: false,
107
+ params: {},
108
+ relations: {},
109
+ run_id: null,
110
+ signature: '',
111
+ steps: [],
112
+ id: test.id,
113
+ execution: {
114
+ status: test.state
115
+ ? CypressQaseReporter.statusMap[test.state]
116
+ : qase_javascript_commons_1.TestStatusEnum.invalid,
117
+ start_time: null,
118
+ end_time: null,
119
+ duration: test.duration ?? 0,
120
+ stacktrace: test.err?.stack ?? null,
121
+ thread: null,
122
+ },
123
+ testops_id: ids.length > 0 ? ids : null,
124
+ title: test.title,
125
+ // suiteTitle: test.parent?.titlePath(),
126
+ };
127
+ void this.reporter.addTestResult(result);
128
+ }
129
+ /**
130
+ * @private
131
+ */
132
+ preventExit() {
133
+ // eslint-disable-next-line @typescript-eslint/unbound-method
134
+ const _exit = process.exit;
135
+ const mutableProcess = process;
136
+ mutableProcess.exit = (code) => {
137
+ process.exitCode = code || 0;
138
+ process.exit = _exit;
139
+ };
140
+ }
141
+ }
142
+ exports.CypressQaseReporter = CypressQaseReporter;
143
+ /**
144
+ * @type {RegExp}
145
+ */
146
+ CypressQaseReporter.qaseIdRegExp = /\(Qase ID:? ([\d,]+)\)/;
147
+ /**
148
+ * @type {Record<CypressState, TestStatusEnum>}
149
+ */
150
+ CypressQaseReporter.statusMap = {
151
+ failed: qase_javascript_commons_1.TestStatusEnum.failed,
152
+ passed: qase_javascript_commons_1.TestStatusEnum.passed,
153
+ pending: qase_javascript_commons_1.TestStatusEnum.blocked,
154
+ };
@@ -0,0 +1 @@
1
+ export declare const traverseDir: (dirPath: string, callback: (filePath: string) => void) => void;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.traverseDir = void 0;
7
+ const fs_1 = require("fs");
8
+ const path_1 = __importDefault(require("path"));
9
+ // TODO: get rid of recursion
10
+ const traverseDir = (dirPath, callback) => {
11
+ const items = (0, fs_1.readdirSync)(dirPath, { withFileTypes: true });
12
+ items.forEach((item) => {
13
+ const itemPath = path_1.default.join(dirPath, item.name);
14
+ if (item.isFile()) {
15
+ callback(itemPath);
16
+ }
17
+ else if (item.isDirectory()) {
18
+ try {
19
+ (0, exports.traverseDir)(itemPath, callback);
20
+ }
21
+ catch (error) { /* ignore */ }
22
+ }
23
+ });
24
+ };
25
+ exports.traverseDir = traverseDir;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cypress-qase-reporter",
3
- "version": "2.0.0-beta.0",
3
+ "version": "2.0.0-beta.2",
4
4
  "description": "Qase Cypress Reporter",
5
5
  "homepage": "https://github.com/qase-tms/qase-javascript",
6
6
  "sideEffects": false,
@@ -44,7 +44,8 @@
44
44
  "author": "Nikita Fedorov <nik333r@gmail.com>",
45
45
  "license": "Apache-2.0",
46
46
  "dependencies": {
47
- "qase-javascript-commons": "^2.0.0-beta.0"
47
+ "qase-javascript-commons": "^2.0.0-beta.8",
48
+ "uuid": "^9.0.1"
48
49
  },
49
50
  "peerDependencies": {
50
51
  "cypress": ">=8.0.0"