[Из песочницы] Единственно верный способ загружать и скачивать файлы в Selenium тестах
Selenium WebDriver создавался как кросс-платформенный инструмент для управления веб браузерами. И вот уже почти 14 лет он делает эту работу очень и очень хорошо. Впрочем, автотесты из реального мира создают ситуации, в которых Selenium бессилен. Например, по сценарию нужно загрузить или скачать какой-либо файл. После нажатия кнопки «Загрузить» или «Скачать», поверх окна браузера появляется окно файлового менеджера операционной системы к которому Selenium уже не имеет доступа. Тест останавливается.
Я слышал рекомендации использовать утилиты типа AutoIt или Sikuli для работы с такими системными окнами. Мой совет — никогда так не делайте, это порочная практика, которая приводит к нестабильным тестам:
- Такое решение не кросс-платформенное. Приходится изобретать свой велосипед для каждой новой операционной системы.
- Хрупкое. Нет гарантии, что скрипт будет работать корректно если на машине открыто несколько браузеров.
- Это делает невозможным использование headless режима браузера
Меня зовут Ярослав Пернеровский. Я уже 15 лет в тестировании и около 8 лет в автоматизации. Сейчас я расскажу как нужно правильно обрабатывать такие ситуации.
Все сложности с окнами операционной системы решаются довольно прямолинейно — надо сделать так, чтобы эти окна не появлялись и не мешали работе браузера.
В этом случае все просто, даже банально. Чтобы проблема не возникала — не нужно нажимать на кнопку «Загрузить». Вместо этого лучше поискать на странице элемент, который является файловым полем ввода (специальный input
с типом file
) и с помощью метода sendKeys()
«напечатать» в него абсолютный путь к нужному файлу:
By fileInput = By.cssSelector("input[type=file]");
String filePath = "/home/selenium/files/upload.txt";
driver.findElement(fileInput).sendKeys(filePath);
Это все. Дальше Selenium все сделает за вас. В любой операционной системе. В любом режиме браузера. После выполнения этого метода, сразу же, без лишних вопросов, начнется загрузка файла.
Что, если загрузка не началась? Возможно, вы пишите не в то поле, так как в DOM дереве присутствует несколько полей нужного типа. Это легко проверить в консоли браузера командой $$("input[type=file]")
Надо помнить, что метод findElement()
возвращает самый первый элемент из найденных. Это не всегда может быть то что нужно. Следует более точно специфицировать локатор, чтобы он указывал на правильное поле. Тут надо поэкспериментировать, но в результате все заработает как надо.
В угоду красивому дизайну, разработчики часто делают файловое поле скрытым. В таких случаях, Selenium ругается на то, что не может работать с невидимым элементом. Довольно прямолинейное решение — надо сделать поле видимым. Об этом хорошо написал Алексей Баранцев (barancev) в своем блоге. После таких манипуляций, никаких проблем с загрузкой быть не должно.
Если с загрузкой все абсолютно понятно, то скачивание файла уже не так однозначно. Современные браузеры по-умолчанию не спрашивают куда сохранять файл, а просто сохраняют в системную папку Downloads, а так как Selenium запускает браузеры с чистыми настройками и изолированными профилями (кроме Internet Explorer), то простое нажатие на «Скачать» начнет скачивание файла без открытия окна проводника.
Хотя, даже в таком случае, бывают внезапные уточняющие диалоги уже от самого браузера:
Это решается таким же способом, как и с загрузкой файла. Не надо нажимать никаких кнопок. Вместо этого давайте заглянем в код:
Как видно, кнопка реализована в виде ссылки. Атрибут href
содержит путь к нужному файлу на сервере. Это идеальный вариант, так как все что нужно, это вытащить ссылку и скачать файл с помощью любого http клиента. Например для Java это выглядит следующим образом:
//Get download link
String downloadLink = driver
.findElement(By.cssSelector("main#content a.btn"))
.getAttribute("href");
//Set file to save
File fileToSave = new File("/path/to/file.zip");
//Download file using default org.apache.http client
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(downloadLink);
HttpResponse response = httpClient.execute(httpGet, new BasicHttpContext());
//Save file on disk
copyInputStreamToFile(response.getEntity().getContent(), fileToSave);
Конечно, для корректной загрузки через http, надо написать чуть более сложный код. Ведь нужно передавать http заголовки и куки текущей сессии браузера, обрабатывать ошибки и прочее. Полный код реализации такого механизма можно подсмотреть в исходниках библиотеки Selenide. Нужный класс называется DownloadFileWithHttpRequest.
К сожалению, не всегда бывает так просто. Случается, что ссылка для скачивания генерируется с помощью JavaScript и ее сложно получить в чистом виде. Тогда, без нажатия на кнопку уже не обойтись. Хорошая новость в том, что окошки операционной системы открываются не после нажатия на кнопку, а уже когда браузер получает серверный ответ, в котором содержится информация для сохранения на диск. Все что нам нужно сделать, это перехватить этот ответ и подменить метод сохранения из браузера на свой. Звучит просто, но на деле это требует установки прокси-сервера между браузером и настоящим бекендом. К счастью, существуют специальные прокси, которые прекрасно работают в связке с Selenium. Например современный BrowserUpProxy или классический BrowserMobProxy.
Реализация этого метода довольно громоздкая, но работающий пример можно найти в уже знакомом проекте Selenide. За это отвечает класс DownloadFileWithProxyServer.
В целом, этот способ самый надежный, но и он не лишен недостатков. Установленный прокси нарушает работу https, а это может повлиять на поведение тестируемого приложения.
В общем случае, надо хорошо подумать, что именно тестируется и действительно ли для этого нужно скачивать файлы. Если можно обойтись без скачивания, то лучше обойтись. Если нет — выбирайте самый простой способ из вышеописанных.
Чтобы тесты оставались стабильными, надо убирать факторы, которые делают их хрупкими. Диалоговые окна операционной системы, это один из таких факторов. В следующих статьях я планирую раскрыть другие важные аспекты, которые сильно влияют на стабильную работу Selenium тестов. Слушайте подкаст QA Guild и хорошей вам автоматизации.