@piwitests/reporter 0.23.0 → 0.25.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 +20 -0
- package/dist/cli/index.js +469 -83
- package/dist/global-setup-module.js +9 -1
- package/dist/index.d.ts +110 -2
- package/dist/index.js +991 -29
- package/dist/internal/capture/attachments.d.ts +2 -0
- package/dist/internal/capture/attachments.js +3 -1
- package/dist/internal/capture/capture-fixtures.js +109 -24
- package/dist/internal/capture/locator-healing.d.ts +28 -1
- package/dist/internal/capture/locator-healing.js +44 -0
- package/package.json +8 -1
|
@@ -14,6 +14,8 @@ declare const ATTACHMENT_NAMES: {
|
|
|
14
14
|
readonly locatorSuggestion: "piwi-locator-suggestion";
|
|
15
15
|
readonly pageState: "piwi-page-state";
|
|
16
16
|
readonly userPick: "piwi-user-pick";
|
|
17
|
+
readonly aiUsage: "piwi-ai-usage";
|
|
18
|
+
readonly aiMeta: "piwi-ai-meta";
|
|
17
19
|
};
|
|
18
20
|
/** Set of every internal attachment name — used to skip them when collecting user attachments. */
|
|
19
21
|
declare const INTERNAL_ATTACHMENT_NAMES: ReadonlySet<string>;
|
|
@@ -34,7 +34,9 @@ var ATTACHMENT_NAMES = {
|
|
|
34
34
|
webVitals: "piwi-web-vitals",
|
|
35
35
|
locatorSuggestion: "piwi-locator-suggestion",
|
|
36
36
|
pageState: "piwi-page-state",
|
|
37
|
-
userPick: "piwi-user-pick"
|
|
37
|
+
userPick: "piwi-user-pick",
|
|
38
|
+
aiUsage: "piwi-ai-usage",
|
|
39
|
+
aiMeta: "piwi-ai-meta"
|
|
38
40
|
};
|
|
39
41
|
var INTERNAL_ATTACHMENT_NAMES = new Set(Object.values(ATTACHMENT_NAMES));
|
|
40
42
|
var LOCATOR_SUGGESTION_ANNOTATION = ATTACHMENT_NAMES.locatorSuggestion;
|
|
@@ -309,7 +309,16 @@ function probeElementAttrs(el, arg) {
|
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
311
|
const hasLabel = !!(el.labels && el.labels.length > 0);
|
|
312
|
-
|
|
312
|
+
let labelText;
|
|
313
|
+
if (includeLabelText) {
|
|
314
|
+
labelText = null;
|
|
315
|
+
try {
|
|
316
|
+
const label = hasLabel ? el.labels[0] : null;
|
|
317
|
+
labelText = (label && label.textContent || "").replace(/\s+/g, " ").trim().slice(0, 120) || null;
|
|
318
|
+
} catch {
|
|
319
|
+
labelText = null;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
313
322
|
return {
|
|
314
323
|
tagName: el.tagName?.toLowerCase?.() ?? "unknown",
|
|
315
324
|
attributes: attrMap,
|
|
@@ -1013,6 +1022,7 @@ var CAPTURED_ATTRIBUTES = [
|
|
|
1013
1022
|
"alt",
|
|
1014
1023
|
"title",
|
|
1015
1024
|
"aria-label",
|
|
1025
|
+
"aria-labelledby",
|
|
1016
1026
|
"aria-level",
|
|
1017
1027
|
"role",
|
|
1018
1028
|
"type",
|
|
@@ -1548,6 +1558,43 @@ function extractAccessibleName(ariaSnapshot) {
|
|
|
1548
1558
|
if (match) return match[1];
|
|
1549
1559
|
return null;
|
|
1550
1560
|
}
|
|
1561
|
+
var FORM_FIELD_TAGS = /* @__PURE__ */ new Set(["input", "select", "textarea"]);
|
|
1562
|
+
var NAME_FROM_CONTENT_ROLES = /* @__PURE__ */ new Set([
|
|
1563
|
+
"button",
|
|
1564
|
+
"cell",
|
|
1565
|
+
"checkbox",
|
|
1566
|
+
"columnheader",
|
|
1567
|
+
"gridcell",
|
|
1568
|
+
"heading",
|
|
1569
|
+
"link",
|
|
1570
|
+
"menuitem",
|
|
1571
|
+
"menuitemcheckbox",
|
|
1572
|
+
"menuitemradio",
|
|
1573
|
+
"option",
|
|
1574
|
+
"radio",
|
|
1575
|
+
"row",
|
|
1576
|
+
"rowheader",
|
|
1577
|
+
"switch",
|
|
1578
|
+
"tab",
|
|
1579
|
+
"tooltip",
|
|
1580
|
+
"treeitem"
|
|
1581
|
+
]);
|
|
1582
|
+
var PROBE_TEXT_CAP = 80;
|
|
1583
|
+
var PROBE_LABEL_TEXT_CAP = 120;
|
|
1584
|
+
function exactAccessibleName(attrs, role) {
|
|
1585
|
+
if (attrs.attributes["aria-labelledby"]) return null;
|
|
1586
|
+
const ariaLabel = attrs.attributes["aria-label"];
|
|
1587
|
+
if (ariaLabel) return ariaLabel;
|
|
1588
|
+
if (FORM_FIELD_TAGS.has(attrs.tagName)) {
|
|
1589
|
+
const labelText = attrs.labelText;
|
|
1590
|
+
return labelText && labelText.length < PROBE_LABEL_TEXT_CAP ? labelText : null;
|
|
1591
|
+
}
|
|
1592
|
+
if (role && NAME_FROM_CONTENT_ROLES.has(role)) {
|
|
1593
|
+
const text = attrs.textContent;
|
|
1594
|
+
return text && text.length < PROBE_TEXT_CAP ? text : null;
|
|
1595
|
+
}
|
|
1596
|
+
return null;
|
|
1597
|
+
}
|
|
1551
1598
|
var NAME_BASED_METHODS = /* @__PURE__ */ new Set([
|
|
1552
1599
|
"getByText",
|
|
1553
1600
|
"getByRole",
|
|
@@ -1685,7 +1732,9 @@ var ATTACHMENT_NAMES = {
|
|
|
1685
1732
|
webVitals: "piwi-web-vitals",
|
|
1686
1733
|
locatorSuggestion: "piwi-locator-suggestion",
|
|
1687
1734
|
pageState: "piwi-page-state",
|
|
1688
|
-
userPick: "piwi-user-pick"
|
|
1735
|
+
userPick: "piwi-user-pick",
|
|
1736
|
+
aiUsage: "piwi-ai-usage",
|
|
1737
|
+
aiMeta: "piwi-ai-meta"
|
|
1689
1738
|
};
|
|
1690
1739
|
var INTERNAL_ATTACHMENT_NAMES = new Set(Object.values(ATTACHMENT_NAMES));
|
|
1691
1740
|
var LOCATOR_SUGGESTION_ANNOTATION = ATTACHMENT_NAMES.locatorSuggestion;
|
|
@@ -2007,7 +2056,7 @@ function createSink() {
|
|
|
2007
2056
|
capturedLocators: [],
|
|
2008
2057
|
capturePromises: [],
|
|
2009
2058
|
failedLocators: [],
|
|
2010
|
-
|
|
2059
|
+
probedLocations: /* @__PURE__ */ new Set(),
|
|
2011
2060
|
lastActivePage: null,
|
|
2012
2061
|
testInfo: null,
|
|
2013
2062
|
stashedWebVitals: null,
|
|
@@ -2234,7 +2283,6 @@ var INSTRUMENTED_CONTEXTS = /* @__PURE__ */ new WeakSet();
|
|
|
2234
2283
|
var PATCHED_BROWSERS = /* @__PURE__ */ new WeakSet();
|
|
2235
2284
|
var CHAIN_METHOD_SET = new Set(CHAIN_METHODS);
|
|
2236
2285
|
var ACTION_METHOD_SET = new Set(ACTION_METHODS);
|
|
2237
|
-
var FORM_FIELD_TAGS = /* @__PURE__ */ new Set(["input", "select", "textarea"]);
|
|
2238
2286
|
var CAPTURED_ATTRS_ARG = {
|
|
2239
2287
|
keep: [...CAPTURED_ATTRIBUTES],
|
|
2240
2288
|
tagRoles: TAG_TO_ROLE,
|
|
@@ -2243,11 +2291,32 @@ var CAPTURED_ATTRS_ARG = {
|
|
|
2243
2291
|
// special-cased logic in the probe, so add them explicitly).
|
|
2244
2292
|
roleSources: [.../* @__PURE__ */ new Set(["[role]", "input", "select", ...Object.keys(TAG_TO_ROLE)])].join(","),
|
|
2245
2293
|
// The reporter always wants ancestor-anchored alternatives (the picker's
|
|
2246
|
-
// anchors step and generateAnchoredAlternatives both need them)
|
|
2247
|
-
//
|
|
2294
|
+
// anchors step and generateAnchoredAlternatives both need them). `labelText`
|
|
2295
|
+
// is what names a form field, and the probe reads it from `el.labels` in the
|
|
2296
|
+
// same pass — so asking for it here is free and settles the accessible name
|
|
2297
|
+
// of a labeled field without a second round trip (`exactAccessibleName`).
|
|
2248
2298
|
includeStructural: true,
|
|
2249
|
-
includeLabelText:
|
|
2299
|
+
includeLabelText: true
|
|
2250
2300
|
};
|
|
2301
|
+
var PROBE_GLOBAL = "__piwiProbeElement";
|
|
2302
|
+
var PROBE_INIT_SCRIPT = `(() => {
|
|
2303
|
+
const probe = ${probeElementAttrs.toString()};
|
|
2304
|
+
const arg = ${JSON.stringify(CAPTURED_ATTRS_ARG)};
|
|
2305
|
+
globalThis[${JSON.stringify(PROBE_GLOBAL)}] = (el) => probe(el, arg);
|
|
2306
|
+
})();`;
|
|
2307
|
+
var PROBE_UNSEEDED_PAGES = /* @__PURE__ */ new WeakSet();
|
|
2308
|
+
function probeElement(page, target) {
|
|
2309
|
+
const shipSource = () => target.evaluate(probeElementAttrs, CAPTURED_ATTRS_ARG);
|
|
2310
|
+
if (!page || PROBE_UNSEEDED_PAGES.has(page)) return shipSource();
|
|
2311
|
+
return target.evaluate((el, name) => {
|
|
2312
|
+
const seeded = globalThis[name];
|
|
2313
|
+
return typeof seeded === "function" ? seeded(el) : null;
|
|
2314
|
+
}, PROBE_GLOBAL).then((attrs) => {
|
|
2315
|
+
if (attrs) return attrs;
|
|
2316
|
+
PROBE_UNSEEDED_PAGES.add(page);
|
|
2317
|
+
return shipSource();
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2251
2320
|
async function ariaSnapshotBestEffort(target, timeout) {
|
|
2252
2321
|
if (typeof target.ariaSnapshot !== "function") return null;
|
|
2253
2322
|
try {
|
|
@@ -2266,8 +2335,8 @@ async function ariaSnapshotBestEffort(target, timeout) {
|
|
|
2266
2335
|
}
|
|
2267
2336
|
}
|
|
2268
2337
|
}
|
|
2269
|
-
function startElementCapture(sink, target, seq, callerLocation, used) {
|
|
2270
|
-
const probe =
|
|
2338
|
+
function startElementCapture(sink, page, target, seq, callerLocation, used) {
|
|
2339
|
+
const probe = probeElement(page, target);
|
|
2271
2340
|
const settledProbe = probe.then(
|
|
2272
2341
|
() => void 0,
|
|
2273
2342
|
() => void 0
|
|
@@ -2286,8 +2355,9 @@ function startElementCapture(sink, target, seq, callerLocation, used) {
|
|
|
2286
2355
|
]);
|
|
2287
2356
|
const role = resolveAriaRole({ ...attrs, accessibleName: null });
|
|
2288
2357
|
const isFormField = FORM_FIELD_TAGS.has(attrs.tagName);
|
|
2289
|
-
const
|
|
2290
|
-
const
|
|
2358
|
+
const exactName = exactAccessibleName(attrs, role);
|
|
2359
|
+
const aria = exactName === null && (role || isFormField) ? await ariaSnapshotBestEffort(target, 500) : null;
|
|
2360
|
+
const accessibleName = exactName ?? (extractAccessibleName(aria) || approximateAccessibleName({ ...attrs, accessibleName: null }));
|
|
2291
2361
|
sink.capturedLocators[seq] = {
|
|
2292
2362
|
location: callerLocation,
|
|
2293
2363
|
used,
|
|
@@ -2307,6 +2377,7 @@ function startElementCapture(sink, target, seq, callerLocation, used) {
|
|
|
2307
2377
|
alternatives: generateAlternatives({ ...attrs, accessibleName })
|
|
2308
2378
|
};
|
|
2309
2379
|
} catch {
|
|
2380
|
+
if (callerLocation) sink.probedLocations.delete(callerLocation);
|
|
2310
2381
|
} finally {
|
|
2311
2382
|
clearTimeout(deadline);
|
|
2312
2383
|
}
|
|
@@ -2343,17 +2414,17 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
2343
2414
|
args: originArgs,
|
|
2344
2415
|
raw: `${originMethod}(${JSON.stringify(originArgs)})`
|
|
2345
2416
|
};
|
|
2346
|
-
const
|
|
2417
|
+
const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
|
|
2347
2418
|
let seq = -1;
|
|
2348
|
-
if (!
|
|
2419
|
+
if (!alreadyProbed) {
|
|
2349
2420
|
seq = sink.capturedLocators.length;
|
|
2350
2421
|
sink.capturedLocators.push({ location: callerLocation, used, element: null, alternatives: [] });
|
|
2351
2422
|
}
|
|
2352
2423
|
const result = await fn.apply(target, callArgs);
|
|
2353
2424
|
const matches = result?.matches;
|
|
2354
|
-
if (matches === true && !
|
|
2355
|
-
if (callerLocation) sink.
|
|
2356
|
-
startElementCapture(sink, target, seq, callerLocation, used);
|
|
2425
|
+
if (matches === true && !alreadyProbed) {
|
|
2426
|
+
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
2427
|
+
startElementCapture(sink, page, target, seq, callerLocation, used);
|
|
2357
2428
|
} else if (matches === false) {
|
|
2358
2429
|
sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
|
|
2359
2430
|
}
|
|
@@ -2365,19 +2436,23 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
2365
2436
|
const sink = currentSink;
|
|
2366
2437
|
if (!sink) return fn.apply(target, callArgs);
|
|
2367
2438
|
sink.lastActivePage = page;
|
|
2368
|
-
const seq = sink.capturedLocators.length;
|
|
2369
2439
|
const callerLocation = captureCallerLocation();
|
|
2370
2440
|
const used = {
|
|
2371
2441
|
method: originMethod,
|
|
2372
2442
|
args: originArgs,
|
|
2373
2443
|
raw: `${originMethod}(${JSON.stringify(originArgs)})`
|
|
2374
2444
|
};
|
|
2375
|
-
sink.
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2445
|
+
const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
|
|
2446
|
+
let seq = -1;
|
|
2447
|
+
if (!alreadyProbed) {
|
|
2448
|
+
seq = sink.capturedLocators.length;
|
|
2449
|
+
sink.capturedLocators.push({
|
|
2450
|
+
location: callerLocation,
|
|
2451
|
+
used,
|
|
2452
|
+
element: null,
|
|
2453
|
+
alternatives: []
|
|
2454
|
+
});
|
|
2455
|
+
}
|
|
2381
2456
|
let result;
|
|
2382
2457
|
try {
|
|
2383
2458
|
result = await fn.apply(target, callArgs);
|
|
@@ -2385,7 +2460,10 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
2385
2460
|
sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
|
|
2386
2461
|
throw error;
|
|
2387
2462
|
}
|
|
2388
|
-
|
|
2463
|
+
if (!alreadyProbed) {
|
|
2464
|
+
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
2465
|
+
startElementCapture(sink, page, target, seq, callerLocation, used);
|
|
2466
|
+
}
|
|
2389
2467
|
return result;
|
|
2390
2468
|
};
|
|
2391
2469
|
}
|
|
@@ -2418,6 +2496,9 @@ function instrumentPage(page) {
|
|
|
2418
2496
|
return wrapLocator(page, original(...args), method, args);
|
|
2419
2497
|
};
|
|
2420
2498
|
}
|
|
2499
|
+
if (typeof page.on === "function") {
|
|
2500
|
+
page.on("framenavigated", () => PROBE_UNSEEDED_PAGES.delete(page));
|
|
2501
|
+
}
|
|
2421
2502
|
}
|
|
2422
2503
|
page.on("console", (msg) => {
|
|
2423
2504
|
const sink = currentSink;
|
|
@@ -2482,6 +2563,10 @@ function instrumentPage(page) {
|
|
|
2482
2563
|
function instrumentContext(context) {
|
|
2483
2564
|
if (!context || INSTRUMENTED_CONTEXTS.has(context)) return;
|
|
2484
2565
|
INSTRUMENTED_CONTEXTS.add(context);
|
|
2566
|
+
if (process.env.PIWI_CAPTURE_LOCATORS !== "false" && typeof context.addInitScript === "function") {
|
|
2567
|
+
void Promise.resolve(context.addInitScript({ content: PROBE_INIT_SCRIPT })).catch(() => {
|
|
2568
|
+
});
|
|
2569
|
+
}
|
|
2485
2570
|
const originalNewPage = context.newPage.bind(context);
|
|
2486
2571
|
context.newPage = async (...args) => {
|
|
2487
2572
|
const page = await originalNewPage(...args);
|
|
@@ -83,6 +83,33 @@ declare const EXPECT_CAPTURE_EXPRESSIONS: ReadonlySet<string>;
|
|
|
83
83
|
* Returns the first quoted string after the role, or null if none found.
|
|
84
84
|
*/
|
|
85
85
|
declare function extractAccessibleName(ariaSnapshot: string | null): string | null;
|
|
86
|
+
/** Tags whose accessible name comes from an associated `<label>`. */
|
|
87
|
+
declare const FORM_FIELD_TAGS: ReadonlySet<string>;
|
|
88
|
+
/**
|
|
89
|
+
* Roles whose accessible name is computed from the element's own content. For
|
|
90
|
+
* these, an element carrying neither `aria-label` nor `aria-labelledby` is
|
|
91
|
+
* named by its trimmed text — which the probe already returns.
|
|
92
|
+
*/
|
|
93
|
+
declare const NAME_FROM_CONTENT_ROLES: ReadonlySet<string>;
|
|
94
|
+
/** The subset of a probe result the accessible-name shortcut reads. */
|
|
95
|
+
interface NameSource {
|
|
96
|
+
tagName: string;
|
|
97
|
+
attributes: Record<string, string | null>;
|
|
98
|
+
textContent: string | null;
|
|
99
|
+
labelText?: string | null;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The accessible name when the probed attributes already settle it, or null
|
|
103
|
+
* when only the browser can. Capture consults this before spending an
|
|
104
|
+
* `ariaSnapshot()` round trip on an element: in each case below the name is
|
|
105
|
+
* what the accessible-name computation would return anyway, so the round trip
|
|
106
|
+
* would buy nothing.
|
|
107
|
+
*
|
|
108
|
+
* Deliberately narrow — anything outside these cases (an `aria-labelledby`
|
|
109
|
+
* reference, an image named by `alt`, an element with no text) still goes to
|
|
110
|
+
* the browser, because a wrong name here becomes a wrong `getByRole` locator.
|
|
111
|
+
*/
|
|
112
|
+
declare function exactAccessibleName(attrs: NameSource, role: string | null): string | null;
|
|
86
113
|
/** A failed locator action, used to suggest a fresh locator from the live page. */
|
|
87
114
|
interface FailedLocatorInfo {
|
|
88
115
|
method: string;
|
|
@@ -126,4 +153,4 @@ declare function suggestLocatorsFromAria(failed: FailedLocatorInfo, ariaSnapshot
|
|
|
126
153
|
*/
|
|
127
154
|
declare function captureCallerLocation(stack?: string): string | null;
|
|
128
155
|
|
|
129
|
-
export { ACTION_METHODS, CHAIN_METHODS, EXPECT_CAPTURE_EXPRESSIONS, EXPECT_METHOD, type FailedLocatorInfo, LOCATOR_CREATING_CHAINS, LOCATOR_METHODS, type LocatorSuggestion, captureCallerLocation, dedupeSnapshotsByLocation, extractAccessibleName, renderFailing, suggestLocatorsFromAria };
|
|
156
|
+
export { ACTION_METHODS, CHAIN_METHODS, EXPECT_CAPTURE_EXPRESSIONS, EXPECT_METHOD, FORM_FIELD_TAGS, type FailedLocatorInfo, LOCATOR_CREATING_CHAINS, LOCATOR_METHODS, type LocatorSuggestion, NAME_FROM_CONTENT_ROLES, type NameSource, captureCallerLocation, dedupeSnapshotsByLocation, exactAccessibleName, extractAccessibleName, renderFailing, suggestLocatorsFromAria };
|
|
@@ -35,14 +35,17 @@ __export(locator_healing_exports, {
|
|
|
35
35
|
CHAIN_METHODS: () => CHAIN_METHODS,
|
|
36
36
|
EXPECT_CAPTURE_EXPRESSIONS: () => EXPECT_CAPTURE_EXPRESSIONS,
|
|
37
37
|
EXPECT_METHOD: () => EXPECT_METHOD,
|
|
38
|
+
FORM_FIELD_TAGS: () => FORM_FIELD_TAGS,
|
|
38
39
|
INPUT_TYPE_TO_ROLE: () => INPUT_TYPE_TO_ROLE,
|
|
39
40
|
LOCATOR_CREATING_CHAINS: () => LOCATOR_CREATING_CHAINS,
|
|
40
41
|
LOCATOR_METHODS: () => LOCATOR_METHODS,
|
|
42
|
+
NAME_FROM_CONTENT_ROLES: () => NAME_FROM_CONTENT_ROLES,
|
|
41
43
|
TAG_TO_ROLE: () => TAG_TO_ROLE,
|
|
42
44
|
approximateAccessibleName: () => approximateAccessibleName,
|
|
43
45
|
captureCallerLocation: () => captureCallerLocation,
|
|
44
46
|
classifyCssStability: () => classifyCssStability,
|
|
45
47
|
dedupeSnapshotsByLocation: () => dedupeSnapshotsByLocation,
|
|
48
|
+
exactAccessibleName: () => exactAccessibleName,
|
|
46
49
|
extractAccessibleName: () => extractAccessibleName,
|
|
47
50
|
generateAlternatives: () => generateAlternatives,
|
|
48
51
|
headingLevel: () => headingLevel,
|
|
@@ -195,6 +198,7 @@ var CAPTURED_ATTRIBUTES = [
|
|
|
195
198
|
"alt",
|
|
196
199
|
"title",
|
|
197
200
|
"aria-label",
|
|
201
|
+
"aria-labelledby",
|
|
198
202
|
"aria-level",
|
|
199
203
|
"role",
|
|
200
204
|
"type",
|
|
@@ -573,6 +577,43 @@ function extractAccessibleName(ariaSnapshot) {
|
|
|
573
577
|
if (match) return match[1];
|
|
574
578
|
return null;
|
|
575
579
|
}
|
|
580
|
+
var FORM_FIELD_TAGS = /* @__PURE__ */ new Set(["input", "select", "textarea"]);
|
|
581
|
+
var NAME_FROM_CONTENT_ROLES = /* @__PURE__ */ new Set([
|
|
582
|
+
"button",
|
|
583
|
+
"cell",
|
|
584
|
+
"checkbox",
|
|
585
|
+
"columnheader",
|
|
586
|
+
"gridcell",
|
|
587
|
+
"heading",
|
|
588
|
+
"link",
|
|
589
|
+
"menuitem",
|
|
590
|
+
"menuitemcheckbox",
|
|
591
|
+
"menuitemradio",
|
|
592
|
+
"option",
|
|
593
|
+
"radio",
|
|
594
|
+
"row",
|
|
595
|
+
"rowheader",
|
|
596
|
+
"switch",
|
|
597
|
+
"tab",
|
|
598
|
+
"tooltip",
|
|
599
|
+
"treeitem"
|
|
600
|
+
]);
|
|
601
|
+
var PROBE_TEXT_CAP = 80;
|
|
602
|
+
var PROBE_LABEL_TEXT_CAP = 120;
|
|
603
|
+
function exactAccessibleName(attrs, role) {
|
|
604
|
+
if (attrs.attributes["aria-labelledby"]) return null;
|
|
605
|
+
const ariaLabel = attrs.attributes["aria-label"];
|
|
606
|
+
if (ariaLabel) return ariaLabel;
|
|
607
|
+
if (FORM_FIELD_TAGS.has(attrs.tagName)) {
|
|
608
|
+
const labelText = attrs.labelText;
|
|
609
|
+
return labelText && labelText.length < PROBE_LABEL_TEXT_CAP ? labelText : null;
|
|
610
|
+
}
|
|
611
|
+
if (role && NAME_FROM_CONTENT_ROLES.has(role)) {
|
|
612
|
+
const text = attrs.textContent;
|
|
613
|
+
return text && text.length < PROBE_TEXT_CAP ? text : null;
|
|
614
|
+
}
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
576
617
|
var NAME_BASED_METHODS = /* @__PURE__ */ new Set([
|
|
577
618
|
"getByText",
|
|
578
619
|
"getByRole",
|
|
@@ -707,14 +748,17 @@ function captureCallerLocation(stack = new Error().stack ?? "") {
|
|
|
707
748
|
CHAIN_METHODS,
|
|
708
749
|
EXPECT_CAPTURE_EXPRESSIONS,
|
|
709
750
|
EXPECT_METHOD,
|
|
751
|
+
FORM_FIELD_TAGS,
|
|
710
752
|
INPUT_TYPE_TO_ROLE,
|
|
711
753
|
LOCATOR_CREATING_CHAINS,
|
|
712
754
|
LOCATOR_METHODS,
|
|
755
|
+
NAME_FROM_CONTENT_ROLES,
|
|
713
756
|
TAG_TO_ROLE,
|
|
714
757
|
approximateAccessibleName,
|
|
715
758
|
captureCallerLocation,
|
|
716
759
|
classifyCssStability,
|
|
717
760
|
dedupeSnapshotsByLocation,
|
|
761
|
+
exactAccessibleName,
|
|
718
762
|
extractAccessibleName,
|
|
719
763
|
generateAlternatives,
|
|
720
764
|
headingLevel,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@piwitests/reporter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.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.github.io",
|
|
@@ -51,6 +51,10 @@
|
|
|
51
51
|
"reporter:test:coverage": "vitest run --coverage",
|
|
52
52
|
"reporter:test:watch": "vitest",
|
|
53
53
|
"reporter:test:integration": "npm run reporter:build && playwright test --config=tests/integration/playwright.config.ts",
|
|
54
|
+
"reporter:test:integration:ai": "npm run reporter:build && node tests/integration/ai/run.mjs",
|
|
55
|
+
"reporter:test:integration:ai:live": "npm run reporter:build && playwright test --config=tests/integration/ai/live.config.ts",
|
|
56
|
+
"reporter:bench": "npm run reporter:build && node tests/bench/run.mjs",
|
|
57
|
+
"reporter:bench:micro": "vitest bench --run",
|
|
54
58
|
"test": "npm run reporter:test",
|
|
55
59
|
"prepublishOnly": "npm run reporter:build"
|
|
56
60
|
},
|
|
@@ -58,6 +62,9 @@
|
|
|
58
62
|
"dist/",
|
|
59
63
|
"templates/"
|
|
60
64
|
],
|
|
65
|
+
"engines": {
|
|
66
|
+
"node": ">=20"
|
|
67
|
+
},
|
|
61
68
|
"peerDependencies": {
|
|
62
69
|
"@playwright/test": "^1.61.1"
|
|
63
70
|
},
|