Skip to main content

Quick Start

Scaffold Atomic Testing into your project with one command:

  • npm
  • pnpm
  • yarn
npm create atomic-testing@latest

Run it from inside an existing project — it scaffolds Atomic Testing into your app, it doesn't create the app. create atomic-testing:

  1. Detects your framework and major version, test runner, package manager, TypeScript, and design system from package.json, lockfiles, and config files.
  2. Prompts for anything it couldn't detect confidently — interactively in a terminal, or automatically (non-interactive) in CI.
  3. Resolves a recipe for your framework × runner × design system, and refuses impossible or unsupported combinations with a clear message.
  4. Writes a standalone runner config plus an example component, ScenePart, and a passing test under atomic-testing-example/, and adds a test script to package.json. By default it also writes four Claude Code skills under .claude/skills/ and a root CLAUDE.md guide adapted to your detected stack (skip both with --no-agents).
  5. 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.

Three quick detours

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 # guide adapted to your detected framework, runner, and driver package
├─ 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 CLAUDE.md are on by default; pass --no-agents to skip 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
Before you run this

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

✨ The Magic

Notice how the test logic is identical across frameworks:

  1. Same Scene Definition: welcomeScene works everywhere
  2. Same Test Code: engine.parts.button.click() works everywhere
  3. 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?

  1. ScenePart: Defined which components matter (greeting, button)
  2. Locators: Found components using byDataTestId()
  3. Drivers: Used semantic APIs (click(), getText()) instead of DOM manipulation
  4. TestEngine: Orchestrated everything together

Next Steps

🏗️ 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?" → We're working on a migration guide!

Morty Proxy This is a proxified and sanitized view of the page, visit original site.