WebDriver + EventListener + waitForAngular

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

Как решение есть библиотека ngWebDriver на джаве, которая имеет метод waitForAngular который исполняет джаваскрипт взятый с протрактора и проверяет готовность ангуляра.
Это может помочь сократить кастомные вейтеры и унифицировать подход.
Остаётся момент что этот вейтер надо вставлять везде, где автотест может бежать вперёд приложения.

Наткнулся на WebDriverEventListener и возникла идея встроить вызов waitForAngular в методы как beforeFindBy, afterClickOn и автоматизировать синхронизацию, как это сделано в протракторе.

Понятно что этот вейтер будет кушать какое-то время, но не заметно чтобы это было что-то серьёзнее пол секунды\секунда на действие.

Хотел бы услышать мнение о потенциальных проблемах которые это может нести.
Спасибо

2 лайка

В принципе решение правильное.

Правда, есть один нюанс: в библиотеке Selenide это уже сделано.
Она автоматически ждёт всего, чего нужно, на каждом действии.

Не нужно ничего изобретать. :slight_smile:

1 лайк

действительно

@Override
 public NgWebElement findElement(final By by) {
    this.waitForAngular();
   return new NgWebElement(this, this.driver.findElement(by));
 }

и т.д. очень помогает

см. jProtractor

для “classic” WebDriver наверное многие делают декораторы

private List<WebElement> findElements(String selectorKind,
               			String selectorValue, WebElement parent) {
        		SearchContext finder;
/// ....
if (selectorKind == "css_selector") {
/// ...
try {
 		wait.until(ExpectedConditions.visibilityOfElementLocated(By
        						.cssSelector(selectorValue)));
        			} catch (RuntimeException timeoutException) {
        				return null;
        			}
        			elements = finder.findElements(By.cssSelector(selectorValue));

Ждет, пока контроллер ангуляр выполнит асинхронный запрос, распарсит результат, закинет в скоуп и проапдейтит биндинги в DOM? Бггг…

С учетом того, что среднее время поиска элемента занимает 50мс получите 10/20-ти кратную прибавку - если у вас “шустрое” окружение и тесты - заметите сразу.

@vmaximv Да, почти.
Там более универсальный механизм: Selenide ждёт, пока нужное изменение появится на экране. А уж как оно туда попало - через ангуляр или как-то по-другому - ему фиолетово.

Просто и надёжно.

1 лайк

Открою вам секрет, что это необходимое но, зачастую, далеко недостаточное условие.
Так же искренне надеюсь, что вам доводилось использовать angular/react или другие фреймворки-рендеры SPA и вы в курсе “подводных камней” :slight_smile:

В дополнение о Selenide, он действительно ждёт чего нужно на странице, но лучше опредлённо указывать чего ждать у элемента. Как правило следует ожидать: появления css класса или атрибута, или значения атрибута. Это очень просто выполняется с помощью методов should(), к примеру button.shouldHave(Condition.cssClass(“ng-visible”)); или обратный: shouldNotHave(cssClass(“ng-hide”)). Или же ожидать атрибутов.

Ну кончено protractor по умолчанию ждёт чего надо, а селенид будет ждать доступности для манипуляции над элементом, но может быть такое что элемент доступен, но асинрхонный запрос не успел обработаться и кликнет в него. И как на тех сайтах успел заметить, то всегда когда элемент полноценно готов к работе, ему добавляется уникальный css class или дата атрибут - и ожидая именно их - можно добиться стабильности

2 лайка

У нас, например, изначально был проект на ангуляре и теперь мигрировали на react. Selenide справляется со своей задачей отлично. Главное при написании теста знать чего ожидать - какие условия определяют готовность элемента к манипуляциям, ну а подводные камни всегда были и будут при любом раскладе, тут спору нет. Это я к чему - напишете кастомное решение для ангуляра, а завтра девы мигрируют на реакт. И что будете делать с protractor’ом?

2 лайка

Ну твоё решение тоже будет полностью оправдано, время которое будет затрачено на ожидание готовности элемента - это то из-за чего тесты становятся стабильными :slight_smile: Ты же этого и хочешь и это наша цель. Не парься о времени главное стабильность ведь
Остальное это дело оптимизации сценириев тестов, типа часть запихивать в js, а часть проверок оставить на долю селениума и assert’ов

Раз уж была реклама Selenide, то добавлю, что JDI тоже ожидает по умолчанию появления элемента, как и Селенид
Более того чего ждать можно описать в PageObject, а в самих тестах уже не думать об этом и писать просто
button.click() или dropdown.select(“blue”)

Просто, надежно и практично

1 лайк

Это вы вот это рекламируете что ли?

Гы гы

То что CI работает это плохо? На Selenide тоже последний билд failed


и что? Вряд ли это повод им не пользоваться )))

Не, ну то что последний билд завалился, это не показатель, что публичная версия забаженая. На то и есть эти тесты, чтобы не релизить забаженый код

Но всё равно jdi это очень не удобная штука если у вас больше 10 тестов :slight_smile: Слышал отзывы из Российского офиса epam о том как им приходится мучаться с этой обёрткой.

Нужно будет всё таки в свою репу как то выложить сравнение обёрток FluentLenium, Selenide и, о боже, JDI.

А особенно, ультра показательно будет: как удобно применять ожидания с ajax, reactjs и angular

2 лайка