Утверждения (expect)
Урок: Утверждения (expect)
Введение
Представь, что ты проверяешь форму логина вручную. Ты вводишь данные, нажимаешь кнопку и… что дальше?
Ты смотришь:
- появилась ли надпись «Успешно»;
- открылся ли нужный экран;
- исчезла ли ошибка.
То есть ты не просто выполняешь действия — ты проверяешь результат.
В автоматизации это ключевой момент. Если тест только кликает и вводит данные, но ничего не проверяет — он бесполезен.
Именно для проверок в Playwright используются утверждения — expect.
Они позволяют ответить на главный вопрос любого теста: «всё прошло правильно или нет?»
Официальный обзор: Assertions | Playwright. Про таймауты проверок и теста — Test timeouts. Список «обычных» матчеров (числа, объекты, строки) — Generic assertions; для DOM — Locator assertions и Page assertions.
Что такое expect
expect — это способ проверить, что состояние страницы (или произвольное значение) соответствует ожиданиям.
Два семейства:
- Общие матчеры —
expect(значение).toEqual(...),toBeTruthy()и т.д. Они синхронны, автоматически не ждут изменений на странице. - Веб-матчеры у локатора и страницы —
await expect(locator).toHaveText(...),await expect(page).toHaveURL(...). Они асинхронны: Playwright многократно перечитывает DOM, пока условие не выполнится или не истечёт таймаут (по умолчанию для таких проверок обычно 5 секунд, см.use.expect.timeoutв конфиге).
Пример с автоповтором:
await expect(page.getByText('Успешно')).toBeVisible();
Разберём:
expect(...)— «ожидаем, что…»;toBeVisible()— элемент должен стать видимым;awaitобязателен: внутри идёт ожидание и повторные попытки.
Если условие выполняется — тест проходит. Если нет — тест падает с логом последних попыток.
Почему expect важнее, чем кажется
Рассмотрим два теста.
Без expect:
await page.getByRole('button', { name: 'Отправить' }).click();
Этот тест ничего не проверяет. Даже если ничего не произошло — он «пройдёт».
С expect:
await page.getByRole('button', { name: 'Отправить' }).click();
await expect(page.getByText('Успешно')).toBeVisible();
Теперь тест:
- выполняет действие;
- проверяет результат.
И только теперь он имеет смысл.
Автоматические ожидания внутри expect
Одна из самых сильных сторон Playwright — это встроенные ожидания.
await expect(page.getByText('Успешно')).toBeVisible();
Что происходит:
- Playwright не проверяет сразу;
- он ждёт, пока текст появится;
- проверяет условие;
- если не появилось — падает с ошибкой.
Это избавляет от ручных ожиданий:
// так делать не нужно
await page.waitForTimeout(2000);
Утверждения над локатором (с автоповтором)
Ниже — основные асинхронные матчеры из таблицы auto-retrying assertions. Их вызывают как await expect(locator).….
Видимость и положение в DOM
await expect(page.getByRole('alert')).toBeVisible();
await expect(page.getByText('Черновик')).toBeHidden(); // не виден
await expect(page.getByTestId('modal')).toBeAttached(); // есть в DOM, даже если скрыт
await expect(page.getByRole('navigation')).toBeInViewport(); // пересекается с вьюпортом
Состояние контролов (форма, фокус, доступность клика)
await expect(page.getByRole('button', { name: 'Отправить' })).toBeEnabled();
await expect(page.getByRole('button', { name: 'Сохранить' })).toBeDisabled();
await expect(page.getByRole('textbox', { name: 'Комментарий' })).toBeEditable();
await expect(page.getByRole('checkbox', { name: 'Согласен' })).toBeChecked();
await expect(page.getByRole('checkbox', { name: 'Опция' })).not.toBeChecked();
await expect(page.getByLabel('Поиск')).toBeFocused();
await expect(page.getByRole('list')).toBeEmpty(); // контейнер без детей
Текст (точное совпадение и вхождение)
await expect(page.getByRole('heading')).toHaveText('Добро пожаловать');
await expect(page.getByRole('status')).toHaveText(/успешн/i);
// локатор сразу на несколько узлов — массив ожидаемых текстов по порядку
await expect(page.getByRole('listitem')).toHaveText(['Товар A', 'Товар B', 'Товар C']);
await expect(page.getByRole('paragraph')).toContainText('часть фразы');
Опции вроде { ignoreCase: true } и таймаут см. в toHaveText.
Значения полей и <select>
await expect(page.getByLabel('Email')).toHaveValue('test@mail.com');
// несколько выбранных option (multiple)
await expect(page.getByLabel('Теги')).toHaveValues([/bug/i, 'docs']);
Роли и доступное имя (a11y)
await expect(page.getByTestId('widget')).toHaveRole('dialog');
await expect(page.getByRole('button')).toHaveAccessibleName('Закрыть');
await expect(page.getByRole('textbox')).toHaveAccessibleDescription(/минимум 8 символов/);
await expect(page.getByRole('navigation')).toMatchAriaSnapshot(`
- navigation:
- link "Главная"
`);
toMatchAriaSnapshot удобен для стабильной сверки дерева доступности; формат — в документации.
Атрибуты, классы, стили, id
await expect(page.getByRole('link', { name: 'Документация' })).toHaveAttribute('href', /docs\//);
await expect(page.locator('.pill')).toHaveClass(/active/);
await expect(page.locator('.badge')).toContainClass('badge--new');
await expect(page.getByRole('textbox')).toHaveId('email-field');
await expect(page.getByTestId('banner')).toHaveCSS('background-color', 'rgb(0, 128, 0)');
await expect(page.locator('canvas')).toHaveJSProperty('width', 800);
Количество совпадений локатора
await expect(page.getByRole('listitem')).toHaveCount(3);
Полезно для списков, строк таблицы, карточек.
Скриншот элемента
await expect(page.getByTestId('chart')).toHaveScreenshot('chart.png');
Первый прогон может записать эталон; дальше сравнивается пиксельно (настройки — в доке по toHaveScreenshot).
Утверждения над страницей (page)
await expect(page).toHaveURL(/\/dashboard/);
await expect(page).toHaveTitle('Кабинет — Мой сервис');
await expect(page).toHaveScreenshot({ fullPage: true });
Ответ HTTP (APIResponse)
После page.goto или request.get можно проверить статус:
const response = await page.goto('https://example.com');
await expect(response).toBeOK();
Отрицание (not)
await expect(page.getByText('Ошибка')).not.toBeVisible();
await expect(page.getByRole('textbox', { name: 'Логин' })).not.toHaveValue('');
Таймаут и своё сообщение об ошибке
Увеличить ожидание для одной проверки:
await expect(page.getByText('Готово')).toBeVisible({ timeout: 15_000 });
Второй аргумент у expect — текст для отчёта (видно и при успехе, и при падении):
await expect(page.getByRole('banner'), 'пользователь должен быть залогинен').toContainText('Анна');
Глобально таймаут проверок задаётся в playwright.config → use.expect.timeout (см. Test timeouts).
Мягкие проверки (expect.soft)
По умолчанию при падении expect тест сразу останавливается. expect.soft помечает ошибку, но даёт дойти до следующих строк — удобно собрать несколько независимых проверок за один прогон:
await expect.soft(page.getByTestId('status')).toHaveText('Успех');
await expect.soft(page.getByTestId('eta')).toHaveText('1 день');
await page.getByRole('link', { name: 'Дальше' }).click();
Работает только в раннере @playwright/test. После блока soft-проверок можно явно проверить, не было ли ошибок: expect(test.info().errors).toHaveLength(0) — см. Soft assertions.
Свой expect: expect.configure
Общий таймаут или режим soft для группы проверок:
const slowExpect = expect.configure({ timeout: 10_000 });
await slowExpect(page.getByText('Отправлено')).toBeVisible();
const softExpect = expect.configure({ soft: true });
await softExpect(page.getByRole('status')).toHaveText('OK');
Опрос до условия: expect.poll
Когда нужно ждать не DOM, а произвольное условие (например, API ответил 200):
await expect
.poll(
async () => {
const res = await page.request.get('https://api.example.com/health');
return res.status();
},
{ message: 'API должен ожить', timeout: 10_000 },
)
.toBe(200);
Интервалы между опросами настраиваются опцией intervals. Подробнее — expect.poll.
Повтор блока кода: expect.toPass
Несколько обычных expect внутри функции выполняются снова и снова, пока все не пройдут:
await expect(async () => {
const res = await page.request.get('https://api.example.com/health');
expect(res.status()).toBe(200);
}).toPass({ timeout: 60_000, intervals: [1_000, 2_000, 5_000] });
По умолчанию у toPass своё поведение по таймауту — см. expect.toPass.
«Обычные» матчеры без автоповтора
Для чисел, объектов, массивов, функций используй синхронные матчеры из Generic assertions: toEqual, toStrictEqual, toContain, toMatch, toHaveProperty, toBeGreaterThan, toThrow и др. Они один раз сравнивают значение — для UI это часто хуже, чем await expect(locator)…, потому что страница может обновиться чуть позже.
Асимметричные матчеры (expect.objectContaining, expect.stringMatching, …) — в доке.
Свои матчеры: expect.extend
Можно добавить, например, toHaveAmount через expect.extend и экспортировать общий expect из fixtures.ts — шаблон в документации. Несколько модулей с матчерами объединяют через mergeExpects.
Важно: не путай expect из @playwright/test с библиотекой Jest expect — для тестов Playwright нужен встроенный expect, иначе потеряются автоповтор и интеграция с отчётами.
Пример полного сценария
await page.goto('https://example.com/login');
await page.getByLabel('Email').fill('test@mail.com');
await page.getByLabel('Пароль').fill('secret');
await page.getByRole('button', { name: 'Войти' }).click();
await expect(page.getByRole('heading'), 'после входа виден кабинет').toHaveText(
/добро пожаловать/i,
);
Здесь:
- выполняются действия пользователя;
- затем проверяется результат.
Это правильная структура теста.
Разница между expect и ручной проверкой
Плохо:
const text = await page.textContent('.message');
if (text !== 'Успешно') {
throw new Error('Ошибка');
}
Проблемы:
- нет ожидания;
- код громоздкий;
- ошибки менее информативные.
Лучше:
await expect(page.getByText('Успешно')).toBeVisible();
Playwright:
- сам ждёт;
- даёт понятную ошибку;
- делает код чище.
Частые ошибки
Ошибка 1 — отсутствие проверок:
await page.getByRole('button', { name: 'OK' }).click();
// дальше ни одного expect — тест «зелёный», но ничего не гарантирует
Ошибка 2 — разовое чтение DOM вместо автоповторя у expect(locator):
const text = await page.locator('.message').textContent();
expect(text).toBe('Готово'); // гонка: сообщение могло ещё не обновиться
Ошибка 3 — waitForTimeout вместо ожидания условия:
await page.waitForTimeout(2000);
Ошибка 4 — ждать UI через «голый» expect без локатора там, где нужен DOM:
expect(await page.locator('.x').isVisible()).toBe(true); // хрупко; лучше await expect(locator).toBeVisible()
Где это используется на практике
Утверждения используются в каждом тесте:
- проверка логина;
- проверка отображения данных;
- проверка ошибок;
- проверка переходов между страницами.
Без expect невозможно понять, работает ли приложение правильно.
Итоговое понимание
expect превращает сценарий в проверяемый тест.
Для интерфейса опирайся на асинхронные матчеры локатора и страницы из Assertions: видимость, текст, значения, роли, атрибуты, количество, URL, заголовок, скриншоты, toMatchAriaSnapshot. Для произвольной логики — expect.poll и expect.toPass. Для нескольких независимых проверок подряд — expect.soft и кастомное сообщение у expect.
Хороший e2e-тест явно отвечает на вопрос «стало ли состояние таким, как нужно» — в первую очередь через await expect(…) к DOM и странице, а не через случайные таймауты.
Веб-страница из видео
Демо для отработки действий с элементами: Assertions — демо-страница.