1 package com.atlassian.webdriver.utils.by;
2
3 import com.atlassian.webdriver.utils.JavaScriptUtils;
4 import com.google.common.base.Preconditions;
5 import org.apache.commons.lang.StringUtils;
6 import org.apache.commons.lang.Validate;
7 import org.openqa.selenium.By;
8 import org.openqa.selenium.NoSuchElementException;
9 import org.openqa.selenium.SearchContext;
10 import org.openqa.selenium.WebDriver;
11 import org.openqa.selenium.WebElement;
12
13 import java.util.ArrayList;
14 import java.util.List;
15 import java.util.regex.Matcher;
16 import java.util.regex.Pattern;
17
18 import static com.atlassian.webdriver.LifecycleAwareWebDriverGrid.getDriver;
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 public abstract class ByJquery extends By
34 {
35
36
37
38 private final Pattern SIMPLE_SELECTOR_PATTERN = Pattern.compile("^([#]|[.]|[a-zA-Z])[\\w-]+(\\s[^~]*)?$");
39 private final Pattern ID_SELECTOR = Pattern.compile("^#(\\S+)(\\s?.*)$");
40 private final Pattern CLASSNAME_SELECTOR = Pattern.compile("^[.](\\S+)(\\s?.*)$");
41 private final Pattern TAGNAME_SELECTOR = Pattern.compile("^([A-Za-z]\\w+)(\\s?.*)$");
42
43 private enum SelectorType {
44 FIND,
45 PARENT,
46 PARENTS,
47 SIBLINGS,
48 CHILDREN,
49 PREV,
50 CLOSEST,
51 FILTER;
52 }
53
54 private class Selector {
55
56 private final String selector;
57 private final SelectorType type;
58
59 public Selector(String selector, SelectorType type) {
60 this.selector = selector;
61 this.type = type;
62 }
63
64 public String getSelector()
65 {
66 return selector;
67 }
68
69 public SelectorType getType()
70 {
71 return type;
72 }
73 }
74
75 private final List<Selector> selectors;
76
77 private ByJquery(String selector, SelectorType type)
78 {
79 this.selectors = new ArrayList<Selector>();
80 this.selectors.add(new Selector(selector, type));
81 }
82
83 private ByJquery() {
84 this.selectors = new ArrayList<Selector>();
85 }
86
87 private ByJquery(String selector, SelectorType type, List<Selector> selectors)
88 {
89 this.selectors = selectors;
90 this.selectors.add(new Selector(selector, type));
91 }
92
93 List<WebElement> findElementsWithSelectors(SearchContext context)
94 {
95 List<WebElement> elements = new ArrayList<WebElement>();
96
97 for (Selector selector : selectors)
98 {
99
100 String selectorStr = fixSelector(selector.getSelector());
101
102 Object[] args = null;
103
104 switch (selector.type)
105 {
106 case FIND:
107 if (elements.isEmpty())
108 {
109 elements = $findElements(selector.getSelector(), context);
110 }
111 else {
112 elements = $findElements(selector.getSelector(), elements);
113 }
114 break;
115 case PARENT:
116 case SIBLINGS:
117 case PARENTS:
118 case CHILDREN:
119 case PREV:
120 case CLOSEST:
121 args = new Object[]{"ATLWD.$(context)." + selector.type.name().toLowerCase() + "(" + selectorStr + ")", elements };
122 elements = JavaScriptUtils.execute("return ATLWD.byJquery.execute(arguments[0],arguments[1])", getDriver(), args);
123 break;
124 case FILTER:
125 args = new Object[]{"ATLWD.$(context)." + selector.type.name().toLowerCase() + "(" + selector.getSelector() + ")", elements };
126 elements = JavaScriptUtils.execute("return ATLWD.byJquery.execute(arguments[0],arguments[1])", getDriver(), args);
127 break;
128
129 default:
130 throw new UnsupportedOperationException("Unknown selector type: " + selector.type);
131 }
132
133 }
134
135 return elements;
136 }
137
138 private String fixSelector(String selector)
139 {
140 if (selector == null)
141 {
142 return "";
143 }
144 else
145 {
146 return "'" + selector.replaceAll("'", "\"") + "'";
147 }
148
149
150 }
151
152 private boolean isSimpleSelector(String selector)
153 {
154 return SIMPLE_SELECTOR_PATTERN.matcher(selector).matches();
155 }
156
157 private List<WebElement> executeMatcher(Matcher matcher, SearchContext context, By by)
158 {
159 if (matcher.matches())
160 {
161 if (StringUtils.isEmpty(matcher.group(2)))
162 {
163 return context.findElements(by);
164 }
165 else
166 {
167 return $findElements(matcher.group(2), context.findElements(by));
168 }
169 }
170
171 throw new IllegalStateException("ID Selector failed match");
172 }
173
174 private List<WebElement> executeIdMatcher(Matcher matcher, SearchContext context)
175 {
176 if (matcher.find()) {
177 return executeMatcher(matcher, context, By.id(matcher.group(1)));
178 }
179
180 throw new IllegalArgumentException("Invalid matcher.");
181 }
182
183 private List<WebElement> executeClassNameMatcher(Matcher matcher, SearchContext context)
184 {
185 if (matcher.find())
186 {
187 return executeMatcher(matcher, context, By.className(matcher.group(1)));
188 }
189
190 throw new IllegalArgumentException("Invalid matcher.");
191 }
192
193 private List<WebElement> executeTagNameMatcher(Matcher matcher, SearchContext context)
194 {
195 if (matcher.find())
196 {
197 return executeMatcher(matcher, context, By.tagName(matcher.group(1)));
198 }
199
200 throw new IllegalArgumentException("Invalid matcher.");
201 }
202
203 private List<WebElement> executeSimpleSelector(String selector, SearchContext context)
204 {
205 if (selector.startsWith("#"))
206 {
207 Matcher matcher = ID_SELECTOR.matcher(selector);
208 return executeIdMatcher(matcher, context);
209 }
210 else if (selector.startsWith("."))
211 {
212 Matcher matcher = CLASSNAME_SELECTOR.matcher(selector);
213 return executeClassNameMatcher(matcher, context);
214 }
215 else
216 {
217 Matcher matcher = TAGNAME_SELECTOR.matcher(selector);
218 return executeTagNameMatcher(matcher, context);
219 }
220 }
221
222 List<WebElement> $findElements(final String selector, final SearchContext context)
223 {
224 if (isSimpleSelector(selector))
225 {
226 return executeSimpleSelector(selector, context);
227 }
228
229 String fixedSelector = fixSelector(selector);
230
231 if (context instanceof WebElement)
232 {
233 Object[] args = { "ATLWD.$(context).find(" + fixedSelector + ")", (WebElement) context };
234 return JavaScriptUtils.execute("return ATLWD.byJquery.execute(arguments[0],arguments[1])", getDriver(), args);
235 }
236 else
237 {
238 Object[] args = { "ATLWD.$(document).find(" + fixedSelector + ")"};
239 return JavaScriptUtils.execute("return ATLWD.byJquery.execute(arguments[0])", getDriver(), args);
240 }
241 }
242
243 List<WebElement> $findElements(final String selector, List<WebElement> els)
244 {
245
246
247
248 List<WebElement> newElements = new ArrayList<WebElement>();
249 for (WebElement element : els)
250 {
251 Object[] args = { "ATLWD.$(context).find(" + fixSelector(selector) + ")", element };
252 List<WebElement> temp = JavaScriptUtils.execute("return ATLWD.byJquery.execute(arguments[0],arguments[1])", getDriver(), args);
253 newElements.addAll(temp);
254 }
255
256 return newElements;
257
258
259 }
260
261 public static ByJquery $(final WebElement element)
262 {
263 loadJqueryLocator(getDriver());
264
265 return new ByJquery() {
266 @Override
267 public List<WebElement> findElements(final SearchContext context)
268 {
269 return findElementsWithSelectors(element);
270 }
271
272 @Override
273 public WebElement findElement(final SearchContext context)
274 {
275 List<WebElement> els = findElementsWithSelectors(element);
276 if(!els.isEmpty())
277 {
278 return els.get(0);
279 }
280 else
281 {
282 throw new NoSuchElementException("Element not found by jQuery.");
283 }
284 }
285
286 };
287 }
288
289 public static ByJquery $(final String selector)
290 {
291 if (selector == null)
292 {
293 throw new IllegalArgumentException("The jquery selector cannot be null.");
294 }
295 loadJqueryLocator(getDriver());
296
297 return new ByJquery(selector, SelectorType.FIND)
298 {
299 @Override
300 public List<WebElement> findElements(final SearchContext context)
301 {
302 return findElementsWithSelectors(context);
303 }
304
305 @Override
306 public WebElement findElement(final SearchContext context)
307 {
308 List<WebElement> els = findElementsWithSelectors(context);
309 if(!els.isEmpty())
310 {
311 return els.get(0);
312 }
313 else
314 {
315 throw new NoSuchElementException(this.toString());
316 }
317 }
318
319 @Override
320 public String toString()
321 {
322 return "jQuery selector: " + selector;
323 }
324 };
325
326 }
327
328 private void addSelector(Selector selector)
329 {
330 this.selectors.add(selector);
331 }
332
333 public ByJquery find(String selector) {
334 Validate.notNull(selector, "The find selector cannot be null");
335 addSelector(new Selector(selector, SelectorType.FIND));
336
337 return this;
338 }
339
340 public ByJquery parent()
341 {
342 return parent(null);
343 }
344
345 public ByJquery parent(String selector)
346 {
347
348 addSelector(new Selector(selector, SelectorType.PARENT));
349
350 return this;
351 }
352
353 public ByJquery parents()
354 {
355 return parents(null);
356 }
357
358 public ByJquery parents(String selector)
359 {
360
361 addSelector(new Selector(selector, SelectorType.PARENTS));
362
363 return this;
364 }
365
366 public ByJquery siblings()
367 {
368 return siblings(null);
369 }
370
371 public ByJquery siblings(String selector)
372 {
373
374 addSelector(new Selector(selector, SelectorType.SIBLINGS));
375
376 return this;
377 }
378
379 public ByJquery children()
380 {
381 return children(null);
382 }
383
384 public ByJquery children(String selector)
385 {
386 addSelector(new Selector(selector, SelectorType.CHILDREN));
387
388 return this;
389 }
390
391 public ByJquery prev() {
392 return prev(null);
393 }
394
395 public ByJquery prev(String selector)
396 {
397 addSelector(new Selector(selector, SelectorType.PREV));
398
399 return this;
400 }
401
402 public ByJquery closest(String selector)
403 {
404 addSelector(new Selector(selector, SelectorType.CLOSEST));
405 return this;
406 }
407
408
409
410
411
412
413
414
415 public ByJquery filter(String selector)
416 {
417 addSelector(new Selector(selector, SelectorType.FILTER));
418 return this;
419 }
420
421
422
423
424
425
426
427 public WebElement findElement(SearchContext context)
428 {
429 List<WebElement> allElements = findElements(context);
430 if (allElements == null || allElements.size() == 0)
431 {
432 throw new NoSuchElementException("Cannot locate an element using " + toString());
433 }
434 return allElements.get(0);
435 }
436
437 private static void loadJqueryLocator(WebDriver driver)
438 {
439 JavaScriptUtils.loadScript("js/byjquery/byJquery.js", driver);
440 }
441
442 @Override
443 public boolean equals(Object o)
444 {
445 if (this == o)
446 {
447 return true;
448 }
449 if (o == null || getClass() != o.getClass())
450 {
451 return false;
452 }
453
454 By by = (By) o;
455
456 return toString().equals(by.toString());
457 }
458
459 @Override
460 public int hashCode()
461 {
462 return toString().hashCode();
463 }
464
465 }