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: pendingfulfilledrejected

  • Used to handle async operations like API calls, file I/O, timers

  • Chainable with .then().catch(), and .finally()

Playwright Example:

typescript
// 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 await expressions

await Expression:

  • Pauses execution until Promise settles

  • Can only be used inside async functions

Playwright Example:

typescript
// 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

typescript
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

typescript
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

typescript
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

typescript
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:

text
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:

typescript
// 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:

typescript
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 page

  • context: Browser context

  • browser: Browser instance

  • request: API testing client

Custom Fixture Example:

typescript
// 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

typescript
// 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

bash
# 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

typescript
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

bash
# 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)

typescript
// 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

typescript
// playwright.config.ts
export default defineConfig({
  use: {
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
});

4. Console & Network Debugging

typescript
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

bash
# 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:

typescript
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:

typescript
// 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:

  1. Client sends POST request with data in body

  2. Server processes data

  3. Server creates/updates resource

  4. Server returns response (often 201 Created)

11. What Test Scenarios Do You Cover for a POST Request?

1. Success Scenarios

typescript
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

typescript
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

typescript
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

typescript
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

typescript
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

typescript
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:

  1. P is shorter than Q → P < Q

  2. P is taller than R → P > R

  3. 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

xpath
//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"

typescript
// 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?

typescript
// 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?

typescript
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?

typescript
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:

  1. Practice with real projects

  2. Understand both the "how" and "why"

  3. Stay updated with Playwright's rapid releases

  4. 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)

Popular posts from this blog

18 Demo Websites for Selenium Automation Practice in 2026

Selenium Automation for E-commerce Websites: End-to-End Testing Scenarios

Mastering Selenium Practice: Automating Web Tables with Demo Examples

14+ Best Selenium Practice Exercises to Master Automation Testing (with Code & Challenges)

Top 10 Highly Paid Indian-Origin CEOs in the USA

Selenium WebDriver Integration with OpenAI, Sikuli, Appium, Python & Linux

Top 7 Web Development Trends in the Market (2026)

25+ Selenium WebDriver Commands: The Complete Cheat Sheet with Examples

Top Selenium Interview Questions & Answers of 2026

How to Learn Selenium WebDriver in 4 Weeks: A Step-by-Step Self-Study Roadmap