@testomatio/reporter 2.8.3 â 2.8.5-beta.1-remote
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/bin/cli.js +50 -4
- package/lib/client.js +2 -0
- package/lib/pipe/coverage.js +8 -8
- package/lib/pipe/testomatio.d.ts +2 -0
- package/lib/pipe/testomatio.js +40 -0
- package/package.json +1 -1
- package/src/bin/cli.js +54 -4
- package/src/client.js +3 -0
- package/src/pipe/coverage.js +8 -8
- package/src/pipe/testomatio.js +39 -0
- package/types/types.d.ts +23 -1
package/lib/bin/cli.js
CHANGED
|
@@ -35,10 +35,13 @@ program
|
|
|
35
35
|
dotenv_1.default.config();
|
|
36
36
|
}
|
|
37
37
|
// --filter-list produces a machine-readable test list on stdout, so route
|
|
38
|
-
//
|
|
38
|
+
// remaining output to stderr, skip the banner, and silence info-level
|
|
39
|
+
// logs so the terminal isn't flooded with progress noise.
|
|
40
|
+
// Set TESTOMATIO_LOG_LEVEL=INFO to re-enable progress logs for debugging.
|
|
39
41
|
const subOpts = actionCommand.opts();
|
|
40
42
|
if (subOpts.filterList || subOpts.format) {
|
|
41
43
|
process.env.TESTOMATIO_LOG_STDERR = '1';
|
|
44
|
+
process.env.TESTOMATIO_LOG_LEVEL ||= 'WARN';
|
|
42
45
|
}
|
|
43
46
|
else {
|
|
44
47
|
console.log(picocolors_1.default.cyan(picocolors_1.default.bold(` 𤊠Testomat.io Reporter v${version}`)));
|
|
@@ -88,7 +91,19 @@ program
|
|
|
88
91
|
.option('--filter-list <filter>', 'Get a list of all tests by filter before running')
|
|
89
92
|
.option('--format <format>', 'Machine-readable output format for --filter-list (grep, json, newline, ids)')
|
|
90
93
|
.option('--kind <type>', 'Specify run type: automated, manual, or mixed')
|
|
94
|
+
.option('--remote <profile>', 'Trigger run on the named Testomat.io CI profile instead of executing locally')
|
|
95
|
+
.option('--remote-param <kv>', 'key=value pair forwarded to the CI profile config (repeat for multiple)', (value, prev) => prev.concat([value]), [])
|
|
91
96
|
.action(async (command, opts) => {
|
|
97
|
+
if (opts.remote) {
|
|
98
|
+
if (opts.filterList) {
|
|
99
|
+
log_js_1.log.warn(picocolors_1.default.red('â ī¸ --filter-list cannot be combined with --remote'));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
process.env.TESTOMATIO_CI_PROFILE = opts.remote;
|
|
103
|
+
if (opts.remoteParam?.length) {
|
|
104
|
+
process.env.TESTOMATIO_CI_PARAMS = opts.remoteParam.join(',');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
92
107
|
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config_js_1.config.TESTOMATIO;
|
|
93
108
|
const title = process.env.TESTOMATIO_TITLE;
|
|
94
109
|
const client = new client_js_1.default({ apiKey, title });
|
|
@@ -106,7 +121,11 @@ program
|
|
|
106
121
|
try {
|
|
107
122
|
const tests = await client.prepareRun(prepareRunParams);
|
|
108
123
|
if (!tests || tests.length === 0) {
|
|
109
|
-
log_js_1.log.
|
|
124
|
+
log_js_1.log.warn(picocolors_1.default.yellow('No tests found.'));
|
|
125
|
+
// Exit non-zero on --filter-list so scripts can detect "nothing to run"
|
|
126
|
+
// via $? and skip launching the runner.
|
|
127
|
+
if (opts.filterList)
|
|
128
|
+
process.exit(1);
|
|
110
129
|
return;
|
|
111
130
|
}
|
|
112
131
|
if (opts.filterList) {
|
|
@@ -120,15 +139,42 @@ program
|
|
|
120
139
|
}
|
|
121
140
|
return;
|
|
122
141
|
}
|
|
123
|
-
if (command && command.split) {
|
|
142
|
+
if (command && command.split && !opts.remote) {
|
|
124
143
|
command = (0, utils_js_1.applyFilter)(command, tests);
|
|
125
144
|
}
|
|
126
145
|
}
|
|
127
146
|
catch (err) {
|
|
128
|
-
log_js_1.log.
|
|
147
|
+
log_js_1.log.error(err.message || err);
|
|
148
|
+
if (opts.filterList)
|
|
149
|
+
process.exit(1);
|
|
129
150
|
return;
|
|
130
151
|
}
|
|
131
152
|
}
|
|
153
|
+
if (opts.remote) {
|
|
154
|
+
if (!apiKey) {
|
|
155
|
+
log_js_1.log.warn(picocolors_1.default.red('â ī¸ TESTOMATIO API key required for --remote'));
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
if (command) {
|
|
159
|
+
log_js_1.log.warn(picocolors_1.default.yellow('Note: positional command is ignored when --remote is set; CI runs the workflow.'));
|
|
160
|
+
}
|
|
161
|
+
const createRunParams = {};
|
|
162
|
+
if (title)
|
|
163
|
+
createRunParams.title = title;
|
|
164
|
+
if (opts.kind)
|
|
165
|
+
createRunParams.kind = opts.kind;
|
|
166
|
+
try {
|
|
167
|
+
await client.createRun(createRunParams);
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
log_js_1.log.warn(picocolors_1.default.red(`CI launch failed: ${err.message || err}`));
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
log_js_1.log.info(`đ CI build triggered on profile ${picocolors_1.default.cyan(opts.remote)}`);
|
|
174
|
+
if (client.pipeStore.runUrl)
|
|
175
|
+
log_js_1.log.info(`đ Report URL: ${picocolors_1.default.magenta(client.pipeStore.runUrl)}`);
|
|
176
|
+
return process.exit(0);
|
|
177
|
+
}
|
|
132
178
|
// just create a run (wich tests which match filters) without executing tests
|
|
133
179
|
if (!command || !command.split) {
|
|
134
180
|
const createRunParams = {};
|
package/lib/client.js
CHANGED
|
@@ -92,6 +92,8 @@ class Client {
|
|
|
92
92
|
// Run only the selected pipe
|
|
93
93
|
const rawResult = await p.prepareRun(pipeOptions);
|
|
94
94
|
const result = Array.isArray(rawResult) ? rawResult : [];
|
|
95
|
+
// Expose resolved test ids to other pipes (e.g. TestomatioPipe builds `ci.grep` from this).
|
|
96
|
+
this.pipeStore.preparedTestIds = result;
|
|
95
97
|
debug('Execution tests list', result);
|
|
96
98
|
return result;
|
|
97
99
|
}
|
package/lib/pipe/coverage.js
CHANGED
|
@@ -125,7 +125,7 @@ class CoveragePipe {
|
|
|
125
125
|
log_js_1.log.info(`Matched files: ${[...lines].join(', ')}`);
|
|
126
126
|
}
|
|
127
127
|
if (lines.size === 0) {
|
|
128
|
-
log_js_1.log.
|
|
128
|
+
log_js_1.log.warn('âšī¸ No matching entries in coverage file for provided Git changes.');
|
|
129
129
|
return [];
|
|
130
130
|
}
|
|
131
131
|
// Step 3: Handle tag labels tests from the server
|
|
@@ -142,7 +142,7 @@ class CoveragePipe {
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
if (this.tests.size === 0 && this.suiteIds.size === 0) {
|
|
145
|
-
log_js_1.log.
|
|
145
|
+
log_js_1.log.warn('âšī¸ No tests found for execution based on Git changes.');
|
|
146
146
|
return [];
|
|
147
147
|
}
|
|
148
148
|
this.results = [...this.tests, ...this.suiteIds];
|
|
@@ -201,7 +201,7 @@ class CoveragePipe {
|
|
|
201
201
|
...q,
|
|
202
202
|
});
|
|
203
203
|
if (!Array.isArray(resp.data?.tests) && resp.data?.tests?.length === 0) {
|
|
204
|
-
log_js_1.log.
|
|
204
|
+
log_js_1.log.warn(`đ No test by ${type}=${id} were found on the Testomat.io server side!`);
|
|
205
205
|
return undefined;
|
|
206
206
|
}
|
|
207
207
|
return resp.data.tests;
|
|
@@ -276,7 +276,7 @@ class CoveragePipe {
|
|
|
276
276
|
log_js_1.log.error(err.message);
|
|
277
277
|
return undefined;
|
|
278
278
|
}
|
|
279
|
-
log_js_1.log.
|
|
279
|
+
log_js_1.log.info(`We will use '${cmd}' Git command.`);
|
|
280
280
|
try {
|
|
281
281
|
// For clear unit testing process -> Like test_defaultGitChangedFile = todomvc-tests/edit-todos_test.js
|
|
282
282
|
if (this.isDefaultGitChanges) {
|
|
@@ -285,7 +285,7 @@ class CoveragePipe {
|
|
|
285
285
|
else {
|
|
286
286
|
this.changedFiles = this.#getChangedFilesFromGit(cmd);
|
|
287
287
|
if (this.changedFiles.length === 0) {
|
|
288
|
-
log_js_1.log.
|
|
288
|
+
log_js_1.log.warn('âšī¸ No files changed in the latest Git commit. Skipping coverage processing.');
|
|
289
289
|
return undefined;
|
|
290
290
|
}
|
|
291
291
|
}
|
|
@@ -314,18 +314,18 @@ class CoveragePipe {
|
|
|
314
314
|
validateCoverageFile() {
|
|
315
315
|
// Validate the presence of the coverage filepath
|
|
316
316
|
if (!fs_1.default.existsSync(this.coverageFilePath)) {
|
|
317
|
-
log_js_1.log.
|
|
317
|
+
log_js_1.log.error('â Coverage file not found:', this.coverageFilePath);
|
|
318
318
|
return undefined;
|
|
319
319
|
}
|
|
320
320
|
// Ensure the given path is a file (not a directory or other type)
|
|
321
321
|
const stat = fs_1.default.statSync(this.coverageFilePath);
|
|
322
322
|
if (!stat.isFile()) {
|
|
323
|
-
log_js_1.log.
|
|
323
|
+
log_js_1.log.error('â Provided coverage path is not a file:', this.coverageFilePath);
|
|
324
324
|
return undefined;
|
|
325
325
|
}
|
|
326
326
|
// Validate the file extension to be ".yml" to ensure it's a YAML file
|
|
327
327
|
if (path_1.default.extname(this.coverageFilePath) !== ".yml") {
|
|
328
|
-
log_js_1.log.
|
|
328
|
+
log_js_1.log.error('â Coverage file must have a .yml extension:', this.coverageFilePath);
|
|
329
329
|
return undefined;
|
|
330
330
|
}
|
|
331
331
|
debug('Coverage file validation is OK!');
|
package/lib/pipe/testomatio.d.ts
CHANGED
package/lib/pipe/testomatio.js
CHANGED
|
@@ -15,6 +15,26 @@ const log_js_1 = require("../utils/log.js");
|
|
|
15
15
|
const debug = (0, debug_1.default)('@testomatio/reporter:pipe:testomatio');
|
|
16
16
|
if (process.env.TESTOMATIO_RUN)
|
|
17
17
|
process.env.runId = process.env.TESTOMATIO_RUN;
|
|
18
|
+
/**
|
|
19
|
+
* Parse `TESTOMATIO_CI_PARAMS` (comma-separated `key=value` pairs) into an object.
|
|
20
|
+
* Entries without `=` or with empty keys are skipped. Returns undefined when input is empty.
|
|
21
|
+
*
|
|
22
|
+
* @param {string|undefined} raw
|
|
23
|
+
* @returns {Record<string, string>|undefined}
|
|
24
|
+
*/
|
|
25
|
+
function parseCiParams(raw) {
|
|
26
|
+
if (!raw)
|
|
27
|
+
return undefined;
|
|
28
|
+
/** @type {Record<string, string>} */
|
|
29
|
+
const result = {};
|
|
30
|
+
for (const entry of raw.split(',')) {
|
|
31
|
+
const idx = entry.indexOf('=');
|
|
32
|
+
if (idx <= 0)
|
|
33
|
+
continue;
|
|
34
|
+
result[entry.slice(0, idx).trim()] = entry.slice(idx + 1);
|
|
35
|
+
}
|
|
36
|
+
return Object.keys(result).length ? result : undefined;
|
|
37
|
+
}
|
|
18
38
|
/**
|
|
19
39
|
* @typedef {import('../../types/types.js').Pipe} Pipe
|
|
20
40
|
* @typedef {import('../../types/types.js').TestData} TestData
|
|
@@ -68,6 +88,12 @@ class TestomatioPipe {
|
|
|
68
88
|
this.env = process.env.TESTOMATIO_ENV;
|
|
69
89
|
this.label = process.env.TESTOMATIO_LABEL;
|
|
70
90
|
this.description = params.description || process.env.TESTOMATIO_DESCRIPTION;
|
|
91
|
+
// Remote CI launch â when `TESTOMATIO_CI_PROFILE` is set the run will be created
|
|
92
|
+
// on the server *and* the named CI profile will be dispatched. Optional
|
|
93
|
+
// `TESTOMATIO_CI_PARAMS` is a comma-separated list of `key=value` pairs
|
|
94
|
+
// forwarded to the CI profile config (e.g. `branch=develop,REGION=eu`).
|
|
95
|
+
this.ciProfile = process.env.TESTOMATIO_CI_PROFILE;
|
|
96
|
+
this.ciParams = parseCiParams(process.env.TESTOMATIO_CI_PARAMS);
|
|
71
97
|
// Create a new instance of gaxios with a custom config
|
|
72
98
|
this.client = new gaxios_1.Gaxios({
|
|
73
99
|
baseURL: `${this.url.trim()}`,
|
|
@@ -213,6 +239,19 @@ class TestomatioPipe {
|
|
|
213
239
|
this.store.configuration = { ...(this.store.configuration || {}), ...params.configuration };
|
|
214
240
|
}
|
|
215
241
|
}
|
|
242
|
+
// Assemble the `ci` block when the user asked for a remote CI launch via
|
|
243
|
+
// TESTOMATIO_CI_PROFILE (e.g. `--remote github`). Grep is taken from whatever
|
|
244
|
+
// `--filter` resolution already stashed in the shared pipeStore.
|
|
245
|
+
/** @type {{profile: string, grep?: string, override?: Record<string, any>}|null} */
|
|
246
|
+
let ci = null;
|
|
247
|
+
if (this.ciProfile) {
|
|
248
|
+
ci = { profile: this.ciProfile };
|
|
249
|
+
const grepIds = this.store?.preparedTestIds;
|
|
250
|
+
if (grepIds?.length)
|
|
251
|
+
ci.grep = grepIds.join('|');
|
|
252
|
+
if (this.ciParams)
|
|
253
|
+
ci.override = this.ciParams;
|
|
254
|
+
}
|
|
216
255
|
const runParams = Object.fromEntries(Object.entries({
|
|
217
256
|
ci_build_url: buildUrl,
|
|
218
257
|
api_key: this.apiKey.trim(),
|
|
@@ -227,6 +266,7 @@ class TestomatioPipe {
|
|
|
227
266
|
kind: params.kind,
|
|
228
267
|
configuration,
|
|
229
268
|
description,
|
|
269
|
+
ci,
|
|
230
270
|
}).filter(([, value]) => !!value));
|
|
231
271
|
debug(' >>>>>> Run params', JSON.stringify(runParams, null, 2));
|
|
232
272
|
if (this.runId) {
|
package/package.json
CHANGED
package/src/bin/cli.js
CHANGED
|
@@ -33,10 +33,13 @@ program
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// --filter-list produces a machine-readable test list on stdout, so route
|
|
36
|
-
//
|
|
36
|
+
// remaining output to stderr, skip the banner, and silence info-level
|
|
37
|
+
// logs so the terminal isn't flooded with progress noise.
|
|
38
|
+
// Set TESTOMATIO_LOG_LEVEL=INFO to re-enable progress logs for debugging.
|
|
37
39
|
const subOpts = actionCommand.opts();
|
|
38
40
|
if (subOpts.filterList || subOpts.format) {
|
|
39
41
|
process.env.TESTOMATIO_LOG_STDERR = '1';
|
|
42
|
+
process.env.TESTOMATIO_LOG_LEVEL ||= 'WARN';
|
|
40
43
|
} else {
|
|
41
44
|
console.log(pc.cyan(pc.bold(` 𤊠Testomat.io Reporter v${version}`)));
|
|
42
45
|
}
|
|
@@ -94,7 +97,25 @@ program
|
|
|
94
97
|
.option('--filter-list <filter>', 'Get a list of all tests by filter before running')
|
|
95
98
|
.option('--format <format>', 'Machine-readable output format for --filter-list (grep, json, newline, ids)')
|
|
96
99
|
.option('--kind <type>', 'Specify run type: automated, manual, or mixed')
|
|
100
|
+
.option('--remote <profile>', 'Trigger run on the named Testomat.io CI profile instead of executing locally')
|
|
101
|
+
.option(
|
|
102
|
+
'--remote-param <kv>',
|
|
103
|
+
'key=value pair forwarded to the CI profile config (repeat for multiple)',
|
|
104
|
+
(value, prev) => prev.concat([value]),
|
|
105
|
+
[],
|
|
106
|
+
)
|
|
97
107
|
.action(async (command, opts) => {
|
|
108
|
+
if (opts.remote) {
|
|
109
|
+
if (opts.filterList) {
|
|
110
|
+
log.warn(pc.red('â ī¸ --filter-list cannot be combined with --remote'));
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
process.env.TESTOMATIO_CI_PROFILE = opts.remote;
|
|
114
|
+
if (opts.remoteParam?.length) {
|
|
115
|
+
process.env.TESTOMATIO_CI_PARAMS = opts.remoteParam.join(',');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
98
119
|
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
|
|
99
120
|
const title = process.env.TESTOMATIO_TITLE;
|
|
100
121
|
const client = new TestomatClient({ apiKey, title });
|
|
@@ -116,7 +137,10 @@ program
|
|
|
116
137
|
const tests = await client.prepareRun(prepareRunParams);
|
|
117
138
|
|
|
118
139
|
if (!tests || tests.length === 0) {
|
|
119
|
-
log.
|
|
140
|
+
log.warn( pc.yellow('No tests found.'));
|
|
141
|
+
// Exit non-zero on --filter-list so scripts can detect "nothing to run"
|
|
142
|
+
// via $? and skip launching the runner.
|
|
143
|
+
if (opts.filterList) process.exit(1);
|
|
120
144
|
return;
|
|
121
145
|
}
|
|
122
146
|
|
|
@@ -131,16 +155,42 @@ program
|
|
|
131
155
|
return;
|
|
132
156
|
}
|
|
133
157
|
|
|
134
|
-
if (command && command.split) {
|
|
158
|
+
if (command && command.split && !opts.remote) {
|
|
135
159
|
command = applyFilter(command, tests);
|
|
136
160
|
}
|
|
137
161
|
}
|
|
138
162
|
catch (err) {
|
|
139
|
-
log.
|
|
163
|
+
log.error( err.message || err);
|
|
164
|
+
if (opts.filterList) process.exit(1);
|
|
140
165
|
return;
|
|
141
166
|
}
|
|
142
167
|
}
|
|
143
168
|
|
|
169
|
+
if (opts.remote) {
|
|
170
|
+
if (!apiKey) {
|
|
171
|
+
log.warn(pc.red('â ī¸ TESTOMATIO API key required for --remote'));
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
if (command) {
|
|
175
|
+
log.warn(pc.yellow('Note: positional command is ignored when --remote is set; CI runs the workflow.'));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const createRunParams = {};
|
|
179
|
+
if (title) createRunParams.title = title;
|
|
180
|
+
if (opts.kind) createRunParams.kind = opts.kind;
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
await client.createRun(createRunParams);
|
|
184
|
+
} catch (err) {
|
|
185
|
+
log.warn(pc.red(`CI launch failed: ${err.message || err}`));
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
log.info(`đ CI build triggered on profile ${pc.cyan(opts.remote)}`);
|
|
190
|
+
if (client.pipeStore.runUrl) log.info(`đ Report URL: ${pc.magenta(client.pipeStore.runUrl)}`);
|
|
191
|
+
return process.exit(0);
|
|
192
|
+
}
|
|
193
|
+
|
|
144
194
|
// just create a run (wich tests which match filters) without executing tests
|
|
145
195
|
if (!command || !command.split) {
|
|
146
196
|
const createRunParams = {};
|
package/src/client.js
CHANGED
|
@@ -102,6 +102,9 @@ class Client {
|
|
|
102
102
|
const rawResult = await p.prepareRun(pipeOptions);
|
|
103
103
|
const result = Array.isArray(rawResult) ? rawResult : [];
|
|
104
104
|
|
|
105
|
+
// Expose resolved test ids to other pipes (e.g. TestomatioPipe builds `ci.grep` from this).
|
|
106
|
+
this.pipeStore.preparedTestIds = result;
|
|
107
|
+
|
|
105
108
|
debug('Execution tests list', result);
|
|
106
109
|
|
|
107
110
|
return result;
|
package/src/pipe/coverage.js
CHANGED
|
@@ -140,7 +140,7 @@ class CoveragePipe { // or Changes for the future???
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
if (lines.size === 0) {
|
|
143
|
-
log.
|
|
143
|
+
log.warn( 'âšī¸ No matching entries in coverage file for provided Git changes.');
|
|
144
144
|
return [];
|
|
145
145
|
}
|
|
146
146
|
|
|
@@ -163,7 +163,7 @@ class CoveragePipe { // or Changes for the future???
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
if (this.tests.size === 0 && this.suiteIds.size === 0) {
|
|
166
|
-
log.
|
|
166
|
+
log.warn( 'âšī¸ No tests found for execution based on Git changes.');
|
|
167
167
|
return [];
|
|
168
168
|
}
|
|
169
169
|
|
|
@@ -234,7 +234,7 @@ class CoveragePipe { // or Changes for the future???
|
|
|
234
234
|
});
|
|
235
235
|
|
|
236
236
|
if (!Array.isArray(resp.data?.tests) && resp.data?.tests?.length === 0) {
|
|
237
|
-
log.
|
|
237
|
+
log.warn( `đ No test by ${type}=${id} were found on the Testomat.io server side!`);
|
|
238
238
|
|
|
239
239
|
return undefined;
|
|
240
240
|
}
|
|
@@ -322,7 +322,7 @@ class CoveragePipe { // or Changes for the future???
|
|
|
322
322
|
return undefined;
|
|
323
323
|
}
|
|
324
324
|
|
|
325
|
-
log.
|
|
325
|
+
log.info( `We will use '${cmd}' Git command.`);
|
|
326
326
|
|
|
327
327
|
try {
|
|
328
328
|
// For clear unit testing process -> Like test_defaultGitChangedFile = todomvc-tests/edit-todos_test.js
|
|
@@ -333,7 +333,7 @@ class CoveragePipe { // or Changes for the future???
|
|
|
333
333
|
this.changedFiles = this.#getChangedFilesFromGit(cmd);
|
|
334
334
|
|
|
335
335
|
if (this.changedFiles.length === 0) {
|
|
336
|
-
log.
|
|
336
|
+
log.warn('âšī¸ No files changed in the latest Git commit. Skipping coverage processing.');
|
|
337
337
|
|
|
338
338
|
return undefined;
|
|
339
339
|
}
|
|
@@ -365,20 +365,20 @@ class CoveragePipe { // or Changes for the future???
|
|
|
365
365
|
validateCoverageFile() {
|
|
366
366
|
// Validate the presence of the coverage filepath
|
|
367
367
|
if (!fs.existsSync(this.coverageFilePath)) {
|
|
368
|
-
log.
|
|
368
|
+
log.error( 'â Coverage file not found:', this.coverageFilePath);
|
|
369
369
|
return undefined;
|
|
370
370
|
}
|
|
371
371
|
|
|
372
372
|
// Ensure the given path is a file (not a directory or other type)
|
|
373
373
|
const stat = fs.statSync(this.coverageFilePath);
|
|
374
374
|
if (!stat.isFile()) {
|
|
375
|
-
log.
|
|
375
|
+
log.error( 'â Provided coverage path is not a file:', this.coverageFilePath);
|
|
376
376
|
return undefined;
|
|
377
377
|
}
|
|
378
378
|
|
|
379
379
|
// Validate the file extension to be ".yml" to ensure it's a YAML file
|
|
380
380
|
if (path.extname(this.coverageFilePath) !== ".yml") {
|
|
381
|
-
log.
|
|
381
|
+
log.error( 'â Coverage file must have a .yml extension:', this.coverageFilePath);
|
|
382
382
|
return undefined;
|
|
383
383
|
}
|
|
384
384
|
|
package/src/pipe/testomatio.js
CHANGED
|
@@ -24,6 +24,25 @@ const debug = createDebugMessages('@testomatio/reporter:pipe:testomatio');
|
|
|
24
24
|
|
|
25
25
|
if (process.env.TESTOMATIO_RUN) process.env.runId = process.env.TESTOMATIO_RUN;
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Parse `TESTOMATIO_CI_PARAMS` (comma-separated `key=value` pairs) into an object.
|
|
29
|
+
* Entries without `=` or with empty keys are skipped. Returns undefined when input is empty.
|
|
30
|
+
*
|
|
31
|
+
* @param {string|undefined} raw
|
|
32
|
+
* @returns {Record<string, string>|undefined}
|
|
33
|
+
*/
|
|
34
|
+
function parseCiParams(raw) {
|
|
35
|
+
if (!raw) return undefined;
|
|
36
|
+
/** @type {Record<string, string>} */
|
|
37
|
+
const result = {};
|
|
38
|
+
for (const entry of raw.split(',')) {
|
|
39
|
+
const idx = entry.indexOf('=');
|
|
40
|
+
if (idx <= 0) continue;
|
|
41
|
+
result[entry.slice(0, idx).trim()] = entry.slice(idx + 1);
|
|
42
|
+
}
|
|
43
|
+
return Object.keys(result).length ? result : undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
27
46
|
/**
|
|
28
47
|
* @typedef {import('../../types/types.js').Pipe} Pipe
|
|
29
48
|
* @typedef {import('../../types/types.js').TestData} TestData
|
|
@@ -85,6 +104,13 @@ class TestomatioPipe {
|
|
|
85
104
|
this.label = process.env.TESTOMATIO_LABEL;
|
|
86
105
|
this.description = params.description || process.env.TESTOMATIO_DESCRIPTION;
|
|
87
106
|
|
|
107
|
+
// Remote CI launch â when `TESTOMATIO_CI_PROFILE` is set the run will be created
|
|
108
|
+
// on the server *and* the named CI profile will be dispatched. Optional
|
|
109
|
+
// `TESTOMATIO_CI_PARAMS` is a comma-separated list of `key=value` pairs
|
|
110
|
+
// forwarded to the CI profile config (e.g. `branch=develop,REGION=eu`).
|
|
111
|
+
this.ciProfile = process.env.TESTOMATIO_CI_PROFILE;
|
|
112
|
+
this.ciParams = parseCiParams(process.env.TESTOMATIO_CI_PARAMS);
|
|
113
|
+
|
|
88
114
|
// Create a new instance of gaxios with a custom config
|
|
89
115
|
this.client = new Gaxios({
|
|
90
116
|
baseURL: `${this.url.trim()}`,
|
|
@@ -248,6 +274,18 @@ class TestomatioPipe {
|
|
|
248
274
|
this.store.configuration = { ...(this.store.configuration || {}), ...params.configuration };
|
|
249
275
|
}
|
|
250
276
|
}
|
|
277
|
+
|
|
278
|
+
// Assemble the `ci` block when the user asked for a remote CI launch via
|
|
279
|
+
// TESTOMATIO_CI_PROFILE (e.g. `--remote github`). Grep is taken from whatever
|
|
280
|
+
// `--filter` resolution already stashed in the shared pipeStore.
|
|
281
|
+
/** @type {{profile: string, grep?: string, override?: Record<string, any>}|null} */
|
|
282
|
+
let ci = null;
|
|
283
|
+
if (this.ciProfile) {
|
|
284
|
+
ci = { profile: this.ciProfile };
|
|
285
|
+
const grepIds = this.store?.preparedTestIds;
|
|
286
|
+
if (grepIds?.length) ci.grep = grepIds.join('|');
|
|
287
|
+
if (this.ciParams) ci.override = this.ciParams;
|
|
288
|
+
}
|
|
251
289
|
const runParams = Object.fromEntries(
|
|
252
290
|
Object.entries({
|
|
253
291
|
ci_build_url: buildUrl,
|
|
@@ -263,6 +301,7 @@ class TestomatioPipe {
|
|
|
263
301
|
kind: params.kind,
|
|
264
302
|
configuration,
|
|
265
303
|
description,
|
|
304
|
+
ci,
|
|
266
305
|
}).filter(([, value]) => !!value),
|
|
267
306
|
);
|
|
268
307
|
debug(' >>>>>> Run params', JSON.stringify(runParams, null, 2));
|
package/types/types.d.ts
CHANGED
|
@@ -293,7 +293,7 @@ export interface Pipe {
|
|
|
293
293
|
store: {};
|
|
294
294
|
|
|
295
295
|
/** starts run */
|
|
296
|
-
createRun(): Promise<void>;
|
|
296
|
+
createRun(params?: CreateRunParams): Promise<void>;
|
|
297
297
|
|
|
298
298
|
/** adds a test to the current run */
|
|
299
299
|
addTest(test: TestData): any;
|
|
@@ -316,6 +316,28 @@ export interface PipeResult {
|
|
|
316
316
|
result?: any;
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
+
/**
|
|
320
|
+
* Parameters accepted by `Client.createRun`.
|
|
321
|
+
*
|
|
322
|
+
* Remote CI launch (via `--remote <profile>` / matching `TESTOMATIO_CI_PROFILE`
|
|
323
|
+
* env var) is **not** passed through this object â `TestomatioPipe` reads the
|
|
324
|
+
* env vars directly and assembles the request body. See
|
|
325
|
+
* `TESTOMATIO_CI_PROFILE` and `TESTOMATIO_CI_OVERRIDE`.
|
|
326
|
+
*/
|
|
327
|
+
export interface CreateRunParams {
|
|
328
|
+
/** Run kind. Defaults to `automated` server-side. */
|
|
329
|
+
kind?: 'automated' | 'manual' | 'mixed';
|
|
330
|
+
|
|
331
|
+
/** Run title. */
|
|
332
|
+
title?: string;
|
|
333
|
+
|
|
334
|
+
/** Run configuration merged into the server-side run configuration. */
|
|
335
|
+
configuration?: Record<string, any>;
|
|
336
|
+
|
|
337
|
+
/** Override batch upload on/off. */
|
|
338
|
+
isBatchEnabled?: boolean;
|
|
339
|
+
}
|
|
340
|
+
|
|
319
341
|
/**
|
|
320
342
|
* Represents a step in a test.
|
|
321
343
|
*/
|