1 package com.atlassian.pageobjects.elements;
2
3 import com.atlassian.annotations.Internal;
4 import com.atlassian.pageobjects.elements.query.AbstractTimedQuery;
5 import com.atlassian.pageobjects.elements.query.ExpirationHandler;
6 import com.atlassian.pageobjects.elements.query.Poller;
7 import com.atlassian.pageobjects.elements.query.TimedQuery;
8 import com.atlassian.pageobjects.elements.timeout.DefaultTimeouts;
9 import com.atlassian.webdriver.utils.Check;
10 import org.hamcrest.StringDescription;
11 import org.openqa.selenium.*;
12
13 import javax.annotation.Nonnull;
14 import java.util.List;
15 import java.util.concurrent.TimeUnit;
16
17 import static com.atlassian.pageobjects.elements.WebDriverLocatable.LocateTimeout.zero;
18 import static com.atlassian.pageobjects.elements.query.Poller.*;
19 import static com.atlassian.pageobjects.elements.util.StringConcat.asString;
20 import static com.google.common.base.Preconditions.checkArgument;
21 import static com.google.common.base.Preconditions.checkNotNull;
22 import static org.hamcrest.Matchers.notNullValue;
23
24
25
26
27
28 @Internal
29 public class WebDriverLocators
30 {
31
32
33
34
35
36 @Nonnull
37 public static WebDriverLocatable root()
38 {
39 return new WebDriverRootLocator();
40 }
41
42
43
44
45
46
47
48 @Nonnull
49 public static WebDriverLocatable single(By locator)
50 {
51 return new WebDriverSingleLocator(locator, root());
52 }
53
54
55
56
57
58
59
60
61 @Nonnull
62 public static WebDriverLocatable nested(By locator, WebDriverLocatable parent)
63 {
64 return new WebDriverSingleLocator(locator, parent);
65 }
66
67
68
69
70
71
72
73
74
75
76 @Nonnull
77 public static WebDriverLocatable list(WebElement element, By locator, int locatorIndex, WebDriverLocatable parent)
78 {
79 return new WebDriverListLocator(element, locator, locatorIndex, parent);
80 }
81
82 @Nonnull
83 public static WebDriverLocatable staticElement(WebElement element)
84 {
85 return new WebDriverStaticLocator(element);
86 }
87
88
89
90
91
92
93
94 public static boolean isStale(final WebElement webElement)
95 {
96 try
97 {
98 webElement.getTagName();
99 return false;
100 }
101 catch (StaleElementReferenceException ignored)
102 {
103 return true;
104 }
105 }
106
107 private static Poller.WaitTimeout withinTimeout(WebDriverLocatable.LocateTimeout timeout)
108 {
109 return timeout.timeout() > 0 ? by(timeout.timeout()) : now();
110 }
111
112 private static long getTimeout(WebDriverLocatable.LocateTimeout timeout) {
113 return timeout.timeout() > 0 ? timeout.timeout() : DefaultTimeouts.DEFAULT;
114 }
115
116 private static abstract class DeprecatedApiBridge implements WebDriverLocatable
117 {
118 @Override
119 @Nonnull
120 public final SearchContext waitUntilLocated(@Nonnull WebDriver driver, int timeoutInSeconds)
121 throws NoSuchElementException
122 {
123 return waitUntilLocated(driver, new LocateTimeout.Builder()
124 .timeout(timeoutInSeconds, TimeUnit.SECONDS)
125 .build());
126 }
127
128 @Override
129 public final boolean isPresent(@Nonnull WebDriver driver, int timeoutInSeconds)
130 {
131 return isPresent(driver, new LocateTimeout.Builder()
132 .timeout(timeoutInSeconds, TimeUnit.SECONDS)
133 .build());
134 }
135 }
136
137
138
139
140
141
142 private static class WebDriverStaticLocator extends DeprecatedApiBridge implements WebDriverLocatable
143 {
144 private final WebElement element;
145
146 WebDriverStaticLocator(WebElement element)
147 {
148 this.element = element;
149 }
150
151 @Override
152 public By getLocator()
153 {
154 if (isStale(element))
155 {
156 return null;
157 }
158
159 final String id = element.getAttribute("id");
160 if (id != null)
161 {
162 return By.id(id);
163 }
164
165 return null;
166 }
167
168 @Override
169 @Nonnull
170 public WebDriverLocatable getParent()
171 {
172 return root();
173 }
174
175 @Override
176 @Nonnull
177 public SearchContext waitUntilLocated(@Nonnull WebDriver driver, @Nonnull LocateTimeout timeout)
178 throws NoSuchElementException
179 {
180 checkNotNull(driver, "driver");
181 checkNotNull(timeout, "timeout");
182 if (isStale(element))
183 {
184 throw new NoSuchElementException("WebElement got stale");
185 }
186 return element;
187 }
188
189 @Override
190 public boolean isPresent(@Nonnull WebDriver driver, @Nonnull LocateTimeout timeout)
191 {
192 checkNotNull(driver, "driver");
193 checkNotNull(timeout, "timeout");
194 return !isStale(element);
195 }
196 }
197
198 private static class WebDriverRootLocator extends DeprecatedApiBridge implements WebDriverLocatable
199 {
200 public By getLocator()
201 {
202 return null;
203 }
204
205 public WebDriverLocatable getParent()
206 {
207 return null;
208 }
209
210 @Override
211 @Nonnull
212 public final SearchContext waitUntilLocated(@Nonnull WebDriver driver, @Nonnull LocateTimeout timeout)
213 {
214 checkNotNull(driver, "driver");
215 checkNotNull(timeout, "timeout");
216 return driver;
217 }
218
219 @Override
220 public final boolean isPresent(@Nonnull WebDriver driver, @Nonnull LocateTimeout timeout)
221 {
222 checkNotNull(driver, "driver");
223 checkNotNull(timeout, "timeout");
224 return true;
225 }
226 }
227
228 private static class WebDriverSingleLocator extends DeprecatedApiBridge implements WebDriverLocatable
229 {
230 private WebElement webElement = null;
231 private boolean webElementLocated = false;
232
233 private final By locator;
234 private final WebDriverLocatable parent;
235
236 public WebDriverSingleLocator(By locator, WebDriverLocatable parent)
237 {
238 this.locator = checkNotNull(locator, "locator");
239 this.parent = checkNotNull(parent, "parent");
240 }
241
242 @Nonnull
243 public By getLocator()
244 {
245 return locator;
246 }
247
248 @Nonnull
249 public WebDriverLocatable getParent()
250 {
251 return parent;
252 }
253
254 @Override
255 @Nonnull
256 public SearchContext waitUntilLocated(@Nonnull WebDriver driver, @Nonnull LocateTimeout timeout)
257 {
258 checkNotNull(driver, "driver");
259 checkNotNull(timeout, "timeout");
260 if(!webElementLocated || WebDriverLocators.isStale(webElement))
261 {
262 try
263 {
264 webElement = waitUntil(queryForSingleElement(driver, timeout), notNullValue(WebElement.class),
265 withinTimeout(timeout));
266 }
267 catch(AssertionError notFound)
268 {
269 throw new NoSuchElementException(new StringDescription()
270 .appendText("Unable to locate element by timeout.")
271 .appendText("\nLocator: ").appendValue(locator)
272 .appendText("\nTimeout: ").appendValue(timeout.timeout()).appendText("ms.")
273 .toString());
274 }
275 webElementLocated = true;
276 }
277 return webElement;
278 }
279
280 @Override
281 public boolean isPresent(@Nonnull WebDriver driver, @Nonnull LocateTimeout timeout)
282 {
283 checkNotNull(driver, "driver");
284 checkNotNull(timeout, "timeout");
285 try
286 {
287 return Check.elementExists(this.locator, parent.waitUntilLocated(driver, timeout));
288 }
289 catch (NoSuchElementException e)
290 {
291
292 return false;
293 }
294 }
295
296 @Override
297 @Nonnull
298 public String toString()
299 {
300 return asString("WebDriverSingleLocator[locator=", locator, "]");
301 }
302
303 private TimedQuery<WebElement> queryForSingleElement(final WebDriver driver, final LocateTimeout timeout)
304 {
305 return new AbstractTimedQuery<WebElement>(getTimeout(timeout), timeout.pollInterval(), ExpirationHandler.RETURN_NULL)
306 {
307 @Override
308 protected boolean shouldReturn(WebElement currentEval)
309 {
310 return true;
311 }
312
313 @Override
314 protected WebElement currentValue() {
315
316 if (parent.isPresent(driver, zero()))
317 {
318 try
319 {
320 return parent.waitUntilLocated(driver, zero()).findElement(locator);
321 }
322 catch (NoSuchElementException e)
323 {
324
325 return null;
326 }
327 }
328 else
329 {
330
331 return null;
332 }
333 }
334 };
335 }
336
337 }
338
339 private static class WebDriverListLocator extends DeprecatedApiBridge implements WebDriverLocatable
340 {
341 private WebElement webElement = null;
342
343 private final By locator;
344 private final int locatorIndex;
345 private final WebDriverLocatable parent;
346
347 public WebDriverListLocator(WebElement element, By locator, int locatorIndex, WebDriverLocatable parent)
348 {
349 checkArgument(locatorIndex >= 0, "locator index is negative:" + locatorIndex);
350 this.webElement = checkNotNull(element);
351 this.locatorIndex = locatorIndex;
352 this.locator = checkNotNull(locator, "locator");
353 this.parent = checkNotNull(parent, "parent");
354 }
355
356 public By getLocator()
357 {
358 return null;
359 }
360
361 public WebDriverLocatable getParent()
362 {
363 return parent;
364 }
365
366 @Override
367 @Nonnull
368 public SearchContext waitUntilLocated(@Nonnull WebDriver driver, @Nonnull LocateTimeout timeout)
369 {
370 checkNotNull(driver, "driver");
371 checkNotNull(timeout, "timeout");
372 if(WebDriverLocators.isStale(webElement))
373 {
374 try
375 {
376 webElement = waitUntil(queryForElementInList(driver, timeout), notNullValue(WebElement.class),
377 withinTimeout(timeout));
378 }
379 catch (AssertionError notLocated)
380 {
381 throw new NoSuchElementException(new StringDescription()
382 .appendText("Unable to locate element in collection.")
383 .appendText("\nLocator: ").appendValue(locator)
384 .appendText("\nLocator Index: ").appendValue(locatorIndex)
385 .toString());
386 }
387 }
388 return webElement;
389 }
390
391 @Override
392 public boolean isPresent(@Nonnull WebDriver driver, @Nonnull LocateTimeout timeout)
393 {
394 checkNotNull(driver, "driver");
395 checkNotNull(timeout, "timeout");
396 SearchContext searchContext = parent.waitUntilLocated(driver, timeout);
397 List<WebElement> webElements = searchContext.findElements(this.locator);
398 return locatorIndex <= webElements.size() - 1;
399 }
400
401 @Override
402 public String toString()
403 {
404 return asString("WebDriverListLocator[locator=", locator, ",index=", locatorIndex, "]");
405 }
406
407 private TimedQuery<WebElement> queryForElementInList(final WebDriver driver, final LocateTimeout timeout)
408 {
409 return new AbstractTimedQuery<WebElement>(getTimeout(timeout), timeout.pollInterval(), ExpirationHandler.RETURN_NULL)
410 {
411 @Override
412 protected boolean shouldReturn(WebElement currentEval) {
413 return true;
414 }
415
416 @Override
417 protected WebElement currentValue() {
418
419 if (parent.isPresent(driver, zero())) {
420 List<WebElement> webElements = parent.waitUntilLocated(driver, zero()).findElements(locator);
421 return locatorIndex < webElements.size() ? webElements.get(locatorIndex) : null;
422 }
423 return null;
424 }
425 };
426 }
427 }
428 }