@testomatio/reporter 2.1.0-beta.1-codeceptjs → 2.1.0-beta.2-codeceptjs

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/lib/client.js CHANGED
@@ -179,7 +179,7 @@ class Client {
179
179
  /**
180
180
  * @type {TestData}
181
181
  */
182
- const { rid, error = null, time = 0, example = null, files = [], filesBuffers = [], steps, code = null, title, file, suite_title, suite_id, test_id, timestamp, manuallyAttachedArtifacts, labels, } = testData;
182
+ const { rid, error = null, time = 0, example = null, files = [], filesBuffers = [], steps, code = null, title, file, suite_title, suite_id, test_id, timestamp, manuallyAttachedArtifacts, labels, overwrite, } = testData;
183
183
  let { message = '', meta = {} } = testData;
184
184
  // stringify meta values and limit keys and values length to 255
185
185
  meta = Object.entries(meta)
@@ -267,6 +267,7 @@ class Client {
267
267
  artifacts,
268
268
  meta,
269
269
  labels,
270
+ overwrite,
270
271
  ...(rootSuiteId && { root_suite_id: rootSuiteId }),
271
272
  };
272
273
  // debug('Adding test run...', data);
package/lib/replay.js CHANGED
@@ -35,7 +35,10 @@ class Replay {
35
35
  throw new Error(`Debug file not found: ${debugFile}`);
36
36
  }
37
37
  const fileContent = fs_1.default.readFileSync(debugFile, 'utf-8');
38
- const lines = fileContent.trim().split('\n').filter(line => line.trim() !== '');
38
+ const lines = fileContent
39
+ .trim()
40
+ .split('\n')
41
+ .filter(line => line.trim() !== '');
39
42
  if (lines.length === 0) {
40
43
  throw new Error('Debug file is empty');
41
44
  }
@@ -79,7 +82,8 @@ class Replay {
79
82
  // Merge artifacts arrays
80
83
  mergedTest.artifacts = [...(existingTest.artifacts || []), ...test[key]];
81
84
  }
82
- else if (existingTest[key] === null || existingTest[key] === undefined ||
85
+ else if (existingTest[key] === null ||
86
+ existingTest[key] === undefined ||
83
87
  (Array.isArray(existingTest[key]) && existingTest[key].length === 0)) {
84
88
  // Use new value if existing is null/undefined/empty array
85
89
  mergedTest[key] = test[key];
@@ -145,7 +149,7 @@ class Replay {
145
149
  envVars,
146
150
  parseErrors,
147
151
  totalLines: lines.length,
148
- runId
152
+ runId,
149
153
  };
150
154
  }
151
155
  /**
@@ -190,7 +194,7 @@ class Replay {
190
194
  finishParams,
191
195
  envVars,
192
196
  runId,
193
- dryRun: true
197
+ dryRun: true,
194
198
  };
195
199
  }
196
200
  // Create client and restore the run
@@ -213,13 +217,13 @@ class Replay {
213
217
  let failureCount = 0;
214
218
  for (const [index, test] of tests.entries()) {
215
219
  try {
216
- await client.addTestRun(test.status, test);
220
+ await client.addTestRun(test.status, { ...test, overwrite: true });
217
221
  successCount++;
218
222
  this.onProgress({
219
223
  current: index + 1,
220
224
  total: tests.length,
221
225
  test,
222
- success: true
226
+ success: true,
223
227
  });
224
228
  }
225
229
  catch (err) {
@@ -230,7 +234,7 @@ class Replay {
230
234
  total: tests.length,
231
235
  test,
232
236
  success: false,
233
- error: err.message
237
+ error: err.message,
234
238
  });
235
239
  }
236
240
  }
@@ -243,7 +247,7 @@ class Replay {
243
247
  runParams,
244
248
  finishParams,
245
249
  envVars,
246
- runId: runId || client.runId
250
+ runId: runId || client.runId,
247
251
  };
248
252
  this.onLog(`Successfully replayed ${successCount}/${tests.length} tests from debug file`);
249
253
  return result;
package/lib/xmlReader.js CHANGED
@@ -186,6 +186,7 @@ class XmlReader {
186
186
  if (test.file)
187
187
  r.file = test.file;
188
188
  r.create = true;
189
+ r.overwrite = true;
189
190
  if (r.status === 'Passed')
190
191
  r.status = constants_js_1.STATUS.PASSED;
191
192
  if (r.status === 'Failed')
@@ -254,6 +255,7 @@ class XmlReader {
254
255
  title,
255
256
  suite_title,
256
257
  run_time,
258
+ retry: false,
257
259
  });
258
260
  });
259
261
  });
@@ -509,6 +511,7 @@ function reduceTestCases(prev, item) {
509
511
  root_suite_id: TESTOMATIO_SUITE,
510
512
  suite_title: suiteTitle,
511
513
  files,
514
+ retry: false,
512
515
  });
513
516
  });
514
517
  return prev;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "2.1.0-beta.1-codeceptjs",
3
+ "version": "2.1.0-beta.2-codeceptjs",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "engines": {
6
6
  "node": ">=18"
@@ -14,8 +14,10 @@ class WebdriverReporter extends WDIOReporter {
14
14
  this._addTestPromises = [];
15
15
 
16
16
  this._isSynchronising = false;
17
- // NOTE: new functionality; may break everything
18
- this.client.createRun();
17
+
18
+ // run is created by cli, if enabling the row below, it mat lead to multiple runs being created
19
+ // thus, need to check if process.env.runId is set and/or add more checks to avoid creating multiple runs
20
+ // this.client.createRun();
19
21
  }
20
22
 
21
23
  get isSynchronised() {
@@ -40,7 +42,6 @@ class WebdriverReporter extends WDIOReporter {
40
42
 
41
43
  onRunnerStart() {
42
44
  // clear dir with artifacts/logs
43
- //
44
45
  fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
45
46
  }
46
47
 
@@ -140,3 +141,10 @@ function getTestLogs(fullTestTitle) {
140
141
  }
141
142
 
142
143
  export default WebdriverReporter;
144
+
145
+ /* INVESTIGATION RESULTS:
146
+ If you run tests in parallel, the WDIO creates a separate process for each parallel instance.
147
+ As a result, there is own WDIOReporter instance for each parallel process.
148
+ This means, its impossible to create or finish run, because can't understand if its was already created
149
+ in other process or not.
150
+ */
package/src/bin/cli.js CHANGED
@@ -7,7 +7,7 @@ import createDebugMessages from 'debug';
7
7
  import TestomatClient from '../client.js';
8
8
  import XmlReader from '../xmlReader.js';
9
9
  import { APP_PREFIX, STATUS } from '../constants.js';
10
- import { getPackageVersion } from '../utils/utils.js';
10
+ import { cleanLatestRunId, getPackageVersion } from '../utils/utils.js';
11
11
  import { config } from '../config.js';
12
12
  import { readLatestRunId } from '../utils/utils.js';
13
13
  import pc from 'picocolors';
@@ -36,6 +36,8 @@ program
36
36
  .command('start')
37
37
  .description('Start a new run and return its ID')
38
38
  .action(async () => {
39
+ cleanLatestRunId();
40
+
39
41
  console.log('Starting a new Run on Testomat.io...');
40
42
  const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
41
43
  const client = new TestomatClient({ apiKey });
@@ -70,6 +72,7 @@ program
70
72
 
71
73
  program
72
74
  .command('run')
75
+ .alias('test')
73
76
  .description('Run tests with the specified command')
74
77
  .argument('<command>', 'Test runner command')
75
78
  .option('--filter <filter>', 'Additional execution filter')
@@ -100,25 +103,25 @@ program
100
103
 
101
104
  console.log(APP_PREFIX, `🚀 Running`, pc.green(command));
102
105
 
103
- const runTests = () => {
106
+ const runTests = async () => {
104
107
  const testCmds = command.split(' ');
105
108
  const cmd = spawn(testCmds[0], testCmds.slice(1), { stdio: 'inherit' });
106
109
 
107
- cmd.on('close', code => {
110
+ cmd.on('close', async code => {
108
111
  const emoji = code === 0 ? '🟢' : '🔴';
109
112
  console.log(APP_PREFIX, emoji, `Runner exited with ${pc.bold(code)}`);
110
113
  if (apiKey) {
111
114
  const status = code === 0 ? 'passed' : 'failed';
112
- client.updateRunStatus(status, true);
115
+ await client.updateRunStatus(status, true);
113
116
  }
114
117
  process.exit(code);
115
118
  });
116
119
  };
117
120
 
118
121
  if (apiKey) {
119
- client.createRun().then(runTests);
122
+ await client.createRun().then(runTests);
120
123
  } else {
121
- runTests();
124
+ await runTests();
122
125
  }
123
126
  });
124
127
 
package/src/client.js CHANGED
@@ -10,7 +10,7 @@ import { glob } from 'glob';
10
10
  import path, { sep } from 'path';
11
11
  import { fileURLToPath } from 'node:url';
12
12
  import { S3Uploader } from './uploader.js';
13
- import { formatStep, storeRunId, validateSuiteId } from './utils/utils.js';
13
+ import { formatStep, readLatestRunId, storeRunId, validateSuiteId } from './utils/utils.js';
14
14
  import { filesize as prettyBytes } from 'filesize';
15
15
 
16
16
  const debug = createDebugMessages('@testomatio/reporter:client');
@@ -34,7 +34,7 @@ class Client {
34
34
  constructor(params = {}) {
35
35
  this.paramsForPipesFactory = params;
36
36
  this.pipeStore = {};
37
- this.runId = randomUUID(); // will be replaced by real run id
37
+ this.runId = '';
38
38
  this.queue = Promise.resolve();
39
39
 
40
40
  // @ts-ignore this line will be removed in compiled code, because __dirname is defined in commonjs
@@ -139,6 +139,9 @@ class Client {
139
139
  * @returns {Promise<PipeResult[]>}
140
140
  */
141
141
  async addTestRun(status, testData) {
142
+ if (!this.pipes || !this.pipes.length)
143
+ this.pipes = await pipesFactory(this.paramsForPipesFactory || {}, this.pipeStore);
144
+
142
145
  // all pipes disabled, skipping
143
146
  if (!this.pipes?.filter(p => p.isEnabled).length) return [];
144
147
 
@@ -180,6 +183,7 @@ class Client {
180
183
  timestamp,
181
184
  manuallyAttachedArtifacts,
182
185
  labels,
186
+ overwrite,
183
187
  } = testData;
184
188
  let { message = '', meta = {} } = testData;
185
189
 
@@ -277,6 +281,7 @@ class Client {
277
281
  artifacts,
278
282
  meta,
279
283
  labels,
284
+ overwrite,
280
285
  ...(rootSuiteId && { root_suite_id: rootSuiteId }),
281
286
  };
282
287
 
@@ -308,7 +313,10 @@ class Client {
308
313
  * @param {boolean} [isParallel] - Whether the current test run was executed in parallel with other tests.
309
314
  * @returns {Promise<any>} - A Promise that resolves when finishes the run.
310
315
  */
311
- updateRunStatus(status, isParallel = false) {
316
+ async updateRunStatus(status, isParallel = false) {
317
+ this.pipes ||= await pipesFactory(this.paramsForPipesFactory || {}, this.pipeStore);
318
+ this.runId ||= readLatestRunId();
319
+
312
320
  debug('Updating run status...');
313
321
  // all pipes disabled, skipping
314
322
  if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
@@ -3,7 +3,7 @@ import pc from 'picocolors';
3
3
  import { Gaxios } from 'gaxios';
4
4
  import JsonCycle from 'json-cycle';
5
5
  import { APP_PREFIX, STATUS, AXIOS_TIMEOUT, REPORTER_REQUEST_RETRIES } from '../constants.js';
6
- import { isValidUrl, foundedTestLog } from '../utils/utils.js';
6
+ import { isValidUrl, foundedTestLog, readLatestRunId } from '../utils/utils.js';
7
7
  import { parseFilterParams, generateFilterRequestParams, setS3Credentials } from '../utils/pipe_utils.js';
8
8
  import { config } from '../config.js';
9
9
 
@@ -367,10 +367,14 @@ class TestomatioPipe {
367
367
  * Adds a test to the batch uploader (or reports a single test if batch uploading is disabled)
368
368
  */
369
369
  addTest(data) {
370
- this.isEnabled = this.apiKey ?? this.isEnabled;
371
-
370
+ this.isEnabled = !!(this.apiKey ?? this.isEnabled);
372
371
  if (!this.isEnabled) return;
373
- if (!this.runId) return;
372
+
373
+ this.runId = this.runId || process.env.runId || this.store.runId || readLatestRunId();
374
+ if (!this.runId) {
375
+ console.warn(APP_PREFIX, pc.red('Run ID is not set, skipping test reporting'));
376
+ return;
377
+ }
374
378
 
375
379
  // add test ID + run ID
376
380
  if (data.rid) data.rid = `${this.runId}-${data.rid}`;
package/src/replay.js CHANGED
@@ -33,7 +33,10 @@ export class Replay {
33
33
  }
34
34
 
35
35
  const fileContent = fs.readFileSync(debugFile, 'utf-8');
36
- const lines = fileContent.trim().split('\n').filter(line => line.trim() !== '');
36
+ const lines = fileContent
37
+ .trim()
38
+ .split('\n')
39
+ .filter(line => line.trim() !== '');
37
40
 
38
41
  if (lines.length === 0) {
39
42
  throw new Error('Debug file is empty');
@@ -77,8 +80,11 @@ export class Replay {
77
80
  } else if (key === 'artifacts' && Array.isArray(test[key]) && test[key].length > 0) {
78
81
  // Merge artifacts arrays
79
82
  mergedTest.artifacts = [...(existingTest.artifacts || []), ...test[key]];
80
- } else if (existingTest[key] === null || existingTest[key] === undefined ||
81
- (Array.isArray(existingTest[key]) && existingTest[key].length === 0)) {
83
+ } else if (
84
+ existingTest[key] === null ||
85
+ existingTest[key] === undefined ||
86
+ (Array.isArray(existingTest[key]) && existingTest[key].length === 0)
87
+ ) {
82
88
  // Use new value if existing is null/undefined/empty array
83
89
  mergedTest[key] = test[key];
84
90
  }
@@ -139,7 +145,7 @@ export class Replay {
139
145
  envVars,
140
146
  parseErrors,
141
147
  totalLines: lines.length,
142
- runId
148
+ runId,
143
149
  };
144
150
  }
145
151
 
@@ -193,7 +199,7 @@ export class Replay {
193
199
  finishParams,
194
200
  envVars,
195
201
  runId,
196
- dryRun: true
202
+ dryRun: true,
197
203
  };
198
204
  }
199
205
 
@@ -219,13 +225,13 @@ export class Replay {
219
225
 
220
226
  for (const [index, test] of tests.entries()) {
221
227
  try {
222
- await client.addTestRun(test.status, test);
228
+ await client.addTestRun(test.status, { ...test, overwrite: true });
223
229
  successCount++;
224
230
  this.onProgress({
225
231
  current: index + 1,
226
232
  total: tests.length,
227
233
  test,
228
- success: true
234
+ success: true,
229
235
  });
230
236
  } catch (err) {
231
237
  failureCount++;
@@ -235,7 +241,7 @@ export class Replay {
235
241
  total: tests.length,
236
242
  test,
237
243
  success: false,
238
- error: err.message
244
+ error: err.message,
239
245
  });
240
246
  }
241
247
  }
@@ -250,7 +256,7 @@ export class Replay {
250
256
  runParams,
251
257
  finishParams,
252
258
  envVars,
253
- runId: runId || client.runId
259
+ runId: runId || client.runId,
254
260
  };
255
261
 
256
262
  this.onLog(`Successfully replayed ${successCount}/${tests.length} tests from debug file`);
@@ -259,4 +265,4 @@ export class Replay {
259
265
  }
260
266
  }
261
267
 
262
- export default Replay;
268
+ export default Replay;
@@ -353,20 +353,38 @@ function storeRunId(runId) {
353
353
  fs.writeFileSync(filePath, runId);
354
354
  }
355
355
 
356
+ /**
357
+ *
358
+ * @returns {String|null} latest run ID
359
+ */
356
360
  function readLatestRunId() {
357
361
  try {
358
362
  const filePath = path.join(os.tmpdir(), `testomatio.latest.run`);
359
363
  const stats = fs.statSync(filePath);
360
364
  const diff = +new Date() - +stats.mtime;
361
365
  const diffHours = diff / 1000 / 60 / 60;
362
- if (diffHours > 1) return;
366
+ if (diffHours > 1) return null;
363
367
 
364
- return fs.readFileSync(filePath)?.toString()?.trim();
368
+ return fs.readFileSync(filePath)?.toString()?.trim() ?? null;
365
369
  } catch (e) {
370
+ console.warn('Could not read latest run ID from file: ', e);
366
371
  return null;
367
372
  }
368
373
  }
369
374
 
375
+ function cleanLatestRunId() {
376
+ try {
377
+ const filePath = path.join(os.tmpdir(), `testomatio.latest.run`);
378
+ const runId = readLatestRunId();
379
+ if (fs.existsSync(filePath)) {
380
+ fs.unlinkSync(filePath);
381
+ }
382
+ debug(`Cleaned latest run ID (${runId}) file`, filePath);
383
+ } catch (e) {
384
+ console.warn('Could not clean latest run ID file: ', e);
385
+ }
386
+ }
387
+
370
388
  function formatStep(step, shift = 0) {
371
389
  const prefix = ' '.repeat(shift);
372
390
 
@@ -393,6 +411,7 @@ export function getPackageVersion() {
393
411
 
394
412
  export {
395
413
  ansiRegExp,
414
+ cleanLatestRunId,
396
415
  isSameTest,
397
416
  fetchSourceCode,
398
417
  fetchSourceCodeFromStackTrace,
package/src/xmlReader.js CHANGED
@@ -217,6 +217,7 @@ class XmlReader {
217
217
  if (test.example) r.example = test.example;
218
218
  if (test.file) r.file = test.file;
219
219
  r.create = true;
220
+ r.overwrite = true;
220
221
  if (r.status === 'Passed') r.status = STATUS.PASSED;
221
222
  if (r.status === 'Failed') r.status = STATUS.FAILED;
222
223
  if (r.status === 'Skipped') r.status = STATUS.SKIPPED;
@@ -293,6 +294,7 @@ class XmlReader {
293
294
  title,
294
295
  suite_title,
295
296
  run_time,
297
+ retry: false,
296
298
  });
297
299
  });
298
300
  });
@@ -569,6 +571,7 @@ function reduceTestCases(prev, item) {
569
571
  root_suite_id: TESTOMATIO_SUITE,
570
572
  suite_title: suiteTitle,
571
573
  files,
574
+ retry: false,
572
575
  });
573
576
  });
574
577
  return prev;