Если коротко, чтобы ускорить тесты в «Селениуме», грамотный тестировщик делает следующее:
- продуманно выбираются wait-ожидания;
- и локаторы;
- меняются настройки и дизайн тестов;
- применяются продуманные подходы (стратегии);
- а также оптимизируется тестовая инфраструктура (переход в облако).
Теперь подробнее.
Стандартный тест
Стандартный тест в Selenium умеет, например:
- Открыть URL тестируемой страницы (через WebDriver / Remote WebDriver).
- Найти нужные элементы с помощью локаторов (XPath, CssSelector, LinkText).
- Выполнить действия с элементами.
- Освободить ресурсы, с которыми работал WebDriver.
Скорость выполнения зависит от структуры теста, его методов.
- Следует иметь в виду, то обслуживание тестов иногда усложняется с выходом крупных обновлений Selenium.
Правильные веб-локаторы
Веб-локаторы Selenium это узловая вещь тестового сценария. В процессе автоматизации мы находим элемент нужным веб-локатором, и выполняем действие.
Применяются следующие веб-локаторы Selenium:
- XPath
- CSS Selector
- Name
- LinkText
- Partial LinkText
- TagName
- ClassName
Веб-локаторы сочетаются с методом find_element (или find_elements).
Какой локатор самый быстрый?
В терминах скорости, самый быстро работающий — ID; поскольку ID-локатор в WebDriver уникален для каждого элемента страницы. ID-локатор возвращает веб-элемент (WebElement), соответствующий указанному значению (или строке). Если на странице найдено несколько элементов с одинаковым ID, то document.getElementById() возвращает первый найденный из них.
Популярные веб-браузеры оптимизируют работу метода document.getElementById(), что дает некоторое ускорение поиска WebElement в DOM.
Если WebElement не имеет атрибута ID, рекомендуется работать через атрибут NAME. Если нет ни ID, ни NAME-атрибута, придется работать с локатором CSS Selector. CSS Engine почти одинаковый во всех распространенных браузерах, и их скорость оптимизирована для связки CSS Selectors + Selenium. Это значит, что не предвидится особых проблем браузерной совместимости с этим веб-локатором.
CSS Selector в целом находит элементы быстро, и тесты выполняются за небольшой интервал. CSS Selector лучше подходит для устаревших версий и браузеров (типа Internet Explorer); а также, лучше читается код чисто визуально, по сравнению с XPath.
XPath — самый “тормозной” веб-локатор, с ним случаются проблемы на разных браузерах. Рекомендуется применять XPath только если нет другого варианта.
Итак, выстроим веб-локаторы по желательности применения:
- ID
- Name
- CSS Selector
- XPath
Меньше веб-локаторов
Следующий момент: снизить количество применяемых веб-локаторов, по возможности.
Каждый запрос метода find_element(By) “нагружает” DOM-дерево. Чем больше запросов, тем дольше выполняется скрипт. Лучшая практика состоит в ограничении количества таких запросов, (особенно если скорость критична). Да и сами скрипты будут лучше читаться (то есть лучше обслуживаться).
Избегай Thread.sleep()
Контент сайта (приложения) может быть как статическим так и динамическим. Сейчас везде используется AJAX (Asynchronous JavaScript And XML) для динамической подгрузки контента. Элементы страницы могут загружаться с разным интервалом; что создает трудности при операциях с элементами которых пока нет в DOM.
Рекомендуется проверять состояние DOM, путем мониторинга статуса document.readyState. Если document.readyState в “завершенном” состоянии, то все ресурсы страницы считаются загруженными, и доступными для операций.
Ожидания на несколько секунд, прописываемые в коде теста, также добавляют задержку загрузки ресурсов.
В Selenium существует несколько способов задания ожиданий. Из них Thread.sleep() — надо избегать в любом случае. (Этот метод приостанавливает выполнение кода на указанное количество миллисекунд):
/* Pauses test execution for specified time in milliseconds */ Thread.sleep(5000);
В этом коде ожидание составляет 5 секунд (то есть 5000 миллисекунд). А если все элементы уже загрузились? (например, за 2 секунды) Тогда 3 секунды — попусту потраченное время. Скорость загрузки зависит от многих вещей (нагрузка на сервер, особенности дизайна страницы, кеширование, тип сети), поэтому тяжело точно знать время загрузки наперед. Хорошая практика автоматизации Selenium: точнее оценивать время загрузки при автоматизации.
Данный метод ожидания Thread.sleep() с фиксированным временем — плохая практика, практически для любой страницы.
Повторное использование экземпляра браузера
Все фреймворки автоматизации, работающие в связке с Selenium, поддерживают Аннотации, ускоряющие написание и выполнение тестов (особенно с разными входными данными). Тут важно выбрать правильную аннотацию:
Фреймворк | Аннотации |
---|---|
JUnit [Selenium Java] | @BeforeClass, @Before, @Test, @After, @AfterClass, @Ignore |
TestNG [Selenium Java] | @BeforeSuite, @BeforeTest, @BeforeClass, @BeforeMethod, @Test, @AfterMethod, @AfterClass, @AfterTest, etc. |
NUnit [Selenium C#] | [SetUp], [TearDown], [Test], [TestCase], [OneTimeSetUp], [OneTimeTearDown], [Ignore], [Category], etc. |
XUnit [Selenium C#] | [Theory], [InlineData], [Fact], etc. |
Есть сценарии, в которых запускается один тест (или группа тестов) в одном браузере / в одной ОС. Тогда создание нового экземпляра WebDriver при запуске каждого теста замедляет процесс.
JUnit + Selenium
Порядок выполнения аннотаций в JUnit for Selenium:
В автотесте Selenium + JUnit, экземпляр Selenium WebDriver создается методом SetUp, имплементированном аннотацией @Before. Созданный экземпляр удаляется методом TearDown, имплементированном аннотацией @After.
TestNG + Selenium
Порядок выполнения аннотаций в TestNG + Selenium:
Аналогично как с JUnit, SetUp-метод имплементируется аннотацией @BeforeMethod, и TearDown-метод — аннотацией @AfterMethod.
Серии тестов в комбинациях Браузер+ОС
Например, нужно выполнить три теста для комбинации браузера и ОС; тогда это выглядит следующим образом:
JUnit
Test – 1 | setUp() under @Before annotation -> Test-1 -> tearDown() under @After annotation |
Test – 2 | setUp() under @Before annotation -> Test-2 -> tearDown() under @After annotation |
Test – 3 | setUp() under @Before annotation -> Test-3 -> tearDown() under @After annotation |
TestNG
Test – 1 | setUp() under @BeforeMethod annotation -> Test-1 -> tearDown() under @AfterMethod annotation |
Test – 2 | setUp() under @BeforeMethod annotation -> Test-2 -> tearDown() under @AfterMethod annotation |
Test – 3 | setUp() under @BeforeMethod annotation -> Test-3 -> tearDown() under @AfterMethod annotation |
Экземпляр браузера создается и удаляется после каждого сценария.
Повторное использование (re-using) экземпляра браузера в JUnit и TestNG
Может применяться один и тот же экземпляр, с соответствующими аннотациями:
JUnit
Test – 1, Test – 2, and Test -3 | setUp() under @BeforeClass annotation -> Test-1, Test-2, and Test-3 -> tearDown() under @AfterClass annotation |
TestNG
Test – 1, Test – 2, and Test -3 | setUp() under @BeforeTest (or @BeforeClass) annotation -> Test-1, Test-2, and Test-3 -> tearDown() under @AfterTest (or @AfterClass) annotation |
В этом варианте один экземпляр браузера используется во всех сценариях, и удаляется после выполнения всех тестов.
Подобная техника неприменима на разных комбинациях браузер+ОС, когда комбинации должны создаваться и уничтожаться после каждого сценария.
Применение эксплицитных ожиданий в Selenium
Имплицитное wait-ожидание в Selenium применяется ко всем веб-элементам скрипта. Крупный недостаток имплицитного ожидания в том, что оно неприменимо на параметрах типа Visible, Clickable, Selectable и т.п.
А вот эксплицитное ожидание позволяет прописывать ожидание “по условию появления” элементов страницы. Например, ElementNotVisibleException выдается, когда указанный WebElement все еще появился (не видимый) по истечению интервала, указанного в эксплицитном wait-е. Метод elementToBeClickable возвращает WebElement, если найденный элемент кликабельный.
Для эксплицитного ожидания применяется сочетание классов WebDriverWait и ExpectedConditions. Эксплицитное ожидание не ожидает конца интервала, а завершается по указанному условию. Если WebElement есть, он возвращается в качестве результата. Если WebElement-а нет в DOM, выбрасывается TimeOutException, (не глядя на время прописанное в условии).
В примере ниже — эксплицитное ожидание 5 секунд по условию visibilityOfElementLocated. Если веб-элемент с ID = “element” найден за 5 секунд, эксплицитное ожидание завершается, и возвращается нужный WebElement.
/* Trigger an explicit wait of 5 Seconds */ WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5)); WebElement element = wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("element"));
В целом эксплицитное ожидание ускоряет выполнение скриптов, поскольку WebElement доступен сразу после того как найден. Эксплицитные ожидания ускоряют тесты в Selenium, потому что более гибко обрабатывают сценарии.
“Независимые” автономные скрипты
Видимо основное требование по части дизайна “селениумных” тестов. Независимо от сложности сценария, лучше разбивать сложный сценарий на несколько “независимых” тестов-кейсов.
Фреймворки автоматизации типа TestNG поддерживают объявление эксплицитных зависимостей между тестовыми методами, через аннотации — например dependsOnMethods (для методов) и dependsOnGroups (для групп). Однако, зависимости должны создаваться только если данные и состояния будут расшариваться между методами.
“Атомарные” тесты лучше при определении багов. Маленькие “атомарные” тесты экономят время на обслуживание.
“Атомарность” Selenium-тестов упрощает тесты, минимизирует проблемы при имплементации, облегчает обслуживание, и поэтому ускоряет процесс.
Параллельное тестирование
Параллельное тестирование позволяет запускать тесты одновременно в разных тестовых окружениях. Если планируется применять локальный Selenium Grid в распределенном тестировании, рекомендуется воспользоваться Selenium Grid 4, так быстрее.
В зависимости от сценариев, возможна параллелизация на уровне классов или методов. Параллелизация может быть через:
- Группирование тестовых сценариев
- Параметризация тестовых сценариев
- Облачный Selenium Grid
Группы тестовых сценариев
Сложность тестового набора растет с добавлением в него новых файлов и методов. Для минимизации проблем с имплементацией и обслуживанием, следует группировать тесты, по их функциональности.
Как показано на примере группирования тест-кейсов в TestNG, создаются две группы тестов (Search и ToDo), и идет параллельное выполнение, на уровне методов. Атрибут thread-count в TestNG прописывает параллельное выполнение методов, с указанным максимальным числом потоков — 4.
- Группирование тестовых сценариев уменьшает сложности обслуживания и ускоряет выполнение.
Selenium 4 — лучше чем Selenium 3
QA-комьюнити ждет официального релиза “Четвертого Селениума”, а пока есть бета. Заметны продвижения:
- Оптимизированный-улучшенный Grid
- Стандартизация WebDriver под W3C
- Улучшенный Selenium 4 IDE
- Оптимизированные Chrome DevTools
- Оптимизированные Relative Locators
Все это должно ускорить тесты в Selenium.
В 3-ей версии используется JSON Wired Protocol коммуникации между браузером и тестовым кодом. Это вызывает иногда проблемы с API-запросами и W3C-протоколом. В Selenium 4 используется WebDriver W3C-протокол, работающий с браузером быстрее. Кроме быстроты, заявляется улучшение стабильности тестов.
Relative Locators (бывшие friendly locators) в Selenium 4 полезны для работы с веб-элементами, “прилегающими” к какому-то веб-элементу, то есть соседними. Ниже показаны новые такие локаторы в Selenium 4:
Вместо методов find_element() и findelements() для каждого веб-элемента страницы, применяются relative locators + TagName (если это Selenium 4 Java), и запросы в DOM идут быстрее.
Могут быть и другие улучшения. Переход на 4-ю версию должен быть без проблем.
Облачный Selenium Grid
Параллельное выполнение тестов из локального Selenium Grid имеет серьезные недостатки, если идет речь о масштабируемости и надежности. Не подходит для больших веб-приложений, где много тест-сьютов запускаются на множестве браузеров, ОС и устройств.
Облачное тестирование в Selenium ускоряет процессы, поскольку параллельное выполнение выполняется более надежно, и хорошо масштабируемо. Ниже показан testng.xml; параллельное выполнение идет на уровне теста, то есть каждый <test>-тег выполняется в отдельном потоке.
Как видно выше, два теста выполняются параллельно в облаке.
Облачный Selenium Grid = ускорение тестов:
Отключение изображений на странице
После создания экземпляра Selenium WebDriver, применяется метод driver.get() для открытия страницы. Скорость загрузки сильно зависит от структуры страницы, и конечно большое количество изображений замедляет загрузку.
Можно отключить их программно.
Отключение изображений в Chrome при выполнении Selenium-скрипта
package com.disableimages; import org.openqa.selenium.*; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.firefox.FirefoxOptions; import org.openqa.selenium.firefox.FirefoxProfile; import org.testng.annotations.AfterTest; import org.testng.annotations.Test; import java.util.HashMap; public class test_disable_image_demo { String test_url = "https://www.amazon.com"; WebDriver driver = null; @Test(enabled=true, priority = 1) public void test_disable_images_chrome() throws InterruptedException { ChromeOptions options =new ChromeOptions(); HashMap<String, Object> prefs = new HashMap<String, Object>(); prefs.put("profile.managed_default_content_settings.images", 2); options.setExperimentalOption("prefs", prefs); driver = new ChromeDriver(options); driver.get(test_url); driver.manage().window().maximize(); Thread.sleep(5000); } @AfterTest public void tearDown() { if (driver != null) { driver.quit(); } } }
Выше мы отключили изображения на странице Amazon, это должно сильно ускорить загрузку страницы.
Отключение изображений в Firefox
package com.disableimages; import org.openqa.selenium.*; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.firefox.FirefoxOptions; import org.openqa.selenium.firefox.FirefoxProfile; import org.testng.annotations.AfterTest; import org.testng.annotations.Test; import java.util.HashMap; public class test_disable_image_demo { String test_url = "https://www.amazon.com"; WebDriver driver = null; @Test(enabled=true, priority = 1) public void test_disable_images_firefox() throws InterruptedException { FirefoxProfile profile = new FirefoxProfile(); profile.setPreference("permissions.default.image", 2); FirefoxOptions options = new FirefoxOptions(); options.setProfile(profile); driver = new FirefoxDriver(options); driver.get(test_url); driver.manage().window().maximize(); Thread.sleep(5000); } @AfterTest public void tearDown() { if (driver != null) { driver.quit(); } } }
Выше изображения отключены установкой параметра permissions.default.image = 2.
Одна из хороших практик тестирования, которая недооценивается junior QA, учитывая обилие изображений на современных сайтах.
Data-Driven-тестирование и параметризация
Если надо запустить сценарий на многих комбинациях браузеров и ОС, или при разных комбинациях входных данных, hard-coding значений в тестовых методах — не лучшее решение. Лучше сделать параметризацию, особенно если в проекте большой набор данных.
Параметризация в Selenium как известно улучшает тестовое покрытие и ускоряет сами тесты. Параметризацию поддерживают все популярные фреймворки автоматизации, типа MSTest, NUnit, JUnit, TestNG и PyTest.
Например TestNG передает параметры в testng.xml, и при параллельном выполнении (на уровне тестов) значительно ускоряет процесс.
Headless-браузеры и драйверы
Для проверки действий с UI-элементами вызывается драйвер в non-headless-режиме.
Headless-браузер запускает UI-тесты без GUI. Такое тестирование ускоряет кроссбраузерные тесты — они выполняются в фоновом режиме. Популярные браузеры типа Chrome и Firefox могут работать в headless-режиме.
DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability("build", "your build name"); capabilities.setCapability("name", "your test name"); capabilities.setCapability("platform", "Windows 10"); capabilities.setCapability("browserName", "Chrome"); capabilities.setCapability("version","89.0"); capabilities.setCapability("headless",true);
Headless-тесты это она из лучших практик в Selenium, которая должна применяться, если не нужно проверять UI-действия.
Другие (возможно даже более удобные) headless-браузеры:
- HTMLUnit
- Splash
- PhantomJS
- TrifleJS
- ZombieJS
- SimpleBrowser
HTMLUnitDriver на основе HTMLUnit — это упрощенная имплементация headless-браузера под Selenium WebDriver. HTMLUnitDriver похож на драйверы Chrome, Firefox, кроме того что нет GUI.
Браузер HTMLUnit написан на Java, поддерживает AJAX и JavaScript. Есть, частично, функции рендеринга. Ниже показан сниппет (на Selenium Java) создания экземпляра HTMLUnitDriver и запуска в Selenium:
import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.htmlunit.HtmlUnitDriver; /* Create a new instance of the HtmlUnitDriver */ WebDriver driver = new HtmlUnitDriver(); /* Perform necessary actions as per the desired test requirement */ ................ ................ /* Release the resources held by HtmlUnitDriver */ driver.quit();
PhantomJS — другой популярный headless-браузер, работающий через JavaScript API. Для бекенда здесь QtWebKit, нативно поддерживаются стандарты JSON, Canvas, SVG, обработка DOM и другое. Интегрированный GhostDriver. Браузер доступен для Windows, Linux и MacOS X. Годен для:
- Headless-тестирования
- Автоматизации
- Захвата экрана
- Мониторинга сети
Ниже — сниппет (Selenium Java) создания PhantomJS Driver и и выполнения браузерных тестов в Selenium:
import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.phantomjs.PhantomJSDriver; /* Create a new instance of the PhantomJS Driver*/ WebDriver driver = new PhantomJSDriver(); /* Perform necessary actions as per the desired test requirement */ ................ ................ /* Release the resources held by PhantomJS Driver */ driver.quit();
Headless-тестирование должно применяться для ускорения тестов в Selenium. Кроме приведенных HtmlUnitDriver и PhantomJSDriver, можно (и нужно) использовать headless-версии Chrome и Firefox.
Итого
Важно всегда иметь в виду продуктивность тестов, главным показателем которой является скорость. Предпочтительный путь: параллельное выполнение + облачный Selenium Grid. Как видим выше, существует много практик ускорения тестов; быстрые тесты это довольные тестировщики, хорошее покрытие, и в конечном итоге приемлемое качество продукта.