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.PollingQuery;
6 import com.atlassian.pageobjects.elements.query.TimedQuery;
7 import com.atlassian.webdriver.AtlassianWebDriver;
8 import com.atlassian.webdriver.utils.element.ElementLocated;
9 import org.hamcrest.StringDescription;
10 import org.openqa.selenium.By;
11 import org.openqa.selenium.NoSuchElementException;
12 import org.openqa.selenium.SearchContext;
13 import org.openqa.selenium.StaleElementReferenceException;
14 import org.openqa.selenium.WebElement;
15 import org.openqa.selenium.support.ui.TimeoutException;
16
17 import java.util.List;
18 import java.util.concurrent.TimeUnit;
19
20 import static com.atlassian.pageobjects.elements.query.Poller.waitUntil;
21 import static org.hamcrest.Matchers.notNullValue;
22
23
24
25
26 public class WebDriverLocators
27 {
28
29
30
31
32 public static WebDriverLocatable root()
33 {
34 return new WebDriverRootLocator();
35 }
36
37
38
39
40
41
42
43 public static WebDriverLocatable single(By locator)
44 {
45 return new WebDriverSingleLocator(locator, root());
46 }
47
48
49
50
51
52
53
54
55 public static WebDriverLocatable nested(By locator, WebDriverLocatable parent)
56 {
57 return new WebDriverSingleLocator(locator, parent);
58 }
59
60
61
62
63
64
65
66
67
68
69 public static WebDriverLocatable list(WebElement element, By locator, int locatorIndex, WebDriverLocatable parent)
70 {
71 return new WebDriverListLocator(element,locator, locatorIndex, parent);
72 }
73
74
75
76
77
78
79 public static boolean isStale(final WebElement webElement)
80 {
81 try
82 {
83 webElement.getTagName();
84 return false;
85 }
86 catch (StaleElementReferenceException ignored)
87 {
88 return true;
89 }
90 }
91
92 private static class WebDriverRootLocator implements WebDriverLocatable
93 {
94 public By getLocator()
95 {
96 return null;
97 }
98
99 public WebDriverLocatable getParent()
100 {
101 return null;
102 }
103
104 public SearchContext waitUntilLocated(AtlassianWebDriver driver, int timeoutInSeconds)
105 {
106 return driver;
107 }
108
109 public boolean isPresent(AtlassianWebDriver driver, int timeoutForParentInSeconds)
110 {
111 return true;
112 }
113 }
114
115 private static class WebDriverSingleLocator implements WebDriverLocatable
116 {
117 private WebElement webElement = null;
118 private boolean webElementLocated = false;
119
120 private final By locator;
121 private final WebDriverLocatable parent;
122
123 public WebDriverSingleLocator(By locator, WebDriverLocatable parent)
124 {
125 this.locator = locator;
126 this.parent = parent;
127 }
128
129 public By getLocator()
130 {
131 return locator;
132 }
133
134 public WebDriverLocatable getParent()
135 {
136 return parent;
137 }
138
139 public SearchContext waitUntilLocated(AtlassianWebDriver driver, int timeoutInSeconds)
140 {
141 if(!webElementLocated || WebDriverLocators.isStale(webElement))
142 {
143
144
145
146
147 SearchContext searchContext = parent.waitUntilLocated(driver, timeoutInSeconds);
148
149 if(!driver.elementExistsAt(locator, searchContext) && timeoutInSeconds > 0)
150 {
151 try
152 {
153 driver.waitUntil(new ElementLocated(locator, searchContext), timeoutInSeconds);
154 }
155 catch(TimeoutException e)
156 {
157 throw new org.openqa.selenium.NoSuchElementException(new StringDescription()
158 .appendText("Unable to locate element after timeout.")
159 .appendText("\nLocator: ").appendValue(locator)
160 .appendText("\nTimeout: ").appendValue(timeoutInSeconds).appendText(" seconds.")
161 .toString(), e);
162 }
163 }
164 webElement = searchContext.findElement(locator);
165 webElementLocated = true;
166 }
167
168 return webElement;
169 }
170
171 public boolean isPresent(AtlassianWebDriver driver, int timeoutForParentInSeconds)
172 {
173 return driver.elementExistsAt(this.locator, parent.waitUntilLocated(driver, timeoutForParentInSeconds));
174 }
175
176 }
177
178 private static class WebDriverListLocator implements WebDriverLocatable
179 {
180 private WebElement webElement = null;
181
182 private final By locator;
183 private final int locatorIndex;
184 private final WebDriverLocatable parent;
185
186 public WebDriverListLocator(WebElement element, By locator, int locatorIndex, WebDriverLocatable parent)
187 {
188 this.webElement = element;
189 this.locatorIndex = locatorIndex;
190 this.locator = locator;
191 this.parent = parent;
192 }
193
194 public By getLocator()
195 {
196 return null;
197 }
198
199 public WebDriverLocatable getParent()
200 {
201 return parent;
202 }
203
204 public SearchContext waitUntilLocated(final AtlassianWebDriver driver, int timeoutInSeconds)
205 {
206 if(WebDriverLocators.isStale(webElement))
207 {
208 try
209 {
210 webElement = waitUntil(queryForElement(driver, TimeUnit.SECONDS.toMillis(timeoutInSeconds)),
211 notNullValue(WebElement.class));
212 }
213 catch (AssertionError notLocated)
214 {
215 throw new NoSuchElementException(new StringDescription()
216 .appendText("Unable to locate element in collection.")
217 .appendText("\nLocator: ").appendValue(locator)
218 .appendText("\nLocator Index: ").appendValue(locatorIndex)
219 .toString());
220 }
221 }
222 return webElement;
223 }
224
225 private TimedQuery<WebElement> queryForElement(final AtlassianWebDriver driver, long timeout) {
226 return new AbstractTimedQuery<WebElement>(timeout, PollingQuery.DEFAULT_INTERVAL, ExpirationHandler.RETURN_NULL)
227 {
228 @Override
229 protected boolean shouldReturn(WebElement currentEval) {
230 return true;
231 }
232
233 @Override
234 protected WebElement currentValue() {
235
236 if (parent.isPresent(driver, 0)) {
237 List<WebElement> webElements = parent.waitUntilLocated(driver, 0).findElements(locator);
238 return locatorIndex < webElements.size() ? webElements.get(locatorIndex) : null;
239 }
240 return null;
241 }
242 };
243 }
244
245 public boolean isPresent(AtlassianWebDriver driver, int timeoutForParentInSeconds)
246 {
247 SearchContext searchContext = parent.waitUntilLocated(driver, timeoutForParentInSeconds);
248
249 List<WebElement> webElements = searchContext.findElements(this.locator);
250 return locatorIndex <= webElements.size() - 1;
251 }
252 }
253 }