@unotest/web 0.9.1 → 0.11.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/.claude/skills/write-e2e-test/SKILL.md +118 -38
- package/.claude/skills/write-e2e-test-ground/SKILL.md +787 -0
- package/CHANGELOG.md +297 -0
- package/dist/config/schema.d.ts +29 -10
- package/dist/config/schema.js +1 -1
- package/dist/driver/index.d.ts +5 -2
- package/dist/driver/index.js +1 -1
- package/dist/dsl/index.d.ts +19 -3
- package/dist/dsl/index.js +1 -1
- package/dist/dsl/web-dsl-language-service.js +1 -1
- package/dist/inspection/page-inject.d.ts +11 -35
- package/dist/inspection/page-inject.js +173 -1032
- package/dist/{interfaces-dbZG10RS.d.ts → interfaces-MxHi1Air.d.ts} +50 -3
- package/dist/mcp/server.js +1 -1
- package/dist/runner/cli.d.ts +11 -1
- package/dist/runner/cli.js +1 -1
- package/dist/runner/init/chrome-probe.js +1 -1
- package/dist/runner/init.js +1 -1
- package/dist/runner/install-chromium.js +1 -1
- package/dist/runner/prepare-fix.js +1 -1
- package/dist/runner/scaffold-workspace.js +1 -1
- package/dist/runner/serve-fixture.js +1 -1
- package/dist/runner/web-runner-adapter.js +1 -1
- package/guides/agent-integration.md +103 -2
- package/guides/dsl-reference.md +150 -13
- package/package.json +10 -38
- package/src/mcp/prompts/agent-test-author.md +71 -3
|
@@ -520,12 +520,9 @@ function semanticDomAlgorithm(options) {
|
|
|
520
520
|
return count;
|
|
521
521
|
}
|
|
522
522
|
__name(countInteractiveInSubtree, "countInteractiveInSubtree");
|
|
523
|
-
|
|
524
|
-
const node = nodes[i];
|
|
525
|
-
if (node.children.length < 3) continue;
|
|
526
|
-
if (collectionMemberSet.has(i)) continue;
|
|
523
|
+
function tryDetectCollection(parentIdx, candidates) {
|
|
527
524
|
const sigGroups = /* @__PURE__ */ new Map();
|
|
528
|
-
for (const ci of
|
|
525
|
+
for (const ci of candidates) {
|
|
529
526
|
const sig = nodes[ci].depth2Signature || "";
|
|
530
527
|
if (!sigGroups.has(sig)) sigGroups.set(sig, []);
|
|
531
528
|
sigGroups.get(sig).push(ci);
|
|
@@ -538,7 +535,7 @@ function semanticDomAlgorithm(options) {
|
|
|
538
535
|
dominantGroup = group;
|
|
539
536
|
}
|
|
540
537
|
}
|
|
541
|
-
if (dominantGroup.length < 3)
|
|
538
|
+
if (dominantGroup.length < 3) return;
|
|
542
539
|
const dominantTag = nodes[dominantGroup[0]].tag;
|
|
543
540
|
const dominantChildTags = nodes[dominantGroup[0]].children.map((ci) => nodes[ci].tag);
|
|
544
541
|
let fuzzyCount = 0;
|
|
@@ -557,11 +554,11 @@ function semanticDomAlgorithm(options) {
|
|
|
557
554
|
fuzzyCount++;
|
|
558
555
|
}
|
|
559
556
|
}
|
|
560
|
-
if (allItems.length < 3)
|
|
557
|
+
if (allItems.length < 3) return;
|
|
561
558
|
const avgInteractive = allItems.reduce((sum, ci) => sum + countInteractiveInSubtree(ci), 0) / allItems.length;
|
|
562
|
-
if (avgInteractive < 3)
|
|
559
|
+
if (avgInteractive < 3) return;
|
|
563
560
|
detectedCollections.push({
|
|
564
|
-
parentIdx
|
|
561
|
+
parentIdx,
|
|
565
562
|
itemIndices: allItems,
|
|
566
563
|
dominantSignature: dominantSig,
|
|
567
564
|
fuzzyCount
|
|
@@ -572,6 +569,13 @@ function semanticDomAlgorithm(options) {
|
|
|
572
569
|
addDescendantsToSet(ci, collectionMemberSet);
|
|
573
570
|
}
|
|
574
571
|
}
|
|
572
|
+
__name(tryDetectCollection, "tryDetectCollection");
|
|
573
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
574
|
+
const node = nodes[i];
|
|
575
|
+
if (node.children.length < 3) continue;
|
|
576
|
+
if (collectionMemberSet.has(i)) continue;
|
|
577
|
+
tryDetectCollection(i, node.children);
|
|
578
|
+
}
|
|
575
579
|
for (let i = 0; i < nodes.length; i++) {
|
|
576
580
|
const node = nodes[i];
|
|
577
581
|
if (node.children.length < 3) continue;
|
|
@@ -587,53 +591,7 @@ function semanticDomAlgorithm(options) {
|
|
|
587
591
|
grandchildIndices.push(child.children[0]);
|
|
588
592
|
}
|
|
589
593
|
if (!valid || grandchildIndices.length < 3) continue;
|
|
590
|
-
|
|
591
|
-
for (const gi of grandchildIndices) {
|
|
592
|
-
const sig = nodes[gi].depth2Signature || "";
|
|
593
|
-
if (!sigGroups.has(sig)) sigGroups.set(sig, []);
|
|
594
|
-
sigGroups.get(sig).push(gi);
|
|
595
|
-
}
|
|
596
|
-
let dominantSig = "";
|
|
597
|
-
let dominantGroup = [];
|
|
598
|
-
for (const [sig, group] of sigGroups) {
|
|
599
|
-
if (group.length > dominantGroup.length) {
|
|
600
|
-
dominantSig = sig;
|
|
601
|
-
dominantGroup = group;
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
if (dominantGroup.length < 3) continue;
|
|
605
|
-
const dominantTag = nodes[dominantGroup[0]].tag;
|
|
606
|
-
const dominantChildTags = nodes[dominantGroup[0]].children.map((ci) => nodes[ci].tag);
|
|
607
|
-
let fuzzyCount = 0;
|
|
608
|
-
const allItems = [...dominantGroup];
|
|
609
|
-
for (const [sig, group] of sigGroups) {
|
|
610
|
-
if (sig === dominantSig) continue;
|
|
611
|
-
for (const gi of group) {
|
|
612
|
-
const candidate = nodes[gi];
|
|
613
|
-
if (candidate.tag !== dominantTag) continue;
|
|
614
|
-
const candidateChildTags = candidate.children.map((ci) => nodes[ci].tag);
|
|
615
|
-
const jaccard = jaccardMultiset(dominantChildTags, candidateChildTags);
|
|
616
|
-
if (jaccard < 0.7) continue;
|
|
617
|
-
const iOverlap = interactiveOverlap(nodes[dominantGroup[0]].children, candidate.children);
|
|
618
|
-
if (iOverlap < 0.5) continue;
|
|
619
|
-
allItems.push(gi);
|
|
620
|
-
fuzzyCount++;
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
if (allItems.length < 3) continue;
|
|
624
|
-
const avgInteractive = allItems.reduce((sum, ci) => sum + countInteractiveInSubtree(ci), 0) / allItems.length;
|
|
625
|
-
if (avgInteractive < 3) continue;
|
|
626
|
-
detectedCollections.push({
|
|
627
|
-
parentIdx: i,
|
|
628
|
-
itemIndices: allItems,
|
|
629
|
-
dominantSignature: dominantSig,
|
|
630
|
-
fuzzyCount
|
|
631
|
-
});
|
|
632
|
-
totalFuzzyMatched += fuzzyCount;
|
|
633
|
-
for (const ci of allItems) {
|
|
634
|
-
collectionMemberSet.add(ci);
|
|
635
|
-
addDescendantsToSet(ci, collectionMemberSet);
|
|
636
|
-
}
|
|
594
|
+
tryDetectCollection(i, grandchildIndices);
|
|
637
595
|
}
|
|
638
596
|
const LANDMARK_SELECTOR = 'header, nav, main, aside, footer, form, section[aria-label], section[aria-labelledby], article, dialog, [role="banner"], [role="navigation"], [role="main"], [role="contentinfo"], [role="search"], [role="region"], [role="complementary"], [role="dialog"], [role="alertdialog"]';
|
|
639
597
|
function landmarkKind(landmark) {
|
|
@@ -1229,87 +1187,71 @@ function semanticDomAlgorithm(options) {
|
|
|
1229
1187
|
return action;
|
|
1230
1188
|
}
|
|
1231
1189
|
__name(nodeToAction, "nodeToAction");
|
|
1232
|
-
function
|
|
1233
|
-
const
|
|
1190
|
+
function groupNodes(nodeList, min, keyOf) {
|
|
1191
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
1234
1192
|
const ungrouped = [];
|
|
1235
|
-
for (const node of
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
const segments = node.href.split("/").filter(Boolean);
|
|
1241
|
-
const patternKey = segments.length >= 1 ? `/${segments[0]}/` : null;
|
|
1242
|
-
if (patternKey) {
|
|
1243
|
-
if (!pathGroups.has(patternKey)) pathGroups.set(patternKey, []);
|
|
1244
|
-
pathGroups.get(patternKey).push(node);
|
|
1193
|
+
for (const node of nodeList) {
|
|
1194
|
+
const key = keyOf(node);
|
|
1195
|
+
if (key) {
|
|
1196
|
+
if (!buckets.has(key)) buckets.set(key, []);
|
|
1197
|
+
buckets.get(key).push(node);
|
|
1245
1198
|
} else {
|
|
1246
1199
|
ungrouped.push(node);
|
|
1247
1200
|
}
|
|
1248
1201
|
}
|
|
1249
|
-
const
|
|
1250
|
-
for (const [key, ns] of
|
|
1251
|
-
if (ns.length >=
|
|
1252
|
-
|
|
1202
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1203
|
+
for (const [key, ns] of buckets) {
|
|
1204
|
+
if (ns.length >= min) {
|
|
1205
|
+
groups.set(key, ns);
|
|
1253
1206
|
} else {
|
|
1254
1207
|
ungrouped.push(...ns);
|
|
1255
1208
|
}
|
|
1256
1209
|
}
|
|
1257
|
-
return {
|
|
1210
|
+
return { groups, ungrouped };
|
|
1211
|
+
}
|
|
1212
|
+
__name(groupNodes, "groupNodes");
|
|
1213
|
+
function groupLinksByHrefPattern(linkNodes) {
|
|
1214
|
+
const { groups, ungrouped } = groupNodes(linkNodes, 3, (node) => {
|
|
1215
|
+
if (!node.href) return null;
|
|
1216
|
+
const segments = node.href.split("/").filter(Boolean);
|
|
1217
|
+
return segments.length >= 1 ? `/${segments[0]}/` : null;
|
|
1218
|
+
});
|
|
1219
|
+
return { grouped: groups, ungrouped };
|
|
1258
1220
|
}
|
|
1259
1221
|
__name(groupLinksByHrefPattern, "groupLinksByHrefPattern");
|
|
1260
1222
|
function groupByTestId(nodeList) {
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
if (!groups.has(key)) groups.set(key, []);
|
|
1267
|
-
groups.get(key).push(node);
|
|
1268
|
-
} else {
|
|
1269
|
-
ungrouped.push(node);
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
const validGroups = /* @__PURE__ */ new Map();
|
|
1273
|
-
for (const [key, ns] of groups) {
|
|
1274
|
-
if (ns.length >= 3) {
|
|
1275
|
-
validGroups.set(key, ns);
|
|
1276
|
-
} else {
|
|
1277
|
-
ungrouped.push(...ns);
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
return { groups: validGroups, ungrouped };
|
|
1223
|
+
return groupNodes(
|
|
1224
|
+
nodeList,
|
|
1225
|
+
3,
|
|
1226
|
+
(node) => node.testId ? `testid:${node.testId}` : node.qa ? `qa:${node.qa}` : null
|
|
1227
|
+
);
|
|
1281
1228
|
}
|
|
1282
1229
|
__name(groupByTestId, "groupByTestId");
|
|
1283
1230
|
function groupByTagWithType(nodeList) {
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
if (!groups.has(key)) groups.set(key, []);
|
|
1290
|
-
groups.get(key).push(node);
|
|
1291
|
-
} else if (node.tag.includes("-")) {
|
|
1292
|
-
if (!groups.has(node.tag)) groups.set(node.tag, []);
|
|
1293
|
-
groups.get(node.tag).push(node);
|
|
1294
|
-
} else {
|
|
1295
|
-
ungrouped.push(node);
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
const validGroups = /* @__PURE__ */ new Map();
|
|
1299
|
-
for (const [key, ns] of groups) {
|
|
1300
|
-
if (ns.length >= 5) {
|
|
1301
|
-
validGroups.set(key, ns);
|
|
1302
|
-
} else {
|
|
1303
|
-
ungrouped.push(...ns);
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
return { groups: validGroups, ungrouped };
|
|
1231
|
+
return groupNodes(nodeList, 5, (node) => {
|
|
1232
|
+
if (node.tag === "input") return `input:${node.type || "text"}`;
|
|
1233
|
+
if (node.tag.includes("-")) return node.tag;
|
|
1234
|
+
return null;
|
|
1235
|
+
});
|
|
1307
1236
|
}
|
|
1308
1237
|
__name(groupByTagWithType, "groupByTagWithType");
|
|
1309
1238
|
const TIER2_CAP_PER_ZONE = 15;
|
|
1310
1239
|
const regions = {};
|
|
1311
1240
|
const textLines = [];
|
|
1312
1241
|
for (const zoneName of allZoneNames) {
|
|
1242
|
+
let pushTagGroupPatterns2 = function(tagGroups) {
|
|
1243
|
+
for (const [tagKey, ns] of tagGroups) {
|
|
1244
|
+
const displayName = tagKey.includes(":") ? `${tagKey.split(":")[0]}[type=${tagKey.split(":")[1]}]` : tagKey;
|
|
1245
|
+
patterns.push({
|
|
1246
|
+
pattern: displayName,
|
|
1247
|
+
selectorPattern: tagKey.includes("-") ? tagKey : void 0,
|
|
1248
|
+
count: ns.length,
|
|
1249
|
+
samples: pickPatternSamples(ns, ns.length)
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
var pushTagGroupPatterns = pushTagGroupPatterns2;
|
|
1254
|
+
__name(pushTagGroupPatterns2, "pushTagGroupPatterns");
|
|
1313
1255
|
const zoneNodes = zoneElements.get(zoneName) || [];
|
|
1314
1256
|
const zoneCollIndices = zoneCollections.get(zoneName) || [];
|
|
1315
1257
|
const meta = zoneMeta.get(zoneName) || { kind: "unknown" };
|
|
@@ -1433,15 +1375,7 @@ function semanticDomAlgorithm(options) {
|
|
|
1433
1375
|
});
|
|
1434
1376
|
}
|
|
1435
1377
|
const { groups: t2TagGroups, ungrouped: t2Ungrouped } = groupByTagWithType(tier2Nodes);
|
|
1436
|
-
|
|
1437
|
-
const displayName = tagKey.includes(":") ? `${tagKey.split(":")[0]}[type=${tagKey.split(":")[1]}]` : tagKey;
|
|
1438
|
-
patterns.push({
|
|
1439
|
-
pattern: displayName,
|
|
1440
|
-
selectorPattern: tagKey.includes("-") ? tagKey : void 0,
|
|
1441
|
-
count: ns.length,
|
|
1442
|
-
samples: pickPatternSamples(ns, ns.length)
|
|
1443
|
-
});
|
|
1444
|
-
}
|
|
1378
|
+
pushTagGroupPatterns2(t2TagGroups);
|
|
1445
1379
|
const cappedTier2 = t2Ungrouped.slice(0, TIER2_CAP_PER_ZONE).map(nodeToAction);
|
|
1446
1380
|
const tier2Overflow = Math.max(0, t2Ungrouped.length - TIER2_CAP_PER_ZONE);
|
|
1447
1381
|
const { groups: testidGroups, ungrouped: noTestidTier3 } = groupByTestId(tier3);
|
|
@@ -1457,15 +1391,7 @@ function semanticDomAlgorithm(options) {
|
|
|
1457
1391
|
});
|
|
1458
1392
|
}
|
|
1459
1393
|
const { groups: tier3TagGroups, ungrouped: ungroupedTier3 } = groupByTagWithType(noTestidTier3);
|
|
1460
|
-
|
|
1461
|
-
const displayName = tagKey.includes(":") ? `${tagKey.split(":")[0]}[type=${tagKey.split(":")[1]}]` : tagKey;
|
|
1462
|
-
patterns.push({
|
|
1463
|
-
pattern: displayName,
|
|
1464
|
-
selectorPattern: tagKey.includes("-") ? tagKey : void 0,
|
|
1465
|
-
count: ns.length,
|
|
1466
|
-
samples: pickPatternSamples(ns, ns.length)
|
|
1467
|
-
});
|
|
1468
|
-
}
|
|
1394
|
+
pushTagGroupPatterns2(tier3TagGroups);
|
|
1469
1395
|
const otherCount = ungroupedTier3.length + tier2Overflow;
|
|
1470
1396
|
const actions = [...tier1, ...cappedTier2];
|
|
1471
1397
|
const region = { kind, total: totalInZone };
|
|
@@ -1643,6 +1569,9 @@ function semanticDomAlgorithm(options) {
|
|
|
1643
1569
|
}
|
|
1644
1570
|
__name(semanticDomAlgorithm, "semanticDomAlgorithm");
|
|
1645
1571
|
|
|
1572
|
+
// src/inspection/outline-capture.ts
|
|
1573
|
+
import { composeInjectedScript } from "@unotest/grounder-client/dom-walker";
|
|
1574
|
+
|
|
1646
1575
|
// src/util/debug-log.ts
|
|
1647
1576
|
import {
|
|
1648
1577
|
appendFileSync as fsAppend,
|
|
@@ -1661,25 +1590,7 @@ var debugStore = new AsyncLocalStorage();
|
|
|
1661
1590
|
|
|
1662
1591
|
// src/inspection/outline-capture.ts
|
|
1663
1592
|
import { outlineAlgorithm as outlineAlgorithmInjectable } from "@unotest/web/internal/page-inject";
|
|
1664
|
-
function outlineAlgorithm(assignRefs) {
|
|
1665
|
-
const INTERACTIVE_ARIA_ROLES = /* @__PURE__ */ new Set([
|
|
1666
|
-
"button",
|
|
1667
|
-
"link",
|
|
1668
|
-
"checkbox",
|
|
1669
|
-
"radio",
|
|
1670
|
-
"menuitem",
|
|
1671
|
-
"menuitemcheckbox",
|
|
1672
|
-
"menuitemradio",
|
|
1673
|
-
"tab",
|
|
1674
|
-
"option",
|
|
1675
|
-
"switch",
|
|
1676
|
-
"combobox",
|
|
1677
|
-
"listbox",
|
|
1678
|
-
"slider",
|
|
1679
|
-
"spinbutton",
|
|
1680
|
-
"searchbox",
|
|
1681
|
-
"textbox"
|
|
1682
|
-
]);
|
|
1593
|
+
function outlineAlgorithm(h, assignRefs) {
|
|
1683
1594
|
const LANDMARK_TAGS = {
|
|
1684
1595
|
header: "banner",
|
|
1685
1596
|
nav: "navigation",
|
|
@@ -1706,65 +1617,11 @@ function outlineAlgorithm(assignRefs) {
|
|
|
1706
1617
|
"article"
|
|
1707
1618
|
]);
|
|
1708
1619
|
const GENERIC_ROLES = /* @__PURE__ */ new Set(["generic", "none", "presentation"]);
|
|
1709
|
-
function* deepWalk(root) {
|
|
1710
|
-
const children = root.children;
|
|
1711
|
-
for (let i = 0; i < children.length; i++) {
|
|
1712
|
-
const el = children[i];
|
|
1713
|
-
yield el;
|
|
1714
|
-
const sr = el.shadowRoot;
|
|
1715
|
-
if (sr) yield* deepWalk(sr);
|
|
1716
|
-
yield* deepWalk(el);
|
|
1717
|
-
}
|
|
1718
|
-
}
|
|
1719
|
-
__name(deepWalk, "deepWalk");
|
|
1720
|
-
function parentOrHost(node) {
|
|
1721
|
-
const parent = node.parentNode;
|
|
1722
|
-
if (!parent) return null;
|
|
1723
|
-
if (parent instanceof ShadowRoot) return parent.host;
|
|
1724
|
-
return parent instanceof Element ? parent : null;
|
|
1725
|
-
}
|
|
1726
|
-
__name(parentOrHost, "parentOrHost");
|
|
1727
|
-
function rootForScope(el) {
|
|
1728
|
-
const root = el.getRootNode();
|
|
1729
|
-
return root instanceof ShadowRoot ? root : document;
|
|
1730
|
-
}
|
|
1731
|
-
__name(rootForScope, "rootForScope");
|
|
1732
|
-
function getElementByIdInScope(el, id) {
|
|
1733
|
-
return rootForScope(el).getElementById(id);
|
|
1734
|
-
}
|
|
1735
|
-
__name(getElementByIdInScope, "getElementByIdInScope");
|
|
1736
|
-
function isVisible(el) {
|
|
1737
|
-
if (!el.getClientRects || el.getClientRects().length === 0) return false;
|
|
1738
|
-
const cs = window.getComputedStyle(el);
|
|
1739
|
-
if (cs.display === "none" || cs.visibility === "hidden") return false;
|
|
1740
|
-
if (Number.parseFloat(cs.opacity || "1") < 0.01) return false;
|
|
1741
|
-
for (let p = el; p; p = parentOrHost(p)) {
|
|
1742
|
-
if (p.getAttribute && p.getAttribute("aria-hidden") === "true") return false;
|
|
1743
|
-
}
|
|
1744
|
-
const r = el.getBoundingClientRect();
|
|
1745
|
-
if (r.width === 0 && r.height === 0) return false;
|
|
1746
|
-
return true;
|
|
1747
|
-
}
|
|
1748
|
-
__name(isVisible, "isVisible");
|
|
1749
1620
|
function implicitRole(el) {
|
|
1750
1621
|
const tag = el.tagName.toLowerCase();
|
|
1751
1622
|
if (tag === "a" && el.hasAttribute("href")) return "link";
|
|
1752
1623
|
if (tag === "button") return "button";
|
|
1753
|
-
if (tag === "textarea") return
|
|
1754
|
-
if (tag === "select") {
|
|
1755
|
-
return el.multiple || el.size > 1 ? "listbox" : "combobox";
|
|
1756
|
-
}
|
|
1757
|
-
if (tag === "input") {
|
|
1758
|
-
const t = (el.type || "text").toLowerCase();
|
|
1759
|
-
if (t === "button" || t === "submit" || t === "reset" || t === "image") return "button";
|
|
1760
|
-
if (t === "checkbox") return "checkbox";
|
|
1761
|
-
if (t === "radio") return "radio";
|
|
1762
|
-
if (t === "range") return "slider";
|
|
1763
|
-
if (t === "number") return "spinbutton";
|
|
1764
|
-
if (t === "search") return "searchbox";
|
|
1765
|
-
if (t === "hidden") return null;
|
|
1766
|
-
return "textbox";
|
|
1767
|
-
}
|
|
1624
|
+
if (tag === "textarea" || tag === "select" || tag === "input") return formFieldRole(el, tag);
|
|
1768
1625
|
if (tag === "h1" || tag === "h2" || tag === "h3" || tag === "h4" || tag === "h5" || tag === "h6") {
|
|
1769
1626
|
return "heading";
|
|
1770
1627
|
}
|
|
@@ -1784,111 +1641,32 @@ function outlineAlgorithm(assignRefs) {
|
|
|
1784
1641
|
}
|
|
1785
1642
|
__name(computeRole, "computeRole");
|
|
1786
1643
|
function isInteractive(el) {
|
|
1787
|
-
|
|
1788
|
-
if (tag === "iframe") return true;
|
|
1789
|
-
const role = computeRole(el);
|
|
1790
|
-
if (role && INTERACTIVE_ARIA_ROLES.has(role)) return true;
|
|
1791
|
-
if (el.hasAttribute("onclick")) return true;
|
|
1792
|
-
const tiRaw = el.getAttribute("tabindex");
|
|
1793
|
-
const ti = tiRaw === null ? null : Number.parseInt(tiRaw, 10);
|
|
1794
|
-
if (ti !== null && ti >= 0) return true;
|
|
1795
|
-
if (tag === "div" || tag === "span") {
|
|
1796
|
-
}
|
|
1797
|
-
return false;
|
|
1644
|
+
return h.isInteractive(el, computeRole(el));
|
|
1798
1645
|
}
|
|
1799
1646
|
__name(isInteractive, "isInteractive");
|
|
1800
|
-
const
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1647
|
+
const {
|
|
1648
|
+
trim,
|
|
1649
|
+
cap,
|
|
1650
|
+
textContentVisible,
|
|
1651
|
+
parentOrHost,
|
|
1652
|
+
deepWalk,
|
|
1653
|
+
isVisible,
|
|
1654
|
+
stableDataAttr,
|
|
1655
|
+
clickAffordance,
|
|
1656
|
+
testIdOf,
|
|
1657
|
+
formFieldRole,
|
|
1658
|
+
nameFromLabelledby,
|
|
1659
|
+
svgAccname,
|
|
1660
|
+
makeAccname,
|
|
1661
|
+
ariaStateFlags,
|
|
1662
|
+
headingLevel
|
|
1663
|
+
} = h;
|
|
1809
1664
|
function svgIconName(host, seen) {
|
|
1810
1665
|
const svg = host.querySelector("svg");
|
|
1811
|
-
|
|
1812
|
-
const labelledby = svg.getAttribute("aria-labelledby");
|
|
1813
|
-
if (labelledby) {
|
|
1814
|
-
const parts = [];
|
|
1815
|
-
for (const id of labelledby.split(/\s+/)) {
|
|
1816
|
-
if (!id) continue;
|
|
1817
|
-
const ref = getElementByIdInScope(svg, id);
|
|
1818
|
-
if (ref && !seen.has(ref)) {
|
|
1819
|
-
const n = accname(ref, seen);
|
|
1820
|
-
if (n) parts.push(n);
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
const joined = trim(parts.join(" "));
|
|
1824
|
-
if (joined) return cap(joined);
|
|
1825
|
-
}
|
|
1826
|
-
const ariaLabel = trim(svg.getAttribute("aria-label"));
|
|
1827
|
-
if (ariaLabel) return cap(ariaLabel);
|
|
1828
|
-
for (const child of Array.from(svg.children)) {
|
|
1829
|
-
if (child.tagName.toLowerCase() === "title") {
|
|
1830
|
-
const t = trim(child.textContent);
|
|
1831
|
-
if (t) return cap(t);
|
|
1832
|
-
break;
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
return "";
|
|
1666
|
+
return svg ? svgAccname(svg, seen, accname) : "";
|
|
1836
1667
|
}
|
|
1837
1668
|
__name(svgIconName, "svgIconName");
|
|
1838
|
-
|
|
1839
|
-
const seen = visited ?? /* @__PURE__ */ new Set();
|
|
1840
|
-
if (seen.has(el)) return "";
|
|
1841
|
-
seen.add(el);
|
|
1842
|
-
const labelledby = el.getAttribute("aria-labelledby");
|
|
1843
|
-
if (labelledby) {
|
|
1844
|
-
const parts = [];
|
|
1845
|
-
for (const id of labelledby.split(/\s+/)) {
|
|
1846
|
-
if (!id) continue;
|
|
1847
|
-
const ref = getElementByIdInScope(el, id);
|
|
1848
|
-
if (ref && !seen.has(ref)) {
|
|
1849
|
-
const n = accname(ref, seen);
|
|
1850
|
-
if (n) parts.push(n);
|
|
1851
|
-
}
|
|
1852
|
-
}
|
|
1853
|
-
const joined = trim(parts.join(" "));
|
|
1854
|
-
if (joined) return cap(joined);
|
|
1855
|
-
}
|
|
1856
|
-
const ariaLabel = trim(el.getAttribute("aria-label"));
|
|
1857
|
-
if (ariaLabel) return cap(ariaLabel);
|
|
1858
|
-
const tag = el.tagName.toLowerCase();
|
|
1859
|
-
if (tag === "input" || tag === "textarea" || tag === "select") {
|
|
1860
|
-
const id = el.id;
|
|
1861
|
-
if (id) {
|
|
1862
|
-
const lbl = rootForScope(el).querySelector(
|
|
1863
|
-
`label[for="${cssEscape(id)}"]`
|
|
1864
|
-
);
|
|
1865
|
-
if (lbl) {
|
|
1866
|
-
const n = trim(textContentVisible(lbl));
|
|
1867
|
-
if (n) return cap(n);
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
let p = parentOrHost(el);
|
|
1871
|
-
while (p) {
|
|
1872
|
-
if (p.tagName.toLowerCase() === "label") {
|
|
1873
|
-
const n = trim(textContentVisible(p));
|
|
1874
|
-
if (n) return cap(n);
|
|
1875
|
-
break;
|
|
1876
|
-
}
|
|
1877
|
-
p = parentOrHost(p);
|
|
1878
|
-
}
|
|
1879
|
-
if (tag === "input") {
|
|
1880
|
-
const it = el.type?.toLowerCase();
|
|
1881
|
-
if (it === "button" || it === "submit" || it === "reset") {
|
|
1882
|
-
const v = trim(el.value);
|
|
1883
|
-
if (v) return cap(v);
|
|
1884
|
-
}
|
|
1885
|
-
const ph = trim(el.placeholder);
|
|
1886
|
-
if (ph) return cap(ph);
|
|
1887
|
-
}
|
|
1888
|
-
const t2 = trim(el.getAttribute("title"));
|
|
1889
|
-
if (t2) return cap(t2);
|
|
1890
|
-
return "";
|
|
1891
|
-
}
|
|
1669
|
+
const accname = makeAccname((el, tag, seen) => {
|
|
1892
1670
|
if (tag === "button" || tag === "a" || tag === "summary") {
|
|
1893
1671
|
const txt = trim(textContentVisible(el));
|
|
1894
1672
|
if (txt) return cap(txt);
|
|
@@ -1920,32 +1698,15 @@ function outlineAlgorithm(assignRefs) {
|
|
|
1920
1698
|
if (inner) return cap(inner);
|
|
1921
1699
|
const t = trim(el.getAttribute("title"));
|
|
1922
1700
|
return cap(t);
|
|
1923
|
-
}
|
|
1924
|
-
__name(accname, "accname");
|
|
1701
|
+
});
|
|
1925
1702
|
function srcBasename(src) {
|
|
1926
1703
|
const noQuery = src.split(/[?#]/)[0] ?? "";
|
|
1927
1704
|
const segments = noQuery.split("/").filter((s) => s.length > 0);
|
|
1928
1705
|
return segments[segments.length - 1] ?? "";
|
|
1929
1706
|
}
|
|
1930
1707
|
__name(srcBasename, "srcBasename");
|
|
1931
|
-
function textContentVisible(el) {
|
|
1932
|
-
const ht = el;
|
|
1933
|
-
if (typeof ht.innerText === "string") return ht.innerText;
|
|
1934
|
-
return el.textContent ?? "";
|
|
1935
|
-
}
|
|
1936
|
-
__name(textContentVisible, "textContentVisible");
|
|
1937
|
-
function cssEscape(s) {
|
|
1938
|
-
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1939
|
-
}
|
|
1940
|
-
__name(cssEscape, "cssEscape");
|
|
1941
1708
|
const MAX_HREF = 60;
|
|
1942
|
-
const pageOrigin =
|
|
1943
|
-
try {
|
|
1944
|
-
return window.location.origin;
|
|
1945
|
-
} catch {
|
|
1946
|
-
return "";
|
|
1947
|
-
}
|
|
1948
|
-
})();
|
|
1709
|
+
const pageOrigin = h.pageOrigin;
|
|
1949
1710
|
function shortenedHref(rawHref) {
|
|
1950
1711
|
if (!rawHref) return "";
|
|
1951
1712
|
const t = trim(rawHref);
|
|
@@ -1975,37 +1736,9 @@ function outlineAlgorithm(assignRefs) {
|
|
|
1975
1736
|
if (formDisabled || el.getAttribute("aria-disabled") === "true") {
|
|
1976
1737
|
state.disabled = true;
|
|
1977
1738
|
}
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
} else if (ariaChecked === "mixed") {
|
|
1982
|
-
state.checked = "mixed";
|
|
1983
|
-
} else if (tag === "input" && (el.type === "checkbox" || el.type === "radio") && el.checked) {
|
|
1984
|
-
state.checked = true;
|
|
1985
|
-
}
|
|
1986
|
-
const ariaPressed = el.getAttribute("aria-pressed");
|
|
1987
|
-
if (ariaPressed === "true") state.pressed = true;
|
|
1988
|
-
else if (ariaPressed === "mixed") state.pressed = "mixed";
|
|
1989
|
-
if (el.getAttribute("aria-expanded") === "true") state.expanded = true;
|
|
1990
|
-
if (el.getAttribute("aria-selected") === "true") {
|
|
1991
|
-
state.selected = true;
|
|
1992
|
-
} else if (tag === "option" && el.selected) {
|
|
1993
|
-
state.selected = true;
|
|
1994
|
-
}
|
|
1995
|
-
if (rootForScope(el).activeElement === el) {
|
|
1996
|
-
state.active = true;
|
|
1997
|
-
}
|
|
1998
|
-
if (tag === "h1") state.level = 1;
|
|
1999
|
-
else if (tag === "h2") state.level = 2;
|
|
2000
|
-
else if (tag === "h3") state.level = 3;
|
|
2001
|
-
else if (tag === "h4") state.level = 4;
|
|
2002
|
-
else if (tag === "h5") state.level = 5;
|
|
2003
|
-
else if (tag === "h6") state.level = 6;
|
|
2004
|
-
const ariaLevel = el.getAttribute("aria-level");
|
|
2005
|
-
if (ariaLevel) {
|
|
2006
|
-
const n = Number.parseInt(ariaLevel, 10);
|
|
2007
|
-
if (Number.isFinite(n) && n > 0) state.level = n;
|
|
2008
|
-
}
|
|
1739
|
+
Object.assign(state, ariaStateFlags(el));
|
|
1740
|
+
const level = headingLevel(el);
|
|
1741
|
+
if (level !== void 0) state.level = level;
|
|
2009
1742
|
return Object.keys(state).length > 0 ? state : void 0;
|
|
2010
1743
|
}
|
|
2011
1744
|
__name(collectState, "collectState");
|
|
@@ -2033,17 +1766,12 @@ function outlineAlgorithm(assignRefs) {
|
|
|
2033
1766
|
function landmarkLabel(landmark) {
|
|
2034
1767
|
const aria = trim(landmark.getAttribute("aria-label"));
|
|
2035
1768
|
if (aria) return cap(aria);
|
|
2036
|
-
const
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
if (ref) parts.push(trim(textContentVisible(ref)));
|
|
2043
|
-
}
|
|
2044
|
-
const joined = trim(parts.join(" "));
|
|
2045
|
-
if (joined) return cap(joined);
|
|
2046
|
-
}
|
|
1769
|
+
const fromLabelledby = nameFromLabelledby(
|
|
1770
|
+
landmark,
|
|
1771
|
+
/* @__PURE__ */ new Set(),
|
|
1772
|
+
(ref) => trim(textContentVisible(ref))
|
|
1773
|
+
);
|
|
1774
|
+
if (fromLabelledby) return fromLabelledby;
|
|
2047
1775
|
const heading = landmark.querySelector("h1, h2, h3, h4, h5, h6");
|
|
2048
1776
|
if (heading) {
|
|
2049
1777
|
const txt = trim(textContentVisible(heading));
|
|
@@ -2052,33 +1780,6 @@ function outlineAlgorithm(assignRefs) {
|
|
|
2052
1780
|
return "";
|
|
2053
1781
|
}
|
|
2054
1782
|
__name(landmarkLabel, "landmarkLabel");
|
|
2055
|
-
function testIdOf(el) {
|
|
2056
|
-
const t = el.getAttribute("data-testid");
|
|
2057
|
-
if (t) return t;
|
|
2058
|
-
const qa = el.getAttribute("data-qa");
|
|
2059
|
-
if (qa) return qa;
|
|
2060
|
-
return void 0;
|
|
2061
|
-
}
|
|
2062
|
-
__name(testIdOf, "testIdOf");
|
|
2063
|
-
function idStable(id) {
|
|
2064
|
-
if (!id || id.length > 40) return false;
|
|
2065
|
-
if (id.startsWith(":") || id.startsWith("__")) return false;
|
|
2066
|
-
const digits = id.replace(/\D/g, "").length;
|
|
2067
|
-
return !(id.length > 8 && digits * 2 > id.length);
|
|
2068
|
-
}
|
|
2069
|
-
__name(idStable, "idStable");
|
|
2070
|
-
function stableDataAttr(el) {
|
|
2071
|
-
const skip = /^data-(unotest|v-|react|ng-|svelte|qa|testid|sort|shadow|tooltip|toggle|target|bs-|original-title)/;
|
|
2072
|
-
const idLike = /(^|[-_])(id|guid|uid|key|code|ref|num|no)$/;
|
|
2073
|
-
for (const a of Array.from(el.attributes)) {
|
|
2074
|
-
if (!a.name.startsWith("data-") || skip.test(a.name) || !idLike.test(a.name)) continue;
|
|
2075
|
-
const v = (a.value ?? "").trim();
|
|
2076
|
-
if (!v || v.length > 64) continue;
|
|
2077
|
-
if (/^\d+$/.test(v) || idStable(v)) return { name: a.name, value: v };
|
|
2078
|
-
}
|
|
2079
|
-
return void 0;
|
|
2080
|
-
}
|
|
2081
|
-
__name(stableDataAttr, "stableDataAttr");
|
|
2082
1783
|
function tooltipLabel(el) {
|
|
2083
1784
|
for (const a of Array.from(el.attributes)) {
|
|
2084
1785
|
if (!a.name.startsWith("data-") || a.name.startsWith("data-unotest")) continue;
|
|
@@ -2095,19 +1796,6 @@ function outlineAlgorithm(assignRefs) {
|
|
|
2095
1796
|
return t.length > 120 ? t.slice(0, 119) + "\u2026" : t;
|
|
2096
1797
|
}
|
|
2097
1798
|
__name(tooltipText, "tooltipText");
|
|
2098
|
-
function clickAffordance(el) {
|
|
2099
|
-
if (el.hasAttribute("onclick")) return true;
|
|
2100
|
-
const cls = el.getAttribute("class") ?? "";
|
|
2101
|
-
if (/(^|\s)hover:/.test(cls) || /\bcursor-pointer\b/.test(cls) || /\bclickable\b/.test(cls)) {
|
|
2102
|
-
return true;
|
|
2103
|
-
}
|
|
2104
|
-
try {
|
|
2105
|
-
return window.getComputedStyle(el).cursor === "pointer";
|
|
2106
|
-
} catch {
|
|
2107
|
-
return false;
|
|
2108
|
-
}
|
|
2109
|
-
}
|
|
2110
|
-
__name(clickAffordance, "clickAffordance");
|
|
2111
1799
|
const viewport = { width: window.innerWidth, height: window.innerHeight };
|
|
2112
1800
|
function classifyViewport(b) {
|
|
2113
1801
|
if (viewport.width <= 0 || viewport.height <= 0) return void 0;
|
|
@@ -2168,18 +1856,11 @@ function outlineAlgorithm(assignRefs) {
|
|
|
2168
1856
|
clickableEls.add(el);
|
|
2169
1857
|
}
|
|
2170
1858
|
}
|
|
2171
|
-
const refRegex = /^e\d+$/;
|
|
2172
1859
|
const previouslyRefed = /* @__PURE__ */ new Map();
|
|
2173
1860
|
for (const el of deepWalk(document)) {
|
|
2174
1861
|
const v = el.getAttribute("data-unotest-ref");
|
|
2175
|
-
if (v && refRegex.test(v)) previouslyRefed.set(el, v);
|
|
2176
|
-
}
|
|
2177
|
-
const html = document.documentElement;
|
|
2178
|
-
let refCounter = Number.parseInt(
|
|
2179
|
-
html.getAttribute("data-unotest-ref-counter") || "0",
|
|
2180
|
-
10
|
|
2181
|
-
);
|
|
2182
|
-
if (!Number.isFinite(refCounter) || refCounter < 0) refCounter = 0;
|
|
1862
|
+
if (v && h.refRegex.test(v)) previouslyRefed.set(el, v);
|
|
1863
|
+
}
|
|
2183
1864
|
const refByElement = /* @__PURE__ */ new Map();
|
|
2184
1865
|
if (!assignRefs) {
|
|
2185
1866
|
for (const el of interactiveElements) {
|
|
@@ -2189,19 +1870,9 @@ function outlineAlgorithm(assignRefs) {
|
|
|
2189
1870
|
} else {
|
|
2190
1871
|
const kept = /* @__PURE__ */ new Set();
|
|
2191
1872
|
for (const el of interactiveElements) {
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
el.setAttribute("data-unotest-ref-kind", "i");
|
|
2196
|
-
kept.add(el);
|
|
2197
|
-
} else {
|
|
2198
|
-
refCounter += 1;
|
|
2199
|
-
const ref = "e" + refCounter;
|
|
2200
|
-
el.setAttribute("data-unotest-ref", ref);
|
|
2201
|
-
el.setAttribute("data-unotest-ref-kind", "i");
|
|
2202
|
-
refByElement.set(el, ref);
|
|
2203
|
-
kept.add(el);
|
|
2204
|
-
}
|
|
1873
|
+
refByElement.set(el, h.ensureRef(el, "i"));
|
|
1874
|
+
el.setAttribute("data-unotest-ref-kind", "i");
|
|
1875
|
+
kept.add(el);
|
|
2205
1876
|
}
|
|
2206
1877
|
for (const [el] of previouslyRefed) {
|
|
2207
1878
|
if (kept.has(el)) continue;
|
|
@@ -2211,7 +1882,7 @@ function outlineAlgorithm(assignRefs) {
|
|
|
2211
1882
|
el.removeAttribute("data-unotest-ref-role");
|
|
2212
1883
|
el.removeAttribute("data-unotest-ref-name");
|
|
2213
1884
|
}
|
|
2214
|
-
|
|
1885
|
+
h.persistRefCounter();
|
|
2215
1886
|
}
|
|
2216
1887
|
const MAX_TEXT = 80;
|
|
2217
1888
|
const entries = [];
|
|
@@ -2226,8 +1897,8 @@ function outlineAlgorithm(assignRefs) {
|
|
|
2226
1897
|
if (!name) name = tooltipLabel(el) ?? "";
|
|
2227
1898
|
let href;
|
|
2228
1899
|
if (!name && tag === "a") {
|
|
2229
|
-
const
|
|
2230
|
-
if (
|
|
1900
|
+
const h2 = shortenedHref(el.getAttribute("href"));
|
|
1901
|
+
if (h2) href = h2;
|
|
2231
1902
|
}
|
|
2232
1903
|
const role = computeRole(el) ?? (tag === "tr" ? "row" : tag === "li" ? "listitem" : null);
|
|
2233
1904
|
if (assignRefs && refByElement.has(el)) {
|
|
@@ -2314,31 +1985,22 @@ function outlineAlgorithm(assignRefs) {
|
|
|
2314
1985
|
runRows.push({ tr: nextTr, start: scan, end: nextEnd, sig: nextSig });
|
|
2315
1986
|
scan = nextEnd;
|
|
2316
1987
|
}
|
|
2317
|
-
|
|
2318
|
-
for (let
|
|
2319
|
-
|
|
2320
|
-
for (let k = g.start; k < g.end; k++) {
|
|
2321
|
-
out.push({ kind: "entry", entry: entries[k] });
|
|
2322
|
-
}
|
|
1988
|
+
const pushRow = /* @__PURE__ */ __name((g) => {
|
|
1989
|
+
for (let k = g.start; k < g.end; k++) {
|
|
1990
|
+
out.push({ kind: "entry", entry: entries[k] });
|
|
2323
1991
|
}
|
|
1992
|
+
}, "pushRow");
|
|
1993
|
+
if (runRows.length >= ROW_COLLAPSE_MIN) {
|
|
1994
|
+
for (let r = 0; r < ROW_KEEP_HEAD; r++) pushRow(runRows[r]);
|
|
2324
1995
|
const omitted = runRows.length - ROW_KEEP_HEAD - ROW_KEEP_TAIL;
|
|
2325
1996
|
out.push({
|
|
2326
1997
|
kind: "summary",
|
|
2327
1998
|
text: `\u2026 ${omitted} similar rows \u2026`,
|
|
2328
1999
|
landmark: entries[runRows[0].start].landmark
|
|
2329
2000
|
});
|
|
2330
|
-
for (let r = runRows.length - ROW_KEEP_TAIL; r < runRows.length; r++)
|
|
2331
|
-
const g = runRows[r];
|
|
2332
|
-
for (let k = g.start; k < g.end; k++) {
|
|
2333
|
-
out.push({ kind: "entry", entry: entries[k] });
|
|
2334
|
-
}
|
|
2335
|
-
}
|
|
2001
|
+
for (let r = runRows.length - ROW_KEEP_TAIL; r < runRows.length; r++) pushRow(runRows[r]);
|
|
2336
2002
|
} else {
|
|
2337
|
-
for (const g of runRows)
|
|
2338
|
-
for (let k = g.start; k < g.end; k++) {
|
|
2339
|
-
out.push({ kind: "entry", entry: entries[k] });
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2003
|
+
for (const g of runRows) pushRow(g);
|
|
2342
2004
|
}
|
|
2343
2005
|
i = scan;
|
|
2344
2006
|
}
|
|
@@ -2405,66 +2067,9 @@ function outlineAlgorithm(assignRefs) {
|
|
|
2405
2067
|
__name(outlineAlgorithm, "outlineAlgorithm");
|
|
2406
2068
|
|
|
2407
2069
|
// src/inspection/aria-snapshot-capture.ts
|
|
2070
|
+
import { composeInjectedScript as composeInjectedScript2 } from "@unotest/grounder-client/dom-walker";
|
|
2408
2071
|
import { ariaSnapshotAlgorithm as ariaSnapshotAlgorithmInjectable } from "@unotest/web/internal/page-inject";
|
|
2409
|
-
function ariaSnapshotAlgorithm() {
|
|
2410
|
-
function* deepWalk(root) {
|
|
2411
|
-
const children = root.children;
|
|
2412
|
-
for (let i = 0; i < children.length; i++) {
|
|
2413
|
-
const el = children[i];
|
|
2414
|
-
yield el;
|
|
2415
|
-
const sr = el.shadowRoot;
|
|
2416
|
-
if (sr) yield* deepWalk(sr);
|
|
2417
|
-
yield* deepWalk(el);
|
|
2418
|
-
}
|
|
2419
|
-
}
|
|
2420
|
-
__name(deepWalk, "deepWalk");
|
|
2421
|
-
function parentOrHost(node) {
|
|
2422
|
-
const parent = node.parentNode;
|
|
2423
|
-
if (!parent) return null;
|
|
2424
|
-
if (parent instanceof ShadowRoot) return parent.host;
|
|
2425
|
-
return parent instanceof Element ? parent : null;
|
|
2426
|
-
}
|
|
2427
|
-
__name(parentOrHost, "parentOrHost");
|
|
2428
|
-
function rootForScope(el) {
|
|
2429
|
-
const root = el.getRootNode();
|
|
2430
|
-
return root instanceof ShadowRoot ? root : document;
|
|
2431
|
-
}
|
|
2432
|
-
__name(rootForScope, "rootForScope");
|
|
2433
|
-
function getElementByIdInScope(el, id) {
|
|
2434
|
-
return rootForScope(el).getElementById(id);
|
|
2435
|
-
}
|
|
2436
|
-
__name(getElementByIdInScope, "getElementByIdInScope");
|
|
2437
|
-
function isVisible(el) {
|
|
2438
|
-
if (!el.getClientRects || el.getClientRects().length === 0) return false;
|
|
2439
|
-
const cs = window.getComputedStyle(el);
|
|
2440
|
-
if (cs.display === "none" || cs.visibility === "hidden") return false;
|
|
2441
|
-
if (Number.parseFloat(cs.opacity || "1") < 0.01) return false;
|
|
2442
|
-
for (let p = el; p; p = parentOrHost(p)) {
|
|
2443
|
-
if (p.getAttribute && p.getAttribute("aria-hidden") === "true") return false;
|
|
2444
|
-
}
|
|
2445
|
-
const r = el.getBoundingClientRect();
|
|
2446
|
-
if (r.width === 0 && r.height === 0) return false;
|
|
2447
|
-
return true;
|
|
2448
|
-
}
|
|
2449
|
-
__name(isVisible, "isVisible");
|
|
2450
|
-
const INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
|
|
2451
|
-
"button",
|
|
2452
|
-
"link",
|
|
2453
|
-
"checkbox",
|
|
2454
|
-
"radio",
|
|
2455
|
-
"menuitem",
|
|
2456
|
-
"menuitemcheckbox",
|
|
2457
|
-
"menuitemradio",
|
|
2458
|
-
"tab",
|
|
2459
|
-
"option",
|
|
2460
|
-
"switch",
|
|
2461
|
-
"combobox",
|
|
2462
|
-
"listbox",
|
|
2463
|
-
"slider",
|
|
2464
|
-
"spinbutton",
|
|
2465
|
-
"searchbox",
|
|
2466
|
-
"textbox"
|
|
2467
|
-
]);
|
|
2072
|
+
function ariaSnapshotAlgorithm(h) {
|
|
2468
2073
|
const NAMED_STRUCTURAL_ROLES = /* @__PURE__ */ new Set([
|
|
2469
2074
|
"heading",
|
|
2470
2075
|
"list",
|
|
@@ -2495,337 +2100,12 @@ function ariaSnapshotAlgorithm() {
|
|
|
2495
2100
|
"alertdialog",
|
|
2496
2101
|
"article"
|
|
2497
2102
|
]);
|
|
2498
|
-
function implicitRole(el) {
|
|
2499
|
-
const tag = el.tagName.toLowerCase();
|
|
2500
|
-
if (tag === "a") {
|
|
2501
|
-
return el.hasAttribute("href") ? "link" : null;
|
|
2502
|
-
}
|
|
2503
|
-
if (tag === "button") return "button";
|
|
2504
|
-
if (tag === "summary") return "button";
|
|
2505
|
-
if (tag === "textarea") return "textbox";
|
|
2506
|
-
if (tag === "select") {
|
|
2507
|
-
const s = el;
|
|
2508
|
-
return s.multiple || s.size > 1 ? "listbox" : "combobox";
|
|
2509
|
-
}
|
|
2510
|
-
if (tag === "input") {
|
|
2511
|
-
const t = (el.type || "text").toLowerCase();
|
|
2512
|
-
if (t === "button" || t === "submit" || t === "reset" || t === "image") return "button";
|
|
2513
|
-
if (t === "checkbox") return "checkbox";
|
|
2514
|
-
if (t === "radio") return "radio";
|
|
2515
|
-
if (t === "range") return "slider";
|
|
2516
|
-
if (t === "number") return "spinbutton";
|
|
2517
|
-
if (t === "search") return "searchbox";
|
|
2518
|
-
if (t === "hidden") return null;
|
|
2519
|
-
return "textbox";
|
|
2520
|
-
}
|
|
2521
|
-
if (tag === "h1" || tag === "h2" || tag === "h3" || tag === "h4" || tag === "h5" || tag === "h6") {
|
|
2522
|
-
return "heading";
|
|
2523
|
-
}
|
|
2524
|
-
if (tag === "nav") return "navigation";
|
|
2525
|
-
if (tag === "header") return "banner";
|
|
2526
|
-
if (tag === "main") return "main";
|
|
2527
|
-
if (tag === "aside") return "complementary";
|
|
2528
|
-
if (tag === "footer") return "contentinfo";
|
|
2529
|
-
if (tag === "form") return "form";
|
|
2530
|
-
if (tag === "article") return "article";
|
|
2531
|
-
if (tag === "dialog") return "dialog";
|
|
2532
|
-
if (tag === "section") {
|
|
2533
|
-
if (el.hasAttribute("aria-label") || el.hasAttribute("aria-labelledby")) {
|
|
2534
|
-
return "region";
|
|
2535
|
-
}
|
|
2536
|
-
return null;
|
|
2537
|
-
}
|
|
2538
|
-
if (tag === "ul" || tag === "ol" || tag === "menu") return "list";
|
|
2539
|
-
if (tag === "li") return "listitem";
|
|
2540
|
-
if (tag === "table") return "table";
|
|
2541
|
-
if (tag === "tr") return "row";
|
|
2542
|
-
if (tag === "td") return "cell";
|
|
2543
|
-
if (tag === "th") {
|
|
2544
|
-
const scope = (el.getAttribute("scope") || "").toLowerCase();
|
|
2545
|
-
return scope === "row" ? "rowheader" : "columnheader";
|
|
2546
|
-
}
|
|
2547
|
-
if (tag === "p") return "paragraph";
|
|
2548
|
-
if (tag === "figure") return "figure";
|
|
2549
|
-
if (tag === "figcaption") return "caption";
|
|
2550
|
-
if (tag === "img") {
|
|
2551
|
-
const alt = el.getAttribute("alt");
|
|
2552
|
-
if (alt === "") return null;
|
|
2553
|
-
return "img";
|
|
2554
|
-
}
|
|
2555
|
-
if (tag === "code") return "code";
|
|
2556
|
-
if (tag === "pre") return "code";
|
|
2557
|
-
if (tag === "blockquote") return "blockquote";
|
|
2558
|
-
if (tag === "hr") return "separator";
|
|
2559
|
-
if (tag === "details") return "group";
|
|
2560
|
-
if (tag === "fieldset") return "group";
|
|
2561
|
-
if (tag === "iframe") return "iframe";
|
|
2562
|
-
return null;
|
|
2563
|
-
}
|
|
2564
|
-
__name(implicitRole, "implicitRole");
|
|
2565
|
-
function computeRole(el) {
|
|
2566
|
-
const explicit = el.getAttribute("role");
|
|
2567
|
-
if (explicit) return explicit.split(/\s+/)[0].toLowerCase();
|
|
2568
|
-
return implicitRole(el);
|
|
2569
|
-
}
|
|
2570
|
-
__name(computeRole, "computeRole");
|
|
2571
|
-
const MAX_NAME = 80;
|
|
2572
|
-
function trim(s) {
|
|
2573
|
-
return (s ?? "").replace(/\s+/g, " ").trim();
|
|
2574
|
-
}
|
|
2575
|
-
__name(trim, "trim");
|
|
2576
|
-
function cap(s) {
|
|
2577
|
-
return s.length > MAX_NAME ? s.slice(0, MAX_NAME - 1) + "\u2026" : s;
|
|
2578
|
-
}
|
|
2579
|
-
__name(cap, "cap");
|
|
2580
|
-
function cssEscape(s) {
|
|
2581
|
-
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
2582
|
-
}
|
|
2583
|
-
__name(cssEscape, "cssEscape");
|
|
2584
|
-
function textContentVisible(el) {
|
|
2585
|
-
const ht = el;
|
|
2586
|
-
if (typeof ht.innerText === "string") return ht.innerText;
|
|
2587
|
-
return el.textContent ?? "";
|
|
2588
|
-
}
|
|
2589
|
-
__name(textContentVisible, "textContentVisible");
|
|
2590
|
-
function composedAccname(el, seen) {
|
|
2591
|
-
const parts = [];
|
|
2592
|
-
for (const child of Array.from(el.childNodes)) {
|
|
2593
|
-
if (child.nodeType === 3) {
|
|
2594
|
-
const t = trim(child.nodeValue);
|
|
2595
|
-
if (t) parts.push(t);
|
|
2596
|
-
continue;
|
|
2597
|
-
}
|
|
2598
|
-
if (child.nodeType !== 1) continue;
|
|
2599
|
-
const ce = child;
|
|
2600
|
-
if (!isVisible(ce)) continue;
|
|
2601
|
-
if (seen.has(ce)) continue;
|
|
2602
|
-
seen.add(ce);
|
|
2603
|
-
const tag = ce.tagName.toLowerCase();
|
|
2604
|
-
if (tag === "img") {
|
|
2605
|
-
const alt = trim(ce.getAttribute("alt"));
|
|
2606
|
-
if (alt) parts.push(alt);
|
|
2607
|
-
continue;
|
|
2608
|
-
}
|
|
2609
|
-
if (tag === "svg") {
|
|
2610
|
-
const sn = svgAccname(ce, seen);
|
|
2611
|
-
if (sn) parts.push(sn);
|
|
2612
|
-
continue;
|
|
2613
|
-
}
|
|
2614
|
-
const sub = composedAccname(ce, seen);
|
|
2615
|
-
if (sub) parts.push(sub);
|
|
2616
|
-
}
|
|
2617
|
-
return trim(parts.join(" "));
|
|
2618
|
-
}
|
|
2619
|
-
__name(composedAccname, "composedAccname");
|
|
2620
|
-
function svgAccname(svg, seen) {
|
|
2621
|
-
const labelledby = svg.getAttribute("aria-labelledby");
|
|
2622
|
-
if (labelledby) {
|
|
2623
|
-
const parts = [];
|
|
2624
|
-
for (const id of labelledby.split(/\s+/)) {
|
|
2625
|
-
if (!id) continue;
|
|
2626
|
-
const ref = getElementByIdInScope(svg, id);
|
|
2627
|
-
if (ref && !seen.has(ref)) {
|
|
2628
|
-
const n = accname(ref, seen);
|
|
2629
|
-
if (n) parts.push(n);
|
|
2630
|
-
}
|
|
2631
|
-
}
|
|
2632
|
-
const joined = trim(parts.join(" "));
|
|
2633
|
-
if (joined) return cap(joined);
|
|
2634
|
-
}
|
|
2635
|
-
const ariaLabel = trim(svg.getAttribute("aria-label"));
|
|
2636
|
-
if (ariaLabel) return cap(ariaLabel);
|
|
2637
|
-
for (const child of Array.from(svg.children)) {
|
|
2638
|
-
if (child.tagName.toLowerCase() === "title") {
|
|
2639
|
-
const t = trim(child.textContent);
|
|
2640
|
-
if (t) return cap(t);
|
|
2641
|
-
break;
|
|
2642
|
-
}
|
|
2643
|
-
}
|
|
2644
|
-
return "";
|
|
2645
|
-
}
|
|
2646
|
-
__name(svgAccname, "svgAccname");
|
|
2647
|
-
function accname(el, visited) {
|
|
2648
|
-
const seen = visited ?? /* @__PURE__ */ new Set();
|
|
2649
|
-
if (seen.has(el)) return "";
|
|
2650
|
-
seen.add(el);
|
|
2651
|
-
const labelledby = el.getAttribute("aria-labelledby");
|
|
2652
|
-
if (labelledby) {
|
|
2653
|
-
const parts = [];
|
|
2654
|
-
for (const id of labelledby.split(/\s+/)) {
|
|
2655
|
-
if (!id) continue;
|
|
2656
|
-
const ref = getElementByIdInScope(el, id);
|
|
2657
|
-
if (ref && !seen.has(ref)) {
|
|
2658
|
-
const n = accname(ref, seen);
|
|
2659
|
-
if (n) parts.push(n);
|
|
2660
|
-
}
|
|
2661
|
-
}
|
|
2662
|
-
const joined = trim(parts.join(" "));
|
|
2663
|
-
if (joined) return cap(joined);
|
|
2664
|
-
}
|
|
2665
|
-
const ariaLabel = trim(el.getAttribute("aria-label"));
|
|
2666
|
-
if (ariaLabel) return cap(ariaLabel);
|
|
2667
|
-
const tag = el.tagName.toLowerCase();
|
|
2668
|
-
if (tag === "input" || tag === "textarea" || tag === "select") {
|
|
2669
|
-
const id = el.id;
|
|
2670
|
-
if (id) {
|
|
2671
|
-
const lbl = rootForScope(el).querySelector(
|
|
2672
|
-
`label[for="${cssEscape(id)}"]`
|
|
2673
|
-
);
|
|
2674
|
-
if (lbl) {
|
|
2675
|
-
const n = trim(textContentVisible(lbl));
|
|
2676
|
-
if (n) return cap(n);
|
|
2677
|
-
}
|
|
2678
|
-
}
|
|
2679
|
-
let p = parentOrHost(el);
|
|
2680
|
-
while (p) {
|
|
2681
|
-
if (p.tagName.toLowerCase() === "label") {
|
|
2682
|
-
const n = trim(textContentVisible(p));
|
|
2683
|
-
if (n) return cap(n);
|
|
2684
|
-
break;
|
|
2685
|
-
}
|
|
2686
|
-
p = parentOrHost(p);
|
|
2687
|
-
}
|
|
2688
|
-
if (tag === "input") {
|
|
2689
|
-
const it = el.type?.toLowerCase();
|
|
2690
|
-
if (it === "button" || it === "submit" || it === "reset") {
|
|
2691
|
-
const v = trim(el.value);
|
|
2692
|
-
if (v) return cap(v);
|
|
2693
|
-
}
|
|
2694
|
-
const ph = trim(el.placeholder);
|
|
2695
|
-
if (ph) return cap(ph);
|
|
2696
|
-
}
|
|
2697
|
-
const t2 = trim(el.getAttribute("title"));
|
|
2698
|
-
if (t2) return cap(t2);
|
|
2699
|
-
return "";
|
|
2700
|
-
}
|
|
2701
|
-
if (tag === "button" || tag === "a" || tag === "summary") {
|
|
2702
|
-
const composed = composedAccname(el, seen);
|
|
2703
|
-
if (composed) return cap(composed);
|
|
2704
|
-
const t2 = trim(el.getAttribute("title"));
|
|
2705
|
-
if (t2) return cap(t2);
|
|
2706
|
-
return "";
|
|
2707
|
-
}
|
|
2708
|
-
if (tag === "h1" || tag === "h2" || tag === "h3" || tag === "h4" || tag === "h5" || tag === "h6") {
|
|
2709
|
-
return cap(trim(textContentVisible(el)));
|
|
2710
|
-
}
|
|
2711
|
-
if (tag === "img") {
|
|
2712
|
-
return cap(trim(el.getAttribute("alt")));
|
|
2713
|
-
}
|
|
2714
|
-
if (tag === "iframe") {
|
|
2715
|
-
const t2 = trim(el.getAttribute("title"));
|
|
2716
|
-
if (t2) return cap(t2);
|
|
2717
|
-
return "";
|
|
2718
|
-
}
|
|
2719
|
-
if (tag === "figure") {
|
|
2720
|
-
for (const child of Array.from(el.children)) {
|
|
2721
|
-
if (child.tagName.toLowerCase() === "figcaption") {
|
|
2722
|
-
const t2 = trim(textContentVisible(child));
|
|
2723
|
-
if (t2) return cap(t2);
|
|
2724
|
-
break;
|
|
2725
|
-
}
|
|
2726
|
-
}
|
|
2727
|
-
return "";
|
|
2728
|
-
}
|
|
2729
|
-
if (tag === "fieldset") {
|
|
2730
|
-
for (const child of Array.from(el.children)) {
|
|
2731
|
-
if (child.tagName.toLowerCase() === "legend") {
|
|
2732
|
-
const t2 = trim(textContentVisible(child));
|
|
2733
|
-
if (t2) return cap(t2);
|
|
2734
|
-
break;
|
|
2735
|
-
}
|
|
2736
|
-
}
|
|
2737
|
-
return "";
|
|
2738
|
-
}
|
|
2739
|
-
if (tag === "form" || tag === "nav" || tag === "header" || tag === "main" || tag === "aside" || tag === "footer" || tag === "section" || tag === "article" || tag === "dialog") {
|
|
2740
|
-
return "";
|
|
2741
|
-
}
|
|
2742
|
-
if (tag === "ul" || tag === "ol" || tag === "menu" || tag === "li" || tag === "table" || tag === "tr" || tag === "td" || tag === "th" || tag === "p" || tag === "code" || tag === "pre" || tag === "blockquote" || tag === "details") {
|
|
2743
|
-
return "";
|
|
2744
|
-
}
|
|
2745
|
-
const inner = trim(textContentVisible(el));
|
|
2746
|
-
if (inner) return cap(inner);
|
|
2747
|
-
const t = trim(el.getAttribute("title"));
|
|
2748
|
-
return cap(t);
|
|
2749
|
-
}
|
|
2750
|
-
__name(accname, "accname");
|
|
2751
|
-
function collectState(el, role) {
|
|
2752
|
-
const state = {};
|
|
2753
|
-
const tag = el.tagName.toLowerCase();
|
|
2754
|
-
if (el.getAttribute("aria-disabled") === "true" || el.disabled) {
|
|
2755
|
-
state.disabled = true;
|
|
2756
|
-
}
|
|
2757
|
-
const ariaChecked = el.getAttribute("aria-checked");
|
|
2758
|
-
if (ariaChecked === "true") state.checked = true;
|
|
2759
|
-
else if (ariaChecked === "mixed") state.checked = "mixed";
|
|
2760
|
-
else if (tag === "input" && (el.type === "checkbox" || el.type === "radio") && el.checked) {
|
|
2761
|
-
state.checked = true;
|
|
2762
|
-
}
|
|
2763
|
-
const ariaPressed = el.getAttribute("aria-pressed");
|
|
2764
|
-
if (ariaPressed === "true") state.pressed = true;
|
|
2765
|
-
else if (ariaPressed === "mixed") state.pressed = "mixed";
|
|
2766
|
-
if (el.getAttribute("aria-expanded") === "true") state.expanded = true;
|
|
2767
|
-
if (el.getAttribute("aria-selected") === "true") state.selected = true;
|
|
2768
|
-
else if (tag === "option" && el.selected) {
|
|
2769
|
-
state.selected = true;
|
|
2770
|
-
}
|
|
2771
|
-
if (rootForScope(el).activeElement === el) state.active = true;
|
|
2772
|
-
if (role === "heading") {
|
|
2773
|
-
if (tag === "h1") state.level = 1;
|
|
2774
|
-
else if (tag === "h2") state.level = 2;
|
|
2775
|
-
else if (tag === "h3") state.level = 3;
|
|
2776
|
-
else if (tag === "h4") state.level = 4;
|
|
2777
|
-
else if (tag === "h5") state.level = 5;
|
|
2778
|
-
else if (tag === "h6") state.level = 6;
|
|
2779
|
-
const ariaLevel = el.getAttribute("aria-level");
|
|
2780
|
-
if (ariaLevel) {
|
|
2781
|
-
const n = Number.parseInt(ariaLevel, 10);
|
|
2782
|
-
if (Number.isFinite(n) && n > 0) state.level = n;
|
|
2783
|
-
}
|
|
2784
|
-
}
|
|
2785
|
-
return Object.keys(state).length > 0 ? state : void 0;
|
|
2786
|
-
}
|
|
2787
|
-
__name(collectState, "collectState");
|
|
2788
|
-
const pageOrigin = (() => {
|
|
2789
|
-
try {
|
|
2790
|
-
return window.location.origin;
|
|
2791
|
-
} catch {
|
|
2792
|
-
return "";
|
|
2793
|
-
}
|
|
2794
|
-
})();
|
|
2795
|
-
function normaliseHref(raw) {
|
|
2796
|
-
if (!raw) return void 0;
|
|
2797
|
-
let href = raw.trim();
|
|
2798
|
-
if (!href) return void 0;
|
|
2799
|
-
if (pageOrigin && href.startsWith(pageOrigin)) {
|
|
2800
|
-
href = href.slice(pageOrigin.length) || "/";
|
|
2801
|
-
}
|
|
2802
|
-
return href;
|
|
2803
|
-
}
|
|
2804
|
-
__name(normaliseHref, "normaliseHref");
|
|
2805
|
-
const REF_REGEX = /^e\d+$/;
|
|
2806
|
-
const html = document.documentElement;
|
|
2807
|
-
let refCounter = Number.parseInt(
|
|
2808
|
-
html.getAttribute("data-unotest-ref-counter") || "0",
|
|
2809
|
-
10
|
|
2810
|
-
);
|
|
2811
|
-
if (!Number.isFinite(refCounter) || refCounter < 0) refCounter = 0;
|
|
2812
|
-
function ensureRef(el, role) {
|
|
2813
|
-
const existing = el.getAttribute("data-unotest-ref");
|
|
2814
|
-
if (existing && REF_REGEX.test(existing)) return existing;
|
|
2815
|
-
refCounter += 1;
|
|
2816
|
-
const ref = "e" + refCounter;
|
|
2817
|
-
el.setAttribute("data-unotest-ref", ref);
|
|
2818
|
-
const interactive = INTERACTIVE_ROLES.has(role);
|
|
2819
|
-
el.setAttribute("data-unotest-ref-kind", interactive ? "i" : "n");
|
|
2820
|
-
return ref;
|
|
2821
|
-
}
|
|
2822
|
-
__name(ensureRef, "ensureRef");
|
|
2823
2103
|
let totalNodes = 0;
|
|
2824
2104
|
let totalRefs = 0;
|
|
2825
2105
|
let frameCounter = 0;
|
|
2826
2106
|
function shouldInclude(role) {
|
|
2827
2107
|
if (!role) return false;
|
|
2828
|
-
if (
|
|
2108
|
+
if (h.interactiveRoles.has(role)) return true;
|
|
2829
2109
|
if (NAMED_STRUCTURAL_ROLES.has(role)) return true;
|
|
2830
2110
|
if (LANDMARK_ROLES.has(role)) return true;
|
|
2831
2111
|
if (role === "caption") return true;
|
|
@@ -2833,7 +2113,7 @@ function ariaSnapshotAlgorithm() {
|
|
|
2833
2113
|
}
|
|
2834
2114
|
__name(shouldInclude, "shouldInclude");
|
|
2835
2115
|
function makeRef(el, role, name, refPrefix) {
|
|
2836
|
-
const localRef = ensureRef(el, role);
|
|
2116
|
+
const localRef = h.ensureRef(el, h.interactiveRoles.has(role) ? "i" : "n");
|
|
2837
2117
|
el.setAttribute("data-unotest-ref-role", role);
|
|
2838
2118
|
el.setAttribute("data-unotest-ref-name", name);
|
|
2839
2119
|
totalRefs += 1;
|
|
@@ -2853,7 +2133,7 @@ function ariaSnapshotAlgorithm() {
|
|
|
2853
2133
|
function descendInto(parent, bucket, refPrefix, parentName) {
|
|
2854
2134
|
const buf = { value: "" };
|
|
2855
2135
|
function flush() {
|
|
2856
|
-
const t = trim(buf.value);
|
|
2136
|
+
const t = h.trim(buf.value);
|
|
2857
2137
|
if (t) pushText(t, bucket, parentName);
|
|
2858
2138
|
buf.value = "";
|
|
2859
2139
|
}
|
|
@@ -2865,7 +2145,7 @@ function ariaSnapshotAlgorithm() {
|
|
|
2865
2145
|
}
|
|
2866
2146
|
if (node.nodeType !== 1) return;
|
|
2867
2147
|
const el = node;
|
|
2868
|
-
if (!isVisible(el)) return;
|
|
2148
|
+
if (!h.isVisible(el)) return;
|
|
2869
2149
|
const tag = el.tagName.toLowerCase();
|
|
2870
2150
|
if (tag === "script" || tag === "style" || tag === "noscript") return;
|
|
2871
2151
|
if (tag === "iframe") {
|
|
@@ -2873,7 +2153,7 @@ function ariaSnapshotAlgorithm() {
|
|
|
2873
2153
|
visit(el, bucket, refPrefix, parentName);
|
|
2874
2154
|
return;
|
|
2875
2155
|
}
|
|
2876
|
-
const role = computeRole(el);
|
|
2156
|
+
const role = h.computeRole(el);
|
|
2877
2157
|
if (shouldInclude(role)) {
|
|
2878
2158
|
flush();
|
|
2879
2159
|
visit(el, bucket, refPrefix, parentName);
|
|
@@ -2897,7 +2177,7 @@ function ariaSnapshotAlgorithm() {
|
|
|
2897
2177
|
function visit(el, parentBucket, refPrefix, _parentName) {
|
|
2898
2178
|
const tag = el.tagName.toLowerCase();
|
|
2899
2179
|
if (tag === "iframe") {
|
|
2900
|
-
const name2 = accname(el);
|
|
2180
|
+
const name2 = h.accname(el);
|
|
2901
2181
|
const ref = makeRef(el, "iframe", name2, refPrefix);
|
|
2902
2182
|
const node2 = { role: "iframe", name: name2, ref, children: [] };
|
|
2903
2183
|
let inner = null;
|
|
@@ -2915,23 +2195,23 @@ function ariaSnapshotAlgorithm() {
|
|
|
2915
2195
|
parentBucket.push(node2);
|
|
2916
2196
|
return;
|
|
2917
2197
|
}
|
|
2918
|
-
const role = computeRole(el);
|
|
2919
|
-
const name = accname(el);
|
|
2198
|
+
const role = h.computeRole(el);
|
|
2199
|
+
const name = h.accname(el);
|
|
2920
2200
|
const node = { role, name, children: [] };
|
|
2921
2201
|
node.ref = makeRef(el, role, name, refPrefix);
|
|
2922
2202
|
if (role === "link") {
|
|
2923
|
-
const url = normaliseHref(
|
|
2203
|
+
const url = h.normaliseHref(
|
|
2924
2204
|
el.getAttribute("href")
|
|
2925
2205
|
);
|
|
2926
2206
|
if (url) node.url = url;
|
|
2927
2207
|
}
|
|
2928
|
-
const state = collectState(el, role);
|
|
2208
|
+
const state = h.collectState(el, role);
|
|
2929
2209
|
if (state) node.state = state;
|
|
2930
2210
|
totalNodes += 1;
|
|
2931
2211
|
parentBucket.push(node);
|
|
2932
2212
|
if (role === "paragraph" || role === "code") {
|
|
2933
2213
|
if (!node.name) {
|
|
2934
|
-
const t = trim(textContentVisible(el));
|
|
2214
|
+
const t = h.trim(h.textContentVisible(el));
|
|
2935
2215
|
if (t) node.name = t;
|
|
2936
2216
|
}
|
|
2937
2217
|
return;
|
|
@@ -3004,7 +2284,7 @@ function ariaSnapshotAlgorithm() {
|
|
|
3004
2284
|
if (body) {
|
|
3005
2285
|
descendInto(body, rootBucket, "", "");
|
|
3006
2286
|
}
|
|
3007
|
-
|
|
2287
|
+
h.persistRefCounter();
|
|
3008
2288
|
for (const r of rootBucket) collapseRepetitiveRows(r);
|
|
3009
2289
|
const recounted = recountTree(rootBucket);
|
|
3010
2290
|
totalNodes = recounted.nodes;
|
|
@@ -3095,12 +2375,31 @@ function pickerOverlayAlgorithm(op) {
|
|
|
3095
2375
|
}, "onScroll")
|
|
3096
2376
|
};
|
|
3097
2377
|
const norm = /* @__PURE__ */ __name((s) => (s ?? "").replace(/\s+/g, " ").trim(), "norm");
|
|
2378
|
+
const deepElementFromPoint = /* @__PURE__ */ __name((x, y) => {
|
|
2379
|
+
let el = document.elementFromPoint(x, y);
|
|
2380
|
+
while (el && el.shadowRoot) {
|
|
2381
|
+
const inner = el.shadowRoot.elementFromPoint(x, y);
|
|
2382
|
+
if (!inner || inner === el) break;
|
|
2383
|
+
el = inner;
|
|
2384
|
+
}
|
|
2385
|
+
return el;
|
|
2386
|
+
}, "deepElementFromPoint");
|
|
2387
|
+
const closestRef = /* @__PURE__ */ __name((el, sel) => {
|
|
2388
|
+
let node = el;
|
|
2389
|
+
while (node) {
|
|
2390
|
+
const found = node.closest(sel);
|
|
2391
|
+
if (found) return found;
|
|
2392
|
+
const root = node.getRootNode();
|
|
2393
|
+
node = root instanceof ShadowRoot ? root.host : null;
|
|
2394
|
+
}
|
|
2395
|
+
return null;
|
|
2396
|
+
}, "closestRef");
|
|
3098
2397
|
const targetFrom = /* @__PURE__ */ __name((el) => {
|
|
3099
2398
|
if (!el) return null;
|
|
3100
2399
|
const sel = state.mode === "locator" ? `[${KIND_ATTR}="i"]` : `[${REF_ATTR}]`;
|
|
3101
|
-
return el
|
|
2400
|
+
return closestRef(el, sel);
|
|
3102
2401
|
}, "targetFrom");
|
|
3103
|
-
const refOf = /* @__PURE__ */ __name((el) => el instanceof Element ? el
|
|
2402
|
+
const refOf = /* @__PURE__ */ __name((el) => el instanceof Element ? closestRef(el, `[${REF_ATTR}]`) : null, "refOf");
|
|
3104
2403
|
const push = /* @__PURE__ */ __name((node, action, extra) => {
|
|
3105
2404
|
const ref = node.getAttribute(REF_ATTR);
|
|
3106
2405
|
if (!ref) return;
|
|
@@ -3145,7 +2444,7 @@ function pickerOverlayAlgorithm(op) {
|
|
|
3145
2444
|
}, "clearHighlight");
|
|
3146
2445
|
state.onMove = (e) => {
|
|
3147
2446
|
if (!e.isTrusted) return;
|
|
3148
|
-
const node = targetFrom(
|
|
2447
|
+
const node = targetFrom(deepElementFromPoint(e.clientX, e.clientY));
|
|
3149
2448
|
if (!node) {
|
|
3150
2449
|
clearHighlight();
|
|
3151
2450
|
return;
|
|
@@ -3153,7 +2452,7 @@ function pickerOverlayAlgorithm(op) {
|
|
|
3153
2452
|
state.current = node;
|
|
3154
2453
|
showHighlight(node);
|
|
3155
2454
|
};
|
|
3156
|
-
const overTarget = /* @__PURE__ */ __name((e) => targetFrom(
|
|
2455
|
+
const overTarget = /* @__PURE__ */ __name((e) => targetFrom(deepElementFromPoint(e.clientX, e.clientY)), "overTarget");
|
|
3157
2456
|
const swallow = /* @__PURE__ */ __name((e) => {
|
|
3158
2457
|
e.preventDefault();
|
|
3159
2458
|
e.stopPropagation();
|
|
@@ -3342,175 +2641,9 @@ function serializeRuntimeDom() {
|
|
|
3342
2641
|
__name(serializeRuntimeDom, "serializeRuntimeDom");
|
|
3343
2642
|
|
|
3344
2643
|
// src/inspection/find-element.ts
|
|
2644
|
+
import { composeInjectedScript as composeInjectedScript3 } from "@unotest/grounder-client/dom-walker";
|
|
3345
2645
|
import { findElementAlgorithm as findElementAlgorithmInjectable } from "@unotest/web/internal/page-inject";
|
|
3346
|
-
function findElementAlgorithm(args) {
|
|
3347
|
-
function* deepWalk(root) {
|
|
3348
|
-
const children = root.children;
|
|
3349
|
-
for (let i = 0; i < children.length; i++) {
|
|
3350
|
-
const el = children[i];
|
|
3351
|
-
yield el;
|
|
3352
|
-
const sr = el.shadowRoot;
|
|
3353
|
-
if (sr) yield* deepWalk(sr);
|
|
3354
|
-
yield* deepWalk(el);
|
|
3355
|
-
}
|
|
3356
|
-
}
|
|
3357
|
-
__name(deepWalk, "deepWalk");
|
|
3358
|
-
function parentOrHost(node) {
|
|
3359
|
-
const parent = node.parentNode;
|
|
3360
|
-
if (!parent) return null;
|
|
3361
|
-
if (parent instanceof ShadowRoot) return parent.host;
|
|
3362
|
-
return parent instanceof Element ? parent : null;
|
|
3363
|
-
}
|
|
3364
|
-
__name(parentOrHost, "parentOrHost");
|
|
3365
|
-
function rootForScope(el) {
|
|
3366
|
-
const root = el.getRootNode();
|
|
3367
|
-
return root instanceof ShadowRoot ? root : document;
|
|
3368
|
-
}
|
|
3369
|
-
__name(rootForScope, "rootForScope");
|
|
3370
|
-
function getElementByIdInScope(el, id) {
|
|
3371
|
-
return rootForScope(el).getElementById(id);
|
|
3372
|
-
}
|
|
3373
|
-
__name(getElementByIdInScope, "getElementByIdInScope");
|
|
3374
|
-
function isVisible(el) {
|
|
3375
|
-
if (!el.getClientRects || el.getClientRects().length === 0) return false;
|
|
3376
|
-
const cs = window.getComputedStyle(el);
|
|
3377
|
-
if (cs.display === "none" || cs.visibility === "hidden") return false;
|
|
3378
|
-
if (Number.parseFloat(cs.opacity || "1") < 0.01) return false;
|
|
3379
|
-
for (let p = el; p; p = parentOrHost(p)) {
|
|
3380
|
-
if (p.getAttribute && p.getAttribute("aria-hidden") === "true") return false;
|
|
3381
|
-
}
|
|
3382
|
-
const r = el.getBoundingClientRect();
|
|
3383
|
-
if (r.width === 0 && r.height === 0) return false;
|
|
3384
|
-
return true;
|
|
3385
|
-
}
|
|
3386
|
-
__name(isVisible, "isVisible");
|
|
3387
|
-
function implicitRole(el) {
|
|
3388
|
-
const tag = el.tagName.toLowerCase();
|
|
3389
|
-
if (tag === "a") return el.hasAttribute("href") ? "link" : null;
|
|
3390
|
-
if (tag === "button") return "button";
|
|
3391
|
-
if (tag === "summary") return "button";
|
|
3392
|
-
if (tag === "textarea") return "textbox";
|
|
3393
|
-
if (tag === "select") {
|
|
3394
|
-
const s = el;
|
|
3395
|
-
return s.multiple || s.size > 1 ? "listbox" : "combobox";
|
|
3396
|
-
}
|
|
3397
|
-
if (tag === "input") {
|
|
3398
|
-
const t = (el.type || "text").toLowerCase();
|
|
3399
|
-
if (t === "button" || t === "submit" || t === "reset" || t === "image") return "button";
|
|
3400
|
-
if (t === "checkbox") return "checkbox";
|
|
3401
|
-
if (t === "radio") return "radio";
|
|
3402
|
-
if (t === "range") return "slider";
|
|
3403
|
-
if (t === "number") return "spinbutton";
|
|
3404
|
-
if (t === "search") return "searchbox";
|
|
3405
|
-
if (t === "hidden") return null;
|
|
3406
|
-
return "textbox";
|
|
3407
|
-
}
|
|
3408
|
-
if (/^h[1-6]$/.test(tag)) return "heading";
|
|
3409
|
-
if (tag === "nav") return "navigation";
|
|
3410
|
-
if (tag === "header") return "banner";
|
|
3411
|
-
if (tag === "main") return "main";
|
|
3412
|
-
if (tag === "aside") return "complementary";
|
|
3413
|
-
if (tag === "footer") return "contentinfo";
|
|
3414
|
-
if (tag === "form") return "form";
|
|
3415
|
-
if (tag === "article") return "article";
|
|
3416
|
-
if (tag === "dialog") return "dialog";
|
|
3417
|
-
if (tag === "section") {
|
|
3418
|
-
return el.hasAttribute("aria-label") || el.hasAttribute("aria-labelledby") ? "region" : null;
|
|
3419
|
-
}
|
|
3420
|
-
if (tag === "ul" || tag === "ol" || tag === "menu") return "list";
|
|
3421
|
-
if (tag === "li") return "listitem";
|
|
3422
|
-
if (tag === "table") return "table";
|
|
3423
|
-
if (tag === "tr") return "row";
|
|
3424
|
-
if (tag === "td") return "cell";
|
|
3425
|
-
if (tag === "th") {
|
|
3426
|
-
const scope = (el.getAttribute("scope") || "").toLowerCase();
|
|
3427
|
-
return scope === "row" ? "rowheader" : "columnheader";
|
|
3428
|
-
}
|
|
3429
|
-
if (tag === "p") return "paragraph";
|
|
3430
|
-
if (tag === "figure") return "figure";
|
|
3431
|
-
if (tag === "img") {
|
|
3432
|
-
const alt = el.getAttribute("alt");
|
|
3433
|
-
if (alt === "") return null;
|
|
3434
|
-
return "img";
|
|
3435
|
-
}
|
|
3436
|
-
if (tag === "details" || tag === "fieldset") return "group";
|
|
3437
|
-
if (tag === "iframe") return "iframe";
|
|
3438
|
-
return null;
|
|
3439
|
-
}
|
|
3440
|
-
__name(implicitRole, "implicitRole");
|
|
3441
|
-
function computeRole(el) {
|
|
3442
|
-
const explicit = el.getAttribute("role");
|
|
3443
|
-
if (explicit) return explicit.split(/\s+/)[0].toLowerCase();
|
|
3444
|
-
return implicitRole(el);
|
|
3445
|
-
}
|
|
3446
|
-
__name(computeRole, "computeRole");
|
|
3447
|
-
function trim(s) {
|
|
3448
|
-
return (s ?? "").replace(/\s+/g, " ").trim();
|
|
3449
|
-
}
|
|
3450
|
-
__name(trim, "trim");
|
|
3451
|
-
function textContentVisible(el) {
|
|
3452
|
-
const ht = el;
|
|
3453
|
-
if (typeof ht.innerText === "string") return ht.innerText;
|
|
3454
|
-
return el.textContent ?? "";
|
|
3455
|
-
}
|
|
3456
|
-
__name(textContentVisible, "textContentVisible");
|
|
3457
|
-
const MAX_NAME = 200;
|
|
3458
|
-
function cap(s) {
|
|
3459
|
-
return s.length > MAX_NAME ? s.slice(0, MAX_NAME - 1) + "\u2026" : s;
|
|
3460
|
-
}
|
|
3461
|
-
__name(cap, "cap");
|
|
3462
|
-
function accname(el, visited) {
|
|
3463
|
-
const seen = visited ?? /* @__PURE__ */ new Set();
|
|
3464
|
-
if (seen.has(el)) return "";
|
|
3465
|
-
seen.add(el);
|
|
3466
|
-
const labelledby = el.getAttribute("aria-labelledby");
|
|
3467
|
-
if (labelledby) {
|
|
3468
|
-
const parts = [];
|
|
3469
|
-
for (const id of labelledby.split(/\s+/)) {
|
|
3470
|
-
if (!id) continue;
|
|
3471
|
-
const ref = getElementByIdInScope(el, id);
|
|
3472
|
-
if (ref && !seen.has(ref)) {
|
|
3473
|
-
const n = accname(ref, seen);
|
|
3474
|
-
if (n) parts.push(n);
|
|
3475
|
-
}
|
|
3476
|
-
}
|
|
3477
|
-
const joined = trim(parts.join(" "));
|
|
3478
|
-
if (joined) return cap(joined);
|
|
3479
|
-
}
|
|
3480
|
-
const ariaLabel = trim(el.getAttribute("aria-label"));
|
|
3481
|
-
if (ariaLabel) return cap(ariaLabel);
|
|
3482
|
-
const tag = el.tagName.toLowerCase();
|
|
3483
|
-
if (tag === "input" || tag === "textarea") {
|
|
3484
|
-
const ph = trim(el.placeholder);
|
|
3485
|
-
if (ph) return cap(ph);
|
|
3486
|
-
const t = trim(el.getAttribute("title"));
|
|
3487
|
-
if (t) return cap(t);
|
|
3488
|
-
return "";
|
|
3489
|
-
}
|
|
3490
|
-
if (tag === "img") return cap(trim(el.getAttribute("alt")));
|
|
3491
|
-
if (tag === "form" || tag === "nav" || tag === "header" || tag === "main" || tag === "aside" || tag === "footer" || tag === "section" || tag === "article" || tag === "dialog" || tag === "ul" || tag === "ol" || tag === "menu" || tag === "li" || tag === "table" || tag === "tr" || tag === "td" || tag === "th" || tag === "p" || tag === "code" || tag === "pre" || tag === "blockquote" || tag === "fieldset" || tag === "details") {
|
|
3492
|
-
return "";
|
|
3493
|
-
}
|
|
3494
|
-
return cap(trim(textContentVisible(el)));
|
|
3495
|
-
}
|
|
3496
|
-
__name(accname, "accname");
|
|
3497
|
-
const REF_REGEX = /^e\d+$/;
|
|
3498
|
-
const html = document.documentElement;
|
|
3499
|
-
let refCounter = Number.parseInt(
|
|
3500
|
-
html.getAttribute("data-unotest-ref-counter") || "0",
|
|
3501
|
-
10
|
|
3502
|
-
);
|
|
3503
|
-
if (!Number.isFinite(refCounter) || refCounter < 0) refCounter = 0;
|
|
3504
|
-
function ensureRef(el) {
|
|
3505
|
-
const existing = el.getAttribute("data-unotest-ref");
|
|
3506
|
-
if (existing && REF_REGEX.test(existing)) return existing;
|
|
3507
|
-
refCounter += 1;
|
|
3508
|
-
const ref = "e" + refCounter;
|
|
3509
|
-
el.setAttribute("data-unotest-ref", ref);
|
|
3510
|
-
el.setAttribute("data-unotest-ref-kind", "n");
|
|
3511
|
-
return ref;
|
|
3512
|
-
}
|
|
3513
|
-
__name(ensureRef, "ensureRef");
|
|
2646
|
+
function findElementAlgorithm(h, args) {
|
|
3514
2647
|
let scopeRoot = document;
|
|
3515
2648
|
if (args.near) {
|
|
3516
2649
|
const scoped = document.querySelector(
|
|
@@ -3521,12 +2654,12 @@ function findElementAlgorithm(args) {
|
|
|
3521
2654
|
const wantName = args.name ? args.name.toLowerCase() : null;
|
|
3522
2655
|
const hits = [];
|
|
3523
2656
|
function visitOne(el) {
|
|
3524
|
-
if (!isVisible(el)) return;
|
|
3525
|
-
const role = computeRole(el);
|
|
2657
|
+
if (!h.isVisible(el)) return;
|
|
2658
|
+
const role = h.computeRole(el);
|
|
3526
2659
|
if (role === args.role) {
|
|
3527
2660
|
if (wantName === null) {
|
|
3528
2661
|
hits.push(el);
|
|
3529
|
-
} else if (accname(el).toLowerCase().includes(wantName)) {
|
|
2662
|
+
} else if (h.accname(el).toLowerCase().includes(wantName)) {
|
|
3530
2663
|
hits.push(el);
|
|
3531
2664
|
}
|
|
3532
2665
|
}
|
|
@@ -3535,10 +2668,10 @@ function findElementAlgorithm(args) {
|
|
|
3535
2668
|
if (scopeRoot !== document) {
|
|
3536
2669
|
const sr = scopeRoot.shadowRoot;
|
|
3537
2670
|
if (sr) {
|
|
3538
|
-
for (const el of deepWalk(sr)) visitOne(el);
|
|
2671
|
+
for (const el of h.deepWalk(sr)) visitOne(el);
|
|
3539
2672
|
}
|
|
3540
2673
|
}
|
|
3541
|
-
for (const el of deepWalk(scopeRoot)) visitOne(el);
|
|
2674
|
+
for (const el of h.deepWalk(scopeRoot)) visitOne(el);
|
|
3542
2675
|
const totalCount = hits.length;
|
|
3543
2676
|
let selected = hits;
|
|
3544
2677
|
if (args.nth !== void 0) {
|
|
@@ -3549,28 +2682,28 @@ function findElementAlgorithm(args) {
|
|
|
3549
2682
|
}
|
|
3550
2683
|
function namedAncestors(el) {
|
|
3551
2684
|
const chain = [];
|
|
3552
|
-
let p = parentOrHost(el);
|
|
2685
|
+
let p = h.parentOrHost(el);
|
|
3553
2686
|
while (p) {
|
|
3554
|
-
const role = computeRole(p);
|
|
2687
|
+
const role = h.computeRole(p);
|
|
3555
2688
|
if (role) {
|
|
3556
|
-
const name = accname(p);
|
|
2689
|
+
const name = h.accname(p);
|
|
3557
2690
|
if (role !== "generic" || name) {
|
|
3558
|
-
chain.push({ role, name, ref: ensureRef(p) });
|
|
2691
|
+
chain.push({ role, name, ref: h.ensureRef(p, "n") });
|
|
3559
2692
|
}
|
|
3560
2693
|
}
|
|
3561
|
-
p = parentOrHost(p);
|
|
2694
|
+
p = h.parentOrHost(p);
|
|
3562
2695
|
}
|
|
3563
2696
|
return chain.reverse().slice(-4);
|
|
3564
2697
|
}
|
|
3565
2698
|
__name(namedAncestors, "namedAncestors");
|
|
3566
2699
|
const matches = selected.map((el, idx) => ({
|
|
3567
|
-
ref: ensureRef(el),
|
|
2700
|
+
ref: h.ensureRef(el, "n"),
|
|
3568
2701
|
role: args.role,
|
|
3569
|
-
name: accname(el),
|
|
2702
|
+
name: h.accname(el),
|
|
3570
2703
|
index: args.nth !== void 0 ? args.nth : idx,
|
|
3571
2704
|
ancestors: namedAncestors(el)
|
|
3572
2705
|
}));
|
|
3573
|
-
|
|
2706
|
+
h.persistRefCounter();
|
|
3574
2707
|
const query = { role: args.role };
|
|
3575
2708
|
if (args.name !== void 0) query.name = args.name;
|
|
3576
2709
|
if (args.near !== void 0) query.near = args.near;
|
|
@@ -3653,9 +2786,13 @@ function inspectElementRaw(el) {
|
|
|
3653
2786
|
"colgroup",
|
|
3654
2787
|
"col"
|
|
3655
2788
|
]);
|
|
3656
|
-
const
|
|
2789
|
+
const structural = STRUCTURAL_TAGS.has(tag);
|
|
2790
|
+
const rawText = structural ? "" : (e.textContent ?? "").replace(/\s+/g, " ").trim().slice(0, 200);
|
|
3657
2791
|
const text = rawText || void 0;
|
|
3658
2792
|
if (!name && text) name = text;
|
|
2793
|
+
const CONTAINER_TEXT_CAP = 120;
|
|
2794
|
+
const containerFull = structural && !name ? (e.textContent ?? "").replace(/\s+/g, " ").trim() : "";
|
|
2795
|
+
const containerText = containerFull && containerFull.length <= CONTAINER_TEXT_CAP ? containerFull : void 0;
|
|
3659
2796
|
const href = tag === "a" ? e.getAttribute("href") ?? void 0 : void 0;
|
|
3660
2797
|
const placeholder = e.placeholder?.trim() || void 0;
|
|
3661
2798
|
const alt = e.getAttribute("alt")?.trim() || void 0;
|
|
@@ -3735,6 +2872,7 @@ function inspectElementRaw(el) {
|
|
|
3735
2872
|
alt,
|
|
3736
2873
|
titleAttr,
|
|
3737
2874
|
text,
|
|
2875
|
+
containerText,
|
|
3738
2876
|
href,
|
|
3739
2877
|
elementId,
|
|
3740
2878
|
nameAttr,
|
|
@@ -3763,6 +2901,9 @@ function inspectElementRaw(el) {
|
|
|
3763
2901
|
if (tag2 === "footer") return "contentinfo";
|
|
3764
2902
|
if (tag2 === "aside") return "complementary";
|
|
3765
2903
|
if (tag2 === "form") return "form";
|
|
2904
|
+
if (tag2 === "li") return "listitem";
|
|
2905
|
+
if (tag2 === "tr") return "row";
|
|
2906
|
+
if (tag2 === "article") return "article";
|
|
3766
2907
|
return void 0;
|
|
3767
2908
|
}
|
|
3768
2909
|
__name(inferRole, "inferRole");
|