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

Advanced Hybrid Automation Configurations

This page is the comprehensive reference for all Browserless.liveURL options and advanced patterns. It covers bandwidth tuning, viewport control, recording integration, CAPTCHA fallback handling, multi-stage workflows, and error recovery.

If you are new to hybrid automation, start with the Hybrid Automation guide.

Prerequisites
  • A Browserless API token from your account dashboard
  • Puppeteer or Playwright installed locally
Session limits

The timeout values in this guide have to fit within your plan's maximum session duration, and a LiveURL created partway through a session can't extend the browser's existing deadline.

Bandwidth and Quality Optimization​

Fine-tune live sessions for different network conditions and device capabilities.

const { liveURL } = await cdp.send('Browserless.liveURL', {
quality: 30, // Optimized for mobile/slow connections
type: 'jpeg', // Better compression than PNG
timeout: 300000, // 5 minutes for complex workflows
});

Advanced Viewport Control​

Control viewport behavior and browser UI visibility for live sessions.

By default, the live URL viewport resizes to match the end user's screen. Set resizable: false to lock the browser at its current viewport dimensions. The live URL client preserves the aspect ratio.

const { liveURL } = await cdp.send('Browserless.liveURL', {
resizable: false, // Maintain fixed viewport
interactable: true, // Allow user interaction
showBrowserInterface: false, // Hide browser tabs and UI
});

Multi-Tab Workflows​

Browserless.liveURL binds to the page whose CDP session sends it, so one CDP session per tab gives you one independent link per tab.

Open both tabs before sharing either link. These recipes keep each tab's liveURLId so you can close its link with Browserless.closeLiveURL later.

Open both links and keep the original tabs open until both viewers finish. These minimal examples wait for each tab's Browserless.liveComplete event. An unopened link or a detached tab listener can leave that wait pending, even after the browser disconnects. In production, add an application timeout and disconnect handling. The five-minute browser session timeout doesn't settle the application promises.

import puppeteer from 'puppeteer-core';

const browser = await puppeteer.connect({
browserWSEndpoint: 'wss://production-sfo.browserless.io?token=YOUR_API_TOKEN_HERE&timeout=300000',
});
try {
const tab1 = await browser.newPage();
await tab1.goto('https://example.com');
const tab2 = await browser.newPage();
await tab2.goto('https://browserless.io');

const liveViews = await Promise.all([tab1, tab2].map(async (page) => {
const cdp = await page.createCDPSession();
const complete = new Promise((resolve) => cdp.once('Browserless.liveComplete', resolve));
const { error, liveURL, liveURLId } = await cdp.send('Browserless.liveURL', {
timeout: 300000,
});
if (error) throw new Error(error);
return { cdp, liveURL, liveURLId, complete };
}));

liveViews.forEach(({ liveURL }, index) => console.log(`Tab ${index + 1}:`, liveURL));

await Promise.all(liveViews.map(({ complete }) => complete));
} finally {
await browser.close();
}

How per-tab links behave

  • Minting again for the same tab returns the same link (same liveURLId) with the new options and timeout, and disconnects anyone currently watching it. Minting for a different tab returns a separate link, and the two coexist.
  • With showBrowserInterface off, a link switches to any new tab opened after the viewer connected, anywhere in the browser, including other browser contexts. Every open link switches, not only the one whose tab opened a popup. Open all the tabs you want to stream before sharing the links. A mode that pins a link to its tab is planned.
  • Closing a tab doesn't end its link. The link moves to another tab it can see, and ends only when none are left. Call Browserless.closeLiveURL with that tab's liveURLId before closing the tab if you don't want this.
  • Filling a stored credential closes all links for the browser. See Viewer behavior and limits.

If you'd rather hand out a single link that shows every tab, set showBrowserInterface: true. The viewer shows live tab titles and favicons, lets users switch and close tabs, provides back and forward controls, and adds a draggable page scrollbar. This option injects UI overlay code into the page, which may increase CAPTCHA challenge likelihood on some sites.

const { liveURL } = await cdp.send('Browserless.liveURL', {
showBrowserInterface: true, // Show all tabs and browser UI
quality: 50,
timeout: 300000, // 5 minutes for complex workflows
});

Mobile Viewers​

LiveURL links work when the end user opens them on a phone or tablet, but support is limited to single-touch interaction:

  • Touch input: A single-finger tap maps to a mouse click, and a single-finger drag maps to mouse scroll. Multi-touch gestures (including pinch-to-zoom and two-finger pan) are not forwarded to the remote browser.
  • Virtual keyboard: When the LiveURL is opened on a device identified as mobile by its user agent, Browserless automatically injects an on-screen keyboard into the remote page so end users can type.
  • Remote viewport: With the default resizable: true, the remote browser's viewport is set to the end user's device width and height, which can cause target sites to render their mobile layout. Use resizable: false to keep the viewport you set in your automation script; the stream will be letterboxed to fit the device while preserving aspect ratio.
  • Local zoom: The LiveURL page does not disable the browser's native pinch-zoom, so end users can zoom the LiveURL page itself, but this only scales the local canvas — it does not zoom the remote page.
  • Bandwidth: Lower quality (for example quality: 30) and keep type: 'jpeg' to reduce data usage on cellular connections.

Clipboard Support​

The viewer supports copy and paste in both directions. Ctrl-based shortcuts work on Windows and Linux, and Command-based shortcuts work on macOS. When you embed the Live URL, add allow="clipboard-read; clipboard-write" to the iframe so the browser can grant clipboard access.

Depending on the end user's browser and operating system configuration, clipboard access may require explicit user permission or interaction before it can be used within the LiveURL session.

Read-Only Monitoring Sessions​

Create view-only sessions for compliance monitoring or training by setting interactable: false. The viewer sees the browser but cannot click, type, or interact. Browserless enforces this on the server by dropping viewer input before it reaches the remote browser; client-side CSS isn't a security boundary. See View-only embedding.

const { liveURL } = await cdp.send('Browserless.liveURL', {
interactable: false, // View-only mode
quality: 80, // Higher quality for monitoring
timeout: 1800000, // 30 minutes for extended monitoring
});

// Share with compliance team or supervisors
console.log('Monitoring URL (read-only):', liveURL);

Session Recording Integration​

Combine screen recording with live sessions to capture an audit trail. Set record=true as a query parameter when connecting to the browser endpoint. See Screen Recording for full details.

// Start recording before creating live session
await cdp.send('Browserless.startRecording');

const { liveURL } = await cdp.send('Browserless.liveURL', {
timeout: 300000
});

// Wait for session completion
await new Promise(resolve => cdp.on('Browserless.liveComplete', resolve));

// Save recording (see recording docs for details)
const recording = await cdp.send('Browserless.stopRecording');
fs.writeFileSync('audit-trail.webm', Buffer.from(recording.value, 'binary'));

CAPTCHA Handling with Hybrid Fallback​

Try automated CAPTCHA solving first, then fall back to a live URL for human intervention if the solver fails. Listen for the Browserless.captchaFound event to trigger this flow. For full CAPTCHA solver documentation, see CAPTCHA Solving.

// Set up CAPTCHA detection before navigation
cdp.on('Browserless.captchaFound', async () => {
console.log('CAPTCHA detected, attempting automatic solve');

const { solved } = await cdp.send('Browserless.solveCaptcha');

if (!solved) {
// Fall back to human intervention
const { liveURL } = await cdp.send('Browserless.liveURL', {
timeout: 120000 // 2 minutes for CAPTCHA solving
});
console.log('Manual CAPTCHA solving needed:', liveURL);
}
});

Multi-Stage Workflows​

Chain multiple hybrid sessions for complex business processes. Each stage alternates between automated steps and human handoffs.

Browserless.liveComplete means the link completed, not that the person finished the task: timeout, cancellation, or replacement can also trigger it. Verify each stage's expected page state before advancing. Viewer-driven completion waits about two seconds after the last viewer disconnects, allowing a quick reload without completing the link.

const multiStageWorkflow = async () => {
const browser = await puppeteer.connect({
browserWSEndpoint: 'wss://production-sfo.browserless.io?token=YOUR_TOKEN'
});

try {
const page = await browser.newPage();
const cdp = await page.createCDPSession();

await page.goto('https://practice.expandtesting.com/inputs');
await page.waitForSelector('input[name="input-number"]');

// Stage 1: User fills the number input via liveURL
let { liveURL } = await cdp.send('Browserless.liveURL', {
timeout: 180000 // 3 minutes for number entry
});
console.log('Stage 1 - Enter a number:', liveURL);
await new Promise(resolve => cdp.on('Browserless.liveComplete', resolve));
const numberEntered = await page.$eval(
'input[name="input-number"]',
input => input.value !== '' && input.checkValidity()
);
if (!numberEntered) {
throw new Error('Stage 1 ended without a valid number');
}

// Stage 2: Automated filling of text and password fields
await page.type('input[name="input-text"]', 'Automated text entry');
await page.type('input[name="input-password"]', 'SecurePass123');
console.log('Stage 2 - Automated text and password fields completed');

// Stage 3: User fills the date input via liveURL
({ liveURL } = await cdp.send('Browserless.liveURL', {
timeout: 300000 // 5 minutes for date selection
}));
console.log('Stage 3 - Select a date:', liveURL);
await new Promise(resolve => cdp.on('Browserless.liveComplete', resolve));
const dateEntered = await page.$eval(
'input[name="input-date"]',
input => input.value !== '' && input.checkValidity()
);
if (!dateEntered) {
throw new Error('Stage 3 ended without a valid date');
}

console.log('Multi-stage workflow completed successfully');

} finally {
await browser.close();
}
};

Error Recovery and Resilience​

Implement retry logic for production hybrid automation. This helper retries on transient failures but exits immediately on permanent errors like invalid tokens or rate limits.

import puppeteer from 'puppeteer-core';

const createResilientLiveSession = async (cdp, options = {}) => {
const { timeout = 300000, quality = 50, maxRetries = 2 } = options;

for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const { liveURL, liveURLId } = await cdp.send('Browserless.liveURL', {
timeout,
quality,
type: 'jpeg'
});

console.log(`Live session created on attempt ${attempt}`);
return { liveURL, liveURLId };

} catch (error) {
console.log(`Live session attempt ${attempt} failed:`, error.message);

// Don't retry on permanent errors
if (error.message.includes('Invalid token') ||
error.message.includes('Rate limit exceeded')) {
throw error;
}

if (attempt === maxRetries) {
throw new Error(`Failed to create live session after ${maxRetries} attempts: ${error.message}`);
}

// Brief wait before retry
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
};

(async () => {
let browser = null;

try {
const BROWSERLESS_TOKEN = 'YOUR_API_TOKEN_HERE';

browser = await puppeteer.connect({
browserWSEndpoint: `wss://production-sfo.browserless.io?token=${BROWSERLESS_TOKEN}`,
});

const page = await browser.newPage();
await page.goto('https://practicetestautomation.com/practice-test-login/');

const cdp = await page.createCDPSession();

const { liveURL, liveURLId } = await createResilientLiveSession(cdp, {
timeout: 300000,
quality: 50,
maxRetries: 3
});

console.log('Live URL created:', liveURL);
console.log('Live URL ID:', liveURLId);

await new Promise((resolve) => {
cdp.on('Browserless.liveComplete', resolve);
});

console.log('Live session completed');

} catch (error) {
console.error('Error:', error);
} finally {
if (browser) {
await browser.close();
}
}
})();

FAQ & Troubleshooting​

Why am I getting a 403 Forbidden error?

Your API token is missing or expired. Pass it as a ?token= query parameter in the WebSocket or HTTP URL. Verify the token in your account dashboard.

My script works locally but fails on Browserless

Local browser settings may differ from the Browserless environment. Use launch parameters to match your local setup (viewport, user agent, timezone). See launch parameters for the full list.

Next steps​

Was this page helpful?