Step-by-Step Tutorial
This tutorial walks you through the example-mui-signup-form found in the examples folder. You may run and see it live in Codesandbox. It demonstrates how to run the example application and introduces the basic Atomic Testing APIs for writing tests. Component drivers work the same in isolated unit tests or full browser tests so the code you see here applies to both environments.
1. Install dependencies
From the repository root run:
pnpm install
The example project has its own dependencies so install them as well:
cd examples/example-mui-signup-form
pnpm install
2. Start the example application
Run the app locally to see the signup form in action:
pnpm dev
Open http://localhost:5371 in your browser to interact with the multi‑step form.
3. Run the component tests
Unit tests for each form step live under their respective __tests__
directories. Execute them with:
pnpm test:dom
4. Run the end‑to‑end test
The example also includes an end‑to‑end scenario located in e2e/success.spec.ts
. Launch it in Chrome with:
pnpm test:e2e:chrome
Add the --ui
flag to open Playwright in UI mode if you want to watch the interactions.
5. Explore Storybook (optional)
Some components contain Storybook stories with interaction tests. To view them, run:
pnpm storybook
6. Write your first Atomic test
The signup form example includes unit tests written with Atomic Testing. The steps below outline the main pieces required to create a test.
Declare data-testid
Assign the data-testid
attribute on components that you need to interact with. This example shows the markup for the credential form:
<form data-testid={DataTestId.form}>
<TextField data-testid={DataTestId.emailInput} label='Email' />
<TextField data-testid={DataTestId.passwordInput} label='Password' />
<WizardButton data-testid={DataTestId.navigation} onNextStep={onNextStep} />
</form>
Define a ScenePart
Create a ScenePart describing how to locate each component and which driver to use:
const parts = {
form: {
locator: byDataTestId(DataTestId.form),
driver: CredentialFormDriver,
},
} satisfies ScenePart;
Instantiate the Test Engine and write a test
let testEngine: TestEngine<typeof parts>;
let onNext: jest.Mock;
beforeEach(() => {
onNext = jest.fn();
testEngine = createTestEngineForComponent(
<CredentialForm data-testid={DataTestId.form} onNextStep={onNext} />,
parts
);
});
afterEach(async () => {
await testEngine.cleanUp();
});
test('submits valid data', async () => {
await testEngine.parts.form.setValue({
email: 'john@example.com',
password: 'secret123',
confirmPassword: 'secret123',
birthday: '1990-01-01',
});
await testEngine.parts.form.next();
expect(onNext).toHaveBeenCalled();
});
The test engine renders the component, exposes the parts defined in the ScenePart
, and provides helper methods from the driver to interact with the component. Always call cleanUp()
after each test to unmount the component and release resources.
7. Build a form driver
For larger forms it is convenient to create a driver that wraps the individual inputs. Below is a simplified driver for a login form.
import { TextFieldDriver, ButtonDriver } from '@atomic-testing/component-driver-mui-v5';
import {
ComponentDriver,
Interactor,
IComponentDriverOption,
IInputDriver,
PartLocator,
ScenePart,
byDataTestId,
} from '@atomic-testing/core';
const parts = {
username: { locator: byDataTestId('username'), driver: TextFieldDriver },
password: { locator: byDataTestId('password'), driver: TextFieldDriver },
submit: { locator: byDataTestId('submit'), driver: ButtonDriver },
} satisfies ScenePart;
export interface LoginCredential {
username: string;
password: string;
}
export class LoginFormDriver extends ComponentDriver<typeof parts> implements IInputDriver<LoginCredential> {
constructor(locator: PartLocator, interactor: Interactor, option?: Partial<IComponentDriverOption>) {
super(locator, interactor, { ...option, parts });
}
async setValue(value: LoginCredential): Promise<boolean> {
await this.parts.username.setValue(value.username);
await this.parts.password.setValue(value.password);
return true;
}
async login(value: LoginCredential): Promise<void> {
await this.setValue(value);
await this.parts.submit.click();
}
get driverName(): string {
return 'LoginFormDriver';
}
}
Use it in a test:
const parts = {
form: { locator: byDataTestId('login-form'), driver: LoginFormDriver },
} satisfies ScenePart;
const engine = createTestEngineForComponent(<LoginForm />, parts);
await engine.parts.form.login({ username: 'admin', password: 'secret' });
With the login()
helper tests stay declarative and don't depend on the form's markup. If the implementation changes, update only LoginFormDriver
.
8. Write an end-to-end test
Drivers work the same way in browser-based tests. The snippet below shows a Playwright test that reuses the drivers from the unit tests.
import { createTestEngine } from '@atomic-testing/playwright';
import { expect, test } from '@playwright/test';
import { getGoodCredentialMock } from './__mocks__/signup';
import { parts } from './signupScenePart';
// Shortened end-to-end test using Playwright
test('user can sign up', async ({ page }) => {
await page.goto('/');
const engine = createTestEngine(page, parts);
await engine.parts.credentialStep.setValue(getGoodCredentialMock());
await engine.parts.credentialStep.next();
await expect(await engine.parts.shippingStep.isVisible()).toBe(true);
});
Because the driver encapsulates how to fill in the form, the test logic is short and can run in any browser supported by Playwright.
Next steps
Browse the example source code to see how scene parts, drivers and the test engine are set up. Then refer back to the Setup and Core Concepts pages for more details on creating your own tests.