Автоматизация 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]); }
