Главная · Блог о тестировании ·

Playwright для тестировщика: UI и API автотесты с нуля

Первый полезный автотест — не тот, который просто нажимает кнопку, а тот, который независимо создаёт данные, проверяет бизнес-результат и оставляет понятный trace при падении. Разберём путь от ручного сценария до устойчивого Playwright suite.

QA-инженер анализирует Playwright-тест, API-запросы, trace и результат CI
Жизненный цикл устойчивого автотеста
  1. Выбрать риск и oracle
  2. Подготовить состояние через API
  3. Выполнить пользовательский путь
  4. Сохранить evidence и очистить данные
Как распределить проверки по слоям
Риск Лучший слой Почему
Формула скидкиUnit/APIБыстро покрывает много данных
Права на чужой объектAPI + один UI путьПроверяет серверную границу и UX
Checkout пользователяUI end-to-endПроверяет склейку критичного пути
Schema ответаAPI contractТочная и быстрая диагностика
Вёрстка и доступностьUI + visual/a11yНужен отрисованный интерфейс

1. Выбери первый сценарий по риску, а не по удобству

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

Разложи проверки по пирамиде. Расчёты и правила дешевле проверять ниже UI; браузер оставь для поведения, которое действительно требует страницы. Один end-to-end тест не заменяет проверки API, прав доступа и БД, а десятки UI-вариантов делают suite медленным и хрупким.

  • Сценарий важен для бизнеса.
  • Oracle наблюдаем и не зависит от глаз автора.
  • Данные можно создать и удалить независимо.
  • Тест не требует результата другого теста.
  • Падение локализуется по шагу и evidence.
Практика

Для CRM выбери один из сценариев: создание клиента, массовый импорт или сложный отчёт. Обоснуй выбор по риску, данным, стабильности и диагностике.

2. Собери минимальный тест с Arrange, Act и Assert

Playwright Test даёт runner, assertions, fixtures, parallelism и отчёты. Структурируй сценарий как Arrange, Act, Assert: подготовить пользователя и данные, выполнить одно бизнес-действие, проверить наблюдаемый итог. Название теста должно описывать правило, а не последовательность кликов.

Используй web-first assertions: они ожидают нужное состояние в пределах timeout и показывают фактическое значение. Не заменяй их waitForTimeout. Фиксированный sleep либо замедляет быстрый тест, либо остаётся слишком коротким на медленном CI.

Первый тест с бизнес-смыслом
import { test, expect } from '@playwright/test';

test('manager converts a new lead into one customer', async ({ page }) => {
  await page.goto('/leads/42');
  await page.getByRole('button', { name: 'Convert lead' }).click();
  await expect(page.getByRole('heading', { name: 'Customer Alex' })).toBeVisible();
  await expect(page.getByText('Converted')).toBeVisible();
});
Практика

Перепиши ручной сценарий логина в Arrange, Act, Assert. Назови точный результат для успешного входа и отдельный oracle для неверного пароля.

3. Выбирай locators по пользовательскому контракту

Предпочитай getByRole, getByLabel, getByText и getByTestId как согласованный запасной вариант. Role и accessible name отражают то, как элемент видит пользователь и assistive technology. Длинный CSS по div:nth-child ломается после косметической перестановки, не связанной с поведением.

Locator должен однозначно находить элемент. Если кнопок Save несколько, сузь область через контейнер или добавь различимое имя; не выбирай first() только чтобы тест прошёл. Отсутствие доступного имени может быть настоящим дефектом продукта, а не поводом спрятать проблему в селекторе.

Устойчивые и хрупкие locators
// Устойчиво: пользовательский смысл
await page.getByRole('button', { name: 'Save customer' }).click();
await page.getByLabel('Email').fill('alex@example.test');

// Хрупко: структура DOM
await page.locator('.card:nth-child(2) > div > button').click();
Практика

Для формы клиента предложи locators для name, email, owner и Save. Объясни, какой дефект доступности может обнаружить каждый выбор.

4. Изолируй данные через fixtures и API setup

Тесты не должны зависеть от порядка. Каждый получает собственный пользовательский или бизнес-контекст и создаёт только нужные данные. Через API setup быстрее и надёжнее создать lead, чем повторять UI-форму перед каждой проверкой. UI остаётся для действия, которое является предметом теста.

Fixture инкапсулирует setup и teardown, но не должна скрывать половину бизнеса. Делай данные уникальными для parallel run и очищай их через поддерживаемый API или TTL. Shared production-like account быстро превращается в источник гонок и ложных падений.

Setup через API и проверка через UI
test('owner sees the assigned customer', async ({ page, request }) => {
  const created = await request.post('/api/test/customers', {
    data: { name: 'Alex', owner: 'manager-a' }
  });
  const { id } = await created.json();
  await page.goto(`/customers/${id}`);
  await expect(page.getByText('Owner: manager-a')).toBeVisible();
});
Практика

Спроектируй fixture для двух менеджеров и клиента A. Опиши уникальные данные, authentication state, cleanup и возможность parallel execution.

5. Используй APIRequestContext для setup и самостоятельных API-тестов

Playwright умеет отправлять HTTP-запросы тем же runner. Это удобно для подготовки состояния, проверки серверных контрактов и подтверждения результата UI через другой канал. Но статус 200 не доказывает бизнес-успех: проверяй schema, значения, persistence, permission и side effects.

API-тесты должны явно владеть baseURL, auth и test data. Не строй длинную цепочку, где один тест создаёт данные для следующего. Для негативных сценариев проверь отсутствие изменений после 4xx, а для retry — один итоговый эффект по business key.

API-тест с проверкой сохранённого состояния
test('duplicate conversion creates one customer', async ({ request }) => {
  const first = await request.post('/api/leads/42/convert');
  const second = await request.post('/api/leads/42/convert');
  expect([200, 201]).toContain(first.status());
  expect([200, 409]).toContain(second.status());
  const customers = await request.get('/api/customers?source_lead_id=42');
  expect((await customers.json()).data).toHaveLength(1);
});
Практика

Добавь к POST /orders проверки response, повторного GET, stock и duplicate retry. Реши, какие assertions оставить в API, а какие — в одном UI smoke.

6. Добавляй Page Object и компоненты только при реальном повторении

Page Object хранит locators и действия страницы, но не должен превращаться в огромный класс со всеми assertions. Тесту полезно читать бизнес-намерение, а компонентам — скрывать стабильную механику. Для повторяемой таблицы или modal отдельный component object часто точнее целой страницы.

Избегай преждевременной абстракции. Три понятные строки иногда лучше универсального метода с десятью флагами. Разделяй данные, действия и assertions; используй TypeScript types; не глотай ошибки и не добавляй retry внутри helper, который скрывает реальный дефект.

Небольшой объект с пользовательским действием
class LeadPage {
  constructor(private page: Page) {}
  async convert() {
    await this.page.getByRole('button', { name: 'Convert lead' }).click();
  }
  customerHeading(name: string) {
    return this.page.getByRole('heading', { name });
  }
}
Практика

Посмотри на три сценария CRM и выдели общие компоненты. Не создавай общий метод, если параметры меняют смысл действия.

7. Управляй сетью, visual-проверками и доступностью осознанно

Route interception полезен, чтобы воспроизвести 500, timeout или редкий response зависимости, но замоканный happy path не доказывает интеграцию. Отделяй contract tests реального API от UI-теста реакции на управляемый сбой. Проверяй loading, retry, недублирование действия и понятное сообщение пользователю.

Visual comparison ловит неожиданные изменения пикселей, но требует стабильных шрифтов, viewport и данных. Accessibility scan помогает искать классы нарушений, однако не заменяет keyboard navigation и проверку screen reader semantics. Эти методы дополняют функциональные assertions, а не подменяют их.

Управляемый сбой API в UI-тесте
await page.route('**/api/customers/42', route =>
  route.fulfill({ status: 503, body: JSON.stringify({ error: 'unavailable' }) })
);
await page.goto('/customers/42');
await expect(page.getByRole('alert')).toContainText('Try again');
await expect(page.getByRole('button', { name: 'Retry' })).toBeEnabled();
Практика

Спроектируй три UI-теста для 200, медленного ответа и 503. Укажи, что мокается, какой UX ожидается и где отдельно проверяется реальный контракт.

8. Диагностируй flaky tests через trace и запускай в CI

Flaky test — тест, который при одинаковом коде и ожидаемом состоянии то проходит, то падает. Частые причины: shared data, неподходящее ожидание, гонка, внешняя зависимость, нестабильный locator и утечка состояния. Retry помогает собрать второй сигнал, но не лечит причину и не должен превращать красный suite в зелёный.

В CI сохраняй trace on-first-retry, screenshot и video по разумной политике, HTML/JUnit report и сетевые данные без секретов. Раздели быстрый smoke для pull request и более широкий regression по расписанию или перед release. Падение должно показывать commit, browser, environment, test data id и первый полезный stack frame.

Базовая конфигурация для диагностики
export default defineConfig({
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 4 : undefined,
  use: { baseURL: process.env.BASE_URL, trace: 'on-first-retry' },
  reporter: [['html'], ['junit', { outputFile: 'results.xml' }]],
});
Практика

Для теста, который падает 2 раза из 20 на CI, составь план расследования: evidence, воспроизведение, гипотезы, эксперимент и критерий исправления.

Частые вопросы о Playwright

Какой язык выбрать для Playwright?

Для веб-проекта часто выбирают TypeScript из-за экосистемы и типов, но важнее язык команды и поддерживаемость. Playwright также доступен для Python, Java и .NET.

Нужно ли автоматизировать все ручные тест-кейсы?

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

Можно ли использовать waitForTimeout?

Для отладки временно можно, но в suite это почти всегда признак неверной синхронизации. Жди конкретное состояние UI, response или событие.

Как понять, что автотест хорош?

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

Playwright становится рабочим инструментом QA, когда тест выражает риск продукта, управляет своими данными и объясняет падение без ручного повторения всего сценария.

Получить бесплатный доступ к QA-практике Следующий разбор

← Все разборы