Testning
Feste
0.1.260913Apache-2.0One test runner for everything.
Chromium, Firefox, WebKit · iOS, Android, macOS, Windows · Bun or Node
Component tests in a real browser run 2.18x faster than Vitest browser mode and 3.80x faster than Playwright. End-to-end runs 1.22x faster.
What it runs
Component and end-to-end tests are layers of one application’s test suite. Feste runs both, and the audits, stories and device tests alongside them, from one configuration.
Component
Mounted in a real browser, with layout and computed styles.
@feste/test
End to end
Chromium, Firefox and WebKit.
@feste/test
Lighthouse
Audits against the page the tests already opened.
@feste/lighthouse
Storybook
Stories run as tests.
@feste/storybook
iOS
Real devices and simulators.
@feste/mobile
Android
Real devices and emulators.
@feste/mobile
Desktop
Native macOS and Windows apps, and Electron wherever it runs.
@feste/desktop
Real browsers
Tests in a real browser find real browser problems. A simulated DOM like jsdom or happy-dom has no layout engine, so it cannot tell you that a button moved, that text wrapped, or that something is covering the element you clicked.
Feste runs the same tests in Chromium, Firefox and WebKit — the engines your users actually have.
Bun runtime
Workers are native Bun processes, so Bun’s own APIs are available inside a test. Postgres, SQLite, S3, Redis and a shell are part of the runtime, so a test can check what the page actually wrote — no driver, no pool, no teardown hook before the assertion.
Run the suite under Bun
bun feste testUse Bun APIs in a test
import { test, expect } from "@feste/test";
import { sql } from "bun";
test("sending an invoice writes it to the ledger", async ({ page }) => {
await page.goto("/invoices/INV-42");
await page.getByRole("button", { name: "Send" }).click();
await expect(page.getByText("Sent to the customer")).toBeVisible();
// Postgres, no driver and no pool to tear down. The page can only tell you
// what it rendered; this is what was written.
const [invoice] = await sql`
SELECT total, status FROM invoices WHERE number = ${"INV-42"}
`;
expect(invoice).toMatchObject({ total: 2048, status: "sent" });
});Speed
In a real browser, component tests run 3.80x faster than Playwright and 2.18x faster than Vitest browser mode.
Component, jsdom
A simulated DOM, for tests that do not need layout.
| Runner | Time |
|---|---|
| Feste, Bun | 1.42s |
| Feste, Node | 1.68s |
| Vitest, Node | 2.55s |
| Playwright, Node | 2.10s |
| 0–2.55s · lower is better | |
Component, real browser
Real layout, real computed styles, real hit-testing.
| Runner | Time |
|---|---|
| Feste, Bun | 2.21s |
| Feste, Node | 2.27s |
| Vitest, Node | 4.80s |
| Playwright, Node | 8.39s |
| 0–8.39s · lower is better | |
End to end
Chromium, Firefox and WebKit.
| Runner | Time |
|---|---|
| Feste, Bun | 5.62s |
| Feste, Node | 6.29s |
| Playwright, Node | 6.85s |
| 0–6.85s · lower is better | |
The same application, measured at each layer. Each chart is its own scale, in seconds.
60 tests, 10 files, 4 workers, median of 5 runs, each cell in its own container, against a Vite dev server. Linux, 4 CPUs, 8GB. Feste 0.1.260913, Node 26.8.1, Bun 1.4.2, Playwright 1.63.0, Vitest 5.0.0.
Reproduce: npm run bench -- docker
Built in
Feste conforms to the Playwright API, so existing tests and the knowledge behind them carry over.
| Area | Detail |
|---|---|
| Runtime | Bun alongside Node. Workers are native Bun processes, so Bun APIs work inside a test. |
| Mobile | Native iOS and Android, with the same locators and assertions as a browser test. Screenshot baselines are per device. |
| Hybrid apps | WebViews are discovered and switched into mid-test, so one test can cross from native into web and back with the same locators. |
| Device farms | Point a run at a remote device farm instead of the devices on your desk. |
| Desktop | Native macOS and Windows applications, and Electron. |
| Storybook | Your existing stories become tests, and a story’s play function can be replayed inside one. |
| Lighthouse | Audits run against the page the test already opened, with pass thresholds per category. No second browser, and no separate run. |
| Composition | The fixtures compose, so one test can mount a story and hold it to a performance budget. |
| Locators | getById and getByClassName on Page, Locator, Frame and FrameLocator. Substring by default, exact on request. |
| Matchers | toBeWithinRange, toHaveResponseProperty, toMatchJsonSchema. |
| Reporters | catalog, ai, csv, jira, new-relic, perfetto and xray, alongside the standard reporters. |