skip to content

Isolated Component Runs

Running one component in a real browser instead of the whole app, driven by the runner's mount fixture and a page you serve yourself. Interviewers probe how far that isolation really reaches.

on this pageshow

explore

questions

14

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
open as a page

In a Playwright component test, what does the mount fixture hand back after it renders a story?

level: juniorimportance: must knowfreq 52%

basics

~20 s

The mount fixture resolves to a Locator pointing at the rendered component's root element. You drive and assert on it like any other locator, and the same handle also exposes update and unmount for that instance.

open as a page

In a Playwright component test, why can't you pass an onSelect callback in mount's props?

level: juniorimportance: must knowfreq 58%

basics

~20 s

Playwright sends mount's props across a process boundary into the browser, so only plain serialisable data survives. A function carries behaviour that cannot be transferred, so callbacks belong in the story file, not in the props object.

open as a page

In Playwright, what does mounting in a real browser engine catch that a simulated DOM cannot?

level: middleimportance: must knowfreq 62%

basics

~20 s

A Playwright component test renders in Chromium, Firefox or WebKit, so real CSS, real layout and real hit-testing apply. A simulated DOM in Node computes no boxes, so clipping, overlap and interception bugs stay invisible there.

open as a page

In Playwright component tests, how does calling update on a mounted component differ from mounting it again?

level: middleimportance: must knowfreq 44%

basics

~20 s

update re-renders the component already on the page with new props, so its internal state — an open menu, scroll position, focus — survives. Mounting again builds a fresh instance and throws all of that away.

open as a page

How does a Playwright component test prove that a toast's onUndo callback fired with the right payload?

level: middleimportance: must knowfreq 51%

basics

~20 s

The story wires the callback and writes what it received into a hidden input marked with a data-testid. The Playwright test drives the UI, then asserts on that input with toHaveValue, which retries until the recorded value appears.

open as a page

What does the withdrawal of @playwright/experimental-ct-react mean for a suite that imports mount from it?

level: middleimportance: should knowfreq 31%

basics

~20 s

Those packages were shipped as experimental, named so precisely to withhold a stability promise, and Playwright 1.63 no longer ships them. A suite importing from them has to move to the component support in @playwright/test.

open as a page

What does Playwright's mount fixture actually do in the browser when a component test calls it?

level: middleimportance: should knowfreq 38%

basics

~20 s

It drives a page you host. Playwright puts the browser on the story page found under baseURL, calls that page's window.mount global with the story id and props, and returns a locator for whatever the page rendered.

open as a page

In Playwright component tests, how do you mount a data grid that needs children rendered inside it?

level: middleimportance: should knowfreq 37%

basics

~20 s

Children cannot travel in the props object because a framework element is not serialisable data. Write the composed tree as its own story export, mount that export, and keep only plain values such as labels and row arrays in props.

open as a page

Your date picker passes every Playwright component test but is clipped in the real app: what did the mount miss?

level: seniorimportance: should knowfreq 44%

basics

~20 s

The mount renders the picker in a bare harness page, so the real app's ancestors never existed: no overflow-hidden scroll container, no stacking context, no narrow column. Recreate the ancestor in the story, or cover the case against the assembled app.

open as a page

Your Playwright component tests intermittently render a stale build of a design-system toast and the story page registers a service worker; how do you make the runs deterministic?

level: seniorimportance: should knowfreq 25%

basics

~20 s

Stop the story page from registering one. Setting serviceWorkers to 'block' in the config's use block means no worker installs for the run, so every mount loads the bundle you just built instead of a cached copy.

open as a page

In a Playwright component test, what fails when mount's props carry a renderCell function and a JSX empty state?

level: seniorimportance: should knowfreq 29%

basics

~20 s

Neither prop can cross into the browser: a function and a framework element are not serialisable, so the grid renders as if they were missing. Move both into the story file and send a plain variant flag instead.

open as a page

In a design-system suite, when should a Playwright component test update a mounted component instead of unmounting and mounting a fresh one?

level: principalimportance: should knowfreq 17%

basics

~10 s

Update when the prop transition itself is what you are asserting; mount fresh when the scenario is independent. Long update chains run faster but couple assertions together and make a failure hard to localise.

open as a page

As owner of a design system's Playwright story registry, when do you add a story export instead of a prop?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Add an export when the rendered tree or the wiring differs, and a prop when only data differs. Exports are code you maintain forever, so each one should stand for a composition consumers actually build, not a data permutation.

open as a page