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.
- A Browserless API token from your account dashboard
- The BAP SDK installed and connecting, from the BAP Quickstart
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.
- TypeScript
- Python
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();
}
import bap.sync_api as bap
TOKEN = "YOUR_API_TOKEN_HERE"
with bap.Browserless.connect(
browser_ws_endpoint="wss://production-sfo.browserless.io/chromium/bql",
token=TOKEN,
) as browser:
with browser.page() as page:
page.goto("https://example.com/login")
# interactable lets the viewer click and type, not just watch.
live = page.live_url(interactable=True)
print(f"Hand off to a human: {live['liveURL']}")
# Block until the human's part is done, then continue the automation.
page.wait_for_selector("#dashboard", timeout=120000)
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
| Option | Default | What it does |
|---|---|---|
interactable | true | Forward the viewer's clicks and keystrokes into the browser. Set false for watch-only streams |
type | jpeg | Stream codec. jpeg uses less bandwidth; png looks better and costs more |
quality | 70 | JPEG quality from 1 to 100. Ignored for png |
resizable | true | Resize 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 |
showBrowserInterface | false | Show a navigation bar and tabs around the stream |
emulateComponents | true | Emulate <select> dropdowns and the virtual keyboard for touch devices. Set false to suppress the virtual keyboard |
timeout | — | How 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.
- TypeScript
- Python
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();
}
with bap.Browserless.connect(
# Recording starts automatically because of replay=true.
browser_ws_endpoint="wss://production-sfo.browserless.io/chromium/bql?replay=true",
token=TOKEN,
) as browser:
with browser.page() as page:
page.goto("https://example.com")
# Optional: stop early and upload now instead of at session end.
result = page.stop_session_recording()
print("uploaded" if result["success"] else f"failed: {result['error']}")
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.
- TypeScript
- Python
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*" });
page.click("#open-checkout")
# The click spawned a tab; follow it.
page.switch_to_window(newest=True)
print(page.url())
# Or match the window you want explicitly.
page.switch_to_window(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.