У меня есть экспериментально-образовательный проект на котором провожу различного рода “опыты”, так сказать b-side проект для самого себя. Во имя поддержки паттерна PageObject взялся переделать локаторы типа By на WebElement с аннотацией @FindBy (PageFactory паттерн). И в процессе столкнулся с первой проблемой, что WebElement, например, не поддерживается методом ExpectedConditions.invisibilityOfElementLocated(WebElement a), который иногда очень полезен. Другая проблема это то, что в некоторых местах я использовал удобные конструкции вроде следующей, получая тем самым “динамичный” локатор:
Конечно эти все “проблемы” можно обойти, например, используя оба: By и @FindBy. Но в итоге я задался вопросом — чем же таки выгодно использование @FindBy кроме удобной читаемости, @CacheLookup и lazy evaluation? (хотя эти аргументы несомненно весомые)
Сложилось впечталение, что By является более гибким решением. Интересно кто что использует на практике.
И то и другое приводит к одному и тому же результату - webelemen’у.
Pageobject сделан для удобства создания и поддержки. По сути в pagefactory - сама библиотека за вас делает просто findElement.
Она много чего ещё делает. Например, ожидание элемента перед кликом по нему.
Но эффект от использования автоматической инициализации элементов только через FindBy достигается далеко не всегда.
Гораздо чаще работает механизм совместного использования автоматически инициализированных элементов и вызова findElement по требованию. А кто-то и вовсе не использует этот механизм
ожидание будет так же если вы сделаете findElement(By…).click(). Итоговой разницы нету.
Разница во времени инициализации. В одном случае - при обращении к полю, в другом - разумеется сразу.
Про совсместное использование - да. Я , например, иногда использую findBy хотя основную часть делаю через pagefactory
Поле класса страницы инициализируется в момент создания экземпляра класса, в методе initElements(), который навешивает на поля прокси. И одной из задач этих прокси является ожидание появления элемента в DOM-е страницы.
Поэтому разница есть. И она в том, что findElement(By...).click() не ждет появления элемента в DOM-е. Если он его не находит, то сразу выдает исключение. В отличие от элемента, инициализированного через @FindBy - он будет ожидать появления элемента заданное время и только потом упадет
Там есть свои нюансы, которые я не описал, но в общих чертах разница такая
Точнее так: click2 точно не будет ждать, а click подождет в случае, если использовался AjaxElementLocator при декорировании полей класса во время инициализации. Т.к.DefaultElementLocator тоже никого не ждет
Если включить, тогда по идее findElement() должен будет подождать. Но по умолчанию они отключены, так что я их не учитывал
P.S. Я как-то раз включил их и у меня тесты стали падать не дожидаясь элементов. Хотя по идее implicitWait казалось бы ничего не может сломать. Поэтому я их тут же выключил
Просто всё ожидание оно вроде как завязывается на имплицидные ожидания. Не знаю, как насчет AjaxElementLocator(не копался), но в дефолтном виде initElements - будет ожидать если выставлены ожидания. и в том и в том случае.
Имплисит вейт работает на уровне драйвера браузера.
AjaxElementLocator тыкается с уровня селениума каждые 250мс, и посылает команду findElement драйверу, который будет ожидать установленный ранее имплисит вейт.
implicit waits (или ожидания драйвера) позволяют полить DOM, пока элемент не появится. Срабатывают они на уровне findElement. Живут по времени жизни драйвера. В случае AjaxElementLocatorFactory, в частности AjaxElementLocator будет в цикле делать то, что описали выше.
explicit waits (или клиентские ожидания) придуманы для ожидания конкретного элемента по заданному условию. Далеко не всегда нам нужно ждать именно появления элемента. Иногда нужно проверять, кликабелен он или нет, или вовсе - появление какого-то текста. В данном случае WebDriverWait в комбинации с ExpectedConditions предоставляют более гибкие решения. А ввиду того, что их механизм в большинстве основан на By локаторах, которые не привязаны ко времени загрузки страницы (ввиду поиска по запросу), то для многих ситуаций эта техника будет являться решением всех проблем с ожиданиями.
Лично для меня связка By + WebDriverWait + ExpectedConditions является более приемлемым вариантом. К тому же проблема красоты решается очень просто путем добавления кастомной аннотации и обертки над классом By.
Склоняюсь к этим двум вариантам. Хотя сам, до экспериментов с @FindBy, использовал второй способ, описанный @ArtOfLife. Похоже, что этот вариант является наиболее гибким.
Обращаться к элементу страницы, в каждом отдельном методе задавая способ его поиска и ожидания, конечно, более гибко, но и накладывает дополнительные требования:
локатор элемента должен быть единым, независимо от того в скольких методах происходит findElement() по локатору. Иначе, при изменении верстки выловить баги будет сложнее
ожидание элемента так же необходимо делать при каждом findElement()
Получается, что лучше в классе страницы использовать единый метод получения рабочего элемента страницы, типа getOKButton(), перед взаимодействием с ним. Этот get-метод как раз должен выполнять поиск и ожидание необходимого элемента и возвращать рабочий элемент
В добавок отмечу, что гораздо эффективней в данной ситуации осуществлять поиск с ожиданием не в конкретной странице, а в базовой пейдже, сокрыв сам findElement внутри всех методов-оберток над WebDriver API. К примеру:
public void setText(final By locator, final CharSequence text, final Integer timeout) {
findElement(locator, timeout).sendKeys(text);
}
public void setText(final By locator, final CharSequence text) {
setText(locator, text, null);
}
При этом findElement может выглядеть следующим образом (без exceptions handling):
public WebElement findElement(final By locator, final Integer timeout) {
if (timeout != null) {
wait.withTimeout(timeout >= 0 ? timeout : 0, TimeUnit.SECONDS);
}
final WebElement element = wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
wait.withTimeout(DEFAULT_VISIBILITY_TIMEOUT, TimeUnit.SECONDS);
return element;
}
где wait - объект WebDriverWait, а DEFAULT_VISIBILITY_TIMEOUT - некая константа.
По аналогии, любая другая обертка (click, getText, etc.) будет точно так же дергать кастомный findElement, что позволит нам использовать универсальный механизм ожиданий для элементов любой страницы.
При этом, ExpectedConditions можно сделать условным, в зависимости от, к примеру, дополнительного enum параметра. Либо вообще организовать гибкий механизм переключения между стандартным findElement и wait.until. Тут уже нет предела творчеству.
Кстати, в Java 8 при помощи функциональных интерфейсов можно очень легко соорудить универсальный waiter:
public WebElement waitForElement(final By locator, final Function<By, ExpectedCondition<WebElement>> condition, final Integer timeout) {
final WebElement element = wait.withTimeout(
Optional.ofNullable(timeout)
.filter(value -> value >= 0)
.orElse(DEFAULT_TIMEOUT), TimeUnit.SECONDS)
.until(condition.apply(locator));
wait.withTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
return element;
}