Если обернуть проверку Playwright в try/catch, провал заглушится: тест пройдёт когда не должен, и баг попадёт в прод. Этот гайд про то, когда обработка ошибок уместна в тестовом коде (сбои настройки, логика повторов, очистка) и когда нет. Плюс finally для гарантированного teardown, кастомные сообщения throw вместо общих ошибок проверок и async-паттерны предотвращающие незамеченные провалы тестов.
Основы: try/catch
Блок try/catch позволяет выполнять код и обрабатывать ошибки без крэша программы:
try {
// Код который может выбросить ошибку
const result = JSON.parse('not valid json');
} catch (error) {
// Выполняется если блок try выбрасывает ошибку
console.log('Parsing failed:', error.message);
}Без try/catch вызов JSON.parse('not valid json') крэшнет скрипт с необработанной ошибкой. С try/catch обрабатываешь её корректно.
Переменная error в блоке catch это объект Error со свойствами: error.message (читаемое описание), error.name (тип ошибки: 'SyntaxError', 'TypeError' и др.) и error.stack (полный стек-трейс).
Блок finally
finally выполняется независимо от того была ошибка или нет. Полезен для очистки:
test('загрузка файла с очисткой', async ({ page }) => {
const tempFile = await createTempFile();
try {
await page.setInputFiles('[data-testid="upload"]', tempFile);
await expect(page.locator('[data-testid="upload-success"]')).toBeVisible();
} catch (error) {
console.error('Upload test failed:', error.message);
throw error; // Перебрасываем чтобы тест всё же провалился
} finally {
await deleteTempFile(tempFile); // Всегда очищает, даже при провале
}
});finally выполняется всегда. Ценно для кода очистки тестов.
async/await и ошибки
В Playwright почти всё async. Ошибки в асинхронном коде работают так же: просто нужен await в правильном месте.
// Без try/catch: необработанный rejection, тест падает с некрасивой ошибкой
async function getUserFromApi() {
const response = await fetch('/api/user/999');
const data = await response.json();
return data;
}
// С try/catch: обработанный rejection, контролируешь сообщение ошибки
async function getUserFromApi() {
try {
const response = await fetch('/api/user/999');
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Failed to fetch user:', error.message);
throw error; // Перебрасываем если хочешь чтобы вызывающий тоже обработал
}
}Выброс собственных ошибок
Можно выбрасывать ошибки намеренно для сигнализации о сбое:
function validateEmail(email: string): void {
if (!email.includes('@')) {
throw new Error(`Invalid email format: "${email}"`);
}
if (email.length > 254) {
throw new Error(`Email too long: ${email.length} characters (max 254)`);
}
}
try {
validateEmail('not-an-email');
} catch (error) {
console.log(error.message); // 'Invalid email format: "not-an-email"'
}В тестах throw используется чтобы провалиться с конкретным сообщением вместо общей ошибки проверки:
async function getAuthToken(request: APIRequestContext): Promise<string> {
const response = await request.post('/api/auth/login', {
data: { email: 'test@test.com', password: 'TestPass1' },
});
if (response.status() !== 200) {
throw new Error(`Login failed: ${response.status()}, cannot proceed with tests`);
}
const { token } = await response.json();
if (!token) {
throw new Error('Login response missing token field');
}
return token;
}Типы ошибок
В JavaScript несколько встроенных типов ошибок:
| Тип ошибки | Когда появляется |
|-----------|----------------|
| Error | Общая ошибка (базовый класс) |
| TypeError | Неверный тип: null.something, вызов не-функции |
| SyntaxError | Невалидный JavaScript или парсинг JSON |
| RangeError | Значение вне диапазона: new Array(-1) |
| ReferenceError | Переменная не существует |
Можно проверять тип чтобы по-разному обрабатывать разные ошибки:
try {
await doSomething();
} catch (error) {
if (error instanceof TypeError) {
console.log('Type error: проверь типы данных');
} else if (error instanceof SyntaxError) {
console.log('Syntax error: невалидный JSON или похожее');
} else {
throw error; // Неизвестная ошибка: перебрасываем
}
}Частые паттерны в тестах Playwright
1. Повтор операции при сбое
async function waitForApiReady(request: APIRequestContext, maxAttempts = 5): Promise<void> {
for (let i = 0; i < maxAttempts; i++) {
try {
const response = await request.get('/api/health');
if (response.status() === 200) return;
} catch (error) {
// API ещё не готово, пробуем снова
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
throw new Error(`API not ready after ${maxAttempts} attempts`);
}2. Сбор ошибок без остановки
const errors: string[] = [];
for (const email of testEmails) {
try {
await validateEmailField(page, email);
} catch (error) {
errors.push(`${email}: ${error.message}`);
}
}
if (errors.length > 0) {
throw new Error(`Validation failures:\n${errors.join('\n')}`);
}3. Кастомный класс ошибки для чистоты кода
class TestDataError extends Error {
constructor(message: string) {
super(message);
this.name = 'TestDataError';
}
}
// Теперь можно отличать ошибки настройки тест-данных от ошибок проверок
try {
const user = await createTestUser(request);
} catch (error) {
if (error instanceof TestDataError) {
console.error('Test setup failed: skipping test');
test.skip();
}
throw error;
}Ошибки в Playwright: что встретишь
TimeoutError
Самая частая ошибка Playwright. Локатор не стал видимым вовремя:
TimeoutError: locator.click: Timeout 30000ms exceeded.
Locator: [data-testid="submit-button"]В большинстве случаев это не та JavaScript-ошибка которую нужно перехватывать: Playwright сам обрабатывает её и проваливает тест с понятным сообщением. Но если нужно обработать таймаут корректно (попробовать что-то другое если элемент не появился), можно:
try {
await page.locator('[data-testid="popup"]').click({ timeout: 2000 });
} catch (error) {
// Попап не появился за 2 секунды: нормально
// Продолжаем тест без взаимодействия с попапом
}Unhandled Promise Rejection
Если вызываешь async-функцию без await и она выбрасывает ошибку, это может стать «необработанным rejection»: тест не падает сразу, и сообщение об ошибке может быть запутанным.
// Плохо: без await, ошибка может быть проглочена
page.goto('/admin'); // Если упадёт, тест может пройти с неверным состоянием
// Хорошо: ошибка всплывает сразу
await page.goto('/admin');Всегда await для async-операций в тестах.
Когда НЕ использовать try/catch в тестах
Не используй try/catch чтобы скрыть провалы проверок. Если проверка упала, тест должен провалиться:
// Неправильно: глушит провал проверки, тест "проходит" когда не должен
try {
await expect(page.locator('[data-testid="success"]')).toBeVisible();
} catch (error) {
console.log('Element not visible but continuing...');
}
// Правильно: даёт провалам проверок проваливать тест
await expect(page.locator('[data-testid="success"]')).toBeVisible();try/catch в тестах подходит для кода настройки и очистки где сбой не должен маскировать сам тест, для намеренной устойчивости (логика повторов, необязательные взаимодействия) и для создания тест-данных где нужно чёткое кастомное сообщение об ошибке. Не для заглушения проверок.
Краткое резюме
try/catchобрабатывает ошибки: код вtryвыполняется,catchобрабатывает любую выброшенную ошибкуfinallyвыполняется всегда: используй для очистки- Всегда
awaitдля async-операций: необождаемые async-сбои сложно отлаживать throwнамеренно когда нужно конкретное сообщение об ошибке- Не перехватывай провалы проверок: они должны проваливать тест
instanceofпозволяет проверить тип ошибки и обрабатывать разные ошибки по-разному