@testomatio/reporter 0.8.0-beta.9 → 0.8.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.
@@ -1,5 +1,5 @@
1
1
  const TestomatClient = require('../client');
2
- const TRConstants = require('../constants');
2
+ const { STATUS } = require('../constants');
3
3
  const { parseTest, ansiRegExp } = require('../util');
4
4
 
5
5
  class JestReporter {
@@ -16,26 +16,22 @@ class JestReporter {
16
16
 
17
17
  const { testResults } = testResult;
18
18
  for (const result of testResults) {
19
- let error = null;
20
- let steps = null;
19
+ let error;
20
+ let steps;
21
21
  const { status, title, duration, failureMessages } = result;
22
22
  if (failureMessages[0]) {
23
23
  let errorMessage = failureMessages[0].replace(ansiRegExp(), '');
24
24
  errorMessage = errorMessage.split('\n')[0];
25
- error = {
26
- message: errorMessage,
27
- };
25
+ error = new Error(errorMessage);
28
26
  steps = failureMessages[0];
29
27
  }
30
28
  const testId = parseTest(title);
31
29
  const deducedStatus = status === 'pending' ? 'skipped' : status;
32
-
33
- const suite_title = result.ancestorTitles && result.ancestorTitles[result.ancestorTitles.length - 1];
34
30
  // In jest if test is not matched with test name pattern it is considered as skipped.
35
31
  // So adding a check if it is skipped for real or because of test pattern
36
32
  if (!this._globalConfig.testNamePattern || deducedStatus !== 'skipped') {
37
- this.client.addTestRun(testId, deducedStatus, {
38
- suite_title,
33
+ this.client.addTestRun(deducedStatus, {
34
+ test_id: testId,
39
35
  error,
40
36
  steps,
41
37
  title,
@@ -49,7 +45,7 @@ class JestReporter {
49
45
  if (!this.client) return;
50
46
 
51
47
  const { numFailedTests } = results;
52
- const status = numFailedTests === 0 ? TRConstants.PASSED : TRConstants.FAILED;
48
+ const status = numFailedTests === 0 ? STATUS.PASSED : STATUS.FAILED;
53
49
  this.client.updateRunStatus(status);
54
50
  }
55
51
  }
@@ -1,47 +1,98 @@
1
1
  // eslint-disable-next-line global-require, import/no-extraneous-dependencies
2
2
  const Mocha = require('mocha');
3
+ const debug = require('debug')('@testomatio/reporter:adapter:mocha');
4
+ const chalk = require('chalk');
3
5
  const TestomatClient = require('../client');
4
- const TRConstants = require('../constants');
5
- const { parseTest } = require('../util');
6
+ const { STATUS } = require('../constants');
7
+ const { parseTest, specificTestInfo } = require('../util');
8
+ const ArtifactStorage = require('../ArtifactStorage');
6
9
 
7
- const { EVENT_RUN_BEGIN, EVENT_RUN_END, EVENT_TEST_FAIL, EVENT_TEST_PASS } = Mocha.Runner.constants;
10
+ const { EVENT_RUN_BEGIN, EVENT_RUN_END, EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_TEST_PENDING } = Mocha.Runner.constants;
8
11
 
9
12
  function MochaReporter(runner, opts) {
10
13
  Mocha.reporters.Base.call(this, runner);
11
- let passes = 0;
12
- let failures = 0;
14
+ let passes = 0; let failures = 0; let skipped = 0;
15
+ let artifactStore;
13
16
 
14
- const client = new TestomatClient({ apiKey: opts?.reporterOptions?.apiKey });
17
+ const apiKey = opts?.reporterOptions?.apiKey || process.env.TESTOMATIO;
18
+
19
+ if (!apiKey) {
20
+ debug('TESTOMATIO key is empty, ignoring reports');
21
+ return;
22
+ }
23
+ const client = new TestomatClient({ apiKey });
15
24
 
16
25
  runner.on(EVENT_RUN_BEGIN, () => {
17
26
  client.createRun();
27
+
28
+ const params = {
29
+ toFile: true
30
+ };
31
+
32
+ artifactStore = runner._workerReporter !== undefined
33
+ ? new ArtifactStorage(params)
34
+ : new ArtifactStorage();
18
35
  });
19
36
 
20
- runner.on(EVENT_TEST_PASS, test => {
37
+ runner.on(EVENT_TEST_PASS, async(test) => {
21
38
  passes += 1;
22
- console.log('pass: %s', test.fullTitle());
39
+ console.log(chalk.bold.green(''), test.fullTitle());
40
+ const testId = parseTest(test.title);
41
+
42
+ const specificTest = specificTestInfo(test);
43
+ const content = await artifactStore.artifactByTestName(specificTest);
44
+
45
+ debug(`test=${specificTest} content = `, content);
46
+
47
+ client.addTestRun(
48
+ STATUS.PASSED,
49
+ {
50
+ test_id: testId,
51
+ title: test.title,
52
+ time: test.duration,
53
+ },
54
+ content
55
+ );
56
+ });
57
+
58
+ runner.on(EVENT_TEST_PENDING, test => {
59
+ skipped += 1;
60
+ console.log('skip: %s', test.fullTitle());
23
61
  const testId = parseTest(test.title);
24
- client.addTestRun(testId, TRConstants.PASSED, {
62
+ client.addTestRun(STATUS.SKIPPED, {
25
63
  title: test.title,
64
+ test_id: testId,
26
65
  time: test.duration,
27
66
  });
28
67
  });
29
68
 
30
- runner.on(EVENT_TEST_FAIL, (test, err) => {
69
+ runner.on(EVENT_TEST_FAIL, async(test, err) => {
31
70
  failures += 1;
32
- console.log('fail: %s -- error: %s', test.fullTitle(), err.message);
71
+ console.log(chalk.bold.red(''), test.fullTitle(), chalk.gray(err.message));
33
72
  const testId = parseTest(test.title);
34
- client.addTestRun(testId, TRConstants.FAILED, {
35
- error: err,
36
- title: test.title,
37
- time: test.duration,
38
- });
73
+
74
+ const specificTest = specificTestInfo(test);
75
+ const content = await artifactStore.artifactByTestName(specificTest);
76
+
77
+ debug(`fail test=${specificTest} content = `, content);
78
+
79
+ client.addTestRun(
80
+ STATUS.FAILED, {
81
+ error: err,
82
+ test_id: testId,
83
+ title: test.title,
84
+ time: test.duration,
85
+ },
86
+ content
87
+ );
39
88
  });
40
89
 
41
90
  runner.on(EVENT_RUN_END, () => {
42
- console.log('end: %d/%d', passes, passes + failures);
43
- const status = failures === 0 ? TRConstants.PASSED : TRConstants.FAILED;
91
+ const status = failures === 0 ? STATUS.PASSED : STATUS.FAILED;
92
+ console.log(chalk.bold(status), `${passes} passed, ${failures} failed, ${skipped} skipped`);
44
93
  client.updateRunStatus(status);
94
+
95
+ artifactStore.cleanup();
45
96
  });
46
97
  }
47
98
 
@@ -3,11 +3,12 @@ const crypto = require('crypto');
3
3
  const os = require('os');
4
4
  const path = require('path');
5
5
  const fs = require('fs');
6
- const Status = require('../constants');
6
+ const { APP_PREFIX, STATUS: Status } = require('../constants');
7
7
  const TestomatioClient = require('../client');
8
8
  const upload = require('../fileUploader');
9
9
  const { parseTest } = require('../util');
10
10
 
11
+
11
12
  class TestomatioReporter {
12
13
  constructor(config = {}) {
13
14
  this.client = new TestomatioClient({ apiKey: config?.apiKey });
@@ -56,8 +57,9 @@ class TestomatioReporter {
56
57
  files.push({ path: fileName, type: attachment.contentType });
57
58
  }
58
59
 
59
- this.client.addTestRun(testId, checkStatus(result.status), {
60
+ this.client.addTestRun(checkStatus(result.status), {
60
61
  error,
62
+ test_id: testId,
61
63
  suite_title,
62
64
  title,
63
65
  files,
@@ -69,15 +71,16 @@ class TestomatioReporter {
69
71
  async onEnd(result) {
70
72
  if (!this.client) return;
71
73
 
72
- if (this.videos.length && upload.isArtifactsEnabled) {
73
- console.log(Status.APP_PREFIX, `🎞️ Uploading ${this.videos.length} videos...`);
74
+ if (this.videos.length && upload.isArtifactsEnabled()) {
75
+ console.log(APP_PREFIX, `🎞️ Uploading ${this.videos.length} videos...`);
74
76
 
75
77
  const promises = [];
76
78
  for (const video of this.videos) {
77
79
  const { testId, title, attachment, suite_title } = video;
78
80
  const file = { path: attachment.path, title, type: attachment.contentType };
79
81
  promises.push(
80
- this.client.addTestRun(testId, undefined, {
82
+ this.client.addTestRun(undefined, {
83
+ test_id: testId,
81
84
  title,
82
85
  suite_title,
83
86
  files: [file],
@@ -43,9 +43,10 @@ class WebdriverReporter extends WDIOReporter {
43
43
  .filter(el => el.endpoint === screenshotEndpoint && el.result && el.result.value)
44
44
  .map(el => Buffer.from(el.result.value, 'base64'));
45
45
 
46
- await this.client.addTestRun(testId, state, {
46
+ await this.client.addTestRun(state, {
47
47
  error,
48
48
  title,
49
+ test_id: testId,
49
50
  time: duration,
50
51
  filesBuffers: screenshotsBuffers,
51
52
  });
@@ -2,7 +2,7 @@
2
2
  const program = require("commander");
3
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
7
  const XmlReader = require("../xmlReader");
8
8
 
@@ -22,7 +22,10 @@ program
22
22
  pattern += '.xml';
23
23
  }
24
24
  let { javaTests, lang } = opts;
25
- if (opts.envFile) require('dotenv').config(opts.envFile); // eslint-disable-line
25
+ if (opts.envFile) {
26
+ debug('Loading env file: %s', opts.envFile)
27
+ require('dotenv').config({ path: opts.envFile }); // eslint-disable-line
28
+ }
26
29
  if (javaTests === true) javaTests = 'src/test/java';
27
30
  lang = lang?.toLowerCase();
28
31
  const runReader = new XmlReader({ javaTests, lang });
@@ -3,7 +3,7 @@ const { spawn } = require('child_process');
3
3
  const program = require('commander');
4
4
  const chalk = require('chalk');
5
5
  const TestomatClient = require('../client');
6
- const { APP_PREFIX, FINISHED } = require('../constants');
6
+ const { APP_PREFIX, STATUS } = require('../constants');
7
7
  const { version } = require('../../package.json');
8
8
 
9
9
  console.log(chalk.cyan.bold(` 🤩 Testomat.io Reporter v${version}`));
@@ -42,7 +42,7 @@ program
42
42
 
43
43
  const client = new TestomatClient({ apiKey });
44
44
 
45
- client.updateRunStatus(FINISHED, true).then(() => {
45
+ client.updateRunStatus(STATUS.FINISHED, true).then(() => {
46
46
  console.log(chalk.yellow(`Run ${process.env.TESTOMATIO_RUN} was finished`));
47
47
  process.exit(0);
48
48
  });
package/lib/client.js CHANGED
@@ -1,29 +1,30 @@
1
- const axios = require('axios');
1
+ const debug = require('debug')('@testomatio/reporter:client');
2
2
  const createCallsiteRecord = require('callsite-record');
3
3
  const { sep, join } = require('path');
4
4
  const fs = require('fs');
5
5
  const chalk = require('chalk');
6
+ const { randomUUID } = require('crypto');
6
7
  const upload = require('./fileUploader');
7
- const { PASSED, FAILED, FINISHED, APP_PREFIX } = require('./constants');
8
+ const { APP_PREFIX } = require('./constants');
8
9
  const pipesFactory = require('./pipe');
9
10
 
10
- const { TESTOMATIO_ENV } = process.env;
11
+ /**
12
+ * @typedef {import('../types').TestData} TestData
13
+ * @typedef {import('../types').RunStatus} RunStatus
14
+ */
11
15
 
12
-
13
- class TestomatClient {
16
+ class Client {
14
17
  /**
15
18
  * Create a Testomat client instance
16
19
  *
17
20
  * @param {*} params
18
21
  */
19
22
  constructor(params = {}) {
20
- this.title = params.title || process.env.TESTOMATIO_TITLE;
21
- this.env = TESTOMATIO_ENV;
22
23
  this.parallel = params.parallel;
23
- const store = {}
24
+ const store = {};
25
+ this.uuid = randomUUID();
24
26
  this.pipes = pipesFactory(params, store);
25
27
  this.queue = Promise.resolve();
26
- this.axios = axios.create();
27
28
  this.totalUploaded = 0;
28
29
  this.version = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString()).version;
29
30
  console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
@@ -32,11 +33,11 @@ class TestomatClient {
32
33
  /**
33
34
  * Used to create a new Test run
34
35
  *
35
- * @returns {Promise} - resolves to Run id which should be used to update / add test
36
+ * @returns {Promise<void>} - resolves to Run id which should be used to update / add test
36
37
  */
37
38
  createRun() {
38
39
  // all pipes disabled, skipping
39
- if (!this.pipes.filter(p => p.isEnabled).length) return;
40
+ if (!this.pipes.filter(p => p.isEnabled).length) return Promise.resolve();
40
41
 
41
42
  const runParams = {
42
43
  title: this.title,
@@ -46,27 +47,39 @@ class TestomatClient {
46
47
 
47
48
  global.testomatioArtifacts = [];
48
49
 
49
- this.queue = this.queue.then(() => Promise.all(this.pipes.map(p => p.createRun(runParams))));
50
-
50
+ this.queue = this.queue
51
+ .then(() => Promise.all(this.pipes.map(p => p.createRun(runParams))))
52
+ .catch(err => console.log(APP_PREFIX, err))
53
+ .then(() => undefined); // fixes return type
54
+ debug('Run', this.queue);
51
55
  return this.queue;
52
56
  }
53
57
 
54
58
  /**
55
- * Used to add a new test to Run instance
56
- *
57
- * @returns {Promise}
59
+ * Updates test status and its data
60
+ *
61
+ * @param {string|undefined} status
62
+ * @param {TestData} [testData]
63
+ * @param {string[]} [storeArtifacts]
64
+ * @returns {Promise<void>}
58
65
  */
59
- async addTestRun(testId, status, testData = {}) {
66
+ async addTestRun(status, testData, storeArtifacts = []) {
60
67
  // all pipes disabled, skipping
61
68
  if (!this.pipes.filter(p => p.isEnabled).length) return;
62
69
 
70
+ if (!testData) testData = {
71
+ title: 'Unknown test',
72
+ suite_title: 'Unknown suite',
73
+ }
74
+
63
75
  const {
64
- error = '',
76
+ error = null,
65
77
  time = '',
66
78
  example = null,
67
79
  files = [],
68
80
  filesBuffers = [],
69
81
  steps,
82
+ code = null,
70
83
  title,
71
84
  suite_title,
72
85
  suite_id,
@@ -76,33 +89,37 @@ class TestomatClient {
76
89
 
77
90
  const uploadedFiles = [];
78
91
 
79
- if (testId) testData.test_id = testId;
80
-
81
92
  let stack = '';
82
93
 
83
94
  if (error) {
84
- stack = this.formatError(error);
85
- message = error.message;
95
+ stack = this.formatError(error) || '';
96
+ message = error?.message;
86
97
  }
87
98
  if (steps) {
88
99
  stack = this.formatSteps(stack, steps);
89
100
  }
90
101
 
102
+ if (Array.isArray(storeArtifacts) && storeArtifacts.length) {
103
+ debug("CLIENT storeArtifact", storeArtifacts);
104
+ files.push(...storeArtifacts);
105
+ }
106
+
91
107
  if (Array.isArray(global.testomatioArtifacts)) {
108
+ debug("CLIENT global[testomatioArtifacts]", global.testomatioArtifacts);
92
109
  files.push(...global.testomatioArtifacts);
93
110
  global.testomatioArtifacts = [];
94
111
  }
95
112
 
96
113
  for (const file of files) {
97
- uploadedFiles.push(upload.uploadFileByPath(file, this.runId));
114
+ uploadedFiles.push(upload.uploadFileByPath(file, this.uuid));
98
115
  }
99
116
 
100
117
  for (const [idx, buffer] of filesBuffers.entries()) {
101
118
  const fileName = `${idx + 1}-${title.replace(/\s+/g, '-')}`;
102
- uploadedFiles.push(upload.uploadFileAsBuffer(buffer, fileName, this.runId));
119
+ uploadedFiles.push(upload.uploadFileAsBuffer(buffer, fileName, this.uuid));
103
120
  }
104
121
 
105
- const artifacts = await Promise.all(uploadedFiles)
122
+ const artifacts = await Promise.all(uploadedFiles);
106
123
 
107
124
  global.testomatioArtifacts = [];
108
125
 
@@ -114,7 +131,8 @@ class TestomatClient {
114
131
  status,
115
132
  stack,
116
133
  example,
117
- title,
134
+ code,
135
+ title,
118
136
  suite_title,
119
137
  suite_id,
120
138
  test_id,
@@ -123,36 +141,42 @@ class TestomatClient {
123
141
  artifacts,
124
142
  };
125
143
 
126
- this.queue = this.queue.then(() => Promise.all(this.pipes.map(p => p.addTest(data))));
144
+ this.queue = this.queue
145
+ .then(() => Promise.all(this.pipes.map(p => p.addTest(data))))
146
+ .catch(err => console.log(APP_PREFIX, err))
147
+ .then(() => undefined);
127
148
  return this.queue;
128
149
  }
129
150
 
130
151
  /**
131
- * Update run status
132
152
  *
133
- * @returns {Promise}
153
+ * Updates the status of the current test run and finishes the run.
154
+ * @param {RunStatus} status - The status of the current test run. Must be one of "passed", "failed", or "finished"
155
+ * @param {boolean} [isParallel] - Whether the current test run was executed in parallel with other tests.
156
+ * @returns {Promise<void>} - A Promise that resolves when finishes the run.
134
157
  */
135
- updateRunStatus(status, isParallel) {
158
+ updateRunStatus(status, isParallel = false) {
136
159
  // all pipes disabled, skipping
137
- if (!this.pipes.filter(p => p.isEnabled).length) return;
160
+ if (!this.pipes.filter(p => p.isEnabled).length) return Promise.resolve();
161
+
162
+ const runParams = { status, parallel: isParallel };
163
+
164
+ this.queue = this.queue
165
+ .then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
166
+ .then(() => {
167
+ debug("TOTAL uploaded files", this.totalUploaded);
168
+
169
+ if (upload.isArtifactsEnabled() && this.totalUploaded > 0) {
170
+ console.log(
171
+ APP_PREFIX,
172
+ `🗄️ Total ${this.totalUploaded} artifacts ${
173
+ process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
174
+ } uploaded to S3 bucket `,
175
+ );
176
+ }
177
+ })
178
+ .catch(err => console.log(APP_PREFIX, err));
138
179
 
139
- let statusEvent;
140
- if (status === FINISHED) statusEvent = 'finish';
141
- if (status === PASSED) statusEvent = 'pass';
142
- if (status === FAILED) statusEvent = 'fail';
143
- if (isParallel) statusEvent += '_parallel';
144
- const runParams = { status, statusEvent }
145
-
146
- this.queue = this.queue.then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))));
147
-
148
- if (upload.isArtifactsEnabled && this.totalUploaded > 0) {
149
- console.log(
150
- APP_PREFIX,
151
- `🗄️ Total ${this.totalUploaded} artifacts ${
152
- process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
153
- } uploaded to S3 bucket `,
154
- );
155
- }
156
180
  return this.queue;
157
181
  }
158
182
 
@@ -193,4 +217,4 @@ class TestomatClient {
193
217
  }
194
218
  }
195
219
 
196
- module.exports = TestomatClient;
220
+ module.exports = Client;
package/lib/constants.js CHANGED
@@ -1,11 +1,26 @@
1
1
  const chalk = require('chalk');
2
2
 
3
3
  const APP_PREFIX = chalk.gray('[TESTOMATIO]');
4
+ const TESTOMAT_ARTIFACT_SUFFIX = "testomatio_artifact_";
4
5
 
5
- module.exports = Object.freeze({
6
+ const CSV_HEADERS = [
7
+ { id: 'suite_title', title: 'Suite_title' },
8
+ { id: 'title', title: 'Title' },
9
+ { id: 'status', title: 'Status' },
10
+ { id: 'message', title: 'Message' },
11
+ { id: 'stack', title: 'Stack' },
12
+ ];
13
+
14
+ const STATUS = {
6
15
  PASSED: 'passed',
7
16
  FAILED: 'failed',
8
17
  SKIPPED: 'skipped',
9
18
  FINISHED: 'finished',
19
+ };
20
+
21
+ module.exports = {
10
22
  APP_PREFIX,
11
- });
23
+ TESTOMAT_ARTIFACT_SUFFIX,
24
+ CSV_HEADERS,
25
+ STATUS,
26
+ }