# How to Manage Multiple Browser Tabs and Windows in Zendriver

> Master browser tab and window management with Zendriver. Programmatically control multiple contexts using the Chrome DevTools Protocol. Open, switch, resize, and close tabs seamlessly.

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

---

**Zendriver treats every browser view—whether a tab, separate window, iframe, or background script—as a `Tab` object, allowing you to open, switch, resize, and close multiple contexts programmatically using the Chrome DevTools Protocol (CDP).**

Managing multiple browser tabs and windows in Zendriver requires understanding how the library abstracts CDP targets into high-level Python objects. Unlike traditional Selenium-based tools, Zendriver uses a stateful `Tab` class that maintains persistent connections to browser contexts, enabling sophisticated window management without losing DOM references or session state.

## Understanding the Tab and Browser Architecture

In [`zendriver/core/tab.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/tab.py), the **`Tab`** class inherits from the low-level **`Connection`** class, which handles the WebSocket communication with Chrome's DevTools Protocol. This design means every tab, window, or iframe is technically a `Tab` instance with its own CDP session.

The **`Browser`** class in [`zendriver/core/browser.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/browser.py) serves as the central registry. It maintains a mutable list called **`self.targets`** that tracks every active CDP target, including pages, service workers, and background scripts. When you open new tabs or windows, Zendriver adds corresponding `Tab` objects to this registry.

## Opening New Tabs and Windows

Zendriver provides a unified interface for creating new browsing contexts through the `Browser.get()` method, which supports two mutually exclusive flags for controlling window behavior.

### Opening a New Tab

To open a URL in a new tab within the existing browser window, pass `new_tab=True` to `Browser.get()`:

```python
import zendriver as zd

browser = await zd.start()
main_tab = await browser.get("https://example.com")

# Open a new tab in the same window

second_tab = await browser.get("https://python.org", new_tab=True)

```

Internally, this triggers `cdp.target.create_target` and returns a fresh `Tab` instance that Zendriver adds to `browser.targets`.

### Opening a New Window

To spawn a separate OS window, use `new_window=True` instead:

```python

# Open a new separate window

new_window_tab = await browser.get("https://github.com", new_window=True)

```

Despite being in a different window, the returned object is still a `Tab` instance with identical methods and properties.

## Switching Between Tabs and Windows

Once you have multiple tabs open, you need to shift focus between them. Zendriver provides several mechanisms for context switching.

### Activating a Specific Tab

The `Tab.bring_to_front()` method activates a specific target, sending the CDP `Target.activateTarget` command:

```python

# Switch focus to the second tab

await second_tab.bring_to_front()

# Now perform actions on this active tab

await second_tab.wait_for("h1")

```

This is equivalent to clicking on a browser tab or window to focus it.

### Enumerating Open Tabs

The `Browser` class provides the **`tabs`** property (defined in [`zendriver/core/browser.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/browser.py) lines 61-68) that returns a list of all page-type targets:

```python

# Get all open tabs/windows

all_tabs = browser.tabs

for tab in all_tabs:
    print(f"Tab URL: {await tab.url}")

```

This property filters `self.targets` to include only objects where `type_ == "page"`, excluding service workers and background scripts.

### Accessing the Main Tab

The **`main_tab`** property provides direct access to the initial tab created when the browser started:

```python
original_tab = browser.main_tab
await original_tab.bring_to_front()

```

## Managing Window State and Geometry

Beyond simple navigation, Zendriver allows precise control over window positioning, sizing, and state.

### Querying Window Information

The `Tab.get_window()` method returns the underlying OS window identifier and current bounds:

```python
window_id, bounds = await tab.get_window()
print(f"Window ID: {window_id}")
print(f"Position: ({bounds.left}, {bounds.top})")
print(f"Size: {bounds.width} x {bounds.height}")

```

This wraps the CDP `Browser.getWindowBounds` command.

### Resizing and Repositioning

Use `Tab.set_window_state()` to modify window geometry:

```python

# Move and resize

await tab.set_window_state(left=100, top=100, width=1280, height=720)

# Convenience methods

await tab.maximize()
await tab.minimize()
await tab.fullscreen()

```

These methods are implemented in [`zendriver/core/tab.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/tab.py) (lines 109-160) and handle the CDP `Browser.setWindowBounds` call.

### Tiling Multiple Windows

For managing multiple windows simultaneously, the `Browser.tile_windows()` method automatically arranges all open windows in a grid:

```python

# Arrange all windows evenly across the screen

await browser.tile_windows()

```

This method (found in [`zendriver/core/browser.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/browser.py) lines 86-145) calculates optimal positions based on screen size and calls `set_window_size` for each window.

## Closing Tabs and Windows

To clean up specific contexts without shutting down the entire browser, use the `Tab.close()` method:

```python

# Close a specific tab or window

await second_tab.close()

```

This sends the `Target.closeTarget` CDP command and removes the target from `browser.targets`. If the closed tab was the active one in a window, Chrome automatically switches focus to another tab in that window.

## Complete Working Example

Here is a comprehensive example demonstrating the full lifecycle of multiple tab and window management:

```python
import asyncio
import zendriver as zd

async def manage_multiple_contexts():
    # Launch browser with UI visible

    browser = await zd.start(headless=False)
    
    try:
        # 1. Open initial page in main tab

        main = await browser.get("https://example.com")
        
        # 2. Open new tab in same window

        docs_tab = await browser.get("https://docs.python.org", new_tab=True)
        
        # 3. Open separate window

        github_tab = await browser.get("https://github.com", new_window=True)
        
        # 4. List all open contexts

        print(f"Total tabs: {len(browser.tabs)}")
        for idx, tab in enumerate(browser.tabs):
            url = await tab.url
            print(f"  [{idx}] {url}")
        
        # 5. Switch to Python docs and resize

        await docs_tab.bring_to_front()
        await docs_tab.set_window_state(left=50, top=50, width=1000, height=800)
        
        # 6. Tile all windows (arranges GitHub window too)

        await browser.tile_windows()
        
        # 7. Close specific tab

        await docs_tab.close()
        
        # 8. Return focus to main tab

        await browser.main_tab.bring_to_front()
        
    finally:
        # Cleanup

        await browser.stop()

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

```

## Summary

- **Zendriver unifies tabs, windows, and iframes** under the `Tab` class, which inherits from `Connection` to maintain persistent CDP sessions.
- **Create new contexts** using `Browser.get()` with `new_tab=True` or `new_window=True`, both returning `Tab` instances tracked in `browser.targets`.
- **Switch focus** between contexts by calling `await tab.bring_to_front()`, which activates the specific CDP target.
- **Enumerate open pages** via the `browser.tabs` property (filtering for page-type targets) or access the original context through `browser.main_tab`.
- **Control window geometry** using `tab.set_window_state()`, `maximize()`, `minimize()`, and `browser.tile_windows()` for automatic arrangement.
- **Clean up** individual contexts with `await tab.close()` without terminating the entire browser session.

## Frequently Asked Questions

### How do I switch between tabs in Zendriver?

Use the `bring_to_front()` method on the specific `Tab` instance you want to activate. This sends the CDP `Target.activateTarget` command to Chrome, bringing that tab or window into focus. For example: `await my_tab.bring_to_front()`. You can enumerate available tabs using `browser.tabs` to find the specific instance you need.

### What is the difference between a tab and a window in Zendriver?

Technically, both are represented by the same `Tab` class and stored in the same `browser.targets` registry. The distinction lies in how they are created: use `new_tab=True` in `Browser.get()` to open a new tab within an existing window, or `new_window=True` to spawn a separate OS window. Both return a `Tab` instance with identical methods for navigation, resizing, and closing.

### How can I resize browser windows programmatically?

Zendriver provides several methods on the `Tab` class for window management. Use `await tab.set_window_state(left, top, width, height)` for precise positioning, or use convenience methods like `await tab.maximize()`, `await tab.minimize()`, and `await tab.fullscreen()`. For arranging multiple windows automatically, use `await browser.tile_windows()`, which calculates a grid layout and applies it to all open windows.

### Does Zendriver support headless mode with multiple windows?

Yes, Zendriver supports headless mode via the `headless=True` parameter in `start()`, and you can still create multiple tabs using `new_tab=True`. However, opening new separate windows with `new_window=True` in headless mode may behave differently depending on your Chrome version, as headless Chrome traditionally runs without a window manager. For full window management capabilities including resizing and tiling, run with `headless=False` to enable the graphical interface.