@testomatio/reporter 2.8.4 → 2.8.5-beta.2-yarn
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.js +7 -2
- package/lib/bin/cli.js +70 -10
- package/lib/pipe/coverage.js +1 -0
- package/lib/pipe/testomatio.d.ts +2 -0
- package/lib/pipe/testomatio.js +49 -0
- package/package.json +2 -2
- package/src/adapter/codecept.js +7 -2
- package/src/bin/cli.js +81 -11
- package/src/pipe/coverage.js +1 -0
- package/src/pipe/testomatio.js +47 -0
- package/types/types.d.ts +23 -1
package/lib/adapter/codecept.js
CHANGED
|
@@ -422,11 +422,16 @@ function createHookSection(hookName, steps) {
|
|
|
422
422
|
function formatHookName(hookName) {
|
|
423
423
|
return hookName.replace(/Hook$/, '');
|
|
424
424
|
}
|
|
425
|
+
function getCodeceptStepCategory(origin = 'test') {
|
|
426
|
+
if (origin === 'hook')
|
|
427
|
+
return 'hook';
|
|
428
|
+
return 'user';
|
|
429
|
+
}
|
|
425
430
|
// Format CodeceptJS step using its built-in methods
|
|
426
431
|
function formatCodeceptStep(step, screenshotOnFailPath = null) {
|
|
427
432
|
if (!step)
|
|
428
433
|
return null;
|
|
429
|
-
const category =
|
|
434
|
+
const category = getCodeceptStepCategory('test');
|
|
430
435
|
const title = (0, utils_js_1.truncate)(String(step));
|
|
431
436
|
const duration = step.duration || 0;
|
|
432
437
|
const formattedStep = (0, step_formatter_js_1.formatStep)({
|
|
@@ -471,7 +476,7 @@ function formatHookStep(step) {
|
|
|
471
476
|
}
|
|
472
477
|
title = (0, utils_js_1.truncate)(title);
|
|
473
478
|
const formattedStep = (0, step_formatter_js_1.formatStep)({
|
|
474
|
-
category: 'hook',
|
|
479
|
+
category: getCodeceptStepCategory('hook'),
|
|
475
480
|
title,
|
|
476
481
|
duration: step.duration || 0,
|
|
477
482
|
});
|
package/lib/bin/cli.js
CHANGED
|
@@ -34,9 +34,9 @@ program
|
|
|
34
34
|
else {
|
|
35
35
|
dotenv_1.default.config();
|
|
36
36
|
}
|
|
37
|
-
// --filter-list
|
|
38
|
-
// remaining output to stderr, skip the banner, and silence info-level
|
|
39
|
-
//
|
|
37
|
+
// --format / --filter-list produce machine-readable output on stdout, so route
|
|
38
|
+
// remaining output to stderr, skip the banner, and silence info-level logs so
|
|
39
|
+
// stdout stays clean for capture (e.g. RUN_ID=$(... start --format id)).
|
|
40
40
|
// Set TESTOMATIO_LOG_LEVEL=INFO to re-enable progress logs for debugging.
|
|
41
41
|
const subOpts = actionCommand.opts();
|
|
42
42
|
if (subOpts.filterList || subOpts.format) {
|
|
@@ -51,19 +51,37 @@ program
|
|
|
51
51
|
.command('start')
|
|
52
52
|
.description('Start a new run and return its ID')
|
|
53
53
|
.option('--kind <type>', 'Specify run type: automated, manual, or mixed')
|
|
54
|
+
.option('--filter <filter>', 'Scope the prepared run to tests matching the filter (no execution)')
|
|
55
|
+
.option('--format <format>', 'Machine-readable output: print only the run id to stdout (e.g. --format id)')
|
|
54
56
|
.action(async (opts) => {
|
|
55
57
|
(0, utils_js_1.cleanLatestRunId)();
|
|
56
|
-
|
|
58
|
+
log_js_1.log.info('Starting a new Run on Testomat.io...');
|
|
57
59
|
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config_js_1.config.TESTOMATIO;
|
|
58
60
|
const client = new client_js_1.default({ apiKey });
|
|
59
61
|
const createRunParams = {};
|
|
60
|
-
if (opts.kind)
|
|
62
|
+
if (opts.kind)
|
|
61
63
|
createRunParams.kind = opts.kind;
|
|
64
|
+
if (opts.filter) {
|
|
65
|
+
const [pipe, ...optsArray] = opts.filter.split(':');
|
|
66
|
+
const tests = await client.prepareRun({ pipe, pipeOptions: optsArray.join(':') });
|
|
67
|
+
if (!tests || tests.length === 0) {
|
|
68
|
+
log_js_1.log.warn(picocolors_1.default.yellow('No tests found for the filter. Run not created.'));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
createRunParams.configuration = {
|
|
72
|
+
tests: tests.filter(id => id.startsWith('T')).map(id => id.slice(1)),
|
|
73
|
+
suites: tests.filter(id => id.startsWith('S')).map(id => id.slice(1)),
|
|
74
|
+
};
|
|
62
75
|
}
|
|
63
|
-
client.createRun(createRunParams)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
await client.createRun(createRunParams);
|
|
77
|
+
const runId = client.pipeStore.runId || process.env.runId;
|
|
78
|
+
if (!runId) {
|
|
79
|
+
log_js_1.log.error(picocolors_1.default.red('Failed to create run on Testomat.io.'));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
// stdout carries ONLY the run id so it can be captured: RUN_ID=$(reporter start)
|
|
83
|
+
console.log(runId);
|
|
84
|
+
process.exit(0);
|
|
67
85
|
});
|
|
68
86
|
program
|
|
69
87
|
.command('finish')
|
|
@@ -91,7 +109,19 @@ program
|
|
|
91
109
|
.option('--filter-list <filter>', 'Get a list of all tests by filter before running')
|
|
92
110
|
.option('--format <format>', 'Machine-readable output format for --filter-list (grep, json, newline, ids)')
|
|
93
111
|
.option('--kind <type>', 'Specify run type: automated, manual, or mixed')
|
|
112
|
+
.option('--remote <profile>', 'Trigger run on the named Testomat.io CI profile instead of executing locally')
|
|
113
|
+
.option('--remote-param <kv>', 'key=value pair forwarded to the CI profile config (repeat for multiple)', (value, prev) => prev.concat([value]), [])
|
|
94
114
|
.action(async (command, opts) => {
|
|
115
|
+
if (opts.remote) {
|
|
116
|
+
if (opts.filterList) {
|
|
117
|
+
log_js_1.log.warn(picocolors_1.default.red('⚠️ --filter-list cannot be combined with --remote'));
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
process.env.TESTOMATIO_CI_PROFILE = opts.remote;
|
|
121
|
+
if (opts.remoteParam?.length) {
|
|
122
|
+
process.env.TESTOMATIO_CI_PARAMS = opts.remoteParam.join(',');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
95
125
|
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config_js_1.config.TESTOMATIO;
|
|
96
126
|
const title = process.env.TESTOMATIO_TITLE;
|
|
97
127
|
const client = new client_js_1.default({ apiKey, title });
|
|
@@ -127,7 +157,7 @@ program
|
|
|
127
157
|
}
|
|
128
158
|
return;
|
|
129
159
|
}
|
|
130
|
-
if (command && command.split) {
|
|
160
|
+
if (command && command.split && !opts.remote) {
|
|
131
161
|
command = (0, utils_js_1.applyFilter)(command, tests);
|
|
132
162
|
}
|
|
133
163
|
}
|
|
@@ -138,6 +168,36 @@ program
|
|
|
138
168
|
return;
|
|
139
169
|
}
|
|
140
170
|
}
|
|
171
|
+
if (opts.remote) {
|
|
172
|
+
if (!apiKey) {
|
|
173
|
+
log_js_1.log.warn(picocolors_1.default.red('⚠️ TESTOMATIO API key required for --remote'));
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
if (command) {
|
|
177
|
+
log_js_1.log.warn(picocolors_1.default.yellow('Note: positional command is ignored when --remote is set; CI runs the workflow.'));
|
|
178
|
+
}
|
|
179
|
+
const createRunParams = {};
|
|
180
|
+
if (title)
|
|
181
|
+
createRunParams.title = title;
|
|
182
|
+
if (opts.kind)
|
|
183
|
+
createRunParams.kind = opts.kind;
|
|
184
|
+
try {
|
|
185
|
+
await client.createRun(createRunParams);
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
log_js_1.log.error(picocolors_1.default.red(`CI launch failed: ${err.message || err}`));
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
// createRun swallows pipe-level errors, so a resolved promise is not proof
|
|
192
|
+
// the launch succeeded — the pipe only records runUrl on a real 2xx response.
|
|
193
|
+
if (!client.pipeStore.runUrl) {
|
|
194
|
+
log_js_1.log.error(picocolors_1.default.red('CI launch failed — no run was created (see the error above).'));
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
log_js_1.log.info(`🚀 CI build triggered on profile ${picocolors_1.default.cyan(opts.remote)}`);
|
|
198
|
+
log_js_1.log.info(`📊 Report URL: ${picocolors_1.default.magenta(client.pipeStore.runUrl)}`);
|
|
199
|
+
return process.exit(0);
|
|
200
|
+
}
|
|
141
201
|
// just create a run (wich tests which match filters) without executing tests
|
|
142
202
|
if (!command || !command.split) {
|
|
143
203
|
const createRunParams = {};
|
package/lib/pipe/coverage.js
CHANGED
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()}`,
|
|
@@ -155,6 +181,8 @@ class TestomatioPipe {
|
|
|
155
181
|
});
|
|
156
182
|
if (Array.isArray(resp.data?.tests) && resp.data?.tests?.length > 0) {
|
|
157
183
|
(0, utils_js_1.foundedTestLog)(constants_js_1.APP_PREFIX, resp.data.tests);
|
|
184
|
+
if (this.store)
|
|
185
|
+
this.store.preparedTestIds = resp.data.tests;
|
|
158
186
|
return resp.data.tests;
|
|
159
187
|
}
|
|
160
188
|
log_js_1.log.warn(`⛔ No tests found for your --filter --> ${type}=${id}`);
|
|
@@ -213,6 +241,26 @@ class TestomatioPipe {
|
|
|
213
241
|
this.store.configuration = { ...(this.store.configuration || {}), ...params.configuration };
|
|
214
242
|
}
|
|
215
243
|
}
|
|
244
|
+
// Assemble the `ci` block when the user asked for a remote CI launch via
|
|
245
|
+
// TESTOMATIO_CI_PROFILE (e.g. `--remote github`). Grep is taken from whatever
|
|
246
|
+
// `--filter` resolution already stashed in the shared pipeStore. When launching
|
|
247
|
+
// an already-prepared run (TESTOMATIO_RUN set) with no fresh filter, ask the
|
|
248
|
+
// server to grep that run's own stored scope via `{ type: 'run', id }`.
|
|
249
|
+
/** @type {{profile: string, grep?: string, type?: string, id?: string, override?: Record<string, any>}|null} */
|
|
250
|
+
let ci = null;
|
|
251
|
+
if (this.ciProfile) {
|
|
252
|
+
ci = { profile: this.ciProfile };
|
|
253
|
+
const grepIds = this.store?.preparedTestIds;
|
|
254
|
+
if (grepIds?.length) {
|
|
255
|
+
ci.grep = grepIds.join('|');
|
|
256
|
+
}
|
|
257
|
+
else if (this.runId) {
|
|
258
|
+
ci.type = 'run';
|
|
259
|
+
ci.id = this.runId;
|
|
260
|
+
}
|
|
261
|
+
if (this.ciParams)
|
|
262
|
+
ci.override = this.ciParams;
|
|
263
|
+
}
|
|
216
264
|
const runParams = Object.fromEntries(Object.entries({
|
|
217
265
|
ci_build_url: buildUrl,
|
|
218
266
|
api_key: this.apiKey.trim(),
|
|
@@ -227,6 +275,7 @@ class TestomatioPipe {
|
|
|
227
275
|
kind: params.kind,
|
|
228
276
|
configuration,
|
|
229
277
|
description,
|
|
278
|
+
ci,
|
|
230
279
|
}).filter(([, value]) => !!value));
|
|
231
280
|
debug(' >>>>>> Run params', JSON.stringify(runParams, null, 2));
|
|
232
281
|
if (this.runId) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testomatio/reporter",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.5-beta.2-yarn",
|
|
4
4
|
"description": "Testomatio Reporter Client",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=18"
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"build:watch:bun": "rm -rf ./cjs && bun build ./src/bin/reportXml.js ./src/bin/startTest.js ./src/bin/uploadArtifacts.js --outdir ./cjs --target node --watch --onSuccess \"build/scripts/post-build.js\""
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
|
89
|
-
"@playwright/test": "^1.
|
|
89
|
+
"@playwright/test": "^1.60.0",
|
|
90
90
|
"@redocly/cli": "^1.0.0-beta.125",
|
|
91
91
|
"@types/cross-spawn": "^6.0.6",
|
|
92
92
|
"@types/cucumber": "^7.0.0",
|
package/src/adapter/codecept.js
CHANGED
|
@@ -490,11 +490,16 @@ function formatHookName(hookName) {
|
|
|
490
490
|
return hookName.replace(/Hook$/, '');
|
|
491
491
|
}
|
|
492
492
|
|
|
493
|
+
function getCodeceptStepCategory(origin = 'test') {
|
|
494
|
+
if (origin === 'hook') return 'hook';
|
|
495
|
+
return 'user';
|
|
496
|
+
}
|
|
497
|
+
|
|
493
498
|
// Format CodeceptJS step using its built-in methods
|
|
494
499
|
function formatCodeceptStep(step, screenshotOnFailPath = null) {
|
|
495
500
|
if (!step) return null;
|
|
496
501
|
|
|
497
|
-
const category =
|
|
502
|
+
const category = getCodeceptStepCategory('test');
|
|
498
503
|
const title = truncate(String(step));
|
|
499
504
|
const duration = step.duration || 0;
|
|
500
505
|
|
|
@@ -548,7 +553,7 @@ function formatHookStep(step) {
|
|
|
548
553
|
title = truncate(title);
|
|
549
554
|
|
|
550
555
|
const formattedStep = formatStep({
|
|
551
|
-
category: 'hook',
|
|
556
|
+
category: getCodeceptStepCategory('hook'),
|
|
552
557
|
title,
|
|
553
558
|
duration: step.duration || 0,
|
|
554
559
|
});
|
package/src/bin/cli.js
CHANGED
|
@@ -32,9 +32,9 @@ program
|
|
|
32
32
|
dotenv.config();
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
// --filter-list
|
|
36
|
-
// remaining output to stderr, skip the banner, and silence info-level
|
|
37
|
-
//
|
|
35
|
+
// --format / --filter-list produce machine-readable output on stdout, so route
|
|
36
|
+
// remaining output to stderr, skip the banner, and silence info-level logs so
|
|
37
|
+
// stdout stays clean for capture (e.g. RUN_ID=$(... start --format id)).
|
|
38
38
|
// Set TESTOMATIO_LOG_LEVEL=INFO to re-enable progress logs for debugging.
|
|
39
39
|
const subOpts = actionCommand.opts();
|
|
40
40
|
if (subOpts.filterList || subOpts.format) {
|
|
@@ -49,22 +49,42 @@ program
|
|
|
49
49
|
.command('start')
|
|
50
50
|
.description('Start a new run and return its ID')
|
|
51
51
|
.option('--kind <type>', 'Specify run type: automated, manual, or mixed')
|
|
52
|
+
.option('--filter <filter>', 'Scope the prepared run to tests matching the filter (no execution)')
|
|
53
|
+
.option('--format <format>', 'Machine-readable output: print only the run id to stdout (e.g. --format id)')
|
|
52
54
|
.action(async opts => {
|
|
53
55
|
cleanLatestRunId();
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
log.info('Starting a new Run on Testomat.io...');
|
|
56
58
|
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
|
|
57
59
|
const client = new TestomatClient({ apiKey });
|
|
58
60
|
|
|
59
61
|
const createRunParams = {};
|
|
60
|
-
if (opts.kind)
|
|
61
|
-
|
|
62
|
+
if (opts.kind) createRunParams.kind = opts.kind;
|
|
63
|
+
|
|
64
|
+
if (opts.filter) {
|
|
65
|
+
const [pipe, ...optsArray] = opts.filter.split(':');
|
|
66
|
+
const tests = await client.prepareRun({ pipe, pipeOptions: optsArray.join(':') });
|
|
67
|
+
if (!tests || tests.length === 0) {
|
|
68
|
+
log.warn(pc.yellow('No tests found for the filter. Run not created.'));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
createRunParams.configuration = {
|
|
72
|
+
tests: tests.filter(id => id.startsWith('T')).map(id => id.slice(1)),
|
|
73
|
+
suites: tests.filter(id => id.startsWith('S')).map(id => id.slice(1)),
|
|
74
|
+
};
|
|
62
75
|
}
|
|
63
76
|
|
|
64
|
-
client.createRun(createRunParams)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
await client.createRun(createRunParams);
|
|
78
|
+
|
|
79
|
+
const runId = client.pipeStore.runId || process.env.runId;
|
|
80
|
+
if (!runId) {
|
|
81
|
+
log.error(pc.red('Failed to create run on Testomat.io.'));
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// stdout carries ONLY the run id so it can be captured: RUN_ID=$(reporter start)
|
|
86
|
+
console.log(runId);
|
|
87
|
+
process.exit(0);
|
|
68
88
|
});
|
|
69
89
|
|
|
70
90
|
program
|
|
@@ -97,7 +117,25 @@ program
|
|
|
97
117
|
.option('--filter-list <filter>', 'Get a list of all tests by filter before running')
|
|
98
118
|
.option('--format <format>', 'Machine-readable output format for --filter-list (grep, json, newline, ids)')
|
|
99
119
|
.option('--kind <type>', 'Specify run type: automated, manual, or mixed')
|
|
120
|
+
.option('--remote <profile>', 'Trigger run on the named Testomat.io CI profile instead of executing locally')
|
|
121
|
+
.option(
|
|
122
|
+
'--remote-param <kv>',
|
|
123
|
+
'key=value pair forwarded to the CI profile config (repeat for multiple)',
|
|
124
|
+
(value, prev) => prev.concat([value]),
|
|
125
|
+
[],
|
|
126
|
+
)
|
|
100
127
|
.action(async (command, opts) => {
|
|
128
|
+
if (opts.remote) {
|
|
129
|
+
if (opts.filterList) {
|
|
130
|
+
log.warn(pc.red('⚠️ --filter-list cannot be combined with --remote'));
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
process.env.TESTOMATIO_CI_PROFILE = opts.remote;
|
|
134
|
+
if (opts.remoteParam?.length) {
|
|
135
|
+
process.env.TESTOMATIO_CI_PARAMS = opts.remoteParam.join(',');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
101
139
|
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
|
|
102
140
|
const title = process.env.TESTOMATIO_TITLE;
|
|
103
141
|
const client = new TestomatClient({ apiKey, title });
|
|
@@ -137,7 +175,7 @@ program
|
|
|
137
175
|
return;
|
|
138
176
|
}
|
|
139
177
|
|
|
140
|
-
if (command && command.split) {
|
|
178
|
+
if (command && command.split && !opts.remote) {
|
|
141
179
|
command = applyFilter(command, tests);
|
|
142
180
|
}
|
|
143
181
|
}
|
|
@@ -148,6 +186,38 @@ program
|
|
|
148
186
|
}
|
|
149
187
|
}
|
|
150
188
|
|
|
189
|
+
if (opts.remote) {
|
|
190
|
+
if (!apiKey) {
|
|
191
|
+
log.warn(pc.red('⚠️ TESTOMATIO API key required for --remote'));
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
if (command) {
|
|
195
|
+
log.warn(pc.yellow('Note: positional command is ignored when --remote is set; CI runs the workflow.'));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const createRunParams = {};
|
|
199
|
+
if (title) createRunParams.title = title;
|
|
200
|
+
if (opts.kind) createRunParams.kind = opts.kind;
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
await client.createRun(createRunParams);
|
|
204
|
+
} catch (err) {
|
|
205
|
+
log.error(pc.red(`CI launch failed: ${err.message || err}`));
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// createRun swallows pipe-level errors, so a resolved promise is not proof
|
|
210
|
+
// the launch succeeded — the pipe only records runUrl on a real 2xx response.
|
|
211
|
+
if (!client.pipeStore.runUrl) {
|
|
212
|
+
log.error(pc.red('CI launch failed — no run was created (see the error above).'));
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
log.info(`🚀 CI build triggered on profile ${pc.cyan(opts.remote)}`);
|
|
217
|
+
log.info(`📊 Report URL: ${pc.magenta(client.pipeStore.runUrl)}`);
|
|
218
|
+
return process.exit(0);
|
|
219
|
+
}
|
|
220
|
+
|
|
151
221
|
// just create a run (wich tests which match filters) without executing tests
|
|
152
222
|
if (!command || !command.split) {
|
|
153
223
|
const createRunParams = {};
|
package/src/pipe/coverage.js
CHANGED
|
@@ -169,6 +169,7 @@ class CoveragePipe { // or Changes for the future???
|
|
|
169
169
|
|
|
170
170
|
this.results = [...this.tests, ...this.suiteIds];
|
|
171
171
|
if (this.store) {
|
|
172
|
+
this.store.preparedTestIds = this.results;
|
|
172
173
|
this.store.coverageConfiguration = {
|
|
173
174
|
tests: [...this.tests],
|
|
174
175
|
suites: [...this.suiteIds],
|
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()}`,
|
|
@@ -184,6 +210,7 @@ class TestomatioPipe {
|
|
|
184
210
|
|
|
185
211
|
if (Array.isArray(resp.data?.tests) && resp.data?.tests?.length > 0) {
|
|
186
212
|
foundedTestLog(APP_PREFIX, resp.data.tests);
|
|
213
|
+
if (this.store) this.store.preparedTestIds = resp.data.tests;
|
|
187
214
|
return resp.data.tests;
|
|
188
215
|
}
|
|
189
216
|
|
|
@@ -248,6 +275,25 @@ class TestomatioPipe {
|
|
|
248
275
|
this.store.configuration = { ...(this.store.configuration || {}), ...params.configuration };
|
|
249
276
|
}
|
|
250
277
|
}
|
|
278
|
+
|
|
279
|
+
// Assemble the `ci` block when the user asked for a remote CI launch via
|
|
280
|
+
// TESTOMATIO_CI_PROFILE (e.g. `--remote github`). Grep is taken from whatever
|
|
281
|
+
// `--filter` resolution already stashed in the shared pipeStore. When launching
|
|
282
|
+
// an already-prepared run (TESTOMATIO_RUN set) with no fresh filter, ask the
|
|
283
|
+
// server to grep that run's own stored scope via `{ type: 'run', id }`.
|
|
284
|
+
/** @type {{profile: string, grep?: string, type?: string, id?: string, override?: Record<string, any>}|null} */
|
|
285
|
+
let ci = null;
|
|
286
|
+
if (this.ciProfile) {
|
|
287
|
+
ci = { profile: this.ciProfile };
|
|
288
|
+
const grepIds = this.store?.preparedTestIds;
|
|
289
|
+
if (grepIds?.length) {
|
|
290
|
+
ci.grep = grepIds.join('|');
|
|
291
|
+
} else if (this.runId) {
|
|
292
|
+
ci.type = 'run';
|
|
293
|
+
ci.id = this.runId;
|
|
294
|
+
}
|
|
295
|
+
if (this.ciParams) ci.override = this.ciParams;
|
|
296
|
+
}
|
|
251
297
|
const runParams = Object.fromEntries(
|
|
252
298
|
Object.entries({
|
|
253
299
|
ci_build_url: buildUrl,
|
|
@@ -263,6 +309,7 @@ class TestomatioPipe {
|
|
|
263
309
|
kind: params.kind,
|
|
264
310
|
configuration,
|
|
265
311
|
description,
|
|
312
|
+
ci,
|
|
266
313
|
}).filter(([, value]) => !!value),
|
|
267
314
|
);
|
|
268
315
|
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
|
*/
|