← назад к разделу

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

Почему sleep не работает

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

Первый порыв — поставить паузу:

await page.waitForTimeout(1000); // подождём секунду...
await expect(page.getByText("Заказ оформлен")).toBeVisible();

Проблема двойная. На быстрой машине теряешь секунду зря — результат давно готов. На медленной или под нагрузкой секунды может не хватить — и тест снова нестабильный. Фиксированная пауза не привязана к реальному состоянию приложения.

Web-first утверждения

Playwright решает это иначе. Утверждение на локаторе повторяется автоматически до тех пор, пока не выполнится или не истечёт таймаут. Это не «проверить прямо сейчас», а «дождаться, что станет так».

await expect(page.getByText("Заказ оформлен")).toBeVisible();
await expect(page.getByRole("row")).toHaveCount(3);
await expect(page.getByLabel("Итого")).toHaveText("4990 ₽");

toBeVisible() будет перепроверять видимость элемента снова и снова, пока он не появится или не истечёт таймаут (по умолчанию 5 секунд). Не нужно ни паузы, ни ручного ожидания — утверждение само синхронизируется с интерфейсом.

Это и называется web-first: проверки знают про асинхронную природу браузера.

Часто используемые web-first утверждения:

УтверждениеЧто проверяет
toBeVisible()элемент виден на странице
toBeHidden()элемент скрыт или отсутствует
toHaveText(text)текстовое содержимое совпадает
toHaveValue(value)значение поля ввода совпадает
toHaveCount(n)количество найденных элементов
toBeEnabled() / toBeDisabled()кнопка активна или заблокирована
toBeChecked()чекбокс отмечен

Авто-ожидание действий

Не только утверждения — действия тоже ждут. Перед тем как выполнить клик, Playwright проверяет, что элемент виден, не перекрыт другим элементом, не заблокирован и готов принять взаимодействие.

await page.getByRole("button", { name: "Оплатить" }).click();

Не нужно явно проверять «появилась ли кнопка» перед кликом — click() сделает это сам. Это убирает горы проверок-ожиданий, которые приходилось писать вручную в старых инструментах автоматизации.

Тот же принцип работает у fill(), selectOption(), check() и других действий.

Когда нужно явное ожидание

Авто-ожидание не покрывает всё. Иногда нужно дождаться чего-то, что не выражается через состояние элемента, — например, конкретного сетевого ответа.

await Promise.all([
  page.waitForResponse((r) => r.url().includes("/api/orders") && r.ok()),
  page.getByRole("button", { name: "Оформить" }).click(),
]);

Здесь тест ждёт именно условия (пришёл нужный ответ с кодом 200), а не фиксированного времени. Promise.all связывает клик и ожидание ответа вместе — это исключает гонку между отправкой запроса и началом ожидания.

Похожий приём — waitForURL(), когда нужно убедиться, что браузер перешёл на нужную страницу:

await page.getByRole("button", { name: "Войти" }).click();
await page.waitForURL("**/dashboard");

Настройка таймаута

По умолчанию web-first утверждения ждут 5 секунд. Это можно изменить глобально в конфигурации или для конкретного утверждения:

// для одного утверждения
await expect(page.getByText("Отчёт готов")).toBeVisible({ timeout: 15_000 });

Увеличивать таймаут стоит осторожно: если тест стабильно ждёт 10 секунд — возможно, проблема в самом приложении, а не в тесте.

Коротко

  • Фиксированные паузы (sleep, waitForTimeout) делают тесты медленными и всё равно нестабильными — их не должно быть в e2e.
  • Web-first утверждения (expect(locator).toXxx()) повторяются автоматически, пока условие не выполнится или не истечёт таймаут.
  • Действия (click, fill и др.) тоже ждут готовности элемента — явная проверка перед действием не нужна.
  • Для ожидания условий, не связанных с DOM, используй waitForResponse(), waitForURL() и подобные — ждут конкретного события, а не времени.
  • Если возник соблазн поставить паузу — значит, надо выразить ожидание через условие.

Что почитать дальше

  • Нестабильные тесты: причины и лечение — откуда берётся нестабильность и как с ней работать системно.
  • Данные и аутентификация — как подготовить данные и сессию так, чтобы тесты не зависели друг от друга.
  • Сетевые запросы и моки — перехват и подмена ответов для контроля внешних зависимостей.