Skip to main content

Testing

Testing in @camunda/orchestration-cluster-webapp. For the scripts that run each suite, see the Scripts table.

Stack​

TypeToolLocationScript
UnitVitest browser mode (Playwright)src/**/*.test.ts(x)npm run test:unit
IntegrationPlaywright + MSW (@msw/playwright)test/integration/npm run test:integration
AccessibilityPlaywright + @axe-core/playwrighttest/a11y/npm run test:a11y
Visual regressionPlaywright + containerized browsertest/visual/npm run test:visual

Mocking the backend​

MSW is the only backend mock layer. Two entrypoints depending on the test type:

  • Unit tests: MSW setupWorker (browser mode), auto-started by the custom it fixture from #/vitest-modules/test-extend.
  • Playwright tests: MSW via @msw/playwright, auto-started by the network fixture from #/pw-modules/test-extend.

Both use createEndpointMock from #/shared-test-modules/mock-endpoint to build typed request handlers. All endpoint mocks are individually named exports from #/shared-test-modules/mock-handlers, so every test (unit and Playwright) reuses the same mock definitions. Reusable response factories live under #/shared-test-modules/api-mocks/.

Unit test example:

import {HttpResponse} from 'msw';
import {createCurrentUser} from '#/shared-test-modules/api-mocks/current-user';
import {mockCurrentUserEndpoint} from '#/shared-test-modules/mock-handlers';
import {it} from '#/vitest-modules/test-extend';

it('should render the current user', async ({worker}) => {
worker.use(
mockCurrentUserEndpoint({
successResponse: HttpResponse.json(createCurrentUser()),
}),
);
// render and assert...
});

Playwright test example:

import {HttpResponse} from 'msw';
import {test} from '#/pw-modules/test-extend';
import {createCurrentUser} from '#/shared-test-modules/api-mocks/current-user';
import {mockCurrentUserEndpoint} from '#/shared-test-modules/mock-handlers';

test.beforeEach(({network}) => {
network.use(
mockCurrentUserEndpoint({
successResponse: HttpResponse.json(createCurrentUser()),
}),
);
});

Prefer testing library selectors​

Both Vitest browser mode and Playwright include testing library selectors out of the box. Use getByRole, getByLabelText, and getByText over raw DOM queries like querySelector or getByTestId. They enforce accessible markup and make tests resilient to structural changes.

// good
screen.getByRole("button", { name: /submit/i });
screen.getByLabelText(/username/i);

// avoid
screen.querySelector("button.submit-btn");
screen.getByTestId("submit-button");

Avoid vitest mocks​

Prefer MSW and real implementations over vi.mock and module mocks. Vitest mocks couple tests to implementation details and break on refactors. Reach for them only when there is no practical alternative, such as faking time with vi.useFakeTimers.

Unit vs. integration tests​

Unit tests​

Test a single component, hook, or utility in isolation. Render with vitest-browser-react render(). Use MSW to mock HTTP when the component fetches data.

Do not mock the router. The only exception is when the component uses <Link>, useNavigate, or another router hook that fails without a provider. In that case wrap in a minimal router provider, nothing more.

Integration tests​

Test a feature across UI sections and pages: navigation, data loading, error states, user flows that span multiple components. Run against the built app via Playwright. Always mock the backend with MSW via the network fixture.

Accessibility tests​

Playwright + @axe-core/playwright. Two Playwright projects run every a11y test in both light and dark themes. Use the makeAxeBuilder fixture and assert that violations is empty.

import { test, expect } from "#/pw-modules/test-extend";

test("should have no a11y violations", async ({ makeAxeBuilder, page }) => {
await page.goto("/some-page");
const results = await makeAxeBuilder().analyze();
expect(results.violations).toEqual([]);
});

Visual regression tests​

Playwright toHaveScreenshot. Four projects cover light/dark and desktop/tablet combinations. Set CONTAINERIZED_BROWSER=true to run inside the official mcr.microsoft.com/playwright Docker image for stable cross-machine rendering.

import { test, expect } from "#/pw-modules/test-extend";

test("should match snapshot", async ({ page }) => {
await page.goto("/some-page");
await expect(page).toHaveScreenshot("some-page.png", { fullPage: true });
});