skip to content

In Playwright, which parts of an application does a component test leave untested?

level: juniorimportance: must knowfreq 55%

answer

  1. The harness page is not your app
  2. Nothing is listening on the API path
  3. No router above one mounted component
  4. One component, never two together
  5. JavaScript and TypeScript runner only

basics

~20 s

A Playwright component test starts no application server, mounts no router and assembles no page. It proves one component in a bare harness, so API behaviour, navigation between screens and interactions between components stay uncovered.

solid answer

~50 s

The runner compiles your component and serves it from a harness page of its own. Your application never starts, so three things are out of reach. There is **no server** behind the component's requests: a `fetch('/api/orders')` lands on the harness server, which has no such route, so you get a 404 or an HTML page where JSON was expected unless you stub it with `page.route()` or pass the data in as props. There is **no router**, so navigation between screens, route guards and deep links cannot be exercised, and a component that reads router context needs that provider supplied around it. And there is **no composition**: one component is on the page, so a toast colliding with an open calendar is not something the run can see. Component testing is also available only in the JavaScript and TypeScript runner, because the component source must be bundled for the browser.

code

typescript · 10 lines
typescript
import { test, expect } from '@playwright/test';

test('the data grid renders rows from the API', async ({ mount, page }) => {
  await page.route('**/api/orders', route =>
    route.fulfill({ json: [{ id: 'A-1', total: 42 }] }));

  const grid = await mount('design-system/data-grid--remote');

  await expect(grid.getByRole('cell', { name: 'A-1' })).toBeVisible();
});

go deeper

for a junior

Know what is in the box: one component, in a bare page, in a real browser. No API server, no navigation between screens, and no neighbouring components.

for a middle

Explain the mechanics of each gap. The harness server answers no API route, no router sits above the component, and only one component is ever on the page.

for a senior

Show how you close the gaps deliberately: stub at the browser boundary, supply the providers the component genuinely needs, and keep a thin set of assembled-app checks for what is left.

for a principal

Own where the boundary sits for the team — what the component suite is allowed to claim, and which risks must be covered by runs against the assembled application.

## What is actually on the page A Playwright component test builds your component with a bundler and serves it into a minimal harness page in a real browser. That page is not your application. It has no application shell, no global stylesheet beyond what the component itself imports, no providers except the ones the story sets up, and one component in it. Everything an application normally supplies around a component is absent by construction, and that absence is the boundary of what the run can prove. ## No server behind the requests Running component tests does not start your API. If a data grid fetches its rows on mount, that request goes to whatever is serving the harness page, which has no such route — the component receives a 404 or an HTML document where it expected JSON, and drops into its error state. Two honest ways out: - **Hand the data in as props**, keeping the component a pure renderer and the test a rendering test. - **Intercept at the browser boundary** with `page.route()` and fulfil the request, which keeps the component's own loading, empty and error paths under test. What neither buys you is confidence in the contract. The shape you stub is the shape you believe the API has, so a component suite is structurally incapable of catching a server that changed its response. ## No routing between screens There is no application router above the mounted component, so nothing about moving between screens is testable here: not route guards, not deep links, not URL-driven state, not a redirect after a successful save. A component that reads router context — a link, a navigation hook — will throw on render unless the story wraps it in whatever provider its framework requires, and even then the provider is a stand-in, not your route table. ## No wiring between components A mount is one component. That makes it silent about every defect that only exists when two things share a screen: - a toast stacking over the confirm button of an open date picker; - a data grid's sticky header covering the first row of results; - focus moving from a modal into the page behind it; - two components competing for the same keyboard shortcut or scroll container. ## JavaScript and TypeScript only Component testing lives in the JavaScript and TypeScript runner, because mounting requires compiling your component source for the browser. Playwright's other language bindings drive browsers perfectly well, but there is no component runner for them — so for a team whose test suite is written in another language, this whole capability is simply unavailable. ## What each gap costs, and where to cover it | Gap | What a mount cannot say | Where it gets covered | |---|---|---| | No API server | Whether the real response shape still fits | A contract check or an assembled-app run | | No router | Whether a guard or redirect fires | An assembled-app run | | One component only | Whether two components collide | An assembled-app run | | No app shell | Whether theme tokens and resets apply | A story that supplies the shell, or an app run | | JS/TS only | Nothing — it is an availability limit | Not applicable | ## The signal that a check has outgrown a mount A useful rule of thumb: when the story starts reconstructing the application — a router, a session, three sibling components, a sequence of API responses — the setup has become the thing under test. At that point the check is cheaper and more honest against the assembled application, and the component suite should keep what it is uniquely good at: 1. the component's own states, rendered in a real engine; 2. its behaviour under real clicks, keys and focus; 3. its layout under widths and content lengths you choose. Stated plainly, a mounted run buys a real engine, real CSS and real clicks. It buys no server, no navigation and no neighbours — and a suite that forgets that ships a green report about a page nobody has assembled.

  • How do you get realistic API data into a mounted component without an application server?
    Two honest options. Pass the data in as props, which keeps the component a pure renderer, or intercept the request in the browser with `page.route()` and fulfil it. The second is worth the extra setup when the component's own loading and error paths are what you want to test.
  • What tells you a behaviour has outgrown a component test?
    When the story has to reconstruct the application to make the assertion meaningful — a router, several sibling components, a session, an ordered sequence of responses. At that point the setup is the thing under test, and the check belongs against the assembled app.

saying these in an interview costs you the question

  • Assuming the application dev server starts with the component run
  • Believing navigation between screens can be covered by mounting
  • Thinking a green component suite means the page assembles correctly
  • Expecting global CSS resets and theme tokens to be present automatically
  • Claiming component tests are available from every Playwright language binding