@piwitests/reporter 0.20.0 → 0.22.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 +12 -0
- package/dist/cli/index.js +653 -10
- package/dist/global-setup-module.js +26 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1738 -1403
- package/dist/internal/capture/capture-fixtures.d.ts +3 -56
- package/dist/internal/capture/capture-fixtures.js +1658 -1349
- package/dist/internal/capture/locator-healing.js +75 -27
- package/dist/internal/capture/pick-on-failure.d.ts +6 -125
- package/dist/internal/capture/pick-on-failure.js +1108 -940
- package/package.json +4 -2
- package/templates/skills/apply-locator-healing/SKILL.md +32 -0
- package/templates/skills/investigate-failure/SKILL.md +36 -0
- package/templates/skills/setup-piwi/SKILL.md +62 -0
- package/templates/skills/stabilize-flaky-tests/SKILL.md +36 -0
|
@@ -54,7 +54,7 @@ __export(locator_healing_exports, {
|
|
|
54
54
|
module.exports = __toCommonJS(locator_healing_exports);
|
|
55
55
|
var path = __toESM(require("path"));
|
|
56
56
|
|
|
57
|
-
// ../
|
|
57
|
+
// ../core/src/locator-fingerprint.ts
|
|
58
58
|
var PRESENT_SIMILARITY = 0.8;
|
|
59
59
|
var MATCH_SIMILARITY = 0.2;
|
|
60
60
|
function parseAriaCandidates(ariaSnapshot) {
|
|
@@ -120,7 +120,7 @@ function matchRenamedElement(fp, candidates) {
|
|
|
120
120
|
return null;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
// ../
|
|
123
|
+
// ../core/src/locator-methods.ts
|
|
124
124
|
var LOCATOR_BUILDER_METHODS = [
|
|
125
125
|
"getByRole",
|
|
126
126
|
"getByTestId",
|
|
@@ -132,7 +132,7 @@ var LOCATOR_BUILDER_METHODS = [
|
|
|
132
132
|
"locator"
|
|
133
133
|
];
|
|
134
134
|
|
|
135
|
-
// ../
|
|
135
|
+
// ../core/src/locator-generation.ts
|
|
136
136
|
var TAG_TO_ROLE = {
|
|
137
137
|
a: "link",
|
|
138
138
|
button: "button",
|
|
@@ -146,6 +146,12 @@ var TAG_TO_ROLE = {
|
|
|
146
146
|
figcaption: "caption",
|
|
147
147
|
blockquote: "blockquote",
|
|
148
148
|
table: "table",
|
|
149
|
+
caption: "caption",
|
|
150
|
+
thead: "rowgroup",
|
|
151
|
+
tbody: "rowgroup",
|
|
152
|
+
tfoot: "rowgroup",
|
|
153
|
+
tr: "row",
|
|
154
|
+
td: "cell",
|
|
149
155
|
ul: "list",
|
|
150
156
|
ol: "list",
|
|
151
157
|
li: "listitem",
|
|
@@ -241,13 +247,19 @@ function classifyCssStability(className) {
|
|
|
241
247
|
function isAutoGenerated(value) {
|
|
242
248
|
if (/^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$/i.test(value)) return true;
|
|
243
249
|
if (/^[a-f0-9]{8,}$/i.test(value)) return true;
|
|
244
|
-
if (/^[a-z]+-\d
|
|
250
|
+
if (/^[a-z]+-\d{4,}$/.test(value)) return true;
|
|
251
|
+
if (/^(tab|panel|input|select|option|dialog|modal|popup|tooltip|menu|listbox|combobox|checkbox|radio|textarea|button|field|label|accordion|collapse|step|slider|toggle|switch|dropdown|overlay|portal|layer)-\d+$/i.test(
|
|
252
|
+
value
|
|
253
|
+
)) {
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
245
256
|
if (/^(emotion-|styled-|css-|sc-)/.test(value)) return true;
|
|
246
257
|
if (value.startsWith("ng-")) return true;
|
|
247
258
|
if (/^(radix-|headlessui-|mui-|mantine-|chakra-)/i.test(value)) return true;
|
|
248
259
|
if (/^:r[0-9a-z]+:$/i.test(value) || /^«r[0-9a-z]+»$/i.test(value)) return true;
|
|
249
260
|
return false;
|
|
250
261
|
}
|
|
262
|
+
var TEST_DATA_ATTRS = /* @__PURE__ */ new Set(["data-test", "data-test-id", "data-qa", "data-qa-id", "data-cy", "data-e2e"]);
|
|
251
263
|
function generateAlternatives(attrs) {
|
|
252
264
|
const alts = [];
|
|
253
265
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -262,6 +274,7 @@ function generateAlternatives(attrs) {
|
|
|
262
274
|
const role = resolveAriaRole(attrs);
|
|
263
275
|
const counts = attrs.selectorCounts;
|
|
264
276
|
const isUnique = (n) => n == null || n <= 1;
|
|
277
|
+
const ambiguityPenalty = (n) => n != null && n > 1 ? 45 : 0;
|
|
265
278
|
const text = attrs.textContent ? attrs.textContent.replace(/\s+/g, " ").trim() : null;
|
|
266
279
|
const testId = attr(attrs, "data-testid");
|
|
267
280
|
if (testId && isUnique(counts?.testId)) {
|
|
@@ -280,7 +293,7 @@ function generateAlternatives(attrs) {
|
|
|
280
293
|
locator: `getByRole('${role}', { name: '${esc(accessibleName)}'${levelPart} })`,
|
|
281
294
|
method: "getByRole",
|
|
282
295
|
args: withLevel({ role, name: accessibleName }),
|
|
283
|
-
score: 90
|
|
296
|
+
score: 90 - ambiguityPenalty(counts?.roleName)
|
|
284
297
|
});
|
|
285
298
|
}
|
|
286
299
|
const ariaLabel = attr(attrs, "aria-label");
|
|
@@ -289,7 +302,7 @@ function generateAlternatives(attrs) {
|
|
|
289
302
|
locator: `getByRole('${role}', { name: '${esc(ariaLabel)}'${levelPart} })`,
|
|
290
303
|
method: "getByRole",
|
|
291
304
|
args: withLevel({ role, name: ariaLabel }),
|
|
292
|
-
score: 85
|
|
305
|
+
score: 85 - ambiguityPenalty(counts?.roleName)
|
|
293
306
|
});
|
|
294
307
|
}
|
|
295
308
|
if (accessibleName && ["input", "select", "textarea"].includes(tag)) {
|
|
@@ -310,7 +323,7 @@ function generateAlternatives(attrs) {
|
|
|
310
323
|
locator: `getByPlaceholder('${esc(placeholder)}')`,
|
|
311
324
|
method: "getByPlaceholder",
|
|
312
325
|
args: { placeholder },
|
|
313
|
-
score: 80
|
|
326
|
+
score: 80 - ambiguityPenalty(counts?.placeholder)
|
|
314
327
|
});
|
|
315
328
|
}
|
|
316
329
|
if (text && text.length < 80) {
|
|
@@ -318,7 +331,7 @@ function generateAlternatives(attrs) {
|
|
|
318
331
|
locator: `getByText('${esc(text)}')`,
|
|
319
332
|
method: "getByText",
|
|
320
333
|
args: { text },
|
|
321
|
-
score: 75
|
|
334
|
+
score: 75 - ambiguityPenalty(counts?.text)
|
|
322
335
|
});
|
|
323
336
|
}
|
|
324
337
|
const id = attr(attrs, "id");
|
|
@@ -347,7 +360,7 @@ function generateAlternatives(attrs) {
|
|
|
347
360
|
locator: `getByAltText('${esc(alt)}')`,
|
|
348
361
|
method: "getByAltText",
|
|
349
362
|
args: { text: alt },
|
|
350
|
-
score: 60
|
|
363
|
+
score: 60 - ambiguityPenalty(counts?.alt)
|
|
351
364
|
});
|
|
352
365
|
}
|
|
353
366
|
const title = attr(attrs, "title");
|
|
@@ -356,48 +369,77 @@ function generateAlternatives(attrs) {
|
|
|
356
369
|
locator: `getByTitle('${esc(title)}')`,
|
|
357
370
|
method: "getByTitle",
|
|
358
371
|
args: { title },
|
|
359
|
-
score: 50
|
|
372
|
+
score: 50 - ambiguityPenalty(counts?.title)
|
|
360
373
|
});
|
|
361
374
|
}
|
|
362
375
|
const hasOwnTestId = !!(testId && isUnique(counts?.testId));
|
|
363
|
-
|
|
364
|
-
const rolePart = level != null ? `'${role}', { level: ${level} }` : `'${role}'`;
|
|
365
|
-
const leafArgs = withLevel({ role });
|
|
376
|
+
const addAnchoredChains = (leaf, scopedCount, scores) => {
|
|
366
377
|
let testIdAnchorDone = false;
|
|
367
378
|
let idAnchorDone = false;
|
|
368
379
|
let roleAnchorDone = false;
|
|
380
|
+
let dataAnchorDone = false;
|
|
381
|
+
let filterAnchorDone = false;
|
|
369
382
|
for (const anc of attrs.ancestors ?? []) {
|
|
370
|
-
if (anc
|
|
383
|
+
if (scopedCount(anc) !== 1) continue;
|
|
371
384
|
if (!testIdAnchorDone && anc.testId && anc.testIdCount === 1) {
|
|
372
385
|
testIdAnchorDone = true;
|
|
373
386
|
add({
|
|
374
|
-
locator: `getByTestId('${esc(anc.testId)}').
|
|
375
|
-
method:
|
|
376
|
-
args: { ...
|
|
377
|
-
score:
|
|
387
|
+
locator: `getByTestId('${esc(anc.testId)}').${leaf.expr}`,
|
|
388
|
+
method: leaf.method,
|
|
389
|
+
args: { ...leaf.args, anchorTestId: anc.testId },
|
|
390
|
+
score: scores.testId
|
|
378
391
|
});
|
|
379
392
|
}
|
|
380
393
|
if (!idAnchorDone && anc.id && !isAutoGenerated(anc.id) && anc.idCount === 1) {
|
|
381
394
|
idAnchorDone = true;
|
|
382
395
|
const anchorSelector = isCssSafeId(anc.id) ? `#${anc.id}` : `[id="${escCssAttrValue(anc.id)}"]`;
|
|
383
396
|
add({
|
|
384
|
-
locator: `locator('${esc(anchorSelector)}').
|
|
385
|
-
method:
|
|
386
|
-
args: { ...
|
|
387
|
-
score:
|
|
397
|
+
locator: `locator('${esc(anchorSelector)}').${leaf.expr}`,
|
|
398
|
+
method: leaf.method,
|
|
399
|
+
args: { ...leaf.args, anchorSelector },
|
|
400
|
+
score: scores.id
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
if (!dataAnchorDone && anc.dataAttr && anc.dataAttrCount === 1) {
|
|
404
|
+
dataAnchorDone = true;
|
|
405
|
+
const { name: name2, value } = anc.dataAttr;
|
|
406
|
+
const anchorSelector = `[${name2}="${escCssAttrValue(value)}"]`;
|
|
407
|
+
add({
|
|
408
|
+
locator: `locator('${esc(anchorSelector)}').${leaf.expr}`,
|
|
409
|
+
method: leaf.method,
|
|
410
|
+
args: { ...leaf.args, anchorSelector },
|
|
411
|
+
score: TEST_DATA_ATTRS.has(name2) ? scores.testData : scores.data
|
|
388
412
|
});
|
|
389
413
|
}
|
|
390
414
|
const ancestorRole = anc.role || TAG_TO_ROLE[anc.tag] || null;
|
|
391
|
-
if (!roleAnchorDone && ancestorRole && ancestorRole !== role && anc.roleCount === 1) {
|
|
415
|
+
if (!roleAnchorDone && ancestorRole && ancestorRole !== leaf.role && anc.roleCount === 1) {
|
|
392
416
|
roleAnchorDone = true;
|
|
393
417
|
add({
|
|
394
|
-
locator: `getByRole('${esc(ancestorRole)}').
|
|
395
|
-
method:
|
|
396
|
-
args: { ...
|
|
397
|
-
score:
|
|
418
|
+
locator: `getByRole('${esc(ancestorRole)}').${leaf.expr}`,
|
|
419
|
+
method: leaf.method,
|
|
420
|
+
args: { ...leaf.args, anchorRole: ancestorRole },
|
|
421
|
+
score: scores.role
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
if (!filterAnchorDone && ancestorRole && anc.filterText && anc.filterRoleCount === 1) {
|
|
425
|
+
filterAnchorDone = true;
|
|
426
|
+
add({
|
|
427
|
+
locator: `getByRole('${esc(ancestorRole)}').filter({ hasText: '${esc(anc.filterText)}' }).${leaf.expr}`,
|
|
428
|
+
method: leaf.method,
|
|
429
|
+
args: { ...leaf.args, anchorRole: ancestorRole, anchorHasText: anc.filterText },
|
|
430
|
+
score: scores.filter
|
|
398
431
|
});
|
|
399
432
|
}
|
|
400
433
|
}
|
|
434
|
+
};
|
|
435
|
+
if (role && !hasOwnTestId) {
|
|
436
|
+
const rolePart = level != null ? `'${role}', { level: ${level} }` : `'${role}'`;
|
|
437
|
+
const leafArgs = withLevel({ role });
|
|
438
|
+
addAnchoredChains(
|
|
439
|
+
{ expr: `getByRole(${rolePart})`, method: "getByRole", args: leafArgs, role },
|
|
440
|
+
(anc) => anc.scopedRoleCount,
|
|
441
|
+
{ testId: 72, testData: 70, id: 64, data: 60, role: 55, filter: 53 }
|
|
442
|
+
);
|
|
401
443
|
const pos = attrs.rolePosition;
|
|
402
444
|
if (pos && pos.role === role && (pos.count === 1 || level != null && pos.levelCount === 1)) {
|
|
403
445
|
add({
|
|
@@ -407,6 +449,12 @@ function generateAlternatives(attrs) {
|
|
|
407
449
|
score: 58
|
|
408
450
|
});
|
|
409
451
|
}
|
|
452
|
+
} else if (!role && text && text.length < 80 && !hasOwnTestId) {
|
|
453
|
+
addAnchoredChains(
|
|
454
|
+
{ expr: `getByText('${esc(text)}')`, method: "getByText", args: { text }, role: null },
|
|
455
|
+
(anc) => anc.scopedTextCount,
|
|
456
|
+
{ testId: 68, testData: 66, id: 60, data: 56, role: 51, filter: 49 }
|
|
457
|
+
);
|
|
410
458
|
}
|
|
411
459
|
const clsStr = attr(attrs, "class");
|
|
412
460
|
if (clsStr) {
|
|
@@ -1,80 +1,18 @@
|
|
|
1
1
|
import { TestInfo, Page } from '@playwright/test';
|
|
2
|
+
import { ProbeArg, ProbedAttrs, PickedAnchorInfo } from '@piwitests/picker-dom';
|
|
3
|
+
export { PickedAnchorInfo, PickedLeafInfo, generateAnchoredAlternatives, installPickerOverlay, mergeCandidates, showAnchorPicker, showPickerChoices } from '@piwitests/picker-dom';
|
|
2
4
|
import { FailedLocatorInfo } from './locator-healing.js';
|
|
3
|
-
import {
|
|
5
|
+
import { RankedLocator, LocatorSnapshot } from '@piwitests/core/locator-healing-types';
|
|
4
6
|
import '@piwitests/core/locator-generation';
|
|
5
7
|
|
|
6
|
-
/**
|
|
7
|
-
* Failure-time locator picker: when enabled (opt-in via the
|
|
8
|
-
* `pickLocatorOnFailure` reporter option / `PIWI_PICK_LOCATOR_ON_FAIL`), a
|
|
9
|
-
* test whose locator action failed gets a picker overlay injected into the
|
|
10
|
-
* still-open page. The flow is guided:
|
|
11
|
-
*
|
|
12
|
-
* 1. **Element** — hover highlights, a click picks. The pick snaps to the
|
|
13
|
-
* nearest actionable ancestor (a click on the `<span>` inside a button
|
|
14
|
-
* picks the button), and ↑/↓ walk the DOM tree before the click commits.
|
|
15
|
-
* 2. **Anchors** — the element's ancestors are listed; the human can bless
|
|
16
|
-
* one or more *stable parents* to scope the locator to, with a live
|
|
17
|
-
* "matches N" count for the current selection against the failing page.
|
|
18
|
-
* 3. **Confirm** — ranked replacement locators (standard generation merged
|
|
19
|
-
* with the anchor-scoped candidates) are listed; the human confirms one.
|
|
20
|
-
*
|
|
21
|
-
* The confirmed pick is folded into the failing call site's locator snapshot
|
|
22
|
-
* (so it rides the normal `piwi-locators` wire into the dashboard's healing
|
|
23
|
-
* panel) and attached as `piwi-user-pick` plus a report annotation.
|
|
24
|
-
*
|
|
25
|
-
* Gated exactly like failure-time inspection (`inspect-on-failure.ts`):
|
|
26
|
-
* headed browser, never CI, final attempt only.
|
|
27
|
-
*/
|
|
28
|
-
/** Element shape the in-page probe returns — structural view of what the picker needs. */
|
|
29
|
-
interface ProbedAttrs {
|
|
30
|
-
tagName: string;
|
|
31
|
-
attributes: Record<string, string | null>;
|
|
32
|
-
textContent: string | null;
|
|
33
|
-
center: {
|
|
34
|
-
x: number;
|
|
35
|
-
y: number;
|
|
36
|
-
} | null;
|
|
37
|
-
hasLabel?: boolean;
|
|
38
|
-
selectorCounts?: SelectorCounts;
|
|
39
|
-
rolePosition?: RolePosition | null;
|
|
40
|
-
ancestors?: AncestorAnchor[];
|
|
41
|
-
}
|
|
42
8
|
/**
|
|
43
9
|
* The probe dependency, passed in by the capture fixture to avoid an import
|
|
44
10
|
* cycle. `fn` is the fixture's in-page element probe (`probeElementAttrs`);
|
|
45
11
|
* `el` is a browser-side element, hence `any` (no DOM lib in this package).
|
|
46
|
-
* `arg` is the fixture's `CAPTURED_ATTRS_ARG` — its role maps also drive the
|
|
47
|
-
* anchor step's in-page role resolution.
|
|
48
12
|
*/
|
|
49
13
|
interface PickerProbe {
|
|
50
|
-
fn: (el: any, arg:
|
|
51
|
-
arg:
|
|
52
|
-
}
|
|
53
|
-
/** An ancestor the human blessed as an anchor, with its in-page uniqueness counts. */
|
|
54
|
-
interface PickedAnchorInfo {
|
|
55
|
-
tag: string;
|
|
56
|
-
/** Hops from the picked element (1 = direct parent). */
|
|
57
|
-
depth: number;
|
|
58
|
-
testId: string | null;
|
|
59
|
-
id: string | null;
|
|
60
|
-
ariaLabel: string | null;
|
|
61
|
-
/** Resolved anchor role (explicit attribute or tag-implied). */
|
|
62
|
-
role: string | null;
|
|
63
|
-
/** Document-wide match count for the anchor's own data-testid. */
|
|
64
|
-
testIdCount?: number;
|
|
65
|
-
/** Document-wide match count for the anchor's own id. */
|
|
66
|
-
idCount?: number;
|
|
67
|
-
/** Document-wide count of same-role elements carrying the same aria-label. */
|
|
68
|
-
labeledRoleCount?: number;
|
|
69
|
-
/** Document-wide count of elements resolving to the anchor's role. */
|
|
70
|
-
roleCount?: number;
|
|
71
|
-
/** Leaf matches (picked element's identity) within this anchor's subtree. */
|
|
72
|
-
scopedLeafCount?: number;
|
|
73
|
-
}
|
|
74
|
-
/** The picked element's identity used for anchor-scoped candidates. */
|
|
75
|
-
interface PickedLeafInfo {
|
|
76
|
-
role: string | null;
|
|
77
|
-
level: number | null;
|
|
14
|
+
fn: (el: any, arg: ProbeArg) => ProbedAttrs;
|
|
15
|
+
arg: ProbeArg;
|
|
78
16
|
}
|
|
79
17
|
/** A confirmed pick — everything the fixture records about the human's choice. */
|
|
80
18
|
interface UserPickResult {
|
|
@@ -113,63 +51,6 @@ declare function parseLeafLocatorExpression(rawExpr: string): {
|
|
|
113
51
|
* when no locator can be identified. Exported for tests.
|
|
114
52
|
*/
|
|
115
53
|
declare function deriveFailedLocator(testInfo: TestInfo): FailedLocatorInfo | null;
|
|
116
|
-
/**
|
|
117
|
-
* Build anchor-scoped candidates from the human-blessed parents. Shapes stay
|
|
118
|
-
* consistent with the heuristic chains in `generateAlternatives` — leaf method
|
|
119
|
-
* `getByRole` with flat anchor args, no leaf `name` key. Single-anchor
|
|
120
|
-
* candidates require the leaf to be unique inside that anchor; with two or
|
|
121
|
-
* more anchors a combined chain is added when the whole chain resolves to
|
|
122
|
-
* exactly one element (`chainLeafCount`). Pure — exported for tests.
|
|
123
|
-
*/
|
|
124
|
-
declare function generateAnchoredAlternatives(leaf: PickedLeafInfo, anchors: PickedAnchorInfo[], chainLeafCount?: number): RankedLocator[];
|
|
125
|
-
/** Merge candidate lists, first list winning duplicates, sorted by score. Exported for tests. */
|
|
126
|
-
declare function mergeCandidates(base: RankedLocator[], extra: RankedLocator[]): RankedLocator[];
|
|
127
|
-
/**
|
|
128
|
-
* Runs inside the browser via `evaluate()` — installs the element-picking
|
|
129
|
-
* overlay: a hover highlight, an instruction banner, and capture-phase
|
|
130
|
-
* listeners that suppress the app's own handlers while picking. The hover
|
|
131
|
-
* target snaps to the nearest actionable ancestor (button/link/field/role/
|
|
132
|
-
* testid) and ↑/↓ walk the DOM chain before the click commits. Resolves
|
|
133
|
-
* through `__piwiPickState` ('picked' | 'skipped') polled from Node; the
|
|
134
|
-
* picked element is parked in `__piwiPickedElement` for an `evaluateHandle`
|
|
135
|
-
* read. Must stay fully self-contained (no module-closure references).
|
|
136
|
-
*/
|
|
137
|
-
declare function installPickerOverlay(arg: {
|
|
138
|
-
failing: string | null;
|
|
139
|
-
}): void;
|
|
140
|
-
/**
|
|
141
|
-
* Runs inside the browser via `evaluate()` — the anchor step: lists the picked
|
|
142
|
-
* element's ancestors so the human can bless one or more stable parents to
|
|
143
|
-
* scope the locator to. Each row shows the ancestor's strongest hook and how
|
|
144
|
-
* many leaf matches it contains; the footer shows a live "matches N" count for
|
|
145
|
-
* the combined selection, recomputed against the real failing page on every
|
|
146
|
-
* toggle (exactly 1 = green). Hovering a row outlines that ancestor in the
|
|
147
|
-
* page. Resolves through `__piwiAnchorState` ('done' | 'skipped'); selected
|
|
148
|
-
* anchors land in `__piwiPickAnchors` (+ `__piwiPickChainCount`). Role
|
|
149
|
-
* resolution reuses the maps passed in `arg` (single source of truth in
|
|
150
|
-
* locator-healing.ts). Must stay fully self-contained.
|
|
151
|
-
*/
|
|
152
|
-
declare function showAnchorPicker(arg: {
|
|
153
|
-
tagRoles: Record<string, string>;
|
|
154
|
-
inputRoles: Record<string, string>;
|
|
155
|
-
roleSources: string;
|
|
156
|
-
leafRole: string;
|
|
157
|
-
leafLevel: number | null;
|
|
158
|
-
leafTestId: string | null;
|
|
159
|
-
}): void;
|
|
160
|
-
/**
|
|
161
|
-
* Runs inside the browser via `evaluate()` — replaces the pick overlay with a
|
|
162
|
-
* confirmation panel listing the ranked replacement locators. The chosen index
|
|
163
|
-
* lands in `__piwiPickChoice` (-1 = skipped), polled from Node. Must stay
|
|
164
|
-
* fully self-contained.
|
|
165
|
-
*/
|
|
166
|
-
declare function showPickerChoices(arg: {
|
|
167
|
-
failing: string | null;
|
|
168
|
-
choices: Array<{
|
|
169
|
-
locator: string;
|
|
170
|
-
score: number;
|
|
171
|
-
}>;
|
|
172
|
-
}): void;
|
|
173
54
|
/**
|
|
174
55
|
* Drive the full pick flow on the failing page: element pick (snap +
|
|
175
56
|
* tree-walk), optional stable-parent anchoring with live match counts, ranked
|
|
@@ -195,4 +76,4 @@ declare function runLocatorPicker(page: Page, testInfo: TestInfo, failed: Failed
|
|
|
195
76
|
*/
|
|
196
77
|
declare function applyPickToSnapshots(snapshots: LocatorSnapshot[], pick: UserPickResult): boolean;
|
|
197
78
|
|
|
198
|
-
export { type
|
|
79
|
+
export { type PickerProbe, type UserPickResult, applyPickToSnapshots, deriveFailedLocator, parseLeafLocatorExpression, runLocatorPicker };
|