Переходы между страницами, вкладки, фреймы, auto-waiting, явные ожидания
Надёжная навигация и правильные ожидания — фундамент стабильных тестов. Playwright берёт на себя большую часть работы через auto-waiting, но некоторые сценарии требуют ручного управления.
page.goto() — основной метод перехода на страницу:
# Переход по URL
await page.goto('https://example.com')
# Относительный URL (если задан base_url в конфиге)
await page.goto('/dashboard')
# С настройкой таймаута
await page.goto('/reports', timeout=60000)goto() автоматически ждёт завершения загрузки страницы. По умолчанию — до состояния load (полная загрузка HTML и ресурсов).
Playwright поддерживает три состояния:
# DOM распарсен — HTML загружен, скрипты выполняются
await page.goto('/page', wait_until='domcontentloaded')
# Полная загрузка — HTML + CSS + JS + изображения
await page.goto('/page', wait_until='load')
# Сеть в покое — нет активных запросов 500мс
await page.goto('/page', wait_until='networkidle')| Состояние | Когда использовать |
|---|---|
domcontentloaded | Быстрые тесты, когда не нужны стили/изображения |
load (по умолчанию) | Стандартные тесты |
networkidle | SPA с асинхронной подгрузкой данных |
Важно:
networkidleможет замедлить тесты, если страница постоянно опрашивает сервер (polling). В таких случаях используйтеdomcontentloaded+ явное ожидание нужного элемента.
Playwright автоматически ждёт, прежде чем выполнить действие. Перед кликом он проверяет:
display: none или visibility: hidden — ждёт)disabled)# Playwright ждёт, пока кнопка станет кликабельной
await page.get_by_role('button', name='Загрузить').click()Это означает: вам не нужен time.sleep() в 99% случаев.
Когда auto-waiting недостаточен (например, нужно дождаться появления элемента после AJAX), используйте expect:
from playwright.sync_api import expect
# Ждём появления элемента (до 5 секунд по умолчанию)
expect(page.locator('#result')).to_be_visible()
# Ждём конкретного текста
expect(page.get_by_role('heading')).to_have_text('Готово!')
# Ждём исчезновения элемента
expect(page.locator('.spinner')).to_be_hidden()
# Кастомный таймаут
expect(page.locator('#data')).to_be_visible(timeout=15000)expect — это polling: Playwright проверяет условие каждые 100мс до наступления таймаута.
Когда действие вызывает API-запрос, дождитесь именно его:
# Ожидание ответа от API
with page.expect_response('**/api/users') as response_info:
await page.get_by_role('button', name='Загрузить').click()
response = response_info.value
assert response.status == 200
body = response.json()
assert len(body['users']) > 0Глоб-паттерн ** совпадает с любым количеством символов. Можно использовать функцию для сложной фильтрации:
def is_user_response(response):
return '/api/users' in response.url and response.status == 200
with page.expect_response(is_user_response) as info:
await page.get_by_role('button', name='Загрузить').click()Если клик вызывает переход на другую страницу:
with page.expect_navigation():
await page.get_by_role('link', name='Профиль').click()
assert '/profile' in page.urlМожно ждать конкретный URL:
with page.expect_navigation(url='**/dashboard'):
await page.get_by_role('button', name='Войти').click()Важно: если переход происходит не сразу (redirect chain),
expect_navigation()дождётся финального URL.
Новая вкладка открывается через expect_popup():
with page.expect_popup() as popup_info:
await page.get_by_role('link', name='Открыть').click()
new_page = popup_info.value
assert 'report' in new_page.url
# Работа с новой вкладкой
await new_page.wait_for_load_state()
title = await new_page.title()expect_popup() перехватывает и target='_blank', и window.open().
Закрытие вкладки и возврат:
await new_page.close()
# page — исходная вкладка, всё ещё активнаСписок всех открытых вкладок:
pages = page.context.pages
print(f'Открыто вкладок: {len(pages)}')# Получить фрейм по имени
frame = page.frame(name='payment-frame')
# Получить фрейм по URL
frame = page.frame(url='**/payment-provider.com')
# Взаимодействие с фреймом
await frame.get_by_label('Номер карты').fill('4242 4242 4242 4242')
await frame.get_by_role('button', name='Оплатить').click()Если фрейм динамически загружается:
# Ждём появления фрейма
frame = page.wait_for_event('frameattached')
await frame.get_by_label('Карта').fill('4242')# Назад
await page.go_back()
# Вперёд
await page.go_forward()
# Текущий URL
print(page.url)
# Обновить страницу
await page.reload()❌ time.sleep() вместо expect. sleep(2) — это гадание на кофейной гуще. Может, элемент появится через 1 секунду, а может через 5. expect ждёт ровно столько, сколько нужно.
❌ networkidle для страниц с polling. Если приложение опрашивает сервер каждые 2 секунды, networkidle никогда не наступит. Используйте domcontentloaded + ожидание конкретного элемента.
❌ Доступ к context.pages[-1] без ожидания. Новая вкладка может ещё не открыться — это race condition. Всегда используйте expect_popup().
✅ Правильный подход: доверяйте auto-waiting, используйте expect для AJAX-контента, expect_response для API, expect_popup для вкладок.
/dashboard, ждёт загрузки данных через API (перехватите ответ /api/dashboard-data) и проверяет наличие заголовка.target='_blank', переключитесь на новую вкладку, проверите её URL и закроете её.<iframe> на странице, заполните форму внутри него и вернитесь к основной странице.wait_until='domcontentloaded' и wait_until='networkidle' на SPA-странице с медленным API.Изучите фикстуры и параметризацию, чтобы научиться организовывать тесты с помощью pytest.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.