npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

bunwright

v0.3.1

Published

The lightweight browser automation library for Bun

Downloads

298

Readme


import { browser } from "bunwright";

const page = await browser.newPage();

await page
  .navigate("https://example.com/login")
  .type("label:Username", "[email protected]")
  .type("label:Password", process.env.APP_PASSWORD!)
  .click("role:button[name='Login']")
  .waitForURL("**/dashboard")
  .screenshot("./dashboard.png");

await browser.close();

Why Bunwright

Fast, zero-dependency, TypeScript-first, and small enough for AI agents to drive reliably, Bunwright is the lighter alternative to a full end-to-end testing stack like Playwright. Use it for logins, screenshots, data collection, scraping, and other browser tasks that don't need a full test framework.

Bunwright targets exactly that space:

  • Bun-native — built directly on Bun.WebView, zero runtime dependencies, no browser downloads. Uses the Chrome or WebKit already on the machine.
  • Playwright-style API — pages, locators, auto-waiting, semantic selectors. Familiar surface, smaller scope.
  • Chainable — actions queue lazily and run on await, with fail-fast semantics and per-step results.
  • Scriptable — write a TypeScript file, run it with bunx bunwright script.ts or plain bun run. .env loading built in.

There are no fixtures, parallel workers, or trace viewers yet — for full end-to-end test suites, use Playwright.

Use Cases

  • Automate repetitive internal web workflows: logins, form filling, admin panel tasks
  • Capture screenshots of pages or post-login states from scripted sessions
  • Run lightweight browser-driven data collection or verification flows
  • Prototype browser automations in Bun without adopting a larger testing framework
  • Drive browser automation from other Bun tools, scripts, or local CLIs

Installation

bun add bunwright        # as a project dependency
npm install -g bunwright # or globally, for the CLI

Requires Bun and an existing Chrome (or WebKit) installation — bunwright does not download browsers.

Quick Start

Write a script:

// shot.ts
import { browser } from "bunwright";

const page = await browser.newPage();
await page.navigate("https://example.com").screenshot("./example.png");
await browser.close();

Run it:

bunx bunwright shot.ts

The CLI loads .env.local/.env first and runs the script's default export if one exists. The CLI is optional — any bun run script can import bunwright directly.

Selectors

Selectors are prefixed strings; unprefixed strings are treated as CSS.

| Prefix | Example | Matches by | | -------- | --------------------------- | ------------------------- | | css: | css:button[type=submit] | CSS selector | | role: | role:button[name='Login'] | ARIA role + name | | label: | label:Username | Associated <label> text | | text: | text:Sign in | Visible text content | | xpath: | xpath://button[1] | XPath expression |

role: matches explicit [role=...] attributes and implicit roles — role:button finds <button>, input[type=submit], and [role="button"] elements. The optional [name='...'] part matches against aria-label, input value, or trimmed text content.

Every page action auto-waits for the element to be visible and enabled, then retries up to 3 times with exponential backoff within retryTimeout (10s by default).

Chaining

Methods on Page, Locator, and ElementHandle chain without intermediate awaits. A chain is a lazy queue: each call enqueues a step, awaiting executes them sequentially.

await page
  .navigate("https://example.com/login")
  .type("label:Username", "user")
  .click("role:button[name='Login']")
  .waitForURL("**/dashboard");

Fail-fast. If a step throws, every step queued after it is skipped, and the await rejects with the original error:

import { browser, TimeoutError } from "bunwright";

try {
  await page
    .navigate("https://example.com")
    .click("role:button[name='Missing']") // throws TimeoutError
    .waitForURL("**/success"); // never runs
} catch (error) {
  if (error instanceof TimeoutError) {
    // handle, fall back, continue
  }
}

Await result. Awaiting a chain resolves to the final target — the page, or a locator if the chain switched to one. If the last step returns a value (count(), evaluate(), exists()), awaiting resolves to that value:

const count = await page.locator("css:input").count(); // number
const title = await page.evaluate(() => document.title); // string

Per-step results. Call .all() instead of awaiting to get every step's result as an array, in call order:

const [, , title] = await page
  .navigate("https://example.com")
  .click("role:button")
  .evaluate(() => document.title)
  .all();

API Overview

Full reference: docs/api-reference.md (generated from the type declarations).

browser

Singleton entry point.

| Method | Description | | ---------------------- | --------------------------------------------------- | | newPage(options?) | Open a page (creates the WebView lazily) | | newContext(options?) | Create a context (viewport, extra headers, cookies) | | close() | Close all contexts, the WebView, and spawned Chrome |

Page

| Group | Methods | | ----------- | --------------------------------------------------------------------------- | | Navigation | navigate, back, forward, reload, waitForLoadState, waitForURL | | Interaction | click, dblClick, type, press, scroll, scrollTo, resize | | Inspection | evaluate, locator, $, $$, exists, expect, check, screenshot | | Waiting | waitForSelector, waitFor, waitForTimeout | | Low-level | cdp(method, params) — raw Chrome DevTools Protocol calls |

waitForURL accepts a URL glob (** spans /, * stays within a segment, ? is one character; anchored to the full URL) or a RegExp.

Locator

Created with page.locator(selector). Lazy — resolves on each action.

| Group | Methods | | --------- | ------------------------------------------------------------- | | Actions | click, dblClick, type, fill, press, screenshot | | Reading | innerText, innerHTML, getAttribute, evaluate, count | | State | isVisible, isEnabled, isChecked | | Narrowing | filter, first, last, nth, toElement |

Errors

All errors extend BunwrightError: TimeoutError, ElementNotFoundError, SelectorError, BrowserError. Import them to branch on failure type.

Configuration

Via bunwright.config.ts in the project root, or programmatically with defineConfig:

// bunwright.config.ts
import { defineConfig } from "bunwright";

export default defineConfig({
  backend: "chrome", // "webkit" | "chrome" | { type: "chrome", path, argv }
  width: 1440, // default 1280
  height: 900, // default 800
  url: "", // initial URL when the WebView opens
  console: true, // forward page console logs
  dataStore: "ephemeral", // or a directory path for persistent state
  retryTimeout: 10000, // auto-wait/retry budget per action, ms
  headless: true, // default: true on Windows, false elsewhere
});

Resolution order: built-in defaults ← config file ← defineConfig() call.

Environment Variables

The CLI loads .env.local and .env from the working directory before running the script. Existing environment variables are never overridden.

| Variable | Effect | | ----------------- | ----------------------------------------------------------- | | BUN_CHROME_PATH | Path to the Chrome executable (checked first on Windows) | | BUNWRIGHT_DEBUG | 1 logs the spawned Chrome debug port (Windows workaround) |

Windows

Bun.WebView has a known issue spawning its own Chrome subprocess on Windows. Bunwright works around it automatically: it launches Chrome with --remote-debugging-port=<port>, polls http://127.0.0.1:<port>/json/version for the webSocketDebuggerUrl, and connects Bun.WebView to that endpoint.

  • Chrome executable resolution: BUN_CHROME_PATHconfig.backend.path → common install locations.
  • In workaround mode, backend.path and backend.argv are ignored.
  • The spawned Chrome is killed on browser.close() and on process exit.

Examples

Runnable demos live in examples/:

| File | Shows | | ------------------- | ------------------------------------------------------------- | | screenshot.ts | Minimal navigate + screenshot | | login.ts | label:/role: selectors, glob waitForURL, parallel reads | | form-fill.ts | CSS selectors, Locator.count() | | error-handling.ts | Fail-fast chains, TimeoutError handling, soft exists() | | evaluate.ts | evaluate() and raw CDP calls | | multi-context.ts | Two contexts driven in parallel |

bun run src/bunwright.ts examples/login.ts

Development

bun install          # dependencies
bun test             # unit + integration tests (bun:test)
bun run typecheck    # tsc, also covers examples/
bun run lint         # oxlint
bun run format       # oxfmt
bun run build        # bundle dist/ + emit type declarations
bun run docs         # regenerate docs/api-reference.md

To use a local checkout from another Bun project:

bun link             # in this repository
bun link bunwright   # in the consuming project

License

LGPL-2.1 © Jonas Perusquia Morales