From 00031458669a19203d6695b6f831457052468c27 Mon Sep 17 00:00:00 2001 From: Alessandro Miccoli Date: Wed, 14 Feb 2024 11:16:38 +0000 Subject: [PATCH 01/10] feat: add pollDelay mechanism into AppiumFluentWait --- .../appium/java_client/AppiumFluentWait.java | 72 +++++++++++++------ 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/src/main/java/io/appium/java_client/AppiumFluentWait.java b/src/main/java/io/appium/java_client/AppiumFluentWait.java index 9061600a0..420e7eb2f 100644 --- a/src/main/java/io/appium/java_client/AppiumFluentWait.java +++ b/src/main/java/io/appium/java_client/AppiumFluentWait.java @@ -35,6 +35,9 @@ public class AppiumFluentWait extends FluentWait { private Function pollingStrategy = null; + private static final Duration DEFAULT_POLL_DELAY_DURATION = Duration.ZERO; + private Duration pollDelay = DEFAULT_POLL_DELAY_DURATION; + public static class IterationInfo { /** * The current iteration number. @@ -98,6 +101,11 @@ public AppiumFluentWait(T input, Clock clock, Sleeper sleeper) { super(input, clock, sleeper); } + public AppiumFluentWait withPollDelay(Duration pollDelay) { + this.pollDelay = pollDelay; + return this; + } + private B getPrivateFieldValue(String fieldName, Class fieldType) { return ReflectionHelpers.getPrivateFieldValue(FluentWait.class, this, fieldName, fieldType); } @@ -201,9 +209,17 @@ public AppiumFluentWait withPollingStrategy(Function @Override public V until(Function isTrue) { final Instant start = getClock().instant(); - final Instant end = getClock().instant().plus(getTimeout()); + final Instant end = start.plus(getTimeout()).plus(pollDelay); + + return performIteration(isTrue, start, end); + } + + private V performIteration(Function isTrue, Instant start, Instant end) { long iterationNumber = 1; Throwable lastException; + + sleepWaitFor(pollDelay); + while (true) { try { V value = isTrue.apply(getInput()); @@ -222,32 +238,46 @@ public V until(Function isTrue) { // Check the timeout after evaluating the function to ensure conditions // with a zero timeout can succeed. if (end.isBefore(getClock().instant())) { - String message = getMessageSupplier() != null ? getMessageSupplier().get() : null; - - String timeoutMessage = String.format( - "Expected condition failed: %s (tried for %d second(s) with %s interval)", - message == null ? "waiting for " + isTrue : message, - getTimeout().getSeconds(), getInterval()); - throw timeoutException(timeoutMessage, lastException); + handleTimeoutException(lastException, isTrue); } - try { - Duration interval = getInterval(); - if (pollingStrategy != null) { - final IterationInfo info = new IterationInfo(iterationNumber, - Duration.between(start, getClock().instant()), getTimeout(), - interval); - interval = pollingStrategy.apply(info); - } - getSleeper().sleep(interval); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new WebDriverException(e); - } + Duration interval = getIntervalWithPollingStrategy(start, iterationNumber); + sleepWaitFor(interval); + ++iterationNumber; } } + private void handleTimeoutException(Throwable lastException, Function isTrue) { + String message = getMessageSupplier() != null ? getMessageSupplier().get() : null; + + String timeoutMessage = String.format( + "Expected condition failed: %s (tried for %d second(s) with %s interval)", + message == null ? "waiting for " + isTrue : message, + getTimeout().getSeconds(), getInterval()); + + throw timeoutException(timeoutMessage, lastException); + } + + private Duration getIntervalWithPollingStrategy(Instant start, long iterationNumber) { + Duration interval = getInterval(); + if (pollingStrategy != null) { + final IterationInfo info = new IterationInfo(iterationNumber, + Duration.between(start, getClock().instant()), getTimeout(), interval); + interval = pollingStrategy.apply(info); + } + return interval; + } + + private void sleepWaitFor(Duration duration) { + try { + getSleeper().sleep(duration); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new WebDriverException(e); + } + } + protected Throwable propagateIfNotIgnored(Throwable e) { for (Class ignoredException : getIgnoredExceptions()) { if (ignoredException.isInstance(e)) { From 8b8a9ddd5171f510b049fdf4e0cc76366f581fbc Mon Sep 17 00:00:00 2001 From: Alessandro Miccoli Date: Wed, 14 Feb 2024 14:05:13 +0000 Subject: [PATCH 02/10] docs: add javadoc for withPollDelay --- src/main/java/io/appium/java_client/AppiumFluentWait.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/io/appium/java_client/AppiumFluentWait.java b/src/main/java/io/appium/java_client/AppiumFluentWait.java index 420e7eb2f..b7ed76417 100644 --- a/src/main/java/io/appium/java_client/AppiumFluentWait.java +++ b/src/main/java/io/appium/java_client/AppiumFluentWait.java @@ -101,6 +101,13 @@ public AppiumFluentWait(T input, Clock clock, Sleeper sleeper) { super(input, clock, sleeper); } + /** + * Sets how long to wait before starting to evaluate condition to be true. + * The default pollDelay is {@link #DEFAULT_POLL_DELAY_DURATION}. + * + * @param pollDelay The pollDelay duration. + * @return A self reference. + */ public AppiumFluentWait withPollDelay(Duration pollDelay) { this.pollDelay = pollDelay; return this; From f6b861a2f4324437b40a0b4ad3c3f84479cb63f8 Mon Sep 17 00:00:00 2001 From: Alessandro Miccoli Date: Wed, 14 Feb 2024 22:29:43 +0000 Subject: [PATCH 03/10] refactor: improved until and nested method of AppiumFluentWait --- .../appium/java_client/AppiumFluentWait.java | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/main/java/io/appium/java_client/AppiumFluentWait.java b/src/main/java/io/appium/java_client/AppiumFluentWait.java index b7ed76417..481a97733 100644 --- a/src/main/java/io/appium/java_client/AppiumFluentWait.java +++ b/src/main/java/io/appium/java_client/AppiumFluentWait.java @@ -18,6 +18,7 @@ import com.google.common.base.Throwables; import io.appium.java_client.internal.ReflectionHelpers; +import java.util.Optional; import lombok.AccessLevel; import lombok.Getter; import org.openqa.selenium.TimeoutException; @@ -216,6 +217,7 @@ public AppiumFluentWait withPollingStrategy(Function @Override public V until(Function isTrue) { final Instant start = getClock().instant(); + // Adding pollDelay to end instant will allow to verify the condition for the expected timeout duration. final Instant end = start.plus(getTimeout()).plus(pollDelay); return performIteration(isTrue, start, end); @@ -225,7 +227,7 @@ private V performIteration(Function isTrue, Instant start, Ins long iterationNumber = 1; Throwable lastException; - sleepWaitFor(pollDelay); + sleepUninterruptible(pollDelay); while (true) { try { @@ -249,7 +251,7 @@ private V performIteration(Function isTrue, Instant start, Ins } Duration interval = getIntervalWithPollingStrategy(start, iterationNumber); - sleepWaitFor(interval); + sleepUninterruptible(interval); ++iterationNumber; } @@ -257,28 +259,35 @@ private V performIteration(Function isTrue, Instant start, Ins private void handleTimeoutException(Throwable lastException, Function isTrue) { String message = getMessageSupplier() != null ? getMessageSupplier().get() : null; + String waitingMessage = message != null ? message : "waiting for " + isTrue; String timeoutMessage = String.format( - "Expected condition failed: %s (tried for %d second(s) with %s interval)", - message == null ? "waiting for " + isTrue : message, - getTimeout().getSeconds(), getInterval()); + "Expected condition failed: %s (tried for %s millis with an interval of %s millis)", + waitingMessage, + getTimeout().toMillis(), + getInterval().toMillis() + ); throw timeoutException(timeoutMessage, lastException); } private Duration getIntervalWithPollingStrategy(Instant start, long iterationNumber) { Duration interval = getInterval(); - if (pollingStrategy != null) { - final IterationInfo info = new IterationInfo(iterationNumber, - Duration.between(start, getClock().instant()), getTimeout(), interval); - interval = pollingStrategy.apply(info); - } - return interval; + return Optional.ofNullable(pollingStrategy) + .map(strategy -> { + final IterationInfo info = new IterationInfo( + iterationNumber, + Duration.between(start, getClock().instant()), getTimeout(), interval); + return strategy.apply(info); + }) + .orElse(interval); } - private void sleepWaitFor(Duration duration) { + private void sleepUninterruptible(Duration duration) { try { - getSleeper().sleep(duration); + if(!duration.isZero()) { + getSleeper().sleep(duration); + } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new WebDriverException(e); From d3db1101b5ea2240210bf1657cb19e8f5fef6848 Mon Sep 17 00:00:00 2001 From: Alessandro Miccoli Date: Wed, 14 Feb 2024 22:47:23 +0000 Subject: [PATCH 04/10] refactor: change order of imports in AppiumFluentWait --- src/main/java/io/appium/java_client/AppiumFluentWait.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/AppiumFluentWait.java b/src/main/java/io/appium/java_client/AppiumFluentWait.java index 481a97733..9ab5afebd 100644 --- a/src/main/java/io/appium/java_client/AppiumFluentWait.java +++ b/src/main/java/io/appium/java_client/AppiumFluentWait.java @@ -18,7 +18,6 @@ import com.google.common.base.Throwables; import io.appium.java_client.internal.ReflectionHelpers; -import java.util.Optional; import lombok.AccessLevel; import lombok.Getter; import org.openqa.selenium.TimeoutException; @@ -30,6 +29,7 @@ import java.time.Duration; import java.time.Instant; import java.util.List; +import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; From 6e47723949609914d53e304064cbfe14f8ea0129 Mon Sep 17 00:00:00 2001 From: Alessandro Miccoli Date: Wed, 14 Feb 2024 22:56:24 +0000 Subject: [PATCH 05/10] refactor: add whitespace after if --- src/main/java/io/appium/java_client/AppiumFluentWait.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/AppiumFluentWait.java b/src/main/java/io/appium/java_client/AppiumFluentWait.java index 9ab5afebd..38e844bfc 100644 --- a/src/main/java/io/appium/java_client/AppiumFluentWait.java +++ b/src/main/java/io/appium/java_client/AppiumFluentWait.java @@ -285,7 +285,7 @@ private Duration getIntervalWithPollingStrategy(Instant start, long iterationNum private void sleepUninterruptible(Duration duration) { try { - if(!duration.isZero()) { + if (!duration.isZero()) { getSleeper().sleep(duration); } } catch (InterruptedException e) { From a42bc242a26d754e4b19c81d808a199f6d822c54 Mon Sep 17 00:00:00 2001 From: AlessandroMiccoli <57630383+AlessandroMiccoli@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:08:45 +0000 Subject: [PATCH 06/10] refactor: amend message generation in AppiumFluentWait Co-authored-by: Valery Yatsynovich --- src/main/java/io/appium/java_client/AppiumFluentWait.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/appium/java_client/AppiumFluentWait.java b/src/main/java/io/appium/java_client/AppiumFluentWait.java index 38e844bfc..b6edc50a1 100644 --- a/src/main/java/io/appium/java_client/AppiumFluentWait.java +++ b/src/main/java/io/appium/java_client/AppiumFluentWait.java @@ -258,12 +258,11 @@ private V performIteration(Function isTrue, Instant start, Ins } private void handleTimeoutException(Throwable lastException, Function isTrue) { - String message = getMessageSupplier() != null ? getMessageSupplier().get() : null; - String waitingMessage = message != null ? message : "waiting for " + isTrue; + String message = Optional.ofNullable(getMessageSupplier()).map(Supplier::get).orElseGet(() -> "waiting for " + isTrue); String timeoutMessage = String.format( "Expected condition failed: %s (tried for %s millis with an interval of %s millis)", - waitingMessage, + message, getTimeout().toMillis(), getInterval().toMillis() ); From d6effd215da9f0ba8679016f78ec61747cf7bdd9 Mon Sep 17 00:00:00 2001 From: Alessandro Miccoli Date: Thu, 15 Feb 2024 12:44:40 +0000 Subject: [PATCH 07/10] refactor: change method name from sleepUninterruptible to sleepInterruptibly --- .../java/io/appium/java_client/AppiumFluentWait.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/appium/java_client/AppiumFluentWait.java b/src/main/java/io/appium/java_client/AppiumFluentWait.java index b6edc50a1..e86eecf12 100644 --- a/src/main/java/io/appium/java_client/AppiumFluentWait.java +++ b/src/main/java/io/appium/java_client/AppiumFluentWait.java @@ -227,7 +227,7 @@ private V performIteration(Function isTrue, Instant start, Ins long iterationNumber = 1; Throwable lastException; - sleepUninterruptible(pollDelay); + sleepInterruptibly(pollDelay); while (true) { try { @@ -251,14 +251,16 @@ private V performIteration(Function isTrue, Instant start, Ins } Duration interval = getIntervalWithPollingStrategy(start, iterationNumber); - sleepUninterruptible(interval); + sleepInterruptibly(interval); ++iterationNumber; } } private void handleTimeoutException(Throwable lastException, Function isTrue) { - String message = Optional.ofNullable(getMessageSupplier()).map(Supplier::get).orElseGet(() -> "waiting for " + isTrue); + String message = Optional.ofNullable(getMessageSupplier()) + .map(Supplier::get) + .orElseGet(() -> "waiting for " + isTrue); String timeoutMessage = String.format( "Expected condition failed: %s (tried for %s millis with an interval of %s millis)", @@ -282,9 +284,9 @@ private Duration getIntervalWithPollingStrategy(Instant start, long iterationNum .orElse(interval); } - private void sleepUninterruptible(Duration duration) { + private void sleepInterruptibly(Duration duration) { try { - if (!duration.isZero()) { + if (!duration.isZero() && !duration.isNegative()) { getSleeper().sleep(duration); } } catch (InterruptedException e) { From 928ec445b38fef4bd5b095aa5915b6912fc9a7b9 Mon Sep 17 00:00:00 2001 From: Alessandro Miccoli Date: Fri, 16 Feb 2024 08:57:16 +0000 Subject: [PATCH 08/10] refactor: amend type to var in AppiumFluentWait --- .../io/appium/java_client/AppiumFluentWait.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/io/appium/java_client/AppiumFluentWait.java b/src/main/java/io/appium/java_client/AppiumFluentWait.java index e86eecf12..a706c4597 100644 --- a/src/main/java/io/appium/java_client/AppiumFluentWait.java +++ b/src/main/java/io/appium/java_client/AppiumFluentWait.java @@ -216,15 +216,15 @@ public AppiumFluentWait withPollingStrategy(Function */ @Override public V until(Function isTrue) { - final Instant start = getClock().instant(); + final var start = getClock().instant(); // Adding pollDelay to end instant will allow to verify the condition for the expected timeout duration. - final Instant end = start.plus(getTimeout()).plus(pollDelay); + final var end = start.plus(getTimeout()).plus(pollDelay); return performIteration(isTrue, start, end); } private V performIteration(Function isTrue, Instant start, Instant end) { - long iterationNumber = 1; + var iterationNumber = 1; Throwable lastException; sleepInterruptibly(pollDelay); @@ -250,7 +250,7 @@ private V performIteration(Function isTrue, Instant start, Ins handleTimeoutException(lastException, isTrue); } - Duration interval = getIntervalWithPollingStrategy(start, iterationNumber); + var interval = getIntervalWithPollingStrategy(start, iterationNumber); sleepInterruptibly(interval); ++iterationNumber; @@ -258,11 +258,11 @@ private V performIteration(Function isTrue, Instant start, Ins } private void handleTimeoutException(Throwable lastException, Function isTrue) { - String message = Optional.ofNullable(getMessageSupplier()) + var message = Optional.ofNullable(getMessageSupplier()) .map(Supplier::get) .orElseGet(() -> "waiting for " + isTrue); - String timeoutMessage = String.format( + var timeoutMessage = String.format( "Expected condition failed: %s (tried for %s millis with an interval of %s millis)", message, getTimeout().toMillis(), @@ -273,10 +273,10 @@ private void handleTimeoutException(Throwable lastException, Function { - final IterationInfo info = new IterationInfo( + final var info = new IterationInfo( iterationNumber, Duration.between(start, getClock().instant()), getTimeout(), interval); return strategy.apply(info); From 1894a47325ac0b95d68961a2da21c13871c32fe9 Mon Sep 17 00:00:00 2001 From: Alessandro Miccoli Date: Fri, 16 Feb 2024 08:58:57 +0000 Subject: [PATCH 09/10] refactor: amend getIntervalWithPollingStrategy in AppiumFluentWait --- .../java/io/appium/java_client/AppiumFluentWait.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/appium/java_client/AppiumFluentWait.java b/src/main/java/io/appium/java_client/AppiumFluentWait.java index a706c4597..ab69e4281 100644 --- a/src/main/java/io/appium/java_client/AppiumFluentWait.java +++ b/src/main/java/io/appium/java_client/AppiumFluentWait.java @@ -275,12 +275,9 @@ private void handleTimeoutException(Throwable lastException, Function { - final var info = new IterationInfo( - iterationNumber, - Duration.between(start, getClock().instant()), getTimeout(), interval); - return strategy.apply(info); - }) + .map(strategy -> strategy.apply(new IterationInfo( + iterationNumber, + Duration.between(start, getClock().instant()), getTimeout(), interval))) .orElse(interval); } From d0914470e0caf92474a782d1292a0dd89a97a683 Mon Sep 17 00:00:00 2001 From: Alessandro Miccoli Date: Fri, 16 Feb 2024 15:36:44 +0000 Subject: [PATCH 10/10] refactor: amend millis to ms in timeoutMessage --- src/main/java/io/appium/java_client/AppiumFluentWait.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/AppiumFluentWait.java b/src/main/java/io/appium/java_client/AppiumFluentWait.java index ab69e4281..6361a5652 100644 --- a/src/main/java/io/appium/java_client/AppiumFluentWait.java +++ b/src/main/java/io/appium/java_client/AppiumFluentWait.java @@ -263,7 +263,7 @@ private void handleTimeoutException(Throwable lastException, Function "waiting for " + isTrue); var timeoutMessage = String.format( - "Expected condition failed: %s (tried for %s millis with an interval of %s millis)", + "Expected condition failed: %s (tried for %s ms with an interval of %s ms)", message, getTimeout().toMillis(), getInterval().toMillis()