Issue No. 04

The Vibium Python API
— all 157 methods.

July 12, 2026 · 14 min read ·
Python Browser Navigate Find Element Keyboard Wait Capture Clock

The TypeScript/JavaScript API is async by default — every call is a promise, every step an await. The Python API takes the same object model and fits it to the language that QA engineers actually reach for: synchronous by default, snake_case throughout, and as readable in a test file as it is in a REPL. Same browser, same power, different idiom.

Browser
9
start · page · new_page
new_context · pages
on_page · on_popup
stop · remove_all_listeners
Context
9
new_page · close · cookies
set_cookies · clear_cookies
storage · set_storage
clear_storage · add_init_script
Navigate
7
go · back · forward
reload · url · title
content
Find
4
page.find · page.find_all
el.find · el.find_all
Element — Act
16
click · dblclick · fill
type · press · clear
check · uncheck · select_option
hover · focus · drag_to
tap · scroll_into_view
dispatch_event · set_files
Element — Read
17
text · inner_text · html
value · attr · get_attribute
bounds · bounding_box
is_visible · is_hidden
is_enabled · is_checked
is_editable · role · label
screenshot · wait_until
Input
10
keyboard.press · keyboard.type
keyboard.down · keyboard.up
mouse.click · mouse.move
mouse.down · mouse.up
mouse.wheel · touch.tap
Page — State
10
set_viewport · viewport
emulate_media · set_content
set_geolocation · set_window
window · scroll
bring_to_front · close
Page — Output
11
screenshot · pdf
evaluate · eval
add_script · add_style
expose · a11y_tree
frames · frame
main_frame
Wait
4
wait · wait_until
wait_until.url
wait_until.loaded
Capture
6
capture.dialog
capture.navigation
capture.response
capture.request
capture.download
capture.event
Network
13
route · unroute · set_headers
on_request · on_response
on_web_socket · on_dialog
on_console · console_messages
on_error · errors
on_download
remove_all_listeners
Clock
8
install · fast_forward
run_for · pause_at
resume · set_fixed_time
set_system_time · set_timezone
Recording
6
start · stop
start_chunk · stop_chunk
start_group · stop_group
Dialog
5
message · type
default_value · accept
dismiss
Route
3
fulfill · continue_
abort
Request
5
url · method · headers
request_id · post_data
Response
6
url · status · headers
request_id · body · json
Download
4
url · suggested_filename
save_as · path
WebSocket
4
url · on_message
on_close · is_closed

Starting up

Install with pip install vibium, then import and start. browser.start() returns a Browser instance synchronously. No event loop to manage, no async def, no asyncio.run() — just a function call that blocks until the browser is ready.

from vibium import browser

bro = browser.start()
page = bro.new_page()

page.go('https://github.com/login')

inp = page.find(role='textbox', label='Username or email address')
inp.fill('your-username')

bro.stop()

The naming follows Python conventions throughout: new_page, find_all, is_visible, set_content, inner_text. If you've used any Python test automation tool before, the method names will feel familiar before you finish the import line.

Pass headless=True to browser.start() for CI. The default is headed. Pass executable_path to point at a specific browser binary, or headers to set default request headers for every page in the session.

Navigating

page.go() · page.back() · page.forward() · page.reload() · page.url() · page.title() · page.content()

Seven methods on page, identical in purpose to the JS surface. page.go(url) waits for load by default. For SPAs where the initial HTML lands fast but data populates asynchronously, follow the navigation with page.wait_until.url() or page.wait_until.loaded().

page.go('https://app.example.com')
page.wait_until.loaded() # for SPAs — wait for full hydration after go()

url = page.url()
title = page.title()

assert title == 'Dashboard'
assert 'app.example.com' in url

page.url() and page.title() read as natural assertions in Python — no boilerplate, no dot-notation on a result object. They return plain strings that sit cleanly inside assert expressions or pytest's assert introspection.

page.content() returns the current full-page HTML. Useful for snapshot testing — diff it against a stored baseline, or parse it with BeautifulSoup for structural assertions without depending on Vibium's element model.

Finding elements

page.find() · page.find_all()

page.find() returns a single Element. page.find_all() returns a list. Both accept keyword arguments: role, label, text, placeholder, selector. The most durable pattern is role + label — it survives layout changes and refactors because it targets semantics, not structure.

# Semantic — role + label
btn = page.find(role='button', text='Sign in')
field = page.find(role='textbox', label='Email')

# Text content
msg = page.find(text='Welcome back')

# CSS selector — last resort
badge = page.find('.notification-badge')

# All matching elements
rows = page.find_all(role='row')
assert len(rows) == 5

Find once, act many times. Once you hold an Element reference, every subsequent method call — .click(), .fill(), .text() — goes directly to that element without re-querying the DOM. This makes long interaction sequences both faster and more explicit about what the script is operating on.

Find once, act many. The element handle is the stable reference — don't re-query what you already have.

Element interactions

click · dblclick · fill · type · check · uncheck · select_option · focus · hover · scroll_into_view · text · inner_text · value · get_attribute · bounds · is_visible · is_enabled · is_checked · is_editable

Nineteen methods on every Element. The split between write and read methods is deliberate: fill() clears then types; type() types character-by-character without clearing first. Use fill for form inputs, type when you need to trigger per-keystroke event handlers.

# Write
email.fill('user@example.com')
search.type('pyt') # triggers autocomplete after each char

# State checks — good assertion primitives
assert submit.is_enabled()
assert checkbox.is_checked() is False
assert field.is_editable()

# Read
label_text = heading.text() # raw text content (includes hidden)
visible = heading.inner_text() # rendered visible text only
href = link.get_attribute('href')
rect = btn.bounds() # rect.x, rect.y, rect.width, rect.height

is_visible(), is_enabled(), is_checked(), and is_editable() return plain Python booleans. They are the cleanest assertion primitives in the API — no wrapping, no helpers, no matcher libraries needed. Use them directly in assert statements.

bounds() returns a BoundingBox object with x, y, width, and height as attributes — access them as rect.x, rect.width, not dict-style. It's the right tool for layout assertions — checking that a tooltip appears above its trigger, that a modal is centred, or that two elements don't overlap.

Keyboard and mouse

page.keyboard.press() · page.keyboard.type() · page.keyboard.down() · page.keyboard.up() · page.mouse.click() · page.mouse.move() · page.mouse.down() · page.mouse.up()

When the element API doesn't reach — global keyboard shortcuts, drag operations, coordinate-based clicks in canvas elements — the page.keyboard and page.mouse sub-objects give you direct input control.

# Global shortcuts
page.keyboard.press('Control+a') # select all
page.keyboard.press('Escape') # dismiss modals
page.keyboard.press('Tab') # advance focus

# Hold a modifier while clicking
page.keyboard.down('Shift')
page.mouse.click(400, 300)
page.keyboard.up('Shift')

# Drag via mouse primitives
page.mouse.move(100, 200)
page.mouse.down()
page.mouse.move(300, 200)
page.mouse.up()

page.keyboard.type(text) types a full string with realistic per-character timing. Use it when element.type() isn't an option — for example, when the target element is a canvas-based input or a custom rich-text editor that intercepts keyboard events at the window level.

Waiting

page.wait() · page.wait_until() · page.wait_until.url() · page.wait_until.loaded()

Vibium's synchronous model means most actions already wait for the browser to settle. The explicit wait API is for cases where settling isn't defined by a single navigation — polling for an element to appear, waiting for a URL change after an async redirect, or confirming that a full SPA hydration is complete.

# Fixed pause — ms integer
page.wait(1000) # 1 second

# Wait for URL to match a glob pattern
page.wait_until.url('**/dashboard')

# Wait for full page load
page.wait_until.loaded()

# Conditional wait — JS expression polled until truthy
page.wait_until('!!document.querySelector(".toast.success")')

page.wait(ms) pauses execution for the given number of milliseconds. For waiting on element state, page.find() already auto-waits — the element wait is built into the find, not a separate step.

page.wait_until(expr) takes a JavaScript expression string. It evaluates the expression inside the page every 100ms until it returns a truthy value. This is the escape hatch for anything the built-in wait methods don't cover — custom DOM conditions, cross-element checks, or anything requiring a JS predicate before deciding the page is ready.

Capturing output and network

capture.dialog() · capture.navigation() · capture.response() · capture.request() · console_messages() · route() · unroute() · set_headers()

Dialog capture in Python follows the same fire-and-forget rule as TypeScript/JavaScript and MCP: the action that triggers the dialog must be called after starting the capture, and the call to the triggering action must not block before the dialog handler has run. In Python's synchronous model, this means starting the capture context manager, then calling the triggering action inside it.

# Dialog — action fires inside the context; value available after exit
with page.capture.dialog() as dlg:
btn.click() # triggers the alert; auto-dismissed on exit

assert dlg.value['message'] == 'Are you sure?'

# Intercept a network response
with page.capture.response('**/api/user') as resp:
page.reload()

assert resp.value['status'] == 200
body = resp.value['body'] # plain string — parse with json.loads()

# Mock a route
page.route('**/api/flags', lambda r: r.fulfill(
body='{"dark_mode": true}', content_type='application/json'
))
page.go('https://app.example.com')

page.route(url_pattern, handler) intercepts matching requests before they leave the browser. The handler receives a Route object with fulfill(), continue_(), and abort() — use it to stub APIs, inject feature flags, or simulate network errors without touching the server.

Route mocking at the browser level means no server changes, no test doubles — just intercept and respond.

page.console_messages() returns a list of console entries captured since the last call. Filter by type"error", "warning", "log" — to assert that a feature runs without producing console errors.

Browser contexts and the virtual clock

bro.new_context() · ctx.new_page() · ctx.storage() · ctx.set_cookies() · page.clock.install() · page.clock.set_fixed_time() · page.clock.fast_forward() · page.clock.pause_at() · page.clock.run_for() · page.clock.resume()

A browser context is an isolated session: separate cookies, separate storage, separate authentication state. Use bro.new_context() when you need to run tests as different users, or when parallel test workers must not share state.

# Two isolated contexts — two users
ctx_a = bro.new_context()
ctx_b = bro.new_context()

page_a = ctx_a.new_page()
page_b = ctx_b.new_page()

# Read cookies + storage from a logged-in context
state = ctx_a.storage() # returns StorageState dict
cookies = ctx_a.cookies()

# Virtual clock — control Date.now() inside the page
page.set_content('<div id="ts"></div><script>setInterval(()=>{document.getElementById("ts").textContent=Date.now()},100)</script>')
page.clock.install()
page.clock.set_fixed_time('2026-01-01T00:00:00Z')

ts = page.find('#ts').text()
assert ts == '1735689600000'

# Advance time without waiting in real time
page.clock.fast_forward(3_600_000) # jump one hour (ms)
page.clock.run_for(5000) # run 5 seconds of timers

ctx.storage() and ctx.cookies() read the current session state from a context where you've completed a login flow. Use them for inspection or to seed a new context via ctx.set_cookies() — keeping authentication state portable across contexts without repeating the login flow for every test.

The virtual clock intercepts Date, setTimeout, setInterval, and requestAnimationFrame inside the page. clock.install() freezes time at the current moment. clock.set_fixed_time() pins it to a specific ISO timestamp. clock.fast_forward() jumps time forward without blocking the test process — a timer that would take an hour to fire in production fires immediately in the test.

That's the Python API: 157 methods, synchronous by default, snake_case throughout. The same object model as the JS API — browser → context → page → element — but written for the language that QA engineers spend most of their time in. No boilerplate, no event loop ceremony, assertions that read like plain English.

The full docs with per-method demos is at daisyladybug.com/vibium/python/. Next up: the Java API — the same surface one more time, typed, fluent, and built for teams running on the JVM.

Previous · Issue No. 03
The Vibium TypeScript/JavaScript API — all 160 methods.
Up next
Issue No. 05 — The Vibium Java API — all 164 methods.

No spam. Unsubscribe any time.

Something went wrong — please try again.