The Popup Will Come: Building an Interruption Layer for BAS Scripts
Cookie banners, newsletter modals, session expiry and surprise layout tests break more BAS scripts than bad selectors ever will. Here is the interruption layer I build into every Browser Automation Studio project.
Pavel Duglas
AI Automation & MVP Architect
Every BAS script I have debugged for a client has the same story. It worked for two weeks, then one morning half the threads started failing on a click that had worked ten thousand times. The selector was fine. The page was fine. A cookie banner had changed its markup, or a newsletter modal now appeared on the third page view, or the site started showing a “rate us” overlay to visitors from one country. The element was there, just covered by something nobody planned for.
Most people respond by adding another “if element exists, click close” block right before the failing step. A month later the script is a maze of one-off patches, and it still breaks. I stopped doing that. Now every project gets a dedicated interruption layer from day one. This article shows how I structure it.
Your selectors are not the real problem
When a click fails, the instinct is to blame the selector. In my experience, most failures in mature scripts come from the page being in a state the script did not expect, not from the target element being hard to find.
The script assumes a linear world: open page, click login, type email, submit. Real sites are not linear. They are a main flow plus a cloud of things that can appear at any moment: consent dialogs, promo modals, chat widgets that slide over buttons, “your session expired” screens, A/B tests that move the button, captchas, maintenance pages. If your script only knows the main flow, every one of those becomes a crash.
The fix is to separate two concerns:
- The flow: the steps that do the actual work.
- The interruptions: everything that can get in the way, handled in one place.
Three kinds of interruptions
Before building anything, I sort what can go wrong into three buckets, because each needs a different response.
1. Dismissable overlays
Cookie banners, newsletter popups, app install prompts, chat widgets, “we use cookies” bars at the bottom. They block clicks but closing them is harmless. Response: dismiss and continue.
2. State interruptions
Session expired, forced logout, “confirm it’s you” screens, captcha, age gate, region selector. Closing them does not help; the script needs to do something meaningful, like log in again or solve the challenge. Response: run a recovery routine, then return to a known checkpoint.
3. Hard stops
Account banned, maintenance page, 403 from the proxy, the product page is gone. Response: stop this task, record why, and move on. Retrying a ban is how you burn a whole proxy pool.
When you mix these up, bad things happen. A script that treats a captcha like a popup clicks around forever. A script that treats a ban like a timeout retries it fifty times.
The interruption layer: one function, called at checkpoints
In BAS I build this as a single function, usually called HandleInterruptions. Every flow step that matters calls it before acting. The function looks at the current page, decides which bucket it is in, and returns a result the flow can react to: clear, recovered, or stop with a reason.
A registry, not a pile of if blocks
Inside the function, I keep the known interruptions as data, not as scattered logic. Each entry has:
- name:
cookie_banner_v2,newsletter_modal,session_expired - detector: a selector or a text check that identifies it
- type: overlay, state or hard stop
- action: the selector to click, or the name of a recovery function
- max hits: how many times per task it is allowed to fire
In practice this is a list of JSON objects in a resource or a variable, and the function loops over it. When a site adds a new popup, I add one entry. I do not touch the flow. That single change is what keeps scripts maintainable after month three.
Order matters. Check hard stops first, then state interruptions, then overlays. If the page shows a ban message under a cookie banner, you want to know about the ban, not happily close the banner and continue into a dead account.
Where to call it
Not before every block, or the script crawls. I call it at three kinds of points:
- After every navigation or anything that triggers a page load.
- Before every critical action: submitting a form, clicking a buy button, saving data.
- On failure: when a click or wait times out, call the handler once, then retry the step. This catches the overlays that appeared between your checks.
That third point is the most valuable. Wrap the important steps so that a failure does not immediately end the thread. The pattern is: try the step, if it fails run HandleInterruptions, if it returned clear or recovered retry the step once, otherwise propagate the failure.
Bound everything
An interruption layer without limits is an infinite loop waiting to happen. A modal that reappears every time you close it, a login that redirects back to login, a captcha solver that keeps failing. I enforce three limits:
- Per-entry max hits per task, usually 2 or 3.
- Total handler invocations per task, around 10.
- Total recoveries per task, usually 1 or 2 logins.
When a limit is hit, the result becomes stop with a reason like loop:newsletter_modal. That reason is gold when you review logs later.
Pre-empt instead of dismissing
The cheapest popup is the one that never appears. Once I know a site, I try to prevent overlays rather than click them away:
- Set consent state up front. Most consent tools store the decision in a cookie or localStorage. Accept the banner once manually, inspect what got written, and set the same values in your profile or via JavaScript before the first real navigation. Many banners never render after that.
- Block third-party widget scripts. Chat widgets, survey tools and some consent managers load from known domains. Use the request blocking options in BAS to stop those URLs. Fewer scripts also means faster pages and lower proxy traffic.
- Reuse profiles. A returning profile with existing cookies sees fewer first-visit modals than a fresh one every time.
- Keep the environment consistent. If your proxy is in Germany but the browser language is Russian and the timezone is Vietnam, expect region selectors, extra verification and odd layouts. Consistency removes a whole class of interruptions.
- Consider the HTTP client. If the data you need comes from an API the page calls, the HTTP client mode in BAS skips the entire visual layer. No banners, no modals. It is not always possible, but always worth checking first.
I still keep the overlay entries in the registry even after pre-empting, because consent tools update and the cookie format changes. Pre-emption reduces frequency; the handler covers the gaps.
Check “am I on the right page”, not just “does the element exist”
A quiet failure mode: the script finds an element with the right selector on the wrong page. A login form on a session-expired screen, a “Submit” button inside a modal, a price on a recommended product instead of the main one. The step succeeds and the data is garbage.
So each checkpoint in my flow has a page identity assertion: a combination of URL pattern plus one or two markers that only exist on that page. Before extracting or submitting, the script confirms where it is. If the assertion fails, that is a signal to call the interruption handler, not to proceed.
This turns “the script did something weird” into “expected product page, got region selector”, which you can actually fix.
Make unknown interruptions loud
The registry handles what you know. The real value comes from learning about what you do not know yet. When a step fails and the handler finds nothing it recognizes, I log a structured “unknown blocker” event:
- task id and thread number
- URL and page title
- the step that failed
- a screenshot
- the page HTML, saved to a file
- the proxy and profile used
Once a day I look at unknown blockers grouped by URL and title. Usually two or three patterns cover most of them. Each pattern becomes a new registry entry. After a few weeks the unknown rate drops close to zero, and new spikes tell me the site changed before a client tells me.
One more thing: count every handler hit, even successful ones. If cookie_banner_v2 suddenly fires on 100% of tasks instead of 5%, your pre-emption broke. If session_expired jumps, the site may have shortened session lifetime or started flagging your profiles. These counters are an early warning system you get almost for free.
The checklist I use on new BAS projects
- Build
HandleInterruptionsas a function before writing the main flow. - Store known interruptions as data: detector, type, action, max hits.
- Check hard stops first, then state, then overlays.
- Call the handler after navigation, before critical actions, and on failure.
- Retry a failed step once after the handler, never in an open loop.
- Enforce per-entry, per-task and recovery limits.
- Pre-empt consent and widgets with cookies, storage and request blocking.
- Assert page identity before extracting or submitting.
- Log unknown blockers with screenshot and HTML, review them daily.
- Track hit counts per entry and alert on sudden changes.
None of this is clever. It is just admitting that the page will not behave and designing for it. The scripts I build this way do not stop breaking entirely, but when they break, they break in one place, with a clear reason, and the fix takes minutes instead of an afternoon of reading through tangled conditions.
FAQ
Won't calling an interruption handler at every checkpoint slow my BAS script down?
Only if the detectors are expensive. Keep each detector to a fast existence check with a short or zero wait, and order the registry so the most common entries are checked first. In practice the handler adds a fraction of a second per checkpoint, which is far cheaper than a failed thread, a wasted proxy session and a retry from scratch.
Should I just use a generic 'close any modal' approach instead of a registry?
I tried that and stopped. Generic close logic eventually clicks the wrong thing, like a modal that asks you to confirm a purchase or a dialog you actually needed to read. A registry of named, known interruptions is explicit, testable and shows up clearly in logs. Unknown overlays should be logged and reviewed, not blindly dismissed.
How do I handle captchas inside this layer?
Treat a captcha as a state interruption, not an overlay. The registry entry points to a recovery function that calls your solving service, verifies the page actually moved forward, and counts toward the recovery limit. If captchas start firing on most tasks, that is a signal about your proxies, profiles or behavior, and the fix belongs there rather than in more solving attempts.
Related articles
Done for you
Rather not build it yourself? I will build a BAS bot for your task
Multithreading, proxies, antidetect profiles and scheduled runs. The whole project and every access stay with you.
from $300 · 3 to 7 days
"Pavel is a great specialist, he delivered the parser in a few hours. I will keep ordering from him. Huge thanks for the professionalism and the speed."