Главная · Блог о тестировании ·
Playwright для тестировщика: UI и API автотесты с нуля
Первый полезный автотест — не тот, который просто нажимает кнопку, а тот, который независимо создаёт данные, проверяет бизнес-результат и оставляет понятный trace при падении. Разберём путь от ручного сценария до устойчивого Playwright suite.
- Выбрать риск и oracle
- Подготовить состояние через API
- Выполнить пользовательский путь
- Сохранить 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() только чтобы тест прошёл. Отсутствие доступного имени может быть настоящим дефектом продукта, а не поводом спрятать проблему в селекторе.
// Устойчиво: пользовательский смысл
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 быстро превращается в источник гонок и ложных падений.
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.
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, а не подменяют их.
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, когда тест выражает риск продукта, управляет своими данными и объясняет падение без ручного повторения всего сценария.