Все браузеры на iOS, включая Chrome, работают на движке WebKit: CSS-баг в Safari затрагивает все iOS-браузеры, а тест который прошёл на Android Chrome ничего не говорит о поведении на iOS. Большинство команд закрывают 80% ценности мобильного тестирования встроенной эмуляцией устройств Playwright без физических девайсов: она выставляет размер вьюпорта, user agent, pixel ratio и поддержку тач-событий одной строкой конфига. Гайд охватывает проверку адаптивной вёрстки, эмуляцию сети через CDP, баги с размером тач-целей и автозумом iOS, а также когда реальные устройства через BrowserStack действительно оправданы.

Сложности мобильного тестирования

Фрагментация устройств: Android работает на тысячах разных девайсов с разными размерами экранов, версиями ОС и железом. iOS контролируется жёстче, но тоже имеет несколько поколений размеров и версий. Тач-взаимодействия: Мобильные пользователи тапают, свайпают, пинчат и поворачивают экран. Тесты с кликами мышью это не покрывают. Сетевые условия: Мобильные пользователи часто сидят на медленном и нестабильном соединении. 3G, 4G, плохой вайфай: приложение должно с этим справляться. Ограничения производительности: В мобильных устройствах меньше RAM, медленнее процессор, меньше батарея. То что быстро на десктопе, может тормозить на телефоне. Платформенные различия: iOS и Android по-разному обрабатывают уведомления, диплинки, шаринг файлов, доступ к камере, пуш-уведомления. Проблема WebKit на iOS: ВСЕ браузеры на iOS используют WebKit. Chrome на iPhone не является Chrome: это движок Safari в обёртке с логотипом Chrome. Баги в WebKit затрагивают все iOS-браузеры без исключения.

Виды мобильного тестирования

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

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

// Playwright — тест мобильного вьюпорта
test('login page works on mobile', async ({ page }) => {
  await page.setViewportSize({ width: 375, height: 667 });  // iPhone SE
  await page.goto('/login');
  
  // Мобильный UI должен отображаться
  await expect(page.getByTestId('mobile-header')).toBeVisible();
  await expect(page.getByTestId('desktop-nav')).not.toBeVisible();
  
  // Основная функциональность работает
  await page.fill('[data-testid="email"]', 'user@test.com');
  await page.fill('[data-testid="password"]', 'ValidPass1');
  await page.tap('[data-testid="submit"]');  // tap вместо click
  
  await expect(page).toHaveURL('/dashboard');
});

Тестирование нативных приложений

Приложения из App Store или Google Play. Требуют Appium, Detox (React Native) или напрямую XCUITest/Espresso.

Тестирование PWA

Веб-приложения которые ведут себя как нативные: устанавливаются, работают офлайн, отправляют пуш-уведомления.

Playwright для тестирования мобильного веба

Эмуляция устройств в Playwright покрывает большинство мобильных сценариев для веб-приложений:

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

export default defineConfig({
  projects: [
    // Десктоп
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    
    // Мобайл
    { name: 'mobile-chrome', use: { ...devices['Pixel 7'] } },
    { name: 'mobile-safari', use: { ...devices['iPhone 14'] } },
    { name: 'tablet', use: { ...devices['iPad Pro'] } },
  ],
});

Доступные устройства (см. npx playwright show-devices): iPhone 14, iPhone 14 Pro, iPhone SE, Pixel 7, Galaxy S21, iPad Pro, iPad Mini.

Эмуляция устройства выставляет:

  • Размер вьюпорта
  • User agent строку
  • Device pixel ratio
  • Поддержку тач-событий

Тач-события и события мыши

Мобильные пользователи тапают, а не кликают. click() в Playwright работает для обоих случаев, но иногда нужны явные тач-события:

test('swipe carousel works', async ({ page }) => {
  await page.setViewportSize({ width: 375, height: 812 });
  await page.goto('/products');
  
  const carousel = page.getByTestId('product-carousel');
  const box = await carousel.boundingBox();
  
  if (box) {
    // Симулируем свайп влево (палец движется справа налево)
    await page.touchscreen.tap(box.x + box.width - 50, box.y + box.height / 2);
    await page.mouse.move(box.x + box.width - 50, box.y + box.height / 2);
    await page.mouse.down();
    await page.mouse.move(box.x + 50, box.y + box.height / 2, { steps: 20 });
    await page.mouse.up();
  }
  
  // Проверяем что следующий слайд виден
  await expect(page.getByTestId('slide-2')).toBeVisible();
});

Проверка адаптивной вёрстки

Проверяем что UI корректно перестраивается на разных брейкпоинтах:

const viewports = [
  { name: 'mobile', width: 375, height: 667 },
  { name: 'mobile-large', width: 414, height: 896 },
  { name: 'tablet', width: 768, height: 1024 },
  { name: 'desktop', width: 1280, height: 720 },
];

for (const viewport of viewports) {
  test(`navigation renders correctly at ${viewport.name}`, async ({ page }) => {
    await page.setViewportSize({ width: viewport.width, height: viewport.height });
    await page.goto('/');
    
    if (viewport.width < 768) {
      // Мобайл: гамбургер-меню
      await expect(page.getByTestId('hamburger')).toBeVisible();
      await expect(page.getByTestId('desktop-nav')).not.toBeVisible();
    } else {
      // Десктоп/планшет: полная навигация
      await expect(page.getByTestId('hamburger')).not.toBeVisible();
      await expect(page.getByTestId('desktop-nav')).toBeVisible();
    }
  });
}

Эмуляция медленной сети

Проверяем как приложение ведёт себя на медленном мобильном соединении:

test('app loads on slow 3G', async ({ page, context }) => {
  // Эмулируем скорости 3G
  const cdpSession = await context.newCDPSession(page);
  await cdpSession.send('Network.emulateNetworkConditions', {
    offline: false,
    downloadThroughput: 780 * 1024 / 8,  // 780 kbps загрузка
    uploadThroughput: 330 * 1024 / 8,    // 330 kbps отдача
    latency: 100,                          // 100ms задержка
  });
  
  const startTime = Date.now();
  await page.goto('/');
  const loadTime = Date.now() - startTime;
  
  // Страница должна загрузиться за 10 секунд на 3G
  expect(loadTime).toBeLessThan(10000);
  
  // Состояние загрузки должно отображаться во время ожидания
  await expect(page.getByTestId('main-content')).toBeVisible();
});

test('app works offline (PWA)', async ({ page, context }) => {
  await page.goto('/');
  
  // Отключаем сеть
  await context.setOffline(true);
  
  await page.reload();
  
  // PWA должен показать закешированный контент или офлайн-страницу
  await expect(page.getByTestId('offline-message')).toBeVisible();
  // ИЛИ
  await expect(page.getByTestId('main-content')).toBeVisible();  // Закешированный контент
  
  // Восстанавливаем сеть
  await context.setOffline(false);
});

Тестирование на реальных устройствах через BrowserStack

Эмуляция закрывает большинство сценариев. Когда нужны реальные устройства, есть несколько вариантов: BrowserStack даёт реальные iOS и Android устройства в облаке, Sauce Labs предоставляет аналогичный сервис, а Firebase Test Lab это девайс-ферма от Google с фокусом на Android.

// Конфиг Playwright для реальных устройств BrowserStack
export default defineConfig({
  projects: [
    {
      name: 'real-iphone-14',
      use: {
        connectOptions: {
          wsEndpoint: `wss://cdp.browserstack.com/playwright?caps=${encodeURIComponent(JSON.stringify({
            'browserstack.user': process.env.BS_USER,
            'browserstack.key': process.env.BS_KEY,
            'browserName': 'safari',
            'bstack:options': {
              deviceName: 'iPhone 14',
              osVersion: '16',
            }
          }))}`,
        },
      },
    },
  ],
});

Типичные мобильные баги

Размер тач-целей: Кнопки слишком маленькие для надёжного нажатия (менее 44×44px на iOS, 48×48dp на Android).

test('all interactive elements are large enough to tap', async ({ page }) => {
  await page.setViewportSize({ width: 375, height: 667 });
  await page.goto('/');
  
  const buttons = await page.locator('button, a, [role="button"]').all();
  
  for (const button of buttons) {
    const box = await button.boundingBox();
    if (box) {
      expect(box.width, `Button too narrow: ${await button.textContent()}`).toBeGreaterThanOrEqual(44);
      expect(box.height, `Button too short: ${await button.textContent()}`).toBeGreaterThanOrEqual(44);
    }
  }
});

Горизонтальная прокрутка: На мобиле контент не должен требовать горизонтального скролла.

test('no horizontal scrolling on mobile', async ({ page }) => {
  await page.setViewportSize({ width: 375, height: 667 });
  await page.goto('/');
  
  const scrollWidth = await page.evaluate(() => document.documentElement.scrollWidth);
  const clientWidth = await page.evaluate(() => document.documentElement.clientWidth);
  
  expect(scrollWidth).toBe(clientWidth);  // Нет горизонтального overflow
});

Автозум инпутов: На iOS, если у поля ввода font-size меньше 16px, iOS автоматически зумирует страницу. Минимум 16px.

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

Перед каждым релизом:

  • [ ] Основные флоу проверены на iPhone (Safari) и Android (Chrome)
  • [ ] Адаптивная вёрстка проверена на ширинах 375px, 768px, 1280px
  • [ ] Тач-цели не менее 44×44px
  • [ ] Горизонтальный скролл отсутствует на мобильных вьюпортах
  • [ ] Формы работают с мобильными клавиатурами
  • [ ] Состояния загрузки отображаются на медленных соединениях
  • [ ] Изображения оптимизированы для мобиля (не десктопные размеры)
  • [ ] Текст читается без зума (минимум 16px)

Итоговая таблица

| Тип тестирования | Инструмент | Когда применять |

|-----------------|-----------|----------------|

| Адаптивный веб | Вьюпорт Playwright | Каждый спринт |

| Кросс-браузер мобайл | Пресеты устройств Playwright | Перед релизом |

| Эмуляция сети | Chrome DevTools Protocol в Playwright | Мобильные фичи |

| Реальные устройства | BrowserStack, Sauce Labs | Перед крупными релизами |

| Нативные приложения | Appium, Detox | Отдельный нативный проект |

Большинству веб-приложений хватает тестирования адаптивного веба (80% ценности мобильного тестирования) плюс периодические прогоны на реальных устройствах для проверки специфики iOS Safari. Реальные девайс-фермы стоит подключать перед крупными релизами и для воспроизведения платформенных багов.

→ See also: Мобильная эмуляция в Playwright: тестирование адаптивности и сенсорного ввода | Стратегии кросс-браузерного тестирования: когда и как тестировать в разных браузерах | Тестирование доступности с Playwright: автоматизированные a11y проверки