feat(browser): add scrollintoview action
This commit is contained in:
@@ -36,6 +36,12 @@ export type BrowserActRequest =
|
|||||||
}
|
}
|
||||||
| { kind: "press"; key: string; targetId?: string; delayMs?: number }
|
| { kind: "press"; key: string; targetId?: string; delayMs?: number }
|
||||||
| { kind: "hover"; ref: string; targetId?: string; timeoutMs?: number }
|
| { kind: "hover"; ref: string; targetId?: string; timeoutMs?: number }
|
||||||
|
| {
|
||||||
|
kind: "scrollIntoView";
|
||||||
|
ref: string;
|
||||||
|
targetId?: string;
|
||||||
|
timeoutMs?: number;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
kind: "drag";
|
kind: "drag";
|
||||||
startRef: string;
|
startRef: string;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export {
|
|||||||
pressKeyViaPlaywright,
|
pressKeyViaPlaywright,
|
||||||
resizeViewportViaPlaywright,
|
resizeViewportViaPlaywright,
|
||||||
responseBodyViaPlaywright,
|
responseBodyViaPlaywright,
|
||||||
|
scrollIntoViewViaPlaywright,
|
||||||
selectOptionViaPlaywright,
|
selectOptionViaPlaywright,
|
||||||
setDeviceViaPlaywright,
|
setDeviceViaPlaywright,
|
||||||
setExtraHTTPHeadersViaPlaywright,
|
setExtraHTTPHeadersViaPlaywright,
|
||||||
|
|||||||
@@ -1160,6 +1160,25 @@ export async function responseBodyViaPlaywright(opts: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function scrollIntoViewViaPlaywright(opts: {
|
||||||
|
cdpUrl: string;
|
||||||
|
targetId?: string;
|
||||||
|
ref: string;
|
||||||
|
timeoutMs?: number;
|
||||||
|
}): Promise<void> {
|
||||||
|
const page = await getPageForTargetId(opts);
|
||||||
|
ensurePageState(page);
|
||||||
|
const timeout = normalizeTimeoutMs(opts.timeoutMs, 20_000);
|
||||||
|
|
||||||
|
const ref = requireRef(opts.ref);
|
||||||
|
const locator = refLocator(page, ref);
|
||||||
|
try {
|
||||||
|
await locator.scrollIntoViewIfNeeded({ timeout });
|
||||||
|
} catch (err) {
|
||||||
|
throw toAIFriendlyError(err, ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function navigateViaPlaywright(opts: {
|
export async function navigateViaPlaywright(opts: {
|
||||||
cdpUrl: string;
|
cdpUrl: string;
|
||||||
targetId?: string;
|
targetId?: string;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ type ActKind =
|
|||||||
| "evaluate"
|
| "evaluate"
|
||||||
| "fill"
|
| "fill"
|
||||||
| "hover"
|
| "hover"
|
||||||
|
| "scrollIntoView"
|
||||||
| "press"
|
| "press"
|
||||||
| "resize"
|
| "resize"
|
||||||
| "select"
|
| "select"
|
||||||
@@ -154,6 +155,7 @@ export function registerBrowserAgentRoutes(
|
|||||||
kind !== "evaluate" &&
|
kind !== "evaluate" &&
|
||||||
kind !== "fill" &&
|
kind !== "fill" &&
|
||||||
kind !== "hover" &&
|
kind !== "hover" &&
|
||||||
|
kind !== "scrollIntoView" &&
|
||||||
kind !== "press" &&
|
kind !== "press" &&
|
||||||
kind !== "resize" &&
|
kind !== "resize" &&
|
||||||
kind !== "select" &&
|
kind !== "select" &&
|
||||||
@@ -257,6 +259,21 @@ export function registerBrowserAgentRoutes(
|
|||||||
});
|
});
|
||||||
return res.json({ ok: true, targetId: tab.targetId });
|
return res.json({ ok: true, targetId: tab.targetId });
|
||||||
}
|
}
|
||||||
|
case "scrollIntoView": {
|
||||||
|
const ref = toStringOrEmpty(body.ref);
|
||||||
|
if (!ref) return jsonError(res, 400, "ref is required");
|
||||||
|
const timeoutMs = toNumber(body.timeoutMs);
|
||||||
|
const scrollRequest: Parameters<
|
||||||
|
typeof pw.scrollIntoViewViaPlaywright
|
||||||
|
>[0] = {
|
||||||
|
cdpUrl,
|
||||||
|
targetId: tab.targetId,
|
||||||
|
ref,
|
||||||
|
};
|
||||||
|
if (timeoutMs) scrollRequest.timeoutMs = timeoutMs;
|
||||||
|
await pw.scrollIntoViewViaPlaywright(scrollRequest);
|
||||||
|
return res.json({ ok: true, targetId: tab.targetId });
|
||||||
|
}
|
||||||
case "drag": {
|
case "drag": {
|
||||||
const startRef = toStringOrEmpty(body.startRef);
|
const startRef = toStringOrEmpty(body.startRef);
|
||||||
const endRef = toStringOrEmpty(body.endRef);
|
const endRef = toStringOrEmpty(body.endRef);
|
||||||
|
|||||||
@@ -272,6 +272,50 @@ export function registerBrowserActionInputCommands(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
browser
|
||||||
|
.command("scrollintoview")
|
||||||
|
.description("Scroll an element into view by ref from snapshot")
|
||||||
|
.argument("<ref>", "Ref id from snapshot")
|
||||||
|
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||||
|
.option(
|
||||||
|
"--timeout-ms <ms>",
|
||||||
|
"How long to wait for scroll (default: 20000)",
|
||||||
|
(v: string) => Number(v),
|
||||||
|
)
|
||||||
|
.action(async (ref: string | undefined, opts, cmd) => {
|
||||||
|
const parent = parentOpts(cmd);
|
||||||
|
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||||
|
const profile = parent?.browserProfile;
|
||||||
|
const refValue = typeof ref === "string" ? ref.trim() : "";
|
||||||
|
if (!refValue) {
|
||||||
|
defaultRuntime.error(danger("ref is required"));
|
||||||
|
defaultRuntime.exit(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const result = await browserAct(
|
||||||
|
baseUrl,
|
||||||
|
{
|
||||||
|
kind: "scrollIntoView",
|
||||||
|
ref: refValue,
|
||||||
|
targetId: opts.targetId?.trim() || undefined,
|
||||||
|
timeoutMs: Number.isFinite(opts.timeoutMs)
|
||||||
|
? opts.timeoutMs
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
|
{ profile },
|
||||||
|
);
|
||||||
|
if (parent?.json) {
|
||||||
|
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
defaultRuntime.log(`scrolled into view: ${refValue}`);
|
||||||
|
} catch (err) {
|
||||||
|
defaultRuntime.error(danger(String(err)));
|
||||||
|
defaultRuntime.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
browser
|
browser
|
||||||
.command("drag")
|
.command("drag")
|
||||||
.description("Drag from one ref to another")
|
.description("Drag from one ref to another")
|
||||||
|
|||||||
Reference in New Issue
Block a user