Перейти к содержанию

Разные размеры экрана

На широком экране наш каталог показывает навигацию в хедере и две колонки карточек. На узком экране появляется кнопка «Меню», а список становится одной колонкой. Изменение ширины затрагивает не только внешний вид: часть элементов скрывается, и посетителю нужен другой путь к ссылкам.

В этом уроке проверим тот же поиск в двух размерах области страницы. Результат фильтра остаётся прежним, а путь к навигации меняется по договорённости CSS. Полный файл lesson-09/expected/lesson.spec.js находится в архиве. Примеры не запускались; значения ширины обозначают условия будущего сценария, а не измеренные свойства устройств.

Viewport и физическое устройство

Viewport — область, относительно которой страница рассчитывает компоновку и медиазапросы. Ширина 390 CSS-пикселей позволяет исследовать наш узкий вариант. Она не превращает настольный Chromium в настоящий телефон: сохраняются выбранный браузерный движок, настройки ввода и окружение. Проверка размера и проверка конкретного устройства — связанные, но различные задачи.

Для первого знакомства выберем две точки: 1280 на 800 и 390 на 844. В CSS лаборатории граница мобильного меню равна 640. Таким образом, одна точка находится выше границы, другая ниже. Мы не пытаемся вывести из двух точек правильность всех промежуточных размеров; они дают понятный начальный набор для двух разных путей интерфейса.

Размер задаётся до перехода:

await page.setViewportSize({ width: 390, height: 844 });
await page.goto('/');
await expect(page.getByTestId('results-status'))
  .toHaveText('Найдено курсов: 4');

В этом уроке используем встроенную page, а не catalogPage. Так размер устанавливается до загрузки документа. Данные берутся из настоящего локального /api/courses, без подмены. Они по-прежнему неизменны. Варианты внутри полного файла создают отдельные тесты, и каждый получает собственную страницу и контекст.

Эмуляция размеров и других условий описана в Playwright Emulation. Для нашей задачи достаточно одной переменной. Если одновременно менять язык, часовой пояс, touch и user agent, станет сложнее объяснить, какое условие вызвало различие поведения. Начинаем с ширины, а дополнительные свойства вводим по отдельной причине.

Меню на узком экране

В широком варианте навигация сразу видна, а кнопка «Меню» скрыта CSS. На узком варианте ожидается обратное исходное состояние. После нажатия кнопка получает aria-expanded="true", а навигация становится видимой:

const nav = page.getByRole('navigation', { name: 'Главная навигация' });
const menu = page.getByRole('button', { name: 'Меню', exact: true });
await expect(nav).toBeHidden();
await expect(menu).toBeVisible();
await menu.click();
await expect(menu).toHaveAttribute('aria-expanded', 'true');
await expect(nav).toBeVisible();
await expect(nav.getByRole('link', { name: 'Каталог', exact: true }))
  .toBeVisible();

Это фрагмент узкого варианта. В полном тесте широкий вариант, напротив, ждёт скрытую кнопку и видимую навигацию без нажатия. Проверка атрибута связывает техническое состояние управляющей кнопки с отображением управляемой области. Если приложение раскрыло ссылки, но оставило aria-expanded="false", состояния расходятся и могут вводить пользователя в заблуждение.

Важно различать невидимость и отсутствие. Наше меню остаётся в DOM и скрывается медиазапросом. Локатор может описывать область, хотя пользователь ещё не видит её. Утверждение toBeHidden учитывает состояние видимости, поэтому подходит договорённости. Проверка количества найденных узлов была бы другим вопросом и не доказала бы, что меню скрыто.

Один результат поиска при двух компоновках

После проверки навигации выберем frontend и отправим форму. Ожидается «Найдено курсов: 2». Важно сохранить пользовательский результат при изменении компоновки: два правильных курса должны быть доступны и на широком, и на узком экране. Переход к одной колонке не должен менять данные или удалять управление фильтром.

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

Видимость кнопки не подтверждает, что весь интерфейс помещается на экран. Элемент может быть доступен для нажатия, но соседний текст или карточка выйти за область. Для проверки горизонтального переполнения потребуются отдельные наблюдения размеров и разбор изображения. В этом уроке мы не добавляем вывод, для которого нет соответствующего утверждения.

То же относится к визуальному качеству. Правильный результат фильтра не оценивает длину строк, интервалы или читаемость заголовков. Браузерный сценарий здесь защищает путь к меню и поиск. Визуальное сравнение позже поможет обнаружить часть изменений оформления, но также будет иметь свои ограничения.

Граница медиазапроса и новые варианты

Если меню работает на 390, это не доказывает правильность точно на 640. В нашей CSS-записи условие max-width:640px включает границу. При будущем расширении полезно добавить точки 640 и 641, чтобы исследовать переход между двумя состояниями. Это уже конкретная новая причина для вариантов, а не произвольный список моделей телефонов.

Ещё один вариант — длинное название курса. Сейчас «Статический сайт из Markdown» длиннее других, но остаётся обычной строкой. Чтобы проверить очень длинное слово, нужно явно добавить такие данные и сформулировать ожидаемое поведение переноса. Использование случайного текста каждый раз усложнит воспроизведение и сравнение результата.

Изменять размер в середине одного сценария тоже можно, если задача — динамический переход между состояниями. Например, открытое узкое меню после расширения окна должно оставлять навигацию доступной. Однако текущие два независимых теста исследуют загрузку сразу в каждом размере. Их не следует выдавать за проверку всех переходов окна.

В результате мы получили две осмысленные точки компоновки, согласованное состояние кнопки и навигации и один результат фильтра в обеих точках. Следующий урок оставит размер постоянным, но изменит ответ сервера. Это позволит разделить успешный каталог, нормальную пустоту и ошибку загрузки, которые выглядят похоже при беглом просмотре.

Готовность перед сменой условия

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

Кнопка «Меню» остаётся стандартной кнопкой. Проверка видимости и aria-expanded не зависит от её цвета или толщины рамки. Такой результат переживёт многие изменения оформления, сохраняя связь с пользовательским действием. Когда изменится сама модель навигации, например появится отдельный диалог, понадобится другая договорённость.

Уменьшение высоты также может повлиять на необходимость прокрутки. Текущий сценарий не проверяет, сколько экрана занимает каждая карточка. Высота задана для воспроизводимых условий, а конкретные гарантии остаются связанными с меню и фильтром.