skip to content

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

level: juniorimportance: must knowfreq 52%

answer

  1. Not the component object itself
  2. Same family as page.getByRole
  3. A query, not a pinned node
  4. Scoped to the component root
  5. Carries update and unmount

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.

solid answer

~50 s

`mount` arrives as a fixture on the test callback, and `await mount('date-picker-default', { value: '2026-03-14' })` renders that story on the page you host, then resolves to a `Locator` scoped to the component's root element. From there the whole locator API applies — `picker.getByRole('button', { name: '14' })`, `picker.click()`, `await expect(picker).toBeVisible()` — and because it is a locator rather than a captured node it re-resolves on every use, so a re-render does not invalidate it. Chaining from it searches only inside the component, so a story rendering two toasts side by side stays unambiguous. The same handle carries two methods that belong to the mounted instance: `update(props)` re-renders it with new props, and `unmount()` removes it. Note what it is not: it is not the framework's component object, so you cannot call the component's own methods or read its state through it.

code

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

test('date picker marks the selected day', async ({ mount }) => {
  const picker = await mount('date-picker-default', { value: '2026-03-14' });

  await expect(picker).toBeVisible();
  await expect(picker.getByRole('button', { name: '14' })).toHaveAttribute('aria-pressed', 'true');

  await picker.getByRole('button', { name: '15' }).click();
  await expect(picker.getByRole('button', { name: '15' })).toHaveAttribute('aria-pressed', 'true');
});

go deeper

for a junior

Remember the shape: mount is awaited, takes a story id and props, and gives you a locator for the component's root. Chain your queries from that locator instead of going back to the page.

for a middle

Be able to explain why a locator and not an element handle: it is a re-run query, so it survives re-renders, auto-waits and enforces strictness. Name update and unmount as the extras on that handle.

for a senior

Show that DOM-only access is a feature, not a limitation. Assertions land on what users perceive, tests survive internal refactors, and anything with no DOM footprint must be surfaced by the story on purpose.

for a principal

Own the consequence for a design system: the mount handle defines the contract your component tests can express. Decide what components must expose in the DOM so behaviour stays testable without leaking internals into test code.

## The call and what resolves from it A Playwright component test is an ordinary test whose callback destructures the `mount` fixture, and the call takes the id of a story you have registered plus the props for that scenario: ```ts const picker = await mount('date-picker-default', { value: '2026-03-14' }); ``` `mount` renders that story on the page you host and resolves to a **`Locator`** for the **root element** of what was rendered. In Playwright 1.63 that locator is the whole test-facing surface of the mounted component. There is no framework component object handed back, no virtual-DOM node, and no hook into the renderer's internals — if you want to know something about the component, you ask the DOM. ## Why a locator rather than an element reference A `Locator` is not a captured node. It is a stored query that Playwright re-runs every time you use it, and that distinction is what makes a component test survive the re-renders a component test exists to provoke. - **Lazy** — nothing is looked up when `mount` resolves; the query runs at the moment you click or assert. - **Re-resolving** — when the component re-renders and replaces its root, the next action finds the new node instead of failing on a detached one. - **Auto-waiting** — actions and web-first assertions retry until the element is ready or the timeout expires, so a component that renders asynchronously needs no sleep. - **Strict** — if the query ever matches more than one element the action fails loudly rather than silently taking the first match. | Handle | What it really is | After the component re-renders | |---|---|---| | The `Locator` from `mount` | a query re-run on every use | still works; resolves the new node | | An `ElementHandle` | a pinned reference to one node | goes stale once that node is replaced | | A framework component instance | not exposed by `mount` at all | not applicable — assert through the DOM | ## The root is a scope, not just a target Because the locator points at the component's root, it doubles as a search scope. Chaining from it — `picker.getByRole('button', { name: '14' })` — searches only inside the mounted subtree. That matters more than it sounds: - A story that renders two toasts side by side stays unambiguous, because each chain starts from its own root. - Chrome that your story page draws around the component (a theme switcher, a padding wrapper) is outside the root and therefore invisible to the chained query. - Strict-mode failures become informative: "two matches inside the data grid" is a real finding about the component, not noise from the rest of the document. ## The two methods that belong to the instance Beyond everything a locator can do, the handle `mount` returns carries two methods that address the mounted instance itself: - `update(props)` re-renders that same instance with new props, keeping whatever state it had built up. - `unmount()` removes the component from the page again. Both are about the *instance*, not about the query. A plain locator you built with `page.getByRole()` has neither. ## How a test typically uses it 1. Mount the story with the props that set up the scenario. 2. Chain locators off the returned root for the parts you care about. 3. Act on those chained locators — click, fill, press. 4. Assert with web-first matchers on the root or on a chained locator. 5. Optionally `update(props)` and assert again, or `unmount()` when the scenario is done. ## What this shape pushes you toward The only handle being a DOM query is a design choice with consequences. You cannot reach in and call a method on the component or read its internal state, so assertions land on what a user can perceive: rendered text, roles and accessible names, attributes, classes, geometry. That is usually what you wanted anyway — a design-system date picker whose internal flag says `open: true` while nothing is visible on screen is broken, and only the DOM-level assertion catches it. It also means the tests do not have to change when the component's internals are refactored, as long as the rendered output stays the same. The cost is that anything with no DOM footprint at all is simply not observable from a mount, and has to be surfaced deliberately by the story before a test can see it.

  • Why does it matter that mount returns a locator instead of an element handle?
    A locator is a query that re-runs on every use, so it keeps working after the component re-renders and replaces its root. An element handle pins one node and goes stale the moment that node is swapped out, which is exactly what a component under test does.
  • Can you read a component's internal state through what mount returns?
    No. The handle is a DOM query, not the framework object, so there is no state or method access. Anything a test needs to know has to be observable in the rendered output — text, roles, attributes, classes — or the story has to surface it deliberately.
  • What does chaining off the returned root change compared with querying the page?
    It restricts the search to the mounted subtree. Wrappers and chrome the story page draws around the component are excluded, two similar components in one story stay distinguishable, and a strict-mode violation then tells you something real about the component's own markup.

saying these in an interview costs you the question

  • Thinks mount returns the framework component instance
  • Treats the returned value as a fixed DOM node that goes stale
  • Queries the whole page instead of chaining from the returned root
  • Assumes mount is synchronous and skips the await
  • Expects to read component state through the returned handle