Changelog

September 27, 2026 ยท View on GitHub

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

Fixed

Added

Changed

Removed

[0.17.0] - 2026-09-26

This is quite a large release, including a number of fixes from first-time contributors (thank you!) as well as ports for all nodriver features and bugfixes that have been added upstream since Zendriver was forked. Additionally, I've managed to remove the last remaining # type: ignore comments, which uncovered a few additional bugs that have also been fixed in this release.

This release marks two years of Zendriver! Huge thanks to everyone that has contributed so far.

Fixed

  • Fix ValueError on startup when the lang option is set @bercedev
  • Fix verify_cf() never clicking the Turnstile checkbox on Turnstile markup where the cf-turnstile-response/cf_challenge_response input is rendered as a document-level sibling of the challenge's shadow-DOM host rather than nested inside it. The lookup was scoped to host_element, always found nothing, and verify_cf() returned before ever calling mouse_click. Now queries the input from the document (tab.query_selector) instead. @A-Nolan
  • Fix HTTP method casing to resolve CDP connection issues on some Windows installations @fitudao3788
  • Fix 404 Not Found WebSocket connection error for iframe targets by routing all target connections to /devtools/page/<target_id> in Browser._handle_target_update.
  • Fix Browser.stop() always waiting 3 seconds and then force-killing the browser process, even when the browser had already exited gracefully. The exit check read Popen.returncode, which is only updated by poll()/wait(). @stephanlensky
  • Fix Browser.start() always sleeping for browser_connection_timeout before the first connection attempt. It now tries to connect immediately and only waits between retries. @stephanlensky
  • Fix Browser instances never being garbage collected after Browser.stop(). The asyncio atexit callback and the global registered instances set kept a reference to every browser, and per-target connections were left open. @stephanlensky
  • Fix the connection listener crashing with InvalidStateError when a response arrived for a command that was already cancelled (for example by asyncio.wait_for), which left later commands hanging forever @stephanlensky
  • Fix every received CDP event being kept in memory for the lifetime of its connection @stephanlensky
  • Fix Tab.find() raising ProtocolException: No search session with given id found instead of retrying when the page's document is replaced during the search @stephanlensky
  • Fix await tab raising ValueError: No listener created yet on a tab which no commands were sent to yet, such as one just opened with browser.get(url, new_tab=True) @stephanlensky
  • Fix browser.get(url, new_tab=True) intermittently raising StopIteration when the new tab was not registered yet, and browser.get() returning before the navigation when another target changed in the meantime @stephanlensky
  • Fix Widevine DRM being unavailable on platforms where Chrome installs it through the component updater, such as Windows. Removed the --disable-component-update, --disable-background-networking, --disable-backgrounding-occluded-windows and --disable-renderer-backgrounding default browser arguments @stephanlensky
  • Fix Browser.start() sometimes returning before the browser's initial tab was registered, which made browser.get(url) raise RuntimeError: coroutine raised StopIteration. browser.get(url) now raises a descriptive error if there is no tab to navigate @stephanlensky
  • Fix extensions added with Config.add_extension() not loading on Chrome 137+. The default --disable-features switch was overridden by a second one, since Chrome only uses the last. All --disable-features switches, including user-provided ones, are now merged into one, and --enable-unsafe-extension-debugging is passed when extensions are added @stephanlensky
  • Fix commands hanging forever when the browser connection closed while they were waiting for a response, for example because the browser crashed or Connection.aclose() was called. Pending commands now raise instead, and Connection.send() raises ProtocolException instead of returning None when the connection is closed @stephanlensky
  • Fix mouse helpers dispatching inconsistent button and pressure state that pages can observe. Mouse clicks and drags now report a pressure of 0.5 while a button is held and buttons=0 on release, drag movements keep buttons=1, mouse_move() no longer sends a mouseReleased event, and Tab.mouse_move() with steps now moves from the last mouse position instead of from (0, 0). The buttons argument of mouse_click() is deprecated and ignored @stephanlensky
  • Fix add_handler() with a CDP domain module (for example tab.add_handler(cdp.network, handler)) registering the domain's enum types instead of its events, so the handler was never called @stephanlensky
  • Fix synchronous event handlers sometimes calling only the last registered handler when several handlers were registered for the same event @stephanlensky
  • Fix event handlers being called a second time with only the event when they raised a TypeError. Handlers now receive the connection as a second argument only if their signature accepts one @stephanlensky
  • Fix Element.get() returning None for attributes with an empty value, such as disabled="". It now returns "" and only returns None when the attribute does not exist @stephanlensky
  • Fix remove_handlers(event_type) raising KeyError when no handlers were registered for that event @stephanlensky
  • Fix Element.get_position(abs=True) raising AttributeError @stephanlensky
  • Fix Element.get() returning dictionary methods for attribute names such as items or keys instead of None @stephanlensky
  • Fix Tab.find_elements_by_text() skipping matches which are not part of the fetched document tree @stephanlensky
  • Fix CookieJar.get_all(requests_cookie_format=True) raising ImportError when requests is not installed, and returning session cookies as already expired. It now builds http.cookiejar.Cookie objects with the standard library @stephanlensky

Added

  • Add support for unstable variants of Microsoft Edge on macOS @mokurin000
  • Add Tab.get_frames() and Element.get_frame() to access out-of-process (e.g. cross-origin) iframes as Tab objects, and an include_frames option to find(), find_all(), select() and select_all() which also searches these iframes @stephanlensky
  • Add Browser.create_context() to open a tab in a new browser context with its own proxy. Authenticated HTTP, HTTPS and SOCKS5 proxies (e.g. socks5://user:pass@host:port) are supported through a local forwarding proxy @stephanlensky
  • Add Tab.mouse_down(), Tab.mouse_up() and Tab.mouse_drag() to press, hold and release mouse buttons separately, for example to drag a slider with custom movement @stephanlensky
  • Add Browser.get_tab() to find a tab by text in its URL or title @stephanlensky
  • Add Element.shadow_children to access the children of an element's open or closed shadow root @stephanlensky
  • Add Tab.is_scrolled_to_bottom() and Tab.bypass_insecure_connection_warning() @stephanlensky

Changed

  • Tabs and other targets now share the browser's websocket connection using flat CDP sessions (Target.attachToTarget with flatten=True) instead of opening a separate websocket per target. This fixes HTTP 500 errors when connecting to iframe targets. @stephanlensky
  • Tab.set_download_path() now creates the download directory if it does not exist @stephanlensky
  • Expert mode no longer passes --disable-web-security @stephanlensky

[0.16.0] - 2026-08-16

Fixed

  • Fix pattern argument in CookieJar.save @Lyesbcb

Added

  • Add support for Microsoft Edge browser @mokurin000

[0.15.5] - 2026-07-15

Fixed

  • Fix SyntaxError on Python 3.14 in Connection._register_handlers. PEP 765 makes continue/break/return that exits a finally block a SyntaxError (was a SyntaxWarning in 3.13), so zendriver/core/connection.py failed to import under 3.14. Removed the no-op finally: continue (the loop already falls through to the next iteration, and the enclosing bare except: catches everything, so nothing changed behaviorally). Fixes #252

[0.15.4] - 2026-06-27

Fixed

  • Fix query_selector_all and select_all(include_frames=True) not searching nested iframes. CDP's querySelectorAll only queries within a single document boundary, so elements inside nested iframes were never found. Now walks the full DOM tree to collect each iframe's content_document and queries them individually. Also adds a guard for cross-origin iframes where content_document is None. @chronoAP
  • Fix Connection._register_handlers reenabling already manually enabled domains @S-Tarr
  • Fix flakey behavior in api-reponses-tutorial-2 tutorial @S-Tarr

Changed

  • Updated CDP models @nathanfallet

[0.15.3] - 2026-03-11

Fixed

  • Fix crash in browser.cookies.get_all() on Chrome 146 when the sameParty field is missing in the cookie JSON returned by CDP.
  • Fix clear_input() and clear_input_by_deleting() silently failing on React controlled inputs due to _valueTracker mechanism; also fix infinite loop in clear_input_by_deleting() on some VM environments caused by VK_DELETE being treated as backward-delete. @nathanfallet

Changed

  • Docs: core modules included in documentation reference section. Slight modifications were done, mostly to remove warnings. @Kajmany

[0.15.2] - 2025-11-29

Fixed

  • Fix race condition in Tab.query_selector and Tab.query_selector_all on stale document. @Avejack

[0.15.1] - 2025-11-18

Fixed

  • Fix NameError in Tab.query_selector and Tab.query_selector_all due to type-checking import of Element. @Avejack

[0.15.0] - 2025-11-04

Fixed

  • Fix browser.stopped to detect when the browser was closed by the user. @puc9
  • Fix typo in Browser._handle_target_update that prevented target_info updates @puc9
  • Fix #128: TimeoutError caused by Tab.xpath @ionutcatana
  • Fix WebRTC IP Leaks @ethcipher

Added

  • Add disable_webrtc and disable_webgl args to Config class @ethcipher

Changed

  • Fix .dockerignore @tomokinakamaru

[0.14.2] - 2025-09-09

Fixed

  • Fix evaluate to return the correct value instead of None for falsy values @thromer

[0.14.1] - 2025-09-07

Fixed

  • Fix calls to evaluate that return JSON: only set serialization_options when return_by_value=False @thromer
  • Fix #184: download_path error in DownloadExpectation @MadsHangaard

[0.14.0] - 2025-08-28

Fixed

  • Add nodriver evaluate serialization options for improved JavaScript evaluation @dbrun3

Added

  • Allow reset expect and intercept @nathanfallet

[0.13.1] - 2025-07-27

Fixed

  • Remove unnecessary usage of typing_extensions (fixes No module named 'typing_extensions' error) @stephanlensky
  • Fix verify_cf() @H1steria
  • Fix relative coordinates of flash_point() @H1steria

Added

  • Add flash_point() to Tab.mouse_click() @H1steria

Changed

  • Remove Connection __getattr__ and replace it with declared @property @nathanfallet
  • Deprecate Element __getattr__ and add get(name: str) method @nathanfallet

[0.13.0] - 2025-07-24

Fixed

  • Gracefully close the browser process on exit @nathanfallet

Added

  • Add support for Brave browser @H1steria

[0.12.1] - 2025-07-17

Fixed

  • Remove usage of StrEnum to fix compatibility with Python 3.10 @stephanlensky

[0.12.0] - 2025-07-17

Added

  • Added Tab.intercept @nathanfallet

[0.11.0] - 2025-07-16

Added

  • Complete rewrite of keyboard input system with new KeyEvents class in zendriver.core.keys @hvgupta
    • Added support for modifiers (Ctrl, Alt, Shift, Meta)
    • Added support for special keys including arrows, escape, delete and backspace
    • Added KeyEvents.from_text() class method for converting plain text to cdp events
    • Added KeyEvents.from_mixed_input() class method for handling mixed sequences of text, special keys to cdp events
    • Proper Handling of shift variants of keys
    • Comprehensive key event types: CHAR, KEY_DOWN, KEY_UP
    • Added key event type (DOWN_AND_UP) as a combination of KEY_DOWN and KEY_UP

Changed

  • Element.send_keys() now uses the new KeyEvents system (it is still backwards compatible with passing a string) @hvgupta
  • Key event processing now properly handles modifier key sequences @hvgupta
  • Update CDP schemas @nathanfallet

[0.10.2] - 2025-07-06

Fixed

  • Refactor to asynchronous handlers to avoid blocking the listening loop @nathanfallet

[0.10.1] - 2025-07-06

Fixed

  • Added Tab.flash_point from nodriver @nathanfallet
  • Ensured loading is finished in expect @nathanfallet

[0.10.0] - 2025-07-05

Fixed

  • Refactor element waiting loops in find_* and select* methods. @H1steria
  • Improve query_selector error handling to consistently return None when a node is not found. @H1steria
  • Add user_agent option to allow bypassing cloudflare javascript challenge in headless mode. @H1steria
  • Fixed lang parameter never being set in Browser.start @nathanfallet

Added

  • Added mouse_move and mouse_click methods from nodriver. @H1steria

[0.9.0] - 2025-07-01

Added

  • Added speed in Tab.scroll_down and Tab.scroll_up methods to control the scroll speed @nathanfallet
  • Allow to wait for promise in Element.apply method @nathanfallet
  • Added Element.clear_input_by_deleting to handle inputs with custom delete behavior @nathanfallet
  • Added Tab.xpath from nodriver @nathanfallet

[0.8.1] - 2025-06-07

Fixed

  • Add an optional special_characters flag to Element.send_keys to support sending special characters (e.g. emojis) @nathanfallet

[0.8.0] - 2025-06-01

Fixed

  • Fixed tests so that they can run on Windows (and still run on Linux like before) @nathanfallet
  • Remove usage of asyncio subprocess for better compatibility on Windows @nathanfallet
  • Added a missing Chrome Canary path for Windows @nathanfallet
  • Added a flag to re-enable --load-extension (disabled by default in Chrome 136+) @nathanfallet

[0.7.1] - 2025-05-08

Changed

  • Updated CDP models @jsuarezl

[0.7.0] - 2025-04-28

Added

  • Added Tab.screenshot_b64 and Element.screenshot_b64 methods to return screenshot as base64 string @falmar
  • Added Tab.print_to_pdf to print the current page to a PDF file @stephanlensky

[0.6.1] - 2025-04-25

Fixed

  • Fix race condition in Browser.get and Tab.close which could cause exceptions, especially when running multiple browsers in parallel @stephanlensky

[0.6.0] - 2025-04-20

Fixed

  • Browser.get and Tab.close will now wait for their appropiate target events before returning @ccev

Added

  • Added Tab.save_snapshot to export the current page to MHTML format.

[0.5.2] - 2025-04-09

Fixed

  • Fixed type annotation of Element.children @stephanlensky

[0.5.1] - 2025-02-16

Changed

  • Deprecated zendriver.loop() function. You should instead use asyncio functions directly, for example:

    asyncio.run(your_main_method())
    

[0.5.0] - 2025-02-16

Added

  • Add tab.expect_download methods to wait for download file @3mora2

[0.4.3] - 2025-02-11

Added

  • Add logs for Chrome process output on connection failure @stephanlensky

Changed

  • Default and launch changed to use about:blank (faster start and less bandwidth) @raycardillo

[0.4.2] - 2025-02-11

Fixed

  • Multiple Browsers can be created without one affecting the other @raycardillo

[0.4.1] - 2025-02-09

Fixed

  • Ignore irrelevant DOM.disable errors @raycardillo
  • Test scripts improved for running on macOS @raycardillo

[0.4.0] - 2025-02-06

Added

  • Add tab.expect_request and tab.expect_response methods to wait for a specific request or response @3mora2
  • Add tab.wait_for_ready_state method for to wait for page to load @3mora2
  • Add tab.remove_handlers method for removing handlers @khamaileon
  • Clean up temporary profiles when Browser.stop() is called @barrycarey

[0.3.1] - 2025-01-28

Fixed

  • Fixed bug in find/find_element_by_text which caused ProtocolException when no results were found @stephanlensky

[0.3.0] - 2025-01-25

Fixed

  • Added Tab.set_user_agent() function for programmatically configuring the user-agent, language, and platform @stephanlensky
  • Improved a few type annotations (Connection.send() function now returns correctly typed values based on the provided cdp_obj) @stephanlensky

[0.2.3] - 2024-12-14

Fixed

  • Fixed mypy linting errors (attempt 2) @stephanlensky

Added

  • Handle browser process shutdown on 'Failed to connect to browser' @desoul99
  • Added configurable browser connection timeout and tries @desoul99

[0.2.2] - 2024-11-23

Fixed

  • Fix AttributeError: 'tuple' object has no attribute 'value' error in connection.py when using headless browser, @slimshreydy

[0.2.1] - 2024-11-23

Added

  • Add automated testing framework! @stephanlensky
    • For now, just a few tests are written, including one to test browserscan.com bot detection
    • In the future, we can expand this test suite further (see Zendriver#18)
  • Add return type annotation to Tab.get_content() @stephanlensky

Changed

  • Upgraded websockets to latest version (>=14.0) @yoori @stephanlensky

[0.2.0] - 2024-11-17

Changed

  • Updated CDP models @stephanlensky

[0.1.5] - 2024-11-17

Fixed

  • Reverted non-functional fixes for mypy linting errors (oops) @stephanlensky

[0.1.4] - 2024-11-17

Fixed

  • Fixed a large number of mypy linting errors (should not result in any functional change) @stephanlensky

Added

  • Added zendriver.__version__ attribute to get current package version at runtime @stephanlensky

[0.1.3] - 2024-11-12

Added

  • Added support for DOM.scrollableFlagUpdated experimental CDP event. @michaellee94

[0.1.2] - 2024-11-11

Fixed

  • Pinned requirement websockets<14, fixing the AttributeError: 'NoneType' object has no attribute 'closed' crash which occurs on the latest version of websockets. @stephanlensky
  • Fixed incorrect browser.close() method in examples and documentation -- the correct method is browser.stop(). @stephanlensky
  • Fixed atexit handler to correctly handle async browser.stop() method. @stephanlensky

[0.1.1] - 2024-10-29

Added

  • Support for Python 3.10 and Python 3.11. All versions >=3.10 are now supported. @stephanlensky

[0.1.0] - 2024-10-20

Initial version, forked from ultrafunkamsterdam/nodriver@1bb6003 with a variety of improvements.

Fixed

  • Browser.set_all cookies function now correctly uses provided cookies @ilkecan
  • "successfully removed temp profile" message printed on exit is now only shown only when a profile was actually removed. Message is now logged at debug level instead of printed. @mreiden @stephanlensky
  • Fix crash on starting browser in headless mode @ilkecan
  • Fix Browser.stop() method to give the browser instance time to shut down before force killing @stephanlensky
  • Many ruff lint issues @stephanlensky

Added

  • Support for linting with ruff and mypy. All ruff lints are fixed in the initial release, but many mypy issues remain to be fixed at a later date. @stephanlensky
  • py.typed marker so importing as a library in other packages no longer causes mypy errors. @stephanlensky

Changed

  • Project is now built with uv. Automatically install dependencies to a venv with uv sync, run commands from the venv with uv run, and build the project with uv build. See the official uv docs for more information. @stephanlensky
  • Docs migrated from sphinx to mkdocs-material. @stephanlensky
  • Browser.stop() is now async (so it must be awaited) @stephanlensky

Removed

  • Twitter account creation example @stephanlensky