SolveitCDP API

Drive the user’s Chrome from a solveit kernel
from fastcore.test import test_eq

Channel connects to Solveit’s /wsx relay for ongoing JSON messaging. Set SOLVEIT_URL to the Solveit server’s URL, or pass a full WebSocket url to Channel.connect. The default server is http://localhost:5001.


source

Channel

def Channel(
    chan, url:NoneType=None
):

Duplex JSON messaging with the other peers on a named /wsx relay channel

Connect each peer to the same channel name on Solveit’s /wsx relay. The relay forwards each frame unchanged to every other peer on that channel. solvecdp uses Channel to communicate with the solveit-chrome extension.

Channel sends JSON dictionaries. Pass the fields as keyword arguments to send. Incoming frames become AttrDict objects. If a frame’s id matches a pending request, it completes that request. Other frames go into the events queue.

Here we connect two peers and send a greeting. The second peer reads it from events.

ch1,ch2 = await Channel.connect('test'),await Channel.connect('test')
await ch1.send(hello='world')
test_eq((await ch2.events.get()).hello, 'world')

request adds a unique id and waits for a reply with that id. Requests can run concurrently. The peer answering a request must include its id in the reply. This example replies with twice the supplied number.

async def _responder():
    msg = await ch2.events.get()
    await ch2.send(id=msg.id, result=msg.x*2)

task = asyncio.create_task(_responder())
test_eq((await ch1.request(x=21)).result, 42)

Check is_open before and after closing the connections.

assert ch1.is_open
await ch1.close()
await ch2.close()
assert not ch1.is_open

source

JsCDP

def JsCDP(
    wsconn:str=None, # WebSocket URL, populated by connection helpers
    debug:bool=False, # Log protocol events
    command_timeout:float=10, # Seconds before an unanswered command raises TimeoutError
):

CDP over the solveit-chrome extension: frames relayed via solveit’s /wsx channel

Everything else comes from fastcdp.ext’s ExtCDP: the sessionId↔︎tabId mapping, the lifecycle actions, and new_page/attach_page/pages. JsCDP just supplies the channel, meeting the extension at this solveit’s /wsx relay instead of listening on a local port (a solveit kernel usually runs in a container that the user’s browser can’t reach, so both sides dial out to the relay).

Connect, open a tab, and drive it. goto, eval, and friends are all inherited from fastcdp through the Page proxy:

cdp = await JsCDP.connect()
page = await cdp.new_page()
await page.goto('https://httpbingo.org/forms/post')
await page.eval('document.title')
assert 'httpbingo.org' in await page.eval('document.title')
assert first(await cdp.pages, lambda t: 'httpbingo' in t.get('url',''))

The accessibility tree and form helpers also come straight from fastcdp: find elements with ax_tree/find_id, then fill_text, click, select_option, and click_and_wait.

root = await page.ax_tree()
await page.fill_text(root.find_id('textbox', 'Customer name'), 'Jeremy Howard')
await page.click(root.find_id('radio', 'Large'))
await page.click(root.find_id('checkbox', 'Extra Cheese'))
await page.click_and_wait(root.find_id('button', 'Submit order'))
assert 'Jeremy Howard' in await page.eval('document.body.innerText')

The debugging buffers work too, since every chrome.debugger event is streamed over the channel and dispatched by the inherited machinery:

await page.start_console()
await page.eval(r'console.warn("watch out")')
test_eq(await page.console(r'watch'), ['warning: watch out'])
await page.close()
await cdp.close()

source

cdp_yolo

def cdp_yolo():

Allow all CDP classes in safepyrun