Home / Blog / 35 Playwright Interview Questions (With Answers)

Interview Prep

35 Playwright Interview Questions (With Answers)

QA Knowledge Hub·2026-04-19·12 min read

Playwright is now the first choice for UI automation at many product companies. If you are interviewing for a QA or SDET role at a startup or tech company in 2026, expect at least 5–8 Playwright questions. These 35 questions cover what actually gets asked.

Basics

1. What is Playwright and how does it differ from Selenium?

Playwright is a browser automation library developed by Microsoft, released in 2020. It supports Chromium, Firefox, and WebKit (Safari engine) from a single API.

Key differences from Selenium:

  • Playwright auto-waits for elements before actions — no explicit wait boilerplate
  • Built-in support for multiple tabs, iframes, file downloads, network interception
  • Tests run in parallel by default with separate browser contexts
  • Official support for TypeScript, JavaScript, Python, Java, C#
  • Playwright Test (the built-in runner) is included; Selenium relies on external runners

Selenium has a larger ecosystem and more legacy usage. Playwright is faster to set up and more reliable for modern SPAs.


2. What languages does Playwright support?

JavaScript/TypeScript, Python, Java, and C#. The JavaScript/TypeScript API is the most mature and most commonly used in interviews.


3. How do you install Playwright for Python?

pip install playwright
playwright install  # Downloads browser binaries

For TypeScript/JavaScript:

npm init playwright@latest

This scaffolds the project with config, example tests, and installs browser binaries.


4. What browsers does Playwright support?

  • Chromium — Chrome and Edge
  • Firefox — Mozilla Firefox
  • WebKit — Safari's browser engine
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()    # Or p.firefox / p.webkit

5. What is the difference between page, browser, and browser_context in Playwright?

  • browser — The browser instance (Chrome/Firefox/WebKit). One per test run unless parallelising.
  • browser_context — An isolated session within a browser. Equivalent to an incognito window. Has its own cookies, localStorage, and auth state. Multiple contexts can run in one browser.
  • page — A single tab within a browser context. This is what you interact with in tests.
browser = p.chromium.launch()
context = browser.new_context()  # Isolated session
page = context.new_page()        # New tab in that session

6. What is the difference between sync_api and async_api in Playwright Python?

sync_api uses synchronous code (blocking calls). async_api uses Python's asyncio for non-blocking execution.

For most test automation: use sync_api. It is simpler and works with Pytest without extra configuration.

Use async_api if you are integrating Playwright into an existing async application or need to run many browsers concurrently.


Locators

7. What is a Playwright locator?

A locator is a lazy reference to an element or set of elements. Unlike Selenium's find_element (which queries the DOM immediately), a locator is evaluated only when an action is performed. This enables Playwright's auto-wait.

# Locator — not evaluated yet
submit_btn = page.locator("button[type='submit']")

# Evaluated + action performed (auto-waits here)
submit_btn.click()

8. What locator strategies does Playwright support?

# CSS selector
page.locator("button.primary")

# Text content (exact or partial)
page.get_by_text("Submit")
page.get_by_text("Submit", exact=True)

# Role (ARIA role — most robust)
page.get_by_role("button", name="Sign in")

# Label (for form elements)
page.get_by_label("Email")

# Placeholder
page.get_by_placeholder("Enter your email")

# Test ID (data-testid attribute — best for automation)
page.get_by_test_id("login-btn")

# XPath
page.locator("//input[@id='email']")

Best practice: Prefer get_by_role and get_by_test_id. They are resilient to CSS changes.


9. What is get_by_role and why is it preferred?

get_by_role locates elements by their ARIA role — how assistive technologies and browsers identify elements semantically.

page.get_by_role("button", name="Submit")
page.get_by_role("textbox", name="Email")
page.get_by_role("checkbox", name="Remember me")
page.get_by_role("link", name="Forgot password?")

It is preferred because:

  1. Tied to semantics, not implementation — survives CSS class changes
  2. Forces developers to use semantic HTML (accessibility benefit)
  3. Closely mirrors how a user identifies elements visually

10. How do you locate elements inside an iframe?

# Get the iframe as a frame locator
frame = page.frame_locator("#payment-iframe")

# Then interact with elements inside it
frame.get_by_label("Card number").fill("4111111111111111")
frame.get_by_label("Expiry date").fill("12/27")

Auto-Wait and Actions

11. What is Playwright's auto-wait mechanism?

Before performing any action, Playwright automatically waits for the element to be:

  • Attached to the DOM
  • Visible (not display:none or visibility:hidden)
  • Enabled (not disabled)
  • Stable (not animating)

This eliminates the need for explicit time.sleep() or WebDriverWait calls in most cases.

# Playwright waits automatically — no explicit wait needed
page.get_by_role("button", name="Submit").click()

12. When do you still need explicit waits in Playwright?

For conditions that auto-wait does not cover:

  • Waiting for a network response after an action
  • Waiting for specific text to appear
  • Waiting for an element count to change
# Wait for a specific URL after form submission
page.wait_for_url("**/dashboard")

# Wait for a specific response
with page.expect_response("**/api/login") as response_info:
    page.get_by_role("button", name="Login").click()
response = response_info.value

# Wait for an element to contain text
expect(page.locator(".status")).to_have_text("Success")

13. What is page.expect_response()?

A context manager that intercepts a network response matching a URL pattern. Useful for validating what the API returned during a UI action.

with page.expect_response("**/api/orders") as response_info:
    page.get_by_role("button", name="Place Order").click()

response = response_info.value
assert response.status == 200
data = response.json()
assert data["status"] == "created"

14. How do you handle file uploads in Playwright?

# Set the file input before clicking (no dialog opens)
page.set_input_files("input[type='file']", "path/to/test-file.pdf")

# Or via locator
page.get_by_label("Upload Document").set_input_files("test.pdf")

15. How do you handle browser dialogs (alert, confirm, prompt)?

# Register handler BEFORE the action that triggers the dialog
page.on("dialog", lambda dialog: dialog.accept())
page.get_by_role("button", name="Delete").click()

# For prompt dialogs, pass the text
page.on("dialog", lambda dialog: dialog.accept("My input text"))

Assertions

16. What is Playwright's built-in assertion library?

expect() from playwright.sync_api (Python) or @playwright/test (TypeScript). These are web-first assertions that retry until the condition is met or times out.

from playwright.sync_api import expect

expect(page.get_by_role("heading")).to_have_text("Dashboard")
expect(page.locator(".cart-badge")).to_have_text("3")
expect(page.get_by_role("button", name="Submit")).to_be_disabled()
expect(page.locator(".error")).to_be_visible()
expect(page).to_have_url("https://example.com/dashboard")
expect(page).to_have_title("My App - Dashboard")

17. What is the difference between to_be_visible() and to_be_attached()?

  • to_be_visible() — Element is in the DOM AND visible (not hidden via CSS)
  • to_be_attached() — Element is in the DOM, but may be hidden
expect(page.locator(".modal")).to_be_visible()    # Must be visible
expect(page.locator(".hidden-input")).to_be_attached()  # In DOM, may be hidden

Playwright Test (TypeScript/JavaScript)

18. What is Playwright Test?

The built-in test runner that ships with Playwright for TypeScript/JavaScript. It provides:

  • Fixtures (page, browser, context)
  • Parallel execution by default
  • Screenshot on failure
  • HTML report generation
  • expect() assertions
import { test, expect } from '@playwright/test';

test('login test', async ({ page }) => {
  await page.goto('https://example.com/login');
  await page.get_by_label('Email').fill('user@test.com');
  await page.get_by_label('Password').fill('password123');
  await page.get_by_role('button', { name: 'Sign in' }).click();
  await expect(page).to_have_url(/dashboard/);
});

19. What are Playwright fixtures?

Fixtures provide test dependencies (like page, context, browser) and are automatically set up and torn down by the runner.

Built-in fixtures: page, context, browser, browserName, request

Custom fixtures extend built-in ones:

// fixtures.ts
import { test as base } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';

export const test = base.extend<{ loginPage: LoginPage }>({
  loginPage: async ({ page }, use) => {
    const loginPage = new LoginPage(page);
    await loginPage.open();
    await use(loginPage);
  }
});

20. How does Playwright run tests in parallel?

Playwright Test runs test files in parallel by default using worker processes. Each worker gets its own browser context, so tests do not share state.

// playwright.config.ts
export default defineConfig({
  workers: 4,                    // 4 parallel workers
  fullyParallel: true,           // Parallelise within files too
});

Each test in a file can also be parallelised with test.describe.parallel.


Network and API

21. How do you intercept and modify network requests?

# Block all image requests (speeds up tests)
page.route("**/*.{png,jpg,jpeg,gif,svg}", lambda route: route.abort())

# Modify a response (mock API)
def handle_route(route):
    route.fulfill(
        status=200,
        content_type="application/json",
        body='{"status": "ok", "count": 42}'
    )

page.route("**/api/dashboard/stats", handle_route)
page.goto("https://example.com/dashboard")

22. How do you perform API testing with Playwright?

Playwright's APIRequestContext sends HTTP requests without a browser:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    request = p.request.new_context(base_url="https://api.example.com")
    
    response = request.post("/login", data={
        "username": "admin",
        "password": "secret"
    })
    assert response.status == 200
    token = response.json()["token"]
    
    # Use the token for subsequent requests
    response = request.get("/users", headers={"Authorization": f"Bearer {token}"})
    assert response.status == 200

23. What is storage state in Playwright and why is it useful?

Storage state saves browser cookies and localStorage to a file, which can be loaded in subsequent tests to skip login.

# Save auth state after login (run once)
context.storage_state(path="auth/user.json")

# Load auth state in tests (skip login)
context = browser.new_context(storage_state="auth/user.json")

This dramatically speeds up test suites where many tests require authentication.


Debugging

24. How do you debug a Playwright test?

# Run in headed mode (see the browser)
playwright test --headed

# Slow down execution (ms between actions)
playwright test --slow-mo=500

# Open Playwright Inspector (pause and step through)
PWDEBUG=1 pytest test_login.py

# Or add pause in code
page.pause()  # Opens Inspector at this point

25. What is the Playwright Trace Viewer?

A built-in debugging tool that records a trace (screenshots, network requests, DOM snapshots) during test execution and lets you replay it step by step.

# Enable trace recording
playwright test --trace on

# View the trace
playwright show-trace trace.zip

QA use case: When a test fails in CI, download the trace artifact to see exactly what the browser was doing.


26. How do you take screenshots in Playwright?

# Full page screenshot
page.screenshot(path="screenshots/dashboard.png", full_page=True)

# Element screenshot
page.locator(".chart-widget").screenshot(path="chart.png")

# On failure (automatic in Playwright Test config)
# playwright.config.ts: screenshot: 'only-on-failure'

Configuration

27. What does playwright.config.ts contain?

The global configuration for Playwright Test:

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

export default defineConfig({
  testDir: './tests',
  timeout: 30_000,
  retries: 2,                          // Retry failed tests
  workers: 4,
  reporter: [['html'], ['list']],
  use: {
    baseURL: 'https://staging.example.com',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    trace: 'on-first-retry',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox',  use: { ...devices['Desktop Firefox'] } },
    { name: 'mobile',   use: { ...devices['iPhone 14'] } },
  ],
});

28. How do you run tests on mobile viewport?

// playwright.config.ts
projects: [
  { name: 'mobile-chrome', use: { ...devices['Pixel 7'] } },
  { name: 'mobile-safari', use: { ...devices['iPhone 14'] } },
]

devices is a built-in registry of real device presets with correct viewport, user agent, and touch settings.


Scenario Questions

29. How would you test a login flow with Playwright?

def test_valid_login(page):
    page.goto("https://example.com/login")
    page.get_by_label("Email").fill("user@test.com")
    page.get_by_label("Password").fill("secret123")
    page.get_by_role("button", name="Sign in").click()
    
    expect(page).to_have_url(re.compile(r"/dashboard"))
    expect(page.get_by_role("heading", name="Welcome")).to_be_visible()

30. How would you test a multi-step form?

def test_checkout_flow(page):
    # Step 1: Cart
    page.get_by_role("button", name="Checkout").click()
    expect(page.get_by_text("Order Summary")).to_be_visible()
    page.get_by_role("button", name="Continue").click()
    
    # Step 2: Shipping
    page.get_by_label("Full Name").fill("Test User")
    page.get_by_label("Address").fill("123 Main St")
    page.get_by_role("button", name="Continue to Payment").click()
    
    # Step 3: Payment
    frame = page.frame_locator("#payment-frame")
    frame.get_by_label("Card number").fill("4111111111111111")
    page.get_by_role("button", name="Place Order").click()
    
    expect(page.get_by_text("Order Confirmed")).to_be_visible()

31. How would you implement Page Object Model with Playwright (Python)?

# pages/login_page.py
class LoginPage:
    def __init__(self, page):
        self.page = page
        self.email_field = page.get_by_label("Email")
        self.password_field = page.get_by_label("Password")
        self.submit_btn = page.get_by_role("button", name="Sign in")
        self.error_msg = page.locator("[data-testid='error-message']")

    def navigate(self):
        self.page.goto("/login")
        return self

    def login(self, email, password):
        self.email_field.fill(email)
        self.password_field.fill(password)
        self.submit_btn.click()

    def get_error(self):
        return self.error_msg.inner_text()

# tests/test_login.py
def test_login(page):
    login_page = LoginPage(page)
    login_page.navigate()
    login_page.login("user@test.com", "secret")
    expect(page).to_have_url(re.compile(r"/dashboard"))

32. Playwright vs Cypress — when would you choose each?

FactorPlaywrightCypress
Multi-browserYes (Chrome, Firefox, WebKit)Chrome/Edge only (Firefox beta)
LanguageJS, TS, Python, Java, C#JavaScript/TypeScript only
Multi-tabYesLimited
Iframe supportNativeWorkaround needed
API testingBuilt-inVia cy.request()
SpeedFaster (parallel by default)Slower (single-tab serial)
Learning curveModerateEasier for beginners

Choose Playwright for: multi-browser, Python teams, API+UI combined testing, cross-platform. Choose Cypress for: JavaScript teams who prefer integrated dashboard and component testing.


33. How does Playwright handle dynamic content that changes after each load?

Use get_by_role or get_by_text with partial matching, or get_by_test_id if developers add stable data-testid attributes. For content that loads asynchronously:

# Wait for the element to appear and have specific content
expect(page.locator(".price")).to_have_text(re.compile(r"\d+"))

# Wait for a loading spinner to disappear
expect(page.locator(".loading-spinner")).not_to_be_visible()

34. How do you handle test flakiness in Playwright?

  1. Never use time.sleep() — use expect() assertions with built-in retry
  2. Use data-testid attributes — stable locators immune to CSS refactors
  3. Set appropriate timeouts — increase per-test timeout for slow environments
  4. Enable retriesretries: 2 in config reruns failed tests up to 2 times
  5. Use Trace Viewer to investigate intermittent failures
  6. Mock external dependenciespage.route() to intercept flaky third-party calls

35. How do you run Playwright tests in CI (GitHub Actions)?

# .github/workflows/playwright.yml
name: Playwright Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright browsers
        run: npx playwright install --with-deps

      - name: Run tests
        run: npx playwright test

      - name: Upload test report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-report
          path: playwright-report/

The --with-deps flag installs system dependencies (OS libraries) needed for browsers in Ubuntu CI environments.


Interview Tips for Playwright

Know the fundamentals cold: auto-wait, locator priority (get_by_role > get_by_test_id > CSS), and the browser/context/page hierarchy.

Be ready to write code: Most Playwright interviews include a live coding portion — a login test or a product search flow. Practice on playwright.dev/docs/intro.

Compare to Selenium honestly: "Playwright has auto-wait and is faster to set up, but Selenium has wider language support and more established ecosystem." Showing you know both signals maturity.

Recommended Resource

QA Interview Kit

Interview prep kit with real-world QA and API scenarios.

999Get This Guide →

Related Posts

📝
Interview Prep
Apr 2026·12 min read

Behavioral Interview Questions for QA Engineers (With Sample Answers)

Behavioral interview questions specifically for QA and SDET roles — with the STAR method explained and sample answers that actually sound human.

Read article →
📝
Interview Prep
Apr 2026·12 min read

QA Interview Preparation Checklist: Everything You Need to Know

A complete QA interview preparation checklist — what to study, what to practice, and how to approach each round of a QA or SDET interview.

Read article →
📝
Interview Prep
Apr 2026·10 min read

30 Performance Testing Interview Questions and Answers

Performance testing interview questions for QA roles — covering load testing, stress testing, JMeter, key metrics, and how to analyze results.

Read article →