@piwitests/reporter 0.55.0 → 0.56.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 +1339 -1369
- package/dist/global-setup-module.js +32 -22
- package/dist/index.d.ts +9 -0
- package/dist/index.js +460 -331
- package/dist/internal/capture/capture-fixtures.d.ts +2 -1
- package/dist/internal/capture/capture-fixtures.js +1035 -980
- package/dist/internal/capture/locator-healing.js +70 -26
- package/dist/internal/capture/pick-on-failure.js +84 -54
- package/package.json +1 -1
|
@@ -56,6 +56,35 @@ __export(locator_healing_exports, {
|
|
|
56
56
|
module.exports = __toCommonJS(locator_healing_exports);
|
|
57
57
|
var path = __toESM(require("path"));
|
|
58
58
|
|
|
59
|
+
// ../core/src/locator-chain.ts
|
|
60
|
+
var LOCATING_METHODS = /* @__PURE__ */ new Set([
|
|
61
|
+
"getByRole",
|
|
62
|
+
"getByTestId",
|
|
63
|
+
"getByText",
|
|
64
|
+
"getByLabel",
|
|
65
|
+
"getByPlaceholder",
|
|
66
|
+
"getByAltText",
|
|
67
|
+
"getByTitle",
|
|
68
|
+
"locator",
|
|
69
|
+
"frameLocator"
|
|
70
|
+
]);
|
|
71
|
+
var NARROWING_METHODS = /* @__PURE__ */ new Set([
|
|
72
|
+
"filter",
|
|
73
|
+
"first",
|
|
74
|
+
"last",
|
|
75
|
+
"nth",
|
|
76
|
+
"and",
|
|
77
|
+
"or",
|
|
78
|
+
"visible",
|
|
79
|
+
"contentFrame",
|
|
80
|
+
"owner"
|
|
81
|
+
]);
|
|
82
|
+
var LOCATOR_CHAIN_METHODS = /* @__PURE__ */ new Set([...LOCATING_METHODS, ...NARROWING_METHODS]);
|
|
83
|
+
function quoteJsString(value) {
|
|
84
|
+
const json = JSON.stringify(value).slice(1, -1);
|
|
85
|
+
return `'${json.replace(/\\"/g, '"').replace(/'/g, "\\'").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029")}'`;
|
|
86
|
+
}
|
|
87
|
+
|
|
59
88
|
// ../core/src/locator-fingerprint.ts
|
|
60
89
|
var PRESENT_SIMILARITY = 0.8;
|
|
61
90
|
var MATCH_SIMILARITY = 0.2;
|
|
@@ -86,6 +115,23 @@ function textSimilarity(a, b) {
|
|
|
86
115
|
for (const t of sa) if (sb.has(t)) common++;
|
|
87
116
|
return 2 * common / (sa.size + sb.size);
|
|
88
117
|
}
|
|
118
|
+
var FUNCTION_WORDS = new Set(
|
|
119
|
+
"the and for with from your you our this that les des une pour avec par sur aux dans vos votre nos notre".split(" ")
|
|
120
|
+
);
|
|
121
|
+
function significantWords(text) {
|
|
122
|
+
const all = (text ?? "").replace(new RegExp("(\\p{Ll})(\\p{Lu})", "gu"), "$1 $2").toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
123
|
+
const meaningful = all.filter((w) => w.length >= 3 && !FUNCTION_WORDS.has(w));
|
|
124
|
+
return new Set(meaningful.length > 0 ? meaningful : all);
|
|
125
|
+
}
|
|
126
|
+
function significantWordSimilarity(a, b) {
|
|
127
|
+
const sa = significantWords(a);
|
|
128
|
+
const sb = significantWords(b);
|
|
129
|
+
if (sa.size === 0 && sb.size === 0) return 1;
|
|
130
|
+
if (sa.size === 0 || sb.size === 0) return 0;
|
|
131
|
+
let common = 0;
|
|
132
|
+
for (const w of sa) if (sb.has(w)) common++;
|
|
133
|
+
return 2 * common / (sa.size + sb.size);
|
|
134
|
+
}
|
|
89
135
|
function fingerprintPresent(fp, candidates) {
|
|
90
136
|
if (!fp.name) return false;
|
|
91
137
|
return candidates.some(
|
|
@@ -107,7 +153,7 @@ function matchRenamedElement(fp, candidates) {
|
|
|
107
153
|
let best = null;
|
|
108
154
|
let bestScore = -1;
|
|
109
155
|
for (const c of pool) {
|
|
110
|
-
const s =
|
|
156
|
+
const s = significantWordSimilarity(c.name, fp.name);
|
|
111
157
|
if (s > bestScore) {
|
|
112
158
|
bestScore = s;
|
|
113
159
|
best = c;
|
|
@@ -230,7 +276,6 @@ function headingLevel(attrs, role) {
|
|
|
230
276
|
return null;
|
|
231
277
|
}
|
|
232
278
|
var attr = (a, key) => a.attributes[key] || null;
|
|
233
|
-
var esc = (s) => s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
234
279
|
var escCssAttrValue = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
235
280
|
var isCssSafeId = (id) => /^[A-Za-z][A-Za-z0-9_-]*$/.test(id);
|
|
236
281
|
function classifyCssStability(className) {
|
|
@@ -285,7 +330,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
285
330
|
const testId = attr(attrs, testIdAttribute);
|
|
286
331
|
if (testId && isUnique(counts?.testId)) {
|
|
287
332
|
add({
|
|
288
|
-
locator: `getByTestId(
|
|
333
|
+
locator: `getByTestId(${quoteJsString(testId)})`,
|
|
289
334
|
method: "getByTestId",
|
|
290
335
|
args: { testId },
|
|
291
336
|
score: 100
|
|
@@ -296,7 +341,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
296
341
|
const withLevel = (base) => level != null ? { ...base, level } : base;
|
|
297
342
|
if (role && accessibleName) {
|
|
298
343
|
add({
|
|
299
|
-
locator: `getByRole(
|
|
344
|
+
locator: `getByRole(${quoteJsString(role)}, { name: ${quoteJsString(accessibleName)}${levelPart} })`,
|
|
300
345
|
method: "getByRole",
|
|
301
346
|
args: withLevel({ role, name: accessibleName }),
|
|
302
347
|
score: 90 - ambiguityPenalty(counts?.roleName)
|
|
@@ -305,7 +350,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
305
350
|
const ariaLabel = attr(attrs, "aria-label");
|
|
306
351
|
if (role && ariaLabel && ariaLabel !== accessibleName) {
|
|
307
352
|
add({
|
|
308
|
-
locator: `getByRole(
|
|
353
|
+
locator: `getByRole(${quoteJsString(role)}, { name: ${quoteJsString(ariaLabel)}${levelPart} })`,
|
|
309
354
|
method: "getByRole",
|
|
310
355
|
args: withLevel({ role, name: ariaLabel }),
|
|
311
356
|
score: 85 - ambiguityPenalty(counts?.roleName)
|
|
@@ -316,7 +361,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
316
361
|
const labelBacked = attrs.hasLabel === void 0 ? true : attrs.hasLabel === true || label === accessibleName;
|
|
317
362
|
if (labelBacked) {
|
|
318
363
|
add({
|
|
319
|
-
locator: `getByLabel(
|
|
364
|
+
locator: `getByLabel(${quoteJsString(accessibleName)})`,
|
|
320
365
|
method: "getByLabel",
|
|
321
366
|
args: { label: accessibleName },
|
|
322
367
|
score: 85
|
|
@@ -326,7 +371,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
326
371
|
const placeholder = attr(attrs, "placeholder");
|
|
327
372
|
if (placeholder) {
|
|
328
373
|
add({
|
|
329
|
-
locator: `getByPlaceholder(
|
|
374
|
+
locator: `getByPlaceholder(${quoteJsString(placeholder)})`,
|
|
330
375
|
method: "getByPlaceholder",
|
|
331
376
|
args: { placeholder },
|
|
332
377
|
score: 80 - ambiguityPenalty(counts?.placeholder)
|
|
@@ -334,7 +379,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
334
379
|
}
|
|
335
380
|
if (text && text.length < 80) {
|
|
336
381
|
add({
|
|
337
|
-
locator: `getByText(
|
|
382
|
+
locator: `getByText(${quoteJsString(text)})`,
|
|
338
383
|
method: "getByText",
|
|
339
384
|
args: { text },
|
|
340
385
|
score: 75 - ambiguityPenalty(counts?.text)
|
|
@@ -344,7 +389,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
344
389
|
if (id && selectable("id") && !isAutoGenerated(id) && isUnique(counts?.id)) {
|
|
345
390
|
const selector = isCssSafeId(id) ? `#${id}` : `[id="${escCssAttrValue(id)}"]`;
|
|
346
391
|
add({
|
|
347
|
-
locator: `locator(
|
|
392
|
+
locator: `locator(${quoteJsString(selector)})`,
|
|
348
393
|
method: "locator",
|
|
349
394
|
args: { selector },
|
|
350
395
|
score: 65
|
|
@@ -354,7 +399,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
354
399
|
if (name && selectable("name") && isUnique(counts?.name)) {
|
|
355
400
|
const selector = `[name="${escCssAttrValue(name)}"]`;
|
|
356
401
|
add({
|
|
357
|
-
locator: `locator(
|
|
402
|
+
locator: `locator(${quoteJsString(selector)})`,
|
|
358
403
|
method: "locator",
|
|
359
404
|
args: { selector },
|
|
360
405
|
score: 60
|
|
@@ -363,7 +408,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
363
408
|
const alt = attr(attrs, "alt");
|
|
364
409
|
if (alt) {
|
|
365
410
|
add({
|
|
366
|
-
locator: `getByAltText(
|
|
411
|
+
locator: `getByAltText(${quoteJsString(alt)})`,
|
|
367
412
|
method: "getByAltText",
|
|
368
413
|
args: { text: alt },
|
|
369
414
|
score: 60 - ambiguityPenalty(counts?.alt)
|
|
@@ -372,7 +417,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
372
417
|
const title = attr(attrs, "title");
|
|
373
418
|
if (title) {
|
|
374
419
|
add({
|
|
375
|
-
locator: `getByTitle(
|
|
420
|
+
locator: `getByTitle(${quoteJsString(title)})`,
|
|
376
421
|
method: "getByTitle",
|
|
377
422
|
args: { title },
|
|
378
423
|
score: 50 - ambiguityPenalty(counts?.title)
|
|
@@ -390,7 +435,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
390
435
|
if (!testIdAnchorDone && anc.testId && anc.testIdCount === 1) {
|
|
391
436
|
testIdAnchorDone = true;
|
|
392
437
|
add({
|
|
393
|
-
locator: `getByTestId(
|
|
438
|
+
locator: `getByTestId(${quoteJsString(anc.testId)}).${leaf.expr}`,
|
|
394
439
|
method: leaf.method,
|
|
395
440
|
args: { ...leaf.args, anchorTestId: anc.testId },
|
|
396
441
|
score: scores.testId
|
|
@@ -400,7 +445,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
400
445
|
idAnchorDone = true;
|
|
401
446
|
const anchorSelector = isCssSafeId(anc.id) ? `#${anc.id}` : `[id="${escCssAttrValue(anc.id)}"]`;
|
|
402
447
|
add({
|
|
403
|
-
locator: `locator(
|
|
448
|
+
locator: `locator(${quoteJsString(anchorSelector)}).${leaf.expr}`,
|
|
404
449
|
method: leaf.method,
|
|
405
450
|
args: { ...leaf.args, anchorSelector },
|
|
406
451
|
score: scores.id
|
|
@@ -411,7 +456,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
411
456
|
const { name: name2, value } = anc.dataAttr;
|
|
412
457
|
const anchorSelector = `[${name2}="${escCssAttrValue(value)}"]`;
|
|
413
458
|
add({
|
|
414
|
-
locator: `locator(
|
|
459
|
+
locator: `locator(${quoteJsString(anchorSelector)}).${leaf.expr}`,
|
|
415
460
|
method: leaf.method,
|
|
416
461
|
args: { ...leaf.args, anchorSelector },
|
|
417
462
|
score: TEST_DATA_ATTRS.has(name2) ? scores.testData : scores.data
|
|
@@ -421,7 +466,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
421
466
|
if (!roleAnchorDone && ancestorRole && ancestorRole !== leaf.role && anc.roleCount === 1) {
|
|
422
467
|
roleAnchorDone = true;
|
|
423
468
|
add({
|
|
424
|
-
locator: `getByRole(
|
|
469
|
+
locator: `getByRole(${quoteJsString(ancestorRole)}).${leaf.expr}`,
|
|
425
470
|
method: leaf.method,
|
|
426
471
|
args: { ...leaf.args, anchorRole: ancestorRole },
|
|
427
472
|
score: scores.role
|
|
@@ -430,7 +475,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
430
475
|
if (!filterAnchorDone && ancestorRole && anc.filterText && anc.filterRoleCount === 1) {
|
|
431
476
|
filterAnchorDone = true;
|
|
432
477
|
add({
|
|
433
|
-
locator: `getByRole(
|
|
478
|
+
locator: `getByRole(${quoteJsString(ancestorRole)}).filter({ hasText: ${quoteJsString(anc.filterText)} }).${leaf.expr}`,
|
|
434
479
|
method: leaf.method,
|
|
435
480
|
args: { ...leaf.args, anchorRole: ancestorRole, anchorHasText: anc.filterText },
|
|
436
481
|
score: scores.filter
|
|
@@ -439,7 +484,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
439
484
|
}
|
|
440
485
|
};
|
|
441
486
|
if (role && !hasOwnTestId) {
|
|
442
|
-
const rolePart = level != null ?
|
|
487
|
+
const rolePart = level != null ? `${quoteJsString(role)}, { level: ${level} }` : quoteJsString(role);
|
|
443
488
|
const leafArgs = withLevel({ role });
|
|
444
489
|
addAnchoredChains(
|
|
445
490
|
{ expr: `getByRole(${rolePart})`, method: "getByRole", args: leafArgs, role },
|
|
@@ -457,7 +502,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
457
502
|
}
|
|
458
503
|
} else if (!role && text && text.length < 80 && !hasOwnTestId) {
|
|
459
504
|
addAnchoredChains(
|
|
460
|
-
{ expr: `getByText(
|
|
505
|
+
{ expr: `getByText(${quoteJsString(text)})`, method: "getByText", args: { text }, role: null },
|
|
461
506
|
(anc) => anc.scopedTextCount,
|
|
462
507
|
{ testId: 68, testData: 66, id: 60, data: 56, role: 51, filter: 49 }
|
|
463
508
|
);
|
|
@@ -468,7 +513,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
468
513
|
const classes = clsStr.split(/\s+/).filter((c) => c.length > 1 && /^[A-Za-z_-][A-Za-z0-9_-]*$/.test(c) && isUnique(countsClasses?.[c])).map((cls) => ({ cls, score: classifyCssStability(cls) })).sort((a, b) => b.score - a.score).slice(0, 3);
|
|
469
514
|
for (const { cls, score } of classes) {
|
|
470
515
|
add({
|
|
471
|
-
locator: `locator(
|
|
516
|
+
locator: `locator(${quoteJsString(`.${cls}`)})`,
|
|
472
517
|
method: "locator",
|
|
473
518
|
args: { selector: `.${cls}` },
|
|
474
519
|
score
|
|
@@ -631,7 +676,6 @@ var NAME_BASED_METHODS = /* @__PURE__ */ new Set([
|
|
|
631
676
|
"getByTitle",
|
|
632
677
|
"getByAltText"
|
|
633
678
|
]);
|
|
634
|
-
var escAttr = (s) => s.replaceAll("\\", "\\\\").replaceAll("'", "\\'");
|
|
635
679
|
var SUGG_TEXT_ROLES = /* @__PURE__ */ new Set([
|
|
636
680
|
"button",
|
|
637
681
|
"link",
|
|
@@ -664,9 +708,9 @@ function failedNameAndRole(failed) {
|
|
|
664
708
|
function renderFailing(failed) {
|
|
665
709
|
const { role, name } = failedNameAndRole(failed);
|
|
666
710
|
if (failed.method === "getByRole") {
|
|
667
|
-
return name ? `getByRole(
|
|
711
|
+
return name ? `getByRole(${quoteJsString(role ?? "")}, { name: ${quoteJsString(name)} })` : `getByRole(${quoteJsString(role ?? "")})`;
|
|
668
712
|
}
|
|
669
|
-
return `${failed.method}(
|
|
713
|
+
return `${failed.method}(${quoteJsString(name ?? "")})`;
|
|
670
714
|
}
|
|
671
715
|
function freshSuggestions(candidate, failedMethod) {
|
|
672
716
|
const out = [];
|
|
@@ -676,9 +720,9 @@ function freshSuggestions(candidate, failedMethod) {
|
|
|
676
720
|
if (!out.includes(s)) out.push(s);
|
|
677
721
|
};
|
|
678
722
|
const levelPart = candidate.level != null ? `, level: ${candidate.level}` : "";
|
|
679
|
-
const roleLoc = `getByRole(
|
|
680
|
-
const textLoc = `getByText(
|
|
681
|
-
const labelLoc = `getByLabel(
|
|
723
|
+
const roleLoc = `getByRole(${quoteJsString(role)}, { name: ${quoteJsString(name)}${levelPart} })`;
|
|
724
|
+
const textLoc = `getByText(${quoteJsString(name)})`;
|
|
725
|
+
const labelLoc = `getByLabel(${quoteJsString(name)})`;
|
|
682
726
|
if (failedMethod === "getByText" && SUGG_TEXT_ROLES.has(role)) push(textLoc);
|
|
683
727
|
if (failedMethod === "getByLabel" && SUGG_FIELD_ROLES.has(role)) push(labelLoc);
|
|
684
728
|
push(roleLoc);
|
|
@@ -294,6 +294,10 @@ function tryParseLocatorChain(expr) {
|
|
|
294
294
|
return null;
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
|
+
function quoteJsString(value) {
|
|
298
|
+
const json = JSON.stringify(value).slice(1, -1);
|
|
299
|
+
return `'${json.replace(/\\"/g, '"').replace(/'/g, "\\'").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029")}'`;
|
|
300
|
+
}
|
|
297
301
|
function parseLeafLocatorCall(expr) {
|
|
298
302
|
const chain = tryParseLocatorChain(expr);
|
|
299
303
|
if (!chain) return null;
|
|
@@ -326,6 +330,44 @@ function locatorCallValues(call, opts = {}) {
|
|
|
326
330
|
return call.args.map(value).filter((v) => v !== void 0);
|
|
327
331
|
}
|
|
328
332
|
|
|
333
|
+
// ../core/src/locator-methods.ts
|
|
334
|
+
var LOCATOR_BUILDER_METHODS = [
|
|
335
|
+
"getByRole",
|
|
336
|
+
"getByTestId",
|
|
337
|
+
"getByText",
|
|
338
|
+
"getByLabel",
|
|
339
|
+
"getByPlaceholder",
|
|
340
|
+
"getByAltText",
|
|
341
|
+
"getByTitle",
|
|
342
|
+
"locator"
|
|
343
|
+
];
|
|
344
|
+
|
|
345
|
+
// ../core/src/error-parse.ts
|
|
346
|
+
var ANSI_RE = new RegExp("\\u001B\\[[0-9;]*m", "g");
|
|
347
|
+
function stripAnsi(text) {
|
|
348
|
+
return text.replace(ANSI_RE, "");
|
|
349
|
+
}
|
|
350
|
+
var CHAIN_LINK_METHODS = /* @__PURE__ */ new Set([
|
|
351
|
+
...LOCATOR_BUILDER_METHODS,
|
|
352
|
+
"frameLocator",
|
|
353
|
+
"contentFrame",
|
|
354
|
+
"filter",
|
|
355
|
+
"first",
|
|
356
|
+
"last",
|
|
357
|
+
"nth",
|
|
358
|
+
"and",
|
|
359
|
+
"or",
|
|
360
|
+
"describe"
|
|
361
|
+
]);
|
|
362
|
+
var LEAF_METHODS = new Set(LOCATOR_BUILDER_METHODS);
|
|
363
|
+
var SUBJECTS = "locator|page|frame|frameLocator|elementHandle|mouse|keyboard|touchscreen|browserContext|browser|apiRequestContext|request|response|route|download|dialog|fileChooser|expect|worker|jsHandle|clock|tracing|video";
|
|
364
|
+
var CALL_RE = new RegExp(`\\b(${SUBJECTS})\\.(\\w+): `);
|
|
365
|
+
function readLocatorHeader(text) {
|
|
366
|
+
const header = /^[ \t]*Locator:[ \t]*(.+)$/m.exec(text);
|
|
367
|
+
return header ? header[1].trim() : null;
|
|
368
|
+
}
|
|
369
|
+
var MAX_PARSED_ERROR_CHARS = 64 * 1024;
|
|
370
|
+
|
|
329
371
|
// ../picker-dom/src/overlay-element.ts
|
|
330
372
|
function installPickerOverlay(arg) {
|
|
331
373
|
const g = globalThis;
|
|
@@ -414,26 +456,26 @@ function installPickerOverlay(arg) {
|
|
|
414
456
|
}
|
|
415
457
|
};
|
|
416
458
|
mount(highlight, label, banner);
|
|
417
|
-
const
|
|
459
|
+
const quoteJs = (s) => `'${JSON.stringify(s).slice(1, -1).replace(/\\"/g, '"').replace(/'/g, "\\'").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029")}'`;
|
|
418
460
|
const describe = (el) => {
|
|
419
461
|
const tag = (el.tagName || "?").toLowerCase();
|
|
420
462
|
const testId = el.getAttribute && el.getAttribute(testIdAttr);
|
|
421
|
-
if (testId) return `getByTestId(
|
|
463
|
+
if (testId) return `getByTestId(${quoteJs(testId)})`;
|
|
422
464
|
if (el.labels && el.labels.length > 0) {
|
|
423
465
|
const labelText = (el.labels[0].textContent || "").replace(/\s+/g, " ").trim().slice(0, 80);
|
|
424
|
-
if (labelText) return `getByLabel(
|
|
466
|
+
if (labelText) return `getByLabel(${quoteJs(labelText)})`;
|
|
425
467
|
}
|
|
426
468
|
const ariaLabel = el.getAttribute && el.getAttribute("aria-label");
|
|
427
|
-
if (ariaLabel) return `getByLabel(
|
|
469
|
+
if (ariaLabel) return `getByLabel(${quoteJs(ariaLabel)})`;
|
|
428
470
|
const placeholder = el.getAttribute && el.getAttribute("placeholder");
|
|
429
|
-
if (placeholder) return `getByPlaceholder(
|
|
471
|
+
if (placeholder) return `getByPlaceholder(${quoteJs(placeholder)})`;
|
|
430
472
|
const alt = el.getAttribute && el.getAttribute("alt");
|
|
431
|
-
if (alt) return `getByAltText(
|
|
473
|
+
if (alt) return `getByAltText(${quoteJs(alt)})`;
|
|
432
474
|
const titleAttr = el.getAttribute && el.getAttribute("title");
|
|
433
|
-
if (titleAttr) return `getByTitle(
|
|
434
|
-
if (el.id) return `locator(
|
|
475
|
+
if (titleAttr) return `getByTitle(${quoteJs(titleAttr)})`;
|
|
476
|
+
if (el.id) return `locator(${quoteJs(`#${el.id}`)})`;
|
|
435
477
|
const cls = (el.getAttribute && el.getAttribute("class") || "").split(/\s+/).find((c) => c.length > 1);
|
|
436
|
-
return cls ? `locator(
|
|
478
|
+
return cls ? `locator(${quoteJs(`.${cls}`)})` : tag;
|
|
437
479
|
};
|
|
438
480
|
const buildChain = (raw) => {
|
|
439
481
|
const chain2 = [];
|
|
@@ -1126,7 +1168,6 @@ function headingLevel(attrs, role) {
|
|
|
1126
1168
|
return null;
|
|
1127
1169
|
}
|
|
1128
1170
|
var attr = (a, key) => a.attributes[key] || null;
|
|
1129
|
-
var esc = (s) => s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1130
1171
|
var escCssAttrValue = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1131
1172
|
var isCssSafeId = (id) => /^[A-Za-z][A-Za-z0-9_-]*$/.test(id);
|
|
1132
1173
|
function classifyCssStability(className) {
|
|
@@ -1181,7 +1222,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
1181
1222
|
const testId = attr(attrs, testIdAttribute);
|
|
1182
1223
|
if (testId && isUnique(counts?.testId)) {
|
|
1183
1224
|
add({
|
|
1184
|
-
locator: `getByTestId(
|
|
1225
|
+
locator: `getByTestId(${quoteJsString(testId)})`,
|
|
1185
1226
|
method: "getByTestId",
|
|
1186
1227
|
args: { testId },
|
|
1187
1228
|
score: 100
|
|
@@ -1192,7 +1233,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
1192
1233
|
const withLevel = (base) => level != null ? { ...base, level } : base;
|
|
1193
1234
|
if (role && accessibleName) {
|
|
1194
1235
|
add({
|
|
1195
|
-
locator: `getByRole(
|
|
1236
|
+
locator: `getByRole(${quoteJsString(role)}, { name: ${quoteJsString(accessibleName)}${levelPart} })`,
|
|
1196
1237
|
method: "getByRole",
|
|
1197
1238
|
args: withLevel({ role, name: accessibleName }),
|
|
1198
1239
|
score: 90 - ambiguityPenalty(counts?.roleName)
|
|
@@ -1201,7 +1242,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
1201
1242
|
const ariaLabel = attr(attrs, "aria-label");
|
|
1202
1243
|
if (role && ariaLabel && ariaLabel !== accessibleName) {
|
|
1203
1244
|
add({
|
|
1204
|
-
locator: `getByRole(
|
|
1245
|
+
locator: `getByRole(${quoteJsString(role)}, { name: ${quoteJsString(ariaLabel)}${levelPart} })`,
|
|
1205
1246
|
method: "getByRole",
|
|
1206
1247
|
args: withLevel({ role, name: ariaLabel }),
|
|
1207
1248
|
score: 85 - ambiguityPenalty(counts?.roleName)
|
|
@@ -1212,7 +1253,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
1212
1253
|
const labelBacked = attrs.hasLabel === void 0 ? true : attrs.hasLabel === true || label === accessibleName;
|
|
1213
1254
|
if (labelBacked) {
|
|
1214
1255
|
add({
|
|
1215
|
-
locator: `getByLabel(
|
|
1256
|
+
locator: `getByLabel(${quoteJsString(accessibleName)})`,
|
|
1216
1257
|
method: "getByLabel",
|
|
1217
1258
|
args: { label: accessibleName },
|
|
1218
1259
|
score: 85
|
|
@@ -1222,7 +1263,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
1222
1263
|
const placeholder = attr(attrs, "placeholder");
|
|
1223
1264
|
if (placeholder) {
|
|
1224
1265
|
add({
|
|
1225
|
-
locator: `getByPlaceholder(
|
|
1266
|
+
locator: `getByPlaceholder(${quoteJsString(placeholder)})`,
|
|
1226
1267
|
method: "getByPlaceholder",
|
|
1227
1268
|
args: { placeholder },
|
|
1228
1269
|
score: 80 - ambiguityPenalty(counts?.placeholder)
|
|
@@ -1230,7 +1271,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
1230
1271
|
}
|
|
1231
1272
|
if (text && text.length < 80) {
|
|
1232
1273
|
add({
|
|
1233
|
-
locator: `getByText(
|
|
1274
|
+
locator: `getByText(${quoteJsString(text)})`,
|
|
1234
1275
|
method: "getByText",
|
|
1235
1276
|
args: { text },
|
|
1236
1277
|
score: 75 - ambiguityPenalty(counts?.text)
|
|
@@ -1240,7 +1281,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
1240
1281
|
if (id && selectable("id") && !isAutoGenerated(id) && isUnique(counts?.id)) {
|
|
1241
1282
|
const selector = isCssSafeId(id) ? `#${id}` : `[id="${escCssAttrValue(id)}"]`;
|
|
1242
1283
|
add({
|
|
1243
|
-
locator: `locator(
|
|
1284
|
+
locator: `locator(${quoteJsString(selector)})`,
|
|
1244
1285
|
method: "locator",
|
|
1245
1286
|
args: { selector },
|
|
1246
1287
|
score: 65
|
|
@@ -1250,7 +1291,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
1250
1291
|
if (name && selectable("name") && isUnique(counts?.name)) {
|
|
1251
1292
|
const selector = `[name="${escCssAttrValue(name)}"]`;
|
|
1252
1293
|
add({
|
|
1253
|
-
locator: `locator(
|
|
1294
|
+
locator: `locator(${quoteJsString(selector)})`,
|
|
1254
1295
|
method: "locator",
|
|
1255
1296
|
args: { selector },
|
|
1256
1297
|
score: 60
|
|
@@ -1259,7 +1300,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
1259
1300
|
const alt = attr(attrs, "alt");
|
|
1260
1301
|
if (alt) {
|
|
1261
1302
|
add({
|
|
1262
|
-
locator: `getByAltText(
|
|
1303
|
+
locator: `getByAltText(${quoteJsString(alt)})`,
|
|
1263
1304
|
method: "getByAltText",
|
|
1264
1305
|
args: { text: alt },
|
|
1265
1306
|
score: 60 - ambiguityPenalty(counts?.alt)
|
|
@@ -1268,7 +1309,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
1268
1309
|
const title = attr(attrs, "title");
|
|
1269
1310
|
if (title) {
|
|
1270
1311
|
add({
|
|
1271
|
-
locator: `getByTitle(
|
|
1312
|
+
locator: `getByTitle(${quoteJsString(title)})`,
|
|
1272
1313
|
method: "getByTitle",
|
|
1273
1314
|
args: { title },
|
|
1274
1315
|
score: 50 - ambiguityPenalty(counts?.title)
|
|
@@ -1286,7 +1327,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
1286
1327
|
if (!testIdAnchorDone && anc.testId && anc.testIdCount === 1) {
|
|
1287
1328
|
testIdAnchorDone = true;
|
|
1288
1329
|
add({
|
|
1289
|
-
locator: `getByTestId(
|
|
1330
|
+
locator: `getByTestId(${quoteJsString(anc.testId)}).${leaf.expr}`,
|
|
1290
1331
|
method: leaf.method,
|
|
1291
1332
|
args: { ...leaf.args, anchorTestId: anc.testId },
|
|
1292
1333
|
score: scores.testId
|
|
@@ -1296,7 +1337,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
1296
1337
|
idAnchorDone = true;
|
|
1297
1338
|
const anchorSelector = isCssSafeId(anc.id) ? `#${anc.id}` : `[id="${escCssAttrValue(anc.id)}"]`;
|
|
1298
1339
|
add({
|
|
1299
|
-
locator: `locator(
|
|
1340
|
+
locator: `locator(${quoteJsString(anchorSelector)}).${leaf.expr}`,
|
|
1300
1341
|
method: leaf.method,
|
|
1301
1342
|
args: { ...leaf.args, anchorSelector },
|
|
1302
1343
|
score: scores.id
|
|
@@ -1307,7 +1348,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
1307
1348
|
const { name: name2, value } = anc.dataAttr;
|
|
1308
1349
|
const anchorSelector = `[${name2}="${escCssAttrValue(value)}"]`;
|
|
1309
1350
|
add({
|
|
1310
|
-
locator: `locator(
|
|
1351
|
+
locator: `locator(${quoteJsString(anchorSelector)}).${leaf.expr}`,
|
|
1311
1352
|
method: leaf.method,
|
|
1312
1353
|
args: { ...leaf.args, anchorSelector },
|
|
1313
1354
|
score: TEST_DATA_ATTRS.has(name2) ? scores.testData : scores.data
|
|
@@ -1317,7 +1358,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
1317
1358
|
if (!roleAnchorDone && ancestorRole && ancestorRole !== leaf.role && anc.roleCount === 1) {
|
|
1318
1359
|
roleAnchorDone = true;
|
|
1319
1360
|
add({
|
|
1320
|
-
locator: `getByRole(
|
|
1361
|
+
locator: `getByRole(${quoteJsString(ancestorRole)}).${leaf.expr}`,
|
|
1321
1362
|
method: leaf.method,
|
|
1322
1363
|
args: { ...leaf.args, anchorRole: ancestorRole },
|
|
1323
1364
|
score: scores.role
|
|
@@ -1326,7 +1367,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
1326
1367
|
if (!filterAnchorDone && ancestorRole && anc.filterText && anc.filterRoleCount === 1) {
|
|
1327
1368
|
filterAnchorDone = true;
|
|
1328
1369
|
add({
|
|
1329
|
-
locator: `getByRole(
|
|
1370
|
+
locator: `getByRole(${quoteJsString(ancestorRole)}).filter({ hasText: ${quoteJsString(anc.filterText)} }).${leaf.expr}`,
|
|
1330
1371
|
method: leaf.method,
|
|
1331
1372
|
args: { ...leaf.args, anchorRole: ancestorRole, anchorHasText: anc.filterText },
|
|
1332
1373
|
score: scores.filter
|
|
@@ -1335,7 +1376,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
1335
1376
|
}
|
|
1336
1377
|
};
|
|
1337
1378
|
if (role && !hasOwnTestId) {
|
|
1338
|
-
const rolePart = level != null ?
|
|
1379
|
+
const rolePart = level != null ? `${quoteJsString(role)}, { level: ${level} }` : quoteJsString(role);
|
|
1339
1380
|
const leafArgs = withLevel({ role });
|
|
1340
1381
|
addAnchoredChains(
|
|
1341
1382
|
{ expr: `getByRole(${rolePart})`, method: "getByRole", args: leafArgs, role },
|
|
@@ -1353,7 +1394,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
1353
1394
|
}
|
|
1354
1395
|
} else if (!role && text && text.length < 80 && !hasOwnTestId) {
|
|
1355
1396
|
addAnchoredChains(
|
|
1356
|
-
{ expr: `getByText(
|
|
1397
|
+
{ expr: `getByText(${quoteJsString(text)})`, method: "getByText", args: { text }, role: null },
|
|
1357
1398
|
(anc) => anc.scopedTextCount,
|
|
1358
1399
|
{ testId: 68, testData: 66, id: 60, data: 56, role: 51, filter: 49 }
|
|
1359
1400
|
);
|
|
@@ -1364,7 +1405,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
1364
1405
|
const classes = clsStr.split(/\s+/).filter((c) => c.length > 1 && /^[A-Za-z_-][A-Za-z0-9_-]*$/.test(c) && isUnique(countsClasses?.[c])).map((cls) => ({ cls, score: classifyCssStability(cls) })).sort((a, b) => b.score - a.score).slice(0, 3);
|
|
1365
1406
|
for (const { cls, score } of classes) {
|
|
1366
1407
|
add({
|
|
1367
|
-
locator: `locator(
|
|
1408
|
+
locator: `locator(${quoteJsString(`.${cls}`)})`,
|
|
1368
1409
|
method: "locator",
|
|
1369
1410
|
args: { selector: `.${cls}` },
|
|
1370
1411
|
score
|
|
@@ -1392,26 +1433,25 @@ function approximateAccessibleName(attrs) {
|
|
|
1392
1433
|
}
|
|
1393
1434
|
|
|
1394
1435
|
// ../picker-dom/src/anchor-alternatives.ts
|
|
1395
|
-
var escStr = (s) => s.replaceAll("\\", "\\\\").replaceAll("'", "\\'");
|
|
1396
1436
|
var cssIdSelector = (id) => /^[A-Za-z_][A-Za-z0-9_-]*$/.test(id) ? `#${id}` : `[id="${id.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"]`;
|
|
1397
1437
|
var ANCHOR_KIND_SCORES = { testid: 80, id: 74, labeledRole: 68, role: 62 };
|
|
1398
1438
|
function anchorSegment(a) {
|
|
1399
1439
|
if (a.testId && a.testIdCount === 1) {
|
|
1400
|
-
return { code: `getByTestId(
|
|
1440
|
+
return { code: `getByTestId(${quoteJsString(a.testId)})`, kind: "testid", flatArgs: { anchorTestId: a.testId } };
|
|
1401
1441
|
}
|
|
1402
1442
|
if (a.id && !isAutoGenerated(a.id) && a.idCount === 1) {
|
|
1403
1443
|
const anchorSelector = cssIdSelector(a.id);
|
|
1404
|
-
return { code: `locator(
|
|
1444
|
+
return { code: `locator(${quoteJsString(anchorSelector)})`, kind: "id", flatArgs: { anchorSelector } };
|
|
1405
1445
|
}
|
|
1406
1446
|
if (a.role && a.ariaLabel && a.labeledRoleCount === 1) {
|
|
1407
1447
|
return {
|
|
1408
|
-
code: `getByRole(
|
|
1448
|
+
code: `getByRole(${quoteJsString(a.role)}, { name: ${quoteJsString(a.ariaLabel)} })`,
|
|
1409
1449
|
kind: "labeledRole",
|
|
1410
1450
|
flatArgs: { anchorRole: a.role, anchorName: a.ariaLabel }
|
|
1411
1451
|
};
|
|
1412
1452
|
}
|
|
1413
1453
|
if (a.role && a.roleCount === 1) {
|
|
1414
|
-
return { code: `getByRole(
|
|
1454
|
+
return { code: `getByRole(${quoteJsString(a.role)})`, kind: "role", flatArgs: { anchorRole: a.role } };
|
|
1415
1455
|
}
|
|
1416
1456
|
return null;
|
|
1417
1457
|
}
|
|
@@ -1419,7 +1459,7 @@ function generateAnchoredAlternatives(leaf, anchors, chainLeafCount) {
|
|
|
1419
1459
|
if (!leaf.role || anchors.length === 0) return [];
|
|
1420
1460
|
const out = [];
|
|
1421
1461
|
const seen = /* @__PURE__ */ new Set();
|
|
1422
|
-
const rolePart = leaf.level != null ?
|
|
1462
|
+
const rolePart = leaf.level != null ? `${quoteJsString(leaf.role)}, { level: ${leaf.level} }` : quoteJsString(leaf.role);
|
|
1423
1463
|
const leafArgs = leaf.level != null ? { role: leaf.role, level: leaf.level } : { role: leaf.role };
|
|
1424
1464
|
const add = (l) => {
|
|
1425
1465
|
if (!seen.has(l.locator)) {
|
|
@@ -1470,22 +1510,14 @@ function mergeCandidates(base, extra) {
|
|
|
1470
1510
|
// src/internal/capture/locator-healing.ts
|
|
1471
1511
|
var path = __toESM(require("path"));
|
|
1472
1512
|
|
|
1473
|
-
// ../core/src/locator-
|
|
1474
|
-
var
|
|
1475
|
-
"
|
|
1476
|
-
|
|
1477
|
-
"getByText",
|
|
1478
|
-
"getByLabel",
|
|
1479
|
-
"getByPlaceholder",
|
|
1480
|
-
"getByAltText",
|
|
1481
|
-
"getByTitle",
|
|
1482
|
-
"locator"
|
|
1483
|
-
];
|
|
1513
|
+
// ../core/src/locator-fingerprint.ts
|
|
1514
|
+
var FUNCTION_WORDS = new Set(
|
|
1515
|
+
"the and for with from your you our this that les des une pour avec par sur aux dans vos votre nos notre".split(" ")
|
|
1516
|
+
);
|
|
1484
1517
|
|
|
1485
1518
|
// src/internal/capture/locator-healing.ts
|
|
1486
1519
|
var LOCATOR_METHODS = [...LOCATOR_BUILDER_METHODS];
|
|
1487
1520
|
var LOCATOR_CREATING_CHAINS = new Set(LOCATOR_METHODS);
|
|
1488
|
-
var escAttr = (s) => s.replaceAll("\\", "\\\\").replaceAll("'", "\\'");
|
|
1489
1521
|
function failedNameAndRole(failed) {
|
|
1490
1522
|
if (failed.method === "getByRole") {
|
|
1491
1523
|
const role = typeof failed.args[0] === "string" ? failed.args[0] : null;
|
|
@@ -1500,13 +1532,12 @@ function failedNameAndRole(failed) {
|
|
|
1500
1532
|
function renderFailing(failed) {
|
|
1501
1533
|
const { role, name } = failedNameAndRole(failed);
|
|
1502
1534
|
if (failed.method === "getByRole") {
|
|
1503
|
-
return name ? `getByRole(
|
|
1535
|
+
return name ? `getByRole(${quoteJsString(role ?? "")}, { name: ${quoteJsString(name)} })` : `getByRole(${quoteJsString(role ?? "")})`;
|
|
1504
1536
|
}
|
|
1505
|
-
return `${failed.method}(
|
|
1537
|
+
return `${failed.method}(${quoteJsString(name ?? "")})`;
|
|
1506
1538
|
}
|
|
1507
1539
|
|
|
1508
1540
|
// src/internal/capture/pick-on-failure.ts
|
|
1509
|
-
var ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
1510
1541
|
function parseLeafLocatorExpression(rawExpr) {
|
|
1511
1542
|
const leaf = parseLeafLocatorCall(rawExpr);
|
|
1512
1543
|
return leaf ? { method: leaf.method, args: locatorCallValues(leaf) } : null;
|
|
@@ -1515,11 +1546,10 @@ function deriveFailedLocator(testInfo) {
|
|
|
1515
1546
|
const info = testInfo;
|
|
1516
1547
|
const errors = info.errors && info.errors.length > 0 ? info.errors : info.error ? [info.error] : [];
|
|
1517
1548
|
for (const err of errors) {
|
|
1518
|
-
const
|
|
1519
|
-
${err.stack ?? ""}
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
const parsed = parseLeafLocatorExpression(line[1].trim());
|
|
1549
|
+
const header = readLocatorHeader(stripAnsi(`${err.message ?? ""}
|
|
1550
|
+
${err.stack ?? ""}`));
|
|
1551
|
+
if (!header) continue;
|
|
1552
|
+
const parsed = parseLeafLocatorExpression(header);
|
|
1523
1553
|
if (!parsed) continue;
|
|
1524
1554
|
const loc = err.location;
|
|
1525
1555
|
const location = loc ? `${path2.relative(process.cwd(), loc.file).split(path2.sep).join("/")}:${loc.line}:${loc.column}` : null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@piwitests/reporter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.56.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",
|