@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 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
- // logs to stderr and skip the banner to keep stdout clean for piping.
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.info(picocolors_1.default.yellow('No tests found.'));
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.info(err.message || err);
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
  }
@@ -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.info('â„šī¸ No matching entries in coverage file for provided Git changes.');
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.info('â„šī¸ No tests found for execution based on Git changes.');
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.info(`🔍 No test by ${type}=${id} were found on the Testomat.io server side!`);
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.warn(`We will use '${cmd}' Git command.`);
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.info('â„šī¸ No files changed in the latest Git commit. Skipping coverage processing.');
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.info('❌ Coverage file not found:', this.coverageFilePath);
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.info('❌ Provided coverage path is not a file:', this.coverageFilePath);
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.info('❌ Coverage file must have a .yml extension:', this.coverageFilePath);
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!');
@@ -31,6 +31,8 @@ declare class TestomatioPipe implements Pipe {
31
31
  env: string;
32
32
  label: string;
33
33
  description: any;
34
+ ciProfile: string;
35
+ ciParams: Record<string, string>;
34
36
  client: Gaxios;
35
37
  proceed: boolean;
36
38
  jiraId: string;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "2.8.3",
3
+ "version": "2.8.5-beta.1-remote",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "engines": {
6
6
  "node": ">=18"
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
- // logs to stderr and skip the banner to keep stdout clean for piping.
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.info( pc.yellow('No tests found.'));
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.info( err.message || err);
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;
@@ -140,7 +140,7 @@ class CoveragePipe { // or Changes for the future???
140
140
  }
141
141
 
142
142
  if (lines.size === 0) {
143
- log.info( 'â„šī¸ No matching entries in coverage file for provided Git changes.');
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.info( 'â„šī¸ No tests found for execution based on Git changes.');
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.info( `🔍 No test by ${type}=${id} were found on the Testomat.io server side!`);
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.warn( `We will use '${cmd}' Git command.`);
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.info('â„šī¸ No files changed in the latest Git commit. Skipping coverage processing.');
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.info( '❌ Coverage file not found:', this.coverageFilePath);
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.info( '❌ Provided coverage path is not a file:', this.coverageFilePath);
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.info( '❌ Coverage file must have a .yml extension:', this.coverageFilePath);
381
+ log.error( '❌ Coverage file must have a .yml extension:', this.coverageFilePath);
382
382
  return undefined;
383
383
  }
384
384
 
@@ -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
  */