Эмуляция устройств в Playwright задаёт вьюпорт, pixel ratio, user agent и touch-события вместе, но не тестирует реальные мобильные движки: под капотом всё равно Chromium, независимо от того выбрал ли ты devices['iPhone 14']. Быстрый метод setViewportSize меняет только вьюпорт, но не user agent и не поведение тача, что достаточно для проверки адаптивной вёрстки, но неверно для всего что проверяет мобильный CSS или user-agent sniffing. Эта статья разбирает пресеты устройств, эмуляцию для отдельных тестов, разницу между setViewportSize и полной эмуляцией через newContext, touch-события, скриншот-регрессию для мобильных и сценарии когда нужен отдельный мобильный проект.

Что делает мобильная эмуляция (и чего не делает)

Эмуляция Playwright симулирует:

  • Размеры вьюпорта
  • Device pixel ratio (retina-экраны)
  • Строку user agent
  • Touch-события (вместо событий мыши)
  • Геолокацию (опционально)
  • Локаль и часовой пояс (опционально)

Что она не тестирует:

  • Реальную производительность устройства
  • Настоящие мобильные браузерные движки (по-прежнему Chromium, Firefox или WebKit)
  • Рендеринг специфичный для железа
  • Нативные мобильные приложения

Для тестирования на реальных устройствах нужны сервисы вроде BrowserStack или Sauce Labs. Эмуляция покрывает самую частую потребность: убедиться что приложение корректно работает при мобильных размерах вьюпорта и реагирует на тач.

Встроенные пресеты устройств

Playwright включает библиотеку пресетов устройств. Используй их в конфиге:

// 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'] },
    },
  ],
});

Запуск только мобильных тестов:

npx playwright test --project=mobile-chrome

Все доступные устройства:

npx playwright --list-devices

Список включает около 80 пресетов: устройства Pixel, iPhone, Galaxy, iPad, Surface.

Эмуляция устройства для отдельного теста

Переопределить эмуляцию для конкретного теста без изменения конфига:

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('/');
  // Кнопка-гамбургер должна быть видима на мобильном
  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-события

На мобильных устройствах Playwright использует touch-события вместо событий мыши. Большинство взаимодействий работают одинаково: click(), fill(), type() корректно транслируются. Там где тач важен:

// Тап (аналог клика на мобильном)
await page.getByRole('button').tap();

// Жест свайпа
await page.touchscreen.tap(100, 200);

// Перетаскивание на тачскрине
await page.touchscreen.tap(100, 400);
// затем симулируй свайп через серию точек

Для свайп-каруселей, pull-to-refresh и сложных жестов может понадобиться page.evaluate() для генерации кастомных touch-событий. Большинство простых мобильных взаимодействий работают через обычные методы локаторов.

Тестирование адаптивного поведения

Самое практичное применение: проверить вёрстку на разных брейкпоинтах.

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 меняет только вьюпорт и не меняет user agent. Для полной эмуляции устройства используй пресеты devices или контекст с viewport, userAgent и deviceScaleFactor заданными вместе.

Только вьюпорт vs. полная конфигурация

// Только вьюпорт — не меняет user agent и touch-события
await page.setViewportSize({ width: 375, height: 812 });

// Полная эмуляция — вьюпорт + user agent + тач + 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,
});

Для адаптивных тестов которые проверяют только вёрстку, setViewportSize достаточно. Для тестов которые проверяют мобильное поведение (touch-события, мобильный CSS, user-agent sniffing) используй полную эмуляцию через пресеты devices.

Скриншот-сравнение для адаптивного тестирования

Визуальная регрессия особенно ценна для мобильных: баги вёрстки часто невидимы в обычных ассёртах, но очевидны на скриншоте.

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,
  });
});

Запусти один раз чтобы сгенерировать базовый скриншот. Последующие запуски сравнивают с ним. Сдвиг вёрстки даже на один пиксель провалит тест.

Что тестировать на мобильном

Не каждый тест нуждается в мобильной версии. Приоритет для:

  • Навигации (кнопки-гамбургеры, мобильные драверы)
  • Форм (виртуальная клавиатура не перекрывает кнопку отправки, тач-цели достаточно крупные)
  • Таблиц (горизонтальный скролл vs. вертикальное расположение)
  • Изображений (адаптивный srcset, нет overflow)
  • Платёжных флоу (критический путь, кнопки удобные для тача)
  • Модалов (контент прокручивается, кнопка закрытия доступна)

Пропускать мобильные тесты для административных панелей которые явно не поддерживают мобильный, для тестов проверяющих одинаковое поведение независимо от вьюпорта, а также для API-тестов и юнит-тестов.

→ See also: Кросс-браузерное тестирование с Playwright: Chrome, Firefox, Safari | Стратегии кросс-браузерного тестирования: когда и как тестировать в разных браузерах | Файл конфигурации Playwright: все опции, которые нужно знать