# How to Find and Interact with Elements Within Iframes Using Zendriver

> Learn how to find and interact with elements within iframes using Zendriver. Discover how Zendriver simplifies iframe interaction by treating them as regular DOM nodes.

- Repository: [CDP Driver/zendriver](https://github.com/cdpdriver/zendriver)
- Tags: how-to-guide
- Published: 2026-02-27

---

**Zendriver treats iframes as regular DOM nodes that expose a `content_document` property, allowing you to query and interact with nested elements using the same `select_all`, `query_selector_all`, and `Element` methods you use for the main page.**

Working with iframes in browser automation traditionally requires complex context switching, but the `cdpdriver/zendriver` library eliminates this friction. By exposing iframe contents as accessible document trees, you can search across all frames automatically or target specific iframes directly while maintaining the same high-level API for clicks, evaluations, and text input.

## How Zendriver Handles Iframe Documents

In zendriver, an iframe is simply a DOM node whose children live in a separate *content document*. The core classes (`Tab` and `Element`) expose this relationship transparently. When you query an iframe element, the library automatically resolves the underlying `content_document` before executing Chrome DevTools Protocol (CDP) commands, allowing seamless traversal between parent pages and nested frames.

## Querying Elements Across Frames and Inside Iframes

### Automatic Cross-Frame Search with `select_all`

The `Tab.select_all` method provides built-in support for searching across all frames via the `include_frames` parameter. When set to `True`, the method first fetches all `<iframe>` elements, executes the selector within each frame's document, and then searches the top-level document.

```python

# zendriver/core/tab.py – select_all implementation

# https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/tab.py#L306-L334

async def select_all(..., include_frames: bool = False) -> List[Element]:
    ...
    if include_frames:
        frames = await self.query_selector_all("iframe")
        for fr in frames:
            items.extend(await fr.query_selector_all(selector))
    items.extend(await self.query_selector_all(selector))
    ...

```

This approach is ideal when you know an element exists somewhere on the page but are unsure which frame contains it.

### Targeting Specific Iframes with `query_selector_all`

For precise control, manually locate an iframe and query within it. The `Tab.query_selector_all` method detects when the target node is an iframe and swaps it for the `content_document` before issuing the CDP `querySelectorAll` command.

```python

# zendriver/core/tab.py – query_selector_all handling of iframes

# https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/tab.py#L311-L340

if _node:
    doc = _node
    if _node.node_name == "IFRAME":
        doc = _node.content_document

```

This automatic resolution means you can chain selectors naturally without manually switching contexts.

### Traversing Iframe DOM Trees with `Element.children`

The `Element` class handles iframe traversal in its `children` property. When the element is an iframe, the property returns the children of the `content_document` instead of the iframe element itself.

```python

# zendriver/core/element.py – special case for iframe children

# https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/element.py#L67-L82

@property
def children(self) -> list[Element]:
    if self._node.node_name == "IFRAME":
        frame = self._node.content_document
        ...

```

This allows you to walk nested DOM structures transparently using standard tree navigation.

## Practical Implementation Example

```python
import asyncio
from zendriver import Browser

async def demo():
    # launch a headful Chromium instance

    async with Browser() as browser:
        tab = await browser.new_tab()

        # load a page that contains an <iframe>

        await tab.get("https://example.com/page-with-iframe")

        # 1️⃣ Find a button inside any iframe (search all frames automatically)

        button = await tab.select_all("button.submit", include_frames=True)
        # `button` is a list; pick the first match

        if button:
            await button[0].click()          # works exactly like a normal element

        # 2️⃣ Manually locate a specific iframe and then query inside it

        iframe = await tab.query_selector('iframe[name="target"]')
        # query inside the iframe's document

        inside = await iframe.query_selector_all('input[name="email"]')
        if inside:
            await inside[0].apply("(el) => el.value = 'test@example.com'")
            await inside[0].click()          # maybe a submit button nearby

        # 3️⃣ Walk the DOM tree of an iframe

        children = iframe.children          # returns Element objects for iframe's DOM

        for child in children:
            print(child.node.node_name, child.attrs.get("id"))

# Run the demo

asyncio.run(demo())

```

## Key Source Files and Implementation Details

The iframe handling logic spans several files in the repository:

- **[`zendriver/core/tab.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/tab.py)**: Implements `Tab.select_all`, `query_selector_all`, and the logic that swaps an iframe node for its `content_document`
- **[`zendriver/core/element.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/element.py)**: Provides the `Element` wrapper; its `children` property exposes the DOM inside an iframe
- **[`zendriver/core/util.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/util.py)**: Contains helper functions (e.g., `filter_recurse`) used by element tree traversal
- **[`zendriver/cdp/dom.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/cdp/dom.py)**: Low-level CDP commands (`querySelectorAll`, `getDocument`, etc.) called by the high-level API

## Summary

- Use **`select_all(..., include_frames=True)`** to search across all frames in a single call
- Query specific iframes using **`query_selector_all`** on the iframe element, which automatically resolves the `content_document`
- Access nested iframe children via the **`Element.children`** property for transparent DOM walking
- Interact with found elements using standard methods like **`click()`** and **`apply()`** regardless of whether they reside inside iframes

## Frequently Asked Questions

### Does zendriver automatically search inside iframes by default?

No. By default, `query_selector` and `query_selector_all` only search the top-level document. You must explicitly set `include_frames=True` in `select_all` or manually target the iframe element first to search within it.

### How do I access the DOM inside a specific iframe?

First, select the iframe element using `await tab.query_selector('iframe[name="target"]')`. Then call `query_selector_all` on that element. The library automatically detects the iframe node and uses its `content_document` for the query.

### Can I click elements that are found inside iframes?

Yes. Once you obtain an `Element` reference, it does not matter whether the element is inside an iframe or the main document. You can call `click()`, `apply()`, `evaluate()`, and other methods exactly as you would for top-level elements.

### What is the `content_document` property in zendriver?

The `content_document` represents the document root of an iframe's internal DOM as exposed by the Chrome DevTools Protocol. Zendriver handles the context switching internally when you query against an iframe element, allowing you to treat the nested document as a standard queryable node.