Browser tools: full accessibility snapshot, trusted input, dialogs, evaluate, and tab controls (#1881)
* feat(browser): replace flat snapshot with ARIA tree and page-side refs * feat(browser): trusted CDP input with actionability waits * feat(browser): auto-handle javascript dialogs * feat(browser): add browser_evaluate * feat(browser): add scroll, resize, and close_tab tools * fix(browser): review fixes for snapshot/input/dialog seams * fix(browser): address review findings on dialog capture, truncation, and keypress targets * fix(harness): drop resize check that cannot run under parked compositor surfaces * fix(browser): second review round — snapshot fidelity, key input, dialog scoping
This commit is contained in:
@@ -15,7 +15,39 @@ const commandParseCases = [
|
||||
{
|
||||
name: "click",
|
||||
command: { command: "click", args: { browserId: BROWSER_ID, ref: "@e1" } },
|
||||
expected: { command: "click", args: { browserId: BROWSER_ID, ref: "@e1" } },
|
||||
expected: {
|
||||
command: "click",
|
||||
args: {
|
||||
browserId: BROWSER_ID,
|
||||
ref: "@e1",
|
||||
button: "left",
|
||||
doubleClick: false,
|
||||
modifiers: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "click options",
|
||||
command: {
|
||||
command: "click",
|
||||
args: {
|
||||
browserId: BROWSER_ID,
|
||||
ref: "@e1",
|
||||
button: "right",
|
||||
doubleClick: true,
|
||||
modifiers: ["Meta", "Shift"],
|
||||
},
|
||||
},
|
||||
expected: {
|
||||
command: "click",
|
||||
args: {
|
||||
browserId: BROWSER_ID,
|
||||
ref: "@e1",
|
||||
button: "right",
|
||||
doubleClick: true,
|
||||
modifiers: ["Meta", "Shift"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fill",
|
||||
@@ -123,6 +155,72 @@ const commandParseCases = [
|
||||
command: { command: "logs", args: { browserId: BROWSER_ID } },
|
||||
expected: { command: "logs", args: { browserId: BROWSER_ID, maxEntries: 50 } },
|
||||
},
|
||||
{
|
||||
name: "evaluate",
|
||||
command: {
|
||||
command: "evaluate",
|
||||
args: { browserId: BROWSER_ID, function: "() => document.title" },
|
||||
},
|
||||
expected: {
|
||||
command: "evaluate",
|
||||
args: { browserId: BROWSER_ID, function: "() => document.title" },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "evaluate with ref",
|
||||
command: {
|
||||
command: "evaluate",
|
||||
args: { browserId: BROWSER_ID, function: "(element) => element.textContent", ref: "@e1" },
|
||||
},
|
||||
expected: {
|
||||
command: "evaluate",
|
||||
args: { browserId: BROWSER_ID, function: "(element) => element.textContent", ref: "@e1" },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "scroll",
|
||||
command: {
|
||||
command: "scroll",
|
||||
args: { browserId: BROWSER_ID, deltaX: 0, deltaY: 400 },
|
||||
},
|
||||
expected: {
|
||||
command: "scroll",
|
||||
args: { browserId: BROWSER_ID, deltaX: 0, deltaY: 400 },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "scroll with ref",
|
||||
command: {
|
||||
command: "scroll",
|
||||
args: { browserId: BROWSER_ID, ref: "@e1", deltaX: 10, deltaY: -20 },
|
||||
},
|
||||
expected: {
|
||||
command: "scroll",
|
||||
args: { browserId: BROWSER_ID, ref: "@e1", deltaX: 10, deltaY: -20 },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "resize",
|
||||
command: {
|
||||
command: "resize",
|
||||
args: { browserId: BROWSER_ID, width: 1024, height: 768 },
|
||||
},
|
||||
expected: {
|
||||
command: "resize",
|
||||
args: { browserId: BROWSER_ID, width: 1024, height: 768 },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "close_tab",
|
||||
command: {
|
||||
command: "close_tab",
|
||||
args: { browserId: BROWSER_ID },
|
||||
},
|
||||
expected: {
|
||||
command: "close_tab",
|
||||
args: { browserId: BROWSER_ID },
|
||||
},
|
||||
},
|
||||
] as const;
|
||||
|
||||
const resultParseCases = [
|
||||
@@ -134,16 +232,10 @@ const resultParseCases = [
|
||||
workspaceId: "workspace-1",
|
||||
url: "https://example.com/form",
|
||||
title: "Fixture",
|
||||
elements: [
|
||||
{
|
||||
ref: "@e1",
|
||||
role: "textbox",
|
||||
tagName: "input",
|
||||
text: "Name",
|
||||
selector: "#name",
|
||||
attributes: { id: "name", type: "text" },
|
||||
},
|
||||
],
|
||||
format: "aria-yaml",
|
||||
snapshot: '- document "Fixture"\n - textbox "Name" [ref=@e1]',
|
||||
truncated: false,
|
||||
stats: { nodeCount: 2, refCount: 1, textLength: 52, iframeCount: 0, maxDepth: 1 },
|
||||
},
|
||||
expected: {
|
||||
command: "snapshot",
|
||||
@@ -151,16 +243,10 @@ const resultParseCases = [
|
||||
workspaceId: "workspace-1",
|
||||
url: "https://example.com/form",
|
||||
title: "Fixture",
|
||||
elements: [
|
||||
{
|
||||
ref: "@e1",
|
||||
role: "textbox",
|
||||
tagName: "input",
|
||||
text: "Name",
|
||||
selector: "#name",
|
||||
attributes: { id: "name", type: "text" },
|
||||
},
|
||||
],
|
||||
format: "aria-yaml",
|
||||
snapshot: '- document "Fixture"\n - textbox "Name" [ref=@e1]',
|
||||
truncated: false,
|
||||
stats: { nodeCount: 2, refCount: 1, textLength: 52, iframeCount: 0, maxDepth: 1 },
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -305,6 +391,68 @@ const resultParseCases = [
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "evaluate",
|
||||
result: {
|
||||
command: "evaluate",
|
||||
browserId: BROWSER_ID,
|
||||
resultJson: '{"title":"Fixture"}',
|
||||
truncated: false,
|
||||
},
|
||||
expected: {
|
||||
command: "evaluate",
|
||||
browserId: BROWSER_ID,
|
||||
resultJson: '{"title":"Fixture"}',
|
||||
truncated: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "scroll",
|
||||
result: {
|
||||
command: "scroll",
|
||||
browserId: BROWSER_ID,
|
||||
ref: "@e1",
|
||||
deltaX: 10,
|
||||
deltaY: 400,
|
||||
x: 40,
|
||||
y: 30,
|
||||
},
|
||||
expected: {
|
||||
command: "scroll",
|
||||
browserId: BROWSER_ID,
|
||||
ref: "@e1",
|
||||
deltaX: 10,
|
||||
deltaY: 400,
|
||||
x: 40,
|
||||
y: 30,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "resize",
|
||||
result: {
|
||||
command: "resize",
|
||||
browserId: BROWSER_ID,
|
||||
width: 1024,
|
||||
height: 768,
|
||||
},
|
||||
expected: {
|
||||
command: "resize",
|
||||
browserId: BROWSER_ID,
|
||||
width: 1024,
|
||||
height: 768,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "close_tab",
|
||||
result: {
|
||||
command: "close_tab",
|
||||
browserId: BROWSER_ID,
|
||||
},
|
||||
expected: {
|
||||
command: "close_tab",
|
||||
browserId: BROWSER_ID,
|
||||
},
|
||||
},
|
||||
] as const;
|
||||
|
||||
describe("browser automation execute RPC schemas", () => {
|
||||
@@ -526,7 +674,10 @@ describe("browser automation execute RPC schemas", () => {
|
||||
workspaceId: "workspace-1",
|
||||
url: "https://example.com",
|
||||
title: "Example",
|
||||
elements: [],
|
||||
format: "aria-yaml",
|
||||
snapshot: "- document",
|
||||
truncated: false,
|
||||
stats: { nodeCount: 1, refCount: 0, textLength: 10 },
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -557,6 +708,53 @@ describe("browser automation execute RPC schemas", () => {
|
||||
},
|
||||
);
|
||||
|
||||
test("success responses can report handled dialogs", () => {
|
||||
const parsed = BrowserAutomationExecuteResponseSchema.parse({
|
||||
type: "browser.automation.execute.response",
|
||||
payload: {
|
||||
requestId: "req-click",
|
||||
ok: true,
|
||||
result: { command: "click", browserId: BROWSER_ID, ref: "@e1" },
|
||||
dialogs: [
|
||||
{
|
||||
type: "alert",
|
||||
message: "Saved",
|
||||
action: "accepted",
|
||||
timestamp: 123,
|
||||
},
|
||||
{
|
||||
type: "prompt",
|
||||
message: "Name?",
|
||||
defaultValue: "Maya",
|
||||
action: "dismissed",
|
||||
timestamp: 124,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(parsed.payload).toEqual({
|
||||
requestId: "req-click",
|
||||
ok: true,
|
||||
result: { command: "click", browserId: BROWSER_ID, ref: "@e1" },
|
||||
dialogs: [
|
||||
{
|
||||
type: "alert",
|
||||
message: "Saved",
|
||||
action: "accepted",
|
||||
timestamp: 123,
|
||||
},
|
||||
{
|
||||
type: "prompt",
|
||||
message: "Name?",
|
||||
defaultValue: "Maya",
|
||||
action: "dismissed",
|
||||
timestamp: 124,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("error responses keep stable codes, messages, and retry defaults", () => {
|
||||
const parsed = BrowserAutomationExecuteResponseSchema.parse({
|
||||
type: "browser.automation.execute.response",
|
||||
@@ -580,4 +778,44 @@ describe("browser automation execute RPC schemas", () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("failure responses can report handled dialogs", () => {
|
||||
const parsed = BrowserAutomationExecuteResponseSchema.parse({
|
||||
type: "browser.automation.execute.response",
|
||||
payload: {
|
||||
requestId: "req-navigate",
|
||||
ok: false,
|
||||
error: {
|
||||
code: "browser_timeout",
|
||||
message: "Timed out waiting for browser URL: /next",
|
||||
},
|
||||
dialogs: [
|
||||
{
|
||||
type: "beforeunload",
|
||||
message: "Leave site?",
|
||||
action: "dismissed",
|
||||
timestamp: 200,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(parsed.payload).toEqual({
|
||||
requestId: "req-navigate",
|
||||
ok: false,
|
||||
error: {
|
||||
code: "browser_timeout",
|
||||
message: "Timed out waiting for browser URL: /next",
|
||||
retryable: false,
|
||||
},
|
||||
dialogs: [
|
||||
{
|
||||
type: "beforeunload",
|
||||
message: "Leave site?",
|
||||
action: "dismissed",
|
||||
timestamp: 200,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,6 +39,10 @@ export const BROWSER_AUTOMATION_COMMAND_NAMES = [
|
||||
"hover",
|
||||
"drag",
|
||||
"logs",
|
||||
"evaluate",
|
||||
"scroll",
|
||||
"resize",
|
||||
"close_tab",
|
||||
] as const;
|
||||
|
||||
export const BrowserAutomationCommandNameSchema = z.enum(BROWSER_AUTOMATION_COMMAND_NAMES);
|
||||
@@ -55,6 +59,8 @@ const BrowserAutomationTabTargetSchema = z
|
||||
.strict();
|
||||
|
||||
const BrowserAutomationRefSchema = z.string().regex(/^@e\d+$/);
|
||||
const BrowserAutomationMouseButtonSchema = z.enum(["left", "right", "middle"]);
|
||||
const BrowserAutomationInputModifierSchema = z.enum(["Alt", "Control", "Meta", "Shift"]);
|
||||
const BrowserAutomationHttpUrlSchema = z
|
||||
.string()
|
||||
.url()
|
||||
@@ -87,6 +93,9 @@ export const BrowserAutomationClickCommandSchema = z.object({
|
||||
command: z.literal("click"),
|
||||
args: BrowserAutomationTabTargetSchema.extend({
|
||||
ref: BrowserAutomationRefSchema,
|
||||
button: BrowserAutomationMouseButtonSchema.default("left"),
|
||||
doubleClick: z.boolean().default(false),
|
||||
modifiers: z.array(BrowserAutomationInputModifierSchema).default([]),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -192,6 +201,36 @@ export const BrowserAutomationLogsCommandSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export const BrowserAutomationEvaluateCommandSchema = z.object({
|
||||
command: z.literal("evaluate"),
|
||||
args: BrowserAutomationTabTargetSchema.extend({
|
||||
function: z.string().min(1),
|
||||
ref: BrowserAutomationRefSchema.optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const BrowserAutomationScrollCommandSchema = z.object({
|
||||
command: z.literal("scroll"),
|
||||
args: BrowserAutomationTabTargetSchema.extend({
|
||||
ref: BrowserAutomationRefSchema.optional(),
|
||||
deltaX: z.number(),
|
||||
deltaY: z.number(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const BrowserAutomationResizeCommandSchema = z.object({
|
||||
command: z.literal("resize"),
|
||||
args: BrowserAutomationTabTargetSchema.extend({
|
||||
width: z.number().int().positive(),
|
||||
height: z.number().int().positive(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const BrowserAutomationCloseTabCommandSchema = z.object({
|
||||
command: z.literal("close_tab"),
|
||||
args: BrowserAutomationTabTargetSchema,
|
||||
});
|
||||
|
||||
export const BrowserAutomationCommandSchema = z.discriminatedUnion("command", [
|
||||
BrowserAutomationListTabsCommandSchema,
|
||||
BrowserAutomationNewTabCommandSchema,
|
||||
@@ -211,6 +250,10 @@ export const BrowserAutomationCommandSchema = z.discriminatedUnion("command", [
|
||||
BrowserAutomationHoverCommandSchema,
|
||||
BrowserAutomationDragCommandSchema,
|
||||
BrowserAutomationLogsCommandSchema,
|
||||
BrowserAutomationEvaluateCommandSchema,
|
||||
BrowserAutomationScrollCommandSchema,
|
||||
BrowserAutomationResizeCommandSchema,
|
||||
BrowserAutomationCloseTabCommandSchema,
|
||||
]);
|
||||
|
||||
export const BrowserAutomationTabInfoSchema = z.object({
|
||||
@@ -236,14 +279,15 @@ export const BrowserAutomationNewTabResultSchema = z.object({
|
||||
url: z.string().min(1),
|
||||
});
|
||||
|
||||
export const BrowserAutomationSnapshotElementSchema = z.object({
|
||||
ref: z.string().regex(/^@e\d+$/),
|
||||
role: z.string(),
|
||||
tagName: z.string(),
|
||||
text: z.string(),
|
||||
selector: z.string(),
|
||||
attributes: z.record(z.string(), z.string()).default({}),
|
||||
});
|
||||
export const BrowserAutomationSnapshotStatsSchema = z
|
||||
.object({
|
||||
nodeCount: z.number().int().nonnegative(),
|
||||
refCount: z.number().int().nonnegative(),
|
||||
textLength: z.number().int().nonnegative(),
|
||||
iframeCount: z.number().int().nonnegative().optional(),
|
||||
maxDepth: z.number().int().nonnegative().optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const BrowserAutomationSnapshotResultSchema = z.object({
|
||||
command: z.literal("snapshot"),
|
||||
@@ -251,13 +295,18 @@ export const BrowserAutomationSnapshotResultSchema = z.object({
|
||||
workspaceId: z.string().min(1).optional(),
|
||||
url: z.string(),
|
||||
title: z.string(),
|
||||
elements: z.array(BrowserAutomationSnapshotElementSchema),
|
||||
format: z.literal("aria-yaml"),
|
||||
snapshot: z.string(),
|
||||
truncated: z.boolean(),
|
||||
stats: BrowserAutomationSnapshotStatsSchema,
|
||||
});
|
||||
|
||||
export const BrowserAutomationClickResultSchema = z.object({
|
||||
command: z.literal("click"),
|
||||
browserId: BrowserAutomationBrowserIdSchema,
|
||||
ref: BrowserAutomationRefSchema,
|
||||
x: z.number().optional(),
|
||||
y: z.number().optional(),
|
||||
});
|
||||
|
||||
export const BrowserAutomationFillResultSchema = z.object({
|
||||
@@ -276,6 +325,8 @@ export const BrowserAutomationTypeResultSchema = z.object({
|
||||
command: z.literal("type"),
|
||||
browserId: BrowserAutomationBrowserIdSchema,
|
||||
ref: BrowserAutomationRefSchema.optional(),
|
||||
x: z.number().optional(),
|
||||
y: z.number().optional(),
|
||||
});
|
||||
|
||||
export const BrowserAutomationKeypressResultSchema = z.object({
|
||||
@@ -283,6 +334,8 @@ export const BrowserAutomationKeypressResultSchema = z.object({
|
||||
browserId: BrowserAutomationBrowserIdSchema,
|
||||
key: z.string().min(1),
|
||||
ref: BrowserAutomationRefSchema.optional(),
|
||||
x: z.number().optional(),
|
||||
y: z.number().optional(),
|
||||
});
|
||||
|
||||
export const BrowserAutomationNavigateResultSchema = z.object({
|
||||
@@ -333,6 +386,8 @@ export const BrowserAutomationHoverResultSchema = z.object({
|
||||
command: z.literal("hover"),
|
||||
browserId: BrowserAutomationBrowserIdSchema,
|
||||
ref: BrowserAutomationRefSchema,
|
||||
x: z.number().optional(),
|
||||
y: z.number().optional(),
|
||||
});
|
||||
|
||||
export const BrowserAutomationDragResultSchema = z.object({
|
||||
@@ -340,6 +395,10 @@ export const BrowserAutomationDragResultSchema = z.object({
|
||||
browserId: BrowserAutomationBrowserIdSchema,
|
||||
sourceRef: BrowserAutomationRefSchema,
|
||||
targetRef: BrowserAutomationRefSchema,
|
||||
sourceX: z.number().optional(),
|
||||
sourceY: z.number().optional(),
|
||||
targetX: z.number().optional(),
|
||||
targetY: z.number().optional(),
|
||||
});
|
||||
|
||||
export const BrowserAutomationConsoleLogEntrySchema = z.object({
|
||||
@@ -367,6 +426,35 @@ export const BrowserAutomationLogsResultSchema = z.object({
|
||||
network: z.array(BrowserAutomationNetworkLogEntrySchema),
|
||||
});
|
||||
|
||||
export const BrowserAutomationEvaluateResultSchema = z.object({
|
||||
command: z.literal("evaluate"),
|
||||
browserId: BrowserAutomationBrowserIdSchema,
|
||||
resultJson: z.string(),
|
||||
truncated: z.boolean(),
|
||||
});
|
||||
|
||||
export const BrowserAutomationScrollResultSchema = z.object({
|
||||
command: z.literal("scroll"),
|
||||
browserId: BrowserAutomationBrowserIdSchema,
|
||||
ref: BrowserAutomationRefSchema.optional(),
|
||||
deltaX: z.number(),
|
||||
deltaY: z.number(),
|
||||
x: z.number().optional(),
|
||||
y: z.number().optional(),
|
||||
});
|
||||
|
||||
export const BrowserAutomationResizeResultSchema = z.object({
|
||||
command: z.literal("resize"),
|
||||
browserId: BrowserAutomationBrowserIdSchema,
|
||||
width: z.number().int().positive(),
|
||||
height: z.number().int().positive(),
|
||||
});
|
||||
|
||||
export const BrowserAutomationCloseTabResultSchema = z.object({
|
||||
command: z.literal("close_tab"),
|
||||
browserId: BrowserAutomationBrowserIdSchema,
|
||||
});
|
||||
|
||||
export const BrowserAutomationResultSchema = z.discriminatedUnion("command", [
|
||||
BrowserAutomationListTabsResultSchema,
|
||||
BrowserAutomationNewTabResultSchema,
|
||||
@@ -386,6 +474,10 @@ export const BrowserAutomationResultSchema = z.discriminatedUnion("command", [
|
||||
BrowserAutomationHoverResultSchema,
|
||||
BrowserAutomationDragResultSchema,
|
||||
BrowserAutomationLogsResultSchema,
|
||||
BrowserAutomationEvaluateResultSchema,
|
||||
BrowserAutomationScrollResultSchema,
|
||||
BrowserAutomationResizeResultSchema,
|
||||
BrowserAutomationCloseTabResultSchema,
|
||||
]);
|
||||
|
||||
export const BrowserAutomationErrorSchema = z.object({
|
||||
@@ -394,6 +486,15 @@ export const BrowserAutomationErrorSchema = z.object({
|
||||
retryable: z.boolean().default(false),
|
||||
});
|
||||
|
||||
export const BrowserAutomationDialogEventSchema = z.object({
|
||||
type: z.enum(["alert", "confirm", "prompt", "beforeunload"]),
|
||||
message: z.string(),
|
||||
defaultValue: z.string().optional(),
|
||||
action: z.enum(["accepted", "dismissed"]),
|
||||
promptText: z.string().optional(),
|
||||
timestamp: z.number(),
|
||||
});
|
||||
|
||||
export const BrowserAutomationExecuteRequestSchema = z
|
||||
.object({
|
||||
type: z.literal("browser.automation.execute.request"),
|
||||
@@ -412,11 +513,13 @@ export const BrowserAutomationExecuteResponseSchema = z.object({
|
||||
requestId: z.string().min(1),
|
||||
ok: z.literal(true),
|
||||
result: BrowserAutomationResultSchema,
|
||||
dialogs: z.array(BrowserAutomationDialogEventSchema).optional(),
|
||||
}),
|
||||
z.object({
|
||||
requestId: z.string().min(1),
|
||||
ok: z.literal(false),
|
||||
error: BrowserAutomationErrorSchema,
|
||||
dialogs: z.array(BrowserAutomationDialogEventSchema).optional(),
|
||||
}),
|
||||
]),
|
||||
});
|
||||
@@ -431,6 +534,7 @@ export type BrowserAutomationConsoleLogEntry = z.infer<
|
||||
export type BrowserAutomationNetworkLogEntry = z.infer<
|
||||
typeof BrowserAutomationNetworkLogEntrySchema
|
||||
>;
|
||||
export type BrowserAutomationDialogEvent = z.infer<typeof BrowserAutomationDialogEventSchema>;
|
||||
export type BrowserAutomationExecuteRequest = z.infer<typeof BrowserAutomationExecuteRequestSchema>;
|
||||
export type BrowserAutomationExecuteResponse = z.infer<
|
||||
typeof BrowserAutomationExecuteResponseSchema
|
||||
|
||||
@@ -100,6 +100,28 @@ describe("browser automation protocol integration", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("browser host capability accepts new tool commands as supported commands", () => {
|
||||
expect(
|
||||
WSHelloMessageSchema.parse({
|
||||
type: "hello",
|
||||
clientId: "client-1",
|
||||
clientType: "mobile",
|
||||
protocolVersion: 1,
|
||||
capabilities: {
|
||||
[CLIENT_CAPS.browserHost]: {
|
||||
supportedCommands: ["evaluate", "scroll", "resize", "close_tab"],
|
||||
hostKind: "desktop app",
|
||||
},
|
||||
},
|
||||
}).capabilities,
|
||||
).toMatchObject({
|
||||
[CLIENT_CAPS.browserHost]: {
|
||||
supportedCommands: ["evaluate", "scroll", "resize", "close_tab"],
|
||||
hostKind: "desktop app",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("hello remains valid when no browser host capability is advertised", () => {
|
||||
expect(
|
||||
WSHelloMessageSchema.parse({
|
||||
|
||||
Reference in New Issue
Block a user