Issue No. 03

The Vibium JS API
— all 160 methods.

June 19, 2026 · 13 min read ·
TypeScript/JavaScript Navigate Find Element Keyboard Mouse Wait Capture Clock

The CLI is a terminal. The MCP server is a tool list. The TypeScript/JavaScript API is a library — async, typed, and structured around objects that hold state. You import it, you instantiate a browser, you get a page, and every method is a promise. It's the surface you reach for when automation lives inside a Node.js codebase and needs to compose cleanly with everything else already there.

Browser
9
start · page · newPage
newContext · pages
onPage · onPopup
stop · removeAllListeners
Context
9
newPage · close · cookies
setCookies · clearCookies
storage · setStorage
clearStorage · addInitScript
Navigate
7
go · back · forward
reload · url · title
content
Find
4
page.find · page.findAll
el.find · el.findAll
Element — Act
16
click · dblclick · fill
type · press · clear
check · uncheck · selectOption
hover · focus · dragTo
tap · scrollIntoView
dispatchEvent · setFiles
Element — Read
17
text · innerText · html
value · attr · getAttribute
bounds · boundingBox
isVisible · isHidden
isEnabled · isChecked
isEditable · role · label
screenshot · waitUntil
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
setViewport · viewport
emulateMedia · setContent
setGeolocation · setWindow
window · scroll
bringToFront · close
Page — Output
10
screenshot · pdf
evaluate · addScript
addStyle · expose
a11yTree · frames
frame · mainFrame
Wait
4
wait · waitUntil
waitUntil.url
waitUntil.loaded
Capture
6
capture.dialog
capture.navigation
capture.response
capture.request
capture.download
capture.event
Network
13
route · unroute · setHeaders
onRequest · onResponse
onWebSocket · onDialog
onConsole · consoleMessages
onError · errors
onDownload
removeAllListeners
Clock
8
install · fastForward
runFor · pauseAt
resume · setFixedTime
setSystemTime · setTimezone
Recording
6
start · stop
startChunk · stopChunk
startGroup · stopGroup
Dialog
5
message · type
defaultValue · accept
dismiss
Route
3
fulfill · continue
abort
Request
5
url · method · headers
requestId · postData
Response
6
url · status · headers
requestId · body · json
Download
4
url · suggestedFilename
saveAs · path
WebSocket
4
url · onMessage
onClose · isClosed
ConsoleMsg
4
type · text
args · location

Starting up

Install with npm install vibium, then import and start. The entry point is browser.start() — it returns a Browser instance. From there, every operation flows through the object model: browser → page → element.

import { browser } from 'vibium';

const bro = await browser.start();
const page = await bro.newPage();

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

const input = await page.find({ role: 'textbox', label: 'Username or email address' });
await input.fill('your-username');

await bro.stop();

Three lines to go from import to first interaction. The async/await model is consistent throughout — every method on Page, Element, and Browser returns a promise. There are no callbacks, no event emitters, no synchronous steps.

Pass { headless: true } to browser.start() for CI runs. The default is headed — the browser window is visible, which is what you want during development when you're watching what the script does.

Navigating

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

Seven methods, all on page. page.go(url) navigates and waits for load by default. For SPAs where the initial HTML lands before data populates, follow the navigation with page.waitUntil.loaded() or page.waitUntil.url() to wait for the settled state.

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

const url = await page.url();
const title = await page.title();
// use as assertions
assert(title === 'Dashboard');

page.url() and page.title() are the most common assertion primitives in navigation-heavy test suites — lightweight, readable, and independent of any element on the page. page.content() returns the full page HTML as a string, useful when you need to inspect raw markup without querying individual elements.

page.back(), page.forward(), and page.reload() mirror browser button behaviour. All three wait for the page to settle before resolving, so you can call page.find() immediately after without sleeping.

Finding elements

page.find() · page.findAll()

Two methods. The entire element interaction model flows through these two. page.find() returns a single Element handle — an object with 19 methods on it. page.findAll() returns an array of them.

// Semantic: role + label — the most durable selector
const btn = await page.find({ role: 'button', text: 'Sign in' });
const input = await page.find({ role: 'textbox', label: 'Email' });

// By placeholder, alt, or test ID
const search = await page.find({ placeholder: 'Search…' });
const avatar = await page.find({ alt: 'User avatar' });
const widget = await page.find({ testid: 'price-widget' });

// All matching elements
const rows = await page.findAll('table tbody tr');
for (const row of rows) {
  const text = await row.text();
  console.log(text);
}

The semantic locators — role, label, placeholder, alt, testid — are the right default. They survive HTML refactors as long as the UX intent stays the same. Reach for CSS selectors when none of those apply, and XPath when CSS can't express the query.

page.find() waits for the element to appear before returning. The default timeout is 30 seconds — pass a timeout property in the selector object to override it. If the element doesn't appear within the timeout, the promise rejects with a descriptive error.

Element interactions

el.click() · el.dblclick() · el.fill() · el.type() · el.check() · el.uncheck() · el.selectOption() · el.focus() · el.hover() · el.scrollIntoView() · el.text() · el.innerText() · el.value() · el.getAttribute() · el.bounds() · el.isVisible() · el.isEnabled() · el.isChecked() · el.isEditable()

Nineteen methods on the Element object returned by page.find(). The pattern is consistent: find once, interact many times on the same handle.

const form = {
  email: await page.find({ role: 'textbox', label: 'Email' }),
  password: await page.find({ role: 'textbox', label: 'Password' }),
  submit: await page.find({ role: 'button', text: 'Sign in' }),
};

await form.email.fill('user@example.com');
await form.password.fill('secret');
await form.submit.click();

el.fill() clears the field and types in one operation. el.type() appends character-by-character — use it when autocomplete needs to fire on each keystroke. el.selectOption() picks a <select> option by its visible label or value attribute.

The four read methods cover the full spread of what you need from an element's content. el.text() is raw textContent — includes hidden text, line breaks from block-level children. el.innerText() is the rendered version — what a user would actually see and copy. el.value() reads the current value of any form input. el.getAttribute(name) retrieves any HTML attribute by name.

The four boolean methods are guard tools. Check el.isEnabled() before clicking a submit button if you're verifying that the form's validation state is correct. el.isVisible() tests whether the element is in the viewport and not hidden by CSS. el.isChecked() reads checkbox and radio state. el.isEditable() checks that a field is neither disabled nor read-only.

el.bounds() returns the element's position and dimensions as { x, y, width, height } — useful for layout assertions or for computing coordinates when you need to interact with a specific region inside an element.

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

Eight methods across two sub-objects. The keyboard and mouse APIs are the low-level layer beneath the element methods — reach for them when you need precise control over input timing, key sequences, or coordinates.

// Keyboard shortcuts
await page.keyboard.press('Control+a');
await page.keyboard.press('Shift+Tab');

// Hold shift while pressing arrows
await page.keyboard.down('Shift');
await page.keyboard.press('ArrowRight');
await page.keyboard.press('ArrowRight');
await page.keyboard.up('Shift');

// Raw mouse — for canvas or custom drag targets
await page.mouse.move(120, 240);
await page.mouse.down();
await page.mouse.move(380, 240);
await page.mouse.up();

page.keyboard.down() and page.keyboard.up() are the modifier-key pair — hold a key across multiple other key events. The example above selects two characters to the right using shift+arrow, which no single press() call can express.

The mouse API operates on raw page coordinates. The sequence move → down → move → up is how you implement a custom drag gesture on a target that el.scrollIntoView() can't reach — a canvas slider, a map pane, a custom range control built without semantic HTML.

Waiting

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

Four methods. page.wait(ms) is the unconditional sleep — fixed milliseconds, always blocking. Use it sparingly. The other three are conditional: they resolve the moment their condition is met, which makes tests faster and less brittle than a fixed sleep.

// Wait for a JS condition in the page context
await page.waitUntil('document.querySelector(".spinner") === null');

// Wait for URL after form submission redirect
await page.waitUntil.url('https://app.example.com/dashboard');
await page.waitUntil.url('**/dashboard**');

// Wait for full page load after navigation
await page.waitUntil.loaded();

page.waitUntil(expression) evaluates a JavaScript string in the page context on a polling interval and resolves when the expression returns truthy. It's the general-purpose wait — use it for application-specific conditions that no CSS selector can express: a flag on window, a data attribute set by app logic, a third-party widget's internal state.

page.waitUntil.url() accepts a string or a glob. Globs are the pragmatic choice after redirects where the exact final URL varies by environment — a pattern like '**/dashboard**' matches regardless of the host.

Page utilities and network capture

page.screenshot() · page.pdf() · page.scroll() · page.setContent() · page.addScript() · page.addStyle() · page.expose() · page.setViewport() · page.evaluate() · page.capture.dialog() · page.capture.navigation() · page.capture.response() · page.capture.request() · page.consoleMessages() · page.route() · page.unroute() · page.setHeaders()

Seventeen methods split across two categories. The Page methods modify or observe the page as a whole. The Capture methods intercept events — dialogs, navigations, network requests — that would otherwise be hard to test because they happen asynchronously in response to an action.

// page.evaluate — run JS, get the result back in Node
const count = await page.evaluate('document.querySelectorAll("li").length');

// page.addStyle — inject CSS to suppress flaky animations in tests
await page.addStyle('*, *::before, *::after { animation-duration: 0s !important; }');

// page.setContent — render a component without a dev server
await page.setContent('<div class="card"><h2>Hello</h2></div>');

// page.setViewport — simulate a specific device before navigation
await page.setViewport({ width: 390, height: 844 }); // iPhone 15

The page.capture.* methods handle the hardest category of browser interaction to test: things that happen in response to another action. The key rule is that the action triggering the event must be fire-and-forget inside the capture callback — never awaited.

// Handle a confirm dialog before the action that triggers it
const dialog = await page.capture.dialog(async () => {
  page.find({ role: 'button', text: 'Delete account' }).then(b => b.click());
});
await dialog.accept();

// Intercept an API response triggered by a button click
const res = await page.capture.response('/api/users', async () => {
  const btn = await page.find({ role: 'button', text: 'Load users' });
  await btn.click(); // this is fine — it's inside capture.response's fn
});
const data = await res.json();

page.route() intercepts all requests matching a URL pattern and routes them through a handler. Use it to mock API responses in tests — return a fixed JSON payload without hitting the real server. page.unroute() removes the handler when the test is done.

page.setHeaders() adds custom HTTP headers to every subsequent request from the page — the clean way to inject an auth token for tests that can't use cookie-based sessions.

Browser context and the virtual clock

browser.newPage() · browser.newContext() · browser.pages() · clock.install() · clock.setFixedTime() · clock.setSystemTime() · clock.setTimezone() · clock.fastForward() · clock.pauseAt() · clock.resume() · clock.runFor()

The three browser.* methods manage multi-page and multi-context sessions. browser.newPage() opens a fresh tab in the current context — it shares cookies and storage with existing pages. browser.newContext() creates an isolated context with its own cookies, storage, and permissions — the right choice when you need two sessions running in parallel without interference, such as testing an admin view alongside a user view.

// Two independent sessions, no shared state
const bro = await browser.start();
const adminCtx = await bro.newContext();
const userCtx = await bro.newContext();

const adminPage = await adminCtx.newPage();
const userPage = await userCtx.newPage();

await adminPage.go('https://app.example.com/admin');
await userPage.go('https://app.example.com/dashboard');

The Clock API replaces the browser's real-time functions with a controllable fake. Call clock.install() once at the start of the test — after that, Date.now(), setTimeout, setInterval, and all related browser APIs respond to the clock you control.

// Test an expiry countdown — no real waiting
await clock.install({ time: new Date('2026-01-01T09:00:00Z') });

await page.go('https://app.example.com/session');

// Jump 30 minutes into the future
await clock.fastForward(30 * 60 * 1000);

// Verify the session expired banner appeared
const banner = await page.find({ role: 'alert', text: 'Session expired' });
assert(await banner.isVisible());

clock.setTimezone() accepts any IANA timezone name and overrides what the page sees as its local timezone — making locale-sensitive UI logic fully testable without changing the system's actual timezone or running a VM. clock.runFor(ms) advances the clock by a duration and fires all timers that fall within that window, then pauses — the right tool for testing that a setInterval callback fires the expected number of times.

160 methods. 21 categories. One async object model from browser to page to element.

The pattern worth internalising is the separation between find and act: resolve the element handle once with page.find(), then call methods on the handle as many times as you need. It's more readable than chaining finders, and it makes it obvious at a glance what the test is targeting.

The next issue covers the Python API — 157 methods across the same surface, with snake_case naming and synchronous execution by default — no event loop, no await, just straight function calls.

No spam. Unsubscribe any time.

Something went wrong — please try again.