diff --git a/.changeset/early-brooms-draw.md b/.changeset/early-brooms-draw.md new file mode 100644 index 000000000..af7b7ce04 --- /dev/null +++ b/.changeset/early-brooms-draw.md @@ -0,0 +1,5 @@ +--- +"@browserbasehq/stagehand": patch +--- + +Add a page.sendCDP method diff --git a/packages/core/lib/v3/tests/page-send-cdp.spec.ts b/packages/core/lib/v3/tests/page-send-cdp.spec.ts new file mode 100644 index 000000000..4affbc75d --- /dev/null +++ b/packages/core/lib/v3/tests/page-send-cdp.spec.ts @@ -0,0 +1,57 @@ +import { test, expect } from "@playwright/test"; +import { V3 } from "../v3"; +import { v3TestConfig } from "./v3.config"; + +test.describe("Page sendCDP method", () => { + let v3: V3; + + test.beforeEach(async () => { + v3 = new V3(v3TestConfig); + await v3.init(); + }); + + test.afterEach(async () => { + await v3?.close?.().catch(() => {}); + }); + + test("sends CDP commands and requires domain to be enabled first", async () => { + const page = v3.context.pages()[0]; + await page.goto("https://example.com"); + + // Try to add a virtual authenticator without enabling WebAuthn first + // This should fail because the domain needs to be enabled + await expect( + page.sendCDP("WebAuthn.addVirtualAuthenticator", { + options: { + protocol: "ctap2", + transport: "usb", + hasResidentKey: false, + hasUserVerification: false, + isUserVerified: false, + }, + }), + ).rejects.toThrow(); + + // Enable the WebAuthn domain + await page.sendCDP("WebAuthn.enable"); + + // Now adding a virtual authenticator should succeed + const result = await page.sendCDP<{ authenticatorId: string }>( + "WebAuthn.addVirtualAuthenticator", + { + options: { + protocol: "ctap2", + transport: "usb", + hasResidentKey: false, + hasUserVerification: false, + isUserVerified: false, + }, + }, + ); + + // Verify we got an authenticator ID back + expect(result).toHaveProperty("authenticatorId"); + expect(typeof result.authenticatorId).toBe("string"); + expect(result.authenticatorId.length).toBeGreaterThan(0); + }); +}); diff --git a/packages/core/lib/v3/understudy/page.ts b/packages/core/lib/v3/understudy/page.ts index a9dd0751e..95da31f08 100644 --- a/packages/core/lib/v3/understudy/page.ts +++ b/packages/core/lib/v3/understudy/page.ts @@ -478,6 +478,32 @@ export class Page { return this._targetId; } + /** + * Send a CDP command through the main session. + * Allows external consumers to execute arbitrary Chrome DevTools Protocol commands. + * + * @param method - The CDP method name (e.g., "Page.enable", "Runtime.evaluate") + * @param params - Optional parameters for the CDP command + * @returns Promise resolving to the typed CDP response + * + * @example + * // Enable the Runtime domain + * await page.sendCDP("Runtime.enable"); + * + * @example + * // Evaluate JavaScript with typed response + * const result = await page.sendCDP( + * "Runtime.evaluate", + * { expression: "1 + 1" } + * ); + */ + public async sendCDP( + method: string, + params?: object, + ): Promise { + return this.mainSession.send(method, params); + } + /** Seed the cached URL before navigation events converge. */ public seedCurrentUrl(url: string | undefined | null): void { if (!url) return;