@nuanu-ai/agentbrowse 0.2.21 → 0.2.23
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 +52 -9
- package/dist/commands/act.d.ts.map +1 -1
- package/dist/commands/act.js +100 -73
- package/dist/commands/action-acceptance.d.ts +4 -1
- package/dist/commands/action-acceptance.d.ts.map +1 -1
- package/dist/commands/action-acceptance.js +127 -37
- package/dist/commands/action-executor-helpers.d.ts.map +1 -1
- package/dist/commands/action-executor-helpers.js +12 -4
- package/dist/commands/action-fallbacks.d.ts.map +1 -1
- package/dist/commands/action-result-resolution.d.ts.map +1 -1
- package/dist/commands/action-result-resolution.js +1 -1
- package/dist/commands/action-value-projection.d.ts.map +1 -1
- package/dist/commands/close.d.ts +12 -1
- package/dist/commands/close.d.ts.map +1 -1
- package/dist/commands/close.js +19 -21
- package/dist/commands/datepicker-action-executor.d.ts.map +1 -1
- package/dist/commands/datepicker-action-executor.js +1 -1
- package/dist/commands/descriptor-validation.d.ts.map +1 -1
- package/dist/commands/descriptor-validation.js +13 -172
- package/dist/commands/extract-scoped-dialog-text.d.ts.map +1 -1
- package/dist/commands/extract-scoped-dialog-text.js +1 -4
- package/dist/commands/extract-snapshot-sanitizer.d.ts.map +1 -1
- package/dist/commands/extract-stagehand-executor.d.ts.map +1 -1
- package/dist/commands/extract.d.ts.map +1 -1
- package/dist/commands/extract.js +23 -4
- package/dist/commands/launch.d.ts +22 -1
- package/dist/commands/launch.d.ts.map +1 -1
- package/dist/commands/launch.js +122 -59
- package/dist/commands/observe-accessibility.d.ts +22 -0
- package/dist/commands/observe-accessibility.d.ts.map +1 -0
- package/dist/commands/observe-accessibility.js +497 -0
- package/dist/commands/observe-display-label.d.ts +4 -0
- package/dist/commands/observe-display-label.d.ts.map +1 -0
- package/dist/commands/observe-display-label.js +26 -0
- package/dist/commands/observe-dom-label-contract.d.ts +2 -0
- package/dist/commands/observe-dom-label-contract.d.ts.map +1 -0
- package/dist/commands/observe-dom-label-contract.js +521 -0
- package/dist/commands/observe-fallback-semantics.d.ts +6 -0
- package/dist/commands/observe-fallback-semantics.d.ts.map +1 -0
- package/dist/commands/observe-fallback-semantics.js +86 -0
- package/dist/commands/observe-inventory.d.ts +23 -18
- package/dist/commands/observe-inventory.d.ts.map +1 -1
- package/dist/commands/observe-inventory.js +172 -719
- package/dist/commands/observe-label-policy.d.ts +8 -0
- package/dist/commands/observe-label-policy.d.ts.map +1 -0
- package/dist/commands/observe-label-policy.js +21 -0
- package/dist/commands/observe-page-state.d.ts +1 -1
- package/dist/commands/observe-page-state.d.ts.map +1 -1
- package/dist/commands/observe-persistence.d.ts.map +1 -1
- package/dist/commands/observe-persistence.js +10 -3
- package/dist/commands/observe-projection.d.ts +2 -1
- package/dist/commands/observe-projection.d.ts.map +1 -1
- package/dist/commands/observe-projection.js +5 -2
- package/dist/commands/observe-semantics.d.ts.map +1 -1
- package/dist/commands/observe-semantics.js +18 -1
- package/dist/commands/observe-signals.d.ts +48 -0
- package/dist/commands/observe-signals.d.ts.map +1 -0
- package/dist/commands/observe-signals.js +461 -0
- package/dist/commands/observe-stagehand.d.ts.map +1 -1
- package/dist/commands/observe-stagehand.js +5 -18
- package/dist/commands/observe-surfaces.d.ts.map +1 -1
- package/dist/commands/observe-surfaces.js +5 -4
- package/dist/commands/observe.d.ts +0 -6
- package/dist/commands/observe.d.ts.map +1 -1
- package/dist/commands/observe.js +30 -6
- package/dist/commands/observe.test-harness.d.ts +0 -6
- package/dist/commands/observe.test-harness.d.ts.map +1 -1
- package/dist/commands/screenshot.d.ts.map +1 -1
- package/dist/commands/select-action-executor.d.ts.map +1 -1
- package/dist/commands/select-action-executor.js +1 -1
- package/dist/commands/semantic-observe.d.ts.map +1 -1
- package/dist/commands/semantic-observe.js +1 -4
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/text-input-action-executor.d.ts.map +1 -1
- package/dist/commands/text-input-action-executor.js +1 -1
- package/dist/control-semantics.d.ts.map +1 -1
- package/dist/control-semantics.js +2 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -9
- package/dist/owned-process.d.ts +4 -0
- package/dist/owned-process.d.ts.map +1 -0
- package/dist/owned-process.js +57 -0
- package/dist/playwright-runtime.d.ts.map +1 -1
- package/dist/playwright-runtime.js +3 -4
- package/dist/runtime-state.d.ts +3 -0
- package/dist/runtime-state.d.ts.map +1 -1
- package/dist/runtime-state.js +3 -0
- package/dist/secrets/catalog-applicability.d.ts.map +1 -1
- package/dist/secrets/form-matcher.d.ts.map +1 -1
- package/dist/secrets/form-matcher.js +2 -1
- package/dist/secrets/mock-agentpay-backend.d.ts +1 -1
- package/dist/secrets/mock-agentpay-backend.d.ts.map +1 -1
- package/dist/secrets/mock-agentpay-backend.js +2 -4
- package/dist/secrets/mock-agentpay-cabinet.d.ts.map +1 -1
- package/dist/secrets/mock-agentpay-cabinet.js +6 -1
- package/dist/secrets/protected-field-values.d.ts.map +1 -1
- package/dist/secrets/protected-field-values.js +1 -1
- package/dist/secrets/protected-fill.d.ts.map +1 -1
- package/dist/secrets/protected-fill.js +16 -4
- package/dist/session.d.ts +13 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +52 -7
- package/dist/solver/captcha-detector.d.ts.map +1 -1
- package/dist/solver/types.d.ts +2 -0
- package/dist/solver/types.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import { resolveLocatorRoot } from './action-fallbacks.js';
|
|
2
|
+
import { buildObserveDisplayLabel } from './observe-display-label.js';
|
|
3
|
+
import { fallbackContextNodeLabelOf, fallbackHintTextOf, fallbackSurfaceLabelOf, fallbackTargetLabelOf, } from './observe-fallback-semantics.js';
|
|
4
|
+
const AX_ENRICHMENT_CONCURRENCY = 12;
|
|
5
|
+
const AX_SNAPSHOT_TIMEOUT_MS = 250;
|
|
6
|
+
const GENERIC_FALLBACK_LABELS = new Set([
|
|
7
|
+
'button',
|
|
8
|
+
'link',
|
|
9
|
+
'combobox',
|
|
10
|
+
'text input',
|
|
11
|
+
'email input',
|
|
12
|
+
'password input',
|
|
13
|
+
'phone input',
|
|
14
|
+
'search input',
|
|
15
|
+
'date input',
|
|
16
|
+
'text area',
|
|
17
|
+
'option',
|
|
18
|
+
'menu item',
|
|
19
|
+
'grid cell',
|
|
20
|
+
]);
|
|
21
|
+
const NON_CONTAINER_AX_ROLES = new Set([
|
|
22
|
+
'button',
|
|
23
|
+
'checkbox',
|
|
24
|
+
'combobox',
|
|
25
|
+
'gridcell',
|
|
26
|
+
'img',
|
|
27
|
+
'link',
|
|
28
|
+
'menuitem',
|
|
29
|
+
'option',
|
|
30
|
+
'radio',
|
|
31
|
+
'switch',
|
|
32
|
+
'tab',
|
|
33
|
+
'textbox',
|
|
34
|
+
]);
|
|
35
|
+
function emptyStats() {
|
|
36
|
+
return {
|
|
37
|
+
axAttempts: 0,
|
|
38
|
+
axHits: 0,
|
|
39
|
+
fallbackUses: 0,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function normalizeText(value) {
|
|
43
|
+
return (value ?? '').replace(/\s+/g, ' ').trim();
|
|
44
|
+
}
|
|
45
|
+
function isMeaningfulName(value) {
|
|
46
|
+
const normalized = normalizeText(value);
|
|
47
|
+
return normalized.length >= 2 && normalized !== '[object Object]';
|
|
48
|
+
}
|
|
49
|
+
function normalizeLabelKey(value) {
|
|
50
|
+
return normalizeText(value).toLowerCase();
|
|
51
|
+
}
|
|
52
|
+
export function parseObserveAriaSnapshot(snapshot) {
|
|
53
|
+
const rawSnapshot = typeof snapshot === 'string' ? snapshot : '';
|
|
54
|
+
if (!normalizeText(rawSnapshot)) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const lines = rawSnapshot
|
|
58
|
+
.split(/\r?\n/)
|
|
59
|
+
.map((line) => line.trim())
|
|
60
|
+
.filter(Boolean);
|
|
61
|
+
const headerLine = lines.find((line) => line.startsWith('- '));
|
|
62
|
+
if (!headerLine) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const header = headerLine
|
|
66
|
+
.replace(/^-+\s*/, '')
|
|
67
|
+
.replace(/:\s*$/, '')
|
|
68
|
+
.trim();
|
|
69
|
+
if (!header) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const roleMatch = header.match(/^([a-z][a-z0-9_-]*)\b/i);
|
|
73
|
+
const quotedParts = [...header.matchAll(/"([^"]+)"/g)].map((match) => normalizeText(match[1]));
|
|
74
|
+
const stateParts = [...header.matchAll(/\[([^\]]+)\]/g)].map((match) => normalizeText(match[1]).toLowerCase());
|
|
75
|
+
const states = {};
|
|
76
|
+
for (const state of stateParts) {
|
|
77
|
+
if (!state) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (state === 'checked')
|
|
81
|
+
states.checked = true;
|
|
82
|
+
else if (state === 'disabled')
|
|
83
|
+
states.disabled = true;
|
|
84
|
+
else if (state === 'expanded')
|
|
85
|
+
states.expanded = true;
|
|
86
|
+
else if (state === 'pressed')
|
|
87
|
+
states.pressed = true;
|
|
88
|
+
else if (state === 'selected')
|
|
89
|
+
states.selected = true;
|
|
90
|
+
else if (state.startsWith('level=')) {
|
|
91
|
+
const level = Number(state.slice('level='.length));
|
|
92
|
+
if (Number.isFinite(level)) {
|
|
93
|
+
states.level = level;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
role: roleMatch?.[1]?.toLowerCase(),
|
|
99
|
+
name: quotedParts.length > 0 ? quotedParts.join(' ') : undefined,
|
|
100
|
+
states: Object.keys(states).length > 0 ? states : undefined,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function shouldPreferAccessibilityName(target, accessibilityName) {
|
|
104
|
+
const currentLabel = normalizeText(target.label);
|
|
105
|
+
const currentKey = normalizeLabelKey(currentLabel);
|
|
106
|
+
const nextLabel = normalizeText(accessibilityName);
|
|
107
|
+
const visibleText = normalizeText(target.text);
|
|
108
|
+
if (!isMeaningfulName(nextLabel)) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
if (!isMeaningfulName(currentLabel)) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
if (GENERIC_FALLBACK_LABELS.has(currentKey)) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
if (currentLabel === nextLabel) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
if (isMeaningfulName(visibleText) &&
|
|
121
|
+
visibleText === nextLabel &&
|
|
122
|
+
nextLabel.startsWith(currentLabel) &&
|
|
123
|
+
nextLabel.length > currentLabel.length) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
return currentLabel.startsWith(nextLabel) && currentLabel.length - nextLabel.length >= 12;
|
|
127
|
+
}
|
|
128
|
+
function patchDisplayLabel(displayLabel, previousLabel, nextLabel) {
|
|
129
|
+
if (!displayLabel || !previousLabel) {
|
|
130
|
+
return displayLabel;
|
|
131
|
+
}
|
|
132
|
+
const normalizedPrevious = normalizeText(previousLabel);
|
|
133
|
+
if (!normalizedPrevious) {
|
|
134
|
+
return displayLabel;
|
|
135
|
+
}
|
|
136
|
+
return displayLabel.startsWith(previousLabel)
|
|
137
|
+
? nextLabel + displayLabel.slice(previousLabel.length)
|
|
138
|
+
: displayLabel;
|
|
139
|
+
}
|
|
140
|
+
function shouldUseCurrentValueDisplayLabel(target) {
|
|
141
|
+
return target.controlFamily === 'select' && target.kind !== 'select';
|
|
142
|
+
}
|
|
143
|
+
function chooseDisplayLabel(target, nextLabel) {
|
|
144
|
+
if (shouldUseCurrentValueDisplayLabel(target)) {
|
|
145
|
+
const currentValueDisplayLabel = buildObserveDisplayLabel(nextLabel ?? target.label, target.currentValue);
|
|
146
|
+
if (currentValueDisplayLabel) {
|
|
147
|
+
return currentValueDisplayLabel;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return nextLabel && nextLabel !== target.label
|
|
151
|
+
? patchDisplayLabel(target.displayLabel, target.label, nextLabel)
|
|
152
|
+
: target.displayLabel;
|
|
153
|
+
}
|
|
154
|
+
function framePathKey(framePath) {
|
|
155
|
+
return framePath?.join('>') ?? '__top__';
|
|
156
|
+
}
|
|
157
|
+
function accessibilityCacheKeyOf(target) {
|
|
158
|
+
return `${framePathKey(target.framePath)}::${target.selector?.trim() ?? ''}`;
|
|
159
|
+
}
|
|
160
|
+
function hasAccessibilitySemantics(semantics) {
|
|
161
|
+
return Boolean(semantics &&
|
|
162
|
+
(isMeaningfulName(semantics.name) ||
|
|
163
|
+
isMeaningfulName(semantics.role) ||
|
|
164
|
+
(semantics.states && Object.keys(semantics.states).length > 0)));
|
|
165
|
+
}
|
|
166
|
+
async function readAccessibilitySemantics(page, target, rootCache, snapshotCache, stats) {
|
|
167
|
+
const selector = target.selector?.trim();
|
|
168
|
+
if (!selector) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
const cacheKey = accessibilityCacheKeyOf(target);
|
|
172
|
+
const cachedSnapshot = snapshotCache.get(cacheKey);
|
|
173
|
+
if (cachedSnapshot) {
|
|
174
|
+
return cachedSnapshot;
|
|
175
|
+
}
|
|
176
|
+
stats.axAttempts += 1;
|
|
177
|
+
const pendingSnapshot = (async () => {
|
|
178
|
+
try {
|
|
179
|
+
const rootKey = framePathKey(target.framePath);
|
|
180
|
+
const root = rootCache.get(rootKey) ??
|
|
181
|
+
(() => {
|
|
182
|
+
const resolvedRoot = resolveLocatorRoot(page, target.framePath);
|
|
183
|
+
rootCache.set(rootKey, resolvedRoot);
|
|
184
|
+
return resolvedRoot;
|
|
185
|
+
})();
|
|
186
|
+
const locator = root.locator(selector).first();
|
|
187
|
+
const snapshot = await locator.ariaSnapshot({ timeout: AX_SNAPSHOT_TIMEOUT_MS });
|
|
188
|
+
const semantics = parseObserveAriaSnapshot(snapshot);
|
|
189
|
+
if (hasAccessibilitySemantics(semantics)) {
|
|
190
|
+
stats.axHits += 1;
|
|
191
|
+
}
|
|
192
|
+
return semantics;
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
})();
|
|
198
|
+
snapshotCache.set(cacheKey, pendingSnapshot);
|
|
199
|
+
return pendingSnapshot;
|
|
200
|
+
}
|
|
201
|
+
async function readDomFallbackSemantics(page, target, rootCache, fallbackCache) {
|
|
202
|
+
const selector = target.selector?.trim();
|
|
203
|
+
if (!selector) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
const cacheKey = accessibilityCacheKeyOf(target);
|
|
207
|
+
const cachedFallback = fallbackCache.get(cacheKey);
|
|
208
|
+
if (cachedFallback) {
|
|
209
|
+
return cachedFallback;
|
|
210
|
+
}
|
|
211
|
+
const pendingFallback = (async () => {
|
|
212
|
+
try {
|
|
213
|
+
const rootKey = framePathKey(target.framePath);
|
|
214
|
+
const root = rootCache.get(rootKey) ??
|
|
215
|
+
(() => {
|
|
216
|
+
const resolvedRoot = resolveLocatorRoot(page, target.framePath);
|
|
217
|
+
rootCache.set(rootKey, resolvedRoot);
|
|
218
|
+
return resolvedRoot;
|
|
219
|
+
})();
|
|
220
|
+
const locator = root.locator(selector).first();
|
|
221
|
+
return await locator.evaluate((element) => {
|
|
222
|
+
if (!(element instanceof HTMLElement)) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
const normalizeText = (value) => {
|
|
226
|
+
const normalized = (value ?? '').replace(/\s+/g, ' ').trim();
|
|
227
|
+
return normalized || undefined;
|
|
228
|
+
};
|
|
229
|
+
const relationTextOf = (attribute) => {
|
|
230
|
+
const relation = element.getAttribute(attribute)?.trim();
|
|
231
|
+
if (!relation) {
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
const text = relation
|
|
235
|
+
.split(/\s+/)
|
|
236
|
+
.map((id) => {
|
|
237
|
+
const related = element.ownerDocument.getElementById(id);
|
|
238
|
+
return related instanceof HTMLElement
|
|
239
|
+
? related.innerText || related.textContent || ''
|
|
240
|
+
: '';
|
|
241
|
+
})
|
|
242
|
+
.join(' ');
|
|
243
|
+
return normalizeText(text);
|
|
244
|
+
};
|
|
245
|
+
const heading = element.querySelector('h1, h2, h3, h4, h5, h6, [role="heading"]');
|
|
246
|
+
const headingText = heading instanceof HTMLElement && heading !== element && !heading.contains(element)
|
|
247
|
+
? normalizeText(heading.getAttribute('aria-label') ||
|
|
248
|
+
heading.getAttribute('title') ||
|
|
249
|
+
heading.innerText ||
|
|
250
|
+
heading.textContent)
|
|
251
|
+
: undefined;
|
|
252
|
+
return {
|
|
253
|
+
label: normalizeText(element.getAttribute('aria-label') ||
|
|
254
|
+
relationTextOf('aria-labelledby') ||
|
|
255
|
+
element.getAttribute('title') ||
|
|
256
|
+
headingText),
|
|
257
|
+
hintText: relationTextOf('aria-describedby'),
|
|
258
|
+
};
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
})();
|
|
265
|
+
fallbackCache.set(cacheKey, pendingFallback);
|
|
266
|
+
return pendingFallback;
|
|
267
|
+
}
|
|
268
|
+
function chooseSemanticName(fallbackValue, semantics, stats) {
|
|
269
|
+
const normalizedFallback = normalizeText(fallbackValue);
|
|
270
|
+
const normalizedSemanticName = normalizeText(semantics?.name);
|
|
271
|
+
if (isMeaningfulName(normalizedSemanticName) && normalizedSemanticName === normalizedFallback) {
|
|
272
|
+
return normalizedSemanticName;
|
|
273
|
+
}
|
|
274
|
+
if (isMeaningfulName(fallbackValue)) {
|
|
275
|
+
stats.fallbackUses += 1;
|
|
276
|
+
return fallbackValue;
|
|
277
|
+
}
|
|
278
|
+
if (isMeaningfulName(normalizedSemanticName)) {
|
|
279
|
+
return normalizedSemanticName;
|
|
280
|
+
}
|
|
281
|
+
return fallbackValue;
|
|
282
|
+
}
|
|
283
|
+
function normalizedContainerKind(value) {
|
|
284
|
+
return normalizeText(value).toLowerCase();
|
|
285
|
+
}
|
|
286
|
+
function shouldUseContainerAccessibilityName(expectedKind, semantics) {
|
|
287
|
+
if (!isMeaningfulName(semantics?.name)) {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
const role = normalizedContainerKind(semantics?.role);
|
|
291
|
+
if (!role) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
if (NON_CONTAINER_AX_ROLES.has(role)) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
const expected = normalizedContainerKind(expectedKind);
|
|
298
|
+
if (!expected) {
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
if (expected === role) {
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
if (expected === 'form') {
|
|
305
|
+
return role === 'form' || role === 'search';
|
|
306
|
+
}
|
|
307
|
+
if (expected === 'region' || expected === 'section' || expected === 'aside') {
|
|
308
|
+
return role === 'region' || role === 'complementary';
|
|
309
|
+
}
|
|
310
|
+
if (expected === 'fieldset' || expected === 'group') {
|
|
311
|
+
return role === 'group';
|
|
312
|
+
}
|
|
313
|
+
if (expected === 'dialog') {
|
|
314
|
+
return role === 'dialog' || role === 'alertdialog';
|
|
315
|
+
}
|
|
316
|
+
if (expected === 'datepicker') {
|
|
317
|
+
return role === 'dialog' || role === 'grid';
|
|
318
|
+
}
|
|
319
|
+
if (expected === 'grid') {
|
|
320
|
+
return role === 'grid' || role === 'treegrid';
|
|
321
|
+
}
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
function chooseContainerSemanticName(fallbackValue, expectedKind, semantics, stats) {
|
|
325
|
+
if (shouldUseContainerAccessibilityName(expectedKind, semantics)) {
|
|
326
|
+
return semantics?.name;
|
|
327
|
+
}
|
|
328
|
+
if (isMeaningfulName(fallbackValue)) {
|
|
329
|
+
stats.fallbackUses += 1;
|
|
330
|
+
}
|
|
331
|
+
return fallbackValue;
|
|
332
|
+
}
|
|
333
|
+
function enrichContextNode(node, semantics, domFallback, stats) {
|
|
334
|
+
if (!node) {
|
|
335
|
+
return undefined;
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
...node,
|
|
339
|
+
kind: node.kind ?? semantics?.role,
|
|
340
|
+
label: chooseContainerSemanticName(fallbackContextNodeLabelOf(node) ?? domFallback?.label, node.kind, semantics, stats),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function chooseHintText(context, nextContext, domFallbackHint, stats) {
|
|
344
|
+
const fallbackHint = fallbackHintTextOf(context) ?? normalizeText(domFallbackHint);
|
|
345
|
+
const nextHintCandidate = [
|
|
346
|
+
nextContext?.container?.label,
|
|
347
|
+
nextContext?.landmark?.label,
|
|
348
|
+
nextContext?.group?.label,
|
|
349
|
+
].find(isMeaningfulName);
|
|
350
|
+
if (isMeaningfulName(nextHintCandidate)) {
|
|
351
|
+
return nextHintCandidate;
|
|
352
|
+
}
|
|
353
|
+
if (fallbackHint) {
|
|
354
|
+
stats.fallbackUses += 1;
|
|
355
|
+
return fallbackHint;
|
|
356
|
+
}
|
|
357
|
+
return undefined;
|
|
358
|
+
}
|
|
359
|
+
function chooseStructuredGridZone(target, nextContext, surfaceSemantics, surfaceDomFallback, containerSemantics, containerDomFallback, landmarkSemantics, landmarkDomFallback, stats) {
|
|
360
|
+
if (target.structure?.family !== 'structured-grid' || target.structure.variant !== 'seat-cell') {
|
|
361
|
+
return target.structure?.zone;
|
|
362
|
+
}
|
|
363
|
+
const blockedZoneKeys = new Set([fallbackSurfaceLabelOf(target), target.surfaceLabel]
|
|
364
|
+
.map((value) => normalizeLabelKey(value))
|
|
365
|
+
.filter(Boolean));
|
|
366
|
+
const chooseCandidate = (candidates) => {
|
|
367
|
+
for (const candidate of candidates) {
|
|
368
|
+
const normalized = normalizeText(candidate);
|
|
369
|
+
if (!isMeaningfulName(normalized)) {
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (blockedZoneKeys.has(normalizeLabelKey(normalized))) {
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
return normalized;
|
|
376
|
+
}
|
|
377
|
+
return undefined;
|
|
378
|
+
};
|
|
379
|
+
const semanticZone = chooseCandidate([
|
|
380
|
+
surfaceSemantics?.name,
|
|
381
|
+
landmarkSemantics?.name,
|
|
382
|
+
containerSemantics?.name,
|
|
383
|
+
]);
|
|
384
|
+
if (semanticZone) {
|
|
385
|
+
return semanticZone;
|
|
386
|
+
}
|
|
387
|
+
const fallbackZone = chooseCandidate([
|
|
388
|
+
surfaceDomFallback?.label,
|
|
389
|
+
landmarkDomFallback?.label,
|
|
390
|
+
containerDomFallback?.label,
|
|
391
|
+
nextContext?.landmark?.label,
|
|
392
|
+
nextContext?.container?.label,
|
|
393
|
+
nextContext?.group?.label,
|
|
394
|
+
target.structure?.zone,
|
|
395
|
+
]);
|
|
396
|
+
if (fallbackZone) {
|
|
397
|
+
stats.fallbackUses += 1;
|
|
398
|
+
return fallbackZone;
|
|
399
|
+
}
|
|
400
|
+
return target.structure?.zone;
|
|
401
|
+
}
|
|
402
|
+
function enrichStructure(target, nextContext, surfaceSemantics, surfaceDomFallback, containerSemantics, containerDomFallback, landmarkSemantics, landmarkDomFallback, stats) {
|
|
403
|
+
if (!target.structure) {
|
|
404
|
+
return target.structure;
|
|
405
|
+
}
|
|
406
|
+
const nextZone = chooseStructuredGridZone(target, nextContext, surfaceSemantics, surfaceDomFallback, containerSemantics, containerDomFallback, landmarkSemantics, landmarkDomFallback, stats);
|
|
407
|
+
if (nextZone === target.structure.zone) {
|
|
408
|
+
return target.structure;
|
|
409
|
+
}
|
|
410
|
+
return {
|
|
411
|
+
...target.structure,
|
|
412
|
+
zone: nextZone,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
async function enrichTargetWithAccessibility(page, target, rootCache, snapshotCache, fallbackCache, stats) {
|
|
416
|
+
const semantics = await readAccessibilitySemantics(page, target, rootCache, snapshotCache, stats);
|
|
417
|
+
const surfaceSemantics = await readAccessibilitySemantics(page, { selector: target.surfaceSelector, framePath: target.framePath }, rootCache, snapshotCache, stats);
|
|
418
|
+
const surfaceDomFallback = await readDomFallbackSemantics(page, { selector: target.surfaceSelector, framePath: target.framePath }, rootCache, fallbackCache);
|
|
419
|
+
const itemSemantics = await readAccessibilitySemantics(page, { selector: target.context?.item?.selector, framePath: target.framePath }, rootCache, snapshotCache, stats);
|
|
420
|
+
const itemDomFallback = await readDomFallbackSemantics(page, { selector: target.context?.item?.selector, framePath: target.framePath }, rootCache, fallbackCache);
|
|
421
|
+
const groupSemantics = await readAccessibilitySemantics(page, { selector: target.context?.group?.selector, framePath: target.framePath }, rootCache, snapshotCache, stats);
|
|
422
|
+
const groupDomFallback = await readDomFallbackSemantics(page, { selector: target.context?.group?.selector, framePath: target.framePath }, rootCache, fallbackCache);
|
|
423
|
+
const containerSemantics = await readAccessibilitySemantics(page, { selector: target.context?.container?.selector, framePath: target.framePath }, rootCache, snapshotCache, stats);
|
|
424
|
+
const containerDomFallback = await readDomFallbackSemantics(page, { selector: target.context?.container?.selector, framePath: target.framePath }, rootCache, fallbackCache);
|
|
425
|
+
const landmarkSemantics = await readAccessibilitySemantics(page, { selector: target.context?.landmark?.selector, framePath: target.framePath }, rootCache, snapshotCache, stats);
|
|
426
|
+
const landmarkDomFallback = await readDomFallbackSemantics(page, { selector: target.context?.landmark?.selector, framePath: target.framePath }, rootCache, fallbackCache);
|
|
427
|
+
const targetDomFallback = await readDomFallbackSemantics(page, target, rootCache, fallbackCache);
|
|
428
|
+
const nextLabel = isMeaningfulName(semantics?.name) &&
|
|
429
|
+
shouldPreferAccessibilityName({ label: fallbackTargetLabelOf(target), text: target.text }, semantics.name)
|
|
430
|
+
? semantics.name
|
|
431
|
+
: chooseSemanticName(fallbackTargetLabelOf(target), semantics, stats);
|
|
432
|
+
const nextContext = target.context
|
|
433
|
+
? {
|
|
434
|
+
...target.context,
|
|
435
|
+
item: enrichContextNode(target.context.item, itemSemantics, itemDomFallback, stats),
|
|
436
|
+
group: enrichContextNode(target.context.group, groupSemantics, groupDomFallback, stats),
|
|
437
|
+
container: enrichContextNode(target.context.container, containerSemantics, containerDomFallback, stats),
|
|
438
|
+
landmark: enrichContextNode(target.context.landmark, landmarkSemantics, landmarkDomFallback, stats),
|
|
439
|
+
}
|
|
440
|
+
: undefined;
|
|
441
|
+
const nextStructure = enrichStructure(target, nextContext, surfaceSemantics, surfaceDomFallback, containerSemantics, containerDomFallback, landmarkSemantics, landmarkDomFallback, stats);
|
|
442
|
+
const usedAxSemantics = Boolean((isMeaningfulName(semantics?.name) && nextLabel === semantics?.name) ||
|
|
443
|
+
(semantics?.role && semantics.role === (target.role ?? semantics.role)) ||
|
|
444
|
+
(semantics?.states && Object.keys(semantics.states).length > 0) ||
|
|
445
|
+
isMeaningfulName(surfaceSemantics?.name) ||
|
|
446
|
+
isMeaningfulName(itemSemantics?.name) ||
|
|
447
|
+
isMeaningfulName(groupSemantics?.name) ||
|
|
448
|
+
isMeaningfulName(containerSemantics?.name) ||
|
|
449
|
+
isMeaningfulName(landmarkSemantics?.name));
|
|
450
|
+
return {
|
|
451
|
+
...target,
|
|
452
|
+
label: nextLabel,
|
|
453
|
+
displayLabel: chooseDisplayLabel(target, nextLabel),
|
|
454
|
+
role: target.role ?? semantics?.role,
|
|
455
|
+
semanticsSource: usedAxSemantics ? 'aria-snapshot' : (target.semanticsSource ?? 'dom'),
|
|
456
|
+
states: semantics?.states && Object.keys(semantics.states).length > 0
|
|
457
|
+
? { ...(target.states ?? {}), ...(semantics.states ?? {}) }
|
|
458
|
+
: target.states,
|
|
459
|
+
surfaceLabel: chooseContainerSemanticName(fallbackSurfaceLabelOf(target) ?? surfaceDomFallback?.label, target.surfaceKind, surfaceSemantics, stats),
|
|
460
|
+
structure: nextStructure,
|
|
461
|
+
context: nextContext
|
|
462
|
+
? {
|
|
463
|
+
...nextContext,
|
|
464
|
+
hintText: chooseHintText(target.context, nextContext, targetDomFallback?.hintText, stats),
|
|
465
|
+
}
|
|
466
|
+
: target.context,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
async function mapWithConcurrency(items, limit, worker) {
|
|
470
|
+
const results = new Array(items.length);
|
|
471
|
+
let cursor = 0;
|
|
472
|
+
const runWorker = async () => {
|
|
473
|
+
while (true) {
|
|
474
|
+
const index = cursor;
|
|
475
|
+
cursor += 1;
|
|
476
|
+
if (index >= items.length) {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
results[index] = await worker(items[index], index);
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
const concurrency = Math.max(1, Math.min(limit, items.length));
|
|
483
|
+
await Promise.all(Array.from({ length: concurrency }, () => runWorker()));
|
|
484
|
+
return results;
|
|
485
|
+
}
|
|
486
|
+
export async function enrichDomTargetsWithAccessibility(page, targets, options) {
|
|
487
|
+
const rootCache = new Map();
|
|
488
|
+
const snapshotCache = new Map();
|
|
489
|
+
const fallbackCache = new Map();
|
|
490
|
+
const stats = emptyStats();
|
|
491
|
+
const enrichedTargets = await mapWithConcurrency(targets, AX_ENRICHMENT_CONCURRENCY, (target) => enrichTargetWithAccessibility(page, target, rootCache, snapshotCache, fallbackCache, stats));
|
|
492
|
+
options?.onStats?.(stats);
|
|
493
|
+
return enrichedTargets;
|
|
494
|
+
}
|
|
495
|
+
export const __testObserveAccessibility = {
|
|
496
|
+
parseObserveAriaSnapshot,
|
|
497
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function normalizeObserveLiveValue(value: string | undefined): string | undefined;
|
|
2
|
+
export declare function observeLabelIncludesValue(label: string, value: string): boolean;
|
|
3
|
+
export declare function buildObserveDisplayLabel(baseLabel: string | undefined, liveValue: string | undefined): string | undefined;
|
|
4
|
+
//# sourceMappingURL=observe-display-label.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observe-display-label.d.ts","sourceRoot":"","sources":["../../src/commands/observe-display-label.ts"],"names":[],"mappings":"AAIA,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAMvF;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAI/E;AAED,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,MAAM,GAAG,SAAS,CAapB"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
function normalizeText(value) {
|
|
2
|
+
return (value ?? '').replace(/\s+/g, ' ').trim();
|
|
3
|
+
}
|
|
4
|
+
export function normalizeObserveLiveValue(value) {
|
|
5
|
+
const normalized = normalizeText(value);
|
|
6
|
+
if (!normalized || normalized.length > 80) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
return normalized;
|
|
10
|
+
}
|
|
11
|
+
export function observeLabelIncludesValue(label, value) {
|
|
12
|
+
const normalizedLabel = normalizeText(label).toLowerCase();
|
|
13
|
+
const normalizedValue = normalizeText(value).toLowerCase();
|
|
14
|
+
return Boolean(normalizedValue) && normalizedLabel.includes(normalizedValue);
|
|
15
|
+
}
|
|
16
|
+
export function buildObserveDisplayLabel(baseLabel, liveValue) {
|
|
17
|
+
const normalizedLabel = normalizeText(baseLabel);
|
|
18
|
+
const normalizedValue = normalizeObserveLiveValue(liveValue);
|
|
19
|
+
if (!normalizedLabel || !normalizedValue) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
if (observeLabelIncludesValue(normalizedLabel, normalizedValue)) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
return `${normalizedLabel} — ${normalizedValue}`;
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observe-dom-label-contract.d.ts","sourceRoot":"","sources":["../../src/commands/observe-dom-label-contract.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,wCAAwC,QAkgBpD,CAAC"}
|