|
303 |
|
# UI | 02 | Playwright: взаимодействие со страницей
Открытие url-а
После того как бразуер настроен, первым делом мы грузим какой-либо веб-адрес. Для этого используется метод goto
:
# Переход по URL
page.goto('https://g-oak.ru')
Проверка загруженности страницы
Далее можно проверить, загрузилась страница или нет:
page.wait_for_load_state()
Подробнее данный метод описывался в статье.
Сравнение заглавия страницы
Также мы можем проверить title
и если он нас не устраивает, то выбросить ошибку или завершить выполнение программы:
if "Ожидаемое заглавие" not in page.title():
raise Exception("Некорректное заглавие страницы")
# Exception: Некорректное заглавие страницы
assert "Название старницы" == page.title()
# AssertionError
Обработка доп. диалогов страницы
Работа с фреймами
При тестирвоании страницы возможна ситуация, что элемент не находится, хотя мы видим, что он там есть. Это может произойти по разным причинам, но один из не самых очевидных случаев - это использование фреймов.
На этой странице имеются элементы:
//p[contains(text(), "Playwright")]
//li[1]
//p[text()="Содержание"]
Мы знаем, что эти элементы есть, попробуем проверить их наличие через Playwright
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
И мы упадём на первом же селекторе:
Traceback (most recent call last):
page.wait_for_selector("//p[contains(text(), 'Playwright')]", timeout=3000)
playwright._impl._errors.TimeoutError: Timeout 3000ms exceeded.
Такого элемента "нет на странице" => всё дело во фреймах. Чтобы к нему обратиться, нужно сначала определить фрейм, с которым мы работаем. По структуре DOM
-дерева видно, что он лежит во фрейме TOP
:
<frame src="header.html" name="TOP" scrolling="no" noresize="">
Именно поэтому перед обращением к элементу сначала требуется указать, какому фрейму он принадлежит:
page.frame(name='TOP').wait_for_selector("//p[contains(text(), 'Playwright')]", timeout=3000)
page.frame(name='MENU').wait_for_selector("//li[1]", timeout=3000)
page.frame(name='CONTENT').wait_for_selector("//p[text()='Содержание']", timeout=3000)
Теперь тест проходит успешно.
Также нам ничто не мешает сохранить каждый фрейм в переменную, чтобы с ними было удобнее работать:
page_top = page.frame(name='TOP')
top_page.wait_for_selector("//p[contains(text(), 'Playwright')]", timeout=3000)
page_menu = page.frame(name='MENU').wait_for_selector("//li[1]", timeout=3000)
page_menu.wait_for_selector("//li[1]", timeout=3000)
Обращение к фреймам по индексу
- получить список всех фреймов
# отобразить список всех фреймов
print(f"frames: {page.frames}")
page.frames[1].wait_for_selector("//p[contains(text(), 'Playwright')]", timeout=3000)
page.frames[2].wait_for_selector("//li[1]", timeout=3000)
page.frames[3].wait_for_selector("//p[text()='Содержание']", timeout=3000)
Обращение к фреймам по локатору
Если у фрейма есть какое-либо атрибуты, за которое можно зацепиться, то делается это через метод frame_locator()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
Обработка диалоговых окон
Бывает, что при открытии или выполнении каких-либо действий на странице могут вылезти:
- всякие
pop-up
-окна JS
-алерты- и прочие диалоги
Разбираться, как "победить" каждый из них придётся самостоятельно. Фреймворк Playwright
предоставляет документацию по обработке таких событий как синхронным, так и асинхронным образом:
- https://playwright.dev/.../class-dialog
- https://playwright.dev/.../class-download
- https://playwright.dev/.../class-filechooser
Также ничто не мешает найти JS
-решение, которое можно применить через функцию evaluate
.
Далее приведён пример, который:
- создаёт диалоговое окно
- закрывает его
Если в диалоговом окне имеется только 1 кнопка, то его можно закрыть как через accept
, так и через dismiss
. В противном случае:
accept
=> okdismiss
=> cancel
import time
from playwright.sync_api import sync_playwright
def handle_dialog(dialog):
print(dialog.message)
time.sleep(10)
dialog.dismiss()
#dialog.accept()
playwright = sync_playwright().start()
browser = playwright.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://g-oak.ru/static/examples/playwright-ui/frame.html")
page.frame(name='TOP').wait_for_selector(
"//p[contains(text(), 'Playwright')]",
timeout=3000)
page.frame(name='MENU').wait_for_selector(
"//li[1]",
timeout=3000)
page.frame(name='CONTENT').wait_for_selector(
"//p[text()='Содержание']",
timeout=3000)
page.on("dialog", handle_dialog)
print("page on")
time.sleep(10)
print("evaluate")
page.evaluate("alert('Такое вот сообщение')")
time.sleep(10)
browser.close()
playwright.stop()
Навигация
При работе с браузерами мы обычно прыгаем между вкладками, бывает жмём кнопки "назад" и "вперёд", иногда перезагружаем страницу. Именно эти примеры и показаны ниже:
- возврат на предыдущую страницу
page.goBack()
- Перезагрузка страницы
page.reload()
- возврат на следующую страницу
page.goForward()
Работа с несколькими вкладками
В ходе тестирования может случиться, что при нажатии на тот или иной элемент, может открыться как новая вкладка, так и новый браузер.
Симулируем поведение открытия вкладок:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Тем самым у нас поочердёно откроются три вкладки. Как и в случае с фреймами, чтобы совершить какую-либо проверку, нужно сначала переключиться на необходимую вкладку, в противном случае мы упадём:
print(f"Текущая вкладка: {page.title()}")
assert "Главная страница" in page.title()
Текущая вкладка: # PS-скрипт для работы с учётными записями AD
AssertionError
Несмотря на то, что открытой на уровне браузера является последняя вкладка, framework
считает, что находится на первой (о чём говорит Текущая вкладка: # PS-скрипт для работы с учётными записями AD
).
Именно поэтому - прежде чем производить какие-либо действия, сначала требуется перейти на необходимую вкладку.
К сожалению, простого варианта - как переключаться между вкладками на уровне фреймворка (как, напр., в Selenium
) - вроде как нет. Поэтому:
- при открытии новой вкладки используем особую логику (показано ниже)
- если есть возможность открываем в новом окне бразуера (описано в следующей главе)
Открытие вкладки в контексте
Вышерассмотренный пример не помогает в описании обходного решения (является громоздким), поэтому рассмотрим другой пример, отказавшись от JS
-cкриптов (в котором вкладка открывается в результате реального клика):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Что здесь происходит:
- доабвлен
context = browser.new_context
, чтобы была возможность обращаться к контексту браузера - внутри контекста нажимаем по требуемому элементу (который открывает новую вкладку)
- переменную
new_page
создаём для красоты / чтобы не путаться - обращаемся к результату работы контекста через
as
+ произвольное название и.value
(почему именно так и как оно работает => разбираемся самостоятельно с методомexpect_page
). Простое объяснение - именно такая комбинация позволяет нам работать с результатом контекста, как с обычной страницей (применять все привычные функции.title()
,click()
и пр.) - дожидаемся загрузки нашей новой страницы через
wait_for_load_state
- при необходимости закрываем вкладку
new_page.close()
Открытие вкладок через JS
Зная этот принцип, вернёмся к первому примеру. Открывая новые вкладки без контекста, мы теряем возможность взаимодействовать с ними, поэтому правильный подход будет:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Результат будет:
Page: # PS-скрипт для работы с учётными записями AD
Tab 1: Главная страница
Tab 2: # Права на файлы (теория)
Работа с несколькими браузерами
Во всех ранее рассматриваемых примерах нами всегда использовался только один браузер. Но ничто не мешает тестировать параллельно в нескольких браузерах.
Для этого можно:
- склонировать проекты с тестами и запускать их одновременно
- написать несколько отдельных, самостоятельных тестов
- написать тест так, чтобы внутри использовалось несколько браузеров
Вопрос лишь в том, а действительно ли нам это требуется и зачем. Самый очевидный вариант - для ускорения прохождения тестов.
Но прежде чем говорить об ускорении, рассмотрим несколько способов тестирования в нескольких браузерах.
Запуск второго браузера через JS
Используя ранее описанный подход открытия отдельной вкладки, а также запуск JS
-скриптов через evaluate
, мы можем открыть новый браузер следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Теперь мы можем поочереёдно работать с элементами на страницах этих браузеров. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
Запуск второго браузера через Playwright
Но нет никакой необходимости обращаться к JS
. Учитывая, что мы тестируем с помощью фреймворка, то есть смысл использовать его внутренние инструменты для этого.
Есть много разных способов, опишем самые очевидные.
Работа с вкладками вместо браузеров
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
Несмотря на то, что по факту открылся один браузер с двумя вкладками, это никак не мешает нам взаимодейстовать с ними:
- элементы ищутся на корректных страницах
- все необходимые действия выполняются
- скриншоты соответствуют ожиданиям (фотографируются отдельные вкладки, несмотря на то, что активированной остаётся только одна)
Запуск отдельного браузера
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
Асинхронное взаимодействие с браузером
Во всех вышеперечисленных примерах имеется один недостаток, а именно - все действия выполняются последовательно:
- браузер_1 начинает грузить сайт
- браузер_2 не будет грузить сайт, пока браузер_1 не выполнит это действие
- браузер_1 начнёт ожидать полную загрузку страницы, когда бразуер_2 откроет сайт
- и т. д.
Если тестирование - согласно своей логике - может осуществляться параллельно, то добиться такого поведения можно с помощью:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
Работа с куки
- Получить все файлы cookie
cookies = page.cookies()
- Установить cookie
page.set_cookie(name='example', value='test')
- Удалить файл cookie
page.delete_cookie(name='example')
Закрытие страницы
- закрыть вкладку
page.close()
- проверить, закрылась ли вкладка
page.is_closed()
Просмотр кода страницы
Получение всего содержимого html
-страницы.
page.content()