@nuanu-ai/agentbrowse 0.2.21 → 0.2.22
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 +42 -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/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/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/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/solver/captcha-detector.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import { isButtonLikeObservedInput, shouldAllowLooseFieldLabelFallbackForObservedControl, } from './observe-label-policy.js';
|
|
2
|
+
const OBSERVE_LABEL_POLICY_HELPER_SCRIPT = String.raw `
|
|
3
|
+
const isButtonLikeObservedInput = ${isButtonLikeObservedInput.toString()};
|
|
4
|
+
const shouldAllowLooseFieldLabelFallbackForObservedControl =
|
|
5
|
+
${shouldAllowLooseFieldLabelFallbackForObservedControl.toString()};
|
|
6
|
+
`;
|
|
7
|
+
export const OBSERVE_DOM_LABEL_CONTRACT_HELPER_SCRIPT = String.raw `
|
|
8
|
+
${OBSERVE_LABEL_POLICY_HELPER_SCRIPT}
|
|
9
|
+
const observedOwnerWindowOf = (node) => {
|
|
10
|
+
if (!node?.ownerDocument?.defaultView) {
|
|
11
|
+
if (typeof window !== 'undefined') {
|
|
12
|
+
return window;
|
|
13
|
+
}
|
|
14
|
+
return typeof globalThis !== 'undefined' ? globalThis : undefined;
|
|
15
|
+
}
|
|
16
|
+
return node.ownerDocument.defaultView;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const observedInstanceOfViewCtor = (value, ctorName) => {
|
|
20
|
+
const view = observedOwnerWindowOf(value);
|
|
21
|
+
const ctor = view?.[ctorName];
|
|
22
|
+
return typeof ctor === 'function' && value instanceof ctor;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const observedIsHTMLElementNode = (value) => {
|
|
26
|
+
return observedInstanceOfViewCtor(value, 'HTMLElement');
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const observedIsHTMLInputNode = (value) => {
|
|
30
|
+
return observedInstanceOfViewCtor(value, 'HTMLInputElement');
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const observedNormalizeDescriptorText = (value) => (value || '').replace(/\s+/g, ' ').trim();
|
|
34
|
+
|
|
35
|
+
const observedInputTypeOf = (element) => {
|
|
36
|
+
if (!observedIsHTMLInputNode(element)) {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
return (element.getAttribute('type') || 'text').trim().toLowerCase();
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const observedIsButtonLikeInput = (element) => {
|
|
43
|
+
return isButtonLikeObservedInput({
|
|
44
|
+
tag: element?.tagName?.toLowerCase?.(),
|
|
45
|
+
inputType: observedInputTypeOf(element),
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const observedShouldAllowLooseFieldLabelFallback = (element) => {
|
|
50
|
+
return shouldAllowLooseFieldLabelFallbackForObservedControl({
|
|
51
|
+
tag: element?.tagName?.toLowerCase?.(),
|
|
52
|
+
role: element?.getAttribute?.('role')?.trim?.(),
|
|
53
|
+
inputType: observedInputTypeOf(element),
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const observedTextOf = (target, referenceElement = target) => {
|
|
58
|
+
if (target === referenceElement && observedIsButtonLikeInput(referenceElement)) {
|
|
59
|
+
const value = observedNormalizeDescriptorText(
|
|
60
|
+
referenceElement.value || referenceElement.getAttribute('value') || ''
|
|
61
|
+
);
|
|
62
|
+
if (value) {
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const value = target?.innerText?.trim() || target?.textContent?.trim() || '';
|
|
68
|
+
return value.replace(/\s+/g, ' ');
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const observedIsMeaningfulLabel = (value) => {
|
|
72
|
+
const normalized = observedNormalizeDescriptorText(value);
|
|
73
|
+
return Boolean(normalized) && normalized !== '[object Object]';
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const observedComposedParentElement = (node) => {
|
|
77
|
+
if (!node) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
if (node.parentElement) {
|
|
81
|
+
return node.parentElement;
|
|
82
|
+
}
|
|
83
|
+
const root = node.getRootNode?.();
|
|
84
|
+
return root instanceof ShadowRoot && root.host instanceof HTMLElement ? root.host : null;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const observedMatchesSelector = (element, selector) => {
|
|
88
|
+
if (!observedIsHTMLElementNode(element)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
if (typeof element.matches === 'function') {
|
|
92
|
+
return element.matches(selector);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const tag = element.tagName.toLowerCase();
|
|
96
|
+
const className = (element.getAttribute('class') || '').toLowerCase();
|
|
97
|
+
const testId = (
|
|
98
|
+
element.getAttribute('data-testid') ||
|
|
99
|
+
element.getAttribute('data-test-id') ||
|
|
100
|
+
''
|
|
101
|
+
).toLowerCase();
|
|
102
|
+
|
|
103
|
+
if (selector === 'label, legend') {
|
|
104
|
+
return tag === 'label' || tag === 'legend';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (selector === observedLabelLikeSelector) {
|
|
108
|
+
return tag === 'label' || tag === 'legend' || className.includes('label') || testId.includes('label');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return false;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const observedPrecedesElementInDocument = (candidate, element) => {
|
|
115
|
+
if (typeof candidate.compareDocumentPosition === 'function') {
|
|
116
|
+
return Boolean(candidate.compareDocumentPosition(element) & Node.DOCUMENT_POSITION_FOLLOWING);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const candidateParent = observedComposedParentElement(candidate);
|
|
120
|
+
const elementParent = observedComposedParentElement(element);
|
|
121
|
+
if (candidateParent && candidateParent === elementParent && candidateParent.children) {
|
|
122
|
+
const siblings = Array.from(candidateParent.children);
|
|
123
|
+
const candidateIndex = siblings.indexOf(candidate);
|
|
124
|
+
const elementIndex = siblings.indexOf(element);
|
|
125
|
+
if (candidateIndex >= 0 && elementIndex >= 0) {
|
|
126
|
+
return candidateIndex < elementIndex;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return false;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const observedTokenizeSemanticText = (value) => {
|
|
134
|
+
const normalized = (value || '')
|
|
135
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
136
|
+
.replace(/[^a-zA-Z0-9\u0400-\u04FF]+/g, ' ')
|
|
137
|
+
.toLowerCase()
|
|
138
|
+
.trim();
|
|
139
|
+
if (!normalized) return [];
|
|
140
|
+
return normalized
|
|
141
|
+
.split(/\s+/)
|
|
142
|
+
.filter((token) => token.length >= 2 && token !== 'input' && token !== 'field');
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const observedFieldSemanticTokensOf = (element) => {
|
|
146
|
+
const tokens = new Set();
|
|
147
|
+
const pushValue = (value) => {
|
|
148
|
+
for (const token of observedTokenizeSemanticText(value)) {
|
|
149
|
+
tokens.add(token);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const inputType = observedInputTypeOf(element);
|
|
154
|
+
const autocomplete = (element.getAttribute('autocomplete') || '').trim().toLowerCase();
|
|
155
|
+
|
|
156
|
+
if (observedIsButtonLikeInput(element)) {
|
|
157
|
+
return tokens;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
pushValue(element.getAttribute('placeholder'));
|
|
161
|
+
pushValue(element.getAttribute('name'));
|
|
162
|
+
pushValue(element.getAttribute('id'));
|
|
163
|
+
pushValue(autocomplete);
|
|
164
|
+
pushValue(inputType);
|
|
165
|
+
|
|
166
|
+
if (inputType === 'email' || autocomplete.includes('email')) {
|
|
167
|
+
pushValue('email mail e-mail');
|
|
168
|
+
}
|
|
169
|
+
if (
|
|
170
|
+
inputType === 'tel' ||
|
|
171
|
+
autocomplete.startsWith('tel') ||
|
|
172
|
+
autocomplete.includes('phone')
|
|
173
|
+
) {
|
|
174
|
+
pushValue('phone telephone mobile tel номер телефон');
|
|
175
|
+
}
|
|
176
|
+
if (inputType === 'password' || autocomplete.includes('password')) {
|
|
177
|
+
pushValue('password pass пароль');
|
|
178
|
+
}
|
|
179
|
+
if (inputType === 'search' || autocomplete.includes('search')) {
|
|
180
|
+
pushValue('search find поиск найти');
|
|
181
|
+
}
|
|
182
|
+
if (
|
|
183
|
+
inputType === 'date' ||
|
|
184
|
+
autocomplete.includes('bday') ||
|
|
185
|
+
autocomplete.includes('birth') ||
|
|
186
|
+
autocomplete.includes('dob')
|
|
187
|
+
) {
|
|
188
|
+
pushValue('date birth birthday dob дата рождения');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return tokens;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const observedLooseActionTextRe =
|
|
195
|
+
/^(?:pay|buy|continue|submit|search|book|reserve|checkout|next|done|оплат|куп|продолж|дальше|поиск|заброни)/i;
|
|
196
|
+
|
|
197
|
+
const observedLabelLikeSelector = 'label, legend, [class*="label"], [data-testid*="label"]';
|
|
198
|
+
const observedExplicitFieldLabelSelector = 'label, legend';
|
|
199
|
+
|
|
200
|
+
const observedIsLooseFieldLabelCompatible = (element, candidateText) => {
|
|
201
|
+
const normalizedCandidate = observedNormalizeDescriptorText(candidateText || '');
|
|
202
|
+
if (!normalizedCandidate) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
if (observedLooseActionTextRe.test(normalizedCandidate)) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const semanticTokens = observedFieldSemanticTokensOf(element);
|
|
210
|
+
if (semanticTokens.size === 0) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const candidateTokens = observedTokenizeSemanticText(normalizedCandidate);
|
|
215
|
+
if (candidateTokens.length === 0) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return candidateTokens.some((token) => semanticTokens.has(token));
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const observedNearestExplicitFieldLabelOf = (element) => {
|
|
223
|
+
const restrictLooseCandidatesToPreceding =
|
|
224
|
+
observedShouldAllowLooseFieldLabelFallback(element);
|
|
225
|
+
const anchors = [
|
|
226
|
+
observedComposedParentElement(element),
|
|
227
|
+
observedComposedParentElement(observedComposedParentElement(element)),
|
|
228
|
+
observedComposedParentElement(
|
|
229
|
+
observedComposedParentElement(observedComposedParentElement(element))
|
|
230
|
+
),
|
|
231
|
+
].filter(Boolean);
|
|
232
|
+
|
|
233
|
+
for (const anchor of anchors) {
|
|
234
|
+
if (!observedIsHTMLElementNode(anchor)) continue;
|
|
235
|
+
|
|
236
|
+
const explicitLabels = Array.from(
|
|
237
|
+
anchor.querySelectorAll(observedExplicitFieldLabelSelector)
|
|
238
|
+
).filter((candidate) => {
|
|
239
|
+
return (
|
|
240
|
+
observedIsHTMLElementNode(candidate) &&
|
|
241
|
+
candidate !== element &&
|
|
242
|
+
!candidate.contains(element) &&
|
|
243
|
+
!element.contains(candidate)
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
for (const candidate of explicitLabels) {
|
|
248
|
+
if (
|
|
249
|
+
restrictLooseCandidatesToPreceding &&
|
|
250
|
+
!observedMatchesSelector(candidate, 'label, legend') &&
|
|
251
|
+
!observedPrecedesElementInDocument(candidate, element)
|
|
252
|
+
) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const candidateText = observedTextOf(candidate, element);
|
|
256
|
+
if (observedIsMeaningfulLabel(candidateText)) {
|
|
257
|
+
return candidateText;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return undefined;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const observedNearestFieldLabelOf = (element) => {
|
|
266
|
+
const restrictLooseCandidatesToPreceding =
|
|
267
|
+
observedShouldAllowLooseFieldLabelFallback(element);
|
|
268
|
+
const anchors = [
|
|
269
|
+
observedComposedParentElement(element),
|
|
270
|
+
observedComposedParentElement(observedComposedParentElement(element)),
|
|
271
|
+
observedComposedParentElement(
|
|
272
|
+
observedComposedParentElement(observedComposedParentElement(element))
|
|
273
|
+
),
|
|
274
|
+
].filter(Boolean);
|
|
275
|
+
|
|
276
|
+
const explicitFieldLabel = observedNearestExplicitFieldLabelOf(element);
|
|
277
|
+
if (observedIsMeaningfulLabel(explicitFieldLabel)) {
|
|
278
|
+
return explicitFieldLabel;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
for (const anchor of anchors) {
|
|
282
|
+
if (!observedIsHTMLElementNode(anchor)) continue;
|
|
283
|
+
|
|
284
|
+
const decoratedLabels = Array.from(
|
|
285
|
+
anchor.querySelectorAll('[class*="label"], [data-testid*="label"]')
|
|
286
|
+
).filter((candidate) => {
|
|
287
|
+
return (
|
|
288
|
+
observedIsHTMLElementNode(candidate) &&
|
|
289
|
+
candidate !== element &&
|
|
290
|
+
!candidate.contains(element) &&
|
|
291
|
+
!element.contains(candidate)
|
|
292
|
+
);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
for (const candidate of decoratedLabels) {
|
|
296
|
+
if (
|
|
297
|
+
restrictLooseCandidatesToPreceding &&
|
|
298
|
+
!observedPrecedesElementInDocument(candidate, element)
|
|
299
|
+
) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
const candidateText = observedTextOf(candidate, element);
|
|
303
|
+
if (observedIsMeaningfulLabel(candidateText)) {
|
|
304
|
+
return candidateText;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
for (const child of Array.from(anchor.children).slice(0, 8)) {
|
|
309
|
+
if (!observedIsHTMLElementNode(child)) continue;
|
|
310
|
+
if (child === element || child.contains(element) || element.contains(child)) continue;
|
|
311
|
+
if (
|
|
312
|
+
restrictLooseCandidatesToPreceding &&
|
|
313
|
+
!observedPrecedesElementInDocument(child, element)
|
|
314
|
+
) {
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const candidateText = observedTextOf(child, element);
|
|
319
|
+
if (observedIsMeaningfulLabel(candidateText)) {
|
|
320
|
+
return candidateText;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return undefined;
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const observedInlinePopupCurrentValueOf = (element) => {
|
|
329
|
+
const explicitLabel = observedNormalizeDescriptorText(observedExplicitLabelOf(element) || '');
|
|
330
|
+
const anchors = [
|
|
331
|
+
observedComposedParentElement(element),
|
|
332
|
+
observedComposedParentElement(observedComposedParentElement(element)),
|
|
333
|
+
].filter(Boolean);
|
|
334
|
+
|
|
335
|
+
for (const anchor of anchors) {
|
|
336
|
+
if (!observedIsHTMLElementNode(anchor)) continue;
|
|
337
|
+
|
|
338
|
+
for (const child of Array.from(anchor.children).slice(0, 8)) {
|
|
339
|
+
if (!observedIsHTMLElementNode(child)) continue;
|
|
340
|
+
if (child === element || child.contains(element) || element.contains(child)) continue;
|
|
341
|
+
if (!observedPrecedesElementInDocument(child, element)) continue;
|
|
342
|
+
if (observedMatchesSelector(child, observedLabelLikeSelector)) continue;
|
|
343
|
+
|
|
344
|
+
const tag = child.tagName.toLowerCase();
|
|
345
|
+
if (['input', 'textarea', 'select', 'button', 'a', 'svg', 'img'].includes(tag)) {
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const candidateText = observedNormalizeDescriptorText(observedTextOf(child, element));
|
|
350
|
+
if (!observedIsMeaningfulLabel(candidateText)) continue;
|
|
351
|
+
if (candidateText.length > 48) continue;
|
|
352
|
+
if (observedLooseActionTextRe.test(candidateText)) continue;
|
|
353
|
+
if (explicitLabel && candidateText.toLowerCase() === explicitLabel.toLowerCase()) continue;
|
|
354
|
+
|
|
355
|
+
return candidateText;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return undefined;
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const observedAriaLabelledbyTextOf = (element) => {
|
|
363
|
+
const labelledBy = element.getAttribute('aria-labelledby')?.trim();
|
|
364
|
+
if (!labelledBy) return undefined;
|
|
365
|
+
|
|
366
|
+
const text = labelledBy
|
|
367
|
+
.split(/\s+/)
|
|
368
|
+
.map((id) => observedTextOf(document.getElementById(id), element))
|
|
369
|
+
.filter(Boolean)
|
|
370
|
+
.join(' ')
|
|
371
|
+
.trim();
|
|
372
|
+
|
|
373
|
+
return observedIsMeaningfulLabel(text) ? text : undefined;
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const observedExplicitLabelOf = (element) => {
|
|
377
|
+
const ariaLabel = element.getAttribute('aria-label')?.trim();
|
|
378
|
+
if (observedIsMeaningfulLabel(ariaLabel)) return ariaLabel;
|
|
379
|
+
|
|
380
|
+
const customLabel = element.getAttribute('label')?.trim();
|
|
381
|
+
if (observedIsMeaningfulLabel(customLabel)) return customLabel;
|
|
382
|
+
|
|
383
|
+
const ariaLabelledbyText = observedAriaLabelledbyTextOf(element);
|
|
384
|
+
if (observedIsMeaningfulLabel(ariaLabelledbyText)) return ariaLabelledbyText;
|
|
385
|
+
|
|
386
|
+
const labels = element.labels;
|
|
387
|
+
if (labels && labels.length > 0) {
|
|
388
|
+
const labelText = observedTextOf(labels[0], element);
|
|
389
|
+
if (observedIsMeaningfulLabel(labelText)) return labelText;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const nearbyExplicitFieldLabel = observedNearestExplicitFieldLabelOf(element);
|
|
393
|
+
if (
|
|
394
|
+
observedShouldAllowLooseFieldLabelFallback(element) &&
|
|
395
|
+
observedIsMeaningfulLabel(nearbyExplicitFieldLabel)
|
|
396
|
+
) {
|
|
397
|
+
return nearbyExplicitFieldLabel;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const title = element.getAttribute('title')?.trim();
|
|
401
|
+
if (observedIsMeaningfulLabel(title)) return title;
|
|
402
|
+
|
|
403
|
+
return undefined;
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const observedLooseFieldLabelOf = (element) => {
|
|
407
|
+
if (!observedShouldAllowLooseFieldLabelFallback(element)) {
|
|
408
|
+
return undefined;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const nearestFieldLabel = observedNearestFieldLabelOf(element);
|
|
412
|
+
if (
|
|
413
|
+
observedIsMeaningfulLabel(nearestFieldLabel) &&
|
|
414
|
+
observedIsLooseFieldLabelCompatible(element, nearestFieldLabel)
|
|
415
|
+
) {
|
|
416
|
+
return nearestFieldLabel;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return undefined;
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
const observedPopupCurrentValueOf = (element) => {
|
|
423
|
+
const directValue =
|
|
424
|
+
'value' in element && typeof element.value === 'string'
|
|
425
|
+
? observedNormalizeDescriptorText(element.value)
|
|
426
|
+
: undefined;
|
|
427
|
+
if (observedIsMeaningfulLabel(directValue)) {
|
|
428
|
+
return directValue;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const tag = element.tagName.toLowerCase();
|
|
432
|
+
if (tag !== 'input' && tag !== 'textarea') {
|
|
433
|
+
const directText = observedTextOf(element, element);
|
|
434
|
+
if (observedIsMeaningfulLabel(directText)) {
|
|
435
|
+
return directText;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const nearestFieldLabel = observedNearestFieldLabelOf(element);
|
|
440
|
+
const compatibleLooseFieldLabel = observedLooseFieldLabelOf(element);
|
|
441
|
+
const popupBacked =
|
|
442
|
+
element.getAttribute('role') === 'combobox' ||
|
|
443
|
+
element.hasAttribute('aria-haspopup') ||
|
|
444
|
+
element.hasAttribute('aria-controls') ||
|
|
445
|
+
element.hasAttribute('readonly') ||
|
|
446
|
+
element.getAttribute('aria-readonly') === 'true';
|
|
447
|
+
const inlinePopupValue = popupBacked ? observedInlinePopupCurrentValueOf(element) : undefined;
|
|
448
|
+
if (observedIsMeaningfulLabel(inlinePopupValue)) {
|
|
449
|
+
return inlinePopupValue;
|
|
450
|
+
}
|
|
451
|
+
if (
|
|
452
|
+
popupBacked &&
|
|
453
|
+
observedIsMeaningfulLabel(nearestFieldLabel) &&
|
|
454
|
+
nearestFieldLabel !== compatibleLooseFieldLabel
|
|
455
|
+
) {
|
|
456
|
+
return observedNormalizeDescriptorText(nearestFieldLabel);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return undefined;
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
const observedSyntheticLabelOf = (element) => {
|
|
463
|
+
const tag = element.tagName.toLowerCase();
|
|
464
|
+
const explicitRole = element.getAttribute('role')?.trim();
|
|
465
|
+
if (tag === 'input') {
|
|
466
|
+
const inputType = observedInputTypeOf(element);
|
|
467
|
+
if (observedIsButtonLikeInput(element)) return 'Button';
|
|
468
|
+
if (inputType === 'tel') return 'Phone input';
|
|
469
|
+
if (inputType === 'email') return 'Email input';
|
|
470
|
+
if (inputType === 'password') return 'Password input';
|
|
471
|
+
if (inputType === 'search') return 'Search input';
|
|
472
|
+
if (inputType === 'date') return 'Date input';
|
|
473
|
+
return 'Text input';
|
|
474
|
+
}
|
|
475
|
+
if (tag === 'textarea') return 'Text area';
|
|
476
|
+
if (tag === 'select' || explicitRole === 'combobox') return 'Combobox';
|
|
477
|
+
if (explicitRole === 'textbox') return 'Text input';
|
|
478
|
+
return undefined;
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
const observedInferRole = (element) => {
|
|
482
|
+
const explicitRole = element.getAttribute('role')?.trim();
|
|
483
|
+
if (explicitRole) return explicitRole;
|
|
484
|
+
|
|
485
|
+
const tag = element.tagName.toLowerCase();
|
|
486
|
+
if (tag === 'button') return 'button';
|
|
487
|
+
if (tag === 'a' && element.getAttribute('href')) return 'link';
|
|
488
|
+
if (tag === 'select') return 'combobox';
|
|
489
|
+
if (tag === 'textarea') return 'textbox';
|
|
490
|
+
if (tag === 'input') {
|
|
491
|
+
if (observedIsButtonLikeInput(element)) return 'button';
|
|
492
|
+
return 'textbox';
|
|
493
|
+
}
|
|
494
|
+
return '';
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
const observedLabelOf = (element) => {
|
|
498
|
+
const explicitLabel = observedExplicitLabelOf(element);
|
|
499
|
+
if (observedIsMeaningfulLabel(explicitLabel)) return explicitLabel;
|
|
500
|
+
|
|
501
|
+
const looseFieldLabel = observedLooseFieldLabelOf(element);
|
|
502
|
+
if (observedIsMeaningfulLabel(looseFieldLabel)) return looseFieldLabel;
|
|
503
|
+
|
|
504
|
+
const text = observedTextOf(element, element);
|
|
505
|
+
if (observedIsMeaningfulLabel(text)) return text;
|
|
506
|
+
|
|
507
|
+
const syntheticLabel = observedSyntheticLabelOf(element);
|
|
508
|
+
if (observedIsMeaningfulLabel(syntheticLabel)) return syntheticLabel;
|
|
509
|
+
|
|
510
|
+
const placeholder = element.getAttribute('placeholder')?.trim();
|
|
511
|
+
if (
|
|
512
|
+
observedShouldAllowLooseFieldLabelFallback(element) &&
|
|
513
|
+
observedIsMeaningfulLabel(placeholder)
|
|
514
|
+
) {
|
|
515
|
+
return placeholder;
|
|
516
|
+
}
|
|
517
|
+
if (observedIsMeaningfulLabel(placeholder)) return placeholder;
|
|
518
|
+
|
|
519
|
+
return '';
|
|
520
|
+
};
|
|
521
|
+
`;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DomObservedContextNode, DomObservedTarget, DomObservedTargetContext } from './observe-inventory.js';
|
|
2
|
+
export declare function fallbackTargetLabelOf(target: Pick<DomObservedTarget, 'fallbackLabel' | 'label' | 'kind' | 'role' | 'text' | 'placeholder' | 'title' | 'inputType'>): string | undefined;
|
|
3
|
+
export declare function fallbackSurfaceLabelOf(target: Pick<DomObservedTarget, 'fallbackSurfaceLabel' | 'surfaceLabel'>): string | undefined;
|
|
4
|
+
export declare function fallbackContextNodeLabelOf(node: Pick<DomObservedContextNode, 'fallbackLabel' | 'label'> | undefined): string | undefined;
|
|
5
|
+
export declare function fallbackHintTextOf(context: Pick<DomObservedTargetContext, 'fallbackHintText' | 'hintText'> | undefined): string | undefined;
|
|
6
|
+
//# sourceMappingURL=observe-fallback-semantics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observe-fallback-semantics.d.ts","sourceRoot":"","sources":["../../src/commands/observe-fallback-semantics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,iBAAiB,EACjB,wBAAwB,EACzB,MAAM,wBAAwB,CAAC;AAOhC,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,IAAI,CACV,iBAAiB,EACjB,eAAe,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,aAAa,GAAG,OAAO,GAAG,WAAW,CAC7F,GACA,MAAM,GAAG,SAAS,CAmEpB;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,sBAAsB,GAAG,cAAc,CAAC,GACvE,MAAM,GAAG,SAAS,CAEpB;AAED,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,IAAI,CAAC,sBAAsB,EAAE,eAAe,GAAG,OAAO,CAAC,GAAG,SAAS,GACxE,MAAM,GAAG,SAAS,CAMpB;AAED,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,IAAI,CAAC,wBAAwB,EAAE,kBAAkB,GAAG,UAAU,CAAC,GAAG,SAAS,GACnF,MAAM,GAAG,SAAS,CAMpB"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
function normalizeText(value) {
|
|
2
|
+
const normalized = (value ?? '').replace(/\s+/g, ' ').trim();
|
|
3
|
+
return normalized || undefined;
|
|
4
|
+
}
|
|
5
|
+
export function fallbackTargetLabelOf(target) {
|
|
6
|
+
const explicitLabel = normalizeText(target.fallbackLabel) ?? normalizeText(target.label);
|
|
7
|
+
if (explicitLabel) {
|
|
8
|
+
return explicitLabel;
|
|
9
|
+
}
|
|
10
|
+
const kind = normalizeText(target.kind)?.toLowerCase();
|
|
11
|
+
const role = normalizeText(target.role)?.toLowerCase();
|
|
12
|
+
const inputType = normalizeText(target.inputType)?.toLowerCase();
|
|
13
|
+
const text = normalizeText(target.text);
|
|
14
|
+
const placeholder = normalizeText(target.placeholder);
|
|
15
|
+
const title = normalizeText(target.title);
|
|
16
|
+
const isFieldLike = kind === 'input' ||
|
|
17
|
+
kind === 'textarea' ||
|
|
18
|
+
kind === 'select' ||
|
|
19
|
+
role === 'textbox' ||
|
|
20
|
+
role === 'combobox';
|
|
21
|
+
if (isFieldLike && placeholder) {
|
|
22
|
+
return placeholder;
|
|
23
|
+
}
|
|
24
|
+
if (text) {
|
|
25
|
+
return text;
|
|
26
|
+
}
|
|
27
|
+
if (title) {
|
|
28
|
+
return title;
|
|
29
|
+
}
|
|
30
|
+
if (placeholder) {
|
|
31
|
+
return placeholder;
|
|
32
|
+
}
|
|
33
|
+
if (kind === 'textarea') {
|
|
34
|
+
return 'Text area';
|
|
35
|
+
}
|
|
36
|
+
if (kind === 'select' || role === 'combobox') {
|
|
37
|
+
return 'Combobox';
|
|
38
|
+
}
|
|
39
|
+
if (role === 'textbox') {
|
|
40
|
+
return 'Text input';
|
|
41
|
+
}
|
|
42
|
+
if (kind === 'input') {
|
|
43
|
+
if (inputType === 'email')
|
|
44
|
+
return 'Email input';
|
|
45
|
+
if (inputType === 'password')
|
|
46
|
+
return 'Password input';
|
|
47
|
+
if (inputType === 'search')
|
|
48
|
+
return 'Search input';
|
|
49
|
+
if (inputType === 'tel')
|
|
50
|
+
return 'Phone input';
|
|
51
|
+
if (inputType === 'date')
|
|
52
|
+
return 'Date input';
|
|
53
|
+
return 'Text input';
|
|
54
|
+
}
|
|
55
|
+
if (kind === 'button' || role === 'button') {
|
|
56
|
+
return 'Button';
|
|
57
|
+
}
|
|
58
|
+
if (kind === 'link' || role === 'link') {
|
|
59
|
+
return 'Link';
|
|
60
|
+
}
|
|
61
|
+
if (role === 'option') {
|
|
62
|
+
return 'Option';
|
|
63
|
+
}
|
|
64
|
+
if (role === 'menuitem') {
|
|
65
|
+
return 'Menu item';
|
|
66
|
+
}
|
|
67
|
+
if (role === 'gridcell') {
|
|
68
|
+
return 'Grid cell';
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
export function fallbackSurfaceLabelOf(target) {
|
|
73
|
+
return normalizeText(target.fallbackSurfaceLabel) ?? normalizeText(target.surfaceLabel);
|
|
74
|
+
}
|
|
75
|
+
export function fallbackContextNodeLabelOf(node) {
|
|
76
|
+
if (!node) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
return normalizeText(node.fallbackLabel) ?? normalizeText(node.label);
|
|
80
|
+
}
|
|
81
|
+
export function fallbackHintTextOf(context) {
|
|
82
|
+
if (!context) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
return normalizeText(context.fallbackHintText) ?? normalizeText(context.hintText);
|
|
86
|
+
}
|