@farmslot/recipe-harness 0.4.2 → 0.5.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/CHANGELOG.md +10 -0
- package/dist/adapters/ui.d.ts +3 -1
- package/dist/adapters/ui.d.ts.map +1 -1
- package/dist/adapters/ui.js +11 -0
- package/dist/adapters/ui.js.map +1 -1
- package/dist/core/flows.d.ts +7 -2
- package/dist/core/flows.d.ts.map +1 -1
- package/dist/core/flows.js +30 -2
- package/dist/core/flows.js.map +1 -1
- package/dist/core/passive-observations.d.ts +28 -0
- package/dist/core/passive-observations.d.ts.map +1 -0
- package/dist/core/passive-observations.js +96 -0
- package/dist/core/passive-observations.js.map +1 -0
- package/dist/core/runner.d.ts.map +1 -1
- package/dist/core/runner.js +29 -0
- package/dist/core/runner.js.map +1 -1
- package/dist/core/types.d.ts +16 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/runtime/cdp.d.ts +9 -2
- package/dist/runtime/cdp.d.ts.map +1 -1
- package/dist/runtime/cdp.js +307 -24
- package/dist/runtime/cdp.js.map +1 -1
- package/dist/runtime/deps-readiness.d.ts.map +1 -1
- package/dist/runtime/deps-readiness.js +3 -1
- package/dist/runtime/deps-readiness.js.map +1 -1
- package/dist/runtime/react-native-bridge.d.ts +1 -1
- package/dist/runtime/react-native-bridge.d.ts.map +1 -1
- package/dist/runtime/react-native-bridge.js +30 -0
- package/dist/runtime/react-native-bridge.js.map +1 -1
- package/package.json +3 -3
package/dist/core/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RecipeActionManifestDocument, RecipeArtifactManifestEntry } from '@farmslot/protocol';
|
|
1
|
+
import type { RecipeActionManifestDocument, RecipeArtifactManifestEntry, UiObserverRef } from '@farmslot/protocol';
|
|
2
2
|
export type RecipeRunStatus = 'pass' | 'fail' | 'unknown';
|
|
3
3
|
export type RecipeVideoRecordingMode = 'off' | 'full-run';
|
|
4
4
|
export type RecipeRecordPolicy = 'none' | 'trace_only' | 'proof_window' | 'failure_only';
|
|
@@ -169,6 +169,8 @@ export interface ActionResult {
|
|
|
169
169
|
case?: string;
|
|
170
170
|
output?: unknown;
|
|
171
171
|
artifacts?: RecipeArtifactManifestEntry[];
|
|
172
|
+
observations?: RecipeObservations;
|
|
173
|
+
observationWarnings?: RecipeObservationWarning[];
|
|
172
174
|
}
|
|
173
175
|
export interface ActionExecutionContext {
|
|
174
176
|
nodeId: string;
|
|
@@ -188,6 +190,16 @@ export interface ActionAdapter {
|
|
|
188
190
|
action: string;
|
|
189
191
|
testOnly?: boolean;
|
|
190
192
|
execute(node: Record<string, unknown>, context: ActionExecutionContext): Promise<ActionResult>;
|
|
193
|
+
observe?(refs: readonly UiObserverRef[], node: Record<string, unknown>, context: ActionExecutionContext): Promise<RecipeObservationResult>;
|
|
194
|
+
}
|
|
195
|
+
export type RecipeObservations = Partial<Record<UiObserverRef, unknown>>;
|
|
196
|
+
export interface RecipeObservationWarning {
|
|
197
|
+
ref: UiObserverRef;
|
|
198
|
+
message: string;
|
|
199
|
+
}
|
|
200
|
+
export interface RecipeObservationResult {
|
|
201
|
+
observations?: RecipeObservations;
|
|
202
|
+
warnings?: RecipeObservationWarning[];
|
|
191
203
|
}
|
|
192
204
|
export interface RecipePreconditionGate {
|
|
193
205
|
id: string;
|
|
@@ -223,7 +235,10 @@ export interface TraceEntry {
|
|
|
223
235
|
ok: boolean;
|
|
224
236
|
next?: string;
|
|
225
237
|
status?: RecipeRunStatus;
|
|
238
|
+
case?: string;
|
|
226
239
|
output?: unknown;
|
|
240
|
+
observations?: RecipeObservations;
|
|
241
|
+
observationWarnings?: RecipeObservationWarning[];
|
|
227
242
|
artifacts?: RecipeArtifactManifestEntry[];
|
|
228
243
|
error?: string;
|
|
229
244
|
}
|
package/dist/core/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"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"}
|
package/dist/runtime/cdp.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { UiObserverRef } from '@farmslot/protocol';
|
|
1
2
|
import type { StandardUiAction, UiActionTransport } from '../adapters/ui.js';
|
|
2
|
-
import type { ActionExecutionContext } from '../core/types.js';
|
|
3
|
+
import type { ActionExecutionContext, RecipeObservationResult } from '../core/types.js';
|
|
3
4
|
export declare function sleep(ms: number): Promise<void>;
|
|
4
5
|
export declare function dataTestId(testId: string): string;
|
|
5
6
|
export interface CdpTargetInfo {
|
|
@@ -40,7 +41,12 @@ export declare class CdpWebPage {
|
|
|
40
41
|
readonly session: CdpSession;
|
|
41
42
|
constructor(session: CdpSession);
|
|
42
43
|
static connectToTarget(target: CdpTargetInfo): Promise<CdpWebPage>;
|
|
43
|
-
navigate(url: string): Promise<unknown>;
|
|
44
|
+
navigate(url: string, timeoutMs?: number): Promise<unknown>;
|
|
45
|
+
waitForDocumentReady(options: {
|
|
46
|
+
expectedUrl?: string;
|
|
47
|
+
timeoutMs?: number;
|
|
48
|
+
}): Promise<void>;
|
|
49
|
+
waitForDomSettled(timeoutMs?: number): Promise<void>;
|
|
44
50
|
evaluate<T = unknown>(expression: string): Promise<T>;
|
|
45
51
|
click(selector: string): Promise<unknown>;
|
|
46
52
|
clickText(text: string): Promise<unknown>;
|
|
@@ -73,6 +79,7 @@ export declare class CdpWebPage {
|
|
|
73
79
|
category?: string;
|
|
74
80
|
mimeType: string;
|
|
75
81
|
}>;
|
|
82
|
+
observe(refs: readonly UiObserverRef[]): Promise<RecipeObservationResult>;
|
|
76
83
|
close(): void;
|
|
77
84
|
}
|
|
78
85
|
export interface CdpWebUiTransportInput {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cdp.d.ts","sourceRoot":"","sources":["../../src/runtime/cdp.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAqB,MAAM,mBAAmB,CAAC;AAEhG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"cdp.d.ts","sourceRoot":"","sources":["../../src/runtime/cdp.ts"],"names":[],"mappings":"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"}
|
package/dist/runtime/cdp.js
CHANGED
|
@@ -145,8 +145,53 @@ export class CdpWebPage {
|
|
|
145
145
|
await session.call('Page.enable');
|
|
146
146
|
return new CdpWebPage(session);
|
|
147
147
|
}
|
|
148
|
-
async navigate(url) {
|
|
149
|
-
|
|
148
|
+
async navigate(url, timeoutMs = 10_000) {
|
|
149
|
+
const deadline = Date.now() + timeoutMs;
|
|
150
|
+
const expectedUrl = await this.evaluate(`new URL(${JSON.stringify(url)}, location.href).href`);
|
|
151
|
+
let notifyLoaded;
|
|
152
|
+
const loaded = new Promise((resolve) => {
|
|
153
|
+
notifyLoaded = resolve;
|
|
154
|
+
});
|
|
155
|
+
const unsubscribe = this.session.on('Page.loadEventFired', () => notifyLoaded?.());
|
|
156
|
+
try {
|
|
157
|
+
const result = await this.session.call('Page.navigate', { url });
|
|
158
|
+
if (result.errorText)
|
|
159
|
+
throw new Error(`CDP navigation failed: ${result.errorText}`);
|
|
160
|
+
if (result.loaderId) {
|
|
161
|
+
await withTimeout(loaded, Math.max(1, deadline - Date.now()), `CDP navigation timed out after ${timeoutMs}ms`);
|
|
162
|
+
}
|
|
163
|
+
await this.waitForDocumentReady({
|
|
164
|
+
expectedUrl: result.loaderId ? undefined : expectedUrl,
|
|
165
|
+
timeoutMs: Math.max(1, deadline - Date.now()),
|
|
166
|
+
});
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
finally {
|
|
170
|
+
unsubscribe();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async waitForDocumentReady(options) {
|
|
174
|
+
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
175
|
+
const deadline = Date.now() + timeoutMs;
|
|
176
|
+
let lastError;
|
|
177
|
+
while (Date.now() <= deadline) {
|
|
178
|
+
try {
|
|
179
|
+
const state = await this.evaluate(`(() => ({ ready: document.readyState === 'interactive' || document.readyState === 'complete', url: location.href }))()`);
|
|
180
|
+
if (state.ready && (!options.expectedUrl || state.url === options.expectedUrl))
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
// Runtime contexts can disappear briefly while a document navigation commits.
|
|
185
|
+
lastError = error;
|
|
186
|
+
}
|
|
187
|
+
await sleep(50);
|
|
188
|
+
}
|
|
189
|
+
const detail = lastError instanceof Error ? `: ${lastError.message}` : '';
|
|
190
|
+
throw new Error(`CDP document was not ready within ${timeoutMs}ms${detail}`);
|
|
191
|
+
}
|
|
192
|
+
async waitForDomSettled(timeoutMs = 10_000) {
|
|
193
|
+
const quietMs = Math.min(300, Math.max(1, Math.floor(timeoutMs / 2)));
|
|
194
|
+
await withTimeout(this.evaluate(`new Promise((resolve, reject) => { const quietMs = ${quietMs}; const observedRoots = new Set(); let quietTimer; let rootPoll; let timeout; let finished = false; const cleanup = () => { observer.disconnect(); clearTimeout(quietTimer); clearTimeout(timeout); clearInterval(rootPoll); }; const finish = () => { if (finished) return; const animations = typeof document.getAnimations === 'function' ? document.getAnimations({ subtree: true }) : []; const finiteAnimations = animations.filter((animation) => animation.effect?.getTiming().iterations !== Infinity); if (finiteAnimations.some((animation) => animation.playState === 'running' || animation.playState === 'pending')) { schedule(); return; } finished = true; cleanup(); resolve(true); }; const schedule = () => { clearTimeout(quietTimer); quietTimer = setTimeout(finish, quietMs); }; const observeRoot = (root) => { if (!observedRoots.has(root)) { observedRoots.add(root); observer.observe(root, { subtree: true, childList: true, attributes: true, characterData: true }); schedule(); } for (const element of root.querySelectorAll('*')) { if (element.shadowRoot) observeRoot(element.shadowRoot); } }; const discoverRoots = () => observeRoot(document); const observer = new MutationObserver(() => { discoverRoots(); schedule(); }); discoverRoots(); rootPoll = setInterval(discoverRoots, Math.min(25, quietMs)); requestAnimationFrame(() => requestAnimationFrame(schedule)); timeout = setTimeout(() => { if (finished) return; finished = true; cleanup(); reject(new Error('DOM remained active for ${timeoutMs}ms')); }, ${timeoutMs}); })`), timeoutMs + 50, `CDP document did not settle within ${timeoutMs}ms`);
|
|
150
195
|
}
|
|
151
196
|
async evaluate(expression) {
|
|
152
197
|
const result = await this.session.call('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true });
|
|
@@ -158,22 +203,22 @@ export class CdpWebPage {
|
|
|
158
203
|
return result.result?.value;
|
|
159
204
|
}
|
|
160
205
|
async click(selector) {
|
|
161
|
-
const target = await this.evaluate(`(() => { const el =
|
|
206
|
+
const target = await this.evaluate(`(() => { ${deepQueryHelpersExpression()} const el = querySelectorDeep(${JSON.stringify(selector)}); if (!el) throw new Error('Selector not found: ${escapeForJsMessage(selector)}'); el.scrollIntoView({ block: 'center', inline: 'center' }); const point = clickablePointDeep(el); return { ...point, selector: ${JSON.stringify(selector)}, tagName: el.tagName }; })()`);
|
|
162
207
|
await this.clickPoint(target.x, target.y);
|
|
163
208
|
return { clicked: true, selector: target.selector, tagName: target.tagName };
|
|
164
209
|
}
|
|
165
210
|
async clickText(text) {
|
|
166
|
-
const target = await this.evaluate(`(() => { const expected = ${JSON.stringify(text)}; const candidates =
|
|
211
|
+
const target = await this.evaluate(`(() => { ${deepQueryHelpersExpression()} const expected = ${JSON.stringify(text)}; const candidates = querySelectorAllDeep('button, [role=button], a, label, input, textarea, [tabindex]'); const el = candidates.find((candidate) => (candidate.innerText || candidate.textContent || candidate.getAttribute('aria-label') || candidate.getAttribute('value') || '').trim().includes(expected)); if (!el) throw new Error('Text target not found: ${escapeForJsMessage(text)}'); el.scrollIntoView({ block: 'center', inline: 'center' }); const point = clickablePointDeep(el); return { ...point, text: expected, tagName: el.tagName }; })()`);
|
|
167
212
|
await this.clickPoint(target.x, target.y);
|
|
168
213
|
return { clicked: true, text: target.text, tagName: target.tagName };
|
|
169
214
|
}
|
|
170
215
|
async setInput(selector, value) {
|
|
171
|
-
const before = await this.evaluate(`(() => { const root =
|
|
216
|
+
const before = await this.evaluate(`(() => { ${deepQueryHelpersExpression()} const root = querySelectorDeep(${JSON.stringify(selector)}); if (!root) throw new Error('Selector not found: ${escapeForJsMessage(selector)}'); const el = root.matches('input, textarea, [contenteditable="true"], [contenteditable=""]') ? root : querySelectorDeep('input, textarea, [contenteditable="true"], [contenteditable=""]', root); if (!el) throw new Error('Input target not found inside selector: ${escapeForJsMessage(selector)}'); if (el.disabled || el.getAttribute('aria-disabled') === 'true') throw new Error('Input target is disabled: ${escapeForJsMessage(selector)}'); el.scrollIntoView({ block: 'center', inline: 'nearest' }); el.focus(); if (typeof el.select === 'function') { el.select(); } else { const range = document.createRange(); range.selectNodeContents(el); const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); } return { selector: ${JSON.stringify(selector)}, tagName: el.tagName, previousValue: el.value ?? el.textContent ?? '' }; })()`);
|
|
172
217
|
await this.session.call('Input.dispatchKeyEvent', { type: 'keyDown', key: 'Backspace' });
|
|
173
218
|
await this.session.call('Input.dispatchKeyEvent', { type: 'keyUp', key: 'Backspace' });
|
|
174
219
|
if (value)
|
|
175
220
|
await this.session.call('Input.insertText', { text: value });
|
|
176
|
-
const after = await this.evaluate(`(() => { const root =
|
|
221
|
+
const after = await this.evaluate(`(() => { ${deepQueryHelpersExpression()} const root = querySelectorDeep(${JSON.stringify(selector)}); const el = root && (root.matches('input, textarea, [contenteditable="true"], [contenteditable=""]') ? root : querySelectorDeep('input, textarea, [contenteditable="true"], [contenteditable=""]', root)); if (!el) throw new Error('Input target disappeared after typing: ${escapeForJsMessage(selector)}'); el.dispatchEvent(new Event('change', { bubbles: true })); return { value: el.value ?? el.textContent ?? '' }; })()`);
|
|
177
222
|
if (after.value !== value) {
|
|
178
223
|
throw new Error(`Input target value mismatch after typing ${selector}: expected ${JSON.stringify(value)}, got ${JSON.stringify(after.value)}.`);
|
|
179
224
|
}
|
|
@@ -218,15 +263,15 @@ export class CdpWebPage {
|
|
|
218
263
|
const deltaY = options.deltaY ?? 600;
|
|
219
264
|
if (options.selector) {
|
|
220
265
|
if (options.intoView) {
|
|
221
|
-
return this.evaluate(`(() => { const el =
|
|
266
|
+
return this.evaluate(`(() => { ${deepQueryHelpersExpression()} const el = querySelectorDeep(${JSON.stringify(options.selector)}); if (!el) throw new Error('Selector not found: ${escapeForJsMessage(options.selector)}'); el.scrollIntoView({ block: 'center', inline: 'nearest' }); return { scrolled: true, selector: ${JSON.stringify(options.selector)}, intoView: true }; })()`);
|
|
222
267
|
}
|
|
223
|
-
return this.evaluate(`(() => { const el =
|
|
268
|
+
return this.evaluate(`(() => { ${deepQueryHelpersExpression()} const el = querySelectorDeep(${JSON.stringify(options.selector)}); if (!el) throw new Error('Selector not found: ${escapeForJsMessage(options.selector)}'); el.scrollBy(${JSON.stringify(deltaX)}, ${JSON.stringify(deltaY)}); return { scrolled: true }; })()`);
|
|
224
269
|
}
|
|
225
270
|
return this.evaluate(`(() => { window.scrollBy(${JSON.stringify(deltaX)}, ${JSON.stringify(deltaY)}); return { scrolled: true }; })()`);
|
|
226
271
|
}
|
|
227
272
|
async waitFor(options) {
|
|
228
273
|
const timeoutMs = options.timeoutMs ?? 5_000;
|
|
229
|
-
return this.evaluate(`(async () => { const deadline = Date.now() + ${JSON.stringify(timeoutMs)}; while (Date.now() <= deadline) { const ok = ${waitForPredicateExpression(options)}; if (ok) return { matched: true }; await new Promise((resolve) => setTimeout(resolve, 100)); } throw new Error('ui.wait_for timed out'); })()`);
|
|
274
|
+
return this.evaluate(`(async () => { ${deepQueryHelpersExpression()} const deadline = Date.now() + ${JSON.stringify(timeoutMs)}; while (Date.now() <= deadline) { const ok = ${waitForPredicateExpression(options)}; if (ok) return { matched: true }; await new Promise((resolve) => setTimeout(resolve, 100)); } throw new Error('ui.wait_for timed out'); })()`);
|
|
230
275
|
}
|
|
231
276
|
async waitForSelector(selector, options = {}) {
|
|
232
277
|
return this.waitFor({ selector, timeoutMs: options.timeoutMs });
|
|
@@ -257,6 +302,33 @@ export class CdpWebPage {
|
|
|
257
302
|
mimeType: 'image/png',
|
|
258
303
|
};
|
|
259
304
|
}
|
|
305
|
+
async observe(refs) {
|
|
306
|
+
const observations = {};
|
|
307
|
+
const warnings = [];
|
|
308
|
+
for (const ref of refs) {
|
|
309
|
+
try {
|
|
310
|
+
if (ref === 'ui.screen') {
|
|
311
|
+
observations[ref] = await this.evaluate(`(() => { const hashPath = location.hash.split('?')[0]; const hashRoute = hashPath && !hashPath.includes('=') ? hashPath : undefined; return { provider: 'cdp-web', name: document.title || location.pathname || hashRoute || 'Browser', title: document.title || undefined, route: hashRoute || location.pathname || undefined, url: location.origin + location.pathname }; })()`);
|
|
312
|
+
}
|
|
313
|
+
else if (ref === 'ui.visible') {
|
|
314
|
+
observations[ref] = await this.evaluate(visibleTargetsExpression());
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
warnings.push({ ref, message: `Unsupported UI observer: ${ref}.` });
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
warnings.push({
|
|
322
|
+
ref,
|
|
323
|
+
message: error instanceof Error ? error.message : String(error),
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
...(Object.keys(observations).length ? { observations } : {}),
|
|
329
|
+
...(warnings.length ? { warnings } : {}),
|
|
330
|
+
};
|
|
331
|
+
}
|
|
260
332
|
close() {
|
|
261
333
|
this.session.close();
|
|
262
334
|
}
|
|
@@ -279,34 +351,36 @@ export function createCdpWebUiTransport(options) {
|
|
|
279
351
|
switch (action) {
|
|
280
352
|
case 'ui.navigate': {
|
|
281
353
|
const url = asString(node.url ?? node.target, 'ui.navigate.url');
|
|
282
|
-
return page.navigate(url
|
|
354
|
+
return executeSettledCdpAction(page, page.navigate(url, node.timeout_ms == null
|
|
355
|
+
? undefined
|
|
356
|
+
: asNumber(node.timeout_ms, 'ui.navigate.timeout_ms')), node);
|
|
283
357
|
}
|
|
284
358
|
case 'ui.press': {
|
|
285
359
|
const selector = selectorForUiInput(node);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
360
|
+
return executeSettledCdpAction(page, selector
|
|
361
|
+
? page.click(selector)
|
|
362
|
+
: page.clickText(asString(node.text ?? node.label, 'ui.press.text')), node);
|
|
289
363
|
}
|
|
290
364
|
case 'ui.key_press':
|
|
291
|
-
return page.keyPress(asString(node.key, 'ui.key_press.key'));
|
|
365
|
+
return executeSettledCdpAction(page, page.keyPress(asString(node.key, 'ui.key_press.key')), node);
|
|
292
366
|
case 'ui.set_input':
|
|
293
|
-
return page.setInput(asString(selectorForUiInput(node), 'ui.set_input.selector'), asString(node.value ?? node.text, 'ui.set_input.value'));
|
|
367
|
+
return executeSettledCdpAction(page, page.setInput(asString(selectorForUiInput(node), 'ui.set_input.selector'), asString(node.value ?? node.text, 'ui.set_input.value')), node);
|
|
294
368
|
case 'ui.scroll':
|
|
295
|
-
return page.scroll({
|
|
369
|
+
return executeSettledCdpAction(page, page.scroll({
|
|
296
370
|
selector: asOptionalString(selectorForUiInput(node), 'ui.scroll.selector'),
|
|
297
371
|
intoView: node.scroll_into_view === true || node.into_view === true,
|
|
298
372
|
deltaX: node.delta_x == null ? undefined : asNumber(node.delta_x, 'ui.scroll.delta_x'),
|
|
299
373
|
deltaY: node.delta_y == null ? undefined : asNumber(node.delta_y, 'ui.scroll.delta_y'),
|
|
300
|
-
});
|
|
374
|
+
}), node);
|
|
301
375
|
case 'ui.wait_for':
|
|
302
|
-
return page.waitFor({
|
|
376
|
+
return executeSettledCdpAction(page, page.waitFor({
|
|
303
377
|
selector: asOptionalString(selectorForUiInput(node), 'ui.wait_for.selector'),
|
|
304
378
|
text: asOptionalString(node.text, 'ui.wait_for.text'),
|
|
305
379
|
expected: asOptionalString(node.expected, 'ui.wait_for.expected'),
|
|
306
380
|
timeoutMs: node.timeout_ms == null
|
|
307
381
|
? undefined
|
|
308
382
|
: asNumber(node.timeout_ms, 'ui.wait_for.timeout_ms'),
|
|
309
|
-
});
|
|
383
|
+
}), node);
|
|
310
384
|
case 'ui.screenshot':
|
|
311
385
|
return captureCdpScreenshot(page, node, context);
|
|
312
386
|
case 'app.status':
|
|
@@ -322,8 +396,136 @@ export function createCdpWebUiTransport(options) {
|
|
|
322
396
|
}
|
|
323
397
|
});
|
|
324
398
|
},
|
|
399
|
+
async observe(refs, node, context) {
|
|
400
|
+
const input = { action: 'app.status', node, context };
|
|
401
|
+
return withCdpWebPage(options, input, async (page) => page.observe(refs));
|
|
402
|
+
},
|
|
325
403
|
};
|
|
326
404
|
}
|
|
405
|
+
async function executeSettledCdpAction(page, operation, node) {
|
|
406
|
+
const startedAt = Date.now();
|
|
407
|
+
const result = await operation;
|
|
408
|
+
if (node.settle === false)
|
|
409
|
+
return result;
|
|
410
|
+
// timeout_ms budgets the whole node: settlement only gets what the action left over.
|
|
411
|
+
const timeoutMs = node.timeout_ms == null
|
|
412
|
+
? undefined
|
|
413
|
+
: Math.max(1, asNumber(node.timeout_ms, 'ui action timeout_ms') - (Date.now() - startedAt));
|
|
414
|
+
try {
|
|
415
|
+
await page.waitForDomSettled(timeoutMs);
|
|
416
|
+
return result;
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
const settlementWarning = error instanceof Error ? error.message : String(error);
|
|
420
|
+
if (typeof result === 'object' && result !== null && !Array.isArray(result)) {
|
|
421
|
+
return { ...result, settlementWarning };
|
|
422
|
+
}
|
|
423
|
+
return { result, settlementWarning };
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function visibleTargetsExpression() {
|
|
427
|
+
return `(() => {
|
|
428
|
+
const visibleLimit = 20;
|
|
429
|
+
const hiddenLimit = 10;
|
|
430
|
+
const selector = [
|
|
431
|
+
'button',
|
|
432
|
+
'a[href]',
|
|
433
|
+
'input',
|
|
434
|
+
'textarea',
|
|
435
|
+
'select',
|
|
436
|
+
'[role="button"]',
|
|
437
|
+
'[role="link"]',
|
|
438
|
+
'[role="menuitem"]',
|
|
439
|
+
'[role="tab"]'
|
|
440
|
+
].join(',');
|
|
441
|
+
${deepQueryHelpersExpression()}
|
|
442
|
+
const nodes = querySelectorAllDeep(selector);
|
|
443
|
+
const textFor = (el) => {
|
|
444
|
+
const raw = el.getAttribute('aria-label') || el.getAttribute('title') || el.getAttribute('placeholder') || el.innerText || '';
|
|
445
|
+
return String(raw).replace(/\\s+/g, ' ').trim().slice(0, 120) || undefined;
|
|
446
|
+
};
|
|
447
|
+
const cssPath = (el) => {
|
|
448
|
+
if (el.id) return '#' + CSS.escape(el.id);
|
|
449
|
+
const testAttribute = ['data-testid', 'data-test-id', 'data-test'].find((attribute) => el.hasAttribute(attribute));
|
|
450
|
+
if (testAttribute) {
|
|
451
|
+
const testId = CSS.escape(String(el.getAttribute(testAttribute)));
|
|
452
|
+
return '[' + testAttribute + '="' + testId + '"]';
|
|
453
|
+
}
|
|
454
|
+
if (el.getRootNode() instanceof ShadowRoot) return undefined;
|
|
455
|
+
const parts = [];
|
|
456
|
+
let current = el;
|
|
457
|
+
while (current && current.nodeType === Node.ELEMENT_NODE) {
|
|
458
|
+
if (current.id) {
|
|
459
|
+
parts.unshift('#' + CSS.escape(current.id));
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
const tag = current.tagName.toLowerCase();
|
|
463
|
+
const parent = current.parentElement;
|
|
464
|
+
if (!parent) {
|
|
465
|
+
parts.unshift(tag);
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
const siblings = Array.from(parent.children).filter((child) => child.tagName === current.tagName);
|
|
469
|
+
parts.unshift(siblings.length > 1 ? tag + ':nth-of-type(' + (siblings.indexOf(current) + 1) + ')' : tag);
|
|
470
|
+
current = parent;
|
|
471
|
+
}
|
|
472
|
+
return parts.join(' > ');
|
|
473
|
+
};
|
|
474
|
+
const itemFor = (el, rect, includeLabel) => ({
|
|
475
|
+
role: el.getAttribute('role') || (el.tagName.toLowerCase() === 'a' ? 'link' : el.tagName.toLowerCase() === 'button' ? 'button' : undefined),
|
|
476
|
+
label: includeLabel ? textFor(el) : undefined,
|
|
477
|
+
test_id: el.getAttribute('data-testid') || el.getAttribute('data-test-id') || el.getAttribute('data-test') || undefined,
|
|
478
|
+
selector: cssPath(el),
|
|
479
|
+
enabled: !(el.disabled || el.getAttribute('aria-disabled') === 'true'),
|
|
480
|
+
selected: el.getAttribute('aria-selected') === 'true' || undefined,
|
|
481
|
+
focused: document.activeElement === el || undefined,
|
|
482
|
+
bounds: rect ? { x: Math.round(rect.left), y: Math.round(rect.top), width: Math.round(rect.width), height: Math.round(rect.height) } : undefined
|
|
483
|
+
});
|
|
484
|
+
const composedContains = (ancestor, descendant) => {
|
|
485
|
+
let current = descendant;
|
|
486
|
+
while (current) {
|
|
487
|
+
if (current === ancestor) return true;
|
|
488
|
+
const root = current.getRootNode();
|
|
489
|
+
current = current.parentElement || (root instanceof ShadowRoot ? root.host : null);
|
|
490
|
+
}
|
|
491
|
+
return false;
|
|
492
|
+
};
|
|
493
|
+
const isCoveredDeep = (el, rect) => {
|
|
494
|
+
const x = rect.left + rect.width / 2;
|
|
495
|
+
const y = rect.top + rect.height / 2;
|
|
496
|
+
if (x < 0 || y < 0 || x > window.innerWidth || y > window.innerHeight) return false;
|
|
497
|
+
let hit = document.elementFromPoint(x, y);
|
|
498
|
+
while (hit && hit.shadowRoot) {
|
|
499
|
+
const deeper = hit.shadowRoot.elementFromPoint(x, y);
|
|
500
|
+
if (!deeper || deeper === hit) break;
|
|
501
|
+
hit = deeper;
|
|
502
|
+
}
|
|
503
|
+
if (!hit) return false;
|
|
504
|
+
return !composedContains(el, hit);
|
|
505
|
+
};
|
|
506
|
+
const items = [];
|
|
507
|
+
const hidden_or_offscreen = [];
|
|
508
|
+
for (const el of nodes) {
|
|
509
|
+
const rect = el.getBoundingClientRect();
|
|
510
|
+
const hasBox = rect.width > 0 && rect.height > 0;
|
|
511
|
+
const rendered = hasBox && isRenderedDeep(el);
|
|
512
|
+
const visible = rendered && isVisibleDeep(el);
|
|
513
|
+
const covered = visible && isCoveredDeep(el, rect);
|
|
514
|
+
if (visible && !covered && items.length < visibleLimit) {
|
|
515
|
+
items.push(itemFor(el, rect, true));
|
|
516
|
+
} else if ((!visible || covered) && hidden_or_offscreen.length < hiddenLimit) {
|
|
517
|
+
hidden_or_offscreen.push({ ...itemFor(el, rendered ? rect : undefined, rendered), reason: covered ? 'covered' : rendered ? 'offscreen' : 'hidden_or_no_box' });
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return {
|
|
521
|
+
provider: 'cdp-web',
|
|
522
|
+
items,
|
|
523
|
+
hidden_or_offscreen,
|
|
524
|
+
truncated: nodes.length > items.length + hidden_or_offscreen.length,
|
|
525
|
+
limits: { items: visibleLimit, hidden_or_offscreen: hiddenLimit }
|
|
526
|
+
};
|
|
527
|
+
})()`;
|
|
528
|
+
}
|
|
327
529
|
async function withCdpWebPage(options, input, callback) {
|
|
328
530
|
if (options.withPage)
|
|
329
531
|
return options.withPage(input, callback);
|
|
@@ -544,15 +746,15 @@ function waitForPredicateExpression(options) {
|
|
|
544
746
|
if (options.selector && options.text) {
|
|
545
747
|
const selector = JSON.stringify(options.selector);
|
|
546
748
|
const text = JSON.stringify(options.text);
|
|
547
|
-
presentExpression = `Boolean(
|
|
548
|
-
visibleExpression = `${visibleSelectorExpression(options.selector)} &&
|
|
749
|
+
presentExpression = `Boolean(querySelectorDeep(${selector})) && textIncludesDeep(${text})`;
|
|
750
|
+
visibleExpression = `${visibleSelectorExpression(options.selector)} && textIncludesDeep(${text})`;
|
|
549
751
|
}
|
|
550
752
|
else if (options.selector) {
|
|
551
|
-
presentExpression = `Boolean(
|
|
753
|
+
presentExpression = `Boolean(querySelectorDeep(${JSON.stringify(options.selector)}))`;
|
|
552
754
|
visibleExpression = visibleSelectorExpression(options.selector);
|
|
553
755
|
}
|
|
554
756
|
else if (options.text) {
|
|
555
|
-
presentExpression = `
|
|
757
|
+
presentExpression = `textIncludesDeep(${JSON.stringify(options.text)})`;
|
|
556
758
|
}
|
|
557
759
|
else {
|
|
558
760
|
throw new Error('ui.wait_for requires selector or text.');
|
|
@@ -573,7 +775,88 @@ function waitForPredicateExpression(options) {
|
|
|
573
775
|
return presentExpression;
|
|
574
776
|
}
|
|
575
777
|
function visibleSelectorExpression(selector) {
|
|
576
|
-
return `(() => { const el =
|
|
778
|
+
return `(() => { const el = querySelectorDeep(${JSON.stringify(selector)}); return Boolean(el && isVisibleDeep(el)); })()`;
|
|
779
|
+
}
|
|
780
|
+
function deepQueryHelpersExpression() {
|
|
781
|
+
return `
|
|
782
|
+
const querySelectorAllDeep = (selector, root = document) => {
|
|
783
|
+
const matches = Array.from(root.querySelectorAll(selector));
|
|
784
|
+
for (const element of root.querySelectorAll('*')) {
|
|
785
|
+
if (element.shadowRoot) matches.push(...querySelectorAllDeep(selector, element.shadowRoot));
|
|
786
|
+
}
|
|
787
|
+
return matches;
|
|
788
|
+
};
|
|
789
|
+
const querySelectorDeep = (selector, root = document) => querySelectorAllDeep(selector, root)[0] ?? null;
|
|
790
|
+
const isRenderedDeep = (element) => {
|
|
791
|
+
if (!element || element.getClientRects().length === 0) return false;
|
|
792
|
+
let current = element;
|
|
793
|
+
while (current) {
|
|
794
|
+
const style = getComputedStyle(current);
|
|
795
|
+
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity || 1) <= 0) return false;
|
|
796
|
+
const root = current.getRootNode();
|
|
797
|
+
current = current.parentElement || (root instanceof ShadowRoot ? root.host : null);
|
|
798
|
+
}
|
|
799
|
+
return true;
|
|
800
|
+
};
|
|
801
|
+
const isVisibleDeep = (element) => {
|
|
802
|
+
if (!isRenderedDeep(element)) return false;
|
|
803
|
+
let rect = element.getBoundingClientRect();
|
|
804
|
+
let current = element;
|
|
805
|
+
while (current) {
|
|
806
|
+
const root = current.getRootNode();
|
|
807
|
+
const parent = current.parentElement || (root instanceof ShadowRoot ? root.host : null);
|
|
808
|
+
if (!parent) break;
|
|
809
|
+
const style = getComputedStyle(parent);
|
|
810
|
+
if (/(hidden|clip|scroll|auto)/.test(style.overflow + style.overflowX + style.overflowY)) {
|
|
811
|
+
const parentRect = parent.getBoundingClientRect();
|
|
812
|
+
rect = { left: Math.max(rect.left, parentRect.left), top: Math.max(rect.top, parentRect.top), right: Math.min(rect.right, parentRect.right), bottom: Math.min(rect.bottom, parentRect.bottom) };
|
|
813
|
+
if (rect.right <= rect.left || rect.bottom <= rect.top) return false;
|
|
814
|
+
}
|
|
815
|
+
current = parent;
|
|
816
|
+
}
|
|
817
|
+
return rect.right > 0 && rect.bottom > 0 && rect.left < window.innerWidth && rect.top < window.innerHeight;
|
|
818
|
+
};
|
|
819
|
+
const clickablePointDeep = (element) => {
|
|
820
|
+
if (!isVisibleDeep(element)) throw new Error('Target is not visible');
|
|
821
|
+
if (element.disabled || element.getAttribute('aria-disabled') === 'true') throw new Error('Target is disabled');
|
|
822
|
+
const rect = element.getBoundingClientRect();
|
|
823
|
+
const x = Math.max(0, Math.min(window.innerWidth - 1, rect.left + rect.width / 2));
|
|
824
|
+
const y = Math.max(0, Math.min(window.innerHeight - 1, rect.top + rect.height / 2));
|
|
825
|
+
let hit = document.elementFromPoint(x, y);
|
|
826
|
+
while (hit && hit.shadowRoot) {
|
|
827
|
+
const deeper = hit.shadowRoot.elementFromPoint(x, y);
|
|
828
|
+
if (!deeper || deeper === hit) break;
|
|
829
|
+
hit = deeper;
|
|
830
|
+
}
|
|
831
|
+
const composedContains = (ancestor, descendant) => {
|
|
832
|
+
let current = descendant;
|
|
833
|
+
while (current) {
|
|
834
|
+
if (current === ancestor) return true;
|
|
835
|
+
const root = current.getRootNode();
|
|
836
|
+
current = current.parentElement || (root instanceof ShadowRoot ? root.host : null);
|
|
837
|
+
}
|
|
838
|
+
return false;
|
|
839
|
+
};
|
|
840
|
+
if (!hit || !composedContains(element, hit)) throw new Error('Target is obscured');
|
|
841
|
+
return { x, y };
|
|
842
|
+
};
|
|
843
|
+
const renderedTextDeep = (root = document) => {
|
|
844
|
+
const chunks = [];
|
|
845
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
846
|
+
let node = walker.nextNode();
|
|
847
|
+
while (node) {
|
|
848
|
+
if (node.parentElement && isRenderedDeep(node.parentElement)) chunks.push(node.nodeValue || '');
|
|
849
|
+
node = walker.nextNode();
|
|
850
|
+
}
|
|
851
|
+
for (const element of root.querySelectorAll('*')) {
|
|
852
|
+
if (element.shadowRoot && isRenderedDeep(element)) {
|
|
853
|
+
chunks.push(renderedTextDeep(element.shadowRoot));
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
return chunks.join('\\n');
|
|
857
|
+
};
|
|
858
|
+
const textIncludesDeep = (text) => renderedTextDeep().includes(text);
|
|
859
|
+
`;
|
|
577
860
|
}
|
|
578
861
|
function escapeForJsMessage(value) {
|
|
579
862
|
return value.replaceAll('\\', '\\\\').replaceAll("'", "\\'");
|