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