+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 153 of 355

🎭 Playwright: Modern E2E Testing Framework

Master modern E2E testing with Playwright and TypeScript - test across browsers, handle complex scenarios, and build reliable test automation πŸš€

πŸš€Intermediate
40 min read

Prerequisites

  • Basic TypeScript knowledge πŸ“
  • Understanding of E2E testing concepts ⚑
  • Familiarity with web applications πŸ’»
  • Experience with DOM interactions πŸ”—

What you'll learn

  • Set up Playwright with TypeScript for modern E2E testing 🎯
  • Write cross-browser tests that work everywhere 🌐
  • Handle advanced scenarios like file uploads and mobile testing πŸ“±
  • Implement parallel testing and advanced patterns πŸš€
  • Debug and optimize Playwright tests effectively πŸ›

🎯 Introduction

Welcome to the future of E2E testing with Playwright! 🎭 If Cypress is the rockstar of E2E testing, then Playwright is the superhero with superpowers - it can test across all browsers simultaneously, handle the most complex scenarios, and do it all blazingly fast!

Playwright is Microsoft’s modern testing framework that brings together the best of all worlds: cross-browser support, mobile testing, network interception, and TypeScript-first design. It’s like having a testing Swiss Army knife that just happens to be powered by rockets! πŸš€

By the end of this tutorial, you’ll be writing robust, fast, and reliable E2E tests that work across Chrome, Firefox, Safari, and Edge. Let’s dive into this amazing journey! πŸŠβ€β™‚οΈ

πŸ“š Understanding Playwright

πŸ€” What Makes Playwright Special?

Playwright is like having a team of expert testers, each specialized in different browsers! πŸŽͺ It’s designed from the ground up for modern web applications with features that other testing frameworks can only dream of.

Playwright’s superpowers include:

  • ✨ True Cross-Browser Testing: Chrome, Firefox, Safari, Edge
  • πŸš€ Lightning Fast: Parallel execution by default
  • πŸ“± Mobile Testing: Real device emulation
  • 🌐 Network Control: Mock APIs, offline testing
  • 🎯 Auto-Waiting: Smart waits for elements
  • πŸ“Έ Visual Testing: Screenshot comparisons
  • πŸ”„ Retry Logic: Built-in flaky test handling

πŸ’‘ Playwright vs Cypress: When to Choose What?

Here’s when each framework shines:

Choose Playwright when: 🎭

  1. Cross-Browser Support 🌐: Need to test on multiple browsers
  2. Complex Scenarios 🧩: File uploads, multiple tabs, frames
  3. Performance Testing ⚑: Need parallel execution
  4. Mobile Testing πŸ“±: Testing responsive designs
  5. Enterprise Features 🏒: Advanced reporting and CI/CD

Choose Cypress when: 🌲

  1. Developer Experience 😊: Learning curve and debugging
  2. Real-Time Testing πŸ‘€: Interactive test development
  3. Simple Scenarios πŸ“: Standard web app testing
  4. Team Familiarity πŸ‘₯: Team already knows Cypress

Real-world example: Your TypeScript app needs to work perfectly on Safari for iOS users? Playwright has you covered! 🍎

πŸ”§ Playwright + TypeScript Setup

πŸ“ Installation and Configuration

Let’s get Playwright running with TypeScript superpowers:

# πŸš€ Install Playwright
npm init playwright@latest

# Or install manually with TypeScript
npm install --save-dev @playwright/test
npm install --save-dev typescript

# πŸ“¦ Install browsers
npx playwright install

TypeScript Configuration:

// πŸ“„ playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  // πŸ“ Test directory
  testDir: './tests',
  
  // 🎯 Test file patterns
  testMatch: '**/*.spec.ts',
  
  // ⚑ Global test settings
  timeout: 30000,
  expect: {
    timeout: 10000
  },
  
  // πŸ”„ Retry configuration
  retries: process.env.CI ? 2 : 0,
  
  // 🎭 Parallel execution
  workers: process.env.CI ? 1 : undefined,
  
  // πŸ“Š Reporter configuration
  reporter: [
    ['html', { outputFolder: 'playwright-report' }],
    ['json', { outputFile: 'test-results.json' }],
    ['junit', { outputFile: 'results.xml' }]
  ],
  
  // 🎬 Global setup and teardown
  globalSetup: require.resolve('./tests/global-setup'),
  globalTeardown: require.resolve('./tests/global-teardown'),
  
  // πŸ”§ Shared settings for all tests
  use: {
    // 🌐 Base URL
    baseURL: 'http://localhost:3000',
    
    // πŸŽ₯ Tracing and debugging
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    
    // 🎯 Default timeouts
    actionTimeout: 10000,
    navigationTimeout: 30000,
  },
  
  // πŸ“± Browser and device configuration
  projects: [
    // πŸ–₯️ Desktop browsers
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
    
    // πŸ“± Mobile devices
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 5'] },
    },
    {
      name: 'Mobile Safari',
      use: { ...devices['iPhone 12'] },
    },
    
    // πŸ–₯️ Tablets
    {
      name: 'iPad',
      use: { ...devices['iPad Pro'] },
    },
  ],
  
  // πŸš€ Development server
  webServer: {
    command: 'npm run dev',
    port: 3000,
    reuseExistingServer: !process.env.CI,
  },
});

TypeScript Types and Utilities:

// πŸ“„ tests/types/test-types.ts
export interface TestUser {
  id: string;
  email: string;
  password: string;
  name: string;
  role: 'user' | 'admin' | 'moderator';
  emoji: string;
}

export interface TestProduct {
  id: string;
  name: string;
  price: number;
  category: string;
  emoji: string;
  inStock: boolean;
}

export interface APIResponse<T> {
  success: boolean;
  data: T;
  message?: string;
  error?: string;
}

// 🎯 Page Object Model base class
export abstract class BasePage {
  constructor(public page: Page) {}
  
  abstract navigate(): Promise<void>;
  abstract isLoaded(): Promise<boolean>;
  
  // πŸ”§ Common utilities
  async waitForLoadingComplete(): Promise<void> {
    await this.page.waitForLoadState('networkidle');
    await this.page.locator('[data-testid=loading]').waitFor({ state: 'hidden' });
  }
  
  async takeScreenshot(name: string): Promise<void> {
    await this.page.screenshot({ path: `screenshots/${name}.png` });
  }
}

// πŸ›‘οΈ Test fixtures
export interface TestFixtures {
  authenticatedUser: TestUser;
  testProducts: TestProduct[];
  adminUser: TestUser;
}

🎨 Custom Fixtures and Setup

Create powerful test fixtures:

// πŸ“„ tests/fixtures/test-fixtures.ts
import { test as base, Page } from '@playwright/test';
import { TestUser, TestProduct } from '../types/test-types';

// πŸ—οΈ Extended test with custom fixtures
export const test = base.extend<{
  authenticatedPage: Page;
  testUser: TestUser;
  testProducts: TestProduct[];
}>({
  // πŸ‘€ Authenticated user fixture
  testUser: async ({}, use) => {
    const user: TestUser = {
      id: 'test-user-123',
      email: '[email protected]',
      password: 'PlaywrightTest123!',
      name: 'Playwright Tester 🎭',
      role: 'user',
      emoji: '🎭'
    };
    
    // 🌱 Create user in database
    await setupTestUser(user);
    await use(user);
    
    // 🧹 Cleanup
    await cleanupTestUser(user.id);
  },
  
  // πŸ” Pre-authenticated page
  authenticatedPage: async ({ page, testUser }, use) => {
    // 🎯 Login process
    await page.goto('/login');
    await page.fill('[data-testid=email]', testUser.email);
    await page.fill('[data-testid=password]', testUser.password);
    await page.click('[data-testid=login-button]');
    
    // βœ… Wait for successful login
    await page.waitForURL('**/dashboard');
    await page.waitForSelector('[data-testid=user-menu]');
    
    await use(page);
  },
  
  // πŸ›’ Test products fixture
  testProducts: async ({}, use) => {
    const products: TestProduct[] = [
      {
        id: 'typescript-mug',
        name: 'TypeScript Coffee Mug β˜•',
        price: 19.99,
        category: 'drinkware',
        emoji: 'β˜•',
        inStock: true
      },
      {
        id: 'playwright-shirt',
        name: 'Playwright T-Shirt πŸ‘•',
        price: 24.99,
        category: 'clothing',
        emoji: 'πŸ‘•',
        inStock: true
      }
    ];
    
    // 🌱 Seed products
    await seedTestProducts(products);
    await use(products);
    
    // 🧹 Cleanup
    await cleanupTestProducts();
  }
});

// πŸ”§ Helper functions
async function setupTestUser(user: TestUser): Promise<void> {
  // API call to create test user
  console.log(`🌱 Setting up test user: ${user.name}`);
}

async function cleanupTestUser(userId: string): Promise<void> {
  // API call to cleanup test user
  console.log(`🧹 Cleaning up test user: ${userId}`);
}

πŸ’‘ Practical Playwright Test Examples

πŸ›’ Example 1: Advanced E-commerce Testing

Let’s test a sophisticated shopping experience:

// πŸ§ͺ tests/ecommerce/advanced-shopping.spec.ts
import { test, expect } from '../fixtures/test-fixtures';
import { ShoppingCartPage } from '../pages/ShoppingCartPage';
import { CheckoutPage } from '../pages/CheckoutPage';

test.describe('πŸ›’ Advanced E-commerce Features', () => {
  test('should handle complex shopping workflow across browsers', async ({ 
    authenticatedPage: page, 
    testProducts 
  }) => {
    const cartPage = new ShoppingCartPage(page);
    const checkoutPage = new CheckoutPage(page);
    
    // πŸ›οΈ Start shopping journey
    await page.goto('/products');
    
    // πŸ” Search with autocomplete
    const searchBox = page.locator('[data-testid=search-input]');
    await searchBox.fill('TypeScript');
    
    // ⏳ Wait for autocomplete suggestions
    await page.waitForSelector('[data-testid=search-suggestions]');
    
    // πŸ‘† Click on suggestion
    await page.click('[data-testid=suggestion-typescript-mug]');
    
    // πŸ“‹ Verify product page
    await expect(page.locator('[data-testid=product-title]')).toContainText('TypeScript Coffee Mug');
    
    // 🎨 Test product image gallery
    const imageGallery = page.locator('[data-testid=product-images]');
    await expect(imageGallery.locator('img')).toHaveCount(4);
    
    // πŸ‘† Click through images
    for (let i = 1; i <= 3; i++) {
      await page.click(`[data-testid=image-thumbnail-${i}]`);
      await expect(page.locator('[data-testid=main-image]')).toHaveAttribute(
        'src', 
        new RegExp(`image-${i}`)
      );
    }
    
    // πŸ“Š Test quantity selector
    const quantitySelector = page.locator('[data-testid=quantity-select]');
    await quantitySelector.selectOption('3');
    
    // ✨ Test size/color options
    await page.click('[data-testid=size-large]');
    await page.click('[data-testid=color-blue]');
    
    // πŸ›’ Add to cart with options
    await page.click('[data-testid=add-to-cart]');
    
    // βœ… Verify cart notification
    const cartNotification = page.locator('[data-testid=cart-notification]');
    await expect(cartNotification).toBeVisible();
    await expect(cartNotification).toContainText('3 items added to cart! πŸŽ‰');
    
    // πŸ‘€ View cart
    await page.click('[data-testid=cart-icon]');
    await cartPage.verifyCartItems();
    
    // πŸ’ Apply coupon code
    await cartPage.applyCoupon('PLAYWRIGHT20');
    await expect(page.locator('[data-testid=discount-applied]')).toContainText('20% off applied! πŸŽ‰');
    
    // 🚚 Proceed to checkout
    await cartPage.proceedToCheckout();
    
    // πŸ“¦ Fill shipping information with autocomplete
    await checkoutPage.fillShippingInfo({
      address: '123 Playwright Street',
      city: 'Test City',
      zipCode: '12345',
      country: 'United States πŸ‡ΊπŸ‡Έ'
    });
    
    // 🚚 Select shipping method
    await checkoutPage.selectShippingMethod('express');
    
    // πŸ’³ Fill payment information
    await checkoutPage.fillPaymentInfo({
      cardNumber: '4242424242424242',
      expiryDate: '12/25',
      cvc: '123',
      name: 'Playwright Tester'
    });
    
    // πŸ”’ Enable save payment method
    await page.check('[data-testid=save-payment-method]');
    
    // πŸ“Š Review order summary
    await expect(page.locator('[data-testid=order-total]')).toContainText('$63.97'); // After discount
    
    // 🎯 Place order
    await checkoutPage.placeOrder();
    
    // βœ… Verify order confirmation
    await expect(page.locator('[data-testid=order-confirmation]')).toBeVisible();
    await expect(page.locator('[data-testid=order-number]')).toHaveText(/ORD-\d+/);
    
    // πŸ“§ Verify email confirmation notice
    await expect(page.locator('[data-testid=email-notice]')).toContainText(
      'Confirmation email sent to [email protected]'
    );
    
    console.log('πŸŽ‰ Advanced shopping workflow completed!');
  });
  
  test('should handle wishlist and comparison features', async ({ 
    authenticatedPage: page, 
    testProducts 
  }) => {
    await page.goto('/products');
    
    // ❀️ Add items to wishlist
    await page.hover('[data-testid=product-typescript-mug]');
    await page.click('[data-testid=wishlist-btn-typescript-mug]');
    
    await page.hover('[data-testid=product-playwright-shirt]');
    await page.click('[data-testid=wishlist-btn-playwright-shirt]');
    
    // βœ… Verify wishlist counter
    await expect(page.locator('[data-testid=wishlist-count]')).toHaveText('2');
    
    // πŸ‘€ View wishlist
    await page.click('[data-testid=wishlist-icon]');
    await expect(page.locator('[data-testid=wishlist-item]')).toHaveCount(2);
    
    // πŸ”„ Add to comparison
    await page.check('[data-testid=compare-typescript-mug]');
    await page.check('[data-testid=compare-playwright-shirt]');
    
    // πŸ“Š View comparison
    await page.click('[data-testid=compare-button]');
    
    // βœ… Verify comparison table
    const comparisonTable = page.locator('[data-testid=comparison-table]');
    await expect(comparisonTable.locator('[data-testid=product-row]')).toHaveCount(2);
    await expect(comparisonTable.locator('[data-testid=price-column]')).toHaveCount(2);
    
    console.log('❀️ Wishlist and comparison features working!');
  });
});

πŸ“± Example 2: Mobile-First Testing

Test mobile-specific features:

// πŸ§ͺ tests/mobile/mobile-experience.spec.ts
import { test, expect, devices } from '@playwright/test';

// πŸ“± Mobile-specific test configuration
test.use({ ...devices['iPhone 12 Pro'] });

test.describe('πŸ“± Mobile User Experience', () => {
  test('should have touch-friendly navigation', async ({ page }) => {
    await page.goto('/');
    
    // πŸ” Test hamburger menu
    const hamburgerMenu = page.locator('[data-testid=hamburger-menu]');
    await expect(hamburgerMenu).toBeVisible();
    
    // πŸ“ Verify touch target size (minimum 44px)
    const buttonSize = await hamburgerMenu.boundingBox();
    expect(buttonSize?.height).toBeGreaterThanOrEqual(44);
    expect(buttonSize?.width).toBeGreaterThanOrEqual(44);
    
    // πŸ‘† Test menu interaction
    await hamburgerMenu.tap();
    
    // 🎯 Verify mobile menu appears
    const mobileMenu = page.locator('[data-testid=mobile-menu]');
    await expect(mobileMenu).toBeVisible();
    
    // πŸ”— Test menu items
    const menuItems = mobileMenu.locator('[data-testid=menu-item]');
    await expect(menuItems).toHaveCount(5);
    
    // πŸ‘† Test navigation
    await menuItems.nth(1).tap();
    await expect(page).toHaveURL(/.*\/products/);
    
    console.log('πŸ“± Mobile navigation test passed!');
  });
  
  test('should handle mobile gestures and interactions', async ({ page }) => {
    await page.goto('/products');
    
    // πŸ“œ Test pull-to-refresh
    await page.touchscreen.swipe(640, 100, 640, 400, 10);
    
    // ⏳ Wait for refresh animation
    await page.waitForSelector('[data-testid=refresh-indicator]');
    await page.waitForSelector('[data-testid=refresh-indicator]', { state: 'hidden' });
    
    // πŸ‘† Test product card interactions
    const productCard = page.locator('[data-testid=product-card]').first();
    
    // 🎯 Long press for quick actions
    await productCard.hover();
    await page.mouse.down();
    await page.waitForTimeout(1000); // Long press
    await page.mouse.up();
    
    // βœ… Verify quick actions menu
    await expect(page.locator('[data-testid=quick-actions]')).toBeVisible();
    
    // ❀️ Quick add to wishlist
    await page.tap('[data-testid=quick-wishlist]');
    await expect(page.locator('[data-testid=wishlist-success]')).toBeVisible();
    
    console.log('πŸ‘† Mobile gestures test passed!');
  });
  
  test('should handle mobile forms and keyboards', async ({ page }) => {
    await page.goto('/contact');
    
    // πŸ“ Test form with mobile keyboard
    const emailInput = page.locator('[data-testid=email-input]');
    await emailInput.tap();
    
    // ⌨️ Verify email keyboard appears (input type=email)
    await expect(emailInput).toHaveAttribute('type', 'email');
    
    // πŸ“± Fill form with mobile-friendly inputs
    await emailInput.fill('[email protected]');
    
    const phoneInput = page.locator('[data-testid=phone-input]');
    await phoneInput.tap();
    await expect(phoneInput).toHaveAttribute('type', 'tel');
    await phoneInput.fill('+1-555-123-4567');
    
    // πŸ’¬ Test textarea on mobile
    const messageInput = page.locator('[data-testid=message-input]');
    await messageInput.tap();
    await messageInput.fill('Testing mobile form experience! πŸ“±');
    
    // 🎯 Submit form
    await page.tap('[data-testid=submit-button]');
    
    // βœ… Verify success message
    await expect(page.locator('[data-testid=form-success]')).toBeVisible();
    
    console.log('πŸ“ Mobile form test passed!');
  });
});

🌐 Example 3: Cross-Browser API Testing

Test API interactions across browsers:

// πŸ§ͺ tests/api/cross-browser-api.spec.ts
import { test, expect } from '@playwright/test';

test.describe('🌐 Cross-Browser API Integration', () => {
  test('should handle GraphQL queries consistently', async ({ page, browserName }) => {
    console.log(`🌐 Testing GraphQL on ${browserName}`);
    
    // 🎭 Intercept GraphQL requests
    await page.route('**/graphql', async (route) => {
      const request = route.request();
      const postData = request.postDataJSON();
      
      // πŸ” Log the query for debugging
      console.log(`πŸ“Š GraphQL Query on ${browserName}:`, postData.query);
      
      // πŸ“ Mock different responses based on browser
      if (postData.query.includes('getProducts')) {
        await route.fulfill({
          status: 200,
          contentType: 'application/json',
          body: JSON.stringify({
            data: {
              products: [
                {
                  id: 'test-product',
                  name: `${browserName} Test Product πŸ§ͺ`,
                  price: 29.99,
                  emoji: 'πŸ§ͺ'
                }
              ]
            }
          })
        });
      } else {
        await route.continue();
      }
    });
    
    await page.goto('/products');
    
    // βœ… Verify product loaded with browser-specific data
    await expect(page.locator('[data-testid=product-name]')).toContainText(
      `${browserName} Test Product`
    );
    
    console.log(`βœ… GraphQL test passed on ${browserName}!`);
  });
  
  test('should handle WebSocket connections', async ({ page, browserName }) => {
    console.log(`πŸ”Œ Testing WebSocket on ${browserName}`);
    
    // 🎯 Track WebSocket messages
    const wsMessages: string[] = [];
    
    page.on('websocket', ws => {
      console.log(`πŸ”Œ WebSocket opened on ${browserName}: ${ws.url()}`);
      
      ws.on('framereceived', event => {
        wsMessages.push(event.payload as string);
        console.log(`πŸ“¨ Received: ${event.payload}`);
      });
      
      ws.on('framesent', event => {
        console.log(`πŸ“€ Sent: ${event.payload}`);
      });
    });
    
    await page.goto('/chat');
    
    // πŸ’¬ Send a message through WebSocket
    await page.fill('[data-testid=message-input]', 'Hello from Playwright! πŸ‘‹');
    await page.click('[data-testid=send-button]');
    
    // ⏳ Wait for WebSocket response
    await page.waitForFunction(() => 
      document.querySelector('[data-testid=message-list]')?.children.length ?? 0 > 0
    );
    
    // βœ… Verify message appears
    await expect(page.locator('[data-testid=message-list] .message')).toHaveCount(1);
    await expect(page.locator('[data-testid=message-list] .message')).toContainText(
      'Hello from Playwright!'
    );
    
    // πŸ“Š Verify WebSocket communication
    expect(wsMessages.length).toBeGreaterThan(0);
    
    console.log(`πŸ”Œ WebSocket test passed on ${browserName}!`);
  });
  
  test('should handle file uploads with different MIME types', async ({ page, browserName }) => {
    console.log(`πŸ“ Testing file upload on ${browserName}`);
    
    await page.goto('/upload');
    
    // πŸ“Έ Test image upload
    const fileInput = page.locator('[data-testid=file-input]');
    await fileInput.setInputFiles({
      name: 'test-image.png',
      mimeType: 'image/png',
      buffer: Buffer.from('fake-image-data')
    });
    
    // βœ… Verify file preview
    await expect(page.locator('[data-testid=file-preview]')).toBeVisible();
    await expect(page.locator('[data-testid=file-name]')).toContainText('test-image.png');
    
    // πŸš€ Upload file
    await page.click('[data-testid=upload-button]');
    
    // ⏳ Wait for upload progress
    await expect(page.locator('[data-testid=upload-progress]')).toBeVisible();
    await expect(page.locator('[data-testid=upload-success]')).toBeVisible();
    
    console.log(`πŸ“ File upload test passed on ${browserName}!`);
  });
});

πŸš€ Advanced Playwright Features

πŸ§™β€β™‚οΈ Visual Testing and Screenshots

Implement visual regression testing:

// 🎨 Visual testing configuration
import { test, expect } from '@playwright/test';

test.describe('🎨 Visual Regression Testing', () => {
  test('should match homepage design across browsers', async ({ page }) => {
    await page.goto('/');
    
    // πŸ“Έ Full page screenshot
    await expect(page).toHaveScreenshot('homepage-full.png');
    
    // 🎯 Component-specific screenshots
    const header = page.locator('[data-testid=header]');
    await expect(header).toHaveScreenshot('header-component.png');
    
    const productGrid = page.locator('[data-testid=product-grid]');
    await expect(productGrid).toHaveScreenshot('product-grid.png');
    
    console.log('πŸ“Έ Visual regression tests passed!');
  });
  
  test('should handle different viewport sizes', async ({ page }) => {
    const viewports = [
      { width: 1920, height: 1080, name: 'desktop' },
      { width: 768, height: 1024, name: 'tablet' },
      { width: 375, height: 667, name: 'mobile' }
    ];
    
    for (const viewport of viewports) {
      await page.setViewportSize(viewport);
      await page.goto('/');
      
      // πŸ“± Viewport-specific screenshots
      await expect(page).toHaveScreenshot(`homepage-${viewport.name}.png`);
    }
    
    console.log('πŸ“± Responsive design tests passed!');
  });
});

πŸ”„ Parallel Testing and Performance

Optimize test execution:

// ⚑ Performance testing utilities
import { test, expect } from '@playwright/test';

test.describe.configure({ mode: 'parallel' });

test.describe('⚑ Performance Testing', () => {
  test('should load pages within performance budgets', async ({ page }) => {
    // πŸ“Š Start performance monitoring
    await page.goto('/', { waitUntil: 'networkidle' });
    
    // ⏱️ Measure Core Web Vitals
    const webVitals = await page.evaluate(() => {
      return new Promise(resolve => {
        new PerformanceObserver(list => {
          const entries = list.getEntries();
          const vitals = {
            lcp: 0, // Largest Contentful Paint
            fid: 0, // First Input Delay
            cls: 0  // Cumulative Layout Shift
          };
          
          entries.forEach(entry => {
            if (entry.entryType === 'largest-contentful-paint') {
              vitals.lcp = entry.startTime;
            }
            if (entry.entryType === 'first-input') {
              vitals.fid = entry.processingStart - entry.startTime;
            }
            if (entry.entryType === 'layout-shift') {
              vitals.cls += (entry as any).value;
            }
          });
          
          resolve(vitals);
        }).observe({ entryTypes: ['largest-contentful-paint', 'first-input', 'layout-shift'] });
      });
    });
    
    // βœ… Assert performance budgets
    expect((webVitals as any).lcp).toBeLessThan(2500); // 2.5s
    expect((webVitals as any).cls).toBeLessThan(0.1);  // 0.1
    
    console.log('⚑ Performance tests passed!', webVitals);
  });
  
  test('should handle concurrent user load', async ({ browser }) => {
    // πŸ‘₯ Create multiple browser contexts
    const contexts = await Promise.all(
      Array.from({ length: 10 }, () => browser.newContext())
    );
    
    const pages = await Promise.all(
      contexts.map(context => context.newPage())
    );
    
    // πŸš€ Simulate concurrent users
    const startTime = Date.now();
    
    await Promise.all(
      pages.map((page, index) => 
        page.goto('/', { 
          waitUntil: 'networkidle',
          timeout: 30000 
        }).then(() => {
          console.log(`πŸ‘€ User ${index + 1} loaded page`);
        })
      )
    );
    
    const endTime = Date.now();
    const totalTime = endTime - startTime;
    
    // βœ… Verify all pages loaded within reasonable time
    expect(totalTime).toBeLessThan(15000); // 15 seconds for 10 concurrent users
    
    // 🧹 Cleanup
    await Promise.all(contexts.map(context => context.close()));
    
    console.log(`πŸ‘₯ Concurrent load test passed! Total time: ${totalTime}ms`);
  });
});

⚠️ Common Playwright Pitfalls

😱 Pitfall 1: Not Waiting Properly

// ❌ Wrong - race conditions and flaky tests!
test('flaky test example', async ({ page }) => {
  await page.goto('/dashboard');
  await page.click('[data-testid=load-data-btn]');
  
  // πŸ’₯ This might fail if data hasn't loaded yet!
  await expect(page.locator('[data-testid=data-table]')).toBeVisible();
});

// βœ… Correct - proper waiting strategies!
test('stable test example', async ({ page }) => {
  await page.goto('/dashboard');
  await page.click('[data-testid=load-data-btn]');
  
  // ⏳ Wait for loading to complete
  await page.waitForSelector('[data-testid=loading-spinner]', { state: 'hidden' });
  
  // βœ… Or use built-in auto-waiting
  await expect(page.locator('[data-testid=data-table]')).toBeVisible();
  
  // 🎯 Or wait for specific content
  await expect(page.locator('[data-testid=data-table] tbody tr')).toHaveCount.toBeGreaterThan(0);
});

🀯 Pitfall 2: Browser Context Pollution

// ❌ Wrong - shared state between tests!
test.describe.configure({ mode: 'serial' }); // Wrong approach

test('first test modifies state', async ({ page }) => {
  await page.goto('/login');
  // This test sets cookies/localStorage
});

test('second test assumes clean state', async ({ page }) => {
  await page.goto('/');
  // πŸ’₯ Might fail due to leftover state!
});

// βœ… Correct - isolated test contexts!
test.describe('Isolated Tests', () => {
  test('each test gets fresh context', async ({ page }) => {
    // Each test gets a fresh browser context automatically
    await page.goto('/');
    // Test runs with clean state
  });
  
  test('another independent test', async ({ page }) => {
    // Fresh context again!
    await page.goto('/login');
  });
});

πŸ› οΈ Playwright Best Practices

  1. 🎯 Use Data Attributes: Rely on data-testid for stable selectors
  2. ⚑ Leverage Auto-Waiting: Trust Playwright’s built-in waiting mechanisms
  3. 🧹 Clean Test Data: Use fixtures for consistent test state
  4. πŸ“± Test Cross-Browser: Don’t assume all browsers behave the same
  5. 🎨 Visual Testing: Use screenshots for UI regression testing
  6. πŸ”„ Parallel Execution: Take advantage of Playwright’s speed
  7. πŸ“Š Monitor Performance: Include performance assertions in tests

πŸ§ͺ Hands-On Exercise

🎯 Challenge: Build a Modern Social Platform Test Suite

Create a comprehensive Playwright test suite for a social media platform:

πŸ“‹ Requirements:

  • βœ… Cross-browser testing (Chrome, Firefox, Safari)
  • πŸ“± Mobile and desktop responsive testing
  • πŸ” Authentication flows with social login
  • πŸ“ Content creation (text, images, videos)
  • πŸ’¬ Real-time messaging and notifications
  • πŸŒ™ Dark/light theme switching
  • 🌐 Multi-language support testing
  • πŸ“Š Analytics and performance monitoring

πŸš€ Bonus Points:

  • Visual regression testing
  • Performance budget enforcement
  • Accessibility testing integration
  • Load testing simulation

πŸ’‘ Solution Starter

πŸ” Click to see solution starter
// 🎯 Social platform test starter
import { test, expect, devices } from '@playwright/test';

// 🌐 Cross-browser configuration
const browsers = ['chromium', 'firefox', 'webkit'];
const devices_list = ['Desktop Chrome', 'iPhone 12', 'iPad Pro'];

test.describe('🌐 Social Platform - Cross Browser', () => {
  for (const browserName of browsers) {
    test.describe(`${browserName} browser tests`, () => {
      test.use({ browserName: browserName as any });
      
      test('should handle complete social workflow', async ({ page }) => {
        // πŸ” Authentication
        await page.goto('/login');
        await page.fill('[data-testid=email]', '[email protected]');
        await page.fill('[data-testid=password]', 'SocialPass123!');
        await page.click('[data-testid=login-btn]');
        
        // βœ… Verify dashboard
        await expect(page.locator('[data-testid=user-feed]')).toBeVisible();
        
        // πŸ“ Create post
        await page.click('[data-testid=new-post-btn]');
        await page.fill('[data-testid=post-content]', `Testing on ${browserName}! πŸš€`);
        
        // πŸ“Έ Upload image
        await page.setInputFiles('[data-testid=image-upload]', 'tests/fixtures/test-image.jpg');
        
        // 🎯 Publish post
        await page.click('[data-testid=publish-btn]');
        
        // βœ… Verify post appears
        await expect(page.locator('[data-testid=post-feed] .post').first())
          .toContainText(`Testing on ${browserName}!`);
        
        console.log(`βœ… Social workflow test passed on ${browserName}!`);
      });
    });
  }
  
  // πŸ“± Device-specific tests
  for (const deviceName of devices_list) {
    test.describe(`${deviceName} device tests`, () => {
      test.use({ ...devices[deviceName] });
      
      test('should have responsive design', async ({ page }) => {
        await page.goto('/');
        
        // πŸ“± Test mobile navigation
        if (deviceName.includes('iPhone') || deviceName.includes('iPad')) {
          await expect(page.locator('[data-testid=mobile-nav]')).toBeVisible();
        } else {
          await expect(page.locator('[data-testid=desktop-nav]')).toBeVisible();
        }
        
        console.log(`πŸ“± Responsive test passed on ${deviceName}!`);
      });
    });
  }
});

πŸŽ“ Key Takeaways

You’ve mastered modern E2E testing with Playwright! Here’s what you can now do:

  • βœ… Set up Playwright with TypeScript for comprehensive testing πŸ”§
  • βœ… Write cross-browser tests that work on all platforms 🌐
  • βœ… Handle complex scenarios like file uploads and real-time features πŸ“±
  • βœ… Implement visual regression testing for UI consistency 🎨
  • βœ… Optimize test performance with parallel execution πŸš€
  • βœ… Apply best practices for maintainable test suites πŸ› οΈ

Remember: Playwright gives you superpowers to test like never before - use them wisely! 🎭

🀝 Next Steps

Congratulations! πŸŽ‰ You’ve mastered modern E2E testing with Playwright and TypeScript!

Here’s what to explore next:

  1. πŸ’» Practice with the social platform exercise above
  2. πŸ—οΈ Migrate existing tests from other frameworks to Playwright
  3. πŸ“š Explore Playwright’s API testing capabilities
  4. πŸ”„ Set up CI/CD pipelines with Playwright
  5. 🎨 Dive deeper into visual testing and accessibility

Remember: With great testing power comes great responsibility. Build tests that give you confidence and make your users happy! πŸš€


Happy testing with Playwright! πŸŽ‰πŸŽ­βœ¨