Building an online grocery agent with Browserless Browserless, a browser-as-a-service founded by Joel Griffith, was used to build a Playwright-based AI agent that logs into Italian grocery chain Esselunga and adds items to a cart, relying on the /chromium/stealth route, an Italian residential proxy, native CAPTCHA solving, and Authenticated Profiles plus the Session API to persist cookies, localStorage, and IndexedDB across browser restarts. The test found the F5 BIG-IP TS cookie appeared only on the stealth endpoint, and the agent typed credentials one character at a time with random 70-220 ms pauses while waiting for the Browserless.captchaAutoSolved event. The author moved from Browserless's free plan to the Prototyping plan during testing because the free tier was too limited. When building an AI agent to automate an online task, we recognize the value of our local browser in terms of ease of use. We’re already logged in to most of our preferred websites; the browser’s sessions store the useful part of our most recent navigation, and we’re so used to this that we take it for granted. Our IP score is usually golden, and our hardware is legit, so we don’t get blocked on any website. But when we build an agent, all this disappears: we need a browser-as-a-service to work for us, usually running in a Linux VM inside a data center and using its IP address, which screams red flags from the first request. Today, for this issue of “Tool of the Week”, we’re using Browserless to build an AI agent that logs in to a grocery website in Italy and adds something to the cart, simulating an online shopping experience. Last week Joel Griffith, founder of Browserless https://www.browserless.io/?utm source=thewebscrapingclub&utm medium=tool-review , wrote a guest post here on what breaks in long-running authenticated sessions https://www.scraping.club/p/authenticated-scraper-sessions . He described two Browserless features for this problem: Authenticated Profiles a saved copy of cookies, localStorage, and IndexedDB and the Session API https://docs.browserless.io/baas/session-management/persisting-state?utm source=thewebscrapingclub&utm medium=tool-review&utm content=session-api a browser identity that persists on disk for days . We’ll also test this feature by closing and reopening the browser and will check if, by using the same profile, we’ll be able to avoid logging in for the following runs. The setup Browserless https://www.browserless.io/?utm source=thewebscrapingclub&utm medium=tool-review is a browser-as-a-service: you connect Playwright or Puppeteer to a remote Chromium over a WebSocket, and you configure it with query parameters in the URL. For these tests I used: - the /chromium/stealth route and /chromium as a comparison on the first target - the built-in residential proxy with proxy=residential&proxyCountry=it , so every session exits from an Italian IP - solveCaptchas=true , the native CAPTCHA solver https://docs.browserless.io/baas/bot-detection/captchas?utm source=thewebscrapingclub&utm medium=tool-review&utm content=captchas , which detects a CAPTCHA, solves it and injects the token into the page - Authenticated Profiles https://docs.browserless.io/baas/features/authenticated-profiles?utm source=thewebscrapingclub&utm medium=tool-review&utm content=authenticated-profiles and the Session API for the persistence part All the code is Python with Playwright, connected over CDP connect over cdp , always reusing browser.contexts 0 . I do this because the Browserless docs say that profiles are applied only to the default context, and the CAPTCHA events are visible only if you reuse the existing page. I started on the free plan and moved to the Prototyping plan during testing, since the free tier was a bit limited for me. First target: Esselunga The starting idea for this article was to log in to the Esselunga website one of the most famous grocery chains in Italy using my credentials. So I requested the account.esselunga.it/area-utenti/ page both with the Chromium endpoint and the stealth one. While the page was served on both cases, i got the TS cookie, a signal of the presence of the F5 BIG-IP suite https://www.f5.com/products/big-ip , only with the stealth endpoint, so I completed the test with it. The script types the email and password one character at a time, with a random pause of 70 to 220 ms, and waits for the Browserless.captchaAutoSolved event before clicking the button: endpoint = ws url "chromium/stealth", timeout=120000, proxy="residential", proxyCountry="it", proxySticky="true", solveCaptchas="true" browser = await p.chromium.connect over cdp endpoint context = browser.contexts 0 page = context.pages 0 cdp = await context.new cdp session page cdp.on "Browserless.captchaAutoSolved", lambda e: solved.set result e if not solved.done else None The native solver worked in 5 runs out of 7, with solve times between 10 and 35 seconds. In the other 2 runs it stayed in the solving state for 75 seconds without producing a token, and the checkbox in the screenshot was still empty. When the token arrived, the login POST went through F5 without problems POST /area-utenti/loginExt , then a 302 . So, everything worked? Well, not really in this case. The website asked for a 2FA on my phone, probably because I didn’t set any OS to emulate in the first place. My hypothesis is that the Linux identity triggered the second factor, even if I can’t prove it from the outside. But after the first 2FA request, Esselunga kept asking for it every time, even when I set the Browserless session to emulate macOS with the emulationOs=macos parameter and even from my normal browser. It seems that my account, once flagged, stays in the “ask for 2FA” state for a while, regardless of the device you use. For this, I decided to move on to the second target, Tigros. Second target: Tigros Tigros is another Italian grocery chain, with an online shop at tigros.it/tigros-spesa-online . The stack is completely different from Esselunga: - Cloudflare is in front of the site server: cloudflare , cf-ray - the shop is a Vue single-page app that talks to an API under /ebsn/api/ - the CAPTCHA is reCAPTCHA Enterprise, loaded with render=