A persistent selector is checked on every unit of work for the life of the session. Use it for things that can pop up at any time and aren't part of the workflow itself, like cookie-consent banners or session-timeout dialogs.
WithPersistentSelector adds a selector to a special unit of work that GPAL checks alongside every regular unit of work for the lifetime of the session. Pair it with PersistentCallIfFound and PersistentCallIfNotFound to react when the selector matches or doesn't. This is the right tool for nags not part of your workflow's logic -- cookie banners, rate-our-site popups, session-expired modals -- things that might appear after step 2, or step 20, or never.
// Dismiss a cookie banner whenever it appears, for the rest of the session
var cookieBanner = GPAL.Selector
.WithCSS("#cookie-consent .accept-all")
.WithSelectorName("Cookie Consent Accept")
.CallIfFound((browser, uow, elements) => {
elements[0].Click();
return CallIfStatus.Handled;
})
.ToGPALObject();
GPAL.Browser
.GoTo("https://example.com")
.WithPersistentSelector(cookieBanner);
Persistent selectors run before the UOW selectors at each step -- they are checked first because a persistent element could get in the way of the UOW selector finding or interacting with the target. This is most pronounced with Selenium, where an element sitting in front of the target can block a click or fail a visibility check. It applies to image matching too: a persistent overlay on top of the target changes what the image matcher sees. They do not run on a timer or in a background thread; they run as part of GPAL's step-to-step bookkeeping.
Not every nag needs to stay registered. A cookie banner usually only appears once per session -- once you've dismissed it, there's no reason to keep checking for it. Call selector.Remove() from inside the CallIfFound handler to remove that selector from the persistent unit of work after it's done its job. For handlers that were attached at multiple scopes (selector, UOW, and global), or that you registered as a named method and want gone everywhere at once, call browser.RemoveCallIfHandlerEverywhere(handler) -- it strips that delegate from every CallIfFound and CallIfNotFound list it was added to.
int FoundCookieBanner(Browser browser, UnitOfWork uow, List<GPALElement> elements)
{
elements[0].Click();
// one-time nag - stop checking for it
uow.CurrentSelector.Remove();
// and remove this handler from anywhere else it might be registered
browser.RemoveCallIfHandlerEverywhere(FoundCookieBanner);
return CallIfStatus.Handled;
}
var cookieBanner = GPAL.Selector
.WithCSS("#cookie-consent .accept-all")
.CallIfFound(FoundCookieBanner)
.ToGPALObject();
GPAL.Browser
.GoTo("https://example.com")
.WithPersistentSelector(cookieBanner);
By default, a persistent selector is checked against the top-level document only. If the nag renders inside an iframe -- a third-party cookie-consent widget, an embedded chat popup -- chain InPersistentIframe(iframeSelector) onto WithPersistentSelector. On every check, GPAL first locates that iframe in the main document, then searches for the persistent selector inside it. If the iframe isn't present for a given check, that pass is simply skipped.
// The cookie banner lives inside an iframe, not the main document
var iframeSelector = GPAL.Selector.WithCSS("#consent-frame").ToGPALObject();
var cookieBanner = GPAL.Selector
.WithCSS("#cookie-consent .accept-all")
.CallIfFound((browser, uow, elements) => {
elements[0].Click();
return CallIfStatus.Handled;
})
.ToGPALObject();
GPAL.Browser
.GoTo("https://example.com")
.WithPersistentSelector(cookieBanner)
.InPersistentIframe(iframeSelector);
With OttoMagic, a persistent selector searches all frames by default -- including the target frame -- so InPersistentIframe is optional and the element will still be found without it. On sites with hundreds of frames, that unbounded search is slow. InPersistentIframe limits OttoMagic to content frame zero, keeping persistent selector checks fast. Other automation engines may handle frame scoping differently.
Pass a selector that finds the iframe element itself. Not the element inside it. The iframe selector is always looked up in the main document; only the persistent selector's own search is redirected into that iframe.
Showing off some plain text in these paragraphs eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Here you can find different accents and emphasis sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
This is a link and how it could look like bestlinkinthebeautifulworld. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Here's just some classic bold text adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam notBoldSecondbestlinkinthebeautifulworld illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Obcaecati, iste distinctio veritatis eligendi laboriosam adipisicing elit illo nostrum corporis at adipisicing elit libero vel voluptas? Expedita, adipisicing facere dolores voluptatem ad ab rem assumenda soluta!
Other cuple of colors in case we want to emphasize several ways adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam adipisicing elit illo nostrum corporis at voluptatem libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta! Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quod veniam, quam ad expedita laborum sed at voluptates culpa ipsam ut vel. Ullam temporibus a mollitia quod aliquam ratione exercitationem nesciunt.
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta! Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quod veniam, quam ad expedita laborum sed at voluptates culpa ipsam ut vel. Ullam temporibus a mollitia quod aliquam ratione exercitationem nesciunt.
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Repudiandae quas consequuntur illo numquam assumenda autem exercitationem distinctio perspiciatis in natus. Eius dicta similique ipsam ipsa minima, nemo quae enim tempore.
GPAL
.CallIfNotFound(GenericCallIfNotFound)
.WithPublishToConsole();
//System.Drawing.Rectangle windowSize = new System.Drawing.Rectangle(10, 10, 1500, 1024);
// NOTE: we have to set browser = before we execute any steps
// this is due to the 'GenericCallIfNotFound' which might throw an exception, and BankScraper will not have the browser set when it calls scraper.Close()
// until the complete fluent line gets executed (meaning every step, meaning browser is not set until everything else succeeds)
browser = GPAL.Browser
.WithBrowserType(Enums.BrowserType.Chrome)
.WithProfileDataDirectory(ChromeProfileLocation)
.WithUseAutomationEngine(AutomationEngine.Selenium)
.WithWindowSize(new System.Drawing.Rectangle(0,0,1920,1080))
.ToGPALObject();