@wdio/mcp 2.2.1 → 2.3.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 +15 -1
- package/lib/server.js +497 -265
- package/lib/server.js.map +1 -1
- package/lib/snapshot.d.ts +40 -56
- package/lib/snapshot.js +355 -161
- package/lib/snapshot.js.map +1 -1
- package/package.json +8 -4
package/lib/snapshot.js
CHANGED
|
@@ -1,214 +1,408 @@
|
|
|
1
|
-
// src/scripts/get-browser-accessibility-tree.ts
|
|
2
|
-
function flattenAccessibilityTree(node, result = []) {
|
|
3
|
-
if (!node) return result;
|
|
4
|
-
if (node.role !== "WebArea" || node.name) {
|
|
5
|
-
const entry = {
|
|
6
|
-
role: node.role || "",
|
|
7
|
-
name: node.name || "",
|
|
8
|
-
value: node.value ?? "",
|
|
9
|
-
description: node.description || "",
|
|
10
|
-
disabled: node.disabled ? "true" : "",
|
|
11
|
-
focused: node.focused ? "true" : "",
|
|
12
|
-
selected: node.selected ? "true" : "",
|
|
13
|
-
checked: node.checked === true ? "true" : node.checked === false ? "false" : node.checked === "mixed" ? "mixed" : "",
|
|
14
|
-
expanded: node.expanded === true ? "true" : node.expanded === false ? "false" : "",
|
|
15
|
-
pressed: node.pressed === true ? "true" : node.pressed === false ? "false" : node.pressed === "mixed" ? "mixed" : "",
|
|
16
|
-
readonly: node.readonly ? "true" : "",
|
|
17
|
-
required: node.required ? "true" : "",
|
|
18
|
-
level: node.level ?? "",
|
|
19
|
-
valuemin: node.valuemin ?? "",
|
|
20
|
-
valuemax: node.valuemax ?? "",
|
|
21
|
-
autocomplete: node.autocomplete || "",
|
|
22
|
-
haspopup: node.haspopup || "",
|
|
23
|
-
invalid: node.invalid ? "true" : "",
|
|
24
|
-
modal: node.modal ? "true" : "",
|
|
25
|
-
multiline: node.multiline ? "true" : "",
|
|
26
|
-
multiselectable: node.multiselectable ? "true" : "",
|
|
27
|
-
orientation: node.orientation || "",
|
|
28
|
-
keyshortcuts: node.keyshortcuts || "",
|
|
29
|
-
roledescription: node.roledescription || "",
|
|
30
|
-
valuetext: node.valuetext || ""
|
|
31
|
-
};
|
|
32
|
-
result.push(entry);
|
|
33
|
-
}
|
|
34
|
-
if (node.children && Array.isArray(node.children)) {
|
|
35
|
-
for (const child of node.children) {
|
|
36
|
-
flattenAccessibilityTree(child, result);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return result;
|
|
40
|
-
}
|
|
41
|
-
async function getBrowserAccessibilityTree(browser) {
|
|
42
|
-
const puppeteer = await browser.getPuppeteer();
|
|
43
|
-
const pages = await puppeteer.pages();
|
|
44
|
-
if (pages.length === 0) {
|
|
45
|
-
return [];
|
|
46
|
-
}
|
|
47
|
-
const page = pages[0];
|
|
48
|
-
const snapshot = await page.accessibility.snapshot({
|
|
49
|
-
interestingOnly: true
|
|
50
|
-
});
|
|
51
|
-
if (!snapshot) {
|
|
52
|
-
return [];
|
|
53
|
-
}
|
|
54
|
-
return flattenAccessibilityTree(snapshot);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
1
|
// src/scripts/get-interactable-browser-elements.ts
|
|
58
|
-
var elementsScript = (
|
|
2
|
+
var elementsScript = (includeBounds) => (function() {
|
|
59
3
|
const interactableSelectors = [
|
|
60
4
|
"a[href]",
|
|
61
|
-
// Links with href
|
|
62
5
|
"button",
|
|
63
|
-
// Buttons
|
|
64
6
|
'input:not([type="hidden"])',
|
|
65
|
-
// Input fields (except hidden)
|
|
66
7
|
"select",
|
|
67
|
-
// Select dropdowns
|
|
68
8
|
"textarea",
|
|
69
|
-
// Text areas
|
|
70
9
|
'[role="button"]',
|
|
71
|
-
// Elements with button role
|
|
72
10
|
'[role="link"]',
|
|
73
|
-
// Elements with link role
|
|
74
11
|
'[role="checkbox"]',
|
|
75
|
-
// Elements with checkbox role
|
|
76
12
|
'[role="radio"]',
|
|
77
|
-
// Elements with radio role
|
|
78
13
|
'[role="tab"]',
|
|
79
|
-
// Elements with tab role
|
|
80
14
|
'[role="menuitem"]',
|
|
81
|
-
// Elements with menuitem role
|
|
82
15
|
'[role="combobox"]',
|
|
83
|
-
// Elements with combobox role
|
|
84
16
|
'[role="option"]',
|
|
85
|
-
// Elements with option role
|
|
86
17
|
'[role="switch"]',
|
|
87
|
-
// Elements with switch role
|
|
88
18
|
'[role="slider"]',
|
|
89
|
-
// Elements with slider role
|
|
90
19
|
'[role="textbox"]',
|
|
91
|
-
// Elements with textbox role
|
|
92
20
|
'[role="searchbox"]',
|
|
93
|
-
|
|
21
|
+
'[role="spinbutton"]',
|
|
94
22
|
'[contenteditable="true"]',
|
|
95
|
-
// Editable content
|
|
96
23
|
'[tabindex]:not([tabindex="-1"])'
|
|
97
|
-
|
|
98
|
-
];
|
|
99
|
-
const visualSelectors = [
|
|
100
|
-
"img",
|
|
101
|
-
// Images
|
|
102
|
-
"picture",
|
|
103
|
-
// Picture elements
|
|
104
|
-
"svg",
|
|
105
|
-
// SVG graphics
|
|
106
|
-
"video",
|
|
107
|
-
// Video elements
|
|
108
|
-
"canvas",
|
|
109
|
-
// Canvas elements
|
|
110
|
-
'[style*="background-image"]'
|
|
111
|
-
// Elements with background images
|
|
112
|
-
];
|
|
24
|
+
].join(",");
|
|
113
25
|
function isVisible(element) {
|
|
114
26
|
if (typeof element.checkVisibility === "function") {
|
|
115
|
-
return element.checkVisibility({
|
|
116
|
-
opacityProperty: true,
|
|
117
|
-
visibilityProperty: true,
|
|
118
|
-
contentVisibilityAuto: true
|
|
119
|
-
});
|
|
27
|
+
return element.checkVisibility({ opacityProperty: true, visibilityProperty: true, contentVisibilityAuto: true });
|
|
120
28
|
}
|
|
121
29
|
const style = window.getComputedStyle(element);
|
|
122
30
|
return style.display !== "none" && style.visibility !== "hidden" && style.opacity !== "0" && element.offsetWidth > 0 && element.offsetHeight > 0;
|
|
123
31
|
}
|
|
124
|
-
function
|
|
125
|
-
|
|
126
|
-
|
|
32
|
+
function getAccessibleName(el) {
|
|
33
|
+
const ariaLabel = el.getAttribute("aria-label");
|
|
34
|
+
if (ariaLabel) return ariaLabel.trim();
|
|
35
|
+
const labelledBy = el.getAttribute("aria-labelledby");
|
|
36
|
+
if (labelledBy) {
|
|
37
|
+
const texts = labelledBy.split(/\s+/).map((id) => document.getElementById(id)?.textContent?.trim() || "").filter(Boolean);
|
|
38
|
+
if (texts.length > 0) return texts.join(" ").slice(0, 100);
|
|
39
|
+
}
|
|
40
|
+
const tag = el.tagName.toLowerCase();
|
|
41
|
+
if (tag === "img" || tag === "input" && el.getAttribute("type") === "image") {
|
|
42
|
+
const alt = el.getAttribute("alt");
|
|
43
|
+
if (alt !== null) return alt.trim();
|
|
44
|
+
}
|
|
45
|
+
if (["input", "select", "textarea"].includes(tag)) {
|
|
46
|
+
const id = el.getAttribute("id");
|
|
47
|
+
if (id) {
|
|
48
|
+
const label = document.querySelector(`label[for="${CSS.escape(id)}"]`);
|
|
49
|
+
if (label) return label.textContent?.trim() || "";
|
|
50
|
+
}
|
|
51
|
+
const parentLabel = el.closest("label");
|
|
52
|
+
if (parentLabel) {
|
|
53
|
+
const clone = parentLabel.cloneNode(true);
|
|
54
|
+
clone.querySelectorAll("input,select,textarea").forEach((n) => n.remove());
|
|
55
|
+
const lt = clone.textContent?.trim();
|
|
56
|
+
if (lt) return lt;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const ph = el.getAttribute("placeholder");
|
|
60
|
+
if (ph) return ph.trim();
|
|
61
|
+
const title = el.getAttribute("title");
|
|
62
|
+
if (title) return title.trim();
|
|
63
|
+
return (el.textContent?.trim().replace(/\s+/g, " ") || "").slice(0, 100);
|
|
64
|
+
}
|
|
65
|
+
function getSelector(element) {
|
|
66
|
+
const tag = element.tagName.toLowerCase();
|
|
67
|
+
const text = element.textContent?.trim().replace(/\s+/g, " ");
|
|
68
|
+
if (text && text.length > 0 && text.length <= 50) {
|
|
69
|
+
const sameTagElements = document.querySelectorAll(tag);
|
|
70
|
+
let matchCount = 0;
|
|
71
|
+
sameTagElements.forEach((el) => {
|
|
72
|
+
if (el.textContent?.includes(text)) matchCount++;
|
|
73
|
+
});
|
|
74
|
+
if (matchCount === 1) return `${tag}*=${text}`;
|
|
75
|
+
}
|
|
76
|
+
const ariaLabel = element.getAttribute("aria-label");
|
|
77
|
+
if (ariaLabel && ariaLabel.length <= 80) return `aria/${ariaLabel}`;
|
|
78
|
+
const testId = element.getAttribute("data-testid");
|
|
79
|
+
if (testId) {
|
|
80
|
+
const sel = `[data-testid="${CSS.escape(testId)}"]`;
|
|
81
|
+
if (document.querySelectorAll(sel).length === 1) return sel;
|
|
82
|
+
}
|
|
83
|
+
if (element.id) return `#${CSS.escape(element.id)}`;
|
|
84
|
+
const nameAttr = element.getAttribute("name");
|
|
85
|
+
if (nameAttr) {
|
|
86
|
+
const sel = `${tag}[name="${CSS.escape(nameAttr)}"]`;
|
|
87
|
+
if (document.querySelectorAll(sel).length === 1) return sel;
|
|
127
88
|
}
|
|
128
89
|
if (element.className && typeof element.className === "string") {
|
|
129
90
|
const classes = element.className.trim().split(/\s+/).filter(Boolean);
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
91
|
+
for (const cls of classes) {
|
|
92
|
+
const sel = `${tag}.${CSS.escape(cls)}`;
|
|
93
|
+
if (document.querySelectorAll(sel).length === 1) return sel;
|
|
94
|
+
}
|
|
95
|
+
if (classes.length >= 2) {
|
|
96
|
+
const sel = `${tag}${classes.slice(0, 2).map((c) => `.${CSS.escape(c)}`).join("")}`;
|
|
97
|
+
if (document.querySelectorAll(sel).length === 1) return sel;
|
|
136
98
|
}
|
|
137
99
|
}
|
|
138
100
|
let current = element;
|
|
139
101
|
const path = [];
|
|
140
102
|
while (current && current !== document.documentElement) {
|
|
141
|
-
let
|
|
103
|
+
let seg = current.tagName.toLowerCase();
|
|
142
104
|
if (current.id) {
|
|
143
|
-
|
|
144
|
-
path.unshift(selector);
|
|
105
|
+
path.unshift(`#${CSS.escape(current.id)}`);
|
|
145
106
|
break;
|
|
146
107
|
}
|
|
147
108
|
const parent = current.parentElement;
|
|
148
109
|
if (parent) {
|
|
149
|
-
const siblings = Array.from(parent.children).filter(
|
|
150
|
-
|
|
151
|
-
);
|
|
152
|
-
if (siblings.length > 1) {
|
|
153
|
-
const index = siblings.indexOf(current) + 1;
|
|
154
|
-
selector += `:nth-child(${index})`;
|
|
155
|
-
}
|
|
110
|
+
const siblings = Array.from(parent.children).filter((c) => c.tagName === current.tagName);
|
|
111
|
+
if (siblings.length > 1) seg += `:nth-of-type(${siblings.indexOf(current) + 1})`;
|
|
156
112
|
}
|
|
157
|
-
path.unshift(
|
|
113
|
+
path.unshift(seg);
|
|
158
114
|
current = current.parentElement;
|
|
159
|
-
if (path.length >= 4)
|
|
115
|
+
if (path.length >= 4) break;
|
|
116
|
+
}
|
|
117
|
+
return path.join(" > ");
|
|
118
|
+
}
|
|
119
|
+
const elements = [];
|
|
120
|
+
const seen = /* @__PURE__ */ new Set();
|
|
121
|
+
document.querySelectorAll(interactableSelectors).forEach((el) => {
|
|
122
|
+
if (seen.has(el)) return;
|
|
123
|
+
seen.add(el);
|
|
124
|
+
const htmlEl = el;
|
|
125
|
+
if (!isVisible(htmlEl)) return;
|
|
126
|
+
const inputEl = htmlEl;
|
|
127
|
+
const rect = htmlEl.getBoundingClientRect();
|
|
128
|
+
const isInViewport = rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
|
|
129
|
+
const entry = {
|
|
130
|
+
tagName: htmlEl.tagName.toLowerCase(),
|
|
131
|
+
name: getAccessibleName(htmlEl),
|
|
132
|
+
type: htmlEl.getAttribute("type") || "",
|
|
133
|
+
value: inputEl.value || "",
|
|
134
|
+
href: htmlEl.getAttribute("href") || "",
|
|
135
|
+
selector: getSelector(htmlEl),
|
|
136
|
+
isInViewport
|
|
137
|
+
};
|
|
138
|
+
if (includeBounds) {
|
|
139
|
+
entry.boundingBox = {
|
|
140
|
+
x: rect.x + window.scrollX,
|
|
141
|
+
y: rect.y + window.scrollY,
|
|
142
|
+
width: rect.width,
|
|
143
|
+
height: rect.height
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
elements.push(entry);
|
|
147
|
+
});
|
|
148
|
+
return elements;
|
|
149
|
+
})();
|
|
150
|
+
async function getInteractableBrowserElements(browser, options = {}) {
|
|
151
|
+
const { includeBounds = false } = options;
|
|
152
|
+
return browser.execute(elementsScript, includeBounds);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/scripts/get-browser-accessibility-tree.ts
|
|
156
|
+
var accessibilityTreeScript = () => (function() {
|
|
157
|
+
const INPUT_TYPE_ROLES = {
|
|
158
|
+
text: "textbox",
|
|
159
|
+
search: "searchbox",
|
|
160
|
+
email: "textbox",
|
|
161
|
+
url: "textbox",
|
|
162
|
+
tel: "textbox",
|
|
163
|
+
password: "textbox",
|
|
164
|
+
number: "spinbutton",
|
|
165
|
+
checkbox: "checkbox",
|
|
166
|
+
radio: "radio",
|
|
167
|
+
range: "slider",
|
|
168
|
+
submit: "button",
|
|
169
|
+
reset: "button",
|
|
170
|
+
image: "button",
|
|
171
|
+
file: "button",
|
|
172
|
+
color: "button"
|
|
173
|
+
};
|
|
174
|
+
const LANDMARK_ROLES = /* @__PURE__ */ new Set([
|
|
175
|
+
"navigation",
|
|
176
|
+
"main",
|
|
177
|
+
"banner",
|
|
178
|
+
"contentinfo",
|
|
179
|
+
"complementary",
|
|
180
|
+
"form",
|
|
181
|
+
"dialog",
|
|
182
|
+
"region"
|
|
183
|
+
]);
|
|
184
|
+
const CONTAINER_ROLES = /* @__PURE__ */ new Set([
|
|
185
|
+
"navigation",
|
|
186
|
+
"banner",
|
|
187
|
+
"contentinfo",
|
|
188
|
+
"complementary",
|
|
189
|
+
"main",
|
|
190
|
+
"form",
|
|
191
|
+
"region",
|
|
192
|
+
"group",
|
|
193
|
+
"list",
|
|
194
|
+
"listitem",
|
|
195
|
+
"table",
|
|
196
|
+
"row",
|
|
197
|
+
"rowgroup",
|
|
198
|
+
"generic"
|
|
199
|
+
]);
|
|
200
|
+
function getRole(el) {
|
|
201
|
+
const explicit = el.getAttribute("role");
|
|
202
|
+
if (explicit) return explicit.split(" ")[0];
|
|
203
|
+
const tag = el.tagName.toLowerCase();
|
|
204
|
+
switch (tag) {
|
|
205
|
+
case "button":
|
|
206
|
+
return "button";
|
|
207
|
+
case "a":
|
|
208
|
+
return el.hasAttribute("href") ? "link" : null;
|
|
209
|
+
case "input": {
|
|
210
|
+
const type = (el.getAttribute("type") || "text").toLowerCase();
|
|
211
|
+
if (type === "hidden") return null;
|
|
212
|
+
return INPUT_TYPE_ROLES[type] || "textbox";
|
|
213
|
+
}
|
|
214
|
+
case "select":
|
|
215
|
+
return "combobox";
|
|
216
|
+
case "textarea":
|
|
217
|
+
return "textbox";
|
|
218
|
+
case "h1":
|
|
219
|
+
case "h2":
|
|
220
|
+
case "h3":
|
|
221
|
+
case "h4":
|
|
222
|
+
case "h5":
|
|
223
|
+
case "h6":
|
|
224
|
+
return "heading";
|
|
225
|
+
case "img":
|
|
226
|
+
return "img";
|
|
227
|
+
case "nav":
|
|
228
|
+
return "navigation";
|
|
229
|
+
case "main":
|
|
230
|
+
return "main";
|
|
231
|
+
case "header":
|
|
232
|
+
return !el.closest("article,aside,main,nav,section") ? "banner" : null;
|
|
233
|
+
case "footer":
|
|
234
|
+
return !el.closest("article,aside,main,nav,section") ? "contentinfo" : null;
|
|
235
|
+
case "aside":
|
|
236
|
+
return "complementary";
|
|
237
|
+
case "dialog":
|
|
238
|
+
return "dialog";
|
|
239
|
+
case "form":
|
|
240
|
+
return "form";
|
|
241
|
+
case "section":
|
|
242
|
+
return el.hasAttribute("aria-label") || el.hasAttribute("aria-labelledby") ? "region" : null;
|
|
243
|
+
case "summary":
|
|
244
|
+
return "button";
|
|
245
|
+
case "details":
|
|
246
|
+
return "group";
|
|
247
|
+
case "progress":
|
|
248
|
+
return "progressbar";
|
|
249
|
+
case "meter":
|
|
250
|
+
return "meter";
|
|
251
|
+
case "ul":
|
|
252
|
+
case "ol":
|
|
253
|
+
return "list";
|
|
254
|
+
case "li":
|
|
255
|
+
return "listitem";
|
|
256
|
+
case "table":
|
|
257
|
+
return "table";
|
|
258
|
+
}
|
|
259
|
+
if (el.contentEditable === "true") return "textbox";
|
|
260
|
+
if (el.hasAttribute("tabindex") && parseInt(el.getAttribute("tabindex") || "-1", 10) >= 0) return "generic";
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
function getAccessibleName(el, role) {
|
|
264
|
+
const ariaLabel = el.getAttribute("aria-label");
|
|
265
|
+
if (ariaLabel) return ariaLabel.trim();
|
|
266
|
+
const labelledBy = el.getAttribute("aria-labelledby");
|
|
267
|
+
if (labelledBy) {
|
|
268
|
+
const texts = labelledBy.split(/\s+/).map((id) => document.getElementById(id)?.textContent?.trim() || "").filter(Boolean);
|
|
269
|
+
if (texts.length > 0) return texts.join(" ").slice(0, 100);
|
|
270
|
+
}
|
|
271
|
+
const tag = el.tagName.toLowerCase();
|
|
272
|
+
if (tag === "img" || tag === "input" && el.getAttribute("type") === "image") {
|
|
273
|
+
const alt = el.getAttribute("alt");
|
|
274
|
+
if (alt !== null) return alt.trim();
|
|
275
|
+
}
|
|
276
|
+
if (["input", "select", "textarea"].includes(tag)) {
|
|
277
|
+
const id = el.getAttribute("id");
|
|
278
|
+
if (id) {
|
|
279
|
+
const label = document.querySelector(`label[for="${CSS.escape(id)}"]`);
|
|
280
|
+
if (label) return label.textContent?.trim() || "";
|
|
281
|
+
}
|
|
282
|
+
const parentLabel = el.closest("label");
|
|
283
|
+
if (parentLabel) {
|
|
284
|
+
const clone = parentLabel.cloneNode(true);
|
|
285
|
+
clone.querySelectorAll("input,select,textarea").forEach((n) => n.remove());
|
|
286
|
+
const lt = clone.textContent?.trim();
|
|
287
|
+
if (lt) return lt;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const ph = el.getAttribute("placeholder");
|
|
291
|
+
if (ph) return ph.trim();
|
|
292
|
+
const title = el.getAttribute("title");
|
|
293
|
+
if (title) return title.trim();
|
|
294
|
+
if (role && CONTAINER_ROLES.has(role)) return "";
|
|
295
|
+
return (el.textContent?.trim().replace(/\s+/g, " ") || "").slice(0, 100);
|
|
296
|
+
}
|
|
297
|
+
function getSelector(element) {
|
|
298
|
+
const tag = element.tagName.toLowerCase();
|
|
299
|
+
const text = element.textContent?.trim().replace(/\s+/g, " ");
|
|
300
|
+
if (text && text.length > 0 && text.length <= 50) {
|
|
301
|
+
const sameTagElements = document.querySelectorAll(tag);
|
|
302
|
+
let matchCount = 0;
|
|
303
|
+
sameTagElements.forEach((el) => {
|
|
304
|
+
if (el.textContent?.includes(text)) matchCount++;
|
|
305
|
+
});
|
|
306
|
+
if (matchCount === 1) return `${tag}*=${text}`;
|
|
307
|
+
}
|
|
308
|
+
const ariaLabel = element.getAttribute("aria-label");
|
|
309
|
+
if (ariaLabel && ariaLabel.length <= 80) return `aria/${ariaLabel}`;
|
|
310
|
+
const testId = element.getAttribute("data-testid");
|
|
311
|
+
if (testId) {
|
|
312
|
+
const sel = `[data-testid="${CSS.escape(testId)}"]`;
|
|
313
|
+
if (document.querySelectorAll(sel).length === 1) return sel;
|
|
314
|
+
}
|
|
315
|
+
if (element.id) return `#${CSS.escape(element.id)}`;
|
|
316
|
+
const nameAttr = element.getAttribute("name");
|
|
317
|
+
if (nameAttr) {
|
|
318
|
+
const sel = `${tag}[name="${CSS.escape(nameAttr)}"]`;
|
|
319
|
+
if (document.querySelectorAll(sel).length === 1) return sel;
|
|
320
|
+
}
|
|
321
|
+
if (element.className && typeof element.className === "string") {
|
|
322
|
+
const classes = element.className.trim().split(/\s+/).filter(Boolean);
|
|
323
|
+
for (const cls of classes) {
|
|
324
|
+
const sel = `${tag}.${CSS.escape(cls)}`;
|
|
325
|
+
if (document.querySelectorAll(sel).length === 1) return sel;
|
|
326
|
+
}
|
|
327
|
+
if (classes.length >= 2) {
|
|
328
|
+
const sel = `${tag}${classes.slice(0, 2).map((c) => `.${CSS.escape(c)}`).join("")}`;
|
|
329
|
+
if (document.querySelectorAll(sel).length === 1) return sel;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
let current = element;
|
|
333
|
+
const path = [];
|
|
334
|
+
while (current && current !== document.documentElement) {
|
|
335
|
+
let seg = current.tagName.toLowerCase();
|
|
336
|
+
if (current.id) {
|
|
337
|
+
path.unshift(`#${CSS.escape(current.id)}`);
|
|
160
338
|
break;
|
|
161
339
|
}
|
|
340
|
+
const parent = current.parentElement;
|
|
341
|
+
if (parent) {
|
|
342
|
+
const siblings = Array.from(parent.children).filter((c) => c.tagName === current.tagName);
|
|
343
|
+
if (siblings.length > 1) seg += `:nth-of-type(${siblings.indexOf(current) + 1})`;
|
|
344
|
+
}
|
|
345
|
+
path.unshift(seg);
|
|
346
|
+
current = current.parentElement;
|
|
347
|
+
if (path.length >= 4) break;
|
|
162
348
|
}
|
|
163
349
|
return path.join(" > ");
|
|
164
350
|
}
|
|
165
|
-
function
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
selectors.push(...interactableSelectors);
|
|
351
|
+
function isVisible(el) {
|
|
352
|
+
if (typeof el.checkVisibility === "function") {
|
|
353
|
+
return el.checkVisibility({ opacityProperty: true, visibilityProperty: true, contentVisibilityAuto: true });
|
|
169
354
|
}
|
|
170
|
-
|
|
171
|
-
|
|
355
|
+
const style = window.getComputedStyle(el);
|
|
356
|
+
return style.display !== "none" && style.visibility !== "hidden" && style.opacity !== "0" && el.offsetWidth > 0 && el.offsetHeight > 0;
|
|
357
|
+
}
|
|
358
|
+
function getLevel(el) {
|
|
359
|
+
const m = el.tagName.toLowerCase().match(/^h([1-6])$/);
|
|
360
|
+
if (m) return parseInt(m[1], 10);
|
|
361
|
+
const ariaLevel = el.getAttribute("aria-level");
|
|
362
|
+
if (ariaLevel) return parseInt(ariaLevel, 10);
|
|
363
|
+
return void 0;
|
|
364
|
+
}
|
|
365
|
+
function getState(el) {
|
|
366
|
+
const inputEl = el;
|
|
367
|
+
const isCheckable = ["input", "menuitemcheckbox", "menuitemradio"].includes(el.tagName.toLowerCase()) || ["checkbox", "radio", "switch"].includes(el.getAttribute("role") || "");
|
|
368
|
+
return {
|
|
369
|
+
disabled: el.getAttribute("aria-disabled") === "true" || inputEl.disabled ? "true" : "",
|
|
370
|
+
checked: isCheckable && inputEl.checked ? "true" : el.getAttribute("aria-checked") || "",
|
|
371
|
+
expanded: el.getAttribute("aria-expanded") || "",
|
|
372
|
+
selected: el.getAttribute("aria-selected") || "",
|
|
373
|
+
pressed: el.getAttribute("aria-pressed") || "",
|
|
374
|
+
required: inputEl.required || el.getAttribute("aria-required") === "true" ? "true" : "",
|
|
375
|
+
readonly: inputEl.readOnly || el.getAttribute("aria-readonly") === "true" ? "true" : ""
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
const result = [];
|
|
379
|
+
function walk(el, depth = 0) {
|
|
380
|
+
if (depth > 200) return;
|
|
381
|
+
if (!isVisible(el)) return;
|
|
382
|
+
const role = getRole(el);
|
|
383
|
+
if (!role) {
|
|
384
|
+
for (const child of Array.from(el.children)) {
|
|
385
|
+
walk(child, depth + 1);
|
|
386
|
+
}
|
|
387
|
+
return;
|
|
172
388
|
}
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const rect = el.getBoundingClientRect();
|
|
186
|
-
const isInViewport = rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
|
|
187
|
-
const info = {
|
|
188
|
-
tagName: el.tagName.toLowerCase(),
|
|
189
|
-
type: el.getAttribute("type") || "",
|
|
190
|
-
id: el.id || "",
|
|
191
|
-
className: (typeof el.className === "string" ? el.className : "") || "",
|
|
192
|
-
textContent: el.textContent?.trim() || "",
|
|
193
|
-
value: inputEl.value || "",
|
|
194
|
-
placeholder: inputEl.placeholder || "",
|
|
195
|
-
href: el.getAttribute("href") || "",
|
|
196
|
-
ariaLabel: el.getAttribute("aria-label") || "",
|
|
197
|
-
role: el.getAttribute("role") || "",
|
|
198
|
-
src: el.getAttribute("src") || "",
|
|
199
|
-
alt: el.getAttribute("alt") || "",
|
|
200
|
-
cssSelector: getCssSelector(el),
|
|
201
|
-
isInViewport
|
|
202
|
-
};
|
|
203
|
-
return info;
|
|
204
|
-
});
|
|
205
|
-
return elementInfos;
|
|
389
|
+
const name = getAccessibleName(el, role);
|
|
390
|
+
const isLandmark = LANDMARK_ROLES.has(role);
|
|
391
|
+
const hasIdentity = !!(name || isLandmark);
|
|
392
|
+
const selector = hasIdentity ? getSelector(el) : "";
|
|
393
|
+
const node = { role, name, selector, level: getLevel(el) ?? "", ...getState(el) };
|
|
394
|
+
result.push(node);
|
|
395
|
+
for (const child of Array.from(el.children)) {
|
|
396
|
+
walk(child, depth + 1);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
for (const child of Array.from(document.body.children)) {
|
|
400
|
+
walk(child, 0);
|
|
206
401
|
}
|
|
207
|
-
return
|
|
402
|
+
return result;
|
|
208
403
|
})();
|
|
209
|
-
async function
|
|
210
|
-
|
|
211
|
-
return browser.execute(elementsScript, elementType);
|
|
404
|
+
async function getBrowserAccessibilityTree(browser) {
|
|
405
|
+
return browser.execute(accessibilityTreeScript);
|
|
212
406
|
}
|
|
213
407
|
|
|
214
408
|
// src/locators/constants.ts
|
|
@@ -1172,7 +1366,7 @@ async function getMobileVisibleElements(browser, platform, options = {}) {
|
|
|
1172
1366
|
}
|
|
1173
1367
|
export {
|
|
1174
1368
|
getBrowserAccessibilityTree,
|
|
1175
|
-
|
|
1369
|
+
getInteractableBrowserElements,
|
|
1176
1370
|
getMobileVisibleElements
|
|
1177
1371
|
};
|
|
1178
1372
|
//# sourceMappingURL=snapshot.js.map
|