Top 15 Playwright Interview Questions with Code Examples (2026 Edition)
Introduction
Playwright has emerged as the dominant end-to-end testing framework, with over 65,000 GitHub stars and adoption by companies like Microsoft, Adobe, and Disney+. Mastering Playwright is now a critical skill for SDETs and automation engineers. This comprehensive guide covers the most frequently asked Playwright interview questions with practical code examples and industry best practices.
1. What is a Promise in TypeScript?
A Promise in TypeScript (and JavaScript) represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
Key Characteristics:
Three states: pending, fulfilled, rejected
Used to handle async operations like API calls, file I/O, timers
Chainable with
.then(),.catch(), and.finally()
Playwright Example:
// Promise usage in Playwright const buttonClickPromise: Promise<void> = page.click('#submit'); buttonClickPromise .then(() => console.log('Button clicked successfully')) .catch((error) => console.error('Click failed:', error)) .finally(() => console.log('Click operation completed'));
Common Playwright Methods Returning Promises:
page.click()page.goto()page.waitForSelector()page.waitForResponse()
2. Explain async and await in TypeScript
async/await is syntactic sugar over Promises that makes asynchronous code look synchronous.
async Function:
Always returns a Promise
Can contain
awaitexpressions
await Expression:
Pauses execution until Promise settles
Can only be used inside
asyncfunctions
Playwright Example:
// Without async/await test('old way', ({ page }) => { return page.goto('https://example.com') .then(() => page.click('#button')) .then(() => expect(page).toHaveTitle('Example')); }); // With async/await (Cleaner) test('modern way', async ({ page }) => { await page.goto('https://example.com'); await page.click('#button'); await expect(page).toHaveTitle('Example'); });
3. Explain OOP Concepts and How They Are Used in Playwright
Playwright heavily utilizes Object-Oriented Programming principles:
Encapsulation
class LoginPage { private username: Locator; private password: Locator; constructor(private page: Page) { this.username = page.locator('#username'); this.password = page.locator('#password'); } public async login(user: string, pass: string) { await this.username.fill(user); await this.password.fill(pass); await this.page.click('#login-btn'); } }
Inheritance
class BasePage { constructor(protected page: Page) {} async navigate(url: string) { await this.page.goto(url); await this.page.waitForLoadState('networkidle'); } } class DashboardPage extends BasePage { async verifyDashboardLoaded() { await expect(this.page.locator('.dashboard')).toBeVisible(); } }
Polymorphism
interface PageComponent { verify(): Promise<void>; } class Header implements PageComponent { async verify() { console.log('Verifying header'); } } class Footer implements PageComponent { async verify() { console.log('Verifying footer'); } }
Abstraction
abstract class PaymentPage { abstract makePayment(amount: number): Promise<void>; async verifyPaymentSuccess() { // Common implementation } } class CreditCardPayment extends PaymentPage { async makePayment(amount: number) { // Credit card specific implementation } }
4. Explain Playwright Framework Structure
A well-structured Playwright framework enhances maintainability and scalability:
playwright-automation/ ├── src/ │ ├── pages/ # Page Object Models │ │ ├── base.page.ts │ │ ├── login.page.ts │ │ └── dashboard.page.ts │ ├── components/ # Reusable components │ │ ├── header.component.ts │ │ └── modal.component.ts │ ├── fixtures/ # Test fixtures │ │ └── test.fixture.ts │ ├── utils/ # Helper utilities │ │ ├── api.helper.ts │ │ ├── data.generator.ts │ │ └── report.helper.ts │ └── data/ # Test data │ └── test-data.ts ├── tests/ │ ├── e2e/ # End-to-end tests │ │ ├── login.spec.ts │ │ └── checkout.spec.ts │ ├── api/ # API tests │ │ └── user-api.spec.ts │ └── visual/ # Visual regression tests │ └── homepage.spec.ts ├── config/ │ ├── playwright.config.ts │ └── environment.config.ts ├── reports/ # Test reports ├── test-results/ # Screenshots, traces ├── .github/workflows/ # CI/CD pipelines └── package.json
5. What Are Util Files and Where Are They Used?
Utility files contain reusable helper functions that support tests but aren't tied to specific pages.
Common Utility Examples:
// utils/api.helper.ts export class ApiHelper { static async postRequest(url: string, data: any) { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); return response.json(); } static generateRandomEmail() { return `test${Date.now()}@example.com`; } } // utils/database.helper.ts export class DatabaseHelper { static async cleanTestData() { // Database cleanup logic } static async getUserId(email: string) { // Database query logic } }
Usage in Tests:
import { ApiHelper } from '../utils/api.helper'; test('create user via API', async ({ page }) => { const userData = { email: ApiHelper.generateRandomEmail(), name: 'Test User' }; const response = await ApiHelper.postRequest('/api/users', userData); expect(response.status).toBe(201); });
6. What Are Fixtures in Playwright?
Fixtures provide a way to set up and tear down test context, share state between tests, and manage resources.
Built-in Fixtures:
page: Browser pagecontext: Browser contextbrowser: Browser instancerequest: API testing client
Custom Fixture Example:
// fixtures/test.fixture.ts import { test as base } from '@playwright/test'; import { LoginPage } from '../pages/login.page'; import { ApiHelper } from '../utils/api.helper'; type MyFixtures = { loginPage: LoginPage; apiHelper: ApiHelper; adminUser: { username: string; password: string }; }; export const test = base.extend<MyFixtures>({ loginPage: async ({ page }, use) => { const loginPage = new LoginPage(page); await use(loginPage); }, apiHelper: async ({}, use) => { const apiHelper = new ApiHelper(); await use(apiHelper); }, adminUser: async ({}, use) => { const adminUser = { username: 'admin', password: process.env.ADMIN_PASSWORD || 'default' }; await use(adminUser); }, }); export { expect } from '@playwright/test'; // Usage in tests test('admin login', async ({ loginPage, adminUser }) => { await loginPage.goto(); await loginPage.login(adminUser.username, adminUser.password); await expect(loginPage.page).toHaveURL('/dashboard'); });
7. How Do You Configure Parallel Execution?
Method 1: In playwright.config.ts
// playwright.config.ts import { defineConfig } from '@playwright/test'; export default defineConfig({ // Global parallel execution workers: 4, // Per-project configuration projects: [ { name: 'chrome', use: { browserName: 'chromium' }, fullyParallel: true, // Tests in this project run in parallel }, { name: 'firefox', use: { browserName: 'firefox' }, fullyParallel: false, // Tests run sequentially } ] });
Method 2: Via Command Line
# Run with 4 workers npx playwright test --workers=4 # Run with 1 worker (sequential) npx playwright test --workers=1 # Auto-detect based on CPU cores npx playwright test --workers=process.env.CI ? 2 : '50%'
Method 3: Using test.describe for Serial Execution
import { test } from '@playwright/test'; // These tests run in parallel with other test files test.describe('Login Tests', () => { test('test 1', async () => { /* runs in parallel */ }); test('test 2', async () => { /* runs in parallel */ }); }); // These tests run serially within the group test.describe.serial('Checkout Flow', () => { test('add to cart', async () => { /* runs first */ }); test('enter shipping', async () => { /* runs second */ }); test('complete payment', async () => { /* runs third */ }); }); // Parallel tests within serial group test.describe.serial.parallel('Mixed Mode', () => { // Still runs sequentially despite parallel modifier test('step 1', async () => {}); test('step 2', async () => {}); });
8. What Debugging Methodologies Do You Use in Playwright?
1. Headed Mode Debugging
# Run tests in headed mode npx playwright test --headed # Debug specific test npx playwright test login.spec.ts --debug # Slow down execution npx playwright test --slow-mo=1000
2. Trace Viewer (Most Powerful)
// playwright.config.ts export default defineConfig({ use: { trace: 'on-first-retry', // Recommended for CI // OR trace: 'retain-on-failure', // Keep traces for failed tests }, }); // View traces npx playwright show-trace trace.zip
3. Screenshots & Videos
// playwright.config.ts export default defineConfig({ use: { screenshot: 'only-on-failure', video: 'retain-on-failure', }, });
4. Console & Network Debugging
test('debug network', async ({ page }) => { // Listen to console logs page.on('console', msg => console.log('PAGE LOG:', msg.text())); // Listen to network requests page.on('request', request => console.log('>>', request.method(), request.url()) ); // Listen to responses page.on('response', response => console.log('<<', response.status(), response.url()) ); await page.goto('https://example.com'); });
5. Playwright Inspector
# Interactive debugging npx playwright codegen https://example.com # Record tests npx playwright test --ui
9. Explain API Testing Approach with Playwright
Playwright provides a built-in request fixture for API testing without browser overhead.
Basic API Testing Example:
import { test, expect } from '@playwright/test'; test('API GET request', async ({ request }) => { const response = await request.get('https://api.example.com/users'); expect(response.status()).toBe(200); expect(response.ok()).toBeTruthy(); const users = await response.json(); expect(Array.isArray(users)).toBeTruthy(); expect(users.length).toBeGreaterThan(0); }); test('API POST request with authentication', async ({ request }) => { const response = await request.post('https://api.example.com/users', { data: { name: 'John Doe', email: 'john@example.com' }, headers: { 'Authorization': `Bearer ${process.env.API_TOKEN}`, 'Content-Type': 'application/json' } }); expect(response.status()).toBe(201); const user = await response.json(); expect(user.id).toBeDefined(); expect(user.name).toBe('John Doe'); });
API Testing with Context Reuse:
// api-context.setup.ts import { test as setup } from '@playwright/test'; setup('setup auth token', async ({ request }) => { const response = await request.post('/api/login', { data: { username: 'admin', password: 'admin123' } }); const { token } = await response.json(); // Store token for other tests process.env.AUTH_TOKEN = token; }); // api-tests.spec.ts test.describe('Authenticated API Tests', () => { test('get user profile', async ({ request }) => { const response = await request.get('/api/profile', { headers: { 'Authorization': `Bearer ${process.env.AUTH_TOKEN}` } }); expect(response.ok()).toBeTruthy(); }); });
10. What Does a POST Request Do?
A POST request is an HTTP method used to submit data to a specified resource, often causing a change in state or side effects on the server.
Key Characteristics:
Idempotent: No (multiple identical POST requests may have different effects)
Safe: No (modifies server state)
Use Cases: Creating resources, submitting forms, uploading files
Technical Flow:
Client sends POST request with data in body
Server processes data
Server creates/updates resource
Server returns response (often 201 Created)
11. What Test Scenarios Do You Cover for a POST Request?
1. Success Scenarios
test('POST - Create resource successfully', async ({ request }) => { const response = await request.post('/api/users', { data: { name: 'Alice', email: 'alice@example.com' } }); expect(response.status()).toBe(201); expect(response.headers()['location']).toContain('/api/users/'); const user = await response.json(); expect(user.id).toBeDefined(); expect(user.name).toBe('Alice'); });
2. Validation Error Scenarios
test('POST - Invalid data returns 400', async ({ request }) => { const response = await request.post('/api/users', { data: { name: '', email: 'invalid-email' } }); expect(response.status()).toBe(400); const errors = await response.json(); expect(errors).toHaveProperty('email'); expect(errors).toHaveProperty('name'); });
3. Authentication/Authorization
test('POST - Unauthorized access', async ({ request }) => { const response = await request.post('/api/admin/users', { data: { name: 'Test' } }); expect(response.status()).toBe(401); }); test('POST - Insufficient permissions', async ({ request }) => { const response = await request.post('/api/admin/users', { headers: { 'Authorization': 'Bearer user-token' }, data: { name: 'Test' } }); expect(response.status()).toBe(403); });
4. Conflict & Duplicate Data
test('POST - Duplicate resource conflict', async ({ request }) => { // First request succeeds await request.post('/api/users', { data: { email: 'duplicate@example.com' } }); // Second request should fail const response = await request.post('/api/users', { data: { email: 'duplicate@example.com' } }); expect(response.status()).toBe(409); });
5. Edge Cases
test('POST - Large payload', async ({ request }) => { const largeData = { content: 'x'.repeat(1000000) }; const response = await request.post('/api/documents', { data: largeData }); expect(response.status()).toBeOneOf([201, 413]); }); test('POST - Special characters', async ({ request }) => { const response = await request.post('/api/comments', { data: { text: 'Special chars: 🚀 © ® <script>alert("xss")</script>' } }); // Should either sanitize or reject expect([200, 201, 400]).toContain(response.status()); });
6. Performance Testing
test('POST - Response time SLA', async ({ request }) => { const startTime = Date.now(); const response = await request.post('/api/orders', { data: { items: [{ id: 1, quantity: 2 }] } }); const endTime = Date.now(); expect(response.status()).toBe(201); expect(endTime - startTime).toBeLessThan(1000); // 1 second SLA });
12. Logical Reasoning Solution
Given:
P is shorter than Q → P < Q
P is taller than R → P > R
R is shorter than S → R < S
Combine:
From (1) & (2): R < P < Q
From (3): R < S
Comparison possibilities:
If S is taller than P: R < P < Q and R < S (can't determine shortest between R and others)
If S is shorter than P: R < S < P < Q
Since we only know R is shorter than S, but don't know S's relation to P and Q, R is definitely the shortest because:
R < P (from statement 2)
R < S (from statement 3)
We don't know if anyone is shorter than R
Answer: R is the shortest.
13. XPath to Locate "Business using Advertising" on Google Homepage
//a[contains(text(), 'Business') and contains(text(), 'Advertising')] // More specific XPaths: //a[normalize-space()='Business using Advertising'] // Using data attributes (if available): //a[@data-pid='23' and contains(text(), 'Business')] // Multiple conditions: //*[self::a or self::button][contains(., 'Business') and contains(., 'Advertising')] // Case-insensitive search: //a[contains(translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), 'business using advertising')]
Best Practice: Always inspect the actual page structure as Google frequently changes their HTML.
14. Count Character Occurrences in "Geeks"
// Solution 1: Using reduce() function countChars(str: string): Map<string, number> { return str.split('').reduce((map, char) => { map.set(char, (map.get(char) || 0) + 1); return map; }, new Map<string, number>()); } // Solution 2: Simple object approach function countCharsSimple(str: string): Record<string, number> { const count: Record<string, number> = {}; for (const char of str) { count[char] = (count[char] || 0) + 1; } return count; } // Solution 3: Playwright test example import { test, expect } from '@playwright/test'; test('count character occurrences', async () => { const str = "Geeks"; const expectedCount = { 'G': 1, 'e': 2, 'k': 1, 's': 1 }; const result = countCharsSimple(str); // Assertions expect(result['G']).toBe(1); expect(result['e']).toBe(2); expect(result['k']).toBe(1); expect(result['s']).toBe(1); expect(Object.keys(result).length).toBe(4); }); // Output for "Geeks": // G: 1 // e: 2 // k: 1 // s: 1
Additional Advanced Questions
15. How to Handle Authentication in Playwright?
// Method 1: Storage state test('authenticated test', async ({ browser }) => { const context = await browser.newContext({ storageState: 'auth.json' }); const page = await context.newPage(); // Page is already logged in }); // Method 2: Programmatic login async function login(page: Page) { await page.goto('/login'); await page.fill('#username', 'user'); await page.fill('#password', 'pass'); await page.click('#login-btn'); await page.waitForURL('/dashboard'); // Save state for reuse await page.context().storageState({ path: 'auth.json' }); }
16. How to Test File Uploads?
test('file upload', async ({ page }) => { await page.setInputFiles('input[type="file"]', { name: 'test.png', mimeType: 'image/png', buffer: Buffer.from('test') }); // Or with actual file await page.setInputFiles('input[type="file"]', 'path/to/file.pdf'); });
17. How to Handle Multiple Tabs/Windows?
test('multiple tabs', async ({ context }) => { const page = await context.newPage(); await page.goto('https://example.com'); // Open new tab const [newPage] = await Promise.all([ context.waitForEvent('page'), page.click('a[target="_blank"]') ]); await newPage.waitForLoadState(); await expect(newPage).toHaveTitle('New Page'); });
Conclusion
Mastering these Playwright concepts will prepare you for most automation engineering interviews. Remember:
Practice with real projects
Understand both the "how" and "why"
Stay updated with Playwright's rapid releases
Build a portfolio of test automation projects
Playwright continues to evolve, so follow the official documentation and community for the latest features and best practices.
👉 Suggested Article: Playwright Automation Testing: Complete Guide (2026)
📘Tutorials | 🧠AI | 🧪Selenium | 🥇Top 10 | 🛠️Tools | 📋Software Testing