Home / Blog / 35 Playwright Interview Questions (With Answers)
35 Playwright Interview Questions (With Answers)
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 binariesFor TypeScript/JavaScript:
npm init playwright@latestThis 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.webkit5. 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 session6. 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:
- Tied to semantics, not implementation — survives CSS class changes
- Forces developers to use semantic HTML (accessibility benefit)
- 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:noneorvisibility: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 hiddenPlaywright 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 == 20023. 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 point25. 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.zipQA 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?
| Factor | Playwright | Cypress |
|---|---|---|
| Multi-browser | Yes (Chrome, Firefox, WebKit) | Chrome/Edge only (Firefox beta) |
| Language | JS, TS, Python, Java, C# | JavaScript/TypeScript only |
| Multi-tab | Yes | Limited |
| Iframe support | Native | Workaround needed |
| API testing | Built-in | Via cy.request() |
| Speed | Faster (parallel by default) | Slower (single-tab serial) |
| Learning curve | Moderate | Easier 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?
- Never use
time.sleep()— useexpect()assertions with built-in retry - Use
data-testidattributes — stable locators immune to CSS refactors - Set appropriate timeouts — increase per-test timeout for slow environments
- Enable retries —
retries: 2in config reruns failed tests up to 2 times - Use Trace Viewer to investigate intermittent failures
- Mock external dependencies —
page.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.
Related Posts
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 →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 →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 →