Interview Insights · Playwright
Why Playwright Eliminates More Candidates Than They Expect
Quick summary — Playwright rounds
Playwright knocks out candidates who learned the API but never learned the reasons behind it. The commands are easy to memorise. What I test is whether you know why Playwright waits on its own, why role-based locators survive UI changes, why each test gets a fresh browser context, and how you would read a trace when a test fails only in CI. Candidates who can explain those four things usually pass, even when their syntax is a little rusty.
In this guide you will learn:
- The four filters a typical Playwright round applies, in order
- Why waitForTimeout is the fastest way to lose an interviewer
- How to pick locators that a reviewer will accept
- How fixtures and storageState keep tests independent and fast
- How to talk through a trace when a test fails in CI
SECTION 01The waiting mistake that ends Playwright rounds early
The first thing I watch in a Playwright exercise is what you do when the page is slow. Many candidates come from older Selenium habits, where adding a sleep felt like a fix. In Playwright that habit tells me you have not understood the tool. Playwright actions such as click() and fill() already wait for the element to be attached, visible, stable, enabled and able to receive events. Web-first assertions such as toBeVisible() and toHaveText() keep retrying until they pass or time out.
So when a candidate writes await page.waitForTimeout(5000), I hear two things. First, the test will be slow on a fast day. Second, it will still fail on a slow day. The table below shows the waiting tools I expect you to know and when each one makes sense.
| Tool | What it waits for | When to use it | What it signals in an interview |
|---|---|---|---|
| Actionability on click/fill | Element visible, stable, enabled | Every normal interaction | You trust the tool and keep tests short |
| Web-first expect | Condition becomes true, with retries | Checking text, state, count | You know assertions retry, not just actions |
| page.waitForResponse | A matching network response | When a click triggers an API call you must verify | You think about the network, not just the DOM |
| page.waitForTimeout | A fixed number of milliseconds | Almost never in real tests | Red flag unless you can justify it |
import { test, expect } from '@playwright/test';
test('order is saved', async ({ page }) => {
await page.goto('/orders/new');
await page.click('#save');
await page.waitForTimeout(5000); // hope the spinner is gone
const msg = await page.locator('.toast').textContent();
expect(msg).toBe('Order saved'); // no retry: reads once
});import { test, expect } from '@playwright/test';
test('order is saved', async ({ page }) => {
await page.goto('/orders/new');
const saved = page.waitForResponse(
r => r.url().includes('/api/orders') && r.request().method() === 'POST'
);
await page.getByRole('button', { name: 'Save' }).click();
expect((await saved).ok()).toBeTruthy();
// web-first assertion: retries until text matches or times out
await expect(page.getByRole('status')).toHaveText('Order saved');
});
Look at the last line of the weak version. textContent() reads the value once and hands a plain string to expect, so there is no retry at all. That single detail separates people who used Playwright from people who copied a snippet. When I see a candidate switch to await expect(locator).toHaveText(...) without being told, I relax.
SECTION 02Locators that survive a redesign, and locators that do not
The second filter is how you find elements. I often hand over a small form and ask the candidate to automate it. Weak candidates open DevTools, copy the generated selector and paste something like div:nth-child(3) > button.btn-primary. It works today and breaks the next time a designer moves a div.
Playwright's own guidance pushes user-facing locators: getByRole, getByLabel, getByPlaceholder, getByText and, when the team agrees on it, getByTestId. These locate elements the way a real user or a screen reader would. They also make the test easier to read in a code review, which is what I am really judging.
| Locator | Example | Strength | Weakness |
|---|---|---|---|
| getByRole | getByRole('button', { name: 'Save' }) | Matches accessible role and name, very stable | Needs decent accessibility markup |
| getByLabel | getByLabel('Email address') | Ideal for form fields with labels | Fails if the label is not linked to the input |
| getByTestId | getByTestId('checkout-total') | Stable contract agreed with developers | Invisible to users, so says nothing about UX |
| CSS / XPath chain | div:nth-child(3) > button | Works when nothing else does | Breaks on layout changes, hard to review |
await page.locator('//div[2]/form/div[3]/input').fill('asha@test.com');
await page.locator('div.row > div:nth-child(2) > input').fill('Secret#1');
await page.locator('button.btn.btn-primary.mt-3').click();
// Three selectors tied to layout and styling classes.
// A new wrapper div or a CSS rename breaks all three.await page.getByLabel('Email').fill('asha@test.com');
await page.getByLabel('Password').fill('Secret#1');
await page.getByRole('button', { name: 'Sign in' }).click();
// Narrowing inside a region when names repeat:
const cart = page.getByRole('region', { name: 'Cart' });
await cart.getByRole('button', { name: 'Remove' }).first().click();
// Filtering a list row by its text:
await page.getByRole('listitem')
.filter({ hasText: 'Blue T-shirt' })
.getByRole('button', { name: 'Add' })
.click();
If you must use CSS, say why. 'The table cells have no roles, so I asked the developer for a data-testid and used CSS as a stop-gap' is a perfectly good answer. Interviewers do not expect perfection. They expect a reason.
SECTION 03Isolation and debugging: the filters seniors are judged on
The last two filters catch candidates who are fine at writing single tests but have never kept a suite healthy. Playwright gives each test its own browser context, which is like a fresh incognito profile. That means no shared cookies, no leaked local storage and no order dependency between tests. Candidates who log in once and expect later tests to stay logged in get confused fast.
The right pattern is to log in once in a setup project, save the authenticated state to a file with storageState, and let every test start from that file. When something still fails in CI, I expect you to reach for the trace viewer rather than adding retries and hoping.
| Problem in a suite | Weak fix | Playwright fix | How to explain it |
|---|---|---|---|
| Tests pass alone, fail together | Run tests in a fixed order | Fresh context per test, no shared data | Each test owns its data and state |
| Login repeated in every test | Copy the login steps everywhere | Setup project plus storageState file | Log in once, reuse the saved session |
| Fails only in CI | Add retries until green | trace: 'on-first-retry', open the trace | Find the real cause, then fix it |
| Slow suite | Remove assertions | Parallel workers, API setup for data | Use the UI only for what you are testing |
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
retries: process.env.CI ? 1 : 0,
use: {
baseURL: process.env.BASE_URL,
trace: 'on-first-retry',
},
projects: [
{ name: 'setup', testMatch: /auth\.setup\.ts/ },
{
name: 'chromium',
use: { storageState: 'playwright/.auth/user.json' },
dependencies: ['setup'],
},
],
});import { test as setup, expect } from '@playwright/test';
const authFile = 'playwright/.auth/user.json';
setup('log in once', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill(process.env.TEST_USER!);
await page.getByLabel('Password').fill(process.env.TEST_PASS!);
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await page.context().storageState({ path: authFile });
});
// Open a saved trace locally:
// npx playwright show-trace test-results/TEST-FOLDER/trace.zip
When I ask 'a test fails only in CI, what do you do?', the answer I want starts with evidence: open the trace, look at the action log, the DOM snapshot before and after the failing step, and the network tab. Then form a guess, such as a slower API or a different viewport, and test that guess. Retries are a safety net, not a diagnosis.
SECTION 04A self-check before your Playwright interview
Run this worksheet against a small project of your own, ideally a public demo site you automated end to end. If you cannot tick an item, fix it in the project first, then practise explaining the fix out loud.
PLAYWRIGHT CODE AUDIT
[ ] Zero waitForTimeout calls (or each one has a comment explaining why)
[ ] Every assertion on page state uses await expect(locator)...
[ ] Most locators are getByRole / getByLabel / getByTestId
[ ] No test depends on another test running first
[ ] Login handled by a setup project and storageState
[ ] Test data created through API or fixtures, not long UI flows
[ ] trace configured in playwright.config.ts
[ ] Secrets read from environment variables, not hardcodedSAY EACH ANSWER IN UNDER 60 SECONDS
1. What does Playwright check before it clicks an element?
2. Why is expect(locator).toHaveText() safer than
expect(await locator.textContent()).toBe()?
3. What is a browser context and why does each test get one?
4. How did you handle login in your suite?
5. A test is flaky in CI. Walk me through your first 3 steps.
6. When would you use getByTestId instead of getByRole?BUILD THIS IN A WEEKEND
App: any public practice e-commerce or todo site
Tests:
- login (setup project + storageState)
- add item to cart, assert cart count
- remove item, assert empty state message
- form validation: empty email shows an error
- one test that waits on a network response
Extras:
- run in two browsers via projects
- HTML report and trace on retry
- a short README explaining your locator choices
SECTION 05Habits that keep you past the Playwright filters
These habits are small, but each one removes a reason for an interviewer to say no.
- Say 'auto-wait' before they ask — When you write a click, mention that Playwright waits for actionability. It shows you understand the tool, not just the syntax.
- Read locators out loud — Saying 'the button whose accessible name is Save' while typing getByRole makes your intent clear and invites helpful feedback.
- Keep one test, one purpose — A test that logs in, buys, cancels and checks email is four tests. Split them, and say why splitting helps debugging.
- Know your config file — Be ready to explain every line in your playwright.config.ts. Interviewers often open it first when you share a project.
- Debug in front of them — If your live test fails, do not panic. Run it with the inspector or read the error, and narrate what you see. That is often worth more than a passing test.
SECTION 06Interview Q&A — Playwright rounds
Q1Why should I avoid waitForTimeout in Playwright tests?
A fixed wait makes the test slow when the app is fast and still flaky when the app is slow, because it waits for time rather than for a condition. Playwright actions already wait for actionability, and web-first assertions retry. If I need something specific, I wait for that thing, such as a network response or a visible status message.
Q2What is the difference between a locator and an element handle?
A locator is a description of how to find an element, and Playwright resolves it fresh every time I act on it, so it survives re-renders. An element handle points to one specific DOM node and can go stale. Playwright recommends locators for almost everything, and I use them by default.
Q3How do you share login across tests without making them dependent?
I use a setup project that logs in once and saves the cookies and local storage to a file with storageState. Each test project lists the setup as a dependency and loads that file. Every test still gets its own fresh browser context, so tests stay independent while skipping the repeated login.
Q4Which locator do you choose first, and why?
I start with getByRole because it matches how users and assistive technology see the page, and it is stable across styling changes. For form fields I often use getByLabel. If the markup has no usable roles or labels, I agree a data-testid with developers. CSS or XPath is my last resort, and I note why I used it.
Q5A Playwright test passes locally but fails in CI. What do you do?
I turn on traces for retries, download the trace from the CI artefacts and open it with show-trace. I check the action log, the DOM snapshot at the failing step and the network calls. Common causes are slower APIs, a different viewport, missing test data or environment variables. I fix the cause rather than adding more retries.
SECTION 07Test yourself — Playwright rounds quiz
Five questions. No sign-up.
0 / 5Pick an answer to see why it is right or wrong.
SECTION 08Frequently asked questions
Is Playwright harder to crack in interviews than Selenium?
Not harder, but different. Playwright interviews focus less on writing waits and more on whether you understand auto-waiting, locators, browser contexts and tracing. If you learned Selenium first, the main shift is to stop adding manual waits and trust web-first assertions.
Should I learn Playwright with TypeScript or Python for interviews?
Both are supported. TypeScript with @playwright/test is the most common setup in job descriptions and gives you the built-in test runner, fixtures and config. If your team or target job uses Python, pytest-playwright is fine. Pick one and know it deeply.
What Playwright topics are asked for freshers?
Expect locators, auto-waiting, assertions, basic config, running tests in different browsers, handling a login, and reading a failure. Some interviews add API testing with the request fixture and network mocking with page.route.
How do I show Playwright experience without a job?
Build a small public project against a practice site, with a setup project for login, a few independent tests, traces enabled and a clear README. Be ready to explain every design choice in it during the interview.
Do interviewers ask about Playwright codegen?
Sometimes. It is fine to say you use codegen to discover locators quickly, but say that you then clean up the generated code, rename tests and replace fragile selectors. Submitting raw codegen output as a project is a weak signal.
Does Uncodemy help with Playwright interview questions?
Yes. Uncodemy's Automation Testing Training Course in Noida (classroom and online) includes live projects, one-to-one mock interviews and resume reviews, so you practise Playwright interview questions with trainer feedback before the real interview. You can book a free demo class from this page to see how the sessions work.
SECTION 09Related reads
Classroom & online · Noida
Clear every Playwright filter with confidence
Our Automation Testing Training Course covers Playwright with TypeScript from locators to CI, with hands-on projects and mock rounds where you explain your code out loud.
Talk to a counsellor for current fees- Playwright and Selenium
- Real project suites
- CI and trace debugging
- Mock technical rounds