Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/calm-cursors-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@browserbasehq/stagehand-protocol": patch
"@browserbasehq/stagehand-python": patch
"@browserbasehq/stagehand-extension": patch
"@browserbasehq/stagehand-go": patch
"@browserbasehq/stagehand": patch
---

expose page cursor overlay enablement across the V4 SDKs
38 changes: 38 additions & 0 deletions packages/docs/v4/reference/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,18 @@ await page.addInitScript(() => {
Resolves after the operation completes.
</ResponseField>

## enableCursorOverlay()

Show the Stagehand cursor overlay used for coordinate-based interactions.

```typescript
await page.enableCursorOverlay();
```

<ResponseField name="result" type="Promise<void>">
Resolves after the overlay is enabled.
</ResponseField>

## setExtraHTTPHeaders()

Set additional HTTP headers for this page.
Expand Down Expand Up @@ -1064,6 +1076,18 @@ await page.add_init_script("window.localStorage.clear()")
Resolves after the operation completes.
</ResponseField>

## enable_cursor_overlay()

Show the Stagehand cursor overlay used for coordinate-based interactions.

```python
await page.enable_cursor_overlay()
```

<ResponseField name="result" type="None">
Resolves after the overlay is enabled.
</ResponseField>

## set_extra_http_headers()

Set additional HTTP headers for this page.
Expand Down Expand Up @@ -1778,6 +1802,20 @@ if err := page.AddInitScript(ctx, "window.localStorage.clear()"); err != nil {
Returns `nil` after the operation completes.
</ResponseField>

## EnableCursorOverlay()

Show the Stagehand cursor overlay used for coordinate-based interactions.

```go
if err := page.EnableCursorOverlay(ctx); err != nil {
return err
}
```

<ResponseField name="result" type="error">
Returns `nil` after the overlay is enabled.
</ResponseField>

## SetExtraHTTPHeaders()

Set additional HTTP headers for this page.
Expand Down
6 changes: 6 additions & 0 deletions packages/extension/controllers/pageController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ export function createPageController(runtime: StagehandRuntime) {
return runtime.pageAddInitScript(params);
}

async function enableCursorOverlay(params: PageIdParams, { logger }: HandlerContext) {
logger.debug("page.enable_cursor_overlay", {});
return runtime.pageEnableCursorOverlay(params);
}

async function setExtraHTTPHeaders(
params: PageSetExtraHTTPHeadersParams,
{ logger }: HandlerContext,
Expand Down Expand Up @@ -192,6 +197,7 @@ export function createPageController(runtime: StagehandRuntime) {
keyPress,
evaluate,
addInitScript,
enableCursorOverlay,
setExtraHTTPHeaders,
setViewportSize,
waitForLoadState,
Expand Down
5 changes: 5 additions & 0 deletions packages/extension/rpcRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,11 @@ export class RPCRouter {
parseParams(StagehandMethods.pageAddInitScript, request.params),
context,
);
case "page.enable_cursor_overlay":
return this.pageController.enableCursorOverlay(
parseParams(StagehandMethods.pageEnableCursorOverlay, request.params),
context,
);
case "page.on":
return this.pageController.on(
parseParams(StagehandMethods.pageOn, request.params),
Expand Down
6 changes: 6 additions & 0 deletions packages/extension/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export type UnderstudyRuntimePage = {
keyPress(key: string, options?: PageKeyPressParams["options"]): Promise<void>;
evaluate(expression: string): Promise<unknown>;
addInitScript(source: string): Promise<void>;
enableCursorOverlay(): Promise<void>;
setExtraHTTPHeaders(headers: PageSetExtraHTTPHeadersParams["headers"]): Promise<void>;
setViewportSize(
width: number,
Expand Down Expand Up @@ -739,6 +740,11 @@ export class StagehandRuntime {
return { closed: true };
}

async pageEnableCursorOverlay(params: PageIdParams): Promise<PageVoidResult> {
await this.resolvePage(params.pageId).enableCursorOverlay();
return { ok: true };
}

pageOn(params: PageOnParams): PageVoidResult {
if (this.pageEventSubscriptions.has(params.subscriptionId)) {
throw new DuplicatePageEventSubscriptionError();
Expand Down
21 changes: 21 additions & 0 deletions packages/extension/tests/stagehand-clients.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ class FakeUnderstudyRuntimePage implements UnderstudyRuntimePage {
readonly keyPressCalls: Array<{ key: string; options?: PageKeyPressParams["options"] }> = [];
readonly evaluateCalls: string[] = [];
readonly addInitScriptCalls: string[] = [];
enableCursorOverlayCalls = 0;
readonly setExtraHTTPHeadersCalls: Array<PageSetExtraHTTPHeadersParams["headers"]> = [];
readonly setViewportSizeCalls: Array<{
width: number;
Expand Down Expand Up @@ -349,6 +350,10 @@ class FakeUnderstudyRuntimePage implements UnderstudyRuntimePage {
this.addInitScriptCalls.push(source);
}

async enableCursorOverlay(): Promise<void> {
this.enableCursorOverlayCalls += 1;
}

async setExtraHTTPHeaders(headers: PageSetExtraHTTPHeadersParams["headers"]): Promise<void> {
this.setExtraHTTPHeadersCalls.push(headers);
}
Expand Down Expand Up @@ -1832,6 +1837,22 @@ describe("Stagehand worker clients", () => {
expect(page.addInitScriptCalls).toStrictEqual(["globalThis.ready = true"]);
});

it("routes cursor overlay enablement", async () => {
const page = new FakeUnderstudyRuntimePage("page-a", "https://example.test/current");
const handle = await createConfiguredHandler(new FakeBrowserSession([page]));

await expect(
handle({
jsonrpc: "2.0",
id: 240,
method: "page.enable_cursor_overlay",
params: { page_id: "page-a" },
}),
).resolves.toStrictEqual({ jsonrpc: "2.0", id: 240, result: { ok: true } });

expect(page.enableCursorOverlayCalls).toBe(1);
});

it("routes page headers and viewport configuration", async () => {
const page = new FakeUnderstudyRuntimePage("page-a", "https://example.test/current");
const handle = await createConfiguredHandler(new FakeBrowserSession([page]));
Expand Down
5 changes: 5 additions & 0 deletions packages/protocol/schema-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,11 @@ export const StagehandMethods = {
params: PageAddInitScriptParamsSchema,
result: PageVoidResultSchema,
},
pageEnableCursorOverlay: {
name: "page.enable_cursor_overlay",
params: PageIdParamsSchema,
result: PageVoidResultSchema,
},
pageOn: {
name: "page.on",
params: PageOnParamsSchema,
Expand Down
41 changes: 41 additions & 0 deletions packages/protocol/stagehand.v4.json
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,19 @@
"required": ["params", "result"],
"additionalProperties": false
},
"page.enable_cursor_overlay": {
"type": "object",
"properties": {
"params": {
"$ref": "#/$defs/PageIdParams"
},
"result": {
"$ref": "#/$defs/PageVoidResult"
}
},
"required": ["params", "result"],
"additionalProperties": false
},
"page.on": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -1036,6 +1049,7 @@
"page.key_press",
"page.evaluate",
"page.add_init_script",
"page.enable_cursor_overlay",
"page.on",
"page.off",
"page.set_extra_http_headers",
Expand Down Expand Up @@ -6257,6 +6271,33 @@
"required": ["jsonrpc", "id", "method", "params"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"jsonrpc": {
"type": "string",
"const": "2.0"
},
"id": {
"$ref": "#/$defs/JSONRPCRequestId"
},
"method": {
"type": "string",
"const": "page.enable_cursor_overlay"
},
"params": {
"$ref": "#/$defs/PageIdParams"
},
"traceparent": {
"type": "string"
},
"tracestate": {
"type": "string"
}
},
"required": ["jsonrpc", "id", "method", "params"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ describe("Stagehand object-model protocol", () => {
"page.key_press",
"page.evaluate",
"page.add_init_script",
"page.enable_cursor_overlay",
"page.on",
"page.off",
"page.set_extra_http_headers",
Expand Down
Binary file modified packages/sdk-go/internal/extensionassets/stagehand-extension.zip
Binary file not shown.
7 changes: 7 additions & 0 deletions packages/sdk-go/page.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,13 @@ func (p *Page) AddInitScript(ctx context.Context, source string) error {
return p.rpc.call(ctx, "page.add_init_script", params, &result)
}

// EnableCursorOverlay renders the Stagehand cursor for coordinate-based interactions.
func (p *Page) EnableCursorOverlay(ctx context.Context) error {
params := PageIDParams{PageID: p.PageID()}
var result PageVoidResult
return p.rpc.call(ctx, "page.enable_cursor_overlay", params, &result)
}

// On subscribes to console events for this page and its OOPIF sessions.
func (p *Page) On(
ctx context.Context,
Expand Down
19 changes: 19 additions & 0 deletions packages/sdk-go/page_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,25 @@ func TestPageCoordinateInteractionsReturnOnlyErrors(t *testing.T) {
}
}

func TestPageEnableCursorOverlay(t *testing.T) {
t.Parallel()

rpc := &recordingProtocolClient{responses: map[string]any{
"page.enable_cursor_overlay": PageVoidResult{Ok: true},
}}
page := &Page{rpc: rpc, ref: PageRef{PageID: "page-1"}}

if err := page.EnableCursorOverlay(context.Background()); err != nil {
t.Fatalf("EnableCursorOverlay() error = %v", err)
}
if len(rpc.calls) != 1 || rpc.calls[0].method != "page.enable_cursor_overlay" {
t.Fatalf("EnableCursorOverlay() calls = %#v", rpc.calls)
}
if got := rpc.calls[0].params; !reflect.DeepEqual(got, PageIDParams{PageID: "page-1"}) {
t.Fatalf("EnableCursorOverlay() params = %#v", got)
}
}

func TestPageOnDeliversCanonicalConsoleEventsAndUnsubscribes(t *testing.T) {
t.Parallel()

Expand Down
7 changes: 7 additions & 0 deletions packages/sdk-python/src/stagehand/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,13 @@ async def add_init_script(self, source: str | Path) -> None:
PageVoidResult,
)

async def enable_cursor_overlay(self) -> None:
await self._rpc_client.send(
"page.enable_cursor_overlay",
PageIdParams(page_id=self.page_id),
PageVoidResult,
)

async def on(
self,
event: PageEventName,
Expand Down
13 changes: 13 additions & 0 deletions packages/sdk-python/tests/test_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,19 @@ class EvaluationResult(BaseModel):
RootResultT = TypeVar("RootResultT")


@pytest.mark.asyncio
async def test_page_enables_cursor_overlay() -> None:
recording = RecordingRPCClient({"page.enable_cursor_overlay": PageVoidResult(ok=True)})
page = Page(cast(RPCClient, recording), PageRef(page_id="page-1"))

await page.enable_cursor_overlay()

method, params, result_model = recording.calls[0]
assert method == "page.enable_cursor_overlay"
assert params == PageIdParams(page_id="page-1")
assert result_model is PageVoidResult


@pytest.mark.asyncio
async def test_page_navigation_uses_generated_wire_models_and_updates_the_page_reference() -> None:
recording = RecordingRPCClient({
Expand Down
6 changes: 6 additions & 0 deletions packages/sdk-ts/src/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@ export class Page {
});
}

async enableCursorOverlay(): Promise<void> {
await this.rpcClient.send(StagehandMethods.pageEnableCursorOverlay, {
pageId: this.pageId,
});
}

async on(event: PageEventName, listener: PageEventListener): Promise<CDPSubscription> {
const subscriptionId = crypto.randomUUID();
const removeNotificationListener = this.rpcClient.onNotification((notification) => {
Expand Down
12 changes: 12 additions & 0 deletions packages/sdk-ts/tests/objectWrapper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,18 @@ describe("Stagehand TS object wrapper", () => {
]);
});

it("enables the page cursor overlay", async () => {
const client = new FakeProtocolClient();
client.queueResponse(StagehandMethods.pageEnableCursorOverlay, { ok: true });
const page = new Page(client, { pageId: "page-1" });

await page.enableCursorOverlay();

expect(client.calls).toStrictEqual([
requestCall(StagehandMethods.pageEnableCursorOverlay, { pageId: "page-1" }),
]);
});

it("routes page headers and viewport configuration", async () => {
const client = new FakeProtocolClient();
client.queueResponse(StagehandMethods.pageSetExtraHTTPHeaders, { ok: true });
Expand Down
Loading