Playwright: лучшие практики
Эта глава обобщает наиболее важные лучшие практики написания и поддержки тестов Playwright, помогая вам писать стабильные и поддерживаемые тесты.
Философия тестирования
Тестирование видимого пользователю поведения
Автоматизированные тесты должны проверять то, что конечные пользователи могут видеть и с чем могут взаимодействовать, а не проверять детали реализации кода.
Например, тесты должны проверять, что на странице отображается правильный текст, кнопки кликабельны, а отправка формы приводит к переходу на правильную страницу, а не проверять возвращаемое значение функции JavaScript или внутреннюю структуру DOM.
Пример
expect(await page.evaluate(() => window.__store.getState().user.name))
.toBe('example');
// Рекомендуется: тестировать то, что видят пользователи
await expect(page.getByText('Welcome, example')).toBeVisible();
Изоляция тестов
Каждый тест должен бытьполностью независимымот других тестов и не зависеть от состояния предыдущего теста.
Playwright по умолчанию предоставляет независимый контекст, но также следует убедиться, что вы не полагаетесь на порядок выполнения тестов.
Пример
let createdId;
test('Create resource', async ({ request }) => {
const resp = await request.post('/api/items');
createdId = (await resp.json()).id; // Общее состояние
});
test('Use created resource', async ({ page }) => {
await page.goto(`/items/${createdId}`); // Зависит от результата предыдущего теста
});
// Рекомендуется: каждый тест самодостаточен
test('Create and use resource', async ({ request, page }) => {
const resp = await request.post('/api/items');
const id = (await resp.json()).id;
await page.goto(`/items/${id}`);
await expect(page.getByText('Resource details')).toBeVisible();
});
Принципы приоритета локаторов
Использование локаторов в рекомендуемом порядке может повысить стабильность тестов.
| Приоритет | Метод | Сценарий применения |
|---|---|---|
| 1 | getByRole() | Элемент имеет явную ARIA-роль |
| 2 | getByLabel() | Элемент формы связан с меткой |
| 3 | getByPlaceholder() | Поле ввода имеет placeholder |
| 4 | getByText() | Элемент имеет явный текст |
| 5 | getByAltText() | У изображения есть атрибут alt |
| 6 | getByTitle() | У элемента есть атрибут title |
| 7 | getByTestId() | Резервный вариант |
| 8 | locator() | CSS/XPath — крайняя мера |
Старайтесь не использовать имена CSS-классов в качестве локаторов.
Имена классов склонны к изменениям из-за рефакторинга стилей, что приводит к массовым сбоям тестов.
Избегайте ненужных ожиданий
Используйте механизм автоожидания Playwright, избегайте ручного кодированияsleepилиwaitForTimeout。
Пример
await page.waitForTimeout(3000);
await page.getByText('Data loading complete').click();
// Рекомендуется: полагайтесь на автоожидание и проверки
await expect(page.getByText('Loading data...')).toBeHidden({ timeout: 10000 });
await page.getByText('Data loading complete').click();
Не тестируйте сторонние сервисы
Тестируйте только то, что вы можете контролировать; не зависьте от доступности сторонних сервисов.
Пример
await page.goto('https://www.example.com/');
// Страница может загружать Google Analytics, внешние шрифты и т. д.
// Рекомендуется: перехватывать запросы к сторонним сервисам, имитировать внешние сервисы
await page.route('**/*analytics*', route => route.abort());
await page.route('**/external-api/**', route => {
route.fulfill({ json: { status: 'ok' } });
});
Используйте мягкие утверждения expect.soft()
При проверке нескольких независимых условий мягкие утверждения могут сообщить обо всех сбоях сразу, а не останавливаться на первом сбое.
Пример
await expect.soft(page.getByLabel('Username')).toBeVisible();
await expect.soft(page.getByLabel('Password')).toBeVisible();
await expect.soft(page.getByLabel('Email')).toBeVisible();
await expect.soft(page.getByRole('button', { name: 'Register' })).toBeEnabled();
// Если несколько полей отсутствуют, сообщить обо всех сразу
Разумно организуйте тестовые файлы
| Практика | Описание |
|---|---|
| Разделяйте файлы по функциональным модулям | login.spec.ts、checkout.spec.tsожидание |
| Используйте describe для группировки | Помещайте связанные тесты вtest.describe()Средняя |
| Правильно используйте beforeEach | Вынесите повторяющиеся шаги навигации и настройки вbeforeEach |
| Описательные названия тестов | Хорошие названия объясняют «что было сделано» и «что ожидается» |
| Вынесите общий код | Вспомогательные функции, используемые более чем в 3 файлах, следует вынести в общий модуль |
| Не переусердствуйте с DRY | 2-3 строки повторения легче поддерживать, чем чрезмерную абстракцию |
Общие советы по устранению неполадок
Нестабильные тесты
Причины и решения для нестабильных тестов:
| Причина | Решение |
|---|---|
| Жёстко заданная задержка недостаточно длинная | Используйте механизм автоожидания вместо фиксированных ожиданий |
| Зависимость от сторонних сервисов | Используйтеroute()Мокируйте внешние запросы |
| Анимации вызывают изменение положения элемента | В конфигурацииanimations: 'disabled' |
| Остаточные данные между тестами | Убедитесь, что каждый тест независимо создаёт и очищает данные |
| Логика, связанная со временем | Используйтеpage.clockФиксированное время |
Locator не может найти элемент
Шаги устранения неполадок:
- Проверьте, находится ли элемент в DOM (используйте
toBeAttached()а неtoBeVisible()) - Проверьте, находится ли элемент в области просмотра
- Проверьте, вложен ли он в iframe
- Используйте Pick Locator в UI Mode для проверки локатора
- Проверьте, есть ли несколько совпадающих элементов (Locator по умолчанию требует строгого режима)