@piwitests/reporter 0.57.0 → 0.58.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/dist/cli/index.js CHANGED
@@ -873,6 +873,8 @@ var PIWI_ENV_KEYS = {
873
873
  codeReachRoots: "PIWI_CODE_REACH_ROOTS",
874
874
  coverageBrowser: "PIWI_COVERAGE_BROWSER",
875
875
  coverageServer: "PIWI_COVERAGE_SERVER",
876
+ externalIdPattern: "PIWI_EXTERNAL_ID_PATTERN",
877
+ externalIdOnDuplicate: "PIWI_EXTERNAL_ID_ON_DUPLICATE",
876
878
  captureServerTraces: "PIWI_CAPTURE_SERVER_TRACES",
877
879
  captureResources: "PIWI_CAPTURE_RESOURCES",
878
880
  leakCheck: "PIWI_LEAK_CHECK",
@@ -4877,7 +4879,7 @@ Connection:
4877
4879
  --server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL)
4878
4880
  --api-key <key> API key (env PIWI_API_KEY)
4879
4881
 
4880
- Policy (at least one is required):
4882
+ Policy (at least one rule or warn-only flag is required):
4881
4883
  --require-tag <tags> Comma-separated; every test carrying the tag must pass
4882
4884
  --max-failed <n> Fail when more than n tests failed
4883
4885
  --max-new-regressions <n> Fail when more than n tests newly started failing
@@ -4890,7 +4892,7 @@ Policy (at least one is required):
4890
4892
  --max-new-leaks <n> Fail when more than n of those were never seen on the base branch
4891
4893
 
4892
4894
  Warn-only (reported, never fails the build):
4893
- --max-uncovered-changes <n> Warn when more than n changed files have no observed test reach
4895
+ --max-uncovered-changes <n> Warn when more than n changed files are uncovered changes: no test reached them
4894
4896
  --min-patch-coverage <percent> Warn when fewer of the changed lines ran, in the commit's combined coverage
4895
4897
 
4896
4898
  Other:
@@ -127,6 +127,8 @@ var PIWI_ENV_KEYS = {
127
127
  codeReachRoots: "PIWI_CODE_REACH_ROOTS",
128
128
  coverageBrowser: "PIWI_COVERAGE_BROWSER",
129
129
  coverageServer: "PIWI_COVERAGE_SERVER",
130
+ externalIdPattern: "PIWI_EXTERNAL_ID_PATTERN",
131
+ externalIdOnDuplicate: "PIWI_EXTERNAL_ID_ON_DUPLICATE",
130
132
  captureServerTraces: "PIWI_CAPTURE_SERVER_TRACES",
131
133
  captureResources: "PIWI_CAPTURE_RESOURCES",
132
134
  leakCheck: "PIWI_LEAK_CHECK",
@@ -217,6 +219,13 @@ function resolveOptions(raw) {
217
219
  coverage.browser = coverageBrowser;
218
220
  if (coverage.server === void 0 && coverageServer) coverage.server = coverageServer;
219
221
  if (coverage.browser !== void 0 || coverage.server !== void 0) mergedRaw.coverage = coverage;
222
+ const externalId = { ...mergedRaw.externalId };
223
+ const externalIdPattern = env[PIWI_ENV_KEYS.externalIdPattern];
224
+ const externalIdOnDuplicate = env[PIWI_ENV_KEYS.externalIdOnDuplicate];
225
+ if (externalId.pattern === void 0 && externalIdPattern) externalId.pattern = externalIdPattern;
226
+ if (externalId.onDuplicate === void 0 && externalIdOnDuplicate)
227
+ externalId.onDuplicate = externalIdOnDuplicate;
228
+ if (Object.keys(externalId).length > 0) mergedRaw.externalId = externalId;
220
229
  const opts = { ...DEFAULTS, ...mergedRaw };
221
230
  if (env[PIWI_ENV_KEYS.verbose] !== void 0) opts.verbose = env[PIWI_ENV_KEYS.verbose] === "true";
222
231
  return opts;
package/dist/index.d.ts CHANGED
@@ -129,6 +129,33 @@ interface PiwiDashboardOptions {
129
129
  */
130
130
  server?: 'off' | 'files' | 'functions' | 'lines';
131
131
  };
132
+ /**
133
+ * Match each test to its history on the dashboard by an external ID, the ID the test has in your own test
134
+ * management tool, so a test keeps its history when it is renamed or moved. A test declares its ID with a
135
+ * `piwi:id` annotation (`{ type: 'piwi:id', description: '12526' }`), which wins, or the ID is read from its title
136
+ * with `pattern`. Two tests declaring the same ID are reported before the first test runs, and matched by file,
137
+ * describe path and title instead.
138
+ */
139
+ externalId?: {
140
+ /**
141
+ * Where the ID sits in a test's own title (not its describe path): `/^(\d+), /` reads `12526` from
142
+ * `'12526, Log in with Email'`. The ID is the first capture group, or the whole match without a group. A string
143
+ * is compiled with `new RegExp(value)`; one that does not compile turns external IDs off for the run, with an
144
+ * error, but never fails it. Can also be set with `PIWI_EXTERNAL_ID_PATTERN`.
145
+ */
146
+ pattern?: RegExp | string;
147
+ /**
148
+ * Show the title without its ID on the dashboard, next to the ID. The whole match of `pattern` is removed from
149
+ * the title shown; the title stored and matched on stays whole. Defaults to `false`.
150
+ */
151
+ stripFromTitle?: boolean;
152
+ /**
153
+ * What two tests declaring the same ID do: `'report'` lists them in the console and on the run page; `'fail'`
154
+ * also fails the run once its tests have run. Either way the run matches them by file, describe path and title.
155
+ * Defaults to `'report'`. Can also be set with `PIWI_EXTERNAL_ID_ON_DUPLICATE`.
156
+ */
157
+ onDuplicate?: 'report' | 'fail';
158
+ };
132
159
  /**
133
160
  * Capture server-side spans for each API/document request the test makes,
134
161
  * read from the `X-Piwi-Trace` response header emitted by a Piwi
@@ -457,6 +484,8 @@ declare class PiwiDashboardReporter {
457
484
  private instanceId;
458
485
  private shardInfo;
459
486
  private metadata;
487
+ /** Each planned test's external ID and the IDs two tests share, settled in `onBegin`. */
488
+ private externalIds;
460
489
  private enabled;
461
490
  /**
462
491
  * True under `playwright test --list`, where Playwright still constructs this
@@ -515,6 +544,13 @@ declare class PiwiDashboardReporter {
515
544
  printsToStdio(): boolean;
516
545
  /** Playwright reporter hook: called once at the start of the test run */
517
546
  onBegin(config: FullConfig, suite: Suite): void;
547
+ /**
548
+ * Settle each planned test's external ID before any test runs, name the IDs two tests share, and record them on
549
+ * the run so the dashboard lists them too.
550
+ */
551
+ private planExternalIds;
552
+ /** The external ID fields of a test's payload: `null` tells the dashboard the test has none to match on. */
553
+ private externalIdFields;
518
554
  /** Playwright reporter hook: called when an individual test begins */
519
555
  onTestBegin(test: TestCase, result: TestResult): void;
520
556
  /** Track suite-level setup steps (beforeAll/afterAll) not tied to any test */
@@ -554,8 +590,14 @@ declare class PiwiDashboardReporter {
554
590
  * interrupted) is resolved once from the overall run status by the caller.
555
591
  */
556
592
  private materializeUnrunTests;
557
- /** Playwright reporter hook: called when the full test run finishes */
558
- onEnd(result: FullResult): Promise<void>;
593
+ /**
594
+ * Playwright reporter hook: called when the full test run finishes. Fails the run when two tests share an external
595
+ * ID and `externalId.onDuplicate` is `'fail'`.
596
+ */
597
+ onEnd(result: FullResult): Promise<{
598
+ status?: FullResult['status'];
599
+ } | void>;
600
+ private finishRun;
559
601
  }
560
602
 
561
603
  interface ResolveSelectionResult {
package/dist/index.js CHANGED
@@ -128,6 +128,8 @@ var PIWI_ENV_KEYS = {
128
128
  codeReachRoots: "PIWI_CODE_REACH_ROOTS",
129
129
  coverageBrowser: "PIWI_COVERAGE_BROWSER",
130
130
  coverageServer: "PIWI_COVERAGE_SERVER",
131
+ externalIdPattern: "PIWI_EXTERNAL_ID_PATTERN",
132
+ externalIdOnDuplicate: "PIWI_EXTERNAL_ID_ON_DUPLICATE",
131
133
  captureServerTraces: "PIWI_CAPTURE_SERVER_TRACES",
132
134
  captureResources: "PIWI_CAPTURE_RESOURCES",
133
135
  leakCheck: "PIWI_LEAK_CHECK",
@@ -247,6 +249,13 @@ function resolveOptions(raw) {
247
249
  coverage.browser = coverageBrowser;
248
250
  if (coverage.server === void 0 && coverageServer) coverage.server = coverageServer;
249
251
  if (coverage.browser !== void 0 || coverage.server !== void 0) mergedRaw.coverage = coverage;
252
+ const externalId = { ...mergedRaw.externalId };
253
+ const externalIdPattern = env[PIWI_ENV_KEYS.externalIdPattern];
254
+ const externalIdOnDuplicate = env[PIWI_ENV_KEYS.externalIdOnDuplicate];
255
+ if (externalId.pattern === void 0 && externalIdPattern) externalId.pattern = externalIdPattern;
256
+ if (externalId.onDuplicate === void 0 && externalIdOnDuplicate)
257
+ externalId.onDuplicate = externalIdOnDuplicate;
258
+ if (Object.keys(externalId).length > 0) mergedRaw.externalId = externalId;
250
259
  const opts = { ...DEFAULTS, ...mergedRaw };
251
260
  if (env[PIWI_ENV_KEYS.verbose] !== void 0) opts.verbose = env[PIWI_ENV_KEYS.verbose] === "true";
252
261
  return opts;
@@ -652,6 +661,9 @@ function toWireTestCase(tc) {
652
661
  tags: rest.tags ?? null,
653
662
  locks: rest.locks ?? null,
654
663
  testMeta: rest.testMeta ?? null,
664
+ // Kept `undefined` when unset (a `begin` event): `null` would tell the dashboard the test has no ID.
665
+ externalId: rest.externalId,
666
+ displayTitle: rest.displayTitle ?? null,
655
667
  locatorSnapshots: rest.locatorSnapshots || null,
656
668
  locatorPages: rest.locatorPages || null,
657
669
  codeReach: rest.codeReach || null,
@@ -6102,6 +6114,82 @@ function parseTestMetadata(annotations) {
6102
6114
  }
6103
6115
  return Object.keys(meta).length > 0 ? meta : null;
6104
6116
  }
6117
+ var MAX_EXTERNAL_ID_CHARS = 120;
6118
+ function normalizeExternalId(value) {
6119
+ if (typeof value !== "string") return void 0;
6120
+ const trimmed = value.trim();
6121
+ if (!trimmed || trimmed.length > MAX_EXTERNAL_ID_CHARS) return void 0;
6122
+ for (let i = 0; i < trimmed.length; i++) {
6123
+ const code = trimmed.charCodeAt(i);
6124
+ if (code < 32 || code === 127) return void 0;
6125
+ }
6126
+ return trimmed;
6127
+ }
6128
+ function parseExternalId(annotations) {
6129
+ if (!Array.isArray(annotations)) return void 0;
6130
+ let id;
6131
+ for (const entry of annotations) {
6132
+ if (!entry || typeof entry !== "object") continue;
6133
+ const { type, description } = entry;
6134
+ if (typeof type !== "string" || type.trim().toLowerCase() !== `${PIWI_ANNOTATION_PREFIX}id`) continue;
6135
+ const value = normalizeExternalId(description);
6136
+ if (value) id = value;
6137
+ }
6138
+ return id;
6139
+ }
6140
+ function compileExternalIdPattern(pattern) {
6141
+ if (typeof pattern === "string") return new RegExp(pattern);
6142
+ return new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, ""));
6143
+ }
6144
+ function readExternalIdFromTitle(title, pattern) {
6145
+ const match = pattern.exec(title);
6146
+ if (!match) return null;
6147
+ const externalId = normalizeExternalId(match.length > 1 ? match[1] : match[0]);
6148
+ if (!externalId) return null;
6149
+ const rest = (title.slice(0, match.index) + title.slice(match.index + match[0].length)).trim();
6150
+ return rest ? { externalId, displayTitle: rest } : { externalId };
6151
+ }
6152
+ var MAX_EXTERNAL_ID_DUPLICATES = 50;
6153
+ var MAX_EXTERNAL_ID_DUPLICATE_TESTS = 10;
6154
+ function duplicateTestKey(test) {
6155
+ return `${test.filePath}\0${(test.suitePath ?? []).join("")}\0${test.title}`;
6156
+ }
6157
+ function readDuplicateTest(raw) {
6158
+ if (!raw || typeof raw !== "object") return null;
6159
+ const { filePath: filePath2, suitePath, title, line } = raw;
6160
+ if (typeof filePath2 !== "string" || typeof title !== "string") return null;
6161
+ const test = { filePath: filePath2, title };
6162
+ if (Array.isArray(suitePath) && suitePath.every((s) => typeof s === "string") && suitePath.length > 0)
6163
+ test.suitePath = suitePath;
6164
+ if (typeof line === "number" && Number.isFinite(line)) test.line = line;
6165
+ return test;
6166
+ }
6167
+ function mergeExternalIdDuplicates(...lists) {
6168
+ const byId = /* @__PURE__ */ new Map();
6169
+ for (const list of lists) {
6170
+ if (!Array.isArray(list)) continue;
6171
+ for (const entry of list) {
6172
+ if (!entry || typeof entry !== "object") continue;
6173
+ const externalId = normalizeExternalId(entry.externalId);
6174
+ const tests = entry.tests;
6175
+ if (!externalId || !Array.isArray(tests)) continue;
6176
+ const read = tests.map(readDuplicateTest).filter((test) => test !== null);
6177
+ if (read.length === 0) continue;
6178
+ let known = byId.get(externalId);
6179
+ if (!known) {
6180
+ if (byId.size >= MAX_EXTERNAL_ID_DUPLICATES) continue;
6181
+ known = /* @__PURE__ */ new Map();
6182
+ byId.set(externalId, known);
6183
+ }
6184
+ for (const test of read) {
6185
+ if (known.size >= MAX_EXTERNAL_ID_DUPLICATE_TESTS) break;
6186
+ const key = duplicateTestKey(test);
6187
+ if (!known.has(key)) known.set(key, test);
6188
+ }
6189
+ }
6190
+ }
6191
+ return [...byId].map(([externalId, tests]) => ({ externalId, tests: [...tests.values()] }));
6192
+ }
6105
6193
 
6106
6194
  // src/internal/collect/test-meta.ts
6107
6195
  function collectTestTags(test) {
@@ -6115,6 +6203,84 @@ function collectTestMetadata(annotations) {
6115
6203
  return parseTestMetadata(annotations);
6116
6204
  }
6117
6205
 
6206
+ // src/internal/collect/external-ids.ts
6207
+ function naturalKey(test) {
6208
+ return `${test.filePath}\0${test.suitePath.join("")}\0${test.title}`;
6209
+ }
6210
+ function duplicateTest(test) {
6211
+ return {
6212
+ filePath: test.filePath,
6213
+ ...test.suitePath.length ? { suitePath: test.suitePath } : {},
6214
+ title: test.title,
6215
+ line: test.line
6216
+ };
6217
+ }
6218
+ function planExternalIds(tests, options = {}) {
6219
+ let pattern = null;
6220
+ let patternError;
6221
+ if (options.pattern !== void 0 && options.pattern !== "") {
6222
+ try {
6223
+ pattern = compileExternalIdPattern(options.pattern);
6224
+ } catch (error) {
6225
+ patternError = error instanceof Error ? error.message : String(error);
6226
+ }
6227
+ }
6228
+ const found = /* @__PURE__ */ new Map();
6229
+ const holders = /* @__PURE__ */ new Map();
6230
+ for (const test of tests) {
6231
+ let id = null;
6232
+ const declared = parseExternalId(test.annotations);
6233
+ if (declared) id = { externalId: declared };
6234
+ else if (pattern) {
6235
+ const read = readExternalIdFromTitle(test.title, pattern);
6236
+ if (read) id = options.stripFromTitle ? read : { externalId: read.externalId };
6237
+ }
6238
+ if (!id) continue;
6239
+ found.set(test.testId, id);
6240
+ let keys = holders.get(id.externalId);
6241
+ if (!keys) holders.set(id.externalId, keys = /* @__PURE__ */ new Map());
6242
+ const key = naturalKey(test);
6243
+ if (!keys.has(key)) keys.set(key, test);
6244
+ }
6245
+ const duplicates = [];
6246
+ const duplicateIds = /* @__PURE__ */ new Set();
6247
+ for (const [externalId, keys] of holders) {
6248
+ if (keys.size < 2) continue;
6249
+ duplicateIds.add(externalId);
6250
+ duplicates.push({ externalId, tests: [...keys.values()].map(duplicateTest) });
6251
+ }
6252
+ const byTestId = /* @__PURE__ */ new Map();
6253
+ for (const [testId, id] of found) if (!duplicateIds.has(id.externalId)) byTestId.set(testId, id);
6254
+ return {
6255
+ byTestId,
6256
+ duplicates: mergeExternalIdDuplicates(duplicates),
6257
+ ...patternError ? { patternError } : {}
6258
+ };
6259
+ }
6260
+ function formatExternalIdDuplicates(duplicates, onDuplicate) {
6261
+ if (duplicates.length === 0) return [];
6262
+ const lines = [];
6263
+ for (const { externalId, tests } of duplicates) {
6264
+ lines.push(
6265
+ `External ID ${externalId} is declared by ${tests.length} tests; this run matches them by file, describe path and title:`
6266
+ );
6267
+ for (const test of tests) {
6268
+ const where = test.line != null ? `${test.filePath}:${test.line}` : test.filePath;
6269
+ lines.push(` ${where} ${[...test.suitePath ?? [], test.title].join(" \u203A ")}`);
6270
+ }
6271
+ }
6272
+ lines.push(
6273
+ onDuplicate === "fail" ? "Give each test its own ID: the run fails while two tests share one (externalId.onDuplicate: fail)." : "Give each test its own ID, or set externalId.onDuplicate: 'fail' to fail the run instead."
6274
+ );
6275
+ return lines;
6276
+ }
6277
+ function resolveOnDuplicate(raw) {
6278
+ if (raw === void 0 || raw === null || raw === "") return { onDuplicate: "report" };
6279
+ const value = String(raw).trim().toLowerCase();
6280
+ if (value === "report" || value === "fail") return { onDuplicate: value };
6281
+ return { onDuplicate: "report", invalid: String(raw) };
6282
+ }
6283
+
6118
6284
  // ../core/src/error-text.ts
6119
6285
  function joinErrorMessages(errors) {
6120
6286
  if (!errors || errors.length === 0) return null;
@@ -10632,6 +10798,8 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
10632
10798
  this.instanceId = "";
10633
10799
  this.shardInfo = null;
10634
10800
  this.metadata = {};
10801
+ /** Each planned test's external ID and the IDs two tests share, settled in `onBegin`. */
10802
+ this.externalIds = null;
10635
10803
  this.isFullRun = true;
10636
10804
  this.filterDetails = null;
10637
10805
  /** Configured `maxFailures` (0 = unlimited) — disambiguates an interrupted run's unrun reason. */
@@ -10780,6 +10948,7 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
10780
10948
  this.metadata = flakeRunMetadata(stamp, this.metadata);
10781
10949
  }
10782
10950
  this.plannedTests = suite.allTests();
10951
+ this.planExternalIds();
10783
10952
  this.shardInfo = resolveShardInfo(config);
10784
10953
  if (this.shardInfo) {
10785
10954
  this.logger.info(`Shard ${this.shardInfo.current}/${this.shardInfo.total} detected`);
@@ -10824,6 +10993,44 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
10824
10993
  this.plannedTests.length
10825
10994
  );
10826
10995
  }
10996
+ /**
10997
+ * Settle each planned test's external ID before any test runs, name the IDs two tests share, and record them on
10998
+ * the run so the dashboard lists them too.
10999
+ */
11000
+ planExternalIds() {
11001
+ const options = this.options.externalId ?? {};
11002
+ const plan = planExternalIds(
11003
+ this.plannedTests.map((test) => ({
11004
+ testId: test.id,
11005
+ filePath: testFile(test, this.testPathBase),
11006
+ suitePath: this.metadataCollector.getSuiteInfo(test).suitePath ?? [],
11007
+ title: test.title,
11008
+ line: test.location.line,
11009
+ annotations: test.annotations ?? []
11010
+ })),
11011
+ options
11012
+ );
11013
+ if (plan.patternError) {
11014
+ this.logger.error(
11015
+ `externalId.pattern is not a valid regular expression, so no ID is read from titles: ${plan.patternError}`
11016
+ );
11017
+ }
11018
+ const { onDuplicate, invalid } = resolveOnDuplicate(options.onDuplicate);
11019
+ if (invalid !== void 0) {
11020
+ this.logger.warn(
11021
+ `externalId.onDuplicate is "${invalid}"; expected 'report' or 'fail', so duplicates are reported.`
11022
+ );
11023
+ }
11024
+ for (const line of formatExternalIdDuplicates(plan.duplicates, onDuplicate)) this.logger.warn(line);
11025
+ if (plan.duplicates.length > 0) this.metadata = { ...this.metadata, externalIdDuplicates: plan.duplicates };
11026
+ this.externalIds = plan;
11027
+ }
11028
+ /** The external ID fields of a test's payload: `null` tells the dashboard the test has none to match on. */
11029
+ externalIdFields(test) {
11030
+ if (!this.externalIds) return {};
11031
+ const id = this.externalIds.byTestId.get(test.id);
11032
+ return { externalId: id?.externalId ?? null, displayTitle: id?.displayTitle ?? null };
11033
+ }
10827
11034
  /** Playwright reporter hook: called when an individual test begins */
10828
11035
  onTestBegin(test, result) {
10829
11036
  this.lastTestWatch?.begin(test.id);
@@ -10951,6 +11158,7 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
10951
11158
  tags: tags.length ? tags : null,
10952
11159
  locks: locks.length ? locks : null,
10953
11160
  testMeta: collectTestMetadata(annotations),
11161
+ ...this.externalIdFields(test),
10954
11162
  // An annotation-less skip reclassified to `didnotrun` is a serial-group
10955
11163
  // cascade: an earlier test failed and Playwright skipped the rest.
10956
11164
  didNotRunReason: status === "didnotrun" ? "previous-failure" : null,
@@ -11153,6 +11361,7 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
11153
11361
  tags: tags.length ? tags : null,
11154
11362
  locks: locks.length ? locks : null,
11155
11363
  testMeta: collectTestMetadata(declaredAnnotations),
11364
+ ...this.externalIdFields(test),
11156
11365
  didNotRunReason: reason,
11157
11366
  expectedStatus: test.expectedStatus ?? null
11158
11367
  };
@@ -11164,8 +11373,23 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
11164
11373
  }
11165
11374
  }
11166
11375
  }
11167
- /** Playwright reporter hook: called when the full test run finishes */
11376
+ /**
11377
+ * Playwright reporter hook: called when the full test run finishes. Fails the run when two tests share an external
11378
+ * ID and `externalId.onDuplicate` is `'fail'`.
11379
+ */
11168
11380
  async onEnd(result) {
11381
+ const duplicates = this.externalIds?.duplicates.length ?? 0;
11382
+ const failsOnDuplicates = duplicates > 0 && resolveOnDuplicate(this.options.externalId?.onDuplicate).onDuplicate === "fail";
11383
+ const overridden = failsOnDuplicates && result?.status === "passed";
11384
+ await this.finishRun(overridden ? { ...result, status: "failed" } : result);
11385
+ if (failsOnDuplicates) {
11386
+ this.logger.error(
11387
+ `Failing the run: ${duplicates} external ID${duplicates === 1 ? " is" : "s are"} declared by more than one test.`
11388
+ );
11389
+ return { status: "failed" };
11390
+ }
11391
+ }
11392
+ async finishRun(result) {
11169
11393
  if (this.listMode || !this.enabled) return;
11170
11394
  if (this.startTime === null) {
11171
11395
  this.logger.debug("The run stopped before any test began \u2014 nothing to report.");
@@ -2648,6 +2648,8 @@ var PIWI_ENV_KEYS = {
2648
2648
  codeReachRoots: "PIWI_CODE_REACH_ROOTS",
2649
2649
  coverageBrowser: "PIWI_COVERAGE_BROWSER",
2650
2650
  coverageServer: "PIWI_COVERAGE_SERVER",
2651
+ externalIdPattern: "PIWI_EXTERNAL_ID_PATTERN",
2652
+ externalIdOnDuplicate: "PIWI_EXTERNAL_ID_ON_DUPLICATE",
2651
2653
  captureServerTraces: "PIWI_CAPTURE_SERVER_TRACES",
2652
2654
  captureResources: "PIWI_CAPTURE_RESOURCES",
2653
2655
  leakCheck: "PIWI_LEAK_CHECK",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piwitests/reporter",
3
- "version": "0.57.0",
3
+ "version": "0.58.0",
4
4
  "description": "Playwright reporter that streams results, traces and HTML reports to a Piwi Dashboard instance",
5
5
  "url": "https://github.com/PiwiTests/platform",
6
6
  "homepage": "https://piwitests.dev",
@@ -21,8 +21,9 @@ view, or the reporter CLI with `PIWI_DASHBOARD_URL` / `PIWI_API_KEY` /
21
21
  ## Steps
22
22
 
23
23
  1. **Find the gaps.** On a change, call `get_change_coverage` for the current
24
- branch first — the uncovered changed files are the gaps that matter now.
25
- Otherwise call `list_scenario_gaps` for the project. Filter to what you are
24
+ branch first — the files whose `reach` is `unreached` (the uncovered
25
+ changes) and the `lines.notRun` of the files it did reach are what the
26
+ change left untested. Otherwise call `list_scenario_gaps` for the project. Filter to what you are
26
27
  working on with `class`, `feature`, `minScore` or `pr`. Read the classes:
27
28
  - **blind-spot** — no trusted test reaches it (a route, a page, a control, a
28
29
  new error path).