Issue No. 03

The Vibium JS API
— all 68 methods.

May 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.

Navigate
7
go · back · forward
reload · url · title
content
Find
2
page.find
page.findAll
Element
19
click · dblclick · fill
type · check · uncheck
select · focus · hover
scroll · text · innerText
value · getAttribute
bounds · isVisible
isEnabled · isChecked
isReadOnly
Keyboard
4
press · type
down · up
Mouse
4
click · move
down · up
Wait
4
wait · waitUntil
waitUntil.url
waitUntil.loaded
Page
9
screenshot · pdf · scroll
setContent · addScript
addStyle · expose
setViewport · evaluate
Capture
8
capture.dialog
capture.navigation
capture.response
capture.request
consoleMessages
route · unroute
setHeaders
Browser
3
newPage
newContext
pages
Clock
8
install · setFixedTime
setSystemTime
setTimezone
fastForward · pauseAt
resume · runFor

Starting up

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

import { Vibium } from 'vibium';

const browser = await Vibium.start();
const page = await browser.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 browser.close();

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 Vibium.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. Pass an options object with waitUntil to control the load condition — "networkidle" for SPAs, "commit" when you want to proceed immediately after the navigation commits without waiting for content.

await page.go('https://app.example.com');
await page.go('https://app.example.com', { waitUntil: 'networkidle' });

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.select() · el.focus() · el.hover() · el.scroll() · el.text() · el.innerText() · el.value() · el.getAttribute() · el.bounds() · el.isVisible() · el.isEnabled() · el.isChecked() · el.isReadOnly()

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.select() 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.isReadOnly() checks the readonly attribute on form fields.

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.scroll() 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**');
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, a glob, or a regex. 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 adminCtx = await browser.newContext();
const userCtx = await browser.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
clock.install({ now: new Date('2026-01-01T09:00:00Z') });

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

// Jump 30 minutes into the future
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.

68 methods. 10 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 — 72 methods across the same surface, with snake_case naming and synchronous-style syntax made async-friendly through Python's native async/await.

Previous · Issue No. 02
The Vibium MCP server — all 85 tools.
Up next · Issue No. 04
The Vibium Python API — all 72 methods.

No spam. Unsubscribe any time.

Something went wrong — please try again.