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

Migrate to BAP

BAP borrows Puppeteer's method names in TypeScript and Playwright's in Python, but it isn't a drop-in replacement for either: it runs over BrowserQL instead of a live CDP session, so anything that needs direct browser control has no equivalent. This guide covers what changes, what doesn't carry over, and what BAP adds that neither library has.

Prerequisites
  • An existing Puppeteer or Playwright script
  • A Browserless API token from your account dashboard
  • The BAP Quickstart if you haven't installed and connected yet

Should you migrate?

Migrate to BAP when you want BrowserQL's managed stealth, proxies, and CAPTCHA solving without hand-writing GraphQL, and your script doesn't rely on page.mouse, page.keyboard, frames, or browser contexts. If it does, keep the script as-is and connect it to Browsers as a Service (BaaS) over CDP instead.

Method differences

Page borrows Puppeteer's method names, but some shared methods behave differently:

MethodPuppeteerBAP
$eval(selector, fn)Runs your function against the matched element and returns its resultReturns the text content of the matched selector. No function argument
$$eval(selector, fn)Runs your function against all matched elementsDelegates to mapSelector and returns structured MapSelectorResponse[]
evaluate(fn, ...args)Passes serialized arguments and returns deserialized resultsAccepts a string or function, always returns string | null, and passes no arguments
waitForRequest / waitForResponseAccept a URL string or a predicate functionAccept a URL string or an options object. No predicate functions
scroll()Not on Page. You use mouse.wheel() or evaluate()First-class method with selector and coordinate targeting

What has no BAP equivalent

These Puppeteer APIs have no BAP equivalent, because they need a live CDP session:

  • Input devices: no page.keyboard, page.mouse, or page.touchscreen
  • Frames: no page.frames(), page.mainFrame(), or frame targeting
  • Workers: no page.workers()
  • Function exposure: no exposeFunction()
  • Emulation: no emulate() or emulateCPUThrottling(), though emulateMediaType() is supported
  • Security and cache: no setBypassCSP(), setCacheEnabled(), or setOfflineMode()
  • Coverage, tracing, and accessibility: no page.coverage, page.tracing, or page.accessibility

When only one step needs the missing API

Migrating isn't all-or-nothing. Two escape hatches keep the rest of the script on BAP:

  • Hand the session to Puppeteer or Playwright. reconnect() returns a CDP browserWSEndpoint for the same live browser, so BAP can handle the stealth, proxying, and CAPTCHA work, then your existing CDP code drives the step that needs page.mouse or frames. Nothing is re-run and no state is lost.
  • Send the mutation yourself. For a BrowserQL capability the SDK doesn't wrap yet, page.send() runs any BQL document on the current session. This doesn't help with APIs that need direct CDP access, since BrowserQL has no equivalent either.

What BAP adds

BAP adds methods Puppeteer has no equivalent for, and each one has a guide:

MethodsGuide
html(), text(), markdown(), mapSelector()Extracting content
reject(), fulfill(), request()/response()Network control
proxy()Stealth & humanization
solve(), solveImageCaptcha()Solving CAPTCHAs
reconnect()Reconnecting to sessions
liveURL(), stopSessionRecording(), switchToWindow()Live URLs & recording

check()/uncheck() are checkbox helpers, preferences() sets session-wide defaults such as the timeout, and loadSecret() fills a credential from a 1Password integration without exposing it to your script.

FAQ & Troubleshooting

Can I reuse my existing script unchanged?

Not without changes. BAP covers the subset of Puppeteer or Playwright that BrowserQL can express, so anything using page.mouse, page.keyboard, frames, browser contexts, or page.route() needs rewriting. If only a step or two needs those APIs, hand the session to your existing CDP code with reconnect(). If you'd rather not change the script at all, use BaaS over CDP instead.

Why does evaluate() return a string when my function returns an object?

evaluate() always resolves to a string (or null/None), and it takes no arguments. Serialize inside the page with JSON.stringify() and parse the result yourself.

Next steps

Was this page helpful?