Run Vibium Tests in GitLab CI
Run Vibium tests in GitLab CI: a headless .gitlab-ci.yml pipeline with cached Chrome, JUnit reports, and screenshots or traces as failure artifacts.
To run Vibium tests in GitLab CI, add a .gitlab-ci.yml job that pulls a Python image, installs Chrome's shared libraries, runs pip install vibium, sets HEADLESS=true, and calls pytest — then expose screenshots or a trace as artifacts on failure. Vibium is a single Go binary that auto-downloads Chrome for Testing on first run, so a GitLab runner has no driver to install and no browser version to align — the classic cause of red pipelines in older tools. Because Vibium's find() auto-waits for actionability, the suite that passes on your laptop passes on a slower shared runner without a single sleep(). Wire a JUnit report into artifacts:reports:junit so results appear inside every merge request, cache Chrome so runs stay fast, and upload a failure screenshot or Vibium trace for instant debugging. The result is a browser pipeline that just runs pytest on every push and MR.
New to the tool itself? Start with what is Vibium and installing Vibium, then come back to wire it into your pipeline.
What is the minimal .gitlab-ci.yml for Vibium?
The smallest working pipeline is a single job on a Python image that installs Chrome's libraries, installs Vibium, and runs pytest headless. Drop this at the repository root as .gitlab-ci.yml and GitLab picks it up automatically on the next push.
stages:
- test
vibium-tests:
stage: test
image: python:3.12-slim
variables:
HEADLESS: "true"
before_script:
- apt-get update
- >
apt-get install -y --no-install-recommends
libnss3 libatk-bridge2.0-0 libgbm1 libasound2
libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3
libxrandr2 libdrm2 libpango-1.0-0 libcairo2 ca-certificates
- pip install --no-cache-dir vibium pytest
- vibium install # pre-download Chrome for Testing
script:
- pytest -vThe HEADLESS: "true" variable is read by your launch code (shown below) so Chrome runs without a display. python:3.12-slim is deliberately minimal — the apt-get line adds only the shared libraries Chrome links against, and vibium install fetches Chrome during setup so the first test does not pay the download cost. That is the entire pipeline: no apt install chromium, no driver, no webdriver-manager.
For a Node/TypeScript suite, swap the image and the install commands but keep the same shape:
vibium-tests:
stage: test
image: node:20-slim
variables:
HEADLESS: "true"
before_script:
- apt-get update
- >
apt-get install -y --no-install-recommends
libnss3 libatk-bridge2.0-0 libgbm1 libasound2
libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3
libxrandr2 libdrm2 libpango-1.0-0 libcairo2 ca-certificates
- npm ci
script:
- npm testWhy is there no browser or driver to install in the pipeline?
Vibium bundles its WebDriver BiDi engine into one Go binary and downloads a known-good Chrome for Testing itself, so there is nothing to version-match on the runner. In older stacks, GitLab jobs broke constantly because the browser in the Docker image drifted out of sync with the driver, or a Chrome auto-update on a self-hosted runner silently bumped past the pinned driver. Vibium removes that entire failure class.
The only host setup is Chrome's shared libraries, which you apt-get once, and modern headless Chrome renders with no X server — so you do not need Xvfb or a virtual framebuffer either. This is why a bare python:3.12-slim image is enough where other tools reach for a heavyweight selenium/standalone-chrome image. For the architecture behind this, see how Vibium works.
Here is the difference at a glance against the two most common incumbents:
| Concern | Selenium in GitLab CI | Playwright in GitLab CI | Vibium in GitLab CI |
|---|---|---|---|
| Base image | Often selenium/standalone-chrome or Chrome + chromedriver | mcr.microsoft.com/playwright (bundled browsers) | Any slim Python/Node image |
| Browser install | Install Chrome and matching driver | npx playwright install --with-deps | Auto-downloaded on first run |
| Driver version match | Manual, a frequent break point | Managed by Playwright | No separate driver exists |
| Virtual display | Xvfb historically needed | Not needed (headless) | Not needed (headless) |
| Headless toggle | Chrome option flag | Config / --headed | launch(headless=...) flag |
| Waiting model | Manual explicit waits common | Auto-waiting | Auto-waiting via find() |
None of this makes Vibium strictly "better" than Playwright — Playwright's image is fully managed and battle-tested across browsers. Vibium's edge in CI is specifically the lightness: no dedicated image, no driver concept, Chrome fetched and cached on demand. Compare the tools directly in Vibium vs Playwright and Vibium vs Selenium.
How does my test know to run headless on the runner?
Read headless mode from an environment variable in your launch code, so the identical suite runs headed on your laptop and headless in GitLab. This one switch is what the HEADLESS: "true" pipeline variable flips.
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")
print(vibe.find("h1").text())
vibe.quit()The JavaScript equivalent reads the same variable, so a mixed team keeps one mental model:
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')
console.log(page.find('h1').text())
bro.close()With this pattern you debug headed on your machine, push, and the exact same code runs invisibly on the runner — there is no separate CI code path to drift out of sync. Centralize the flag in a fixture or helper (see structuring a Vibium suite) so every test inherits it instead of each file re-reading the environment.
How do I show Vibium results inside a merge request?
Write a JUnit XML report from pytest and reference it under artifacts:reports:junit, and GitLab renders pass/fail counts right in the merge request widget. This turns a wall of log output into a scannable summary reviewers actually read.
vibium-tests:
stage: test
image: python:3.12-slim
variables:
HEADLESS: "true"
before_script:
- apt-get update
- >
apt-get install -y --no-install-recommends
libnss3 libatk-bridge2.0-0 libgbm1 libasound2
libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3
libxrandr2 libdrm2 libpango-1.0-0 libcairo2 ca-certificates
- pip install --no-cache-dir vibium pytest
- vibium install
script:
- pytest -v --junitxml=report.xml
artifacts:
when: always
reports:
junit: report.xml
paths:
- report.xml
expire_in: 1 weekThe when: always keyword matters: without it, GitLab discards artifacts when the job fails — which is exactly when you most need the report. After a run, open the pipeline's Tests tab or the MR widget to see each test, its duration, and the failure message. For a Node suite, produce the same XML with a JUnit reporter (for example Jest's jest-junit) and point reports:junit at its output file.
How do I capture screenshots and traces on failure?
Because a GitLab runner is headless and remote, a failure screenshot is the fastest way to see what the page actually showed. Save a PNG only when a test fails, and expose it as a job artifact with when: on_failure. Vibium's screenshot() returns PNG bytes, so writing it to disk is a single line.
Capture inside a pytest fixture that inspects the test outcome:
import pytest
from vibium import browser_sync as browser
@pytest.fixture
def vibe(request):
instance = browser.launch(headless=True)
yield instance
if getattr(request.node, "rep_call", None) and request.node.rep_call.failed:
png = instance.screenshot(full_page=True)
with open(f"artifacts/fail-{request.node.name}.png", "wb") as f:
f.write(png)
instance.quit()The rep_call attribute comes from the standard pytest_runtest_makereport hook in your conftest.py, which records each test's result on the node so the fixture can read it during teardown. See taking a full-page screenshot for the capture options, and finding elements for the locator that failed.
Then collect the folder in the job so the images survive the run:
script:
- mkdir -p artifacts
- pytest -v --junitxml=report.xml
artifacts:
when: always
reports:
junit: report.xml
paths:
- report.xml
- artifacts/
expire_in: 1 weekFor richer debugging, record a Vibium trace with screenshots and DOM snapshots, write the trace.zip into artifacts/, and open it in the Vibium Trace viewer to scrub the timeline frame by frame. A trace answers "what happened three steps before the failure?" in a way a single screenshot cannot.
How do I cache Chrome and pip to speed up the pipeline?
Vibium downloads Chrome for Testing once and reuses it, so caching that download — plus the pip wheel cache — trims meaningful time off every run. Add a cache: block keyed on a stable value and the vibium install step becomes a near-instant cache hit instead of a fresh download on each pipeline.
vibium-tests:
stage: test
image: python:3.12-slim
variables:
HEADLESS: "true"
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
cache:
key: vibium-chrome
paths:
- .cache/pip
- .cache/vibium
before_script:
- export XDG_CACHE_HOME="$CI_PROJECT_DIR/.cache"
- apt-get update
- >
apt-get install -y --no-install-recommends
libnss3 libatk-bridge2.0-0 libgbm1 libasound2
libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3
libxrandr2 libdrm2 libpango-1.0-0 libcairo2 ca-certificates
- pip install vibium pytest
- vibium install
script:
- pytest -v --junitxml=report.xml
artifacts:
when: always
reports:
junit: report.xml
expire_in: 1 weekPointing XDG_CACHE_HOME inside the project directory puts Vibium's Chrome download under a path GitLab is allowed to cache, since GitLab only caches files within the build workspace. The apt-get layer still runs each time on a Docker executor because the container is ephemeral — if that install becomes your bottleneck, build a small custom base image with the libraries baked in and push it to your GitLab Container Registry. This matters most on large monorepos where the pipeline fires on every commit.
How do I run Vibium tests in parallel across GitLab jobs?
Split a slow suite across parallel jobs so total pipeline time stays low even as the test count climbs. GitLab's parallel keyword launches N copies of a job, and you shard the suite by reading the built-in CI_NODE_INDEX and CI_NODE_TOTAL variables.
vibium-tests:
stage: test
image: python:3.12-slim
parallel: 4
variables:
HEADLESS: "true"
before_script:
- apt-get update
- >
apt-get install -y --no-install-recommends
libnss3 libatk-bridge2.0-0 libgbm1 libasound2
libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3
libxrandr2 libdrm2 libpango-1.0-0 libcairo2 ca-certificates
- pip install vibium pytest pytest-split
- vibium install
script:
- pytest -v --splits "$CI_NODE_TOTAL" --group "$CI_NODE_INDEX" --junitxml=report.xml
artifacts:
when: always
reports:
junit: report.xml
expire_in: 1 weekHere pytest-split divides tests into balanced groups and each parallel job runs exactly one group; GitLab merges the four JUnit reports into one Tests view automatically. Because each Vibium browser is an isolated process, parallel jobs do not contend for shared state — the model is well suited to sharding. For the design principles behind writing tests that survive parallelization, see parallelizing Vibium tests and writing flake-free tests.
What are the common GitLab CI gotchas with Vibium?
Most Vibium pipeline failures trace back to a handful of environment issues, not the tests themselves. This table maps the symptom you see in the job log to the fix.
| Symptom in the job log | Likely cause | Fix |
|---|---|---|
error while loading shared libraries: libnss3.so | Chrome's system libraries missing from the image | Add the full apt-get library list from the minimal job |
| Browser launch hangs, then times out | First-run Chrome download blocked by network/proxy | Run vibium install in before_script; set the runner's proxy env vars |
| Artifacts empty on a failed job | Artifacts default to upload only on success | Add when: always (or on_failure) to the artifacts block |
| Cache never restores Chrome | Vibium cache path lives outside the workspace | Set XDG_CACHE_HOME inside $CI_PROJECT_DIR |
| Passes locally, fails only in CI | Real bug surfaced by clean state, not flakiness | Reproduce with HEADLESS=true locally; read the failure screenshot |
Permission denied on Chrome sandbox | Container runs without required namespaces | Prefer headless; if needed, launch Chrome with --no-sandbox in a trusted CI context |
A note on --no-sandbox: it is a common CI workaround but weakens Chrome's process isolation, so use it only inside a trusted pipeline, never when loading untrusted pages. Most GitLab SaaS runners tolerate headless Chrome without it. If a test that passes locally fails only in CI, resist blaming flakiness first — a headless runner starts from clean state and often exposes a genuine ordering or timing bug that your warm local browser was hiding. Vibium's auto-waiting removes most timing flakiness, so a CI-only failure usually means a real assertion problem worth reading the screenshot for.
How do I run Vibium on a scheduled nightly pipeline?
Run a heavier end-to-end Vibium suite on a schedule instead of on every push, so fast checks stay fast and long browser flows run overnight. GitLab pipeline schedules let you trigger a pipeline on a cron cadence, and you gate the slow job on the built-in CI_PIPELINE_SOURCE variable so it only fires from the schedule.
nightly-e2e:
stage: test
image: python:3.12-slim
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
variables:
HEADLESS: "true"
before_script:
- apt-get update
- >
apt-get install -y --no-install-recommends
libnss3 libatk-bridge2.0-0 libgbm1 libasound2
libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3
libxrandr2 libdrm2 libpango-1.0-0 libcairo2 ca-certificates
- pip install --no-cache-dir vibium pytest
- vibium install
script:
- pytest -v -m e2e --junitxml=report.xml
artifacts:
when: always
reports:
junit: report.xml
expire_in: 1 weekCreate the schedule under Build → Pipeline schedules in the GitLab project, set the cron interval, and pick the target branch. The rules block keeps this job out of normal merge-request pipelines, while your quick smoke tests (marked with a different pytest marker) run on every push. This split is the standard way to keep a broad browser regression suite from slowing down day-to-day development, and because Vibium browsers are cheap to launch and tear down, a nightly run of hundreds of flows stays practical. Pair it with the failure-screenshot fixture above so a broken overnight test still hands you an image to read in the morning.
Can I run Vibium on a self-hosted or shell GitLab runner?
Yes — Vibium runs on any GitLab runner executor, including a shell runner on your own hardware, and its self-managed Chrome download is an advantage there. On a Docker executor the container is ephemeral, so Chrome's libraries must be installed each job (or baked into a custom image); on a shell or self-hosted runner you install those libraries on the host once and every job reuses them.
vibium-tests:
stage: test
tags:
- self-hosted # route to your own runner
variables:
HEADLESS: "true"
before_script:
- python3 -m venv .venv
- source .venv/bin/activate
- pip install vibium pytest
- vibium install
script:
- source .venv/bin/activate
- pytest -v --junitxml=report.xml
artifacts:
when: always
reports:
junit: report.xml
expire_in: 1 weekThe tags keyword routes the job to a runner registered with that tag. On the host, install Chrome's shared libraries once with the same apt-get list, and Vibium caches its Chrome download under the runner user's home directory so subsequent jobs skip the fetch entirely — no per-job download and no cache configuration needed. This makes a self-hosted runner meaningfully faster for Vibium than for tools that re-pull a browser image each run. The trade-off is the usual one: a persistent host can accumulate state between jobs, so keep tests isolated (fresh browser.launch() per test, no reliance on leftover files) exactly as you would for parallel runs — the same discipline covered in structuring a Vibium suite.
Why is Vibium a clean fit for GitLab CI?
Three design choices make Vibium pipelines low-maintenance on GitLab. First, the single Go binary plus auto-downloaded Chrome means zero driver management and no heavyweight browser image — a python:3.12-slim container is enough. Second, headless is a launch flag rather than a separate code path, so the suite you debug locally is byte-for-byte the suite that runs on the runner. Third, auto-waiting find() keeps tests stable on slower shared runners without manual sleeps.
Together they remove the three things that most often break browser jobs — driver drift, headless quirks, and timing flakiness — leaving you a pipeline that runs pytest, reports into every merge request, and hands you a screenshot when something breaks. Wire the Vibium MCP into Claude Code and you can even ask an agent to draft the next test, then let this pipeline verify it. If you want a guided path from zero to a green pipeline, the Master Vibium course and the 45-day roadmap cover it step by step.
Next steps
Frequently asked questions
How do I run Vibium tests in GitLab CI?
Add a .gitlab-ci.yml job that uses a Python or Node image, installs Chrome's shared libraries, runs pip install vibium, sets HEADLESS=true, and calls pytest. Vibium auto-downloads Chrome for Testing on first run, so the runner needs no separate browser or driver install.
Do I need a Docker image with Chrome for Vibium in GitLab CI?
No. Vibium ships as a single Go binary that downloads its own Chrome for Testing, so a plain python:3.12-slim image works. You only apt-get the shared libraries Chrome links against, such as libnss3 and libgbm1. There is no driver version to match against the browser.
How do I show Vibium test results in a GitLab merge request?
Have pytest write a JUnit XML report with --junitxml=report.xml, then reference that file under artifacts:reports:junit in your job. GitLab parses it and shows passed and failed tests directly in the merge request widget, plus a full test report tab in the pipeline.
How do I debug a failing Vibium test on a GitLab runner?
Capture a screenshot when a test fails and expose it as a job artifact with when: on_failure, or record a Vibium trace and upload the zip. Because runs are headless and remote, these artifacts are the fastest way to see exactly what the page looked like at the moment of failure.
How do I speed up Vibium pipelines in GitLab CI?
Cache both the pip download directory and Vibium's Chrome for Testing folder with the cache: key, so vibium install becomes a near-instant cache hit. Then split slow suites across parallel jobs. This keeps total pipeline time low even as your test count grows.
Can I run Vibium in GitLab CI without Docker, on a shell runner?
Yes. On a shell or self-hosted runner, install Chrome's system libraries once on the host, then pip install vibium in the job. Vibium manages its own Chrome download and caches it under the runner's home directory, so subsequent jobs reuse it without re-downloading.
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→