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.

$pnpm add -D @atomic-testing/core @atomic-testing/react-19 @atomic-testing/component-driver-html
Full setup, incl. peer deps →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.

Test once. Ship with confidence.

Get your first portable test running in five minutes.