@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.
- 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 +31 -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 +259 -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 +57 -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 +13 -5
- package/src/bin/cli.js +78 -7
- 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 +28 -5
- 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 +112 -107
- package/src/replay.js +268 -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 +67 -15
- 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
|
-
|
|
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';
|
|
@@ -15,8 +14,10 @@ class WebdriverReporter extends WDIOReporter {
|
|
|
15
14
|
this._addTestPromises = [];
|
|
16
15
|
|
|
17
16
|
this._isSynchronising = false;
|
|
18
|
-
|
|
19
|
-
|
|
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 {
|
|
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) {
|
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, 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 =
|
|
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();
|
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))})`)
|