How to Wait for Elements with a Specific Timeout in Zendriver

Use Tab.wait_for(), Tab.select(), or Tab.select_all() with the timeout parameter (in seconds) to poll for DOM elements; these methods raise asyncio.TimeoutError if the element doesn't appear within the specified window.

Zendriver (cdpdriver/zendriver) is an async Python library that controls Chrome via the Chrome DevTools Protocol (CDP). When automating dynamic web pages, you need reliable ways to wait for elements to appear before interacting with them. The library provides several timeout-aware polling methods that handle this without busy-waiting.

Understanding Zendriver's Timeout-Based Waiting Mechanism

All waiting methods in Zendriver follow the same asynchronous polling pattern implemented in zendriver/core/tab.py. The mechanism works by:

  1. Starting a timer using asyncio.get_running_loop().time() to track elapsed time.
  2. Polling the DOM via query_selector, find_element_by_text, or similar selectors in a loop.
  3. Sleeping briefly between attempts using await self.sleep(0.5) (defined in zendriver/core/util.py, lines 258-270) to avoid blocking the event loop.
  4. Raising asyncio.TimeoutError when the elapsed time exceeds the user-supplied timeout argument (in seconds).

This approach ensures that your automation script waits efficiently without consuming excessive CPU, while still providing precise control over how long to wait before failing.

Waiting for a Single Element by CSS Selector

Using wait_for() with Selectors

The primary method for waiting with a specific timeout is Tab.wait_for() (implemented in zendriver/core/tab.py, lines 1116-1156). This method accepts either a CSS selector or visible text and polls until the element appears or the timeout expires.

import asyncio
import zendriver as zd

async def main():
    browser = await zd.start()
    tab = await browser.get("https://example.com")
    
    try:
        # Wait up to 5 seconds for the submit button

        element = await tab.wait_for(selector="button.submit", timeout=5)
        await element.click()
    except asyncio.TimeoutError:
        print("Submit button did not appear within 5 seconds")
    finally:
        await browser.stop()

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

Using the select() Convenience Method

For CSS selectors specifically, Tab.select() (lines 1248-1275 in zendriver/core/tab.py) provides a dedicated wrapper around query_selector with the same timeout logic. This method is slightly more concise when you only need CSS selection.

try:
    # Wait up to 3 seconds for the search input

    search_box = await tab.select("input[name='q']", timeout=3)
    await search_box.type("zendriver tutorial")
except asyncio.TimeoutError:
    print("Search box not found within 3 seconds")

Waiting for Elements by Visible Text

When you need to locate an element by its visible text content rather than a CSS selector, use the text parameter of Tab.wait_for(). Internally, this invokes find_element_by_text() (implemented in zendriver/core/tab.py, lines 627-665), which searches the DOM for elements matching the specified text pattern.

try:
    # Wait up to 8 seconds for the welcome message

    welcome_banner = await tab.wait_for(text="Welcome back", timeout=8.0)
    print("Found the welcome banner")
except asyncio.TimeoutError:
    print("Welcome banner never showed up")

This approach is particularly useful for dynamic content where CSS classes may change frequently but the text content remains stable.

Waiting for Multiple Elements

To wait for multiple elements matching a CSS selector (rather than just the first one), use Tab.select_all() (lines 1306-1342 in zendriver/core/tab.py). This method returns a list of all matching elements once at least one is found, or raises asyncio.TimeoutError if none appear within the specified window.

try:
    # Wait up to 4 seconds for article posts to load

    posts = await tab.select_all("article.post", timeout=4, include_frames=False)
    print(f"Found {len(posts)} posts")
    
    for post in posts:
        title = await post.query_selector("h2")
        if title:
            print(await title.get_text())
except asyncio.TimeoutError:
    print("No posts appeared within 4 seconds")

The include_frames parameter controls whether the search should extend into iframe contexts.

Waiting for Page Ready State

Before interacting with elements, you may need to ensure the page has reached a specific loading state. Tab.wait_for_ready_state() (lines 1158-1185 in zendriver/core/tab.py) polls the document's readyState property until it matches the desired value (interactive or complete).

await tab.get("https://example.com")
await tab.wait_for_ready_state("complete", timeout=12)
print("Page fully loaded and ready for interaction")

This method is essential for single-page applications (SPAs) where the initial DOM load completes quickly but dynamic content continues loading via JavaScript.

Handling Timeout Errors

All waiting methods in Zendriver raise asyncio.TimeoutError when the specified timeout expires before the condition is met. This standard Python exception allows you to implement fallback logic or graceful degradation in your automation scripts.

import asyncio

async def safe_click(tab, selector, timeout=5):
    try:
        element = await tab.wait_for(selector=selector, timeout=timeout)
        await element.click()
        return True
    except asyncio.TimeoutError:
        print(f"Element {selector} not found within {timeout}s, skipping")
        return False

Always wrap waiting calls in try-except blocks when the element's presence is uncertain or when dealing with flaky network conditions.

Summary

  • Use Tab.wait_for() (lines 1116-1156 in zendriver/core/tab.py) for flexible waiting by CSS selector or visible text with a specific timeout.
  • Use Tab.select() (lines 1248-1275) for CSS-only single element waiting, and Tab.select_all() (lines 1306-1342) for multiple elements.
  • All methods accept a timeout parameter in seconds (int or float) and poll every 0.5 seconds until the element appears.
  • Handle asyncio.TimeoutError to manage cases where elements fail to appear within the specified window.
  • Use Tab.wait_for_ready_state() (lines 1158-1185) to ensure page load completion before element interaction.

Frequently Asked Questions

What exception does Zendriver raise when a timeout expires?

Zendriver raises the standard asyncio.TimeoutError when the specified timeout is reached before the element appears. This allows you to catch the exception using Python's standard asyncio error handling patterns and implement fallback logic or retry mechanisms as needed.

How often does Zendriver poll the DOM when waiting for an element?

According to the implementation in zendriver/core/tab.py, Zendriver polls the DOM every 0.5 seconds during the wait loop. This interval is hardcoded in the sleep(0.5) calls within methods like wait_for, select, and select_all, balancing responsiveness with CPU efficiency.

Can I wait for an element to appear by its visible text instead of a CSS selector?

Yes. Use the text parameter of Tab.wait_for() to search for elements by their visible text content. Internally, this invokes find_element_by_text() (lines 627-665 in zendriver/core/tab.py), which traverses the DOM looking for text matches. You can combine this with the timeout parameter to limit how long the search continues.

What is the difference between wait_for() and select() in Zendriver?

wait_for() is the generic waiting method that accepts either a selector (CSS) or text parameter, making it flexible for different identification strategies. select() is a convenience method specifically optimized for CSS selectors only (lines 1248-1275), providing a slightly cleaner API when you only need to wait for a CSS-matched element. Both methods implement identical timeout and polling logic internally.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →