AntibotTester walks a list of bot-detection pages, captures a sequence of screenshots on each, and reacts to a few page-specific quirks along the way. It demonstrates nesting BrowserType and AutomationEngine loops with continue-based filtering, a global CallIfNotFound handler with an ignore list, and per-site conditional logic driven by UrlHelper.AreEquivalent.
Here's a trimmed-down version of the workflow, start to finish. Each piece is broken down and explained below.
using GenerallyPositive;
using GenerallyPositive.Browser;
using static GenerallyPositive.Enums;
GPAL
.CallIfNotFound(GenericCallIfNotFound)
.WithUseOttoMagic(@"C:magic")
.WithPublishToConsole()
.WithAutoUpdateWebDriver();
List<string> urls = new List<string>
{
"https://bot-detection.example.com/score",
"https://fingerprint.example.com/check",
"https://example.com/cookie-wall"
};
IBrowser browser = GPAL.Browser
.WithBrowserType(BrowserType.Chrome)
.WithUseStealth(StealthType.DarkMode | StealthType.GoogleReferrer | StealthType.PatchChromeDriver | StealthType.CDP)
.WithLoadImages(true)
.WithWindowSize(new System.Drawing.Rectangle(10, 10, 2000, 2000))
.WithDriverLocation(@"C:drivers")
.WithUseAutomationEngine(AutomationEngine.OttoMagic)
.WithProfileDataDirectory(@"C:ProfilesStealth")
.ToGPALObject();
foreach (string url in urls)
{
browser.GoTo(url);
browser.WaitFor(5_000);
if (true == UrlHelper.AreEquivalent(url, "https://example.com/cookie-wall"))
{
browser.WaitFor(7_000);
browser.LeftClick("#cookie-accept-all");
}
for (int page = 0; page < 10; page++)
{
string fileName = $@"pixpage_{page}.PNG";
GPALFile screenCapFile = GPAL.File.WithFileName(fileName).ToGPALObject();
GPAL.CaptureScreen(browser).SaveToFile(screenCapFile);
_ = browser.PageDown;
if (0 < page && true == browser.IsEndOfPage())
break;
}
}
browser.Close(true);
public static CallIfStatus GenericCallIfNotFound(IBrowser browser, List<GPALElement> foundElements, List<GPALElement> matchedElements, Selector selector, bool matchedAll)
{
string message = $"Unable to locate Selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
GPAL.PublishSimpleEvent(GPALEventType.ERROR, message, browser, GPALObjectType.Browser);
return CallIfStatus.Handled;
}
GPAL.CallIfNotFound registers a workflow-wide handler that fires whenever a selector fails to match, on top of any per-selector CallIfNotFound. Here it logs an ERROR event with the selector's name and first path, then returns CallIfStatus.Handled so the workflow continues instead of throwing.
GPAL
.CallIfNotFound(GenericCallIfNotFound)
.WithUseOttoMagic(@"C:magic")
.WithPublishToConsole()
.WithAutoUpdateWebDriver();
public static CallIfStatus GenericCallIfNotFound(IBrowser browser, List<GPALElement> foundElements, List<GPALElement> matchedElements, Selector selector, bool matchedAll)
{
string message = $"Unable to locate Selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
GPAL.PublishSimpleEvent(GPALEventType.ERROR, message, browser, GPALObjectType.Browser);
return CallIfStatus.Handled;
}
The real test program keeps a List<Selector> of selectors that are expected to be missing on some sites, and checks selector.Name against it before logging - so genuinely optional selectors don't spam the error log.
WithUseStealth combines several flags - dark mode, a Google referrer header, a patched chromedriver binary, and CDP leak protection - to make the browser look less like an automated session. WithProfileDataDirectory points at a real Chrome profile directory, so cookies, history, and fingerprint-relevant state persist between runs instead of starting from a blank slate every time.
IBrowser browser = GPAL.Browser
.WithBrowserType(BrowserType.Chrome)
.WithUseStealth(StealthType.DarkMode | StealthType.GoogleReferrer | StealthType.PatchChromeDriver | StealthType.CDP)
.WithLoadImages(true)
.WithWindowSize(new System.Drawing.Rectangle(10, 10, 2000, 2000))
.WithDriverLocation(@"C:drivers")
.WithUseAutomationEngine(AutomationEngine.OttoMagic)
.WithProfileDataDirectory(@"C:ProfilesStealth")
.ToGPALObject();
StealthType is a flags enum - combine as many as the target site needs with bitwise OR. See Browser Profiles: Signed-In vs Temporary for more on persistent profile directories.
Different anti-bot pages need different one-off handling: some need extra wait time before a fingerprint check finishes, others need a cookie banner dismissed before screenshots make sense. UrlHelper.AreEquivalent compares the current URL against a known target regardless of trailing slashes or minor formatting differences, so each branch only runs for the site it applies to.
if (true == UrlHelper.AreEquivalent(url, "https://example.com/cookie-wall"))
{
browser.WaitFor(7_000);
browser.LeftClick("#cookie-accept-all");
}
The inner loop takes up to 10 screenshots, scrolling down with browser.PageDown between each. GPAL.CaptureScreen(browser).SaveToFile writes each frame to a GPALFile-backed PNG, and browser.IsEndOfPage() stops the loop early once scrolling has reached the bottom.
for (int page = 0; page < 10; page++)
{
string fileName = $@"pixpage_{page}.PNG";
GPALFile screenCapFile = GPAL.File.WithFileName(fileName).ToGPALObject();
GPAL.CaptureScreen(browser).SaveToFile(screenCapFile);
_ = browser.PageDown;
if (0 < page && true == browser.IsEndOfPage())
break;
}
Programmatic scrolling is itself a signal some anti-bot systems watch for. If a site's detection score changes after scrolling, that's the page reacting to PageDown, not a GPAL bug.
The real test program wraps all of this in nested foreach loops over BrowserType and AutomationEngine, using continue inside a switch to skip combinations that aren't relevant to the current test run (for example, skipping every engine except OttoMagic). This is a common pattern for regression-testing a workflow across every supported configuration without duplicating the workflow code.
Array browserTypes = Enum.GetValues(typeof(BrowserType));
foreach (BrowserType bt in browserTypes)
{
switch (bt)
{
case BrowserType.Chrome: break;
default: continue;
}
Array automationEngines = Enum.GetValues(typeof(AutomationEngine));
foreach (AutomationEngine ae in automationEngines)
{
switch (ae)
{
case AutomationEngine.OttoMagic: break;
default: continue;
}
// build browser and run the workflow body here
}
}
A switch with continue for every value you want to skip keeps the loop body focused on the combinations you actually care about, and makes it obvious at a glance which engines and browser types are in scope for a given test run. See Automation Engines and Conditional Logic.
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();