aether-mcp-server 2.0.2 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridge/debugging.js +133 -0
- package/dist/bridge/inspection.js +302 -0
- package/dist/bridge/interaction.js +586 -0
- package/dist/bridge/navigation.js +146 -0
- package/dist/bridge/session.js +287 -0
- package/dist/cdp-bridge.js +598 -1981
- package/dist/cdp-client.js +232 -366
- package/dist/element-collector.js +198 -0
- package/dist/eval-scripts.js +1024 -0
- package/dist/index.js +16 -28
- package/dist/locator-engine.js +21 -259
- package/dist/logger.js +105 -0
- package/dist/mcp-server.js +59 -0
- package/dist/page-snapshot-cache.js +17 -2
- package/dist/types.js +267 -0
- package/package.json +1 -1
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SHARED_DOM_HELPERS = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Shared in-page DOM collection helpers.
|
|
6
|
+
*
|
|
7
|
+
* These helpers are injected (as source text) into every element-collection
|
|
8
|
+
* script so that the LocatorEngine, the Set-of-Marks overlay collector, and the
|
|
9
|
+
* compact snapshot all derive selectors, roles, names, and visibility the SAME
|
|
10
|
+
* way. Previously each call site had its own divergent copy, so `get_state`,
|
|
11
|
+
* `list_interactive_elements`, and the semantic click resolver could disagree
|
|
12
|
+
* about the same element.
|
|
13
|
+
*
|
|
14
|
+
* Role/name resolution follows the WAI-ARIA implicit-role mapping and the
|
|
15
|
+
* accessible-name computation closely enough that role/label targeting matches
|
|
16
|
+
* what the browser's own accessibility tree reports.
|
|
17
|
+
*/
|
|
18
|
+
exports.SHARED_DOM_HELPERS = `
|
|
19
|
+
function aetherNorm(value) {
|
|
20
|
+
return String(value == null ? '' : value).trim().replace(/\\s+/g, ' ');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function aetherVisible(el) {
|
|
24
|
+
if (!el || el.nodeType !== Node.ELEMENT_NODE) return false;
|
|
25
|
+
const rect = el.getBoundingClientRect();
|
|
26
|
+
if (rect.width <= 0 || rect.height <= 0) return false;
|
|
27
|
+
const style = window.getComputedStyle(el);
|
|
28
|
+
return style.display !== 'none' &&
|
|
29
|
+
style.visibility !== 'hidden' &&
|
|
30
|
+
style.opacity !== '0';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// WAI-ARIA implicit role mapping. Mirrors what the accessibility tree reports
|
|
34
|
+
// so role-based targeting (click_role, fill_label) lines up with the browser.
|
|
35
|
+
function aetherImplicitRole(el) {
|
|
36
|
+
const explicit = (el.getAttribute('role') || '').trim().toLowerCase();
|
|
37
|
+
if (explicit) return explicit.split(/\\s+/)[0];
|
|
38
|
+
const tag = el.tagName.toLowerCase();
|
|
39
|
+
const type = (el.getAttribute('type') || '').toLowerCase();
|
|
40
|
+
switch (tag) {
|
|
41
|
+
case 'a':
|
|
42
|
+
case 'area':
|
|
43
|
+
return el.hasAttribute('href') ? 'link' : 'generic';
|
|
44
|
+
case 'button':
|
|
45
|
+
return 'button';
|
|
46
|
+
case 'summary':
|
|
47
|
+
return 'button';
|
|
48
|
+
case 'select':
|
|
49
|
+
return (el.multiple || el.size > 1) ? 'listbox' : 'combobox';
|
|
50
|
+
case 'textarea':
|
|
51
|
+
return 'textbox';
|
|
52
|
+
case 'progress':
|
|
53
|
+
return 'progressbar';
|
|
54
|
+
case 'output':
|
|
55
|
+
return 'status';
|
|
56
|
+
case 'input':
|
|
57
|
+
switch (type) {
|
|
58
|
+
case 'button':
|
|
59
|
+
case 'submit':
|
|
60
|
+
case 'reset':
|
|
61
|
+
case 'image':
|
|
62
|
+
return 'button';
|
|
63
|
+
case 'checkbox':
|
|
64
|
+
return 'checkbox';
|
|
65
|
+
case 'radio':
|
|
66
|
+
return 'radio';
|
|
67
|
+
case 'range':
|
|
68
|
+
return 'slider';
|
|
69
|
+
case 'number':
|
|
70
|
+
return 'spinbutton';
|
|
71
|
+
case 'search':
|
|
72
|
+
return el.getAttribute('list') ? 'combobox' : 'searchbox';
|
|
73
|
+
case 'email':
|
|
74
|
+
case 'tel':
|
|
75
|
+
case 'text':
|
|
76
|
+
case 'url':
|
|
77
|
+
case '':
|
|
78
|
+
return el.getAttribute('list') ? 'combobox' : 'textbox';
|
|
79
|
+
default:
|
|
80
|
+
return 'textbox';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (el.isContentEditable) return 'textbox';
|
|
84
|
+
if (/^h[1-6]$/.test(tag)) return 'heading';
|
|
85
|
+
return tag;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function aetherTextById(doc, id) {
|
|
89
|
+
if (!id) return '';
|
|
90
|
+
try {
|
|
91
|
+
const el = doc.getElementById(id);
|
|
92
|
+
return el ? aetherNorm(el.innerText || el.textContent) : '';
|
|
93
|
+
} catch (e) {
|
|
94
|
+
return '';
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function aetherLabelFor(el) {
|
|
99
|
+
const doc = el.ownerDocument;
|
|
100
|
+
const labelledBy = aetherNorm(
|
|
101
|
+
(el.getAttribute('aria-labelledby') || '')
|
|
102
|
+
.split(/\\s+/)
|
|
103
|
+
.map(function (id) { return aetherTextById(doc, id); })
|
|
104
|
+
.join(' ')
|
|
105
|
+
);
|
|
106
|
+
if (labelledBy) return labelledBy;
|
|
107
|
+
if (el.id) {
|
|
108
|
+
try {
|
|
109
|
+
const direct = doc.querySelector('label[for="' + CSS.escape(el.id) + '"]');
|
|
110
|
+
if (direct) return aetherNorm(direct.innerText || direct.textContent);
|
|
111
|
+
} catch (e) {}
|
|
112
|
+
}
|
|
113
|
+
const wrapping = el.closest && el.closest('label');
|
|
114
|
+
return wrapping ? aetherNorm(wrapping.innerText || wrapping.textContent) : '';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Accessible-name computation (simplified): aria-label > aria-labelledby/label >
|
|
118
|
+
// placeholder > alt > title > control value > visible text.
|
|
119
|
+
function aetherAccessibleName(el) {
|
|
120
|
+
return aetherNorm(
|
|
121
|
+
el.getAttribute('aria-label') ||
|
|
122
|
+
aetherLabelFor(el) ||
|
|
123
|
+
el.getAttribute('placeholder') ||
|
|
124
|
+
el.getAttribute('alt') ||
|
|
125
|
+
el.getAttribute('title') ||
|
|
126
|
+
((el.tagName === 'INPUT' || el.tagName === 'BUTTON') ? el.getAttribute('value') : '') ||
|
|
127
|
+
el.innerText ||
|
|
128
|
+
el.textContent ||
|
|
129
|
+
el.getAttribute('name') ||
|
|
130
|
+
''
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function aetherIsUnique(root, selector) {
|
|
135
|
+
try {
|
|
136
|
+
return root.querySelectorAll(selector).length === 1;
|
|
137
|
+
} catch (e) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function aetherStructuralPath(el) {
|
|
143
|
+
const path = [];
|
|
144
|
+
let node = el;
|
|
145
|
+
const stop = el.ownerDocument ? el.ownerDocument.body : null;
|
|
146
|
+
while (node && node.nodeType === Node.ELEMENT_NODE && node !== stop) {
|
|
147
|
+
let part = node.nodeName.toLowerCase();
|
|
148
|
+
if (node.classList && node.classList.length) {
|
|
149
|
+
part += '.' + Array.from(node.classList).slice(0, 2).map(function (c) { return CSS.escape(c); }).join('.');
|
|
150
|
+
}
|
|
151
|
+
const parent = node.parentElement;
|
|
152
|
+
if (parent) {
|
|
153
|
+
const same = Array.from(parent.children).filter(function (child) { return child.nodeName === node.nodeName; });
|
|
154
|
+
if (same.length > 1) part += ':nth-of-type(' + (same.indexOf(node) + 1) + ')';
|
|
155
|
+
}
|
|
156
|
+
path.unshift(part);
|
|
157
|
+
node = parent;
|
|
158
|
+
}
|
|
159
|
+
return path.join(' > ');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Prefer stable, intent-revealing selectors (test ids, id, name, aria-label)
|
|
163
|
+
// and only fall back to a brittle structural path when nothing stable+unique
|
|
164
|
+
// is available.
|
|
165
|
+
function aetherStableSelector(el) {
|
|
166
|
+
if (!el || el.nodeType !== Node.ELEMENT_NODE) return '';
|
|
167
|
+
const root = el.getRootNode ? el.getRootNode() : el.ownerDocument;
|
|
168
|
+
const tag = el.tagName.toLowerCase();
|
|
169
|
+
|
|
170
|
+
const testAttrs = ['data-testid', 'data-test-id', 'data-test', 'data-cy', 'data-qa', 'data-automation-id'];
|
|
171
|
+
for (let i = 0; i < testAttrs.length; i++) {
|
|
172
|
+
const v = el.getAttribute(testAttrs[i]);
|
|
173
|
+
if (v) {
|
|
174
|
+
const sel = '[' + testAttrs[i] + '=' + JSON.stringify(v) + ']';
|
|
175
|
+
if (aetherIsUnique(root, sel)) return sel;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (el.id) {
|
|
180
|
+
const sel = '#' + CSS.escape(el.id);
|
|
181
|
+
if (aetherIsUnique(root, sel)) return sel;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const name = el.getAttribute('name');
|
|
185
|
+
if (name && (tag === 'input' || tag === 'select' || tag === 'textarea' || tag === 'button')) {
|
|
186
|
+
const sel = tag + '[name=' + JSON.stringify(name) + ']';
|
|
187
|
+
if (aetherIsUnique(root, sel)) return sel;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const aria = el.getAttribute('aria-label');
|
|
191
|
+
if (aria) {
|
|
192
|
+
const sel = tag + '[aria-label=' + JSON.stringify(aria) + ']';
|
|
193
|
+
if (aetherIsUnique(root, sel)) return sel;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return aetherStructuralPath(el);
|
|
197
|
+
}
|
|
198
|
+
`;
|