Цього разу буду розглядату впевнене очікування, хоча його називають ще інколи плаваючим, вільним або ще якось, втім називати в рамках цієї статті буду його впевненим. На жаль в більшості статей про очікування даний тип згадується досить коротко, що особисто мені не дуже подобається тому вважаю, що оминути його існування все-таки було б неправильно, хоча б з таких причи: воно існує, воно досить потужне, воно є базою для явного очікування, а також його можна використати, коли інші очікування не підходять.
Почну з можливостей, які дає цей тип очікуваня:
Керування часом очікування
Керування інтервалом між опитуваннями
Ігнорування винятків (
NoSuchElementExceptions
,ElementNotInteractableException
і т.д.)
А ось і приклад такого очікування:
Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
.withTimeout(Duration.ofSeconds(30)) //Встановлює час очікування 30 секунд
.pollingEvery(Duration.ofSeconds(5)) //Встановлює інтервал опитування 5 секунд
.ignoring(NoSuchElementException.class); //Визначає тип винятку, який буде ігноруватися
WebElement foo = wait.until(new Function<WebDriver, WebElement>() {
@Override
public WebElement apply(WebDriver driver) {
return driver.findElement(By.id("foo"));
}
});
Умовно можемо розділити його на дві частини:
Налаштування очікування (
Wait<Webdriver> wait = ...
)Виконання самого очікування (
wait.until(...
).
З назв методів можна зрозуміти, що вони виконують, а якщо ні, то пан автор додав там коментарі. А ось друга частина буде трішки цікавішою. Що ж, аргументом до методу until
є функція, яка й буде виконувати очікування. Варто пам’ятати, що опис логіки потрібно робити в тілі функції apply
. А от те, як працює все це під капотом — є тим, що потребує опису в окремому пості, а в цьому хочеться описати загалом, що це таке і як його з’їсти. Також варто додати, що запис другої частини може мати дещо інший вигляд, якщо використовувати лямбди:
WebElement foo = wait.until(driver -> driver.findElement(By.id("foo")));
Може здатися, що щоразу писати стіну коду, коли потрібно якесь очікування з різними налаштуваннями не надто зручно, втім можна зробити метод, який буде викликатися, замість постійного повтору коду:
private WebElement fluentWaitMethod(Duration timeout, By by) {
return new FluentWait<WebDriver>(driver)
.withTimeout(timeout)
.ignoring(NoSuchElementException.class)
.until(innerDriver -> innerDriver.findElement(by));
}
Можна помітити, що опущено налаштування інтервалу між опитуваннями і це не викличе помилки, оскільки воно має значення за замовчуванням, яке дорівнює 500мс, як і значення за замовчуванням для часу очікування.
Теорія — це, звісно, добре, а тепер варто перейти до практичного застосування і для цього використаю код з попередньої статті про неявне очікування:
/*
* Старий варіант
*/
WebElement h1 = driver.findElement(By.tagName("h1"));
System.out.println("Header text: " + h1.getText()); //Header text: Implicit Wait Example
WebElement firstParagraph = driver.findElement(By.tagName("p"));
System.out.println("First paragraph text: " + firstParagraph.getText()); //First paragraph text: This loads with page
WebElement clickMe = driver.findElement(By.id("addNewContent"));
System.out.println("Button text: " + clickMe.getText()); //Button text: ClickMe
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
WebElement secondParagraph = driver.findElement(By.id("postponedForFiveSeconds"));
System.out.println("Second paragraph text: " + secondParagraph.getText());//Second paragraph text: Loaded after 5 seconds.
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(11));
WebElement thirdParagraph = driver.findElement(By.id("postponedForElevenSeconds"));
System.out.println("Third paragraph text: " + thirdParagraph.getText());//Third paragraph text: Loaded after 11 seconds.
clickMe.click();
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
WebElement fourthParagraph = driver.findElement(By.id("dynamicElement"));
System.out.println("Fourth paragraph text: " + fourthParagraph.getText());//Fourth paragraph text: Created at: 3:30:01 AM
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(0));//Вимикає неявні очікування
А тепер перепишу його з використанням впевненого очікування:
/*
* З використанням fluent wait
*/
WebElement h1 = driver.findElement(By.tagName("h1"));
System.out.println("Header text: " + h1.getText()); //Header text: Implicit Wait Example
WebElement firstParagraph = driver.findElement(By.tagName("p"));
System.out.println("First paragraph text: " + firstParagraph.getText()); //First paragraph text: This loads with page
WebElement clickMe = driver.findElement(By.id("addNewContent"));
System.out.println("Button text: " + clickMe.getText()); //Button text: ClickMe
WebElement secondParagraph = fluentWaitMethod(Duration.ofSeconds(5), By.id("postponedForFiveSeconds"));
System.out.println("Second paragraph text: " + secondParagraph.getText());//Second paragraph text: Loaded after 5 seconds.
WebElement thirdParagraph = fluentWaitMethod(Duration.ofSeconds(11), By.id("postponedForElevenSeconds"));
System.out.println("Third paragraph text: " + thirdParagraph.getText());//Third paragraph text: Loaded after 11 seconds.
clickMe.click();
WebElement fourthParagraph = fluentWaitMethod(Duration.ofSeconds(5), By.id("dynamicElement"));
System.out.println("Fourth paragraph text: " + fourthParagraph.getText());//Fourth paragraph text: Created at: 3:30:01 AM
Отже, можна побачити, що в такому варіанті ми позбавилися постійних маніпуляцій із неявним очікуванням (driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
), а з використанням написаного методу fluentWaitMethod
і зменшується загальна кількість коду через відсутність його повторювання, що одночасно покращує читабельність коду.
До речі, якщо розглянути умовну другу часнину очікування:
WebElement foo = wait.until(new Function<WebDriver, WebElement>() {
@Override
public WebElement apply(WebDriver driver) {
return driver.findElement(By.id("foo"));
}
});
, то можна зрозуміти чому поєднання неявного очікування з іншими — не бажане, оскільки воно буде впливати на логіку, яку було описано в коді, збільшуючи час очікування описаного в тілі методу apply
.
Тако ж було б добре навести приклад використання не тільки пошуку елементів, але й якоїсь взаємодії. Отже допустимо, що потрібно натиснути кнопку, яка перші 2 секунди перекривається якимось іншим елементом. Якщо спробувати одразу це зробити, то ймовірно буде викинутий виняток ElementClickInterceptedException
, тому можна створити таке очікування:
Wait<WebDriver> wait =
new FluentWait<WebDriver>(driver)
.withTimeout(Duration.ofSeconds(3))
.ignoring(ElementClickInterceptedException.class, NoSuchElementException.class);
wait.until(
new Function<WebDriver, Boolean>() {
public Boolean apply(WebDriver driver) {
driver.findElement(by).click();
return Boolean.TRUE;
}
});
В даному випадку повертати щось з функції при успішному кліку є обов’язково, оскільки метод until
очікує, що з функції в тілі метода apply
повернеться щось типу Boolean, якщо по-простому, адже детальний механізм роботи цього заслуговою більш детального розгляду в окремому пості, як пан автор казав раніше.
І на завершення хотів би зробити такі висноски про впевнене очікування(fluent wait):
Дозволяє налаштовувати такі параметри:
Час очікування
Інтервал між пошуком елементів
Типи винятків, що ігноруюються
Дозволяє описувати власну логіку очікування
Дозволяє налаштовувати очікування для кожного елементу окремо
Достатньо потужний інструмент для використання в тих місцях, де інші типи очікування не підходять
Сподіваюся мені вдалося розкрити дану тему детальніше, аніж це роблять інші та й взагалі маю надію, що даний матеріал був корисним та цікавим для прочитання. Коментарі, критика і т.д. — вітаються.