testomatio-reporter-cli 2.11.0 → 2.13.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testomatio-reporter-cli",
3
- "version": "2.11.0",
3
+ "version": "2.13.0",
4
4
  "description": "Yarn Berry compatible standalone CLI for @testomatio/reporter",
5
5
  "type": "module",
6
6
  "bin": {
@@ -255,7 +255,8 @@ function appendStep(step, shift = 0) {
255
255
  newCategory = 'hook';
256
256
  break;
257
257
  case 'attach':
258
- return null; // Skip steps with category 'attach'
258
+ case 'test.attach':
259
+ return null; // Attachments are reported as artifacts, not standalone steps
259
260
  default:
260
261
  newCategory = 'framework';
261
262
  }
@@ -286,11 +287,11 @@ function appendStep(step, shift = 0) {
286
287
  resultStep.log = truncate(String(step.log), 250);
287
288
  }
288
289
 
289
- // Add artifacts from attachments
290
- if (step.attachments && step.attachments.length > 0 && SCREENSHOTS_ON_STEPS) {
291
- const screenshotAttachment = step.attachments.find(att =>
292
- att.contentType === 'image/png' && att.name === 'screenshot'
293
- );
290
+ // Playwright also associates automatic failure screenshots with the active
291
+ // hook. Only attachments created for an explicit test.step belong to a step;
292
+ // hook and fixture attachments remain test-level result artifacts.
293
+ if (step.category === 'test.step' && step.attachments?.length && SCREENSHOTS_ON_STEPS) {
294
+ const screenshotAttachment = step.attachments.find(isScreenshotArtifact);
294
295
  if (screenshotAttachment && screenshotAttachment.path) {
295
296
  const artifacts = { screenshot: screenshotAttachment.path };
296
297
  addArtifactsToStep(resultStep, artifacts);
package/src/bin/cli.js CHANGED
@@ -62,7 +62,8 @@ program
62
62
  const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
63
63
  const client = new TestomatClient({ apiKey });
64
64
 
65
- const createRunParams = {};
65
+ // nothing is executed yet; the server flips it to running on the first reported test
66
+ const createRunParams = { status: 'scheduled' };
66
67
  if (opts.kind) createRunParams.kind = opts.kind;
67
68
 
68
69
  if (opts.filter) {
@@ -87,6 +88,11 @@ program
87
88
  process.exit(1);
88
89
  }
89
90
 
91
+ // tests are executed later, so report the run as pending with the tests it was scoped to:
92
+ // pipes add their report now and replace it when the run is finished
93
+ const plannedTests = (client.pipeStore.preparedTestIds || []).map(id => ({ test_id: id, title: id }));
94
+ await client.updateRunStatus('pending', { tests: plannedTests });
95
+
90
96
  // stdout carries ONLY the run id so it can be captured: RUN_ID=$(reporter start)
91
97
  console.log(runId);
92
98
  process.exit(0);
package/src/client.js CHANGED
@@ -371,7 +371,7 @@ class Client {
371
371
  /**
372
372
  *
373
373
  * Updates the status of the current test run and finishes the run.
374
- * @param {'passed' | 'failed' | 'skipped' | 'finished'} status - The status of the current test run.
374
+ * @param {'passed' | 'failed' | 'skipped' | 'finished' | 'pending'} status - The status of the current test run.
375
375
  * @param {Partial<import('../types/types.js').RunData>} [params] - Additional run params (e.g. duration).
376
376
  * Must be one of "passed", "failed", or "finished"
377
377
  * @returns {Promise<any>} - A Promise that resolves when finishes the run.
@@ -1,6 +1,13 @@
1
1
  import { APP_PREFIX, testomatLogoURL } from '../constants.js';
2
2
  import { ansiRegExp, isSameTest, truncate } from '../utils/utils.js';
3
- import { statusEmoji, fullName } from '../utils/pipe_utils.js';
3
+ import {
4
+ statusEmoji,
5
+ fullName,
6
+ plannedTestsLabel,
7
+ markdownTable,
8
+ runSummary,
9
+ totalDuration,
10
+ } from '../utils/pipe_utils.js';
4
11
  import { Gaxios } from 'gaxios';
5
12
  import pc from 'picocolors';
6
13
  import humanizeDuration from 'humanize-duration';
@@ -103,37 +110,32 @@ export class BitbucketPipe {
103
110
  }
104
111
 
105
112
  // Create a comment on Bitbucket
106
- const passedCount = this.tests.filter(t => t.status === 'passed').length;
107
- const failedCount = this.tests.filter(t => t.status === 'failed').length;
108
- const skippedCount = this.tests.filter(t => t.status === 'skipped').length;
109
-
110
- // Constructing the table
111
- let summary = `${this.hiddenCommentData}
112
-
113
- | ![Testomat.io Report](${testomatLogoURL}) | ${statusEmoji(
114
- runParams.status,
115
- )} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
116
- | --- | --- |
117
- | **Tests** | ✔️ **${this.tests.length}** tests run |
118
- | **Summary** | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
119
- 'passed',
120
- )} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
121
- | **Duration** | 🕐 **${humanizeDuration(
122
- parseInt(
123
- this.tests.reduce((a, t) => a + (t.run_time || 0), 0),
124
- 10,
125
- ),
126
- {
127
- maxDecimalPoints: 0,
128
- },
129
- )}** |
130
- `;
113
+ // a scheduled run has no results yet: no counters, no duration
114
+ const isPendingRun = runParams.status === 'pending';
115
+
116
+ /** @type {Object<string, string>} */
117
+ const rows = {};
118
+
119
+ if (isPendingRun) {
120
+ if (this.tests.length) rows.Tests = `⚪ ${plannedTestsLabel(this.tests, this.store.runTestsCount)}`;
121
+ } else {
122
+ rows.Tests = `✔️ **${this.tests.length}** tests run`;
123
+ rows.Summary = runSummary(this.tests);
124
+ rows.Duration = `🕐 **${totalDuration(this.tests)}**`;
125
+ }
131
126
 
132
127
  if (this.ENV.BITBUCKET_BRANCH && this.ENV.BITBUCKET_COMMIT) {
133
- // eslint-disable-next-line max-len
134
- summary += `| **Job** | 👷 [#${this.ENV.BITBUCKET_BUILD_NUMBER}](https://bitbucket.org/${this.ENV.BITBUCKET_REPO_FULL_NAME}/pipelines/results/${this.ENV.BITBUCKET_BUILD_NUMBER}") by commit: **${this.ENV.BITBUCKET_COMMIT}** |`;
128
+ const buildNumber = this.ENV.BITBUCKET_BUILD_NUMBER;
129
+ const buildUrl = `https://bitbucket.org/${this.ENV.BITBUCKET_REPO_FULL_NAME}/pipelines/results/${buildNumber}`;
130
+ rows.Job = `👷 [#${buildNumber}](${buildUrl}) by commit: **${this.ENV.BITBUCKET_COMMIT}**`;
135
131
  }
136
132
 
133
+ const header = [
134
+ `![Testomat.io Report](${testomatLogoURL})`,
135
+ `${statusEmoji(runParams.status)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)}`,
136
+ ];
137
+ const summary = `${this.hiddenCommentData}\n\n${markdownTable(header, rows, { boldLabels: true })}`;
138
+
137
139
  const failures = this.tests
138
140
  .filter(t => t.status === 'failed')
139
141
  .slice(0, 20)
@@ -182,7 +184,7 @@ export class BitbucketPipe {
182
184
  }
183
185
  }
184
186
 
185
- if (this.tests.length > 0) {
187
+ if (this.tests.length > 0 && !isPendingRun) {
186
188
  body += `\n\n**🐢 Slowest Tests**\n\n`;
187
189
  body += this.tests
188
190
  .sort((a, b) => b.run_time - a.run_time)
@@ -5,7 +5,14 @@ import humanizeDuration from 'humanize-duration';
5
5
  import merge from 'lodash.merge';
6
6
  import { testomatLogoURL } from '../constants.js';
7
7
  import { ansiRegExp, isSameTest, truncate } from '../utils/utils.js';
8
- import { statusEmoji, fullName } from '../utils/pipe_utils.js';
8
+ import {
9
+ statusEmoji,
10
+ fullName,
11
+ plannedTestsLabel,
12
+ markdownTable,
13
+ runSummary,
14
+ totalDuration,
15
+ } from '../utils/pipe_utils.js';
9
16
  import { log } from '../utils/log.js';
10
17
 
11
18
  const debug = createDebugMessages('@testomatio/reporter:pipe:github');
@@ -75,42 +82,37 @@ class GitHubPipe {
75
82
  if (!(owner || repo)) return;
76
83
 
77
84
  // ... create a comment on GitHub
78
- const passedCount = this.tests.filter(t => t.status === 'passed').length;
79
- const failedCount = this.tests.filter(t => t.status === 'failed').length;
80
- const skippedCount = this.tests.filter(t => t.status === 'skipped').length;
81
-
82
- let summary = `${this.hiddenCommentData}
83
-
84
- | [![Testomat.io Report](${testomatLogoURL})](https://testomat.io) | ${statusEmoji(
85
- runParams.status,
86
- )} ${`${process.env.GITHUB_JOB} ${runParams.status}`.toUpperCase()} |
87
- | --- | --- |
88
- | Tests | ✔️ **${this.tests.length}** tests run |
89
- | Summary | ${failedCount ? `${statusEmoji('failed')} **${failedCount}** failed; ` : ''} ${statusEmoji(
90
- 'passed',
91
- )} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
92
- | Duration | 🕐 **${humanizeDuration(
93
- parseInt(
94
- this.tests.reduce((a, t) => a + (t.run_time || 0), 0),
95
- 10,
96
- ),
97
- {
98
- maxDecimalPoints: 0,
99
- },
100
- )}** |`;
85
+ // a scheduled run has no results yet: no counters, no duration
86
+ const isPendingRun = runParams.status === 'pending';
87
+
88
+ /** @type {Object<string, string>} */
89
+ const rows = {};
90
+
91
+ if (isPendingRun) {
92
+ if (this.tests.length) rows.Tests = `⚪ ${plannedTestsLabel(this.tests, this.store.runTestsCount)}`;
93
+ } else {
94
+ rows.Tests = `✔️ **${this.tests.length}** tests run`;
95
+ rows.Summary = runSummary(this.tests);
96
+ rows.Duration = `🕐 **${totalDuration(this.tests)}**`;
97
+ }
101
98
 
102
99
  if (this.store.runUrl) {
103
- summary += `\n| Testomat.io Report | 📊 [Run #${this.store.runId}](${this.store.runUrl}) | `;
100
+ rows['Testomat.io Report'] = `📊 [Run #${this.store.runId}](${this.store.runUrl})`;
104
101
  }
105
102
  if (process.env.GITHUB_WORKFLOW) {
106
- summary += `\n| Job | 🗂️ [${this.jobKey}](${process.env.GITHUB_SERVER_URL || 'https://github.com'}/${
107
- this.repo
108
- }/actions/runs/${process.env.GITHUB_RUN_ID}) | `;
103
+ const server = process.env.GITHUB_SERVER_URL || 'https://github.com';
104
+ rows.Job = `🗂️ [${this.jobKey}](${server}/${this.repo}/actions/runs/${process.env.GITHUB_RUN_ID})`;
109
105
  }
110
106
  if (process.env.RUNNER_OS) {
111
- summary += `\n| Operating System | 🖥️ \`${process.env.RUNNER_OS}\` ${process.env.RUNNER_ARCH || ''} | `;
107
+ rows['Operating System'] = `🖥️ \`${process.env.RUNNER_OS}\` ${process.env.RUNNER_ARCH || ''}`;
112
108
  }
113
109
 
110
+ const header = [
111
+ `[![Testomat.io Report](${testomatLogoURL})](https://testomat.io)`,
112
+ `${statusEmoji(runParams.status)} ${`${process.env.GITHUB_JOB} ${runParams.status}`.toUpperCase()}`,
113
+ ];
114
+ const summary = `${this.hiddenCommentData}\n\n${markdownTable(header, rows)}`;
115
+
114
116
  const failures = this.tests
115
117
  .filter(t => t.status === 'failed')
116
118
  .slice(0, 20)
@@ -170,7 +172,7 @@ class GitHubPipe {
170
172
  body += '\n\n</details>';
171
173
  }
172
174
 
173
- if (this.tests.length > 0) {
175
+ if (this.tests.length > 0 && !isPendingRun) {
174
176
  body += '\n<details>\n<summary><h3>🐢 Slowest Tests</h3></summary>\n\n';
175
177
  body += this.tests
176
178
  .sort((a, b) => b?.run_time - a?.run_time)
@@ -6,7 +6,14 @@ import merge from 'lodash.merge';
6
6
  import path from 'path';
7
7
  import { APP_PREFIX, testomatLogoURL } from '../constants.js';
8
8
  import { ansiRegExp, isSameTest, truncate } from '../utils/utils.js';
9
- import { statusEmoji, fullName } from '../utils/pipe_utils.js';
9
+ import {
10
+ statusEmoji,
11
+ fullName,
12
+ plannedTestsLabel,
13
+ markdownTable,
14
+ runSummary,
15
+ totalDuration,
16
+ } from '../utils/pipe_utils.js';
10
17
  import { log } from '../utils/log.js';
11
18
 
12
19
  const debug = createDebugMessages('@testomatio/reporter:pipe:gitlab');
@@ -81,37 +88,31 @@ class GitLabPipe {
81
88
  if (runParams.tests) runParams.tests.forEach(t => this.addTest(t));
82
89
 
83
90
  // ... create a comment on GitLab
84
- const passedCount = this.tests.filter(t => t.status === 'passed').length;
85
- const failedCount = this.tests.filter(t => t.status === 'failed').length;
86
- const skippedCount = this.tests.filter(t => t.status === 'skipped').length;
87
-
88
- // constructing the table
89
- let summary = `${this.hiddenCommentData}
90
-
91
- | [![Testomat.io Report](${testomatLogoURL})](https://testomat.io) | ${statusEmoji(
92
- runParams.status,
93
- )} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
94
- | --- | --- |
95
- | Tests | ✔️ **${this.tests.length}** tests run |
96
- | Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
97
- 'passed',
98
- )} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
99
- | Duration | 🕐 **${humanizeDuration(
100
- parseInt(
101
- this.tests.reduce((a, t) => a + (t.run_time || 0), 0),
102
- 10,
103
- ),
104
- {
105
- maxDecimalPoints: 0,
106
- },
107
- )}** |
108
- `;
91
+ // a scheduled run has no results yet: no counters, no duration
92
+ const isPendingRun = runParams.status === 'pending';
93
+
94
+ /** @type {Object<string, string>} */
95
+ const rows = {};
96
+
97
+ if (isPendingRun) {
98
+ if (this.tests.length) rows.Tests = `⚪ ${plannedTestsLabel(this.tests, this.store.runTestsCount)}`;
99
+ } else {
100
+ rows.Tests = `✔️ **${this.tests.length}** tests run`;
101
+ rows.Summary = runSummary(this.tests);
102
+ rows.Duration = `🕐 **${totalDuration(this.tests)}**`;
103
+ }
109
104
 
110
105
  if (this.ENV.CI_JOB_NAME && this.ENV.CI_JOB_ID) {
111
106
  // eslint-disable-next-line max-len
112
- summary += `| Job | 👷 [${this.ENV.CI_JOB_ID}](${this.ENV.CI_JOB_URL})<br>Name: **${this.ENV.CI_JOB_NAME}**<br>Stage: **${this.ENV.CI_JOB_STAGE}** | `;
107
+ rows.Job = `👷 [${this.ENV.CI_JOB_ID}](${this.ENV.CI_JOB_URL})<br>Name: **${this.ENV.CI_JOB_NAME}**<br>Stage: **${this.ENV.CI_JOB_STAGE}**`;
113
108
  }
114
109
 
110
+ const header = [
111
+ `[![Testomat.io Report](${testomatLogoURL})](https://testomat.io)`,
112
+ `${statusEmoji(runParams.status)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)}`,
113
+ ];
114
+ const summary = `${this.hiddenCommentData}\n\n${markdownTable(header, rows)}`;
115
+
115
116
  const failures = this.tests
116
117
  .filter(t => t.status === 'failed')
117
118
  .slice(0, 20)
@@ -158,7 +159,7 @@ class GitLabPipe {
158
159
  body += '\n\n</details>';
159
160
  }
160
161
 
161
- if (this.tests.length > 0) {
162
+ if (this.tests.length > 0 && !isPendingRun) {
162
163
  body += '\n<details>\n<summary><h3>🐢 Slowest Tests</h3></summary>\n\n';
163
164
  body += this.tests
164
165
  .sort((a, b) => b?.run_time - a?.run_time)
package/src/pipe/html.js CHANGED
@@ -268,7 +268,7 @@ class HtmlPipe {
268
268
  executionTime: testExecutionSumTime(aggregatedTests),
269
269
  executionDate: getCurrentDateTimeFormatted(),
270
270
  description:
271
- [runParams.description || this.store.coverageDescription || this.store.description, this.description]
271
+ [this.description, runParams.description || this.store.coverageDescription || this.store.description]
272
272
  .filter(Boolean)
273
273
  .join('\n\n') || '',
274
274
  configuration: buildDisplayConfiguration(
@@ -140,7 +140,7 @@ class MarkdownPipe {
140
140
  executionTime: testExecutionSumTime(aggregated),
141
141
  executionDate: getCurrentDateTimeFormatted(),
142
142
  description:
143
- [runParams?.description || this.store.coverageDescription || this.store.description, this.description]
143
+ [this.description, runParams?.description || this.store.coverageDescription || this.store.description]
144
144
  .filter(Boolean)
145
145
  .join('\n\n') || '',
146
146
  configuration: this.configuration || this.store.configuration || runParams?.configuration || null,
@@ -279,8 +279,9 @@ class TestomatioPipe {
279
279
  suites: coverageConfiguration.suites?.map(id => id.replace(/^S/, '')) || [],
280
280
  };
281
281
  }
282
- // Run description: coverage-derived block (if any) with the user-provided TESTOMATIO_DESCRIPTION appended after it.
283
- const description = [coverageDescription, this.description].filter(Boolean).join('\n\n') || null;
282
+ // Run description: the user-provided TESTOMATIO_DESCRIPTION with the coverage-derived block
283
+ // (if any) added after it. Neither overrides the other.
284
+ const description = [this.description, coverageDescription].filter(Boolean).join('\n\n') || null;
284
285
 
285
286
  // Merge caller-supplied configuration (e.g. { exploratory: true }) into runParams.configuration.
286
287
  // Caller values win on key conflict; coverage-derived tests/suites lists are preserved when not overridden.
@@ -322,6 +323,7 @@ class TestomatioPipe {
322
323
  shared_run: this.sharedRun,
323
324
  shared_run_timeout: this.sharedRunTimeout,
324
325
  kind: params.kind,
326
+ status: params.status,
325
327
  configuration,
326
328
  description,
327
329
  ci,
@@ -372,6 +374,8 @@ class TestomatioPipe {
372
374
  this.store.runUrl = this.runUrl;
373
375
  this.store.runPublicUrl = this.runPublicUrl;
374
376
  this.store.runId = this.runId;
377
+ // only the server knows how many tests a configuration expands to; automated runs report 0
378
+ if (resp.data.tests_count > 0) this.store.runTestsCount = resp.data.tests_count;
375
379
  log.info('📊 Report created. Report ID:', this.runId);
376
380
  process.env.runId = this.runId;
377
381
  debug('Run created', this.runId);
@@ -558,6 +562,9 @@ class TestomatioPipe {
558
562
 
559
563
  const { status } = params;
560
564
 
565
+ // a pending run was just created: nothing to update here, only other pipes report it
566
+ if (status === 'pending') return;
567
+
561
568
  let status_event;
562
569
 
563
570
  if (status === STATUS.FINISHED) status_event = 'finish';
@@ -1,3 +1,4 @@
1
+ import humanizeDuration from 'humanize-duration';
1
2
  import { log } from './log.js';
2
3
 
3
4
  /**
@@ -117,6 +118,7 @@ function statusEmoji(status) {
117
118
  if (status === 'passed') return '🟢';
118
119
  if (status === 'failed') return '🔴';
119
120
  if (status === 'skipped') return '🟡';
121
+ if (status === 'pending') return '🕐';
120
122
  return '';
121
123
  }
122
124
 
@@ -228,6 +230,80 @@ function formatFilterListIds(ids, format) {
228
230
  }
229
231
  }
230
232
 
233
+ /**
234
+ * Summarize a finished run, e.g. `🔴 **1** failed; 🟢 **8** passed; 🟡 **1** skipped`.
235
+ * The failed part is omitted when nothing failed.
236
+ *
237
+ * @param {Array<{status?: string}>} tests
238
+ * @returns {string}
239
+ */
240
+ function runSummary(tests) {
241
+ const countOf = status => tests.filter(t => t.status === status).length;
242
+ const failedCount = countOf('failed');
243
+
244
+ const parts = [];
245
+ if (failedCount) parts.push(`${statusEmoji('failed')} **${failedCount}** failed`);
246
+ parts.push(`${statusEmoji('passed')} **${countOf('passed')}** passed`);
247
+ parts.push(`${statusEmoji('skipped')} **${countOf('skipped')}** skipped`);
248
+
249
+ return parts.join('; ');
250
+ }
251
+
252
+ /**
253
+ * Total run time of the given tests, humanized — e.g. `2 seconds`.
254
+ *
255
+ * @param {Array<{run_time?: number}>} tests
256
+ * @returns {string}
257
+ */
258
+ function totalDuration(tests) {
259
+ const milliseconds = tests.reduce((total, t) => total + (t.run_time || 0), 0);
260
+ return humanizeDuration(Math.trunc(milliseconds), { maxDecimalPoints: 0 });
261
+ }
262
+
263
+ /**
264
+ * Render a two-column markdown table. Rows with an empty value are skipped, so optional rows
265
+ * need no surrounding `if`.
266
+ *
267
+ * @param {string[]} header - The two header cells.
268
+ * @param {Object<string, string>} rows - Label to value, rendered in insertion order.
269
+ * @param {{ boldLabels?: boolean }} [opts] - Set `boldLabels` to wrap every label in `**`.
270
+ * @returns {string} The table, with no trailing newline.
271
+ */
272
+ function markdownTable(header, rows, opts = {}) {
273
+ const lines = [`| ${header[0]} | ${header[1]} |`, '| --- | --- |'];
274
+
275
+ for (const [label, value] of Object.entries(rows)) {
276
+ if (!value) continue;
277
+
278
+ if (opts.boldLabels) {
279
+ lines.push(`| **${label}** | ${value} |`);
280
+ } else {
281
+ lines.push(`| ${label} | ${value} |`);
282
+ }
283
+ }
284
+
285
+ return lines.join('\n');
286
+ }
287
+
288
+ /**
289
+ * Describe the scope of a run that was prepared but not executed yet. Prefers the server's count;
290
+ * without it, falls back to the scoped ids, which mix tests (`T…`) and suites (`S…`).
291
+ *
292
+ * @param {Array<{test_id?: string}>} tests - Prepared tests the run was scoped to.
293
+ * @param {number} [testsCount] - Real number of tests, as reported by Testomat.io.
294
+ * @returns {string} Markdown label, e.g. `**159** tests planned` or `**6** suites planned`.
295
+ */
296
+ function plannedTestsLabel(tests, testsCount) {
297
+ if (testsCount > 0) return `**${testsCount}** tests planned`;
298
+
299
+ const suitesCount = tests.filter(t => `${t.test_id || ''}`.startsWith('S')).length;
300
+ const knownTestsCount = tests.length - suitesCount;
301
+
302
+ if (!suitesCount) return `**${knownTestsCount}** tests planned`;
303
+ if (!knownTestsCount) return `**${suitesCount}** suites planned`;
304
+ return `**${knownTestsCount}** tests and **${suitesCount}** suites planned`;
305
+ }
306
+
231
307
  export {
232
308
  updateFilterType,
233
309
  parseFilterParams,
@@ -235,6 +311,10 @@ export {
235
311
  setS3Credentials,
236
312
  statusEmoji,
237
313
  fullName,
314
+ markdownTable,
315
+ runSummary,
316
+ totalDuration,
317
+ plannedTestsLabel,
238
318
  parsePipeOptions,
239
319
  formatFilterListIds,
240
320
  getObjectSize,
package/types/types.d.ts CHANGED
@@ -289,6 +289,8 @@ export enum RunStatus {
289
289
  Passed = 'passed',
290
290
  Failed = 'failed',
291
291
  Finished = 'finished',
292
+ /** run created but not executed yet, reported by `reporter start` */
293
+ Pending = 'pending',
292
294
  }
293
295
 
294
296
  /** Batch upload strategy:
@@ -344,6 +346,9 @@ export interface CreateRunParams {
344
346
  /** Run configuration merged into the server-side run configuration. */
345
347
  configuration?: Record<string, any>;
346
348
 
349
+ /** Initial run status. `scheduled` marks a prepared run; the server flips it to running. */
350
+ status?: 'scheduled';
351
+
347
352
  /** Override batch upload mode. */
348
353
  batchMode?: BatchMode;
349
354
  }