Skip to content

Commit

Permalink
Add FluentWait.withMessage with string supplier
Browse files Browse the repository at this point in the history
Add a withMethod(Supplier<String> messgaeSupplier) to be able to
evaluate the message when the wait is over, thus enabling developers to
show more info on the context of failure through closures. Internally,
the FluentWait class is using a supplier. withMessage(String message)
method kept for backward compatibility creates a supplier that return
the given string.

Signed-off-by: Alexei Barantsev <barancev@gmail.com>
  • Loading branch information
Olivier SCHNEIDER authored and barancev committed Sep 19, 2015
1 parent 5eb8c3e commit fef98c1
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 44 deletions.
40 changes: 33 additions & 7 deletions java/client/src/org/openqa/selenium/support/ui/FluentWait.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,24 @@

package org.openqa.selenium.support.ui;

import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;

import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriverException;

import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;

/**
* An implementation of the {@link Wait} interface that may have its timeout and polling interval
* configured on the fly.
Expand Down Expand Up @@ -75,7 +77,12 @@ public class FluentWait<T> implements Wait<T> {

private Duration timeout = FIVE_HUNDRED_MILLIS;
private Duration interval = FIVE_HUNDRED_MILLIS;
private String message = null;
private Supplier<String> messageSupplier = new Supplier<String>() {
@Override
public String get() {
return null;
}
};

private List<Class<? extends Throwable>> ignoredExceptions = Lists.newLinkedList();

Expand Down Expand Up @@ -116,8 +123,24 @@ public FluentWait<T> withTimeout(long duration, TimeUnit unit) {
* @param message to be appended to default.
* @return A self reference.
*/
public FluentWait<T> withMessage(String message) {
this.message = message;
public FluentWait<T> withMessage(final String message) {
this.messageSupplier = new Supplier<String>() {
@Override
public String get() {
return message;
}
};
return this;
}

/**
* Sets the message to be evaluated and displayed when time expires.
*
* @param messageSupplier to be evaluated on failure and appended to default.
* @return A self reference.
*/
public FluentWait<T> withMessage(Supplier<String> messageSupplier) {
this.messageSupplier = messageSupplier;
return this;
}

Expand Down Expand Up @@ -221,6 +244,9 @@ public <V> V until(Function<? super T, V> isTrue) {
// Check the timeout after evaluating the function to ensure conditions
// with a zero timeout can succeed.
if (!clock.isNowBefore(end)) {
String message = messageSupplier != null ?
messageSupplier.get() : null;

String toAppend = message == null ?
" waiting for " + isTrue.toString() : ": " + message;

Expand Down
112 changes: 75 additions & 37 deletions java/client/test/org/openqa/selenium/support/ui/FluentWaitTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import static org.mockito.Mockito.when;

import com.google.common.base.Function;
import com.google.common.base.Supplier;

import org.junit.Before;
import org.junit.Test;
Expand All @@ -42,14 +43,18 @@
import java.util.concurrent.TimeUnit;

@RunWith(JUnit4.class)
public class FluentWaitTest{
public class FluentWaitTest {

private static final Object ARBITRARY_VALUE = new Object();

@Mock private WebDriver mockDriver;
@Mock private ExpectedCondition<Object> mockCondition;
@Mock private Clock mockClock;
@Mock private Sleeper mockSleeper;
@Mock
private WebDriver mockDriver;
@Mock
private ExpectedCondition<Object> mockCondition;
@Mock
private Clock mockClock;
@Mock
private Sleeper mockSleeper;

@Before
public void createMocks() {
Expand All @@ -63,9 +68,9 @@ public void shouldWaitUntilReturnValueOfConditionIsNotNull() throws InterruptedE
when(mockCondition.apply(mockDriver)).thenReturn(null, ARBITRARY_VALUE);

Wait<WebDriver> wait = new FluentWait<WebDriver>(mockDriver, mockClock, mockSleeper)
.withTimeout(0, TimeUnit.MILLISECONDS)
.pollingEvery(2, TimeUnit.SECONDS)
.ignoring(NoSuchElementException.class, NoSuchFrameException.class);
.withTimeout(0, TimeUnit.MILLISECONDS)
.pollingEvery(2, TimeUnit.SECONDS)
.ignoring(NoSuchElementException.class, NoSuchFrameException.class);

assertEquals(ARBITRARY_VALUE, wait.until(mockCondition));
verify(mockSleeper, times(1)).sleep(new Duration(2, TimeUnit.SECONDS));
Expand All @@ -78,9 +83,9 @@ public void shouldWaitUntilABooleanResultIsTrue() throws InterruptedException {
when(mockCondition.apply(mockDriver)).thenReturn(false, false, true);

Wait<WebDriver> wait = new FluentWait<WebDriver>(mockDriver, mockClock, mockSleeper)
.withTimeout(0, TimeUnit.MILLISECONDS)
.pollingEvery(2, TimeUnit.SECONDS)
.ignoring(NoSuchElementException.class, NoSuchFrameException.class);
.withTimeout(0, TimeUnit.MILLISECONDS)
.pollingEvery(2, TimeUnit.SECONDS)
.ignoring(NoSuchElementException.class, NoSuchFrameException.class);

assertEquals(true, wait.until(mockCondition));

Expand All @@ -94,7 +99,7 @@ public void checksTimeoutAfterConditionSoZeroTimeoutWaitsCanSucceed() {
when(mockCondition.apply(mockDriver)).thenReturn(null);

Wait<WebDriver> wait = new FluentWait<WebDriver>(mockDriver, mockClock, mockSleeper)
.withTimeout(0, TimeUnit.MILLISECONDS);
.withTimeout(0, TimeUnit.MILLISECONDS);
try {
wait.until(mockCondition);
fail();
Expand All @@ -108,14 +113,14 @@ public void canIgnoreMultipleExceptions() throws InterruptedException {
when(mockClock.laterBy(0L)).thenReturn(2L);
when(mockClock.isNowBefore(2L)).thenReturn(true);
when(mockCondition.apply(mockDriver))
.thenThrow(new NoSuchElementException(""))
.thenThrow(new NoSuchFrameException(""))
.thenReturn(ARBITRARY_VALUE);
.thenThrow(new NoSuchElementException(""))
.thenThrow(new NoSuchFrameException(""))
.thenReturn(ARBITRARY_VALUE);

Wait<WebDriver> wait = new FluentWait<WebDriver>(mockDriver, mockClock, mockSleeper)
.withTimeout(0, TimeUnit.MILLISECONDS)
.pollingEvery(2, TimeUnit.SECONDS)
.ignoring(NoSuchElementException.class, NoSuchFrameException.class);
.withTimeout(0, TimeUnit.MILLISECONDS)
.pollingEvery(2, TimeUnit.SECONDS)
.ignoring(NoSuchElementException.class, NoSuchFrameException.class);

assertEquals(ARBITRARY_VALUE, wait.until(mockCondition));

Expand All @@ -130,9 +135,9 @@ public void propagatesUnIgnoredExceptions() {
when(mockCondition.apply(mockDriver)).thenThrow(exception);

Wait<WebDriver> wait = new FluentWait<WebDriver>(mockDriver, mockClock, mockSleeper)
.withTimeout(0, TimeUnit.MILLISECONDS)
.pollingEvery(2, TimeUnit.SECONDS)
.ignoring(NoSuchElementException.class, NoSuchFrameException.class);
.withTimeout(0, TimeUnit.MILLISECONDS)
.pollingEvery(2, TimeUnit.SECONDS)
.ignoring(NoSuchElementException.class, NoSuchFrameException.class);

try {
wait.until(mockCondition);
Expand All @@ -148,14 +153,14 @@ public void timeoutMessageIncludesLastIgnoredException() {

when(mockClock.laterBy(0L)).thenReturn(2L);
when(mockCondition.apply(mockDriver))
.thenThrow(exception)
.thenReturn(null);
.thenThrow(exception)
.thenReturn(null);
when(mockClock.isNowBefore(2L)).thenReturn(false);

Wait<WebDriver> wait = new FluentWait<WebDriver>(mockDriver, mockClock, mockSleeper)
.withTimeout(0, TimeUnit.MILLISECONDS)
.pollingEvery(2, TimeUnit.SECONDS)
.ignoring(NoSuchWindowException.class);
.withTimeout(0, TimeUnit.MILLISECONDS)
.pollingEvery(2, TimeUnit.SECONDS)
.ignoring(NoSuchWindowException.class);
try {
wait.until(mockCondition);
fail();
Expand All @@ -167,15 +172,45 @@ public void timeoutMessageIncludesLastIgnoredException() {
@Test
public void timeoutMessageIncludesCustomMessage() {
TimeoutException expected = new TimeoutException(
"Timed out after 0 seconds: Expected custom timeout message");
"Timed out after 0 seconds: Expected custom timeout message");

when(mockClock.laterBy(0L)).thenReturn(2L);
when(mockCondition.apply(mockDriver)).thenReturn(null);
when(mockClock.isNowBefore(2L)).thenReturn(false);

Wait<WebDriver> wait = new FluentWait<WebDriver>(mockDriver, mockClock, mockSleeper)
.withTimeout(0, TimeUnit.MILLISECONDS)
.withMessage("Expected custom timeout message");
.withTimeout(0, TimeUnit.MILLISECONDS)
.withMessage("Expected custom timeout message");

try {
wait.until(mockCondition);
fail();
} catch (TimeoutException actual) {
assertEquals(expected.getMessage(), actual.getMessage());
}
}

private String state = null;

@Test
public void timeoutMessageIncludesCustomMessageEvaluatedOnFailure() {
TimeoutException expected = new TimeoutException(
"Timed out after 0 seconds: external state");

when(mockClock.laterBy(0L)).thenReturn(2L);
when(mockCondition.apply(mockDriver)).thenReturn(null);
when(mockClock.isNowBefore(2L)).thenReturn(false);

Wait<WebDriver> wait = new FluentWait<WebDriver>(mockDriver, mockClock, mockSleeper)
.withTimeout(0, TimeUnit.MILLISECONDS)
.withMessage(new Supplier<String>() {
@Override
public String get() {
return state;
}
});

state = "external state";

try {
wait.until(mockCondition);
Expand All @@ -188,7 +223,7 @@ public void timeoutMessageIncludesCustomMessage() {
@Test
public void timeoutMessageIncludesToStringOfCondition() {
TimeoutException expected = new TimeoutException(
"Timed out after 0 seconds waiting for toString called");
"Timed out after 0 seconds waiting for toString called");

Function<Object, Boolean> condition = new Function<Object, Boolean>() {
public Boolean apply(Object ignored) {
Expand All @@ -202,7 +237,7 @@ public String toString() {
};

Wait<Object> wait = new FluentWait<Object>("cheese")
.withTimeout(0, TimeUnit.MILLISECONDS);
.withTimeout(0, TimeUnit.MILLISECONDS);

try {
wait.until(condition);
Expand All @@ -221,9 +256,9 @@ public void canIgnoreThrowables() {
when(mockClock.isNowBefore(2L)).thenReturn(false);

Wait<WebDriver> wait = new FluentWait<WebDriver>(mockDriver, mockClock, mockSleeper)
.withTimeout(0, TimeUnit.MILLISECONDS)
.pollingEvery(2, TimeUnit.SECONDS)
.ignoring(AssertionError.class);
.withTimeout(0, TimeUnit.MILLISECONDS)
.pollingEvery(2, TimeUnit.SECONDS)
.ignoring(AssertionError.class);

try {
wait.until(mockCondition);
Expand All @@ -248,8 +283,8 @@ protected RuntimeException timeoutException(String message, Throwable lastExcept
}
};
wait.withTimeout(0, TimeUnit.MILLISECONDS)
.pollingEvery(2, TimeUnit.SECONDS)
.ignoring(TimeoutException.class);
.pollingEvery(2, TimeUnit.SECONDS)
.ignoring(TimeoutException.class);

try {
wait.until(mockCondition);
Expand All @@ -259,8 +294,11 @@ protected RuntimeException timeoutException(String message, Throwable lastExcept
}
}

private static class TestException extends RuntimeException {}
private static class TestException extends RuntimeException {

}

public interface GenericCondition extends ExpectedCondition<Object> {

}
}

0 comments on commit fef98c1

Please sign in to comment.