@piwitests/reporter 0.63.0 → 0.64.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/README.md CHANGED
@@ -67,7 +67,7 @@ Most options can also be set via a `PIWI_*` environment variable (config wins ov
67
67
  ## Requirements
68
68
 
69
69
  - Node.js 20 or higher (the reporter runs inside your test project — the dashboard *server* itself targets Node 22+, or use its Docker image)
70
- - Playwright Test 1.61.1 or higher
70
+ - Playwright Test 1.59 or higher (a few features need a later version, named in their docs)
71
71
  - A running Piwi Dashboard server
72
72
 
73
73
  ## Contributing
package/dist/cli/index.js CHANGED
@@ -1844,13 +1844,27 @@ function safeLocator(expr) {
1844
1844
  if (!chain || !chainIsSafe(chain)) return null;
1845
1845
  return { chain, text: renderLocatorChain(chain) };
1846
1846
  }
1847
- function chooseLocator(target, options) {
1847
+ var TEXT_CHECKS = /* @__PURE__ */ new Set(["toHaveText", "toContainText"]);
1848
+ function checkedText(step) {
1849
+ const assertion = step?.action === "assert" ? step.assertion : null;
1850
+ if (!assertion || !TEXT_CHECKS.has(assertion.matcher)) return null;
1851
+ const text = assertion.expected?.replace(/\s+/g, " ").trim();
1852
+ return text ? text : null;
1853
+ }
1854
+ function findsByText(chain, text) {
1855
+ const holds = (arg) => arg?.type === "string" && arg.value.replace(/\s+/g, " ").trim().includes(text);
1856
+ const option = (arg, key) => arg?.type === "object" ? arg.entries.find(([name]) => name === key)?.[1] : void 0;
1857
+ return chain.calls.some(
1858
+ (call) => call.method === "getByText" && holds(call.args[0]) || call.method === "getByRole" && holds(option(call.args[1], "name")) || call.args.some((arg) => holds(option(arg, "hasText")))
1859
+ );
1860
+ }
1861
+ function chooseLocator(target, options, check) {
1848
1862
  if (!target) return null;
1849
- const candidates = target.alternatives.flatMap((alt) => {
1863
+ const recorded = target.alternatives.flatMap((alt) => {
1850
1864
  const safe = safeLocator(alt.locator);
1851
1865
  return safe ? [safe] : [];
1852
1866
  });
1853
- if (candidates.length === 0) return null;
1867
+ if (recorded.length === 0) return null;
1854
1868
  const levels = /* @__PURE__ */ new Map();
1855
1869
  const levelOf = (c) => {
1856
1870
  let level = levels.get(c.text);
@@ -1860,6 +1874,9 @@ function chooseLocator(target, options) {
1860
1874
  }
1861
1875
  return level;
1862
1876
  };
1877
+ const text = checkedText(check);
1878
+ const apart = text ? recorded.filter((c) => !findsByText(c.chain, text) && levelOf(c) !== "brittle") : [];
1879
+ const candidates = apart.length ? apart : recorded;
1863
1880
  const prefer = options.preferLocators;
1864
1881
  if (prefer && prefer.size > 0) {
1865
1882
  const known = candidates.find((c) => prefer.has(c.text) && levelOf(c) !== "brittle");
@@ -1940,7 +1957,7 @@ function urlForCode(url, ctx) {
1940
1957
  return isPath && ctx.origin ? `${ctx.origin}${url}` : url;
1941
1958
  }
1942
1959
  function locatorForStep(step, index, ctx) {
1943
- const chosen = chooseLocator(step.target, ctx.options);
1960
+ const chosen = chooseLocator(step.target, ctx.options, step);
1944
1961
  if (!chosen) {
1945
1962
  ctx.warnings.push({ step: index, code: "no-locator", message: "No locator was captured for this element." });
1946
1963
  return `${ctx.page}.locator(${quote("/* no locator captured */")})`;
@@ -2407,19 +2424,11 @@ function renderSpec(session, options = {}) {
2407
2424
  const needs = needsLines("");
2408
2425
  return result([...needs, ...testOpening, ...testBody, `});`, ``], needs.length + testOpening.length);
2409
2426
  }
2410
- const lines = [
2411
- // Package name interpolated rather than adjoining the preceding keyword
2412
- // directly, so this generated-code line does not itself look like a real
2413
- // import to the boundary test in tests/boundary.test.ts.
2414
- `import { test, expect } from ${quote(options.testImport?.trim() || "@playwright/test")};`,
2415
- ...importLines,
2416
- ``,
2417
- ...testOpening,
2418
- ...testBody,
2419
- `});`,
2420
- ``
2421
- ];
2422
- return result(lines, 1 + importLines.length + 1 + testOpening.length);
2427
+ const testModule = options.testImport?.trim() || "@playwright/test";
2428
+ const expectModule = options.expectImport?.trim() || testModule;
2429
+ const testImports = expectModule === testModule ? [`import { test, expect } from ${quote(testModule)};`] : [`import { test } from ${quote(testModule)};`, `import { expect } from ${quote(expectModule)};`];
2430
+ const lines = [...testImports, ...importLines, ``, ...testOpening, ...testBody, `});`, ``];
2431
+ return result(lines, testImports.length + importLines.length + 1 + testOpening.length);
2423
2432
  }
2424
2433
 
2425
2434
  // ../core/src/steps.ts
@@ -3178,14 +3187,23 @@ var DEFAULT_COVERAGE_EXCLUDES = [
3178
3187
  "tests/",
3179
3188
  "test/",
3180
3189
  "__tests__/",
3190
+ "playwright.config.*",
3181
3191
  "*Tests/",
3182
3192
  "*.Tests/",
3183
3193
  "obj/",
3184
3194
  "bin/",
3185
3195
  "node_modules/",
3196
+ "dist/",
3197
+ ".next/",
3198
+ ".nuxt/",
3199
+ ".output/",
3200
+ ".svelte-kit/",
3186
3201
  "*.g.cs",
3187
3202
  "*.Designer.cs",
3188
- "*.generated.*"
3203
+ "*.generated.*",
3204
+ "*.gen.*",
3205
+ "__generated__/",
3206
+ "*.d.ts"
3189
3207
  ];
3190
3208
  var DEFAULT_COVERAGE_SCOPE = { include: [], exclude: [...DEFAULT_COVERAGE_EXCLUDES] };
3191
3209
  var MAX_SCOPE_PATTERNS = 200;
@@ -6825,15 +6843,22 @@ function diffImpactInput(files) {
6825
6843
  var fs17 = __toESM(require("fs"));
6826
6844
  var path21 = __toESM(require("path"));
6827
6845
  var import_node_module2 = require("module");
6846
+
6847
+ // src/internal/support/playwright-version.ts
6848
+ function playwrightVersionAtLeast(version, minimum) {
6849
+ const have = version ? /^(\d+)\.(\d+)/.exec(version) : null;
6850
+ const want = /^(\d+)\.(\d+)/.exec(minimum);
6851
+ if (!have || !want) return false;
6852
+ const major = Number(have[1]);
6853
+ const wantMajor = Number(want[1]);
6854
+ return major > wantMajor || major === wantMajor && Number(have[2]) >= Number(want[2]);
6855
+ }
6856
+
6857
+ // src/cli/add-reporter.ts
6828
6858
  var REPORTER_PACKAGE2 = "@piwitests/reporter";
6829
6859
  var ADD_REPORTER_FLAG = "--add-reporter";
6830
- var ADD_REPORTER_MIN = { major: 1, minor: 63 };
6831
6860
  function playwrightSupportsAddReporter(version) {
6832
- const match = version ? /^(\d+)\.(\d+)/.exec(version) : null;
6833
- if (!match) return false;
6834
- const major = Number(match[1]);
6835
- const minor = Number(match[2]);
6836
- return major > ADD_REPORTER_MIN.major || major === ADD_REPORTER_MIN.major && minor >= ADD_REPORTER_MIN.minor;
6861
+ return playwrightVersionAtLeast(version, "1.63");
6837
6862
  }
6838
6863
  function configHasPiwiReporter(source) {
6839
6864
  return new RegExp(REPORTER_PACKAGE2.replace(/[/\\]/g, "\\$&")).test(source);
@@ -7790,6 +7815,15 @@ function readOption9(argv, name) {
7790
7815
  function escapeRegex2(s) {
7791
7816
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7792
7817
  }
7818
+ function probePlaywrightArgs(playwrightArgs2, testTitles) {
7819
+ const given = (...flags) => playwrightArgs2.some((arg) => flags.some((flag) => arg === flag || arg.startsWith(`${flag}=`)));
7820
+ const grep = testTitles.filter((t) => !!t).map(escapeRegex2).join("|");
7821
+ const args = [...playwrightArgs2];
7822
+ if (grep && !given("--grep", "-g")) args.push("--grep", grep);
7823
+ if (!given("--retries")) args.push("--retries=0");
7824
+ if (!given("--max-failures", "-x")) args.push("--max-failures=0");
7825
+ return args;
7826
+ }
7793
7827
  async function runProbe(argv) {
7794
7828
  const sep6 = argv.indexOf("--");
7795
7829
  const own = sep6 < 0 ? argv : argv.slice(0, sep6);
@@ -7844,9 +7878,10 @@ async function runProbe(argv) {
7844
7878
  const runOutputFile = path24.join(dir, "run.json");
7845
7879
  fs20.writeFileSync(planFile, JSON.stringify(plan));
7846
7880
  fs20.writeFileSync(resultsFile, "");
7847
- const grep = plan.items.map((i) => i.testTitle).filter(Boolean).map(escapeRegex2).join("|");
7848
- const pwArgs = [...playwrightArgs2];
7849
- if (grep && !playwrightArgs2.includes("--grep") && !playwrightArgs2.includes("-g")) pwArgs.push("--grep", grep);
7881
+ const pwArgs = probePlaywrightArgs(
7882
+ playwrightArgs2,
7883
+ plan.items.map((i) => i.testTitle)
7884
+ );
7850
7885
  const childEnv = {
7851
7886
  ...env,
7852
7887
  [PIWI_PROBE_ENV.flag]: "1",
@@ -225,7 +225,7 @@ var PIWI_DESKTOP_CONFIG_ENV = "PIWI_DESKTOP_CONFIG";
225
225
  var PIWI_SHARD_ENV = "PIWI_SHARD";
226
226
  function readBool(val) {
227
227
  if (val === void 0) return void 0;
228
- return val === "true";
228
+ return ["true", "1", "yes", "on"].includes(val.trim().toLowerCase());
229
229
  }
230
230
  var ENV_FALLBACK_SPECS = [
231
231
  { option: "serverUrl", env: PIWI_ENV_KEYS.serverUrl, kind: "string" },
@@ -335,7 +335,7 @@ function resolveOptions(raw, configDir) {
335
335
  externalId.onDuplicate = externalIdOnDuplicate;
336
336
  if (Object.keys(externalId).length > 0) mergedRaw.externalId = externalId;
337
337
  const opts = { ...DEFAULTS, ...mergedRaw };
338
- if (env[PIWI_ENV_KEYS.verbose] !== void 0) opts.verbose = env[PIWI_ENV_KEYS.verbose] === "true";
338
+ if (env[PIWI_ENV_KEYS.verbose] !== void 0) opts.verbose = readBool(env[PIWI_ENV_KEYS.verbose]);
339
339
  return opts;
340
340
  }
341
341
 
@@ -690,6 +690,65 @@ function normalizeRef(ref) {
690
690
  return ref.replace(/^refs\/heads\//, "");
691
691
  }
692
692
 
693
+ // ../core/src/locator-chain.ts
694
+ var LOCATING_METHODS = /* @__PURE__ */ new Set([
695
+ "getByRole",
696
+ "getByTestId",
697
+ "getByText",
698
+ "getByLabel",
699
+ "getByPlaceholder",
700
+ "getByAltText",
701
+ "getByTitle",
702
+ "locator",
703
+ "frameLocator"
704
+ ]);
705
+ var NARROWING_METHODS = /* @__PURE__ */ new Set([
706
+ "filter",
707
+ "first",
708
+ "last",
709
+ "nth",
710
+ "and",
711
+ "or",
712
+ "visible",
713
+ "contentFrame",
714
+ "owner"
715
+ ]);
716
+ var LOCATOR_CHAIN_METHODS = /* @__PURE__ */ new Set([...LOCATING_METHODS, ...NARROWING_METHODS]);
717
+
718
+ // ../core/src/mask.ts
719
+ var SECRET_AUTOCOMPLETE = [
720
+ "current-password",
721
+ "new-password",
722
+ "one-time-code",
723
+ "cc-number",
724
+ "cc-csc"
725
+ ];
726
+ var SECRET_FIELD_SELECTOR = [
727
+ 'input[type="password" i]',
728
+ ...SECRET_AUTOCOMPLETE.flatMap((token) => [
729
+ `input[autocomplete~="${token}" i]`,
730
+ `textarea[autocomplete~="${token}" i]`
731
+ ])
732
+ ].join(", ");
733
+ var SECRET_SHAPES = [
734
+ /\beyJ[\w-]{5,}\.[\w-]{5,}\.[\w-]*/g,
735
+ /\bBearer(?:\s+|%20)[\w.~+/-]{8,}=*/gi,
736
+ /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{8,}/g,
737
+ /\bwhsec_[A-Za-z0-9]{20,}/g,
738
+ /\bsk-(?=[\w-]{0,200}\d)(?=[\w-]{0,200}[A-Z])[\w-]{32,}/g,
739
+ /\bglpat-[\w-]{20,}/g,
740
+ /\bnpm_[A-Za-z0-9]{36}\b/g,
741
+ /\bSK[0-9a-f]{32}\b/g,
742
+ /\bAKIA[0-9A-Z]{16}\b/g,
743
+ /\bAIza[\w-]{30,}/g,
744
+ /\bgh[opsu]_[A-Za-z0-9]{20,}/g,
745
+ /\bgithub_pat_\w{20,}/g,
746
+ /\bxox[abprs]-[\w-]{8,}/g
747
+ ];
748
+ var WHOLE_SHAPES = SECRET_SHAPES.map(
749
+ (re) => new RegExp(`^(?:${re.source})$`, re.flags.replace("g", ""))
750
+ );
751
+
693
752
  // ../core/src/live-view.ts
694
753
  var LIVE_FRAME_BYTE_RATE = 256 * 1024;
695
754
  var MAX_LIVE_FRAME = 192 * 1024;
package/dist/index.d.ts CHANGED
@@ -2,11 +2,8 @@ import { FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright
2
2
  import { PlaywrightTestConfig, Locator, TestType, Page, Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions } from '@playwright/test';
3
3
  export { PlaywrightTestConfig } from '@playwright/test';
4
4
  export { PiwiFixtures, PiwiWorkerFixtures, extendPiwiFixtures, piwiFixtures } from './internal/capture/capture-fixtures.js';
5
- import '@piwitests/picker-dom';
6
- import '@piwitests/core/page-interaction';
7
- import '@piwitests/core/wire';
8
- import '@piwitests/core/run-channel';
9
- import '@piwitests/core/code-reach';
5
+ import './types-CGr9jJZ_.js';
6
+ import './wire-7zsqlBoY.js';
10
7
 
11
8
  /**
12
9
  * Options for configuring the Piwi Dashboard reporter.
@@ -59,8 +56,9 @@ interface PiwiDashboardOptions {
59
56
  collectPerformanceMetrics?: boolean;
60
57
  /**
61
58
  * Capture per-action locator snapshots that power failure-time healing
62
- * suggestions. Adds a small per-action cost (one DOM read, sometimes an ARIA
63
- * snapshot) in the test worker. Defaults to `true`; automatically disabled
59
+ * suggestions. Adds a small per-action cost (one DOM read, rarely two,
60
+ * sometimes an ARIA snapshot) and one DOM read per new document in the test
61
+ * worker. Defaults to `true`; automatically disabled
64
62
  * when `collectPerformanceMetrics` is `false` (the reporter discards the data
65
63
  * in that case anyway). Can also be forced off with `PIWI_CAPTURE_LOCATORS=false`.
66
64
  */
@@ -324,7 +322,7 @@ interface PiwiDashboardOptions {
324
322
  */
325
323
  maxStreamBufferBytes?: number;
326
324
  /**
327
- * Fail the run when any test was flaky (passed only after a retry). Forwarded
325
+ * Fail the run when any test was flaky (one attempt failed and another passed). Forwarded
328
326
  * to Playwright's native `failOnFlakyTests` config option (Playwright 1.52+)
329
327
  * when the reporter is installed via `wrapConfig`, so a flaky-only run exits
330
328
  * non-zero without any server round-trip. Defaults to `false`. Can also be
@@ -505,6 +503,10 @@ declare class PiwiDashboardReporter {
505
503
  private plannedTests;
506
504
  /** Ids of tests that actually reported via `onTestEnd`, to find the ones that never ran. */
507
505
  private reportedTestIds;
506
+ /** The spec files said to have run without the capture fixtures in a run that can stop (`noteUncaptured`). */
507
+ private uncapturedSpecs;
508
+ /** The live steps begun and not ended, by step, sent again when someone opens the run (`onWatched`). */
509
+ private openSteps;
508
510
  /** Per-test attempt history, keyed by `test.id`; snapshotted onto every attempt's payload. */
509
511
  private attemptsByTest;
510
512
  /** The folder test files are named relative to: the Playwright config's, set in `onBegin`. */
@@ -583,8 +585,8 @@ declare class PiwiDashboardReporter {
583
585
  * Name the run's live view on the instance for the workers, which Playwright starts after this hook with this
584
586
  * environment: the key the instance gave the global setup, for a project that turned its live view on, for this run
585
587
  * alone. A CI run's workers stream with it at once; a run outside CI's hold it until a person shares the run (Share
586
- * live), and such a run without a key names why instead, for the surfaces that would share it. Read before the
587
- * stream starts, which takes the setup file.
588
+ * live), and such a run without a key names why instead, for the surfaces that would share it; a CI run without one
589
+ * says why in its log (`ciLiveViewNote`). Read before the stream starts, which takes the setup file.
588
590
  */
589
591
  private shareLiveView;
590
592
  /**
@@ -607,6 +609,15 @@ declare class PiwiDashboardReporter {
607
609
  * human watches; the meaningful readout is the assertion around them.
608
610
  */
609
611
  private static isLiveStep;
612
+ /**
613
+ * In a run that can stop (breakpoints, or a run channel to an editor or the desktop app), say of the first spec file
614
+ * whose test made Playwright calls without the capture fixtures, where the stops happen, that it never stops at a
615
+ * breakpoint nor at Pause, which nothing else says; the others are named in one line as the run ends
616
+ * (`noteOtherUncaptured`).
617
+ */
618
+ private noteUncaptured;
619
+ /** The spec files past the first that ran without the capture fixtures, in one line (`noteUncaptured`). */
620
+ private noteOtherUncaptured;
610
621
  /** Playwright reporter hook: called when a step (including hook/fixture) begins */
611
622
  onStepBegin(test: TestCase | undefined, _result: TestResult | undefined, step: any): void;
612
623
  /** Playwright reporter hook: called when a step (including hook/fixture) ends */