Всемирная Сеть является асинхронной по своей природе. Следовательно, все веб-элементы на страницах могут загружаться не одновременно, т. е. они могут загружаться динамически с помощью AJAX (Asynchronous JavaScript And XML).
При выполнении теста в Selenium ваш тестовый код может привести к ошибочным результатам, если он взаимодействует с веб-элементом, который еще не загружен в DOM. Или если он находится в другом iFrame, другой вкладке или в окне не в фокусе, или в других подобных ситуациях. Такие ошибки с фокусом достаточно часто случаются.
Selenium может управлять браузером, только когда его окно в фокусе. Как гарантировать, чтобы тестовый код взаимодействовал с веб-элементами? Существуют обходные пути, например добавлять минимальную задержку (в секундах), чтобы убедиться, что элемент загрузился. Но это решение ненадежное, ведь малейшее изменение дизайна страницы может сделать тест непригодным.
iFrames
Вы наверняка видели сайты с рекламой или дизайном, где HTML-документ встроен в другой HTML-документ. Такой веб-элемент называется iFrame. iFrame задается тегом < iframe >< /iframe >
. Для переключения между несколькими iFrame можно использовать API switch_to(self, frame-reference)
.
Для демонстрации у нас страница, которая содержит три iFrame. Ниже приведен HTML-код страницы, на которой отображается только информация о iFrames.
iFrame code of the web page https://seleniumhq.github.io/selenium/docs/api/java/index.html <frameset cols="20%,80%" title="Documentation frame" onload="top.loadFrames()"> <frameset rows="30%,70%" title="Left frames" onload="top.loadFrames()"> <frame src="overview-frame.html" name="packageListFrame" title="All Packages"> <frame src="allclasses-frame.html" name="packageFrame" title="All classes and interfaces (except non-static nested types)"> </frameset> <frame src="overview-summary.html" name="classFrame" title="Package, class and interface descriptions" scrolling="yes"> <noframes>
Как показано в HTML-коде выше, имена трех фреймов — packageListFrame
, packageFrame
и classFrame
. Чтобы лучше понять эту проблему, рассмотрим пример. Мы попытаемся нажать на «com.thoughtworks.selenium
«, присутствующий в «classFrame
«.
# Example - Show Selenium lose focus issue with iFrames from selenium import webdriver from selenium.webdriver.chrome.options import Options import time from time import sleep from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from selenium.common.exceptions import TimeoutException from selenium.webdriver.support.ui import WebDriverWait driver = webdriver.Chrome() driver.get("https://seleniumhq.github.io/selenium/docs/api/java/index.html") # iFrame code of the web page https://seleniumhq.github.io/selenium/docs/api/java/index.html #<frameset cols="20%,80%" title="Documentation frame" onload="top.loadFrames()"> #<frameset rows="30%,70%" title="Left frames" onload="top.loadFrames()"> #<frame src="overview-frame.html" name="packageListFrame" title="All Packages"> #<frame src="allclasses-frame.html" name="packageFrame" title="All classes and interfaces (except non-static nested types)"> #</frameset> #<frame src="overview-summary.html" name="classFrame" title="Package, class and interface descriptions" scrolling="yes"> #<noframes> driver.maximize_window() print("Window is maximized") # Test 1 print("[1] - Test on classFrame") driver.find_element_by_link_text("com.thoughtworks.selenium").click() driver.implicitly_wait(20) print("Completion of tests") # Free up the resources driver.close() driver.quit()
При выполнении этого теста возникает следующая ошибка:
Эта ошибка возникает из-за того, что экземпляр WebDriver не может сфокусироваться на iFrame «classFrame
«, в котором присутствует элемент com.thoughtworks.selenium
. Аналогично во многих подобных сценариях, когда фокус экземпляра находится на каком-то другом iFrame, а требуемый элемент — на другом.
Решение
Существует несколько способов перемещения между iFrame на странице. Один из самых простых и популярных — использование метода switch_to().frame()
.
Имя iFrame — Каждый iFrame имеет имя, связанное с ним. Вы можете найти это имя с помощью инструмента Inspect Tool в веб-браузере. Имя iFrame, на который вы хотите переключиться, можно передать API driver.switch_to.frame('frame_name')
. В приведенном выше сценарии, если вам нужно перейти на iFrame classFrame
, используем driver.switch_to.frame("classFrame")
.
Если фокус уже находится на другом iFrame, необходимо переключиться обратно на родительский фрейм с помощью следующих API:
driver.switch_to.default_content() driver.switch_to.parent_frame()
ID iFrame — Вы также можете переключаться между iFrame, используя id
, связанный с iFrame. driver.find_element_by_id('frame-id')
, где frame-id
используется в качестве локатора для перехода к нужному iFrame.
Комбинация имени тега и индекса — Можно использовать API driver.find_elements_by_tag_name()
вместе с индексом нужного iFrame.
Iframe-name = driver.find_elements_by_tag_name('tag-name')[index] driver.switch_to.frame(iframe-name)
Рассмотрим пример переключения между тремя iFrames на странице.
# Example 1 - Switching between iFrames from selenium import webdriver from selenium.webdriver.chrome.options import Options import time from time import sleep from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from selenium.common.exceptions import TimeoutException from selenium.webdriver.support.ui import WebDriverWait driver = webdriver.Chrome() driver.get("https://seleniumhq.github.io/selenium/docs/api/java/index.html") # iFrame code of the web page https://seleniumhq.github.io/selenium/docs/api/java/index.html #<frameset cols="20%,80%" title="Documentation frame" onload="top.loadFrames()"> #<frameset rows="30%,70%" title="Left frames" onload="top.loadFrames()"> #<frame src="overview-frame.html" name="packageListFrame" title="All Packages"> #<frame src="allclasses-frame.html" name="packageFrame" title="All classes and interfaces (except non-static nested types)"> #</frameset> #<frame src="overview-summary.html" name="classFrame" title="Package, class and interface descriptions" scrolling="yes"> #<noframes> driver.maximize_window() print("Window is maximized") # Test 1 print("[1] - Test on classFrame") driver.switch_to.frame("classFrame") driver.find_element_by_link_text("com.thoughtworks.selenium").click() driver.implicitly_wait(20) # Switch to the parent frame driver.switch_to.default_content() driver.switch_to.parent_frame() print("[2] - Test on packageFrame") driver.switch_to.frame("packageFrame") driver.find_element_by_link_text("WebDriver").click() driver.implicitly_wait(20) # Switch to the parent frame driver.switch_to.default_content() driver.switch_to.parent_frame() print("[3] - Switching to the PackageListFrame") driver.switch_to.frame("packageListFrame") driver.find_element_by_link_text("org.openqa.selenium.support.pagefactory").click() driver.implicitly_wait(20) driver.switch_to.default_content() driver.switch_to.parent_frame() time.sleep(20) print("Completion of tests") # Free up the resources driver.close() driver.quit()
API driver.switch_to.frame()
используется с frame-name
в качестве параметра для переключения между различными iFrame. Можно использовать HTML-код страницы для получения информации, связанной с iFrame, или навести курсор на указанный iFrame и проинспектировать элемент с помощью опции Inspect в браузере.
Оказавшись в нужном iFrame, найдите ссылку с помощью функции find_element_by_link_text()
, и операция клика будет выполнена. Между различными операциями, выполняемыми на двух iFrame, добавлено неявное ожидание в 20 секунд (driver.implicitly_wait(20))
, чтобы было достаточно времени для переключения на родительский фрейм. Вам нужно провести несколько пробных запусков, чтобы определить нужное время ожидания; однако необходимо следить за тем, чтобы время ожидания не было слишком большим, иначе это приведет к задержке выполнения других тестов.
Несколько вкладок
У вас могут быть ситуации, когда тесты надо выполнять в разных окнах браузера. В таких случаях необходимо открыть несколько окон или несколько вкладок и выполнить тестовые операции в одном браузере. Это также применимо к параллельным тестам, где чаще всего возникают проблемы с фокусом.
В приведенном ниже примере мы попытаемся открыть несколько вкладок (две) и запустить несколько тестов:
# Example - Selenium lose focus issue with multiple tabs from selenium import webdriver from selenium.webdriver.chrome.options import Options import time from time import sleep from selenium.common.exceptions import TimeoutException from selenium.webdriver.support.ui import WebDriverWait primary_url = 'https://www.duckduckgo.com' secondary_url = 'https://www.lambdatest.com' driver = webdriver.Chrome() driver.get(primary_url) print("[1] - Current Page Title is : %s" %driver.title) # Add sleep for checking the results time.sleep(10) driver.execute_script("window.open('');") driver.get(secondary_url) print("[2] - Current Page Title is : %s" %driver.title) # Let us perform some test on the DuckDuckGo tab, i.e. Tab 1 print("Performing operations on the first tab now") # Clicking on "Add DuckDuckGo to Chrome" button self.driver.find_element_by_xpath(self,'//*[@id="content_homepage"]/div[2]/div/div/div/span').click() print("DuckDuckGo added to Chrome") # Free the resources driver.close() driver.quit()
При выполнении этого теста мы получим ошибку:
Она возникает из-за того, что экземпляр WebDriver не сфокусировался на первой вкладке, когда мы попытались выполнить на ней тесты. Вместо этого фокус был на второй вкладке — потому что мы создали новую вкладку. Такое часто случается.
Решение
С каждым окном связан оконный хэндл, который представляет собой уникальный номер. Например если одновременно открыты два окна или две вкладки браузера, то хэндл окна первого будет равен ‘0’, а другого — ‘1’. Хэндл окна будет освобожден после закрытия этого окна или вкладки. Для переключения вкладок мы используем API driver.window_handles[handle-number]
. Например, чтобы переключиться на окно с хэндлом ‘1’, используем driver.switch_to.window(driver.window_handles[1])
.
Чтобы открыть новую вкладку, то есть дочернее окно из родительского окна, используем driver.execute_script("window.open(");")
. Чтобы открыть новое окно браузера, используем driver.execute_script("window.open('secondary-url', 'new window')")
# Switching between Tabs using Python and Selenium from selenium import webdriver from selenium.webdriver.chrome.options import Options import time from time import sleep from selenium.common.exceptions import TimeoutException from selenium.webdriver.support.ui import WebDriverWait primary_url = 'https://www.duckduckgo.com' secondary_url = 'https://www.lambdatest.com' driver = webdriver.Chrome() driver.get(primary_url) print("[1] - Current Page Title is : %s" %driver.title) print("Perform the operations for the first window here") # Add sleep for checking the results time.sleep(10) driver.execute_script("window.open('');") driver.switch_to.window(driver.window_handles[1]) driver.get(secondary_url) print("[2] - Current Page Title is : %s" %driver.title) print("Perform the operations for the second tab here") time.sleep(10) driver.close() driver.switch_to.window(driver.window_handles[0]) print("[3] - Current Page Title is : %s" %driver.title) # Free the resources driver.close() driver.quit()
Как видно на скриншоте ниже, сначала открывается родительское окно с тестовым URL DuckDuckGo. Хендл окна этого браузера имеет значение ‘0’. После 10 секунд ожидания открывается новая вкладка с тестовым URL LambdaTest, и хэндл окна, связанного с этой вкладкой, равен ‘1’. driver.switch_to.window(driver.window_handles[0])
используется для переключения обратно на родительское окно (т. е. хэндл окна — 0).
Проблемы с onfocus() и onblur()
Существует множество сайтов, на которых вам приходилось валидировать формы. Например вы тестируете сайт, на котором нужно создать имя пользователя в соответствии с заданными критериями. Но как только вы вводите имя пользователя и выходите из текстового поля, появляется предупреждение о том, что введенное имя пользователя неверно, а также показаны правила ввода имени пользователя. Эти всплывающие окна или предупреждения о том, что введенный текст не соответствует требованиям, или поле осталось пустым, и т. д. создаются с помощью методов onfocus()
и onblur()
. События onfocus()
и onblur()
используются в большинстве веб-сайтов и веб-приложений.
Событие onfocus()
срабатывает, когда веб-элемент получает фокус. Веб-элементом может быть гиперссылка (< a >), элемент ввода (< input >), элемент выбора (< select >) и т. д. Например, в приведенном ниже коде цвет окна ввода меняется, когда оно получает фокус.
<!DOCTYPE html> <html> <body> Email: <input type="text" onfocus="lambdaTestFunction(this)"> <p>The above text box gets colored as soon as its gets focus, with help of onfocus() event.</p> <script> function lambdaTestFunction(x) { x.style.background = "aqua"; } </script> </body> </html>
Вывод:
После срабатывания события onfocus
:
Событие onblur()
срабатывает, когда фокус смещается с веб-элемента без изменения его значения. Например, пользователь оставляет поле user-name
пустым, а фокус переключается на следующее поле. В приведенном ниже примере событие onblur
преобразует все символы поля ввода в строчные, как только оно теряет фокус.
<!DOCTYPE html> <html> <body> Email: <input type="text" onblur="LTFunction(this)"> <p>The entered input gets converted to lowercase as soon as the input box loses focus, with help of onblur() event.</p> <script> function LTFunction(x) { x.value = x.value.toLowerCase(); } </script> </body> </html>
Вывод:
После срабатывания:
Когда эти события запускаются одновременно во время тестирования в Selenium, могут возникнуть ситуации, когда то или иное событие коллбека не происходит или не вызывается. Одним из таких сценариев является выпадающий список. Предположим, есть выпадающий список, значение которого вы хотите выбрать с помощью Selenium, причем значение выпадающего списка меняется при срабатывании события onblur()
. В таком случае экземпляр Selenium WebDriver может не успеть вызвать событие onblur()
, что приведет к потере фокуса.
Решение
В примере ниже демонстрация событий onfocus()
и onblur()
на URL https://output.jsbin.com/remozek/1. Метод execute_script()
вызывается экземпляром WebDriver для запуска необходимых событий. driver.execute_script("arguments[0].blur();", textfield_path)
имеет arguments[xx]
в качестве параметра, который передается из Python-кода в JavaScript-код.
# Example 3 - Demonstration of focus(), blur(), and send_keys() from selenium import webdriver from selenium.webdriver.chrome.options import Options import time from time import sleep from selenium.common.exceptions import TimeoutException from selenium.webdriver.support.ui import WebDriverWait driver = webdriver.Chrome() driver.maximize_window() driver.get('https://output.jsbin.com/remozek/1') textfield_path = driver.find_element_by_xpath("//*[@id='textField']") print("Focus Event Triggered") driver.execute_script("arguments[0].focus();", textfield_path) time.sleep(5) print("Blur event triggered") driver.execute_script("arguments[0].blur();", textfield_path) time.sleep(5) driver.find_element_by_id("textField").clear() time.sleep(5) driver.find_element_by_id("textField").send_keys("Lambdatest") time.sleep(5) driver.find_element_by_id("plainButton").click() time.sleep(5) print("Button is clicked") time.sleep(5) # Clear the resources driver.close() driver.quit()
Как показано выше, элемент textbox
находим с помощью XPath-выражения, где в качестве входного аргумента передается id
веб-элемента.
textfield_path = driver.find_element_by_xpath("//*[@id='textField']")
Используется Inspect Element для получения информации об идентификаторе веб-элемента, то есть текстового поля и JavaScript-кода, который должен быть выполнен при наступлении событий onfocus()
и onblur()
.
Код выполняется с помощью команды python < file-name.py >
, как показано на скриншоте; события onfocus()
и onblur()
вызываются с помощью JavaScript-кода, запускаемого через Selenium Webdriver.
Неполная загрузка страницы
Как упоминалось в начале, большинство проблем с фокусом возникает, когда операция выполняется над веб-элементом, который еще не загружен, или загрузка веб-страницы еще не завершена. Сейчас мы рассмотрим два способа решения этих проблем:
- Дождаться загрузки нужного элемента, используя
presence_of_element_located()
- Найти веб-элемент, проинспектировав HTML Source, используя
elem.get_attribute()
В вышеупомянутых случаях может быть использован WebDriverWait()
с заданным интервалом для проверки присутствия веб-элемента на странице. Если требуемый элемент отсутствует, вызывается TimeoutException
и выполняется следующий набор инструкций.
Для демонстрации используется фреймворк unittest. Чтобы получить подробную информацию о веб-локаторе, т. е. XPath, CSS Selector, Name, Link Text, ID и т. д., мы снова используем опцию Inspect Element в веб-браузере. В примере ниже мы использовали Selenium-локаторы NAME и XPath для выполнения действий над этими веб-элементами.
from selenium import webdriver from selenium.webdriver.chrome.options import Options from time import sleep from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from selenium.common.exceptions import TimeoutException from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.edge.options import Options driver = webdriver.Chrome() driver.maximize_window() driver.get("https://www.lambdatest.com") try: myElem_1 = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CLASS_NAME, 'home-btn'))) print("Home Button click under progress") myElem_1.click() myElem_2 = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.XPATH, "/html/body/div[1]/section/div/div[2]/div/form/div[3]/p/a"))) print("Login button click under progress") myElem_2.click() sleep(10) except TimeoutException: print("No element found") sleep(10) driver.close()
Как видим, для двух веб-элементов используется ожидание WebDriverWait()
длительностью 10 секунд. Как только веб-элементы загружаются, с ними выполняются необходимые операции. Однако по истечении 10 секунд возникает TimeOutException()
и выводится сообщение об ошибке No element found
. Скриншот окна выполнения:
Помимо этих, существуют и другие способы решения проблем фокусом. Один из них — инспекция HTML-кода и проверка наличия элемента на странице.
Решения
Помимо использования presence_of_element_located()
для проверки наличия веб-элементов, существует другой подход, который заключается в проверке HTML-кода на наличие требуемого веб-элемента. Чтобы получить HTML-код страницы, используется elem.get_attribute("innerHTML")
, и выполняется поиск требуемого веб-элемента.
В примере ниже HTML-код тестируемого URL записывается во внешний файл (необязательный шаг), и выполняется поиск кнопки home-btn
. Он соответствует строке в HTML-источнике; поэтому операция поиска может занять некоторое время, если исходный код очень большой.
from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.keys import Keys from time import sleep import io driver = webdriver.Chrome() driver.get("https://www.lambdatest.com") elem = driver.find_element_by_xpath("//*") source_code = elem.get_attribute("innerHTML") # Optional - Write the code in an external file on the disk filename = open('lambdatest_page_source.html', 'w') filename.write(source_code) filename.close() # Search for a particular element in the source code if "home-btn" in source_code: print("Home Button is present") else: print("Home button is not present") sleep(5) driver.close()