Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ultrafunkamsterdam/nodriver/llms.txt

Use this file to discover all available pages before exploring further.

Nodriver provides flexible methods for navigating web pages, managing URLs, and controlling browser history.

Basic navigation

Opening pages

Use the get() method to navigate to a URL:
import nodriver as uc

browser = await uc.start()

# Navigate to a URL
tab = await browser.get('https://example.com')
From browser.py:241-283:
async def get(
    self, url="chrome://welcome", new_tab: bool = False, new_window: bool = False
) -> tab.Tab:
    """
    Top level get. Utilizes the first tab to retrieve given url.
    
    This function handles waits/sleeps and detects when DOM events fired, 
    so it's the safest way of navigating.
    """
    if new_tab or new_window:
        # Create new target using the browser session
        target_id = await self.connection.send(
            cdp.target.create_target(
                url, new_window=new_window, enable_begin_frame_control=True
            )
        )
        # Get the connection matching the new target_id
        connection: tab.Tab = next(
            filter(
                lambda item: item.type_ == "page" and item.target_id == target_id,
                self.targets,
            )
        )
    else:
        # Use existing tab
        connection: tab.Tab = next(
            filter(lambda item: item.type_ == "page", self.targets)
        )
        # Navigate to new URL
        frame_id, loader_id, *_ = await connection.send(cdp.page.navigate(url))
        connection.frame_id = frame_id

Opening new tabs

# Open URL in new tab
tab2 = await browser.get('https://github.com', new_tab=True)

# Or from an existing tab
tab3 = await tab.get('https://google.com', new_tab=True)

Opening new windows

# Open URL in new window
window = await browser.get('https://example.com', new_window=True)
When you use new_window=True, it automatically implies new_tab=True.

Managing tabs

Getting current tabs

# Get all tabs
all_tabs = browser.tabs

# Get main tab (first opened)
main = browser.main_tab

# Access tabs by index
first_tab = browser[0]
second_tab = browser[1]

# Get tab containing specific text
google_tab = browser['google']

Iterating over tabs

# Iterate through all tabs
for tab in browser:
    print(tab.url)
    await tab.activate()

# Use slice notation
tabs_subset = browser[0:3]  # First 3 tabs

Closing tabs

# Close a specific tab
await tab.close()

# Close all except main
for tab in browser.tabs:
    if tab != browser.main_tab:
        await tab.close()
From the demo.py example:
for i, tab in enumerate(driver):
    try:
        await tab.get('https://www.google.com')
        await tab.activate()
        # Skip first tab
        if tab == driver.main_tab:
            print('skipping main tab')
            continue
    except:
        pass
    await tab.close()

Browser history

# Go back
await tab.back()

# Go forward  
await tab.forward()
From tab.py:778-788:
async def back(self):
    """history back"""
    await self.send(cdp.runtime.evaluate("window.history.back()"))

async def forward(self):
    """history forward"""
    await self.send(cdp.runtime.evaluate("window.history.forward()"))

Reload page

# Reload with cache
await tab.reload()

# Reload ignoring cache
await tab.reload(ignore_cache=True)

# Reload with custom script
await tab.reload(
    ignore_cache=True,
    script_to_evaluate_on_load='console.log("Page reloaded")'
)

Getting page information

Current URL

# Get current URL
current_url = tab.url
print(f"Current page: {current_url}")

# URL is automatically updated when you await the tab
await tab
print(f"Updated URL: {tab.url}")
Always await tab after navigation to ensure the URL property is up-to-date.

Page source

Get the current HTML content:
html_content = await tab.get_content()
print(html_content)
From tab.py:1047-1056:
async def get_content(self):
    """
    Gets the current page source content (html)
    """
    doc: cdp.dom.Node = await self.send(cdp.dom.get_document(-1, True))
    return await self.send(
        cdp.dom.get_outer_html(backend_node_id=doc.backend_node_id)
    )

Extract all URLs

# Get all URLs from links, images, scripts, etc.
all_urls = await tab.get_all_urls(absolute=True)

for url in all_urls:
    print(url)

Waiting strategies

Simple wait

# Wait for 2 seconds
await tab.wait(2)

# Or use sleep (alias)
await tab.sleep(2)

Wait for element

Both select() and find() have built-in waiting:
# Waits up to 10 seconds for element to appear
element = await tab.select('.dynamic-content', timeout=10)

# Custom timeout
element = await tab.find('Loading complete', timeout=30)

Wait for specific element

Use wait_for() for explicit waiting:
# Wait for selector
element = await tab.wait_for(selector='button.submit', timeout=15)

# Wait for text
element = await tab.wait_for(text='Welcome', timeout=10)
From tab.py:1265-1311:
async def wait_for(
    self,
    selector: Optional[str] = "",
    text: Optional[str] = "",
    timeout: Optional[Union[int, float]] = 10,
) -> element.Element:
    """
    Variant on query_selector_all and find_elements_by_text.
    This variant takes either selector or text, and will block until
    the requested element(s) are found.
    
    It will block for a maximum of <timeout> seconds, after which
    a TimeoutError will be raised.
    """

Awaiting the tab

Calling await tab is important for synchronization:
await tab.get('https://example.com')
await tab  # Ensures everything is up-to-date
The await tab pattern updates the target URL and allows the script to “breathe”, which helps prevent race conditions.

Window management

Activate tab

Bring a tab to the foreground:
await tab.activate()

# Or use the alias
await tab.bring_to_front()

Window size and position

# Set window size and position
await tab.set_window_size(
    left=0,
    top=0, 
    width=1280,
    height=1024
)

# Get current window bounds
window_id, bounds = await tab.get_window()
print(f"Window size: {bounds.width}x{bounds.height}")

Window states

# Maximize
await tab.maximize()

# Minimize
await tab.minimize()

# Fullscreen
await tab.fullscreen()

# Normal (restore)
await tab.medimize()

Tile windows

Arrange multiple windows in a grid:
# Tile all browser windows
grid = await browser.tile_windows(max_columns=3)

# Tile specific tabs
grid = await browser.tile_windows(windows=[tab1, tab2, tab3])
From demo.py:
# Open multiple windows
for _ in range(NUM_WINS):
    if _ % 2 == 0:
        await driver.get(URL1, new_window=True)
    else:
        await driver.get(URL2, new_window=True)

# Arrange in grid
grid = await driver.tile_windows(max_columns=NUM_WINS)

Scrolling

Scroll down and up

# Scroll down 50% of viewport
await tab.scroll_down(50)

# Scroll up 25% of viewport  
await tab.scroll_up(25)

# Scroll down full page (100%)
await tab.scroll_down(100)
From tab.py:1170-1220:
async def scroll_down(self, amount=25):
    """
    Scrolls down maybe
    
    :param amount: number in percentage. 25 is a quarter of page, 
                   50 half, and 1000 is 10x the page
    """
    window_id: cdp.browser.WindowID
    bounds: cdp.browser.Bounds
    (window_id, bounds) = await self.get_window()
    
    await self.send(
        cdp.input_.synthesize_scroll_gesture(
            x=0,
            y=0,
            y_distance=-(bounds.height * (amount / 100)),
            y_overscroll=0,
            x_overscroll=0,
            prevent_fling=True,
            repeat_delay_ms=0,
            speed=7777,
        )
    )

Real-world example

Here’s a complete navigation workflow:
import nodriver as uc

async def navigate_workflow():
    browser = await uc.start()
    
    # Open main page
    tab = await browser.get('https://example.com')
    await tab
    
    # Click link to navigate
    link = await tab.find('Read more')
    await link.click()
    await tab  # Wait for navigation
    
    # Check URL changed
    print(f"Now at: {tab.url}")
    
    # Go back
    await tab.back()
    await tab
    
    # Open new tab with search
    search_tab = await browser.get('https://google.com', new_tab=True)
    await search_tab
    
    # Switch between tabs
    await tab.activate()
    await tab.wait(1)
    
    await search_tab.activate()
    await search_tab.wait(1)
    
    # Close search tab
    await search_tab.close()
    
    # Maximize main tab
    await tab.maximize()

if __name__ == '__main__':
    uc.loop().run_until_complete(navigate_workflow())

Browser contexts (proxy support)

Create isolated browser contexts with separate proxies:
# Create context with proxy
context_tab = await browser.create_context(
    url='https://example.com',
    new_window=True,
    proxy_server='http://user:pass@proxy.example.com:8080'
)

# Or with SOCKS5
context_tab = await browser.create_context(
    url='https://example.com',
    proxy_server='socks5://user:pass@proxy.example.com:1080'
)
Browser contexts are experimental. Each context creates a separate browsing session with its own cookies, storage, and proxy settings.

Best practices

1
Always await tab after navigation
2
Ensure the URL and page state are synchronized.
3
await tab.get('https://example.com')
await tab  # Important!
4
Use get() for navigation
5
Prefer tab.get() over direct CDP commands as it handles timing and events.
6
Handle navigation delays
7
Add small delays after navigation to allow JavaScript to execute:
8
await tab.get('https://spa-site.com')
await tab.wait(1)  # Let SPA initialize
9
Manage multiple tabs carefully
10
Always keep a reference to tabs you want to use later, as tab indices can change.

Build docs developers (and LLMs) love