What Apostate changes
Bot protection looks for traces of automation, such asHeadlessChrome in the User-Agent, navigator.webdriver set to true, and the 800x600 screen of headless Chrome. Apostate’s browser shows none of them. It presents the machine of a persona, the Windows, macOS or Linux platform you choose. Detection lists the traces and what the browser does about each. A fixed seed, the value that selects the machine, presents the same machine on every run, so a failure reproduces.
The example app stands in for your protection. It refuses a signup whose User-Agent contains HeadlessChrome or whose page reports navigator.webdriver.
Set up
apostate fonts install windows is for Linux and macOS hosts (Fonts). The folder holds:
app.py, the signup app.conftest.py, the fixtures.test_signup.py, two tests.e2e.yml, a GitHub Actions workflow.node/, the Playwright Test version.
The fixtures
conftest.py starts the app, launches one browser for the test session and gives each test a new page:
browser and page, so in a project that has the pytest-playwright plugin they replace the plugin’s fixtures of the same names.
The tests
expect comes from patchright.sync_api. With driver="playwright", import it from playwright.sync_api.
Run the tests
APP_URL:
GitHub Actions
e2e.yml runs the tests on every push and pull request. Copy it to .github/workflows/e2e.yml in your repository:
- The browser download for Linux x64 is 192 MB. The cache step keeps it in
~/.cache/apostatebetween runs, andapostate installfinds it there. - The Linux archive is zstd-compressed. Python 3.14 reads it, and older versions need the
zstandardpackage or thezstdcommand (Installation). - The tests run headless, the default, so the runner needs no display.
- A runner image without Chrome’s shared libraries needs the packages that the Docker example’s Dockerfile installs.
Playwright Test for Node
The Node package needs Node 22 or later. Patchright, the driver it launches with, includes Playwright Test aspatchright/test. The fixture file replaces Playwright Test’s page with a page from Apostate:
playwright.config.mjs starts the Python app for the tests, unless APP_URL names a running site:
test and expect from ./fixtures.mjs instead of patchright/test. Run them with Patchright’s test runner:
Points for this job
- Fixed seed. Assert on values the seed decides, such as the platform, screen and GPU. The core count and memory are capped at the host’s, so they can differ between your computer and a CI runner (The host cap).
- Isolation. All tests share one browser and its user data directory, and the fixtures clear cookies before each test. For a fresh user data directory per test, make
browsera function-scoped fixture. Each test then launches its own browser, which is slower. - New pages only. Open pages with
new_page(), notnew_context(). Python explains why. - Test keys. Some bot protection products offer test keys for automated tests, such as Cloudflare Turnstile’s test sitekeys. Those tests pass whatever the browser. Apostate is for the tests that go through your real protection, such as a staging site with production keys. Test your own defenses covers that.
- Headed runs. On a Linux runner,
headless=Falseneeds Xvfb installed, and the package starts it (Linux servers).