Skip to main content

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:

CredentialForm.tsx
<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:

credentialScenePart.ts
const parts = {
form: {
locator: byDataTestId(DataTestId.form),
driver: CredentialFormDriver,
},
} satisfies ScenePart;

Instantiate the Test Engine and write a test

CredentialForm.test.tsx
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:

LoginForm.test.tsx
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.