Автоматизация End-2-End тестирования комплексной информационной системы
Часть 2-2. Реализация процесса выгрузки файла из контейнера с браузером в тестовый фреймворк. Поиск имени загруженного браузером файла
Автор: habr.com/ru/users/anrad
Хабы:
Теги: #autotest, #selenium, #selenoid, #headlessbrowser, #download
Когда мы в разработке End-2-End автотестов для UI столкнулись с вопросом “Как получить имя последнего загруженного браузером файла из WebDriver?”, нагуглить ничего по-быстрому не получилось. Поэтому я и написал эту статью, в которой заодно рассказал, в чем именно у нас была проблема и как мы ее решили.
Этой статьей мы продолжаем серию публикаций о том, как мы автоматизировали в одном из крупных проектов ЛАНИТ процесс ручного тестирования (далее – автотесты) большой информационной системы (далее – Системы) и что у нас из этого вышло.
source
Сама статья завершает цикл, ниже ссылки на все предыдущие части:
Часть 1. Организационно-управленческая. Зачем нам была нужна автоматизация. Организация процесса разработки и управления. Организация использования
Часть 2. Техническая. Архитектура и технический стек. Детали реализации и технические сюрпризы
Часть 2-1. Реализация базового класса для всех тестов и JUnit RuleChain
Это было год назад. Итак, сегодня это может быть неактуально. Напишите в комментарии, если вы знаете, какой эффективный способ обработки файлов в случае нескольких серверов selenoid grid.
При разработке “автотестов” у нас возникла задача по выгрузке (download) файлов из системы с последующим анализом их содержимого. Для «выгрузки» файлов из тестируемой системы мы используем следующие решения:
- для «локального» режима запуска (запуск непосредственно на рабочем ПК) – при инициализации «локального» браузера ему в качестве параметра передается имя временной локальной папки. Далее файл читается напрямую из локальной папки для последующего анализа;
- для «удаленного» режима (через Bamboo) – файл «забирался» из контейнера с браузером через фичу Solenoid сервера: selenoid-host.example.com:4444/download/{SESSION_ID}/{FILE_NAME}
Детали описаны в документации.
Для обоих режимов для доступа к файлу необходимо было знать его имя. В этом и был сюрприз. Проблема отсутствия данных об имени «download» файла заключалась в следующем:
- после «клика» на кнопочку «загрузить файл», браузер загружал файл в соответствующую локальную папку в контейнере;
- при этом имя файла формировалось динамически и, как его определить заранее, мы не смогли найти. Это была особенность нашей тестируемой системы;
- далее этот файл следовало «вытащить» из контейнера и провести бизнес-проверки его содержимого;
- чтобы «вытащить» файл, следовало знать его имя, а имени мы и не знали, так как имя генерировалось динамически и в ссылке его значения не было.
Дополнительно ситуация усугублялась тем, что из-за большой сложности некоторых тестов в ходе теста могли выгружаться несколько файлов в рамках разных независимых тест-сценариев из состава самого теста.
Наилучшим для нас решением оказался метод, при котором мы определяли последний загруженный файл. Сделать это можно было несколькими способами:
- анализировать состав файлов непосредственно перед операцией и сразу после. Разница как раз и будет наш искомый файл. Или не будет, если файл не загрузился;
- использовать возможность для браузера Google Chrome открыть через javascript страницу chrome://downloads/, которую можно обработать как обычную html-страницу. Требуемый нам файл будет первым в списке.
Получение имени файла через анализ состава файлов
Анализ состава загруженных браузером файлов для локального режима является тривиальной задачей.
Для удаленного режима необходимо использовать «недокументированную» фичу селеноид сервера: selenoid-host.example.com:4444/download/{SESSION_ID} выводит список всех успешно загруженных файлов, где SESSION_ID – selenoid session ID
В целом схема отлично работает за одним исключением: нам надо ждать некоторое время, пока файл загрузится и появится в списке. Ожидание можно установить через цикл таймаутов, однако так мы не можем получить информацию о возможной ошибке загрузки файла. Мы можем только определить, что файл не загрузился в этой схеме. Поэтому мы в конце концов остановились на методе определения имени файлов через страницу chrome://downloads/.
Получение имени файла через страницу chrome://downloads/
Этот метод одинаково хорошо работает как в локальном, так и в удаленном режиме. Схема работы достаточно простая:
- запустить java script для открытия окна chrome://downloads/;
- обработать данные в окне обычными способами. Дождаться, когда первый в списке файл загрузится и определить его имя;
- закрыть окно chrome://downloads/ и вернуть имя искомого файла.
Идею использования chrome://downloads/ мы нагуглили и, к моему сожалению, я не могу привести ссылку на источник, так как она банально не сохранилась. Сама реализация класса получения имени файла приведена далее.
Класс получения имени загруженного файла через chrome://downloads/
private static final long DOWNLOADS_PAGE_LOAD_TIMEOUT = 5_000;
private static final long MAX_GET_FILE_NAME_ATTEMPT = 5;
public static String getLastDownloadedFilename() throws DownloaderException {
String[] filename = new String[1];
Exception[] ex = new Exception[1];
WebDriver driver = WebDriverRunner.getWebDriver();
JavascriptExecutor executor = (JavascriptExecutor) driver;
Utils.driver.openNewTabCheckAndClose(
() -> {
executor.executeScript("window.open('','_blank');");
},
() -> {
driver.get("chrome://downloads/");
for (int i = 0; i < MAX_GET_FILE_NAME_ATTEMPT; i++) {
ex[0] = null;
sleep();
try {
WebElement element = (WebElement) executor.executeScript(
"return document.querySelector('downloads-manager').shadowRoot.querySelector('downloads-item').shadowRoot.getElementById('file-link')");
filename[0] = element.getText();
LOGGER.info("Attempt get file name " + i + ". Name = '" + filename[0] + "'");
} catch (WebDriverException e) {
ex[0] = e;
LOGGER.info("Failed attempt "+ i + " to get filename text: " + e.getMessage());
continue;
}
if (filename[0] != null && !filename[0].isEmpty()) {
// здесь мы удаляем ссылку на файл со страницы чтобы она не мешала потом
executor.executeScript(
"document.querySelector('downloads-manager').shadowRoot.querySelector('downloads-item').shadowRoot.getElementById('remove').click()");
break;
}
}
}
);
if (filename[0] != null && !filename[0].isEmpty()) {
return filename[0];
}
String message = "Timeout. Can not get last downloaded file name from chrome://downloads/. File name is '" + filename[0] + "'. Exception: " + ex[0].getMessage();
LOGGER.warning(message);
throw new DownloaderException(message, ex[0]);
}