@testomatio/reporter 1.3.1-beta → 1.3.1-beta.1-exclude-skipped-tests

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.
Files changed (47) hide show
  1. package/README.md +61 -54
  2. package/lib/adapter/codecept.js +96 -35
  3. package/lib/adapter/cucumber/current.js +19 -12
  4. package/lib/adapter/cucumber/legacy.js +7 -6
  5. package/lib/adapter/cucumber.js +2 -2
  6. package/lib/adapter/cypress-plugin/index.js +53 -26
  7. package/lib/adapter/jasmine.js +2 -2
  8. package/lib/adapter/jest.js +50 -17
  9. package/lib/adapter/mocha.js +110 -58
  10. package/lib/adapter/playwright.js +95 -33
  11. package/lib/adapter/webdriver.js +47 -2
  12. package/lib/bin/reportXml.js +22 -16
  13. package/lib/bin/startTest.js +27 -6
  14. package/lib/client.js +185 -62
  15. package/lib/config.js +34 -0
  16. package/lib/constants.js +32 -7
  17. package/lib/data-storage.js +203 -0
  18. package/lib/fileUploader.js +135 -54
  19. package/lib/junit-adapter/adapter.js +0 -2
  20. package/lib/junit-adapter/csharp.js +3 -4
  21. package/lib/junit-adapter/index.js +3 -3
  22. package/lib/junit-adapter/java.js +35 -17
  23. package/lib/junit-adapter/javascript.js +1 -2
  24. package/lib/junit-adapter/python.js +12 -14
  25. package/lib/junit-adapter/ruby.js +1 -2
  26. package/lib/pipe/csv.js +37 -31
  27. package/lib/pipe/github.js +27 -39
  28. package/lib/pipe/gitlab.js +22 -26
  29. package/lib/pipe/html.js +361 -0
  30. package/lib/pipe/index.js +3 -1
  31. package/lib/pipe/testomatio.js +337 -54
  32. package/lib/reporter-functions.js +46 -0
  33. package/lib/reporter.js +11 -9
  34. package/lib/services/artifacts.js +57 -0
  35. package/lib/services/index.js +13 -0
  36. package/lib/services/key-values.js +58 -0
  37. package/lib/services/logger.js +311 -0
  38. package/lib/template/emptyData.svg +23 -0
  39. package/lib/template/testomatio.hbs +1421 -0
  40. package/lib/utils/pipe_utils.js +129 -0
  41. package/lib/{util.js → utils/utils.js} +147 -17
  42. package/lib/xmlReader.js +218 -122
  43. package/package.json +17 -9
  44. package/lib/_ArtifactStorageOld.js +0 -142
  45. package/lib/artifactStorage.js +0 -25
  46. package/lib/dataStorage.js +0 -244
  47. package/lib/logger.js +0 -301
@@ -1,7 +1,7 @@
1
1
  // eslint-disable-next-line global-require, import/no-extraneous-dependencies
2
2
  const WDIOReporter = require('@wdio/reporter').default;
3
3
  const TestomatClient = require('../client');
4
- const { parseTest } = require('../util');
4
+ const { getTestomatIdFromTestTitle } = require('../utils/utils');
5
5
 
6
6
  class WebdriverReporter extends WDIOReporter {
7
7
  constructor(options) {
@@ -31,12 +31,24 @@ class WebdriverReporter extends WDIOReporter {
31
31
  this._addTestPromises.push(this.addTest(test));
32
32
  }
33
33
 
34
+ // wdio-cucumber does not trigger onTestEnd hook, thus, using this one
35
+ /**
36
+ *
37
+ * @param {} scerario
38
+ * @returns
39
+ */
40
+ onSuiteEnd(scerario) {
41
+ if (scerario.type === 'scenario') {
42
+ this._addTestPromises.push(this.addBddScenario(scerario));
43
+ }
44
+ }
45
+
34
46
  async addTest(test) {
35
47
  if (!this.client) return;
36
48
 
37
49
  const { title, _duration: duration, state, error, output } = test;
38
50
 
39
- const testId = parseTest(title);
51
+ const testId = getTestomatIdFromTestTitle(title);
40
52
 
41
53
  const screenshotEndpoint = '/session/:sessionId/screenshot';
42
54
  const screenshotsBuffers = output
@@ -51,6 +63,39 @@ class WebdriverReporter extends WDIOReporter {
51
63
  filesBuffers: screenshotsBuffers,
52
64
  });
53
65
  }
66
+
67
+ /**
68
+ * @param {import('../../types').WebdriverIOScenario} scenario
69
+ */
70
+ addBddScenario(scenario) {
71
+ if (!this.client) return;
72
+
73
+ const { title, _duration: duration } = scenario;
74
+
75
+ const testId = getTestomatIdFromTestTitle(title || scenario.tags.map(tag => tag.name).join(' '));
76
+
77
+ let scenarioState = scenario.tests.every(test => test.state === 'passed') ? 'passed' : 'failed';
78
+ if (scenario.tests.every(test => test.state === 'skipped')) {
79
+ scenarioState = 'skipped';
80
+ }
81
+ const errors = scenario.tests
82
+ .filter(test => test.state === 'failed')
83
+ .map(test => test.error?.stack)
84
+ .filter(Boolean);
85
+ const error = errors.join('\n');
86
+
87
+ const tags = scenario.tags.map(tag => tag.name);
88
+
89
+ return this.client.addTestRun(scenarioState, {
90
+ error: error ? Error(error) : null,
91
+ title,
92
+ test_id: testId,
93
+ time: duration,
94
+ tags,
95
+ file: scenario.file,
96
+ // filesBuffers: screenshotsBuffers,
97
+ });
98
+ }
54
99
  }
55
100
 
56
101
  module.exports = WebdriverReporter;
@@ -1,29 +1,30 @@
1
1
  #!/usr/bin/env node
2
- const program = require("commander");
3
- const chalk = require("chalk");
2
+ const program = require('commander');
3
+ const chalk = require('chalk');
4
4
  const glob = require('glob');
5
5
  const debug = require('debug')('@testomatio/reporter:xml-cli');
6
6
  const { APP_PREFIX } = require('../constants');
7
- const XmlReader = require("../xmlReader");
7
+ const XmlReader = require('../xmlReader');
8
8
 
9
9
  const { version } = require('../../package.json');
10
10
 
11
11
  console.log(chalk.cyan.bold(` 🤩 Testomat.io XML Reporter v${version}`));
12
12
 
13
13
  program
14
- .arguments("<pattern>")
15
- .option("-d, --dir <dir>", "Project directory")
16
- .option("--java-tests [java-path]", "Load Java tests from path, by default: src/test/java")
17
- .option("--lang <lang>", "Language used (python, ruby, java)")
18
- .option("--timelimit <time>", "default time limit in seconds to kill a stuck process")
19
- .option("--env-file <envfile>", "Load environment variables from env file")
14
+ .arguments('<pattern>')
15
+ .option('-d, --dir <dir>', 'Project directory')
16
+ .option('--java-tests [java-path]', 'Load Java tests from path, by default: src/test/java')
17
+ .option('--lang <lang>', 'Language used (python, ruby, java)')
18
+ .option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
19
+ .option('--env-file <envfile>', 'Load environment variables from env file')
20
20
  .action(async (pattern, opts) => {
21
21
  if (!pattern.endsWith('.xml')) {
22
22
  pattern += '.xml';
23
23
  }
24
24
  let { javaTests, lang } = opts;
25
25
  if (opts.envFile) {
26
- debug('Loading env file: %s', opts.envFile)
26
+ console.log(APP_PREFIX, 'Loading env file:', opts.envFile);
27
+ debug('Loading env file: %s', opts.envFile);
27
28
  require('dotenv').config({ path: opts.envFile }); // eslint-disable-line
28
29
  }
29
30
  if (javaTests === true) javaTests = 'src/test/java';
@@ -37,16 +38,21 @@ program
37
38
  }
38
39
 
39
40
  for (const file of files) {
40
- console.log(APP_PREFIX,`Parsed ${file}`);
41
+ console.log(APP_PREFIX, `Parsed ${file}`);
41
42
  runReader.parse(file);
42
43
  }
43
44
 
44
45
  let timeoutTimer;
45
46
  if (opts.timelimit) {
46
- timeoutTimer = setTimeout(() => {
47
- console.log(`⚠️ Reached timeout of ${opts.timelimit}s. Exiting... (Exit code is 0 to not fail the pipeline)`);
48
- process.exit(0);
49
- }, parseInt(opts.timelimit, 10) * 1000)
47
+ timeoutTimer = setTimeout(
48
+ () => {
49
+ console.log(
50
+ `⚠️ Reached timeout of ${opts.timelimit}s. Exiting... (Exit code is 0 to not fail the pipeline)`,
51
+ );
52
+ process.exit(0);
53
+ },
54
+ parseInt(opts.timelimit, 10) * 1000,
55
+ );
50
56
  }
51
57
 
52
58
  try {
@@ -56,7 +62,7 @@ program
56
62
  console.log(APP_PREFIX, 'Error updating status, skipping...', err);
57
63
  }
58
64
 
59
- if (timeoutTimer) clearTimeout(timeoutTimer)
65
+ if (timeoutTimer) clearTimeout(timeoutTimer);
60
66
  });
61
67
 
62
68
  if (process.argv.length < 3) {
@@ -5,6 +5,7 @@ const chalk = require('chalk');
5
5
  const TestomatClient = require('../client');
6
6
  const { APP_PREFIX, STATUS } = require('../constants');
7
7
  const { version } = require('../../package.json');
8
+ const config = require('../config');
8
9
 
9
10
  console.log(chalk.cyan.bold(` 🤩 Testomat.io Reporter v${version}`));
10
11
 
@@ -12,13 +13,15 @@ program
12
13
  .option('-c, --command <cmd>', 'Test runner command')
13
14
  .option('--launch', 'Start a new run and return its ID')
14
15
  .option('--finish', 'Finish Run by its ID')
15
- .option("--env-file <envfile>", "Load environment variables from env file")
16
- .action(opts => {
16
+ .option('--env-file <envfile>', 'Load environment variables from env file')
17
+ .option('--filter <filter>', 'Additional execution filter')
18
+ .action(async opts => {
19
+ const { launch, finish, filter } = opts;
20
+ let { command } = opts;
17
21
 
18
- const { command, launch, finish } = opts;
19
22
  if (opts.envFile) require('dotenv').config(opts.envFile); // eslint-disable-line
20
23
 
21
- const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || process.env.TESTOMATIO;
24
+ const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
22
25
  const title = process.env.TESTOMATIO_TITLE;
23
26
 
24
27
  if (launch) {
@@ -57,6 +60,26 @@ program
57
60
  return;
58
61
  }
59
62
 
63
+ const client = new TestomatClient({ apiKey, title, parallel: true });
64
+
65
+ if (filter) {
66
+ const [pipe, ...optsArray] = filter.split(':');
67
+ const pipeOptions = optsArray.join(':');
68
+
69
+ try {
70
+ const tests = await client.prepareRun({ pipe, pipeOptions });
71
+
72
+ if (!tests || tests.length === 0) {
73
+ return;
74
+ }
75
+
76
+ const grep = ` --grep (${tests.join('|')})`;
77
+ command += grep;
78
+ } catch (err) {
79
+ console.log(APP_PREFIX, err);
80
+ }
81
+ }
82
+
60
83
  const testCmds = command.split(' ');
61
84
  console.log(APP_PREFIX, `🚀 Running`, chalk.green(command));
62
85
 
@@ -73,8 +96,6 @@ program
73
96
  return;
74
97
  }
75
98
 
76
- const client = new TestomatClient({ apiKey, title, parallel: true });
77
-
78
99
  client.createRun().then(() => {
79
100
  const cmd = spawn(testCmds[0], testCmds.slice(1), { stdio: 'inherit' });
80
101
 
package/lib/client.js CHANGED
@@ -1,12 +1,17 @@
1
1
  const debug = require('debug')('@testomatio/reporter:client');
2
2
  const createCallsiteRecord = require('callsite-record');
3
3
  const { sep, join } = require('path');
4
+ const { minimatch } = require('minimatch');
4
5
  const fs = require('fs');
5
6
  const chalk = require('chalk');
6
7
  const { randomUUID } = require('crypto');
7
8
  const upload = require('./fileUploader');
8
- const { APP_PREFIX } = require('./constants');
9
+ const { APP_PREFIX, STATUS } = require('./constants');
9
10
  const pipesFactory = require('./pipe');
11
+ const { glob } = require('glob');
12
+ const path = require('path');
13
+
14
+ let listOfTestFilesToExcludeFromReport = null;
10
15
 
11
16
  /**
12
17
  * @typedef {import('../types').TestData} TestData
@@ -21,36 +26,83 @@ class Client {
21
26
  * @param {*} params
22
27
  */
23
28
  constructor(params = {}) {
24
- this.parallel = params.parallel;
25
29
  const store = {};
26
30
  this.uuid = randomUUID();
27
31
  this.pipes = pipesFactory(params, store);
28
32
  this.queue = Promise.resolve();
29
33
  this.totalUploaded = 0;
34
+ this.failedToUpload = 0;
30
35
  this.version = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString()).version;
36
+ this.executionList = Promise.resolve();
37
+
31
38
  console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
32
39
  }
33
40
 
41
+ /**
42
+ * Asynchronously prepares the execution list for running tests through various pipes.
43
+ * Each pipe in the client is checked for enablement,
44
+ * and if all pipes are disabled, the function returns a resolved Promise.
45
+ * Otherwise, it executes the `prepareRun` method for each enabled pipe and collects the results.
46
+ * The results are then filtered to remove any undefined values.
47
+ * If no valid results are found, the function returns undefined.
48
+ * Otherwise, it returns the first non-empty array from the filtered results.
49
+ *
50
+ * @param {Object} params - The options for preparing the test execution list.
51
+ * @param {string} params.pipe - Name of the executed pipe.
52
+ * @param {string} params.pipeOptions - Filter option.
53
+ * @returns {Promise<any>} - A Promise that resolves to an
54
+ * array containing the prepared execution list,
55
+ * or resolves to undefined if no valid results are found or if all pipes are disabled.
56
+ */
57
+ async prepareRun(params) {
58
+ const { pipe, pipeOptions } = params;
59
+ // all pipes disabled, skipping
60
+ if (!this.pipes.some(p => p.isEnabled)) {
61
+ return Promise.resolve();
62
+ }
63
+
64
+ try {
65
+ const filterPipe = this.pipes.find(p => p.constructor.name.toLowerCase() === `${pipe.toLowerCase()}pipe`);
66
+
67
+ if (!filterPipe.isEnabled) {
68
+ // TODO:for the future for the another pipes
69
+ console.warn(
70
+ APP_PREFIX,
71
+ `At the moment processing is available only for the "testomatio" key. Example: "testomatio:tag-name=xxx"`,
72
+ );
73
+ return;
74
+ }
75
+
76
+ const results = await Promise.all(
77
+ this.pipes.map(async p => ({ pipe: p.toString(), result: await p.prepareRun(pipeOptions) })),
78
+ );
79
+
80
+ const result = results.filter(p => p.pipe.includes('Testomatio'))[0]?.result;
81
+
82
+ if (!result || result.length === 0) {
83
+ return;
84
+ }
85
+
86
+ debug('Execution tests list', result);
87
+
88
+ return result;
89
+ } catch (err) {
90
+ console.error(APP_PREFIX, err);
91
+ }
92
+ }
93
+
34
94
  /**
35
95
  * Used to create a new Test run
36
96
  *
37
97
  * @returns {Promise<any>} - resolves to Run id which should be used to update / add test
38
98
  */
39
99
  createRun() {
100
+ debug('Creating run...');
40
101
  // all pipes disabled, skipping
41
102
  if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
42
103
 
43
- const runParams = {
44
- title: this.title,
45
- parallel: this.parallel,
46
- env: this.env,
47
- };
48
-
49
- global.testomatioArtifacts = [];
50
- if (!global.testomatioDataStore) global.testomatioDataStore = {};
51
-
52
104
  this.queue = this.queue
53
- .then(() => Promise.all(this.pipes.map(p => p.createRun(runParams))))
105
+ .then(() => Promise.all(this.pipes.map(p => p.createRun())))
54
106
  .catch(err => console.log(APP_PREFIX, err))
55
107
  .then(() => undefined); // fixes return type
56
108
  // debug('Run', this.queue);
@@ -62,13 +114,18 @@ class Client {
62
114
  *
63
115
  * @param {string|undefined} status
64
116
  * @param {TestData} [testData]
65
- * @param {string[]} [storeArtifacts]
66
117
  * @returns {Promise<PipeResult[]>}
67
118
  */
68
- async addTestRun(status, testData, storeArtifacts = []) {
119
+ async addTestRun(status, testData) {
69
120
  // all pipes disabled, skipping
70
121
  if (!this.pipes?.filter(p => p.isEnabled).length) return [];
71
122
 
123
+ if (isTestShouldBeExculedFromReport(testData)) return [];
124
+
125
+ if (status === STATUS.SKIPPED && process.env.TESTOMATIO_IGNORE_SKIPPED) {
126
+ return; // do not log skipped tests
127
+ }
128
+
72
129
  if (!testData)
73
130
  testData = {
74
131
  title: 'Unknown test',
@@ -84,46 +141,31 @@ class Client {
84
141
  steps,
85
142
  code = null,
86
143
  title,
144
+ file,
87
145
  suite_title,
88
146
  suite_id,
89
147
  test_id,
148
+ manuallyAttachedArtifacts,
149
+ meta,
90
150
  } = testData;
91
151
  let { message = '' } = testData;
92
152
 
93
- const uploadedFiles = [];
94
-
95
- let stack = '';
96
-
153
+ let errorFormatted = '';
97
154
  if (error) {
98
- stack = this.formatError(error) || '';
155
+ errorFormatted += this.formatError(error) || '';
99
156
  message = error?.message;
100
157
  }
101
- if (steps) {
102
- stack += this.formatSteps(stack, steps);
103
- }
104
-
105
- stack += testData.stack || '';
106
158
 
107
- // in some cases (e.g. using cucumber) logger instance becomes empty object, if import at the top of the file
108
- const logger = require('./logger');
109
- const testLogs = logger.getLogs(test_id);
110
- // debug(`Test logs for ${test_id}:\n`, testLogs);
111
- if (stack) stack += '\n\n';
112
- stack += testLogs ? `${chalk.bold('Logs:')}\n${testLogs}` : '';
159
+ // Attach logs
160
+ const fullLogs = this.formatLogs({ error: errorFormatted, steps, logs: testData.logs });
113
161
 
114
- if (Array.isArray(storeArtifacts) && storeArtifacts.length) {
115
- debug('CLIENT storeArtifact', storeArtifacts);
116
- files.push(...storeArtifacts);
117
- }
162
+ // add artifacts
163
+ if (manuallyAttachedArtifacts?.length) files.push(...manuallyAttachedArtifacts);
118
164
 
119
- if (Array.isArray(global.testomatioArtifacts)) {
120
- debug('CLIENT global[testomatioArtifacts]', global.testomatioArtifacts);
121
- files.push(...global.testomatioArtifacts);
122
- global.testomatioArtifacts = [];
123
- }
165
+ const uploadedFiles = [];
124
166
 
125
- for (const file of files) {
126
- uploadedFiles.push(upload.uploadFileByPath(file, this.uuid));
167
+ for (const f of files) {
168
+ uploadedFiles.push(upload.uploadFileByPath(f, this.uuid));
127
169
  }
128
170
 
129
171
  for (const [idx, buffer] of filesBuffers.entries()) {
@@ -131,18 +173,22 @@ class Client {
131
173
  uploadedFiles.push(upload.uploadFileAsBuffer(buffer, fileName, this.uuid));
132
174
  }
133
175
 
134
- const artifacts = await Promise.all(uploadedFiles);
176
+ const artifacts = (await Promise.all(uploadedFiles)).filter(n => !!n);
135
177
 
136
- global.testomatioArtifacts = [];
178
+ if (artifacts.length < uploadedFiles.length) {
179
+ const failedUploading = uploadedFiles.length - artifacts.length;
180
+ this.failedToUpload += failedUploading;
181
+ }
137
182
 
138
- this.totalUploaded += uploadedFiles.filter(n => n).length;
183
+ this.totalUploaded += artifacts.length;
139
184
 
140
185
  const data = {
141
186
  files,
142
187
  steps,
143
188
  status,
144
- stack,
189
+ stack: fullLogs,
145
190
  example,
191
+ file,
146
192
  code,
147
193
  title,
148
194
  suite_title,
@@ -151,16 +197,19 @@ class Client {
151
197
  message,
152
198
  run_time: parseFloat(time),
153
199
  artifacts,
200
+ meta,
154
201
  };
155
202
 
203
+ // debug('Adding test run...', data);
204
+
156
205
  this.queue = this.queue.then(() =>
157
206
  Promise.all(
158
- this.pipes.map(async p => {
207
+ this.pipes.map(async pipe => {
159
208
  try {
160
- const result = await p.addTest(data);
161
- return { pipe: p.toString(), result };
209
+ const result = await pipe.addTest(data);
210
+ return { pipe: pipe.toString(), result };
162
211
  } catch (err) {
163
- console.log(APP_PREFIX, p.toString(), err);
212
+ console.log(APP_PREFIX, pipe.toString(), err);
164
213
  }
165
214
  }),
166
215
  ),
@@ -177,6 +226,7 @@ class Client {
177
226
  * @returns {Promise<any>} - A Promise that resolves when finishes the run.
178
227
  */
179
228
  updateRunStatus(status, isParallel = false) {
229
+ debug('Updating run status...');
180
230
  // all pipes disabled, skipping
181
231
  if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
182
232
 
@@ -185,15 +235,27 @@ class Client {
185
235
  this.queue = this.queue
186
236
  .then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
187
237
  .then(() => {
188
- debug('TOTAL uploaded files', this.totalUploaded);
238
+ debug('TOTAL artifacts', this.totalUploaded);
239
+ if (this.totalUploaded && !upload.isArtifactsEnabled())
240
+ debug(`${this.totalUploaded} artifacts are not uploaded, because artifacts uploading is not enabled`);
189
241
 
190
- if (upload.isArtifactsEnabled() && this.totalUploaded > 0) {
242
+ if (this.totalUploaded && upload.isArtifactsEnabled()) {
191
243
  console.log(
192
244
  APP_PREFIX,
193
- `🗄️ Total ${this.totalUploaded} artifacts ${
245
+ `🗄️ ${this.totalUploaded} artifacts ${
194
246
  process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
195
- } uploaded to S3 bucket `,
247
+ } uploaded to S3 bucket`,
196
248
  );
249
+
250
+ if (this.failedToUpload > 0) {
251
+ console.log(
252
+ APP_PREFIX,
253
+ chalk.yellow(
254
+ `Some artifacts were not uploaded. ${this.failedToUpload} artifacts could not be uploaded.
255
+ Run tests with DEBUG="@testomatio/reporter:file-uploader" to see details"`,
256
+ ),
257
+ );
258
+ }
197
259
  }
198
260
  })
199
261
  .catch(err => console.log(APP_PREFIX, err));
@@ -201,15 +263,27 @@ class Client {
201
263
  return this.queue;
202
264
  }
203
265
 
204
- formatSteps(stack, steps) {
205
- return stack ? `${steps}\n\n${chalk.bold.red('################[ Failure ]################')}\n${stack}` : steps;
266
+ /**
267
+ * Returns the formatted stack including the stack trace, steps, and logs.
268
+ * @returns {string}
269
+ */
270
+ formatLogs({ error, steps, logs }) {
271
+ error = error?.trim();
272
+ steps = steps?.trim();
273
+ logs = logs?.trim();
274
+
275
+ let testLogs = '';
276
+ if (steps) testLogs += `${chalk.bold.blue('################[ Steps ]################')}\n${steps}\n\n`;
277
+ if (logs) testLogs += `${chalk.bold.gray('################[ Logs ]################')}\n${logs}\n\n`;
278
+ if (error) testLogs += `${chalk.bold.red('################[ Failure ]################')}\n${error}`;
279
+ return testLogs;
206
280
  }
207
281
 
208
282
  formatError(error, message) {
209
283
  if (!message) message = error.message;
210
284
  if (error.inspect) message = error.inspect() || '';
211
285
 
212
- let stack = `\n${chalk.bold(message)}\n`;
286
+ let stack = `${message}\n`;
213
287
 
214
288
  // diffs for mocha, cypress, codeceptjs style
215
289
  if (error.actual && error.expected) {
@@ -219,17 +293,21 @@ class Client {
219
293
  stack += '\n\n';
220
294
  }
221
295
 
296
+ const customFilter = process.env.TESTOMATIO_STACK_IGNORE;
297
+
222
298
  try {
299
+ let hasFrame = false;
223
300
  const record = createCallsiteRecord({
224
301
  forError: error,
302
+ isCallsiteFrame: frame => {
303
+ if (customFilter && minimatch(frame.getFileName(), customFilter)) return false;
304
+ if (hasFrame) return false;
305
+ if (isNotInternalFrame(frame)) hasFrame = true;
306
+ return hasFrame;
307
+ },
225
308
  });
226
309
  if (record && !record.filename.startsWith('http')) {
227
- stack += record.renderSync({
228
- stackFilter: frame =>
229
- frame.getFileName().indexOf(sep) > -1 &&
230
- frame.getFileName().indexOf('node_modules') < 0 &&
231
- frame.getFileName().indexOf('internal') < 0,
232
- });
310
+ stack += record.renderSync({ stackFilter: isNotInternalFrame });
233
311
  }
234
312
  return stack;
235
313
  } catch (e) {
@@ -238,4 +316,49 @@ class Client {
238
316
  }
239
317
  }
240
318
 
319
+ function isNotInternalFrame(frame) {
320
+ return (
321
+ frame.getFileName() &&
322
+ frame.getFileName().includes(sep) &&
323
+ !frame.getFileName().includes('node_modules') &&
324
+ !frame.getFileName().includes('internal')
325
+ );
326
+ }
327
+
328
+ /**
329
+ *
330
+ * @param {TestData} testData
331
+ * @returns boolean
332
+ */
333
+ function isTestShouldBeExculedFromReport(testData) {
334
+ // const fileName = path.basename(test.location?.file || '');
335
+ const globExcludeFilesPattern = process.env.TESTOMATIO_EXCLUDE_FILES_FROM_REPORT_GLOB_PATTERN;
336
+ if (!globExcludeFilesPattern) return false;
337
+
338
+ if (!testData.file) {
339
+ debug('No "file" property found for test ', testData.title);
340
+ return false;
341
+ }
342
+
343
+ const excludeParretnsList = globExcludeFilesPattern.split(';');
344
+
345
+ // as scanning files is time consuming operation, just save the result in variable to avoid multiple scans
346
+ if (!listOfTestFilesToExcludeFromReport) {
347
+ // list of files with relative paths
348
+ listOfTestFilesToExcludeFromReport = glob.sync(excludeParretnsList, { ignore: '**/node_modules/**' });
349
+ debug('Tests from next files will not be reported:', listOfTestFilesToExcludeFromReport);
350
+ }
351
+
352
+ const testFileRelativePath = path.relative(process.cwd(), testData.file);
353
+
354
+ // no files found matching the exclusion pattern
355
+ if (!listOfTestFilesToExcludeFromReport.length) return false;
356
+
357
+ if (listOfTestFilesToExcludeFromReport.includes(testFileRelativePath)) {
358
+ debug(`Excluding test '${testData.title}' <${testFileRelativePath}> from reporting`);
359
+ return true;
360
+ }
361
+ return false;
362
+ }
363
+
241
364
  module.exports = Client;
package/lib/config.js ADDED
@@ -0,0 +1,34 @@
1
+ // This file is used to read environment variables from .env file
2
+
3
+ // require('dotenv').config({ path: process.env.TESTOMATIO_ENV_FILE_PATH });
4
+
5
+ const debug = require('debug')('@testomatio/reporter:config');
6
+
7
+ /* for possibility to use multiple env files (reading different paths)
8
+ const dotenv = require('dotenv');
9
+ const envFileVars = dotenv.config({ path: '.env' }).parsed; */
10
+
11
+ if (process.env.TESTOMATIO_API_KEY) {
12
+ process.env.TESTOMATIO = process.env.TESTOMATIO_API_KEY;
13
+ }
14
+ if (process.env.TESTOMATIO_TOKEN) {
15
+ process.env.TESTOMATIO = process.env.TESTOMATIO_TOKEN;
16
+ }
17
+
18
+ if (process.env.TESTOMATIO === 'undefined')
19
+ console.error('TESTOMATIO is "undefined". Something went wrong. Contact dev team.');
20
+
21
+ // select only TESTOMATIO related variables (only to print them in debug)
22
+ const testomatioEnvVars =
23
+ Object.keys(process.env)
24
+ .filter(key => key.startsWith('TESTOMATIO') || key.startsWith('S3_'))
25
+ .reduce((obj, key) => {
26
+ obj[key] = process.env[key];
27
+ return obj;
28
+ }, {}) || {};
29
+ debug('TESTOMATIO variables:', testomatioEnvVars);
30
+
31
+ // includes variables from .env file and process.env
32
+ const config = process.env;
33
+
34
+ module.exports = config;