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