@testomatio/reporter 2.1.0-beta.1-codeceptjs → 2.1.0-beta.3-filter-plan

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.
@@ -48,8 +48,9 @@ class WebdriverReporter extends reporter_1.default {
48
48
  options = Object.assign(options, { stdout: true });
49
49
  this._addTestPromises = [];
50
50
  this._isSynchronising = false;
51
- // NOTE: new functionality; may break everything
52
- this.client.createRun();
51
+ // run is created by cli, if enabling the row below, it mat lead to multiple runs being created
52
+ // thus, need to check if process.env.runId is set and/or add more checks to avoid creating multiple runs
53
+ // this.client.createRun();
53
54
  }
54
55
  get isSynchronised() {
55
56
  return this._isSynchronising === false;
@@ -68,7 +69,6 @@ class WebdriverReporter extends reporter_1.default {
68
69
  }
69
70
  onRunnerStart() {
70
71
  // clear dir with artifacts/logs
71
- //
72
72
  utils_js_1.fileSystem.clearDir(constants_js_1.TESTOMAT_TMP_STORAGE_DIR);
73
73
  }
74
74
  onTestStart(test) {
@@ -153,3 +153,9 @@ function getTestLogs(fullTestTitle) {
153
153
  return logs;
154
154
  }
155
155
  module.exports = WebdriverReporter;
156
+ /* INVESTIGATION RESULTS:
157
+ If you run tests in parallel, the WDIO creates a separate process for each parallel instance.
158
+ As a result, there is own WDIOReporter instance for each parallel process.
159
+ This means, its impossible to create or finish run, because can't understand if its was already created
160
+ in other process or not.
161
+ */
package/lib/bin/cli.js CHANGED
@@ -38,6 +38,7 @@ program
38
38
  .command('start')
39
39
  .description('Start a new run and return its ID')
40
40
  .action(async () => {
41
+ (0, utils_js_1.cleanLatestRunId)();
41
42
  console.log('Starting a new Run on Testomat.io...');
42
43
  const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config_js_1.config.TESTOMATIO;
43
44
  const client = new client_js_1.default({ apiKey });
@@ -66,6 +67,7 @@ program
66
67
  });
67
68
  program
68
69
  .command('run')
70
+ .alias('test')
69
71
  .description('Run tests with the specified command')
70
72
  .argument('<command>', 'Test runner command')
71
73
  .option('--filter <filter>', 'Additional execution filter')
@@ -91,24 +93,24 @@ program
91
93
  }
92
94
  }
93
95
  console.log(constants_js_1.APP_PREFIX, `🚀 Running`, picocolors_1.default.green(command));
94
- const runTests = () => {
96
+ const runTests = async () => {
95
97
  const testCmds = command.split(' ');
96
98
  const cmd = (0, cross_spawn_1.spawn)(testCmds[0], testCmds.slice(1), { stdio: 'inherit' });
97
- cmd.on('close', code => {
99
+ cmd.on('close', async (code) => {
98
100
  const emoji = code === 0 ? '🟢' : '🔴';
99
101
  console.log(constants_js_1.APP_PREFIX, emoji, `Runner exited with ${picocolors_1.default.bold(code)}`);
100
102
  if (apiKey) {
101
103
  const status = code === 0 ? 'passed' : 'failed';
102
- client.updateRunStatus(status, true);
104
+ await client.updateRunStatus(status, true);
103
105
  }
104
106
  process.exit(code);
105
107
  });
106
108
  };
107
109
  if (apiKey) {
108
- client.createRun().then(runTests);
110
+ await client.createRun().then(runTests);
109
111
  }
110
112
  else {
111
- runTests();
113
+ await runTests();
112
114
  }
113
115
  });
114
116
  // program
File without changes
File without changes
File without changes
package/lib/client.d.ts CHANGED
@@ -13,7 +13,7 @@ export class Client {
13
13
  constructor(params?: {});
14
14
  paramsForPipesFactory: {};
15
15
  pipeStore: {};
16
- runId: `${string}-${string}-${string}-${string}-${string}`;
16
+ runId: string;
17
17
  queue: Promise<void>;
18
18
  version: any;
19
19
  executionList: Promise<void>;
package/lib/client.js CHANGED
@@ -68,7 +68,7 @@ class Client {
68
68
  constructor(params = {}) {
69
69
  this.paramsForPipesFactory = params;
70
70
  this.pipeStore = {};
71
- this.runId = (0, crypto_1.randomUUID)(); // will be replaced by real run id
71
+ this.runId = '';
72
72
  this.queue = Promise.resolve();
73
73
  // @ts-ignore this line will be removed in compiled code, because __dirname is defined in commonjs
74
74
  const pathToPackageJSON = path_1.default.join(__dirname, '../package.json');
@@ -158,6 +158,8 @@ class Client {
158
158
  * @returns {Promise<PipeResult[]>}
159
159
  */
160
160
  async addTestRun(status, testData) {
161
+ if (!this.pipes || !this.pipes.length)
162
+ this.pipes = await (0, index_js_1.pipesFactory)(this.paramsForPipesFactory || {}, this.pipeStore);
161
163
  // all pipes disabled, skipping
162
164
  if (!this.pipes?.filter(p => p.isEnabled).length)
163
165
  return [];
@@ -179,7 +181,7 @@ class Client {
179
181
  /**
180
182
  * @type {TestData}
181
183
  */
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;
184
+ 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
185
  let { message = '', meta = {} } = testData;
184
186
  // stringify meta values and limit keys and values length to 255
185
187
  meta = Object.entries(meta)
@@ -267,6 +269,7 @@ class Client {
267
269
  artifacts,
268
270
  meta,
269
271
  labels,
272
+ overwrite,
270
273
  ...(rootSuiteId && { root_suite_id: rootSuiteId }),
271
274
  };
272
275
  // debug('Adding test run...', data);
@@ -291,7 +294,9 @@ class Client {
291
294
  * @param {boolean} [isParallel] - Whether the current test run was executed in parallel with other tests.
292
295
  * @returns {Promise<any>} - A Promise that resolves when finishes the run.
293
296
  */
294
- updateRunStatus(status, isParallel = false) {
297
+ async updateRunStatus(status, isParallel = false) {
298
+ this.pipes ||= await (0, index_js_1.pipesFactory)(this.paramsForPipesFactory || {}, this.pipeStore);
299
+ this.runId ||= (0, utils_js_1.readLatestRunId)();
295
300
  debug('Updating run status...');
296
301
  // all pipes disabled, skipping
297
302
  if (!this.pipes?.filter(p => p.isEnabled).length)
@@ -113,7 +113,8 @@ class TestomatioPipe {
113
113
  const resp = await this.client.request({
114
114
  method: 'GET',
115
115
  url: '/api/test_grep',
116
- params: q
116
+ params: q.params,
117
+ responseType: q.responseType
117
118
  });
118
119
  if (Array.isArray(resp.data?.tests) && resp.data?.tests?.length > 0) {
119
120
  (0, utils_js_1.foundedTestLog)(constants_js_1.APP_PREFIX, resp.data.tests);
@@ -331,11 +332,14 @@ class TestomatioPipe {
331
332
  * Adds a test to the batch uploader (or reports a single test if batch uploading is disabled)
332
333
  */
333
334
  addTest(data) {
334
- this.isEnabled = this.apiKey ?? this.isEnabled;
335
+ this.isEnabled = !!(this.apiKey ?? this.isEnabled);
335
336
  if (!this.isEnabled)
336
337
  return;
337
- if (!this.runId)
338
+ this.runId = this.runId || process.env.runId || this.store.runId || (0, utils_js_1.readLatestRunId)();
339
+ if (!this.runId) {
340
+ console.warn(constants_js_1.APP_PREFIX, picocolors_1.default.red('Run ID is not set, skipping test reporting'));
338
341
  return;
342
+ }
339
343
  // add test ID + run ID
340
344
  if (data.rid)
341
345
  data.rid = `${this.runId}-${data.rid}`;
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;
@@ -2,6 +2,7 @@ export function getPackageVersion(): any;
2
2
  export const TEST_ID_REGEX: RegExp;
3
3
  export const SUITE_ID_REGEX: RegExp;
4
4
  export function ansiRegExp(): RegExp;
5
+ export function cleanLatestRunId(): void;
5
6
  export function isSameTest(test: any, t: any): boolean;
6
7
  export function fetchSourceCode(contents: any, opts?: {}): string;
7
8
  export function fetchSourceCodeFromStackTrace(stack?: string): string;
@@ -29,7 +30,11 @@ export function isValidUrl(s: any): boolean;
29
30
  * @returns {String|null} suiteId
30
31
  */
31
32
  export function parseSuite(suiteTitle: string): string | null;
32
- export function readLatestRunId(): string;
33
+ /**
34
+ *
35
+ * @returns {String|null} latest run ID
36
+ */
37
+ export function readLatestRunId(): string | null;
33
38
  /**
34
39
  * Used to remove color codes
35
40
  * @param {*} input
@@ -38,6 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.validateSuiteId = exports.testRunnerHelper = exports.specificTestInfo = exports.parseSuite = exports.isValidUrl = exports.humanize = exports.getTestomatIdFromTestTitle = exports.getCurrentDateTime = exports.foundedTestLog = exports.fileSystem = exports.fetchFilesFromStackTrace = exports.fetchIdFromOutput = exports.fetchIdFromCode = exports.fetchSourceCodeFromStackTrace = exports.fetchSourceCode = exports.isSameTest = exports.ansiRegExp = exports.SUITE_ID_REGEX = exports.TEST_ID_REGEX = void 0;
40
40
  exports.getPackageVersion = getPackageVersion;
41
+ exports.cleanLatestRunId = cleanLatestRunId;
41
42
  exports.formatStep = formatStep;
42
43
  exports.readLatestRunId = readLatestRunId;
43
44
  exports.removeColorCodes = removeColorCodes;
@@ -391,6 +392,10 @@ function storeRunId(runId) {
391
392
  const filePath = path_1.default.join(os_1.default.tmpdir(), `testomatio.latest.run`);
392
393
  fs_1.default.writeFileSync(filePath, runId);
393
394
  }
395
+ /**
396
+ *
397
+ * @returns {String|null} latest run ID
398
+ */
394
399
  function readLatestRunId() {
395
400
  try {
396
401
  const filePath = path_1.default.join(os_1.default.tmpdir(), `testomatio.latest.run`);
@@ -398,13 +403,27 @@ function readLatestRunId() {
398
403
  const diff = +new Date() - +stats.mtime;
399
404
  const diffHours = diff / 1000 / 60 / 60;
400
405
  if (diffHours > 1)
401
- return;
402
- return fs_1.default.readFileSync(filePath)?.toString()?.trim();
406
+ return null;
407
+ return fs_1.default.readFileSync(filePath)?.toString()?.trim() ?? null;
403
408
  }
404
409
  catch (e) {
410
+ console.warn('Could not read latest run ID from file: ', e);
405
411
  return null;
406
412
  }
407
413
  }
414
+ function cleanLatestRunId() {
415
+ try {
416
+ const filePath = path_1.default.join(os_1.default.tmpdir(), `testomatio.latest.run`);
417
+ const runId = readLatestRunId();
418
+ if (fs_1.default.existsSync(filePath)) {
419
+ fs_1.default.unlinkSync(filePath);
420
+ }
421
+ debug(`Cleaned latest run ID (${runId}) file`, filePath);
422
+ }
423
+ catch (e) {
424
+ console.warn('Could not clean latest run ID file: ', e);
425
+ }
426
+ }
408
427
  function formatStep(step, shift = 0) {
409
428
  const prefix = ' '.repeat(shift);
410
429
  const lines = [];
@@ -427,6 +446,8 @@ function getPackageVersion() {
427
446
 
428
447
  module.exports.getPackageVersion = getPackageVersion;
429
448
 
449
+ module.exports.cleanLatestRunId = cleanLatestRunId;
450
+
430
451
  module.exports.formatStep = formatStep;
431
452
 
432
453
  module.exports.readLatestRunId = readLatestRunId;
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.3-filter-plan",
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;