И еще раз о Wait в Selenium WedDriver

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

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

Вкратце о структуре объектов фреймверка.

Использую фукедид. Струтура наследования страниц стандартная. PageObject -> BasePage -> Pages
Вебэлементы обернуты - есть разделение на Input, DataPicker, CheckBox …

До полноценного декоратора, правда, еще руки не дошли, так чтобы PageFactory работала непосредственно c типами Input и т.д. Пока что, PageFactory, все также, работает с WebElement, а инициализация оберток проходит в конструкторах страниц. Все обертки имплементируют интерфейс HtmlElemtnt, что позволяет гибко работать с разными типами контролов.

Теперь ближе к вейтам.

Идея в том, чтобы в обертках, перед тем как выполнить какое-то действие, элемент ждал рендеринга самого себя.

С этим все относительно ясно, кроме того момента, что ожидания приходится вызывать в начале каждого действия, каждого типа обертки. фактически это дублирование кода.
Решить эту проблему можно вводом Proxy класа между интерфейсом и реализацией.

IHtmlElement -> ProxyHtmlElement -> реализация ( e.g. CheckBox)

Контракт для страниц остается прежним, но прокси позволяет, в одном месте, для каждого контрактного метода интерфейса, вызвать ожидание, а дальше перенаправить исполнение к реализации.

Интересней вопрос с гридами, что есть самое наболевшее при работе с Ajax.
Элемент отбражается, а данные еще в пути.

Для того, чтобы все ждало нормально, необходимо ждать рендеринга данных таблиц.
Для работы с таблицами фукедид предоставляет класс HtmlTable.

Его можно также, обернуть, перегрузить необходимые действия, например findFirstRowWhere,
и перед вызовом super.findFirstRowWhere(), вызывать некий метод waitForRendering(),
который, в свою очередь будет, проверять наличие содержимого в ячейках таблицы и ждать, если такового нет.

Тем самым у нас будет, грубо говоря, 1 .setValue(), 2 .getValue(), 3 .click () в прокси классе. и 4 .findRow, 5 .containsRow в таблице. Вейты в 5 местах, в системе.

1 лайк

можно попробовать всё тот же PageFactory.initElements использовать с кастомным классом декоратора и ElementLocator, подменяя стандартный метод ожидания появления элемента в DOM-е, своим методом, который может ждать более конкретного вида элемента.

Из комментария к методу AjaxElementLocator.isElementUsable(WebElement):

  /**
   * By default, elements are considered "found" if they are in the DOM. Override this method in
   * order to change whether or not you consider the element loaded. For example, perhaps you need
   * the element to be displayed:
   * 
   * <pre class="code>
   *   return ((RenderedWebElement) element).isDisplayed();
   * </pre>
   * 
   * @param element The element to use
   * @return Whether or not it meets your criteria for "found"
   */
  protected boolean isElementUsable(WebElement element) {
    return true;
  }

Тогда при инициализации элементы автоматом получают нужный функционал Wait-а. Но это концепт. Как это реализуется, особенно в части гридов - вопрос не простой

С гридами, в таком случае, будет схожая с остальными типами, кастомизация, если я правильно понял. Решение красивое, но меня смущает момент, что в каждом необходимом случае нужно явно вызывать element.wait . с одной стороны читабельно, с другой лишние строки и можно забыть поставить.

не нужно ничего явно вызывать. Либо я не вижу такую ситуацию, приведите пример?

Смысл в том, что все проверки готовности элемента производятся внутри объекта-обертки и в момент обращения к методу элемента. То есть всё, как вы описали в хотелках.

Как я вижу реализацию (собственно, у меня так и сделано, только не ожидания, а переключение между разными окнами и фреймами):

  1. все обертки наследуются от некоего базового класса-обертки, которому в конструктор передается оборачиваемый WebElement

  2. этот базовый класс предоставляет некий метод

    protected WebElement getElement() { return this.baseElement}

который возвращает обернутый элемент

  1. все наследники во всех своих методах используют этот метод для получения доступа к своему обернутому элементу
class Checkbox extends AbstractElement {
// ... skipped
    /*
    * метод неправильный, важен принцип доступа к обернутому элементу - через единый метод
    */
    public void check() {
        getElement().click;
    }
// ... skipped
}
  1. Таким образом, получаем единую точку доступа к элементу. Здесь можно и wait-ы делать и что угодно ещё без дублирования кода и излишних наворотов типа Proxy, AOP и т.д., например
protected WebElement getElement() {
    return waitElementReady(this.baseElement);
}

/*
* осторожно, псевдокод
*/
protected WebElement waitElementReady(WebElement element) {
    getDriver().waitElementEnabled(element)
return element;
}

Соответственно, waitElementReady можно переопределять в наследниках при необходимости

P.S. для ясности - к AjaxElementLocator.isElementUsable(WebElement), о котором я упоминал выше, описанное здесь отношения не имеет - это другой велосипед

Привет!
Нас тоже в своё время посетило желание избавиться от вейтов раз и навсегда. В результате мы сделали библиотеку Selenide. В неё как раз все операции с элементами, если сразу не удалось сделать, ждут некоторое время (по умолчанию не более 4 секунд). Как раз то, что вам надо.

Selenide можно использовать вместе с Thucydides. Но мы предпочитаем отдельно.

Если вы хотите помочь, можете вкратце описать как устроена система ожиданий в вашей системе. библиотеку непременно как-нибудь посмотрю

Очень просто устроена.

 protected WebElement waitUntil(String prefix, Condition condition, long timeoutMs) {
    WebElement element;
    do {
      element = tryToGetElement();
      if (condition.apply(element))
        return element;  // ОК, условие сработало
      }
      sleep(100 миллисекунд);
    }
    while (пока не прошло 4 секунды);

    ОШИБКА: условие не сработало
  }

Полный код лежит на гитхабе.