1 package com.atlassian.pageobjects.elements;
2
3 import com.atlassian.pageobjects.PageBinder;
4 import com.atlassian.pageobjects.elements.query.AbstractTimedQuery;
5 import com.atlassian.pageobjects.elements.query.ExpirationHandler;
6 import com.atlassian.pageobjects.elements.query.TimedQuery;
7 import com.atlassian.webdriver.utils.JavaScriptUtils;
8 import com.google.common.collect.ImmutableSet;
9 import com.google.common.collect.Iterables;
10 import org.apache.commons.lang.ArrayUtils;
11 import org.openqa.selenium.JavascriptExecutor;
12 import org.openqa.selenium.WebDriver;
13 import org.openqa.selenium.WebElement;
14
15 import java.util.List;
16
17 import static com.atlassian.pageobjects.elements.WebDriverLocators.staticElement;
18 import static com.google.common.base.Preconditions.checkNotNull;
19 import static com.google.common.base.Preconditions.checkState;
20
21
22
23
24 final class WebDriverElementJavascript implements PageElementJavascript
25 {
26 private static final Iterable<String> SIMPLE_JS_RESULT_TYPES = ImmutableSet.of(
27 Boolean.class.getName(),
28 String.class.getName(),
29 Long.class.getName(),
30 List.class.getName()
31 );
32
33
34 private final WebDriver driver;
35 private final PageBinder pageBinder;
36 private final WebDriverElement element;
37
38 private final JSMouse jsMouse = new JSMouse();
39 private final JSForm jsForm = new JSForm();
40
41
42
43 WebDriverElementJavascript(WebDriverElement element)
44 {
45 this.element = checkNotNull(element, "element");
46 this.driver = element.driver;
47 this.pageBinder = element.pageBinder;
48 }
49
50 public PageElementMouseJavascript mouse()
51 {
52 return jsMouse;
53 }
54
55 public PageElementFormJavascript form()
56 {
57 return jsForm;
58 }
59
60
61 public Object execute(String script, Object... arguments)
62 {
63 checkNotNull(script, "script");
64 return convertResult(getExecutor().executeScript(script, convertArguments(arguments)));
65 }
66
67 public <T> T execute(Class<T> resultType, String script, Object... arguments)
68 {
69 checkNotNull(script, "script");
70 validateResultType(resultType);
71 return executeConverted(resultType, script, arguments);
72 }
73
74 private <T> T executeConverted(Class<T> resultType, String script, Object[] arguments)
75 {
76 return convertResult(getExecutor().executeScript(script, convertArguments(arguments)), resultType);
77 }
78
79 @Override
80 public <T> TimedQuery<T> executeTimed(final Class<T> resultType, final String script, final Object... arguments)
81 {
82 checkNotNull(script, "script");
83 validateSimpleResultType(resultType);
84 return new AbstractTimedQuery<T>(element.timed().isPresent(), ExpirationHandler.RETURN_CURRENT)
85 {
86 @Override
87 protected boolean shouldReturn(T currentEval)
88 {
89 return true;
90 }
91
92 @Override
93 protected T currentValue()
94 {
95 return executeConverted(resultType, script, arguments);
96 }
97 };
98 }
99
100 @Override
101 public <T> T executeAsync(Class<T> resultType, String script, Object... arguments)
102 {
103 checkNotNull(script, "script");
104 validateResultType(resultType);
105 return executeAsyncConverted(resultType, script, arguments);
106 }
107
108 public Object executeAsync(String script, Object... arguments)
109 {
110 checkNotNull(script, "script");
111 return convertResult(getExecutor().executeAsyncScript(script, convertArguments(arguments)));
112 }
113
114 private <T> T executeAsyncConverted(Class<T> resultType, String script, Object[] arguments)
115 {
116 return convertResult(getExecutor().executeAsyncScript(script, convertArguments(arguments)), resultType);
117 }
118
119 private Object[] convertArguments(Object[] arguments)
120 {
121 for (int i=0; i<arguments.length;i++)
122 {
123 if (arguments[i] instanceof PageElement)
124 {
125 arguments[i] = WebDriverElement.getWebElement((PageElement) arguments[i]);
126 }
127 }
128 return ArrayUtils.add(arguments, 0, element.waitForWebElement());
129 }
130
131 private Object convertResult(Object result)
132 {
133 if (result instanceof WebElement)
134 {
135 final WebElement webElement = (WebElement) result;
136 if (element.getAttribute("id") != null && element.getAttribute("id").equals(webElement.getAttribute("id")))
137 {
138
139 return element;
140 }
141 return pageBinder.bind(WebDriverElement.class, staticElement(webElement), element.defaultTimeout);
142 }
143 else
144 {
145 return result;
146 }
147 }
148
149 private <T> T convertResult(Object result, Class<T> expected)
150 {
151 if (result instanceof WebElement)
152 {
153 if (!PageElement.class.isAssignableFrom(expected))
154 {
155 throw new ClassCastException("Expected instance of PageElement, was: " + expected);
156 }
157 final WebElement webElement = (WebElement) result;
158 if (element.getAttribute("id") != null && element.getAttribute("id").equals(webElement.getAttribute("id")))
159 {
160
161 return expected.cast(element);
162 }
163 @SuppressWarnings("unchecked") final Object stupidWorkaroundsForStupidGenericsProblemsHaaaaaateIt =
164 pageBinder.bind(WebDriverElementMappings.findMapping((Class)expected), staticElement(webElement), element.defaultTimeout);
165 return expected.cast(stupidWorkaroundsForStupidGenericsProblemsHaaaaaateIt);
166 }
167 else
168 {
169 return expected.cast(result);
170 }
171 }
172
173 private JavascriptExecutor getExecutor()
174 {
175 checkState(driver instanceof JavascriptExecutor, driver + " does not support Javascript");
176 return (JavascriptExecutor) driver;
177 }
178
179 private <T> void validateSimpleResultType(Class<T> resultType)
180 {
181 checkNotNull(resultType, "resultType");
182 if (!Iterables.contains(SIMPLE_JS_RESULT_TYPES, resultType.getName()))
183 {
184 throw new IllegalArgumentException("Class '" + resultType.getName() + "' is not a simple JS return type");
185 }
186 }
187
188 private <T> void validateResultType(Class<T> resultType)
189 {
190 if (Iterables.contains(SIMPLE_JS_RESULT_TYPES, resultType.getName()))
191 {
192 return;
193 }
194 if (PageElement.class.isAssignableFrom(resultType))
195 {
196 return;
197 }
198 throw new IllegalArgumentException("Class '" + resultType.getName() + "' is not a supported JS return type");
199 }
200
201 private class JSMouse implements PageElementMouseJavascript
202 {
203
204 public PageElementJavascript click()
205 {
206 return dispatch("click");
207 }
208
209 public PageElementJavascript doubleClick()
210 {
211 return dispatch("dblclick");
212 }
213
214 public PageElementJavascript mouseup()
215 {
216 return dispatch("mouseup");
217 }
218
219 public PageElementJavascript mousedown()
220 {
221 return dispatch("mousedown");
222 }
223
224 public PageElementJavascript mouseover()
225 {
226 return dispatch("mouseover");
227 }
228
229 public PageElementJavascript mousemove()
230 {
231 return dispatch("mousemove");
232 }
233
234 public PageElementJavascript mouseout()
235 {
236 return dispatch("mouseout");
237 }
238
239 }
240
241
242 private class JSForm implements PageElementFormJavascript
243 {
244
245 public PageElementJavascript select()
246 {
247 return dispatch("select");
248 }
249
250 public PageElementJavascript change()
251 {
252 return dispatch("change");
253 }
254
255 public PageElementJavascript submit()
256 {
257 return dispatch("submit");
258 }
259
260 public PageElementJavascript focus()
261 {
262 return dispatch("focus");
263 }
264
265 public PageElementJavascript blur()
266 {
267 return dispatch("blur");
268 }
269 }
270
271 private PageElementJavascript dispatch(String eventType)
272 {
273 JavaScriptUtils.dispatchEvent(eventType, element.waitForWebElement(), driver);
274 return this;
275 }
276 }