Back to QA Automation

Module 2: Web Automation with Playwright

Master modern web automation with Playwright - the fastest and most reliable testing framework

🎭 Why Playwright?

Playwright is like having a super-powered robot that can control any browser. It's faster, more reliable, and easier to use than older tools like Selenium.

✓ Auto-Wait

Automatically waits for elements to be ready

✓ Multi-Browser

Test on Chrome, Firefox, Safari, Edge

✓ Fast Execution

Parallel testing out of the box

✓ Built-in Tools

Screenshots, videos, trace viewer

🎯 Locator Strategies

Locators are like addresses that help you find elements on a webpage. Think of it like finding a house - you can use the street address, the house color, or the name on the mailbox.

Best Locator Strategies

// 1. Role-based (BEST - accessible)

await page.getByRole('button', { name: 'Sign in' });

await page.getByRole('textbox', { name: 'Email' });

// 2. Text content

await page.getByText('Welcome back');

await page.getByLabel('Password');

// 3. Test ID (reliable for testing)

await page.getByTestId('login-button');

// 4. CSS Selector (when needed)

await page.locator('.submit-btn');

await page.locator('#username');

// 5. XPath (last resort)

await page.locator('//button[text()="Submit"]');

Locator Best Practices

Use role-based locators for accessibility

Add data-testid attributes for stable tests

Avoid complex XPath expressions

Don't rely on CSS classes that might change

📄 Page Object Model (POM)

POM is like creating a blueprint for each page. Instead of writing locators everywhere, you create a class that represents the page with all its elements and actions.

Without POM (Bad)

// Repeated code in every test

test('login test 1', async ({ page }) => {

await page.fill('#username', 'user1');

await page.fill('#password', 'pass1');

await page.click('button[type="submit"]');

});

test('login test 2', async ({ page }) => {

await page.fill('#username', 'user2');

await page.fill('#password', 'pass2');

await page.click('button[type="submit"]');

});

With POM (Good)

// pages/LoginPage.ts

export class LoginPage {

constructor(private page: Page) {}

// Locators

get usernameInput() {

return this.page.locator('#username');

}

get passwordInput() {

return this.page.locator('#password');

}

get submitButton() {

return this.page.getByRole('button', { name: 'Sign in' });

}

// Actions

async login(username: string, password: string) {

await this.usernameInput.fill(username);

await this.passwordInput.fill(password);

await this.submitButton.click();

}

}

// Using in tests

test('login test', async ({ page }) => {

const loginPage = new LoginPage(page);

await loginPage.login('user@test.com', 'password123');

});

⏱️ Handling Dynamic Elements & Waits

Web pages load at different speeds. Playwright automatically waits, but sometimes you need more control.

// Wait for element to be visible

await page.waitForSelector('.loading-spinner', { state: 'hidden' });

// Wait for navigation

await Promise.all([

page.waitForNavigation(),

page.click('#submit-button')

]);

// Wait for API response

await page.waitForResponse(response =>

response.url().includes('/api/users') && response.status() =>= 200

);

// Custom timeout

await page.locator('.slow-element').click({ timeout: 10000 });

// Wait for element count

await expect(page.locator('.product-card')).toHaveCount(10);

🌐 Browser Contexts & Parallel Testing

Browser contexts are like incognito windows - isolated environments for testing. This allows parallel testing without interference.

// playwright.config.ts

export default defineConfig({

// Run tests in parallel

workers: 4,

// Test on multiple browsers

projects: [

{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },

{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },

{ name: 'webkit', use: { ...devices['Desktop Safari'] } },

{ name: 'mobile', use: { ...devices['iPhone 13'] } },

]

});

// Create isolated context

const context = await browser.newContext({

viewport: { width: 1920, height: 1080 },

userAgent: 'Custom User Agent'

});

📸 Screenshots & Video Recording

// Take screenshot

await page.screenshot({ path: 'screenshot.png' });

// Full page screenshot

await page.screenshot({ path: 'full-page.png', fullPage: true });

// Screenshot specific element

await page.locator('.header').screenshot({ path: 'header.png' });

// Enable video recording in config

use: {

video: 'on', // or 'retain-on-failure'

screenshot: 'only-on-failure'

}

🔌 Network Interception & Mocking

Control network requests to test different scenarios without changing the backend.

// Mock API response

await page.route('**/api/users', route => route.fulfill({

status: 200,

body: JSON.stringify([

{ id: 1, name: 'Test User' },

{ id: 2, name: 'Another User' }

])

}));

// Block images for faster tests

await page.route('**/*.{ png,jpg,jpeg }', route => route.abort());

// Simulate slow network

await page.route('**/api/**', route => {

setTimeout(() => route.continue(), 3000);

});

🚀 Complete E2E Test Suite Project

Build a complete test suite for an e-commerce website with POM pattern.

// tests/e2e/checkout.spec.ts

import { test, expect } from '@playwright/test';

import { LoginPage } from '../pages/LoginPage';

import { ProductPage } from '../pages/ProductPage';

import { CartPage } from '../pages/CartPage';

import { CheckoutPage } from '../pages/CheckoutPage';

test.describe('E-Commerce Checkout Flow', () => {

test('complete purchase flow', async ({ page }) => {

// 1. Login

const loginPage = new LoginPage(page);

await loginPage.goto();

await loginPage.login('test@email.com', 'password123');

// 2. Add products to cart

const productPage = new ProductPage(page);

await productPage.addToCart('Product 1');

await productPage.addToCart('Product 2');

// 3. Verify cart

const cartPage = new CartPage(page);

await cartPage.goto();

await expect(cartPage.cartItems).toHaveCount(2);

// 4. Checkout

const checkoutPage = new CheckoutPage(page);

await cartPage.proceedToCheckout();

await checkoutPage.fillShippingInfo({

address: '123 Test St',

city: 'Test City',

zip: '12345'

});

await checkoutPage.completeOrder();

// 5. Verify success

await expect(page).toHaveURL(/order-confirmation/);

await expect(page.getByText('Order Confirmed')).toBeVisible();

});

});

🎯 Module Summary

You've mastered Playwright web automation:

  • ✓ Playwright architecture and features
  • ✓ Locator strategies and best practices
  • ✓ Page Object Model pattern
  • ✓ Handling dynamic elements and waits
  • ✓ Browser contexts and parallel testing
  • ✓ Screenshots and video recording
  • ✓ Network interception and mocking
  • ✓ Complete E2E test suite

Next: Advanced web testing with Cypress in Module 3!