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/live/?i=a1b2c3d4...

The URL needs no token or account, so the person you hand it to doesn't need Browserless credentials. It streams exactly one session and dies with it.

Treat a live URL as a bearer secret. With interactable: true, anyone holding the URL controls the browser session until it ends, including any logged-in state inside it. Send it over a trusted private channel only, and keep it out of logs, tickets, and shared messages.

Tuning the stream

OptionDefaultWhat it does
interactabletrueForward the viewer's clicks and keystrokes into the browser. Set false for watch-only streams
typejpegStream codec. jpeg uses less bandwidth; png looks better and costs more
quality70JPEG quality from 1 to 100. Ignored for png
resizabletrueResize the browser to match the viewer's screen. Set false to keep your viewport and letterbox the stream instead, which you want when a later screenshot depends on the viewport
showBrowserInterfacefalseShow a navigation bar and tabs around the stream
emulateComponentstrueEmulate <select> dropdowns and the virtual keyboard for touch devices. Set false to suppress the virtual keyboard
timeoutHow long the browser may stay alive; when it expires the viewer sees a session-closed prompt

The response also carries liveURLId, a stable identifier for the stream that's useful for correlating with other APIs. The generated LiveURLOptions reference stays current with the schema, including the compressed bandwidth toggle.

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

The session ended: your script called close(), the operation finished, or the session hit its timeout. A live URL streams one session and can't outlive it. For handoff flows, keep the script blocked on something like waitForSelector() while the human works.

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?