@piwitests/reporter 0.55.0 → 0.57.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.
@@ -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 = textSimilarity(c.name, fp.name);
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('${esc(testId)}')`,
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('${role}', { name: '${esc(accessibleName)}'${levelPart} })`,
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('${role}', { name: '${esc(ariaLabel)}'${levelPart} })`,
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('${esc(accessibleName)}')`,
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('${esc(placeholder)}')`,
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('${esc(text)}')`,
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('${esc(selector)}')`,
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('${esc(selector)}')`,
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('${esc(alt)}')`,
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('${esc(title)}')`,
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('${esc(anc.testId)}').${leaf.expr}`,
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('${esc(anchorSelector)}').${leaf.expr}`,
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('${esc(anchorSelector)}').${leaf.expr}`,
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('${esc(ancestorRole)}').${leaf.expr}`,
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('${esc(ancestorRole)}').filter({ hasText: '${esc(anc.filterText)}' }).${leaf.expr}`,
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 ? `'${role}', { level: ${level} }` : `'${role}'`;
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('${esc(text)}')`, method: "getByText", args: { text }, role: null },
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('.${esc(cls)}')`,
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('${escAttr(role ?? "")}', { name: '${escAttr(name)}' })` : `getByRole('${escAttr(role ?? "")}')`;
711
+ return name ? `getByRole(${quoteJsString(role ?? "")}, { name: ${quoteJsString(name)} })` : `getByRole(${quoteJsString(role ?? "")})`;
668
712
  }
669
- return `${failed.method}('${escAttr(name ?? "")}')`;
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('${escAttr(role)}', { name: '${escAttr(name)}'${levelPart} })`;
680
- const textLoc = `getByText('${escAttr(name)}')`;
681
- const labelLoc = `getByLabel('${escAttr(name)}')`;
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 escJs = (s) => s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
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('${escJs(testId)}')`;
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('${escJs(labelText)}')`;
466
+ if (labelText) return `getByLabel(${quoteJs(labelText)})`;
425
467
  }
426
468
  const ariaLabel = el.getAttribute && el.getAttribute("aria-label");
427
- if (ariaLabel) return `getByLabel('${escJs(ariaLabel)}')`;
469
+ if (ariaLabel) return `getByLabel(${quoteJs(ariaLabel)})`;
428
470
  const placeholder = el.getAttribute && el.getAttribute("placeholder");
429
- if (placeholder) return `getByPlaceholder('${escJs(placeholder)}')`;
471
+ if (placeholder) return `getByPlaceholder(${quoteJs(placeholder)})`;
430
472
  const alt = el.getAttribute && el.getAttribute("alt");
431
- if (alt) return `getByAltText('${escJs(alt)}')`;
473
+ if (alt) return `getByAltText(${quoteJs(alt)})`;
432
474
  const titleAttr = el.getAttribute && el.getAttribute("title");
433
- if (titleAttr) return `getByTitle('${escJs(titleAttr)}')`;
434
- if (el.id) return `locator('#${escJs(el.id)}')`;
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('.${escJs(cls)}')` : tag;
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('${esc(testId)}')`,
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('${role}', { name: '${esc(accessibleName)}'${levelPart} })`,
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('${role}', { name: '${esc(ariaLabel)}'${levelPart} })`,
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('${esc(accessibleName)}')`,
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('${esc(placeholder)}')`,
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('${esc(text)}')`,
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('${esc(selector)}')`,
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('${esc(selector)}')`,
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('${esc(alt)}')`,
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('${esc(title)}')`,
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('${esc(anc.testId)}').${leaf.expr}`,
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('${esc(anchorSelector)}').${leaf.expr}`,
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('${esc(anchorSelector)}').${leaf.expr}`,
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('${esc(ancestorRole)}').${leaf.expr}`,
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('${esc(ancestorRole)}').filter({ hasText: '${esc(anc.filterText)}' }).${leaf.expr}`,
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 ? `'${role}', { level: ${level} }` : `'${role}'`;
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('${esc(text)}')`, method: "getByText", args: { text }, role: null },
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('.${esc(cls)}')`,
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('${escStr(a.testId)}')`, kind: "testid", flatArgs: { anchorTestId: a.testId } };
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('${escStr(anchorSelector)}')`, kind: "id", flatArgs: { anchorSelector } };
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('${escStr(a.role)}', { name: '${escStr(a.ariaLabel)}' })`,
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('${escStr(a.role)}')`, kind: "role", flatArgs: { anchorRole: a.role } };
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 ? `'${leaf.role}', { level: ${leaf.level} }` : `'${leaf.role}'`;
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-methods.ts
1474
- var LOCATOR_BUILDER_METHODS = [
1475
- "getByRole",
1476
- "getByTestId",
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('${escAttr(role ?? "")}', { name: '${escAttr(name)}' })` : `getByRole('${escAttr(role ?? "")}')`;
1535
+ return name ? `getByRole(${quoteJsString(role ?? "")}, { name: ${quoteJsString(name)} })` : `getByRole(${quoteJsString(role ?? "")})`;
1504
1536
  }
1505
- return `${failed.method}('${escAttr(name ?? "")}')`;
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 text = `${err.message ?? ""}
1519
- ${err.stack ?? ""}`.replace(ANSI_RE, "");
1520
- const line = /^\s*Locator:\s*(.+)$/m.exec(text);
1521
- if (!line) continue;
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.55.0",
3
+ "version": "0.57.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",