@testomatio/reporter 2.7.1 → 2.7.2-beta.1
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 +2 -1
- package/lib/adapter/codecept.js +81 -26
- package/lib/adapter/playwright.d.ts +1 -1
- package/lib/adapter/playwright.js +54 -34
- package/lib/adapter/utils/step-formatter.d.ts +134 -0
- package/lib/adapter/utils/step-formatter.js +237 -0
- package/lib/bin/cli.js +28 -31
- package/lib/bin/reportXml.js +5 -6
- package/lib/bin/uploadArtifacts.js +6 -6
- package/lib/client.d.ts +8 -0
- package/lib/client.js +71 -10
- package/lib/constants.d.ts +1 -0
- package/lib/constants.js +7 -1
- package/lib/pipe/bitbucket.js +2 -1
- package/lib/pipe/coverage.js +16 -15
- package/lib/pipe/debug.js +3 -3
- package/lib/pipe/github.js +3 -2
- package/lib/pipe/gitlab.js +2 -1
- package/lib/pipe/index.js +5 -5
- package/lib/pipe/testomatio.js +21 -24
- package/lib/uploader.js +3 -2
- package/lib/utils/log.d.ts +45 -0
- package/lib/utils/log.js +98 -0
- package/lib/utils/pipe_utils.js +5 -5
- package/lib/utils/utils.d.ts +10 -0
- package/lib/utils/utils.js +16 -1
- package/lib/xmlReader.js +5 -4
- package/package.json +1 -1
- package/src/adapter/codecept.js +99 -29
- package/src/adapter/playwright.js +64 -39
- package/src/adapter/utils/step-formatter.js +232 -0
- package/src/bin/cli.js +34 -31
- package/src/bin/reportXml.js +5 -6
- package/src/bin/uploadArtifacts.js +6 -6
- package/src/client.js +76 -26
- package/src/constants.js +4 -0
- package/src/pipe/bitbucket.js +2 -1
- package/src/pipe/coverage.js +16 -15
- package/src/pipe/debug.js +3 -3
- package/src/pipe/github.js +4 -3
- package/src/pipe/gitlab.js +2 -1
- package/src/pipe/index.js +5 -7
- package/src/pipe/testomatio.js +32 -25
- package/src/uploader.js +3 -2
- package/src/utils/log.js +87 -0
- package/src/utils/pipe_utils.js +5 -5
- package/src/utils/utils.js +14 -0
- package/src/xmlReader.js +5 -4
- package/types/types.d.ts +3 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateShortFilename = generateShortFilename;
|
|
7
|
+
exports.formatStep = formatStep;
|
|
8
|
+
exports.addStatusToStep = addStatusToStep;
|
|
9
|
+
exports.addArtifactsToStep = addArtifactsToStep;
|
|
10
|
+
exports.addArtifactPathToStep = addArtifactPathToStep;
|
|
11
|
+
const utils_js_1 = require("../../utils/utils.js");
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
14
|
+
const fs_1 = __importDefault(require("fs"));
|
|
15
|
+
/**
|
|
16
|
+
* Generates a short unique filename from screenshot path
|
|
17
|
+
* If original filename is too long, uses hash-based name
|
|
18
|
+
*
|
|
19
|
+
* @param {string} screenshotPath - Path to screenshot file
|
|
20
|
+
* @returns {string} Short filename (max 80 chars)
|
|
21
|
+
*/
|
|
22
|
+
function generateShortFilename(screenshotPath) {
|
|
23
|
+
const originalFilename = path_1.default.basename(screenshotPath);
|
|
24
|
+
const stepPrefix = originalFilename.match(/^(\d{3,4}_)/)?.[1] || '';
|
|
25
|
+
if (originalFilename.length < 40) {
|
|
26
|
+
return originalFilename;
|
|
27
|
+
}
|
|
28
|
+
const ext = path_1.default.extname(screenshotPath);
|
|
29
|
+
const hash = crypto_1.default
|
|
30
|
+
.createHash('sha256')
|
|
31
|
+
.update(screenshotPath)
|
|
32
|
+
.digest('hex')
|
|
33
|
+
.slice(0, 16);
|
|
34
|
+
return `${stepPrefix}screenshot_${hash}${ext}`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Formats a step object according to Testomat.io Step Schema
|
|
38
|
+
*
|
|
39
|
+
* This function transforms a raw step object from test frameworks (CodeceptJS, Playwright, etc.)
|
|
40
|
+
* into a standardized format compatible with Testomat.io API. It ensures all text fields are
|
|
41
|
+
* truncated to 250 characters as defined in testomat-api-definition.yml.
|
|
42
|
+
*
|
|
43
|
+
* Processed fields:
|
|
44
|
+
* - category: step type (framework, user, hook) - defaults to 'user'
|
|
45
|
+
* - title: step name/description, truncated to 250 chars
|
|
46
|
+
* - duration: step execution time in seconds
|
|
47
|
+
* - log: optional log output, truncated to 250 chars
|
|
48
|
+
* - artifacts: optional array of artifact URLs (screenshots), each truncated to 250 chars
|
|
49
|
+
* - error: error details (message + stack) if step failed, each truncated to 250 chars
|
|
50
|
+
* - steps: recursively formats nested steps
|
|
51
|
+
*
|
|
52
|
+
* Schema reference: testomat-api-definition.yml (Step object)
|
|
53
|
+
*
|
|
54
|
+
* @param {Object} step - Raw step object from test framework
|
|
55
|
+
* @param {string} [step.category] - Step category: 'user', 'framework', or 'hook'
|
|
56
|
+
* @param {string} [step.title] - Step title/name
|
|
57
|
+
* @param {number} [step.duration] - Step duration in seconds
|
|
58
|
+
* @param {string} [step.log] - Log output for this step
|
|
59
|
+
* @param {string[]} [step.artifacts] - Array of artifact URLs (screenshots)
|
|
60
|
+
* @param {string|Object} [step.error] - Error details - can be string or object with message/stack
|
|
61
|
+
* @param {Object[]} [step.steps] - Array of nested child steps
|
|
62
|
+
* @returns {Object} Formatted step object matching Testomat.io Step Schema with:
|
|
63
|
+
* category, title, duration, and optional log, artifacts, error, and steps fields
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* const rawStep = {
|
|
67
|
+
* category: 'user',
|
|
68
|
+
* title: 'I click on button',
|
|
69
|
+
* duration: 1.5,
|
|
70
|
+
* error: { message: 'Element not found', stack: 'at test.js:10:5' }
|
|
71
|
+
* };
|
|
72
|
+
* const formatted = formatStep(rawStep);
|
|
73
|
+
* // Returns: { category: 'user', title: 'I click on button', duration: 1.5, error: {...} }
|
|
74
|
+
*/
|
|
75
|
+
function formatStep(step) {
|
|
76
|
+
const formattedStep = {
|
|
77
|
+
category: step.category || 'user',
|
|
78
|
+
title: (0, utils_js_1.truncate)(String(step.title || ''), 250),
|
|
79
|
+
duration: step.duration || 0,
|
|
80
|
+
};
|
|
81
|
+
if (step.log) {
|
|
82
|
+
formattedStep.log = (0, utils_js_1.truncate)(String(step.log), 250);
|
|
83
|
+
}
|
|
84
|
+
if (step.artifacts && Array.isArray(step.artifacts)) {
|
|
85
|
+
formattedStep.artifacts = step.artifacts.map(artifact => (0, utils_js_1.truncate)(String(artifact), 250));
|
|
86
|
+
}
|
|
87
|
+
if (step.error) {
|
|
88
|
+
if (typeof step.error === 'object') {
|
|
89
|
+
formattedStep.error = {
|
|
90
|
+
message: (0, utils_js_1.truncate)(String(step.error.message || 'Step failed'), 250),
|
|
91
|
+
stack: (0, utils_js_1.truncate)(String(step.error.stack || ''), 250),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
formattedStep.error = (0, utils_js_1.truncate)(String(step.error), 250);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (step.steps && Array.isArray(step.steps)) {
|
|
99
|
+
formattedStep.steps = step.steps.map(s => formatStep(s));
|
|
100
|
+
}
|
|
101
|
+
return formattedStep;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Adds status field to step
|
|
105
|
+
*
|
|
106
|
+
* Normalizes step status from test frameworks to Testomat.io standard format.
|
|
107
|
+
* Maps framework-specific statuses ('success', 'failed', 'passed') to Testomat.io
|
|
108
|
+
* standard values ('passed', 'failed').
|
|
109
|
+
*
|
|
110
|
+
* Status mapping:
|
|
111
|
+
* - 'success' → 'passed'
|
|
112
|
+
* - 'passed' → 'passed'
|
|
113
|
+
* - 'failed' → 'failed'
|
|
114
|
+
* - Any other value → 'passed' (default)
|
|
115
|
+
*
|
|
116
|
+
* If step already has a status, it won't be overwritten. If error is provided
|
|
117
|
+
* and step doesn't have status, it will be set to 'failed'.
|
|
118
|
+
*
|
|
119
|
+
* Schema reference: testomat-api-definition.yml (Step.status enum)
|
|
120
|
+
*
|
|
121
|
+
* @param {Object} step - Step object to add status to (modified in place)
|
|
122
|
+
* @param {string} [step.status] - Existing status (won't be overwritten if present)
|
|
123
|
+
* @param {string} status - Status from test framework: 'success', 'failed', or 'passed'
|
|
124
|
+
* @param {Error|Object|null} err - Error object if step failed
|
|
125
|
+
* @returns {Object} The same step object with added status field
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* const step = { title: 'Click button' };
|
|
129
|
+
* addStatusToStep(step, 'success', null);
|
|
130
|
+
* // step.status === 'passed'
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* const step2 = { title: 'Find element' };
|
|
134
|
+
* addStatusToStep(step2, 'failed', new Error('Not found'));
|
|
135
|
+
* // step2.status === 'failed'
|
|
136
|
+
*/
|
|
137
|
+
function addStatusToStep(step, status, err) {
|
|
138
|
+
if (step.status)
|
|
139
|
+
return step;
|
|
140
|
+
const statusMap = {
|
|
141
|
+
'success': 'passed',
|
|
142
|
+
'failed': 'failed',
|
|
143
|
+
'passed': 'passed',
|
|
144
|
+
};
|
|
145
|
+
step.status = statusMap[status] || 'passed';
|
|
146
|
+
if (err && !step.status) {
|
|
147
|
+
step.status = 'failed';
|
|
148
|
+
}
|
|
149
|
+
return step;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Adds screenshot to step as artifacts array
|
|
153
|
+
*
|
|
154
|
+
* Extracts screenshot path from artifacts and adds it to the step's artifacts array.
|
|
155
|
+
* The actual upload will happen in the client's addTestRun method.
|
|
156
|
+
*
|
|
157
|
+
* Artifact format supports:
|
|
158
|
+
* - Array format: [{ screenshot: '/path/to/screenshot.png' }]
|
|
159
|
+
* - Object format: { screenshot: '/path/to/screenshot.png' }
|
|
160
|
+
*
|
|
161
|
+
* Screenshot path can be specified as:
|
|
162
|
+
* - Object with path property: { screenshot: { path: '/path/to/file.png' } }
|
|
163
|
+
* - Object with screenshot property: { screenshot: { screenshot: '/path/to/file.png' } }
|
|
164
|
+
* - Direct string path: { screenshot: '/path/to/file.png' }
|
|
165
|
+
*
|
|
166
|
+
* @param {Object} step - Step object to add artifacts to (modified in place)
|
|
167
|
+
* @param {string[]} [step.artifacts] - Existing artifacts array (won't be overwritten if present)
|
|
168
|
+
* @param {Object|Object[]|null} artifacts - Artifacts from test framework
|
|
169
|
+
* @returns {Object} The same step object with artifacts array added
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* const step = { title: 'Click button' };
|
|
173
|
+
* const artifacts = { screenshot: '/tmp/screenshot.png' };
|
|
174
|
+
* addArtifactsToStep(step, artifacts);
|
|
175
|
+
* // step.artifacts === ['/tmp/screenshot.png']
|
|
176
|
+
*/
|
|
177
|
+
function addArtifactsToStep(step, artifacts) {
|
|
178
|
+
if (!artifacts)
|
|
179
|
+
return step;
|
|
180
|
+
let screenshotPath = null;
|
|
181
|
+
if (Array.isArray(artifacts)) {
|
|
182
|
+
const screenshotArtifact = artifacts.find(a => a.screenshot);
|
|
183
|
+
if (screenshotArtifact && screenshotArtifact.path) {
|
|
184
|
+
screenshotPath = screenshotArtifact.path;
|
|
185
|
+
}
|
|
186
|
+
else if (screenshotArtifact && screenshotArtifact.screenshot) {
|
|
187
|
+
screenshotPath = screenshotArtifact.screenshot;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else if (artifacts.screenshot) {
|
|
191
|
+
screenshotPath = artifacts.screenshot;
|
|
192
|
+
}
|
|
193
|
+
if (screenshotPath && fs_1.default.existsSync(screenshotPath)) {
|
|
194
|
+
const truncatedPath = (0, utils_js_1.truncate)(String(screenshotPath), 250);
|
|
195
|
+
if (step.artifacts && Array.isArray(step.artifacts)) {
|
|
196
|
+
step.artifacts.push(truncatedPath);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
step.artifacts = [truncatedPath];
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return step;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Appends one artifact path to a step.
|
|
206
|
+
*
|
|
207
|
+
* Unlike addArtifactsToStep, this helper accepts a direct path (or URL-like string)
|
|
208
|
+
* and does not check file existence, so callers can attach fallback artifacts
|
|
209
|
+
* collected from logs or async trace outputs.
|
|
210
|
+
*
|
|
211
|
+
* @param {Object} step - Step object to update (modified in place)
|
|
212
|
+
* @param {string} artifactPath - Artifact path to append
|
|
213
|
+
* @returns {Object} The same step object with updated artifacts
|
|
214
|
+
*/
|
|
215
|
+
function addArtifactPathToStep(step, artifactPath) {
|
|
216
|
+
if (!step || !artifactPath)
|
|
217
|
+
return step;
|
|
218
|
+
const truncatedPath = (0, utils_js_1.truncate)(String(artifactPath), 250);
|
|
219
|
+
if (step.artifacts && Array.isArray(step.artifacts)) {
|
|
220
|
+
if (!step.artifacts.includes(truncatedPath))
|
|
221
|
+
step.artifacts.push(truncatedPath);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
step.artifacts = [truncatedPath];
|
|
225
|
+
}
|
|
226
|
+
return step;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
module.exports.generateShortFilename = generateShortFilename;
|
|
230
|
+
|
|
231
|
+
module.exports.formatStep = formatStep;
|
|
232
|
+
|
|
233
|
+
module.exports.addStatusToStep = addStatusToStep;
|
|
234
|
+
|
|
235
|
+
module.exports.addArtifactsToStep = addArtifactsToStep;
|
|
236
|
+
|
|
237
|
+
module.exports.addArtifactPathToStep = addArtifactPathToStep;
|
package/lib/bin/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ const picocolors_1 = __importDefault(require("picocolors"));
|
|
|
18
18
|
const filesize_1 = require("filesize");
|
|
19
19
|
const dotenv_1 = __importDefault(require("dotenv"));
|
|
20
20
|
const replay_js_1 = __importDefault(require("../replay.js"));
|
|
21
|
+
const log_js_1 = require("../utils/log.js");
|
|
21
22
|
const debug = (0, debug_1.default)('@testomatio/reporter:cli');
|
|
22
23
|
const version = (0, utils_js_1.getPackageVersion)();
|
|
23
24
|
console.log(picocolors_1.default.cyan(picocolors_1.default.bold(` 🤩 Testomat.io Reporter v${version}`)));
|
|
@@ -82,7 +83,7 @@ program
|
|
|
82
83
|
const title = process.env.TESTOMATIO_TITLE;
|
|
83
84
|
const client = new client_js_1.default({ apiKey, title });
|
|
84
85
|
if (opts.filter || opts.filterList) {
|
|
85
|
-
|
|
86
|
+
log_js_1.log.info('Filtering tests...');
|
|
86
87
|
// Example of use: npx @testomatio/reporter run "npx jest" --filter "testomatio:tag-name=frontend"
|
|
87
88
|
// Example of use: npx @testomatio/reporter run "npx jest" --filter "coverage:file=coverage.yml"
|
|
88
89
|
// Example of use: npx @testomatio/reporter run "npx jest" --filter-list "coverage:file=coverage.yml"
|
|
@@ -95,16 +96,16 @@ program
|
|
|
95
96
|
try {
|
|
96
97
|
const tests = await client.prepareRun(prepareRunParams);
|
|
97
98
|
if (!tests || tests.length === 0) {
|
|
98
|
-
|
|
99
|
+
log_js_1.log.info(picocolors_1.default.yellow('No tests found.'));
|
|
99
100
|
return;
|
|
100
101
|
}
|
|
101
102
|
const pattern = `(${tests.join('|')})`;
|
|
102
103
|
const filteredCommand = (0, utils_js_1.applyFilter)(command, tests);
|
|
103
104
|
debug(`Execution pattern: "${pattern}"`);
|
|
104
105
|
if (opts.filterList) {
|
|
105
|
-
|
|
106
|
+
log_js_1.log.info(picocolors_1.default.blue(`Matched test/suite IDs: ${tests.join(', ')}`));
|
|
106
107
|
if (command)
|
|
107
|
-
|
|
108
|
+
log_js_1.log.info(picocolors_1.default.green(`Full Running Command: ${filteredCommand}`));
|
|
108
109
|
return;
|
|
109
110
|
}
|
|
110
111
|
if (command && command.split) {
|
|
@@ -112,7 +113,7 @@ program
|
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
catch (err) {
|
|
115
|
-
|
|
116
|
+
log_js_1.log.info(err.message || err);
|
|
116
117
|
return;
|
|
117
118
|
}
|
|
118
119
|
}
|
|
@@ -129,19 +130,19 @@ program
|
|
|
129
130
|
await client.createRun(createRunParams);
|
|
130
131
|
const runId = process.env.TESTOMATIO_RUN || process.env.runId;
|
|
131
132
|
if (client.pipeStore.runUrl)
|
|
132
|
-
|
|
133
|
+
log_js_1.log.info(`📊 Report URL: ${picocolors_1.default.magenta(client.pipeStore.runUrl)}`);
|
|
133
134
|
if (opts.kind !== 'manual') {
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
log_js_1.log.info(`No command passed, so you need to run tests yourself:`);
|
|
136
|
+
log_js_1.log.info(`TESTOMATIO_RUN=${runId} <command>`);
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
139
|
else {
|
|
139
|
-
|
|
140
|
+
log_js_1.log.info('⚠️ No API key provided. Cannot create run without TESTOMATIO key.');
|
|
140
141
|
process.exit(1);
|
|
141
142
|
}
|
|
142
143
|
return process.exit(0);
|
|
143
144
|
}
|
|
144
|
-
|
|
145
|
+
log_js_1.log.info(`🚀 Running`, picocolors_1.default.green(command));
|
|
145
146
|
const runTests = async () => {
|
|
146
147
|
const testCmds = command.split(' ');
|
|
147
148
|
const cmd = (0, cross_spawn_1.spawn)(testCmds[0], testCmds.slice(1), {
|
|
@@ -150,7 +151,7 @@ program
|
|
|
150
151
|
});
|
|
151
152
|
cmd.on('close', async (code) => {
|
|
152
153
|
const emoji = code === 0 ? '🟢' : '🔴';
|
|
153
|
-
|
|
154
|
+
log_js_1.log.info(emoji, `Runner exited with ${picocolors_1.default.bold(code)}`);
|
|
154
155
|
if (apiKey) {
|
|
155
156
|
const status = code === 0 ? 'passed' : 'failed';
|
|
156
157
|
await client.updateRunStatus(status);
|
|
@@ -190,7 +191,7 @@ program
|
|
|
190
191
|
// const runReader = new XmlReader({ javaTests, lang });
|
|
191
192
|
// const files = glob.sync(pattern, { cwd: opts.dir || process.cwd() });
|
|
192
193
|
// if (!files.length) {
|
|
193
|
-
//
|
|
194
|
+
// log.info( `Report can't be created. No XML files found 😥`);
|
|
194
195
|
// process.exit(1);
|
|
195
196
|
// }
|
|
196
197
|
program
|
|
@@ -212,11 +213,11 @@ program
|
|
|
212
213
|
const runReader = new xmlReader_js_1.default({ javaTests, lang });
|
|
213
214
|
const files = glob_1.glob.sync(pattern, { cwd: opts.dir || process.cwd() });
|
|
214
215
|
if (!files.length) {
|
|
215
|
-
|
|
216
|
+
log_js_1.log.info(`Report can't be created. No XML files found 😥`);
|
|
216
217
|
process.exit(1);
|
|
217
218
|
}
|
|
218
219
|
for (const file of files) {
|
|
219
|
-
|
|
220
|
+
log_js_1.log.info(`Parsed ${file}`);
|
|
220
221
|
runReader.parse(file);
|
|
221
222
|
}
|
|
222
223
|
let timeoutTimer;
|
|
@@ -231,7 +232,7 @@ program
|
|
|
231
232
|
await runReader.uploadData();
|
|
232
233
|
}
|
|
233
234
|
catch (err) {
|
|
234
|
-
|
|
235
|
+
log_js_1.log.info('Error updating status, skipping...', err);
|
|
235
236
|
}
|
|
236
237
|
if (timeoutTimer)
|
|
237
238
|
clearTimeout(timeoutTimer);
|
|
@@ -259,10 +260,10 @@ program
|
|
|
259
260
|
if (!opts.force)
|
|
260
261
|
testruns = testruns.filter(tr => !tr.uploaded);
|
|
261
262
|
if (!testruns.length) {
|
|
262
|
-
|
|
263
|
+
log_js_1.log.info('🗄️ Total artifacts:', numTotalArtifacts);
|
|
263
264
|
if (numTotalArtifacts) {
|
|
264
|
-
|
|
265
|
-
|
|
265
|
+
log_js_1.log.info('No new artifacts to upload');
|
|
266
|
+
log_js_1.log.info('To re-upload artifacts run this command with --force flag');
|
|
266
267
|
}
|
|
267
268
|
process.exit(0);
|
|
268
269
|
}
|
|
@@ -281,7 +282,7 @@ program
|
|
|
281
282
|
const files = testrunsByRid[rid];
|
|
282
283
|
await client.addTestRun(undefined, { rid, files });
|
|
283
284
|
}
|
|
284
|
-
|
|
285
|
+
log_js_1.log.info('🗄️', client.uploader.successfulUploads.length, 'artifacts 🟢uploaded');
|
|
285
286
|
if (client.uploader.successfulUploads.length) {
|
|
286
287
|
debug('\n', constants_js_1.APP_PREFIX, `🗄️ ${client.uploader.successfulUploads.length} artifacts uploaded to S3 bucket`);
|
|
287
288
|
const uploadedArtifacts = client.uploader.successfulUploads.map(file => ({
|
|
@@ -316,34 +317,30 @@ program
|
|
|
316
317
|
const replayService = new replay_js_1.default({
|
|
317
318
|
apiKey: config_js_1.config.TESTOMATIO,
|
|
318
319
|
dryRun: opts.dryRun,
|
|
319
|
-
onLog: message =>
|
|
320
|
-
onError: message =>
|
|
320
|
+
onLog: message => log_js_1.log.info(message),
|
|
321
|
+
onError: message => log_js_1.log.error('⚠️ ', message),
|
|
321
322
|
onProgress: ({ current, total }) => {
|
|
322
323
|
if (current % 10 === 0 || current === total) {
|
|
323
|
-
|
|
324
|
+
log_js_1.log.info(`📊 Progress: ${current}/${total} tests processed`);
|
|
324
325
|
}
|
|
325
326
|
},
|
|
326
327
|
});
|
|
327
328
|
const result = await replayService.replay(debugFile);
|
|
328
329
|
if (result.dryRun) {
|
|
329
|
-
|
|
330
|
-
console.log(constants_js_1.APP_PREFIX, ` - Tests found: ${result.testsCount}`);
|
|
331
|
-
console.log(constants_js_1.APP_PREFIX, ` - Environment variables: ${Object.keys(result.envVars).length}`);
|
|
332
|
-
console.log(constants_js_1.APP_PREFIX, ` - Run parameters:`, result.runParams);
|
|
333
|
-
console.log(constants_js_1.APP_PREFIX, ' Use without --dry-run to actually send the data');
|
|
330
|
+
log_js_1.log.info('🔍 Dry run completed:\n', ` - Tests found: ${result.testsCount}\n`, ` - Environment variables: ${Object.keys(result.envVars).length}\n`, ' - Run parameters:', result.runParams, '\n', ' Use without --dry-run to actually send the data');
|
|
334
331
|
}
|
|
335
332
|
else {
|
|
336
|
-
|
|
333
|
+
log_js_1.log.info(`✅ Successfully replayed ${result.successCount}/${result.testsCount} tests`);
|
|
337
334
|
if (result.failureCount > 0) {
|
|
338
|
-
|
|
335
|
+
log_js_1.log.info(`⚠️ ${result.failureCount} tests failed to upload`);
|
|
339
336
|
}
|
|
340
337
|
}
|
|
341
338
|
process.exit(0);
|
|
342
339
|
}
|
|
343
340
|
catch (err) {
|
|
344
|
-
|
|
341
|
+
log_js_1.log.error('❌ Error replaying debug data:', err.message);
|
|
345
342
|
if (err.message.includes('Debug file not found')) {
|
|
346
|
-
|
|
343
|
+
log_js_1.log.error('💡 Hint: Run tests with TESTOMATIO_DEBUG=1 to generate debug files');
|
|
347
344
|
}
|
|
348
345
|
process.exit(1);
|
|
349
346
|
}
|
package/lib/bin/reportXml.js
CHANGED
|
@@ -8,11 +8,10 @@ const commander_1 = require("commander");
|
|
|
8
8
|
const picocolors_1 = __importDefault(require("picocolors"));
|
|
9
9
|
const glob_1 = require("glob");
|
|
10
10
|
const debug_1 = __importDefault(require("debug"));
|
|
11
|
-
const constants_js_1 = require("../constants.js");
|
|
12
11
|
const xmlReader_js_1 = __importDefault(require("../xmlReader.js"));
|
|
13
12
|
const utils_js_1 = require("../utils/utils.js");
|
|
14
13
|
const dotenv_1 = __importDefault(require("dotenv"));
|
|
15
|
-
const
|
|
14
|
+
const log_js_1 = require("../utils/log.js");
|
|
16
15
|
const version = (0, utils_js_1.getPackageVersion)();
|
|
17
16
|
const debug = (0, debug_1.default)('@testomatio/reporter:xml-cli');
|
|
18
17
|
console.log(picocolors_1.default.cyan(picocolors_1.default.bold(` 🤩 Testomat.io XML Reporter v${version}`)));
|
|
@@ -30,7 +29,7 @@ program
|
|
|
30
29
|
}
|
|
31
30
|
let { javaTests, lang } = opts;
|
|
32
31
|
if (opts.envFile) {
|
|
33
|
-
|
|
32
|
+
log_js_1.log.info('Loading env file:', opts.envFile);
|
|
34
33
|
debug('Loading env file: %s', opts.envFile);
|
|
35
34
|
dotenv_1.default.config({ path: opts.envFile });
|
|
36
35
|
}
|
|
@@ -43,12 +42,12 @@ program
|
|
|
43
42
|
});
|
|
44
43
|
const files = glob_1.glob.sync(pattern, { cwd: opts.dir || process.cwd() });
|
|
45
44
|
if (!files.length) {
|
|
46
|
-
|
|
45
|
+
log_js_1.log.info(`Report can't be created. No XML files found 😥`);
|
|
47
46
|
process.exitCode = 1;
|
|
48
47
|
return;
|
|
49
48
|
}
|
|
50
49
|
for (const file of files) {
|
|
51
|
-
|
|
50
|
+
log_js_1.log.info(`Parsed ${file}`);
|
|
52
51
|
runReader.parse(file);
|
|
53
52
|
}
|
|
54
53
|
let timeoutTimer;
|
|
@@ -63,7 +62,7 @@ program
|
|
|
63
62
|
await runReader.uploadData();
|
|
64
63
|
}
|
|
65
64
|
catch (err) {
|
|
66
|
-
|
|
65
|
+
log_js_1.log.info('Error updating status, skipping...', err);
|
|
67
66
|
}
|
|
68
67
|
if (timeoutTimer)
|
|
69
68
|
clearTimeout(timeoutTimer);
|
|
@@ -8,11 +8,11 @@ const commander_1 = require("commander");
|
|
|
8
8
|
const picocolors_1 = __importDefault(require("picocolors"));
|
|
9
9
|
const debug_1 = __importDefault(require("debug"));
|
|
10
10
|
const client_js_1 = __importDefault(require("../client.js"));
|
|
11
|
-
const constants_js_1 = require("../constants.js");
|
|
12
11
|
const utils_js_1 = require("../utils/utils.js");
|
|
13
12
|
const config_js_1 = require("../config.js");
|
|
14
13
|
const utils_js_2 = require("../utils/utils.js");
|
|
15
14
|
const dotenv_1 = __importDefault(require("dotenv"));
|
|
15
|
+
const log_js_1 = require("../utils/log.js");
|
|
16
16
|
const debug = (0, debug_1.default)('@testomatio/reporter:upload-cli');
|
|
17
17
|
const version = (0, utils_js_1.getPackageVersion)();
|
|
18
18
|
console.log(picocolors_1.default.cyan(picocolors_1.default.bold(` 🤩 Testomat.io Reporter v${version}`)));
|
|
@@ -45,10 +45,10 @@ program
|
|
|
45
45
|
if (!opts.force)
|
|
46
46
|
testruns = testruns.filter(tr => !tr.uploaded);
|
|
47
47
|
if (!testruns.length) {
|
|
48
|
-
|
|
48
|
+
log_js_1.log.info('Total artifacts:', numTotalArtifacts);
|
|
49
49
|
if (numTotalArtifacts) {
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
log_js_1.log.info('No new artifacts to upload');
|
|
51
|
+
log_js_1.log.info('To re-upload artifacts run this command with --force flag');
|
|
52
52
|
}
|
|
53
53
|
process.exit(0);
|
|
54
54
|
}
|
|
@@ -71,9 +71,9 @@ program
|
|
|
71
71
|
files,
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
|
-
|
|
74
|
+
log_js_1.log.info(client.uploader.successfulUploads.length, 'artifacts uploaded');
|
|
75
75
|
if (client.uploader.failedUploads.length) {
|
|
76
|
-
|
|
76
|
+
log_js_1.log.info(client.uploader.failedUploads.length, 'artifacts failed to upload');
|
|
77
77
|
}
|
|
78
78
|
});
|
|
79
79
|
if (process.argv.length <= 1) {
|
package/lib/client.d.ts
CHANGED
|
@@ -45,6 +45,14 @@ export class Client {
|
|
|
45
45
|
* @returns {Promise<any>} - resolves to Run id which should be used to update / add test
|
|
46
46
|
*/
|
|
47
47
|
createRun(params?: {}): Promise<any>;
|
|
48
|
+
/**
|
|
49
|
+
* Recursively uploads artifacts from steps
|
|
50
|
+
*
|
|
51
|
+
* @param {*} steps - Steps payload (validated inside function)
|
|
52
|
+
* @param {string} testRid - Test/result ID
|
|
53
|
+
* @returns {Promise<void>}
|
|
54
|
+
*/
|
|
55
|
+
uploadStepArtifacts(steps: any, testRid: string): Promise<void>;
|
|
48
56
|
/**
|
|
49
57
|
* Updates test status and its data
|
|
50
58
|
*
|