How to Manage Multiple Browser Tabs and Windows in Zendriver

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, 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 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():

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:


# 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:


# 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 lines 61-68) that returns a list of all page-type targets:


# 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:

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:

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:


# 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 (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:


# Arrange all windows evenly across the screen

await browser.tile_windows()

This method (found in 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:


# 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:

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.

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 →