@testomatio/reporter 1.0.17 → 1.1.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.
@@ -1,7 +1,7 @@
1
1
  const debug = require('debug')('@testomatio/reporter:adapter:codeceptjs');
2
2
  const chalk = require('chalk');
3
3
  const TestomatClient = require('../client');
4
- const { STATUS, APP_PREFIX, TESTOMAT_TMP_STORAGE } = require('../constants');
4
+ const { STATUS, APP_PREFIX, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
5
5
  const upload = require('../fileUploader');
6
6
  const { parseTest: getIdFromTestTitle, fileSystem } = require('../utils/utils');
7
7
 
@@ -56,7 +56,7 @@ function CodeceptReporter(config) {
56
56
  // Listening to events
57
57
  event.dispatcher.on(event.all.before, () => {
58
58
  // clear tmp dir
59
- fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
59
+ fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
60
60
 
61
61
  recorder.add('Creating new run', () => client.createRun());
62
62
  videos = [];
@@ -2,9 +2,9 @@
2
2
  const { Formatter, formatterHelpers } = require('@cucumber/cucumber');
3
3
  const chalk = require('chalk');
4
4
  const fs = require('fs');
5
- const { STATUS, TESTOMAT_TMP_STORAGE } = require('../../constants');
5
+ const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../../constants');
6
6
  const TestomatClient = require('../../client');
7
- const logger = require('../../logger');
7
+ const logger = require('../../storages/logger');
8
8
  const { parseTest, fileSystem } = require('../../utils/utils');
9
9
 
10
10
  const { GherkinDocumentParser, PickleParser } = formatterHelpers;
@@ -37,7 +37,7 @@ class CucumberReporter extends Formatter {
37
37
 
38
38
  parseEnvelope(envelope) {
39
39
  if (envelope.testRunStarted) {
40
- fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
40
+ fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
41
41
  }
42
42
  if (envelope.testCaseStarted && this.client) {
43
43
  this.client.createRun();
@@ -2,7 +2,7 @@
2
2
  const { Formatter } = require('cucumber');
3
3
  const chalk = require('chalk');
4
4
  const { parseTest, fileSystem } = require('../../utils/utils');
5
- const { STATUS, TESTOMAT_TMP_STORAGE } = require('../../constants');
5
+ const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../../constants');
6
6
  const TestomatClient = require('../../client');
7
7
 
8
8
  const createTestomatFormatter = apiKey => {
@@ -97,7 +97,7 @@ const createTestomatFormatter = apiKey => {
97
97
 
98
98
  options.eventBroadcaster.on('gherkin-document', addDocument);
99
99
  options.eventBroadcaster.on('test-run-started', () => {
100
- fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
100
+ fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
101
101
  this?.client?.createRun();
102
102
  });
103
103
  options.eventBroadcaster.on('test-case-finished', this.onTestCaseFinished.bind(this));
@@ -1,5 +1,5 @@
1
1
  const TestomatClient = require('../client');
2
- const { STATUS, TESTOMAT_TMP_STORAGE } = require('../constants');
2
+ const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
3
3
  const { parseTest, ansiRegExp, fileSystem } = require('../utils/utils');
4
4
 
5
5
  class JestReporter {
@@ -11,20 +11,9 @@ class JestReporter {
11
11
  this.client.createRun();
12
12
  }
13
13
 
14
- static getIdOfCurrentlyRunningTest() {
15
- if (!process.env.JEST_WORKER_ID) return null;
16
- try {
17
- // @ts-expect-error "expect" could only be defined inside Jest environement (forbidden to import it outside)
18
- // eslint-disable-next-line no-undef
19
- if (expect && expect?.getState()?.currentTestName) return parseTest(expect?.getState()?.currentTestName);
20
- } catch (e) {
21
- return null;
22
- }
23
- }
24
-
25
14
  onRunStart() {
26
15
  // clear tmp dir
27
- fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
16
+ fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
28
17
  }
29
18
 
30
19
  onTestResult(test, testResult) {
@@ -72,9 +61,6 @@ class JestReporter {
72
61
  const { numFailedTests } = results;
73
62
  const status = numFailedTests === 0 ? STATUS.PASSED : STATUS.FAILED;
74
63
  this.client.updateRunStatus(status);
75
-
76
- // clear tmp dir
77
- fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
78
64
  }
79
65
  }
80
66
 
@@ -1,11 +1,9 @@
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
3
  const chalk = require('chalk');
5
4
  const TestomatClient = require('../client');
6
- const { STATUS, TESTOMAT_TMP_STORAGE } = require('../constants');
7
- const { parseTest, specificTestInfo, fileSystem } = require('../utils/utils');
8
- const ArtifactStorage = require('../_ArtifactStorageOld');
5
+ const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
6
+ const { parseTest, fileSystem } = require('../utils/utils');
9
7
 
10
8
  const { EVENT_RUN_BEGIN, EVENT_RUN_END, EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_TEST_PENDING } =
11
9
  Mocha.Runner.constants;
@@ -15,7 +13,7 @@ function MochaReporter(runner, opts) {
15
13
  let passes = 0;
16
14
  let failures = 0;
17
15
  let skipped = 0;
18
- let artifactStore;
16
+ // let artifactStore;
19
17
 
20
18
  const apiKey = opts?.reporterOptions?.apiKey || process.env.TESTOMATIO;
21
19
 
@@ -24,13 +22,7 @@ function MochaReporter(runner, opts) {
24
22
  runner.on(EVENT_RUN_BEGIN, () => {
25
23
  client.createRun();
26
24
 
27
- const params = {
28
- toFile: true,
29
- };
30
-
31
- artifactStore = runner._workerReporter !== undefined ? new ArtifactStorage(params) : new ArtifactStorage();
32
-
33
- fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
25
+ fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
34
26
  });
35
27
 
36
28
  runner.on(EVENT_TEST_PASS, async test => {
@@ -38,20 +30,11 @@ function MochaReporter(runner, opts) {
38
30
  console.log(chalk.bold.green('✔'), test.fullTitle());
39
31
  const testId = parseTest(test.title);
40
32
 
41
- const specificTest = specificTestInfo(test);
42
- const content = await artifactStore.artifactByTestName(specificTest);
43
-
44
- debug(`test=${specificTest} content = `, content);
45
-
46
- client.addTestRun(
47
- STATUS.PASSED,
48
- {
49
- test_id: testId,
50
- title: test.title,
51
- time: test.duration,
52
- },
53
- content,
54
- );
33
+ client.addTestRun(STATUS.PASSED, {
34
+ test_id: testId,
35
+ title: test.title,
36
+ time: test.duration,
37
+ });
55
38
  });
56
39
 
57
40
  runner.on(EVENT_TEST_PENDING, test => {
@@ -70,29 +53,18 @@ function MochaReporter(runner, opts) {
70
53
  console.log(chalk.bold.red('✖'), test.fullTitle(), chalk.gray(err.message));
71
54
  const testId = parseTest(test.title);
72
55
 
73
- const specificTest = specificTestInfo(test);
74
- const content = await artifactStore.artifactByTestName(specificTest);
75
-
76
- debug(`fail test=${specificTest} content = `, content);
77
-
78
- client.addTestRun(
79
- STATUS.FAILED,
80
- {
81
- error: err,
82
- test_id: testId,
83
- title: test.title,
84
- time: test.duration,
85
- },
86
- content,
87
- );
56
+ client.addTestRun(STATUS.FAILED, {
57
+ error: err,
58
+ test_id: testId,
59
+ title: test.title,
60
+ time: test.duration,
61
+ });
88
62
  });
89
63
 
90
64
  runner.on(EVENT_RUN_END, () => {
91
65
  const status = failures === 0 ? STATUS.PASSED : STATUS.FAILED;
92
66
  console.log(chalk.bold(status), `${passes} passed, ${failures} failed, ${skipped} skipped`);
93
67
  client.updateRunStatus(status);
94
-
95
- artifactStore.cleanup();
96
68
  });
97
69
  }
98
70
 
@@ -3,7 +3,7 @@ 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, TESTOMAT_TMP_STORAGE } = require('../constants');
6
+ const { APP_PREFIX, STATUS: Status, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
7
7
  const TestomatioClient = require('../client');
8
8
  const { isArtifactsEnabled } = require('../fileUploader');
9
9
  const { parseTest, fileSystem } = require('../utils/utils');
@@ -19,15 +19,17 @@ class PlaywrightReporter {
19
19
 
20
20
  onBegin(_config, suite) {
21
21
  // clean data storage
22
- fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
22
+ fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
23
23
  if (!this.client) return;
24
24
  this.suite = suite;
25
25
  this.client.createRun();
26
26
  }
27
27
 
28
- // onTestBegin(test) {
29
- // const testId = parseTest(test.title);
30
- // }
28
+ /*
29
+ onTestBegin(test) {
30
+ // does not work; value is not to the storage context
31
+ global.testTitle = test.title;
32
+ } */
31
33
 
32
34
  onTestEnd(test, result) {
33
35
  if (!this.client) return;
@@ -44,23 +46,33 @@ class PlaywrightReporter {
44
46
  appendStep(step, steps);
45
47
  }
46
48
 
47
- const logs = `\n\n${chalk.bold('Logs:')}\n${chalk.red(result.stderr.join(''))}\n${result.stdout.join('')}`;
48
-
49
- const reportTestPromise = this.client.addTestRun(checkStatus(result.status), {
50
- error,
51
- test_id: testId,
52
- suite_title,
53
- title,
54
- steps: steps.join('\n'),
55
- time: duration,
56
- stack: logs,
57
- }).then(pipes => {
58
- testId = pipes?.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
59
-
60
- this.uploads.push({
61
- testId, title, suite_title, files: result.attachments.filter((a) => a.body || a.path)
49
+ let logs = '';
50
+ if (result.stderr.length || result.stdout.length) {
51
+ logs = `\n\n${chalk.bold('Logs:')}\n${chalk.red(result.stderr.join(''))}\n${result.stdout.join('')}`;
52
+ }
53
+
54
+ const reportTestPromise = this.client
55
+ .addTestRun(checkStatus(result.status), {
56
+ error,
57
+ test_id: testId,
58
+ suite_title,
59
+ title,
60
+ steps: steps.join('\n'),
61
+ time: duration,
62
+ stack: logs,
63
+ })
64
+ .then(pipes => {
65
+ testId = pipes?.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
66
+
67
+ this.uploads.push({
68
+ testId,
69
+ title,
70
+ suite_title,
71
+ files: result.attachments.filter(a => a.body || a.path),
72
+ });
73
+ // remove empty uploads
74
+ this.uploads = this.uploads.filter(upload => upload.files.length);
62
75
  });
63
- });
64
76
 
65
77
  reportTestPromises.push(reportTestPromise);
66
78
  }
package/lib/client.js CHANGED
@@ -8,6 +8,7 @@ const { randomUUID } = require('crypto');
8
8
  const upload = require('./fileUploader');
9
9
  const { APP_PREFIX } = require('./constants');
10
10
  const pipesFactory = require('./pipe');
11
+ const artifactStorage = require('./storages/artifact-storage');
11
12
 
12
13
  /**
13
14
  * @typedef {import('../types').TestData} TestData
@@ -96,9 +97,6 @@ class Client {
96
97
  // all pipes disabled, skipping
97
98
  if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
98
99
 
99
- global.testomatioArtifacts = [];
100
- if (!global.testomatioDataStore) global.testomatioDataStore = {};
101
-
102
100
  this.queue = this.queue
103
101
  .then(() => Promise.all(this.pipes.map(p => p.createRun())))
104
102
  .catch(err => console.log(APP_PREFIX, err))
@@ -112,11 +110,9 @@ class Client {
112
110
  *
113
111
  * @param {string|undefined} status
114
112
  * @param {TestData} [testData]
115
- * @param {string[]} [storeArtifacts]
116
113
  * @returns {Promise<PipeResult[]>}
117
114
  */
118
- async addTestRun(status, testData, storeArtifacts = []) {
119
- debug('Adding test run for test', testData?.test_id || 'unknown test');
115
+ async addTestRun(status, testData) {
120
116
  // all pipes disabled, skipping
121
117
  if (!this.pipes?.filter(p => p.isEnabled).length) return [];
122
118
 
@@ -155,23 +151,21 @@ class Client {
155
151
 
156
152
  stack += testData.stack || '';
157
153
 
154
+ // ATTACH LOGS from storage
158
155
  // 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');
156
+ const logger = require('./storages/logger');
160
157
  const testLogs = logger.getLogs(test_id);
161
158
  // debug(`Test logs for ${test_id}:\n`, testLogs);
162
159
  if (stack) stack += '\n\n';
163
160
  stack += testLogs ? `${chalk.bold('Logs:')}\n${testLogs}` : '';
164
161
 
165
- if (Array.isArray(storeArtifacts) && storeArtifacts.length) {
166
- debug('CLIENT storeArtifact', storeArtifacts);
167
- files.push(...storeArtifacts);
168
- }
162
+ // GET ARTIFACTS from storage
163
+ const artifactFiles = artifactStorage.get(test_id);
164
+ if (artifactFiles) files.push(...artifactFiles);
169
165
 
170
- if (Array.isArray(global.testomatioArtifacts)) {
171
- debug('CLIENT global[testomatioArtifacts]', global.testomatioArtifacts);
172
- files.push(...global.testomatioArtifacts);
173
- global.testomatioArtifacts = [];
174
- }
166
+ // GET KEY-VALUEs from storage
167
+ const keyValueStorage = require('./storages/key-value-storage');
168
+ const keyValues = keyValueStorage.get(test_id);
175
169
 
176
170
  for (const file of files) {
177
171
  uploadedFiles.push(upload.uploadFileByPath(file, this.uuid));
@@ -184,7 +178,7 @@ class Client {
184
178
 
185
179
  const artifacts = await Promise.all(uploadedFiles);
186
180
 
187
- global.testomatioArtifacts = [];
181
+ // global.testomatioArtifacts = [];
188
182
 
189
183
  this.totalUploaded += uploadedFiles.filter(n => n).length;
190
184
 
@@ -202,6 +196,7 @@ class Client {
202
196
  message,
203
197
  run_time: parseFloat(time),
204
198
  artifacts,
199
+ meta: keyValues,
205
200
  };
206
201
 
207
202
  this.queue = this.queue.then(() =>
@@ -237,14 +232,16 @@ class Client {
237
232
  this.queue = this.queue
238
233
  .then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
239
234
  .then(() => {
240
- debug('TOTAL uploaded files', this.totalUploaded);
235
+ debug('TOTAL artifacts', this.totalUploaded);
236
+ if (this.totalUploaded && !upload.isArtifactsEnabled())
237
+ debug(`${this.totalUploaded} artifacts are not uploaded, because artifacts uploading is not enabled`);
241
238
 
242
- if (upload.isArtifactsEnabled() && this.totalUploaded > 0) {
239
+ if (this.totalUploaded && upload.isArtifactsEnabled()) {
243
240
  console.log(
244
241
  APP_PREFIX,
245
- `🗄️ Total ${this.totalUploaded} artifacts ${
242
+ `🗄️ ${this.totalUploaded} artifacts ${
246
243
  process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
247
- } uploaded to S3 bucket `,
244
+ } uploaded to S3 bucket`,
248
245
  );
249
246
  }
250
247
  })
package/lib/constants.js CHANGED
@@ -1,11 +1,10 @@
1
1
  const chalk = require('chalk');
2
+ const os = require('os');
3
+ const path = require('path');
2
4
 
3
5
  const APP_PREFIX = chalk.gray('[TESTOMATIO]');
4
- const TESTOMAT_ARTIFACT_SUFFIX = "testomatio_artifact_";
5
6
 
6
- const TESTOMAT_TMP_STORAGE = {
7
- mainDir: "testomatio_tmp",
8
- }
7
+ const TESTOMAT_TMP_STORAGE_DIR = path.join(os.tmpdir(), 'testomatio_tmp');
9
8
 
10
9
  const CSV_HEADERS = [
11
10
  { id: 'suite_title', title: 'Suite_title' },
@@ -24,8 +23,7 @@ const STATUS = {
24
23
 
25
24
  module.exports = {
26
25
  APP_PREFIX,
27
- TESTOMAT_ARTIFACT_SUFFIX,
28
- TESTOMAT_TMP_STORAGE,
26
+ TESTOMAT_TMP_STORAGE_DIR,
29
27
  CSV_HEADERS,
30
28
  STATUS,
31
29
  }
@@ -0,0 +1,43 @@
1
+ const logger = require('./storages/logger');
2
+ const artifactStorage = require('./storages/artifact-storage');
3
+ const keyValueStorage = require('./storages/key-value-storage');
4
+
5
+ /**
6
+ * Stores path to file as artifact and uploads it to the S3 storage
7
+ * @param {string | {path: string, type: string, name: string}} data - path to file or object with path, type and name
8
+ */
9
+ function saveArtifact(data, context = null) {
10
+ if (!data) return;
11
+ artifactStorage.put(data, context);
12
+ }
13
+
14
+ /**
15
+ * Attach log message(s) to the test report
16
+ * @param {...any} args
17
+ */
18
+ function logMessage(...args) {
19
+ logger.log(...args);
20
+ }
21
+
22
+ /**
23
+ * Similar to "log" function but marks message in report as a step
24
+ * @param {*} message
25
+ */
26
+ function addStep(message) {
27
+ logger.step(message);
28
+ }
29
+
30
+ /**
31
+ * Add key-value pair(s) to the test report
32
+ * @param {*} keyValue
33
+ */
34
+ function setKeyValue(keyValue) {
35
+ keyValueStorage.put(keyValue);
36
+ }
37
+
38
+ module.exports = {
39
+ artifact: saveArtifact,
40
+ log: logMessage,
41
+ step: addStep,
42
+ keyValue: setKeyValue,
43
+ };
package/lib/reporter.js CHANGED
@@ -1,10 +1,10 @@
1
- const logger = require('./logger');
1
+ const logger = require('./storages/logger');
2
2
  const TestomatClient = require('./client');
3
3
  const TRConstants = require('./constants');
4
- const TRArtifacts = require('./_ArtifactStorageOld');
5
4
 
6
5
  const log = logger.templateLiteralLog.bind(logger);
7
6
  const step = logger.step.bind(logger);
7
+ const reporterFunctions = require('./reporter-functions');
8
8
 
9
9
  module.exports = {
10
10
  logger,
@@ -13,6 +13,11 @@ module.exports = {
13
13
  step,
14
14
  TestomatClient,
15
15
  TRConstants,
16
- TRArtifacts,
17
- addArtifact: TRArtifacts.artifact,
16
+ };
17
+
18
+ module.exports.testomat = {
19
+ artifact: reporterFunctions.artifact,
20
+ log: reporterFunctions.log,
21
+ step: reporterFunctions.step,
22
+ meta: reporterFunctions.keyValue,
18
23
  };
@@ -0,0 +1,70 @@
1
+ const debug = require('debug')('@testomatio/reporter:artifact-storage');
2
+ const DataStorage = require('./data-storage');
3
+
4
+ /**
5
+ * Artifact storage is supposed to store file paths
6
+ */
7
+ class ArtifactStorage {
8
+
9
+ // there is autocompletion for the class methods if implemented singleton this way
10
+ constructor() {
11
+ this.dataStorage = new DataStorage('artifact');
12
+
13
+ // singleton
14
+ if (!ArtifactStorage.instance) {
15
+ ArtifactStorage.instance = this;
16
+ }
17
+ }
18
+
19
+ // singleton
20
+ static getInstance() {
21
+ if (!ArtifactStorage.instance) {
22
+ ArtifactStorage.instance = new ArtifactStorage();
23
+ }
24
+
25
+ return ArtifactStorage.instance;
26
+ }
27
+
28
+ /**
29
+ * Stores path to file as artifact and uploads it to the S3 storage
30
+ * @param {string | {path: string, type: string, name: string}} data - path to file or object with path, type and name
31
+ * @param {*} context testId or test title
32
+ */
33
+ put(data, context = null) {
34
+ if (!data) return;
35
+ debug('Save artifact:', data);
36
+ this.dataStorage.putData(data, context);
37
+ }
38
+
39
+ /**
40
+ * Returns list of artifacts to upload
41
+ * @param {*} context testId or test context from test runner
42
+ * @returns {string | {path: string, type: string, name: string}[]}
43
+ */
44
+ get(context) {
45
+ if (!context) return null;
46
+
47
+ // array of any data
48
+ let artifacts = this.dataStorage.getData(context);
49
+ if (!artifacts || !artifacts.length) return null;
50
+
51
+ artifacts = artifacts.map(artifactData => {
52
+ // artifact could be an object ({type, path, name} props) or string
53
+ let artifact;
54
+ try {
55
+ artifact = JSON.parse(artifactData);
56
+ } catch (e) {
57
+ artifact = artifactData;
58
+ }
59
+ return artifact;
60
+ });
61
+ artifacts = artifacts.filter(artifact => !!artifact);
62
+ return artifacts.length ? artifacts : null;
63
+ }
64
+ }
65
+
66
+ ArtifactStorage.instance = null;
67
+
68
+ // const artifactStorage = ArtifactStorage.getInstance();
69
+ const artifactStorage = new ArtifactStorage();
70
+ module.exports = artifactStorage;