VLearnVibium

Vibium for QA Engineers

Vibium for QA engineers: write stable Chrome tests with auto-waiting, semantic locators, tracing, and AI checks. Runnable pytest, Jest, and Mocha code.

By Pramod Dutta··14 min read·Verified with Vibium 26.2
▶ Animated overview · made with Remotion

Vibium is a strong fit for QA engineers building Chrome UI tests, because it combines auto-waiting actions, semantic locators, and Playwright-style tracing in a single install. For a tester, the daily loop is simple: launch a browser, find an element, act on it, and assert the outcome — Vibium handles the waiting for you inside its Go engine, so flaky sleeps and stale-element retries mostly disappear. It ships free via pip install vibium or npm install vibium, downloads its own Chrome for Testing (keeping CI deterministic), and works with pytest, Jest, or Mocha. It was created by Jason Huggins, co-creator of Selenium and Appium, so the design reflects two decades of test-automation lessons. This guide shows the QA workflow end to end — locators, assertions, waiting, tracing, and CI — with runnable code you can drop into a suite today.

Why should a QA engineer care about Vibium?

Vibium matters to testers because it targets the exact pain points that make UI suites fragile and slow to maintain. Most flaky tests come from timing (an element wasn't ready) and brittle locators (a CSS path broke when markup changed). Vibium attacks both directly.

It is built on WebDriver BiDi, the modern W3C browser-automation standard, and ships as a single Go binary that auto-downloads Chrome. There is no chromedriver version to match, no Selenium Grid to babysit for local runs, and no separate browser install step in CI. For a QA engineer, that removes a whole category of "works on my machine" setup drift.

Three properties make it especially suited to test work:

  • Auto-waiting on every action — the engine enforces actionability before clicking or typing, so you rarely write explicit waits.
  • Semantic locators — find elements by role, label, text, or test id, which survive refactors far better than deep CSS or XPath.
  • Playwright-compatible tracing — capture a replayable trace of a failed run and open it in the Playwright trace viewer to debug.

This site is an independent learning hub. Vibium itself is Jason Huggins' open-source project at vibium.com; we just teach it. Start with what is Vibium for the full background.

How is a Vibium test structured?

A Vibium test follows the same four beats as any UI test: arrange (launch and navigate), act (find and interact), assert (verify), and clean up (quit). The difference is how little waiting code you write.

Here is a minimal, runnable test in JavaScript using the synchronous API. It searches DuckDuckGo and verifies the results — no await, no manual sleeps:

const assert = require("node:assert");
const { browser } = require("vibium/sync");
 
const bro = browser.launch({ headless: true });
const page = bro.page();
 
try {
  page.go("https://duckduckgo.com");
 
  // Semantic locator: find the search box by its role + label
  page.find({ role: "combobox" }).type("vibium browser automation");
  page.find({ role: "button", text: "Search" }).click();
 
  // Assert the results actually rendered
  const results = page.find("#links");
  assert.ok(results.isVisible(), "search results should be visible");
  console.log("PASS: results rendered");
} finally {
  bro.close(); // always tear down, even on failure
}

The try/finally guarantees the browser closes even when an assertion throws, which keeps CI agents clean. Each find(...) call auto-waits until the element is actionable before typing or clicking.

The same test in Python's sync API reads almost identically. QA teams on Python get first-class support:

from vibium import browser_sync as browser
 
vibe = browser.launch(headless=True)
try:
    vibe.go("https://duckduckgo.com")
    vibe.find(role="combobox").type("vibium browser automation")
    vibe.find(role="button", text="Search").click()
 
    results = vibe.find("#links")
    assert results.is_visible(), "search results should be visible"
    print("PASS: results rendered")
finally:
    vibe.quit()

New to the API? Walk through your first Vibium script before wiring it into a runner.

Which locators should QA engineers use?

Prefer semantic locators — role, label, text, and test id — because they mirror how a user perceives the page and survive markup changes. Deep CSS and XPath are the classic source of locator rot in Selenium suites; a single wrapper <div> can break dozens of tests.

Vibium exposes one find() method with two signatures: a CSS string for the simple 80% case, and an options object for semantic strategies that you can combine.

// CSS — terse, for stable ids and attributes
page.find("#email");
page.find("input[name='q']");
 
// Semantic — resilient, reads like the UI
page.find({ role: "button", text: "Sign In" });
page.find({ label: "Email" });
page.find({ placeholder: "Search..." });
page.find({ testid: "cart-count" });

Combining strategies is the standout feature. Selecting "the Submit button" (not just any button) is a single call, where Playwright would need a filter chain:

page.find({ role: "button", text: "Submit" }).click();

Use this table to choose a strategy per situation:

SituationBest locatorWhy
Buttons, links, inputsrole + textMatches accessible name, survives restyling
Form fields with a <label>labelTies test to visible label, not DOM position
Elements your team controlstestidExplicit, stable contract for automation
Unique ids / attributesCSS stringTerse and fast when the selector is stable
Deeply nested, no better hookxpathLast resort for awkward DOM traversal

A good rule for a maintainable suite: reach for testid on components you own, role/label/text for everything else, and treat raw XPath as a code smell. See find element and selector best practices for deeper patterns.

How does Vibium stop flaky tests?

Vibium reduces flakiness by running actionability checks inside its Go engine before every interaction, so you almost never write manual waits. Before a click or type, it polls until the target element is visible, stable (not animating), enabled, and able to receive the event — up to a 30-second default timeout.

That means the anti-pattern below, common in older Selenium code, is simply unnecessary:

// You DON'T need this with Vibium:
// sleep(2000);
// while (!element.isVisible()) { sleep(100); }
 
// You just do this — the wait is built in:
page.find({ role: "button", text: "Checkout" }).click();

When you do need to wait on a condition rather than an element's readiness — say, a spinner disappearing or a URL change — use explicit waits instead of sleeps:

// Wait for an element's state to change
page.find(".loading-spinner").waitFor({ state: "hidden" });
 
// Wait for navigation to a new URL
page.waitForURL("**/dashboard");

The contrast with a sleep-based approach is stark:

Flaky patternVibium approach
sleep(3000) before every clickBuilt-in actionability wait, no sleep
Retry loop on stale-element errorsEngine re-resolves the element automatically
Fixed timeout guesses in CICondition-based waitFor / waitForURL
Manual scroll-into-view before clickAuto-scroll into view as part of the click

The result is suites that pass on a fast laptop and a slow CI runner alike, without per-machine tuning. For the full model, read waiting strategies and flake-free tests.

How do I assert results in a Vibium test?

You have two complementary options: standard test-runner assertions against Vibium's state methods, and Vibium's AI-native check() for plain-English verification. Most QA teams use the first for precise checks and reach for the second where a visual or fuzzy assertion is clearer.

Deterministic assertions read element state and feed it to your assertion library:

const assert = require("node:assert");
 
// Text content
assert.strictEqual(page.find("h1").text(), "Dashboard");
 
// Attributes and input values
assert.strictEqual(page.find("#email").value(), "qa@example.com");
assert.ok(page.find(".alert-success").isVisible());
 
// Counting matched elements
assert.strictEqual(page.findAll(".product-card").count(), 12);

Common state methods a tester leans on: text(), value(), attr(name), isVisible(), isEnabled(), isChecked(), and count(). They map cleanly onto whatever runner you already use — no special matchers required.

AI-native check() verifies a claim in natural language and returns a structured result. It is optional and layered on top of the deterministic API — useful for assertions that are painful to express as selectors:

const result = page.check("the cart shows 3 items and a subtotal");
assert.ok(result.passed, result.reason);

Under the hood check() captures a screenshot (optionally augmented with the DOM/accessibility tree) and evaluates the claim, returning { passed, reason, confidence }. Treat it as a complement to el.text(), not a replacement — precise assertions stay deterministic, and check() covers the fuzzy, visual, or "does this look right" cases.

How do I plug Vibium into pytest, Jest, or Mocha?

Vibium is runner-agnostic: launch the browser in a setup hook, expose it to tests, and quit it in teardown. Because it is just a library, there is no plugin to install — it drops into your existing suite.

pytest — a fixture that yields a page and cleans up:

import pytest
from vibium import browser_sync as browser
 
@pytest.fixture
def page():
    bro = browser.launch(headless=True)
    vibe = bro.page()
    yield vibe
    bro.close()
 
def test_homepage_title(page):
    page.go("https://example.com")
    assert page.find("h1").text() == "Example Domain"
 
def test_more_info_link(page):
    page.go("https://example.com")
    page.find(role="link", text="More information...").click()
    assert "iana.org" in page.url()

Jest / Mocha — set up and tear down around each test with the sync client:

const assert = require("node:assert");
const { browser } = require("vibium/sync");
 
let bro, page;
 
beforeEach(() => {
  bro = browser.launch({ headless: true });
  page = bro.page();
});
 
afterEach(() => {
  bro.close();
});
 
test("homepage has the right title", () => {
  page.go("https://example.com");
  assert.strictEqual(page.find("h1").text(), "Example Domain");
});

For isolation between tests without paying the cost of a fresh browser each time, launch once and create a new context per test — each context has its own cookies and storage:

// One browser for the whole file, isolated state per test
const bro = browser.launch({ headless: true });
 
test("logged-out user sees sign-in", () => {
  const ctx = bro.newContext();
  const page = ctx.newPage();
  page.go("https://app.example.com");
  assert.ok(page.find({ text: "Sign in" }).isVisible());
  ctx.close();
});

For structuring a real suite, see structure a test suite and the page object model guide.

How do I organize tests with the Page Object Model?

Wrap each screen in a class that exposes locators and actions, so tests describe intent and the page details live in one place. The Page Object Model (POM) is the single most effective way to keep a growing Vibium suite maintainable.

// pages/login-page.js
class LoginPage {
  constructor(page) {
    this.page = page;
  }
 
  open() {
    this.page.go("https://app.example.com/login");
  }
 
  login(user, pass) {
    this.page.find({ label: "Username" }).type(user);
    this.page.find({ label: "Password" }).type(pass);
    this.page.find({ role: "button", text: "Log in" }).click();
  }
 
  errorMessage() {
    return this.page.find(".error").text();
  }
}
 
module.exports = { LoginPage };

The test then reads like a specification, and a markup change only touches the page class:

const login = new LoginPage(page);
login.open();
login.login("qa@example.com", "wrong-pass");
assert.match(login.errorMessage(), /invalid credentials/i);

For a full walkthrough with a base class and multiple pages, follow the page object model guide. A complete login automation is covered in automate a login with Vibium.

How do I debug a failed test?

Capture a trace during the run and open it in the Playwright trace viewer to step through every action, DOM snapshot, and screenshot. Tracing is Vibium's most powerful QA-debugging feature, and the format is Playwright-compatible, so you use tooling you may already know.

Enable tracing on the context, run your steps, then stop and save a zip:

const { browser } = require("vibium/sync");
 
const bro = browser.launch({ headless: true });
const ctx = bro.newContext();
 
ctx.tracing.start({ screenshots: true, snapshots: true });
const page = ctx.newPage();
 
try {
  page.go("https://app.example.com");
  page.find({ role: "button", text: "Buy now" }).click();
} finally {
  ctx.tracing.stop({ path: "trace.zip" }); // saved even on failure
  bro.close();
}

Open trace.zip with npx playwright show-trace trace.zip to replay the run visually. For quick checks, a plain screenshot on failure is often enough:

const fs = require("node:fs");
fs.writeFileSync("failure.png", page.screenshot({ fullPage: true }));

See take a full-page screenshot and the screenshot command for capture options. A common CI pattern is to save a trace and a screenshot only when a test fails, then upload them as build artifacts.

How do I run Vibium tests in CI?

Run headless, install Vibium in the pipeline, and let it download its own pinned Chrome — no browser provisioning step required. Because the Go engine cleans up its Chrome processes on exit, CI agents don't accumulate zombie browsers between runs.

A minimal GitHub Actions job for a JavaScript suite:

name: e2e
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: "20" }
      - run: npm ci
      - run: npm test               # your Vibium suite, headless
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: traces
          path: "**/trace.zip"      # debug failures after the fact

On Linux runners you may need Chrome's shared libraries (libnss3, libgbm1, and friends) installed once; this is standard for any headless-Chrome tool. For end-to-end pipeline recipes, see CI/CD with GitHub Actions and run on a server.

Vibium vs Selenium and Playwright for QA: what's different?

Vibium sits between Selenium's reach and Playwright's developer experience: it matches much of Playwright's DX (auto-wait, tracing, locators) in one Go binary, and modernizes the Selenium workflow for Chrome. Here is an honest, at-a-glance comparison for a QA lead deciding what to adopt.

FactorVibiumSeleniumPlaywright
BrowsersChrome only (today)All major browsersChromium, Firefox, WebKit
SetupSingle binary, auto-downloads ChromeDriver + browser to managenpm install + browser download
Auto-waitingBuilt in (actionability)Manual waits / WebDriverWaitBuilt in
LocatorsOne find(), semantic + CSSBy id/CSS/XPath, no built-in rolegetByRole and friends
TracingPlaywright-compatible tracesThird-party add-onsFirst-class trace viewer
LanguagesPython, JS/TSJava, C#, Python, Ruby, JSJS/TS, Python, Java, .NET
AI featuresBuilt-in MCP server, check()/do()None built inNone built in
EcosystemNew, smallHuge, 20-yearLarge, mature

When to choose Vibium: new Chrome-only test suites, teams that want Playwright-style ergonomics without the footprint, or projects where AI agents drive the browser via MCP.

When to keep Selenium or Playwright: you need cross-browser coverage today, depend on Java or .NET clients, or rely on a mature third-party ecosystem of reporters and plugins.

Verdict: for a QA engineer starting fresh on Chrome, Vibium is a genuinely pleasant, low-setup choice with the anti-flake and debugging tools you want. For broad browser matrices or non-Python/JS stacks, pair it with — or stick to — an established tool. Dig deeper in Vibium vs Playwright and Vibium vs Selenium.

Migration note and gotchas. Moving a Chrome suite from Selenium is mostly mechanical: swap driver.get for page.go, replace find_element(By.CSS_SELECTOR, ...) with find(...), and delete your explicit-wait boilerplate — the auto-wait covers it. The main gotchas to plan for: Vibium is Chrome-only, so any Firefox/Safari coverage stays on your old tool; there is no official Java or .NET client yet; and the ecosystem of reporters and integrations is young, so you may wire up Allure or JUnit output yourself. None of these are correctness problems — they are coverage trade-offs. See migrate from Selenium to Vibium.

Where do AI features fit into a QA workflow?

The AI features are optional accelerators, not a requirement — your regression suite works fully without them. Vibium ships a built-in MCP server, so an AI agent like Claude Code can drive the browser directly, and two natural-language methods, check() and do(), that sit on top of the deterministic API.

For QA, the pragmatic uses are:

  • Exploratory checkspage.check("the checkout total matches the cart") for assertions that are awkward to encode as selectors.
  • Resilient setup stepspage.do("dismiss the cookie banner") to get past incidental UI without hard-coding a fragile selector.
  • Agent-driven testing — connect the MCP server so an LLM can navigate and verify flows during development.

Crucially, do() plans with AI but executes through Vibium's own find/click/type commands — it is AI planning, not opaque puppeteering, so the actions remain the same ones you would write by hand. Keep your core assertions deterministic and add AI where it genuinely reduces maintenance. Learn more in Vibium MCP with Claude Code.

Next steps

Frequently asked questions

Is Vibium good for QA and test automation?

Yes, for Chrome-based UI testing. Vibium gives QA engineers auto-waiting actions, semantic locators, Playwright-compatible tracing, network mocking, and a built-in MCP server. It installs with one pip or npm command and downloads its own Chrome, so CI stays deterministic. Cross-browser matrices still need another tool today.

How do QA engineers write a test with Vibium?

Launch a browser, open a page, find an element with a semantic locator, act on it (Vibium auto-waits), then assert the result with a normal test-runner assertion or Vibium's check(). Wrap it in pytest, Jest, or Mocha and quit the browser in teardown. A first test is about fifteen lines.

Does Vibium replace Selenium or Playwright for QA teams?

Not entirely. Vibium overlaps heavily with Playwright's DX (auto-wait, tracing, locators) in a single Go binary, and is a natural upgrade from Selenium for Chrome work. But Selenium and Playwright cover more browsers and languages today, so many teams adopt Vibium for new Chrome suites while keeping existing tools for cross-browser.

How does Vibium reduce flaky tests?

Vibium runs actionability checks before every interaction inside its Go engine. It waits until an element is visible, stable, enabled, and able to receive the event before clicking or typing, up to a 30-second default. That removes most manual sleeps and stale-element retries that make Selenium suites flaky.

Can Vibium run in a CI pipeline for QA?

Yes. Run headless, install Vibium with pip or npm, and it downloads a pinned Chrome for Testing so runners do not depend on the system browser. Quit the browser in a finally block or fixture teardown. Vibium cleans up its Chrome processes on exit, which keeps CI agents free of zombie browsers.

Do I need to know AI to use Vibium as a QA engineer?

No. Vibium's core API is a normal, deterministic automation library — find, click, type, assert. The AI features (natural-language check() and do(), and the MCP server) are optional layers on top. You can write an entire regression suite without touching them and add AI checks later where they help.

Vibium is created by Jason Huggins. This is an independent tutorial — see the official Vibium site and GitHub repo for canonical docs.

Related guides