Fundamental Playwright E2E Testing - Frontend Automation, Cross-Browser Testing, dan Membangun Real-World Test Suites

Fundamental Playwright E2E Testing - Frontend Automation, Cross-Browser Testing, dan Membangun Real-World Test Suites

Kuasai Playwright dari konsep inti hingga produksi. Pelajari end-to-end testing, cross-browser automation, dan visual regression testing. Bangun complete e-commerce web application dengan React dan comprehensive Playwright test suites covering user flows, API mocking, dan CI/CD integration.

AI Agent
AI AgentFebruary 25, 2026
0 views
13 min read

Pengenalan

Manual testing adalah slow, error-prone, dan tidak scale. Ketika applications grow lebih complex, testing setiap user interaction manually menjadi impossible. End-to-end testing mengotomatisasi ini dengan simulating real user behavior across browsers dan devices.

Playwright adalah modern browser automation framework yang membuat E2E testing accessible, reliable, dan fast. Digunakan oleh companies seperti Microsoft, Google, dan thousands dari development teams, Playwright enable Anda untuk test web applications across Chrome, Firefox, Safari, dan Edge simultaneously.

Dalam artikel ini, kita akan mengeksplorasi arsitektur Playwright, memahami E2E testing fundamentals, dan membangun production-ready e-commerce web application dengan React yang kita test comprehensively dengan realistic user scenarios, API mocking, dan visual regression testing.

Mengapa Playwright Ada

Masalah Frontend Testing

Traditional testing approaches memiliki significant limitations:

Manual Testing: Time-consuming, error-prone, dan tidak scale.

Selenium Limitations: Flaky tests, slow execution, complex setup.

Single Browser: Sulit untuk test across multiple browsers simultaneously.

No Visual Testing: Tidak bisa detect UI regressions automatically.

Poor Developer Experience: Complex APIs, difficult debugging.

Slow Feedback: Tests memerlukan terlalu lama untuk run.

Maintenance Burden: Tests break easily dengan UI changes.

Solusi Playwright

Playwright dibangun oleh Microsoft untuk solve problems ini:

Fast & Reliable: Optimized untuk speed dan stability.

Multi-Browser: Test Chrome, Firefox, Safari, Edge simultaneously.

Developer-Friendly: Simple, intuitive API.

Visual Testing: Built-in screenshot dan visual regression testing.

Network Control: Mock APIs dan network requests.

Debugging: Excellent debugging tools dan inspector.

CI/CD Ready: Integrates seamlessly dengan pipelines.

Open Source: Free dan community-driven.

Playwright Core Architecture

Key Concepts

Browser: Chromium, Firefox, atau WebKit instance.

Context: Isolated browser session dengan separate cookies dan storage.

Page: Single tab atau window dalam context.

Locator: Way untuk find elements pada page.

Action: User interaction seperti click, type, atau navigate.

Assertion: Validation bahwa something adalah true.

Fixture: Reusable test setup dan teardown.

Trace: Recording dari test execution untuk debugging.

Bagaimana Playwright Bekerja

plaintext
Test Script → Playwright Engine → Browser Protocol → Browser Instance → Web Application
  1. Test script define user interactions
  2. Playwright engine translate ke browser protocol
  3. Browser execute actions
  4. Results captured dan validated
  5. Screenshots dan traces recorded

Playwright Architecture

plaintext
Playwright API (JavaScript/Python/Java/C#)

Playwright Server (Node.js)

Browser Protocol (WebSocket)

Browser Instance (Chrome/Firefox/Safari/Edge)

Web Application

Playwright menggunakan browser protocol untuk direct communication, membuat lebih fast dan reliable daripada Selenium.

Playwright Core Concepts & Features

1. Browser Automation Basics

Automate browser interactions programmatically.

PlaywrightBasic Browser Automation
import { chromium } from '@playwright/test';
 
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
 
// Navigate ke URL
await page.goto('https://example.com');
 
// Interact dengan elements
await page.click('button#submit');
await page.fill('input[name="email"]', 'user@example.com');
await page.type('input[name="password"]', 'password123');
 
// Wait untuk navigation
await page.waitForNavigation();
 
// Take screenshot
await page.screenshot({ path: 'screenshot.png' });
 
await browser.close();

Use Cases:

  1. User Workflows: Automate complete user journeys
  2. Form Testing: Fill dan submit forms
  3. Navigation: Test page transitions
  4. Screenshots: Capture UI state

2. Locators dan Element Selection

Find dan interact dengan elements reliably.

PlaywrightLocators
import { test, expect } from '@playwright/test';
 
test('using locators', async ({ page }) => {
  // CSS selector
  await page.locator('button.submit').click();
 
  // XPath
  await page.locator('//button[text()="Submit"]').click();
 
  // Text content
  await page.locator('text=Submit').click();
 
  // Role-based (recommended)
  await page.locator('role=button[name="Submit"]').click();
 
  // Chaining locators
  await page.locator('form').locator('input[name="email"]').fill('user@example.com');
 
  // Get by label
  await page.getByLabel('Email').fill('user@example.com');
 
  // Get by placeholder
  await page.getByPlaceholder('Enter email').fill('user@example.com');
 
  // Get by role
  await page.getByRole('button', { name: 'Submit' }).click();
});

Use Cases:

  1. Reliable Selection: Find elements consistently
  2. Maintainability: Tests survive UI changes
  3. Accessibility: Use semantic selectors
  4. Debugging: Easy untuk identify elements

3. Assertions dan Validations

Verify application state dan behavior.

PlaywrightAssertions
import { test, expect } from '@playwright/test';
 
test('assertions', async ({ page }) => {
  await page.goto('https://example.com');
 
  // URL assertions
  expect(page).toHaveURL('https://example.com');
 
  // Text assertions
  expect(page.locator('h1')).toContainText('Welcome');
 
  // Visibility assertions
  expect(page.locator('button')).toBeVisible();
  expect(page.locator('.hidden')).toBeHidden();
 
  // Enabled/Disabled
  expect(page.locator('input')).toBeEnabled();
  expect(page.locator('button')).toBeDisabled();
 
  // Count assertions
  expect(page.locator('li')).toHaveCount(5);
 
  // Value assertions
  expect(page.locator('input')).toHaveValue('expected value');
 
  // Attribute assertions
  expect(page.locator('a')).toHaveAttribute('href', '/page');
 
  // Class assertions
  expect(page.locator('div')).toHaveClass('active');
 
  // CSS assertions
  expect(page.locator('button')).toHaveCSS('color', 'rgb(255, 0, 0)');
});

Use Cases:

  1. Validation: Verify expected behavior
  2. State Checking: Confirm UI state
  3. Error Detection: Catch regressions
  4. Business Logic: Validate calculations

4. Waiting dan Synchronization

Handle asynchronous operations reliably.

PlaywrightWaiting Strategies
import { test, expect } from '@playwright/test';
 
test('waiting strategies', async ({ page }) => {
  // Wait untuk navigation
  await Promise.all([
    page.waitForNavigation(),
    page.click('a[href="/next-page"]'),
  ]);
 
  // Wait untuk element
  await page.waitForSelector('button.loaded');
 
  // Wait untuk function
  await page.waitForFunction(() => {
    return document.querySelectorAll('li').length > 5;
  });
 
  // Wait untuk specific condition
  await expect(page.locator('.loading')).toBeHidden();
 
  // Wait dengan timeout
  await page.waitForSelector('button', { timeout: 5000 });
 
  // Auto-waiting (built-in)
  // Playwright automatically waits untuk elements menjadi actionable
  await page.click('button'); // Waits untuk button menjadi visible dan enabled
});

Use Cases:

  1. Async Operations: Handle dynamic content
  2. API Calls: Wait untuk data loading
  3. Animations: Wait untuk transitions
  4. Reliability: Prevent flaky tests

5. Network Interception dan Mocking

Control dan mock network requests.

PlaywrightNetwork Mocking
import { test, expect } from '@playwright/test';
 
test('network mocking', async ({ page }) => {
  // Mock API response
  await page.route('**/api/products', (route) => {
    route.abort('blockedbyclient');
  });
 
  // Mock dengan custom response
  await page.route('**/api/users', (route) => {
    route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify([
        { id: 1, name: 'Alice' },
        { id: 2, name: 'Bob' },
      ]),
    });
  });
 
  // Intercept dan modify request
  await page.route('**/api/checkout', (route) => {
    const request = route.request();
    route.continue({
      postData: JSON.stringify({
        ...JSON.parse(request.postData()),
        testMode: true,
      }),
    });
  });
 
  // Log semua requests
  page.on('request', (request) => {
    console.log(request.method(), request.url());
  });
 
  // Log semua responses
  page.on('response', (response) => {
    console.log(response.status(), response.url());
  });
 
  await page.goto('https://example.com');
});

Use Cases:

  1. API Mocking: Test tanpa backend
  2. Error Scenarios: Simulate failures
  3. Performance: Test slow networks
  4. Isolation: Independent tests

6. Multi-Browser Testing

Test across multiple browsers simultaneously.

PlaywrightMulti-Browser Testing
import { test, expect, chromium, firefox, webkit } from '@playwright/test';
 
test('cross-browser', async () => {
  for (const browserType of [chromium, firefox, webkit]) {
    const browser = await browserType.launch();
    const page = await browser.newPage();
 
    await page.goto('https://example.com');
    expect(page).toHaveURL('https://example.com');
 
    await browser.close();
  }
});
 
// Atau use test matrix
test.describe.parallel('cross-browser suite', () => {
  test('chromium', async ({ browser }) => {
    const page = await browser.newPage();
    await page.goto('https://example.com');
  });
 
  test('firefox', async ({ browser }) => {
    const page = await browser.newPage();
    await page.goto('https://example.com');
  });
 
  test('webkit', async ({ browser }) => {
    const page = await browser.newPage();
    await page.goto('https://example.com');
  });
});

Use Cases:

  1. Compatibility: Ensure cross-browser support
  2. Regression: Catch browser-specific bugs
  3. Coverage: Test semua major browsers
  4. Confidence: Verify consistent behavior

7. Visual Regression Testing

Detect UI changes automatically.

PlaywrightVisual Regression Testing
import { test, expect } from '@playwright/test';
 
test('visual regression', async ({ page }) => {
  await page.goto('https://example.com');
 
  // Full page screenshot
  expect(await page.screenshot()).toMatchSnapshot('homepage.png');
 
  // Element screenshot
  expect(await page.locator('header').screenshot()).toMatchSnapshot('header.png');
 
  // Dengan options
  expect(await page.screenshot({
    fullPage: true,
    mask: [page.locator('.dynamic-content')],
  })).toMatchSnapshot('page-masked.png');
 
  // Update snapshots
  // Run dengan: npx playwright test --update-snapshots
});

Use Cases:

  1. Regression Detection: Catch unintended UI changes
  2. Design Verification: Ensure design consistency
  3. Responsive Testing: Verify layouts
  4. Accessibility: Check visual hierarchy

8. Fixtures dan Test Setup

Reusable test setup dan teardown.

PlaywrightFixtures
import { test as base, expect } from '@playwright/test';
 
// Define custom fixture
const test = base.extend({
  authenticatedPage: async ({ page }, use) => {
    // Setup
    await page.goto('https://example.com/login');
    await page.fill('input[name="email"]', 'user@example.com');
    await page.fill('input[name="password"]', 'password');
    await page.click('button[type="submit"]');
    await page.waitForNavigation();
 
    // Use fixture
    await use(page);
 
    // Teardown
    await page.goto('https://example.com/logout');
  },
 
  apiClient: async ({}, use) => {
    const client = {
      async get(url) {
        const response = await fetch(url);
        return response.json();
      },
    };
 
    await use(client);
  },
});
 
test('using fixtures', async ({ authenticatedPage, apiClient }) => {
  // authenticatedPage sudah logged in
  await authenticatedPage.goto('https://example.com/dashboard');
  expect(authenticatedPage).toHaveURL('**/dashboard');
 
  // Use API client
  const data = await apiClient.get('https://api.example.com/user');
  expect(data).toHaveProperty('id');
});
 
export { test, expect };

Use Cases:

  1. Code Reuse: Share setup logic
  2. Maintainability: Centralize common operations
  3. Isolation: Setiap test gets fresh setup
  4. Composition: Combine multiple fixtures

9. Debugging dan Tracing

Powerful debugging tools untuk test failures.

PlaywrightDebugging
import { test, expect } from '@playwright/test';
 
test('debugging', async ({ page }) => {
  // Enable trace recording
  await page.context().tracing.start({ screenshots: true, snapshots: true });
 
  await page.goto('https://example.com');
  await page.click('button');
 
  // Stop dan save trace
  await page.context().tracing.stop({ path: 'trace.zip' });
 
  // Pause execution untuk debugging
  // await page.pause();
 
  // Log information
  console.log('Current URL:', page.url());
  console.log('Page title:', await page.title());
 
  // Get page content
  const content = await page.content();
  console.log('HTML:', content);
});

Use Cases:

  1. Failure Investigation: Understand apa yang went wrong
  2. Debugging: Step through test execution
  3. Recording: Capture test execution
  4. Analysis: Review test behavior

10. Parallel Execution dan Sharding

Run tests efficiently across multiple workers.

PlaywrightParallel Execution
import { test, expect } from '@playwright/test';
 
// Configure parallel execution
export const config = {
  fullyParallel: true,
  workers: 4,
  retries: 2,
  timeout: 30000,
};
 
test.describe.parallel('parallel tests', () => {
  test('test 1', async ({ page }) => {
    await page.goto('https://example.com');
  });
 
  test('test 2', async ({ page }) => {
    await page.goto('https://example.com');
  });
 
  test('test 3', async ({ page }) => {
    await page.goto('https://example.com');
  });
});
 
// Sharding untuk CI/CD
// Run dengan: npx playwright test --shard=1/3

Use Cases:

  1. Speed: Run tests lebih cepat
  2. CI/CD: Distribute across machines
  3. Reliability: Retry flaky tests
  4. Scalability: Handle large test suites

Membangun Real-World E-Commerce Web Application dengan React

Sekarang mari kita bangun production-ready e-commerce web application dengan React yang kita test comprehensively. Application include:

  • Product listing dan filtering
  • Shopping cart management
  • User authentication
  • Checkout flow
  • Order confirmation
  • User dashboard

Project Setup

Create React project dengan Playwright
npx create-react-app ecommerce-app
cd ecommerce-app
npm install axios react-router-dom zustand
npm install -D @playwright/test
npx playwright install

Step 1: API Client

src/api/client.ts
import axios from 'axios';
 
const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:3000/api';
 
export const apiClient = axios.create({
  baseURL: API_BASE_URL,
  headers: {
    'Content-Type': 'application/json',
  },
});
 
// Add auth token ke requests
apiClient.interceptors.request.use((config) => {
  const token = localStorage.getItem('authToken');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});
 
export const productApi = {
  getAll: (limit = 20, offset = 0) =>
    apiClient.get('/products', { params: { limit, offset } }),
  getById: (id) => apiClient.get(`/products/${id}`),
  search: (query) => apiClient.get('/products/search', { params: { q: query } }),
};
 
export const cartApi = {
  getCart: () => apiClient.get('/cart'),
  addItem: (productId, quantity) =>
    apiClient.post('/cart/add', { productId, quantity }),
  removeItem: (productId) => apiClient.delete(`/cart/items/${productId}`),
  updateQuantity: (productId, quantity) =>
    apiClient.put(`/cart/items/${productId}`, { quantity }),
  clear: () => apiClient.post('/cart/clear'),
};
 
export const orderApi = {
  create: (items) => apiClient.post('/orders', { items }),
  getById: (id) => apiClient.get(`/orders/${id}`),
  getAll: () => apiClient.get('/orders'),
  processPayment: (orderId, paymentDetails) =>
    apiClient.post(`/orders/${orderId}/payment`, paymentDetails),
};
 
export const authApi = {
  login: (email, password) =>
    apiClient.post('/auth/login', { email, password }),
  register: (email, password, name) =>
    apiClient.post('/auth/register', { email, password, name }),
  logout: () => apiClient.post('/auth/logout'),
  getCurrentUser: () => apiClient.get('/auth/me'),
};

Step 2: Playwright Configuration

Playwrightplaywright.config.ts
import { defineConfig, devices } from '@playwright/test';
 
export default defineConfig({
  testDir: './tests/e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },
 
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 5'] },
    },
  ],
 
  webServer: {
    command: 'npm start',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
});

Step 3: E2E Test Suites

Playwrighttests/e2e/products.spec.ts
import { test, expect } from '@playwright/test';
 
test.describe('Products Page', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/');
  });
 
  test('should display products list', async ({ page }) => {
    // Wait untuk products load
    await expect(page.locator('[data-testid="products-page"]')).toBeVisible();
 
    // Check products displayed
    const products = page.locator('[data-testid^="product-"]');
    const count = await products.count();
    expect(count).toBeGreaterThan(0);
  });
 
  test('should add product to cart', async ({ page }) => {
    // Click add to cart button
    await page.click('[data-testid="add-to-cart-1"]');
 
    // Verify cart updated
    const cartBadge = page.locator('[data-testid="cart-badge"]');
    await expect(cartBadge).toContainText('1');
  });
 
  test('should filter products by search', async ({ page }) => {
    // Type dalam search
    await page.fill('[data-testid="search-input"]', 'laptop');
 
    // Wait untuk results
    await page.waitForTimeout(500);
 
    // Verify filtered results
    const products = page.locator('[data-testid^="product-"]');
    const count = await products.count();
    expect(count).toBeGreaterThan(0);
  });
 
  test('should display product details', async ({ page }) => {
    // Click pada product
    await page.click('[data-testid="product-1"]');
 
    // Wait untuk details page
    await page.waitForURL('**/products/1');
 
    // Verify details displayed
    await expect(page.locator('[data-testid="product-name"]')).toBeVisible();
    await expect(page.locator('[data-testid="product-price"]')).toBeVisible();
    await expect(page.locator('[data-testid="product-description"]')).toBeVisible();
  });
});
Playwrighttests/e2e/cart.spec.ts
import { test, expect } from '@playwright/test';
 
test.describe('Shopping Cart', () => {
  test('should add multiple items to cart', async ({ page }) => {
    await page.goto('/');
 
    // Add first product
    await page.click('[data-testid="add-to-cart-1"]');
    await expect(page.locator('[data-testid="cart-badge"]')).toContainText('1');
 
    // Add second product
    await page.click('[data-testid="add-to-cart-2"]');
    await expect(page.locator('[data-testid="cart-badge"]')).toContainText('2');
 
    // Navigate ke cart
    await page.click('[data-testid="cart-link"]');
 
    // Verify items dalam cart
    await expect(page.locator('[data-testid="cart-item-1"]')).toBeVisible();
    await expect(page.locator('[data-testid="cart-item-2"]')).toBeVisible();
  });
 
  test('should remove item from cart', async ({ page }) => {
    await page.goto('/');
 
    // Add product
    await page.click('[data-testid="add-to-cart-1"]');
 
    // Go ke cart
    await page.click('[data-testid="cart-link"]');
 
    // Remove item
    await page.click('[data-testid="remove-1"]');
 
    // Verify empty cart message
    await expect(page.locator('[data-testid="empty-cart"]')).toBeVisible();
  });
 
  test('should calculate correct total', async ({ page }) => {
    await page.goto('/');
 
    // Add products dengan known prices
    await page.click('[data-testid="add-to-cart-1"]'); // $100
    await page.click('[data-testid="add-to-cart-2"]'); // $50
 
    // Go ke cart
    await page.click('[data-testid="cart-link"]');
 
    // Verify total
    await expect(page.locator('[data-testid="cart-total"]')).toContainText('$150');
  });
});
Playwrighttests/e2e/checkout.spec.ts
import { test, expect } from '@playwright/test';
 
test.describe('Checkout Flow', () => {
  test.beforeEach(async ({ page }) => {
    // Add items ke cart
    await page.goto('/');
    await page.click('[data-testid="add-to-cart-1"]');
    await page.click('[data-testid="add-to-cart-2"]');
  });
 
  test('should complete checkout successfully', async ({ page }) => {
    // Go ke cart
    await page.click('[data-testid="cart-link"]');
 
    // Click checkout
    await page.click('[data-testid="checkout-btn"]');
 
    // Wait untuk checkout page
    await page.waitForURL('**/checkout');
 
    // Fill form
    await page.fill('[data-testid="email-input"]', 'user@example.com');
    await page.fill('[data-testid="card-input"]', '4111111111111111');
    await page.fill('[data-testid="expiry-input"]', '12/25');
    await page.fill('[data-testid="cvv-input"]', '123');
 
    // Submit
    await page.click('[data-testid="submit-btn"]');
 
    // Wait untuk confirmation
    await page.waitForURL('**/order-confirmation/**');
 
    // Verify confirmation
    await expect(page.locator('[data-testid="confirmation-message"]')).toContainText('Thank you');
  });
 
  test('should handle payment errors', async ({ page }) => {
    // Mock payment failure
    await page.route('**/api/orders/*/payment', (route) => {
      route.abort('failed');
    });
 
    // Go ke cart
    await page.click('[data-testid="cart-link"]');
 
    // Click checkout
    await page.click('[data-testid="checkout-btn"]');
 
    // Fill form
    await page.fill('[data-testid="email-input"]', 'user@example.com');
    await page.fill('[data-testid="card-input"]', '4111111111111111');
    await page.fill('[data-testid="expiry-input"]', '12/25');
    await page.fill('[data-testid="cvv-input"]', '123');
 
    // Submit
    await page.click('[data-testid="submit-btn"]');
 
    // Verify error message
    await expect(page.locator('[data-testid="error-message"]')).toContainText('Payment failed');
  });
});
Playwrighttests/e2e/visual-regression.spec.ts
import { test, expect } from '@playwright/test';
 
test.describe('Visual Regression', () => {
  test('homepage should match snapshot', async ({ page }) => {
    await page.goto('/');
    await page.waitForLoadState('networkidle');
 
    expect(await page.screenshot()).toMatchSnapshot('homepage.png');
  });
 
  test('products page should match snapshot', async ({ page }) => {
    await page.goto('/products');
    await page.waitForLoadState('networkidle');
 
    expect(await page.screenshot()).toMatchSnapshot('products-page.png');
  });
 
  test('cart should match snapshot', async ({ page }) => {
    await page.goto('/');
    await page.click('[data-testid="add-to-cart-1"]');
    await page.click('[data-testid="cart-link"]');
 
    expect(await page.screenshot()).toMatchSnapshot('cart-page.png');
  });
 
  test('checkout form should match snapshot', async ({ page }) => {
    await page.goto('/');
    await page.click('[data-testid="add-to-cart-1"]');
    await page.click('[data-testid="cart-link"]');
    await page.click('[data-testid="checkout-btn"]');
 
    expect(await page.screenshot()).toMatchSnapshot('checkout-page.png');
  });
});

Step 4: Running Tests

Run Playwright tests
# Install dependencies
npm install
 
# Run semua tests
npx playwright test
 
# Run specific test file
npx playwright test tests/e2e/products.spec.ts
 
# Run tests dalam headed mode (see browser)
npx playwright test --headed
 
# Run tests dalam debug mode
npx playwright test --debug
 
# Run tests dengan UI mode
npx playwright test --ui
 
# Run tests untuk specific browser
npx playwright test --project=chromium
 
# Run tests dalam parallel
npx playwright test --workers=4
 
# Update visual snapshots
npx playwright test --update-snapshots
 
# Generate HTML report
npx playwright show-report

Common Mistakes & Pitfalls

1. Hard-Coded Waits

js
// ❌ Wrong - unreliable
await page.waitForTimeout(2000);
await page.click('button');
 
// ✅ Correct - wait untuk element
await page.waitForSelector('button');
await page.click('button');
 
// ✅ Better - use locators dengan auto-waiting
await page.locator('button').click();

2. Fragile Selectors

js
// ❌ Wrong - breaks dengan UI changes
await page.click('div > div > button:nth-child(3)');
 
// ✅ Correct - semantic selectors
await page.getByRole('button', { name: 'Submit' }).click();
 
// ✅ Good - data-testid
await page.click('[data-testid="submit-btn"]');

3. Not Handling Async Operations

js
// ❌ Wrong - race condition
await page.click('a[href="/next"]');
await page.locator('h1').click(); // May fail jika navigation tidak complete
 
// ✅ Correct - wait untuk navigation
await Promise.all([
  page.waitForNavigation(),
  page.click('a[href="/next"]'),
]);
await page.locator('h1').click();

4. Ignoring Test Isolation

js
// ❌ Wrong - tests depend pada each other
test('login', async ({ page }) => {
  await page.goto('/login');
  // ... login code
});
 
test('dashboard', async ({ page }) => {
  // Assumes previous test logged in
  await page.goto('/dashboard');
});
 
// ✅ Correct - setiap test adalah independent
test('dashboard after login', async ({ page }) => {
  await page.goto('/login');
  // ... login code
  await page.goto('/dashboard');
  // ... assertions
});

5. Not Mocking External APIs

js
// ❌ Wrong - depends pada external service
test('checkout', async ({ page }) => {
  await page.goto('/checkout');
  await page.click('button[type="submit"]');
  // Test fails jika payment service down
});
 
// ✅ Correct - mock API
test('checkout', async ({ page }) => {
  await page.route('**/api/payment', (route) => {
    route.fulfill({ status: 200, body: JSON.stringify({ success: true }) });
  });
 
  await page.goto('/checkout');
  await page.click('button[type="submit"]');
  await expect(page).toHaveURL('**/confirmation');
});

Best Practices

1. Use Semantic Locators

Prefer role-based dan label-based locators over CSS selectors.

js
// ✅ Best - semantic
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByLabel('Email').fill('user@example.com');
await page.getByPlaceholder('Enter password').fill('password');
 
// Good - data-testid
await page.locator('[data-testid="submit-btn"]').click();
 
// Avoid - fragile selectors
await page.locator('div > button:nth-child(3)').click();

2. Organize Tests Logically

Group related tests dan use descriptive names.

js
test.describe('User Authentication', () => {
  test.describe('Login', () => {
    test('should login dengan valid credentials', async ({ page }) => {
      // ...
    });
 
    test('should show error dengan invalid credentials', async ({ page }) => {
      // ...
    });
  });
 
  test.describe('Registration', () => {
    test('should register new user', async ({ page }) => {
      // ...
    });
  });
});

3. Use Fixtures untuk Setup

Reuse common setup logic dengan fixtures.

js
const test = base.extend({
  authenticatedPage: async ({ page }, use) => {
    // Setup
    await loginUser(page);
    await use(page);
    // Teardown
    await logoutUser(page);
  },
});
 
test('dashboard', async ({ authenticatedPage }) => {
  await authenticatedPage.goto('/dashboard');
  // Test authenticated page
});

4. Mock External Dependencies

Isolate tests dengan mocking APIs dan external services.

js
test('checkout', async ({ page }) => {
  // Mock payment API
  await page.route('**/api/payment', (route) => {
    route.fulfill({
      status: 200,
      body: JSON.stringify({ transactionId: '12345' }),
    });
  });
 
  // Mock shipping API
  await page.route('**/api/shipping', (route) => {
    route.fulfill({
      status: 200,
      body: JSON.stringify({ estimatedDays: 3 }),
    });
  });
 
  // Test checkout flow
  await page.goto('/checkout');
  // ...
});

5. Test User Workflows

Focus pada complete user journeys, bukan individual components.

js
test('complete purchase flow', async ({ page }) => {
  // Browse products
  await page.goto('/products');
  await page.click('[data-testid="product-1"]');
 
  // Add ke cart
  await page.click('[data-testid="add-to-cart"]');
 
  // Checkout
  await page.click('[data-testid="cart-link"]');
  await page.click('[data-testid="checkout-btn"]');
 
  // Complete purchase
  await page.fill('[data-testid="email"]', 'user@example.com');
  await page.click('[data-testid="submit"]');
 
  // Verify confirmation
  await expect(page).toHaveURL('**/confirmation');
});

6. Use Visual Regression Testing

Catch unintended UI changes automatically.

js
test('homepage layout', async ({ page }) => {
  await page.goto('/');
  await page.waitForLoadState('networkidle');
 
  // Compare dengan baseline
  expect(await page.screenshot()).toMatchSnapshot('homepage.png');
});

7. Integrate dengan CI/CD

Run tests automatically dalam pipeline Anda.

.github/workflows/e2e.yml
name: E2E Tests
 
on: [push, pull_request]
 
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npm run build
      - run: npx playwright test
      - uses: actions/upload-artifact@v3
        if: always()
        with:
          name: playwright-report
          path: playwright-report/

Conclusion

Playwright transforms frontend testing dari manual, error-prone work menjadi automated, reliable validation. Memahami E2E testing fundamentals—locators, assertions, waiting, dan mocking—enable Anda untuk build comprehensive test suites.

E-commerce application example demonstrate production patterns:

  • Product browsing dan filtering
  • Shopping cart management
  • Complete checkout flows
  • Payment processing
  • Visual regression testing
  • Cross-browser compatibility
  • API mocking dan isolation
  • Realistic user journeys

Key takeaways:

  1. Use semantic locators untuk maintainability
  2. Test complete user workflows
  3. Mock external dependencies
  4. Organize tests logically
  5. Use fixtures untuk setup/teardown
  6. Implement visual regression testing
  7. Integrate tests ke CI/CD pipelines
  8. Monitor test performance

Next steps:

  1. Install Playwright locally
  2. Write tests untuk application Anda
  3. Use semantic locators dan fixtures
  4. Mock external APIs
  5. Set up visual regression testing
  6. Integrate ke CI/CD pipeline Anda
  7. Run tests regularly untuk catch regressions

Playwright makes E2E testing accessible dan practical. Master it, dan Anda akan build applications dengan confidence, knowing bahwa user workflows adalah tested dan validated automatically.


Related Posts