fix(browser): refresh target after guarded existing-session actions (#104094)

* fix(browser): refresh target after guarded existing-session actions

* test(browser): isolate guarded target cases

* test(browser): mock safe replacement URL resolution

* fix(browser): refresh target after guarded evaluate

* style(browser): format guarded action matrix

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
llagy007
2026-07-11 16:11:48 -07:00
committed by GitHub
co-authored by Peter Steinberger
parent 6852016b51
commit c059d7d062
3 changed files with 42 additions and 6 deletions
@@ -43,6 +43,14 @@ vi.mock("../navigation-guard.js", () => navigationGuardMocks);
vi.mock("./agent.shared.js", () => createExistingSessionAgentSharedModule());
const DEFAULT_SSRF_POLICY = { allowPrivateNetwork: false } as const;
const GUARDED_TARGET_REFRESH_ACTIONS = [
{ kind: "hover", ref: "btn-1" },
{ kind: "scrollIntoView", ref: "btn-1" },
{ kind: "drag", startRef: "item-1", endRef: "slot-1" },
{ kind: "select", ref: "menu-1", values: ["alpha"] },
{ kind: "fill", fields: [{ ref: "input-1", value: "Ada" }] },
{ kind: "evaluate", fn: "() => document.title" },
] as const;
const { registerBrowserAgentActRoutes } = await import("./agent.act.js");
const routeState = existingSessionRouteState;
@@ -150,6 +158,24 @@ describe("existing-session interaction navigation guard", () => {
expectNavigationProbeUrls(Array.from({ length: 8 }, () => "https://example.com"));
});
it.each(GUARDED_TARGET_REFRESH_ACTIONS)(
"resolves current target after guarded $kind interaction",
async (body) => {
routeState.profileCtx.listTabs
.mockResolvedValueOnce([routeState.tab])
.mockResolvedValue([{ targetId: "new-target", url: routeState.tab.url }]);
const response = await runAction(body);
expect(response.statusCode).toBe(200);
expect(response.body).toMatchObject({
ok: true,
targetId: "new-target",
url: routeState.tab.url,
});
},
);
it("threads one request budget through coordinate actions and navigation probes", async () => {
const handler = getActPostHandler();
const response = createBrowserRouteResponse();
@@ -542,7 +542,7 @@ export function registerBrowserAgentActRoutes(
}),
guard: existingSessionNavigationGuard,
});
return await jsonOk();
return await jsonOk(undefined, { resolveCurrentTarget: true });
case "scrollIntoView":
await runExistingSessionActionWithNavigationGuard({
execute: () =>
@@ -553,7 +553,7 @@ export function registerBrowserAgentActRoutes(
}),
guard: existingSessionNavigationGuard,
});
return await jsonOk();
return await jsonOk(undefined, { resolveCurrentTarget: true });
case "drag":
await runExistingSessionActionWithNavigationGuard({
execute: () =>
@@ -564,7 +564,7 @@ export function registerBrowserAgentActRoutes(
}),
guard: existingSessionNavigationGuard,
});
return await jsonOk();
return await jsonOk(undefined, { resolveCurrentTarget: true });
case "select":
await runExistingSessionActionWithNavigationGuard({
execute: () =>
@@ -575,7 +575,7 @@ export function registerBrowserAgentActRoutes(
}),
guard: existingSessionNavigationGuard,
});
return await jsonOk();
return await jsonOk(undefined, { resolveCurrentTarget: true });
case "fill":
await runExistingSessionActionWithNavigationGuard({
execute: () =>
@@ -588,7 +588,7 @@ export function registerBrowserAgentActRoutes(
}),
guard: existingSessionNavigationGuard,
});
return await jsonOk();
return await jsonOk(undefined, { resolveCurrentTarget: true });
case "resize":
await resizeChromeMcpPage({
...existingSessionTarget,
@@ -621,7 +621,7 @@ export function registerBrowserAgentActRoutes(
}),
guard: existingSessionNavigationGuard,
});
return await jsonOk({ result });
return await jsonOk({ result }, { resolveCurrentTarget: true });
}
case "close":
await profileCtx.closeTab(tab.targetId, {
@@ -54,6 +54,16 @@ export function createExistingSessionAgentSharedModule() {
throw new Error("Playwright should not be used for existing-session tests");
}),
resolveProfileContext: vi.fn(() => existingSessionRouteState.profileCtx),
resolveSafeRouteTabUrl: vi.fn(
async (params: {
profileCtx: typeof existingSessionRouteState.profileCtx;
targetId: string;
fallbackUrl?: string;
}) => {
const tabs = await params.profileCtx.listTabs();
return tabs.find((tab) => tab.targetId === params.targetId)?.url ?? params.fallbackUrl;
},
),
resolveTargetIdFromBody: vi.fn((body: Record<string, unknown>) =>
typeof body.targetId === "string" ? body.targetId : undefined,
),