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