Playwright's device emulation sets viewport, pixel ratio, user agent, and touch events together, but it does not test real mobile browser engines: you're still running Chromium regardless of whether you've selected devices['iPhone 14']. The setViewportSize shortcut changes only the viewport, not the user agent or touch behavior, which is sufficient for responsive layout tests but wrong for anything that checks mobile-specific CSS or user-agent sniffing. This article covers device presets, per-test emulation, the difference between setViewportSize and full newContext emulation, touch events, screenshot regression for mobile, and which scenarios warrant a dedicated mobile test project.

What mobile emulation does (and doesn't do)

Playwright emulation simulates:

  • Viewport dimensions
  • Device pixel ratio (retina screens)
  • User agent string
  • Touch events (replaces mouse events)
  • Geolocation (optional)
  • Locale and timezone (optional)

It does not test:

  • Real device performance
  • Actual mobile browser engines (you're still on Chromium, Firefox, or WebKit)
  • Hardware-specific rendering
  • Native mobile apps

For real device testing you need services like BrowserStack or Sauce Labs. Emulation covers the most common need: verifying your app behaves correctly at mobile viewport sizes and responds to touch.

Using built-in device presets

Playwright includes a library of device presets. Use them in your config:

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

export default defineConfig({
  projects: [
    {
      name: 'chromium-desktop',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'mobile-chrome',
      use: { ...devices['Pixel 7'] },
    },
    {
      name: 'mobile-safari',
      use: { ...devices['iPhone 14'] },
    },
    {
      name: 'tablet',
      use: { ...devices['iPad Pro 11'] },
    },
  ],
});

Run mobile tests only:

npx playwright test --project=mobile-chrome

See all available devices:

npx playwright --list-devices

The list includes ~80 presets: Pixel devices, iPhones, Galaxy phones, iPads, Surface tablets.

Per-test device emulation

Override emulation for a single test without changing config:

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

test('mobile navigation works', async ({ browser }) => {
  const context = await browser.newContext({
    ...devices['iPhone 14'],
  });
  const page = await context.newPage();

  await page.goto('/');
  // Hamburger menu should be visible on mobile
  await expect(page.getByRole('button', { name: 'Menu' })).toBeVisible();
  await page.getByRole('button', { name: 'Menu' }).click();
  await expect(page.getByRole('navigation')).toBeVisible();

  await context.close();
});

Touch events

On mobile devices, Playwright uses touch events instead of mouse events. Most interactions work identically: click(), fill(), type() all translate correctly. Where touch matters:

// Tap (same as click on mobile)
await page.getByRole('button').tap();

// Swipe gesture
await page.touchscreen.tap(100, 200);

// Drag on touchscreen
await page.touchscreen.tap(100, 400);
// then simulate swipe by tapping a series of points

For swipe carousels, pull-to-refresh, or complex gestures, you may need page.evaluate() to dispatch custom touch events. Most simple mobile interactions work with regular locator methods.

Testing responsive behavior

The most practical use: verify your layout at different breakpoints.

test.describe('responsive layout', () => {
  test('desktop shows full nav', async ({ page }) => {
    await page.setViewportSize({ width: 1280, height: 720 });
    await page.goto('/');
    await expect(page.getByRole('navigation')).toBeVisible();
    await expect(page.getByRole('button', { name: 'Menu' })).not.toBeVisible();
  });

  test('mobile shows hamburger', async ({ page }) => {
    await page.setViewportSize({ width: 375, height: 812 });
    await page.goto('/');
    await expect(page.getByRole('button', { name: 'Menu' })).toBeVisible();
    await expect(page.getByRole('navigation')).not.toBeVisible();
  });
});

setViewportSize changes only the viewport: it doesn't change the user agent. For full device simulation, use devices presets or a context with viewport + userAgent + deviceScaleFactor set together.

Viewport vs. use configuration

// Viewport only — doesn't change user agent or touch events
await page.setViewportSize({ width: 375, height: 812 });

// Full device emulation — viewport + user agent + touch + pixel ratio
const context = await browser.newContext({
  viewport: { width: 390, height: 844 },
  userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X)...',
  deviceScaleFactor: 3,
  isMobile: true,
  hasTouch: true,
});

For responsive tests that check layout only, setViewportSize is fine. For tests that check mobile-specific behavior (touch events, mobile-specific CSS, user-agent sniffing), use full device emulation via devices presets.

Screenshot comparison for responsive testing

Visual regression is especially valuable for mobile: layout bugs are often invisible in test assertions but obvious in a screenshot:

test('mobile homepage matches baseline', async ({ page }) => {
  await page.setViewportSize({ width: 375, height: 812 });
  await page.goto('/');
  await expect(page).toHaveScreenshot('mobile-homepage.png', {
    fullPage: true,
  });
});

Run once to generate the baseline. Subsequent runs compare against it. A layout shift of a single pixel fails the test.

What to test on mobile specifically

Not every test needs a mobile version. Prioritize:

  • Navigation (hamburger menus, mobile drawers)
  • Forms (virtual keyboard doesn't cover submit button, tap targets are large enough)
  • Tables (horizontal scroll vs. stacked layout)
  • Images (responsive srcset, no overflow)
  • Payment flows (critical path, touch-friendly buttons)
  • Modals (scrollable content, close button accessible)

Skip mobile tests for:

  • Admin dashboards that explicitly don't support mobile
  • Tests that check identical behavior regardless of viewport
  • API tests and unit tests
→ See also: Cross-Browser Testing with Playwright: Chrome, Firefox, Safari | Cross-Browser Testing Strategies: When and How to Test Multiple Browsers | Playwright Config File Explained: Every Option You Need to Know