A 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.
A complete Vibium CI/CD pipeline is five stages wired to your version control: install vibium, lint and type-check, run the suite headless in parallel shards, capture screenshots and traces as artifacts on failure, and gate the merge on the test exit code. Because Vibium ships as a single Go binary that auto-downloads Chrome for Testing, the pipeline has no driver step, no Selenium Grid, and no browser Docker image to maintain — the three things that most often break browser jobs. Headless is a launch flag read from an environment variable, so the suite you debug on your laptop is byte-for-byte the suite that runs on the runner. Auto-waiting find() keeps tests stable on slower shared hardware without sleep() calls. This guide builds the whole pipeline end to end — stages, quality gates, parallel sharding, artifact debugging, Docker, and a merge-blocking check — in a way that works on GitHub Actions, GitLab CI, CircleCI, or Jenkins with only the YAML syntax changing.
What are the stages of a Vibium CI/CD pipeline?
A Vibium pipeline runs the same five stages regardless of which CI provider executes them. Each stage has one job, and a failure in any stage stops the merge.
The stages map directly to the sections below. Install fetches vibium and Chrome. Lint + type-check catches shape errors before a browser ever launches. Test runs the suite headless across parallel shards. Capture artifacts saves screenshots and traces when something fails. Gate merge turns a red suite into a blocked pull request. Everything after install is standard CI plumbing — Vibium's job is to make the browser step boring.
Why is Vibium a clean fit for CI/CD?
Vibium removes the three failure modes that make browser pipelines fragile: driver drift, headless quirks, and timing flakiness. It does this by design, not by configuration.
- Zero driver management. The single Go binary bundles the WebDriver BiDi engine and downloads a known-good Chrome for Testing itself. There is nothing to version-match, so the classic "driver out of sync with browser" break cannot happen. See how Vibium works for the architecture.
- Headless is a flag, not a code path. You flip
headlessfrom an environment variable, so there is no separate CI build of your tests to drift out of sync with local. - Auto-waiting is built in.
find()waits for actionability — visible, stable, enabled — before it acts, so tests stay green on slower runners without manual sleeps. See flake-free tests.
That combination is why a Vibium job is often just pip install vibium && pytest. If you are new to the tool, start with what is Vibium for the mental model, then come back for the pipeline.
How do I install Vibium on a CI runner?
Install Vibium with the package manager for your language and let it fetch Chrome — the runner needs only Chrome's shared system libraries, not a browser or driver install. On Linux runners those libraries are a single apt-get line.
For Python:
pip install vibium pytest
vibium install # pre-download Chrome for Testing so the first test is fastFor JavaScript or TypeScript:
npm ci # installs vibium; the postinstall step downloads ChromeOn a Debian or Ubuntu runner, add Chrome's shared libraries once:
sudo apt-get update
sudo apt-get install -y \
libnss3 libatk-bridge2.0-0 libgbm1 libasound2 \
libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 \
libxrandr2 libdrm2 libpango-1.0-0 libcairo2Running vibium install (or npx vibium install) as its own step pre-fetches Chrome during setup, so the first test does not pay the download cost mid-run. For a full walkthrough of installation across platforms, see install Vibium.
How does my code know to run headless in CI?
Read headless mode from an environment variable so the identical suite runs headed locally and headless on the runner. This one switch is the whole difference between your laptop and CI.
import os
from vibium import browser_sync as browser
headless = os.getenv("HEADLESS", "false") == "true"
vibe = browser.launch(headless=headless)
vibe.go("https://example.com")
assert vibe.find("h1").text() == "Example Domain"
vibe.quit()The JavaScript sync client uses the same pattern with vibium/sync:
const { browser } = require('vibium/sync')
const headless = process.env.HEADLESS === 'true'
const bro = browser.launch({ headless })
const page = bro.page()
page.go('https://example.com')
if (page.find('h1').text() !== 'Example Domain') {
throw new Error('unexpected heading')
}
bro.close()The CI job sets HEADLESS=true; you leave it unset locally and watch the browser drive itself. Centralize this read in a fixture or a launch helper (see structuring a Vibium suite) so every test agrees on the mode.
Which CI providers does this pipeline work on?
The same five stages port to any provider — only the YAML dialect changes. Vibium adds no provider-specific requirement because it has no external services to wire up.
| Provider | Install step | Parallelism primitive | Artifact upload |
|---|---|---|---|
| GitHub Actions | pip install vibium in a run step | strategy.matrix jobs | actions/upload-artifact@v4 |
| GitLab CI | pip install vibium in script | parallel: N | artifacts.paths |
| CircleCI | pip install vibium in a step | parallelism: N | store_artifacts |
| Jenkins | sh 'pip install vibium' | parallel stages | archiveArtifacts |
For a complete, copy-paste GitHub Actions workflow — including caching and artifact steps — see running Vibium in CI/CD with GitHub Actions. This article stays provider-agnostic and focuses on the pipeline design so the concepts transfer wherever you run.
How do I add lint and type-check quality gates?
Run static checks before the browser stage so cheap, fast failures happen first. A type error should never wait for Chrome to launch to surface. Put linting and type-checking in their own stage that gates the test stage.
For a TypeScript suite, the gate is your compiler:
npx tsc --noEmit # type-check, emit nothing
npx eslint . --max-warnings 0Because Vibium ships its own .d.ts files, tsc checks your find() selectors, option shapes, and missing awaits against the real command signatures — no @types/vibium needed. See using Vibium with TypeScript for the setup.
For a Python suite, gate on a linter and an optional type-checker:
ruff check .
mypy tests/ # optional, if you annotateOrdering matters: if lint or type-check fails, the pipeline stops before spending runner minutes on browsers. This is the cheapest quality gate you own, and it catches a real class of bugs — wrong argument shapes, typo'd selectors, forgotten awaits — that would otherwise fail deep in a browser run.
How do I run Vibium tests in parallel in CI?
Parallelize on two axes: shard across CI jobs with a build matrix, then split further inside each job with your test runner. Every test must own an isolated browser context so parallel workers never share cookies or storage.
Inside a single job, let the runner fan out. With pytest:
pytest -n auto # pytest-xdist: one worker per CPU coreWith Jest, workers are on by default; cap them for a shared runner:
npx jest --maxWorkers=50%Across jobs, split the suite by shard index. A four-way matrix runs a quarter of the tests per job, cutting wall-clock time to roughly a quarter:
# Job N of 4 runs its slice
pytest --splits 4 --group ${SHARD_INDEX}The rule that makes parallelism safe: one context per test. Vibium's browser.newContext() gives each test its own cookie jar and storage, so concurrent workers cannot corrupt each other's state.
from vibium import browser_sync as browser
bro = browser.launch(headless=True)
def test_checkout():
ctx = bro.new_context() # isolated from every other test
vibe = ctx.new_page()
vibe.go("https://store.example.com")
assert vibe.find(".cart-count").text() == "0"
ctx.close() # clean up this test's stateFor the full parallelization playbook — sharding math, worker tuning, and isolation patterns — see parallelizing Vibium tests.
How do I capture screenshots and traces on failure?
Since CI runs are headless and remote, an on-failure screenshot and trace are the fastest way to see what the page actually showed. Save both when a test fails and let the pipeline upload them as artifacts.
Vibium's screenshot() returns PNG bytes, so writing one on failure is a few lines in a pytest fixture:
import pytest
from vibium import browser_sync as browser
@pytest.fixture
def vibe(request):
instance = browser.launch(headless=True)
yield instance
if request.node.rep_call.failed:
png = instance.screenshot(full_page=True)
with open(f"fail-{request.node.name}.png", "wb") as f:
f.write(png)
instance.quit()For richer debugging, record a Vibium trace around the run. The trace captures periodic screenshots and DOM snapshots you can scrub frame by frame:
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()
page.go('https://example.com')
// ... your assertions ...
ctx.tracing.stop({ path: 'trace.zip' })
bro.close()Then archive the artifacts so they survive the run. On GitHub Actions:
- name: Upload failure artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: vibium-failures
path: |
fail-*.png
trace.zipOpen trace.zip at trace.vibium.dev to replay the session. See taking a screenshot for full-page and clip options.
How do I speed up the pipeline with caching?
Cache the two slow parts — dependencies and Vibium's downloaded Chrome — so most runs skip both. Vibium downloads Chrome for Testing once and reuses it, so caching that directory turns the vibium install step into a near-instant cache hit.
| What to cache | Keyed on | Payoff |
|---|---|---|
| pip / npm packages | lockfile hash | Skips dependency resolution and download |
| Vibium's Chrome build | Vibium version | Skips the browser download entirely |
| Test split timings | branch | Balances shards so no job straggles |
On GitHub Actions, cache Vibium's browser directory with actions/cache keyed on your Vibium version; on GitLab use cache:key; on CircleCI use save_cache/restore_cache. Combined with parallel sharding, caching keeps total pipeline time low even as the suite grows into the hundreds of tests. This matters most on large monorepos where the workflow fires on every commit.
How do I run the pipeline in Docker?
Run Vibium in a plain Python or Node base image plus Chrome's shared libraries — you do not need a special browser image or Selenium Grid container. This keeps your CI image small and identical to local Docker runs.
FROM python:3.12-slim
RUN apt-get update && apt-get install -y \
libnss3 libatk-bridge2.0-0 libgbm1 libasound2 \
libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 \
libxrandr2 libdrm2 libpango-1.0-0 libcairo2 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt && vibium install
COPY . .
ENV HEADLESS=true
CMD ["pytest", "-n", "auto"]The single-binary design is what makes this so short: there is no driver to download, no Grid to stand up, and no browser image to pin. Modern headless Chrome renders with no X display, so you do not need Xvfb either. For deeper Docker guidance — layer caching, non-root users, and image size — see installing Vibium in Docker.
How do I gate a merge on the Vibium suite?
Make the pipeline block the merge by requiring the test job to pass on the target branch. The mechanism is your test runner's exit code plus a branch protection rule.
The exit code is automatic: pytest and jest return non-zero when any test fails, and CI marks the job red. The gate is a repository setting:
- GitHub: mark the Vibium job a required status check in branch protection so pull requests cannot merge while it is red.
- GitLab: enable "Pipelines must succeed" in merge request settings.
- Bitbucket / others: require the build to pass in merge checks.
You can also gate on Vibium's AI check() method for high-value flows. It returns a structured result, so you assert on passed:
result = vibe.check("the order confirmation page shows a tracking number")
assert result["passed"], result["reason"]Treat AI checks as a complement to deterministic assertions, not a replacement — keep them on stable, load-bearing journeys where a screenshot-plus-reasoning verdict adds signal. For the deterministic backbone of your gates, keep using el.text(), el.isVisible(), and the rest of the API described in finding elements. A worked login gate lives in automating login with Vibium.
How do I generate CI tests faster with Vibium MCP?
Use Vibium's built-in MCP server to have an AI agent drive a real browser and write the test, then commit the generated deterministic script into the pipeline. The binary ships an MCP server, so an assistant like Claude Code can explore a flow and emit find()/click()/check() code.
The workflow is: describe the journey to the agent, let it drive Chrome via MCP and produce a Vibium script, review the emitted selectors, and add the file to your suite so it runs on every push like any other test. Because page.do() and the MCP tools plan actions and then execute them through Vibium's own deterministic API, the code you commit is the same find() and click() a human would have written — it is AI planning, not AI puppeteering at run time.
This shortens the slowest part of growing a suite — authoring new tests — while keeping CI fully deterministic. See Vibium MCP in Claude Code to set it up.
What does a production-ready Vibium pipeline check off?
A mature pipeline nails install, isolation, artifacts, and gating without any special browser infrastructure. Use this as a readiness checklist.
| Concern | Do this | Why it matters |
|---|---|---|
| Browser install | pip install vibium + vibium install | No driver drift; reproducible browser |
| Headless | Read from HEADLESS env var | Local and CI run identical code |
| Speed | Matrix shards + -n auto + cache | Flat pipeline time as suite grows |
| Isolation | One new_context() per test | Parallel workers never share state |
| Stability | Auto-waiting find(), no sleep() | Green on slow runners |
| Debuggability | Screenshot + trace artifacts on failure | See what a headless run rendered |
| Merge safety | Required status check on the test job | Red suite blocks the merge |
Hit every row and you have a pipeline whose browser stage is as boring and reliable as its unit tests — which is exactly the goal.
Next steps
Frequently asked questions
What does a complete Vibium CI/CD pipeline look like?
A complete Vibium pipeline has five stages: install (pip install vibium), lint and type-check, run tests headless in parallel shards, capture screenshots and traces as artifacts on failure, and gate the merge on the exit code. Vibium auto-downloads Chrome, so there is no driver step.
Do I need Selenium Grid or a Docker browser image for Vibium in CI?
No. Vibium ships as a single Go binary that downloads its own Chrome for Testing on first run. You do not need Selenium Grid, a standalone driver, or a browser Docker image. On a Linux runner you install Chrome's shared system libraries and pip install vibium, and the same binary runs everywhere.
How do I run Vibium tests in parallel in CI?
Shard by CI job using a build matrix, then split further inside each job with your test runner: pytest -n auto or Jest workers. Give every test its own isolated browser context so parallel workers never share cookies or storage. This keeps total pipeline time flat as the suite grows.
How do I keep a Vibium CI pipeline from going flaky?
Rely on Vibium's auto-waiting find() instead of sleep() calls, run headless from an environment variable so local and CI code match, isolate state per test with a fresh context, and pin your Vibium version. These four choices remove driver drift, timing, and shared-state flakiness.
How do I debug a Vibium test that only fails in CI?
Capture a full-page screenshot and a Vibium trace on failure, then upload both as pipeline artifacts. The trace records screenshots and DOM snapshots you can scrub frame by frame at trace.vibium.dev, which is the fastest way to see what a headless remote run actually rendered.
Can I use Vibium's AI check() method as a CI quality gate?
Yes. page.check('the dashboard loaded') returns a structured result with passed, reason, and confidence, so you can assert on passed in a test that fails the build. Treat AI checks as a complement to deterministic assertions, not a replacement, and keep them on stable, high-value flows.
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 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→Best PracticesRun Vibium Tests on Kubernetes
Run Vibium tests on Kubernetes: build a headless image, ship it as a Job, scale parallel Pods, and pull screenshots and traces as artifacts.
15 min read→