Vibium Performance Optimization
Vibium performance optimization: run headless, reuse contexts, parallelize, trust auto-wait, and cut network overhead to make browser automation fast.
To optimize Vibium performance, run headless, reuse a single browser with a fresh context per test instead of relaunching, parallelize across workers, lean on auto-wait rather than sleep(), and block network requests you do not need. Vibium is already fast by design — it is a single Go binary that talks to Chrome over WebDriver BiDi, and its actionability engine runs server-side so timing decisions never round-trip to your script. That means most slowness in a Vibium suite is not Vibium itself but how it is used: relaunching a browser for every test, waiting on fixed timers, downloading megabytes of images you never assert on, or running everything serially on a multi-core machine. Each of those has a direct fix. The techniques below are ordered by impact — parallelism and context reuse move the needle most, headless and network trimming add steady gains, and auto-wait discipline removes the hidden delays that fixed sleeps quietly bake in. Applied together, they routinely cut wall-clock time by more than half while your tests check exactly the same behavior.
What actually makes a Vibium run slow?
Vibium performance is dominated by four costs, and knowing which one you are paying tells you which fix to reach for. The costs are: browser startup, page load and network, action timing, and serial execution. Vibium's architecture already minimizes two of them — the Go engine keeps action timing tight, and auto-downloaded Chrome for Testing avoids driver mismatch — so your effort belongs on the other two.
Here is how each cost maps to a fix and roughly how much it is worth.
| Cost | Symptom | Fix | Typical impact |
|---|---|---|---|
| Browser startup | Every test pauses ~0.5s before doing anything | Reuse one browser, new context per test | Medium–High |
| Serial execution | Suite time grows linearly with test count | Parallelize across workers | High |
| Network / page load | Heavy sites take seconds to become ready | Block images/fonts/analytics, wait on real signals | Medium |
| Action timing | Tests pause on fixed sleep() calls | Replace with auto-wait | Medium |
| Rendering (headed) | Extra CPU on a visible window | Run headless | Low–Medium |
Start at the top-impact rows. A suite that relaunches Chrome for all 200 tests and runs them one at a time is leaving the two biggest wins — reuse and parallelism — on the table entirely.
Should I run Vibium headless for performance?
Yes — headless is the correct default for any run without a human watching, and it is modestly faster. Headless Chrome skips the window compositing and paint work tied to a visible display, so it uses less CPU per browser and starts a touch quicker, which compounds when you run many browsers at once.
Gate it on an environment variable so the same code runs headed on your laptop and headless in CI.
const { browser } = require('vibium/sync');
// Headed locally to watch; headless in CI for speed.
const headless = process.env.HEADLESS === 'true';
const bro = browser.launch({ headless });
const page = bro.page();
page.go('https://example.com');
console.log(page.find('h1').text());
bro.close();import os
from vibium import browser_sync as browser
vibe = browser.launch(headless=os.getenv("HEADLESS", "true") == "true")
vibe.go("https://example.com")
print(vibe.find("h1").text())
vibe.quit()Headless is a real but bounded win — treat it as free hygiene, not your main lever. The parallelism and reuse below matter far more. See how to run Vibium on a server for the headless CI setup end to end.
How do I reuse a browser to avoid relaunch overhead?
Launch one browser and create a fresh context per test, rather than launching a new browser each time. A context is an isolated cookie jar and storage that costs milliseconds to create, while a browser launch costs hundreds of milliseconds — so reuse turns a per-test cost into a one-time cost while keeping every test fully isolated.
const { browser } = require('vibium/sync');
// Launch ONCE for the whole suite.
const bro = browser.launch({ headless: true });
function runTest(name, fn) {
const ctx = bro.newContext(); // fresh cookies + storage, cheap
const page = ctx.page();
try {
fn(page);
} finally {
ctx.close(); // tear down, keep the browser alive
}
}
runTest('dashboard loads', (page) => {
page.go('https://app.example.com/dashboard');
console.log(page.find('.widget').count(), 'widgets');
});
runTest('settings load', (page) => {
page.go('https://app.example.com/settings');
console.log(page.find('h1').text());
});
bro.close(); // close the browser once at the very endThe try/finally guarantees each context is closed even when a test throws, so Chrome processes never leak. Because each context starts with empty cookies and storage, a logged-in session from one test never bleeds into the next — you get isolation without paying the launch tax every time. This is the single cheapest speedup for most suites, and it composes directly with the test-suite structure and page object model patterns.
How do I parallelize Vibium for the biggest speedup?
Give each worker its own isolated browser or context and run workers concurrently — this is the highest-impact optimization because browser work is I/O-bound. While one test waits on a network response or a render, another worker's test can be doing real work, so throughput scales close to linearly until you hit a memory ceiling.
In Python, the standard path is pytest-xdist:
pip install pytest-xdist
pytest -n auto # one worker per CPU core
pytest -n 8 # or pin an explicit worker countWith a function-scoped browser or context fixture, xdist forks worker processes and each test runs in exactly one worker, so isolation is automatic:
# conftest.py
import os
import pytest
from vibium import browser_sync as browser
@pytest.fixture(scope="session")
def shared_browser():
instance = browser.launch(headless=os.getenv("HEADLESS", "true") == "true")
yield instance
instance.quit()
@pytest.fixture
def vibe(shared_browser):
ctx = shared_browser.new_context() # isolated per test
page = ctx.new_page()
yield page
ctx.close()Each worker gets its own shared_browser (fixtures are per-worker), and within a worker each test gets a clean context — parallelism across workers, isolation within them, at low per-test overhead. The same one-browser-per-task rule applies when you parallelize tests or run parallel scraping. The one hard limit is memory: each headless Chrome is a real process using a few hundred megabytes, so divide available RAM by roughly 400 MB and leave headroom rather than over-subscribing.
Does auto-wait make Vibium faster or slower?
Auto-wait makes a well-written suite faster, because it proceeds the instant an element is actionable instead of pausing for a guessed duration. Vibium runs its actionability checks — visible, stable, receives events, enabled — server-side in the Go engine and acts the moment they pass, so it never adds the slack that a fixed sleep() bakes into every single run.
The performance mistake is a fixed timer: too short and the test races the page and flakes, too long and every run pays the full delay even when the page was ready in 200 ms.
from vibium import browser_sync as browser
vibe = browser.launch(headless=True)
vibe.go("https://example.com/app")
# SLOW + FLAKY: always pays 3s, and still races a slow page.
# import time; time.sleep(3)
# vibe.find(".result").click()
# FAST + RELIABLE: proceeds the instant the element is actionable.
vibe.find(role="button", text="Load data").click()
print(vibe.find(".result-row").text())
vibe.quit()Deleting sleep() calls in favor of auto-wait is one of the rare changes that makes a suite both faster and more reliable at once. When the signal is not a single element — a spinner detaching, an API call finishing — reach for a targeted explicit wait like wait_until("detached") or wait_for_response(pattern); each still proceeds on the real condition. The full playbook is in waiting strategies.
Can trimming network traffic speed up Vibium?
Yes — for scraping and functional tests that do not assert on visuals, routing images, fonts, and analytics to abort() removes bytes the page would otherwise download and decode, which meaningfully shortens load time on media-heavy sites. Vibium exposes request interception over WebDriver BiDi, so you can block by resource type or URL pattern before the request ever leaves the browser.
const { browser } = require('vibium/sync');
const bro = browser.launch({ headless: true });
const page = bro.page();
// Drop images, fonts, media, and trackers; let everything else through.
page.route('**/*', (route) => {
const type = route.request().resourceType();
const blocked = ['image', 'font', 'media', 'stylesheet'];
if (blocked.includes(type)) {
route.abort();
} else {
route.continue();
}
});
page.go('https://example.com/heavy-page');
console.log(page.find('h1').text()); // structure is ready without the media
bro.close();Blocking is a scalpel, not a hammer. Turn it off for visual regression testing, where images and CSS are exactly what you are checking, and keep it on for text scraping where they are pure overhead. Blocking third-party analytics and ad domains also removes slow, unpredictable requests that would otherwise pad every page load. For the related read pattern, see monitoring network requests.
A related trap is waiting on the wrong load signal. go() returns when the load event fires, which is usually what you want, but on single-page apps the meaningful content often arrives after that via a later fetch. Waiting on the real signal — the element you need, or wait_for_response() for the API call that populates it — is both more reliable and faster than a blanket "wait for network idle," because it proceeds the moment your specific data lands instead of blocking until every last background request settles.
How do I make Vibium fast inside Docker and CI containers?
Container performance hinges on three things: enough shared memory, the right Chrome flags, and caching the browser download between runs. Chrome uses /dev/shm for shared memory, and the default 64 MB in many container runtimes is far too small — an undersized /dev/shm causes renderer crashes and slow, retry-heavy runs that look like Vibium being flaky when the real cause is the container.
Give the container adequate shared memory and let Chrome fall back gracefully:
# Docker: raise shared memory so the renderer does not thrash.
docker run --shm-size=1g my-vibium-image
# Or, in docker-compose.yml
# services:
# tests:
# shm_size: '1gb'import os
from vibium import browser_sync as browser
# In constrained containers, disabling /dev/shm usage avoids crashes
# when you cannot raise --shm-size on the host.
extra = ["--disable-dev-shm-usage"] if os.getenv("CI") else []
vibe = browser.launch(headless=True, args=extra)
vibe.go("https://example.com")
print(vibe.find("h1").text())
vibe.quit()The second container win is caching. Vibium auto-downloads Chrome for Testing on first run, and re-downloading it on every CI job is wasted minutes of network time. Persist Vibium's browser cache directory between runs — most CI systems let you declare a cache keyed on the Vibium version — so the download happens once and every subsequent job reuses it. Pair that with the per-worker parallelism above and a cached browser, and a containerized pipeline stays quick even as the suite grows. The full server and container setup, including the system libraries Chrome links against, is in how to run Vibium on a server.
How do I measure Vibium performance before optimizing?
Measure before you optimize, because the slow part is rarely where you assume it is. The cheapest instrument is timing the phases of a single run — launch, navigation, and the actions — so you know whether to spend effort on reuse, network trimming, or something else entirely. Optimizing blind wastes time hardening a step that was already fast.
const { browser } = require('vibium/sync');
const t0 = Date.now();
const bro = browser.launch({ headless: true });
const page = bro.page();
const tLaunch = Date.now();
page.go('https://example.com/app');
const tNav = Date.now();
page.find({ role: 'button', text: 'Load data' }).click();
page.find('.result-row').text();
const tActions = Date.now();
bro.close();
console.log(`launch: ${tLaunch - t0}ms`);
console.log(`nav: ${tNav - tLaunch}ms`);
console.log(`actions: ${tActions - tNav}ms`);Run that against a representative page and the numbers tell you where the time goes. If launch dominates, context reuse is your lever; if navigation dominates, network trimming or direct URLs help; if actions dominate, look for hidden sleep() calls or an over-broad selector the engine must disambiguate. For suite-level timing, most test runners report per-test durations — sort them and attack the slowest handful first, since a few outliers usually account for most of the total.
Vibium's tracing support goes further, recording events and periodic screenshots you can inspect after the fact to see exactly where a run stalled. Treat optimization as a loop: measure, change one thing, measure again. That discipline stops you from stacking changes that individually help but collectively obscure which one actually mattered.
What other habits keep a Vibium suite fast?
Beyond the big four, a handful of small habits stop performance from quietly regressing as a suite grows. None is dramatic on its own, but together they keep runs tight.
- Navigate straight to deep URLs. If a test needs the settings page,
go()there directly instead of clicking through the nav from the home page. Every avoided page load is time saved. - Assert on the element you need, not the whole page. Reading one element via
find()is cheaper and more precise than dumpingcontent()and searching the HTML yourself. - Reuse login state instead of logging in each test. Save cookies or storage state once and load it, rather than driving the full login form in every test. See scraping behind a login.
- Scope selectors tightly. A specific CSS or semantic
find()resolves faster and is less ambiguous than a broad one the engine must disambiguate. - Prefer
find()overevaluate()for element work. Reserveevaluate()for genuine page-state checks; the first-class element API is clearer and lets the engine optimize timing.
Here is how the main levers stack up so you can prioritize by effort versus payoff.
| Optimization | Effort | Payoff | Use when |
|---|---|---|---|
| Parallelize workers | Low | High | Suite has more than a handful of tests |
| Reuse browser + context | Low | High | You currently relaunch per test |
| Run headless | Trivial | Low–Medium | Any unattended / CI run |
| Trim network requests | Medium | Medium | Media-heavy pages, no visual asserts |
| Replace sleep() with auto-wait | Low | Medium | Anywhere a fixed timer exists |
| Direct navigation + tight selectors | Low | Low | Always — cheap hygiene |
Work top-down: parallelism and reuse first, then headless and network trimming, then the hygiene items. That order gives you most of the speedup for the least change, and it scales as the suite grows from dozens of tests to hundreds.
How do I keep Vibium's AI methods from slowing tests down?
Vibium's AI-native check() and do() methods are the one place where a single call can cost seconds, because each one takes a screenshot and consults a multimodal model rather than reading the DOM directly. That is a fair trade when you want to assert intent in plain English, but treating an AI call like a free find() will inflate a suite's runtime fast. The rule is to use the deterministic API for the hot path and reserve AI methods for checks that genuinely need judgment.
Concretely, a value you can read from an element should be read from the element:
// SLOW: an AI round-trip to check something the DOM already states.
// page.check('the cart shows 0 items');
// FAST: read the value directly — no model call, milliseconds not seconds.
const count = page.find('[data-testid="cart-count"]').text();
if (count !== '0') throw new Error(`expected empty cart, saw ${count}`);Save check('the layout looks broken on mobile') or do('dismiss whatever cookie banner appears') for cases where no stable selector captures the intent — a visual judgment, or an action against a UI that varies run to run. Because these methods build on Vibium's own deterministic find/click/fill under the hood, you can also start with AI during exploration and then harden the frequently-run assertions into plain selectors once you know the page. That keeps the expressive AI layer available without letting it dominate the wall-clock time of a suite you run hundreds of times a day.
Why is Vibium fast in the first place?
Vibium's baseline speed comes from its architecture, which is worth understanding because it explains why the optimizations above work. It ships as one Go binary that drives Chrome over WebDriver BiDi — a bidirectional protocol, so events and responses stream over a live connection instead of the request-response polling older WebDriver used. The actionability engine lives in Go, so decisions about whether an element is ready happen next to the browser, not across a slow bridge to your script.
That design means your Vibium code is a thin client: it serializes a command, the engine does the heavy lifting, and a result comes back. There is no driver process to version-match and no polling loop burning cycles in your language runtime. The performance work you do — parallelizing, reusing contexts, trimming network — is therefore about feeding that fast engine efficiently, not fighting overhead it imposes. For the full picture, read how Vibium works and, if you are benchmarking against other tools, is Vibium faster than Playwright.
Next steps
Frequently asked questions
How do I make Vibium tests run faster?
The biggest wins are running headless, reusing one browser with a fresh context per test instead of relaunching, parallelizing across workers, and blocking heavy network requests you do not need. Combined, these routinely cut wall-clock time by more than half without changing what your tests actually check.
Is headless Vibium faster than headed?
Yes, modestly. Headless Chrome skips window compositing and paint work tied to a visible display, so it uses less CPU and starts slightly faster. The larger performance gains come from parallelism and reusing contexts, but headless is the correct default for CI and any unattended run.
Does Vibium's auto-wait slow down my tests?
No. Auto-wait proceeds the instant an element is actionable, so it never adds fixed delay the way a sleep() does. It only 'waits' as long as the page genuinely takes to become ready, which means removing sleep() calls in favor of auto-wait usually makes a suite faster, not slower.
How much can parallelizing Vibium speed up a suite?
Because browser work is dominated by network and rendering I/O, parallel runs scale close to linearly until you hit a resource ceiling. A suite that takes ten minutes serially can finish in two to three minutes across eight workers, provided each worker has its own isolated browser or context.
What is the fastest way to reuse a browser in Vibium?
Launch one browser per worker and create a new context per test with new_context(). A context is a fresh cookie jar and storage that costs milliseconds to create, whereas launching a browser costs hundreds of milliseconds. You keep full isolation while skipping the repeated launch.
Does blocking images make Vibium faster?
Often, yes, for scraping and functional tests that do not assert on visuals. Routing image, font, and analytics requests to abort() removes bytes the page would otherwise download and decode, which shortens load time on media-heavy sites. Keep them enabled for visual regression tests.
Vibium is created by Jason Huggins. This is an independent tutorial — see the official Vibium site and GitHub repo for canonical docs.
Related guides
Vibium Best Practices: The Complete Guide
Vibium best practices for reliable browser automation: semantic locators, actionability waits, page objects, isolation, CI, and AI checks.
13 min read→Best PracticesA Complete Vibium CI/CD Pipeline
Build a complete Vibium CI/CD pipeline: install, headless run, parallel shards, artifact capture, and quality gates that block bad merges on every push.
12 min read→Best PracticesData-Driven Testing with Vibium
Data-driven testing with Vibium: feed one browser test many rows from arrays, CSV, or JSON, loop over cases, and keep the automation logic in one place.
15 min read→Best PracticesRun Vibium with Docker Compose
Run Vibium with Docker Compose: orchestrate a headless test service, an app-under-test, mounted artifact volumes, healthchecks, and parallel workers.
15 min read→