Most Asked Playwright Interview Questions (3-4 Years Exp)
If you have 3-4 years of QA/SDET experience and are preparing for interviews, these are the areas you must master.
Companies are not asking basics anymore. They're testing depth + JavaScript + Architecture.
JavaScript Fundamentals (Very Important)
Playwright is fundamentally a Node.js library. If you have 3β4 years of experience, interviewers will test your raw JavaScript capabilities before diving into automation concepts. You must be comfortable with array manipulation, string manipulation, and error handling.
// Common interview task: Use map/filter to process data
const users = [
{ name: 'Alice', active: true },
{ name: 'Bob', active: false },
{ name: 'Charlie', active: true }
];
// Get names of active users
const activeNames = users
.filter(u => u.active)
.map(u => u.name)
.join(', ');
console.log(activeNames); // "Alice, Charlie"
// Handling Exceptions Properly
try {
await page.getByRole('button', { name: 'Submit' }).click({ timeout: 5000 });
} catch (error) {
console.error('Submission failed, attempting recovery...', error);
await page.screenshot({ path: 'error.png' });
} finally {
console.log('Action complete.');
}π‘ Practice raw JavaScript algorithms on sites like LeetCode or HackerRank (easy level). You will often be asked to solve these in a live coding pad without auto-complete.
If your JS basics are weak, Playwright interviews become difficult.
Playwright Core Concepts
Interviewers will test your depth on the Playwright API. You need to know why `getByRole` is preferred over XPath, how strict mode prevents interacting with the wrong element, and how to handle native browser events like file uploads and alerts.
// βββ Locators: getByRole over CSS/XPath
// β
Best practice
await page.getByRole('button', { name: 'Sign in' }).click();
// βββ Handling native browser alerts automatically
page.on('dialog', async dialog => {
console.log(`Dialog message: ${dialog.message()}`);
await dialog.accept(); // or dialog.dismiss()
});
// βββ Handling iFrames
const frame = page.frameLocator('#payment-frame');
await frame.getByRole('textbox', { name: 'Card Number' }).fill('4242');
// βββ File Uploads (Multiple)
await page.getByLabel('Upload documents').setInputFiles([
'file1.pdf',
'file2.png'
]);π‘ The biggest mistake is explaining Playwright as if it were Selenium. Playwright does NOT need `switchTo().frame()` β you use `frameLocator`. It does NOT need explicit waits β it auto-waits.
Browser Architecture Understanding
You must deeply understand Playwright's architecture. A `Browser` is the physical browser process (expensive). A `BrowserContext` is an isolated incognito-like session (cheap). A `Page` is a single tab within that context.
// Launching browser once (Expensive - worker scoped)
const browser = await chromium.launch();
// Creating completely isolated contexts (Cheap - test scoped)
const contextA = await browser.newContext(); // User A (no cookies)
const contextB = await browser.newContext(); // User B (no cookies)
// Pages within contexts share cookies
const pageA1 = await contextA.newPage(); // User A tab 1
const pageA2 = await contextA.newPage(); // User A tab 2π‘ This architecture is why Playwright can run tests in parallel so quickly. It reuses the Browser process but creates a fresh, isolated BrowserContext for every single test.
These questions test real framework understanding.
Playwright Configuration (Very Common)
At 3-4 years of experience, you are expected to be the one configuring the framework, not just writing tests within it. You must know the `playwright.config.ts` file inside out.
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
timeout: 30000, // Global test timeout (30s)
expect: { timeout: 5000 }, // Assertion timeout (5s)
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 4 : undefined,
reporter: [
['html'], // Built-in HTML
['list'], // Terminal output
['./custom-reporter.ts'] // Custom reporter implementation
],
use: {
baseURL: 'https://staging.myapp.com',
headless: true,
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});π‘ Be prepared to explain the difference between `timeout` (the maximum time the whole test can run) and `expect.timeout` (the maximum time a single assertion will wait).
Interviewers may ask: βHow do you generate custom reports?β
Reporting & Debugging
Debugging in CI is a core senior skill. You must know how to configure Playwright to automatically collect forensic evidence (Traces, Videos, Screenshots) ONLY when a test fails to save disk space.
export default defineConfig({
use: {
// Collect trace when retrying the failed test
trace: 'on-first-retry',
// Take screenshot automatically on failure
screenshot: 'only-on-failure',
// Record video of failing tests
video: 'retain-on-failure',
},
});
// To debug locally step-by-step:
// npx playwright test --debug
// To view a trace file generated in CI:
// npx playwright show-trace path/to/trace.zipπ‘ Trace Viewer is Playwright's killer feature. It captures DOM snapshots, console logs, and network traffic for every action. If you don't mention Trace Viewer in an interview, you are leaving points on the table.
Understanding Playwright trace viewer is a massive plus.
Playwright + Cucumber
If the company uses BDD, they will drill you on Cucumber mechanics. You must understand state sharing between steps and the lifecycle of hooks.
import { setWorldConstructor, Before, After, Given, When, Then } from '@cucumber/cucumber';
import { chromium, Browser, BrowserContext, Page } from 'playwright';
// βββ Using World to share data between steps
class CustomWorld {
browser: Browser;
context: BrowserContext;
page: Page;
sharedData: string; // share state across Given/When/Then
}
setWorldConstructor(CustomWorld);
// βββ Hooks
Before(async function () {
// Runs before EVERY scenario
this.browser = await chromium.launch();
this.context = await this.browser.newContext();
this.page = await this.context.newPage();
});
After(async function () {
// Runs after EVERY scenario
await this.browser.close();
});π‘ `BeforeAll` runs once before the entire test suite (good for starting servers or DB connections). `Before` runs before every single scenario (good for launching a fresh browser context).
Promise & Async Handling
Playwright is entirely async. If you forget an `await`, the promise will execute in the background, your test will pass prematurely, and the action will happen later, causing massive flakiness. Interviewers will look for this.
// β JUNIOR MISTAKE: Forgetting await
page.getByRole('button').click(); // Test continues immediately without waiting!
// β
SENIOR FIX
await page.getByRole('button').click();
// βββ Custom Polling (Wait until a backend process finishes)
await expect(async () => {
const res = await page.request.get('/api/status');
const data = await res.json();
expect(data.status).toBe('COMPLETED');
}).toPass({
intervals: [1000, 2000, 5000], // poll after 1s, then 2s, then 5s
timeout: 30000
});π‘ Playwright auto-waits by injecting JavaScript into the browser that polls the DOM using `requestAnimationFrame`. If you need to wait for a non-DOM event (like an email arriving), use `expect().toPass()`.
Most candidates fail here.
Framework-Level Questions
You must be able to design a framework from scratch. This means separating concerns: Tests should only contain assertions and flow logic. Page Objects should only contain locators and actions. Fixtures should handle setup/teardown.
// βββ Creating a Custom Reusable Fixture
import { test as base } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';
// Define the type of our fixtures
type MyFixtures = { loginPage: LoginPage };
// Extend base test to include our custom fixture
export const test = base.extend<MyFixtures>({
loginPage: async ({ page }, use) => {
// Setup: instantiate POM
const loginPage = new LoginPage(page);
// Inject fixture into the test
await use(loginPage);
// Teardown logic here if needed
},
});
export { expect } from '@playwright/test';π‘ Custom fixtures are the "Playwright Way" to build frameworks, replacing traditional Page Object instantiation in `beforeEach` hooks. They are lazy, composable, and type-safe.
At 3β4 years experience, they expect you to architect the solution.
Best Practices (They Always Ask)
Interviewers will ask "What makes automation stable?" Your answer should hit these core pillars: Atomic tests (no dependencies between tests), Data isolation (creating unique data per test), and Web-First assertions.
// β ANTI-PATTERNS (Never do this)
await page.waitForTimeout(5000); // hard wait
await page.locator('.btn-primary.submit-form').click(); // brittle CSS
test.describe.serial('user flow', () => { ... }); // relying on previous tests
// β
BEST PRACTICES
// Wait for specific network response instead of hard wait
await Promise.all([
page.waitForResponse('/api/submit'),
page.getByRole('button', { name: 'Submit' }).click()
]);
// Use accessibility locators
await page.getByRole('button', { name: 'Submit' }).click();
// Generate unique test data per run
const uniqueEmail = `user_${Date.now()}@test.com`;π‘ Atomic tests mean Test A should not fail because Test B failed. Always generate fresh data via API in the `beforeEach` hook or within the test itself.
β‘ Reality Check
If you only know "how to click and type", you won't clear mid-level interviews.
Want to master all these areas? Check out our complete list.