Below is a full set of 100 interview questions with answers, organized by topic. Each answer is written to be interview-ready.
Section 1: Fundamentals & Architecture (Q1–Q15)
Q1. What is Playwright?
Playwright is an open-source end-to-end testing framework by Microsoft that drives Chromium, Firefox, and WebKit through a single API. It supports auto-waiting, network interception, multiple browser contexts, and cross-language bindings including TypeScript.
Q2. Why use TypeScript with Playwright?
TypeScript gives static typing for locators, fixtures, and page objects, catching errors at compile time. It also provides IDE autocompletion for the Playwright API and makes refactoring safer in large suites.
Q3. What are the core components of Playwright?
Browser, BrowserContext, Page, Locator, Frame, Request/Response, and the Test Runner (@playwright/test).
Q4. Difference between Playwright and Selenium?
Playwright communicates with browsers via the DevTools protocol and its own patched builds, giving auto-waiting, network interception, and multi-context isolation out of the box. Selenium relies on WebDriver and generally requires explicit waits and separate drivers per browser.
Q5. What is auto-waiting?
Before performing an action, Playwright waits for the element to be attached, visible, stable, enabled, and to receive events. This eliminates most manual sleep calls.
Q6. What browsers does Playwright support?
Chromium (Chrome/Edge), Firefox, and WebKit (Safari engine).
Q7. What is a BrowserContext?
An isolated session inside a browser with its own cookies, localStorage, and cache. It behaves like an incognito window and is the unit of test isolation.
Q8. Can one browser have multiple contexts?
Yes. One browser instance can host many contexts, each fully isolated, enabling parallel tests and multi-user scenarios.
Q9. What is the difference between page and context?
A context is the session; a page is a single tab within that session. Multiple pages can exist in one context and share cookies.
Q10. What is a Locator?
A Locator is a lazy reference to an element that is re-resolved on every action, so it never goes stale after a re-render.
Q11. Difference between Locator and ElementHandle?
Locators are auto-waiting, re-resolved, and preferred. ElementHandles are static references that can become detached and are considered legacy.
Q12. What test runners can be used with Playwright?
The official @playwright/test runner, plus Jest, Mocha, Vitest, or Cucumber if configured manually.
Q13. What is playwright.config.ts?
The central configuration file defining test directory, timeouts, retries, reporters, projects, and global setup/teardown.
Q14. How do you run a single test file?npx playwright test tests/login.spec.ts
Q15. How do you run tests in headed mode?npx playwright test --headed
Section 2: Locators & Selectors (Q16–Q30)
Q16. Why prefer getByRole() over CSS selectors?
It targets elements by ARIA role and accessible name, matching how users perceive the page. It survives DOM and class-name refactoring.
Q17. List Playwright’s built-in locators.getByRole, getByText, getByLabel, getByPlaceholder, getByAltText, getByTitle, getByTestId, and locator() for CSS/XPath.
Q18. How do you locate by test ID?page.getByTestId('submit-btn') — the attribute is configurable via testIdAttribute in config.
Q19. Difference between getByText and getByRole?getByText matches visible text; getByRole matches semantic role plus optional name. Role-based is more robust.
Q20. How do you chain locators?page.getByRole('row').filter({ hasText: 'Alice' }).getByRole('button', { name: 'Edit' })
Q21. What does .filter({ hasText }) do?
Narrows a locator to elements containing the given text.
Q22. How do you select the nth element?locator.nth(0) for the first, locator.first(), locator.last().
Q23. What is locator.all()?
Returns an array of all matching elements resolved at call time — useful for iteration but not for auto-waiting.
Q24. How do you handle strict mode violations?
Make the locator more specific, or use .first(), .nth(), or .filter().
Q25. What is strict mode?
Playwright throws if a locator resolves to more than one element during an action, preventing ambiguous interactions.
Q26. Can you use XPath?
Yes: page.locator('xpath=//button[@id="save"]'), though it’s discouraged.
Q27. How do you locate inside an iframe?page.frameLocator('#frame').getByRole('button')
Q28. How do you locate inside shadow DOM?
Playwright pierces open shadow DOM automatically with CSS locators.
Q29. What is locator.waitFor()?
Waits for the locator to reach a state: attached, detached, visible, or hidden.
Q30. How do you count matching elements?await locator.count()
Section 3: Actions & Assertions (Q31–Q45)
Q31. How do you click an element?await page.getByRole('button', { name: 'Submit' }).click()
Q32. Difference between fill() and type()?fill() sets the value directly and is fast; type() (deprecated in favor of pressSequentially) simulates keystrokes.
Q33. How do you handle a dropdown?await page.getByLabel('Country').selectOption('IN')
Q34. How do you check a checkbox?await page.getByLabel('Terms').check()
Q35. How do you hover?await page.getByText('Menu').hover()
Q36. How do you drag and drop?await page.locator('#src').dragTo(page.locator('#dst'))
Q37. How do you upload a file?await page.getByLabel('Upload').setInputFiles('path/to/file.pdf')
Q38. How do you handle a native dialog?
typescript
page.on('dialog', async d => { await d.accept(); });
Q39. Difference between expect(locator).toBeVisible() and locator.isVisible()?expect retries until timeout; isVisible() returns immediately and does not wait.
Q40. What is web-first assertion?
An assertion that retries automatically, e.g. await expect(locator).toHaveText('Done').
Q41. How do you assert a URL?await expect(page).toHaveURL(/dashboard/)
Q42. How do you assert a title?await expect(page).toHaveTitle('Home')
Q43. How do you assert an attribute?await expect(locator).toHaveAttribute('disabled', '')
Q44. How do you assert element count?await expect(locator).toHaveCount(3)
Q45. What is expect.soft()?
A soft assertion that records failure but continues the test instead of aborting.
Section 4: Waits & Timing (Q46–Q55)
Q46. Why is page.waitForTimeout() discouraged?
It introduces fixed delays, making tests slow and flaky. Prefer web-first assertions or waitFor.
Q47. How do you wait for a network response?await page.waitForResponse(r => r.url().includes('/api/users') && r.status() === 200)
Q48. How do you wait for navigation?await page.waitForURL('**/dashboard')
Q49. How do you wait for load state?await page.waitForLoadState('networkidle')
Q50. What are the load states?load, domcontentloaded, and networkidle.
Q51. What is a floating promise?
An async call without await, causing the next line to run before the action completes — the top cause of flakiness.
typescript
page.getByRole('button').click(); // ❌ floating
await page.getByRole('button').click(); // ✅
Q52. How do you set a custom timeout for one action?await locator.click({ timeout: 10000 })
Q53. How do you override the global timeout?
Set timeout in playwright.config.ts, or pass --timeout on the CLI.
Q54. What is the default test timeout?
30 seconds.
Q55. What is the default assertion timeout?
5 seconds, configurable via expect.timeout.
Section 5: Network & API Testing (Q56–Q65)
Q56. How do you intercept a request?
typescript
await page.route('**/api/users', route => route.fulfill({ status: 200, body: '[]' }));
Q57. How do you abort a request?await page.route('**/*.png', route => route.abort())
Q58. How do you modify a response?
Fetch the original with route.fetch(), modify the body, then route.fulfill().
Q59. Difference between page.request and request fixture?page.request shares the page’s cookie jar (authenticated as the user); the request fixture has its own isolated cookie jar.
Q60. How do you mock an API response?await page.route('**/api/login', r => r.fulfill({ json: { token: 'abc' } }))
Q61. How do you wait for multiple responses?await Promise.all([page.waitForResponse(...), page.click(...)])
Q62. How do you test a REST API directly?const res = await request.get('/users'); expect(res.ok()).toBeTruthy();
Q63. Can Playwright test GraphQL?
Yes, via request.post() with a JSON query body, or by intercepting the endpoint.
Q64. How do you record HAR?await page.routeFromHAR('trace.har', { update: true }) or via config recordHar.
Q65. How do you replay a HAR?await page.routeFromHAR('trace.har')
Section 6: Authentication & State (Q66–Q72)
Q66. How do you reuse authentication across tests?
Save storageState once during setup, then reference it via test.use({ storageState: 'auth.json' }).
Q67. How do you save storage state?await context.storageState({ path: 'auth.json' })
Q68. What does storageState contain?
Cookies and localStorage origins.
Q69. How do you run a setup project?
Define a project named setup in config and add dependencies: ['setup'] to test projects.
Q70. How do you log in via API instead of UI?
Use the request fixture to POST credentials, then save the resulting storage state.
Q71. How do you test multiple users in parallel?
Create separate contexts with different storage states.
Q72. How do you clear cookies mid-test?await context.clearCookies()
Section 7: Fixtures & Test Structure (Q73–Q82)
Q73. What is a fixture?
A reusable setup/teardown unit injected into tests, replacing beforeEach boilerplate.
Q74. How do you define a custom fixture?
typescript
export const test = base.extend<{ loginPage: LoginPage }>({
loginPage: async ({ page }, use) => {
await use(new LoginPage(page));
},
});
Q75. Difference between test and test.extend?test is the base runner; test.extend creates a customized runner with additional fixtures.
Q76. What are worker-scoped fixtures?
Fixtures defined with { scope: 'worker' } that are created once per worker rather than per test.
Q77. How do you override a fixture in one file?test.use({ storageState: 'admin.json' }) at the top of the file.
Q78. What does beforeAll do?
Runs once before all tests in a file.
Q79. What does beforeEach do?
Runs before every test in a file.
Q80. What is test.describe?
Groups related tests and can apply hooks or test.use at the group level.
Q81. What is test.step?
A labeled sub-section inside a test that appears in reports, improving readability.
Q82. How do you skip a test?test.skip(), test.skip(condition), or test.fixme().
Section 8: Page Object Model & TypeScript (Q83–Q90)
Q83. What is the Page Object Model?
A design pattern where each page is represented by a class encapsulating locators and actions.
Q84. How do you type a page object in TypeScript?
typescript
export class LoginPage {
constructor(private readonly page: Page) {}
readonly username = this.page.getByLabel('Username');
async login(u: string, p: string): Promise<DashboardPage> {
await this.username.fill(u);
await this.page.getByLabel('Password').fill(p);
await this.page.getByRole('button', { name: 'Sign in' }).click();
return new DashboardPage(this.page);
}
}
Q85. Why should navigation methods return the next page object?
It enforces flow correctness at compile time and chains steps fluently.
Q86. How do you share page objects across tests?
Expose them as fixtures in a custom test.extend.
Q87. What is as const used for in Playwright tests?
Freezing literal types, e.g. role arrays or config maps, so TypeScript narrows them correctly.
Q88. How do you type a locator array?const rows: Locator[] = await page.getByRole('row').all();
Q89. How do you type a network response body?const data = await res.json() as UserDto; or use a Zod schema for runtime validation.
Q90. Should page objects contain assertions?
Prefer keeping assertions in tests; page objects should expose state and actions. Some teams allow lightweight assertions in page objects for reuse.
Section 9: Debugging & Reporting (Q91–Q95)
Q91. How do you debug a failing test?
Run with --debug (Playwright Inspector), --headed, or use page.pause().
Q92. What is the Trace Viewer?
A tool that replays a test with DOM snapshots, network, console, and source — enabled via trace: 'on-first-retry'.
Q93. How do you enable tracing?
In config: use: { trace: 'on' } or per context: context.tracing.start({ screenshots: true, snapshots: true }).
Q94. How do you view a trace?npx playwright show-trace trace.zip
Q95. What reporters ship with Playwright?list, line, dot, html, json, junit, and blob.
Section 10: CI/CD & Best Practices (Q96–Q100)
Q96. How do you run Playwright in CI?
Install browsers with npx playwright install --with-deps, then run npx playwright test. Use the official Docker image or GitHub Action.
Q97. How do you parallelize tests?
Playwright parallelizes by file by default. Use fullyParallel: true for test-level parallelism and configure workers.
Q98. How do you handle retries?
Set retries: 2 in config or --retries=2. Combine with trace: 'on-first-retry' for diagnosis.
Q99. How do you shard tests across CI machines?npx playwright test --shard=1/4
Q100. What are the top best practices for Playwright + TypeScript?
- Always
awaitasync calls (avoid floating promises). - Prefer role/test-id locators over CSS/XPath.
- Use web-first assertions instead of manual waits.
- Isolate tests with contexts and storage state.
- Encapsulate pages in typed page objects.
- Enable traces on retry.
- Keep tests independent and parallel-safe.
- Validate API responses with schemas (e.g., Zod).
- Run in CI with sharding and retries.
- Treat flakiness as a bug, not noise.
100 Advanced Playwright + TypeScript Interview Questions and Answers
Below is a full set of 100 advanced interview questions with answers, assuming you have already mastered the fundamentals covered in the previous set. Each answer is written to be interview-ready and focuses on production-grade patterns, edge cases, and architectural decisions.
Section 1: Advanced Locators & Auto-Waiting (Q1–Q10)
Q1. How does Playwright’s auto-waiting actually work under the hood?
Playwright injects an actionability check before every interaction. The element must pass five conditions: attached to DOM, visible, stable (not animating), enabled, and receiving events (not obscured). Each check retries until timeout. This is implemented via injected scripts that poll the element state and a set of heuristics comparing bounding boxes across animation frames.
Q2. What causes “element is not stable” errors and how do you fix them?
The element’s bounding box changed between two consecutive animation frames, typically due to CSS animations, transitions, or layout shifts. Fixes: disable animations in config (animations: 'disabled'), wait for a stable parent, or target a non-animated ancestor.
Q3. When does getByRole fail to find an element that exists?
When the element lacks an implicit or explicit ARIA role, or when its accessible name is computed from hidden content. Also, custom components that render interactive elements without proper roles (e.g., a <div> styled as a button without role="button") won’t be found.
Q4. How do you handle a locator that matches multiple elements intentionally?
Use .filter(), .nth(), or iterate with .all(). For lists, prefer scoping: page.getByRole('row').filter({ hasText: 'Alice' }).getByRole('button'). Never rely on .first() in production tests; it’s a flakiness signal.
Q5. What is the difference between locator.waitFor({ state: 'attached' }) and locator.waitFor({ state: 'visible' })?attached means the element exists in the DOM even if hidden. visible means it has a non-empty bounding box and is not display: none or visibility: hidden. For interaction, you almost always want visible.
Q6. How do you locate an element by its text that is split across child elements?getByText normalizes whitespace and matches against the element’s full text content, so <span>Hello <b>World</b></span> is matched by getByText('Hello World'). For partial matches, pass { exact: false } (default) or use a regex.
Q7. What is a “strict mode violation” and when is it desirable?
Playwright throws when a locator resolves to more than one element during an action. This is desirable because it forces you to write specific locators. To opt out for intentional multi-element operations, use .all(), .nth(), or .first() with explicit intent.
Q8. How do you handle elements inside closed shadow DOM?
You cannot pierce closed shadow DOM from the page context. Options: expose a test hook on the component, use evaluate to access the shadow root if the host exposes it, or switch to component testing where you mount the component directly.
Q9. What is the “receives events” check and when does it cause false negatives?
Playwright checks that the element at the click point is the target or a descendant. It fails when an overlay, tooltip, or invisible element intercepts the click. Fix: dismiss the overlay, use force: true (last resort), or click the actual receiving element.
Q10. How do you write a locator that survives a component library upgrade?
Avoid internal class names, data attributes not part of the public contract, and deeply nested selectors. Prefer role-based locators, accessible names, and data-testid attributes that the team commits to maintaining.
Section 2: Network Interception & API Testing (Q11–Q25)
Q11. What is the difference between route.fulfill(), route.continue(), and route.fetch()?fulfill() returns a mock response without hitting the server. continue() lets the request proceed, optionally with modified headers, method, or body. fetch() sends the request to the real server and returns the response so you can modify it before fulfilling .
Q12. How do you modify a response body while preserving the original headers and status?
Use route.fetch(), parse the response, modify the data, then route.fulfill({ response, body: JSON.stringify(modified) }). Passing the original response object preserves headers and status .
Q13. What is the page.request vs request fixture distinction in advanced scenarios?page.request shares the page’s cookie jar and is authenticated as the current user. The request fixture has its own isolated cookie jar, making it suitable for admin operations, setup/teardown, or unauthenticated API checks .
Q14. How do you record and replay network traffic with HAR files?
Record with routeFromHAR(path, { update: true }) during development. Replay with routeFromHAR(path) in tests. HAR files capture request/response headers, bodies, and timings, providing realistic test data without hitting live services .
Q15. How do you simulate network throttling in Playwright?
Use CDP: client.send('Network.emulateNetworkConditions', { offline: false, downloadThroughput: speed * 1024 / 8, uploadThroughput: speed * 1024 / 8, latency: ms }). This lets you test loading states, timeouts, and retry logic .
Q16. What is the “Cookie header override” caveat with route.continue()?
You cannot override the Cookie header via route.continue({ headers }). The browser fills cookies from its own cookie jar. To change cookie state, use context.addCookies() or modify storageState .
Q17. How do you test a WebSocket connection in Playwright?
Playwright doesn’t have native WebSocket mocking, but you can: (a) evaluate window.ws.readyState in the page context , (b) use MSW (Mock Service Worker) to intercept WebSocket handlers , or (c) expose the socket on window and assert its state.
Q18. How do you validate API response schemas in Playwright tests?
Use Zod, AJV, or the playwright-schema-validator plugin. Fetch the response, then run schema.parse(await response.json()). This catches contract violations that status code checks miss .
Q19. How do you handle OAuth/SSO flows without driving the IdP UI every time?
Best practice: perform token exchange via API in a setup project, inject tokens into localStorage via addInitScript, navigate to the app, then save storageState. Keep one slow, real SSO test tagged @auth-integration for nightly runs .
Q20. How do you test retry logic with Playwright’s route mocking?
Set up a counter in the route handler. Return 503 for the first N requests, then 200. Assert the UI eventually shows the success state and that the counter equals the expected retry count .
Q21. What is the maxRedirects option in route.fetch() and when would you use it?
It controls how many redirects route.fetch() follows before returning the response. Useful for auth flows that redirect through multiple endpoints; you can assert on the final response or simulate a redirect loop failure.
Q22. How do you abort requests selectively by resource type?
Use route.request().resourceType() inside the handler: if (['image', 'font'].includes(type)) route.abort(); else route.continue();. This speeds up tests and lets you test offline/blocked-resource behavior .
Q23. What is route.fallback() and when do you need it?fallback() continues to the next matching route handler instead of fulfilling immediately. Use it when you have multiple route handlers for the same URL and want conditional logic layered across them.
Q24. How do you test that your app handles a 500 error gracefully?
Intercept the API route, call route.fetch() to get the real response (or skip it), then route.fulfill({ status: 500, body: ... }). Assert the error UI appears .
Q25. Can you intercept requests made by a service worker?
By default, service workers bypass Playwright’s route interception. Set serviceWorkers: 'block' in context options to force all requests through the page’s network stack, making them interceptable .
Section 3: Advanced Fixtures & Test Architecture (Q26–Q40)
Q26. What is the difference between test.extend and mergeTests?test.extend creates a new test object with additional fixtures, typically in one file. mergeTests combines multiple extended test objects into one, useful for composing fixtures from different domains (API, UI, auth) .
Q27. How do you create a fixture that depends on another fixture?
Declare the dependency in the fixture function’s argument destructuring: myFixture: async ({ page, request }, use) => { ... }. Playwright resolves dependencies automatically.
Q28. What is the “automatic fixture” pattern and when is it useful?
An automatic fixture runs even if not explicitly requested, via test.extend({ autoFixture: [async ({}, use) => { ... }, { auto: true }] }). Useful for global setup like console error monitoring or network logging.
Q29. How do you scope a fixture to run once per worker instead of per test?
Use { scope: 'worker' } in the fixture options. The fixture is initialized when the worker starts and torn down when it exits, ideal for expensive resources like database connections.
Q30. What is the “boxed fixture” pattern for safe teardown?
Wrap the value in an object with a dispose method so teardown always runs even if the test fails mid-use. Playwright 1.50+ supports explicit resource management with await using.
Q31. How do you share authentication state across parallel workers?
Create the auth state file in a setup project, then reference it via test.use({ storageState: 'auth/user.json' }). All workers read the same file, avoiding redundant login flows .
Q32. What is a “fixture override” and when should you avoid it?
Overriding a built-in fixture (like page) changes behavior for all tests in scope. Avoid it unless you need global instrumentation; prefer creating a new fixture that wraps the original.
Q33. How do you pass parameters to fixtures from test files?
Use a test.use({ myOption: value }) at the file or describe level, and declare the option as a fixture that reads the value. This is the “option fixture” pattern.
Q34. What is the difference between test.describe.serial and test.describe.parallel?serial runs tests in order and stops on first failure, useful for dependent flows. parallel is the default and runs tests concurrently within the describe block.
Q35. How do you create a fixture that provides a typed page object?
typescript
export const test = base.extend<{ loginPage: LoginPage }>({
loginPage: async ({ page }, use) => {
await use(new LoginPage(page));
},
});
This gives type-safe access to loginPage in every test .
Q36. What is the “fixture composition” pattern for cross-cutting concerns?
Layer fixtures: base.extend(authFixtures).extend(apiFixtures).extend(loggingFixtures). Each layer adds one concern, keeping fixtures focused and testable.
Q37. How do you clean up test data in a worker-scoped fixture?
Create a unique namespace per worker (e.g., worker-${workerInfo.workerIndex}), seed data in the fixture setup, and delete all data matching that namespace in teardown.
Q38. What is the “test data factory” pattern and how does it integrate with fixtures?
A factory is a function that creates entities via API with unique names. Expose it as a fixture that also tracks created entities and cleans them up in teardown.
Q39. How do you handle flaky tests at the framework level?
Set retries: 2 in config, trace: 'on-first-retry' for diagnosis, and use test.fail() to mark known flaky tests. Quarantine persistent flakes in a separate project and fix root causes rather than relying on retries.
Q40. What is test.step and how does it improve debuggability?test.step('Login as admin', async () => { ... }) creates labeled sections in the trace viewer and reports. Steps can be nested and are useful for documenting multi-phase tests.
Section 4: Component Testing (Q41–Q50)
Q41. What is Playwright component testing and how does it differ from E2E?
Component testing mounts individual UI components in a real browser without the full application. It tests rendering, props, and interactions in isolation, running faster than E2E .
Q42. Which frameworks does Playwright component testing support?
React (@playwright/experimental-ct-react), Vue, and Svelte. It requires a separate config (playwright-ct.config.ts) and uses the mount fixture .
Q43. What is the “story” concept in Playwright’s newer component testing model?
A story is a wrapper that embeds a component in a specific scenario (props, providers, mock data). Stories live in *.story.tsx files and are rendered by a “gallery” page .
Q44. How does the mount fixture work?const component = await mount(<App />) renders the component in the gallery and returns a Locator scoped to the component root. You can then use component.getByRole(...) for interactions .
Q45. How do you test a component that makes API calls?
Use route interception in the component test just like E2E: page.route('**/api/data', ...). Alternatively, pass mock data via props or a provider wrapper.
Q46. Can you use Playwright’s trace viewer with component tests?
Yes. Enable trace: 'on-first-retry' in playwright-ct.config.ts. The trace shows component interactions, DOM snapshots, and network calls .
Q47. What is the “gallery” in Playwright component testing?
A single page served by your dev server that exposes window.mount() and window.unmount(). It discovers *.story.* files and renders the selected story into a root element .
Q48. How do you test component state changes (e.g., toggles, accordions)?
Mount the component, assert initial state via toContainText or toHaveAttribute, perform the interaction, then assert the new state. The trace captures each step for debugging .
Q49. What are the limitations of Playwright component testing?
It’s experimental; the API may change. It doesn’t test routing, full-page layout, or backend integration. You still need E2E tests for those concerns .
Q50. When would you choose component testing over E2E?
For design system components, complex form controls, and UI states that are hard to trigger in a full app (e.g., error states, loading skeletons). Component tests run faster and are less flaky.
Section 5: Visual Regression & Accessibility (Q51–Q60)
Q51. How does toHaveScreenshot() work?
On first run, it captures a baseline PNG. On subsequent runs, it compares the current screenshot pixel-by-pixel against the baseline within configured tolerances .
Q52. What are the key config options for reducing false positives in visual tests?animations: 'disabled' freezes CSS animations, caret: 'hide' hides text cursors, scale: 'css' normalizes DPI differences, and maxDiffPixelRatio: 0.01 allows 1% pixel difference .
Q53. How do you mask dynamic content in a screenshot?
Pass a mask array of locators: toHaveScreenshot({ mask: [page.locator('.timestamp')] }). The masked areas are rendered as solid rectangles in both baseline and comparison .
Q54. How do you update visual baselines when the UI changes intentionally?
Run npx playwright test --update-snapshots and commit the new PNGs. Review the diff before committing to ensure changes are intentional .
Q55. What is the “stability gate” pattern in visual testing?
Before taking a screenshot, assert that a critical element is visible (e.g., a heading or data row). This ensures the page has fully rendered and prevents capturing a blank or mid-render state .
Q56. How do you test accessibility with Playwright?
Use @axe-core/playwright: const results = await new AxeBuilder({ page }).analyze(); expect(results.violations).toEqual([]);. Run it as a separate test or integrate into existing tests.
Q57. What are the limitations of visual regression testing?
It catches pixel changes but not semantic regressions (e.g., a button that looks right but has wrong behavior). It can be flaky across OS/browser versions. It doesn’t test responsiveness unless you run multiple viewports.
Q58. How do you handle visual tests across different browsers?
Playwright generates separate baselines per browser and platform by default (e.g., screenshot-chromium-darwin.png). Commit all variants or run visual tests on a single canonical browser in CI.
Q59. What is toMatchSnapshot() vs toHaveScreenshot()?toMatchSnapshot() compares arbitrary data (strings, objects). toHaveScreenshot() compares rendered page images. Use the former for DOM snapshots or API responses.
Q60. How do you test responsive design with Playwright?
Create projects with different viewport sizes (devices['iPhone 13'], devices['Desktop Chrome']), or use page.setViewportSize() within a test. Assert layout changes via element visibility or position.
Section 6: Debugging & Diagnostics (Q61–Q70)
Q61. What is the Trace Viewer and what does it capture?
A timeline of every action, DOM snapshot before/after each step, network requests, console logs, and source code. Enable via trace: 'on-first-retry' and view with npx playwright show-trace trace.zip .
Q62. How do you use page.pause() effectively?
It opens the Playwright Inspector and pauses execution, letting you step through actions, inspect locators, and resume. Use it for interactive debugging, not in committed tests.
Q63. What is the “floating promise” pattern and how do you detect it?
Calling an async method without await: page.click('button') instead of await page.click('button'). ESLint rules (@typescript-eslint/no-floating-promises) and Playwright’s own warnings help detect it.
Q64. How do you debug a test that fails only in CI?
Enable trace: 'on-first-retry', run with --workers=1 to rule out concurrency, check for timezone/locale differences, and compare CI viewport to local. The trace viewer is the primary tool.
Q65. What does the “Call log” in the trace viewer show?
A chronological list of Playwright API calls with their parameters, return values, and timing. It helps identify which action hung or returned unexpected data.
Q66. How do you capture console errors during a test?
typescript
const errors: string[] = [];
page.on('console', msg => {
if (msg.type() === 'error') errors.push(msg.text());
});
// after test:
expect(errors).toEqual([]);
Q67. What is the “Snapshot” tab in the trace viewer?
A DOM snapshot of the page at the selected point in the timeline. You can inspect elements, see computed styles, and verify what the page looked like when an action ran.
Q68. How do you debug locator resolution?
Use page.locator('...').highlight() to visually highlight matched elements, or inspect the locator in the Playwright Inspector. The trace viewer shows which element each action targeted.
Q69. What is the “Network” tab in the trace viewer?
A list of all network requests with headers, bodies, timings, and status codes. Useful for diagnosing API failures, slow responses, or unexpected requests.
Q70. How do you record a video of a test run?
Set video: 'retain-on-failure' or 'on' in config. Videos are attached to the test result and viewable in the HTML report.
Section 7: Performance & Advanced Configuration (Q71–Q80)
Q71. How do you parallelize tests across files vs within files?
By default, Playwright parallelizes by file. fullyParallel: true enables test-level parallelism within files, requiring tests to be independent .
Q72. What is sharding and how do you use it?npx playwright test --shard=1/4 splits tests across 4 machines. Each shard runs a deterministic subset, enabling horizontal scaling in CI.
Q73. How do you configure multiple projects for different browsers and devices?
In playwright.config.ts, define a projects array with different use options: { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, etc. Tests run against all projects by default.
Q74. What is the webServer config option?
It starts a dev server before tests and shuts it down after. Configure with command, url, and reuseExistingServer: !process.env.CI .
Q75. How do you set per-project timeouts?
Add timeout to the project’s use object, or override at the test level with test.setTimeout(ms).
Q76. What is globalSetup vs a setup project?globalSetup is a single function that runs once before all projects. A setup project is a full Playwright project that runs before dependent projects and can use fixtures, reporters, and traces.
Q77. How do you run tests only on changed files in CI?
Use --only-changed (Playwright 1.42+) or integrate with your CI’s change detection to pass specific file paths.
Q78. What is the blob reporter and when is it useful?
It produces a report.zip that can be merged from multiple shards into a single HTML report using npx playwright merge-reports.
Q79. How do you configure retries per project?
Add retries: 2 to the project’s use object. Different projects can have different retry policies (e.g., flaky visual tests get more retries).
Q80. What is expect.configure() and when would you use it?
It creates a customized expect with default options: const slowExpect = expect.configure({ timeout: 10000 }). Useful for slow-running assertions in specific test suites.
Section 8: CI/CD & Production Patterns (Q81–Q90)
Q81. How do you cache Playwright browsers in GitHub Actions?
Cache ~/.cache/ms-playwright keyed on the Playwright version from package.json. This avoids re-downloading browsers on every run.
Q82. How do you handle test data isolation in parallel CI runs?
Use unique prefixes per worker/run (e.g., run-${process.env.GITHUB_RUN_ID}-worker-${workerIndex}). Clean up in worker teardown.
Q83. What is the “smoke test” project pattern?
A subset of critical tests tagged @smoke that run on every PR. Full suite runs on merge to main or nightly.
Q84. How do you report test results to a dashboard like TestDino or Allure?
Add the reporter to playwright.config.ts and set the API token via environment variable. Results stream during the run .
Q85. How do you handle secrets in CI?
Use GitHub Actions secrets or your CI’s secret store. Never commit credentials. For auth state, generate it in a setup step and pass the file path via env var.
Q86. What is the “merge reports” workflow for sharded runs?
Each shard uploads a blob report. A final job downloads all blobs and runs npx playwright merge-reports --reporter=html ./blobs.
Q87. How do you prevent flaky tests from blocking merges?
Run flaky tests in a separate non-blocking job, quarantine persistent flakes, and use test.fixme() with a tracking issue. Retries buy time but don’t fix root causes.
Q88. How do you test against a staging environment with real data?
Use environment-specific configs (baseURL from env), seed test data via API in setup, and clean up in teardown. Never run destructive tests against staging data.
Q89. What is the “canary” pattern for test selection?
Run a small, fast subset of tests on every commit, a medium suite on PR, and the full suite nightly. This balances feedback speed with coverage.
Q90. How do you version and migrate visual baselines?
Store baselines in the repo, update them when UI changes are intentional, and review diffs in PR. Use a dedicated branch for baseline updates to avoid noise.
Section 9: TypeScript-Specific Patterns (Q91–Q100)
Q91. How do you type a page object that returns the next page?
typescript
async login(user: string, pass: string): Promise<DashboardPage> {
// ... actions
return new DashboardPage(this.page);
}
This creates a fluent, type-safe navigation chain .
Q92. What is the satisfies operator and how does it help Playwright tests?const config = { retries: 2 } satisfies PlaywrightTestConfig validates the shape without widening the type. Useful for config objects where you want both validation and literal inference.
Q93. How do you type network response bodies safely?
Define a Zod schema and parse: const data = UserSchema.parse(await response.json()). This gives you a typed, validated object at runtime.
Q94. What is the Locator type and how do you store locators in a page object?Locator is the return type of page.locator() and page.getByRole(). Store them as readonly class properties initialized in the constructor.
Q95. How do you type a custom fixture that wraps a page object?
typescript
type Fixtures = { loginPage: LoginPage };
export const test = base.extend<Fixtures>({ ... });
The generic parameter gives full type safety for test and test.use.
Q96. What is the Page vs Locator return type distinction?Page represents a tab; Locator represents an element. Page objects typically take Page in the constructor and expose Locator properties.
Q97. How do you use discriminated unions for test data?
typescript
type User = { role: 'admin'; permissions: string[] } | { role: 'viewer' };
This forces exhaustive handling and prevents invalid combinations in test factories.
Q98. What is the as const pattern for locator arrays?const roles = ['button', 'link'] as const preserves literal types, enabling roles.map(r => page.getByRole(r)) with proper narrowing.
Q99. How do you type page.evaluate() return values?const title = await page.evaluate<string>(() => document.title) — pass the expected return type as a generic. For complex objects, define an interface and cast.
Q100. What is the “branded type” pattern for test IDs?
typescript
type TestId = string & { __brand: 'TestId' };
This prevents accidentally passing a raw string where a test ID is expected, catching mix-ups at compile time.
Summary of Advanced Topics Covered
| Section | Focus | Key Patterns |
|---|---|---|
| 1 | Advanced Locators | Auto-waiting internals, strict mode, shadow DOM |
| 2 | Network & API | Route interception, HAR, OAuth bypass, WebSockets |
| 3 | Fixtures | Composition, worker scope, boxed teardown |
| 4 | Component Testing | Mount fixture, stories, gallery |
| 5 | Visual & A11y | Baseline management, masking, axe-core |
| 6 | Debugging | Trace viewer, floating promises, console capture |
| 7 | Performance | Sharding, parallelization, webServer |
| 8 | CI/CD | Caching, secrets, smoke tests, merge reports |
| 9 | TypeScript | Page object typing, Zod validation, branded types |