Verdict
No
Do not write automated tests for CSS in a typical web app; lint stylesheets with Stylelint, scope styles to components, and look at each changed screen at a phone width and a desktop width before merging.
Why
- Blast radiususers
- Change frequencyregularly
- Detectabilitysame-day
- Reversibilitytrivial
- Test costheavy
Do not write automated tests for CSS in a typical web app; lint the stylesheets and look at each changed screen instead. Blast radius is users, because a broken layout reaches customers, and Change frequency is regularly, because a page's styles change about once a month. Detectability is same-day, because at the widths most visitors use, users report a button pushed off screen within a day, and Reversibility is trivial, because a reverted stylesheet leaves nothing behind. Test cost is heavy: a useful CSS test needs a real browser and screenshot baselines that differ between machines and change with every intentional restyle. Rule R13 gives Do not test. The case is borderline: when the result compares exactly and Test cost falls to moderate, the decision becomes Test minimally.
| When | Decision | Why |
|---|---|---|
| The stylesheet styles a checkout page, where a sticky element can cover the Pay button on a phone | Test mandatory: an end-to-end test at a phone width that scrolls to the Pay button and clicks it | Blast radius rises to money and Reversibility to costly: orders lost while the button is covered do not come back |
| A cookie consent banner must show its Reject button next to Accept on every screen size | Test mandatory: an end-to-end test at two widths that asserts both banner buttons are in the viewport | Blast radius rises to safety-or-legal: a banner with its Reject button off screen collects consent that EU privacy rules reject |
| A design-system stylesheet styles components on dozens of screens, and each component renders alone with fixed data in a pinned browser image | Test minimally: one screenshot per component variant, reviewed in each pull request | Detectability moves to eventually and Test cost falls to moderate: a component change reaches dozens of screens, a small spacing or colour shift on a rarely visited screen draws no complaint, and fixed data renders the same pixels on every run |
| The CSS decides whether a control is visible, such as a menu or a form step, and a Playwright end-to-end suite already covers the flow | Test minimally: add a visibility assertion for the control to the existing end-to-end test | Test cost falls to moderate: visibility is a yes-or-no result, and the browser setup already exists |
| The layout breaks only in a browser or at a width nobody on the team opens, such as Safari on iPhone, and no end-to-end suite runs there | Test it differently: track completed signups per browser and per screen width, and alert when one falls behind the others | Detectability moves to eventually: nobody opens that browser or width before merging, and complaints rarely name either, which hides the cause for weeks |
What breaks if you don't test
A rule leaks through the cascade. A developer raises the z-index of the sticky header to fix a dropdown, and on phones the header now covers the first field of the settings form, a page users open daily. A user reports it the next morning, and a revert fixes it in minutes.
What you lose if you over-test
A full-page screenshot suite fails on every intentional change, such as a new brand colour. The Playwright documentation warns that rendering varies with the host OS, hardware and headless mode, so baselines made on a Mac fail on Linux CI. Within weeks the team runs --update-snapshots without looking, and the suite passes whatever the pages show.
What to do instead
- Run Stylelint in CI. Rules such as
property-no-unknowncatch the typo that silently drops a declaration. - Scope styles with CSS Modules or Vue scoped styles, so a change reaches only the component you edit.
- Before merging, open each changed screen at phone and desktop widths, and attach both screenshots to the pull request.
- When the CSS decides whether a control shows, add
toBeInViewportfrom Playwright's assertions to the existing end-to-end test for the flow.
Procedure and references
When the answer changes
- The stylesheet styles a checkout or a consent banner.
- The styles live in a design system that other teams' screens use.
- The layout breaks only in a browser or at a width that nobody on the team opens.
Code example
The test that restates the stylesheet
The Stack Overflow question behind this page asks whether to check CSS values with QUnit. The jsdom README says layout is out of its scope. The first test below fails when you change the colour on purpose and passes when the header covers the button. The second runs in a real browser, where Playwright's click fails with a TimeoutError when another element covers the button. I delete the first; the second belongs only in an existing end-to-end suite.
// SettingsForm.test.tsx (Jest + jsdom): guards nothing a user sees
import { render, screen } from '@testing-library/react';
import { SettingsForm } from './SettingsForm';
test('save button is green', () => {
render(<SettingsForm />);
const save = screen.getByRole('button', { name: 'Save' });
expect(getComputedStyle(save).backgroundColor).toBe('rgb(22, 163, 74)');
});
// settings.spec.ts (Playwright): fails when a phone user cannot reach or press Save
import { test, expect } from '@playwright/test';
test.use({ viewport: { width: 390, height: 844 } });
test('Save is reachable on a phone', async ({ page }) => {
await page.goto('/settings');
const save = page.getByRole('button', { name: 'Save' });
await save.scrollIntoViewIfNeeded();
await expect(save).toBeInViewport();
await save.click();
});
Related questions
FAQ
- Is it necessary to test CSS styles?
Testing CSS styles is not necessary in a typical web app, because users report a broken layout within a day and a revert removes it. Lint the stylesheets, scope styles to components, and look at each changed screen at two widths.
- Should I unit test CSS?
Do not unit test CSS values, because a test that asserts a colour or a margin repeats the stylesheet and fails on every intentional change. Lint the stylesheet with Stylelint instead, and when CSS hides or shows a control, assert
toBeInViewportin an existing end-to-end test.- Can Jest test CSS layout?
Jest cannot test CSS layout, because jsdom does not calculate where elements appear. Check layout in a real browser, with Playwright or by eye.
- How do I catch CSS regressions without tests?
Catch CSS regressions with Stylelint in CI, component-scoped styles, and pull request screenshots of each changed screen at phone and desktop widths.