@testomatio/reporter 2.1.0-beta-nightwatch → 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.
Files changed (80) hide show
  1. package/README.md +1 -0
  2. package/lib/adapter/codecept.js +288 -202
  3. package/lib/adapter/cypress-plugin/index.js +0 -2
  4. package/lib/adapter/mocha.js +0 -1
  5. package/lib/adapter/nightwatch.js +5 -5
  6. package/lib/adapter/playwright.js +11 -3
  7. package/lib/adapter/webdriver.d.ts +1 -1
  8. package/lib/adapter/webdriver.js +18 -8
  9. package/lib/bin/cli.js +73 -8
  10. package/lib/bin/reportXml.js +4 -2
  11. package/lib/bin/startTest.js +3 -2
  12. package/lib/bin/uploadArtifacts.js +5 -4
  13. package/lib/client.js +31 -10
  14. package/lib/data-storage.d.ts +5 -5
  15. package/lib/data-storage.js +23 -13
  16. package/lib/junit-adapter/csharp.d.ts +1 -0
  17. package/lib/junit-adapter/csharp.js +11 -1
  18. package/lib/pipe/bitbucket.d.ts +2 -0
  19. package/lib/pipe/bitbucket.js +38 -26
  20. package/lib/pipe/debug.js +27 -6
  21. package/lib/pipe/github.d.ts +2 -2
  22. package/lib/pipe/github.js +35 -3
  23. package/lib/pipe/gitlab.d.ts +2 -0
  24. package/lib/pipe/gitlab.js +27 -9
  25. package/lib/pipe/html.js +0 -3
  26. package/lib/pipe/index.js +17 -7
  27. package/lib/pipe/testomatio.d.ts +3 -2
  28. package/lib/pipe/testomatio.js +85 -75
  29. package/lib/replay.d.ts +31 -0
  30. package/lib/replay.js +259 -0
  31. package/lib/reporter-functions.d.ts +7 -0
  32. package/lib/reporter-functions.js +36 -0
  33. package/lib/reporter.d.ts +15 -12
  34. package/lib/reporter.js +4 -1
  35. package/lib/services/artifacts.d.ts +1 -1
  36. package/lib/services/index.d.ts +2 -0
  37. package/lib/services/index.js +2 -0
  38. package/lib/services/key-values.d.ts +1 -1
  39. package/lib/services/labels.d.ts +22 -0
  40. package/lib/services/labels.js +62 -0
  41. package/lib/services/logger.d.ts +1 -1
  42. package/lib/services/logger.js +1 -2
  43. package/lib/template/testomatio.hbs +443 -68
  44. package/lib/uploader.js +10 -6
  45. package/lib/utils/constants.d.ts +12 -0
  46. package/lib/utils/constants.js +15 -0
  47. package/lib/utils/utils.d.ts +10 -1
  48. package/lib/utils/utils.js +70 -22
  49. package/lib/xmlReader.js +57 -19
  50. package/package.json +16 -11
  51. package/src/adapter/codecept.js +320 -214
  52. package/src/adapter/cypress-plugin/index.js +0 -2
  53. package/src/adapter/mocha.js +0 -1
  54. package/src/adapter/nightwatch.js +1 -1
  55. package/src/adapter/playwright.js +10 -7
  56. package/src/adapter/webdriver.js +13 -5
  57. package/src/bin/cli.js +78 -7
  58. package/src/bin/reportXml.js +4 -1
  59. package/src/bin/startTest.js +2 -1
  60. package/src/bin/uploadArtifacts.js +2 -1
  61. package/src/client.js +28 -5
  62. package/src/data-storage.js +6 -6
  63. package/src/junit-adapter/csharp.js +13 -1
  64. package/src/pipe/bitbucket.js +22 -24
  65. package/src/pipe/debug.js +26 -5
  66. package/src/pipe/github.js +1 -2
  67. package/src/pipe/gitlab.js +27 -9
  68. package/src/pipe/html.js +1 -4
  69. package/src/pipe/testomatio.js +112 -107
  70. package/src/replay.js +268 -0
  71. package/src/reporter-functions.js +41 -0
  72. package/src/reporter.js +3 -0
  73. package/src/services/index.js +2 -0
  74. package/src/services/labels.js +59 -0
  75. package/src/services/logger.js +1 -2
  76. package/src/template/testomatio.hbs +443 -68
  77. package/src/uploader.js +11 -6
  78. package/src/utils/constants.js +12 -0
  79. package/src/utils/utils.js +67 -15
  80. package/src/xmlReader.js +73 -18
@@ -9,6 +9,7 @@ import TestomatioClient from '../client.js';
9
9
  import { getTestomatIdFromTestTitle, fileSystem } from '../utils/utils.js';
10
10
  import { services } from '../services/index.js';
11
11
  import { dataStorage } from '../data-storage.js';
12
+ import { extensionMap } from '../utils/constants.js';
12
13
 
13
14
  const reportTestPromises = [];
14
15
 
@@ -102,6 +103,10 @@ class PlaywrightReporter {
102
103
  projectDependencies: project.dependencies?.length ? project.dependencies : null,
103
104
  ...testMeta,
104
105
  ...project.metadata, // metadata has any type (in playwright), but we will stringify it in client.js
106
+ ...test.annotations?.reduce((acc, annotation) => {
107
+ acc[annotation.type] = annotation.description;
108
+ return acc;
109
+ }, {}),
105
110
  },
106
111
  file: test.location?.file,
107
112
  });
@@ -121,20 +126,18 @@ class PlaywrightReporter {
121
126
  #getArtifactPath(artifact) {
122
127
  if (artifact.path) {
123
128
  if (path.isAbsolute(artifact.path)) return artifact.path;
124
-
125
129
  return path.join(this.config.outputDir || this.config.projects[0].outputDir, artifact.path);
126
130
  }
127
-
128
131
  if (artifact.body) {
129
132
  let filePath = generateTmpFilepath(artifact.name);
130
-
131
- const extension = artifact.contentType?.split('/')[1]?.replace('jpeg', 'jpg');
132
- if (extension) filePath += `.${extension}`;
133
-
133
+ const hasExtension = artifact.name && path.extname(artifact.name);
134
+ if (!hasExtension && artifact.contentType) {
135
+ const extension = extensionMap[artifact.contentType] || artifact.contentType.split('/')[1];
136
+ if (extension) filePath += `.${extension}`;
137
+ }
134
138
  fs.writeFileSync(filePath, artifact.body);
135
139
  return filePath;
136
140
  }
137
-
138
141
  return null;
139
142
  }
140
143
 
@@ -1,5 +1,4 @@
1
- // eslint-disable-next-line
2
- import WDIOReporter, { RunnerStats } from '@wdio/reporter';
1
+ import { default as WDIOReporter, RunnerStats } from '@wdio/reporter';
3
2
  import TestomatClient from '../client.js';
4
3
  import { getTestomatIdFromTestTitle, fileSystem } from '../utils/utils.js';
5
4
  import { services } from '../services/index.js';
@@ -15,8 +14,10 @@ class WebdriverReporter extends WDIOReporter {
15
14
  this._addTestPromises = [];
16
15
 
17
16
  this._isSynchronising = false;
18
- // NOTE: new functionality; may break everything
19
- 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();
20
21
  }
21
22
 
22
23
  get isSynchronised() {
@@ -41,7 +42,6 @@ class WebdriverReporter extends WDIOReporter {
41
42
 
42
43
  onRunnerStart() {
43
44
  // clear dir with artifacts/logs
44
- //
45
45
  fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
46
46
  }
47
47
 
@@ -82,6 +82,7 @@ class WebdriverReporter extends WDIOReporter {
82
82
  .map(el => Buffer.from(el.result.value, 'base64'));
83
83
 
84
84
  await this.client.addTestRun(state, {
85
+ rid: test.uid || '',
85
86
  manuallyAttachedArtifacts: test.artifacts,
86
87
  error,
87
88
  logs: test.logs,
@@ -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
@@ -2,19 +2,21 @@
2
2
 
3
3
  import { Command } from 'commander';
4
4
  import { spawn } from 'cross-spawn';
5
- import glob from 'glob';
5
+ import { glob } from 'glob';
6
6
  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 { version } from '../../package.json';
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';
14
14
  import { filesize as prettyBytes } from 'filesize';
15
15
  import dotenv from 'dotenv';
16
+ import Replay from '../replay.js';
16
17
 
17
18
  const debug = createDebugMessages('@testomatio/reporter:xml-cli');
19
+ const version = getPackageVersion();
18
20
  console.log(pc.cyan(pc.bold(` 🤩 Testomat.io Reporter v${version}`)));
19
21
  const program = new Command();
20
22
 
@@ -34,6 +36,8 @@ program
34
36
  .command('start')
35
37
  .description('Start a new run and return its ID')
36
38
  .action(async () => {
39
+ cleanLatestRunId();
40
+
37
41
  console.log('Starting a new Run on Testomat.io...');
38
42
  const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
39
43
  const client = new TestomatClient({ apiKey });
@@ -68,6 +72,7 @@ program
68
72
 
69
73
  program
70
74
  .command('run')
75
+ .alias('test')
71
76
  .description('Run tests with the specified command')
72
77
  .argument('<command>', 'Test runner command')
73
78
  .option('--filter <filter>', 'Additional execution filter')
@@ -98,28 +103,50 @@ program
98
103
 
99
104
  console.log(APP_PREFIX, `🚀 Running`, pc.green(command));
100
105
 
101
- const runTests = () => {
106
+ const runTests = async () => {
102
107
  const testCmds = command.split(' ');
103
108
  const cmd = spawn(testCmds[0], testCmds.slice(1), { stdio: 'inherit' });
104
109
 
105
- cmd.on('close', code => {
110
+ cmd.on('close', async code => {
106
111
  const emoji = code === 0 ? '🟢' : '🔴';
107
112
  console.log(APP_PREFIX, emoji, `Runner exited with ${pc.bold(code)}`);
108
113
  if (apiKey) {
109
114
  const status = code === 0 ? 'passed' : 'failed';
110
- client.updateRunStatus(status, true);
115
+ await client.updateRunStatus(status, true);
111
116
  }
112
117
  process.exit(code);
113
118
  });
114
119
  };
115
120
 
116
121
  if (apiKey) {
117
- client.createRun().then(runTests);
122
+ await client.createRun().then(runTests);
118
123
  } else {
119
- runTests();
124
+ await runTests();
120
125
  }
121
126
  });
122
127
 
128
+ // program
129
+ // .command('xml')
130
+ // .description('Parse XML reports and upload to Testomat.io')
131
+ // .argument('<pattern>', 'XML file pattern')
132
+ // .option('-d, --dir <dir>', 'Project directory')
133
+ // .option('--java-tests [java-path]', 'Load Java tests from path, by default: src/test/java')
134
+ // .option('--lang <lang>', 'Language used (python, ruby, java)')
135
+ // .option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
136
+ // .action(async (pattern, opts) => {
137
+ // if (!pattern.endsWith('.xml')) {
138
+ // pattern += '.xml';
139
+ // }
140
+ // let { javaTests, lang } = opts;
141
+ // if (javaTests === true) javaTests = 'src/test/java';
142
+ // lang = lang?.toLowerCase();
143
+ // const runReader = new XmlReader({ javaTests, lang });
144
+ // const files = glob.sync(pattern, { cwd: opts.dir || process.cwd() });
145
+ // if (!files.length) {
146
+ // console.log(APP_PREFIX, `Report can't be created. No XML files found 😥`);
147
+ // process.exit(1);
148
+ // }
149
+
123
150
  program
124
151
  .command('xml')
125
152
  .description('Parse XML reports and upload to Testomat.io')
@@ -273,6 +300,50 @@ program
273
300
  }
274
301
  });
275
302
 
303
+ program
304
+ .command('replay')
305
+ .description('Replay test data from debug file and re-send to Testomat.io')
306
+ .argument('[debug-file]', 'Path to debug file (defaults to /tmp/testomatio.debug.latest.json)')
307
+ .option('--dry-run', 'Preview the data without sending to Testomat.io')
308
+ .action(async (debugFile, opts) => {
309
+ try {
310
+ const replayService = new Replay({
311
+ apiKey: config.TESTOMATIO,
312
+ dryRun: opts.dryRun,
313
+ onLog: (message) => console.log(APP_PREFIX, message),
314
+ onError: (message) => console.error(APP_PREFIX, '⚠️ ', message),
315
+ onProgress: ({ current, total }) => {
316
+ if (current % 10 === 0 || current === total) {
317
+ console.log(APP_PREFIX, `📊 Progress: ${current}/${total} tests processed`);
318
+ }
319
+ }
320
+ });
321
+
322
+ const result = await replayService.replay(debugFile);
323
+
324
+ if (result.dryRun) {
325
+ console.log(APP_PREFIX, '🔍 Dry run completed:');
326
+ console.log(APP_PREFIX, ` - Tests found: ${result.testsCount}`);
327
+ console.log(APP_PREFIX, ` - Environment variables: ${Object.keys(result.envVars).length}`);
328
+ console.log(APP_PREFIX, ` - Run parameters:`, result.runParams);
329
+ console.log(APP_PREFIX, ' Use without --dry-run to actually send the data');
330
+ } else {
331
+ console.log(APP_PREFIX, `✅ Successfully replayed ${result.successCount}/${result.testsCount} tests`);
332
+ if (result.failureCount > 0) {
333
+ console.log(APP_PREFIX, `⚠️ ${result.failureCount} tests failed to upload`);
334
+ }
335
+ }
336
+
337
+ process.exit(0);
338
+ } catch (err) {
339
+ console.error(APP_PREFIX, '❌ Error replaying debug data:', err.message);
340
+ if (err.message.includes('Debug file not found')) {
341
+ console.error(APP_PREFIX, '💡 Hint: Run tests with TESTOMATIO_DEBUG=1 to generate debug files');
342
+ }
343
+ process.exit(1);
344
+ }
345
+ });
346
+
276
347
  program.parse(process.argv);
277
348
 
278
349
  if (!process.argv.slice(2).length) {
@@ -5,8 +5,11 @@ import { glob } from 'glob';
5
5
  import createDebugMessages from 'debug';
6
6
  import { APP_PREFIX } from '../constants.js';
7
7
  import XmlReader from '../xmlReader.js';
8
- import { version } from '../../package.json';
8
+ import { getPackageVersion } from '../utils/utils.js';
9
9
  import dotenv from 'dotenv';
10
+ import path from 'path';
11
+
12
+ const version = getPackageVersion();
10
13
 
11
14
  const debug = createDebugMessages('@testomatio/reporter:xml-cli');
12
15
  console.log(pc.cyan(pc.bold(` 🤩 Testomat.io XML Reporter v${version}`)));
@@ -4,10 +4,11 @@ import { Command } from 'commander';
4
4
  import pc from 'picocolors';
5
5
  import TestomatClient from '../client.js';
6
6
  import { APP_PREFIX, STATUS } from '../constants.js';
7
- import { version } from '../../package.json';
7
+ import { getPackageVersion } from '../utils/utils.js';
8
8
  import { config } from '../config.js';
9
9
  import dotenv from 'dotenv';
10
10
 
11
+ const version = getPackageVersion();
11
12
  console.log(pc.cyan(pc.bold(` 🤩 Testomat.io Reporter v${version}`)));
12
13
  const program = new Command();
13
14
 
@@ -5,12 +5,13 @@ import pc from 'picocolors';
5
5
  import createDebugMessages from 'debug';
6
6
  import TestomatClient from '../client.js';
7
7
  import { APP_PREFIX } from '../constants.js';
8
- import { version } from '../../package.json';
8
+ import { getPackageVersion } from '../utils/utils.js';
9
9
  import { config } from '../config.js';
10
10
  import { readLatestRunId } from '../utils/utils.js';
11
11
  import dotenv from 'dotenv';
12
12
 
13
13
  const debug = createDebugMessages('@testomatio/reporter:upload-cli');
14
+ const version = getPackageVersion();
14
15
  console.log(pc.cyan(pc.bold(` 🤩 Testomat.io Reporter v${version}`)));
15
16
  const program = new Command();
16
17
 
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 } 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');
@@ -31,11 +31,10 @@ class Client {
31
31
  * Create a Testomat client instance
32
32
  * @returns
33
33
  */
34
- // eslint-disable-next-line
35
34
  constructor(params = {}) {
36
35
  this.paramsForPipesFactory = params;
37
36
  this.pipeStore = {};
38
- this.runId = randomUUID(); // will be replaced by real run id
37
+ this.runId = '';
39
38
  this.queue = Promise.resolve();
40
39
 
41
40
  // @ts-ignore this line will be removed in compiled code, because __dirname is defined in commonjs
@@ -140,6 +139,9 @@ class Client {
140
139
  * @returns {Promise<PipeResult[]>}
141
140
  */
142
141
  async addTestRun(status, testData) {
142
+ if (!this.pipes || !this.pipes.length)
143
+ this.pipes = await pipesFactory(this.paramsForPipesFactory || {}, this.pipeStore);
144
+
143
145
  // all pipes disabled, skipping
144
146
  if (!this.pipes?.filter(p => p.isEnabled).length) return [];
145
147
 
@@ -156,6 +158,11 @@ class Client {
156
158
  suite_title: 'Unknown suite',
157
159
  };
158
160
 
161
+ // Add timestamp if not already present (microseconds since Unix epoch)
162
+ if (!testData.timestamp && !process.env.TESTOMATIO_NO_TIMESTAMP) {
163
+ testData.timestamp = Math.floor((performance.timeOrigin + performance.now()) * 1000);
164
+ }
165
+
159
166
  /**
160
167
  * @type {TestData}
161
168
  */
@@ -173,7 +180,10 @@ class Client {
173
180
  suite_title,
174
181
  suite_id,
175
182
  test_id,
183
+ timestamp,
176
184
  manuallyAttachedArtifacts,
185
+ labels,
186
+ overwrite,
177
187
  } = testData;
178
188
  let { message = '', meta = {} } = testData;
179
189
 
@@ -214,6 +224,8 @@ class Client {
214
224
  return acc;
215
225
  }, {});
216
226
 
227
+ // Labels are simple array of strings, no processing needed
228
+
217
229
  let errorFormatted = '';
218
230
  if (error) {
219
231
  errorFormatted += this.formatError(error) || '';
@@ -246,6 +258,10 @@ class Client {
246
258
 
247
259
  const artifacts = (await Promise.all(uploadedFiles)).filter(n => !!n);
248
260
 
261
+ const workspaceDir = process.env.TESTOMATIO_WORKDIR || process.cwd();
262
+ const relativeFile = file ? path.relative(workspaceDir, file) : file;
263
+ const rootSuiteId = validateSuiteId(process.env.TESTOMATIO_SUITE);
264
+
249
265
  const data = {
250
266
  rid,
251
267
  files,
@@ -253,7 +269,7 @@ class Client {
253
269
  status,
254
270
  stack: fullLogs,
255
271
  example,
256
- file,
272
+ file: relativeFile,
257
273
  code,
258
274
  title,
259
275
  suite_title,
@@ -261,8 +277,12 @@ class Client {
261
277
  test_id,
262
278
  message,
263
279
  run_time: typeof time === 'number' ? time : parseFloat(time),
280
+ timestamp,
264
281
  artifacts,
265
282
  meta,
283
+ labels,
284
+ overwrite,
285
+ ...(rootSuiteId && { root_suite_id: rootSuiteId }),
266
286
  };
267
287
 
268
288
  // debug('Adding test run...', data);
@@ -293,7 +313,10 @@ class Client {
293
313
  * @param {boolean} [isParallel] - Whether the current test run was executed in parallel with other tests.
294
314
  * @returns {Promise<any>} - A Promise that resolves when finishes the run.
295
315
  */
296
- updateRunStatus(status, isParallel = false) {
316
+ async updateRunStatus(status, isParallel = false) {
317
+ this.pipes ||= await pipesFactory(this.paramsForPipesFactory || {}, this.pipeStore);
318
+ this.runId ||= readLatestRunId();
319
+
297
320
  debug('Updating run status...');
298
321
  // all pipes disabled, skipping
299
322
  if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
@@ -41,7 +41,7 @@ class DataStorage {
41
41
  /**
42
42
  * Puts any data to storage (file or global variable).
43
43
  * If file: stores data as text, if global variable – stores as array of data.
44
- * @param {'log' | 'artifact' | 'keyvalue'} dataType
44
+ * @param {'log' | 'artifact' | 'keyvalue' | 'labels'} dataType
45
45
  * @param {*} data anything you want to store (string, object, array, etc)
46
46
  * @param {*} context could be testId or any context (test name, suite name, including their IDs etc)
47
47
  * suite name + test name is used by default
@@ -70,7 +70,7 @@ class DataStorage {
70
70
  * Returns data, stored for specific test/context (or data which was stored without test id specified).
71
71
  * This method will get data from global variable and/or from from file (previosly saved with put method).
72
72
  *
73
- * @param {'log' | 'artifact' | 'keyvalue'} dataType
73
+ * @param {'log' | 'artifact' | 'keyvalue' | 'labels'} dataType
74
74
  * @param {string} context
75
75
  * @returns {any []} array of data (any type), null (if no data found for context) or string (if data type is log)
76
76
  */
@@ -108,7 +108,7 @@ class DataStorage {
108
108
  }
109
109
 
110
110
  /**
111
- * @param {'log' | 'artifact' | 'keyvalue'} dataType
111
+ * @param {'log' | 'artifact' | 'keyvalue' | 'labels'} dataType
112
112
  * @param {string} context
113
113
  * @returns aray of data (any type)
114
114
  */
@@ -127,7 +127,7 @@ class DataStorage {
127
127
  }
128
128
 
129
129
  /**
130
- * @param {'log' | 'artifact' | 'keyvalue'} dataType
130
+ * @param {'log' | 'artifact' | 'keyvalue' | 'labels'} dataType
131
131
  * @param {*} context
132
132
  * @returns array of data (any type)
133
133
  */
@@ -151,7 +151,7 @@ class DataStorage {
151
151
 
152
152
  /**
153
153
  * Puts data to global variable. Unlike the file storage, stores data in array (file storage just append as string).
154
- * @param {'log' | 'artifact' | 'keyvalue'} dataType
154
+ * @param {'log' | 'artifact' | 'keyvalue' | 'labels'} dataType
155
155
  * @param {*} data
156
156
  * @param {*} context
157
157
  */
@@ -166,7 +166,7 @@ class DataStorage {
166
166
 
167
167
  /**
168
168
  * Puts data to file. Unlike the global variable storage, stores data as string
169
- * @param {'log' | 'artifact' | 'keyvalue'} dataType
169
+ * @param {'log' | 'artifact' | 'keyvalue' | 'labels'} dataType
170
170
  * @param {*} data
171
171
  * @param {string} context
172
172
  * @returns
@@ -1,3 +1,4 @@
1
+ import path from 'path';
1
2
  import Adapter from './adapter.js';
2
3
 
3
4
  class CSharpAdapter extends Adapter {
@@ -7,10 +8,21 @@ class CSharpAdapter extends Adapter {
7
8
  if (example) t.example = { ...example[1].split(',') };
8
9
  const suite = t.suite_title.split('.');
9
10
  t.suite_title = suite.pop();
10
- t.file = suite.join('/');
11
+ t.file = namespaceToFileName(t.file);
11
12
  t.title = title.trim();
12
13
  return t;
13
14
  }
15
+
16
+ getFilePath(t) {
17
+ const fileName = namespaceToFileName(t.file);
18
+ return fileName;
19
+ }
14
20
  }
15
21
 
16
22
  export default CSharpAdapter;
23
+
24
+ function namespaceToFileName(fileName) {
25
+ const fileParts = fileName.split('.');
26
+ fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '');
27
+ return `${fileParts.join(path.sep)}.cs`;
28
+ }
@@ -1,7 +1,7 @@
1
1
  import { APP_PREFIX, testomatLogoURL } from '../constants.js';
2
2
  import { ansiRegExp, isSameTest } from '../utils/utils.js';
3
3
  import { statusEmoji, fullName } from '../utils/pipe_utils.js';
4
- import axios from 'axios';
4
+ import { Gaxios } from 'gaxios';
5
5
  import pc from 'picocolors';
6
6
  import humanizeDuration from 'humanize-duration';
7
7
  import merge from 'lodash.merge';
@@ -40,6 +40,13 @@ export class BitbucketPipe {
40
40
  }
41
41
 
42
42
  this.isEnabled = true;
43
+ this.client = new Gaxios({
44
+ baseURL: 'https://api.bitbucket.org/2.0',
45
+ headers: {
46
+ 'Content-Type': 'application/json',
47
+ 'Authorization': `Bearer ${this.token}`
48
+ }
49
+ });
43
50
 
44
51
  debug('Bitbucket Pipe: Enabled');
45
52
  }
@@ -166,26 +173,21 @@ export class BitbucketPipe {
166
173
 
167
174
  // Construct Bitbucket API URL for comments
168
175
  // eslint-disable-next-line max-len
169
- const commentsRequestURL = `https://api.bitbucket.org/2.0/repositories/${this.ENV.BITBUCKET_WORKSPACE}/${this.ENV.BITBUCKET_REPO_SLUG}/pullrequests/${this.ENV.BITBUCKET_PR_ID}/comments`;
176
+ const commentsRequestURL = `/repositories/${this.ENV.BITBUCKET_WORKSPACE}/${this.ENV.BITBUCKET_REPO_SLUG}/pullrequests/${this.ENV.BITBUCKET_PR_ID}/comments`;
170
177
 
171
178
  // Delete previous report
172
- await deletePreviousReport(axios, commentsRequestURL, this.hiddenCommentData, this.token);
179
+ await deletePreviousReport(this.client, commentsRequestURL, this.hiddenCommentData);
173
180
 
174
181
  // Add current report
175
182
  debug(`Adding comment via URL: ${commentsRequestURL}`);
176
183
  debug(`Final Bitbucket API call body: ${body}`);
177
184
 
178
185
  try {
179
- const addCommentResponse = await axios.post(
180
- commentsRequestURL,
181
- { content: { raw: body } },
182
- {
183
- headers: {
184
- Authorization: `Bearer ${this.token}`,
185
- 'Content-Type': 'application/json',
186
- },
187
- },
188
- );
186
+ const addCommentResponse = await this.client.request({
187
+ method: 'POST',
188
+ url: commentsRequestURL,
189
+ data: { content: { raw: body } }
190
+ });
189
191
 
190
192
  const commentID = addCommentResponse.data.id;
191
193
  // eslint-disable-next-line max-len
@@ -210,18 +212,16 @@ export class BitbucketPipe {
210
212
  updateRun() {}
211
213
  }
212
214
 
213
- async function deletePreviousReport(axiosInstance, commentsRequestURL, hiddenCommentData, token) {
215
+ async function deletePreviousReport(client, commentsRequestURL, hiddenCommentData) {
214
216
  if (process.env.BITBUCKET_KEEP_OUTDATED_REPORTS) return;
215
217
 
216
218
  // Get comments
217
219
  let comments = [];
218
220
 
219
221
  try {
220
- const response = await axiosInstance.get(commentsRequestURL, {
221
- headers: {
222
- Authorization: `Bearer ${token}`,
223
- 'Content-Type': 'application/json',
224
- },
222
+ const response = await client.request({
223
+ method: 'GET',
224
+ url: commentsRequestURL
225
225
  });
226
226
  comments = response.data.values;
227
227
  } catch (e) {
@@ -236,11 +236,9 @@ async function deletePreviousReport(axiosInstance, commentsRequestURL, hiddenCom
236
236
  try {
237
237
  // Delete previous comment
238
238
  const deleteCommentURL = `${commentsRequestURL}/${comment.id}`;
239
- await axiosInstance.delete(deleteCommentURL, {
240
- headers: {
241
- Authorization: `Bearer ${token}`,
242
- 'Content-Type': 'application/json',
243
- },
239
+ await client.request({
240
+ method: 'DELETE',
241
+ url: deleteCommentURL
244
242
  });
245
243
  } catch (e) {
246
244
  console.warn(`Can't delete previously added comment with testomat.io report. Ignored.`);
package/src/pipe/debug.js CHANGED
@@ -25,7 +25,22 @@ export class DebugPipe {
25
25
 
26
26
  debug('Creating debug file:', this.logFilePath);
27
27
  fs.writeFileSync(this.logFilePath, '');
28
- console.log(APP_PREFIX, '🪲. Debug created:');
28
+
29
+ // Create symlink to ensure consistent path to latest debug file
30
+ const symlinkPath = path.join(os.tmpdir(), 'testomatio.debug.latest.json');
31
+ try {
32
+ // Remove existing symlink if it exists
33
+ if (fs.existsSync(symlinkPath)) {
34
+ fs.unlinkSync(symlinkPath);
35
+ }
36
+ // Create new symlink pointing to the timestamped debug file
37
+ fs.symlinkSync(this.logFilePath, symlinkPath);
38
+ debug('Created symlink:', symlinkPath, '->', this.logFilePath);
39
+ } catch (err) {
40
+ debug('Failed to create symlink:', err.message);
41
+ }
42
+
43
+ console.log(APP_PREFIX, '🪲 Debug file created');
29
44
  this.testomatioEnvVars = Object.keys(process.env)
30
45
  .filter(key => key.startsWith('TESTOMATIO_'))
31
46
  .reduce((acc, key) => {
@@ -74,7 +89,11 @@ export class DebugPipe {
74
89
  async addTest(data) {
75
90
  if (!this.isEnabled) return;
76
91
 
77
- if (!this.batch.isEnabled) this.logToFile({ action: 'addTest', testId: data });
92
+ if (!this.batch.isEnabled) {
93
+ const logData = { action: 'addTest', testId: data };
94
+ if (this.store.runId) logData.runId = this.store.runId;
95
+ this.logToFile(logData);
96
+ }
78
97
  else this.batch.tests.push(data);
79
98
 
80
99
  if (!this.batch.intervalFunction) await this.batchUpload();
@@ -87,15 +106,17 @@ export class DebugPipe {
87
106
 
88
107
  const testsToSend = this.batch.tests.splice(0);
89
108
 
90
- this.logToFile({ action: 'addTestsBatch', tests: testsToSend });
109
+ const logData = { action: 'addTestsBatch', tests: testsToSend };
110
+ if (this.store.runId) logData.runId = this.store.runId;
111
+ this.logToFile(logData);
91
112
  }
92
113
 
93
114
  async finishRun(params) {
94
115
  if (!this.isEnabled) return;
95
- this.logToFile({ actions: 'finishRun', params });
96
116
  await this.batchUpload();
97
117
  if (this.batch.intervalFunction) clearInterval(this.batch.intervalFunction);
98
- console.log(APP_PREFIX, '🪲. Debug Saved to', this.logFilePath);
118
+ this.logToFile({ action: 'finishRun', params });
119
+ console.log(APP_PREFIX, '🪲 Debug Saved to', this.logFilePath);
99
120
  }
100
121
 
101
122
  toString() {
@@ -3,7 +3,6 @@ import path from 'path';
3
3
  import pc from 'picocolors';
4
4
  import humanizeDuration from 'humanize-duration';
5
5
  import merge from 'lodash.merge';
6
- import { Octokit } from '@octokit/rest';
7
6
  import { APP_PREFIX, testomatLogoURL } from '../constants.js';
8
7
  import { ansiRegExp, isSameTest } from '../utils/utils.js';
9
8
  import { statusEmoji, fullName } from '../utils/pipe_utils.js';
@@ -64,6 +63,7 @@ class GitHubPipe {
64
63
  if (!this.issue) return;
65
64
 
66
65
  if (runParams.tests) runParams.tests.forEach(t => this.addTest(t));
66
+ const { Octokit } = await import('@octokit/rest');
67
67
 
68
68
  this.octokit = new Octokit({
69
69
  auth: this.token,
@@ -154,7 +154,6 @@ class GitHubPipe {
154
154
  if (this.tests.length > 0) {
155
155
  body += '\n<details>\n<summary><h3>🐢 Slowest Tests</h3></summary>\n\n';
156
156
  body += this.tests
157
- // eslint-disable-next-line no-unsafe-optional-chaining
158
157
  .sort((a, b) => b?.run_time - a?.run_time)
159
158
  .slice(0, 5)
160
159
  .map(t => `* ${fullName(t)} (${humanizeDuration(parseFloat(t.run_time))})`)