🐍 Создайте автотест Web UI на Python и Selenium за 7 шагов: инструкция для новичков
Если вам нужно тестировать веб-интерфейсы и логику отображения графических блоков на странице или просто автоматизировать работу в браузере, эта статья для вас. Читайте инструкцию по созданию автотеста на Python и Selenium за 7 простых шагов. Мы будем использовать Selenium совместно с Python версий 3.x.x. Цель статьи – не дать фундаментальные знания по теории программирования и написания автотестов, а заинтересовать в этой области и показать, как они пишутся в целом. Для начала работы нам потребуется установить Python на рабочую машину. Переходим на официальный сайт Python и качаем установщик для вашей ОС (мы будем использовать Windows). В процессе инсталляции поставьте галочки на добавлении компонентов в системные переменные PATH. Дождитесь завершения процесса, и если программа попросит перезагрузки, перезагрузитесь. Если у вас Linux, интерпретатор может уже присутствовать в системе, в противном случае стоит установить его из репозитория пакетов вашего дистрибутива. Проверьте корректность установки, перейдите в терминал (в Windows нажмите Рис. 1. Должна быть выведена версия, а если что-то не получилось, проверьте выполнение по шагам и повторите попытку Далее нам понадобится сам Selenium: Дождитесь завершения установки. Поскольку мы будем писать тест, воспользуемся популярной библиотекой pytest. Устанавливается она аналогично: Для создания приложений нужна интегрированная среда разработки или IDE (integrated development environment), но можно писать код и в обычном текстовом редакторе. Я выбрал самую популярную и удобную среду PyCharm от компании JetBrains. Чтобы работать с браузером, помимо Selenium потребуется веб-драйвер: в нашем случае ChromeDriver – по сути это связующее звено в цепочке. Обратите внимание, что версия драйвера должна соответствовать версии браузера и вперед – к созданию проекта и написанию первого скрипта. Все компоненты готовы, давайте создадим новый проект. Для этого запускаем PyCharm и в открывшимся окне выбираем Рис. 2 Указываем имя проекта и нажимаем Рис. 3 Напишем первый тест, чтобы проверить работоспособность драйвера. Рис. 4. Пример кода в файле main.py В качестве примера ресурса для тестирования возьмем популярный сайт для практики автоматизированного тестирования: https://www.saucedemo.com. Кейс: main.py После ввода кода необходимо установить библиотеку Selenium в наш проект. Для этого нажмите на подсвеченный текст в редакторе, нажмите Рис. 5. Пример установки пакета в проект Запустить сценарий можно во встроенном эмуляторе терминала IDE или в любом другом: Рис. 6. Пример запуска скрипта из IDE Если все установлено правильно, должен запуститься браузер, который откроет страницу. Результатом запуска нашего сценария на Python, будет сообщение: “Элемент найден”. Рис. 7. Результат выполнения скрипта. В нашем скрипте присутствует следующая строка: Метод Теперь давайте напишем кейс аутентификации пользователя на странице входа: main.py Разберем пример пошагово: Рис 8. Поиск xpath элемента в инструментах разработчика Рис 9. Копирование пути xpath Если кратко рассматривать путь, то main.py При выполнении скрипта получили следующий результат: Рис 10. Результат выполнения скрипта Кейс: main.py Из нового тут добавился только метод После прохождения всех шагов в консоль выводится результат, что в корзине имеется товар. В предыдущем кейсе с большой вероятностью можно столкнуться с проблемой, когда при нажатии на позицию мы сразу кликаем на корзину. При медленном интернете или долгом ответе от сервера в корзину может ничего не добавиться. Для таких случаев предусмотрены ожидания. Selenium driver поддерживает два вида ожиданий: явное (explicit) и неявное (implicity). Для явных ожиданий есть специальные методы, которые помогут рационально использовать время выполнения теста: например, можно установить минимальное время ожидания и возвращать элемент, если он прогрузился раньше предполагаемого времени. Пример явного ожидания: Процесс ждет 10 секунд пока элемент станет доступным, чтобы по нему можно было кликнуть. Если элемент так и не прогрузился и недоступен для клика, генерируется исключение Неявные ожидания в свою очередь устанавливаются один раз для драйвера, а не для каждого элемента. Включается неявное ожидание, когда Selenium не может найти элемент: он ждет установленное время и если не дождется, тоже возвращает Пример неявного ожидания: Ожидать действия можно и с помощью Чтобы pytest понял, что перед ним именно тестовая, а не обычная функция, сама тестовая функция должна начинаться с Обновим наш тест, добавим необходимые ожидания для стабильности тестовых функций. Также я вынес отдельную функцию под ожидания, куда мы просто передаем main.py Для запуска теста с помощью pytest в терминале введите Мы плавно перешли к заключительному этапу написания теста – проверке вывода по известному ответу. Хотя тест выполняется успешно, он ничего не проверяет и является бессмысленным. Будем использовать стандартные инструкции Добавим в тест проверки. Будем проверять, что название куртки «Sauce Labs Fleece Jacket» и описание как в магазине. main.py Теперь при расхождении результата и ожидаемого условия будет возвращена ошибка прохождения. Укажем название куртки «Sauce Labs Fleece Jacket1». Результат выполнения скрипта будет следующим: Рис 11. Результат выполнения теста. Теперь причешем код, распределив логику по методам, как, например, было с main.py В этом примере логика поделилась на функциональные компоненты (фикстуры). Теперь аутентификация пользователя будет происходить в отдельной функции, что позволит не производить одни и те же действия и дублировать код при каждом тесте, в котором нужно проходить аутентификацию. В качестве примера использования фикстур сделана инициализация драйвера, что позволит использовать инстанс драйвера во всех тестах, не пересоздавая его. Логика добавления товара в корзину тоже была вынесена в отдельный функциональный компонент для дальнейшего использования. Суть разнесения логики заключается в принципе конструктора: собирать тесты из отдельных частей, подставляя только необходимые данные при переиспользовании функций в разных тестах. При желании можно и дальше проводить рефакторинг кода. На этом создание первого автотеста закончено. Некоторые моменты были рассмотрены поверхностно, что дает возможность пытливым умам поглотить информацию из других источников. Удачи!1. Установка необходимых компонентов
Win+R
и запустите cmd или Alt+Ctrl+T
в графической среде Linux). Выполните следующую команду:
python --version
pip install selenium
pip install pytest
2. Первый скрипт с использованием драйвера
New Project
.Create
.
from selenium import webdriver options = webdriver.ChromeOptions() options.add_experimental_option("excludeSwitches", ["enable-logging"]) driver = webdriver.Chrome(options=options, executable_path=r'C:/Users/.../.../chromedriver.exe') driver.get("https://www.saucedemo.com/") input_username = driver.find_element_by_id("user-name") if input_username is None: print("Элемент не найден") else: print("Элемент найден")
Alt + Enter
и далее выберите Install package selenium
. Это нужно делать для каждого неустановленного пакета.
python main.py
3. Поиск элементов
input_username = driver.find_element_by_id("user-name")
find_element_by_id
позволяет процессу найти элемент в разметке HTML по наименованию атрибута id
. В реализации драйвера есть несколько способов поиска элементов на странице: по name
, xpath
, css
, id
. Поиск по css
и xpath
являются более универсальным, но он сложнее для начинающих. Использование поиска по name
и id
намного удобнее, но в практической разработке используется редко. Далее я буду использовать только xpath
. username
и password
.
import time from selenium import webdriver from selenium.webdriver.common.keys import Keys def first_test(): options = webdriver.ChromeOptions() options.add_experimental_option("excludeSwitches", ["enable-logging"]) driver = webdriver.Chrome(options=options, executable_path=r'C:/Users/.../.../chromedriver.exe') driver.get("https://www.saucedemo.com/") # Поиск элементов и присваивание к переменным. input_username = driver.find_element_by_xpath("//*[@id="user-name"]") input_password = driver.find_element_by_xpath("//*[@id="password"]") login_button = driver.find_element_by_xpath("//*[@id="login-button"]") # Действия с формами input_username.send_keys("standard_user") input_password.send_keys("secret_sauce") login_button.send_keys(Keys.RETURN) # Поиск и проверка попадания на главную страницу title_text = driver.find_element_by_xpath("//*[@id="header_container"]/div[2]/span") if title_text.text == "PRODUCTS": print("Мы попали на главную страницу") else: print("Ошибка поиска элемента") time.sleep(5) if __name__ == '__main__': first_test()
input_username
, input_password
и login_button
с помощью xpath
.send_keys
с данными, которые хотим передать в текстовое поле. В нашем случае в username
отправляем «standart_user», в password
– «secret_sauce». Проецируя поведение пользователя нажимаем Enter
для ввода данных, используя метод send_keys
для найденной кнопки с переданным аргументом Keys.RETURN
. Этот аргумент позволяет работать с действиями клавиатуры в Selenium, аналогично нажатию на Enter
на клавиатуре.Products
. Как я говорил раннее, не всегда есть возможность найти элемент по id
– здесь как раз тот случай.
title_text = driver.find_element_by_xpath("//*[@id="header_container"]/div[2]/span")
xpath
до элемента: //*[@id="header_container"]/div[2]/span
.xpath
, зайдите на https://www.saucedemo.com и нажмите F12, чтобы открыть инструменты разработчика. Затем выберите стрелку-указатель и кликните по элементу до которого хотите найти путь. В нашем случае до Products
.xpath
. //*
обозначает, что будут найдены все элементы на странице, а [@id="header_container"]
обозначает условие поиска (будут найдены все элементы на странице с тэгом id = "header_container"
).И далее /div[2]/span
– спускаемся на второй дочерний элемент div
и далее на дочерний элемент span
. Сравните полученный xpath
с деревом элемента в инструментах разработчика – сразу станет понятно что к чему.
if title_text.text == "PRODUCTS": print("Мы попали на главную страницу") else: print("Ошибка поиска элемента")
4. Первый тест с поиском и переходом по странице
import time from selenium import webdriver from selenium.webdriver.common.keys import Keys def first_test(): options = webdriver.ChromeOptions() options.add_experimental_option("excludeSwitches", ["enable-logging"]) driver = webdriver.Chrome(options=options, executable_path=r'C:/Users/…/…/chromedriver.exe') driver.get("https://www.saucedemo.com/") # Поиск элементов и присваивание к переменным. input_username = driver.find_element_by_xpath("//*[@id="user-name"]") input_password = driver.find_element_by_xpath("//*[@id="password"]") login_button = driver.find_element_by_xpath("//*[@id="login-button"]") # Действия с формами input_username.send_keys("standard_user") input_password.send_keys("secret_sauce") login_button.send_keys(Keys.RETURN) # Поиск ссылки элемента позиции магазина и клик по ссылке item_name = driver.find_element_by_xpath("//*[@id="item_5_title_link"]/div") item_name.click() # Поиск кнопки добавления товара и клик по этой кнопке item_add_button = driver.find_element_by_xpath("//*[@id="add-to-cart-sauce-labs-fleece-jacket"]") item_add_button.click() # Поиск кнопки коризины и клик по этой кнопке shopping_cart = driver.find_element_by_xpath("//*[@id="shopping_cart_container"]/a") shopping_cart.click() # Еще один поиск ссылки элемента позиции магазина item_name = driver.find_element_by_xpath("//*[@id="item_5_title_link"]/div") if item_name.text == "Sauce Labs Fleece Jacket": print("Товар пристутствует в корзине") else: print("Товар отсутствует") time.sleep(5) if __name__ == '__main__': first_test()
click()
, который просто кликает по найденному элементу.Ожидания в selenium: что нужно знать?
element = WebDriverWait(driver, 10).until( EC.element_to_be_clickable( (By.XPATH, '//*[@id="page_wrapper"]/footer/ul/li[2]/a') ) )
TimeoutException
.TimeoutException
. В отличии от явного ожидания, этот тип менее гибок и может оказать плохое влияние на общее время прогона тестов.
driver.implicitly_wait(10)
time.sleep(5)
. У нас в примерах есть использование этого метода, но оно считается плохой практикой и обычно применяется только для дебага.5. Рефакторинг теста, добавление ожиданий
test_
.xpath
и driver
в виде аргументов.
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from selenium.webdriver.support.wait import WebDriverWait import time from selenium import webdriver from selenium.webdriver.common.keys import Keys # Функция ожидания элементов def wait_of_element_located(xpath, driver): element = WebDriverWait(driver, 10).until( EC.presence_of_element_located( (By.XPATH, xpath) ) ) return element def test_add_jacket_to_the_shopcart(): options = webdriver.ChromeOptions() options.add_experimental_option("excludeSwitches", ["enable-logging"]) driver = webdriver.Chrome(options=options, executable_path=r'C:/Users/…/…/chromedriver.exe') driver.get("https://www.saucedemo.com/") # Поиск и ожидание элементов и присваивание к переменным. input_username = wait_of_element_located(xpath='//*[@id="user-name"]', driver=driver) input_password = wait_of_element_located(xpath='//*[@id="password"]', driver=driver) login_button = wait_of_element_located(xpath='//*[@id="login-button"]', driver=driver) # Действия с формами input_username.send_keys("standard_user") input_password.send_keys("secret_sauce") login_button.send_keys(Keys.RETURN) # Поиск и ождиание прогрузки ссылки элемента товара магазина и клик по ссылке item_name = wait_of_element_located(xpath='//*[@id="item_5_title_link"]/div', driver=driver) item_name.click() # Поиск и ожидание кнопки добавления товара и клик по этой кнопке item_add_button = wait_of_element_located(xpath='//*[@id="add-to-cart-sauce-labs-fleece-jacket"]', driver=driver) item_add_button.click() # Ждем пока товар добавится в корзину, появится span(кол-во позиций в корзине) и кликаем по корзине чтобы перейти wait_of_element_located(xpath='//*[@id="shopping_cart_container"]/a/span', driver=driver).click() # Еще один поиск ссылки элемента позиции магазина item_name = wait_of_element_located(xpath='//*[@id="item_5_title_link"]/div', driver=driver) if item_name.text == "Sauce Labs Fleece Jacket": print("Товар пристутствует в корзине") else: print("Товар отсутствует") time.sleep(5) if __name__ == '__main__': test_add_jacket_to_the_shopcart()
pytest main.py
. После прохождения всех этапов должен отобразиться результат прохождения.6. Проверки, проверки, проверки
assert
или утверждения. Суть инструмента – проверить, что результат соответствует наши ожиданиям. Если соответствует, наш тест будет считаться пройденным, а в противном случае – проваленным.
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from selenium.webdriver.support.wait import WebDriverWait from selenium import webdriver from selenium.webdriver.common.keys import Keys # Функция ожидания элементов def wait_of_element_located(xpath, driver): element = WebDriverWait(driver, 10).until( EC.presence_of_element_located( (By.XPATH, xpath) ) ) return element def test_add_jacket_to_the_shopcart(): options = webdriver.ChromeOptions() options.add_experimental_option("excludeSwitches", ["enable-logging"]) driver = webdriver.Chrome(options=options, executable_path=r'C:/Users/…/…/chromedriver.exe') driver.get("https://www.saucedemo.com/") # Поиск и ожидание элементов и присваивание к переменным. input_username = wait_of_element_located(xpath='//*[@id="user-name"]', driver=driver) input_password = wait_of_element_located(xpath='//*[@id="password"]', driver=driver) login_button = wait_of_element_located(xpath='//*[@id="login-button"]', driver=driver) # Действия с формами input_username.send_keys("standard_user") input_password.send_keys("secret_sauce") login_button.send_keys(Keys.RETURN) # Поиск и ождиание прогрузки ссылки элемента товара магазина и клик по ссылке item_name = wait_of_element_located(xpath='//*[@id="item_5_title_link"]/div', driver=driver) item_name.click() # Поиск и ожидание кнопки добавления товара и клик по этой кнопке item_add_button = wait_of_element_located(xpath='//*[@id="add-to-cart-sauce-labs-fleece-jacket"]', driver=driver) item_add_button.click() # Ждем пока товар добавится в корзину, появится span(кол-во позиций в корзине) и кликаем по корзине чтобы перейти wait_of_element_located(xpath='//*[@id="shopping_cart_container"]/a/span', driver=driver).click() # Еще один поиск ссылки элемента позиции магазина item_name = wait_of_element_located(xpath='//*[@id="item_5_title_link"]/div', driver=driver) item_description = wait_of_element_located( xpath='//*[@id="cart_contents_container"]/div/div[1]/div[3]/div[2]/div[1]', driver=driver ) assert item_name.text == "Sauce Labs Fleece Jacket" assert item_description.text == "It's not every day that you come across a midweight quarter-zip fleece jacket capable of handling everything from a relaxing day outdoors to a busy day at the office." driver.close() if __name__ == '__main__': test_add_jacket_to_the_shopcart()
7. Распределим логику
wait_of_element_located
. Разбивать логику необходимо для написания множества тестов.
import pytest from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from selenium.webdriver.support.wait import WebDriverWait from selenium import webdriver from selenium.webdriver.common.keys import Keys # Функция ожидания элементов def wait_of_element_located(xpath, driver_init): element = WebDriverWait(driver_init, 10).until( EC.presence_of_element_located( (By.XPATH, xpath) ) ) return element # Вынесем инициализцию драйвера в отдельную фикстуру pytest @pytest.fixture def driver_init(): options = webdriver.ChromeOptions() options.add_experimental_option("excludeSwitches", ["enable-logging"]) driver = webdriver.Chrome(options=options, executable_path=r'C:/Users/…/…/chromedriver.exe') driver.get("https://www.saucedemo.com/") yield driver driver.close() # Вынесем аутентификацию юзера в отдельную функцию def auth_user(user_name, password, driver_init): # Поиск и ожидание элементов и присваивание к переменным. input_username = wait_of_element_located(xpath='//*[@id="user-name"]', driver_init=driver_init) input_password = wait_of_element_located(xpath='//*[@id="password"]', driver_init=driver_init) login_button = wait_of_element_located(xpath='//*[@id="login-button"]', driver_init=driver_init) # Действия с формами input_username.send_keys(user_name) input_password.send_keys(password) login_button.send_keys(Keys.RETURN) def add_item_to_cart(xpath_item, driver_init): # Поиск и ождиание прогрузки ссылки элемента товара магазина и клик по ссылке item_name = wait_of_element_located( xpath=xpath_item, driver_init=driver_init) item_name.click() # Поиск и ожидание кнопки добавления товара и клик по этой кнопке item_add_button = wait_of_element_located( xpath='//*[@id="add-to-cart-sauce-labs-fleece-jacket"]', driver_init=driver_init) item_add_button.click() # Ждем пока товар добавится в корзину, появится span(кол-во позиций в корзине) # Возвращаем True или False в зависимости добавлися товар или нет shop_cart_with_item = wait_of_element_located( xpath='//*[@id="shopping_cart_container"]/a/span', driver_init=driver_init) return shop_cart_with_item def test_add_jacket_to_the_shopcart(driver_init): # Аутентификация пользователя auth_user("standard_user", "secret_sauce", driver_init=driver_init) # Добавление товара в корзину и если товар добавлен переход в корзину add_item_to_cart(xpath_item='//*[@id="item_5_title_link"]/div', driver_init=driver_init).click() # Поиск корзины и клик wait_of_element_located(xpath='//*[@id="shopping_cart_container"]/a', driver_init=driver_init).click() # Поиск ссылки элемента позиции магазина item_name = wait_of_element_located(xpath='//*[@id="item_5_title_link"]/div', driver_init=driver_init) # Поиск описания товара item_description = wait_of_element_located(xpath='//*[@id="cart_contents_container"]/div/div[1]/div[3]/div[2]/div[1]', driver_init=driver_init) assert item_name.text == "Sauce Labs Fleece Jacket" assert item_description.text == "It's not every day that you come across a midweight quarter-zip fleece jacket" " capable of handling everything from a relaxing day outdoors to a busy day at " "the office." if __name__ == '__main__': test_add_jacket_to_the_shopcart(driver_init=driver_init)
Рекомендации по архитектуре
Заключение
- 12 views
- 0 Comment