@piwitests/reporter 0.45.0 → 0.47.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 +530 -249
- package/dist/global-setup-module.js +132 -0
- package/dist/index.d.ts +61 -1
- package/dist/index.js +693 -104
- package/dist/internal/capture/capture-fixtures.js +187 -77
- package/dist/internal/capture/locator-healing.js +9 -7
- package/dist/internal/capture/pick-on-failure.js +130 -37
- package/package.json +1 -1
- package/templates/skills/apply-locator-healing/SKILL.md +2 -0
- package/templates/skills/investigate-failure/SKILL.md +5 -1
- package/templates/skills/write-the-missing-test/SKILL.md +9 -2
|
@@ -63,6 +63,8 @@ var import_node_zlib2 = require("zlib");
|
|
|
63
63
|
// ../picker-dom/src/probe.ts
|
|
64
64
|
function probeElementAttrs(el, arg) {
|
|
65
65
|
const { keep, tagRoles, inputRoles, roleSources, includeStructural } = arg;
|
|
66
|
+
const countMatches = arg.countMatches !== false;
|
|
67
|
+
const testIdAttr = arg.testIdAttribute || "data-testid";
|
|
66
68
|
const FIELD_TAGS = ["input", "select", "textarea"];
|
|
67
69
|
const textWithout = (root, skip) => {
|
|
68
70
|
if (root === skip) return "";
|
|
@@ -87,42 +89,45 @@ function probeElementAttrs(el, arg) {
|
|
|
87
89
|
}
|
|
88
90
|
};
|
|
89
91
|
const attrMap = {};
|
|
90
|
-
|
|
92
|
+
const keys = arg.testIdAttribute && keep.indexOf(arg.testIdAttribute) === -1 ? [...keep, arg.testIdAttribute] : keep;
|
|
93
|
+
for (const key of keys) {
|
|
91
94
|
const v = el.getAttribute(key) ?? el[key];
|
|
92
95
|
attrMap[key] = typeof v === "string" ? v.slice(0, 200) : v ? String(v).slice(0, 200) : null;
|
|
93
96
|
}
|
|
94
97
|
const r = el.getBoundingClientRect();
|
|
95
98
|
const selectorCounts = {};
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
if (countMatches) {
|
|
100
|
+
try {
|
|
101
|
+
const doc = el.ownerDocument;
|
|
102
|
+
const cssEsc = (s) => doc.defaultView.CSS.escape(s);
|
|
103
|
+
const count = (sel) => {
|
|
104
|
+
try {
|
|
105
|
+
return doc.querySelectorAll(sel).length;
|
|
106
|
+
} catch {
|
|
107
|
+
return void 0;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
if (attrMap[testIdAttr]) {
|
|
111
|
+
selectorCounts.testId = count(`[${testIdAttr}=${JSON.stringify(attrMap[testIdAttr])}]`);
|
|
104
112
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const n = count(`.${cssEsc(cls)}`);
|
|
121
|
-
if (n !== void 0) classCounts[cls] = n;
|
|
113
|
+
if (attrMap["id"]) selectorCounts.id = count(`#${cssEsc(attrMap["id"])}`);
|
|
114
|
+
if (attrMap["name"]) selectorCounts.name = count(`[name=${JSON.stringify(attrMap["name"])}]`);
|
|
115
|
+
if (attrMap["placeholder"]) {
|
|
116
|
+
selectorCounts.placeholder = count(`[placeholder=${JSON.stringify(attrMap["placeholder"])}]`);
|
|
117
|
+
}
|
|
118
|
+
if (attrMap["alt"]) selectorCounts.alt = count(`[alt=${JSON.stringify(attrMap["alt"])}]`);
|
|
119
|
+
if (attrMap["title"]) selectorCounts.title = count(`[title=${JSON.stringify(attrMap["title"])}]`);
|
|
120
|
+
const classList = (attrMap["class"] || "").split(/\s+/).filter((c) => c.length > 1).slice(0, 10);
|
|
121
|
+
if (classList.length > 0) {
|
|
122
|
+
const classCounts = {};
|
|
123
|
+
for (const cls of classList) {
|
|
124
|
+
const n = count(`.${cssEsc(cls)}`);
|
|
125
|
+
if (n !== void 0) classCounts[cls] = n;
|
|
126
|
+
}
|
|
127
|
+
selectorCounts.classes = classCounts;
|
|
122
128
|
}
|
|
123
|
-
|
|
129
|
+
} catch {
|
|
124
130
|
}
|
|
125
|
-
} catch {
|
|
126
131
|
}
|
|
127
132
|
let rolePosition = null;
|
|
128
133
|
const ancestors = [];
|
|
@@ -211,8 +216,8 @@ function probeElementAttrs(el, arg) {
|
|
|
211
216
|
}
|
|
212
217
|
return total;
|
|
213
218
|
};
|
|
214
|
-
const nodes = doc.querySelectorAll(roleSources);
|
|
215
|
-
const nodesUsable = nodes.length <= 4e3;
|
|
219
|
+
const nodes = countMatches ? doc.querySelectorAll(roleSources) : [];
|
|
220
|
+
const nodesUsable = countMatches && nodes.length <= 4e3;
|
|
216
221
|
const rolesUsable = !!targetRole && nodesUsable;
|
|
217
222
|
if (rolesUsable) {
|
|
218
223
|
let roleCountAll = 0;
|
|
@@ -246,17 +251,18 @@ function probeElementAttrs(el, arg) {
|
|
|
246
251
|
};
|
|
247
252
|
}
|
|
248
253
|
}
|
|
249
|
-
if (textNeedle) {
|
|
254
|
+
if (textNeedle && countMatches) {
|
|
250
255
|
const textCount = countTextOwners(doc.body || doc.documentElement, 4e3);
|
|
251
256
|
if (textCount >= 0) selectorCounts.text = textCount;
|
|
252
257
|
}
|
|
253
|
-
if (rolesUsable || textNeedle) {
|
|
258
|
+
if (rolesUsable || textNeedle || !countMatches) {
|
|
254
259
|
const CONTAINER_TAGS = ["form", "nav", "main", "article", "section", "dialog", "table", "li", "tr"];
|
|
255
260
|
const NOISY_DATA_ATTR = /^data-(v-[0-9a-f]+|reactid|react-checksum|svelte-\w+|ng-\w+|ember\w*)$/i;
|
|
256
261
|
const POSITIONAL_DATA_ATTR = /^data-(index|idx|i|key|row|rownum|col|column|position|pos|order|sort|offset|page)$/i;
|
|
257
262
|
const TEST_DATA_ATTRS2 = ["data-test", "data-test-id", "data-qa", "data-qa-id", "data-cy", "data-e2e"];
|
|
263
|
+
const testIdName = testIdAttr.toLowerCase();
|
|
258
264
|
const usableDataAttr = (name, value) => {
|
|
259
|
-
if (!name || name.slice(0, 5) !== "data-" || name ===
|
|
265
|
+
if (!name || name.slice(0, 5) !== "data-" || name.toLowerCase() === testIdName) return false;
|
|
260
266
|
if (NOISY_DATA_ATTR.test(name) || POSITIONAL_DATA_ATTR.test(name)) return false;
|
|
261
267
|
return !!value && value.length <= 120;
|
|
262
268
|
};
|
|
@@ -286,7 +292,13 @@ function probeElementAttrs(el, arg) {
|
|
|
286
292
|
return digits <= letters;
|
|
287
293
|
};
|
|
288
294
|
const usableDiscriminator = (t) => !!t && t.length <= 60 && t !== targetText && namesRatherThanReports(t);
|
|
295
|
+
const hasAtMost = (root, cap) => {
|
|
296
|
+
const walker = doc.createTreeWalker(root, 1);
|
|
297
|
+
for (let n = 0; walker.nextNode(); n++) if (n >= cap) return false;
|
|
298
|
+
return true;
|
|
299
|
+
};
|
|
289
300
|
const discriminatingText = (anc) => {
|
|
301
|
+
if (!countMatches && !hasAtMost(anc, 200)) return null;
|
|
290
302
|
const heading = anc.querySelector('h1,h2,h3,h4,h5,h6,[role="heading"]');
|
|
291
303
|
if (heading) {
|
|
292
304
|
const t = rawText(heading);
|
|
@@ -318,7 +330,7 @@ function probeElementAttrs(el, arg) {
|
|
|
318
330
|
depth++;
|
|
319
331
|
const tag = (node.tagName || "").toLowerCase();
|
|
320
332
|
if (tag === "body" || tag === "html") break;
|
|
321
|
-
const testId = node.getAttribute(
|
|
333
|
+
const testId = node.getAttribute(testIdAttr);
|
|
322
334
|
const id = node.getAttribute("id");
|
|
323
335
|
const explicitRole = node.getAttribute("role");
|
|
324
336
|
const ariaLabel = node.getAttribute("aria-label");
|
|
@@ -338,10 +350,10 @@ function probeElementAttrs(el, arg) {
|
|
|
338
350
|
}
|
|
339
351
|
}
|
|
340
352
|
}
|
|
341
|
-
const scopedTextCount = countTextOwners(node, 2e3);
|
|
353
|
+
const scopedTextCount = countMatches ? countTextOwners(node, 2e3) : -1;
|
|
342
354
|
let filterText = null;
|
|
343
355
|
try {
|
|
344
|
-
if (anchorRole && nodesUsable) filterText = discriminatingText(node);
|
|
356
|
+
if (anchorRole && (nodesUsable || !countMatches)) filterText = discriminatingText(node);
|
|
345
357
|
} catch {
|
|
346
358
|
filterText = null;
|
|
347
359
|
}
|
|
@@ -354,11 +366,13 @@ function probeElementAttrs(el, arg) {
|
|
|
354
366
|
ariaLabel: ariaLabel || null,
|
|
355
367
|
...scopedRoleCount >= 0 ? { scopedRoleCount } : {},
|
|
356
368
|
...scopedTextCount >= 0 ? { scopedTextCount } : {},
|
|
357
|
-
...testId ? { testIdCount: count(`[
|
|
358
|
-
...id ? { idCount: count(`#${cssEsc(id)}`) } : {},
|
|
369
|
+
...testId && countMatches ? { testIdCount: count(`[${testIdAttr}=${JSON.stringify(testId)}]`) } : {},
|
|
370
|
+
...id && countMatches ? { idCount: count(`#${cssEsc(id)}`) } : {},
|
|
359
371
|
...anchorRole && nodesUsable ? { roleCount: docRoleCount(anchorRole) } : {},
|
|
360
|
-
...filterText ? { filterText
|
|
361
|
-
...
|
|
372
|
+
...filterText ? { filterText } : {},
|
|
373
|
+
...filterText && countMatches ? { filterRoleCount: filterMatchCount(anchorRole, filterText) } : {},
|
|
374
|
+
...dataAttr ? { dataAttr } : {},
|
|
375
|
+
...dataAttr && countMatches ? { dataAttrCount: count(`[${dataAttr.name}=${JSON.stringify(dataAttr.value)}]`) } : {}
|
|
362
376
|
});
|
|
363
377
|
}
|
|
364
378
|
node = node.parentElement;
|
|
@@ -396,6 +410,17 @@ function installPickerOverlay(arg) {
|
|
|
396
410
|
return;
|
|
397
411
|
}
|
|
398
412
|
const Z = 2147483600;
|
|
413
|
+
const testIdAttr = arg.testIdAttribute || "data-testid";
|
|
414
|
+
const words = {
|
|
415
|
+
banner: "Piwi inspector \u2014 click any element to generate locators for it",
|
|
416
|
+
keys: "\u2191 parent \xB7 \u2193 child \xB7 Esc skip",
|
|
417
|
+
keysHovering: "click to pick \xB7 \u2191 parent \xB7 \u2193 child \xB7 Esc skip",
|
|
418
|
+
analyzing: "Analyzing element\u2026"
|
|
419
|
+
};
|
|
420
|
+
for (const key of Object.keys(words)) {
|
|
421
|
+
const given = arg.strings?.[key];
|
|
422
|
+
if (typeof given === "string" && given) words[key] = given;
|
|
423
|
+
}
|
|
399
424
|
const highlight = doc.createElement("div");
|
|
400
425
|
highlight.id = "__piwi_picker_highlight";
|
|
401
426
|
highlight.style.cssText = `position:fixed;pointer-events:none;z-index:${Z};display:none;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),inset 0 0 0 1px rgba(255,255,255,.5);`;
|
|
@@ -420,27 +445,53 @@ function installPickerOverlay(arg) {
|
|
|
420
445
|
const MONO = "ui-monospace,SFMono-Regular,Menlo,Consolas,monospace";
|
|
421
446
|
const hlLocator = (expr) => `<code style="font-family:${MONO}">${hlTokens(expr)}</code>`;
|
|
422
447
|
const head = doc.createElement("div");
|
|
423
|
-
|
|
448
|
+
if (arg.transport === "postMessage") head.textContent = "Click an element to generate locators";
|
|
449
|
+
else if (arg.failing)
|
|
450
|
+
head.innerHTML = `Piwi locator picker \u2014 click the element that should replace ${hlLocator(arg.failing)}`;
|
|
451
|
+
else head.textContent = words.banner;
|
|
424
452
|
const locatorLine = doc.createElement("div");
|
|
425
453
|
locatorLine.id = "__piwi_picker_locator";
|
|
426
454
|
locatorLine.style.cssText = `display:none;margin-top:7px;padding:5px 9px;border-radius:7px;background:#0b1120;border:1px solid #4c1d95;font:13.5px/1.55 ${MONO};word-break:break-word;overflow-wrap:anywhere;`;
|
|
427
455
|
const foot = doc.createElement("div");
|
|
428
456
|
foot.id = "__piwi_picker_foot";
|
|
429
457
|
foot.style.cssText = "color:#9ca3af;margin-top:6px;font-size:12px;";
|
|
430
|
-
foot.textContent =
|
|
458
|
+
foot.textContent = words.keys;
|
|
431
459
|
banner.appendChild(head);
|
|
432
460
|
banner.appendChild(locatorLine);
|
|
433
461
|
banner.appendChild(foot);
|
|
434
462
|
const label = doc.createElement("div");
|
|
435
463
|
label.id = "__piwi_picker_label";
|
|
436
464
|
label.style.cssText = `position:fixed;pointer-events:none;z-index:${Z + 1};display:none;box-sizing:border-box;max-width:min(620px,92vw);background:#0b1120;color:#f9fafb;border:1px solid #7c3aed;border-radius:7px;padding:4px 8px;font:12.5px/1.45 ${MONO};white-space:nowrap;overflow:hidden;text-overflow:ellipsis;box-shadow:0 4px 18px rgba(0,0,0,.5);`;
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
465
|
+
const mountParent = () => {
|
|
466
|
+
let modal = null;
|
|
467
|
+
try {
|
|
468
|
+
modal = doc.querySelector("dialog:modal");
|
|
469
|
+
} catch {
|
|
470
|
+
modal = null;
|
|
471
|
+
}
|
|
472
|
+
if (!modal) return doc.body;
|
|
473
|
+
const s = g.getComputedStyle(modal);
|
|
474
|
+
const holdsFixed = s.transform !== "none" || s.perspective !== "none" || s.filter !== "none" || /\b(paint|layout|strict|content)\b/.test(s.contain) || /\b(transform|perspective|filter)\b/.test(s.willChange);
|
|
475
|
+
return holdsFixed ? doc.body : modal;
|
|
476
|
+
};
|
|
477
|
+
const mount = (...nodes) => {
|
|
478
|
+
const parent = mountParent();
|
|
479
|
+
for (const node of nodes) parent.appendChild(node);
|
|
480
|
+
if (parent !== doc.body) {
|
|
481
|
+
parent.addEventListener(
|
|
482
|
+
"close",
|
|
483
|
+
() => {
|
|
484
|
+
for (const node of nodes) if (node.parentNode === parent) doc.body.appendChild(node);
|
|
485
|
+
},
|
|
486
|
+
{ once: true }
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
mount(highlight, label, banner);
|
|
440
491
|
const escJs = (s) => s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
441
492
|
const describe = (el) => {
|
|
442
493
|
const tag = (el.tagName || "?").toLowerCase();
|
|
443
|
-
const testId = el.getAttribute && el.getAttribute(
|
|
494
|
+
const testId = el.getAttribute && el.getAttribute(testIdAttr);
|
|
444
495
|
if (testId) return `getByTestId('${escJs(testId)}')`;
|
|
445
496
|
if (el.labels && el.labels.length > 0) {
|
|
446
497
|
const labelText = (el.labels[0].textContent || "").replace(/\s+/g, " ").trim().slice(0, 80);
|
|
@@ -475,7 +526,7 @@ function installPickerOverlay(arg) {
|
|
|
475
526
|
const el = chain2[i];
|
|
476
527
|
const tag = (el.tagName || "").toLowerCase();
|
|
477
528
|
if (ACTIONABLE_TAGS.includes(tag)) return i;
|
|
478
|
-
if (el.getAttribute && (el.getAttribute("role") || el.getAttribute(
|
|
529
|
+
if (el.getAttribute && (el.getAttribute("role") || el.getAttribute(testIdAttr))) return i;
|
|
479
530
|
}
|
|
480
531
|
return 0;
|
|
481
532
|
};
|
|
@@ -531,7 +582,7 @@ function installPickerOverlay(arg) {
|
|
|
531
582
|
locatorLine.style.display = "block";
|
|
532
583
|
}
|
|
533
584
|
placeLabel(r);
|
|
534
|
-
foot.textContent =
|
|
585
|
+
foot.textContent = words.keysHovering;
|
|
535
586
|
};
|
|
536
587
|
const redescribe = () => {
|
|
537
588
|
labeled = null;
|
|
@@ -594,6 +645,13 @@ function installPickerOverlay(arg) {
|
|
|
594
645
|
banner.remove();
|
|
595
646
|
if (g.__piwiRedescribe === redescribe) delete g.__piwiRedescribe;
|
|
596
647
|
};
|
|
648
|
+
const teardown = () => {
|
|
649
|
+
removePickingListeners();
|
|
650
|
+
removeSuppressed();
|
|
651
|
+
doc.removeEventListener("click", stop, true);
|
|
652
|
+
doc.removeEventListener("keydown", stop, true);
|
|
653
|
+
cleanup();
|
|
654
|
+
};
|
|
597
655
|
const reportPicked = (el) => {
|
|
598
656
|
removePickingListeners();
|
|
599
657
|
highlight.style.display = "none";
|
|
@@ -604,13 +662,13 @@ function installPickerOverlay(arg) {
|
|
|
604
662
|
g.__piwiSnapshotExtras?.onPick?.();
|
|
605
663
|
doc.addEventListener("click", stop, true);
|
|
606
664
|
doc.addEventListener("keydown", stop, true);
|
|
607
|
-
foot.textContent =
|
|
665
|
+
foot.textContent = words.analyzing;
|
|
608
666
|
g.parent.postMessage({ type: "elementPicked", attrs }, "*");
|
|
609
667
|
} else {
|
|
610
668
|
removeSuppressed();
|
|
611
669
|
g.__piwiPickedElement = el;
|
|
612
670
|
g.__piwiPickState = "picked";
|
|
613
|
-
foot.textContent =
|
|
671
|
+
foot.textContent = words.analyzing;
|
|
614
672
|
}
|
|
615
673
|
};
|
|
616
674
|
const reportSkipped = () => {
|
|
@@ -658,7 +716,7 @@ function installPickerOverlay(arg) {
|
|
|
658
716
|
}
|
|
659
717
|
});
|
|
660
718
|
};
|
|
661
|
-
g.__piwiPickCleanup =
|
|
719
|
+
g.__piwiPickCleanup = teardown;
|
|
662
720
|
doc.addEventListener("mousemove", onMove, true);
|
|
663
721
|
doc.addEventListener("click", onClick, true);
|
|
664
722
|
doc.addEventListener("keydown", onKey, true);
|
|
@@ -680,6 +738,26 @@ function showAnchorPicker(arg) {
|
|
|
680
738
|
}
|
|
681
739
|
const Z = 2147483600;
|
|
682
740
|
const { tagRoles, inputRoles, roleSources, leafRole, leafLevel, leafTestId } = arg;
|
|
741
|
+
const testIdAttr = arg.testIdAttribute || "data-testid";
|
|
742
|
+
const words = {
|
|
743
|
+
title: "Scope to stable parents (optional)",
|
|
744
|
+
hint: "Pick one or more parents to anchor the locator to. Hover a row to see the parent.",
|
|
745
|
+
noneSelected: "No parents selected \u2014 standard alternatives only.",
|
|
746
|
+
matchesOne: "\u2713 Selection matches exactly 1 element",
|
|
747
|
+
matchesMany: "\u2717 Selection matches {count} elements",
|
|
748
|
+
countUnavailable: "Match count unavailable",
|
|
749
|
+
containsOne: "contains exactly 1 matching element",
|
|
750
|
+
containsMany: "contains {count} matching elements",
|
|
751
|
+
needsTestId: `add a ${testIdAttr} to make this usable`,
|
|
752
|
+
noHook: "no stable hook",
|
|
753
|
+
use: "Use selected parents",
|
|
754
|
+
skip: "Skip (Esc)"
|
|
755
|
+
};
|
|
756
|
+
for (const key of Object.keys(words)) {
|
|
757
|
+
const given = arg.strings?.[key];
|
|
758
|
+
if (typeof given === "string" && given) words[key] = given;
|
|
759
|
+
}
|
|
760
|
+
const counted = (template, n) => template.replace("{count}", String(n));
|
|
683
761
|
const roleOf2 = (n) => {
|
|
684
762
|
const explicit = n.getAttribute && n.getAttribute("role");
|
|
685
763
|
if (explicit) return explicit;
|
|
@@ -695,9 +773,11 @@ function showAnchorPicker(arg) {
|
|
|
695
773
|
const al = n.getAttribute && n.getAttribute("aria-level");
|
|
696
774
|
return al && /^\d+$/.test(al) ? Number(al) : null;
|
|
697
775
|
};
|
|
776
|
+
const leafCountable = !!leafTestId || roleOf2(el) === leafRole;
|
|
698
777
|
const leafMatches = (scope) => {
|
|
699
778
|
try {
|
|
700
|
-
if (leafTestId) return scope.querySelectorAll(`[
|
|
779
|
+
if (leafTestId) return scope.querySelectorAll(`[${testIdAttr}=${JSON.stringify(leafTestId)}]`).length;
|
|
780
|
+
if (!leafCountable) return -1;
|
|
701
781
|
const nodes = scope.querySelectorAll(roleSources);
|
|
702
782
|
if (nodes.length > 2e3) return -1;
|
|
703
783
|
let matched = 0;
|
|
@@ -733,12 +813,12 @@ function showAnchorPicker(arg) {
|
|
|
733
813
|
depth++;
|
|
734
814
|
const tag = (node.tagName || "").toLowerCase();
|
|
735
815
|
if (tag === "body" || tag === "html") break;
|
|
736
|
-
const testId = node.getAttribute(
|
|
816
|
+
const testId = node.getAttribute(testIdAttr);
|
|
737
817
|
const id = node.getAttribute("id");
|
|
738
818
|
const ariaLabel = node.getAttribute("aria-label");
|
|
739
819
|
const role = roleOf2(node);
|
|
740
820
|
const info = { tag, depth, testId: testId || null, id: id || null, ariaLabel: ariaLabel || null, role };
|
|
741
|
-
if (testId) info.testIdCount = count(`[
|
|
821
|
+
if (testId) info.testIdCount = count(`[${testIdAttr}=${JSON.stringify(testId)}]`);
|
|
742
822
|
if (id) {
|
|
743
823
|
try {
|
|
744
824
|
info.idCount = count(`#${doc.defaultView.CSS.escape(id)}`);
|
|
@@ -757,7 +837,7 @@ function showAnchorPicker(arg) {
|
|
|
757
837
|
if (ariaLabel) info.labeledRoleCount = labeledCount;
|
|
758
838
|
}
|
|
759
839
|
info.scopedLeafCount = leafMatches(node);
|
|
760
|
-
const hookLabel = testId ?
|
|
840
|
+
const hookLabel = testId ? `${testIdAttr}="${testId}"` : id ? `#${id}` : ariaLabel && role ? `${role} "${ariaLabel}"` : role ? `role ${role}` : words.noHook;
|
|
761
841
|
rows.push({
|
|
762
842
|
node,
|
|
763
843
|
info,
|
|
@@ -779,13 +859,13 @@ function showAnchorPicker(arg) {
|
|
|
779
859
|
const pr = el.getBoundingClientRect();
|
|
780
860
|
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;`;
|
|
781
861
|
const panel = doc.createElement("div");
|
|
782
|
-
panel.style.cssText = `position:fixed;top:12px;right:12px;z-index:${Z + 3};width:340px;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);`;
|
|
862
|
+
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);`;
|
|
783
863
|
const title = doc.createElement("div");
|
|
784
864
|
title.style.cssText = "font-weight:600;font-size:13px;margin-bottom:2px;";
|
|
785
|
-
title.textContent =
|
|
865
|
+
title.textContent = words.title;
|
|
786
866
|
const sub = doc.createElement("div");
|
|
787
867
|
sub.style.cssText = "color:#9ca3af;margin-bottom:10px;";
|
|
788
|
-
sub.textContent =
|
|
868
|
+
sub.textContent = words.hint;
|
|
789
869
|
panel.appendChild(title);
|
|
790
870
|
panel.appendChild(sub);
|
|
791
871
|
const selected = /* @__PURE__ */ new Set();
|
|
@@ -793,7 +873,7 @@ function showAnchorPicker(arg) {
|
|
|
793
873
|
footer.style.cssText = "margin:10px 0;font-weight:600;";
|
|
794
874
|
const segMatches = (scope, info) => {
|
|
795
875
|
try {
|
|
796
|
-
if (info.testId) return Array.from(scope.querySelectorAll(`[
|
|
876
|
+
if (info.testId) return Array.from(scope.querySelectorAll(`[${testIdAttr}=${JSON.stringify(info.testId)}]`));
|
|
797
877
|
if (info.id) return Array.from(scope.querySelectorAll(`#${doc.defaultView.CSS.escape(info.id)}`));
|
|
798
878
|
const nodes = Array.from(scope.querySelectorAll(roleSources));
|
|
799
879
|
if (nodes.length > 2e3) return [];
|
|
@@ -817,14 +897,15 @@ function showAnchorPicker(arg) {
|
|
|
817
897
|
let total = 0;
|
|
818
898
|
for (const s of scopes) {
|
|
819
899
|
const c = leafMatches(s);
|
|
820
|
-
if (c
|
|
900
|
+
if (c < 0) return -1;
|
|
901
|
+
total += c;
|
|
821
902
|
if (total > 50) return total;
|
|
822
903
|
}
|
|
823
904
|
return total;
|
|
824
905
|
};
|
|
825
906
|
const refreshFooter = () => {
|
|
826
907
|
if (selected.size === 0) {
|
|
827
|
-
footer.textContent =
|
|
908
|
+
footer.textContent = words.noneSelected;
|
|
828
909
|
footer.style.color = "#9ca3af";
|
|
829
910
|
g.__piwiPickChainCount = void 0;
|
|
830
911
|
return;
|
|
@@ -832,10 +913,10 @@ function showAnchorPicker(arg) {
|
|
|
832
913
|
const c = chainCount();
|
|
833
914
|
g.__piwiPickChainCount = c;
|
|
834
915
|
if (c === 1) {
|
|
835
|
-
footer.textContent =
|
|
916
|
+
footer.textContent = words.matchesOne;
|
|
836
917
|
footer.style.color = "#4ade80";
|
|
837
918
|
} else {
|
|
838
|
-
footer.textContent = c < 0 ?
|
|
919
|
+
footer.textContent = c < 0 ? words.countUnavailable : counted(words.matchesMany, c);
|
|
839
920
|
footer.style.color = "#fbbf24";
|
|
840
921
|
}
|
|
841
922
|
};
|
|
@@ -852,7 +933,8 @@ function showAnchorPicker(arg) {
|
|
|
852
933
|
code.textContent = `<${row.info.tag}> ${row.hookLabel}`;
|
|
853
934
|
const hint = doc.createElement("span");
|
|
854
935
|
hint.style.cssText = "color:#9ca3af;";
|
|
855
|
-
|
|
936
|
+
const inside = row.info.scopedLeafCount ?? -1;
|
|
937
|
+
hint.textContent = row.selectable ? inside === 1 ? words.containsOne : inside < 0 ? words.countUnavailable : counted(words.containsMany, inside) : words.needsTestId;
|
|
856
938
|
text.appendChild(code);
|
|
857
939
|
text.appendChild(hint);
|
|
858
940
|
line.appendChild(box);
|
|
@@ -890,6 +972,7 @@ function showAnchorPicker(arg) {
|
|
|
890
972
|
outline.remove();
|
|
891
973
|
outlineLabel.remove();
|
|
892
974
|
pickedOutline.remove();
|
|
975
|
+
if (g.__piwiAnchorCleanup === cleanup) delete g.__piwiAnchorCleanup;
|
|
893
976
|
};
|
|
894
977
|
const done = (state) => {
|
|
895
978
|
g.__piwiPickAnchors = state === "done" ? rows.filter((_, i) => selected.has(i)).map((r) => r.info) : [];
|
|
@@ -906,7 +989,7 @@ function showAnchorPicker(arg) {
|
|
|
906
989
|
buttonRow.style.cssText = "display:flex;gap:8px;margin-top:4px;";
|
|
907
990
|
const useBtn = doc.createElement("button");
|
|
908
991
|
useBtn.style.cssText = "flex:1;background:#7c3aed;color:#fff;border:none;border-radius:6px;padding:8px;cursor:pointer;font:600 12px system-ui;";
|
|
909
|
-
useBtn.textContent =
|
|
992
|
+
useBtn.textContent = words.use;
|
|
910
993
|
useBtn.addEventListener("click", (e) => {
|
|
911
994
|
e.preventDefault();
|
|
912
995
|
e.stopImmediatePropagation();
|
|
@@ -914,7 +997,7 @@ function showAnchorPicker(arg) {
|
|
|
914
997
|
});
|
|
915
998
|
const skipBtn = doc.createElement("button");
|
|
916
999
|
skipBtn.style.cssText = "background:none;border:1px solid #374151;color:#9ca3af;border-radius:6px;padding:8px 10px;cursor:pointer;font:12px system-ui;";
|
|
917
|
-
skipBtn.textContent =
|
|
1000
|
+
skipBtn.textContent = words.skip;
|
|
918
1001
|
skipBtn.addEventListener("click", (e) => {
|
|
919
1002
|
e.preventDefault();
|
|
920
1003
|
e.stopImmediatePropagation();
|
|
@@ -926,10 +1009,32 @@ function showAnchorPicker(arg) {
|
|
|
926
1009
|
refreshFooter();
|
|
927
1010
|
g.__piwiAnchorCleanup = cleanup;
|
|
928
1011
|
doc.addEventListener("keydown", onKey, true);
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1012
|
+
const mountParent = () => {
|
|
1013
|
+
let modal = null;
|
|
1014
|
+
try {
|
|
1015
|
+
modal = doc.querySelector("dialog:modal");
|
|
1016
|
+
} catch {
|
|
1017
|
+
modal = null;
|
|
1018
|
+
}
|
|
1019
|
+
if (!modal) return doc.body;
|
|
1020
|
+
const s = g.getComputedStyle(modal);
|
|
1021
|
+
const holdsFixed = s.transform !== "none" || s.perspective !== "none" || s.filter !== "none" || /\b(paint|layout|strict|content)\b/.test(s.contain) || /\b(transform|perspective|filter)\b/.test(s.willChange);
|
|
1022
|
+
return holdsFixed ? doc.body : modal;
|
|
1023
|
+
};
|
|
1024
|
+
const mount = (...nodes) => {
|
|
1025
|
+
const parent = mountParent();
|
|
1026
|
+
for (const node2 of nodes) parent.appendChild(node2);
|
|
1027
|
+
if (parent !== doc.body) {
|
|
1028
|
+
parent.addEventListener(
|
|
1029
|
+
"close",
|
|
1030
|
+
() => {
|
|
1031
|
+
for (const node2 of nodes) if (node2.parentNode === parent) doc.body.appendChild(node2);
|
|
1032
|
+
},
|
|
1033
|
+
{ once: true }
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
mount(pickedOutline, outline, outlineLabel, panel);
|
|
933
1038
|
}
|
|
934
1039
|
|
|
935
1040
|
// ../picker-dom/src/overlay-confirm.ts
|
|
@@ -1145,7 +1250,7 @@ function isAutoGenerated(value) {
|
|
|
1145
1250
|
return false;
|
|
1146
1251
|
}
|
|
1147
1252
|
var TEST_DATA_ATTRS = /* @__PURE__ */ new Set(["data-test", "data-test-id", "data-qa", "data-qa-id", "data-cy", "data-e2e"]);
|
|
1148
|
-
function generateAlternatives(attrs) {
|
|
1253
|
+
function generateAlternatives(attrs, options = {}) {
|
|
1149
1254
|
const alts = [];
|
|
1150
1255
|
const seen = /* @__PURE__ */ new Set();
|
|
1151
1256
|
const add = (loc) => {
|
|
@@ -1156,12 +1261,14 @@ function generateAlternatives(attrs) {
|
|
|
1156
1261
|
};
|
|
1157
1262
|
const { accessibleName } = attrs;
|
|
1158
1263
|
const tag = attrs.tagName;
|
|
1159
|
-
const role = resolveAriaRole(attrs);
|
|
1264
|
+
const role = options.role !== void 0 ? options.role : resolveAriaRole(attrs);
|
|
1160
1265
|
const counts = attrs.selectorCounts;
|
|
1161
1266
|
const isUnique = (n) => n == null || n <= 1;
|
|
1162
1267
|
const ambiguityPenalty = (n) => n != null && n > 1 ? 45 : 0;
|
|
1163
1268
|
const text = attrs.textContent ? attrs.textContent.replace(/\s+/g, " ").trim() : null;
|
|
1164
|
-
const
|
|
1269
|
+
const testIdAttribute = options.testIdAttribute || "data-testid";
|
|
1270
|
+
const selectable = (name2) => name2.toLowerCase() !== testIdAttribute.toLowerCase();
|
|
1271
|
+
const testId = attr(attrs, testIdAttribute);
|
|
1165
1272
|
if (testId && isUnique(counts?.testId)) {
|
|
1166
1273
|
add({
|
|
1167
1274
|
locator: `getByTestId('${esc(testId)}')`,
|
|
@@ -1220,7 +1327,7 @@ function generateAlternatives(attrs) {
|
|
|
1220
1327
|
});
|
|
1221
1328
|
}
|
|
1222
1329
|
const id = attr(attrs, "id");
|
|
1223
|
-
if (id && !isAutoGenerated(id) && isUnique(counts?.id)) {
|
|
1330
|
+
if (id && selectable("id") && !isAutoGenerated(id) && isUnique(counts?.id)) {
|
|
1224
1331
|
const selector = isCssSafeId(id) ? `#${id}` : `[id="${escCssAttrValue(id)}"]`;
|
|
1225
1332
|
add({
|
|
1226
1333
|
locator: `locator('${esc(selector)}')`,
|
|
@@ -1230,7 +1337,7 @@ function generateAlternatives(attrs) {
|
|
|
1230
1337
|
});
|
|
1231
1338
|
}
|
|
1232
1339
|
const name = attr(attrs, "name");
|
|
1233
|
-
if (name && isUnique(counts?.name)) {
|
|
1340
|
+
if (name && selectable("name") && isUnique(counts?.name)) {
|
|
1234
1341
|
const selector = `[name="${escCssAttrValue(name)}"]`;
|
|
1235
1342
|
add({
|
|
1236
1343
|
locator: `locator('${esc(selector)}')`,
|
|
@@ -1275,7 +1382,7 @@ function generateAlternatives(attrs) {
|
|
|
1275
1382
|
score: scores.testId
|
|
1276
1383
|
});
|
|
1277
1384
|
}
|
|
1278
|
-
if (!idAnchorDone && anc.id && !isAutoGenerated(anc.id) && anc.idCount === 1) {
|
|
1385
|
+
if (!idAnchorDone && anc.id && selectable("id") && !isAutoGenerated(anc.id) && anc.idCount === 1) {
|
|
1279
1386
|
idAnchorDone = true;
|
|
1280
1387
|
const anchorSelector = isCssSafeId(anc.id) ? `#${anc.id}` : `[id="${escCssAttrValue(anc.id)}"]`;
|
|
1281
1388
|
add({
|
|
@@ -1285,7 +1392,7 @@ function generateAlternatives(attrs) {
|
|
|
1285
1392
|
score: scores.id
|
|
1286
1393
|
});
|
|
1287
1394
|
}
|
|
1288
|
-
if (!dataAnchorDone && anc.dataAttr && anc.dataAttrCount === 1) {
|
|
1395
|
+
if (!dataAnchorDone && anc.dataAttr && selectable(anc.dataAttr.name) && anc.dataAttrCount === 1) {
|
|
1289
1396
|
dataAnchorDone = true;
|
|
1290
1397
|
const { name: name2, value } = anc.dataAttr;
|
|
1291
1398
|
const anchorSelector = `[${name2}="${escCssAttrValue(value)}"]`;
|
|
@@ -2049,6 +2156,7 @@ var DEFAULTS = {
|
|
|
2049
2156
|
sampleAriaOnPass: true,
|
|
2050
2157
|
uploadManifest: true,
|
|
2051
2158
|
defaultCapture: true,
|
|
2159
|
+
checkBaseUrl: false,
|
|
2052
2160
|
streaming: true,
|
|
2053
2161
|
streamingBatchSize: 5,
|
|
2054
2162
|
streamingBatchDelay: 2e3,
|
|
@@ -2091,6 +2199,7 @@ var PIWI_ENV_KEYS = {
|
|
|
2091
2199
|
sampleAriaOnPass: "PIWI_SAMPLE_ARIA_ON_PASS",
|
|
2092
2200
|
uploadManifest: "PIWI_UPLOAD_MANIFEST",
|
|
2093
2201
|
defaultCapture: "PIWI_DEFAULT_CAPTURE",
|
|
2202
|
+
checkBaseUrl: "PIWI_CHECK_BASE_URL",
|
|
2094
2203
|
inspectOnFailure: "PIWI_INSPECT_ON_FAIL",
|
|
2095
2204
|
pickLocatorOnFailure: "PIWI_PICK_LOCATOR_ON_FAIL",
|
|
2096
2205
|
outputFile: "PIWI_OUTPUT_FILE",
|
|
@@ -2143,6 +2252,7 @@ var ENV_FALLBACK_SPECS = [
|
|
|
2143
2252
|
{ option: "sampleAriaOnPass", env: PIWI_ENV_KEYS.sampleAriaOnPass, kind: "bool" },
|
|
2144
2253
|
{ option: "uploadManifest", env: PIWI_ENV_KEYS.uploadManifest, kind: "bool" },
|
|
2145
2254
|
{ option: "defaultCapture", env: PIWI_ENV_KEYS.defaultCapture, kind: "bool" },
|
|
2255
|
+
{ option: "checkBaseUrl", env: PIWI_ENV_KEYS.checkBaseUrl, kind: "bool" },
|
|
2146
2256
|
{ option: "inspectOnFailure", env: PIWI_ENV_KEYS.inspectOnFailure, kind: "bool" },
|
|
2147
2257
|
{ option: "pickLocatorOnFailure", env: PIWI_ENV_KEYS.pickLocatorOnFailure, kind: "bool" },
|
|
2148
2258
|
{ option: "outputFile", env: PIWI_ENV_KEYS.outputFile, kind: "string" }
|
|
@@ -264,7 +264,7 @@ function isAutoGenerated(value) {
|
|
|
264
264
|
return false;
|
|
265
265
|
}
|
|
266
266
|
var TEST_DATA_ATTRS = /* @__PURE__ */ new Set(["data-test", "data-test-id", "data-qa", "data-qa-id", "data-cy", "data-e2e"]);
|
|
267
|
-
function generateAlternatives(attrs) {
|
|
267
|
+
function generateAlternatives(attrs, options = {}) {
|
|
268
268
|
const alts = [];
|
|
269
269
|
const seen = /* @__PURE__ */ new Set();
|
|
270
270
|
const add = (loc) => {
|
|
@@ -275,12 +275,14 @@ function generateAlternatives(attrs) {
|
|
|
275
275
|
};
|
|
276
276
|
const { accessibleName } = attrs;
|
|
277
277
|
const tag = attrs.tagName;
|
|
278
|
-
const role = resolveAriaRole(attrs);
|
|
278
|
+
const role = options.role !== void 0 ? options.role : resolveAriaRole(attrs);
|
|
279
279
|
const counts = attrs.selectorCounts;
|
|
280
280
|
const isUnique = (n) => n == null || n <= 1;
|
|
281
281
|
const ambiguityPenalty = (n) => n != null && n > 1 ? 45 : 0;
|
|
282
282
|
const text = attrs.textContent ? attrs.textContent.replace(/\s+/g, " ").trim() : null;
|
|
283
|
-
const
|
|
283
|
+
const testIdAttribute = options.testIdAttribute || "data-testid";
|
|
284
|
+
const selectable = (name2) => name2.toLowerCase() !== testIdAttribute.toLowerCase();
|
|
285
|
+
const testId = attr(attrs, testIdAttribute);
|
|
284
286
|
if (testId && isUnique(counts?.testId)) {
|
|
285
287
|
add({
|
|
286
288
|
locator: `getByTestId('${esc(testId)}')`,
|
|
@@ -339,7 +341,7 @@ function generateAlternatives(attrs) {
|
|
|
339
341
|
});
|
|
340
342
|
}
|
|
341
343
|
const id = attr(attrs, "id");
|
|
342
|
-
if (id && !isAutoGenerated(id) && isUnique(counts?.id)) {
|
|
344
|
+
if (id && selectable("id") && !isAutoGenerated(id) && isUnique(counts?.id)) {
|
|
343
345
|
const selector = isCssSafeId(id) ? `#${id}` : `[id="${escCssAttrValue(id)}"]`;
|
|
344
346
|
add({
|
|
345
347
|
locator: `locator('${esc(selector)}')`,
|
|
@@ -349,7 +351,7 @@ function generateAlternatives(attrs) {
|
|
|
349
351
|
});
|
|
350
352
|
}
|
|
351
353
|
const name = attr(attrs, "name");
|
|
352
|
-
if (name && isUnique(counts?.name)) {
|
|
354
|
+
if (name && selectable("name") && isUnique(counts?.name)) {
|
|
353
355
|
const selector = `[name="${escCssAttrValue(name)}"]`;
|
|
354
356
|
add({
|
|
355
357
|
locator: `locator('${esc(selector)}')`,
|
|
@@ -394,7 +396,7 @@ function generateAlternatives(attrs) {
|
|
|
394
396
|
score: scores.testId
|
|
395
397
|
});
|
|
396
398
|
}
|
|
397
|
-
if (!idAnchorDone && anc.id && !isAutoGenerated(anc.id) && anc.idCount === 1) {
|
|
399
|
+
if (!idAnchorDone && anc.id && selectable("id") && !isAutoGenerated(anc.id) && anc.idCount === 1) {
|
|
398
400
|
idAnchorDone = true;
|
|
399
401
|
const anchorSelector = isCssSafeId(anc.id) ? `#${anc.id}` : `[id="${escCssAttrValue(anc.id)}"]`;
|
|
400
402
|
add({
|
|
@@ -404,7 +406,7 @@ function generateAlternatives(attrs) {
|
|
|
404
406
|
score: scores.id
|
|
405
407
|
});
|
|
406
408
|
}
|
|
407
|
-
if (!dataAnchorDone && anc.dataAttr && anc.dataAttrCount === 1) {
|
|
409
|
+
if (!dataAnchorDone && anc.dataAttr && selectable(anc.dataAttr.name) && anc.dataAttrCount === 1) {
|
|
408
410
|
dataAnchorDone = true;
|
|
409
411
|
const { name: name2, value } = anc.dataAttr;
|
|
410
412
|
const anchorSelector = `[${name2}="${escCssAttrValue(value)}"]`;
|