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.
- 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
- TypeScript
- Python
Page borrows Puppeteer's method names, but some shared methods behave differently:
| Method | Puppeteer | BAP |
|---|---|---|
$eval(selector, fn) | Runs your function against the matched element and returns its result | Returns the text content of the matched selector. No function argument |
$$eval(selector, fn) | Runs your function against all matched elements | Delegates to mapSelector and returns structured MapSelectorResponse[] |
evaluate(fn, ...args) | Passes serialized arguments and returns deserialized results | Accepts a string or function, always returns string | null, and passes no arguments |
waitForRequest / waitForResponse | Accept a URL string or a predicate function | Accept 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 |
Page borrows Playwright's method names, but some shared methods behave differently:
| Method | Playwright | BAP |
|---|---|---|
eval_on_selector(selector, expression) | Runs expression against the matched element and returns its result | Returns the text content of the matched selector. No expression argument |
eval_on_selector_all(selector, expression) | Runs expression against every matched element | Delegates to map_selector and returns a list of MapSelectorResponse dicts |
evaluate(expression, arg) | Passes a serialized arg and returns the deserialized result | Accepts only an expression string, always returns str | None, and takes no arg |
wait_for_selector(selector, state=...) | Accepts a state predicate (attached, visible, hidden, detached) | Accepts visible/hidden booleans, no attached/detached states |
| Typing into a field | fill() sets the value instantly; press_sequentially() types key by key | type() is the only entry point, with a per-character delay range for human-like typing |
What has no BAP equivalent
- TypeScript
- Python
These Puppeteer APIs have no BAP equivalent, because they need a live CDP session:
- Input devices: no
page.keyboard,page.mouse, orpage.touchscreen - Frames: no
page.frames(),page.mainFrame(), or frame targeting - Workers: no
page.workers() - Function exposure: no
exposeFunction() - Emulation: no
emulate()oremulateCPUThrottling(), thoughemulateMediaType()is supported - Security and cache: no
setBypassCSP(),setCacheEnabled(), orsetOfflineMode() - Coverage, tracing, and accessibility: no
page.coverage,page.tracing, orpage.accessibility
These Playwright APIs have no BAP equivalent, because they need a live CDP session:
- Browser contexts: no
new_context().Browser.new_page()opens directly against the BrowserQL endpoint - Input devices: no
page.keyboardorpage.mouse - Frames: no
page.frames,page.main_frame, or frame targeting - Function exposure: no
expose_function() - Generic interception: no
page.route(). Usefulfill(),reject(),request(), andresponse()instead, which match filters server-side rather than running a Python callback per request - Tracing and accessibility: no
page.context.tracingor accessibility snapshots
What BAP adds
- TypeScript
- Python
BAP adds methods Puppeteer has no equivalent for: html(), text(), markdown(), mapSelector(), check()/uncheck(), reject(), proxy(), solve(), solveImageCaptcha(), liveURL(), reconnect(), switchToWindow(), stopSessionRecording(), preferences(), loadSecret(), fulfill(), and request()/response().
BAP adds methods Playwright has no equivalent for: html(), markdown(), map_selector(), reject(), proxy(), solve(), solve_image_captcha(), live_url(), reconnect(), switch_to_window(), stop_session_recording(), preferences(), and load_secret().
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 you want to run existing scripts unchanged, 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.