Skip to main content
Pre-1.0Portable UI testing for React · Vue · Playwright

Write your UI tests
once. Run them anywhere.

Stop rewriting your test suite every time you switch UI frameworks or upgrade a component library. Atomic Testing composes tiny, reusable component drivers into portable test scenes — so the same semantic test runs across frameworks, libraries, and environments. Learn once, test any UI.

$npm create atomic-testing@latest
Prefer to wire it up by hand? Manual install →Why Atomic Testing — and when NOT to use it →
byDataTestId()HTMLButtonDriverengine.parts
The magic

One test. Every framework.

Switch the runtime tab — notice the scene definition and the test body never change. Only the import and mount line differ.

welcome.test.ts
import { createTestEngine } from '@atomic-testing/react-19';
import { HTMLButtonDriver, HTMLElementDriver } from '@atomic-testing/component-driver-html';
import { byDataTestId } from '@atomic-testing/core';

// One scene definition — reused on every runtime
const scene = {
greeting: { locator: byDataTestId('greeting'), driver: HTMLElementDriver },
button: { locator: byDataTestId('welcome-btn'), driver: HTMLButtonDriver },
};

it('welcomes the user on click', async () => {
const engine = createTestEngine(<Welcome name="Alice" />, scene);

expect(await engine.parts.greeting.getText()).toBe('Hello Alice!');
await engine.parts.button.click();
expect(await engine.parts.button.getText()).toBe('Welcome!');

await engine.cleanUp();
});
✓ identical scene & assertions— your testing knowledge transfers completely.
Composable by design

Small atoms. Infinite tests.

Each primitive is tiny and replaceable: locators and drivers snap into scenes, scenes into engines, engines into suites that outlive any framework.

Locator
byDataTestId('btn')
Finds the element — by test id, role, attribute or CSS.
Driver
HTMLButtonDriver
Semantic API — .click(), .getText().
Scene → Engine
engine.parts.btn
Orchestrates the whole test, any runtime.
Result
✓ Passing everywhere
React · Vue · Playwright.

Why teams adopt it

Write once, test everywhere

The same driver and assertion code works across React, Vue and Playwright — plus Angular (async setup), Storybook, and plain DOM tests. Learn once, test any UI framework.

High-level semantic APIs

select.selectByLabel('Option 2') instead of brittle DOM queries. Focus on behavior, not implementation.

Framework-agnostic drivers

Reuse component drivers across Material-UI, Radix, shadcn/ui and custom components. Component library changes don’t break your tests.

Future-proof architecture

Framework migrations, library upgrades and environment changes become trivial. Your testing investment scales with your app.

✓ verifiableNot just a claim: this repo's own *.suite.ts test logic runs unmodified under Jest and across Chromium, Firefox & WebKit via Playwright — see package-tests/ in this monorepo.

Is it for you?

Honest tradeoffs

Atomic Testing adds a driver layer on top of Testing Library and Playwright — that's a learning curve and a dependency, not a free lunch. It's a poor fit for a single throwaway component, a prototype you'll never maintain, or a team unwilling to invest in the pattern up front. Each driver call is a thin async wrapper around the same Testing Library/Playwright call you'd write by hand, so it adds a coordination step rather than real work — and lock-in risk is low: every driver bottoms out in those same portable primitives, so dropping the abstraction later means calling them directly, not rewriting your component tree.

$npm create atomic-testing@latest

Test once. Ship with confidence.

Get your first portable test running in five minutes.