For AI agents: a documentation index is available at /llms.txt
Skip to main content

Live URLs and session recording in BAP

A BAP session doesn't have to run blind. liveURL() returns a link that streams the browser to anyone you send it to, optionally letting them click and type, and session replay records everything for the dashboard. This guide covers both, plus switching windows when a flow opens more than one.

Prerequisites

Stream the session with a live URL​

liveURL() returns a shareable link to a live stream of the browser. The two big use cases are watching your own automation while you debug it, and human-in-the-loop flows where a person completes the one step automation can't, such as entering a one-time passcode.

import Browserless from "@browserless.io/bap-ts";

const TOKEN = "YOUR_API_TOKEN_HERE";

const browser = Browserless.connect({
browserWSEndpoint: "wss://production-sfo.browserless.io/chromium/bql",
token: TOKEN,
});

try {
const page = await browser.newPage();
await page.goto("https://example.com/login");

// interactable lets the viewer click and type, not just watch.
const { liveURL } = await page.liveURL({ interactable: true });
console.log(`Hand off to a human: ${liveURL}`);

// Block until the human's part is done, then continue the automation.
await page.waitForSelector("#dashboard", { timeout: 120_000 });
} finally {
// Close even when the human never finishes, or the session keeps billing.
await browser.close();
}

Response:

Hand off to a human: https://production-sfo.browserless.io/e/53b1.../live/index.html?i=a1b2c3d4...&t=...&showBrowserInterface=false

For the complete mint → share → wait → verify → close flow, use Live URLs with BrowserQL and BAP. It distinguishes link expiry from explicit browser keep-alive and explains completion subscriptions. The Live URL overview covers bearer-link security and credential teardown. Deliver links privately; the console output above is for local demonstration only.

Tuning the stream​

Use the generated LiveURLOptions reference for SDK fields and the shared parameter reference for their behavior. BrowserQL/BAP have an additional PNG quality rule. Older SDK releases may expose fewer options and response fields; use the reference matching your installed version.

Choose which tab the viewer sees​

The SDK's windows() method lists open page targets without changing automation's active window. Each entry contains targetId, url, title, active, and browserContextId. Use a returned targetId to mint a link for that tab:

const windows = await page.windows();
const target = windows.find((window) => window.active);
if (!target) throw new Error("No active tab");

const live = await page.liveURL({
targetId: target.targetId,
mode: "tab",
interactable: true,
instructions: "Complete this step, then click Done.",
timeout: 120_000,
});
// Deliver live.liveURL to the viewer through a trusted private channel.
const completion = await page.waitForLiveComplete({ timeout: 120_000 });
if (completion.reason !== "userDone") {
throw new Error(`Handoff ended: ${completion.reason}`);
}
// Verify the application state before continuing automation.
// Switching automation's tab is a separate, explicit operation:
await page.switchToWindow({ targetId: target.targetId });
SelectionDefault modeBehavior
targetId supplied"tab"Stay on the selected tab.
targetId omitted"follow"Start on the active window and follow new tabs and popups.

An explicit mode overrides either default. Minting a link for another tab does not switch automation's active window. The mint response's targetId identifies the starting tab. waitForLiveComplete() and liveComplete listeners receive the link's anchor targetId at completion, including any re-anchor after closure; correlate handoffs by liveURLId.

These additions require an SDK revision that exposes windows() and the targetId/mode options (Python: target_id/mode), plus a server with the corresponding BrowserQL fields and completion targetId. Deploy the server before upgrading the SDK: older servers cannot satisfy the new response selections. These examples describe the updated SDK sources; use them only after the corresponding SDK release and server deployment are available.

For the CDP multi-tab recipe, see multi-tab workflows.

Record the session for replay​

Session replay captures DOM events, console logs, and network activity for the dashboard's Session Replay viewer. Recording is a connection decision, not a method call: add replay=true to the endpoint you connect to, and it starts as soon as the session does.

const browser = Browserless.connect({
// Recording starts automatically because of replay=true.
browserWSEndpoint: "wss://production-sfo.browserless.io/chromium/bql?replay=true",
token: TOKEN,
});

try {
const page = await browser.newPage();
await page.goto("https://example.com");

// Optional: stop early and upload now instead of at session end.
const { success, error } = await page.stopSessionRecording();
console.log(success ? "uploaded" : `failed: ${error}`);
} finally {
// Close once the work is done; an open session keeps billing after the stop.
await browser.close();
}

Recordings upload when the session ends or when stopSessionRecording() succeeds, whichever comes first, and appear in the Session Replay section of your dashboard. Stopping early is worth doing on long sessions where only the first part matters, since everything after the stop isn't captured.

Switch between windows​

When a click opens a new tab or window, your page methods keep talking to the original. switchToWindow() retargets the session at another window, matched by url or title pattern, or just the newest one.

await page.click("#open-checkout");

// The click spawned a tab; follow it.
await page.switchToWindow({ newest: true });
console.log(await page.url());

// Or match the window you want explicitly.
await page.switchToWindow({ url: "*checkout.example.com*" });

Matching waits up to timeout (default 5000 milliseconds) for the window to exist, so calling right after the click that spawns it is safe.

FAQ & Troubleshooting​

The live URL shows a closed-session message

Check both the link lifetime and browser lifetime. Keep your script connected while the person works, or explicitly reserve reconnect keep-alive before disconnecting. Minting a link alone does not reserve that time.

Viewers can see the stream but can't click anything

The stream was created with interactable: false, or the element they're clicking needs the virtual keyboard you disabled with emulateComponents: false. Create a new live URL with interaction on; the option can't be flipped on an existing stream.

My replay never showed up in the dashboard

Recording only happens when the connection URL included replay=true. Confirm the parameter made it onto the endpoint, and if you called stopSessionRecording(), check its success and error fields, since a failed stop means nothing was uploaded.

Is a live URL the same as session replay?

No. liveURL() streams the session while it runs and leaves nothing behind. Replay records the session for viewing afterward in the dashboard. They're independent, and one session can use both.

Next steps​

Was this page helpful?