Skip to main content

Overview

The CDP class provides a direct interface to Chrome DevTools Protocol, allowing low-level control over browser tabs, sessions, and debugging. It communicates with Chrome via HTTP and WebSocket connections.
from undetected import CDP, ChromeOptions

options = uc.ChromeOptions()
options.debugger_address = "127.0.0.1:9222"

cdp = CDP(options)
tabs = cdp.tab_list()
print(f"Open tabs: {len(tabs)}")

Constructor

CDP()

Creates a new CDP client instance.
CDP(options: ChromeOptions)
options
ChromeOptions
required
ChromeOptions instance containing the debugger address (host:port).
import undetected as uc

options = uc.ChromeOptions()
options.debugger_address = "127.0.0.1:9222"

cdp = uc.CDP(options)

Methods

tab_list()

Retrieves a list of all open tabs/pages.
cdp.tab_list() -> list[PageElement]
Returns: List of PageElement objects, each representing an open tab.
cdp = CDP(options)
tabs = cdp.tab_list()

for tab in tabs:
    print(f"Tab: {tab.title}")
    print(f"URL: {tab.url}")
    print(f"ID: {tab.id}")

tab_new()

Opens a new tab with the specified URL.
cdp.tab_new(url: str)
url
str
required
The URL to open in the new tab.
cdp = CDP(options)
cdp.tab_new('https://example.com')

tab_activate()

Activates (focuses) a specific tab.
cdp.tab_activate(id: str = None)
id
str
default:"None"
The tab ID to activate. If None, activates the first tab in the list.
cdp = CDP(options)
tabs = cdp.tab_list()

# Activate specific tab
cdp.tab_activate(tabs[1].id)

# Activate first tab
cdp.tab_activate()

tab_close_last_opened()

Closes the most recently opened tab.
cdp.tab_close_last_opened()
cdp = CDP(options)
cdp.tab_new('https://example.com')
cdp.tab_new('https://example.org')
cdp.tab_close_last_opened()  # Closes example.org tab

send()

Sends a CDP command via WebSocket and waits for the response.
async def send(method: str, params: dict)
method
str
required
The CDP method name (e.g., “Page.navigate”, “Runtime.evaluate”).
params
dict
required
Dictionary of parameters for the CDP method.
import asyncio
from undetected import CDP, ChromeOptions

async def navigate():
    options = uc.ChromeOptions()
    options.debugger_address = "127.0.0.1:9222"
    
    cdp = CDP(options)
    await cdp.send('Page.navigate', {'url': 'https://example.com'})
    print(f"Response: {cdp.last_json}")

asyncio.run(navigate())

get()

Sends an HTTP GET request to a CDP endpoint.
cdp.get(uri: str) -> dict
uri
str
required
The endpoint URI (e.g., “/json”, “/json/list”).
Returns: JSON response as a dictionary.
cdp = CDP(options)
info = cdp.get('/json/version')
print(f"Browser: {info.get('Browser')}")
print(f"Protocol Version: {info.get('Protocol-Version')}")

post()

Sends an HTTP POST request to a CDP endpoint.
cdp.post(uri: str, data: dict = None) -> dict | Response
uri
str
required
The endpoint URI.
data
dict
default:"None"
JSON data to send in the POST request body.
Returns: JSON response as dictionary, or Response object if JSON parsing fails.
cdp = CDP(options)
response = cdp.post('/json/close/tab-id-here')

Attributes

server_addr
str
HTTP address of the Chrome DevTools server (e.g., “http://127.0.0.1:9222”).
sessionId
str
The active session/tab ID.
wsurl
str
WebSocket URL for the active session (e.g., “ws://127.0.0.1:9222/devtools/page/…”).
last_json
dict
The most recent JSON response from a CDP request (property).
endpoints
CDPObject
Predefined CDP endpoint paths:
  • json: “/json”
  • protocol: “/json/protocol”
  • list: “/json/list”
  • new: “/json/new?
  • activate: “/json/activate/
  • close: “/json/close/

Helper Classes

CDPObject

A dictionary subclass that allows attribute-style access to keys.
obj = CDPObject({'name': 'value', 'nested': {'key': 'data'}})
print(obj.name)           # 'value'
print(obj.nested.key)     # 'data'
print(obj['name'])        # 'value' (dict-style also works)

PageElement

Represents a browser tab/page with CDP information.
tabs = cdp.tab_list()
for tab in tabs:
    print(tab.id)                      # Tab ID
    print(tab.url)                     # Current URL
    print(tab.title)                   # Page title
    print(tab.type)                    # Usually 'page'
    print(tab.webSocketDebuggerUrl)    # WebSocket URL for this tab

CDP Endpoints

The CDP class provides access to standard Chrome DevTools HTTP endpoints:

/json

Lists all pages/tabs:
cdp = CDP(options)
pages = cdp.get(cdp.endpoints.json)
for page in pages:
    print(f"{page['title']}: {page['url']}")

/json/version

Returns browser version information:
version_info = cdp.get('/json/version')
print(version_info)
# {
#   'Browser': 'Chrome/120.0.6099.109',
#   'Protocol-Version': '1.3',
#   'User-Agent': 'Mozilla/5.0...',
#   'V8-Version': '12.0.267.8',
#   'WebKit-Version': '537.36'
# }

/json/list

Lists inspectable pages:
pages = cdp.get(cdp.endpoints.list)

/json/new

Creates a new page:
cdp.get(cdp.endpoints.new.format(url='https://example.com'))

/json/activate/

Activates a specific tab:
cdp.post(cdp.endpoints.activate.format(id='page-id'))

/json/close/

Closes a specific tab:
cdp.post(cdp.endpoints.close.format(id='page-id'))

WebSocket Communication

The send() method communicates via WebSocket for real-time CDP commands:
import asyncio
from undetected import CDP, ChromeOptions

async def main():
    options = uc.ChromeOptions()
    options.debugger_address = "127.0.0.1:9222"
    cdp = CDP(options)
    
    # Execute JavaScript
    await cdp.send('Runtime.evaluate', {
        'expression': 'document.title',
        'returnByValue': True
    })
    result = cdp.last_json
    print(f"Page title: {result['result']['result']['value']}")
    
    # Take screenshot
    await cdp.send('Page.captureScreenshot', {
        'format': 'png',
        'quality': 80
    })
    screenshot_data = cdp.last_json['result']['data']
    
    # Navigate to URL
    await cdp.send('Page.navigate', {
        'url': 'https://example.com'
    })

asyncio.run(main())

Complete Example

Here’s a complete example showing various CDP operations:
import asyncio
import undetected as uc
from undetected import CDP

async def main():
    # Start Chrome with debugging port
    driver = uc.Chrome()
    
    # Create CDP client
    cdp = CDP(driver.options)
    
    # List all tabs
    tabs = cdp.tab_list()
    print(f"Open tabs: {len(tabs)}")
    for tab in tabs:
        print(f"  {tab.title}: {tab.url}")
    
    # Open new tab
    cdp.tab_new('https://example.com')
    
    # Execute JavaScript via CDP
    await cdp.send('Runtime.evaluate', {
        'expression': 'navigator.userAgent'
    })
    print(f"User Agent: {cdp.last_json}")
    
    # Get page title
    await cdp.send('Runtime.evaluate', {
        'expression': 'document.title',
        'returnByValue': True
    })
    title = cdp.last_json['result']['result']['value']
    print(f"Page title: {title}")
    
    # Close last opened tab
    cdp.tab_close_last_opened()
    
    driver.quit()

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

Integration with Chrome Class

The Chrome class can use CDP for tab operations:
import undetected as uc

driver = uc.Chrome()

# Open new tab via CDP
driver.tab_new('https://example.com')

# CDP is created internally
from undetected.cdp import CDP
cdp = CDP(driver.options)
tabs = cdp.tab_list()
print(f"Total tabs: {len(tabs)}")

Common CDP Commands

Here are some useful CDP commands you can send via send():

Page Commands

# Navigate
await cdp.send('Page.navigate', {'url': 'https://example.com'})

# Reload
await cdp.send('Page.reload', {})

# Screenshot
await cdp.send('Page.captureScreenshot', {'format': 'png'})

# Print to PDF
await cdp.send('Page.printToPDF', {})

Runtime Commands

# Evaluate JavaScript
await cdp.send('Runtime.evaluate', {
    'expression': 'document.querySelector("h1").textContent'
})

# Call function
await cdp.send('Runtime.callFunctionOn', {
    'functionDeclaration': 'function() { return this.textContent; }',
    'objectId': 'element-object-id'
})

Network Commands

# Enable network tracking
await cdp.send('Network.enable', {})

# Set user agent
await cdp.send('Network.setUserAgentOverride', {
    'userAgent': 'Custom User Agent'
})

# Clear cache
await cdp.send('Network.clearBrowserCache', {})

Important Notes

The CDP class requires an active Chrome instance with a debugger port open. Make sure the Chrome instance is running before creating a CDP client.
The send() method is async and must be called with await inside an async function. Use asyncio.run() to execute async CDP operations.
Each CDP instance maintains a session with a specific tab. Use tab_activate() to switch between tabs, which updates the WebSocket URL.
The last_json property always contains the most recent response. Check this property after calling send(), get(), or post() to access response data.
WebSocket connections are created and closed for each send() call. For high-frequency CDP usage, consider maintaining a persistent WebSocket connection manually.

Build docs developers (and LLMs) love