Урок 4. Почему в каждом автотесте куча await?
Асинхронность JS/TS
Темы урока
Функции и стрелочные функции, Promise, async/await, забытый await, порядок шагов теста, makeUser и registerUser, правка чужого автотеста, browser.newContext, негативная проверка
Видео урока
Конспект урока
Конспект — Урок 4: Почему в каждом автотесте куча await?
Главное за урок
Браузер — отдельный процесс, не мгновенная функция в вашем файле. page.click() не кликает «внутри TypeScript»: он отправляет команду и ждёт ответ. Пока ждём, программа может делать другое. Поэтому API Playwright построен на обещаниях (Promise), а await говорит: «подожди этот шаг, потом иди дальше».
Без await строки на экране идут сверху вниз, а в браузере — почти одновременно. Тест врёт о порядке действий: клика ещё не было, а проверка уже побежала.
Синхронный код vs асинхронный
Синхронный — следующая строка начинается, только когда предыдущая уже вернула результат. Сложение чисел, сбор объекта, JSON.parse — синхронны.
const a = 1 + 1; // сразу 2
const user = { name: "Анна" }; // сразу объект
Асинхронный — результат будет позже: сеть, диск, браузер, таймер. Функция сразу возвращает не ответ, а обещание ответа.
const page = browser.newPage(); // это ещё не страница, а Promise
В автотесте почти всё общение со страницей асинхронное: открыть URL, заполнить поле, кликнуть, дождаться текста.
Promise: три состояния
Promise — объект-обещание: «результат придёт позже — успех или ошибка».
| Состояние | Смысл |
|---|---|
| pending | Ещё ждём |
| fulfilled | Сдержано: есть значение (resolve) |
| rejected | Нарушено: есть ошибка (reject) |
Пока обещание pending, притворяться, что результат уже есть, нельзя. goto, fill, click, newPage, expect(...).toBeVisible() — каждая такая строка возвращает Promise.
Старый способ дождаться — .then() / .catch(). В тестах так почти не пишут: вместо цепочек колбэков используют async / await.
// узнать можно, но в тестах так не пишем
page.goto("/login").then(() => page.getByLabel("Email").fill("a@test.com"));
async и await
| Слово | Что делает |
|---|---|
async перед функцией |
Функция сама вернёт Promise; внутри разрешён await |
await перед выражением |
Пауза, пока Promise не завершится; в переменную попадает уже результат, не обещание |
async function openLogin(page: Page) {
await page.goto("/login");
await page.getByLabel("Email").fill("a@test.com");
}
Правила, без которых код не соберётся или будет врать:
awaitможно писать только внутриasync.async functionвсегда возвращает Promise, даже если вы написалиreturn 5— снаружи этоPromise<number>.awaitдостаёт значение из Promise:const page = await browser.newPage()— вpageстраница, не обещание.- Если Promise rejected,
awaitбросает ошибку — тест падает на этой строке. Это нормально: шаг не удался.
Стрелочная запись та же:
test("логин", async ({ page }) => {
await page.goto("/login");
});
test(..., async ({ page }) => { ... }) — тело теста асинхронное, потому что внутри await.
Что кладётся в переменную: с await и без
Это главная ловушка новичка.
const page = await browser.newPage(); // страница
await page.goto("/login"); // дождались навигации
const notPage = browser.newPage(); // Promise, не страница
// notPage.goto("/login") — странная ошибка: у Promise нет goto
| Код | Что в переменной |
|---|---|
await browser.newPage() |
Страница |
browser.newPage() без await |
Обещание страницы |
await page.goto("/login") |
Навигация закончилась |
page.goto("/login") без await |
Обещание; следующая строка уже бежит |
Забыли await перед click / fill / goto — следующая строка может начаться раньше, чем действие произошло. Симптомы: падение «не там», locator not found, или ложно-зелёный (проверка прошла по старой странице).
Правило: делаете что-то со страницей через Playwright — почти наверняка нужен await.
Порядок строк ≠ порядок в браузере
С await шаги идут строго один за другим:
await page.goto("/login");
await page.getByLabel("Email").fill(email);
await page.getByRole("button", { name: "Войти" }).click();
await expect(page).toHaveURL(/\/dashboard/);
Сначала открыли форму, потом заполнили, потом кликнули, потом проверили URL. Без await эти четыре обещания «выстреливают» почти сразу — проверка URL бежит, пока форма ещё грузится.
Порядок await — это и есть сценарий. В гонке двух пользователей за один ресурс он критичен: кто первый дождался «Подтвердить», тот занял слот.
Что ждать, а что нет: локатор vs действие
Не каждая строка Playwright асинхронная.
| Вызов | await? |
Почему |
|---|---|---|
page.getByRole("button", { name: "Войти" }) |
Нет | Локатор — описание «где искать». Поиск произойдёт в момент действия |
locator.click() / .fill() / .check() |
Да | Команда браузеру |
page.goto() / page.reload() |
Да | Навигация |
browser.newContext() / context.newPage() |
Да | Создаём сессию и вкладку |
expect(locator).toBeVisible() / .toHaveText() |
Да | Проверка сама ждёт и возвращает Promise |
expect(2 + 2).toBe(4) |
Нет | Обычный синхронный expect, без страницы |
const button = page.getByRole("button", { name: "Войти" }); // без await
await button.click(); // с await
await expect(button).toBeVisible(); // с await
Auto-waiting: зачем не нужен sleep(3000)
Playwright сам ждёт, пока элемент станет готовым к действию (виден, стабилен, не перекрыт, enabled). Это auto-waiting. Таймаут по умолчанию — несколько секунд, потом падение.
Поэтому перед кликом не ставят waitForTimeout(3000) «на всякий случай». Сон делает тест медленным и хрупким: то мало, то много.
await и auto-waiting — разные вещи:
- auto-waiting — как долго Playwright готов ждать элемент внутри одного действия;
await— когда ваш код перейдёт к следующей строке.
Ни auto-waiting, ни Promise.all не спасают от забытого await.
Promise.all — когда шаги независимы
Promise.all запускает несколько обещаний параллельно и ждёт все сразу.
await Promise.all([
hostContext.close(),
guestContext.close(),
]);
Имеет смысл, если шаги не зависят друг от друга. Заполнить поле и сразу кликнуть «Войти» параллельно нельзя: клик должен быть после fill. В пользовательском сценарии почти всегда идём цепочкой await.
Синхронная фабрика и асинхронный шаг
Две почти одинаковые регистрации — сигнал вынести функцию. Данные и браузер — разные функции.
function makeUser(role: string, runId: number) {
return {
name: `${role}-${runId}`,
email: `${role}-${runId}@example.com`,
password: "password1234",
};
}
async function registerUser(page: Page, user: { name: string; email: string; password: string }) {
await page.goto("/register");
await page.getByLabel("Имя").fill(user.name);
await page.getByLabel("Email").fill(user.email);
await page.getByLabel("Пароль").fill(user.password);
await page.getByRole("button", { name: "Зарегистрироваться" }).click();
}
makeUser не ходит в UI — обычный return, без async. registerUser кликает по форме — async и await на каждом действии.
Два пользователя — два контекста
Один page делит cookies. Второй «пользователь» в той же вкладке — это всё ещё первый человек.
const hostContext = await browser.newContext();
const guestContext = await browser.newContext();
const hostPage = await hostContext.newPage();
const guestPage = await guestContext.newPage();
newContext() — как отдельное окно инкогнито: своя сессия. Два аккаунта = два контекста и две страницы. Вторая вкладка page.context().newPage() остаётся в той же сессии.
В конце контексты закрывают: await hostContext.close().
Негатив и гонка: порядок await — часть сценария
Ожидаемая ошибка в UI — валидный результат теста. Assert ждёт текст ошибки или недоступность кнопки, а не «всё зелёное».
Если два пользователя бьются за один ресурс, второму часто нужно открыть тот же экран до подтверждения первым. Иначе ресурс уже занят, и второй не увидит ту же форму или ту же ошибку. Это не синтаксис — это порядок await в сценарии.
Как читать асинхронный тест сверху вниз
test("логин", async ({ browser }) => {
const context = await browser.newContext();
const page = await context.newPage();
const user = makeUser("guest", Date.now()); // синхронно
await registerUser(page, user);
await expect(page).toHaveURL(/\/dashboard/);
await context.close();
});
- Тело
test—async, иначеawaitнельзя. - Создание контекста и страницы — с
await. - Данные — без
await. - Шаги в браузере и
expectпо странице — сawait. - Следующая строка = следующий шаг в сценарии.
Ключевые тезисы для теста
- Браузер отвечает не мгновенно: действие Playwright возвращает Promise.
awaitдостаёт результат из Promise и фиксирует порядок шагов.awaitтолько внутриasync; забытыйawaitкладёт в переменную обещание, не страницу.- Локатор (
getByRole) безawait;click/fill/goto/expect(locator)— сawait. - Auto-waiting ждёт готовности элемента;
sleepвместо него не нужен и не заменяетawait. - Фабрика данных синхронная; шаг в UI —
async. - Два пользователя — два
browser.newContext(), не вторая вкладка той же сессии. - Ожидаемая ошибка — нормальный assert; порядок
awaitможет быть частью гонки.
Домашнее задание
Индивидуальная проверка ДЗ — на Boosty
Конспект, видео и тест открыты всем. Текст задания и проверка работы — по подписке.
Индивидуальная проверка ДЗ на Boosty