Skip to main content

Installation

npm install @browseragentprotocol/client
Requires Node.js >= 20.0.0 and a running BAP server.

Quick Start

import { BAPClient, role, label } from "@browseragentprotocol/client";

const client = new BAPClient("ws://localhost:9222");
await client.connect();

await client.launch({ browser: "chromium", headless: false });
await client.createPage({ url: "https://example.com" });

// Semantic selectors
await client.fill(label("Email"), "user@example.com");
await client.click(role("button", "Submit"));

// AI-optimized observation
const obs = await client.observe();
console.log(obs.interactiveElements);

await client.close();

Connection

// Constructor
const client = new BAPClient(url: string, options?: BAPClientOptions);

// Connect (handshake + version check)
await client.connect();

// Shutdown and clean up
await client.close();
| Option | Type | Default | Description | |--------|------|---------|-------------| | token | string | — | Authentication token | | maxReconnectAttempts | number | 5 | Max auto-reconnect attempts | | reconnectDelay | number | 1000 | Base delay for reconnect backoff (ms) | | timeout | number | 30000 | Default request timeout (ms) | | sessionId | string | — | Session ID for persistence |

Factory Function

import { createClient } from "@browseragentprotocol/client";

// Creates + connects in one call
const client = await createClient("ws://localhost:9222");

Browser Control

// Launch browser
await client.launch({
  browser: "chromium", // "chromium" | "firefox" | "webkit"
  channel: "chrome", // Optional: "chrome", "msedge"
  headless: true,
  args: ["--disable-gpu"],
  cdpUrl: "ws://...", // Connect to existing browser via CDP
});

// Close browser
await client.closeBrowser();

Page Management

// Create page (optionally navigate)
const page = await client.createPage({ url: "https://example.com" });

// Navigate
await client.navigate("https://example.com/login");

// Navigate with fused observation
await client.navigate("https://example.com", {
  observe: { includeInteractiveElements: true },
});

// History
await client.goBack();
await client.goForward();
await client.reload();

// Multi-page
const pages = await client.listPages();
await client.activatePage(pageId);
await client.closePage(pageId);

Actions

All action methods accept any selector type.
import { role, text, label, testId, ref } from "@browseragentprotocol/client";

// Click
await client.click(role("button", "Submit"));
await client.dblclick(text("Edit"));

// Form input
await client.fill(label("Email"), "user@example.com");
await client.type(label("Search"), "query"); // Character by character
await client.clear(label("Email"));

// Keyboard
await client.press("Enter");
await client.press("Control+a");

// Mouse
await client.hover(text("Menu"));
await client.scroll({ direction: "down", amount: 500 });

// Selection
await client.select(label("Country"), "US");
await client.check(label("Agree to terms"));
await client.uncheck(label("Marketing emails"));

// File upload
await client.upload(testId("file-input"), "/path/to/file.pdf");

Agent Methods

The three composite methods optimized for AI agent workflows.

observe()

Get an AI-optimized snapshot of the page with interactive elements, stable refs, and optional screenshot annotation.
const obs = await client.observe({
  includeInteractiveElements: true,
  includeScreenshot: true,
  includeAccessibility: true,
  maxElements: 50,
  responseTier: "interactive", // "full" | "interactive" | "minimal"
  incremental: true, // Only changes since last observation
  annotateScreenshot: true, // Set-of-Marks badges
  includeWebMCPTools: true, // Discover WebMCP tools
});

// Interactive elements with stable refs
for (const el of obs.interactiveElements ?? []) {
  console.log(`${el.ref}: ${el.role} "${el.name}" [${el.actionHints}]`);
}

// Incremental changes
if (obs.changes) {
  console.log("Added:", obs.changes.added.length);
  console.log("Updated:", obs.changes.updated.length);
  console.log("Removed:", obs.changes.removed.length);
}

act()

Execute a multi-step action sequence atomically with pre/post observation fusion.
const result = await client.act({
  steps: [
    BAPClient.step("action/fill", {
      selector: label("Email"),
      value: "user@example.com",
    }),
    BAPClient.step("action/fill", {
      selector: label("Password"),
      value: "secret123",
    }),
    BAPClient.step("action/click", {
      selector: role("button", "Sign In"),
    }),
  ],
  postObserve: { includeInteractiveElements: true }, // Fusion
});

console.log(`${result.completed}/${result.total} steps completed`);
console.log("Post-observation:", result.postObservation?.interactiveElements);

extract()

Extract structured data from the page using a JSON Schema.
const data = await client.extract({
  instruction: "Extract all product names and prices",
  schema: {
    type: "array",
    items: {
      type: "object",
      properties: {
        name: { type: "string" },
        price: { type: "number" },
      },
    },
  },
  mode: "list",
});

if (data.success) {
  for (const product of data.data) {
    console.log(`${product.name}: $${product.price}`);
  }
}

Events

// Subscribe to events
client.on("page", (event) => {
  console.log(`Page ${event.type}: ${event.url}`);
});

client.on("console", (event) => {
  if (event.type === "error") console.error(event.text);
});

client.on("network", (event) => {
  if (event.status >= 400) console.warn(`${event.url} -> ${event.status}`);
});

client.on("dialog", (event) => {
  console.log(`Dialog: ${event.type} - ${event.message}`);
});

client.on("download", (event) => {
  console.log(`Download: ${event.suggestedFilename}`);
});

// Connection events
client.on("close", () => console.log("Disconnected"));
client.on("error", (err) => console.error("Error:", err));

Storage

// Get full storage state (cookies + localStorage)
const state = await client.getStorageState();

// Restore storage state
await client.setStorageState(state);

// Cookie operations
const cookies = await client.getCookies();
await client.setCookies([{ name: "session", value: "abc", domain: ".example.com" }]);
await client.clearCookies();

Contexts and Frames

// Isolated browser contexts
const ctx = await client.createContext({
  contextId: "user-session",
  options: { viewport: { width: 1920, height: 1080 } },
});
await client.createPage({ url: "https://example.com", contextId: ctx.contextId });
await client.destroyContext(ctx.contextId);

// Frame navigation
const frames = await client.listFrames();
await client.switchFrame({ selector: { type: "css", value: "iframe#payment" } });
await client.fill(label("Card number"), "4242424242424242");
await client.mainFrame();

Emulation

await client.setViewport({ width: 375, height: 812 }); // iPhone viewport
await client.setUserAgent("Mozilla/5.0 (iPhone; ...)");
await client.setGeolocation({ latitude: 37.7749, longitude: -122.4194 });
await client.setOffline(true);

WebMCP Discovery

const tools = await client.discoverTools();
for (const tool of tools.tools) {
  console.log(`${tool.name}: ${tool.description} (source: ${tool.source})`);
}

Error Handling

import {
  BAPError,
  BAPElementNotFoundError,
  BAPTimeoutError,
  BAPNavigationError,
} from "@browseragentprotocol/client";

try {
  await client.click(role("button", "Submit"));
} catch (error) {
  if (error instanceof BAPElementNotFoundError) {
    // error.retryable === true
    // error.recoveryHint === "Run observe() to refresh..."
    const obs = await client.observe();
    // Retry with updated selector
  } else if (error instanceof BAPTimeoutError) {
    // Increase timeout and retry
  } else if (error instanceof BAPNavigationError) {
    // Check URL validity
  }
}