# Zendriver: async browser automation through Chrome DevTools Protocol

> Zendriver is an AGPL-3.0 Python framework for controlling a real Chrome browser through the Chrome DevTools Protocol. Its async API, profile handling, and element lookup model suit Python developers automating browser tasks, although its claims of undetectability do not guarantee that anti-bot systems will ignore automated traffic.

**cdpdriver/zendriver** — A blazing fast, async-first, undetectable webscraping/web automation framework based on ultrafunkamsterdam/nodriver. Now with Docker support!

- Repository: https://github.com/cdpdriver/zendriver
- Website: https://zendriver.dev
- Stars: 1,459 · Forks: 109
- Language: Python
- License: AGPL-3.0
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/cdpdriver-zendriver

## Zendriver's CDP approach and intended users

Zendriver addresses a specific problem in browser automation: controlling Chrome without going through the Selenium WebDriver layer. It is intended for Python developers who need to visit sites, scrape rendered content, run JavaScript, upload files, inspect network activity, or reproduce interactions that require a browser. The framework starts a real browser and communicates with it through the Chrome DevTools Protocol, or CDP.

This architecture is the project's main distinction. The README says Selenium and WebDriver are not required, while `zendriver.start()` creates the browser context and `browser.get()` opens a URL. CDP also provides the control surface used by higher-level methods for screenshots, element lookup, cookies, tabs, downloads, and other browser operations. The repository includes examples for waiting for pages, monitoring network activity, setting a user agent, performing drag operations, and expecting downloads. Those examples indicate that the intended scope is broader than a one-off page fetcher.

The target audience is therefore developers working in Python 3.10 through 3.13 who are comfortable with `asyncio`. It may fit developers building an authenticated browser workflow, a scraping service, or an automation tool that must interact with a rendered interface. It is less appropriate for teams that want a stable cross-browser abstraction, because the documentation and package metadata focus on Chrome rather than Firefox or WebKit.

Zendriver calls itself "undetectable," but that claim needs careful interpretation. The README qualifies it as almost impossible to detect and attributes the approach to using CDP instead of Selenium. Anti-bot systems can inspect more than the automation API. They may consider network behavior, browser state, interaction patterns, account history, and site-specific signals. The README gives no independent guarantee that automated traffic will pass Cloudflare, CAPTCHA systems, or other anti-bot controls, so those outcomes should not be treated as promised.

## Async startup, page navigation, and shutdown

The smallest documented workflow has four browser operations: start Chrome, navigate to a URL, save a screenshot, and stop the browser. Each potentially asynchronous operation is awaited. An `asyncio.run(main())` entry point connects the script to Python's event loop, while the `if __name__ == "__main__"` guard keeps startup tied to direct execution.

The data flow is straightforward. `zd.start()` initiates browser communication. The returned browser object exposes `get()`, which receives the target URL and returns a page object. The page then performs operations such as `save_screenshot()`. Finally, `browser.stop()` closes the automation session. This is not a request made through an HTTP scraping client. Content is loaded by Chrome, so the browser's normal rendering and JavaScript execution are part of the workflow.

The package is based on `ultrafunkamsterdam/nodriver`. The README describes Zendriver as a fork formed to include unmerged fixes, add features, modernize static analysis, and make contribution through issues and pull requests more accessible. That lineage helps explain the API style, but adopters are choosing Zendriver rather than receiving the original nodriver package under its own maintenance process.

Repository organization shows implementation code under `zendriver/`, documentation under `docs/`, and practical programs under `examples/`. The development dependencies include `pytest`, `pytest-asyncio`, `mypy`, and `ruff`. Those tools affect contributor maintenance rather than runtime requirements, but they indicate that the project has explicit local checks and a test suite rather than relying only on examples.

## Installing Zendriver and running the BrowserScan example

Zendriver is published as the `zendriver` Python package. The README recommends `pip` and says equivalent package-manager commands are available. The repository requires Python 3.10 or newer, so that version constraint should be checked before installation.

The documented installation command is:

```bash
pip install zendriver
```

After installation, save a Python file that imports `asyncio` and `zendriver`, starts Chrome, opens the BrowserScan bot-detection page, saves a screenshot, and stops the browser. This is the first use documented by the project, and it also verifies whether a local browser can start successfully.

```python
import asyncio

import zendriver as zd


async def main():
    browser = await zd.start()
    page = await browser.get("https://www.browserscan.net/bot-detection")
    await page.save_screenshot("browserscan.png")
    await browser.stop()


if __name__ == "__main__":
    asyncio.run(main())
```

The expected visible result is that the script writes `browserscan.png` after loading the named page. The README does not explain Chrome discovery, executable selection, or browser-version troubleshooting, so an installation failure involving Chrome cannot be resolved from the documented `pip` command alone. The Quickstart at `zendriver.dev/quickstart/` is the next documented source for detailed examples.

The example requires network access and a usable local Chrome installation. It also submits an automated browser session to a third-party detection page. A reader reproducing it should inspect that site's terms before automating repeated requests.

## Profiles, cookies, lookup retries, and debugging support

Zendriver manages browser state as part of browser automation. According to the README, each run uses a fresh profile by default and cleans it up when the process exits. Cookies can instead be saved to and loaded from a file, which is useful when a workflow includes a login that is inconvenient to repeat. That design favors isolation by default, but it also means authentication state may not survive unless explicitly persisted.

The README does not document the exact cookie file format, encryption behavior, concurrency policy, or protection against simultaneous writes. It also does not claim that stored session files are secrets that should be treated like passwords. Regardless of the missing format details, such files may contain active session state and should be classified according to the data they actually hold.

Element lookup accepts selectors or text and can search iframe content. The documented `timeout` behavior allows lookup to retry until an element appears, which turns lookup into a wait condition. A single text lookup through `tab.find()` also accepts a `best_match` flag. Rather than naively returning the first result, that option selects among candidates according to similarity in text length. These features are useful for dynamic pages where waiting and element selection need to be combined.

For debugging, element representations are described as showing HTML through `repr`. That can make page state visible without immediately switching to an external protocol inspector. The repository also contains `examples/network_monitor.py` and `examples/wait_for_page.py`, but the README does not explain their complete APIs. Their presence supports the existence of network monitoring and page-waiting workflows without establishing exact reliability guarantees.

## Docker support requires a separate browser environment

The repository includes a `docker-compose.yml`, but its configuration is for tests rather than a turnkey production deployment. The `test` service builds from `tests/Dockerfile`, publishes VNC on port `5911`, mounts the local `zendriver`, `tests`, and `scripts` directories, and runs with `privileged: true`. The README instead points users to the separate `cdpdriver/zendriver-docker` project for a supported browser environment.

That separate template is described as Linux-only and intended to run a real, GPU-accelerated browser that is not headless. This distinction matters. A conventional headless container is not interchangeable with the documented graphical browser arrangement, and the README provides no installation commands or production configuration for that separate project. Copying the test compose file would therefore be a mistake: it uses fixed VNC credentials, a persistent certificate volume, device-group configuration, privileged execution, and mounts from the repository.

The compose file also contains `RENDER_GROUP_GID=107`, `SWAY_RESOLUTION=1920x1080`, `WAYVNC_PORT=5911`, and `WAYVNC_ENABLE_AUTH=true`. These settings belong to the graphical test environment. The README does not say that they are required for ordinary local use, and it does not establish that this test stack is safe or suitable for production deployment.

## Maintenance, compatibility, and AGPL-3.0 obligations

The repository is not archived, and its last push was on 2026-10-02. Recent releases include `v0.17.1` on 2026-10-02, `v0.17.0` on 2026-09-27, and `v0.16.0` on 2026-08-16. This is recent release activity, but the package metadata still classifies Zendriver as `Development Status :: 3 - Alpha`. That classification is a more important adoption constraint than the recent dates for API stability.

The declared runtime dependencies include `asyncio-atexit`, `deprecated`, `emoji`, `grapheme`, `mss`, and `websockets`. Package management and browser upgrades remain separate concerns: updating Zendriver does not itself establish compatibility with every Chrome release, operating system, or website behavior. The project asks bug reports to include the operating system, Chrome version, and a reproducing code example, which shows that browser and platform context are material to diagnosis.

Zendriver uses the GNU Affero General Public License version 3, identified as `AGPL-3.0`. The license file is included in the repository, and `pyproject.toml` records the same license family. Teams embedding or modifying the software should review that license against their deployment and distribution model. This review concerns compliance obligations, not legal advice. The README does not state a permissive alternative licensing option.

Fork lineage also affects upgrade decisions. Changes originating in nodriver may need to be compared with Zendriver's own changes before a migration, and the README does not document a formal compatibility policy for upstream releases.

## Zendriver versus Selenium and nodriver

Selenium is the clearest practical alternative. Selenium uses the WebDriver model, while Zendriver communicates through CDP. That choice makes Zendriver's API asynchronous and ties browser control more closely to Chrome's debugging protocol. Teams already standardized on WebDriver may need to assess whether that model fits their deployment tooling.

The other real alternative is remaining with `ultrafunkamsterdam/nodriver`. Zendriver is a fork of that project and retains its goals and inherited design. The README gives the reason for the fork: contributions to the original repository were restricted and several proposed fixes had remained unresolved. Zendriver adds a separate contribution path and includes tooling such as `ruff` and `mypy`. Developers already standardized on nodriver should first compare the APIs and fixes present in each fork rather than assuming that installing a different package name is a drop-in replacement.

Zendriver is the wrong choice when deterministic API stability is a primary requirement, because the release is still classified as alpha. It is also wrong when the target service prohibits automation, when rendered Chrome is unnecessary, or when a simple HTTP client can retrieve the required data without executing site JavaScript.

## Conclusion

Python developers who want direct Chrome control without Selenium should assess Zendriver after checking local Chrome availability, profile persistence needs, and site terms. Projects that require contractual guarantees of invisibility, broad licensing freedom, or a fully headless Docker setup should reject it because the project uses AGPL-3.0 and describes its own anti-detection guarantee as nearly, but not completely, impossible to detect.

## FAQ

### What Python versions does Zendriver support?

The project requires Python 3.10 or newer. Its classifiers list Python 3.10, 3.11, 3.12, and 3.13.

### Does Zendriver guarantee that anti-bot systems cannot detect it?

No absolute guarantee is documented. The README calls Zendriver almost impossible to detect and attributes that claim to its use of CDP instead of Selenium, but it does not guarantee success against every anti-bot system.

### Does Zendriver persist cookies between browser runs by default?

No. The default is a fresh profile that is cleaned up on exit. Zendriver can save cookies to a file and load them in a later run when a reusable authenticated session is needed.

## Sources

- [cdpdriver/zendriver on GitHub](https://github.com/cdpdriver/zendriver)
- [License: AGPL-3.0](https://github.com/cdpdriver/zendriver/blob/main/LICENSE)
- [Project website](https://zendriver.dev)
- [README](https://github.com/cdpdriver/zendriver/blob/main/README.md)
- [Releases](https://github.com/cdpdriver/zendriver/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/cdpdriver-zendriver
