Тестирование адаптивного дизайна
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 на физических устройствах.