@testomatio/reporter 1.2.0-beta → 1.2.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.
@@ -3,55 +3,54 @@ const Mocha = require('mocha');
3
3
  const debug = require('debug')('@testomatio/reporter:adapter:mocha');
4
4
  const chalk = require('chalk');
5
5
  const TestomatClient = require('../client');
6
- const { STATUS } = require('../constants');
7
- const { parseTest, specificTestInfo } = require('../util');
6
+ const { STATUS, TESTOMAT_TMP_STORAGE } = require('../constants');
7
+ const { parseTest, specificTestInfo, fileSystem } = require('../utils/utils');
8
8
  const ArtifactStorage = require('../_ArtifactStorageOld');
9
9
 
10
- const { EVENT_RUN_BEGIN, EVENT_RUN_END, EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_TEST_PENDING } = Mocha.Runner.constants;
10
+ const { EVENT_RUN_BEGIN, EVENT_RUN_END, EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_TEST_PENDING } =
11
+ Mocha.Runner.constants;
11
12
 
12
13
  function MochaReporter(runner, opts) {
13
14
  Mocha.reporters.Base.call(this, runner);
14
- let passes = 0; let failures = 0; let skipped = 0;
15
+ let passes = 0;
16
+ let failures = 0;
17
+ let skipped = 0;
15
18
  let artifactStore;
16
19
 
17
20
  const apiKey = opts?.reporterOptions?.apiKey || process.env.TESTOMATIO;
18
21
 
19
- if (!apiKey) {
20
- debug('TESTOMATIO key is empty, ignoring reports');
21
- return;
22
- }
23
22
  const client = new TestomatClient({ apiKey });
24
23
 
25
24
  runner.on(EVENT_RUN_BEGIN, () => {
26
25
  client.createRun();
27
26
 
28
- const params = {
29
- toFile: true
27
+ const params = {
28
+ toFile: true,
30
29
  };
31
-
32
- artifactStore = runner._workerReporter !== undefined
33
- ? new ArtifactStorage(params)
34
- : new ArtifactStorage();
30
+
31
+ artifactStore = runner._workerReporter !== undefined ? new ArtifactStorage(params) : new ArtifactStorage();
32
+
33
+ fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
35
34
  });
36
35
 
37
- runner.on(EVENT_TEST_PASS, async(test) => {
36
+ runner.on(EVENT_TEST_PASS, async test => {
38
37
  passes += 1;
39
38
  console.log(chalk.bold.green('✔'), test.fullTitle());
40
39
  const testId = parseTest(test.title);
41
40
 
42
- const specificTest = specificTestInfo(test);
41
+ const specificTest = specificTestInfo(test);
43
42
  const content = await artifactStore.artifactByTestName(specificTest);
44
43
 
45
44
  debug(`test=${specificTest} content = `, content);
46
45
 
47
46
  client.addTestRun(
48
- STATUS.PASSED,
47
+ STATUS.PASSED,
49
48
  {
50
49
  test_id: testId,
51
50
  title: test.title,
52
51
  time: test.duration,
53
52
  },
54
- content
53
+ content,
55
54
  );
56
55
  });
57
56
 
@@ -66,24 +65,25 @@ function MochaReporter(runner, opts) {
66
65
  });
67
66
  });
68
67
 
69
- runner.on(EVENT_TEST_FAIL, async(test, err) => {
68
+ runner.on(EVENT_TEST_FAIL, async (test, err) => {
70
69
  failures += 1;
71
70
  console.log(chalk.bold.red('✖'), test.fullTitle(), chalk.gray(err.message));
72
71
  const testId = parseTest(test.title);
73
72
 
74
- const specificTest = specificTestInfo(test);
73
+ const specificTest = specificTestInfo(test);
75
74
  const content = await artifactStore.artifactByTestName(specificTest);
76
75
 
77
76
  debug(`fail test=${specificTest} content = `, content);
78
77
 
79
78
  client.addTestRun(
80
- STATUS.FAILED, {
79
+ STATUS.FAILED,
80
+ {
81
81
  error: err,
82
82
  test_id: testId,
83
83
  title: test.title,
84
84
  time: test.duration,
85
- },
86
- content
85
+ },
86
+ content,
87
87
  );
88
88
  });
89
89
 
@@ -3,14 +3,14 @@ const crypto = require('crypto');
3
3
  const os = require('os');
4
4
  const path = require('path');
5
5
  const fs = require('fs');
6
- const { APP_PREFIX, STATUS: Status } = require('../constants');
6
+ const { APP_PREFIX, STATUS: Status, TESTOMAT_TMP_STORAGE } = require('../constants');
7
7
  const TestomatioClient = require('../client');
8
8
  const { isArtifactsEnabled } = require('../fileUploader');
9
- const { parseTest } = require('../util');
9
+ const { parseTest, fileSystem } = require('../utils/utils');
10
10
 
11
11
  const reportTestPromises = [];
12
12
 
13
- class TestomatioReporter {
13
+ class PlaywrightReporter {
14
14
  constructor(config = {}) {
15
15
  this.client = new TestomatioClient({ apiKey: config?.apiKey });
16
16
 
@@ -18,11 +18,17 @@ class TestomatioReporter {
18
18
  }
19
19
 
20
20
  onBegin(_config, suite) {
21
+ // clean data storage
22
+ fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
21
23
  if (!this.client) return;
22
24
  this.suite = suite;
23
25
  this.client.createRun();
24
26
  }
25
27
 
28
+ // onTestBegin(test) {
29
+ // const testId = parseTest(test.title);
30
+ // }
31
+
26
32
  onTestEnd(test, result) {
27
33
  if (!this.client) return;
28
34
 
@@ -37,7 +43,9 @@ class TestomatioReporter {
37
43
  for (const step of result.steps) {
38
44
  appendStep(step, steps);
39
45
  }
40
-
46
+
47
+ const logs = `\n\n${chalk.bold('Logs:')}\n${chalk.red(result.stderr.join(''))}\n${result.stdout.join('')}`;
48
+
41
49
  const reportTestPromise = this.client.addTestRun(checkStatus(result.status), {
42
50
  error,
43
51
  test_id: testId,
@@ -45,13 +53,14 @@ class TestomatioReporter {
45
53
  title,
46
54
  steps: steps.join('\n'),
47
55
  time: duration,
56
+ stack: logs,
48
57
  }).then(pipes => {
49
- testId = pipes.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
58
+ testId = pipes?.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
50
59
 
51
60
  this.uploads.push({
52
61
  testId, title, suite_title, files: result.attachments.filter((a) => a.body || a.path)
53
- })
54
- });;
62
+ });
63
+ });
55
64
 
56
65
  reportTestPromises.push(reportTestPromise);
57
66
  }
@@ -67,10 +76,9 @@ class TestomatioReporter {
67
76
  const promises = [];
68
77
 
69
78
  for (const upload of this.uploads) {
70
-
71
79
  const { title, testId, suite_title } = upload;
72
80
 
73
- const files = upload.files.map((attachment) => {
81
+ const files = upload.files.map(attachment => {
74
82
  if (attachment.body) {
75
83
  const fileName = tmpFile();
76
84
  fs.writeFileSync(fileName, attachment.body);
@@ -78,7 +86,6 @@ class TestomatioReporter {
78
86
  return { path: attachment.path, title, type: attachment.contentType };
79
87
  });
80
88
 
81
-
82
89
  promises.push(
83
90
  this.client.addTestRun(undefined, {
84
91
  test_id: testId,
@@ -124,4 +131,4 @@ function tmpFile(prefix = 'tmp.') {
124
131
  return path.join(tmpdir, prefix + crypto.randomBytes(16).toString('hex'));
125
132
  }
126
133
 
127
- module.exports = TestomatioReporter;
134
+ module.exports = PlaywrightReporter;
@@ -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 { parseTest } = require('../utils/utils');
5
5
 
6
6
  class WebdriverReporter extends WDIOReporter {
7
7
  constructor(options) {
@@ -2,8 +2,8 @@
2
2
  const { DataStorage } = require('./dataStorage');
3
3
 
4
4
  class ArtifactStorage {
5
- constructor(params = {}) {
6
- this.dataStorage = new DataStorage('artifact', params);
5
+ constructor() {
6
+ this.dataStorage = new DataStorage('artifact');
7
7
 
8
8
  // singleton
9
9
  if (!ArtifactStorage.instance) {
@@ -23,6 +23,7 @@ program
23
23
  }
24
24
  let { javaTests, lang } = opts;
25
25
  if (opts.envFile) {
26
+ console.log(APP_PREFIX, 'Loading env file:', opts.envFile);
26
27
  debug('Loading env file: %s', opts.envFile)
27
28
  require('dotenv').config({ path: opts.envFile }); // eslint-disable-line
28
29
  }
@@ -13,9 +13,11 @@ program
13
13
  .option('--launch', 'Start a new run and return its ID')
14
14
  .option('--finish', 'Finish Run by its ID')
15
15
  .option("--env-file <envfile>", "Load environment variables from env file")
16
- .action(opts => {
16
+ .option("--filter <filter>", "Additional execution filter")
17
+ .action(async (opts) => {
18
+ const { launch, finish, filter } = opts;
19
+ let { command } = opts;
17
20
 
18
- const { command, launch, finish } = opts;
19
21
  if (opts.envFile) require('dotenv').config(opts.envFile); // eslint-disable-line
20
22
 
21
23
  const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || process.env.TESTOMATIO;
@@ -51,6 +53,27 @@ program
51
53
 
52
54
  let exitCode = 0;
53
55
 
56
+ const client = new TestomatClient({ apiKey, title, parallel: true });
57
+
58
+ if(filter) {
59
+ const [pipe, ...optsArray] = filter.split(":");
60
+ const pipeOptions = optsArray.join(":");
61
+
62
+ try {
63
+ const tests = await client.prepareRun({pipe, pipeOptions});
64
+
65
+ if(!tests || tests.length === 0) {
66
+ return;
67
+ }
68
+
69
+ const grep = ` --grep (${tests.join('|')})`;
70
+ command += grep;
71
+ }
72
+ catch(err) {
73
+ console.log(APP_PREFIX, err);
74
+ }
75
+ }
76
+
54
77
  if (!command.split) {
55
78
  process.exitCode = 255;
56
79
  console.log(APP_PREFIX, `No command provided. Use -c option to launch a test runner.`);
@@ -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,18 +1,18 @@
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
9
  const { APP_PREFIX } = require('./constants');
9
10
  const pipesFactory = require('./pipe');
10
- const logger = require('./logger');
11
11
 
12
12
  /**
13
13
  * @typedef {import('../types').TestData} TestData
14
14
  * @typedef {import('../types').RunStatus} RunStatus
15
- * @typedef {import('../types').PipeResult} PipeResult
15
+ * @typedef {import('../types').PipeResult} PipeResult
16
16
  */
17
17
 
18
18
  class Client {
@@ -22,39 +22,88 @@ class Client {
22
22
  * @param {*} params
23
23
  */
24
24
  constructor(params = {}) {
25
- this.parallel = params.parallel;
26
25
  const store = {};
27
26
  this.uuid = randomUUID();
28
27
  this.pipes = pipesFactory(params, store);
29
28
  this.queue = Promise.resolve();
30
29
  this.totalUploaded = 0;
31
30
  this.version = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString()).version;
31
+ this.executionList = Promise.resolve();
32
+
32
33
  console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
33
34
  }
34
35
 
36
+ /**
37
+ * Asynchronously prepares the execution list for running tests through various pipes.
38
+ * Each pipe in the client is checked for enablement,
39
+ * and if all pipes are disabled, the function returns a resolved Promise.
40
+ * Otherwise, it executes the `prepareRun` method for each enabled pipe and collects the results.
41
+ * The results are then filtered to remove any undefined values.
42
+ * If no valid results are found, the function returns undefined.
43
+ * Otherwise, it returns the first non-empty array from the filtered results.
44
+ *
45
+ * @param {Object} params - The options for preparing the test execution list.
46
+ * @param {string} params.pipe - Name of the executed pipe.
47
+ * @param {string} params.pipeOptions - Filter option.
48
+ * @returns {Promise<any>} - A Promise that resolves to an
49
+ * array containing the prepared execution list,
50
+ * or resolves to undefined if no valid results are found or if all pipes are disabled.
51
+ */
52
+ async prepareRun(params) {
53
+ const { pipe, pipeOptions } = params;
54
+ // all pipes disabled, skipping
55
+ if (!this.pipes.some(p => p.isEnabled)) {
56
+ return Promise.resolve();
57
+ }
58
+
59
+ try {
60
+ const filterPipe = this.pipes.find(p => p.constructor.name.toLowerCase() === `${pipe.toLowerCase()}pipe`);
61
+
62
+ if (!filterPipe.isEnabled) {
63
+ // TODO:for the future for the another pipes
64
+ console.warn(
65
+ APP_PREFIX,
66
+ `At the moment processing is available only for the "testomatio" key. Example: "testomatio:tag-name=xxx"`
67
+ );
68
+ return;
69
+ }
70
+
71
+ const results = await Promise.all(this.pipes.map(async p =>
72
+ ({ pipe: p.toString(), result: await p.prepareRun(pipeOptions) })
73
+ ));
74
+
75
+ const result = results.filter(p => p.pipe.includes('Testomatio'))[0]?.result;
76
+
77
+ if (!result || result.length === 0) {
78
+ return;
79
+ }
80
+
81
+ debug('Execution tests list', result);
82
+
83
+ return result;
84
+ } catch (err) {
85
+ console.error(APP_PREFIX, err);
86
+ }
87
+ }
88
+
35
89
  /**
36
90
  * Used to create a new Test run
37
91
  *
38
92
  * @returns {Promise<any>} - resolves to Run id which should be used to update / add test
39
93
  */
40
94
  createRun() {
95
+ debug('Creating run...');
41
96
  // all pipes disabled, skipping
42
- if (!this.pipes.filter(p => p.isEnabled).length) return Promise.resolve();
43
-
44
- const runParams = {
45
- title: this.title,
46
- parallel: this.parallel,
47
- env: this.env,
48
- };
97
+ if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
49
98
 
50
99
  global.testomatioArtifacts = [];
51
- // TODO: create global storage
100
+ if (!global.testomatioDataStore) global.testomatioDataStore = {};
52
101
 
53
102
  this.queue = this.queue
54
- .then(() => Promise.all(this.pipes.map(p => p.createRun(runParams))))
103
+ .then(() => Promise.all(this.pipes.map(p => p.createRun())))
55
104
  .catch(err => console.log(APP_PREFIX, err))
56
105
  .then(() => undefined); // fixes return type
57
- debug('Run', this.queue);
106
+ // debug('Run', this.queue);
58
107
  return this.queue;
59
108
  }
60
109
 
@@ -67,8 +116,9 @@ class Client {
67
116
  * @returns {Promise<PipeResult[]>}
68
117
  */
69
118
  async addTestRun(status, testData, storeArtifacts = []) {
119
+ debug('Adding test run for test', testData?.test_id || 'unknown test');
70
120
  // all pipes disabled, skipping
71
- if (!this.pipes.filter(p => p.isEnabled).length) return;
121
+ if (!this.pipes?.filter(p => p.isEnabled).length) return [];
72
122
 
73
123
  if (!testData)
74
124
  testData = {
@@ -103,9 +153,14 @@ class Client {
103
153
  stack = this.formatSteps(stack, steps);
104
154
  }
105
155
 
156
+ stack += testData.stack || '';
157
+
158
+ // in some cases (e.g. using cucumber) logger instance becomes empty object, if import at the top of the file
159
+ const logger = require('./logger');
106
160
  const testLogs = logger.getLogs(test_id);
107
- debug(`Test logs for ${test_id}:\n`, testLogs);
108
- stack += testLogs;
161
+ // debug(`Test logs for ${test_id}:\n`, testLogs);
162
+ if (stack) stack += '\n\n';
163
+ stack += testLogs ? `${chalk.bold('Logs:')}\n${testLogs}` : '';
109
164
 
110
165
  if (Array.isArray(storeArtifacts) && storeArtifacts.length) {
111
166
  debug('CLIENT storeArtifact', storeArtifacts);
@@ -149,17 +204,19 @@ class Client {
149
204
  artifacts,
150
205
  };
151
206
 
152
- this.queue = this.queue
153
- .then(() => Promise.all(this.pipes.map(async p => {
154
- try {
155
- const result = await p.addTest(data);
156
- return { pipe: p.toString(), result };
157
- } catch (err) {
158
- console.log(APP_PREFIX, p.toString(), err);
159
- }
160
- }
161
- )));
162
-
207
+ this.queue = this.queue.then(() =>
208
+ Promise.all(
209
+ this.pipes.map(async p => {
210
+ try {
211
+ const result = await p.addTest(data);
212
+ return { pipe: p.toString(), result };
213
+ } catch (err) {
214
+ console.log(APP_PREFIX, p.toString(), err);
215
+ }
216
+ }),
217
+ ),
218
+ );
219
+
163
220
  return this.queue;
164
221
  }
165
222
 
@@ -171,8 +228,9 @@ class Client {
171
228
  * @returns {Promise<any>} - A Promise that resolves when finishes the run.
172
229
  */
173
230
  updateRunStatus(status, isParallel = false) {
231
+ debug('Updating run status...');
174
232
  // all pipes disabled, skipping
175
- if (!this.pipes.filter(p => p.isEnabled).length) return Promise.resolve();
233
+ if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
176
234
 
177
235
  const runParams = { status, parallel: isParallel };
178
236
 
@@ -213,17 +271,21 @@ class Client {
213
271
  stack += '\n\n';
214
272
  }
215
273
 
274
+ const customFilter = process.env.TESTOMATIO_STACK_IGNORE;
275
+
216
276
  try {
277
+ let hasFrame = false;
217
278
  const record = createCallsiteRecord({
218
279
  forError: error,
280
+ isCallsiteFrame: frame => {
281
+ if (customFilter && minimatch(frame.getFileName(), customFilter)) return false;
282
+ if (hasFrame) return false;
283
+ if (isNotInternalFrame(frame)) hasFrame = true;
284
+ return hasFrame;
285
+ }
219
286
  });
220
287
  if (record && !record.filename.startsWith('http')) {
221
- stack += record.renderSync({
222
- stackFilter: frame =>
223
- frame.getFileName().indexOf(sep) > -1 &&
224
- frame.getFileName().indexOf('node_modules') < 0 &&
225
- frame.getFileName().indexOf('internal') < 0,
226
- });
288
+ stack += record.renderSync({ stackFilter: isNotInternalFrame });
227
289
  }
228
290
  return stack;
229
291
  } catch (e) {
@@ -232,4 +294,10 @@ class Client {
232
294
  }
233
295
  }
234
296
 
297
+ function isNotInternalFrame(frame) {
298
+ return frame.getFileName().includes(sep) &&
299
+ !frame.getFileName().includes('node_modules') &&
300
+ !frame.getFileName().includes('internal')
301
+ }
302
+
235
303
  module.exports = Client;
package/lib/constants.js CHANGED
@@ -21,6 +21,12 @@ const STATUS = {
21
21
  SKIPPED: 'skipped',
22
22
  FINISHED: 'finished',
23
23
  };
24
+ // html pipe var
25
+ const HTML_REPORT = {
26
+ FOLDER: "html-report",
27
+ REPORT_DEFAULT_NAME: "testomatio-report.html",
28
+ TEMPLATE_NAME: 'testomatio.hbs'
29
+ };
24
30
 
25
31
  module.exports = {
26
32
  APP_PREFIX,
@@ -28,4 +34,5 @@ module.exports = {
28
34
  TESTOMAT_TMP_STORAGE,
29
35
  CSV_HEADERS,
30
36
  STATUS,
37
+ HTML_REPORT
31
38
  }