Table of contents

Can I use Playwright to test Progressive Web Apps (PWAs)?

Yes, Playwright is an excellent choice for testing Progressive Web Apps (PWAs). It provides comprehensive support for PWA-specific features including service workers, offline functionality, app manifests, and installation behavior. Playwright's powerful browser automation capabilities make it particularly well-suited for testing the complex interactions and behaviors that define modern PWAs.

What Makes PWAs Unique for Testing

Progressive Web Apps present unique testing challenges compared to traditional web applications:

  • Service Workers: Background scripts that enable offline functionality and caching
  • App Manifests: JSON files that define app metadata and installation behavior
  • Installation Prompts: Browser-native installation dialogs and behaviors
  • Offline Functionality: Network-independent features and cached content
  • Push Notifications: Background messaging capabilities
  • Responsive Design: Adaptive layouts across different screen sizes and orientations

Setting Up Playwright for PWA Testing

Basic Configuration

First, install and configure Playwright with the necessary dependencies:

npm install @playwright/test
npx playwright install

Create a basic test configuration for PWA testing:

// playwright.config.js
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  use: {
    baseURL: 'http://localhost:3000',
    browserName: 'chromium', // PWA features work best in Chromium-based browsers
    viewport: { width: 1280, height: 720 },
    ignoreHTTPSErrors: true,
    permissions: ['notifications', 'geolocation'],
  },
  projects: [
    {
      name: 'desktop',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'mobile',
      use: { ...devices['Pixel 5'] },
    },
  ],
});

Python Setup

For Python users, install playwright and set up basic configuration:

pip install playwright
playwright install
# conftest.py
import pytest
from playwright.sync_api import sync_playwright

@pytest.fixture(scope="session")
def browser():
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)
        yield browser
        browser.close()

@pytest.fixture
def context(browser):
    context = browser.new_context(
        viewport={"width": 1280, "height": 720},
        permissions=["notifications", "geolocation"]
    )
    yield context
    context.close()

Testing Service Workers

Service workers are crucial for PWA functionality. Here's how to test them with Playwright:

JavaScript Example

// test-service-worker.spec.js
import { test, expect } from '@playwright/test';

test('should register service worker', async ({ page }) => {
  await page.goto('/');

  // Wait for service worker registration
  await page.waitForFunction(() => {
    return navigator.serviceWorker.ready;
  });

  // Check if service worker is registered
  const swRegistration = await page.evaluate(() => {
    return navigator.serviceWorker.controller !== null;
  });

  expect(swRegistration).toBeTruthy();
});

test('should cache resources offline', async ({ page, context }) => {
  await page.goto('/');

  // Wait for service worker to be active
  await page.waitForFunction(() => navigator.serviceWorker.controller);

  // Go offline
  await context.setOffline(true);

  // Navigate to a previously visited page
  await page.goto('/about');

  // Check if page loads from cache
  await expect(page.locator('h1')).toContainText('About');

  // Go back online
  await context.setOffline(false);
});

Python Example

# test_service_worker.py
import pytest
from playwright.sync_api import Page, BrowserContext

def test_service_worker_registration(page: Page):
    page.goto("/")

    # Wait for service worker registration
    page.wait_for_function("navigator.serviceWorker.ready")

    # Check if service worker is registered
    sw_registered = page.evaluate("navigator.serviceWorker.controller !== null")
    assert sw_registered is True

def test_offline_functionality(page: Page, context: BrowserContext):
    page.goto("/")

    # Wait for service worker to be active
    page.wait_for_function("navigator.serviceWorker.controller")

    # Go offline
    context.set_offline(True)

    # Navigate to a cached page
    page.goto("/about")

    # Verify page loads from cache
    assert page.locator("h1").text_content() == "About"

    # Go back online
    context.set_offline(False)

Testing App Manifest and Installation

PWAs rely on web app manifests for installation behavior. Here's how to test these features:

Testing Manifest Properties

// test-manifest.spec.js
import { test, expect } from '@playwright/test';

test('should have valid app manifest', async ({ page }) => {
  await page.goto('/');

  // Check if manifest is linked in HTML
  const manifestLink = await page.locator('link[rel="manifest"]');
  await expect(manifestLink).toBeVisible();

  // Get manifest URL and fetch content
  const manifestUrl = await manifestLink.getAttribute('href');
  const response = await page.request.get(manifestUrl);
  const manifest = await response.json();

  // Validate manifest properties
  expect(manifest.name).toBeTruthy();
  expect(manifest.short_name).toBeTruthy();
  expect(manifest.start_url).toBeTruthy();
  expect(manifest.display).toBeTruthy();
  expect(manifest.theme_color).toBeTruthy();
  expect(manifest.icons).toHaveLength.greaterThan(0);
});

Testing Installation Prompts

// test-installation.spec.js
import { test, expect } from '@playwright/test';

test('should trigger install prompt', async ({ page }) => {
  await page.goto('/');

  // Listen for beforeinstallprompt event
  await page.addInitScript(() => {
    window.installPromptEvent = null;
    window.addEventListener('beforeinstallprompt', (e) => {
      window.installPromptEvent = e;
    });
  });

  // Wait for the install prompt event
  await page.waitForFunction(() => window.installPromptEvent !== null);

  // Trigger install prompt
  const canInstall = await page.evaluate(() => {
    return window.installPromptEvent !== null;
  });

  expect(canInstall).toBeTruthy();
});

Testing Responsive Design and Mobile Features

PWAs must work seamlessly across different devices and screen sizes:

Mobile Viewport Testing

// test-responsive.spec.js
import { test, expect, devices } from '@playwright/test';

test.describe('Mobile PWA Tests', () => {
  test.use({ ...devices['iPhone 12'] });

  test('should display mobile navigation', async ({ page }) => {
    await page.goto('/');

    // Check mobile-specific elements
    await expect(page.locator('.mobile-menu-toggle')).toBeVisible();
    await expect(page.locator('.desktop-nav')).toBeHidden();

    // Test touch interactions
    await page.locator('.mobile-menu-toggle').tap();
    await expect(page.locator('.mobile-menu')).toBeVisible();
  });

  test('should handle orientation changes', async ({ page }) => {
    await page.goto('/');

    // Test portrait orientation
    await page.setViewportSize({ width: 375, height: 812 });
    await expect(page.locator('.portrait-layout')).toBeVisible();

    // Test landscape orientation
    await page.setViewportSize({ width: 812, height: 375 });
    await expect(page.locator('.landscape-layout')).toBeVisible();
  });
});

Testing Push Notifications

PWAs often implement push notifications for user engagement:

// test-notifications.spec.js
import { test, expect } from '@playwright/test';

test('should request notification permission', async ({ page, context }) => {
  // Grant notification permission
  await context.grantPermissions(['notifications']);

  await page.goto('/');

  // Test notification permission request
  const permissionStatus = await page.evaluate(() => {
    return Notification.permission;
  });

  expect(permissionStatus).toBe('granted');
});

test('should display notifications', async ({ page, context }) => {
  await context.grantPermissions(['notifications']);
  await page.goto('/');

  // Trigger notification
  await page.evaluate(() => {
    new Notification('Test Notification', {
      body: 'This is a test notification',
      icon: '/icon-192x192.png'
    });
  });

  // Note: Actual notification testing requires additional setup
  // as browser notifications are system-level
});

Testing Network Strategies

PWAs implement various network strategies for optimal performance:

Testing Cache-First Strategy

# test_network_strategies.py
def test_cache_first_strategy(page: Page, context: BrowserContext):
    page.goto("/")

    # Make initial request to cache resource
    page.goto("/api/data")

    # Go offline
    context.set_offline(True)

    # Request should be served from cache
    response = page.goto("/api/data")
    assert response.status == 200

    # Go back online
    context.set_offline(False)

Testing Network-First Strategy

// test-network-first.spec.js
test('should use network-first strategy', async ({ page, context }) => {
  await page.goto('/');

  // Monitor network requests
  const requests = [];
  page.on('request', request => requests.push(request));

  // Make request - should go to network first
  await page.goto('/api/fresh-data');

  // Verify network request was made
  const networkRequest = requests.find(req => 
    req.url().includes('/api/fresh-data')
  );
  expect(networkRequest).toBeTruthy();
});

Advanced PWA Testing Scenarios

Testing App Shell Architecture

// test-app-shell.spec.js
test('should load app shell quickly', async ({ page }) => {
  const startTime = Date.now();

  await page.goto('/');

  // Wait for app shell to be ready
  await page.waitForSelector('[data-testid="app-shell"]');

  const loadTime = Date.now() - startTime;
  expect(loadTime).toBeLessThan(2000); // App shell should load in under 2 seconds
});

Testing Background Sync

// test-background-sync.spec.js
test('should handle background sync', async ({ page, context }) => {
  await page.goto('/');

  // Go offline
  await context.setOffline(true);

  // Perform action that requires sync
  await page.fill('[data-testid="message-input"]', 'Test message');
  await page.click('[data-testid="send-button"]');

  // Message should be queued for background sync
  const queuedMessage = await page.locator('[data-testid="queued-message"]');
  await expect(queuedMessage).toBeVisible();

  // Go back online
  await context.setOffline(false);

  // Message should be sent via background sync
  await expect(queuedMessage).toBeHidden();
});

Best Practices for PWA Testing

1. Test Across Different Network Conditions

// Simulate different network conditions
await page.route('**/*', route => {
  setTimeout(() => route.continue(), 1000); // Simulate slow network
});

2. Validate Performance Metrics

// Measure performance metrics
const metrics = await page.evaluate(() => {
  const navigation = performance.getEntriesByType('navigation')[0];
  return {
    loadTime: navigation.loadEventEnd - navigation.loadEventStart,
    domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart
  };
});

3. Test Error Handling

Similar to how you might handle errors in Puppeteer, PWAs require robust error handling for offline scenarios and service worker failures.

Integration with CI/CD

For continuous integration, configure your PWA tests to run in headless mode:

# .github/workflows/pwa-tests.yml
name: PWA Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: '18'
      - run: npm ci
      - run: npx playwright install
      - run: npm run test:pwa

Conclusion

Playwright provides comprehensive support for testing Progressive Web Apps, offering powerful tools to validate service worker functionality, offline capabilities, responsive design, and installation behavior. The framework's ability to simulate various network conditions, device types, and user interactions makes it an ideal choice for ensuring your PWA delivers a consistent, high-quality experience across all platforms and scenarios.

By implementing the testing strategies outlined in this guide, you can build confidence in your PWA's functionality and ensure it meets the high standards expected of modern web applications. Remember to test not just the happy path, but also edge cases like network failures, service worker updates, and cross-device compatibility to deliver a truly robust Progressive Web App.

Try WebScraping.AI for Your Web Scraping Needs

Looking for a powerful web scraping solution? WebScraping.AI provides an LLM-powered API that combines Chromium JavaScript rendering with rotating proxies for reliable data extraction.

Key Features:

  • AI-powered extraction: Ask questions about web pages or extract structured data fields
  • JavaScript rendering: Full Chromium browser support for dynamic content
  • Rotating proxies: Datacenter and residential proxies from multiple countries
  • Easy integration: Simple REST API with SDKs for Python, Ruby, PHP, and more
  • Reliable & scalable: Built for developers who need consistent results

Getting Started:

Get page content with AI analysis:

curl "https://api.webscraping.ai/ai/question?url=https://example.com&question=What is the main topic?&api_key=YOUR_API_KEY"

Extract structured data:

curl "https://api.webscraping.ai/ai/fields?url=https://example.com&fields[title]=Page title&fields[price]=Product price&api_key=YOUR_API_KEY"

Try in request builder

Related Questions

Get Started Now

WebScraping.AI provides rotating proxies, Chromium rendering and built-in HTML parser for web scraping
Icon