Когда тест Playwright падает, большинство разработчиков сразу листают к номеру строки в стек-трейсе и читают не ту часть. Самое полезное место в сообщении об ошибке: call log в середине. Там показано, какое условие Playwright проверял и что он наблюдал. Из него видно, был ли элемент отсутствующим, скрытым, задизейбленным или перекрытым оверлеем. В этом гайде: как читать все три части ошибки Playwright, что означают самые частые типы ошибок и четырёхшаговый процесс отладки который закрывает большинство падений.
Анатомия ошибки Playwright
Error: locator.click: Timeout 30000ms exceeded.
Call log:
- waiting for getByRole('button', { name: 'Submit' })
waiting for element to be visible, enabled and stable
element is not visible - waiting...
element is not visible - waiting...
element is not visible - waiting...
at Object.<anonymous> (tests/checkout.spec.ts:45:40)Три части:
1. Тип ошибки и сообщение: locator.click: Timeout 30000ms exceeded (какое действие не выполнилось)
2. Call log: что Playwright делал и наблюдал во время ожидания
3. Стек-трейс: где в тестовом коде это случилось
Читай в таком порядке: сначала тип ошибки, потом call log, потом стек-трейс. Call log говорит больше всего.
Частые ошибки и что они означают
Timeout exceeded
Error: locator.click: Timeout 30000ms exceeded.Playwright не смог выполнить действие за отведённый таймаут. Call log подскажет почему. Частые причины:
- Элемента нет в DOM
- Элемент есть, но скрыт (
display: none,visibility: hidden,opacity: 0) - Элемент есть и видим, но задизейблен
- Элемент перекрыт другим элементом (оверлей, модалка, тост-уведомление)
- Навигация по странице не завершилась до следующего действия
element is not visible, element is not stable, element is disabled). Это и есть реальная проблема.
Strict mode violation
Error: strict mode violation: getByRole('button', { name: 'OK' }) resolved to 2 elementsЛокатор совпал с несколькими элементами. Playwright требует уникальных локаторов, если не указано иное.
Как исправить: конкретизировать. Добавь контекстный фильтр:// Слишком широко
page.getByRole('button', { name: 'OK' })
// Конкретнее: ограничено модалкой
page.getByRole('dialog').getByRole('button', { name: 'OK' })
// Или nth если порядок стабилен
page.getByRole('button', { name: 'OK' }).first()Locator not found
Error: locator.fill: Error: strict mode violation: getByLabel('Email') resolved to 0 elementsНоль совпавших элементов. Либо локатор неверный, либо элемент ещё не появился, либо ты на другой странице.
Как исправить: проверь локатор. Открой тест с флагом--debug чтобы посмотреть состояние страницы:
npx playwright test --debug tests/login.spec.tsNavigation timeout
Error: page.goto: Timeout 30000ms exceeded.
call log:
navigating to "http://localhost:3000/login", waiting until "load"Страница не загрузилась до конца. Возможные причины: сервер медленный или не запущен, опция waitUntil слишком строгая ('networkidle' на странице с фоновым поллингом), или какой-то ресурс (картинка, скрипт) подвешивает событие load.
waitUntil: 'domcontentloaded' если не нужно ждать все ресурсы. Открой вкладку Network в Trace Viewer чтобы найти зависший ресурс.
Expect timeout
Error: expect(locator).toHaveText() with timeout 5000ms
Received string: ""
Expected string: "Order confirmed"Проверка не прошла за отведённый таймаут. Элемент есть, но содержит не тот текст (или ещё не загрузился).
Как исправить: контент реально грузится? Временно увеличь таймаут чтобы исключить проблему с тимингом:await expect(el).toHaveText('Order confirmed', { timeout: 15000 }). Если с большим таймаутом проходит: проблема с производительностью, а не с селектором.
Execution context destroyed
Error: Execution context was destroyed, most likely because of a navigation.Playwright держал ссылку на элемент страницы, когда страница перешла на другой URL. Частая причина: ты сделал const text = page.getByText('foo') а потом перешёл на другую страницу до того как обратился к этому элементу.
// Сломано: локатор вычисляется после навигации
const text = page.getByText('Confirmation');
await page.getByRole('button', { name: 'Submit' }).click(); // вызывает навигацию
await expect(text).toBeVisible(); // страница уже ушла
// Исправлено: проверяй до навигации или заново после
await page.getByRole('button', { name: 'Submit' }).click();
await expect(page).toHaveURL('/confirmation');
await expect(page.getByText('Order confirmed')).toBeVisible();Target closed
Error: Target page, context or browser has been closedКонтекст браузера закрылся до того как тест завершился. Обычно это происходит когда тест вручную закрывает контекст в неправильном месте, когда блок beforeAll закрыл контекст, который ещё нужен afterAll или тестам, или когда тест упал и код очистки запустился пока что-то ещё работало.
Network errors
Error: net::ERR_CONNECTION_REFUSED at http://localhost:3000Сервер не запущен. Проверь: запущен ли dev-сервер до старта тестов, правильный ли baseURL и правильный ли порт.
В CI добавь шаг запуска сервера перед Playwright или используй webServer в конфиге:
// playwright.config.ts
webServer: {
command: 'npm run start',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},Процесс отладки
Шаг 1: читай тип ошибки и call log. Call log показывает, что Playwright ждал и что наблюдал. Шаг 2: открой Trace Viewer.npx playwright show-trace test-results/.../trace.zip: снимок DOM показывает точно, что было на странице в момент падения.
Шаг 3: запусти в режиме отладки (--debug) чтобы пройти тест шаг за шагом интерактивно.
Шаг 4: используй page.pause() чтобы заморозить тест в конкретном месте и изучить вручную.
await page.goto('/checkout');
await page.pause(); // тест замирает здесь, открывается Inspector
await page.getByRole('button', { name: 'Pay' }).click();Inspector открывает браузер с тулбаром Playwright: можно взаимодействовать со страницей, запускать локаторные запросы и наблюдать действия в реальном времени.
→ See also: Playwright Trace Viewer: отлаживайте упавшие тесты как профессионал | Отладка нестабильных тестов: практическое руководство | Assertions в Playwright: полное руководство