Введение Последние три месяца мне приходилось работать с Selenium 2.0 (WebDriver).
В этой статье я опишу свои впечатления, мысли и опыт, которые я приобрел.
Также я опишу основные действия, которые чаще всего вызывают проблемы, и покажу наиболее удачные решения, которые мне удалось для них реализовать.
Возможно, есть более правильные подходы — буду рад, если вы оставите их в комментариях.
Коротко о Селене
Библиотека Selenium позволяет тестировать графические веб-интерфейсы.Его принцип — максимально точно имитировать активность пользователя.
По сути, это написание бота, который бегает по страницам сайта, выполняет действия и проверяет ожидаемый результат. Selenium 2.0 реализует связь с браузерами с помощью специальных драйверов.
В отличие от Selenium 1.0, он не использует JavaScript, а напрямую взаимодействует с API браузера.
Что мне удалось реализовать
Нам удалось написать тесты на базе JUnit и Selenium 2.0, объединенные в одно приложение.Это приложение может быть выполнено в Selenium Grid — это сеть, возглавляемая Selenium Hub, которая принимает и распределяет входящие задачи тестирования по своим узлам Selenium. Различные узлы Selenium можно настроить с любыми необходимыми браузерами.
Используемые драйверы являются собственными драйверами для каждого браузера.
Первая часть.
Впечатление
Разное поведение в разных браузерах
Под браузерами я подразумеваю основные: Firefox, Google Chrome, Opera, Safari, IE8, IE9. Чтобы один и тот же код одинаково хорошо работал в разных браузерах, нужно потратить огромное количество времени.Иногда нужна железная воля, чтобы не бросить это гибельное дело.
В этом плане наиболее послушными браузерами являются Firefox и Google Chrome. По моему личному опыту, очень важно, чтобы тест мог менять поведение в нужных местах в зависимости от того, какой браузер в данный момент используется.
Те.
он должен иметь информацию о среде, в которой он проходит. Совет: Старайтесь не использовать объект webDriver непосредственно в своих тестах! Создайте методы-оболочки вокруг основных необходимых вам методов.
Проще изменить поведение в одном месте, чем во всем коде всех тестов.
Селен 2.0 - сырой продукт
Читая множество постов на Stackoverflow в поисках лучших практик или просто решения проблемы, постоянно натыкаешься на обходные пути.Причин несколько: различия в работе драйверов браузера, драйверы, не выполняющие требуемый функционал контракта, ошибки в версиях, а также наличие прямой зависимости версии браузера от версии драйвера.
Иногда он способен просто сбросить тест из ниоткуда (с точки зрения пользователя API) — элемент есть, но он его не видит. По моему опыту, очень много плавающих ошибок, которые намеренно воспроизводятся только через раз при абсолютно одинаковых условиях и действиях.
В Firefox иногда поднимается температура, и браузер может просто закрыться с примерно таким сообщением: Ошибка связи с удаленным браузером.
Возможно, оно умерло .
Найти причину крайне сложно, если она вообще доступна пользователю Selenium. Поэтому иногда ситуация безвыходна – функционал просто не работает. Совет: Эта неудачная ситуация вынуждает нас изменить поведение тестового примера.
К счастью, одни и те же действия в клиентах с графическим интерфейсом часто можно сделать либо в другой последовательности, либо другим способом.
Если вы не смогли найти решение с помощью Google, попробуйте найти другое поведение, которое будет успешно отработано.
Не сосредотачивайтесь на конкретном действии, если в этом нет абсолютной необходимости.
Селеновые тесты — зависимые тесты
Это означает, что, если не принять дополнительные меры предосторожности, действия одного теста могут повлиять на результат другого теста.Это совершенно очевидно; пользователь также меняет данные во время своей активности.
При тестировании такого функционала вы будете вынуждены изменить исходные данные.
Если от него зависят другие тесты и вы не вернули данные в исходное состояние — или не смогли это сделать, поскольку тест был прерван из-за ошибки — другой тест также может сломаться.
Это принцип домино.
Когда впервые осознаешь это, становится очень больно.
Руки вниз.
Совет: Если есть возможность воспроизвести условия тестирования самостоятельно, т.е.
есть прямой доступ к тестируемому приложению и нет препятствий для развертывания исходных тестовых данных - вам повезло, изолируйте свои тесты таким образом - подготавливая данные до протестируйте и почистите его после.
Например, инструмент Liquibase может помочь с восстановлением данных в базе данных.
Скорее всего такой возможности нет. В этом случае выход только один — помимо самих тестируемых действий, с помощью Selenium можно еще описать действия для их «отката».
Те.
если пользователь удаляет сущность, ее необходимо создать заново или загрузить в конце теста.
Это греховный путь.
Так как подобные действия тоже уязвимы и тоже могут быть прекращены по ошибке, не выполнив своей цели.
Селеновые тесты — медленные тесты
Нужно быть готовым, что последовательное выполнение большого набора тестов для всех браузеров может занять большое количество времени, измеряемое часами (от 30 минут до 2-3 часов).Это превращает все, что я описал выше, в трагедию, а иногда и в издевательство.
Причина в том, что тесты сильно насыщены различными ожиданиями, поиском элементов и другими медленными действиями.
Совет: Тестируйте только то, что действительно необходимо протестировать.
Из всех возможных рабочих вариантов реализации одного и того же действия выберите самый быстрый.
Selenium IDE бесполезен
Selenium IDE — специальный плагин для Firefox, способный записывать все действия пользователя в виде скриптов.Также имеется возможность экспорта скомпилированных скриптов на различные языки и два формата: Selenium 1.0 (RC) и Selenium 2.0 (WebDriver).
В большинстве случаев бесполезная вещь.
Проблемы:
- сгенерированный код не читается
- сгенерированный код не работает в случае сложного интерфейса из-за всех описанных выше особенностей
- если идентификаторы элементов (div, table, span, input) генерируются автоматически, предлагаемые на выбор указатели XPath не подходят
- большое количество тестов (уже достаточно 5 тестов) заставит вас встать на правильный джедайский путь и создать собственную реализацию часто выполняемых действий — а затем использовать их как унаследованный метод. Однажды описанный и усовершенствованный.
Как только такой набор методов сформирован, полезность IDE резко падает. Ему нельзя приказать использовать собственные методы — среда разработки будет генерировать собственные несовершенные, несовершенные шаблоны.
Просмотр сгенерированного кода в дальнейшем и замена всех необходимых мест в конечном итоге сводится к полному переписыванию этого кода.
Эту же идею можно продолжить с помощью единственного «справочника» — списка всех XPath-локаторов ключевых элементов.
Как только все подобные локаторы включены в константы или в отдельный справочник, ими становится проще пользоваться, чем лишний раз проверять, что там сгенерировала среда разработки.
Совет: Поиграйтесь с IDE, поймите суть Selenium, с его помощью даже можно писать тесты.
Но как только вы почувствуете, что выгоды меньше затрат, начните готовиться самостоятельно.
Соберите их в общий абстрактный класс-предок или в служебный класс.
После определенного момента ваши тесты могут превратиться в простое перечисление таких методов, разбавленное проверками результата и текущего состояния.
Часть вторая.
Практические решения возникающих проблем (Java) Описанные ниже решения не красивы, не идеальны, могут вызвать отторжение, но работают. По моему опыту, они устраняют проблемы.
Надеюсь, они окажутся полезными и избавят вас от потраченных впустую часов и дней.
Получение элемента (findElement)
Проблема: WebDriver предоставляет механизм для поиска и получения объекта WebElement:Теоретически на поведение этого метода влияет параметр 'неявное ожидание' который можно указать при создании самого веб-драйвера.webDriver.findElement(By.id("elementId"));
Например, вот так: webDriver.manage().
timeouts().
implicitlyWait(5, TimeUnit.SECONDS);
Опять же, теоретически это должно явно заставлять веб-драйвер искать элемент в течение указанного времени и ждать либо пока искомый элемент не появится, либо пока не истечет указанный тайм-аут. Кстати, этот таймаут, видимо, можно установить только один раз.
На практике происходит нечто странное.
Стоит пауза, но есть внутреннее ощущение, что даже если поиск ведется по модели DOM, то эта модель DOM не обновляется.
Для некоторых браузеров ситуация другая — элемент уже находится в DOM модели, но еще не отрисован или отрисован частично (Google Chrome).
WebDriver возвращает найденный полуотрисованный элемент, а событие щелчка попадает в еще не отрисованные координаты.
Метод isDisplayed() в таких случаях не помогает. В любом случае результат у меня всегда один и тот же — элемент визуально гарантированно уже появился, но webDriver его всё равно не обнаруживает. Решение: Сделайте грубую паузу.
Чтобы не удваивать количество строк кода, я рекомендую сделать собственную реализацию метода findElement(); Как я писал выше, для более эффективной работы тест должен знать, какой браузер в данный момент запущен.
По моим наблюдениям, Firefox не требует такой задержки.
Вы также можете использовать инструмент WebDriverWait. Я не буду здесь описывать этот вариант, так как решил остановиться на потоковой гибернации, мне этого достаточно — так что проверенного варианта нет. Но там все довольно просто.
В дальнейшем используйте во всех тестах только этот метод и не используйте webDriver.findElement() напрямую.
Образец кода: protected WebElement findElement(By elementLocatorToFind) {
if(isSafari() || isChrome() || isIE()) {
// for example, use simple Thread.sleep(1000) inside
doDelayForMilliseconds(1000);
}
return webDriver.findElement(elementLocatorToFind);
}
Получение элементов (findElements)
Проблема и решение аналогичны поиску одного элемента.
Проверка существования элемента
Если вам необходимо проверить отсутствие элемента, рекомендуется использовать следующую конструкцию: findElements(elementLocatorToFind).
isEmpty();
Вот рекомендация JavaDocs:
findElement не следует использовать для поиска отсутствующих элементов, вместо этого используйте findElements(By) и утверждайте ответ нулевой длины.
Загрузка изображения или файла
Проблема: Есть желание протестировать загрузку файла, что скачанный файл соответствует ожидаемому, и если это картинка, то он действительно доступен по указанной ссылке.Аргументация: В 99% случаев вам это не нужно.
Спросите себя еще раз: что вы хотите протестировать? Я почти уверен, что все, что вам нужно знать, это то, что загрузка доступна.
Что ссылка активна, кнопка загрузки включена и статус ответа после начала загрузки 200. У вас нет задачи протестировать браузер и процесс его загрузки.
Также, если тесты выполняются на Selenium Grid, то вы не сможете скачать файл, а затем проверить его местоположение.
Файл будет загружен на Selenium Node, и вы проверите его в Selenium Hub. Это разные хосты, по крайней мере, в обычной практике.
Решение: Решение — выполнить обычный HTTP-запрос по ссылке, ведущей на файл на сервере, или ссылке, по которой сервер должен вернуть такой файл.
Если статус полученного от сервера ответа 200, ссылка верная, файл существует. Все остальные варианты я рассматриваю как невозможность скачивания файла.
Поскольку запросы часто должны содержать авторизованные файлы cookie, такие файлы cookie необходимо импортировать из webDriver.
Если одного статуса недостаточно, ничто не мешает прочитать весь InputStream из HttpEntity, а затем сравнить его содержимое со ссылкой, будь то сумма MD5 или какой-то другой метод.
Образец кода: // just look at your cookie's content (e.g. using browser) and import these settings from it
private static final String SESSION_COOKIE_NAME = "JSESSIONID";
private static final String DOMAIN = "domain.here.com";
private static final String COOKIE_PATH = "/cookie/path/here";
protected boolean isResourceAvailableByUrl(String resourceUrl) {
HttpClient httpClient = new DefaultHttpClient();
HttpContext localContext = new BasicHttpContext();
BasicCookieStore cookieStore = new BasicCookieStore();
cookieStore.addCookie(getSessionCookie());
localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
// resourceUrl - is url which leads to image
HttpGet httpGet = new HttpGet(resourceUrl);
try {
HttpResponse httpResponse = httpClient.execute(httpGet, localContext);
return httpResponse.getStatusLine().
getStatusCode() == HttpStatus.SC_OK; } catch (IOException e) { return false; } } protected BasicClientCookie getSessionCookie() { Cookie originalCookie = webDriver.manage().
getCookieNamed(SESSION_COOKIE_NAME);
if (originalCookie == null) {
return null;
}
String cookieName = originalCookie.getName();
String cookieValue = originalCookie.getValue();
BasicClientCookie resultCookie = new BasicClientCookie(cookieName, cookieValue);
resultCookie.setDomain(DOMAIN);
resultCookie.setExpiryDate(originalCookie.getExpiry());
resultCookie.setPath(COOKIE_PATH);
return resultCookie;
}
Очистка значения поля ввода
UPD: ниже, в комментариях, можно найти обсуждение.В результате метод, судя по всему, работает корректно, а причина скрывалась в разнице версий браузеров и еще в одном связанном с этим конфликте.
Но я решил не удалять описание этой проблемы, потому что.
Возможно, кому-то подобные методы тоже будут полезны, как в свое время они были мне.
Проблема: Иногда вам нужно очистить значение поля ввода.
Например, вам нужно заменить старое значение на новое.
WebDriver предоставляет для этого специальный метод: webElement.clear();
По моему опыту, этот метод не работает; более того, он выдает ошибку и прерывает тест.
Вам нужно найти другой способ очистить значение поля.
Решение: Есть несколько основных способов.
Первый способ — смоделировать действие «выбрать все» и сразу после этого отправить новое значение: inputElement.sendKeys(Keys.chord(Keys.CONTROL, "a") + Keys.DELETE + newValue);
Но у меня это решение работает не во всех браузерах и не всегда.
Второй способ — отправить количество символов возврата, равное длине старого значения.
Это решение некрасивое, но эффективное и гарантированно работает во всех браузерах.
Ниже публикую вариант, которым пользуюсь сам.
Там отдельно рассмотрена ситуация, когда браузер IE, а вход имеет тип file. Это особая ситуация.
При выполнении команды sendKeys для такого элемента IE заменит старое значение новым, а не допишет его в конец.
Поэтому очищать такое поле нет смысла.
Более того, такая попытка приведет к ошибке.
Либо из-за несуществующего файла (так как будет попытка найти файл по пустому пути), либо из-за попытки найти файл по пути, строковое значение которого будет равно символу возврата.
Образец кода: protected void clearInput(WebElement webElement) {
// isIE() - just checks is it IE or not - use your own implementation
if (isIE() && "file".
equals(webElement.getAttribute("type"))) {
// workaround
// if IE and input's type is file - do not try to clear it.
// If you send:
// - empty string - it will find file by empty path
// - backspace char - it will process like a non-visible char
// In both cases it will throw a bug.
//
// Just replace it with new value when it is need to.
} else {
// if you have no StringUtils in project, check value still empty yet
while (!StringUtils.isEmpty(webElement.getAttribute("value"))) {
// "\u0008" - is backspace char
webElement.sendKeys("\u0008");
}
}
}
Загрузка файла на сервер
Проблема: Вам необходимо загрузить файл на сервер, используя стандартные элементы HTML: <input name="uploadFile" type="file">
<input name="doUpload" value="Upload" type="button"/>
Решение: Рекомендую просто вынести это в отдельный универсальный метод и использовать его каждый раз, когда вам нужно что-то загрузить через такую форму.
Исключение: Драйвер Safari не полностью поддерживает загрузку файлов, потому что.
Насколько я понимаю, он основан на JavaScript. Появившееся окно выбора файла ставит его в тупик.
Таких сценариев нужно либо избегать, либо добиваться другим способом — созданием собственного HTTP-запроса или вставкой данных непосредственно на стороне сервера, если это возможно.
Образец кода: protected void uploadFile(By uploadInput, By uploadButton, String filePath) {
clearInput(uploadInput);
findElement(uploadInput).
sendKeys(filePath); findElement(uploadButton).
click();
}
Действия с элементами внутри iframe
Проблема: Если требуемый элемент находится внутри iframe, он недоступен из контекста по умолчанию.Вы не сможете обнаружить его в DOM, и webDriver выдаст исключение NoSuchElementException. Решение: Прежде чем взаимодействовать с этим элементом, вам необходимо переключить webDriver на контекст iframe элемента.
Насколько я понимаю, это связано с тем, что контекст страницы и контекст iframe на этой странице — это две разные модели DOM.
Образец кода: webDriver.switchTo().
frame(findElement(By.id("id_of_your_iframe"))); // do actions against inner web element, located in iframe webDriver.switchTo().
defaultContent();
// continue to do actions in default content
IE8. Проблема с XPath
Проблема: IE8 в свойственной ему эксцентричной манере иногда неправильно интерпретирует указатели элементов (By.id, By.xpath и другие).У меня были ситуации, когда он игнорировал спецификацию искомого элемента, в которой указан его атрибут класса.
Например, IEDriver отказывался различать два разных элемента, найденных с помощью таких локаторов, и отображал элементы, соответствующие обоим вариантам: findElement(By.xpath("http://div[@id='elementContainer']/div[@class='someProcessInProgress']"));
findElement(By.xpath("http://div[@id='elementContainer']/div[@class='someProcessFinished']"));
Я не смог понять, в каких ситуациях у него возникали проблемы.
Абсолютно идентичная ситуация произошла и с прямым указанием id элемента.
WebDriver делает вид, что его не существует. Решение: Если IEDriver имеет галлюциногенную ошибку при поиске элемента (но не других драйверов и браузеров), лучшее решение — изменить XPath. К счастью, благодаря гибкости XPath всегда существует множество возможных вариантов.
IE8. элемент не кликабельный
Проблема: IE8, в отличие от других браузеров, не всегда способен самостоятельно прокрутиться до элемента, если щелкнуть элемент, расположенный за пределами видимой части контейнера (слоя, таблицы и т. д.).В конечном итоге такое поведение приводит к ошибке.
Решение: Вам нужно прокрутить.
Единственный способ, который я нашел, это использовать JavaScript. На самом деле в WebDriver есть специальный механизм, призванный помочь с прокруткой к нужному элементу: new Actions(webDriver).
moveToElement(elementToScrollTo).
perform();
Но это не будет работать в IE8.
Образец кода: ((JavascriptExecutor) webDriver).
executeScript("container.scrollLeft=1000;");
Где контейнер — это идентификатор элемента, который необходимо прокручивать.
Те.
в нашем случае это div или таблица, внутри которой находится элемент. Как вы понимаете, этот скрипт будет прокручиваться горизонтально.
Firefox может умереть
Проблема: Драйвер Firefox мог бы стать примером для других драйверов, но у него есть один очень неприятный недостаток.Как можно понять из комментариев к разным версиям WebDriver, этот недостаток то исчезает, то появляется вновь от версии к версии.
Дело в том, что иногда демон находит Firefox и вдруг, без каких-либо внешних воздействий и изменений, как кажется, начинает зависать на ровном месте.
Выглядит это примерно так: вы наблюдаете, как уже отлаженный до совершенства тест успешно выполняется в окне браузера.
А потом при совершенно незначительном шаге или действии окно браузера просто исчезает. В журналах вы обнаружите следующую запись: Ошибка связи с удаленным браузером.
Возможно, оно умерло.
Всё, больше никакой информации вы не найдете.
Решение: Это регрессивная ошибка, и гарантированного лечения не существует. Он заключается в том, что между браузером и драйвером возникло недопонимание.
Например, поскольку ваш браузер обновился, вы не обратили на это внимания и продолжаете использовать старый WebDriver. Как я чувствовал, существует зависимость между Firefox и его драйвером.
Оно не является абсолютным, т.е.
не каждый раз при обновлении Firefox нужно бежать обновлять и веб-драйвер.
Но первое, что я советую вам сделать, это погуглить, какая версия веб-драйвера наиболее подходит для вашей версии Firefox.
В случае с Firefox 19 мне помогло обновление автономного сервера Selenium до версии 2.30.0.
Заключение
Я благодарен за этот опыт и за возможность работать с этим фреймворком.За последние месяцы XPath стал для меня как родной язык, и, возможно, скоро я смогу на нем писать.
Судя по всему, я получил довольно много знаний о том, как использовать Selenium и как это делать эффективно.
Но все же.
Мне бы не хотелось в будущем сталкиваться с подобными задачами.
Это крайне утомительно, отладка подобна агонии, иногда вынуждает писать плохой код, но самое страшное, что тестируемый веб-клиент будет модифицирован.
Я гарантированно знаю, что оно будет изменено.
И это еще один болезненный момент. Поэтому, если вы решите писать серьезные тесты на этой платформе, подготовьтесь психологически.
Теги: #selenium 2.0 #webdriver #тестирование веб-приложений #java #разработка веб-сайтов #тестирование ИТ-систем
-
Нло: Вторжение Инопланетян
19 Oct, 24 -
Wp Text Ads - Рекламный Скрипт
19 Oct, 24 -
Как Ит Могут Помочь В Борьбе С Коррупцией
19 Oct, 24 -
Супер-Жадные Квантификаторы
19 Oct, 24