@testomatio/reporter 2.0.0-beta-esm → 2.0.0-beta.1-xml
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/lib/adapter/codecept.d.ts +2 -0
- package/lib/adapter/codecept.js +31 -26
- package/lib/adapter/cucumber/current.d.ts +14 -0
- package/lib/adapter/cucumber/legacy.d.ts +0 -0
- package/lib/adapter/cucumber.d.ts +2 -0
- package/lib/adapter/cypress-plugin/index.d.ts +2 -0
- package/lib/adapter/cypress-plugin/index.js +10 -10
- package/lib/adapter/jasmine.d.ts +11 -0
- package/lib/adapter/jest.d.ts +13 -0
- package/lib/adapter/mocha.d.ts +2 -0
- package/lib/adapter/mocha.js +4 -4
- package/lib/adapter/nightwatch.d.ts +4 -0
- package/lib/adapter/nightwatch.js +80 -0
- package/lib/adapter/playwright.d.ts +14 -0
- package/lib/adapter/playwright.js +58 -33
- package/lib/adapter/vitest.d.ts +35 -0
- package/lib/adapter/vitest.js +6 -6
- package/lib/adapter/webdriver.d.ts +24 -0
- package/lib/adapter/webdriver.js +51 -14
- package/lib/bin/cli.d.ts +2 -0
- package/lib/bin/cli.js +250 -0
- package/lib/bin/reportXml.d.ts +2 -0
- package/lib/bin/reportXml.js +15 -11
- package/lib/bin/startTest.d.ts +2 -0
- package/lib/bin/startTest.js +12 -7
- package/lib/bin/uploadArtifacts.d.ts +2 -0
- package/lib/bin/uploadArtifacts.js +82 -0
- package/lib/client.d.ts +76 -0
- package/lib/client.js +128 -53
- package/lib/config.d.ts +1 -0
- package/lib/config.js +2 -2
- package/lib/constants.d.ts +25 -0
- package/lib/constants.js +5 -1
- package/lib/data-storage.d.ts +34 -0
- package/lib/data-storage.js +19 -9
- package/lib/junit-adapter/adapter.d.ts +9 -0
- package/lib/junit-adapter/csharp.d.ts +5 -0
- package/lib/junit-adapter/csharp.js +11 -1
- package/lib/junit-adapter/index.d.ts +3 -0
- package/lib/junit-adapter/java.d.ts +5 -0
- package/lib/junit-adapter/javascript.d.ts +4 -0
- package/lib/junit-adapter/python.d.ts +5 -0
- package/lib/junit-adapter/ruby.d.ts +4 -0
- package/lib/output.d.ts +11 -0
- package/lib/package.json +3 -1
- package/lib/pipe/bitbucket.d.ts +23 -0
- package/lib/pipe/bitbucket.js +19 -9
- package/lib/pipe/csv.d.ts +47 -0
- package/lib/pipe/csv.js +2 -2
- package/lib/pipe/debug.d.ts +29 -0
- package/lib/pipe/debug.js +108 -0
- package/lib/pipe/github.d.ts +30 -0
- package/lib/pipe/github.js +37 -5
- package/lib/pipe/gitlab.d.ts +23 -0
- package/lib/pipe/gitlab.js +2 -3
- package/lib/pipe/html.d.ts +35 -0
- package/lib/pipe/html.js +9 -4
- package/lib/pipe/index.d.ts +1 -0
- package/lib/pipe/index.js +20 -10
- package/lib/pipe/testomatio.d.ts +70 -0
- package/lib/pipe/testomatio.js +54 -39
- package/lib/reporter-functions.d.ts +34 -0
- package/lib/reporter-functions.js +17 -7
- package/lib/reporter.d.ts +232 -0
- package/lib/reporter.js +19 -33
- package/lib/services/artifacts.d.ts +33 -0
- package/lib/services/index.d.ts +9 -0
- package/lib/services/key-values.d.ts +27 -0
- package/lib/services/key-values.js +1 -1
- package/lib/services/logger.d.ts +64 -0
- package/lib/services/logger.js +1 -2
- package/lib/template/testomatio.hbs +651 -1366
- package/lib/uploader.d.ts +60 -0
- package/lib/uploader.js +312 -0
- package/lib/utils/pipe_utils.d.ts +41 -0
- package/lib/utils/pipe_utils.js +3 -5
- package/lib/utils/utils.d.ts +47 -0
- package/lib/utils/utils.js +99 -12
- package/lib/xmlReader.d.ts +92 -0
- package/lib/xmlReader.js +64 -25
- package/package.json +19 -13
- package/src/adapter/codecept.js +30 -26
- package/src/adapter/cypress-plugin/index.js +5 -5
- package/src/adapter/mocha.cjs +1 -1
- package/src/adapter/mocha.js +4 -4
- package/src/adapter/nightwatch.js +88 -0
- package/src/adapter/playwright.js +59 -31
- package/src/adapter/vitest.js +6 -6
- package/src/adapter/webdriver.js +42 -12
- package/src/bin/cli.js +303 -0
- package/src/bin/reportXml.js +19 -9
- package/src/bin/startTest.js +9 -4
- package/src/bin/uploadArtifacts.js +91 -0
- package/src/client.js +137 -57
- package/src/config.js +2 -2
- package/src/constants.js +5 -1
- package/src/data-storage.js +2 -2
- package/src/junit-adapter/csharp.js +13 -1
- package/src/pipe/bitbucket.js +2 -2
- package/src/pipe/csv.js +3 -3
- package/src/pipe/debug.js +104 -0
- package/src/pipe/github.js +3 -5
- package/src/pipe/gitlab.js +6 -7
- package/src/pipe/html.js +14 -7
- package/src/pipe/index.js +5 -7
- package/src/pipe/testomatio.js +75 -76
- package/src/reporter-functions.js +18 -7
- package/src/reporter.cjs_decprecated +21 -0
- package/src/reporter.js +20 -11
- package/src/services/key-values.js +1 -1
- package/src/services/logger.js +5 -4
- package/src/template/testomatio.hbs +651 -1366
- package/src/uploader.js +371 -0
- package/src/utils/pipe_utils.js +4 -12
- package/src/utils/utils.js +64 -15
- package/src/xmlReader.js +76 -26
- package/lib/adapter/jasmine/jasmine.js +0 -63
- package/lib/adapter/mocha/mocha.js +0 -125
- package/lib/fileUploader.js +0 -245
- package/lib/utils/chalk.js +0 -10
- package/src/fileUploader.js +0 -307
- package/src/reporter.cjs +0 -22
- package/src/utils/chalk.js +0 -13
package/src/bin/reportXml.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import { Command } from 'commander';
|
|
3
3
|
import pc from 'picocolors';
|
|
4
|
-
import glob from 'glob';
|
|
4
|
+
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
|
+
import dotenv from 'dotenv';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
|
|
12
|
+
const version = getPackageVersion();
|
|
9
13
|
|
|
10
14
|
const debug = createDebugMessages('@testomatio/reporter:xml-cli');
|
|
11
15
|
console.log(pc.cyan(pc.bold(` 🤩 Testomat.io XML Reporter v${version}`)));
|
|
16
|
+
const program = new Command();
|
|
12
17
|
|
|
13
18
|
program
|
|
14
19
|
.arguments('<pattern>')
|
|
@@ -25,10 +30,10 @@ program
|
|
|
25
30
|
if (opts.envFile) {
|
|
26
31
|
console.log(APP_PREFIX, 'Loading env file:', opts.envFile);
|
|
27
32
|
debug('Loading env file: %s', opts.envFile);
|
|
28
|
-
|
|
33
|
+
dotenv.config({ path: opts.envFile });
|
|
29
34
|
}
|
|
30
|
-
if (javaTests === true) javaTests = 'src/test/java';
|
|
31
35
|
lang = lang?.toLowerCase();
|
|
36
|
+
if (javaTests === true || (lang === 'java' && !javaTests)) javaTests = 'src/test/java';
|
|
32
37
|
const runReader = new XmlReader({ javaTests, lang });
|
|
33
38
|
const files = glob.sync(pattern, { cwd: opts.dir || process.cwd() });
|
|
34
39
|
if (!files.length) {
|
|
@@ -44,10 +49,15 @@ program
|
|
|
44
49
|
|
|
45
50
|
let timeoutTimer;
|
|
46
51
|
if (opts.timelimit) {
|
|
47
|
-
timeoutTimer = setTimeout(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
timeoutTimer = setTimeout(
|
|
53
|
+
() => {
|
|
54
|
+
console.log(
|
|
55
|
+
`⚠️ Reached timeout of ${opts.timelimit}s. Exiting... (Exit code is 0 to not fail the pipeline)`,
|
|
56
|
+
);
|
|
57
|
+
process.exit(0);
|
|
58
|
+
},
|
|
59
|
+
parseInt(opts.timelimit, 10) * 1000,
|
|
60
|
+
);
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
try {
|
package/src/bin/startTest.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from 'cross-spawn';
|
|
3
|
-
import
|
|
3
|
+
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 {
|
|
8
|
-
import {config} from '../config.js';
|
|
7
|
+
import { getPackageVersion } from '../utils/utils.js';
|
|
8
|
+
import { config } from '../config.js';
|
|
9
|
+
import dotenv from 'dotenv';
|
|
9
10
|
|
|
11
|
+
const version = getPackageVersion();
|
|
10
12
|
console.log(pc.cyan(pc.bold(` 🤩 Testomat.io Reporter v${version}`)));
|
|
13
|
+
const program = new Command();
|
|
11
14
|
|
|
12
15
|
program
|
|
13
16
|
.option('-c, --command <cmd>', 'Test runner command')
|
|
@@ -19,7 +22,7 @@ program
|
|
|
19
22
|
const { launch, finish, filter } = opts;
|
|
20
23
|
let { command } = opts;
|
|
21
24
|
|
|
22
|
-
if (opts.envFile)
|
|
25
|
+
if (opts.envFile) dotenv.config({ path: opts.envFile });
|
|
23
26
|
|
|
24
27
|
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
|
|
25
28
|
const title = process.env.TESTOMATIO_TITLE;
|
|
@@ -36,6 +39,8 @@ program
|
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
if (finish) {
|
|
42
|
+
// TODO: add error in case of TESTOMATIO environment variable is not set
|
|
43
|
+
// because command is fine in console, but actually (on testomat.io) run is not finished
|
|
39
44
|
if (!process.env.TESTOMATIO_RUN) {
|
|
40
45
|
console.log('TESTOMATIO_RUN environment variable must be set.');
|
|
41
46
|
return process.exit(1);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import pc from 'picocolors';
|
|
5
|
+
import createDebugMessages from 'debug';
|
|
6
|
+
import TestomatClient from '../client.js';
|
|
7
|
+
import { APP_PREFIX } from '../constants.js';
|
|
8
|
+
import { getPackageVersion } from '../utils/utils.js';
|
|
9
|
+
import { config } from '../config.js';
|
|
10
|
+
import { readLatestRunId } from '../utils/utils.js';
|
|
11
|
+
import dotenv from 'dotenv';
|
|
12
|
+
|
|
13
|
+
const debug = createDebugMessages('@testomatio/reporter:upload-cli');
|
|
14
|
+
const version = getPackageVersion();
|
|
15
|
+
console.log(pc.cyan(pc.bold(` 🤩 Testomat.io Reporter v${version}`)));
|
|
16
|
+
const program = new Command();
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.option('--env-file <envfile>', 'Load environment variables from env file')
|
|
20
|
+
.option('--force', 'Re-upload artifacts even if they were uploaded before')
|
|
21
|
+
.action(async opts => {
|
|
22
|
+
if (opts.envFile) {
|
|
23
|
+
dotenv.config({ path: opts.envFile });
|
|
24
|
+
} else {
|
|
25
|
+
dotenv.config();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const apiKey = config.TESTOMATIO;
|
|
29
|
+
process.env.TESTOMATIO_DISABLE_ARTIFACTS = '';
|
|
30
|
+
const runId = process.env.TESTOMATIO_RUN || process.env.runId || readLatestRunId();
|
|
31
|
+
|
|
32
|
+
if (!runId) {
|
|
33
|
+
console.log('TESTOMATIO_RUN environment variable must be set or restored from a previous run.');
|
|
34
|
+
return process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const client = new TestomatClient({
|
|
38
|
+
apiKey,
|
|
39
|
+
runId,
|
|
40
|
+
isBatchEnabled: false,
|
|
41
|
+
});
|
|
42
|
+
let testruns = client.uploader.readUploadedFiles(process.env.TESTOMATIO_RUN);
|
|
43
|
+
|
|
44
|
+
const numTotalArtifacts = testruns.length;
|
|
45
|
+
|
|
46
|
+
debug('Found testruns:', testruns);
|
|
47
|
+
|
|
48
|
+
if (!opts.force) testruns = testruns.filter(tr => !tr.uploaded);
|
|
49
|
+
|
|
50
|
+
if (!testruns.length) {
|
|
51
|
+
console.log(APP_PREFIX, 'Total artifacts:', numTotalArtifacts);
|
|
52
|
+
if (numTotalArtifacts) {
|
|
53
|
+
console.log(APP_PREFIX, 'No new artifacts to upload');
|
|
54
|
+
console.log(APP_PREFIX, 'To re-upload artifacts run this command with --force flag');
|
|
55
|
+
}
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const testrunsByRid = testruns.reduce((acc, { rid, file }) => {
|
|
60
|
+
if (!acc[rid]) {
|
|
61
|
+
acc[rid] = [];
|
|
62
|
+
}
|
|
63
|
+
if (!acc[rid].includes(file)) acc[rid].push(file);
|
|
64
|
+
return acc;
|
|
65
|
+
}, {});
|
|
66
|
+
|
|
67
|
+
// we need to obtain S3 credentials
|
|
68
|
+
await client.createRun();
|
|
69
|
+
|
|
70
|
+
client.uploader.checkEnabled();
|
|
71
|
+
client.uploader.disableLogStorage();
|
|
72
|
+
|
|
73
|
+
for (const rid in testrunsByRid) {
|
|
74
|
+
const files = testrunsByRid[rid];
|
|
75
|
+
await client.addTestRun(undefined, {
|
|
76
|
+
rid,
|
|
77
|
+
files,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.log(APP_PREFIX, client.uploader.successfulUploads.length, 'artifacts uploaded');
|
|
82
|
+
if (client.uploader.failedUploads.length) {
|
|
83
|
+
console.log(APP_PREFIX, client.uploader.failedUploads.length, 'artifacts failed to upload');
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (process.argv.length <= 1) {
|
|
88
|
+
program.outputHelp();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
program.parse(process.argv);
|
package/src/client.js
CHANGED
|
@@ -4,24 +4,26 @@ import { minimatch } from 'minimatch';
|
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import pc from 'picocolors';
|
|
6
6
|
import { randomUUID } from 'crypto';
|
|
7
|
-
import {upload} from './fileUploader.js';
|
|
8
7
|
import { APP_PREFIX, STATUS } from './constants.js';
|
|
9
8
|
import { pipesFactory } from './pipe/index.js';
|
|
10
9
|
import { glob } from 'glob';
|
|
11
|
-
import path, { sep} from 'path';
|
|
10
|
+
import path, { sep } from 'path';
|
|
12
11
|
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { S3Uploader } from './uploader.js';
|
|
13
|
+
import { formatStep, storeRunId } from './utils/utils.js';
|
|
14
|
+
import { filesize as prettyBytes } from 'filesize';
|
|
13
15
|
|
|
14
16
|
const debug = createDebugMessages('@testomatio/reporter:client');
|
|
15
17
|
|
|
16
18
|
// removed __dirname usage, because:
|
|
17
19
|
// 1. replaced with ESM syntax (import.meta.url), but it throws an error on tsc compilation;
|
|
18
|
-
// 2. got error "__dirname already defined" in compiles js code (cjs dir)
|
|
20
|
+
// 2. got error "__dirname already defined" in compiles js code (cjs dir)
|
|
19
21
|
|
|
20
22
|
let listOfTestFilesToExcludeFromReport = null;
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
|
-
* @typedef {import('../types').TestData} TestData
|
|
24
|
-
* @typedef {import('../types').PipeResult} PipeResult
|
|
25
|
+
* @typedef {import('../types/types.js').TestData} TestData
|
|
26
|
+
* @typedef {import('../types/types.js').PipeResult} PipeResult
|
|
25
27
|
*/
|
|
26
28
|
|
|
27
29
|
class Client {
|
|
@@ -29,12 +31,11 @@ class Client {
|
|
|
29
31
|
* Create a Testomat client instance
|
|
30
32
|
* @returns
|
|
31
33
|
*/
|
|
32
|
-
// eslint-disable-next-line
|
|
33
34
|
constructor(params = {}) {
|
|
34
|
-
this.
|
|
35
|
+
this.paramsForPipesFactory = params;
|
|
36
|
+
this.pipeStore = {};
|
|
37
|
+
this.runId = randomUUID(); // will be replaced by real run id
|
|
35
38
|
this.queue = Promise.resolve();
|
|
36
|
-
this.totalUploaded = 0;
|
|
37
|
-
this.failedToUpload = 0;
|
|
38
39
|
|
|
39
40
|
// @ts-ignore this line will be removed in compiled code, because __dirname is defined in commonjs
|
|
40
41
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -47,6 +48,7 @@ class Client {
|
|
|
47
48
|
}
|
|
48
49
|
this.executionList = Promise.resolve();
|
|
49
50
|
|
|
51
|
+
this.uploader = new S3Uploader();
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
/**
|
|
@@ -66,8 +68,7 @@ class Client {
|
|
|
66
68
|
* or resolves to undefined if no valid results are found or if all pipes are disabled.
|
|
67
69
|
*/
|
|
68
70
|
async prepareRun(params) {
|
|
69
|
-
|
|
70
|
-
this.pipes = await pipesFactory(params, store);
|
|
71
|
+
this.pipes = await pipesFactory(params || this.paramsForPipesFactory || {}, this.pipeStore);
|
|
71
72
|
const { pipe, pipeOptions } = params;
|
|
72
73
|
// all pipes disabled, skipping
|
|
73
74
|
if (!this.pipes.some(p => p.isEnabled)) {
|
|
@@ -77,7 +78,7 @@ class Client {
|
|
|
77
78
|
try {
|
|
78
79
|
const filterPipe = this.pipes.find(p => p.constructor.name.toLowerCase() === `${pipe.toLowerCase()}pipe`);
|
|
79
80
|
|
|
80
|
-
if (!filterPipe
|
|
81
|
+
if (!filterPipe?.isEnabled) {
|
|
81
82
|
// TODO:for the future for the another pipes
|
|
82
83
|
console.warn(
|
|
83
84
|
APP_PREFIX,
|
|
@@ -110,7 +111,8 @@ class Client {
|
|
|
110
111
|
* @returns {Promise<any>} - resolves to Run id which should be used to update / add test
|
|
111
112
|
*/
|
|
112
113
|
async createRun(params) {
|
|
113
|
-
if (!this.pipes || !this.pipes.length)
|
|
114
|
+
if (!this.pipes || !this.pipes.length)
|
|
115
|
+
this.pipes = await pipesFactory(params || this.paramsForPipesFactory || {}, this.pipeStore);
|
|
114
116
|
debug('Creating run...');
|
|
115
117
|
// all pipes disabled, skipping
|
|
116
118
|
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
@@ -118,6 +120,12 @@ class Client {
|
|
|
118
120
|
this.queue = this.queue
|
|
119
121
|
.then(() => Promise.all(this.pipes.map(p => p.createRun())))
|
|
120
122
|
.catch(err => console.log(APP_PREFIX, err))
|
|
123
|
+
.then(() => {
|
|
124
|
+
const runId = this.pipeStore?.runId;
|
|
125
|
+
if (runId) this.runId = runId;
|
|
126
|
+
storeRunId(this.runId);
|
|
127
|
+
})
|
|
128
|
+
.then(() => this.uploader.checkEnabled())
|
|
121
129
|
.then(() => undefined); // fixes return type
|
|
122
130
|
// debug('Run', this.queue);
|
|
123
131
|
return this.queue;
|
|
@@ -165,9 +173,45 @@ class Client {
|
|
|
165
173
|
suite_id,
|
|
166
174
|
test_id,
|
|
167
175
|
manuallyAttachedArtifacts,
|
|
168
|
-
meta,
|
|
169
176
|
} = testData;
|
|
170
|
-
let { message = '' } = testData;
|
|
177
|
+
let { message = '', meta = {} } = testData;
|
|
178
|
+
|
|
179
|
+
// stringify meta values and limit keys and values length to 255
|
|
180
|
+
meta = Object.entries(meta)
|
|
181
|
+
.filter(([, value]) => value !== null && value !== undefined)
|
|
182
|
+
.map(([key, value]) => {
|
|
183
|
+
try {
|
|
184
|
+
if (typeof value === 'object') {
|
|
185
|
+
value = JSON.stringify(value);
|
|
186
|
+
} else if (typeof value !== 'string') {
|
|
187
|
+
try {
|
|
188
|
+
value = value.toString();
|
|
189
|
+
} catch (err) {
|
|
190
|
+
console.warn(APP_PREFIX, `Can't convert meta value to string`, err);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (value?.length > 255) {
|
|
195
|
+
value = value.substring(0, 255);
|
|
196
|
+
debug(APP_PREFIX, `Meta info value "${value}" is too long, trimmed to 255 characters`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (key?.length > 255) {
|
|
200
|
+
const newKey = key.substring(0, 255);
|
|
201
|
+
debug(APP_PREFIX, `Meta info key "${key}" is too long, trimmed to 255 characters`);
|
|
202
|
+
return [newKey, value];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return [key, value];
|
|
206
|
+
} catch (err) {
|
|
207
|
+
debug(APP_PREFIX, `Error while processing meta info key ${key}`, err);
|
|
208
|
+
return [null, null];
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
.reduce((acc, [key, value]) => {
|
|
212
|
+
if (key) acc[key] = value;
|
|
213
|
+
return acc;
|
|
214
|
+
}, {});
|
|
171
215
|
|
|
172
216
|
let errorFormatted = '';
|
|
173
217
|
if (error) {
|
|
@@ -183,24 +227,24 @@ class Client {
|
|
|
183
227
|
|
|
184
228
|
const uploadedFiles = [];
|
|
185
229
|
|
|
186
|
-
for (
|
|
187
|
-
|
|
230
|
+
for (let f of files) {
|
|
231
|
+
if (!f) continue; // f === null
|
|
232
|
+
if (typeof f === 'object') {
|
|
233
|
+
if (!f.path) continue;
|
|
234
|
+
|
|
235
|
+
f = f.path;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
uploadedFiles.push(this.uploader.uploadFileByPath(f, [this.runId, rid, path.basename(f)]));
|
|
188
239
|
}
|
|
189
240
|
|
|
190
241
|
for (const [idx, buffer] of filesBuffers.entries()) {
|
|
191
242
|
const fileName = `${idx + 1}-${title.replace(/\s+/g, '-')}`;
|
|
192
|
-
uploadedFiles.push(
|
|
243
|
+
uploadedFiles.push(this.uploader.uploadFileAsBuffer(buffer, [this.runId, rid, fileName]));
|
|
193
244
|
}
|
|
194
245
|
|
|
195
246
|
const artifacts = (await Promise.all(uploadedFiles)).filter(n => !!n);
|
|
196
247
|
|
|
197
|
-
if (artifacts.length < uploadedFiles.length) {
|
|
198
|
-
const failedUploading = uploadedFiles.length - artifacts.length;
|
|
199
|
-
this.failedToUpload += failedUploading;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
this.totalUploaded += artifacts.length;
|
|
203
|
-
|
|
204
248
|
const data = {
|
|
205
249
|
rid,
|
|
206
250
|
files,
|
|
@@ -258,27 +302,81 @@ class Client {
|
|
|
258
302
|
this.queue = this.queue
|
|
259
303
|
.then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
|
|
260
304
|
.then(() => {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
305
|
+
if (!this.uploader.isEnabled) return;
|
|
306
|
+
|
|
307
|
+
const filesizeStrMaxLength = 7;
|
|
308
|
+
|
|
309
|
+
if (this.uploader.successfulUploads.length) {
|
|
310
|
+
debug('\n', APP_PREFIX, `🗄️ ${this.uploader.successfulUploads.length} artifacts uploaded to S3 bucket`);
|
|
311
|
+
const uploadedArtifacts = this.uploader.successfulUploads.map(file => ({
|
|
312
|
+
relativePath: file.path.replace(process.cwd(), ''),
|
|
313
|
+
link: file.link,
|
|
314
|
+
sizePretty: prettyBytes(file.size, { round: 0 }).toString(),
|
|
315
|
+
}));
|
|
316
|
+
|
|
317
|
+
uploadedArtifacts.forEach(upload => {
|
|
318
|
+
debug(
|
|
319
|
+
`🟢Uploaded artifact`,
|
|
320
|
+
`${upload.relativePath},`,
|
|
321
|
+
'size:',
|
|
322
|
+
`${upload.sizePretty},`,
|
|
323
|
+
'link:',
|
|
324
|
+
`${upload.link}`,
|
|
325
|
+
);
|
|
326
|
+
});
|
|
327
|
+
}
|
|
264
328
|
|
|
265
|
-
if (this.
|
|
329
|
+
if (this.uploader.failedUploads.length) {
|
|
266
330
|
console.log(
|
|
267
331
|
APP_PREFIX,
|
|
268
|
-
`🗄️ ${this.
|
|
269
|
-
process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : pc.bold('publicly')
|
|
270
|
-
} uploaded to S3 bucket`,
|
|
332
|
+
`🗄️ ${this.uploader.failedUploads.length} artifacts 🔴${pc.bold('failed')} to upload`,
|
|
271
333
|
);
|
|
334
|
+
const failedUploads = this.uploader.failedUploads.map(file => ({
|
|
335
|
+
relativePath: file.path.replace(process.cwd(), ''),
|
|
336
|
+
sizePretty: prettyBytes(file.size, { round: 0 }).toString(),
|
|
337
|
+
}));
|
|
272
338
|
|
|
273
|
-
|
|
339
|
+
const pathPadding = Math.max(...failedUploads.map(upload => upload.relativePath.length)) + 1;
|
|
340
|
+
|
|
341
|
+
failedUploads.forEach(upload => {
|
|
274
342
|
console.log(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
Run tests with DEBUG="@testomatio/reporter:file-uploader" to see details"`,
|
|
279
|
-
),
|
|
343
|
+
` ${pc.gray('|')} 🔴 ${upload.relativePath.padEnd(pathPadding)} ${pc.gray(
|
|
344
|
+
`| ${upload.sizePretty.padStart(filesizeStrMaxLength)} |`,
|
|
345
|
+
)}`,
|
|
280
346
|
);
|
|
281
|
-
}
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (this.uploader.skippedUploads.length) {
|
|
351
|
+
console.log(
|
|
352
|
+
'\n',
|
|
353
|
+
APP_PREFIX,
|
|
354
|
+
`🗄️ ${pc.bold(this.uploader.skippedUploads.length)} artifacts uploading 🟡${pc.bold('skipped')}`,
|
|
355
|
+
);
|
|
356
|
+
const skippedUploads = this.uploader.skippedUploads.map(file => ({
|
|
357
|
+
relativePath: file.path.replace(process.cwd(), ''),
|
|
358
|
+
sizePretty: file.size === null ? 'unknown' : prettyBytes(file.size, { round: 0 }).toString(),
|
|
359
|
+
}));
|
|
360
|
+
const pathPadding = Math.max(...skippedUploads.map(upload => upload.relativePath.length)) + 1;
|
|
361
|
+
skippedUploads.forEach(upload => {
|
|
362
|
+
console.log(
|
|
363
|
+
` ${pc.gray('|')} 🟡 ${upload.relativePath.padEnd(pathPadding)} ${pc.gray(
|
|
364
|
+
`| ${upload.sizePretty.padStart(filesizeStrMaxLength)} |`,
|
|
365
|
+
)}`,
|
|
366
|
+
);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (this.uploader.skippedUploads.length || this.uploader.failedUploads.length) {
|
|
371
|
+
const command = `TESTOMATIO=<your_api_key> TESTOMATIO_RUN=${
|
|
372
|
+
this.runId
|
|
373
|
+
} npx @testomatio/reporter upload-artifacts`;
|
|
374
|
+
const numberOfNotUploadedArtifacts = this.uploader.skippedUploads.length + this.uploader.failedUploads.length;
|
|
375
|
+
console.log(
|
|
376
|
+
APP_PREFIX,
|
|
377
|
+
`${numberOfNotUploadedArtifacts} artifacts were not uploaded.
|
|
378
|
+
Run "${pc.magenta(command)}" with valid S3 credentials to upload skipped & failed artifacts`,
|
|
379
|
+
);
|
|
282
380
|
}
|
|
283
381
|
})
|
|
284
382
|
.catch(err => console.log(APP_PREFIX, err));
|
|
@@ -365,24 +463,6 @@ function isNotInternalFrame(frame) {
|
|
|
365
463
|
);
|
|
366
464
|
}
|
|
367
465
|
|
|
368
|
-
function formatStep(step, shift = 0) {
|
|
369
|
-
const prefix = ' '.repeat(shift);
|
|
370
|
-
|
|
371
|
-
const lines = [];
|
|
372
|
-
|
|
373
|
-
if (step.error) {
|
|
374
|
-
lines.push(`${prefix}${pc.red(step.title)} ${pc.gray(`${step.duration}ms`)}`);
|
|
375
|
-
} else {
|
|
376
|
-
lines.push(`${prefix}${step.title} ${pc.gray(`${step.duration}ms`)}`);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
for (const child of step.steps || []) {
|
|
380
|
-
lines.push(...formatStep(child, shift + 2));
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
return lines;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
466
|
/**
|
|
387
467
|
*
|
|
388
468
|
* @param {TestData} testData
|
package/src/config.js
CHANGED
|
@@ -6,10 +6,10 @@ const debug = createDebugMessages('@testomatio/reporter:config');
|
|
|
6
6
|
/* for possibility to use multiple env files (reading different paths)
|
|
7
7
|
const envFileVars = dotenv.config({ path: '.env' }).parsed; */
|
|
8
8
|
|
|
9
|
-
if (process.env.TESTOMATIO_API_KEY) {
|
|
9
|
+
if (process.env.TESTOMATIO_API_KEY && !process.env.TESTOMATIO) {
|
|
10
10
|
process.env.TESTOMATIO = process.env.TESTOMATIO_API_KEY;
|
|
11
11
|
}
|
|
12
|
-
if (process.env.TESTOMATIO_TOKEN) {
|
|
12
|
+
if (process.env.TESTOMATIO_TOKEN && !process.env.TESTOMATIO) {
|
|
13
13
|
process.env.TESTOMATIO = process.env.TESTOMATIO_TOKEN;
|
|
14
14
|
}
|
|
15
15
|
|
package/src/constants.js
CHANGED
|
@@ -3,7 +3,11 @@ import os from 'os';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
|
|
5
5
|
const APP_PREFIX = pc.gray('[TESTOMATIO]');
|
|
6
|
-
const
|
|
6
|
+
const TESTOMATIO_REQUEST_TIMEOUT = parseInt(process.env.TESTOMATIO_REQUEST_TIMEOUT, 10);
|
|
7
|
+
if (TESTOMATIO_REQUEST_TIMEOUT) {
|
|
8
|
+
console.log(`${APP_PREFIX} Request timeout is set to ${TESTOMATIO_REQUEST_TIMEOUT / 1000}s`);
|
|
9
|
+
}
|
|
10
|
+
const AXIOS_TIMEOUT = TESTOMATIO_REQUEST_TIMEOUT || 20 * 1000;
|
|
7
11
|
|
|
8
12
|
const TESTOMAT_TMP_STORAGE_DIR = path.join(os.tmpdir(), 'testomatio_tmp');
|
|
9
13
|
|
package/src/data-storage.js
CHANGED
|
@@ -52,7 +52,7 @@ class DataStorage {
|
|
|
52
52
|
|
|
53
53
|
context = context || this.context || testRunnerHelper.getNameOfCurrentlyRunningTest();
|
|
54
54
|
if (!context) {
|
|
55
|
-
debug(`No context provided for "${dataType}" data:`, data);
|
|
55
|
+
// debug(`No context provided for "${dataType}" data:`, data);
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
58
|
const contextHash = stringToMD5Hash(context);
|
|
@@ -101,7 +101,7 @@ class DataStorage {
|
|
|
101
101
|
if (testDataFromFile.length) {
|
|
102
102
|
return testDataFromFile;
|
|
103
103
|
}
|
|
104
|
-
debug(`No "${dataType}" data for context "${contextHash}" in both file and global variable`);
|
|
104
|
+
// debug(`No "${dataType}" data for context "${contextHash}" in both file and global variable`);
|
|
105
105
|
|
|
106
106
|
// in case no data found for context
|
|
107
107
|
return null;
|
|
@@ -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
|
@@ -15,8 +15,8 @@ const debug = createDebugMessages('@testomatio/reporter:pipe:bitbucket');
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* @class BitbucketPipe
|
|
18
|
-
* @typedef {import('../../types').Pipe} Pipe
|
|
19
|
-
* @typedef {import('../../types').TestData} TestData
|
|
18
|
+
* @typedef {import('../../types/types.js').Pipe} Pipe
|
|
19
|
+
* @typedef {import('../../types/types.js').TestData} TestData
|
|
20
20
|
*/
|
|
21
21
|
export class BitbucketPipe {
|
|
22
22
|
constructor(params, store = {}) {
|
package/src/pipe/csv.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import createDebugMessages from 'debug';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import fs from 'fs';
|
|
4
|
-
import {createObjectCsvWriter} from 'csv-writer';
|
|
4
|
+
import { createObjectCsvWriter } from 'csv-writer';
|
|
5
5
|
import pc from 'picocolors';
|
|
6
6
|
import merge from 'lodash.merge';
|
|
7
7
|
import { isSameTest, getCurrentDateTime, ansiRegExp } from '../utils/utils.js';
|
|
@@ -9,8 +9,8 @@ import { CSV_HEADERS } from '../constants.js';
|
|
|
9
9
|
|
|
10
10
|
const debug = createDebugMessages('@testomatio/reporter:pipe:csv');
|
|
11
11
|
/**
|
|
12
|
-
* @typedef {import('../../types').Pipe} Pipe
|
|
13
|
-
* @typedef {import('../../types').TestData} TestData
|
|
12
|
+
* @typedef {import('../../types/types.js').Pipe} Pipe
|
|
13
|
+
* @typedef {import('../../types/types.js').TestData} TestData
|
|
14
14
|
* @class CsvPipe
|
|
15
15
|
* @implements {Pipe}
|
|
16
16
|
*/
|