How to Use Vibium with TypeScript
How to use Vibium with TypeScript: install, configure tsconfig and ESM, get typed Browser/Page/Element autocomplete, and write sync or async automation.
Using Vibium with TypeScript takes three steps: npm install vibium, add a standard tsconfig.json, and import from 'vibium' (async) or 'vibium/sync' (sync). Vibium ships its own type definitions inside the package, so TypeScript resolves the Browser, Context, Page, and Element types automatically — there is no separate @types/vibium to install and no ambient declarations to write. That gives you full autocomplete on every command (vibe.find(...), el.click(), page.check(...)), compile-time checking of argument shapes, and red squiggles the moment you forget an await in async code. The design is deliberate: Vibium's types are formal and self-documenting (browser.newPage() returns a Page), while the example variable names stay playful (const vibe = await bro.newPage()). JS/TS is Vibium's primary client, built directly on WebDriver BiDi, so TypeScript users get first-class support rather than an afterthought binding. This guide walks the full setup, both sync and async, page objects, and CI.
What does the Vibium TypeScript setup pipeline look like?
The end-to-end path from an empty folder to a green CI run is short and linear. Vibium bundles its own .d.ts files, so most of the friction that usually comes with typed browser automation simply is not there.
Each stage maps to a section below: install the package, configure the compiler, write typed code against the shipped definitions, run it fast in development with tsx, then compile with tsc and execute the emitted JavaScript in your pipeline.
Why use Vibium with TypeScript instead of plain JavaScript?
TypeScript turns Vibium's API surface into a guided experience: every method, option, and return type is checked before the browser ever launches. Because the JS/TS client is Vibium's reference implementation on WebDriver BiDi, the type definitions track the real command signatures closely.
The concrete wins for automation code:
- Autocomplete on the whole object model. Typing
vibe.surfacesfind,findAll,go,check,screenshot, and the rest — no doc lookup. - Missing
awaitbecomes a compile error. In the async API, forgettingawaityields aPromise<Element>where anElementis expected, andstrictmode flags it. - Refactors are safe. Rename a page-object method and the compiler finds every call site.
- Selectors and options are shape-checked.
find({ role: 'button', text: 'Submit' })is validated against the option type, so a typo liker?lefails fast. - Self-documenting tests. A function returning
Promise<DashboardPage>tells the next reader exactly what a login helper produces.
If you are brand new to the tool, start with what is Vibium for the mental model, then come back here for the typed workflow.
How do I install Vibium in a TypeScript project?
Install Vibium, add TypeScript and a fast runner, then create a tsconfig.json. Vibium's postinstall downloads a matching Chrome build, so the first install may take a minute.
mkdir vibium-ts && cd vibium-ts
npm init -y
npm install vibium
npm install -D typescript tsx @types/nodevibium— the client plus its bundled type definitions and the Go engine binary.typescript— the compiler (tsc).tsx— runs.tsfiles directly during development, no build step.@types/node— Node globals likeprocessand thefsmodule used to save screenshots.
There is intentionally no @types/vibium line. The types live in the vibium package itself, which is why autocomplete works the instant the install finishes. For a deeper look at what lands on disk, see install Vibium.
What tsconfig.json should I use for Vibium?
A minimal, strict tsconfig.json covers almost every Vibium project. The two decisions that matter are the module system (ESM vs CommonJS) and turning on strict, which is what makes the missing-await safety net work.
{
"compilerOptions": {
"target": "ES2020",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}NodeNext lets TypeScript honor whatever your package.json declares. Add "type": "module" to package.json for ESM import, or leave it off for CommonJS. skipLibCheck keeps builds fast by not re-checking dependency declarations. strict is the single most valuable flag for automation code — leave it on.
How do I write a first Vibium script in TypeScript (sync)?
The sync API reads top to bottom with no await, which makes it the best default for scripts and linear tests. Import it from vibium/sync; the returned handles are fully typed.
import { writeFileSync } from 'node:fs';
import { browser } from 'vibium/sync';
import type { Browser, Page, Element } from 'vibium/sync';
const bro: Browser = browser.launch();
const vibe: Page = bro.page();
vibe.go('https://example.com');
console.log('Title:', vibe.title());
// find() returns a typed Element — autocomplete on .text(), .click(), etc.
const heading: Element = vibe.find('h1');
console.log('Heading:', heading.text());
const png = vibe.screenshot();
writeFileSync('example.png', png);
bro.close();Run it during development with tsx — no compile step:
npx tsx src/hello.tsYou usually will not need to annotate bro, vibe, and heading explicitly — the types are inferred. They are written out here so you can see exactly what each call returns. In real code, const vibe = bro.page() is enough and still fully typed.
How do I use the async Vibium API in TypeScript?
Import from the package root (vibium) to get the async API, and await every command. Reach for async when you drive multiple pages at once or plug Vibium into an async web framework or test runner.
import { browser } from 'vibium';
import type { Browser, Page } from 'vibium';
async function main(): Promise<void> {
const bro: Browser = await browser.launch();
const vibe: Page = await bro.newPage();
await vibe.go('https://example.com/login');
await vibe.find({ label: 'Email' }).fill('jane@acme.com');
await vibe.find({ label: 'Password' }).fill('secret');
await vibe.find({ role: 'button', text: 'Sign in' }).click();
const ok = await vibe.check('the dashboard loaded');
console.log(ok.passed, ok.reason);
await bro.close();
}
main().catch((err) => {
console.error(err);
process.exit(1);
});Here strict mode earns its keep: drop the await before vibe.find(...) and TypeScript complains that a Promise has no method fill, catching the bug before the browser opens. Vibium's find() already auto-waits for the element to be actionable, so you do not sprinkle manual waits — the types and the engine both keep the code clean.
Sync vs async in TypeScript: which should I choose?
Choose sync for readability and async for concurrency. Both are fully typed, expose the same methods, and are equally supported — the only real difference is whether calls return values directly or as promises.
| Aspect | Sync (vibium/sync) | Async (vibium) |
|---|---|---|
| Import | import { browser } from 'vibium/sync' | import { browser } from 'vibium' |
| Style | No await, reads top to bottom | await on every command |
| Best for | Scripts, learning, linear tests | Many pages at once, async frameworks |
| Page handle | bro.page() | await bro.newPage() |
| Concurrency | One flow at a time | Drive several pages in parallel |
| Error handling | try/finally | try/finally or .catch() |
| Typing quality | Full types, inferred | Full types, await errors caught |
A good rule: start sync, move to async only when you actually need to overlap work. Mixing both in one project is fine — they are separate entry points. For a language-agnostic treatment of this trade-off, see Vibium sync vs async.
What types does the Vibium TypeScript API expose?
Vibium's public types mirror its object model, so annotating helpers and page objects is straightforward. The three you touch daily are Browser, Page, and Element.
| Type | Where it comes from | Example |
|---|---|---|
Browser | browser.launch() | const bro: Browser = await browser.launch() |
Context | bro.newContext() | isolated cookies/storage per user |
Page | bro.newPage() / bro.page() | vibe.go(url), vibe.find(...) |
Element | vibe.find('css') | el.click(), el.text(), el.fill(v) |
Element[] | vibe.findAll('css') | iterate matched nodes |
Return types are already precise, so you rarely write any. el.text() returns string, el.isVisible() returns boolean, and vibe.check(...) returns a structured result object.
import type { Page, Element } from 'vibium';
// Typed helper: the signature documents intent and is checked at call sites.
async function firstVisibleText(vibe: Page, selector: string): Promise<string> {
const items: Element[] = await vibe.findAll(selector);
for (const item of items) {
if (await item.isVisible()) {
return item.text(); // string
}
}
return '';
}Because find() accepts either a CSS string or a structured options object, TypeScript validates both forms. find({ role: 'button', text: 'Submit' }) is checked against the semantic-selector option type, so an unknown key or wrong value type is a compile error. See find element for the full selector matrix and screenshot for capture options.
How do I apply the Page Object Model with TypeScript types?
Wrap each screen in a class, type its constructor with Page, and let methods return the next page object. TypeScript then enforces the flow: a login() that returns DashboardPage guarantees callers get the right handle.
import type { Page } from 'vibium';
export class LoginPage {
static readonly URL = 'https://example.com/login';
constructor(private readonly vibe: Page) {}
async open(): Promise<this> {
await this.vibe.go(LoginPage.URL);
return this;
}
async login(user: string, password: string): Promise<DashboardPage> {
await this.vibe.find({ label: 'Email' }).fill(user);
await this.vibe.find({ label: 'Password' }).fill(password);
await this.vibe.find({ role: 'button', text: 'Sign in' }).click();
return new DashboardPage(this.vibe);
}
}
export class DashboardPage {
constructor(private readonly vibe: Page) {}
async welcomeText(): Promise<string> {
return this.vibe.find('[data-testid="welcome"]').text();
}
}The typed return values turn the page flow into something the compiler can verify — you cannot accidentally call a dashboard method on a login page. Private vibe keeps the browser handle encapsulated, and data-testid selectors survive cosmetic UI changes. For the pattern in depth (in Python, but the structure is identical), read the Page Object Model with Vibium. A concrete end-to-end flow lives in automate login with Vibium.
How do I keep tests clean and correctly typed?
Lean on strict mode, annotate boundaries, and always tear the browser down. A few habits keep a TypeScript suite maintainable as it grows.
- Enable
strictso missingawaitandnull/undefinedslips are caught at compile time. - Annotate function boundaries, not every local —
async function checkout(vibe: Page): Promise<void>documents intent; innerconstcan be inferred. - Use semantic selectors (
role,label,testid) over brittle CSS so refactors do not cascade. - Never sleep — Vibium auto-waits for actionability, so avoid
setTimeout-style delays. - Always close the browser, even on failure:
import { browser } from 'vibium';
const bro = await browser.launch();
try {
const vibe = await bro.newPage();
await vibe.go('https://example.com');
// ...assertions...
} finally {
await bro.close(); // runs even if an assertion throws
}Prefer Vibium's typed AI helpers where an intent is easier to state than to select. await vibe.check('prices are sorted low to high') returns { passed, reason, confidence }, and TypeScript autocompletes those fields so your assertions stay honest.
How do I integrate Vibium with a TypeScript test runner?
Vibium is runner-agnostic: it is a browser client, not a test framework, so you drive it from whatever runner your project already uses — Vitest, Jest, or node --test. A small typed fixture that launches once per file and gives each test a fresh page keeps things fast and isolated.
The pattern that scales best is one browser process with a fresh Context per test. A context is an isolated cookie jar and storage, so tests never leak state into one another while still sharing a single Chrome launch.
import { beforeAll, afterAll, beforeEach, afterEach, test, expect } from 'vitest';
import { browser } from 'vibium';
import type { Browser, Context, Page } from 'vibium';
let bro: Browser;
let ctx: Context;
let vibe: Page;
beforeAll(async () => {
bro = await browser.launch({ headless: true });
});
beforeEach(async () => {
ctx = await bro.newContext(); // isolated state per test
vibe = await ctx.newPage();
});
afterEach(async () => {
await ctx.close();
});
afterAll(async () => {
await bro.close();
});
test('the home page shows a heading', async () => {
await vibe.go('https://example.com');
const text: string = await vibe.find('h1').text();
expect(text).toContain('Example');
});
test('login lands on the dashboard', async () => {
const dash = await new LoginPage(vibe).open()
.then((p) => p.login('jane@acme.com', 'secret'));
const result = await vibe.check('the dashboard is visible');
expect(result.passed).toBe(true);
});Typing the fixtures (Browser, Context, Page) means autocomplete works inside every test body, and the compiler flags a vibe used before it is assigned. Because each test gets its own context, you can safely run files in parallel — the shared bro is the only cross-test state. This is the TypeScript equivalent of the isolation pattern many teams already use, with the added benefit that the fixture signatures document exactly what each hook provides.
How do I run Vibium TypeScript in CI?
In CI, compile once with tsc and run the emitted JavaScript, or run tsx directly for simplicity. Compiling first gives you a real type-check gate before anything executes.
Add scripts to package.json:
{
"scripts": {
"typecheck": "tsc --noEmit",
"build": "tsc",
"test": "node dist/run.js",
"test:dev": "tsx src/run.ts"
}
}A minimal GitHub Actions job:
name: vibium
on: [push]
jobs:
browser-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run typecheck
- run: npm run build
- run: npm testnpm run typecheck fails the build on any type error before a browser launches — cheap feedback. npm ci installs Vibium, which fetches its own Chrome, so you do not manage browser binaries yourself. Run headless in CI by passing { headless: true } to browser.launch().
Vibium vs Playwright and Selenium for TypeScript users
Vibium, Playwright, and Selenium are all usable from TypeScript, but they differ in how much typing ceremony and setup they demand. This quick decision table is honest about where each shines.
| Consideration | Vibium | Playwright | Selenium (WebDriver) |
|---|---|---|---|
| Types shipped in package | Yes, no add-on | Yes, first-class | Yes (@types community/bundled) |
| Setup weight | npm install vibium + tsconfig | Install + npx playwright install | Driver + browser management |
| Finding elements | One find(), CSS or semantic | Several getBy* methods | Verbose By locators |
| Auto-wait | Built in | Built in | Manual/explicit waits |
AI-native check()/do() | Yes | No | No |
| Maturity / ecosystem | Newer, fast-moving | Very mature | Most mature, huge install base |
When to choose Vibium: you want minimal setup, one terse typed find(), built-in auto-wait, and optional AI assertions — especially if you also drive the browser from an LLM via Vibium's MCP server. When Playwright fits better: you need its very large mature ecosystem, built-in test runner, and trace viewer today. When Selenium fits: you have an existing Selenium estate or need its unmatched browser and language breadth.
Be fair to the alternatives: Playwright and Selenium are more established, and Vibium is the newer entrant (currently 26.2). Vibium's differentiator is the AI-native layer plus a smaller, terser typed API — not raw ecosystem size. For side-by-side detail, read Vibium vs Playwright and Vibium vs Selenium.
How does Vibium with TypeScript pair with AI agents and MCP?
Typed TypeScript code and Vibium's MCP server are complementary, not competing. You write deterministic, compiler-checked flows for the paths you know, and let an LLM drive the browser through the same engine for exploratory or fuzzy steps.
Vibium ships a built-in MCP server, so an agent in Claude Code or a similar client can call the very same find/click/fill/check commands your TypeScript uses. That means a page.do('close the cookie banner') in a script and an agent-issued action take identical code paths under the hood — AI planning, not AI puppeteering. To wire this up, see Vibium MCP in Claude Code.
The practical pattern: keep stable, high-value journeys as typed page objects for reliability and refactor safety, and use check()/do() (or a full agent) for the parts that change often or are tedious to select. Both live in one codebase and share one browser session.
Common TypeScript setup gotchas
Most issues are configuration, not Vibium. This table maps the symptom to the fix.
| Symptom | Likely cause | Fix |
|---|---|---|
Cannot find module 'vibium' | Package not installed | npm install vibium in the project |
| Import works but no autocomplete | Editor using wrong TS version | Point the IDE at the workspace typescript |
require/import interop errors | ESM/CJS mismatch | Align type in package.json with module in tsconfig |
Missing-await bugs slip through | strict off | Set "strict": true |
.ts file will not run directly | No dev runner | Use npx tsx file.ts or compile with tsc first |
| Browser fails to start in CI | Sandbox/deps | Run headless; npm ci lets Vibium fetch its Chrome |
If a build error looks like it comes from Vibium's own type declarations, "skipLibCheck": true in tsconfig.json usually clears it without hiding errors in your own code.
Next steps
- What is Vibium — the mental model behind the tool.
- Install Vibium — full install and what lands on disk.
- Vibium sync vs async — choose the right flavour.
- Find element — the complete selector reference.
- The Page Object Model with Vibium — structure a growing suite.
- Automate login with Vibium — an end-to-end typed flow.
- Vibium MCP in Claude Code — let an LLM drive the same engine.
- Course and Roadmap — go from basics to production.
Frequently asked questions
Does Vibium support TypeScript out of the box?
Yes. Vibium ships type definitions in the npm package, so TypeScript resolves Browser, Page, and Element types automatically after npm install vibium. You get autocomplete and type checking with no @types add-on and no extra configuration beyond a standard tsconfig.json.
How do I set up Vibium in a TypeScript project?
Run npm install vibium, add a tsconfig.json targeting ES2020 or later, and import from 'vibium' for async or 'vibium/sync' for sync. Run files with tsx or ts-node during development, or compile with tsc and run the emitted JavaScript in CI.
Should I use the sync or async Vibium API in TypeScript?
Use the sync API from 'vibium/sync' for scripts and linear tests that read top to bottom. Use the async API from 'vibium' when you drive several pages concurrently or integrate with an async framework. Both are fully typed and expose the same methods and capabilities.
Do I need ESM or can I use CommonJS with Vibium and TypeScript?
Both work. For CommonJS set module to commonjs and use import syntax that tsc downlevels to require. For ESM set type to module in package.json and module to nodenext in tsconfig. Vibium exposes both entry points, so pick whichever your project already uses.
How do I get typed elements and avoid any in Vibium tests?
Return values are already typed: find() returns Element, findAll() returns Element[], and el.text() returns a string. Annotate helper functions and page-object methods with these types, enable strict mode in tsconfig, and TypeScript will flag missing awaits and wrong argument shapes for you.
Can I use Vibium's AI check() and do() methods from TypeScript?
Yes. page.check('the cart is empty') and page.do('log in as admin') are part of the typed API and return structured results such as { passed, reason, confidence }. TypeScript autocompletes the result fields, so you can assert on them without guessing the shape.
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→