Автоматизация End-2-End тестирования комплексной информационной системы
Часть 2-2. Реализация процесса выгрузки файла из контейнера с браузером в тестовый фреймворк. Поиск имени загруженного браузером файла


Автор: habr.com/ru/users/anrad
Хабы:
Теги: #autotest, #selenium, #selenoid, #headlessbrowser, #download


Когда мы в разработке End-2-End автотестов для UI столкнулись с вопросом “Как получить имя последнего загруженного браузером файла из WebDriver?”, нагуглить ничего по-быстрому не получилось. Поэтому я и написал эту статью, в которой заодно рассказал, в чем именно у нас была проблема и как мы ее решили.

Этой статьей мы продолжаем серию публикаций о том, как мы автоматизировали в одном из крупных проектов ЛАНИТ процесс ручного тестирования (далее – автотесты) большой информационной системы (далее – Системы) и что у нас из этого вышло.

image
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]);
	}