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
from playwright.sync_api import sync_playwright

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.wait_for_selector("//p[contains(text(), 'Playwright')]", timeout=3000)
page.wait_for_selector("//li[1]", timeout=3000)
page.wait_for_selector("//p[text()='Содержание']", timeout=3000)

browser.close()
playwright.stop()

И мы упадём на первом же селекторе:

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
from playwright.sync_api import sync_playwright

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")

print(f"frames: {page.frames}")

element_1 = page.frame_locator(
    '//frame[@src="header.html"]'
    ).locator("//p[contains(text(), 'Playwright')]")

element_2 = page.frame_locator(
    '//frame[not(@index="1") and not(@index="2")]'
    ).locator("//li[1]")

print(f"element_1: {element_1.text_content()}")
print(f"element_2: {element_2.text_content()}")

browser.close()
playwright.stop()

Обработка диалоговых окон

Бывает, что при открытии или выполнении каких-либо действий на странице могут вылезти:

  • всякие pop-up-окна
  • JS-алерты
  • и прочие диалоги

Разбираться, как "победить" каждый из них придётся самостоятельно. Фреймворк Playwright предоставляет документацию по обработке таких событий как синхронным, так и асинхронным образом:

Также ничто не мешает найти JS-решение, которое можно применить через функцию evaluate.

Далее приведён пример, который:

  • создаёт диалоговое окно
  • закрывает его

Если в диалоговом окне имеется только 1 кнопка, то его можно закрыть как через accept, так и через dismiss. В противном случае:

  • accept => ok
  • dismiss => 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
import time

from playwright.sync_api import sync_playwright

playwright = sync_playwright().start()

browser = playwright.chromium.launch(headless=False)
page = browser.new_page()

page.goto("https://g-oak.ru/post/Windows|PowerShell|Active Directory|add_account_actions")

page.evaluate("window.open('https://g-oak.ru', '_blank').focus();")
page.evaluate("window.open('https://g-oak.ru/post/Linux|Права|theory_permissions', '_blank').focus();")

time.sleep(30)

browser.close()
playwright.stop()

Тем самым у нас поочердёно откроются три вкладки. Как и в случае с фреймами, чтобы совершить какую-либо проверку, нужно сначала переключиться на необходимую вкладку, в противном случае мы упадём:

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
from playwright.sync_api import sync_playwright

playwright = sync_playwright().start()

browser = playwright.chromium.launch(headless=False)
context = browser.new_context(ignore_https_errors=True)
page = context.new_page()

page.goto("https://g-oak.ru/post/Тестирование|Автоматизация|Python|Библиотеки и фреймворки|playwright|UI|01_Theory|02_pw_vs_page")

with context.expect_page() as my_tmp_tab:
    page.locator("//a[text()='этой']").click()

print(f"my_tmp_tab.value: {my_tmp_tab.value}")
new_page = my_tmp_tab.value
new_page.wait_for_load_state()

print(f"Tab: {new_page.title()}")
print(f"Page: {page.title()}")

browser.close()
playwright.stop()

Что здесь происходит:

  • доабвлен 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
from playwright.sync_api import sync_playwright

playwright = sync_playwright().start()

browser = playwright.chromium.launch(headless=False)
context = browser.new_context(ignore_https_errors=True)
page = context.new_page()

page.goto("https://g-oak.ru/post/Windows|PowerShell|Active Directory|add_account_actions")

with context.expect_page() as my_tab_1:
    page.evaluate("window.open('https://g-oak.ru', '_blank')")

page_tab_1 = my_tab_1.value
page_tab_1.wait_for_load_state()

with context.expect_page() as my_tab_2:
    page.evaluate("window.open('https://g-oak.ru/post/Linux|Права|theory_permissions', '_blank');")

page_tab_2 = my_tab_2.value
page_tab_2.wait_for_load_state()

print(f"Page: {page.title()}")
print(f"Tab 1: {page_tab_1.title()}")
print(f"Tab 2: {page_tab_2.title()}")

browser.close()
playwright.stop()

Результат будет:

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
import time
from playwright.sync_api import sync_playwright

playwright = sync_playwright().start()

browser = playwright.chromium.launch(headless=False)
context = browser.new_context(ignore_https_errors=True)
page = context.new_page()

page.goto("https://g-oak.ru/post/Windows|PowerShell|Active Directory|add_account_actions")

with context.expect_page() as my_browser_2:
    page.evaluate("window.open(document.URL, '_blank', 'location=yes,height=650,width=520,scrollbars=yes,status=yes');")

page_browser_2 = my_browser_2.value
page_browser_2.wait_for_load_state()

print(f"Browser 1: {page.title()}")
print(f"Browser 2: {page_browser_2.title()}")

time.sleep(30)

browser.close()
playwright.stop()

Теперь мы можем поочереёдно работать с элементами на страницах этих браузеров. Например:

 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
import time
from playwright.sync_api import sync_playwright

playwright = sync_playwright().start()

browser = playwright.chromium.launch(headless=False)
context = browser.new_context(ignore_https_errors=True)
page = context.new_page()

page.goto("https://g-oak.ru/")

with context.expect_page() as my_browser_2:
    page.evaluate("window.open('https://g-oak.ru/post/Windows|PowerShell|Active Directory|add_account_actions', '_blank', 'location=yes,height=650,width=520,scrollbars=yes,status=yes');")

page_browser_2 = my_browser_2.value
page_browser_2.wait_for_load_state()

print(f"Browser 1: {page.title()}")
print(f"Browser 2: {page_browser_2.title()}")
time.sleep(5)

page_browser_2.locator('//a[@href="/"]').click()
page.locator('//li[1]//label[text()="Читать полностью"]').click()

page_browser_2.wait_for_load_state()
page.wait_for_load_state()

print(f"Browser 1: {page.title()}")
print(f"Browser 2: {page_browser_2.title()}")

time.sleep(30)

browser.close()
playwright.stop()

Запуск второго браузера через 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
import time
from playwright.sync_api import sync_playwright

playwright = sync_playwright().start()

browser = playwright.chromium.launch(headless=False)
context = browser.new_context(ignore_https_errors=True)
page_1 = context.new_page()
page_2 = context.new_page()

page_1.goto("https://g-oak.ru/")
page_2.goto("https://g-oak.ru/post/Windows|PowerShell|Active Directory|add_account_actions")

page_1.wait_for_load_state()
page_2.wait_for_load_state()

print(f"Browser 1: {page_1.title()}")
print(f"Browser 2: {page_2.title()}")

page_1.locator('//li[1]//label[text()="Читать полностью"]').click()
page_2.locator('//a[@href="/"]').click()

page_1.wait_for_load_state()
page_2.wait_for_load_state()

page_1.screenshot(path='page_1.png')
page_2.screenshot(path='page_2.png')

print(f"Browser 1: {page_1.title()}")
print(f"Browser 2: {page_2.title()}")

time.sleep(3)

browser.close()
playwright.stop()

Несмотря на то, что по факту открылся один браузер с двумя вкладками, это никак не мешает нам взаимодейстовать с ними:

  • элементы ищутся на корректных страницах
  • все необходимые действия выполняются
  • скриншоты соответствуют ожиданиям (фотографируются отдельные вкладки, несмотря на то, что активированной остаётся только одна)

Запуск отдельного браузера

 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
import time
from playwright.sync_api import sync_playwright

playwright = sync_playwright().start()

browser_1 = playwright.chromium.launch(headless=False)
context_1 = browser_1.new_context(ignore_https_errors=True)
page_1 = context_1.new_page()

browser_2 = playwright.chromium.launch(headless=False)
context_2 = browser_2.new_context(ignore_https_errors=True)
page_2 = context_2.new_page()

page_1.goto("https://g-oak.ru/")
page_2.goto("https://g-oak.ru/post/Windows|PowerShell|Active Directory|add_account_actions")

page_1.wait_for_load_state()
page_2.wait_for_load_state()

print(f"Browser 1: {page_1.title()}")
print(f"Browser 2: {page_2.title()}")

page_1.locator('//li[1]//label[text()="Читать полностью"]').click()
page_2.locator('//a[@href="/"]').click()

page_1.wait_for_load_state()
page_2.wait_for_load_state()

page_1.screenshot(path='browser_1.png')
page_2.screenshot(path='browser_2.png')

print(f"Browser 1: {page_1.title()}")
print(f"Browser 2: {page_2.title()}")

time.sleep(3)

browser_1.close()
browser_2.close()
playwright.stop()

Асинхронное взаимодействие с браузером

Во всех вышеперечисленных примерах имеется один недостаток, а именно - все действия выполняются последовательно:

  • браузер_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
import asyncio
from playwright.async_api import async_playwright, Playwright

async def launch_test(playwright: Playwright):

    browser_1 = await playwright.chromium.launch(headless=False)
    context_1 = await browser_1.new_context(ignore_https_errors=True)
    page_1 = await context_1.new_page()

    browser_2 = await playwright.chromium.launch(headless=False)
    context_2 = await browser_2.new_context(ignore_https_errors=True)
    page_2 = await context_2.new_page()


    await page_1.goto("https://g-oak.ru/")
    await page_2.goto("https://g-oak.ru/post/Windows|PowerShell|Active Directory|add_account_actions")

    await page_1.wait_for_load_state()
    await page_2.wait_for_load_state()

    await page_1.locator('//li[1]//label[text()="Читать полностью"]').click()
    await page_2.locator('//a[@href="/"]').click()

    await page_1.wait_for_load_state()
    await page_2.wait_for_load_state()

    await page_1.screenshot(path='browser_1.png')
    await page_2.screenshot(path='browser_2.png')

    await browser_1.close()
    await browser_2.close()

async def main():
    async with async_playwright() as playwright:
        await launch_test(playwright)

asyncio.run((main()))

Работа с куки

  • Получить все файлы 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()