Quick Start
Scaffold Atomic Testing into your project with one command:
- npm
- pnpm
- yarn
npm create atomic-testing@latest
pnpm create atomic-testing
yarn create atomic-testing
Run it from inside an existing project — it scaffolds Atomic Testing into your app, it doesn't create the app. create atomic-testing:
- Detects your framework and major version, test runner, package manager, TypeScript, and design system from
package.json, lockfiles, and config files. - Prompts for anything it couldn't detect confidently — interactively in a terminal, or automatically (non-interactive) in CI.
- Resolves a recipe for your framework × runner × design system, and refuses impossible or unsupported combinations with a clear message.
- Writes a standalone runner config plus an example component,
ScenePart, and a passing test underatomic-testing-example/, and adds atestscript topackage.json. By default it also writes four agent skills under.claude/skills/and rootCLAUDE.md+AGENTS.mdguides adapted to your detected stack (skip all of it with--no-agents). - Installs the dependencies after asking — or, with
--no-install, prints the exact per-package-manager commands so you can run them yourself.
When it finishes you have a green example test in atomic-testing-example/, and the CLI prints the exact command to run it. Combinations outside the verified set (React + Jest, Vue 3 + Jest) are marked experimental — the CLI warns before writing one.
- Prefer to wire it up by hand? → Manual Installation
- Is my stack supported? → Framework and Runner Support
- Just need the runner config? → Configure your test runner
What the CLI writes
For the canonical React + Jest stack, the scaffolder drops:
your-project/
├─ .claude/
│ └─ skills/
│ ├─ scaffold-test-driver/SKILL.md
│ ├─ author-component-tests/SKILL.md
│ ├─ diagnose-test-failure/SKILL.md
│ └─ sync-test-driver/SKILL.md
├─ CLAUDE.md # agent guide adapted to your detected framework, runner, and driver package
├─ AGENTS.md # the same guide for AGENTS.md-reading agents (Codex CLI, …)
├─ jest.config.cjs # standalone runner config (.cjs sidesteps the ESM rename trap)
├─ package.json # gains a "test" script
└─ atomic-testing-example/
├─ ExampleComponent.tsx # a sample component to replace with your own
├─ scenePart.ts # maps a named part to a locator + driver
└─ ExampleComponent.test.tsx # a passing test
The skills and the CLAUDE.md/AGENTS.md guides are on by default; pass --no-agents to skip them. They are the fastest path from here to a real test: open a skills-aware coding agent (Claude Code, Grok Build, Codex CLI, …) and say "Write tests for <YourComponent>" — the skills walk the agent through decomposing the page, reusing shipped drivers, and verifying in your runner. Build Tests with Agent Skills explains each skill, the prompts that trigger it, and which agents read them.
The generated test drives a real state change through the framework-agnostic HTML driver — HTMLTextInputDriver.setValue() then reads it back with getValue() — so on any in-process runner (Jest, Vitest, or Vitest browser mode) it passes on the first run, whatever framework you picked. Those stacks differ only in the details: Vue and Angular emit a .ts component, Vitest emits vitest.config.ts, and the example test filename follows the runner. Playwright is the exception — it drives a real browser against your running app, so the scaffolder emits playwright.config.ts plus a placeholder *.e2e starter test (no example component or scene part) that you point at your dev server; it turns green once your app serves the page, not out of the box.
Write it yourself
Prefer to author the test by hand — or just want to see what a richer one looks like? The example below is the same scene and test logic across React, Vue, and Playwright. It's the code the sections that follow unpack.
- ⚛️ React
- 💚 Vue 3
- 🎭 Playwright
This test needs a jsdom-based runner (Jest or Vitest) with IS_REACT_ACT_ENVIRONMENT set. See Configure your test runner — one-time setup, applies to every example on this page. Installing the packages by hand? See Manual Installation.
import { useState } from 'react';
import { HTMLButtonDriver, HTMLElementDriver } from '@atomic-testing/component-driver-html';
import { byDataTestId, TestEngine } from '@atomic-testing/core';
import { createTestEngine } from '@atomic-testing/react-19';
// Component to test
function WelcomeButton({ name }: { name: string }) {
const [clicked, setClicked] = useState(false);
return (
<div>
<h1 data-testid='greeting'>Hello {name}!</h1>
<button data-testid='welcome-btn' onClick={() => setClicked(true)}>
{clicked ? 'Welcome!' : 'Click me'}
</button>
</div>
);
}
// Test setup
const welcomeScene = {
greeting: { locator: byDataTestId('greeting'), driver: HTMLElementDriver },
button: { locator: byDataTestId('welcome-btn'), driver: HTMLButtonDriver },
};
// Test
describe('WelcomeButton', () => {
let engine: TestEngine<typeof welcomeScene>;
beforeEach(() => {
engine = createTestEngine(<WelcomeButton name='Alice' />, welcomeScene);
});
afterEach(async () => {
await engine.cleanUp();
});
it('should welcome the user when clicked', async () => {
// Check initial state
expect(await engine.parts.greeting.getText()).toBe('Hello Alice!');
expect(await engine.parts.button.getText()).toBe('Click me');
// Interact and verify
await engine.parts.button.click();
expect(await engine.parts.button.getText()).toBe('Welcome!');
});
});
Run it:
npx jest
Vue tests run under a jsdom-based runner (Jest or Vitest). Unlike the React setup, no IS_REACT_ACT_ENVIRONMENT flag is needed — VueInteractor calls Vue's own nextTick() internally. See Configure your test runner for the config, and Manual Installation for the packages.
import { HTMLButtonDriver, HTMLElementDriver } from '@atomic-testing/component-driver-html';
import { byDataTestId, TestEngine } from '@atomic-testing/core';
import { createTestEngine } from '@atomic-testing/vue-3';
import { ref } from 'vue';
// Component to test (SFC-like). createTestEngine() doesn't forward props into
// the rendered component, so — unlike React's <WelcomeButton name="Alice" /> —
// parametrize via a closure instead of a Vue `props` schema.
function makeWelcomeButton(name: string) {
return {
template: `
<div>
<h1 data-testid="greeting">Hello ${name}!</h1>
<button
data-testid="welcome-btn"
@click="handleClick"
>
{{ clicked ? 'Welcome!' : 'Click me' }}
</button>
</div>
`,
setup() {
const clicked = ref(false);
const handleClick = () => (clicked.value = true);
return { clicked, handleClick };
},
};
}
// Test setup (same shape as React!)
const welcomeScene = {
greeting: { locator: byDataTestId('greeting'), driver: HTMLElementDriver },
button: { locator: byDataTestId('welcome-btn'), driver: HTMLButtonDriver },
};
// Test (same logic as React!)
describe('WelcomeButton', () => {
let engine: TestEngine<typeof welcomeScene>;
beforeEach(() => {
engine = createTestEngine(makeWelcomeButton('Alice'), welcomeScene);
});
afterEach(async () => {
await engine.cleanUp();
});
it('should welcome the user when clicked', async () => {
// Same test logic as React version ✨
expect(await engine.parts.greeting.getText()).toBe('Hello Alice!');
await engine.parts.button.click();
expect(await engine.parts.button.getText()).toBe('Welcome!');
});
});
Run it:
npx vitest run
The React and Vue tabs render components directly into an in-memory jsdom document — nothing is served over HTTP. Playwright is different: page.goto('/welcome') drives a real browser against a page your app actually serves. The snippet below is illustrative — it only turns green once you have an app running that serves a /welcome route rendering the equivalent of the WelcomeButton component from the React tab (same data-testids). Point Playwright at that server with webServer/baseURL in playwright.config.ts:
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
baseURL: 'http://localhost:5173',
},
webServer: {
command: 'pnpm dev', // starts your app's dev server
url: 'http://localhost:5173',
reuseExistingServer: !process.env.CI,
},
});
Installing the packages by hand? See Manual Installation.
import { HTMLButtonDriver, HTMLElementDriver } from '@atomic-testing/component-driver-html';
import { byDataTestId, TestEngine } from '@atomic-testing/core';
import { createTestEngine } from '@atomic-testing/playwright';
import { test, expect } from '@playwright/test';
// Same scene definition as React/Vue ✨
const welcomeScene = {
greeting: { locator: byDataTestId('greeting'), driver: HTMLElementDriver },
button: { locator: byDataTestId('welcome-btn'), driver: HTMLButtonDriver },
};
// Assumes a served page at "/welcome" that renders a WelcomeButton with
// name="Alice" (see the caveat above) — this is illustrative, not copy-paste
// runnable against an empty project.
test.describe('WelcomeButton', () => {
let engine: TestEngine<typeof welcomeScene>;
test.beforeEach(async ({ page }) => {
// Navigate to your app (driven by baseURL from playwright.config.ts)
await page.goto('/welcome');
// Create test engine with same scene
engine = createTestEngine(page, welcomeScene);
});
test.afterEach(async () => {
await engine.cleanUp();
});
test('should welcome the user when clicked', async () => {
// Same test logic as React/Vue versions ✨
expect(await engine.parts.greeting.getText()).toBe('Hello Alice!');
await engine.parts.button.click();
expect(await engine.parts.button.getText()).toBe('Welcome!');
});
});
Once your app is served at the configured baseURL, run it:
npx playwright test
✨ The Magic
Notice how the test logic is identical across frameworks:
- Same Scene Definition:
welcomeSceneworks everywhere - Same Test Code:
engine.parts.button.click()works everywhere - Same Component Drivers:
component-driver-html(or a component-library driver package, if you use one) works everywhere
This means: Learn once, test everywhere. Your testing knowledge transfers completely between frameworks.
🎯 What Just Happened?
- ScenePart: Defined which components matter (
greeting,button) - Locators: Found components using
byDataTestId() - Drivers: Used semantic APIs (
click(),getText()) instead of DOM manipulation - TestEngine: Orchestrated everything together
Next Steps
🤖 Let Your Agent Write the Tests
The scaffolder just installed four agent skills — say "write tests for MyComponent" and review the plan.
Meet the Skills →
🏗️ Build Your First Real Test
Follow our step-by-step tutorial with a complete login form example.
Start Tutorial →
📦 Choose Your Packages
Not sure which packages you need? We'll help you decide based on your stack.
Package Guide →
🤔 Questions?
"This seems like extra setup..." → See Why Atomic Testing? for long-term benefits "How do I install without the CLI?" → See Manual Installation "How do I test complex forms?" → Check out the Step-by-Step Tutorial "What about my existing tests?" → They keep working — nothing to migrate. See gradual adoption