Selenium 2.0 — WebDriver. Впечатления, проблемы и советы по использованию

Введение


Последние три месяца мне пришлось работать с Selenium 2.0 (WebDriver).
В данной статье я опишу свои впечатления, мысли и опыт, который я приобрел.
Так же я опишу основные действия, которые чаще всего вызывают проблемы и покажу наиболее удачные решения, которые я смог реализовать для них. Возможно есть более правильные подходы — буду рад если оставите их в комментариях.


Коротко о Selenium

Библиотека Selenium позволяет производить тестирование графических web интерфейсов. Ее принцип — максимально точно симулировать деятельность пользователя. По сути — это написание бота, который бегает по страничкам сайта, производит действия и проверяет ожидаемый результат. Selenium 2.0 реализует сообщения с браузерами посредством специальных драйверов. В отличие от Selenium 1.0 он не использует JavaScript, а сообщается напрямую с API браузеров.

Что у меня получилось реализовать

Получилось написать тесты на основе JUnit и Selenium 2.0, объединенные в одно приложение. Данное приложение может выполняться на Selenium Grid — это сеть, во главе с Selenium Hub, который принимает и распределяет поступившие задачи тестирования по своим Selenium Nodes. На различных Selenium Nodes могут быть настроены любые требуемые браузеры. Используемые драйвера — родные драйвера для каждого браузера.

Часть первая. Впечатления


Разное поведение в разных браузерах

Под браузерами я понимаю основные: Firefox, Google Chrome, Opera, Safari, IE8, IE9
Чтобы один и тот же код срабатывал одинаково успешно в разных браузерах, нужно потратить огромное количество времени. Порой нужна железная воля, чтобы не бросить это гиблое дело. В этом плане самые послушные браузеры — Firefox и Google Chrome. По моему личному опыту — жизненно необходимо, чтобы тест мог менять поведение в нужных местах в зависимости от того, какой браузер в данный момент используется. Т.е. он должен иметь информацию о среде, в которой он проходит.

Совет:
Старайся не использовать в тестах объект webDriver напрямую! Создай методы-обертки вокруг основных необходимых тебе методов. Проще менять поведение в одном месте, чем повсеместно в коде всех тестов.


Selenium 2.0 — сырой продукт

Читая множество постов на Stackoverflow в поисках best practices или просто решения проблемы, постоянно натыкаешься на workaround'ы. Причин несколько: различия в работе драйверов браузеров, не выполнение драйверами контракта требуемого функционала, наличие ошибок в версиях, наличие прямой зависимости версии браузера от версии драйвера. Порой он способен просто ронять тест на голом месте (с точки зрения пользователя API) — элемент есть, но он его не видит. По моему опыту много плавающих ошибок, которые намеренно воспроизводятся только через раз при абсолютно схожих условиях и действиях. С Firefox вообще иногда начинается лихорадка и браузер может просто закрыться с примерно таким сообщением: Error communicating with the remote browser. It may have died. Крайне тяжело найти причину, если она вообще доступна пользователю Selenium. По этому порой ситуация беспомощная — просто не работает функционал.

Совет:
Такой прескорбный расклад дел заставляет менять поведение тест-кейса. К счастью, одни и те же вещи в GUI клиентах зачастую можно выполнить либо разной последовательностью, либо разным способом. В случае если ты не смог найти решения погуглив — попытайся подобрать другое поведение, которое будет успешно отработано. Не замыкайся на конкретном действии, если на то нет особой необходимости.


Тесты Selenium — зависимые тесты

Это означает, что если дополнительно не позаботиться, действия одного теста могут влиять на результат другого теста. Это вполне очевидно, пользователь в том числе изменяет данные в процессе своей активности. Тестируя подобный функционал, вы вынуждено будете изменять начальные данные. Если от этого зависят другие тесты и вы не вернули данные в исходное состояние — или не смогли этого сделать по причине того, что тест прервался ошибкой — другой тест может тоже сломаться. Этакий принцип домино. Когда впервые осознаешь это, становится очень больно. Руки опускаются…

Совет:
Если есть возможность воспроизводить условия теста независимо, т.е. есть прямой доступ к тестируемому приложению и нет преград для разворачивания исходных тестовых данных — вам повезло, изолируйте свои тесты подобным образом — подготавливая данные до теста и очищая их после. К примеру, в восстановлении данных в БД может помочь инструмент Liquibase.
Скорей всего такой возможности нет. В этом случае выход один — помимо самих тестируемых действий, описывать с помощью Selenium так же и действия по их «откатке». Т.е. если пользователь удалил сущность — в конце теста ее необходимо заново создать или загрузить.
Это грешный путь. Так как подобные действия тоже уязвимы и тоже могут оборваться ошибкой, не выполнив своего предназначения.


Тесты Selenium — медленные тесты

Нужно быть готовым, что последовательный прогон большого набора тестов для всех браузеров может занимать большое количество времени, измеряемое в часах (от 30 минут — до 2-3 часов). Это делает все что я описал выше трагедиями и порой похоже на издевательство. Причина в том, что тесты сильно насыщены различными ожиданиями, поиском элементов и прочими медленными действиями.

Совет:
Тестируйте только то, что действительно надо тестировать. Из всех возможных работающих вариантов реализации одного и того же действия — выбирайте наиболее быстрый.


Selenium IDE — не помощник

Selenium IDE — специальный плагин для Firefox, который способен записать все выполняемые действия пользователя в виде скриптов. Там же есть возможность экспорта составленных скриптов в различные языки и в два формата: Selenium 1.0 (RC) и Selenium 2.0 (WebDriver).
В большинстве случаев бесполезная вещь.

Проблемы:
  • генерируемый код — не читаем
  • генерируемый код — не рабочий, в случае сложного интерфейса, в силу всех вышеописанных особенностей
  • в случае если id элементов (div, table, span, input) автоматически генерируемые — предлагаемые на выбор XPath указатели не подходят
  • большое количество тестов (5 тестов уже достаточно) заставит встать на правильный путь джедая и составить собственную реализацию часто выполняемых действий — и далее использовать их как унаследованный метод. Единожды описанный и отточенный. Как только образуется такой набор методов — польза от IDE резко падает. Ей нельзя указать использовать свои методы — среда разработки будет генерить свои собственные неработающие неидеальные шаблоны. Просматривать потом сгенерированный код и замещать все необходимые места со временем сводится к полному переписыванию этого кода. Эту же мысль можно продолжить единым «справочником»-перечнем всех XPath локаторов ключевых элементов. Как только все подобные локаторы вынесены в константы или в отдельный справочник — проще становится использовать их, чем в очередной раз проверять — что там нагенерила среда разработки


Польза:
Единственная польза, которую я постоянно ощущаю — возможность проверить XPath указатель. Очень удобная функция — если указатель верный и такой элемент существует — он подсвечивается рамочкой.

Совет:
Поиграйтесь с IDE, поймите суть Selenium, можете даже пописать тесты с ее помощью. Но как только почувствуете, что пользы меньше, чем затрат — начинайте делать собственные заготовки. Аккумулируйте их в общем абстрактном классе-предке или в утилитном классе. Начиная с определенного момента ваши тесты могут превратиться просто в перечисление таких методов, разбавленные проверками результата и текущего состояния.


Часть вторая. Практические решения возникающих проблем (Java)


Нижеописанные решения не являются красивыми, идеальными, они могут вызывать отторжение, но они являются рабочими. По моему опыту они избавляют от проблем. Надеюсь они принесут пользу и избавят от потерянных часов и дней.

Получение элемента (findElement)

Проблема:
WebDriver предоставляет механизм для поиска и получения сущности WebElement:
webDriver.findElement(By.id("elementId"));

Теоретически, на поведение этого метода влияет параметр 'implicit wait' который можно указать при построении самого webdriver. К примеру, так:
webDriver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);

Опять таки, теоретически, это должно явно заставлять webdriver искать элемент в течении указанного времени и ждать либо пока не появится искомый элемент, либо пока не закончится указанный таймаут. Кстати, данный таймаут судя по всему можно выставлять только единожды.
На практике, происходит нечто странное. Пауза выдерживается, но есть внутреннее ощущение, что поиск если и идет по DOM модели, то обновления этой DOM модели не происходит. Для некоторых браузеров получается другая ситуация — элемент уже есть в DOM модели, но еще не отрисовался или отрисовался частично (Google Chrome). WebDriver возвращает найденный наполовину отрисованный элемент и событие click попадает в еще неотрисованные координаты. Метод 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 should not be used to look for non-present elements, use findElements(By) and assert zero length response instead.


Скачивание картинки или файла

Проблема:
Есть желание протестировать скачивание файла, что скачанный файл соответствует ожидаемому, а если это картинка — она действительно доступна по указанной ссылке.

Рассуждения:
В 99% случаев вам это не нужно. Еще раз задайтесь вопросом, что вы хотите протестировать? Я практически уверен, что вам достаточно знать, что загрузка доступна. Что ссылка активна, кнопка загрузки enabled и статус ответа после начала скачки равен 200. У вас нет задачи тестировать браузер и его процесс скачивания.
Так же, если тесты проходят на Selenium Grid — то у вас не получится скачать файл и проверить после этого его местонахождение. Файл скачается на Selenium Node, а проверять вы его будете на Selenium Hub. Это разные хосты, по крайней мере в обычной практике.

Решение:
Решение заключается в том, чтобы выполнить обыкновенный HTTP запрос по ссылке ведущей к файлу на сервере, либо по ссылке, по которой сервер должен вернуть такой файл. Если статус полученного от сервера ответа 200 — ссылка верная, файл существует. Все остальные варианты я рассматриваю как недоступность скачивания файла. Так как зачастую запросы должны иметь при себе авторизованные cookies — такие cookies необходимо импортировать из 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;
    }


Очистка значения поля input

UPD: ниже, в комментариях, можно найти обсуждение. По итогу, метод, видимо, отрабатывает корректно, а причина скрывалась в разнице версий браузера и в другом связанном с этим конфликте. Но решил не удалять описание этой проблемы, т.к. возможно для кого-то подобные способы тоже будут полезны, как для меня в свое время.

Проблема:
Порой, требуется выполнить очистку значения поля типа input. К примеру, необходимо заменить старое значение новым.
WebDriver предоставляет для этого специальный метод:
webElement.clear();

По моему опыту данный метод не работает, более того выбрасывает ошибку и ломает тест.
Требуется найти другой способ очищения значения поля.

Решение:
Есть несколько основных способов.
Первый способ — моделировать действие «выделить все» и сразу после этого выслать новое значение:
inputElement.sendKeys(Keys.chord(Keys.CONTROL, "a") + Keys.DELETE + newValue);

Но данное решение у меня работает не на всех браузерах и не всегда.

Второй способ — выслать количество символов backspace, равное длине старого значения. Данное решение некрасиво, но оно эффективно и работает гарантированно во всех браузерах.
Ниже я публикую вариант, которым пользуюсь сам. В нем есть отдельное рассмотрение ситуации, когда браузер — IE, а input с типом file.
Это особенная ситуация. При выполнении команды sendKeys к такому элементу IE заменит старое значение, новым, а не допишет его в конец. Поэтому делать очистку такого поля смысла нет. Более того, подобная попытка приведет к ошибке. Либо по причине несуществующего файла (так как будет попытка найти файл по пустому пути), либо по причине попытки найти файл по пути, строковое значение которого будет равно символу backspace.


Пример кода:
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 Driver полностью не поддерживает загрузку файла, т.к. насколько я понял, он javascript-based. Появляющееся окно с выбором файла ставит его в тупиковое положение. Такие сценарии нужно либо избегать, либо добиваться результата другим способом — создавать собственный 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 и другие).
У меня возникали ситуации когда он игнорировал уточнение для искомого элемента указывающее на его атрибут class.
К примеру IEDriver отказывался различать два разных элемента, найденных по таким локаторам и выводил элементы подходящие обоим вариантам:
findElement(By.xpath("//div[@id='elementContainer']/div[@class='someProcessInProgress']"));
findElement(By.xpath("//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;");

Где container — это id элемента, который необходимо прокрутить. Т.е. в нашем случае — div или table, внутри которого располагается элемент. Как можно понять, данный скрипт выполнит прокрутку по горизонтали.

Firefox may die

Проблема:
Firefox Driver мог быть примером для остальных драйверов, но у него есть один очень неприятный недостаток. Как можно понять по коментариям к разным версиям WebDriver'a — этот недостаток, то исчезает, то вновь появляется от версии к версии.
Суть в том, что иногда на Firefox находит бес и он вдруг, без каких либо внешних, как это кажется, воздействий и изменений, начинает падать на ровном месте.
Выглядит это примерно так: вы наблюдаете, как в окне браузера успешно выполняется уже отлаженный до блеска тест. И тут на абсолютно мелочном шаге или действии окно браузера просто исчезает. В логах вы обнаруживаете такую запись: Error communicating with the remote browser. It may have died. Все, больше никакой информации вы не найдете.

Решение:
Это регрессивная ошибка и гарантированного лекарства быть не может. Заключается она в том, что между браузером и драйвером возникает непонимание. К примеру по причине того, что ваш браузер обновился, вы не обратили на это внимание и продолжаете использовать старый WebDriver. Между Firefox и его драйвером есть, как я это ощутил, зависимость. Она не абсолютная, т.е. не всякий раз когда Firefox обновился, нужно бежать обновлять вебдрайвер тоже. Но первое что я посоветую сделать — погуглите, какая версия вебдрайвера наиболее подходит под вашу версию Firefox.
В случае с Firefox 19 мне помогло обновить stand-alone-server селениума до версии 2.30.0.


Заключение


Я благодарен за такой опыт и за возможность поработать с этим фреймворком. За последние месяцы XPath для меня стал как родной язык, наверное скоро переписываться на нем смогу. Судя по всему, я получил достаточно много знаний о том, как использовать Selenium и как это делать эффективно.

Но все же… Я не хотел бы в будущем сталкиваться с задачами такого рода. Это крайне утомительно, отладка подобна мукам, заставляет порой писать плохой код, но больше всего страшно, что тестируемый web-клиент будет видоизменен. Я гарантированно знаю, что он будет изменен. И это еще один болезненный момент.

Поэтому, если вы решите писать серьезные тесты на этой платформе — подготовьтесь психологически.
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 27

    –1
    Тесты Selenium — зависимые тесты

    Тесты должны быть независимыми друг от друга и подчищать за собой. Странно что этот базовый принцип вы открыли для себя с такой болью.
    Тесты Selenium — медленные тесты

    Они медленны настолько насколько медленно ваше приложение.
    Selenium IDE — не помощник

    Откройте для себя Developer tools различных браузеров и не мучайтесь с этим «IDE»
    Получение элемента (findElement)

    Откройте для себя Explicit Waits с ожиданием состояния.

    WebDriver далеко не идеален, однако он вполне юзабелен. И я бы не стал говорить о том что вы «эффективно использовали» Selenium после фразы «больше всего страшно, что тестируемый web-клиент будет видоизменен». ;)
      +3
      В самой статье я постарался описать те разочарования, которые ожидают разработчика, впервые столкнувшегося с подобной задачей. Так что тут вполне могут быть описаны наивные вещи.

      Странно что этот базовый принцип вы открыли для себя с такой болью.

      Мне стало больно скорее от того, что, конкретно в случае с Selenium, возможность «подчистить», в большинстве случаев, сводится к дополнительным шагам, аналогичным по природе тестовым действиям. А такие действия уязвимы для описанных проблем. Т.е. большинство раз тест отработает, но могут случиться исключения. Более того, такие неэффективные методы приносят дополнительные достаточно существенные временные затраты. Вот, видимо, такая ненадежность меня и удивила, во время первого опыта.

      Откройте для себя Developer tools различных браузеров и не мучайтесь с этим «IDE»

      Именно к этому я и пришел. Большую часть времени разработка проходит в процессе подбора XPath ключей, использовании различных плагинов, файр-багов и обычной JS консоли.

      WebDriver далеко не идеален, однако он вполне юзабелен. И я бы не стал говорить о том что вы «эффективно использовали» Selenium после фразы «больше всего страшно, что тестируемый web-клиент будет видоизменен». ;)

      На своем уровне познания Selenium я не смог придумать, как, не изменяя исходные кода веб-клиента, отвязать процесс тестирования от жестких завязок на структуру элементов. Я стараюсь их минимизировать, но идеальная ситуация, когда к любому элементу можно обратиться по уникальному ID. К сожалению, для меня это невозможно. По крайней мере, я не понимаю как это сделать :)
      +1
      > Они медленны настолько насколько медленно ваше приложение.
      Не совсем. У нас вот приложение быстрое, а тесты на селениуме гоняются долго. Почему? Потому что любой hover, нажатие на кнопку — передача сообщения по сети, выполнение в браузере (и так не для одного браузера) и ожидание ответа обратно. Если front-end'а и тестов на него в приложении много, тесты тоже будут выполняться долго, в сравнении с unit-тестами на порядок больше.
        0
        Сравнивать тесты через Selenium и простые юнит тесты по скорости полностью не корректно :) Говоря о быстроте приложения я, как ни странно, и имел ввиду отклик уже на стороне пользователя.
      +1
      Недавно прикручивал selenium web driver к приложению. Под visual studio тесты гоняются на ура, изолированность присутствует, всё довольно быстро (конечно упирается в скорость приложения). Лучшим себя показал Chrome driver, а IE категорически отказывался делать клики элементам.

      Это крайне утомительно, отладка подобна мукам, заставляет порой писать плохой код, но больше всего страшно, что тестируемый web-клиент будет видоизменен. Я гарантированно знаю, что он будет изменен. И это еще один болезненный момент.

      Если id контролов гарантировано не поменяются, то большой проблемы в изменениях нет.
      Для улучшения кода можно сделать карту страницы — описать класс с нужными контролами и их идентификаторами и обёртки по поиску элементов на страницах. Дальше работать со всем этим в «объектно подобном» стиле.
        +1
        Я в конечном итоге пришел к функциональным слоям:
        — абстрактный рутовый слой, содержащий всю низкоуровневую работу с веб-драйвером (построение, обертки над методами WebDriver API)
        — слой навигации (просто набор методов goToMainPage, goToSearchTab)
        — слой частых действий или действий, которые требуются более чем в одном тесте
        — слой локаторов — огромный, строк на 300, справочник констант с объектами типа By. Есть желание сделать его вообще в виде внешнего property файла, но это уже детали.

        Пока не могу сказать, насколько это удачное решение — видно станет, когда мне предстоит передать этот код на поддержку заказчику :)))
        +2
        Понимаю и уважаю вашу боль…

        У меня C#/NUnit/TeamCity

        В процессе работы с Selenium я пришел к мысли, что не важно как получать доступ к WebElement-у, важно что и как с ним дальше делать. Исходя из этого, в своих тестах, я стараюсь делать сложные выборки с помощью JS кода. Выходит гораздо более стабильно и точно (хотя этому способствует ExtJS, на котором написан front-end). Даже если я получаю элемент, недоступный, скажем, для нажатия, то в момент нажатия правда вскрывается и тест законно падает. Зато бубнов нет с селекторами, ожиданиями и прочей болью.

        Если посмотреть лог файл после тестов, то видно все низкоуровневые команды от Selenium к браузеру. Там видно как он делает это ожидание, так что ничего плохого в собственных велосипедах по этой части нету.

        Все что пулается от Selenium к браузеру может потеряться. По этому не грех повторять действия, а на JS команды вешать хитрые маркеры, чтоб понять дошли ли они, интерпретировались ли.

        Снимать скриншоты — великое добро.

        В API, через которое пишут тесты, хорошо бы встроить дополнительную информацию о шагах теста. Помогает понять что происходило и где упало. Причем это можно формировать автоматически.

        Бить нещадно лопатой в голову разработчиков, которые пишут ассерты, но не пишут почему так должно быть, что они проверяют. Когда такие разработчики закончатся, можно приниматься за тех, которые не пишут кейсы к своим тестам, не поясняют что и как они проверяют, а главное почему.

        Лямбды, фанки и подобное сильно способствует ускорению теста за счет упрощения кодирования ожиданий.

        «Error communicating with the remote browser» — встречал и под ChromeDriver :\ Честно сказать есть некоторый страх при обновлении драйвера или тестового браузера, что очень плохо.

        Самая большая проблема возникла с разработчиками тестов. Я им предоставил API, они пишут тесты, тесты выполняются через NUnit. Проблема в том, что они пишут их как Unit тесты, а не как какие-то большие интеграционные тесты. Получается полная хрень, типа тест на 30 секунд, который проверяет нажимается ли кнопочка в выпадающем меню. При этом эта функция используется во всех тестах, но есть специальный тест, который типа это проверяет. В общем сдвинуть парадигму восприятия нереально трудно. С таким подходом тестовый билд (TeamCity) пухнет по времени до часу, а по факту проверяет элементарные, скучные вещи, причем стопицотраз дублировано. Беда.

        Очень интересно как вы разворачивали Selenium Grid. У нас тесты есть, но мы их на гриде не параллели, скоро предстоит, может поделитесь опытом/ссылками? :)
          +1
          По поводу Selenium Grid, все необходимое я нашел здесь:
          code.google.com/p/selenium/wiki/Grid2

          Принцип простой — в одной сети должны быть Selenium Hub и одна или несколько Selenium Node. По сути различия в структуре нет — это один и тот же jar файл, запущенный в разных режимах. (к примеру такой: selenium-server-standalone-2.30.0.jar).

          Selenium Hub — точка, куда необходимо отправлять запрос на выполнение теста. Хаб распределяет задачи между своими нодами (исполнителями), учитывая наличие у них свободных слотов и сравнивая соответствия (capabilities) пришедшего теста и свободного слота. Если подходящий слот не найден — тест будет оборван.

          Настройка всей сетки проста и не занимает много времени. Для профилирования тестов по отдельным браузерам надо создавать инстансы вебдрайверов — соответствующих этим браузерам, все они являются расширениями RemoteWebDriver'а.

          Важно держать в уме, что параллельные тесты будут одновременно менять данные, тем самым есть огромная вероятность, что тестирование одного и того же инстанса веб-клиента — не принесет успеха, будет постоянно падать с ошибками. Нужно либо предоставлять набор таких инстансов (включая серверную часть — целиком воспроизводить всю инфраструктуру) для каждой ноды, либо, если клиент это поддерживает — разделять зоны в зависимости от разных аккаунтов. Еще можно пытаться составить матрицу методов, которые гарантированно не влияют друг на друга даже в пессимистичных сценариях и полагаясь на это гонять их параллельно.

            +1
            Кстати, еще вспомнил один момент — ноды необходимо запускать так, чтобы они могли создать в родном сеансе окно браузера. Именно графическое окно.
          0
          В самой статье я постарался описать те разочарования, которые ожидают разработчика, впервые столкнувшегося с подобной задачей. Так что тут вполне могут быть описаны наивные вещи.

          Если честно то я не вижу ни одного реальной причины для разочарований.
          Различное поведение имплементаций? — Так WebDriver это всего лишь интерфейс для взаимодействия с браузером. Конкретную имплементацию разрабатывают конкретные люди и естественно могут быть разные поведения.
          Проблемы с ожиданием? — Так используйте полные возможности а не только implicitlyWait. Кстати Thread.sleep(1000) это просто офигенный костыль к которому можно прибегать только в экстренных случаях.
          Проблемы с изменением локаторов в процессе разработки? — Используйте Page Object и другие связанные паттерны и средства.
          Мне стало больно скорее от того, что, конкретно в случае с Selenium, возможность «подчистить», в большинстве случаев, сводится к дополнительным шагам, аналогичным по природе тестовым действиям. А такие действия уязвимы для описанных проблем. Т.е. большинство раз тест отработает, но могут случиться исключения. Более того, такие неэффективные методы приносят дополнительные достаточно существенные временные затраты. Вот, видимо, такая ненадежность меня и удивила, во время первого опыта.

          Установка предусловий и последующая подчистка через selenium это плохая практика. Нужно стараться от этого максимально уходить.
          Именно к этому я и пришел. Большую часть времени разработка проходит в процессе подбора XPath ключей, использовании различных плагинов, файр-багов и обычной JS консоли.

          А можно ещё и другими локаторами пользоваться ;) А умение работать с Fire Bug, Chrome Developer Tools, Dragonfly и IE Developer Tools это очень важно и я, честно, не вижу ни какой проблемы в этом.
            +1
            Selenium 2.0 — сырой продукт


            Он не сырой, просто количество поддерживаемых версий браузеров и многочисленные «особенности» самих браузеров никогда не позволят сделать его мега-стабильным «на века».

            Тесты Selenium — зависимые тесты


            Тут Selenium ни при чем. Любые функциональные и почти всегда интеграционные тесты могут стать зависимыми, если вы не приложите усилий по обеспечению независимости по данным. Есть много техник. Надежнее всего найти ключевой разделитель областей видимости (логин, адрес электронной почты, имя и т.д.), а затем генерировать его уникальным в каждом тесте. Можно пользоваться пулами, вставками наборов данных в БД, вызовами API напрямую и другими способами. Было бы желание…

            Тесты Selenium — медленные тесты


            Медленные они из-за ваших пауз в коде, за которые надо отрывать руки. А в остальном скорость упирается в скорость работы вашего приложения и в железо. С помощью грида можно очень сильно параллелить тесты и запуск будет очень быстрым. А еще есть SSD, RAM disk, облака с возможностью дешево создать много нод для грида…

            Selenium IDE — не помощник


            Не лучший инструмент для проверки XPath. Firefinder или Firepath куда лучше.

            Получение элемента (findElement)


            Тут вообще без комментариев. Вставлять паузы недопустимо. Вы можете воспользоваться explicit wait — это цикл до выполнения условия с минимальными паузами между проверками. Но лупить паузу в секунду на каждый поиск элемента — это жесткая жесть!

            Очистка значения поля input


            Вы не могли бы опубликовать пример, на котором у вас метод не работает. В идеале как issue для разработчиков WebDriver. Потому что на моем опыте все работает. Альтернативный вариант — использовать JavaScript. Будет быстро, но не так честно.

            Firefox may die


            В основном эта ошибка встречается у тех, кто не отключает автообновления в Firefox. Версию для тестирования надо выбирать аккуратно. Ну и обязательно проверять версию WebDriver на совместимость. Вы тестируете прежде всего функциональность а не кроссбраузерность. Не забывайте об этом. Для кроссбраузерности можно подобные ошибки перехватывать и повторять тест.

              +2
              По поводу Thread.sleep(1000);
              Я вспомнил с чем связан такой варварский подход.

              Суть в том, что это пауза не только для того, чтобы элемент появился в DOM модели и стал доступен для выборки. Суть в том, что элементу еще необходимо успеть отрисоваться, перед тем как с ним начнут далее взаимодействовать (click()). Я пробовал делать перед действием проверку isDisplayed(), но с огромной постоянностью тесты падали на начальном этапе с сообщениями, что элемент не доступен для действия click, так как невидим. Т.е. эта проверка возвращала true еще до того, как элемент полностью отрисуется. Видимо, она возвращает true, как только нет никаких причин считать элемент невидимым с точки зрения значений его стилей, атрибутов, а не визуального отображения, но это догадка.
              После поиска альтернативного решения я попробовал все возможные ExpectedConditions, обернутые в WebDriverWait с достаточными таймаутами и периодами, чтобы элемент успел отрисоваться и даже больше. Но по сути это всего лишь обертка над тем же самым isDisplayed. Элемент возвращается неотрисованным.
              Делать свою реализацию, которая пытается в таком цикле выполнять действие, пока не добьется успеха, а все исключения ловить и игнорировать — мне показалось тоже грязным вариантом, который при этом не достаточно понятен и читаем, в ситуации если требуется передать код на поддержку третьим лицам.
              С другой стороны… хм, можно реализовать WebDriverWait, который не ждет, когда элемент будет отрисован, а просто ждет указанное время — аналогично и взамен Thread.sleep(1000);
              Если делать подобные захардкоженые паузы — в принципе плохо, не суть важно будь это спящий поток или цикл с таймингом — дайте плз знать.

              Вообщем, у меня нет ответа на вопрос, как это сделать правильно. Если знаете выход — отпишите плз, т.к. все варианты на stackoverflow я уже перечитал.

              Вы не могли бы опубликовать пример, на котором у вас метод не работает.

              Вы имеете в виду webElement.clear()? или выделение текста и его последующее замещение?
              Ок, проверю еще раз оба варианта и выложу результаты, дабы не было заблуждений, но уже завтра :)
                +1
                Интересно глянуть конкретный пример, где это не срабатывало (упрощенный код страницы и тест). Можно в личку. Просто я очень много раз видел такое, что локатор написан неверно или же постоянно обновляется DOM и элемент устаревает. Обсуждать абстрактного коня в вакууме очень тяжело. Пауза в секунду — не выход в любом случае, потому что завтра секунды может не хватить и что тогда? Ставить 5 секунд? :) Если такая беда случается, то лучше уже кликнуть на элемент из JavaScript или написать кусочек JavaScript, который убедится, что элемент уже доступен. Просто как-то догадка с «прорисовыванием» звучит странно. Другой вопрос, что этот элемент может появляться «с хаками», когда он сначала добавляется в DOM, а потом на него навешивают кучу всего. Надо смотреть.

                Тоже самое и с clear(). Выделение с замещением — это известный обходной маневр. Мне не приходилось видеть чтобы clear() не работал. Если примеры будут, то я могу обратиться к разработчикам WebDriver знакомым и показать им.
                  0
                  Прошу прощения что задержался с ответом — был отпуск :)

                  Сделал демку, webElement.clear() — срабатывает. Попробовал отработать его в собственном проекте — тоже срабатывает.
                  На данный момент рабочая версия webdriver'а — 2.30.0.
                  В качестве webElement выступает элемент input с type=«text»

                  Возможно проблема с этим методом была в версиях, на которых я стартовал разработку тестов — 2.24, 2.25, 2.26, т.к. от этого метода я отказался в начале разработки.

                  Вообщем странная ситуация, я точно помню что выпадала ошибка и точно помню, что находил на stackoverflow комментарии о проблеме с этим методом. К сожалению в данный момент мало времени, чтобы эксперементировать, но если оно появится — попробую воспроизвести на старых версиях.

                    0
                    Нашел в старых логах сообщение падавшей ошибки:
                    Caused by: org.openqa.selenium.InvalidElementStateException: Element must not be hidden, disabled or read-only

                    При этом элемент отображался, был виден и не имел никаких атрибутов, скрывающих его.

                    Но, повторюсь, лучше проверить на старых версиях
                      0
                      Вероятнее всего был другой элемент, который его перекрывал просто. Такое часто встречается.
                        0
                        Сейчас понял в чем была проблема. Я тогда о ней еще не знал, поэтому не запомнил. Практически гарантированно, что проблема была в несоответствии версии вебдрайвера и версии браузера firefox. Только что опять напоролся, после обновления firefox.
              0
              Есть опыт написания тестов под WatiN, ситуация аналогичная. Нестабильно, нужна куча костылей, долго. Плюс только под IE. В какой-то момент коллеги перешли на Selenium, но я так понял, что мало что улучшилось :)
                0
                Для Selenium WebDriver существует несколько библиотек-обёрток, в той или иной мере решающие описанные вами проблемы: Thucydides, HtmlElements, Selenide.

                Первые две являются фреймворками, заставляющими вас писать структурированные тесты с использованием Page Objects, Step Object и т.п. Я лично советую библиотеку Selenide, т.к. она позволяет писать тесты проще и надёжнее.

                См. selenide.org
                  0
                  По-моему TestNG больше подходит для работы в паре с Selenium 2 чем JUnit.
                    0
                    Аргументируйте?
                      0
                      Нормальная возможность настраивать зависимости\последовательности исполнение тестов, группировка тестов. Совсем не спец в тестировании (разработчик), но не так давно мне пришлось сделать небольшое тестирование через UI, где как раз возможности TestNG пригодились (начинал с JUnit, но не хватило функицонала), и это нормально по-моему тк JUnit для классических юнит тестов (независимых друг от друга). Зачем использовать JUnit если у TestNG есть все тоже самое плюс много других плюшек.
                        0
                        Так UI тесты в этмо плане ничем от юнит-тестов не отличаются: все тесты тоже должны быть независимыми.
                          0
                          Во первых зависимость тестов была необходима, так если один тест не прошел (верхнего уровня, допустим переход по меню в определенный сеанс), то и следующие уже выполнятся не должны. Также в моем случае последовательность выполнения тестов тоде была важна. Группировка тестов как бонус.
                        0
                        Лично мои аргументы за TestNG — возможность легко и гибко настраивать группы тестов, а также простота запуска тестов в несколько потоков.
                          0
                          да кстати про распараллеливание тестов забыл

                    Only users with full accounts can post comments. Log in, please.