@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.
- package/README.md +1 -0
- package/lib/adapter/codecept.js +288 -202
- package/lib/adapter/cypress-plugin/index.js +0 -2
- package/lib/adapter/mocha.js +0 -1
- package/lib/adapter/nightwatch.js +5 -5
- package/lib/adapter/playwright.js +11 -3
- package/lib/adapter/webdriver.d.ts +1 -1
- package/lib/adapter/webdriver.js +18 -8
- package/lib/bin/cli.js +73 -8
- package/lib/bin/reportXml.js +4 -2
- package/lib/bin/startTest.js +3 -2
- package/lib/bin/uploadArtifacts.js +5 -4
- package/lib/client.js +30 -10
- package/lib/data-storage.d.ts +5 -5
- package/lib/data-storage.js +23 -13
- package/lib/junit-adapter/csharp.d.ts +1 -0
- package/lib/junit-adapter/csharp.js +11 -1
- package/lib/pipe/bitbucket.d.ts +2 -0
- package/lib/pipe/bitbucket.js +38 -26
- package/lib/pipe/debug.js +27 -6
- package/lib/pipe/github.d.ts +2 -2
- package/lib/pipe/github.js +35 -3
- package/lib/pipe/gitlab.d.ts +2 -0
- package/lib/pipe/gitlab.js +27 -9
- package/lib/pipe/html.js +0 -3
- package/lib/pipe/index.js +17 -7
- package/lib/pipe/testomatio.d.ts +3 -2
- package/lib/pipe/testomatio.js +85 -75
- package/lib/replay.d.ts +31 -0
- package/lib/replay.js +255 -0
- package/lib/reporter-functions.d.ts +7 -0
- package/lib/reporter-functions.js +36 -0
- package/lib/reporter.d.ts +15 -12
- package/lib/reporter.js +4 -1
- package/lib/services/artifacts.d.ts +1 -1
- package/lib/services/index.d.ts +2 -0
- package/lib/services/index.js +2 -0
- package/lib/services/key-values.d.ts +1 -1
- package/lib/services/labels.d.ts +22 -0
- package/lib/services/labels.js +62 -0
- package/lib/services/logger.d.ts +1 -1
- package/lib/services/logger.js +1 -2
- package/lib/template/testomatio.hbs +443 -68
- package/lib/uploader.js +10 -6
- package/lib/utils/constants.d.ts +12 -0
- package/lib/utils/constants.js +15 -0
- package/lib/utils/utils.d.ts +10 -1
- package/lib/utils/utils.js +70 -22
- package/lib/xmlReader.js +54 -19
- package/package.json +16 -11
- package/src/adapter/codecept.js +320 -214
- package/src/adapter/cypress-plugin/index.js +0 -2
- package/src/adapter/mocha.js +0 -1
- package/src/adapter/nightwatch.js +1 -1
- package/src/adapter/playwright.js +10 -7
- package/src/adapter/webdriver.js +2 -2
- package/src/bin/cli.js +70 -2
- package/src/bin/reportXml.js +4 -1
- package/src/bin/startTest.js +2 -1
- package/src/bin/uploadArtifacts.js +2 -1
- package/src/client.js +18 -3
- package/src/data-storage.js +6 -6
- package/src/junit-adapter/csharp.js +13 -1
- package/src/pipe/bitbucket.js +22 -24
- package/src/pipe/debug.js +26 -5
- package/src/pipe/github.js +1 -2
- package/src/pipe/gitlab.js +27 -9
- package/src/pipe/html.js +1 -4
- package/src/pipe/testomatio.js +106 -105
- package/src/replay.js +262 -0
- package/src/reporter-functions.js +41 -0
- package/src/reporter.js +3 -0
- package/src/services/index.js +2 -0
- package/src/services/labels.js +59 -0
- package/src/services/logger.js +1 -2
- package/src/template/testomatio.hbs +443 -68
- package/src/uploader.js +11 -6
- package/src/utils/constants.js +12 -0
- package/src/utils/utils.js +46 -13
- 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
|
-
|
|
132
|
-
|
|
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
|
|
package/src/adapter/webdriver.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
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 {
|
|
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) {
|
package/src/bin/reportXml.js
CHANGED
|
@@ -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 {
|
|
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}`)));
|
package/src/bin/startTest.js
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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);
|
package/src/data-storage.js
CHANGED
|
@@ -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 =
|
|
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
|
+
}
|
package/src/pipe/bitbucket.js
CHANGED
|
@@ -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
|
|
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 =
|
|
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(
|
|
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
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
{
|
|
183
|
-
|
|
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(
|
|
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
|
|
221
|
-
|
|
222
|
-
|
|
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
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
118
|
+
this.logToFile({ action: 'finishRun', params });
|
|
119
|
+
console.log(APP_PREFIX, '🪲 Debug Saved to', this.logFilePath);
|
|
99
120
|
}
|
|
100
121
|
|
|
101
122
|
toString() {
|
package/src/pipe/github.js
CHANGED
|
@@ -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))})`)
|
package/src/pipe/gitlab.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import createDebugMessages from 'debug';
|
|
2
|
-
import
|
|
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 =
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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}
|
|
216
|
-
await
|
|
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, '<script>')
|
|
263
|
-
.replace(/<\/script>/g, '</script>'),
|
|
261
|
+
.replace(/<\/script>/g, '</script>'),
|
|
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;
|