Home / Blog / Playwright interview questions

Eight Playwright interview questions a rehearsed answer cannot survive

Quick answer

Most Playwright interview questions can be memorised, which makes them useless: a candidate who revised looks exactly like one who has done the work. The eight below cannot be memorised. None has a single right answer, each carries a follow-up that a rehearsed answer cannot survive, and every claim under them is checked against Playwright 1.62.

You have a call in forty minutes with somebody whose CV says four years of Playwright, and you already suspect your interview cannot tell the difference between four years of it and four weekends. This page is the set we would use, with the answers we would accept written out, so you can disagree with them before the call rather than during it.

Why do most Playwright interview questions not work?

Because their answers fit in a sentence. "What is a fixture", "which languages does Playwright support", "what is the difference between Playwright and Puppeteer" — every one of those has a correct answer that a candidate can hold in their head for an hour, and a question with a memorisable answer measures whether somebody revised. Revision is cheap, and it has never been cheaper than it is now.

Our sample is small and here it is, so you can weigh it. Twelve commercial URLs were opened on 31 August 2026, guessed by hand because search is unavailable from where this was written. Ten returned 404. One served a live page whose headings were advertisements for an AI course. One served a full article: testmuai.com's learning-hub page, which carries 29 questions, answers several of them in a single sentence, and labels its top tier as real-world scenarios.

Those scenarios are a complex dynamic form, an MFA flow, multiple payment methods in an e-commerce checkout, and a site protected by CAPTCHA. The page answers all four by explaining how to automate them, and for three of the four the correct senior answer is to refuse the premise. You do not drive a live payment gateway or a CAPTCHA through a browser test, so a candidate who cheerfully explains how has told you something the question did not set out to ask.

Twenty-nine questions about Playwright, and not one of them is about strict mode, which is the most distinctive property of a Playwright locator and the first error most people meet. We checked that absence with a decoy before writing it down, handing the fetcher an invented proposition alongside a true one to see whether it would agree with whatever it was given. It did not.

How do you use these eight questions to interview a Playwright engineer?

Each question below comes with the tell, which is what a memorised answer sounds like; what a strong answer contains; and the follow-up. The follow-up is the part that does the work. It always asks about a consequence, and a consequence is either something the candidate lived through or something they are about to invent in front of you.

Ask three of the eight. Forty minutes gives you time to go two questions deep on three subjects. A candidate who has genuinely owned a suite will take the follow-up as an invitation and talk for four minutes; a candidate who revised will answer it in one sentence that restates the first answer in different words.

Skipping the follow-ups reproduces the genre. Eight questions asked flat, each answered once and ticked off, is a longer version of the list this page is arguing against, and it will leave you with eight competent-sounding answers and no way to rank two people who both gave them.

This page is written to the person holding the interview, which usually means somebody deciding between hiring one engineer and buying capacity for a quarter. If the second is what you are weighing, engineers who already work in Playwright every day are the arrangement we sell for it, and the eight below still tell you what to expect from whoever arrives.

QuestionThe tellThe follow-up
Three elements match this locator. Bug in the test or bug in the page?Reaching straight for .first()When would you use .first() and be right?
The suite is green. Why might it be lying?"We look at the flaky report."Show me an assertion in your suite that cannot fail.
The button has no stable accessible name. What do you do?"XPath."Who fixes it, and what do you do this sprint while they do?
When is a fixture the wrong tool?Reciting the advantages over hooksWhat does your fixture do when the test fails halfway through?
You set retries: 3. How many times does a failing test run?"Three."Who reads the flaky markers, and what happens to them?
Five hundred tests each log in through the UI. What changes?"Use storageState," then nothingWho creates those accounts, and what happens when the session format changes?
When does mocking the network make a test worthless?"Mock everything, it is faster and more stable."What in your suite would still pass if the API were down, and should it?
Which of your tests would you delete?"None, we need the coverage."What did deleting it cost you?

Strict mode: "This locator matches three elements. Is that a bug in the test or a bug in the page?"

Strict mode is the behaviour a new Playwright user meets first and understands last. The locators guide states it plainly: "Locators are strict. This means that all operations on locators that imply some target DOM element will throw an exception if more than one element matches." The same page adds the other half, which is the half candidates forget: "On the other hand, Playwright understands when you perform a multiple-element operation, so the following call works perfectly fine when the locator resolves to multiple elements."

Every technical claim on this page is checked against Playwright 1.62. The three code samples ran on 1.62.1, which is the patch an unpinned npm install returned on the day, with Node 20.19.6 and Chromium on Windows 11.

import { test, expect } from '@playwright/test';

const basket = `
  <ul>
    <li><span class="sku">A-1004</span> <button>Remove</button></li>
    <li><span class="sku">B-2210</span> <button>Remove</button></li>
    <li><span class="sku">C-0087</span> <button>Remove</button></li>
  </ul>`;

test('three matches are fine for one operation and a fault for another', async ({ page }) => {
  await page.setContent(basket);
  const remove = page.getByRole('button', { name: 'Remove' });

  // Multiple-element operations. Three matches are the thing being asserted.
  await expect(remove).toHaveCount(3);
  await expect(page.locator('.sku')).toHaveText(['A-1004', 'B-2210', 'C-0087']);

  // A single-element operation on the same locator. Three matches are a fault.
  const error = await remove.click().catch((e: Error) => e);
  console.log(String(error).split('\n').slice(0, 6).join('\n'));
  expect(String(error)).toContain('strict mode violation');
});
Playwright 1.62.1 · TypeScript · tests/strict.spec.ts · passes

One locator, two outcomes, and the difference is in what you asked it to do. toHaveCount(3) and toHaveText against a list are multiple-element operations, so three matches are the thing being asserted. click() implies one element, so three matches throw. The error printed by that run names all three candidates and suggests a replacement locator for each: strict mode violation: getByRole('button', { name: 'Remove' }) resolved to 3 elements.

The tell is a candidate who reaches for .first() as soon as the word "three" is in the air. A strong answer treats the throw as a fault detector: three matches usually mean the page contains three of something a developer believed was one, and .first() silences the alarm while leaving the fault. Playwright's own guide is blunt about the cost — .first(), .last() and .nth() "are not recommended because when your page changes, Playwright may click on an element you did not intend."

The answer has to go the other way too. A candidate who cannot name a case where several matches are correct has memorised a rule instead of understanding one, so the follow-up is "when would you use .first() and be right?" Good answers exist: a paginated list where the first row is the newest record, or a result set where position is the assertion. The ranking that puts role first and XPath last sets out the full ladder underneath this and what breaks each rung.

Web-first assertions: "The suite is green. Why might it be lying?"

This question has many correct answers and no complete one, which is what makes it hard to rehearse for. A candidate who has never been burned by a green suite has nothing to say, and ninety seconds is not enough to bluff the missing experience.

The mechanism is in the assertions reference, which splits its matchers into two lists. Of auto-retrying assertions it says they "will retry until the assertion passes, or the assertion timeout is reached". Of the others: "These assertions allow to test any conditions, but do not auto-retry. Most of the time, web pages show information asynchronously, and using non-retrying assertions can lead to a flaky test."

import { test, expect } from '@playwright/test';

const checkout = `
  <div id="status"></div>
  <script>
    setTimeout(() => {
      document.querySelector('#status').textContent = 'Payment confirmed';
    }, 1200);
  </script>`;

test('waits for the page to catch up', async ({ page }) => {
  await page.setContent(checkout);
  await expect(page.getByText('Payment confirmed')).toBeVisible();
});

test('reads the page once and believes it', async ({ page }) => {
  await page.setContent(checkout);
  const confirmed = await page.getByText('Payment confirmed').isVisible();
  expect(confirmed).toBe(true);
});

test('cannot fail', async ({ page }) => {
  await page.setContent('<div id="app"></div>');
  await expect(page.getByText('Payment failed')).not.toBeVisible();
  await expect(page.locator('.error')).toHaveCount(0);
});
Playwright 1.62.1 · TypeScript · tests/green-suite.spec.ts · 2 passed, 1 failed, deliberately

The first test waited 1.4 seconds for the page and passed. The second failed in 95 milliseconds, because locator.isVisible() is documented as returning straight away: its timeout option is deprecated, and the reference says the option "is ignored" and the method "does not wait for the element to become visible and returns immediately".

The third test uses two auto-retrying assertions, it is written the way the documentation recommends, and it passed in 133 milliseconds against a page holding an empty div and nothing else. Nothing named "Payment failed" was visible and no element carried class error, because there was no checkout at all. A negative assertion on a page that has not rendered yet is green by construction, and a suite of them is green forever.

The tell here is "we look at the flaky report", which answers a different question. A strong answer names two or three mechanisms: an assertion that samples once where the page is still arriving; a negative assertion that passes before the page exists; a mock that mocks the thing under test; a soft assertion whose failure nobody reads. It also knows the escape hatches and what they cost — expect.poll and expect.toPass both buy retry behaviour for a check that has none, and both are a way of saying "keep asking until this is true", which is the right tool and a dangerous habit.

Then ask them to show you an assertion in their own suite that cannot fail. Everyone has one. The ones who know they have one are the ones who have gone looking.

Locators: "The button has no stable accessible name. What do you do?"

The tell is a single word, and the word is XPath. It is a workable answer and it will hold until the next release. A candidate reaches for it when they think of the test suite as the only thing they are allowed to change.

A strong answer treats this as a ladder and knows the rungs. The first rung is off the test suite entirely: a button with no accessible name is an accessibility defect, and a screen reader user meets it long before the test does, so the first move is a ticket against the page. The second is a test id you own, which is what getByTestId is for; if the codebase already has an attribute convention, testIdAttribute in the use block points Playwright at that attribute, so the codebase does not grow a second convention beside the first. Scoped CSS is the last rung, and XPath sits below it.

Playwright's other-locators guide is direct about why: it recommends "prioritizing user-visible locators like text or accessible role instead of using XPath that is tied to the implementation and easily break when the page changes", and it adds a constraint people discover the hard way — "XPath does not pierce shadow roots". A team on a component library that uses shadow DOM will find that out in production, not in review.

The senior answer travels in a different direction. A mid-level engineer absorbs the problem into the suite; a senior one routes it back to the product and does something reasonable in the meantime. So the follow-up is "who fixes it, and what do you do this sprint while they do?" A candidate who files the ticket and ships a test id in the same pull request has worked somewhere with a backlog.

Fixtures: "When is a fixture the wrong tool?"

The fixtures guide lists six advantages over before and after hooks: fixtures encapsulate setup and teardown in one place, they are reusable between files, they are set up on demand, they compose, they are flexible, and they remove the need to wrap tests in describe blocks just to share an environment. A candidate who recites those six has accurately answered a different question, the one about what fixtures are good for. That is the tell: a correct answer that tells you nothing, because it would be identical coming from somebody who read the guide on the train.

A strong answer names the conditions. A fixture pays for itself when something has to be torn down, when something has to be shared across a worker rather than per test, or when it composes out of other fixtures. The guide's own worker-scoped example does all three: it builds an account shared by every test in a worker and overrides page to log in with it. A helper that returns a piece of data needs none of the three, and wrapping that in a fixture hides a test's dependencies from the test's own signature.

The cost is invisibility. A fixture does its work at the call site without appearing there, which is a feature until somebody is debugging at six in the evening and cannot see where the state came from. Fixture setup and teardown also count towards the test timeout, which the documentation says outright, so a slow fixture reads on the report as a slow test and sends whoever is investigating into the wrong file.

Ask what their fixture does when the test fails halfway through. Teardown is where a memorised answer runs out, and the documented rule is short: test-scoped fixtures are torn down after each test, worker-scoped ones only when the worker process goes away, so a worker fixture that leaks state leaks it into every test that follows in that worker.

Retries: "You set retries: 3. How many times does a failing test run, and what did you buy?"

The answer is four: the first attempt plus three retries. The configuration reference calls retries "The maximum number of retry attempts given to failed tests", and a retry is by definition an attempt after the first one, so the count is one plus three. This is a small piece of arithmetic on top of a documented value, which is the class of claim nobody re-checks, and this repository published exactly this error once — three retries described three times as "passed within three attempts".

import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  retries: 3,
  retryStrategy: 'isolated',
  reporter: [['list']],
});
Playwright 1.62.1 · TypeScript · playwright.config.ts

Run a test that always fails under that config and the runner settles it for you. A spec logging test.info().retry printed attempt index 0, 1, 2 and 3, and the list reporter labelled the last three retry #1, retry #2 and retry #3. Four runs, one failure, roughly 2.2 seconds burned on each of the three that were never going to pass.

Retries buy a green pipeline and a flake marker; they do not buy a fixed test. Playwright's own categories say so: a test that fails then passes is reported as flaky rather than as passed, which is the runner telling you it knows something is wrong and is proceeding anyway. On 1.62 you can also ask which kind of wrong. retryStrategy arrived in that release with two values — 'immediate', the default, retries as soon as a worker is free and interleaved with the run, while 'isolated' runs the retries at the end, one at a time in a single worker, which separates a test that fails on its own from a test that fails because of what was running beside it.

One thing to know before you ask this: retryStrategy is on the TestConfig API reference and it is not on the retries guide, so a candidate who learned retries from the guide will not have met it. Its absence is not a mark against them. Whether they can tell you who reads the flaky markers, and what happens to a test after it earns one, is the mark.

Authentication: "Five hundred tests, each logging in through the UI. What do you change, and what breaks?"

Everyone gets the first half. You authenticate once, save the browser state, and every test starts already signed in — storageState is the answer, it is the first pattern on Playwright's authentication guide, and a candidate who has read anything about the framework will produce it. The tell is stopping there.

The second half is where the work is. One shared account across parallel workers means the tests mutate each other's data: one test asserts the rendering of the settings page while another is changing that setting. The guide's answer is an account per worker, keyed on testInfo.parallelIndex, which it recommends specifically for tests that modify server-side state. That answer creates a new problem the moment it is spoken. Multiple testing accounts have to exist, somebody has to create them, somebody has to own them when they rot, and they have to exist in every environment the suite runs against.

A strong answer also reaches, unprompted, for what the saved file contains. The documentation carries a danger notice about it: the browser state file "may contain sensitive cookies and headers that could be used to impersonate you or your test account", and it recommends a playwright/.auth directory added to .gitignore. A candidate who has set this up will mention the gitignore line before you ask, because they have watched somebody nearly commit one.

This question is a staffing question wearing a technical costume. Half of it is a test problem and half of it is an environment problem, and the follow-up finds out whether the candidate has ever been on the wrong end of the second half: who creates those accounts, and what happens the week the session format changes?

Network mocking: "When does mocking the network make the test worthless?"

The tell is enthusiasm. "Mock everything, it makes the suite fast and stable" is true about speed, true about stability and wrong about what the suite is then measuring.

A strong answer draws the line at ownership, and Playwright's best-practices guide draws it in three words under its Avoid testing third-party dependencies heading: "Only test what you control." Mocking a payment provider or a maps API is testing your integration against a contract, which is the right thing to do and the reason page.route() exists. Mocking your own API converts an end-to-end test into a rendering test, and a rendering test stays green through a backend outage.

The second thing a strong answer knows is that recorded fixtures drift. page.routeFromHAR() replays a HAR file, which is a photograph of an API taken on the day somebody recorded it, and nothing in the suite tells you when the real API stopped matching the photograph. The test does not go red on the day the contract changes. It goes red on the day somebody re-records, which may be months later and is usually during an incident.

Then ask the question that turns it into an audit of their own suite: what in it would still pass if the API were down, and should it? Some of it should, because a component-level check does not need a backend. A candidate who can sort their own suite into those two piles has thought about this before you asked. What else belongs in a suite's ground rules carries the rest of that argument, including the parts we would fight about.

Judgement: "Which of your tests would you delete?"

No documentation stands behind this one, deliberately. It takes thirty seconds to ask, it cannot be revised for, and it separates people faster than the seven above.

The tell is "none" or "we need the coverage". Both are answers from somebody who has added tests to a suite and never carried one. A strong answer names a class of test: the ones asserting something that cannot fail, the ones whose failure nobody has read in six months, the fourth login test, the screenshot check that has been re-baselined eleven times without anybody looking at the diff.

Then ask what deleting it cost. Anybody who has been through it has a story with a bad week in it, because deletion occasionally removes the one check that was quietly holding a line, and that story is the answer you are listening for. A candidate who deletes nothing has never been responsible for how long the suite takes. A candidate who deletes cheerfully and cannot name a cost has never been responsible for what it catches.

What would we not ask, and why?

Definitions, first. "What is Playwright", "which languages does it support", "what is a fixture" — the answers are one search away, they are the same for every candidate, and the only thing they measure is preparation. If a definition is what you came for, then the explainer that starts from what the framework drives answers it properly in ten minutes and does not need a candidate present.

The CAPTCHA and live-payment-gateway scenarios filed elsewhere under "advanced" are out too. Their correct answer is a refusal, and a question answered by refusing rewards caution rather than skill. Ask it openly if that is the signal you want.

And framework trivia. Which release added which flag is a question about a changelog, and it ages badly on both sides of the table: component testing changed shape in 1.62, so an interviewer working from an older set will mark a correct answer wrong.

One boundary decides whether a whole question is fair, so state it before you ask anything near it. Playwright drives browsers. It emulates a mobile browser: a viewport, a user agent, touch input. It does not drive an iOS or Android application, so a question about automating your native app in Playwright has no correct answer available to the candidate. The same holds for load testing at scale and for penetration testing, which belong to other tools, and neither of them is work this company sells.

If you are the candidate reading this

You came for the list and this page will not give you the memorisable version, because the interviews worth passing use the follow-up and the follow-up is not a fact. Knowing what strict mode is will not carry you past "when would you use .first() and be right?"

Being able to say what something cost does carry you. Pick three decisions you made on a suite you owned: a locator strategy you changed, a test you deleted, a mock you regretted. Be ready to say what happened afterwards, including the parts that went badly. A good interviewer is listening for evidence that you have lived with the consequences of your own choices, and nobody can revise their way to evidence they have not got.

Questions

What should I ask in a Playwright interview?

A small number of questions that have no clean answer, and a follow-up for each one. A question whose answer is a definition measures whether somebody revised. A question about a consequence measures whether they have run a suite: what strictness caught on their code, what their fixture does when the test dies halfway through, who seeds the accounts the parallel workers log in with. Three of those with their follow-ups separate people better than thirty definitions do.

How do you tell a senior Playwright engineer from a mid-level one?

By what they can tell you a decision cost them. Both will describe strict mode, fixtures and storageState competently, because all three are documented and both have read the documentation. The difference arrives on the second question: the senior engineer names the accounts somebody had to seed and who owned them, the test they deleted and what deleting it lost, the mock that kept a suite green through a backend outage. Years in the field do not carry this. Having owned a suite does.

Is retries: 3 three runs or four?

Four. Playwright's configuration reference describes retries as the maximum number of retry attempts given to failed tests, and a retry is an attempt after the first one, so retries: 3 gives a failing test its first run plus three retries. The runner agrees when you make it prove the point: a test that always fails, run under retries: 3, executed four times here on 1.62.1 and the reporter labelled the last three retry #1, retry #2 and retry #3. retryStrategy, added in 1.62, changes when those retries run and not how many of them there are.

What is strict mode in Playwright?

Locators are strict by default, which means an operation that implies a single target element throws when more than one element matches. Playwright's locators guide states it as all operations on locators that imply some target DOM element will throw an exception if more than one element matches. Operations meant for many elements are unaffected — counting a list or asserting all of its text works fine with several matches. So the throw is a fault detector rather than an obstacle, and reaching for .first() silences it without changing what it found.

Is the gap a person or a project?

Tell us what the suite is written in today, and whether you are short one engineer or short a suite. That is what a first call needs, and we will tell you which of the two problems we think you have and what changes if you are wrong about it. The eight questions above are the standard we hold.