Skip to content

Medium Playwright Testing Interview Questions

226 medium questions with answers. Applied questions that check whether you can use the concepts on real work.

Topics in the medium set

JavaScript and TypeScript: 28 API Testing: 27 Playwright Core: 26 Framework Design and CI/CD: 25 Network Mocking: 21 Locators and Assertions: 20 Debugging and Trace Viewer: 18 Fixtures and Hooks: 18 Component Testing: 15
Solved — mediumStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
3 questions free Unlock the rest in seconds
01

What is the difference between waitForSelector and expect with locator?

Save to ▾
Show answer
waitForSelector is a lower-level wait; locator assertions are preferred as they retry and are more readable.
02

interface vs type in TypeScript?

Save to ▾
Show answer
Both describe object shapes. Interfaces can be extended and merged; type aliases can also describe unions and primitives. Use interfaces for page or test-data shapes, types for unions.
03

How do you reuse login state across tests?

Save to ▾
Show answer
Log in once in global setup, save storageState to a JSON file and set use.storageState in the config so every test starts logged in.
04

Why choose Playwright over Selenium?

Save to ▾
Show answer
Auto-waiting, a built-in test runner, Chromium/Firefox/WebKit with one API, network interception, the trace viewer and parallel runs out of the box — usually faster and less flaky.
05

How do you share cookies between API login and UI?

Save to ▾
Show answer
Log in with request.post, call request.storageState(), and create a browser context using that state.
06

How do retries work?

Save to ▾
Show answer
Set retries in the config or CLI and failed tests re-run in a fresh context, with trace recorded on first retry to diagnose flakiness.
07

How do you tag and filter tests?

Save to ▾
Show answer
Add tags in the title (@smoke) or use the tag option, and run with --grep @smoke or --grep-invert.
08

How do you integrate Playwright with Allure or other reports?

Save to ▾
Show answer
Install the allure-playwright reporter, add it in the config reporters array and generate the report from the allure-results folder in CI.
09

How do you mock an API response?

Save to ▾
Show answer
await page.route('**/api/users', route => route.fulfill({ status: 200, json: [{ id: 1, name: 'Test' }] }) );
10

What are projects in playwright.config?

Save to ▾
Show answer
Named configurations that run the same tests across different browsers, devices or environments.
11

What are generics in TypeScript?

Save to ▾
Show answer
Placeholders for types that make reusable code type-safe, for example a helper function returning the same type of item that it receives.
12

How does Playwright compare with Selenium and Cypress?

Save to ▾
Show answer
Playwright supports multiple browsers, tabs, languages and has auto-waiting and tracing; Selenium has the widest ecosystem; Cypress runs in-browser with a great UI but has more limits across tabs.
13

How do you test file downloads and verify their content?

Save to ▾
Show answer
Capture the download event, save it with download.saveAs, then read the file in Node and assert on name, size or content.
14

How do you test pagination or chained API calls?

Save to ▾
Show answer
Read the next page link or IDs from earlier responses, loop until done and assert counts and uniqueness; pass IDs between calls in the same test.
15

Explain the event loop.

Save to ▾
Show answer
JavaScript runs one call stack and uses queues for async work; the event loop pushes callbacks from the microtask (promises) and task queues when the stack is empty.
16

What is the difference between map, filter and reduce?

Save to ▾
Show answer
map transforms each item, filter keeps items that match a condition, and reduce combines all items into a single value.
17

Which locators does Playwright recommend?

Save to ▾
Show answer
User-facing ones: getByRole, getByText, getByLabel, getByPlaceholder and getByTestId, which are more resilient than CSS or XPath.
18

How do you run tests in parallel and control workers?

Save to ▾
Show answer
Files run in parallel by default; set workers in the config and use test.describe.configure({ mode: 'parallel' }) for tests inside a file.
19

How do you test accessibility with Playwright?

Save to ▾
Show answer
Integrate axe-core through @axe-core/playwright, run an analysis on the page and assert that there are no violations.
20

How do you test geolocation, permissions and time zones?

Save to ▾
Show answer
Set them in the context options (geolocation, permissions, timezoneId, locale) to simulate users from other regions.
21

How do you test file upload via API?

Save to ▾
Show answer
Use request.post with multipart: { file: { name, mimeType, buffer } } and assert the response and stored file details.
22

What reporters does Playwright provide?

Save to ▾
Show answer
List, dot, line, HTML, JSON and JUnit reporters, plus integrations such as Allure, configurable per environment.
23

How does Playwright's trace viewer help?

Save to ▾
Show answer
It records screenshots, DOM snapshots, network calls and actions per step so you can replay a failed run and see exactly what happened.
24

What is the difference between null and undefined?

Save to ▾
Show answer
undefined means a variable has not been assigned; null is an intentional empty value.
25

How do you shard tests across machines?

Save to ▾
Show answer
Use npx playwright test --shard=1/4 in each CI job and merge reports afterwards to reduce total run time.
26

How do you handle dialogs such as alerts?

Save to ▾
Show answer
Register page.on('dialog', d => d.accept()) before the action triggers the dialog.
27

How do you structure environment-specific configs in Playwright?

Save to ▾
Show answer
Use dotenv files per environment, read them in playwright.config and set baseURL and credentials from variables, selecting with an ENV flag.
28

What does the optional chaining operator do?

Save to ▾
Show answer
obj?.a?.b safely reads nested properties and returns undefined instead of throwing if something in the chain is missing.
29

How do you use test.step and test annotations?

Save to ▾
Show answer
test.step groups actions into named steps in reports; annotations such as test.skip, test.fixme and test.slow communicate intent and adjust behaviour.
30

How do you handle token refresh in long API test runs?

Save to ▾
Show answer
Fetch tokens in a fixture with expiry tracking and refresh it when it is about to expire, keeping tests unaware of the details.
31

How do you run tests in Docker?

Save to ▾
Show answer
Use the official Playwright image which contains browsers and dependencies, mount the project and run npx playwright test for consistent CI environments.
32

What is the difference between beforeEach and fixtures?

Save to ▾
Show answer
beforeEach runs the same code before every test in scope; fixtures are lazy, composable and only set up what a test uses.
33

What is the difference between a type and an enum in TypeScript?

Save to ▾
Show answer
A type alias describes a shape or union of values; an enum creates a named set of constants that exists at runtime.
34

Promise.all vs Promise.allSettled vs Promise.race?

Save to ▾
Show answer
all waits for every promise and rejects on the first failure; allSettled waits for all and reports each outcome; race settles with the first one to finish.
35

How do you run Playwright in GitHub Actions?

Save to ▾
Show answer
Workflow: checkout → setup-node → npm ci → npx playwright install --with-deps → npx playwright test → upload playwright-report as an artifact.
36

How does visual testing work in Playwright?

Save to ▾
Show answer
await expect(page).toHaveScreenshot('home.png') compares against a baseline image; tune maxDiffPixels and mask dynamic areas like dates.
37

What is codegen?

Save to ▾
Show answer
npx playwright codegen records user actions and generates test code with suggested locators, handy for starting tests.
38

How do you debug a Playwright test?

Save to ▾
Show answer
Use --debug or the Inspector, page.pause(), UI mode, the trace viewer and headed runs with slowMo.
39

How do you take screenshots and videos?

Save to ▾
Show answer
page.screenshot() for captures and config options use: { screenshot: 'only-on-failure', video: 'retain-on-failure' } for automatic evidence.
40

What is destructuring and the spread operator?

Save to ▾
Show answer
Destructuring pulls values from arrays or objects into variables; spread expands an array or object into another, useful for copying and merging config.
41

How do you perform drag and drop and hover?

Save to ▾
Show answer
locator.hover() and locator.dragTo(target) perform these actions, with auto-waiting for actionability.
42

What are fixtures in Playwright?

Save to ▾
Show answer
Reusable setup/teardown units injected into tests (page, context, request). You can extend test with custom fixtures such as loggedInPage.
43

How do you mock third-party APIs in UI tests?

Save to ▾
Show answer
Use page.route to intercept the call and fulfil it with fixed JSON so tests do not depend on external systems.
44

How do you validate an API response in Playwright?

Save to ▾
Show answer
Assert the status with expect(res.status()).toBe(200), parse res.json() and check fields, using schema validation for the structure.
45

What are web-first assertions?

Save to ▾
Show answer
Assertions like expect(locator).toBeVisible() that retry until they pass or time out, unlike one-off checks.
46

How do you handle date pickers and custom dropdowns?

Save to ▾
Show answer
Click to open the control, then use getByRole('option') or a text locator for the target item, instead of relying on fragile index-based selectors.
47

How do you handle iframes in Playwright?

Save to ▾
Show answer
Use page.frameLocator('#frame').getByRole(...) so actions work inside the iframe with auto-waiting.
48

What is APIRequestContext?

Save to ▾
Show answer
The object behind the request fixture that holds base URL, headers and cookies, and can share cookies with a browser context.
49

How do you measure API response time?

Save to ▾
Show answer
Record timestamps around the request or use response timing, then assert the duration is under an agreed threshold.
50

How do you combine API and UI tests?

Save to ▾
Show answer
Create test data through API calls, verify it in the UI and clean up via API, which makes UI tests faster and more reliable.
51

How do you send authentication headers?

Save to ▾
Show answer
Set extraHTTPHeaders in the config or pass headers per request, for example Authorization: Bearer token fetched in a setup step.
52

How do you handle authentication to avoid logging in every test?

Save to ▾
Show answer
Log in once in a setup project, save storageState to a file and reuse it in other projects or tests.
53

How do you manage test data and environments?

Save to ▾
Show answer
Use environment variables or .env files, a baseURL in config, API calls for setup and cleanup, and unique data per run.
54

How do you handle file uploads and downloads?

Save to ▾
Show answer
setInputFiles on the input for uploads; for downloads wait on page.waitForEvent('download') and save or inspect the file.
55

What is the difference between synchronous and asynchronous code?

Save to ▾
Show answer
Synchronous code runs line by line and blocks; asynchronous code starts a task and continues, handling the result later with promises or await.
56

How do you wait for a network response in Playwright?

Save to ▾
Show answer
Use page.waitForResponse(res => res.url().includes('/api/orders') && res.ok()) together with the action that triggers it.
57

How do you handle shadow DOM?

Save to ▾
Show answer
Playwright locators pierce open shadow roots by default, so getByRole and CSS locators work without special handling.
58

How do you validate JSON schema?

Save to ▾
Show answer
Use a library such as zod or ajv to define the expected schema and parse the response to fail on missing or wrongly typed fields.
59

What are interfaces and types in TypeScript?

Save to ▾
Show answer
Both describe the shape of data. Interfaces are extendable and suit object contracts; type aliases can also model unions and primitives.
60

What is hoisting?

Save to ▾
Show answer
Declarations of var and functions are moved to the top of their scope at compile time; let and const are hoisted but stay in the temporal dead zone until declared.
61

What is the difference between page, context and browser?

Save to ▾
Show answer
A browser is the engine instance; a context is an isolated session (cookies, storage); a page is a tab inside a context.
62

What are fixtures and how do you create a custom one?

Save to ▾
Show answer
Fixtures set up and tear down test dependencies. Extend with test.extend({ loggedInPage: async ({ page }, use) => { ...; await use(page); } }).
63

What are template literals?

Save to ▾
Show answer
Strings in backticks that allow embedded expressions like `Hello ${name}` and multi-line text, useful for building dynamic locators and URLs.
64

How do you do visual regression testing?

Save to ▾
Show answer
Use expect(page).toHaveScreenshot() to compare with stored baselines, with thresholds and masks for dynamic regions.
65

How do you handle GraphQL APIs in Playwright?

Save to ▾
Show answer
POST the query and variables as JSON to the single endpoint and assert both data and errors fields in the response.
66

What are closures?

Save to ▾
Show answer
A function that remembers variables from its outer scope even after that scope has finished. They are used for private state and factory helpers.
67

How do you organise tests with page object model in Playwright?

Save to ▾
Show answer
Create a class that takes a page, defines locators as properties and exposes action methods; instantiate it in a fixture for clean tests.
68

What happens if you forget an await?

Save to ▾
Show answer
The action runs without waiting, so the next step may run first — causing flaky failures or 'Target closed' errors. The @typescript-eslint/no-floating-promises rule catches it.
69

What are soft assertions?

Save to ▾
Show answer
await expect.soft(locator).toHaveText('...') records the failure but lets the test continue, so you see every mismatch in one run.
70

How do you send a POST request with a JSON body?

Save to ▾
Show answer
await request.post('/api/users', { data: { name: 'Asha' } }) automatically sets JSON headers and serialises the payload.
71

What are global setup and teardown?

Save to ▾
Show answer
Scripts that run once before or after the whole run, for seeding data, starting services or creating authentication state.
72

What is the difference between click and dispatchEvent?

Save to ▾
Show answer
click simulates a real user click with actionability checks; dispatchEvent fires a DOM event directly without those checks.
73

How do you handle multiple tabs or pop-ups?

Save to ▾
Show answer
Wait for the popup event with const [popup] = await Promise.all([page.waitForEvent('popup'), page.click('a')]), then interact with popup.
74

How do you mock network requests?

Save to ▾
Show answer
page.route('**/api/items', route => route.fulfill({ json: [...] })) intercepts and replies with test data; route.abort() simulates failures.
75

How do you test API error scenarios?

Save to ▾
Show answer
Send invalid payloads or missing auth and assert 400, 401 or 404 codes and the error message bodies.
76

How do you handle rate limits or slow APIs in tests?

Save to ▾
Show answer
Add retries with backoff for the API step, set a suitable timeout, and use mocks for non-critical dependencies.
77

How do you run tests on mobile viewports?

Save to ▾
Show answer
Use device descriptors such as devices['iPhone 13'] in a project config to emulate viewport, user agent and touch.
78

How do you set test timeouts?

Save to ▾
Show answer
Use timeout and expect.timeout in config, test.setTimeout() for one test and test.slow() for known slower tests.
79

What are higher-order functions?

Save to ▾
Show answer
Functions that take other functions as arguments or return them, such as map, filter and reduce, or a helper that wraps retry logic around a test step.
80

How do you test a file download?

Save to ▾
Show answer
const [download] = await Promise.all([ page.waitForEvent('download'), page.getByText('Export').click(), ]); await download.saveAs('reports/file.csv');
81

Write an API test with the request fixture.

Save to ▾
Show answer
test('get user', async ({ request }) => { const res = await request.get('/api/users/1'); expect(res.ok()).toBeTruthy(); expect((await res.json()).id).toBe(1); });
82

What is locator strictness in Playwright?

Save to ▾
Show answer
Locators are strict: an action such as click or fill throws a strict mode violation if the locator matches more than one element. This prevents a test from silently clicking the wrong item. Fix it by making the locator more specific with a name, filter or chaining. Methods like count(), all() or first() are allowed to work with many matches on purpose.
83

How do you filter a list of locators by text or by a child element?

Save to ▾
Show answer
// pick one product card from many const card = page.getByRole('listitem') .filter({ hasText: 'Wireless Mouse' }) .filter({ has: page.getByRole('button', { name: 'Add to cart' }) }); await card.getByRole('button', { name: 'Add to cart' }).click(); // hasNotText and hasNot exclude matches
84

How does expect.poll work and when would you use it?

Save to ▾
Show answer
expect.poll calls a function again and again until the value it returns passes the matcher or the timeout runs out. It is useful for values that are not locators, such as an API status or a database count. Example: await expect.poll(async () => (await request.get('/api/job/7')).status()).toBe(200). You can tune the intervals and the timeout.
85

A locator matches three Delete buttons and the test fails with a strict mode violation. How do you fix it?

Save to ▾
Show answer
First scope the locator to the item you mean, for example the row that contains the user's name: page.getByRole('row', { name: 'Ravi Kumar' }).getByRole('button', { name: 'Delete' }). Do not reach for first() or nth() as a quick fix, because the order can change and the test may delete the wrong record. Scoping by meaningful context is stable and readable.
86

Your test clicks a button before the page's JavaScript has attached its handler, so nothing happens. What do you do?

Save to ▾
Show answer
This is a hydration problem: the button is visible and enabled but not yet interactive. Wait for a signal that the app is ready, such as a loaded data request or a visible element that only appears after hydration, then click. A better fix is to report it to developers, because real users hit the same issue. Avoid fixed waitForTimeout calls.
87

What do locator.and() and locator.or() do?

Save to ▾
Show answer
and() returns a locator that matches elements satisfying both locators, for example a button that also has a certain title. or() matches either locator, which helps when the UI may show one of two states, such as a New email button or a security dialog. With or(), wrap the assertion carefully, because if both appear the strict check fails.
88

How do you assert an element's CSS class, attribute or value?

Save to ▾
Show answer
const btn = page.getByRole('button', { name: 'Submit' }); await expect(btn).toHaveClass(/disabled/); await expect(btn).toHaveAttribute('aria-busy', 'true'); await expect(page.getByLabel('City')).toHaveValue('Pune'); await expect(btn).toHaveCSS('cursor', 'not-allowed');
89

What are nth(), first() and last(), and why are they risky?

Save to ▾
Show answer
They pick one element by position from a locator that matches many. They are fine when order is truly meaningful, such as the first search result. They are risky when order depends on data, sorting or rendering speed, because the test may silently act on the wrong element. Prefer filtering by text or a child element that identifies the item.
90

A toast message disappears after two seconds and the assertion on it is flaky. How do you test it reliably?

Save to ▾
Show answer
Assert immediately after the action that triggers it, using a web-first assertion like toBeVisible and toHaveText, which start checking at once. Do not add sleeps before the check. If the toast is still missed, control time with page.clock so it does not auto-hide, or verify the result through the data it confirms, such as the saved record.
91

How do you locate elements inside a specific row or card?

Save to ▾
Show answer
Chain locators: find the container first, then search inside it. Example: page.getByRole('row', { name: 'INV-104' }).getByRole('button', { name: 'Download' }). Each step narrows the search area, so short names like Edit or Download stay unique. Chaining also documents intent clearly and avoids long CSS paths that break when markup changes.
92

How do you verify a page's URL and title after navigation?

Save to ▾
Show answer
await page.getByRole('link', { name: 'Orders' }).click(); await expect(page).toHaveURL(/\/orders$/); await expect(page).toHaveTitle('My Orders | Shop'); // both retry, so no need to wait for navigation manually
93

Describe a time a test passed but the feature was actually broken.

Save to ▾
Show answer
Situation: a checkout test passed, but users saw a blank order summary. Task: find why. Action: I found the test only asserted that the summary container was visible, not its contents. I added assertions on item names, totals and the order id. Result: the test caught the regression, and I reviewed other tests for similar weak assertions.
94

How do you balance pressure to automate quickly against writing stable tests?

Save to ▾
Show answer
Situation: a manager wanted 50 tests for a release in one week. Task: deliver without creating a flaky suite. Action: I agreed on the 20 highest-risk flows first, used stable locators and shared fixtures, and listed the rest as follow-ups. Result: the 20 tests ran green in CI from day one and the rest were added next sprint.
95

What is the difference between test-scoped and worker-scoped fixtures?

Save to ▾
Show answer
A test-scoped fixture is created and torn down for every test, which keeps tests isolated. A worker-scoped fixture is set up once per worker process and shared by all tests that run in it, which suits expensive things like a started server or a seeded account. Declare it with { scope: 'worker' }. Worker fixtures cannot depend on test fixtures.
96

Write a custom fixture that gives a logged-in page.

Save to ▾
Show answer
import { test as base } from '@playwright/test'; export const test = base.extend({ loggedInPage: async ({ page }, use) => { await page.goto('/login'); await page.getByLabel('Email').fill('qa@test.in'); await page.getByLabel('Password').fill('Pass@123'); await page.getByRole('button', { name: 'Sign in' }).click(); await use(page); }, });
97

What happens before and after await use() in a fixture?

Save to ▾
Show answer
Code before await use(value) is setup and runs before the test. use hands the value to the test and waits while the test runs. Code after it is teardown and runs when the test finishes, even if it failed. This keeps setup and cleanup for one resource in one place, which is cleaner than a separate beforeEach and afterEach pair.
98

Why can beforeAll run more than once in a Playwright run?

Save to ▾
Show answer
Each worker is a separate process with its own state. When tests from a file are split across workers, or a worker restarts after a failure, beforeAll runs again in the new worker. So never assume it runs only once globally. For one-time setup across the whole run, use a setup project with dependencies or globalSetup.
99

What are automatic fixtures?

Save to ▾
Show answer
A fixture defined with { auto: true } runs for every test even when the test does not ask for it. It is useful for cross-cutting work such as attaching console logs on failure, setting a feature flag or checking for leftover data. Keep auto fixtures light, because they add cost to every test in the suite.
100

A beforeAll creates one user and tests in the same file fail randomly in parallel mode. Why?

Save to ▾
Show answer
With fullyParallel, the file's tests run in different workers, so each worker creates its own user, or tests in one worker share one user and change its state. Shared mutable data causes random failures. Give each test its own data through a test-scoped fixture, or create a unique user per worker with workerInfo.workerIndex in the name.
101

How do you provide page objects as fixtures?

Save to ▾
Show answer
type Pages = { loginPage: LoginPage; cartPage: CartPage }; export const test = base.extend<Pages>({ loginPage: async ({ page }, use) => { await use(new LoginPage(page)); }, cartPage: async ({ page }, use) => { await use(new CartPage(page)); }, }); // test('x', async ({ loginPage }) => { ... })
102

What are the serial and parallel modes of test.describe.configure?

Save to ▾
Show answer
test.describe.configure({ mode: 'serial' }) runs tests in order in one worker, and if one fails the rest are skipped. mode: 'parallel' lets tests in one file run across workers. Serial mode suits a true step-by-step flow, but it makes tests depend on each other, so prefer independent tests and use serial only when there is no alternative.
103

What is testInfo and what can you do with it?

Save to ▾
Show answer
testInfo is passed as the second argument to tests and hooks. It gives the test title, project name, retry number, status and output path. You can attach files, add annotations, change the timeout with testInfo.setTimeout, or skip conditionally. For example, attach extra logs only when testInfo.status differs from testInfo.expectedStatus.
104

Test data is left behind when tests fail midway. How do you guarantee cleanup?

Save to ▾
Show answer
Create the data inside a fixture and delete it after await use(). Fixture teardown runs even when the test fails or times out, so cleanup is reliable. Avoid cleanup steps at the end of the test body, because they never run after a failed assertion. As a safety net, tag test data with a prefix and run a nightly cleanup job.
105

How do you attach a screenshot or log to the report from inside a test?

Save to ▾
Show answer
test('report', async ({ page }, testInfo) => { await page.goto('/reports'); await testInfo.attach('report page', { body: await page.screenshot(), contentType: 'image/png', }); await testInfo.attach('meta', { body: 'build 42', contentType: 'text/plain' }); });
106

Tell me about a time you refactored duplicated setup into fixtures.

Save to ▾
Show answer
Situation: forty test files each repeated twenty lines of login and data setup. Task: reduce duplication and maintenance. Action: I created fixtures for the logged-in page, a test customer and page objects, migrated files in small reviewed pull requests and wrote a short guide. Result: test files became half the size and setup changes now touch one file.
107

How do you change part of a real API response?

Save to ▾
Show answer
await page.route('**/api/profile', async route => { const response = await route.fetch(); const json = await response.json(); json.plan = 'premium'; await route.fulfill({ response, json }); }); await page.goto('/account'); await expect(page.getByText('Premium plan')).toBeVisible();
108

What is the difference between page.route and context.route?

Save to ▾
Show answer
page.route applies only to requests from that page. context.route applies to every page in the browser context, including pop-ups and new tabs opened later. Use context.route when a flow opens new windows, such as a payment pop-up, or when you set up mocks in a fixture before the page exists. Page routes take priority over context routes.
109

How do you mock network traffic using a HAR file?

Save to ▾
Show answer
Record once with page.routeFromHAR('data/orders.har', { url: '**/api/**', update: true }) while running against a real server. Later runs replay the saved responses from the HAR instead of hitting the backend. You can edit the stored JSON to create edge cases. Re-record when the API changes, otherwise the replay drifts from reality.
110

Why should you start waitForResponse before the action that triggers it?

Save to ▾
Show answer
If you click first and then wait, the response may already have arrived, so the wait misses it and times out. Create the promise first, then act, then await it: const resp = page.waitForResponse('**/api/cart'); await addButton.click(); await resp. waitForRequest works the same way for outgoing requests.
111

Your mocked route never fires. What do you check?

Save to ▾
Show answer
Check that the route is registered before the request is sent, usually before goto. Compare the pattern with the real URL in the trace network tab, including query strings and host. Make sure a service worker is not handling the request, and set serviceWorkers: 'block' if it is. Also check that another handler is not matching first or the route was not removed.
112

How do you test how the UI handles a 500 error from the orders API?

Save to ▾
Show answer
Mock the endpoint with route.fulfill({ status: 500, json: { message: 'Server error' } }), then open the page. Assert the user sees a friendly error message and a retry button, and that no broken half-rendered table appears. Then make the retry return 200 and check the data loads. This covers a path that is hard to trigger on a real server.
113

How do you simulate a slow API to test a loading spinner?

Save to ▾
Show answer
await page.route('**/api/report', async route => { await new Promise(r => setTimeout(r, 3000)); await route.continue(); }); await page.getByRole('button', { name: 'Generate' }).click(); await expect(page.getByRole('progressbar')).toBeVisible(); await expect(page.getByRole('progressbar')).toBeHidden();
114

How do you listen to all network requests on a page?

Save to ▾
Show answer
Subscribe to events: page.on('request', r => ...), page.on('response', r => ...) and page.on('requestfailed', r => ...). You can log URLs, collect API calls or count failures. Unlike route, listeners only observe and cannot change traffic. They are handy for checks like 'no request returned a 5xx status during this flow'.
115

How do you assert that a request was sent with the correct payload?

Save to ▾
Show answer
const reqPromise = page.waitForRequest(r => r.url().endsWith('/api/orders') && r.method() === 'POST' ); await page.getByRole('button', { name: 'Place order' }).click(); const req = await reqPromise; expect(req.postDataJSON()).toMatchObject({ qty: 2, sku: 'MOU-01' });
116

How do you test offline behaviour?

Save to ▾
Show answer
Call context.setOffline(true) to cut all network access for the context, then check that the app shows an offline banner or queues actions. Switch back with setOffline(false) and verify it syncs. You can also set offline: true in the context options. For a single failing endpoint, use route.abort instead of going fully offline.
117

How does URL matching work in page.route?

Save to ▾
Show answer
The first argument can be a glob string, a regular expression or a function that receives a URL object. Globs like '**/api/users*' are simple but * does not cross slashes and ** does. Regex gives precise control over query strings. A predicate function suits complex rules, for example matching host plus a specific search parameter.
118

A third-party analytics script makes tests slow and flaky. What do you do?

Save to ▾
Show answer
Block it in tests with context.route on its domain and route.abort, or fulfill it with an empty script. Analytics is not what you are testing, and blocking it removes delays and random network errors. If tracking events matter, write a few separate tests that capture the requests with waitForRequest and assert their parameters.
119

How do you mock one GraphQL query when all calls go to /graphql?

Save to ▾
Show answer
await page.route('**/graphql', async route => { const body = route.request().postDataJSON(); if (body.operationName === 'GetCart') { return route.fulfill({ json: { data: { cart: { items: [] } } } }); } return route.fallback(); });
120

How do you add or change request headers for only some requests?

Save to ▾
Show answer
Register a route for the matching URLs and call route.continue({ headers: { ...route.request().headers(), 'x-test-mode': 'true' } }). Other requests are untouched. For headers on every request, use extraHTTPHeaders in the config instead. This is useful for feature toggles, tenant ids or bypass tokens that only certain endpoints accept.
121

The app polls a status endpoint every few seconds. How do you test the change from Pending to Done?

Save to ▾
Show answer
Mock the endpoint with a counter: return Pending for the first two calls and Done after that. Then assert the UI shows Pending and later Done using web-first assertions with a suitable timeout. You can also use page.clock to speed up the polling interval instead of waiting real seconds. This makes a slow real-world process quick and deterministic.
122

Tell me about a time mocking helped you test a hard edge case.

Save to ▾
Show answer
Situation: we could not reproduce a bug where the cart broke when the stock API returned zero for one item. Task: cover it with a test. Action: I mocked only the stock endpoint with route.fetch and changed one item's quantity, then asserted the out-of-stock label. Result: the bug was fixed and the test now protects that case on every run.
123

What are the options for the trace setting in playwright.config?

Save to ▾
Show answer
Values are 'off', 'on', 'retain-on-failure', 'on-first-retry' and 'on-all-retries'. 'on-first-retry' records only when a failed test is retried, which keeps the normal run fast and gives a trace for flaky failures. 'retain-on-failure' records every test but keeps only failed ones. 'on' records everything and is mainly for local investigation.
124

What does a Playwright trace contain?

Save to ▾
Show answer
A trace contains every action with its timing, DOM snapshots before and after each action, screenshots in a filmstrip, console messages, network requests with headers and bodies, the source line of each step and the error. Because the DOM snapshots are real, you can inspect elements after the run. It is usually the fastest way to understand a CI failure.
125

What is UI mode and how is it different from --debug?

Save to ▾
Show answer
UI mode, opened with npx playwright test --ui, is a dashboard to run, filter and watch tests, with a trace-like timeline for each one and a watch mode that reruns on file changes. --debug opens the Inspector to step through a single run live. Use UI mode for daily development and --debug for stepping through a specific action.
126

How do you record a trace manually with the tracing API?

Save to ▾
Show answer
Call await context.tracing.start({ screenshots: true, snapshots: true }) before the steps, and await context.tracing.stop({ path: 'trace.zip' }) after them. This is useful in scripts that use the library without the test runner, or when you want a trace of only part of a flow. tracing.group can label sections of steps.
127

How do you capture browser console messages and page errors?

Save to ▾
Show answer
Listen with page.on('console', msg => ...) for console.log, warnings and errors, and page.on('pageerror', err => ...) for uncaught exceptions. Collect them in an array and attach them to the report on failure. Traces also show console output. Uncaught page errors often explain why a button did nothing or a page stayed blank.
128

How do you fail a test if the page logs any console error?

Save to ▾
Show answer
test('no console errors', async ({ page }) => { const errors: string[] = []; page.on('console', m => { if (m.type() === 'error') errors.push(m.text()); }); page.on('pageerror', e => errors.push(e.message)); await page.goto('/dashboard'); expect(errors).toEqual([]); });
129

What does the DEBUG=pw:api environment variable do?

Save to ▾
Show answer
Setting DEBUG=pw:api prints detailed logs of every Playwright API call to the terminal, including waits and retries inside actions. It helps when a test hangs and you need to see exactly which step is waiting and why. Other channels such as pw:browser show browser process output. It is noisy, so use it only while investigating.
130

How do you read a Playwright timeout error message?

Save to ▾
Show answer
The error names the action and locator, for example locator.click: Timeout 30000ms exceeded, followed by a call log. The call log lists each attempt, such as waiting for the element to be visible, enabled and stable, or another element intercepting pointer events. The last lines show what blocked the action. Read it before changing timeouts.
131

A trace shows the click landed on an overlay instead of the button. How do you fix it?

Save to ▾
Show answer
The call log will say another element intercepts pointer events. Find what the overlay is, such as a loading mask, cookie banner or chat widget. Wait for it to disappear with expect(overlay).toBeHidden(), or close it in a fixture. Do not use force: true, because it hides a real issue: a user would also be blocked by that overlay.
132

How do you debug a test in VS Code with the Playwright extension?

Save to ▾
Show answer
Install the Playwright Test for VS Code extension. Open the Testing panel to see tests, then set a breakpoint in the test file and click Debug Test. Execution stops at the breakpoint with the browser open, so you can inspect variables. Turn on Show browser to watch runs, and use Pick locator or Record at cursor to add steps.
133

How do test.slow() and longer timeouts help or hurt debugging?

Save to ▾
Show answer
test.slow() triples the timeout for a test that is genuinely long, such as a report export. It is honest and visible in code. But raising timeouts to make a flaky test pass hides the real cause, such as a missing wait or slow backend, and makes failures take longer. Investigate the trace first and change timeouts only with a reason.
134

How do you reproduce a flaky test locally?

Save to ▾
Show answer
Run it many times in isolation: npx playwright test cart.spec.ts -g 'apply coupon' --repeat-each=30 --workers=4 --trace=on. Repeating it with several workers creates the timing pressure CI has. Add --retries=0 so failures are not hidden. Once it fails, open the trace for that run and compare it with a passing run.
135

How do you inspect a specific network request in the trace viewer?

Save to ▾
Show answer
Open the Network tab in the trace viewer, which lists every request in the test. Click one to see method, URL, status, request and response headers and bodies, and timing. Selecting an action filters the list to requests around that step. This shows whether a UI failure came from a failed API, a slow response or unexpected data.
136

Write a test that updates a user with PUT and verifies the change.

Save to ▾
Show answer
test('update user', async ({ request }) => { const res = await request.put('/api/users/12', { data: { name: 'Neha', city: 'Jaipur' }, }); expect(res.status()).toBe(200); const get = await request.get('/api/users/12'); expect(await get.json()).toMatchObject({ name: 'Neha', city: 'Jaipur' }); });
137

How do you set baseURL and common headers for API tests?

Save to ▾
Show answer
Put them in the use block of playwright.config: use: { baseURL: process.env.API_URL, extraHTTPHeaders: { Accept: 'application/json', Authorization: `Bearer ${process.env.TOKEN}` } }. The request fixture then sends them on every call, and tests use short paths like /api/users. Keep tokens in environment variables, never in the repository.
138

How do you test the difference between 401 and 403 responses?

Save to ▾
Show answer
401 Unauthorized means the caller is not authenticated, such as a missing or expired token. 403 Forbidden means the caller is known but lacks permission. Test 401 by calling without a token or with an invalid one. Test 403 by calling an admin endpoint with a normal user's token. Assert the status and that no data leaks in the body.
139

An API returns 200 but the body contains an error field. How should your test catch this?

Save to ▾
Show answer
Never trust status alone. Assert the body structure, for example that error is absent and data has the expected fields, ideally with a schema check. Report the issue, because returning 200 for errors breaks clients and monitoring. Add a helper that validates both status and body shape so every test checks it consistently.
140

Why should you dispose an APIRequestContext?

Save to ▾
Show answer
A context created with request.newContext() holds cookies, connections and stored responses in memory. Calling await context.dispose() frees them, and any later call on it fails. Without disposing, long runs can leak memory. The built-in request fixture is disposed automatically, so this matters for contexts you create in fixtures or helpers.
141

Write a test that deletes a record and confirms it is gone.

Save to ▾
Show answer
test('delete product', async ({ request }) => { const created = await request.post('/api/products', { data: { name: 'Temp' } }); const { id } = await created.json(); const del = await request.delete(`/api/products/${id}`); expect(del.status()).toBe(204); const get = await request.get(`/api/products/${id}`); expect(get.status()).toBe(404); });
142

How do you validate response headers?

Save to ▾
Show answer
Use response.headers() for an object of lower-cased header names, or headersArray() for duplicates. Assert content-type contains application/json, cache-control matches the policy, and security headers such as strict-transport-security are present. Also check that sensitive headers like server version are not exposed. Header checks catch configuration bugs that body checks miss.
143

What does the failOnStatusCode option do?

Save to ▾
Show answer
By default Playwright API calls do not throw on error statuses, you check them yourself. Setting failOnStatusCode: true on a request or context makes it throw for any status other than 2xx or 3xx. It is useful in setup helpers, where any failure should stop the test immediately with a clear message, instead of continuing with bad data.
144

How do you write data-driven API tests with many inputs?

Save to ▾
Show answer
const cases = [ { email: '', status: 400 }, { email: 'bad', status: 400 }, { email: 'ok@test.in', status: 201 }, ]; for (const c of cases) { test(`signup with '${c.email}'`, async ({ request }) => { const res = await request.post('/api/signup', { data: { email: c.email } }); expect(res.status()).toBe(c.status); }); }
145

An endpoint should reject invalid email addresses. Which negative tests would you write?

Save to ▾
Show answer
Cover empty string, missing field, null, wrong type such as a number, no at sign, no domain, spaces, very long strings and a duplicate existing email. For each, assert a 400 or 422 status, a clear error message naming the field, and that nothing was created. Also check that a valid email with uppercase letters is accepted.
146

Tell me about a bug you found through API testing that UI tests missed.

Save to ▾
Show answer
Situation: the UI hid a delete button for normal users, so UI tests passed. Task: check permissions properly. Action: I called the delete endpoint directly with a normal user's token and it returned 204. Result: the team fixed the server-side permission check, and I added role-based API tests for every admin endpoint.
147

What actionability checks does Playwright run before a click?

Save to ▾
Show answer
Before clicking, Playwright waits until the element is attached, visible, stable (not animating), enabled and able to receive events (not covered by another element). It also scrolls it into view. fill checks for an editable element. If the checks are not met before the timeout, the action fails with a log of what was missing.
148

What does force: true do on an action, and why should you avoid it?

Save to ▾
Show answer
force: true skips the actionability checks, so Playwright clicks even if the element is hidden, disabled or covered. It can make a test pass while a real user would be blocked. Use it only for special cases like a custom checkbox whose real input is visually hidden by design. Otherwise fix the cause, such as an overlay.
149

How do you use keyboard shortcuts in Playwright?

Save to ▾
Show answer
Use locator.press('Enter') for a key on a focused element, or page.keyboard.press('Control+A') for page-level shortcuts. Modifiers combine with a plus sign, and ControlOrMeta picks Ctrl on Windows and Linux and Cmd on macOS. keyboard.down and keyboard.up hold keys. Use pressSequentially only when the app needs real keystrokes, such as autocomplete.
150

Does headless mode behave differently from headed mode?

Save to ▾
Show answer
Modern Chromium headless uses the same browser code as headed, so behaviour is very close. Differences still appear in viewport size, GPU rendering, fonts and some OS dialogs. Tests should set an explicit viewport and not depend on window focus. If something passes headed but fails headless, record a trace and compare screenshots.
151

A product list uses infinite scroll. How do you test that more items load?

Save to ▾
Show answer
Count the items, then scroll the last item into view with lastItem.scrollIntoViewIfNeeded() or page.mouse.wheel. Assert with expect(items).toHaveCount greater than before, for example using expect.poll on items.count(). Repeat until an end-of-list message appears. Also check no duplicates appear and that a loading indicator shows while fetching.
152

How do you test a page in dark mode or with reduced motion?

Save to ▾
Show answer
Set colorScheme: 'dark' or reducedMotion: 'reduce' in the context options or test.use. You can also switch during a test with page.emulateMedia({ colorScheme: 'dark' }). Then assert the right styles or take a screenshot comparison. Reduced motion also helps stability, because many apps skip animations when that preference is set.
153

Tell me about a time you automated a difficult UI flow.

Save to ▾
Show answer
Situation: a booking page had a custom calendar, drag-to-select seats and a timer. Task: automate the full booking. Action: I used role locators for the calendar, mouse actions for seat selection and page.clock to control the timer, keeping each part in a page object method. Result: the flow ran reliably in CI and replaced a 20-minute manual check.
154

What is the Clock API in Playwright?

Save to ▾
Show answer
page.clock lets tests control time. clock.install() fakes Date and timers, clock.setFixedTime() freezes the current date, and clock.fastForward() or runFor() moves time ahead. This is useful for session timeouts, countdowns, scheduled banners and date-based logic, without waiting real minutes. Install the clock before the page loads so the app uses fake time from the start.
155

What are union types and type narrowing in TypeScript?

Save to ▾
Show answer
A union type allows several types, such as string | number or 'admin' | 'user'. Before using type-specific methods, you narrow the type with checks like typeof, instanceof, in or comparing a literal field. TypeScript then knows the exact type inside that branch. In tests, unions model options like a role parameter that accepts only known values.
156

What is the difference between unknown and any in TypeScript?

Save to ▾
Show answer
any switches off type checking, so any property access compiles even if it crashes at runtime. unknown accepts any value but forces you to narrow or validate it before use. For API JSON in tests, typing it as unknown and then validating or asserting a type is safer than any, because mistakes appear at compile time.
157

What are Partial, Pick, Omit and Record used for?

Save to ▾
Show answer
They are built-in utility types. Partial<User> makes all fields optional, handy for override objects in test data builders. Pick<User, 'id' | 'email'> keeps chosen fields, and Omit<User, 'password'> removes some. Record<Role, string> maps every key of a union to a value type, for example storing a credentials file path per role.
158

Write a typed helper that retries an async function a few times.

Save to ▾
Show answer
async function retry<T>(fn: () => Promise<T>, times = 3): Promise<T> { let lastError: unknown; for (let i = 0; i < times; i++) { try { return await fn(); } catch (e) { lastError = e; } } throw lastError; }
159

Why turn on strict mode in tsconfig for a test project?

Save to ▾
Show answer
strict enables checks such as strictNullChecks and noImplicitAny. They catch bugs like using a value that may be undefined, missing parameters or wrong property names before tests run. In a large suite this saves debugging time and makes page objects safer to refactor. Pair it with the no-floating-promises lint rule to catch missing awaits.
160

How does this behave in arrow functions compared with regular functions?

Save to ▾
Show answer
A regular function gets this from how it is called, so passing a class method as a callback can lose this. An arrow function has no own this and uses the value from the surrounding code. In page objects, arrow functions in callbacks such as map keep access to this.page, which avoids undefined errors.
161

A test uses forEach with an async callback and the checks inside never seem to run. Why?

Save to ▾
Show answer
forEach does not wait for async callbacks, so it starts each callback and returns at once. The test may finish before the assertions run, or their errors are lost. Use for...of with await to run steps in order, or Promise.all with map when the steps can safely run in parallel. A lint rule can flag this pattern.
162

How do access modifiers help in page object classes?

Save to ▾
Show answer
private and protected hide internal locators and helper steps, so tests can only call meaningful public methods such as login or addToCart. This keeps tests readable and lets you change internals without breaking callers. readonly on the page field prevents it from being replaced. TypeScript enforces these at compile time only, not at runtime.
163

How do you read environment variables safely in a TypeScript test project?

Save to ▾
Show answer
Load a .env file with a package like dotenv in playwright.config, then read process.env.BASE_URL. Values are string | undefined, so validate them early and throw a clear error if a required one is missing. Keep .env out of git and commit an .env.example instead. In CI, set the same names as secret variables.
164

How do you check that a product list on the page is sorted by price?

Save to ▾
Show answer
const texts = await page.getByTestId('price').allTextContents(); const prices = texts.map(t => Number(t.replace(/[^0-9.]/g, ''))); const sorted = [...prices].sort((a, b) => a - b); expect(prices).toEqual(sorted); // wait for the list first with toHaveCount
165

What does fullyParallel change?

Save to ▾
Show answer
By default Playwright runs test files in parallel but tests within one file in order in a single worker. fullyParallel: true lets tests inside a file run in parallel across workers too. It speeds up suites with big files, but every test must be independent. You can also enable it per file with test.describe.configure({ mode: 'parallel' }).
166

How do you publish the Playwright HTML report from a CI pipeline?

Save to ▾
Show answer
Keep the html reporter in the config. After the test step, add an upload-artifact step that runs even when tests fail, using a condition like always(), and upload the playwright-report folder with a retention period. Team members download and open it with npx playwright show-report. Some teams also publish it to static hosting for direct links.
167

What does the webServer option do?

Save to ▾
Show answer
webServer tells Playwright to start your app with a command, such as npm run start, and wait until a URL or port responds before running tests. reuseExistingServer: !process.env.CI lets you use an already running dev server locally. You can define several servers, for example frontend and a mock API. It shuts them down after the run.
168

How do you handle secrets such as passwords and tokens in CI?

Save to ▾
Show answer
Store them in the CI system's secret store and pass them as environment variables, never in the repository or config files. Read them through process.env and fail fast if they are missing. Use dedicated test accounts with limited rights. Avoid printing them in logs, and make sure traces and screenshots do not expose them in reports.
169

Tell me about a time a pipeline you owned broke just before a release.

Save to ▾
Show answer
Situation: on release day the test job failed for every build after a runner image update. Task: unblock the release safely. Action: I compared logs, found missing browser dependencies, pinned the Playwright Docker image version and reran. Result: tests passed within an hour, the release went out on time, and I added an alert for image changes.
170

Why should the Playwright Docker image version match your package version?

Save to ▾
Show answer
Each Playwright version expects specific browser builds. The official Playwright Docker image tagged v1.x.y-noble contains the browsers for that exact version. If package.json has a different version, tests fail because browsers are missing or mismatched. Pin both together and update them in the same pull request.
171

Show a config with a setup project and a browser project that reuses its login.

Save to ▾
Show answer
projects: [ { name: 'setup', testMatch: /auth\.setup\.ts/ }, { name: 'chromium', use: { ...devices['Desktop Chrome'], storageState: '.auth/user.json' }, dependencies: ['setup'], }, ],
172

What does maxFailures do?

Save to ▾
Show answer
maxFailures stops the run after a set number of test failures, for example maxFailures: process.env.CI ? 10 : undefined. If an environment is down and every test fails, the build stops early instead of burning an hour of CI time. Set it high enough that a few real failures still show their full picture.
173

Tell me about setting up CI for a test suite.

Save to ▾
Show answer
Situation: our team ran tests only on laptops before releases. Task: run them automatically on every pull request. Action: I created a CI workflow with browser install, sharding over four machines, merged HTML reports and traces on retry. Result: feedback came within twelve minutes on each pull request, and release-day surprises dropped sharply.
174

What do maxDiffPixels, maxDiffPixelRatio and threshold control in a screenshot comparison?

Save to ▾
Show answer
threshold sets how different two pixels can be in colour, from 0 to 1, before they count as different; the default is 0.2. maxDiffPixels allows a fixed number of differing pixels, and maxDiffPixelRatio allows a fraction of the total image. Keep them tight so real regressions fail, and set them in the expect section of the config rather than loosening single tests one by one.
175

Write a visual assertion that masks a live clock and an ad banner.

Save to ▾
Show answer
// masked areas are painted with a solid box await expect(page).toHaveScreenshot('home.png', { mask: [ page.getByTestId('live-clock'), page.locator('.ad-banner'), ], maskColor: '#FF00FF', animations: 'disabled', fullPage: true, });
176

Visual tests pass on developers' laptops but fail in CI with tiny font differences. Why, and how do you fix it?

Save to ▾
Show answer
Font rendering, anti-aliasing and installed fonts differ between Windows, macOS and Linux, so the same page produces slightly different pixels. The fix is to generate and compare baselines in one fixed environment, usually the official Playwright Docker image used in CI. Developers update baselines by running that container locally or through a CI job, not from their own machines. Loosening thresholds should be the last resort.
177

How can you apply extra CSS only while a screenshot is taken?

Save to ▾
Show answer
Pass the stylePath option to toHaveScreenshot or set it in the expect.toHaveScreenshot section of the config. The stylesheet is injected just for the capture, so you can hide scrollbars, freeze a carousel, or set visibility hidden on dynamic widgets without changing the app. It is cleaner than adding test-only classes and also works when a mask box would cover too much of the layout.
178

Write an accessibility scan that checks only WCAG 2.1 A and AA rules.

Save to ▾
Show answer
import { test, expect } from '@playwright/test'; import AxeBuilder from '@axe-core/playwright'; test('signup page has no WCAG A/AA issues', async ({ page }) => { await page.goto('/signup'); const results = await new AxeBuilder({ page }) .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']) .analyze(); expect(results.violations).toEqual([]); });
179

How do you exclude a third-party widget or switch off one rule in an axe scan?

Save to ▾
Show answer
// scan the page but skip the chat widget you do not own const results = await new AxeBuilder({ page }) .include('main') .exclude('#third-party-chat') .disableRules(['duplicate-id']) .analyze(); expect(results.violations).toEqual([]); // log a ticket for anything you exclude
180

What do toHaveAccessibleName and toHaveAccessibleDescription check?

Save to ▾
Show answer
They assert the name and description that assistive technology computes for an element, using the accessibility tree rather than raw attributes. The name may come from a label, aria-label, aria-labelledby or inner text, and the description often from aria-describedby. For example an icon-only close button should have the name Close. These checks catch problems that a plain text assertion would miss.
181

What is an ARIA snapshot and how does toMatchAriaSnapshot work?

Save to ▾
Show answer
An ARIA snapshot is a YAML description of the accessibility tree for a part of the page: roles, names and states such as checked or level. toMatchAriaSnapshot compares the live tree of a locator against that template, and partial templates or regular expressions can be used for changing text. It tests structure and semantics, so it survives styling changes that would break a pixel screenshot.
182

Write a test that checks keyboard Tab order through a login form.

Save to ▾
Show answer
test('login form is keyboard friendly', async ({ page }) => { await page.goto('/login'); await page.getByLabel('Email').focus(); await page.keyboard.press('Tab'); await expect(page.getByLabel('Password')).toBeFocused(); await page.keyboard.press('Tab'); await expect(page.getByRole('button', { name: 'Sign in' })).toBeFocused(); await page.keyboard.press('Enter'); });
183

What can automated accessibility tools not catch?

Save to ▾
Show answer
Scanners like axe find rule-based problems such as missing labels, bad contrast or invalid ARIA, which is roughly a third to a half of real issues. They cannot judge whether alt text is meaningful, whether focus order makes sense, whether error messages are understandable, or whether a screen reader user can actually finish the task. So automated scans are combined with keyboard tests and periodic manual checks with screen readers.
184

How do you attach axe results to the Playwright HTML report?

Save to ▾
Show answer
test('cart is accessible', async ({ page }, testInfo) => { await page.goto('/cart'); const results = await new AxeBuilder({ page }).analyze(); await testInfo.attach('axe-results', { body: JSON.stringify(results.violations, null, 2), contentType: 'application/json', }); expect(results.violations).toEqual([]); });
185

How do you run the same visual tests at mobile, tablet and desktop sizes?

Save to ▾
Show answer
Create one project per size in playwright.config, for example using devices['Pixel 7'], devices['iPad Mini'] and a desktop viewport, and point them at the same visual test files with testMatch or a tag. Because the project name is part of the baseline file name, each size keeps its own images automatically. This keeps the test code identical while covering responsive layouts.
186

Why are screenshot baselines specific to browser and operating system?

Save to ▾
Show answer
Each browser engine lays out and paints text, shadows and form controls slightly differently, and each operating system has its own fonts and smoothing. A Chromium image on Linux will rarely match WebKit on macOS pixel for pixel. That is why Playwright includes project and platform in the file name and why teams fix one rendering environment for baselines.
187

Tell me about a time an accessibility check caught a real issue before release.

Save to ▾
Show answer
Situation: a new checkout redesign was ready for release. Task: I had just added an axe scan to our Playwright smoke tests. Action: the scan flagged that the payment form inputs had placeholder text but no labels, and a keyboard test showed the pay button was unreachable with Tab. I raised it with screenshots and the rule links. Result: the developer fixed both in a day and the release went out on time.
188

What is the difference between a type annotation and a type assertion with as?

Save to ▾
Show answer
A type annotation such as const user: User = data asks the compiler to check that the value really fits User. A type assertion such as data as User tells the compiler to trust you, so mismatches are not reported. In test code assertions are risky on API responses, because a wrong field passes compilation and fails later. Prefer annotations, type guards or runtime schema validation.
189

Describe a time strict TypeScript types caught a bug in your test code.

Save to ▾
Show answer
Situation: our API helpers returned loosely typed objects and tests often read fields that no longer existed. Task: make helpers safer. Action: I added response interfaces, turned on strict mode and replaced any with unknown plus type guards. Result: the compiler immediately showed eleven tests reading a renamed field, which had been passing because they compared undefined with undefined. We fixed them before the next release.
190

A test must confirm that an error banner does not appear, but it always waits the full timeout. How do you fix it?

Save to ▾
Show answer
Use a web-first negative assertion such as await expect(banner).toBeHidden() or not.toBeVisible(); they pass as soon as the element is absent, so they do not wait the full timeout. The real problem is timing: assert after a positive signal, such as the success message appearing, otherwise the banner may simply not have rendered yet and the check passes too early.
191

How is a Playwright component test different from a Jest unit test with jsdom?

Save to ▾
Show answer
jsdom is a JavaScript imitation of the DOM running in Node, so layout, CSS, real focus and many browser APIs are missing or fake. A Playwright component test renders in real Chromium, Firefox or WebKit, so visibility, scrolling, styles and keyboard behaviour are real. The cost is a build step and a browser, so it is slower than jsdom. Pure logic and hooks are still cheaper in unit tests.
192

Write a component test that checks an onSubmit callback receives the form data.

Save to ▾
Show answer
test('submits the entered name', async ({ mount }) => { const submitted: string[] = []; const component = await mount( <NameForm onSubmit={(name) => submitted.push(name)} /> ); await component.getByLabel('Name').fill('Asha'); await component.getByRole('button', { name: 'Submit' }).click(); await expect.poll(() => submitted).toEqual(['Asha']); });
193

What is the playwright/index.tsx file used for?

Save to ▾
Show answer
It is the entry file bundled into the component test page, so it runs in the browser before every mount. You import global CSS, fonts and theme files there, and register beforeMount and afterMount hooks to wrap components in providers such as a router, theme or state store. Keeping this setup in one place means individual tests can mount components without repeating boilerplate.
194

How do you wrap a component in a theme or router provider using hooksConfig?

Save to ▾
Show answer
// playwright/index.tsx import { beforeMount } from '@playwright/experimental-ct-react/hooks'; import { ThemeProvider } from '../src/theme'; export type HooksConfig = { theme?: 'light' | 'dark' }; beforeMount<HooksConfig>(async ({ App, hooksConfig }) => { return <ThemeProvider mode={hooksConfig?.theme ?? 'light'}><App /></ThemeProvider>; }); // in a test await mount(<Header />, { hooksConfig: { theme: 'dark' } });
195

How do you change a mounted component's props during a test?

Save to ▾
Show answer
Call component.update with new JSX, for example await component.update(<Badge count={5} />). Playwright re-renders the same instance with the new props, so internal state is kept, just like a parent re-rendering it in the app. This lets you test effects that react to prop changes. To test fresh mounting behaviour instead, unmount and mount again.
196

How do you mock API calls made by a component in a component test?

Save to ▾
Show answer
Newer Playwright versions provide a router fixture in component tests. You call router.route with a URL pattern and fulfil it with test data, much like page.route, and it can also use mock handlers written for Mock Service Worker. Alternatively, inject a fake data client through props or a provider in hooksConfig. Mocking keeps component tests fast and independent of any backend.
197

How do you pass slots and listen to events in a Vue component test?

Save to ▾
Show answer
With the Vue package you can mount with an options object: mount(Button, { props: { label: 'Save' }, slots: { default: 'Click me' }, on: { click: handler } }). The on map receives emitted events, so you can push payloads into an array and assert on them. JSX syntax is also supported if the project is configured for it.
198

What is ctViteConfig and when do you need it?

Save to ▾
Show answer
ctViteConfig is an option in the use block of playwright-ct.config.ts that passes extra Vite settings to the component bundle. You need it when the app relies on path aliases, plugins, environment defines or special asset handling that Playwright's default Vite setup does not know about. A typical case is adding resolve.alias so imports like @/components work in tests.
199

Component tests fail because imports like @/utils cannot be resolved, though the app builds fine. How do you fix it?

Save to ▾
Show answer
The app uses a bundler alias that the component test bundle does not know. Add the same alias in ctViteConfig under resolve.alias, or reuse the project's Vite config by importing it. Also make sure tsconfig paths match, so editors and type checks agree. After that, clear the cache folder that the component bundler writes under playwright/.cache so the bundle rebuilds.
200

How do you take visual snapshots of each component state in component tests?

Save to ▾
Show answer
Mount the component once per state, for example default, disabled, loading and error, and call await expect(component).toHaveScreenshot() for each. Since only the component is on the page, images are small, stable and fast to review. Run them in the same Docker image as CI. This often replaces many full-page visual tests that break for unrelated reasons.
201

When should you choose a component test over an end-to-end test?

Save to ▾
Show answer
Choose a component test when the behaviour lives inside one component: validation messages, disabled states, sorting in a table, keyboard handling in a dropdown or rendering of edge-case data. These are slow and awkward to reach through full flows. Choose end-to-end when the risk is in integration, such as routing, authentication or data saved on the server.
202

Write a component test that checks a required field shows an error on blur.

Save to ▾
Show answer
test('shows error when email is left empty', async ({ mount }) => { const component = await mount(<EmailField required />); const input = component.getByLabel('Email'); await input.focus(); await input.blur(); await expect(component.getByText('Email is required')).toBeVisible(); await expect(input).toHaveAttribute('aria-invalid', 'true'); });
203

Component and end-to-end tests run together and slow down every commit. How do you separate them?

Save to ▾
Show answer
Give them separate configs, such as playwright-ct.config.ts with its own testDir for component specs and playwright.config.ts for end-to-end tests, because component tests also need the bundler options and framework test import. In CI, run component tests on every commit as a fast job and end-to-end tests later or in parallel. Shared settings like reporters can live in a common module both configs import.
204

How do you test a dropdown component's keyboard support in a component test?

Save to ▾
Show answer
Mount the dropdown, focus the trigger and press Enter or ArrowDown to open it. Assert the listbox is visible and the first option has aria-selected or focus. Press ArrowDown again and check the active option moves, then press Enter and assert the trigger shows the chosen value and the list is closed. Finally press Escape on an open list to confirm it closes without changing the value.
205

Tell me about a time you introduced component tests to a frontend team.

Save to ▾
Show answer
Situation: our end-to-end suite took 40 minutes and most failures were in form validation. Task: speed up feedback. Action: I set up Playwright component tests for the form library, wrote ten examples and a short guide, and paired with two developers on their first tests. Result: validation checks moved to component level, the end-to-end suite dropped to 25 minutes and developers began writing component tests themselves.
206

How do you share one authenticated API context across all tests in a worker?

Save to ▾
Show answer
Create a worker-scoped fixture that calls request.newContext with baseURL and an auth header, or logs in through the API and keeps the cookies. Pass it to use, and dispose it after use returns. Every test in that worker gets the same ready context, so login happens once per worker. Do not store per-test data on it, or tests start depending on each other.
207

How do you return a fake JSON response that depends on the request body?

Save to ▾
Show answer
await page.route('**/api/quote', async (route) => { const body = route.request().postDataJSON(); const price = body.plan === 'pro' ? 999 : 299; await route.fulfill({ status: 200, json: { plan: body.plan, price }, }); });
208

How do you keep test code, page objects and utilities organised as a repository grows?

Save to ▾
Show answer
Use a clear layout: tests grouped by feature, a pages folder for page objects, a fixtures folder that exposes them, and api and data folders for helpers and builders. Add path aliases in tsconfig, a lint config with the Playwright plugin, and a short contributing guide. Review new helpers in pull requests so duplicate utilities do not spread.
209

Describe a time you convinced developers to add accessible labels or test ids.

Save to ▾
Show answer
Situation: many inputs had no labels, so our tests relied on long CSS selectors that broke often. Task: get stable hooks without slowing developers. Action: I listed the worst screens, showed how getByLabel tests read and how the same labels help screen reader users, and offered to raise small pull requests myself. Result: the team added labels to new work as a rule and locator breakages dropped sharply.
210

What is the difference between workerIndex and parallelIndex?

Save to ▾
Show answer
workerIndex is a unique number for every worker process started during the run, so it keeps growing when a worker is restarted after a failure. parallelIndex is a slot number from 0 to workers minus 1, and a restarted worker reuses its slot. Use parallelIndex, also exposed as TEST_PARALLEL_INDEX, to pick a fixed test account or database per slot.
211

Write a fixture that gives each parallel worker its own test account.

Save to ▾
Show answer
import { test as base } from '@playwright/test'; export const test = base.extend<{}, { account: { email: string; password: string } }>({ account: [async ({}, use, workerInfo) => { const i = workerInfo.parallelIndex; await use({ email: `qa.user${i}@example.test`, password: process.env.QA_PASSWORD!, }); }, { scope: 'worker' }], });
212

Why does Playwright start a new worker after a test fails?

Save to ▾
Show answer
A failed test may leave the browser or worker-scoped fixtures in a bad state, such as an open dialog or corrupted login. To protect the remaining tests, Playwright throws that worker away and starts a fresh one for the next tests. This means worker fixtures run again and beforeAll hooks may run more than once, which is normal and something your setup must tolerate.
213

Write a GitHub Actions job that runs the suite in four shards.

Save to ▾
Show answer
jobs: e2e: runs-on: ubuntu-latest strategy: fail-fast: false matrix: shard: [1, 2, 3, 4] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 - run: npm ci && npx playwright install --with-deps - run: npx playwright test --shard=${{ matrix.shard }}/4 --reporter=blob - uses: actions/upload-artifact@v4 with: name: blob-${{ matrix.shard }} path: blob-report
214

What is the blob reporter and why does it exist?

Save to ▾
Show answer
The blob reporter writes a compact archive of everything about a run, including results, attachments and traces, into the blob-report folder. It is meant for machines rather than people. Each shard produces one blob, and npx playwright merge-reports later combines them into a single HTML, JUnit or JSON report. Without it, every shard would produce its own separate HTML report.
215

Which methods does the Playwright Reporter interface provide?

Save to ▾
Show answer
The main ones are onBegin with the config and full suite, onTestBegin and onTestEnd for each test, onStepBegin and onStepEnd for steps, onStdOut and onStdErr for output, onError for errors outside tests and onEnd with the overall result. onEnd can be async, so you can send results to an API before the process exits. You register the reporter by path in the reporter config.
216

What does the reportSlowTests option do?

Save to ▾
Show answer
reportSlowTests makes Playwright list test files that took longer than a threshold at the end of the run. You set max, the number of files to show, and threshold in milliseconds, for example { max: 5, threshold: 60000 }. It is a cheap way to find files worth splitting, because one slow file can hold up a worker or a whole shard.
217

CI runners have 2 CPU cores and workers is set to 8. Tests start timing out randomly. Why?

Save to ▾
Show answer
Eight browsers compete for two cores and limited memory, so pages render slowly, actions hit timeouts and the app under test may also be overloaded. More workers than cores rarely helps. Set workers to match the runner, such as 2, and scale out with shards on more machines instead. Watch CPU and memory metrics in CI to confirm.
218

Parallel tests create users with the same email and fail with 'already exists'. How do you fix it?

Save to ▾
Show answer
Generate unique data per test, for example by adding testInfo.testId, the parallel index or a random suffix to the email, or by using a data builder that creates a fresh user through the API. Avoid hard-coded shared records in test files. Clean up created users in a fixture teardown so the database does not fill up over time.
219

How do you run one group of tests serially while the rest of the suite runs in parallel?

Save to ▾
Show answer
Put the group in its own project with fullyParallel: false and limit it to one worker, which recent versions allow with a workers setting on the project, or run it in a separate CI step with --project=serial --workers=1. Inside a file, test.describe.configure({ mode: 'serial' }) keeps order. Use project dependencies if the group must run after the parallel tests.
220

How do you add build details such as the commit and environment to the test report?

Save to ▾
Show answer
Set the metadata field in playwright.config, for example metadata: { commit: process.env.GIT_SHA, env: process.env.TEST_ENV }, which reporters can read and recent HTML reports show. Inside tests use testInfo.annotations.push for per-test details like a ticket id. Custom reporters can read config.metadata in onBegin and include it in messages or uploads.
221

What does the JSON reporter output and how would you use it?

Save to ▾
Show answer
The JSON reporter writes the full suite tree with every test, its status, retries, duration, errors and attachment paths into one file. Teams parse it in scripts to post a summary to chat, to collect flaky tests that passed on retry, or to push results to a database for trends. Set outputFile so the JSON does not mix with console output.
222

What does outputDir contain and why does it matter in parallel runs?

Save to ▾
Show answer
outputDir, test-results by default, holds per-test artifacts such as screenshots, videos and traces. Playwright creates a separate subfolder for each test and retry, so parallel workers do not overwrite each other's files. testInfo.outputPath gives you a safe path inside it for your own files. It is cleared at the start of every run, so do not store anything permanent there.
223

Tell me about a time you reduced pipeline time with parallel runs or sharding.

Save to ▾
Show answer
Situation: our regression suite took 55 minutes, so developers skipped it. Task: bring it under 15. Action: I removed shared test data so fullyParallel could be turned on, split the three slowest files, and ran four shards with blob reports merged into one HTML report. Result: the suite finished in 12 minutes and it became a required check on every pull request.
224

What does eslint-plugin-playwright help with?

Save to ▾
Show answer
It is a lint plugin with rules for common Playwright mistakes, such as missing await on assertions, leftover test.only or page.pause, forced clicks, fixed waits with waitForTimeout and element handles instead of locators. Running it in pull requests stops bad patterns before review. Teams usually enable the recommended rule set and add a few stricter rules over time.
225

A test fails with 'Target page, context or browser has been closed'. What usually causes it?

Save to ▾
Show answer
Most often an action was not awaited, so the test finished and Playwright closed the page while the action was still running. Other causes are a test timeout closing the page, the code closing the page or context itself, or the browser crashing. Check the stack for the missing await, look at whether a timeout happened first, and turn on the lint rule for floating promises.
226

How do you test that the app retries a request that failed the first time?

Save to ▾
Show answer
Register a route with a counter: on the first call use route.fulfill with status 503, and on later calls fulfil with a success body. Perform the action, then assert the UI shows the final data and the counter equals 2. You can also check that no error message flashed. This proves the retry logic works without waiting for a real outage.

Frequently asked questions

Who should practise medium Playwright Testing questions?

Anyone with some hands-on practice: most real interview time is spent on medium questions like these.

How should I practise these?

Answer each question aloud before opening the answer, mark it as solved only when your answer matches the key idea, and save misses to Last-Minute Revision.

Other difficulty levels