cypress-qase-reporter 2.3.1 → 3.0.0-beta.1

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
@@ -20,7 +20,7 @@ run the following steps:
20
20
  ```diff
21
21
  - import { qase } from 'cypress-qase-reporter/dist/mocha'
22
22
  + import { qase } from 'cypress-qase-reporter/mocha'
23
- ```
23
+ ```
24
24
 
25
25
  2. Update reporter configuration in `cypress.config.js` and/or environment variables —
26
26
  see the [configuration reference](#configuration) below.
@@ -76,6 +76,28 @@ run the following steps:
76
76
  }
77
77
  ...
78
78
 
79
+ ## Updating from v2.3.x to v3.0.0-beta.1
80
+
81
+ To update an existing test project using Qase reporter from version 2.3.x to version 3.0.0-beta.1,
82
+ run the following steps:
83
+
84
+ 1. Update reporter configuration in `cypress.config.js` file.
85
+
86
+ ```diff
87
+ + import { afterSpecHook } from 'cypress-qase-reporter/hooks';
88
+ ...
89
+ e2e: {
90
+ setupNodeEvents(on, config) {
91
+ require('cypress-qase-reporter/plugin')(on, config)
92
+ require('cypress-qase-reporter/metadata')(on)
93
+ + on('after:spec', async (spec, results) => {
94
+ + await afterSpecHook(spec, config);
95
+ + });
96
+ }
97
+ }
98
+ ...
99
+ ```
100
+
79
101
  ## Getting started
80
102
 
81
103
  The Cypress reporter can auto-generate test cases
@@ -167,6 +189,7 @@ Example `cypress.config.js` config:
167
189
 
168
190
  ```javascript
169
191
  import cypress from 'cypress';
192
+ import { afterSpecHook } from 'cypress-qase-reporter/hooks';
170
193
 
171
194
  module.exports = cypress.defineConfig({
172
195
  reporter: 'cypress-multi-reporters',
@@ -194,6 +217,7 @@ module.exports = cypress.defineConfig({
194
217
  framework: {
195
218
  cypress: {
196
219
  screenshotsFolder: 'cypress/screenshots',
220
+ videosFolder: 'cypress/videos',
197
221
  }
198
222
  }
199
223
  },
@@ -203,6 +227,9 @@ module.exports = cypress.defineConfig({
203
227
  setupNodeEvents(on, config) {
204
228
  require('cypress-qase-reporter/plugin')(on, config)
205
229
  require('cypress-qase-reporter/metadata')(on)
230
+ on('after:spec', async (spec, results) => {
231
+ await afterSpecHook(spec, config);
232
+ });
206
233
  },
207
234
  },
208
235
  });
@@ -215,6 +242,7 @@ If you use Cucumber, you need to add the following configuration in `cypress.con
215
242
 
216
243
  ```javascript
217
244
  import cypress from 'cypress';
245
+ import { afterSpecHook } from 'cypress-qase-reporter/hooks';
218
246
 
219
247
  const cucumber = require('cypress-cucumber-preprocessor').default;
220
248
 
@@ -254,11 +282,14 @@ module.exports = cypress.defineConfig({
254
282
  on('file:preprocessor', cucumber());
255
283
  require('cypress-qase-reporter/plugin')(on, config);
256
284
  require('cypress-qase-reporter/metadata')(on);
285
+ on('after:spec', async (spec, results) => {
286
+ await afterSpecHook(spec, config);
287
+ });
257
288
  },
258
289
  specPattern: 'cypress/e2e/*.feature',
259
290
  },
260
291
  });
261
- ```
292
+ ```
262
293
 
263
294
  And add the following lines in `support/e2e.js` file:
264
295
 
@@ -278,5 +309,3 @@ We maintain the reporter on [LTS versions of Node](https://nodejs.org/en/about/r
278
309
  `cypress >= 8.0.0`
279
310
 
280
311
  <!-- references -->
281
-
282
- [auth]: https://developers.qase.io/#authentication
package/changelog.md CHANGED
@@ -1,3 +1,10 @@
1
+ # cypress-qase-reporter@3.0.0-beta.1
2
+
3
+ ## What's new
4
+
5
+ - Added support for video attachments.
6
+ - Changed reporter configuration in `cypress.config.js` file.
7
+
1
8
  # cypress-qase-reporter@2.3.0
2
9
 
3
10
  ## What's new
@@ -16,7 +16,11 @@ exports.configSchema = {
16
16
  screenshotsFolder: {
17
17
  type: 'string',
18
18
  nullable: true,
19
- }
19
+ },
20
+ videosFolder: {
21
+ type: 'string',
22
+ nullable: true,
23
+ },
20
24
  }
21
25
  }
22
26
  }
@@ -9,5 +9,23 @@ export declare class FileSearcher {
9
9
  * @returns Array of absolute paths to the matching files.
10
10
  */
11
11
  static findFilesBeforeTime(screenshotFolderPath: string, specFileName: string, time: Date): string[];
12
+ /**
13
+ * Finds all mp4 video files in the given directory and its subdirectories
14
+ * that were created after the specified time.
15
+ *
16
+ * @param videoFolderPath Path to the folder with video files.
17
+ * @param specFileName Name of the spec file (without extension).
18
+ * @param time Time threshold as a Date object.
19
+ * @returns Array of absolute paths to the matching mp4 files.
20
+ */
21
+ static findVideoFilesBeforeTime(videoFolderPath: string, specFileName: string, time: Date): string[];
22
+ /**
23
+ * Finds all mp4 video files in the given directory and its subdirectories.
24
+ *
25
+ * @param videoFolderPath Path to the folder with video files.
26
+ * @param specFileName Name of the spec file (without extension).
27
+ * @returns Array of absolute paths to the matching mp4 files.
28
+ */
29
+ static findVideoFiles(videoFolderPath: string, specFileName: string): string[];
12
30
  private static findFolderByName;
13
31
  }
@@ -67,6 +67,74 @@ class FileSearcher {
67
67
  }
68
68
  return result;
69
69
  }
70
+ /**
71
+ * Finds all mp4 video files in the given directory and its subdirectories
72
+ * that were created after the specified time.
73
+ *
74
+ * @param videoFolderPath Path to the folder with video files.
75
+ * @param specFileName Name of the spec file (without extension).
76
+ * @param time Time threshold as a Date object.
77
+ * @returns Array of absolute paths to the matching mp4 files.
78
+ */
79
+ static findVideoFilesBeforeTime(videoFolderPath, specFileName, time) {
80
+ const absolutePath = path.resolve(process.cwd(), videoFolderPath);
81
+ const result = [];
82
+ const paths = this.findFolderByName(absolutePath, specFileName);
83
+ if (paths.length === 0) {
84
+ return result;
85
+ }
86
+ const searchVideoFiles = (dir) => {
87
+ if (!fs.existsSync(dir)) {
88
+ return;
89
+ }
90
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
91
+ for (const entry of entries) {
92
+ const entryPath = path.join(dir, entry.name);
93
+ if (entry.isDirectory()) {
94
+ searchVideoFiles(entryPath);
95
+ }
96
+ else if (entry.isFile()) {
97
+ // Check if the file is an mp4 video file
98
+ if (entry.name.toLowerCase().endsWith('.mp4')) {
99
+ const stats = fs.statSync(entryPath);
100
+ if (stats.birthtime > time) {
101
+ result.push(entryPath);
102
+ }
103
+ }
104
+ }
105
+ }
106
+ };
107
+ for (const path of paths) {
108
+ searchVideoFiles(path);
109
+ }
110
+ return result;
111
+ }
112
+ /**
113
+ * Finds all mp4 video files in the given directory and its subdirectories.
114
+ *
115
+ * @param videoFolderPath Path to the folder with video files.
116
+ * @param specFileName Name of the spec file (without extension).
117
+ * @returns Array of absolute paths to the matching mp4 files.
118
+ */
119
+ static findVideoFiles(videoFolderPath, specFileName) {
120
+ const absolutePath = path.resolve(process.cwd(), videoFolderPath);
121
+ const result = [];
122
+ if (!fs.existsSync(absolutePath)) {
123
+ return result;
124
+ }
125
+ const entries = fs.readdirSync(absolutePath, { withFileTypes: true });
126
+ for (const entry of entries) {
127
+ if (entry.isFile()) {
128
+ const fileName = entry.name;
129
+ // Check if the file is an mp4 video file with the expected format: {specFileName}.cy.js.mp4
130
+ if (fileName.toLowerCase().endsWith('.mp4') && fileName.startsWith(specFileName)) {
131
+ const entryPath = path.join(absolutePath, fileName);
132
+ result.push(entryPath);
133
+ }
134
+ }
135
+ }
136
+ return result;
137
+ }
70
138
  static findFolderByName(startPath, folderName) {
71
139
  const result = [];
72
140
  function searchDirectory(currentPath) {
package/dist/hooks.js CHANGED
@@ -1,8 +1,15 @@
1
1
  "use strict";
2
2
  /// <reference types="cypress" />
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
3
6
  Object.defineProperty(exports, "__esModule", { value: true });
4
7
  const qase_javascript_commons_1 = require("qase-javascript-commons");
5
8
  const configSchema_1 = require("./configSchema");
9
+ const resultsManager_1 = require("./metadata/resultsManager");
10
+ const fileSearcher_1 = require("./fileSearcher");
11
+ const uuid_1 = require("uuid");
12
+ const path_1 = __importDefault(require("path"));
6
13
  async function beforeRunHook(options) {
7
14
  const configLoader = new qase_javascript_commons_1.ConfigLoader(configSchema_1.configSchema);
8
15
  const config = configLoader.load();
@@ -34,7 +41,50 @@ async function afterRunHook(options) {
34
41
  });
35
42
  await reporter.complete();
36
43
  }
44
+ async function afterSpecHook(spec, options) {
45
+ const results = resultsManager_1.ResultsManager.getResults();
46
+ if (results) {
47
+ const configLoader = new qase_javascript_commons_1.ConfigLoader(configSchema_1.configSchema);
48
+ const config = configLoader.load();
49
+ const { reporterOptions } = options;
50
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
51
+ const { ...composedOptions } = (0, qase_javascript_commons_1.composeOptions)(reporterOptions['cypressQaseReporterReporterOptions'], config);
52
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
53
+ const reporter = qase_javascript_commons_1.QaseReporter.getInstance({
54
+ ...composedOptions,
55
+ frameworkPackage: 'cypress',
56
+ frameworkName: 'cypress',
57
+ reporterName: 'cypress-qase-reporter',
58
+ });
59
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
60
+ const videosFolder = composedOptions.framework?.cypress?.videosFolder;
61
+ const specFileName = path_1.default.basename(spec.name, '.cy.js');
62
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
63
+ const videoFiles = videosFolder ? fileSearcher_1.FileSearcher.findVideoFiles(videosFolder, specFileName) : [];
64
+ if (videoFiles.length > 0) {
65
+ results.forEach((result) => {
66
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
67
+ result.attachments = [...(result.attachments ?? []), ...videoFiles.map((file) => {
68
+ const attachment = {
69
+ content: '',
70
+ id: (0, uuid_1.v4)(),
71
+ mime_type: 'video/mp4',
72
+ size: 0,
73
+ file_name: path_1.default.basename(file),
74
+ file_path: file,
75
+ };
76
+ return attachment;
77
+ })];
78
+ });
79
+ }
80
+ // eslint-disable-next-line @typescript-eslint/await-thenable
81
+ await reporter.setTestResults(results);
82
+ await reporter.sendResults();
83
+ }
84
+ resultsManager_1.ResultsManager.clear();
85
+ }
37
86
  module.exports = {
38
87
  beforeRunHook,
39
88
  afterRunHook,
89
+ afterSpecHook,
40
90
  };
@@ -1,2 +1,3 @@
1
1
  export { qase } from './mocha';
2
2
  export { enableCucumberSupport } from './cucumber';
3
+ export { ResultsManager } from './metadata/resultsManager';
package/dist/index.cjs.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.enableCucumberSupport = exports.qase = void 0;
3
+ exports.ResultsManager = exports.enableCucumberSupport = exports.qase = void 0;
4
4
  var mocha_1 = require("./mocha");
5
5
  Object.defineProperty(exports, "qase", { enumerable: true, get: function () { return mocha_1.qase; } });
6
6
  var cucumber_1 = require("./cucumber");
7
7
  Object.defineProperty(exports, "enableCucumberSupport", { enumerable: true, get: function () { return cucumber_1.enableCucumberSupport; } });
8
+ var resultsManager_1 = require("./metadata/resultsManager");
9
+ Object.defineProperty(exports, "ResultsManager", { enumerable: true, get: function () { return resultsManager_1.ResultsManager; } });
@@ -0,0 +1,8 @@
1
+ import { TestResultType } from 'qase-javascript-commons';
2
+ export declare class ResultsManager {
3
+ static resultsPath: string;
4
+ static getResults(): TestResultType[] | undefined;
5
+ static setResults(results: TestResultType[]): void;
6
+ static clear(): void;
7
+ private static isExists;
8
+ }
@@ -0,0 +1,46 @@
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.ResultsManager = void 0;
7
+ const fs_1 = require("fs");
8
+ const path_1 = __importDefault(require("path"));
9
+ class ResultsManager {
10
+ static resultsPath = path_1.default.resolve(__dirname, 'qaseResults');
11
+ static getResults() {
12
+ if (!this.isExists()) {
13
+ return undefined;
14
+ }
15
+ let results = [];
16
+ try {
17
+ results = JSON.parse((0, fs_1.readFileSync)(this.resultsPath, 'utf8'));
18
+ }
19
+ catch (error) {
20
+ console.log('Error reading results file', error);
21
+ }
22
+ return results;
23
+ }
24
+ static setResults(results) {
25
+ try {
26
+ (0, fs_1.writeFileSync)(this.resultsPath, JSON.stringify(results));
27
+ }
28
+ catch (error) {
29
+ console.log('Error writing results file', error);
30
+ }
31
+ }
32
+ static clear() {
33
+ if (this.isExists()) {
34
+ try {
35
+ (0, fs_1.unlinkSync)(this.resultsPath);
36
+ }
37
+ catch (error) {
38
+ console.log('Error clearing results file', error);
39
+ }
40
+ }
41
+ }
42
+ static isExists() {
43
+ return (0, fs_1.existsSync)(this.resultsPath);
44
+ }
45
+ }
46
+ exports.ResultsManager = ResultsManager;
package/dist/options.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export type ReporterOptionsType = {
2
2
  screenshotsFolder?: string;
3
+ videosFolder?: string;
3
4
  };
@@ -1,6 +1,6 @@
1
1
  /// <reference types="cypress" />
2
2
  import { MochaOptions, reporters, Runner } from 'mocha';
3
- import { ConfigLoader, FrameworkOptionsType, TestStatusEnum } from 'qase-javascript-commons';
3
+ import { ConfigLoader, TestStatusEnum } from 'qase-javascript-commons';
4
4
  import { ReporterOptionsType } from './options';
5
5
  type CypressState = 'failed' | 'passed' | 'pending';
6
6
  export type CypressQaseOptionsType = Omit<MochaOptions, 'reporterOptions'> & {
@@ -36,13 +36,12 @@ export declare class CypressQaseReporter extends reporters.Base {
36
36
  */
37
37
  private reporter;
38
38
  private testBeginTime;
39
- private options;
40
39
  /**
41
40
  * @param {Runner} runner
42
41
  * @param {CypressQaseOptionsType} options
43
42
  * @param {ConfigLoaderInterface} configLoader
44
43
  */
45
- constructor(runner: Runner, options: CypressQaseOptionsType, configLoader?: ConfigLoader<FrameworkOptionsType<"cypress", ReporterOptionsType>>);
44
+ constructor(runner: Runner, options: CypressQaseOptionsType, configLoader?: ConfigLoader<import("qase-javascript-commons").FrameworkOptionsType<"cypress", ReporterOptionsType>>);
46
45
  /**
47
46
  * @param {Runner} runner
48
47
  * @private
package/dist/reporter.js CHANGED
@@ -6,13 +6,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.CypressQaseReporter = void 0;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const uuid_1 = require("uuid");
9
- const child_process_1 = require("child_process");
9
+ // import { spawnSync } from 'child_process';
10
10
  const mocha_1 = require("mocha");
11
11
  const qase_javascript_commons_1 = require("qase-javascript-commons");
12
12
  const configSchema_1 = require("./configSchema");
13
13
  const manager_1 = require("./metadata/manager");
14
14
  const fileSearcher_1 = require("./fileSearcher");
15
15
  const tagParser_1 = require("./utils/tagParser");
16
+ const resultsManager_1 = require("./metadata/resultsManager");
16
17
  const { EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_TEST_PENDING, EVENT_RUN_END, EVENT_TEST_BEGIN, } = mocha_1.Runner.constants;
17
18
  /**
18
19
  * @class CypressQaseReporter
@@ -45,13 +46,18 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
45
46
  * @private
46
47
  */
47
48
  screenshotsFolder;
49
+ // /**
50
+ // * @type {string | undefined}
51
+ // * @private
52
+ // */
53
+ // private videosFolder: string | undefined;
48
54
  /**
49
55
  * @type {ReporterInterface}
50
56
  * @private
51
57
  */
52
58
  reporter;
53
59
  testBeginTime = Date.now();
54
- options;
60
+ // private options: Omit<(FrameworkOptionsType<'cypress', ReporterOptionsType> & ConfigType & ReporterOptionsType & NonNullable<unknown>) | (null & ReporterOptionsType & NonNullable<unknown>), 'framework'>;
55
61
  /**
56
62
  * @param {Runner} runner
57
63
  * @param {CypressQaseOptionsType} options
@@ -63,7 +69,8 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
63
69
  const config = configLoader.load();
64
70
  const { framework, ...composedOptions } = (0, qase_javascript_commons_1.composeOptions)(reporterOptions, config);
65
71
  this.screenshotsFolder = framework?.cypress?.screenshotsFolder;
66
- this.options = composedOptions;
72
+ // this.videosFolder = framework?.cypress?.videosFolder;
73
+ // this.options = composedOptions;
67
74
  this.reporter = qase_javascript_commons_1.QaseReporter.getInstance({
68
75
  ...composedOptions,
69
76
  frameworkPackage: 'cypress',
@@ -87,13 +94,14 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
87
94
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
88
95
  runner.once(EVENT_RUN_END, () => {
89
96
  const results = this.reporter.getResults();
90
- (0, child_process_1.spawnSync)('node', [`${__dirname}/child.js`], {
91
- stdio: 'inherit',
92
- env: Object.assign(process.env, {
93
- reporterConfig: JSON.stringify(this.options),
94
- results: JSON.stringify(results),
95
- }),
96
- });
97
+ resultsManager_1.ResultsManager.setResults(results);
98
+ // spawnSync('node', [`${__dirname}/child.js`], {
99
+ // stdio: 'inherit',
100
+ // env: Object.assign(process.env, {
101
+ // reporterConfig: JSON.stringify(this.options),
102
+ // results: JSON.stringify(results),
103
+ // }),
104
+ // });
97
105
  });
98
106
  }
99
107
  /**
@@ -113,6 +121,9 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
113
121
  const files = this.screenshotsFolder ?
114
122
  fileSearcher_1.FileSearcher.findFilesBeforeTime(this.screenshotsFolder, testFileName, new Date(this.testBeginTime))
115
123
  : [];
124
+ // const videos = this.videosFolder ?
125
+ // FileSearcher.findVideoFiles(this.videosFolder, testFileName)
126
+ // : [];
116
127
  const attachments = files.map((file) => ({
117
128
  content: '',
118
129
  id: (0, uuid_1.v4)(),
@@ -121,6 +132,14 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
121
132
  file_name: path_1.default.basename(file),
122
133
  file_path: file,
123
134
  }));
135
+ // const videoAttachments = videos.map((file) => ({
136
+ // content: '',
137
+ // id: uuidv4(),
138
+ // mime_type: 'video/mp4',
139
+ // size: 0,
140
+ // file_name: path.basename(file),
141
+ // file_path: file,
142
+ // } as Attachment));
124
143
  attachments.push(...(metadata?.attachments ?? []));
125
144
  let relations = {};
126
145
  if (test.parent !== undefined) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cypress-qase-reporter",
3
- "version": "2.3.1",
3
+ "version": "3.0.0-beta.1",
4
4
  "description": "Qase Cypress Reporter",
5
5
  "homepage": "https://github.com/qase-tms/qase-javascript",
6
6
  "sideEffects": false,