Playwright

Что это такое

Playwright автоматизирует браузер, поэтому на нём пишут сквозные и кроссбраузерные тесты. Тест управляет настоящим браузером через небольшой API: находит элемент, щёлкает по нему, вводит текст, проверяет, что показывает страница. Отличительная черта — ожидание: прежде чем действовать, библиотека сама доводит страницу до состояния, нужного человеку, и автору не приходится расставлять задержки по тесту; один и тот же тест к тому же выполняется в нескольких движках браузеров без правки кода.

Почему это важно

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

Ключевые идеи

Локаторы описывают элемент, а не его положение

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

await page.getByRole("button", { name: "Add to basket" }).click();
await expect(page.getByRole("listitem")).toHaveCount(3);

Автоожидание и утверждения о состоянии

Действия ждут, пока с элементом можно будет работать, а утверждения ждут, пока условие станет истинным, опрашивая его до тайм-аута. Тест описывает ожидаемое конечное состояние, а не последовательность догадок о таймингах. В этом самое большое отличие от старых инструментов, поэтому явная пауза в тесте Playwright обычно означает, что что-то другое сделано неправильно.

await expect(page.getByText("Saved")).toBeVisible();
await page.getByLabel("Email").fill("user@example.com");

Каждый тест получает свежий контекст

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

test.describe("checkout", () => {
  test.use({ storageState: "auth.json" });   // старт уже с выполненным входом
});

Один API для всех браузеров

Один и тот же тест выполняется в нескольких движках браузеров, поэтому кроссбраузерное поведение проверяется настройкой, а не переписыванием тестов. Эмуляция размера окна, локали, часового пояса и даже условий сети входит в тот же API, поэтому в одном наборе практично проверять и мобильную вёрстку, и путь медленного соединения.

запустить набор на chromium, firefox и webkit из одной конфигурации

Отладка встроена в рабочий процесс

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

включить трассировку при отказе -> открыть трассировку -> дойти до упавшего действия

Практика

Один небольшой запускаемый тест

Напишите тест, который открывает страницу, заполняет поле поиска, отправляет его и проверяет, что появился результат. Запустите его в безголовом режиме, затем ещё раз с включённой трассировкой.

import { test, expect } from "@playwright/test";

test("search returns at least one result", async ({ page }) => {
  await page.goto("/");
  await page.getByLabel("Search").fill("keyboard");
  await page.getByRole("button", { name: "Search" }).click();
  await expect(page.getByRole("listitem").first()).toBeVisible();
});

Готово, когда: тест проходит на локальном сервере, падает с понятным сообщением, если нарочно очистить поле поиска, а трассировка открывается и показывает упавшее действие.

Замените паузу утверждением

Найдите тест, который ждёт фиксированной задержкой или содержит цикл повторных попыток вокруг утверждения, и замените это утверждением, ждущим нужного состояния.

Готово, когда: тест быстрее и по-прежнему падает правильно, когда ожидание не выполняется.

Частые ошибки

  • Выбор по CSS-классу или сгенерированному id — изменение оформления ломает тест, который проверяет поведение; исправить: выбирайте по роли, подписи или видимому тексту.
  • Явные ожидания и паузы — тест лишь кажется стабильным и падает на более медленной машине; исправить: полагайтесь на автоожидание и утверждения о состоянии.
  • Общая страница для нескольких тестов — состояние протекает, и отказы зависят от порядка; исправить: пусть каждый тест использует свой контекст.
  • Утверждения о значениях, которых пользователь не видит — тест доказывает деталь реализации; исправить: утверждайте то, что страница действительно показывает.
  • Игнорирование трассировки и догадки о причине — та же нестабильность вернётся через неделю; исправить: откройте записанную трассировку, прежде чем что-то менять.

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

  • Сквозное тестирование (01-cs-fundamentals/26-end-to-end-testing) — дисциплина, которой служит инструмент, поэтому сначала прочитайте её: что тестировать и зачем.
  • Надёжность сквозных тестов (01-cs-fundamentals/26-end-to-end-testing/02-making-end-to-end-tests-trustworthy) — привычки, сохраняющие браузерному набору честность, с инструментарием Playwright как конкретным случаем.
  • Отладка (01-cs-fundamentals/11-debugging) — что делать, когда трассировка показывает отказ, который вы пока не понимаете.