Component tests have become real browser tests. Vitest browser mode runs them in Chromium through Playwright, and Storybook's Vitest addon turns every story into a test. They are fast, they render the real thing, and most design systems now have hundreds of them.
What they still lack is memory.
A run tells you that 3 tests failed out of 400. It does not tell you:
- Which components are those 3 tests about?
- Was the
Selectstory already failing yesterday, or did it break with this pull request? - Which stories fail once in a while and pass on retry?
- What did the component look like when the screenshot assertion failed in CI?
The output is a list of tests. The thing you care about is a list of components.
A test is not a component
Test runners report by file and by test title. That is the right unit for running tests, but not for reading the state of a design system.
One story is often covered by several tests: a render check, an interaction, a visual assertion. One test file often covers several stories. When something fails, you have to map the test title back to a component in your head, then go look for the previous runs to know whether it is new.
Storybook already gives you the right unit, the story. Button/Primary means something to everyone on the team, designers included. So the useful question is simple: for each story, what happened in the last runs?
Sending Vitest and Storybook results to Kinora
Kinora started as a dashboard for Playwright reports. It now takes Vitest runs too, through a reporter you add next to the default one:
pnpm add -D @kinora/vitest-reporter
// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
reporters: ['default', ['@kinora/vitest-reporter', { project: { slug: 'design-system' } }]],
},
})
Then run your tests with an API token in the environment:
KINORA_TOKEN=<token> npx vitest run
Three things are worth knowing about how it behaves.
It uploads browser tests only, by default. Node unit tests are usually thousands of fast, deterministic tests with no trace. They add little to a dashboard, so the reporter skips them unless you set include: 'all'.
With Storybook's Vitest addon, there is nothing else to do. Each story is already a Vitest test, and the reporter records which story each test renders, as <Component>/<Story>.
An upload never fails your test run. If the server is unreachable, your tests still pass or fail on their own merits.
One page per design system
Once a run with stories is uploaded, the project gets a Components page. Stories are grouped by component, and each story shows:
- its latest status
- a timeline over the last 20 runs
- the tests that render it
A story's status in a run is the worst status among its tests in that run. If the render check passes and the interaction test fails, the story is failing.
The timeline is what changes the conversation. A story that is red in the latest run and green in the 19 before it is a regression, and the pull request is the suspect. A story that goes red every few runs is flaky, and the pull request is probably innocent. You can tell the two apart in a glance, without opening a single CI log.
What it looked like when it failed
History tells you when a story broke. You still need to see why.
With the Playwright provider, Vitest browser mode can record a Playwright trace per test:
import { playwright } from '@vitest/browser-playwright'
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
browser: {
enabled: true,
provider: playwright(),
instances: [{ browser: 'chromium' }],
trace: 'retain-on-failure',
},
},
})
The reporter uploads the trace.zip of each failing test, and Kinora opens it in its embedded trace viewer: DOM snapshots, console, network, right from the failed test. No artifact to download from CI.
Visual assertions get the same treatment. A failing toMatchScreenshot uploads its reference, actual and diff images, and the run page shows them as a screenshot comparison. You see the two pixels of padding that moved before you even check out the branch.
Not on Vitest? Playwright component tests work too
If your component tests run on Playwright's own mount fixture, they already upload like any other Playwright test. The only missing piece is which story a test mounted, and one import records it:
import { expect, test } from '@kinora/reporter/ct'
test('disabled button is disabled', async ({ mount }) => {
const component = await mount('Button/Disabled')
await expect(component.getByRole('button')).toBeDisabled()
})
Stories from both paths land on the same Components page. This is the setup I use for Kinora's own design system, and the component testing guide has the full Vue and Vite example to copy.
Two limits to know about
A test that renders several stories counts for each of them. Kinora knows which stories a test mounted, not which one an assertion failed on. A failure marks all of them as failing. Keep one story per test when you want precise attribution.
Storybook play functions are not Playwright actions. The trace of a Storybook test shows the rendered story, not each interaction step.
Asking an agent instead of reading the page
The same data is available to coding agents through Kinora's MCP server. Its component_health tool answers questions like "which stories of Dialog are failing or flaky, and which tests render them?". That is a much better starting point for an agent than a raw test log.
Try it
If you already run Vitest in browser mode or Storybook tests in CI, the setup is one dependency and one line in your config.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.