Modern QA2026Тестирование адаптивного дизайна
Join

Course09 Mobile & Cross-Platform Testing

Cutting-edge · Chapter 09

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

Updated Jul 2026

Больше, чем сравнение скриншотов

Традиционное тестирование адаптивности делало скриншоты при разных ширинах и сравнивало их визуально. Этот подход не работает, потому что не может отличить «вёрстка корректно адаптировалась к меньшей ширине» от «вёрстка сломалась при меньшей ширине». И то, и другое выглядит иначе, чем десктопный скриншот.

AI-агенты и современные инструменты тестирования могут рассуждать о функциональной корректности, а не о попиксельном совпадении. Вопрос не «выглядит ли это так же?», а «работает ли это корректно при каждой ширине?»

Конфигурация контрольных точек viewport

Определите тестовые viewport для покрытия критических контрольных точек, где меняется вёрстка:

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

export default defineConfig({
    projects: [
        // Mobile portrait
        { name: 'mobile-portrait', use: { viewport: { width: 375, height: 812 } } },
        // Mobile landscape
        { name: 'mobile-landscape', use: { viewport: { width: 812, height: 375 } } },
        // Small tablet
        { name: 'tablet-portrait', use: { viewport: { width: 768, height: 1024 } } },
        // Tablet landscape
        { name: 'tablet-landscape', use: { viewport: { width: 1024, height: 768 } } },
        // Desktop
        { name: 'desktop', use: { viewport: { width: 1440, height: 900 } } },
        // Ultrawide
        { name: 'ultrawide', use: { viewport: { width: 2560, height: 1080 } } },
        // Foldable inner display (Samsung Galaxy Fold open)
        { name: 'foldable-open', use: { viewport: { width: 1812, height: 2176 } } },
        // Foldable outer display
        { name: 'foldable-closed', use: { viewport: { width: 832, height: 2268 } } },
    ],
});

Стратегический выбор контрольных точек

Не тестируйте только при значениях CSS-контрольных точек. Тестируйте в точках перехода, где вёрстка наиболее вероятно сломается:

Ширина Что тестировать Почему
320px Наименьшая жизнеспособная ширина iPhone SE, маленькие Android-телефоны
374px Чуть ниже типичной мобильной контрольной точки Ловит ошибки «на единицу» в media queries
375px Стандартная мобильная контрольная точка Ширина iPhone 12-15 mini/standard
414px Большие телефоны iPhone Pro Max, серия Galaxy S
767px Чуть ниже планшетной контрольной точки Ловит проблемы срабатывания планшетной вёрстки
768px Планшетная контрольная точка iPad portrait
1023px Чуть ниже десктопной контрольной точки Ловит проблемы десктопной вёрстки
1024px Десктопная контрольная точка iPad landscape, небольшие ноутбуки
1440px Стандартный десктоп Наиболее распространённое десктопное разрешение
1920px Full HD десктоп Проблемы растяжения контента

Паттерн тестирования адаптивности с AI-агентами

Когда AI-агент тестирует адаптивную вёрстку, он может рассуждать о функциональной корректности, а не о визуальном сходстве:

## Responsive Test: Navigation Menu

### At mobile width (375px):
1. Navigate to the homepage
2. Verify the hamburger menu icon is visible
3. Verify the full navigation links are NOT visible
4. Click the hamburger menu
5. Verify the navigation drawer slides in
6. Verify all primary links are accessible
7. Take a screenshot

### At desktop width (1440px):
1. Navigate to the homepage
2. Verify the full navigation bar is visible with all links
3. Verify the hamburger menu icon is NOT visible
4. Verify navigation links are horizontally laid out
5. Take a screenshot

AI-агент может затем сравнить поведение в разных viewport и отметить несоответствия — например, отсутствующие пункты меню, перекрывающиеся элементы или обрезанный текст, которые статическое сравнение скриншотов может пропустить.

Тест динамического изменения размера viewport

Проверьте, что вёрстка остаётся стабильной при непрерывном изменении размера (важно для складных устройств и изменения размера окна на десктопе):

// tests/responsive/layout-stability.spec.ts
import { test, expect } from '@playwright/test';

test('layout remains stable during viewport resize', async ({ page }) => {
    await page.goto('/dashboard');

    const widths = [320, 375, 414, 768, 1024, 1280, 1440, 1920];

    for (const width of widths) {
        await page.setViewportSize({ width, height: 800 });
        // Wait for CSS transitions to complete
        await page.waitForTimeout(300);

        // Verify no horizontal overflow (no horizontal scrollbar)
        const bodyWidth = await page.evaluate(() => document.body.scrollWidth);
        const viewportWidth = await page.evaluate(() => window.innerWidth);
        expect(bodyWidth).toBeLessThanOrEqual(viewportWidth + 1); // +1 for rounding

        // Verify primary content is visible
        await expect(page.locator('[data-testid="main-content"]')).toBeVisible();

        // Verify no text truncation on critical elements
        const heading = page.locator('h1').first();
        const box = await heading.boundingBox();
        if (box) {
            expect(box.width).toBeGreaterThan(50); // Not crushed to nothing
        }
    }
});

test('navigation adapts between mobile and desktop', async ({ page }) => {
    await page.goto('/');

    // Desktop: full nav visible
    await page.setViewportSize({ width: 1440, height: 900 });
    await expect(page.locator('[data-testid="desktop-nav"]')).toBeVisible();
    await expect(page.locator('[data-testid="mobile-menu-btn"]')).not.toBeVisible();

    // Mobile: hamburger menu visible
    await page.setViewportSize({ width: 375, height: 812 });
    await page.waitForTimeout(300);
    await expect(page.locator('[data-testid="mobile-menu-btn"]')).toBeVisible();
    await expect(page.locator('[data-testid="desktop-nav"]')).not.toBeVisible();

    // Open mobile menu
    await page.click('[data-testid="mobile-menu-btn"]');
    await expect(page.locator('[data-testid="mobile-drawer"]')).toBeVisible();

    // Verify all navigation items are present in mobile menu
    const mobileLinks = await page.locator('[data-testid="mobile-drawer"] a').allTextContents();
    expect(mobileLinks).toContain('Home');
    expect(mobileLinks).toContain('Products');
    expect(mobileLinks).toContain('Cart');
    expect(mobileLinks).toContain('Account');
});

Типичные баги адаптивности для тестирования

Горизонтальное переполнение

Наиболее частый баг адаптивности: контент шире viewport вызывает горизонтальную полосу прокрутки.

test('no horizontal overflow on any page', async ({ page }) => {
    const pages = ['/', '/products', '/cart', '/checkout', '/account'];
    const widths = [320, 375, 768, 1024, 1440];

    for (const pagePath of pages) {
        for (const width of widths) {
            await page.setViewportSize({ width, height: 800 });
            await page.goto(pagePath);

            const hasOverflow = await page.evaluate(() => {
                return document.body.scrollWidth > window.innerWidth;
            });

            expect(hasOverflow).toBe(false);
        }
    }
});

Перекрытие областей нажатия

На мобильных устройствах элементы, нормально расположенные на десктопе, могут перекрываться или находиться слишком близко друг к другу:

test('interactive elements have adequate spacing on mobile', async ({ page }) => {
    await page.setViewportSize({ width: 375, height: 812 });
    await page.goto('/');

    const buttons = await page.locator('button, a, [role="button"]').all();
    const rects = await Promise.all(buttons.map(b => b.boundingBox()));

    for (let i = 0; i < rects.length; i++) {
        for (let j = i + 1; j < rects.length; j++) {
            const a = rects[i];
            const b = rects[j];
            if (!a || !b) continue;

            // Check if elements overlap
            const overlapsX = a.x < b.x + b.width && a.x + a.width > b.x;
            const overlapsY = a.y < b.y + b.height && a.y + a.height > b.y;

            if (overlapsX && overlapsY) {
                // Calculate overlap area
                const overlapWidth = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);
                const overlapHeight = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);
                const overlapArea = overlapWidth * overlapHeight;

                expect(overlapArea).toBeLessThan(50);
            }
        }
    }
});

Масштабирование изображений

test('images do not overflow their containers', async ({ page }) => {
    await page.setViewportSize({ width: 375, height: 812 });
    await page.goto('/products');

    const images = await page.locator('img').all();
    for (const img of images) {
        const box = await img.boundingBox();
        if (box) {
            expect(box.width).toBeLessThanOrEqual(375);
        }
    }
});

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

# .github/workflows/responsive-tests.yml
name: Responsive Layout Tests
on: [pull_request]

jobs:
  responsive:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        project: [mobile-portrait, tablet-portrait, desktop, foldable-open]
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test --project=${{ matrix.project }}
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: responsive-report-${{ matrix.project }}
          path: playwright-report/

Тестирование адаптивности не является опциональным для любого публичного приложения. Инвестиции в автоматизированное тестирование viewport окупаются за счёт обнаружения багов вёрстки, которые иначе потребовали бы ручного QA на физических устройствах.