The interview bar for Playwright has moved significantly in 2026. Recruiters no longer accept "I've written some tests" — they want architecture decisions, trade-offs, debugging stories, and increasingly, your understanding of AI-powered testing. Whether you're preparing for your first QA role or interviewing for a senior SDET position, this guide has you covered.
Questions are organized into four levels: Beginner (fundamentals), Intermediate (frameworks & patterns), Senior (architecture & debugging), and AI & MCP (the 2026 differentiator). Each question includes a detailed answer with code examples where relevant.
Beginner Level — Playwright Fundamentals
These questions test your understanding of Playwright's core concepts. Expect 3–5 of these in any Playwright interview.
Playwright is an open-source browser automation framework developed by Microsoft. Unlike Selenium, which communicates with browsers through the WebDriver protocol, Playwright uses the Chrome DevTools Protocol (CDP) for Chromium and similar native protocols for Firefox and WebKit — making it significantly faster.
Key differences: Playwright has auto-waiting built in (no explicit waits needed), supports all modern browsers including WebKit (Safari's engine), has built-in API testing via request context, native network interception, and runs tests in isolated browser contexts by default. Selenium requires separate WebDriver binaries and manual wait strategies.
Playwright's recommended locator priority (from most preferred to least):
getByRole()— finds elements by their ARIA role and accessible name (most resilient)getByLabel()— finds form controls by their associated label textgetByText()— finds elements by their visible text contentgetByPlaceholder()— finds inputs by their placeholder attributegetByTestId()— finds elements by a data-testid attribute (when no semantic role exists)
Avoid raw CSS selectors and XPath — they're brittle and break when the DOM structure changes. Role-based locators survive UI redesigns because they target the element's semantic meaning, not its position in the HTML tree.
Playwright automatically waits for elements to be actionable before performing any action. For a click, this means the element must be visible, stable (not animating), enabled, not obscured by another element, and attached to the DOM.
This eliminates the need for sleep() or waitForSelector() calls that plague Selenium tests. If an element doesn't become actionable within the configured timeout (default 30 seconds), the action fails with a clear error message showing which condition wasn't met.
page.locator() and page.$()?page.locator() returns a Locator object — it's lazy, meaning it doesn't query the DOM until an action is performed. It auto-waits and auto-retries, making it the recommended approach.
page.$() returns an ElementHandle — it queries the DOM immediately and returns a direct reference to the element. ElementHandles can become stale if the DOM changes. Use locator() for tests, not $().
Playwright uses expect() with built-in web-first assertions that auto-retry until the condition is met or the timeout expires:
// Element assertions await expect(page.getByRole('heading')).toBeVisible(); await expect(page.getByRole('button')).toBeEnabled(); await expect(page.getByLabel('Email')).toHaveValue('test@example.com'); await expect(page.getByText('Success')).toHaveCount(1); // Page assertions await expect(page).toHaveURL(/dashboard/); await expect(page).toHaveTitle('My Dashboard'); // Screenshot assertion await expect(page).toHaveScreenshot('dashboard.png');
Playwright creates a fresh BrowserContext for each test by default — this provides complete test isolation (separate cookies, storage, cache). You can also create multiple contexts within a single test to simulate multiple users:
test('two users chatting', async ({ browser }) => { const aliceContext = await browser.newContext(); const bobContext = await browser.newContext(); const alicePage = await aliceContext.newPage(); const bobPage = await bobContext.newPage(); // Alice and Bob are completely isolated await alicePage.goto('/chat'); await bobPage.goto('/chat'); });
Playwright supports three browser engines: Chromium (Chrome, Edge), Firefox, and WebKit (Safari). Unlike Selenium, Playwright ships these browser binaries directly — you install them with npx playwright install and don't need to manage separate driver versions. This guarantees consistent behavior across machines and CI.
Screenshots: await page.screenshot({ path: 'screenshot.png', fullPage: true }). Videos: configure in playwright.config.ts with use: { video: 'on-first-retry' } — this records video only when a test fails and retries, which is optimal for debugging without slowing down green runs.
Intermediate Level — Frameworks & Patterns
These questions test your ability to build and maintain a test framework. Expect these in mid-level SDET and QA Automation Engineer interviews.
Fixtures are Playwright's dependency injection mechanism. Built-in fixtures include page, browser, context, and request. Custom fixtures let you set up reusable state (like an authenticated user) that's automatically available to any test that requests it. Our fixtures and hooks guide covers this topic in depth:
import { test as base } from '@playwright/test'; type MyFixtures = { authenticatedPage: Page }; export const test = base.extend<MyFixtures>({ authenticatedPage: async ({ page }, use) => { await page.goto('/login'); await page.getByLabel('Email').fill('admin@test.com'); await page.getByLabel('Password').fill('password'); await page.getByRole('button', { name: 'Sign In' }).click(); await use(page); // test runs here }, });
POM is a design pattern that encapsulates page interactions into reusable classes. Each page in your application gets a corresponding class with properties (locators) and methods (actions). This keeps test files clean and makes maintenance easier — when a selector changes, you update it in one place, not across dozens of test files. For a hands-on walkthrough, see our Playwright Page Object Model tutorial.
In Playwright, POM classes accept a Page object in the constructor, define locators as readonly properties, and expose async methods for user actions. Tests then use these classes instead of writing raw locator code.
Use page.route() to intercept requests and provide mock responses:
// Mock an API response await page.route('**/api/products', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([ { id: 1, name: 'Mock Product', price: 29.99 } ]), }); }); // Abort image requests (speed up tests) await page.route('**/*.{png,jpg,jpeg}', (route) => route.abort()); // Modify a response (add header, change body) await page.route('**/api/user', async (route) => { const response = await route.fetch(); const json = await response.json(); json.role = 'admin'; // modify response await route.fulfill({ response, json }); });
Use Playwright's storageState to authenticate once and reuse the session across tests. In your playwright.config.ts, configure a global setup script that logs in and saves cookies/localStorage to a file. All test files then load that saved state instead of logging in again:
import { chromium } from '@playwright/test'; export default async function globalSetup() { const browser = await chromium.launch(); const page = await browser.newPage(); await page.goto('http://localhost:3000/login'); await page.getByLabel('Email').fill('admin@test.com'); await page.getByLabel('Password').fill('password'); await page.getByRole('button', { name: 'Sign In' }).click(); await page.context().storageState({ path: 'auth.json' }); await browser.close(); }
Use Playwright's request fixture or APIRequestContext for pure API testing — no browser needed:
test('create and fetch user via API', async ({ request }) => { // POST request const createRes = await request.post('/api/users', { data: { name: 'Jane Doe', email: 'jane@test.com' } }); expect(createRes.status()).toBe(201); const user = await createRes.json(); // GET request const getRes = await request.get(`/api/users/${user.id}`); expect(await getRes.json()).toMatchObject({ name: 'Jane Doe', email: 'jane@test.com', }); });
New tabs/popups: Use page.waitForEvent('popup') to capture the new page before triggering the action. Dialogs: Register a handler with page.on('dialog') before the action that triggers the dialog. Always set up the listener before the action, not after.
// Handle new tab const [newPage] = await Promise.all([ page.waitForEvent('popup'), page.getByRole('link', { name: 'Open docs' }).click(), ]); await expect(newPage).toHaveURL(/docs/); // Handle alert dialog page.on('dialog', dialog => dialog.accept()); await page.getByRole('button', { name: 'Delete' }).click();
Trace Viewer is Playwright's built-in debugging tool that records a complete timeline of test execution: DOM snapshots at every step, network requests, console logs, and action screenshots. Enable it with use: { trace: 'on-first-retry' } in config. When a test fails and retries, the trace is saved — open it with npx playwright show-trace trace.zip. It's the single most powerful tool for diagnosing why a test failed, especially in CI where you can't watch the browser.
Playwright runs test files in parallel by default across multiple worker processes. Tests within a file run sequentially by default. Configure parallelism in playwright.config.ts:
workers: 4— run up to 4 test files simultaneouslyfullyParallel: true— run individual tests (not just files) in paralleltest.describe.parallel()— mark a specific describe block as parallelizable
Each worker gets its own browser instance and context, so tests are naturally isolated without shared state conflicts.
Upload: Use setInputFiles() on the file input element. Download: Use page.waitForEvent('download') to capture the download, then save it with download.saveAs(path). For drag-and-drop file uploads, use page.dispatchEvent() with a DataTransfer mock.
Use page.frameLocator() to target elements inside iframes. It returns a FrameLocator that supports all the same locator methods as page:
// Interact with an element inside an iframe const frame = page.frameLocator('iframe[name="payment"]'); await frame.getByLabel('Card number').fill('4242424242424242'); await frame.getByRole('button', { name: 'Pay' }).click();
Senior Level — Architecture & Debugging
These questions probe your experience with real-world test architecture, debugging, and decision-making. Interviewers want stories, trade-offs, and depth.
This is the most common real-world debugging question. Systematic approach:
- Check the trace — if trace-on-retry is enabled, download the trace artifact and open it in Trace Viewer. Look at the DOM snapshot at the exact step that failed.
- Environment differences — CI often runs headless with a smaller viewport. Check if the element is off-screen or hidden behind a responsive breakpoint.
- Timing — CI machines are slower. Even with auto-waiting, network-dependent tests may hit timeouts. Check if the test relies on an external service that's slow or unavailable in CI.
- Race conditions — parallel execution in CI can cause shared resource conflicts (database state, files). Ensure tests are fully isolated.
- Font/rendering — visual regression tests often fail because CI uses different fonts. Use Docker with pre-installed fonts for consistency.
Key architectural decisions for large suites:
- Page Object Model — non-negotiable at scale; every page/component gets a POM class
- Custom fixtures — extract shared setup (auth, data seeding) into fixtures, not beforeEach blocks
- Test tagging — use
test.describeand@tagsto group tests by feature, allowing selective CI runs - Sharding — use
--shard=1/4to split tests across multiple CI machines - API-first setup — seed test data via API calls in fixtures, not through the UI; 10x faster
- Isolated tests — each test creates its own data and cleans up. No shared state, no execution order dependencies
- Selective retries — configure
retries: 2for CI to handle transient failures without masking real bugs
Flaky tests erode trust in automation. Strategy:
- Identify — use Playwright's built-in
--repeat-each=5flag to detect flaky tests locally before they hit CI - Root causes — most flakiness comes from: timing issues (fixed by replacing hardcoded waits with proper assertions), shared state between tests, network-dependent assertions, or animations interfering with clicks
- Fix, don't skip — marking tests as
skiporfixmeis a temporary measure; track them and fix within 1 sprint - Quarantine — for persistently flaky tests, move them to a separate suite that runs but doesn't block deploys. Fix them as a dedicated task
getByTestId over getByRole?Use getByTestId only when an element has no meaningful ARIA role or accessible name. Examples: a generic <div> that functions as a custom widget with no role attribute, a canvas-based component, or dynamically generated containers without semantic meaning. If you find yourself adding data-testid to buttons, links, or inputs, that's a code smell — those elements already have roles and labels that getByRole can target. Using getByRole also doubles as an accessibility audit.
Playwright provides locator.dragTo(target) for simple drag-and-drop. For complex cases (like Kanban boards with specific drop zones), use the lower-level mouse API:
// Simple drag and drop await page.getByText('Task 1').dragTo(page.getByText('Done')); // Manual mouse control for complex scenarios const source = page.getByTestId('card-1'); const target = page.getByTestId('column-done'); const sourceBox = await source.boundingBox(); const targetBox = await target.boundingBox(); await page.mouse.move(sourceBox.x + sourceBox.width / 2, sourceBox.y + sourceBox.height / 2); await page.mouse.down(); await page.mouse.move(targetBox.x + targetBox.width / 2, targetBox.y + targetBox.height / 2, { steps: 10 }); await page.mouse.up();
Standard GitHub Actions setup:
- Install dependencies with
npm ci - Install browsers with
npx playwright install --with-deps(the--with-depsflag installs OS-level dependencies like fonts and libraries) - Run tests with
npx playwright test - Upload the HTML report and trace files as artifacts using
actions/upload-artifact - For large suites, use sharding: run
npx playwright test --shard=${{ matrix.shard }}across multiple parallel jobs
Key decisions: use retries: 2 in CI config (not local), run only changed test files on PRs (--grep), and run the full suite on merge to main.
Playwright ships with built-in device descriptors for emulation:
import { devices } from '@playwright/test'; export default { projects: [ { name: 'Desktop Chrome', use: { ...devices['Desktop Chrome'] } }, { name: 'iPhone 14', use: { ...devices['iPhone 14'] } }, { name: 'Pixel 7', use: { ...devices['Pixel 7'] } }, ], };
Device descriptors include viewport size, user agent, pixel ratio, touch support, and whether isMobile is true. This is emulation, not real device testing — it catches layout and viewport issues but won't catch actual mobile browser bugs (like Safari's rubber-band scrolling or iOS keyboard behavior).
test.beforeAll and test.beforeEach?beforeAll runs once per worker before any test in the file. Use it for expensive one-time setup like seeding a database. beforeEach runs before every single test. Use it for per-test setup like navigating to a page. Common mistake: putting state setup in beforeAll and assuming it's available to all tests — but if tests run in parallel across workers, each worker has its own beforeAll execution. For truly global setup, use globalSetup in the config.
Playwright's auto-waiting handles most cases, but for complex async patterns:
await page.waitForResponse('**/api/data')— wait for a specific API call to complete before assertingawait expect(locator).toHaveCount(5)— auto-retries until 5 matching elements existawait page.waitForLoadState('networkidle')— wait until no network requests for 500ms (use sparingly)await expect(locator).toBeVisible({ timeout: 10000 })— increase timeout for known slow operations
Never use page.waitForTimeout() (hardcoded sleep). It's the #1 cause of flaky tests.
AI & MCP Questions — The 2026 Differentiator
These are the questions that separate candidates in 2026. Forward-thinking companies now expect SDET candidates to understand AI-powered testing workflows. Knowing this material puts you ahead of 80% of applicants.
MCP (Model Context Protocol) is an open standard created by Anthropic that lets AI assistants connect to external tools and data sources in a standardised way. The Playwright MCP Server is Microsoft's official implementation that exposes Playwright's browser automation as MCP tools.
When an AI like Claude is connected to the Playwright MCP Server, it can: navigate to URLs, read the accessibility tree (DOM structure), click/fill/select elements, take screenshots, and run tests. This gives the AI live DOM context — it reads your actual page structure instead of guessing at selectors, making AI-generated tests dramatically more accurate.
Two approaches:
- Prompt-only: Describe the page structure and user flow in your prompt. Claude generates tests based on your description. Good for scaffolding.
- MCP Server (recommended): Connect Claude to Playwright MCP Server. Claude navigates to your running app, reads the live accessibility tree, and generates tests using real locators. This produces tests with a much higher first-run pass rate.
The workflow: start your dev server, tell Claude "Navigate to localhost:3000/checkout and generate tests for the checkout flow", and Claude produces complete spec files with accurate getByRole/getByLabel locators derived from your actual page.
The accessibility tree is a browser-generated representation of the page's semantic structure — it contains every element's role (button, heading, link, textbox), name (label text), state (enabled, checked, expanded), and relationships (parent, children). It's what screen readers use to navigate a page.
AI agents use the accessibility tree instead of raw HTML because: (1) it's much smaller than the full DOM, fitting within AI context windows; (2) it contains the semantic meaning of elements, not their implementation details; (3) the locators derived from it (getByRole, getByLabel) are the most resilient to UI changes. When Playwright MCP Server takes a "snapshot", it returns the accessibility tree as structured data.
Self-healing locators are an AI-powered approach where broken selectors are automatically fixed when UI changes cause tests to fail. The workflow:
- Test fails in CI — a button was renamed from "Add to Cart" to "Add to Basket"
- Paste the failure into Claude with MCP Server connected
- Claude navigates to the page, reads the current accessibility tree, finds the button by its new name
- Claude updates the test with the correct selector
This reduces per-failure fix time from 30–60 minutes to under 2 minutes. Microsoft benchmarks show a 75%+ success rate on selector-related failures. The remaining 25% involve flow changes (not just selector changes) that require human judgement.
Codegen records your clicks and outputs literal selectors — it's fast but produces brittle, maintenance-heavy tests with no structure. Claude AI understands intent: it generates semantic locators, adds meaningful assertions, follows Page Object Model patterns on request, generates edge case tests, and creates realistic test data. Codegen cannot generate tests for flows you haven't manually performed — Claude can generate tests from a plain English description or user story.
AI-generated tests are standard Playwright TypeScript files — they run in CI exactly like hand-written tests with zero MCP or AI dependency in the pipeline. The MCP Server is only needed during test authoring (when Claude reads the live app). Once tests are generated and committed, they run with npx playwright test like any other test. No special CI configuration, no AI tokens in production, no runtime costs.
Agentic testing uses AI agents that can plan, generate, execute, and heal tests autonomously. The three-agent architecture emerging in 2026:
- Planner Agent — takes a user story and produces a structured test plan (pages to test, flows to cover, edge cases)
- Generator Agent — takes the plan and generates Playwright spec files using MCP for live DOM context
- Healer Agent — monitors CI runs, detects failures, navigates to failing pages, and fixes broken selectors automatically
QA engineers become "test architects" who design pipelines and review AI output, rather than spending time writing individual selectors and assertions.
Rapid-Fire Questions — Quick Answers
Interviewers sometimes rapid-fire short questions to test breadth. Here are 20 common ones:
npm init playwright@latest
30 seconds per test. Change with timeout: 60000 in config or per-test with test.setTimeout(60000).
npx playwright test tests/login.spec.ts
npx playwright test --headed
toBeVisible() and toBeAttached()?toBeVisible() checks the element is in the DOM and visible (not hidden by CSS). toBeAttached() only checks it's in the DOM, regardless of visibility.
test.skip('reason') inside the test, or test.skip(condition, 'reason') for conditional skipping (e.g., skip on WebKit).
test.describe used for?Groups related tests together. Useful for shared beforeEach/afterEach hooks and organizational structure in test reports.
use: { geolocation: { latitude: 40.7128, longitude: -74.0060 }, permissions: ['geolocation'] } in config or context options.
Yes. Playwright locators pierce Shadow DOM by default — no special configuration needed. getByRole, getByText, and other locators work inside shadow roots automatically.
use: { launchOptions: { slowMo: 500 } } adds a 500ms delay between every action. Useful for demos, never for CI.
Built-in: html (interactive report), list (terminal), dot, json, junit (CI integration), line. You can use multiple simultaneously and create custom reporters.
retries: 2 in playwright.config.ts. Only applies to failed tests. Use retries: process.env.CI ? 2 : 0 to retry only in CI.
expect.soft()?Soft assertions don't stop the test on failure — they collect all failures and report them at the end. Useful when you want to check multiple independent conditions in a single test.
Configure multiple projects in playwright.config.ts with different viewport sizes, or use page.setViewportSize({ width: 375, height: 812 }) within a test.
page.evaluate()?Executes JavaScript in the browser context. Use it to access browser APIs not available through Playwright's API, like reading localStorage, checking window.performance, or manipulating the DOM directly.
Use npx playwright test --debug to open the Playwright Inspector, which lets you step through actions, inspect selectors, and see the page state. Alternatively, add await page.pause() in your test to pause execution at that point.
codegen tool?npx playwright codegen localhost:3000 opens a browser and records your interactions, generating Playwright test code in real time. Great for quickly scaffolding tests, but the output should be refactored with better locators and proper structure before committing.
Yes. Playwright has first-class Electron support via _electron.launch(). It can interact with the Electron app's main process, renderer process, and IPC communication.
TypeScript/JavaScript (primary), Python, Java, and C#/.NET. The TypeScript version receives features first and has the largest community.
--grep flag used for?Filters tests by title: npx playwright test --grep "login" runs only tests whose name contains "login". Use --grep-invert to exclude tests matching the pattern.
Interview Day Tips
- Practice coding by hand — interviewers may ask you to write a test without IDE autocomplete. Practice writing
getByRole,getByLabel, andexpect()from memory. - Have a debugging story — prepare a specific example of a flaky or hard-to-debug test you diagnosed. Include: the symptom, your investigation process, the root cause, and the fix.
- Know best practices and trade-offs — "It depends" is acceptable if you explain the trade-offs. Why POM over raw tests? Why
getByRoleovergetByTestId? Why retries in CI but not locally? - Mention AI/MCP — even if not asked directly, mentioning your experience with AI-powered test generation (Claude + MCP) signals that you're up to date with 2026 tooling.
- Ask about their suite — "How many tests do you have?", "What's your flake rate?", "Do you use sharding?" shows you think at the architecture level.
Frequently Asked Questions
What Playwright topics are asked in interviews in 2026?
Interviews cover locators, auto-waiting, fixtures, POM, API testing, network interception, CI/CD, tracing, and — new for 2026 — MCP Server integration, AI test generation with Claude, and self-healing locators.
How many questions should I prepare?
Prepare 30–50 questions across beginner (locators, actions, assertions), intermediate (fixtures, POM, API testing), and senior (architecture, debugging, CI/CD) levels. Add 5–10 questions on AI-powered testing for forward-thinking companies.
Do Playwright interviews include coding questions?
Yes. Most interviews include writing or debugging a test live. Common tasks: login flow test, POM class implementation, API mocking with page.route(), and debugging a flaky test.
Is Playwright replacing Selenium in interviews?
Increasingly, yes. New job listings overwhelmingly specify Playwright in 2026. Selenium knowledge is still valued for legacy suites, but Playwright is the default for new projects.
What AI questions are asked in Playwright interviews?
Common AI-focused questions: What is MCP and how does it work with Playwright? How would you use Claude to generate tests? What are self-healing locators? What is the accessibility tree? How do AI-generated tests run in CI/CD?
Asim Noaman
Senior QA Automation Engineer & AI Testing Specialist
With years of hands-on experience building test automation frameworks for production applications, Asim specializes in combining traditional QA methodologies with cutting-edge AI tools. He has helped teams adopt Playwright and AI-driven testing workflows to ship faster with fewer bugs.