Skip to main content
The browser is real and the network is real. Most blocks come from the automation side, and they’re avoidable.

Keep the logic minimal

The shortest script that gets the result is the best one. It’s faster, it fails in obvious ways, and it gives sites the least to notice. Every retry loop, event listener, request interceptor, “stealth” helper or defensive wait is a place for bugs to hide and a signal that this isn’t a person. Add a mechanism after a specific site has shown you that you need it, and add the smallest one that works.

Go fast, then verify

Before writing browser steps, ask whether the result is reachable faster:
  • Go straight to the URL that has the result instead of clicking through navigation.
  • Do several pages in one session. Creating a session is the expensive step.
  • When start-up itself is the bottleneck (a request handler, an agent loop), keep a browser pool warm and acquire from it.
  • Sign in once on a persisted profile and reuse it.
  • No fixed sleeps. Rely on the framework’s auto-waiting and stop the session the moment you have the result.
Then run it against the live target and check the result, not the exit code: the data is right, the action took effect, the session was stopped. The record under Telemetry → Sessions shows duration and bandwidth, which is how you know a run was actually fast.

Use Patchright

Stock Playwright and Puppeteer enable CDP domains (Runtime.enable, console and binding hooks) that anti-bot vendors fingerprint. Patchright is Playwright with those removed and the same API. Use it for every Driver integration. Use stock Playwright only if you’ve measured that your target doesn’t care.

Keep the browser’s defaults

  • Reuse browser.contexts()[0] and context.pages()[0]. The browser opened with a context and a tab already.
  • Keep the default viewport, user agent, locale, permissions and flags. Set windowSize, country, timezone and language on the create call instead; Driver keeps them coherent with the network the session egresses from.
  • Don’t add timeouts and navigation settings until a specific site needs them.

Never launch a browser

Driver provides the browser. Connect to the returned cdpUrl. Not in a Driver integration:
  • chromium.launch(), chromium.launchPersistentContext(), puppeteer.launch()
  • playwright install, or any browser download
  • invented localhost CDP ports or CDP hosts Driver didn’t return

Keep CDP hooks narrow

Broad listeners, request routing and injected scripts make the browser behave unusually, and that’s detectable. Use them only when the task genuinely needs them. Avoid by default (Playwright and Patchright names, Puppeteer equivalents in brackets):
  • page.on(...), context.on(...), browser.on(...)
  • page.route(...), context.route(...) (page.setRequestInterception(true))
  • page.exposeBinding(...), page.exposeFunction(...) (page.exposeFunction)
  • page.addInitScript(...), context.addInitScript(...) (page.evaluateOnNewDocument)
  • Puppeteer’s page.setUserAgent, page.emulateTimezone, page.emulate, page.setViewport: fingerprint patches, see below
Prefer direct actions and reads: page.goto, page.click, page.fill, page.locator, page.title, page.textContent. When a hook is unavoidable, listen for one event type, route one URL pattern, and remove it as soon as the step is done.

Don’t patch fingerprints

The point of Driver is that the browser is genuine. Overriding navigator, the user agent, WebGL, canvas, timezone or locale from your script makes a real browser look fake. If you need a different location, ask for it on the create call.

Debug with the live view first

When a page behaves unexpectedly, open the session’s live view and look before adding hooks or logging every event. You can take the mouse and keyboard yourself, which is often the fastest way to see what a site is doing.

Always stop sessions

Wrap browser work in try / finally so the session is stopped even when navigation or extraction fails. Stopping releases the browser and ends billing.
Set a duration that matches the job. Sessions end on their own at the duration (an hour by default), which caps the cost of anything you forget. A browser from a pool is released the same way, in the finally; its lease timeout plays the role of duration.

Reuse logins with profiles

Signing in on every run is slow and looks suspicious. Sign in once under a named profile with persist: true, then start later sessions with the same name. They open signed in.

Handle the errors that matter

Full list on Errors.

Label your sessions

Pass note on create, or PATCH it later, with something a teammate would recognise (“nightly price check, client X”). It shows in the dashboard’s session list and record.