Vibium for Developers
Vibium for developers: a practical guide to AI-native browser automation on WebDriver BiDi, with runnable JS and Python, auto-waiting, and CI setup.
Vibium is AI-native browser automation built for developers who want less setup and fewer flaky tests. It ships as a single Go binary that auto-downloads Chrome, speaks the modern WebDriver BiDi protocol, and exposes a Playwright-style API in both JavaScript/TypeScript and Python. For a developer, that means one npm install vibium or pip install vibium, no ChromeDriver to version-match, and no Selenium Grid to run. You launch a browser, find() an element, and click() or type() — and Vibium auto-waits for the element to be actionable before every step, so the manual sleep() calls disappear. It also embeds an MCP server, letting AI coding agents drive the same browser your tests use. Created by Jason Huggins, co-creator of Selenium and Appium, Vibium is a lean, BiDi-first tool designed to fit naturally into modern developer workflows, from a quick local script to a headless CI pipeline.
Why should a developer care about Vibium?
Vibium removes the two things developers dislike most about legacy browser automation: environment setup and flaky waits. There is no driver binary to download and pin to your Chrome version, and no grid process to babysit. The single Go binary handles the browser lifecycle for you, and it auto-downloads a known-good Chrome for Testing on first run.
The second win is reliability. Because Vibium is built on WebDriver BiDi — a bidirectional, event-driven protocol — it can observe the page and wait until an element is genuinely ready. That built-in actionability model is why most Vibium scripts need no explicit waits at all.
Finally, Vibium is designed for the AI-agent era. The same tool that runs your test suite also exposes a built-in MCP server, so an assistant like Claude Code can navigate, click, and verify inside a real browser. You do not maintain two separate automation stacks.
The pipeline above is the whole developer loop: install once, write a linear script, let Vibium auto-wait through each interaction, assert on the result (with either a normal check or an AI check()), then run the same script headless in CI.
How does Vibium fit into my existing stack?
Vibium is a library you call from your own code, not a framework that takes over your project. That distinction matters when you are slotting it into an existing repo. You keep your test runner, your assertion library, and your CI, and Vibium simply becomes the layer that drives the browser.
Here is where each piece sits:
| Layer | What owns it | Vibium's role |
|---|---|---|
Test structure (describe/it, def test_) | Jest, Vitest, Mocha, pytest | None — your runner owns this |
| Assertions | expect, assert, chai | Optional — check() complements them |
| Browser control | Vibium | Launch, navigate, find, act |
| Browser binary + driver | Vibium | Auto-downloaded and managed |
| Reporting / CI | GitHub Actions, GitLab CI | None — Vibium runs inside the job |
Because Vibium owns only the middle rows, adoption is incremental. You can drop it into a single new spec file without rewriting how your suite is organized, then expand from there.
What does a first Vibium script look like?
A complete Vibium script is about ten lines and reads top to bottom with no callbacks. The JavaScript client ships a synchronous entry point at vibium/sync, which is the easiest way to start because there is no await to manage.
Install it into any Node.js 18+ project:
npm init -y
npm install vibiumThen create hello.js:
const fs = require("fs");
const { browser } = require("vibium/sync");
// Launch Chrome and grab the default tab
const bro = browser.launch();
const page = bro.page();
// Navigate and read an element
page.go("https://example.com");
const heading = page.find("h1");
console.log("Heading:", heading.text());
// Capture a screenshot (returns PNG bytes)
fs.writeFileSync("example.png", page.screenshot());
// Close the browser
bro.close();Run it with node hello.js. Chrome opens, loads the page, prints the heading, saves example.png, and closes. Every method maps to one browser action, which keeps scripts readable. For a line-by-line walkthrough, see Your First Vibium Script in JavaScript.
The Python client mirrors the API almost exactly. Install with pip install vibium, then:
from vibium import browser_sync as browser
vibe = browser.launch()
vibe.go("https://example.com")
heading = vibe.find("h1")
print("Heading:", heading.text())
with open("example.png", "wb") as f:
f.write(vibe.screenshot())
vibe.quit()Note the two idiomatic differences: Python uses browser_sync as the sync entry point, and it closes the session with vibe.quit(). Otherwise, go(), find(), text(), and screenshot() behave identically. The full walkthrough lives in Your First Vibium Script in Python.
How do I find and interact with elements?
Vibium uses a single find() method that accepts either a CSS selector string or a semantic options object. This is the core API you will use constantly, and it is deliberately smaller than the eight separate locator methods some tools expose.
For the common case, pass a CSS selector as a string:
const { browser } = require("vibium/sync");
const bro = browser.launch();
const page = bro.page();
page.go("https://www.google.com");
// CSS selector — the 80% case
const box = page.find("textarea[name='q']");
box.type("vibium browser automation");
box.press("Enter");
bro.close();When a CSS selector is awkward — for accessibility-driven tests, or when the markup is messy — pass an options object to match by role, text, or label instead. This mirrors how a real user perceives the page:
// Semantic finds — resilient to markup changes
page.find({ role: "button", text: "Sign in" }).click();
page.find({ label: "Email" }).fill("dev@example.com");
page.find({ placeholder: "Search..." }).type("vibium");In Python, the semantic form uses keyword arguments, which reads cleanly:
vibe.find(role="button", text="Sign in").click()
vibe.find(label="Email").fill("dev@example.com")
vibe.find(placeholder="Search...").type("vibium")The key developer benefit is that combined selectors like { role: "button", text: "Submit" } are a single expression, with no chaining or filtering required. For a deeper reference on the method, its signatures, and semantic strategies, see find element.
Reading state and taking screenshots
Once you have an element, you can read its state with methods like text(), value(), attr(name), and isVisible(). Screenshots work at both the page and element level, and both return PNG bytes you write to disk yourself:
const price = page.find(".product-price");
console.log(price.text()); // "$49.00"
console.log(price.isVisible()); // true
fs.writeFileSync("price.png", price.screenshot()); // element-only shotElement-level screenshots are handy for visual review of a single component without cropping a full-page capture. The full options, including fullPage, are covered in the screenshot command reference.
Why do Vibium scripts have so few waits?
Vibium auto-waits because actionability checks are enforced inside its Go binary, not bolted on in the client. Before every interaction — a click, a fill, a text read — Vibium polls until the target element is visible, stable in position, enabled, and able to receive the event, up to a default timeout.
In practice, this deletes an entire category of flaky code. The pattern you would write defensively in older stacks simply is not needed:
// You do NOT need this in Vibium:
await sleep(2000);
await waitUntilVisible(".submit");
await retryOnStaleElement(() => button.click());
// You write this instead — Vibium waits internally:
page.find(".submit").click();Because the waiting logic lives server-side in the engine, sync and async calls go through the exact same checks. A synchronous click() and an await click() are equally robust. If you do need an explicit wait for a non-element condition — a URL change or a custom JavaScript predicate — Vibium still offers waitForURL() and waitForFunction(). You just reach for them rarely.
Should I use the sync or async API?
Use the sync API for linear scripts and one-off automations, and the async API when you are already inside an async codebase or need concurrency. Both are first-class and drive the same engine.
The sync client is vibium/sync in JavaScript and browser_sync in Python. The async client is the bare vibium import in JS and vibium.async_api in Python:
// Async JavaScript — promises and await
const { browser } = require("vibium");
async function main() {
const bro = await browser.launch();
const page = await bro.page();
await page.go("https://example.com");
await page.find("a").click();
await bro.close();
}
main();# Async Python
import asyncio
from vibium.async_api import browser
async def main():
bro = await browser.launch()
vibe = await bro.new_page()
await vibe.go("https://example.com")
await bro.close()
asyncio.run(main())As a rule of thumb: reach for sync when readability matters most and the script is short, and async when you are running many pages at once or integrating with an event-driven server. The sync vs async guide breaks down the trade-offs in detail.
How do I keep tests maintainable as they grow?
The single most effective habit is to keep selectors out of your test bodies. When a script grows past a handful of steps, inline CSS strings scattered across tests become the main source of churn — one markup change breaks ten files. Centralizing locators in a page object fixes that.
A minimal page object in Vibium is just a class that wraps a page and exposes intent-named methods:
class LoginPage {
constructor(page) {
this.page = page;
}
open() {
this.page.go("https://app.example.com/login");
}
login(email, password) {
this.page.find({ label: "Email" }).fill(email);
this.page.find({ label: "Password" }).fill(password);
this.page.find({ role: "button", text: "Sign in" }).click();
}
}Now your tests read as behavior, not markup: loginPage.login("dev@example.com", "secret"). When the sign-in button's markup changes, you edit one method, not every test. This pattern scales cleanly, and the full treatment — including when a page object is overkill — lives in page object model best practices.
For assertions that would be brittle to express in CSS, Vibium also offers an AI-native check() method that takes a plain-English claim:
const result = await vibe.check("the dashboard shows a welcome message");
// { passed: true, reason: "...", confidence: 0.95 }Use check() for fuzzy, visual, or hard-to-select conditions, and keep deterministic methods like text() for exact values. They complement each other rather than compete.
How do I run Vibium in CI?
Running Vibium in continuous integration is straightforward because there is no separate driver to provision. Install the package, run headless, and execute your test command. The single-binary design means the browser is fetched and managed by Vibium itself inside the job.
Launch headless by passing an option to launch():
const bro = browser.launch({ headless: true });bro = browser.launch(headless=True)A minimal GitHub Actions job looks like this:
name: e2e
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm ci
- run: node hello.js # or: npm testOn Linux runners you may need a handful of shared libraries for Chrome, which most CI images already include. If Chrome fails to launch, install the usual set (libnss3, libgbm1, libatk-bridge2.0-0, and friends) as a step before your test command. For the Python equivalent, swap setup-node for setup-python, npm ci for pip install vibium, and run python hello.py.
How does Vibium compare to Playwright and Selenium for developers?
Vibium, Playwright, and Selenium all drive real browsers, but they optimize for different priorities. This table is an honest at-a-glance view so you can pick the right tool rather than assume Vibium is always the answer.
| Factor | Vibium | Playwright | Selenium |
|---|---|---|---|
| Setup | Single Go binary, auto Chrome | npm install + browser download | Driver + often Grid |
| Protocol | WebDriver BiDi (native) | CDP + partial BiDi | WebDriver (classic + BiDi in 4) |
| Auto-waiting | Yes (built into engine) | Yes | Manual / explicit waits |
| Languages | JS/TS, Python (more planned) | JS/TS, Python, Java, .NET | Java, Python, JS, C#, Ruby, more |
| AI-native features | check(), do(), built-in MCP | Via external tooling | Via external tooling |
| Ecosystem maturity | New (v26.2) | Large, battle-tested | Largest, 20+ years |
When to choose Vibium: you want minimal setup, a BiDi-first foundation, and native AI-agent support, and Python or JavaScript covers your team.
When to choose Playwright: you need a large, proven ecosystem today, first-class Java or .NET clients, or mature tooling like Playwright's trace viewer and codegen.
When to choose Selenium: you have an existing enterprise Selenium estate, need the widest language coverage, or depend on its huge grid and vendor ecosystem.
The fair verdict: Vibium is the most ergonomic choice for new JS/Python projects that value lean setup and AI integration, while Playwright and Selenium win on ecosystem breadth and multi-language reach. If you are weighing a switch, the detailed Vibium vs Playwright and Vibium vs Selenium breakdowns cover migration gotchas — chiefly that Vibium's client list is still growing and its ecosystem is younger, so validate your required integrations before committing a large suite.
Can an AI agent drive Vibium for me?
Yes — and for many developers this is the headline feature. Vibium ships a built-in MCP (Model Context Protocol) server, so an AI coding agent can use the browser as a tool without any glue code. The same find, click, and navigate primitives your tests use are exposed to the agent.
Wiring it into Claude Code is a one-line command:
claude mcp add vibium -- npx -y vibium mcpAfter that, your assistant can open pages, fill forms, take screenshots, and verify results in a real Chrome instance — useful for exploratory testing, reproducing a bug from a description, or scaffolding a new automation. The full setup, including permissions and troubleshooting, is in Vibium MCP with Claude Code.
This dual nature — deterministic API for code, MCP for agents — is the core of what "AI-native" means for Vibium. You are not maintaining a separate agent framework alongside your test tooling; it is one binary serving both.
What mistakes do developers make when starting with Vibium?
Most early friction comes from carrying habits over from other tools rather than from Vibium itself. Knowing the common traps up front saves an afternoon of confusion.
The mistakes cluster into a few predictable buckets:
| Mistake | Why it bites | Do this instead |
|---|---|---|
Adding manual sleep() before actions | Vibium already auto-waits; sleeps only slow tests | Call find().click() directly |
| Mixing sync and async entry points | vibium/sync and vibium have different call styles | Pick one per file; use await only with the async import |
Calling screenshot() expecting a file | It returns PNG bytes, not a path | Write them yourself with fs.writeFileSync / open(...,"wb") |
| Forgetting to close the browser | Leaves Chrome processes running in CI | Always call bro.close() (or vibe.quit() in Python) |
| Over-specific CSS selectors | Deep div > div > span chains break on redesign | Prefer { role, text } or { label } semantic finds |
| Reusing one context for isolated tests | State leaks between tests via cookies/storage | Create a fresh context per test with newContext() |
The last row is worth expanding, because test isolation is a frequent source of mysterious failures. Vibium exposes contexts — isolated cookie-and-storage jars — under one browser process. Give each test its own context and the previous test's login or cart state cannot bleed in:
const bro = browser.launch();
// Each context is a clean slate — own cookies, own localStorage
const ctx = bro.newContext();
const page = ctx.newPage();
page.go("https://store.example.com");
// ... run the test ...
ctx.close(); // tear down just this context, keep the browserReusing a single page across unrelated tests is convenient but couples them; a fresh context is the small habit that keeps a suite deterministic as it grows.
How do I debug a Vibium script when something breaks?
Start by turning off headless mode so you can watch the browser do exactly what your code says. Most "it does not work" reports resolve the moment you see the real page — a cookie banner covering the button, a redirect you did not expect, or an element that never renders.
Run headed (the default) and slow yourself down with a targeted pause while developing:
const bro = browser.launch(); // headed by default — you see the window
const page = bro.page();
page.go("https://app.example.com");
page.pause(); // opens the inspector when running headedFrom there, a few tactics cover most problems. When a find() cannot locate an element, capture a screenshot at that exact point to confirm what was actually on screen, and read the page HTML to check the selector:
fs.writeFileSync("debug.png", page.screenshot());
console.log(page.content().slice(0, 2000)); // inspect the live DOMIf an element genuinely appears later than expected — say, after an XHR resolves — reach for page.waitFor(selector) or page.waitForFunction() rather than a blind sleep, since those return the moment the condition is met. And when the failure is a wrong value rather than a missing element, log the concrete state with el.text(), el.value(), or el.attr("class") so you compare against reality instead of assumptions.
For hard-to-diagnose flakiness, Vibium supports tracing, which records the session for after-the-fact review. Combined with headed replay, that usually turns a vague intermittent failure into an obvious one. A broader checklist lives in the official Vibium debugging docs, and the architecture overview explains why the engine behaves the way it does.
Next steps
- What is Vibium? — the concepts and design in one page
- Install Vibium — get set up on any OS
- find element — the API you will use most
- Automate a login with Vibium — a real end-to-end flow
- Page object model best practices — keep growing suites maintainable
- Vibium vs Playwright — an honest, detailed comparison
- Glossary and the 45-day roadmap — reference terms and a learning path
Frequently asked questions
Is Vibium good for developers who already know Playwright or Selenium?
Yes. Vibium reuses concepts you already know — launch a browser, find an element, click or type — but ships as one Go binary that auto-downloads Chrome and auto-waits for elements. If you know Playwright's locator model, Vibium's find() will feel familiar, with fewer moving parts to install and maintain.
What language should I use with Vibium as a developer?
Use JavaScript/TypeScript if your app or test suite is Node-based, and Python if your team lives in the Python ecosystem. Both are official first-class clients that drive the same Go engine, so the API, auto-waiting behavior, and results are identical across both languages.
Does Vibium replace my existing test runner?
No. Vibium is the browser-driving layer, not a test runner. You call Vibium inside Jest, Vitest, Mocha, or pytest exactly as you would call any library. Vibium handles launching Chrome, finding elements, and actions, while your runner handles test structure, assertions, and reporting.
How does Vibium handle waiting for elements?
Vibium auto-waits using actionability checks built into its Go binary. Before every click, type, or read, it polls until the element is visible, stable, enabled, and able to receive the event, up to a default timeout. This removes most manual sleeps and the flaky retries common in older WebDriver scripts.
Can I run Vibium in CI like GitHub Actions?
Yes. Install Vibium with npm or pip in your CI job, run the browser in headless mode, and execute your script or test command. Because Vibium ships as a single binary that manages its own Chrome, there is no separate ChromeDriver or Selenium Grid to provision in the pipeline.
Is Vibium free and who maintains it?
Vibium is free to install via npm install vibium or pip install vibium. It was created by Jason Huggins, co-creator of Selenium and Appium, and is developed in the open at github.com/VibiumDev/vibium. This site, learnvibium.com, is an independent learning hub and is not the maintainer of the tool.
Vibium is created by Jason Huggins. This is an independent tutorial — see the official Vibium site and GitHub repo for canonical docs.
Related guides
Is Vibium Worth Learning in 2026?
Is Vibium worth learning in 2026? An honest breakdown of who it fits, what it costs to learn, and when to pick it over Playwright or Selenium.
14 min read→Getting StartedLearn Vibium in a Weekend
Learn Vibium in a weekend: a 2-day plan to install it, write real browser scripts, add semantic locators, wire up the MCP server, and ship a project.
14 min read→Getting StartedVibium Cheat Sheet (2026)
The complete Vibium cheat sheet for 2026: install, launch, find, click, type, wait, screenshot, and MCP commands with copy-paste JavaScript and Python.
13 min read→Getting StartedUnderstanding Vibium's Installed Folder Structure
What npm install vibium actually puts on disk — the package, the bundled Go binary, the auto-downloaded Chrome, and where Vibium caches everything.
1 min read→