@piwitests/reporter 0.62.0 → 0.63.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.
@@ -131,6 +131,16 @@ declare function renderFailing(failed: FailedLocatorInfo): string;
131
131
  * present (so the failure wasn't a rename), or when no candidate is confident.
132
132
  */
133
133
  declare function suggestLocatorsFromAria(failed: FailedLocatorInfo, ariaSnapshot: string | null): LocatorSuggestion | null;
134
+ /**
135
+ * The local path a stack frame's file names: a `file://` URL (an ES-module suite's frames) through `fileURLToPath`,
136
+ * any other path as it is. `windows` reads the URL as Windows does. Null for a URL that names no local file.
137
+ */
138
+ declare function framePath(file: string, windows?: boolean): string | null;
139
+ /**
140
+ * A stack frame's file relative to `cwd`, with forward slashes: the form Playwright writes a location in its error
141
+ * messages. A file on another drive stays absolute. Null for a URL that names no local file.
142
+ */
143
+ declare function callSitePath(file: string, cwd?: string, windows?: boolean): string | null;
134
144
  /**
135
145
  * Capture the calling test's source location (`file:line:col`) from the current
136
146
  * stack, for stamping onto a locator snapshot. The path is made cwd-relative so
@@ -147,4 +157,4 @@ declare function suggestLocatorsFromAria(failed: FailedLocatorInfo, ariaSnapshot
147
157
  */
148
158
  declare function captureCallerLocation(stack?: string): string | null;
149
159
 
150
- export { ACTION_METHODS, CHAIN_METHODS, EXPECT_CAPTURE_EXPRESSIONS, EXPECT_METHOD, FORM_FIELD_TAGS, type FailedLocatorInfo, LOCATOR_CREATING_CHAINS, LOCATOR_METHODS, type LocatorSuggestion, type NameSource, captureCallerLocation, dedupeSnapshotsByLocation, exactAccessibleName, extractAccessibleName, renderFailing, suggestLocatorsFromAria };
160
+ export { ACTION_METHODS, CHAIN_METHODS, EXPECT_CAPTURE_EXPRESSIONS, EXPECT_METHOD, FORM_FIELD_TAGS, type FailedLocatorInfo, LOCATOR_CREATING_CHAINS, LOCATOR_METHODS, type LocatorSuggestion, type NameSource, callSitePath, captureCallerLocation, dedupeSnapshotsByLocation, exactAccessibleName, extractAccessibleName, framePath, renderFailing, suggestLocatorsFromAria };
@@ -41,11 +41,13 @@ __export(locator_healing_exports, {
41
41
  LOCATOR_METHODS: () => LOCATOR_METHODS,
42
42
  TAG_TO_ROLE: () => TAG_TO_ROLE,
43
43
  approximateAccessibleName: () => approximateAccessibleName,
44
+ callSitePath: () => callSitePath,
44
45
  captureCallerLocation: () => captureCallerLocation,
45
46
  classifyCssStability: () => classifyCssStability,
46
47
  dedupeSnapshotsByLocation: () => dedupeSnapshotsByLocation,
47
48
  exactAccessibleName: () => exactAccessibleName,
48
49
  extractAccessibleName: () => extractAccessibleName,
50
+ framePath: () => framePath,
49
51
  generateAlternatives: () => generateAlternatives,
50
52
  headingLevel: () => headingLevel,
51
53
  isAutoGenerated: () => isAutoGenerated,
@@ -55,6 +57,7 @@ __export(locator_healing_exports, {
55
57
  });
56
58
  module.exports = __toCommonJS(locator_healing_exports);
57
59
  var path = __toESM(require("path"));
60
+ var import_node_url = require("url");
58
61
 
59
62
  // ../core/src/locator-chain.ts
60
63
  var LOCATING_METHODS = /* @__PURE__ */ new Set([
@@ -744,6 +747,20 @@ function suggestLocatorsFromAria(failed, ariaSnapshot) {
744
747
  if (suggestions.length === 0) return null;
745
748
  return { failing: renderFailing(failed), suggestions };
746
749
  }
750
+ function framePath(file, windows = process.platform === "win32") {
751
+ if (!file.startsWith("file://")) return file;
752
+ try {
753
+ return (0, import_node_url.fileURLToPath)(file, { windows });
754
+ } catch {
755
+ return null;
756
+ }
757
+ }
758
+ function callSitePath(file, cwd = process.cwd(), windows = process.platform === "win32") {
759
+ const local = framePath(file, windows);
760
+ if (local === null) return null;
761
+ const paths = windows ? path.win32 : path.posix;
762
+ return paths.relative(cwd, local).split(paths.sep).join("/");
763
+ }
747
764
  function captureCallerLocation(stack = new Error().stack ?? "") {
748
765
  const lines = stack.split("\n");
749
766
  let prevWasCaptureModule = false;
@@ -756,13 +773,8 @@ function captureCallerLocation(stack = new Error().stack ?? "") {
756
773
  prevWasCaptureModule = false;
757
774
  continue;
758
775
  }
759
- let file = m[2];
760
- if (!file || file.startsWith("node:")) {
761
- prevWasCaptureModule = false;
762
- continue;
763
- }
764
- file = file.replace(/^file:\/\/\/?/, "");
765
- if (!/\.[a-z]+$/i.test(file)) {
776
+ const file = m[2] ? framePath(m[2]) : null;
777
+ if (!file || file.startsWith("node:") || !/\.[a-z]+$/i.test(file)) {
766
778
  prevWasCaptureModule = false;
767
779
  continue;
768
780
  }
@@ -785,11 +797,9 @@ function captureCallerLocation(stack = new Error().stack ?? "") {
785
797
  }
786
798
  let rel = file;
787
799
  try {
788
- rel = path.relative(process.cwd(), file);
800
+ rel = callSitePath(file) ?? file;
789
801
  } catch {
790
802
  }
791
- rel = rel.split(path.sep).join("/");
792
- if (rel.startsWith("./")) rel = rel.slice(2);
793
803
  return `${rel}:${m[3]}:${m[4]}`;
794
804
  }
795
805
  return null;
@@ -807,11 +817,13 @@ function captureCallerLocation(stack = new Error().stack ?? "") {
807
817
  LOCATOR_METHODS,
808
818
  TAG_TO_ROLE,
809
819
  approximateAccessibleName,
820
+ callSitePath,
810
821
  captureCallerLocation,
811
822
  classifyCssStability,
812
823
  dedupeSnapshotsByLocation,
813
824
  exactAccessibleName,
814
825
  extractAccessibleName,
826
+ framePath,
815
827
  generateAlternatives,
816
828
  headingLevel,
817
829
  isAutoGenerated,
@@ -880,13 +880,17 @@ function showAnchorPicker(arg) {
880
880
  }
881
881
  const MONO = "ui-monospace,SFMono-Regular,Menlo,Consolas,monospace";
882
882
  const outline = doc.createElement("div");
883
+ outline.id = "__piwi_anchor_outline";
883
884
  outline.style.cssText = `position:fixed;pointer-events:none;z-index:${Z};box-sizing:border-box;border:2px solid #22c55e;background:rgba(34,197,94,.10);border-radius:4px;display:none;box-shadow:0 0 0 1px rgba(255,255,255,.9),0 0 0 3px rgba(5,46,22,.5);`;
884
885
  const outlineLabel = doc.createElement("div");
886
+ outlineLabel.id = "__piwi_anchor_label";
885
887
  outlineLabel.style.cssText = `position:fixed;pointer-events:none;z-index:${Z + 1};display:none;box-sizing:border-box;max-width:min(420px,90vw);background:#0b1120;color:#f9fafb;border:1px solid #22c55e;border-radius:6px;padding:3px 7px;font:12px/1.45 ${MONO};white-space:nowrap;overflow:hidden;text-overflow:ellipsis;box-shadow:0 4px 18px rgba(0,0,0,.5);`;
886
888
  const pickedOutline = doc.createElement("div");
889
+ pickedOutline.id = "__piwi_anchor_picked";
887
890
  const pr = el.getBoundingClientRect();
888
891
  pickedOutline.style.cssText = `position:fixed;pointer-events:none;z-index:${Z};box-sizing:border-box;border:2px solid #a855f7;background:rgba(168,85,247,.14);border-radius:4px;box-shadow:0 0 0 1px rgba(255,255,255,.9),0 0 0 3px rgba(59,7,100,.55);left:${pr.left}px;top:${pr.top}px;width:${pr.width}px;height:${pr.height}px;`;
889
892
  const panel = doc.createElement("div");
893
+ panel.id = "__piwi_anchor_panel";
890
894
  panel.style.cssText = `position:fixed;top:12px;right:12px;z-index:${Z + 3};width:min(340px,calc(100vw - 56px));max-height:82vh;overflow:auto;background:#111827;color:#f9fafb;border-radius:10px;padding:16px;font:12px/1.5 system-ui,sans-serif;box-shadow:0 8px 40px rgba(0,0,0,.5);`;
891
895
  const title = doc.createElement("div");
892
896
  title.style.cssText = "font-weight:600;font-size:13px;margin-bottom:2px;";
@@ -1075,6 +1079,7 @@ function showPickerChoices(arg) {
1075
1079
  }
1076
1080
  const Z = 2147483600;
1077
1081
  const wrap = doc.createElement("div");
1082
+ wrap.id = "__piwi_picker_choices";
1078
1083
  wrap.style.cssText = `position:fixed;inset:0;z-index:${Z + 3};background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;font:13px/1.5 system-ui,sans-serif;`;
1079
1084
  const panel = doc.createElement("div");
1080
1085
  panel.style.cssText = "background:#111827;color:#f9fafb;border-radius:10px;padding:20px;max-width:640px;width:90vw;max-height:70vh;overflow:auto;box-shadow:0 8px 40px rgba(0,0,0,.5);";
@@ -1105,10 +1110,14 @@ function showPickerChoices(arg) {
1105
1110
  }
1106
1111
  panel.appendChild(title);
1107
1112
  panel.appendChild(sub);
1108
- const done = (choice) => {
1109
- g.__piwiPickChoice = choice;
1113
+ const cleanup = () => {
1110
1114
  doc.removeEventListener("keydown", onKey, true);
1111
1115
  wrap.remove();
1116
+ if (g.__piwiChoiceCleanup === cleanup) delete g.__piwiChoiceCleanup;
1117
+ };
1118
+ const done = (choice) => {
1119
+ g.__piwiPickChoice = choice;
1120
+ cleanup();
1112
1121
  };
1113
1122
  const onKey = (e) => {
1114
1123
  if (e.key !== "Escape") return;
@@ -1144,6 +1153,7 @@ function showPickerChoices(arg) {
1144
1153
  });
1145
1154
  panel.appendChild(skip);
1146
1155
  doc.addEventListener("keydown", onKey, true);
1156
+ g.__piwiChoiceCleanup = cleanup;
1147
1157
  wrap.appendChild(panel);
1148
1158
  doc.body.appendChild(wrap);
1149
1159
  }
@@ -1569,6 +1579,7 @@ function mergeCandidates(base, extra) {
1569
1579
 
1570
1580
  // src/internal/capture/locator-healing.ts
1571
1581
  var path = __toESM(require("path"));
1582
+ var import_node_url = require("url");
1572
1583
 
1573
1584
  // ../core/src/locator-fingerprint.ts
1574
1585
  var FUNCTION_WORDS = new Set(
@@ -1625,8 +1636,10 @@ async function cleanupPicker(page, ended = false) {
1625
1636
  const g = globalThis;
1626
1637
  if (typeof g.__piwiPickCleanup === "function") g.__piwiPickCleanup();
1627
1638
  if (typeof g.__piwiAnchorCleanup === "function") g.__piwiAnchorCleanup();
1639
+ if (typeof g.__piwiChoiceCleanup === "function") g.__piwiChoiceCleanup();
1628
1640
  delete g.__piwiPickCleanup;
1629
1641
  delete g.__piwiAnchorCleanup;
1642
+ delete g.__piwiChoiceCleanup;
1630
1643
  delete g.__piwiPickState;
1631
1644
  delete g.__piwiPickChoice;
1632
1645
  delete g.__piwiPickedElement;
@@ -1652,13 +1665,24 @@ function unlessEnded(run, ended) {
1652
1665
  ]);
1653
1666
  }
1654
1667
  async function pickLocator(page, probe, failing, ended) {
1668
+ let polling = false;
1655
1669
  const answered = async (name) => {
1656
1670
  const wait = page.waitForFunction(
1657
- ([key, endedKey]) => globalThis[key] !== void 0 || globalThis[endedKey] === true,
1671
+ ([key, endedKey]) => {
1672
+ const g = globalThis;
1673
+ if (g[endedKey] !== true) return g[key] !== void 0;
1674
+ delete g[endedKey];
1675
+ return true;
1676
+ },
1658
1677
  [name, PICK_ENDED],
1659
1678
  { timeout: 0, polling: 250 }
1660
1679
  );
1661
- await unlessEnded(wait, ended);
1680
+ polling = true;
1681
+ const done = await unlessEnded(
1682
+ wait.then(() => true),
1683
+ ended
1684
+ );
1685
+ if (done) polling = false;
1662
1686
  return !ended?.aborted;
1663
1687
  };
1664
1688
  try {
@@ -1727,7 +1751,7 @@ async function pickLocator(page, probe, failing, ended) {
1727
1751
  ...anchors.length > 0 ? { anchors } : {}
1728
1752
  };
1729
1753
  } finally {
1730
- await cleanupPicker(page, ended?.aborted);
1754
+ await cleanupPicker(page, polling);
1731
1755
  }
1732
1756
  }
1733
1757
  async function runLocatorPicker(page, testInfo, failed, probe) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piwitests/reporter",
3
- "version": "0.62.0",
3
+ "version": "0.63.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/piwi",
6
6
  "homepage": "https://piwitests.dev",