Answer
Scenario: Multi-Device Testing (Mobile, Tablet, Desktop)
Configure Projects for Each Device
TypeScript
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
workers: 6, // 6 devices running simultaneously
projects: [
// ── Desktop ─────────────────────────────────────
{
name: 'Desktop Chrome',
use: { ...devices['Desktop Chrome'] },
// viewport: 1280x720
},
{
name: 'Desktop Firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'Desktop Safari',
use: { ...devices['Desktop Safari'] },
},
// ── Tablet ──────────────────────────────────────
{
name: 'iPad Pro',
use: { ...devices['iPad Pro'] },
// viewport: 1024x1366
},
{
name: 'iPad Mini',
use: { ...devices['iPad Mini'] },
// viewport: 768x1024
},
// ── Mobile ──────────────────────────────────────
{
name: 'Pixel 5 (Android)',
use: { ...devices['Pixel 5'] },
// viewport: 393x851
},
{
name: 'iPhone 12',
use: { ...devices['iPhone 12'] },
// viewport: 390x844
},
{
name: 'Galaxy S21',
use: { ...devices['Galaxy S21'] },
},
],
});
Tests That Run on All Devices
TypeScript
// tests/responsive/navigation.spec.ts
import { test, expect } from '@playwright/test';
test('navigation is accessible on all devices', async ({ page, isMobile }) => {
await page.goto('/');
if (isMobile) {
// Mobile shows hamburger menu
await expect(page.getByRole('button', { name: 'Menu' })).toBeVisible();
await expect(page.getByRole('navigation').locator('a')).not.toBeVisible();
// Open mobile menu
await page.getByRole('button', { name: 'Menu' }).click();
await expect(page.getByRole('navigation').locator('a')).toBeVisible();
} else {
// Desktop shows full navigation
await expect(page.getByRole('navigation').locator('a')).toBeVisible();
await expect(page.getByRole('button', { name: 'Menu' })).not.toBeVisible();
}
});
Device-Specific Tests
TypeScript
// tests/responsive/product-layout.spec.ts
import { test, expect } from '@playwright/test';
test('product grid layout', async ({ page, viewport }) => {
await page.goto('/products');
await expect(page.locator('.product-card')).toBeVisible();
// Determine expected columns based on viewport width
const width = viewport?.width || 1280;
if (width < 640) {
// Mobile: 1 column
await expect(page.locator('.product-grid')).toHaveClass(/grid-cols-1/);
} else if (width < 1024) {
// Tablet: 2 columns
await expect(page.locator('.product-grid')).toHaveClass(/grid-cols-2/);
} else {
// Desktop: 3 or 4 columns
await expect(page.locator('.product-grid')).toHaveClass(/grid-cols-[34]/);
}
});
Run Specific Device Profiles
Bash
# Run only mobile tests
npx playwright test --project="Pixel 5 (Android)"
npx playwright test --project="iPhone 12"
# Run only desktop
npx playwright test --project="Desktop Chrome"
# Run all devices
npx playwright test
# Skip specific device
npx playwright test --ignore-snapshots --project="Desktop Chrome" --project="iPhone 12"
Visual Regression Per Device
TypeScript
test('hero section renders correctly', async ({ page }) => {
await page.goto('/');
// Each device gets its own baseline snapshot
await expect(page.locator('.hero')).toHaveScreenshot('hero.png');
// Automatically creates: hero-Desktop-Chrome-darwin.png, hero-iPhone-12-darwin.png, etc.
});
GitHub Actions with All Devices
YAML
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
project: ['Desktop Chrome', 'Pixel 5 (Android)', 'iPad Pro']
steps:
- run: npx playwright test --project="${{ matrix.project }}"
