@testomatio/reporter 2.1.0-beta-nightwatch → 2.1.0-beta.1-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 +30 -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 +255 -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 +54 -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 +2 -2
  57. package/src/bin/cli.js +70 -2
  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 +18 -3
  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 +106 -105
  70. package/src/replay.js +262 -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 +46 -13
  80. package/src/xmlReader.js +70 -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';
@@ -82,6 +81,7 @@ class WebdriverReporter extends WDIOReporter {
82
81
  .map(el => Buffer.from(el.result.value, 'base64'));
83
82
 
84
83
  await this.client.addTestRun(state, {
84
+ rid: test.uid || '',
85
85
  manuallyAttachedArtifacts: test.artifacts,
86
86
  error,
87
87
  logs: test.logs,
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 { 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
 
@@ -120,6 +122,28 @@ program
120
122
  }
121
123
  });
122
124
 
125
+ // program
126
+ // .command('xml')
127
+ // .description('Parse XML reports and upload to Testomat.io')
128
+ // .argument('<pattern>', 'XML file pattern')
129
+ // .option('-d, --dir <dir>', 'Project directory')
130
+ // .option('--java-tests [java-path]', 'Load Java tests from path, by default: src/test/java')
131
+ // .option('--lang <lang>', 'Language used (python, ruby, java)')
132
+ // .option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
133
+ // .action(async (pattern, opts) => {
134
+ // if (!pattern.endsWith('.xml')) {
135
+ // pattern += '.xml';
136
+ // }
137
+ // let { javaTests, lang } = opts;
138
+ // if (javaTests === true) javaTests = 'src/test/java';
139
+ // lang = lang?.toLowerCase();
140
+ // const runReader = new XmlReader({ javaTests, lang });
141
+ // const files = glob.sync(pattern, { cwd: opts.dir || process.cwd() });
142
+ // if (!files.length) {
143
+ // console.log(APP_PREFIX, `Report can't be created. No XML files found 😥`);
144
+ // process.exit(1);
145
+ // }
146
+
123
147
  program
124
148
  .command('xml')
125
149
  .description('Parse XML reports and upload to Testomat.io')
@@ -273,6 +297,50 @@ program
273
297
  }
274
298
  });
275
299
 
300
+ program
301
+ .command('replay')
302
+ .description('Replay test data from debug file and re-send to Testomat.io')
303
+ .argument('[debug-file]', 'Path to debug file (defaults to /tmp/testomatio.debug.latest.json)')
304
+ .option('--dry-run', 'Preview the data without sending to Testomat.io')
305
+ .action(async (debugFile, opts) => {
306
+ try {
307
+ const replayService = new Replay({
308
+ apiKey: config.TESTOMATIO,
309
+ dryRun: opts.dryRun,
310
+ onLog: (message) => console.log(APP_PREFIX, message),
311
+ onError: (message) => console.error(APP_PREFIX, '⚠️ ', message),
312
+ onProgress: ({ current, total }) => {
313
+ if (current % 10 === 0 || current === total) {
314
+ console.log(APP_PREFIX, `📊 Progress: ${current}/${total} tests processed`);
315
+ }
316
+ }
317
+ });
318
+
319
+ const result = await replayService.replay(debugFile);
320
+
321
+ if (result.dryRun) {
322
+ console.log(APP_PREFIX, '🔍 Dry run completed:');
323
+ console.log(APP_PREFIX, ` - Tests found: ${result.testsCount}`);
324
+ console.log(APP_PREFIX, ` - Environment variables: ${Object.keys(result.envVars).length}`);
325
+ console.log(APP_PREFIX, ` - Run parameters:`, result.runParams);
326
+ console.log(APP_PREFIX, ' Use without --dry-run to actually send the data');
327
+ } else {
328
+ console.log(APP_PREFIX, `✅ Successfully replayed ${result.successCount}/${result.testsCount} tests`);
329
+ if (result.failureCount > 0) {
330
+ console.log(APP_PREFIX, `⚠️ ${result.failureCount} tests failed to upload`);
331
+ }
332
+ }
333
+
334
+ process.exit(0);
335
+ } catch (err) {
336
+ console.error(APP_PREFIX, '❌ Error replaying debug data:', err.message);
337
+ if (err.message.includes('Debug file not found')) {
338
+ console.error(APP_PREFIX, '💡 Hint: Run tests with TESTOMATIO_DEBUG=1 to generate debug files');
339
+ }
340
+ process.exit(1);
341
+ }
342
+ });
343
+
276
344
  program.parse(process.argv);
277
345
 
278
346
  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, 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,7 +31,6 @@ 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 = {};
@@ -156,6 +155,11 @@ class Client {
156
155
  suite_title: 'Unknown suite',
157
156
  };
158
157
 
158
+ // Add timestamp if not already present (microseconds since Unix epoch)
159
+ if (!testData.timestamp && !process.env.TESTOMATIO_NO_TIMESTAMP) {
160
+ testData.timestamp = Math.floor((performance.timeOrigin + performance.now()) * 1000);
161
+ }
162
+
159
163
  /**
160
164
  * @type {TestData}
161
165
  */
@@ -173,7 +177,9 @@ class Client {
173
177
  suite_title,
174
178
  suite_id,
175
179
  test_id,
180
+ timestamp,
176
181
  manuallyAttachedArtifacts,
182
+ labels,
177
183
  } = testData;
178
184
  let { message = '', meta = {} } = testData;
179
185
 
@@ -214,6 +220,8 @@ class Client {
214
220
  return acc;
215
221
  }, {});
216
222
 
223
+ // Labels are simple array of strings, no processing needed
224
+
217
225
  let errorFormatted = '';
218
226
  if (error) {
219
227
  errorFormatted += this.formatError(error) || '';
@@ -246,6 +254,10 @@ class Client {
246
254
 
247
255
  const artifacts = (await Promise.all(uploadedFiles)).filter(n => !!n);
248
256
 
257
+ const workspaceDir = process.env.TESTOMATIO_WORKDIR || process.cwd();
258
+ const relativeFile = file ? path.relative(workspaceDir, file) : file;
259
+ const rootSuiteId = validateSuiteId(process.env.TESTOMATIO_SUITE);
260
+
249
261
  const data = {
250
262
  rid,
251
263
  files,
@@ -253,7 +265,7 @@ class Client {
253
265
  status,
254
266
  stack: fullLogs,
255
267
  example,
256
- file,
268
+ file: relativeFile,
257
269
  code,
258
270
  title,
259
271
  suite_title,
@@ -261,8 +273,11 @@ class Client {
261
273
  test_id,
262
274
  message,
263
275
  run_time: typeof time === 'number' ? time : parseFloat(time),
276
+ timestamp,
264
277
  artifacts,
265
278
  meta,
279
+ labels,
280
+ ...(rootSuiteId && { root_suite_id: rootSuiteId }),
266
281
  };
267
282
 
268
283
  // debug('Adding test run...', data);
@@ -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))})`)
@@ -1,5 +1,5 @@
1
1
  import createDebugMessages from 'debug';
2
- import axios from 'axios';
2
+ import { Gaxios } from 'gaxios';
3
3
  import pc from 'picocolors';
4
4
  import humanizeDuration from 'humanize-duration';
5
5
  import merge from 'lodash.merge';
@@ -45,6 +45,12 @@ class GitLabPipe {
45
45
  }
46
46
 
47
47
  this.isEnabled = true;
48
+ this.client = new Gaxios({
49
+ baseURL: 'https://gitlab.com/api/v4',
50
+ headers: {
51
+ 'Content-Type': 'application/json',
52
+ }
53
+ });
48
54
 
49
55
  debug('GitLab Pipe: Enabled');
50
56
  }
@@ -149,7 +155,6 @@ class GitLabPipe {
149
155
  if (this.tests.length > 0) {
150
156
  body += '\n<details>\n<summary><h3>🐢 Slowest Tests</h3></summary>\n\n';
151
157
  body += this.tests
152
- // eslint-disable-next-line no-unsafe-optional-chaining
153
158
  .sort((a, b) => b?.run_time - a?.run_time)
154
159
  .slice(0, 5)
155
160
  .map(t => `* ${fullName(t)} (${humanizeDuration(parseFloat(t.run_time))})`)
@@ -158,16 +163,21 @@ class GitLabPipe {
158
163
  }
159
164
 
160
165
  // eslint-disable-next-line max-len
161
- const commentsRequestURL = `https://gitlab.com/api/v4/projects/${this.ENV.CI_PROJECT_ID}/merge_requests/${this.ENV.CI_MERGE_REQUEST_IID}/notes`;
166
+ const commentsRequestURL = `/projects/${this.ENV.CI_PROJECT_ID}/merge_requests/${this.ENV.CI_MERGE_REQUEST_IID}/notes`;
162
167
 
163
168
  // delete previous report
164
- await deletePreviousReport(axios, commentsRequestURL, this.hiddenCommentData, this.token);
169
+ await deletePreviousReport(this.client, commentsRequestURL, this.hiddenCommentData, this.token);
165
170
 
166
171
  // add current report
167
172
  debug(`Adding comment via url: ${commentsRequestURL}`);
168
173
 
169
174
  try {
170
- const addCommentResponse = await axios.post(`${commentsRequestURL}?access_token=${this.token}`, { body });
175
+ const addCommentResponse = await this.client.request({
176
+ method: 'POST',
177
+ url: commentsRequestURL,
178
+ params: { access_token: this.token },
179
+ data: { body }
180
+ });
171
181
 
172
182
  const commentID = addCommentResponse.data.id;
173
183
  // eslint-disable-next-line max-len
@@ -192,14 +202,18 @@ class GitLabPipe {
192
202
  updateRun() {}
193
203
  }
194
204
 
195
- async function deletePreviousReport(axiosInstance, commentsRequestURL, hiddenCommentData, token) {
205
+ async function deletePreviousReport(client, commentsRequestURL, hiddenCommentData, token) {
196
206
  if (process.env.GITLAB_KEEP_OUTDATED_REPORTS) return;
197
207
 
198
208
  // get comments
199
209
  let comments = [];
200
210
 
201
211
  try {
202
- const response = await axiosInstance.get(`${commentsRequestURL}?access_token=${token}`);
212
+ const response = await client.request({
213
+ method: 'GET',
214
+ url: commentsRequestURL,
215
+ params: { access_token: token }
216
+ });
203
217
  comments = response.data;
204
218
  } catch (e) {
205
219
  console.error('Error while attempt to retrieve comments on GitLab Merge Request:\n', e);
@@ -212,8 +226,12 @@ async function deletePreviousReport(axiosInstance, commentsRequestURL, hiddenCom
212
226
  if (comment.body.includes(hiddenCommentData)) {
213
227
  try {
214
228
  // delete previous comment
215
- const deleteCommentURL = `${commentsRequestURL}/${comment.id}?access_token=${token}`;
216
- await axiosInstance.delete(deleteCommentURL);
229
+ const deleteCommentURL = `${commentsRequestURL}/${comment.id}`;
230
+ await client.request({
231
+ method: 'DELETE',
232
+ url: deleteCommentURL,
233
+ params: { access_token: token }
234
+ });
217
235
  } catch (e) {
218
236
  console.warn(`Can't delete previously added comment with testomat.io report. Ignore.`);
219
237
  }
package/src/pipe/html.js CHANGED
@@ -237,7 +237,6 @@ class HtmlPipe {
237
237
  </select>`,
238
238
  ),
239
239
  );
240
- /* eslint-disable */
241
240
  handlebars.registerHelper('emptyDataComponent', () => {
242
241
  const svgFilePath = path.join(__dirname, '..', 'template', 'emptyData.svg');
243
242
  const svgContent = fs.readFileSync(svgFilePath, 'utf8');
@@ -254,13 +253,12 @@ class HtmlPipe {
254
253
  <div>`,
255
254
  );
256
255
  });
257
- /* eslint-enable */
258
256
  handlebars.registerHelper('pageDispleyElements', tests => {
259
257
  // We wrapp the lines to the HTML format we need
260
258
  const totalTests = JSON.parse(
261
259
  JSON.stringify(tests)
262
260
  .replace(/<script>/g, '&lt;script&gt;')
263
- .replace(/<\/script>/g, '&lt;/script&gt;'), // eslint-disable-line
261
+ .replace(/<\/script>/g, '&lt;/script&gt;'),
264
262
  );
265
263
 
266
264
  const paginationOptions = {
@@ -287,7 +285,6 @@ class HtmlPipe {
287
285
 
288
286
  statuses.forEach(status => {
289
287
  for (const option in paginationOptions) {
290
- // eslint-disable-next-line no-prototype-builtins
291
288
  if (paginationOptions.hasOwnProperty(option)) {
292
289
  const pageSize = paginationOptions[option];
293
290
  let filteredItems = totalTests;