Playwright stopped being the interesting choice and became the default one for new end-to-end suites, and that changed what interviews ask. Nobody spends a round on whether you prefer it to Selenium. They spend it finding out whether you understand the execution model or have simply been carried by it. Those two candidates write identical-looking tests, right up until the suite reaches a few hundred cases and one of them starts failing on Tuesdays.
So the useful shape of preparation for Playwright interview questions in 2026 is not a list of API methods. Almost all of the signal sits in five places:
- The execution model - actionability, auto-waiting, and the timeout hierarchy
- Locators and assertions - user-facing locators, strict mode, retrying assertions
- Isolation - contexts, fixtures, and authenticated state you do not re-earn every test
- Controlling the network - interception, stubbing, and not depending on other people's uptime
- Flake, tracing and CI - the area that separates people who wrote a suite from people who run one
Each section below sets out the mechanism and the answer an interviewer is listening for. Reading it front to back is the slow way through, so find out first which parts you actually need.
Start here: a 6-question self-check
Six questions from squizzu's Playwright bank, one for each area with a second on running a suite. Answer, and the explanation opens along with a longer breakdown. No sign-up. The score matters less than the pattern: whatever you miss is the section to read slowly.
Two of them catch experienced people most reliably, and both have an answer that sounds like a refusal. The first is that a fixed sleep is never the right tool in a test you intend to keep. The second is that a run full of flaky tests exits successfully, which means green CI is not the same as a healthy suite. If either surprised you, start there.
What a Playwright interview looks like
Three formats recur, and they probe different things.
The live exercise. You are given a small app and asked to write a handful of tests. Nobody is counting your API recall, and looking things up is usually allowed. What gets watched is which locators you reach for first, whether you assert on state or on timing, and what you do when something does not work immediately.
The debugging round. You are handed a failing test, or a trace from one, and asked what went wrong. Tracing knowledge stops being theoretical here, and candidates spread out further in this round than in either of the others.
The suite conversation. How would you run this in CI, how would you keep it fast, and what would you do when it starts flaking? Anyone can answer the first part. The third part is where people who have only run tests locally run out of road.
None of the three rewards recall. The framework's whole design is a set of opinions about what makes tests reliable, and the interview is mostly checking whether you share them or have been working around them.
Area 1: Auto-waiting and the timeout hierarchy
Start with the mechanism, because almost every weak answer in this area comes from not having one.
Before Playwright performs an action it runs a set of actionability checks on the target. The element has to be attached to the DOM, visible, and stable, meaning its bounding box has not changed across two consecutive animation frames. It has to be able to receive events, so nothing else is covering the point that would be clicked. For a click it must also be enabled. Only when all of that holds does the action fire, and if it never holds, the action times out.
That is what people mean by auto-waiting, and it has one large consequence. The situations where you would reach for a sleep are already handled. An animating button is covered by the stability check. An element that has not rendered yet is covered by the attachment and visibility checks. A button that is disabled until a form validates is covered by the enabled check.
Which leaves the question interviewers actually ask: when is a fixed sleep the right call? The honest answer is that in a test you intend to keep, it is not. A fixed delay cannot adapt. Set it too short and the test fails on a loaded CI machine; set it long enough to be safe and every run pays that cost forever. Playwright's documentation is unusually blunt about this, and puts waitForTimeout in the debugging category.
What replaces it depends on what you are actually waiting for:
- UI state - a web-first assertion.
expect(locator).toHaveText('42')retries until it passes or the expect timeout expires. - A network round trip -
page.waitForResponse, started before the action that triggers it so there is no race. - Navigation -
page.waitForURLorwaitForLoadState, rather than guessing how long a redirect takes. - An arbitrary condition -
page.waitForFunction, orexpect.pollwhen you want assertion semantics. - Time itself -
page.clock, which installs a controllable virtual clock so a sixty-second countdown can be tested in milliseconds.
The timeout hierarchy is worth being able to state cleanly, because it comes up as a scenario question. There is a test timeout, thirty seconds by default, that bounds the whole test. There is an expect timeout, five seconds by default, that bounds a single web-first assertion. There are action and navigation timeouts that bound individual operations. They are not independent: an action configured with a sixty-second timeout inside a test that has twenty-five seconds of budget left will fail at twenty-five, because the test timeout is the outer bound. Candidates who have only read the config reference tend to get this backwards.
One more thing gets probed here, usually as a code smell rather than a definition. force: true skips the actionability checks. It exists for the rare case where the checks are wrong, and it is used far more often as a way to silence a failure that was telling the truth. If you have used it, be ready to say why the checks were wrong rather than inconvenient.
Area 2: Locators, strict mode and web-first assertions
Playwright's locator API encodes an opinion: tests should find elements the way a user does. That is why getByRole, getByLabel and getByText come before getByTestId, and why all of them come before a CSS selector tied to a class name.
The reason is not ideology. A locator built on .btn-primary breaks when someone renames a class, and it passes when the button is visually present but semantically broken. A locator built on the accessible role and name breaks only when the thing a user interacts with actually changes.
That cuts both ways, and the trap is worth knowing. The accessible name comes from real semantics, not appearance. A <div> styled and wired to behave like a button has no button role, so getByRole('button') will not find it. That is usually a finding about the application rather than about the test, which is exactly why the locator strategy is useful.
Strict mode is the other thing to have straight. An operation that targets a single element must resolve to exactly one, or it throws rather than quietly acting on the first match. This surprises people arriving from tools where a selector matching six rows silently picks the first, and it eliminates a whole class of tests that pass for the wrong reason.
The part that separates people who have used the API from people who have read about it is the exception. Methods whose signature already returns a collection do not enforce strictness, because the caller has declared what they expect:
| Operation | Strict mode |
|---|---|
click, fill, hover, check |
enforced |
textContent, getAttribute, evaluate |
enforced |
count, all, allTextContents |
exempt |
expect(locator).toHaveCount(n) |
exempt |
expect(locator).toBeVisible() |
enforced |
Scoping is how you get from many matches to one without resorting to nth(). Chaining (getByRole('row', { name: 'Acme' }).getByRole('button', { name: 'Edit' })) and filtering (filter({ has: page.getByText('Delete') })) both express the relationship that makes the element unique, which survives a reordered list in a way that an index does not.
Finally, web-first assertions retry and plain comparisons do not. await expect(locator).toHaveText('42') polls until the text matches. expect(await locator.textContent()).toBe('42') reads once and compares, so it fails the moment the value has not arrived yet. The two lines look almost identical in a review, and one of them is a flaky test.
Area 3: Fixtures, browser contexts and storageState
Every test gets a fresh BrowserContext, which is Playwright's isolation primitive: separate cookies, separate storage, separate cache, all sharing one browser process so the cost is a few milliseconds rather than a full launch. Isolation is therefore the default rather than something you arrange.
Fixtures are how you extend it. The distinction that gets asked about is scope: a test-scoped fixture is built and torn down for each test, while a worker-scoped one is shared across every test that worker runs. Worker scope is the escape hatch for genuinely expensive setup, and it comes with the obvious caveat that anything you share becomes a way for tests to affect each other.
Which brings up the expensive setup almost every suite has. Logging in through the UI in every test is the single most common waste in a Playwright suite. The standard answer is storageState: perform the login once, serialise the resulting cookies and origin-scoped web storage to a JSON file, and have the other projects load it.
The wiring is a setup project plus a dependency:
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'chromium',
use: { storageState: 'playwright/.auth/user.json' },
dependencies: ['setup'],
},
]
The faster variant is the one worth knowing for an interview. If the backend has a login endpoint, you do not need a browser at all. The request fixture is a full HTTP client with its own cookie jar, so posting credentials to the API and then calling request.storageState({ path }) produces the same file, typically in a fraction of the time a multi-step UI login takes.
Two caveats separate a complete answer from a partial one. Stored web storage is origin-scoped, so a login flow that happens on auth.example.com and redirects to app.example.com will not carry its localStorage across, which is the usual explanation when tests start logged out for no visible reason. And a shared state file means shared data, so a suite where tests mutate the user's records needs per-worker accounts rather than one global login.
The Page Object Model still comes up, though less centrally than it used to. A reasonable answer notes that Playwright's locators already cover much of what page objects were invented to encapsulate, and that the pattern earns its place for multi-step flows rather than for wrapping every button on the page.
Area 4: Network interception and mocking
page.route intercepts requests before they leave the browser, and the handler decides what happens: fulfill with a canned response, abort to fail it, or continue to let it through, optionally with modifications.
The interview version of this is rarely about the API and usually about judgement. A test fails intermittently because a third-party analytics endpoint returns a 503 and the application surfaces an error toast. The test has nothing to do with analytics.
The reflexive fixes are all worse than the real one. Raising retries burns CI time and still fails sometimes. Adding a sleep so the toast clears makes the test slower and no more reliable. Disabling JavaScript breaks the application you came to test. The fix is to stop the test depending on somebody else's uptime, by intercepting that route and returning something deterministic.
The general rule follows from it. Decide, per test, which network dependencies are part of what you are verifying and which are scenery. Scenery gets stubbed. What you are actually testing does not, or you are testing your own fixtures.
routeFromHAR is the option worth naming when a service has a contract too involved to hand-write: record the traffic once, replay it on every run afterwards. And the same request fixture that speeds up login doubles as an API testing client, which matters because the fastest way to set up state for a UI test is usually not through the UI.
Area 5: Flaky tests, tracing and CI
This is the area that separates candidates who wrote a suite from candidates who run one, and it is where the conversation usually ends up.
Retries do not fix flake. Configure retries: 2 and a test that fails then passes is recorded as flaky, and here is the part that gets asked: the run still exits with code 0. CI goes green. Nobody looks. That default is defensible, since a genuine infrastructure blip should not block a release, but it means the flaky count is the number that matters and it is not the one CI shows you by default. Surfacing it, from the JSON reporter or the HTML report, and watching the trend is the answer interviewers are listening for. A suite whose flaky count climbs quietly is one people stop trusting, and that costs more than the failures did.
It helps to name where flake actually comes from, because the causes are predictable:
- Timing assumptions - a sleep, or a plain comparison where a retrying assertion belonged.
- Shared state - the classic signature is two tests that pass alone and fail together.
- Third-party dependencies - the analytics case from area 4.
- Real time - anything reading the clock, which
page.clockexists to make deterministic.
Tracing is the other half of the answer, and it is widely misunderstood. The Trace Viewer timeline looks like a video of the run. It is not. Playwright records the full DOM at each step, including stylesheets and iframe content, and the viewer reconstructs that markup in a sandboxed frame. What you are looking at is the page, rebuilt, so you can right-click an element and inspect it with real dev tools long after the browser closed. That is a categorically different debugging tool from a screenshot, and knowing the difference tends to be the whole answer to "how would you debug a failure you cannot reproduce locally."
For CI, trace: 'on-first-retry' is the setting most suites land on: no tracing cost on the runs that pass, a full trace on the ones worth investigating. Parallelism is worker-based within a machine, and sharding splits a suite across several. The trade-off worth mentioning is contention: more workers means more pressure on whatever those tests share, which is one of the ways a suite that passed locally starts flaking in CI.
Seven days, one evening each
This assumes you already write Playwright tests. What it adds is order, not a starting point.
- Day 1 - Auto-waiting. Write out the actionability checks from memory, then find every
waitForTimeoutin a suite you have access to and work out what each one was really waiting for. Replace two of them properly. - Day 2 - Timeouts. Build a test that deliberately hits each layer: test timeout, expect timeout, action timeout. Reading the error messages until you can predict which one fires is a faster way to learn the hierarchy than reading the config docs.
- Day 3 - Locators. Take a page you know and rewrite its selectors from CSS to role-based, then break one deliberately by renaming a class and confirm the role-based version survives. Trigger a strict-mode violation on purpose so you recognise the message.
- Day 4 - Auth state. Convert a UI login to a setup project. Then, if your backend allows it, convert that to an API login and time both. The two numbers side by side are worth remembering.
- Day 5 - Network. Stub a third-party dependency in an existing test. Then break it on purpose and open the trace: the Network panel marks intercepted requests, so you can see at a glance which responses came from your test.
- Day 6 - Flake and traces. Run something with
--repeat-each=20and see what falls over. Open a trace afterwards and use the DOM inspector on a failed step rather than looking at the screenshot. - Day 7 - Rehearse. Explain each area to an empty room, capped at two minutes: how it works, what it costs, how it fails, and what you saw this week. Then prepare a question of your own. Asking what their flaky count is, and who looks at it, tells you more about a team than the job description will.
How ready are you?
Five minutes and six questions sketched the outline. The full Playwright set draws the rest of it, across 345 questions covering every area above.
Work through the Playwright questions on squizzu. Each one comes with its reasoning and a deeper breakdown, so a miss stops being a vague sense of rustiness and becomes a topic with a name.
Running these in a pipeline is the next problem: the CI/CD quiz covers the half of area 5 that is not about Playwright at all, and the Docker quiz covers the containers most suites end up running inside. If the interview also includes the application rather than only its tests, how to prepare for a React interview in 2026 covers the other side of the same codebase. And if the role expects you to point an agent at the suite, AI coding interviews in 2026 covers reviewing what it writes, tests included. For the layer above the framework, AI in software testing covers self-healing locators, generated suites and the metrics that get gamed.
