@telepath-computer/conform
v0.2.1
Published
Checks whether two pages draw the same UI in Chromium.
Readme
conform
@telepath-computer/conform checks whether a candidate page matches the intent of a reference story in Chromium: its appearance and applicable CSS behaviour. It compares box and text geometry, styles, live control state, images, effects, overlap order and relevant configured behaviour. Different markup can pass when those observations agree. Differences name the item and property, such as button { width }: expected 100px, got 112px.
The spec describes the behaviour in full: pages, options, the comparison rules, paths and the message format.
Install
conform runs in Node 24 or later and uses Playwright to control Chromium:
npm install --save-dev @telepath-computer/conform playwright
npx playwright install chromiumUse it in a test library that runs tests in Node, such as Vitest or Playwright Test.
Compare two pages
import { conform } from '@telepath-computer/conform';
await conform('http://localhost:6006/spec.html', {
url: 'http://localhost:3000/window',
root: '#app',
ready: (page) => page.click('.menu-button'),
});conform rejects with a ConformanceError when the pages differ. Its message lists the differences, and differences holds them as data. conform.report resolves with { pass, differences, message } instead of rejecting.
A page is a URL, or an object with the URL and optional root, setup and ready:
rootis a CSS selector of the element to compare. It must match exactly one element. Defaultbody.setupruns before navigation, for scripts, network routes or cookies the page needs at load time.readyruns after load, for actions that reveal the state being compared, such as opening a menu. conform then waits for fonts, image requests and image decoding before capture.
setup and ready receive the Playwright page. The options can provide defaults for these fields and set the viewport (width: 1280, height: 720 by default).
Before capture, conform finishes animations with an end and pauses repeating animations at their start. It also compares relevant computed animation and transition settings, so two pages can differ in behavior even when their settled captures look the same.
The browser
By default, each comparison launches and closes Chromium. For many comparisons, pass a shared Playwright browser and close it after the tests. conform does not close a browser it receives:
import { afterAll, beforeAll, test } from 'vitest';
import { chromium, type Browser } from 'playwright';
import { conform } from '@telepath-computer/conform';
let browser: Browser;
beforeAll(async () => {
browser = await chromium.launch();
});
afterAll(async () => {
await browser.close();
});
test('the window with its menu open matches the spec', async () => {
await conform('http://localhost:6006/spec.html', 'http://localhost:3000/window', { browser });
});Playwright Test can provide the browser instead.
Compare Storybook stories
@telepath-computer/conform/storybook turns a story ID into a page. StorybookSource opens the preview URL for that story and waits for its render and play function to finish:
import { conform } from '@telepath-computer/conform';
import { StorybookSource } from '@telepath-computer/conform/storybook';
const storybook = new StorybookSource({ url: 'http://localhost:6006', timeout: 30_000 });
await conform(storybook.page('spec-window--menu-open'), storybook.page('app-window--menu-open'));page() also accepts a copied Storybook address, such as http://localhost:6006/?path=/story/spec-window--menu-open, or a preview address with iframe.html?id=…. The source uses the story ID from the address and opens it at the configured url.
The Storybook can be running or built and served as static files. The page captures the story after Storybook reports that it finished.
Failures to render or run play, an unknown story ID, and a story that exceeds timeout reject with an Error naming the story. The default timeout is 30 seconds; a finished story is captured immediately.
On a fresh Vite dev server, Storybook may reload the preview while preparing dependencies. The page follows that reload and waits for the story in the new page.
If a story loads content after play ends, make play wait for that content, for example with a Testing Library findBy query.
The preview sets lang="en"; pages compared with a story should use the same language because it can affect text.
spec/storybook.md describes the Storybook pages in full.
Not covered
conform compares boxes and text beyond the viewport. It does not yet compare:
- overlap order beyond the viewport;
- the geometry of SVG-referenced clip paths, which are compared by declaration;
- pseudo-element rectangles, although their content, styles and effects are compared;
- the contents of SVG, canvas, video or iframe elements, beyond their boxes and styles.
See Not covered yet for the full list.
Develop
npm test # runs the tests in Chromium, against built HTML and React fixture Storybooks and Storybook dev servers
npm run test:report # runs the same tests and writes test-report/index.html with screenshots, captured boxes and differences
npm run typecheck # type checks the source and the tests
npm run build # compiles to dist/The test report shows all 19 example cases, with reference and candidate screenshots side by side, outlines around each captured box and text line, and the differences found. Paired items share a number; hover over one to highlight both. A summary table links to each case. The page uses the Television theme and keeps its screenshots inside the HTML file.
