Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 47 additions & 32 deletions packages/eui/.storybook/test-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,17 @@ import { VRT_SELECTORS } from './vrt';
*/
const SCREENSHOT_OPTIONS = { animations: 'disabled' } as const;

const MOBILE_VIEWPORT_WIDTH = 390;
/**
* Variants each story is snapshotted under. Each variant's `name` becomes the
* suffix on the saved baseline (e.g. `${context.id}-desktop.png`) and is what
* stories pass to `parameters.vrt.skipVariants` to opt out of a specific one.
*/
const VARIANTS = [
{ name: 'desktop', viewport: { width: 1440, height: 900 } },
{ name: 'mobile', viewport: { width: 390, height: 844 } },
] as const;

type VariantName = (typeof VARIANTS)[number]['name'];

/**
* Ensures all `<img>` elements are fully loaded before taking a screenshot.
Expand Down Expand Up @@ -59,41 +69,46 @@ const config: TestRunnerConfig = {

if (storyContext.parameters?.vrt?.skip) return;

await waitForPageReady(page);
await waitForImagesToLoad(page);

const selector =
storyContext.parameters?.vrt?.selector ?? VRT_SELECTORS.default;
const viewport = page.viewportSize();
const project =
viewport?.width === MOBILE_VIEWPORT_WIDTH ? 'mobile' : 'desktop';
const image =
selector === 'page'
? await page.screenshot(SCREENSHOT_OPTIONS)
: await page.locator(selector).first().screenshot(SCREENSHOT_OPTIONS);
const skipVariants: VariantName[] =
storyContext.parameters?.vrt?.skipVariants ?? [];

for (const variant of VARIANTS) {
if (skipVariants.includes(variant.name)) continue;

await page.setViewportSize(variant.viewport);
await waitForPageReady(page);
await waitForImagesToLoad(page);

const image =
selector === 'page'
? await page.screenshot(SCREENSHOT_OPTIONS)
: await page.locator(selector).first().screenshot(SCREENSHOT_OPTIONS);

const snapshotId = `${context.id}-${project}`;
const snapshotPath = path.join(
__dirname,
'..',
'.vrt',
'reference',
`${snapshotId}.png`
);
const snapshotId = `${context.id}-${variant.name}`;
const snapshotPath = path.join(
__dirname,
'..',
'.vrt',
'reference',
`${snapshotId}.png`
);

if (!fs.existsSync(snapshotPath)) {
// No baseline exists yet, write it directly so Jest's CI mode doesn't
// block first-run baseline generation.
fs.mkdirSync(path.dirname(snapshotPath), { recursive: true });
fs.writeFileSync(snapshotPath, new Uint8Array(image));
} else {
expect(image).toMatchImageSnapshot({
customSnapshotsDir: path.join(__dirname, '..', '.vrt', 'reference'),
customDiffDir: path.join(__dirname, '..', '.vrt', 'diff'),
customReceivedDir: path.join(__dirname, '..', '.vrt', 'current'),
storeReceivedOnFailure: true,
customSnapshotIdentifier: snapshotId,
});
if (!fs.existsSync(snapshotPath)) {
// No baseline exists yet, write it directly so Jest's CI mode doesn't
// block first-run baseline generation.
fs.mkdirSync(path.dirname(snapshotPath), { recursive: true });
fs.writeFileSync(snapshotPath, new Uint8Array(image));
} else {
expect(image).toMatchImageSnapshot({
customSnapshotsDir: path.join(__dirname, '..', '.vrt', 'reference'),
customDiffDir: path.join(__dirname, '..', '.vrt', 'diff'),
customReceivedDir: path.join(__dirname, '..', '.vrt', 'current'),
storeReceivedOnFailure: true,
customSnapshotIdentifier: snapshotId,
});
}
}
},
};
Expand Down
15 changes: 15 additions & 0 deletions packages/eui/jest-playwright.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module.exports = {
launchOptions: {
args: [
/**
* See https://peter.sh/experiments/chromium-command-line-switches/ for the
* full list of Chromium command-line switches.
*/

// Disable font hinting; rendered glyphs are independent of subpixel layout shifts in the surrounding DOM.
'--font-render-hinting=none',
// Force pixel-aligned glyph positioning instead of subpixel.
'--disable-font-subpixel-positioning',
],
},
};
16 changes: 0 additions & 16 deletions packages/eui/playwright.config.ts

This file was deleted.

42 changes: 41 additions & 1 deletion wiki/contributing-to-eui/testing/visual-regression-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,39 @@ yarn workspace @elastic/eui test-visual-regression update -- --url https://eui.e

Reference images are stored in `packages/eui/.vrt/reference/`.

## Variants

Each story is snapshotted under multiple **variants** to catch e.g. responsive-layout regressions. Every variant produces its own baseline file, suffixed with the variant name:

packages/eui/.vrt/reference/
navigation-euibutton--playground-desktop.png
navigation-euibutton--playground-mobile.png

Current variants (defined in [`.storybook/test-runner.ts`](https://github.com/elastic/eui/tree/main/packages/eui/.storybook/test-runner.ts)):

| Variant | Viewport |
|---|---|
| `desktop` | 1440 × 900 |
| `mobile` | 390 × 844 |

### Skipping specific variants

If a story can't render correctly under a particular variant, opt out of that variant with `parameters.vrt.skipVariants`. The story still runs under the remaining variants.

```tsx
export const Story: Story = {
parameters: {
vrt: {
skipVariants: ['mobile'],
},
},
};
```

Use `vrt.skip` (see below) if a story should be skipped under *all* variants.

When you add `vrt.skipVariants` to a story that previously had a baseline for that variant, manually delete the corresponding snapshot file from `packages/eui/.vrt/reference/`.

## Filtering stories

Pass any [`test-storybook`](https://storybook.js.org/docs/writing-tests/test-runner) flags after `--`:
Expand Down Expand Up @@ -151,7 +184,14 @@ flowchart TD
WS --> N
```

When VRT finds new stories without baselines, or when the team approves visual changes, **CI commits the updated reference screenshots directly to the PR branch**.
When VRT generates or regenerates reference screenshots, **CI commits them directly to the PR branch** under one of two messages:

| Commit message | When it happens |
|---|---|
| `chore(eui): add VRT baseline screenshots` | VRT passes AND the PR adds new stories. The new baselines are committed automatically, no human approval needed. |
| `chore(eui): update VRT baseline screenshots` | VRT fails (visual diffs found) AND a team member approves the *Approve visual changes* block step in Buildkite. The approved baselines are committed. Includes new stories. |

Either commit auto-triggers CI.

### Skipping in a PR

Expand Down