1 package com.atlassian.webdriver.utils.element;
2
3 import com.atlassian.pageobjects.PageBinder;
4 import com.google.common.base.Function;
5 import com.google.common.base.Predicate;
6 import org.hamcrest.Matcher;
7 import org.openqa.selenium.TimeoutException;
8 import org.openqa.selenium.WebDriver;
9 import org.openqa.selenium.WebElement;
10 import org.openqa.selenium.support.ui.FluentWait;
11 import org.openqa.selenium.support.ui.WebDriverWait;
12
13 import javax.annotation.Nonnull;
14 import javax.inject.Inject;
15 import java.util.concurrent.TimeUnit;
16
17 import static com.google.common.base.Preconditions.checkArgument;
18 import static com.google.common.base.Preconditions.checkNotNull;
19
20 /**
21 * <p/>
22 * A component that can be used to wait for certain conditions to happen on the tested page.
23 *
24 * <p/>
25 * The conditions are expressed as a generic {@link Function} from {@link WebDriver} to {@code boolean}.
26 * {@link ElementConditions} contains factory methods to easily create some commonly used conditions.
27 *
28 * <p/>
29 * The {@link #DEFAULT_TIMEOUT} and {@link #DEFAULT_TIMEOUT_UNIT} specify the default timeout used when
30 * no explicit timeout is provided by the client, which is currently 30 seconds. Clients are encouraged to use
31 * their own timeout specific to the situation.
32 *
33 * <p/>
34 * NOTE: the default poll interval used by this class is as in the underlying {@link WebDriverWait} and is currently
35 * 500ms (subject to change as the underlying {@link WebDriverWait} implementation changes. This may be generally
36 * acceptable, but may not be granular enough for some scenarios (e.g. performance testing).
37 *
38 * <p/>
39 * Example usage:
40 * <pre>
41 * {@code
42 * @literal @Inject private WebDriverPoller poller;
43 *
44 * // ...
45 * // wait for 5s for a 'my-element' to be present on the page
46 * poller.waitUntil(ElementConditions.isPresent(By.id("my-element")), 5);
47 * }
48 * </pre>
49 *
50 * <p/>
51 * As of 2.3, {@code WebDriverPoller} also supports waiting for {@code WebElement}-specific predicates and matchers,
52 * which require the web element to be already located.
53 * <p/>
54 * This component can be injected into page objects running within a {@link PageBinder} context.
55 *
56 * <p/>
57 * For more sophisticated polling/waiting toolkit, check the {@code PageElement} API in the
58 * atlassian-pageobjects-elements module.
59 *
60 * @since 2.2
61 * @see ElementConditions
62 * @see WebDriverWait
63 */
64 public final class WebDriverPoller
65 {
66 public static final long DEFAULT_TIMEOUT = 30;
67 public static final TimeUnit DEFAULT_TIMEOUT_UNIT = TimeUnit.SECONDS;
68
69 private final WebDriver webDriver;
70 private final TimeUnit timeUnit;
71 private final long timeout;
72
73 @Inject
74 public WebDriverPoller(@Nonnull WebDriver webDriver)
75 {
76 this(webDriver, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_UNIT);
77 }
78
79 public WebDriverPoller(@Nonnull WebDriver webDriver, long timeout, @Nonnull TimeUnit timeUnit)
80 {
81 checkArgument(timeout > 0, "Timeout must be >0");
82 this.webDriver = checkNotNull(webDriver, "webDriver");
83 this.timeout = timeout;
84 this.timeUnit = checkNotNull(timeUnit, "timeUnit");
85 }
86
87 @Nonnull
88 public WebDriverPoller withDefaultTimeout(long timeout, @Nonnull TimeUnit timeUnit)
89 {
90 return new WebDriverPoller(webDriver, timeout, timeUnit);
91 }
92
93 /**
94 * Wait until {@literal condition} is {@literal true}, up to the default timeout. The default timeout depends
95 * on the arguments supplied while creating this {@code WebDriverPoller}.
96 *
97 * @param condition condition that must evaluate to {@literal true}
98 * @throws TimeoutException if the condition does not come true before the timeout expires
99 * @see #DEFAULT_TIMEOUT
100 * @see #DEFAULT_TIMEOUT_UNIT
101 */
102 public void waitUntil(@Nonnull Function<WebDriver, Boolean> condition)
103 {
104 waitUntil(condition, timeout, timeUnit);
105 }
106
107 /**
108 * Wait until {@literal condition} up to the {@literal timeoutInSeconds}.
109 *
110 * @param condition condition that must evaluate to {@literal true}
111 * @param timeoutInSeconds timeout in seconds to wait for {@literal condition} to come {@code true}
112 * @throws TimeoutException if the condition does not come true before the timeout expires
113 */
114 public void waitUntil(@Nonnull Function<WebDriver, Boolean> condition, long timeoutInSeconds)
115 {
116 new WebDriverWait(webDriver, timeoutInSeconds).until(condition);
117 }
118
119 /**
120 * Wait until {@literal condition} up to the {@literal timeout} specified by {@literal unit}.
121 *
122 * @param condition condition that must evaluate to {@literal true}
123 * @param timeout timeout to wait for {@literal condition} to come true
124 * @param unit unit of the {@literal timeout}
125 * @throws TimeoutException if the condition does not come true before the timeout expires
126 */
127 public void waitUntil(@Nonnull Function<WebDriver, Boolean> condition, long timeout, @Nonnull TimeUnit unit)
128 {
129 waitUntil(condition, unit.toSeconds(timeout));
130 }
131
132 /**
133 * Wait until {@literal condition} is {@code true} for {@code element}, up to the default timeout. The default
134 * timeout depends on the arguments supplied while creating this {@code WebDriverPoller}.
135 *
136 * @param element the element to examine
137 * @param condition condition that must evaluate to {@literal true}, expressed by a {@link Predicate}
138 * @throws TimeoutException if the condition does not come true before the timeout expires
139 * @since 2.3
140 *
141 * @see #DEFAULT_TIMEOUT
142 * @see #DEFAULT_TIMEOUT_UNIT
143 */
144 public void waitUntil(@Nonnull WebElement element, @Nonnull Predicate<WebElement> condition)
145 {
146 waitUntil(element, condition, timeout, timeUnit);
147 }
148
149 /**
150 * Wait until {@literal condition} is {@code true} for {@code element}, up to the {@literal timeoutInSeconds}.
151 *
152 * @param element the element to examine
153 * @param condition condition that must evaluate to {@literal true}, expressed by a {@link Predicate}
154 * @param timeoutInSeconds timeout in seconds to wait for {@literal condition} to come {@code true}
155 * @throws TimeoutException if the condition does not come true before the timeout expires
156 * @since 2.3
157 */
158 public void waitUntil(@Nonnull WebElement element, @Nonnull Predicate<WebElement> condition, long timeoutInSeconds)
159 {
160 waitUntil(element, condition, timeoutInSeconds, TimeUnit.SECONDS);
161 }
162
163 /**
164 * Wait until {@literal condition} is {@code true} for {@code element}, up to the {@literal timeout} specified
165 * by {@literal unit}.
166 *
167 * @param element the element to examine
168 * @param condition condition that must evaluate to {@literal true}, expressed by a {@link Predicate}
169 * @param timeout timeout to wait for {@literal condition} to come true
170 * @param unit unit of the {@literal timeout}
171 * @throws TimeoutException if the condition does not come true before the timeout expires
172 * @since 2.3
173 */
174 public void waitUntil(@Nonnull WebElement element, @Nonnull Predicate<WebElement> condition,
175 long timeout, TimeUnit unit)
176 {
177 new FluentWait<WebElement>(checkNotNull(element, "element")).withTimeout(timeout, unit).until(condition);
178 }
179
180 /**
181 * Wait until {@literal condition} is {@code true} for {@code element}, up to the default timeout. The default
182 * timeout depends on the arguments supplied while creating this {@code WebDriverPoller}.
183 *
184 * @param element the element to examine
185 * @param condition condition that must evaluate to {@code true}, expressed by a {@code Matcher}
186 * @throws TimeoutException if the condition does not come true before the timeout expires
187 * @since 2.3
188 *
189 * @see #DEFAULT_TIMEOUT
190 * @see #DEFAULT_TIMEOUT_UNIT
191 */
192 public void waitUntil(@Nonnull WebElement element, @Nonnull Matcher<? super WebElement> condition)
193 {
194 waitUntil(element, condition, timeout, timeUnit);
195 }
196
197 /**
198 * Wait until {@code condition} is {@code true} for {@code element}, up to the {@code timeoutInSeconds}.
199 *
200 * @param element the element to examine
201 * @param condition condition that must evaluate to {@code true}, expressed by a {@link Matcher}
202 * @param timeoutInSeconds timeout in seconds to wait for {@code condition} to come {@code true}
203 * @throws TimeoutException if the condition does not come true before the timeout expires
204 * @since 2.3
205 */
206 public void waitUntil(@Nonnull WebElement element, @Nonnull Matcher<? super WebElement> condition,
207 long timeoutInSeconds)
208 {
209 waitUntil(element, condition, timeoutInSeconds, TimeUnit.SECONDS);
210 }
211
212 /**
213 * Wait until {@literal condition} is {@code true} for {@code element}, up to the {@code timeout} specified
214 * by {@code unit}.
215 *
216 * @param element the element to examine
217 * @param condition condition that must evaluate to {@code true}, expressed by a {@link Matcher}
218 * @param timeout timeout to wait for {@code condition} to come true
219 * @param unit unit of the {@code timeout}
220 * @throws TimeoutException if the condition does not come true before the timeout expires
221 * @since 2.3
222 */
223 public void waitUntil(@Nonnull WebElement element, @Nonnull Matcher<? super WebElement> condition,
224 long timeout, TimeUnit unit)
225 {
226 waitUntil(element, newMatcherPredicate(condition), timeout, unit);
227 }
228
229 static <E> Predicate<E> newMatcherPredicate(final Matcher<? super E> filter)
230 {
231 return new Predicate<E>()
232 {
233 @Override
234 public boolean apply(E element)
235 {
236 return filter.matches(element);
237 }
238 };
239 }
240 }