PageRenderTime 70ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/java/client/src/org/openqa/selenium/htmlunit/HtmlUnitDriver.java

https://bitbucket.org/abahdanovich/selenium
Java | 1302 lines | 1005 code | 214 blank | 83 comment | 157 complexity | a7bc76ee5395cc496cbc68851c704ee2 MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception, AGPL-1.0, MIT, Apache-2.0, BSD-3-Clause, GPL-2.0
  1. /*
  2. Copyright 2007-2009 Selenium committers
  3. Portions copyright 2011 Software Freedom Conservancy
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. */
  14. package org.openqa.selenium.htmlunit;
  15. import com.google.common.collect.Collections2;
  16. import com.google.common.collect.ImmutableSet;
  17. import com.google.common.collect.Maps;
  18. import com.google.common.collect.Sets;
  19. import com.gargoylesoftware.htmlunit.BrowserVersion;
  20. import com.gargoylesoftware.htmlunit.CookieManager;
  21. import com.gargoylesoftware.htmlunit.ElementNotFoundException;
  22. import com.gargoylesoftware.htmlunit.Page;
  23. import com.gargoylesoftware.htmlunit.ProxyConfig;
  24. import com.gargoylesoftware.htmlunit.ScriptResult;
  25. import com.gargoylesoftware.htmlunit.SgmlPage;
  26. import com.gargoylesoftware.htmlunit.TopLevelWindow;
  27. import com.gargoylesoftware.htmlunit.WaitingRefreshHandler;
  28. import com.gargoylesoftware.htmlunit.WebClient;
  29. import com.gargoylesoftware.htmlunit.WebClientOptions;
  30. import com.gargoylesoftware.htmlunit.WebResponse;
  31. import com.gargoylesoftware.htmlunit.WebWindow;
  32. import com.gargoylesoftware.htmlunit.WebWindowEvent;
  33. import com.gargoylesoftware.htmlunit.WebWindowListener;
  34. import com.gargoylesoftware.htmlunit.WebWindowNotFoundException;
  35. import com.gargoylesoftware.htmlunit.html.BaseFrameElement;
  36. import com.gargoylesoftware.htmlunit.html.DomElement;
  37. import com.gargoylesoftware.htmlunit.html.DomNode;
  38. import com.gargoylesoftware.htmlunit.html.DomNodeList;
  39. import com.gargoylesoftware.htmlunit.html.FrameWindow;
  40. import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
  41. import com.gargoylesoftware.htmlunit.html.HtmlElement;
  42. import com.gargoylesoftware.htmlunit.html.HtmlPage;
  43. import com.gargoylesoftware.htmlunit.javascript.host.Location;
  44. import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLCollection;
  45. import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLElement;
  46. import net.sourceforge.htmlunit.corejs.javascript.Context;
  47. import net.sourceforge.htmlunit.corejs.javascript.ContextAction;
  48. import net.sourceforge.htmlunit.corejs.javascript.Function;
  49. import net.sourceforge.htmlunit.corejs.javascript.NativeArray;
  50. import net.sourceforge.htmlunit.corejs.javascript.NativeObject;
  51. import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
  52. import net.sourceforge.htmlunit.corejs.javascript.ScriptableObject;
  53. import net.sourceforge.htmlunit.corejs.javascript.Undefined;
  54. import org.openqa.selenium.Alert;
  55. import org.openqa.selenium.By;
  56. import org.openqa.selenium.Capabilities;
  57. import org.openqa.selenium.Cookie;
  58. import org.openqa.selenium.HasCapabilities;
  59. import org.openqa.selenium.HasInputDevices;
  60. import org.openqa.selenium.InvalidCookieDomainException;
  61. import org.openqa.selenium.InvalidSelectorException;
  62. import org.openqa.selenium.JavascriptExecutor;
  63. import org.openqa.selenium.Keyboard;
  64. import org.openqa.selenium.Mouse;
  65. import org.openqa.selenium.NoSuchElementException;
  66. import org.openqa.selenium.NoSuchFrameException;
  67. import org.openqa.selenium.NoSuchWindowException;
  68. import org.openqa.selenium.Platform;
  69. import org.openqa.selenium.Proxy;
  70. import org.openqa.selenium.SearchContext;
  71. import org.openqa.selenium.UnableToSetCookieException;
  72. import org.openqa.selenium.WebDriver;
  73. import org.openqa.selenium.WebDriverException;
  74. import org.openqa.selenium.WebElement;
  75. import org.openqa.selenium.browserlaunchers.Proxies;
  76. import org.openqa.selenium.internal.FindsByCssSelector;
  77. import org.openqa.selenium.internal.FindsById;
  78. import org.openqa.selenium.internal.FindsByLinkText;
  79. import org.openqa.selenium.internal.FindsByName;
  80. import org.openqa.selenium.internal.FindsByTagName;
  81. import org.openqa.selenium.internal.FindsByXPath;
  82. import org.openqa.selenium.internal.WrapsElement;
  83. import org.openqa.selenium.logging.Logs;
  84. import org.openqa.selenium.remote.BrowserType;
  85. import org.openqa.selenium.remote.CapabilityType;
  86. import org.openqa.selenium.remote.DesiredCapabilities;
  87. import org.w3c.dom.Node;
  88. import org.w3c.dom.NodeList;
  89. import java.io.IOException;
  90. import java.net.ConnectException;
  91. import java.net.URL;
  92. import java.net.UnknownHostException;
  93. import java.util.ArrayList;
  94. import java.util.Collection;
  95. import java.util.List;
  96. import java.util.Map;
  97. import java.util.Map.Entry;
  98. import java.util.Set;
  99. import java.util.concurrent.Callable;
  100. import java.util.concurrent.TimeUnit;
  101. import static org.openqa.selenium.remote.CapabilityType.SUPPORTS_FINDING_BY_CSS;
  102. public class HtmlUnitDriver implements WebDriver, JavascriptExecutor,
  103. FindsById, FindsByLinkText, FindsByXPath, FindsByName, FindsByCssSelector,
  104. FindsByTagName, HasCapabilities, HasInputDevices {
  105. private WebClient webClient;
  106. private WebWindow currentWindow;
  107. private boolean enableJavascript;
  108. private ProxyConfig proxyConfig;
  109. private long implicitWait = 0;
  110. private long scriptTimeout = 0;
  111. private HtmlUnitKeyboard keyboard;
  112. private HtmlUnitMouse mouse;
  113. private boolean gotPage;
  114. public static final String INVALIDXPATHERROR = "The xpath expression '%s' cannot be evaluated";
  115. public static final String INVALIDSELECTIONERROR =
  116. "The xpath expression '%s' selected an object of type '%s' instead of a WebElement";
  117. public HtmlUnitDriver(BrowserVersion version) {
  118. webClient = createWebClient(version);
  119. currentWindow = webClient.getCurrentWindow();
  120. webClient.addWebWindowListener(new WebWindowListener() {
  121. public void webWindowOpened(WebWindowEvent webWindowEvent) {
  122. // Ignore
  123. }
  124. public void webWindowContentChanged(WebWindowEvent event) {
  125. if (event.getWebWindow() != currentWindow) {
  126. return;
  127. }
  128. // Do we need to pick some new default content?
  129. switchToDefaultContentOfWindow(currentWindow);
  130. }
  131. public void webWindowClosed(WebWindowEvent event) {
  132. // Check if the event window refers to us or one of our parent windows
  133. // setup the currentWindow appropriately if necessary
  134. WebWindow curr = currentWindow;
  135. do {
  136. // Instance equality is okay in this case
  137. if (curr == event.getWebWindow()) {
  138. currentWindow = currentWindow.getTopWindow();
  139. return;
  140. }
  141. curr = curr.getParentWindow();
  142. } while (curr != currentWindow.getTopWindow());
  143. }
  144. });
  145. // Now put us on the home page, like a real browser
  146. get(webClient.getOptions().getHomePage());
  147. gotPage = false;
  148. resetKeyboardAndMouseState();
  149. }
  150. public HtmlUnitDriver() {
  151. this(false);
  152. }
  153. public HtmlUnitDriver(boolean enableJavascript) {
  154. this(BrowserVersion.getDefault());
  155. setJavascriptEnabled(enableJavascript);
  156. }
  157. /**
  158. * Note: There are two configuration modes for the HtmlUnitDriver using this constructor. The
  159. * first is where the browserName is "firefox", "internet explorer" and browserVersion denotes the
  160. * desired version. The second one is where the browserName is "htmlunit" and the browserVersion
  161. * denotes the required browser AND its version. In this mode the browserVersion could either be
  162. * "firefox" for Firefox or "internet explorer-7" for IE 7. The Remote WebDriver uses the second
  163. * mode - the first mode is deprecated and should not be used.
  164. */
  165. public HtmlUnitDriver(Capabilities capabilities) {
  166. this(determineBrowserVersion(capabilities));
  167. setJavascriptEnabled(capabilities.isJavascriptEnabled());
  168. if (capabilities.getCapability(CapabilityType.PROXY) != null) {
  169. Proxy proxy = Proxies.extractProxy(capabilities);
  170. String fullProxy = proxy.getHttpProxy();
  171. String pacfile = proxy.getProxyAutoconfigUrl();
  172. if (fullProxy != null) {
  173. int index = fullProxy.indexOf(":");
  174. if (index != -1) {
  175. String host = fullProxy.substring(0, index);
  176. int port = Integer.parseInt(fullProxy.substring(index + 1));
  177. setProxy(host, port);
  178. } else {
  179. setProxy(fullProxy, 0);
  180. }
  181. } else if(pacfile != null && !pacfile.equals("")) {
  182. setAutoProxy(pacfile);
  183. }
  184. }
  185. }
  186. // Package visibility for testing
  187. static BrowserVersion determineBrowserVersion(Capabilities capabilities) {
  188. String browserName = null;
  189. String browserVersion = null;
  190. String rawVersion = capabilities.getVersion();
  191. String[] splitVersion = rawVersion == null ? new String[0] : rawVersion.split("-");
  192. if (splitVersion.length > 1) {
  193. browserVersion = splitVersion[1];
  194. browserName = splitVersion[0];
  195. } else {
  196. browserName = capabilities.getVersion();
  197. browserVersion = "";
  198. }
  199. // This is for backwards compatibility - in case there are users who are trying to
  200. // configure the HtmlUnitDriver by using the c'tor with capabilities.
  201. if (!BrowserType.HTMLUNIT.equals(capabilities.getBrowserName())) {
  202. browserName = capabilities.getBrowserName();
  203. browserVersion = capabilities.getVersion();
  204. }
  205. if (BrowserType.FIREFOX.equals(browserName)) {
  206. if (browserVersion != null
  207. && (browserVersion.equals("3") || browserVersion.startsWith("3."))) {
  208. return BrowserVersion.FIREFOX_3_6;
  209. }
  210. return BrowserVersion.FIREFOX_10;
  211. }
  212. if (BrowserType.IE.equals(browserName)) {
  213. // Try and convert the version
  214. try {
  215. int version = Integer.parseInt(browserVersion);
  216. switch (version) {
  217. case 6:
  218. return BrowserVersion.INTERNET_EXPLORER_6;
  219. case 7:
  220. return BrowserVersion.INTERNET_EXPLORER_7;
  221. case 8:
  222. return BrowserVersion.INTERNET_EXPLORER_8;
  223. default:
  224. return BrowserVersion.INTERNET_EXPLORER_8;
  225. }
  226. } catch (NumberFormatException e) {
  227. return BrowserVersion.INTERNET_EXPLORER_8;
  228. }
  229. }
  230. return BrowserVersion.FIREFOX_10;
  231. }
  232. private WebClient createWebClient(BrowserVersion version) {
  233. WebClient client = newWebClient(version);
  234. WebClientOptions options = client.getOptions();
  235. options.setHomePage(WebClient.URL_ABOUT_BLANK.toString());
  236. options.setThrowExceptionOnFailingStatusCode(false);
  237. options.setPrintContentOnFailingStatusCode(false);
  238. options.setJavaScriptEnabled(enableJavascript);
  239. options.setRedirectEnabled(true);
  240. options.setUseInsecureSSL(true);
  241. // Ensure that we've set the proxy if necessary
  242. if (proxyConfig != null) {
  243. options.setProxyConfig(proxyConfig);
  244. }
  245. client.setRefreshHandler(new WaitingRefreshHandler());
  246. return modifyWebClient(client);
  247. }
  248. /**
  249. * Create the underlying webclient, but don't set any fields on it.
  250. *
  251. * @param version Which browser to emulate
  252. * @return a new instance of WebClient.
  253. */
  254. protected WebClient newWebClient(BrowserVersion version) {
  255. return new WebClient(version);
  256. }
  257. /**
  258. * Child classes can override this method to customise the webclient that the HtmlUnit driver
  259. * uses.
  260. *
  261. * @param client The client to modify
  262. * @return The modified client
  263. */
  264. protected WebClient modifyWebClient(WebClient client) {
  265. // Does nothing here to be overridden.
  266. return client;
  267. }
  268. public void setProxy(String host, int port) {
  269. proxyConfig = new ProxyConfig(host, port);
  270. webClient.getOptions().setProxyConfig(proxyConfig);
  271. }
  272. public void setAutoProxy(String autoProxyUrl) {
  273. proxyConfig = new ProxyConfig();
  274. proxyConfig.setProxyAutoConfigUrl(autoProxyUrl);
  275. webClient.getOptions().setProxyConfig(proxyConfig);
  276. }
  277. public Capabilities getCapabilities() {
  278. DesiredCapabilities capabilities = DesiredCapabilities.htmlUnit();
  279. capabilities.setPlatform(Platform.getCurrent());
  280. capabilities.setJavascriptEnabled(isJavascriptEnabled());
  281. capabilities.setCapability(SUPPORTS_FINDING_BY_CSS, true);
  282. return capabilities;
  283. }
  284. public void get(String url) {
  285. // Prevent the malformed url exception.
  286. if (WebClient.URL_ABOUT_BLANK.toString().equals(url)) {
  287. get(WebClient.URL_ABOUT_BLANK);
  288. return;
  289. }
  290. URL fullUrl;
  291. try {
  292. fullUrl = new URL(url);
  293. } catch (Exception e) {
  294. throw new WebDriverException(e);
  295. }
  296. get(fullUrl);
  297. }
  298. /**
  299. * Allows HtmlUnit's about:blank to be loaded in the constructor, and may be useful for other
  300. * tests?
  301. *
  302. * @param fullUrl The URL to visit
  303. */
  304. protected void get(URL fullUrl) {
  305. try {
  306. // A "get" works over the entire page
  307. currentWindow = currentWindow.getTopWindow();
  308. webClient.getPage(fullUrl);
  309. } catch (UnknownHostException e) {
  310. // This should be fine
  311. } catch (ConnectException e) {
  312. // This might be expected
  313. } catch (Exception e) {
  314. throw new WebDriverException(e);
  315. }
  316. gotPage = true;
  317. pickWindow();
  318. resetKeyboardAndMouseState();
  319. }
  320. private void resetKeyboardAndMouseState() {
  321. keyboard = new HtmlUnitKeyboard(this);
  322. mouse = new HtmlUnitMouse(this, keyboard);
  323. }
  324. protected void pickWindow() {
  325. // TODO(simon): HtmlUnit tries to track the current window as the frontmost. We don't
  326. if (currentWindow == null) {
  327. currentWindow = webClient.getCurrentWindow();
  328. }
  329. }
  330. public String getCurrentUrl() {
  331. // TODO(simon): Blech. I can see this being baaad
  332. URL url = getRawUrl();
  333. if (url == null) {
  334. return null;
  335. }
  336. return url.toString();
  337. }
  338. public String getTitle() {
  339. Page page = lastPage();
  340. if (page == null || !(page instanceof HtmlPage)) {
  341. return null; // no page so there is no title
  342. }
  343. if (currentWindow instanceof FrameWindow) {
  344. page = ((FrameWindow) currentWindow).getTopWindow().getEnclosedPage();
  345. }
  346. return ((HtmlPage) page).getTitleText();
  347. }
  348. public WebElement findElement(By by) {
  349. return findElement(by, this);
  350. }
  351. public List<WebElement> findElements(By by) {
  352. return findElements(by, this);
  353. }
  354. public String getPageSource() {
  355. Page page = lastPage();
  356. if (page == null) {
  357. return null;
  358. }
  359. if (page instanceof SgmlPage) {
  360. return ((SgmlPage) page).asXml();
  361. }
  362. WebResponse response = page.getWebResponse();
  363. return response.getContentAsString();
  364. }
  365. public void close() {
  366. if (currentWindow != null) {
  367. ((TopLevelWindow) currentWindow.getTopWindow()).close();
  368. }
  369. if (webClient.getWebWindows().size() == 0) {
  370. quit();
  371. }
  372. }
  373. public void quit() {
  374. if (webClient != null) {
  375. webClient.closeAllWindows();
  376. webClient = null;
  377. }
  378. currentWindow = null;
  379. }
  380. public Set<String> getWindowHandles() {
  381. final Set<String> allHandles = Sets.newHashSet();
  382. for (final WebWindow window : webClient.getTopLevelWindows()) {
  383. allHandles.add(String.valueOf(System.identityHashCode(window)));
  384. }
  385. return allHandles;
  386. }
  387. public String getWindowHandle() {
  388. WebWindow topWindow = currentWindow.getTopWindow();
  389. if (topWindow.isClosed()) {
  390. throw new NoSuchWindowException("Window is closed");
  391. }
  392. return String.valueOf(System.identityHashCode(topWindow));
  393. }
  394. public Object executeScript(String script, final Object... args) {
  395. HtmlPage page = getPageToInjectScriptInto();
  396. script = "function() {" + script + "\n};";
  397. ScriptResult result = page.executeJavaScript(script);
  398. Function func = (Function) result.getJavaScriptResult();
  399. Object[] parameters = convertScriptArgs(page, args);
  400. result = page.executeJavaScriptFunctionIfPossible(
  401. func,
  402. (ScriptableObject) currentWindow.getScriptObject(),
  403. parameters,
  404. page.getDocumentElement());
  405. return parseNativeJavascriptResult(result);
  406. }
  407. public Object executeAsyncScript(String script, Object... args) {
  408. HtmlPage page = getPageToInjectScriptInto();
  409. args = convertScriptArgs(page, args);
  410. Object result = new AsyncScriptExecutor(page, scriptTimeout)
  411. .execute(script, args);
  412. return parseNativeJavascriptResult(result);
  413. }
  414. private Object[] convertScriptArgs(HtmlPage page, final Object[] args) {
  415. final Scriptable scope = (Scriptable) page.getEnclosingWindow().getScriptObject();
  416. final Object[] parameters = new Object[args.length];
  417. final ContextAction action = new ContextAction() {
  418. public Object run(final Context context) {
  419. for (int i = 0; i < args.length; i++) {
  420. parameters[i] = parseArgumentIntoJavsacriptParameter(context, scope, args[i]);
  421. }
  422. return null;
  423. }
  424. };
  425. webClient.getJavaScriptEngine().getContextFactory().call(action);
  426. return parameters;
  427. }
  428. private HtmlPage getPageToInjectScriptInto() {
  429. if (!isJavascriptEnabled()) {
  430. throw new UnsupportedOperationException(
  431. "Javascript is not enabled for this HtmlUnitDriver instance");
  432. }
  433. final Page lastPage = lastPage();
  434. if (!(lastPage instanceof HtmlPage)) {
  435. throw new UnsupportedOperationException("Cannot execute JS against a plain text page");
  436. } else if (!gotPage) {
  437. // just to make ExecutingJavascriptTest.testShouldThrowExceptionIfExecutingOnNoPage happy
  438. // but does this limitation make sense?
  439. throw new WebDriverException("Can't execute JavaScript before a page has been loaded!");
  440. }
  441. return (HtmlPage) lastPage;
  442. }
  443. private Object parseArgumentIntoJavsacriptParameter(
  444. Context context, Scriptable scope, Object arg) {
  445. while (arg instanceof WrapsElement) {
  446. arg = ((WrapsElement) arg).getWrappedElement();
  447. }
  448. if (!(arg instanceof HtmlUnitWebElement ||
  449. arg instanceof HtmlElement || // special case the underlying type
  450. arg instanceof Number ||
  451. arg instanceof String ||
  452. arg instanceof Boolean ||
  453. arg.getClass().isArray() || arg instanceof Collection<?>)) {
  454. throw new IllegalArgumentException(
  455. "Argument must be a string, number, boolean or WebElement: " +
  456. arg + " (" + arg.getClass() + ")");
  457. }
  458. if (arg instanceof HtmlUnitWebElement) {
  459. HtmlElement element = ((HtmlUnitWebElement) arg).getElement();
  460. return element.getScriptObject();
  461. } else if (arg instanceof HtmlElement) {
  462. return ((HtmlElement) arg).getScriptObject();
  463. } else if (arg instanceof Collection<?>) {
  464. List<Object> list = new ArrayList<Object>();
  465. for (Object o : (Collection<?>) arg) {
  466. list.add(parseArgumentIntoJavsacriptParameter(context, scope, o));
  467. }
  468. return context.newArray(scope, list.toArray());
  469. } else {
  470. return arg;
  471. }
  472. }
  473. public Keyboard getKeyboard() {
  474. return keyboard;
  475. }
  476. public Mouse getMouse() {
  477. return mouse;
  478. }
  479. protected interface JavaScriptResultsCollection {
  480. int getLength();
  481. Object item(int index);
  482. }
  483. private Object parseNativeJavascriptResult(Object result) {
  484. Object value;
  485. if (result instanceof ScriptResult) {
  486. value = ((ScriptResult) result).getJavaScriptResult();
  487. } else {
  488. value = result;
  489. }
  490. if (value instanceof HTMLElement) {
  491. return newHtmlUnitWebElement(((HTMLElement) value).getDomNodeOrDie());
  492. }
  493. if (value instanceof Number) {
  494. final Number n = (Number) value;
  495. final String s = n.toString();
  496. if (s.indexOf(".") == -1 || s.endsWith(".0")) { // how safe it is? enough for the unit tests!
  497. return n.longValue();
  498. }
  499. return n.doubleValue();
  500. }
  501. if (value instanceof NativeObject) {
  502. final Map<String, Object> map = Maps.newHashMap((NativeObject) value);
  503. for (final Entry<String, Object> e : map.entrySet()) {
  504. e.setValue(parseNativeJavascriptResult(e.getValue()));
  505. }
  506. return map;
  507. }
  508. if (value instanceof Location) {
  509. return convertLocationtoMap((Location) value);
  510. }
  511. if (value instanceof NativeArray) {
  512. final NativeArray array = (NativeArray) value;
  513. JavaScriptResultsCollection collection = new JavaScriptResultsCollection() {
  514. public int getLength() {
  515. return (int) array.getLength();
  516. }
  517. public Object item(int index) {
  518. return array.get(index);
  519. }
  520. };
  521. return parseJavascriptResultsList(collection);
  522. }
  523. if (value instanceof HTMLCollection) {
  524. final HTMLCollection array = (HTMLCollection) value;
  525. JavaScriptResultsCollection collection = new JavaScriptResultsCollection() {
  526. public int getLength() {
  527. return array.getLength();
  528. }
  529. public Object item(int index) {
  530. return array.get(index);
  531. }
  532. };
  533. return parseJavascriptResultsList(collection);
  534. }
  535. if (value instanceof Undefined) {
  536. return null;
  537. }
  538. return value;
  539. }
  540. private Map<String, Object> convertLocationtoMap(Location location) {
  541. Map<String, Object> map = Maps.newHashMap();
  542. map.put("href", location.getHref());
  543. map.put("protocol", location.getProtocol());
  544. map.put("host", location.getHost());
  545. map.put("hostname", location.getHostname());
  546. map.put("port", location.getPort());
  547. map.put("pathname", location.getPathname());
  548. map.put("search", location.getSearch());
  549. map.put("hash", location.getHash());
  550. map.put("href", location.getHref());
  551. return map;
  552. }
  553. private List<Object> parseJavascriptResultsList(JavaScriptResultsCollection array) {
  554. List<Object> list = new ArrayList<Object>(array.getLength());
  555. for (int i = 0; i < array.getLength(); ++i) {
  556. list.add(parseNativeJavascriptResult(array.item(i)));
  557. }
  558. return list;
  559. }
  560. public TargetLocator switchTo() {
  561. return new HtmlUnitTargetLocator();
  562. }
  563. private void switchToDefaultContentOfWindow(WebWindow window) {
  564. Page page = window.getEnclosedPage();
  565. if (page instanceof HtmlPage) {
  566. currentWindow = window;
  567. }
  568. }
  569. public Navigation navigate() {
  570. return new HtmlUnitNavigation();
  571. }
  572. protected Page lastPage() {
  573. if (currentWindow == null || currentWindow.isClosed()) {
  574. throw new NoSuchWindowException("The current window was closed");
  575. }
  576. return currentWindow.getEnclosedPage();
  577. }
  578. public WebElement findElementByLinkText(String selector) {
  579. if (!(lastPage() instanceof HtmlPage)) {
  580. throw new IllegalStateException("Cannot find links for " + lastPage());
  581. }
  582. String expectedText = selector.trim();
  583. List<HtmlAnchor> anchors = ((HtmlPage) lastPage()).getAnchors();
  584. for (HtmlAnchor anchor : anchors) {
  585. if (expectedText.equals(anchor.asText().trim())) {
  586. return newHtmlUnitWebElement(anchor);
  587. }
  588. }
  589. throw new NoSuchElementException("No link found with text: " + expectedText);
  590. }
  591. protected WebElement newHtmlUnitWebElement(HtmlElement element) {
  592. return new HtmlUnitWebElement(this, element);
  593. }
  594. public List<WebElement> findElementsByLinkText(String selector) {
  595. List<WebElement> elements = new ArrayList<WebElement>();
  596. if (!(lastPage() instanceof HtmlPage)) {
  597. return elements;
  598. }
  599. String expectedText = selector.trim();
  600. List<HtmlAnchor> anchors = ((HtmlPage) lastPage()).getAnchors();
  601. for (HtmlAnchor anchor : anchors) {
  602. if (expectedText.equals(anchor.asText().trim())) {
  603. elements.add(newHtmlUnitWebElement(anchor));
  604. }
  605. }
  606. return elements;
  607. }
  608. public WebElement findElementById(String id) {
  609. if (!(lastPage() instanceof HtmlPage)) {
  610. throw new NoSuchElementException("Unable to locate element by id for " + lastPage());
  611. }
  612. try {
  613. HtmlElement element = ((HtmlPage) lastPage()).getHtmlElementById(id);
  614. return newHtmlUnitWebElement(element);
  615. } catch (ElementNotFoundException e) {
  616. throw new NoSuchElementException("Unable to locate element with ID: " + id);
  617. }
  618. }
  619. public List<WebElement> findElementsById(String id) {
  620. return findElementsByXPath("//*[@id='" + id + "']");
  621. }
  622. public WebElement findElementByCssSelector(String using) {
  623. if (!(lastPage() instanceof HtmlPage)) {
  624. throw new NoSuchElementException("Unable to locate element using css: " + lastPage());
  625. }
  626. DomNode node = ((HtmlPage) lastPage()).querySelector(using);
  627. if (node instanceof HtmlElement) {
  628. return newHtmlUnitWebElement((HtmlElement) node);
  629. }
  630. throw new NoSuchElementException("Returned node was not an HTML element");
  631. }
  632. public List<WebElement> findElementsByCssSelector(String using) {
  633. if (!(lastPage() instanceof HtmlPage)) {
  634. throw new NoSuchElementException("Unable to locate element using css: " + lastPage());
  635. }
  636. DomNodeList<DomNode> allNodes = ((HtmlPage) lastPage()).querySelectorAll(using);
  637. List<WebElement> toReturn = new ArrayList<WebElement>();
  638. for (DomNode node : allNodes) {
  639. if (node instanceof HtmlElement) {
  640. toReturn.add(newHtmlUnitWebElement((HtmlElement) node));
  641. } else {
  642. throw new NoSuchElementException("Returned node was not an HTML element");
  643. }
  644. }
  645. return toReturn;
  646. }
  647. public WebElement findElementByName(String name) {
  648. if (!(lastPage() instanceof HtmlPage)) {
  649. throw new IllegalStateException("Unable to locate element by name for " + lastPage());
  650. }
  651. List<DomElement> allElements = ((HtmlPage) lastPage()).getElementsByName(name);
  652. if (!allElements.isEmpty()) {
  653. return newHtmlUnitWebElement((HtmlElement) allElements.get(0));
  654. }
  655. throw new NoSuchElementException("Unable to locate element with name: " + name);
  656. }
  657. public List<WebElement> findElementsByName(String using) {
  658. if (!(lastPage() instanceof HtmlPage)) {
  659. return new ArrayList<WebElement>();
  660. }
  661. List<DomElement> allElements = ((HtmlPage) lastPage()).getElementsByName(using);
  662. return convertRawHtmlElementsToWebElements(allElements);
  663. }
  664. public WebElement findElementByTagName(String name) {
  665. if (!(lastPage() instanceof HtmlPage)) {
  666. throw new IllegalStateException("Unable to locate element by name for " + lastPage());
  667. }
  668. NodeList allElements = ((HtmlPage) lastPage()).getElementsByTagName(name);
  669. if (allElements.getLength() > 0) {
  670. return newHtmlUnitWebElement((HtmlElement) allElements.item(0));
  671. }
  672. throw new NoSuchElementException("Unable to locate element with name: " + name);
  673. }
  674. public List<WebElement> findElementsByTagName(String using) {
  675. if (!(lastPage() instanceof HtmlPage)) {
  676. return new ArrayList<WebElement>();
  677. }
  678. NodeList allElements = ((HtmlPage) lastPage()).getElementsByTagName(using);
  679. List<WebElement> toReturn = new ArrayList<WebElement>(allElements.getLength());
  680. for (int i = 0; i < allElements.getLength(); i++) {
  681. Node item = allElements.item(i);
  682. if (item instanceof HtmlElement) {
  683. toReturn.add(newHtmlUnitWebElement((HtmlElement) item));
  684. }
  685. }
  686. return toReturn;
  687. }
  688. public WebElement findElementByXPath(String selector) {
  689. if (!(lastPage() instanceof HtmlPage)) {
  690. throw new IllegalStateException("Unable to locate element by xpath for " + lastPage());
  691. }
  692. Object node;
  693. try {
  694. node = ((HtmlPage) lastPage()).getFirstByXPath(selector);
  695. } catch (Exception ex) {
  696. // The xpath expression cannot be evaluated, so the expression is invalid
  697. throw new InvalidSelectorException(
  698. String.format(INVALIDXPATHERROR, selector),
  699. ex);
  700. }
  701. if (node == null) {
  702. throw new NoSuchElementException("Unable to locate a node using " + selector);
  703. }
  704. if (node instanceof HtmlElement) {
  705. return newHtmlUnitWebElement((HtmlElement) node);
  706. }
  707. // The xpath expression selected something different than a WebElement.
  708. // The selector is therefore invalid
  709. throw new InvalidSelectorException(
  710. String.format(INVALIDSELECTIONERROR, selector, node.getClass()));
  711. }
  712. public List<WebElement> findElementsByXPath(String selector) {
  713. if (!(lastPage() instanceof HtmlPage)) {
  714. return new ArrayList<WebElement>();
  715. }
  716. List<?> nodes;
  717. List<WebElement> result;
  718. try {
  719. nodes = ((HtmlPage) lastPage()).getByXPath(selector);
  720. result = convertRawHtmlElementsToWebElements(nodes);
  721. } catch (RuntimeException ex) {
  722. // The xpath expression cannot be evaluated, so the expression is invalid
  723. throw new InvalidSelectorException(String.format(INVALIDXPATHERROR, selector), ex);
  724. }
  725. if (nodes.size() != result.size()) {
  726. // There exist elements in the nodes list which could not be converted to WebElements.
  727. // A valid xpath selector should only select WebElements.
  728. // Find out the type of the element which is not a WebElement
  729. for (Object node : nodes) {
  730. if (!(node instanceof HtmlElement)) {
  731. // We only want to know the type of one invalid element so that we can give this
  732. // information in the exception. We can throw the exception immediately.
  733. throw new InvalidSelectorException(
  734. String.format(INVALIDSELECTIONERROR, selector, node.getClass()));
  735. }
  736. }
  737. }
  738. return result;
  739. }
  740. private List<WebElement> convertRawHtmlElementsToWebElements(List<?> nodes) {
  741. List<WebElement> elements = new ArrayList<WebElement>();
  742. for (Object node : nodes) {
  743. if (node instanceof HtmlElement) {
  744. elements.add(newHtmlUnitWebElement((HtmlElement) node));
  745. }
  746. }
  747. return elements;
  748. }
  749. public boolean isJavascriptEnabled() {
  750. return webClient.getOptions().isJavaScriptEnabled();
  751. }
  752. public void setJavascriptEnabled(boolean enableJavascript) {
  753. this.enableJavascript = enableJavascript;
  754. webClient.getOptions().setJavaScriptEnabled(enableJavascript);
  755. }
  756. private class HtmlUnitTargetLocator implements TargetLocator {
  757. public WebDriver frame(int index) {
  758. HtmlPage currentPage = (HtmlPage) currentWindow.getEnclosedPage();
  759. try {
  760. // 1.) try to find frame in current window ...
  761. currentWindow = currentPage.getFrames().get(index);
  762. } catch (IndexOutOfBoundsException ignored) {
  763. throw new NoSuchFrameException("Cannot find frame: " + index);
  764. }
  765. return HtmlUnitDriver.this;
  766. }
  767. public WebDriver frame(final String nameOrId) {
  768. // First check for a frame with the matching name.
  769. HtmlPage startPage = (HtmlPage) currentWindow.getEnclosedPage();
  770. for (final FrameWindow frameWindow : startPage.getFrames()) {
  771. if (frameWindow.getName().equals(nameOrId)) {
  772. currentWindow = frameWindow;
  773. return HtmlUnitDriver.this;
  774. }
  775. }
  776. // Next, check for a frame with a matching ID. For simplicity, assume the ID is unique.
  777. // Users can still switch to frames with non-unique IDs using a WebElement switch:
  778. // WebElement frameElement = driver.findElement(By.xpath("//frame[@id=\"foo\"]"));
  779. // driver.switchTo().frame(frameElement);
  780. try {
  781. HtmlUnitWebElement element =
  782. (HtmlUnitWebElement) HtmlUnitDriver.this.findElementById(nameOrId);
  783. HtmlElement domElement = element.getElement();
  784. if (domElement instanceof BaseFrameElement) {
  785. currentWindow = ((BaseFrameElement) domElement).getEnclosedWindow();
  786. return HtmlUnitDriver.this;
  787. }
  788. } catch (NoSuchElementException ignored) {
  789. }
  790. throw new NoSuchFrameException("Unable to locate frame with name or ID: " + nameOrId);
  791. }
  792. public WebDriver frame(WebElement frameElement) {
  793. while (frameElement instanceof WrapsElement) {
  794. frameElement = ((WrapsElement) frameElement).getWrappedElement();
  795. }
  796. HtmlUnitWebElement webElement = (HtmlUnitWebElement) frameElement;
  797. webElement.assertElementNotStale();
  798. HtmlElement domElement = webElement.getElement();
  799. if (!(domElement instanceof BaseFrameElement)) {
  800. throw new NoSuchFrameException(webElement.getTagName() + " is not a frame element.");
  801. }
  802. currentWindow = ((BaseFrameElement) domElement).getEnclosedWindow();
  803. return HtmlUnitDriver.this;
  804. }
  805. public WebDriver window(String windowId) {
  806. try {
  807. WebWindow window = webClient.getWebWindowByName(windowId);
  808. return finishSelecting(window);
  809. } catch (WebWindowNotFoundException e) {
  810. List<WebWindow> allWindows = webClient.getWebWindows();
  811. for (WebWindow current : allWindows) {
  812. WebWindow top = current.getTopWindow();
  813. if (String.valueOf(System.identityHashCode(top)).equals(windowId)) {
  814. return finishSelecting(top);
  815. }
  816. }
  817. throw new NoSuchWindowException("Cannot find window: " + windowId);
  818. }
  819. }
  820. private WebDriver finishSelecting(WebWindow window) {
  821. webClient.setCurrentWindow(window);
  822. currentWindow = window;
  823. pickWindow();
  824. return HtmlUnitDriver.this;
  825. }
  826. public WebDriver defaultContent() {
  827. switchToDefaultContentOfWindow(currentWindow.getTopWindow());
  828. return HtmlUnitDriver.this;
  829. }
  830. public WebElement activeElement() {
  831. Page page = currentWindow.getEnclosedPage();
  832. if (page instanceof HtmlPage) {
  833. HtmlElement element = ((HtmlPage) page).getFocusedElement();
  834. if (element == null) {
  835. List<? extends HtmlElement> allBodies =
  836. ((HtmlPage) page).getDocumentElement().getHtmlElementsByTagName("body");
  837. if (!allBodies.isEmpty()) {
  838. return newHtmlUnitWebElement(allBodies.get(0));
  839. }
  840. } else {
  841. return newHtmlUnitWebElement(element);
  842. }
  843. }
  844. throw new NoSuchElementException("Unable to locate element with focus or body tag");
  845. }
  846. public Alert alert() {
  847. throw new UnsupportedOperationException("alert()");
  848. }
  849. }
  850. protected <X> X implicitlyWaitFor(Callable<X> condition) {
  851. long end = System.currentTimeMillis() + implicitWait;
  852. Exception lastException = null;
  853. do {
  854. X toReturn = null;
  855. try {
  856. toReturn = condition.call();
  857. } catch (Exception e) {
  858. lastException = e;
  859. }
  860. if (toReturn instanceof Boolean && !(Boolean) toReturn) {
  861. continue;
  862. }
  863. if (toReturn != null) {
  864. return toReturn;
  865. }
  866. sleepQuietly(200);
  867. } while (System.currentTimeMillis() < end);
  868. if (lastException != null) {
  869. if (lastException instanceof RuntimeException) {
  870. throw (RuntimeException) lastException;
  871. }
  872. throw new WebDriverException(lastException);
  873. }
  874. return null;
  875. }
  876. protected WebClient getWebClient() {
  877. return webClient;
  878. }
  879. protected WebWindow getCurrentWindow() {
  880. return currentWindow;
  881. }
  882. private URL getRawUrl() {
  883. // TODO(simon): I can see this being baaad.
  884. Page page = lastPage();
  885. if (page == null) {
  886. return null;
  887. }
  888. return page.getUrl();
  889. }
  890. private class HtmlUnitNavigation implements Navigation {
  891. public void back() {
  892. try {
  893. currentWindow.getHistory().back();
  894. } catch (IOException e) {
  895. throw new WebDriverException(e);
  896. }
  897. }
  898. public void forward() {
  899. try {
  900. currentWindow.getHistory().forward();
  901. } catch (IOException e) {
  902. throw new WebDriverException(e);
  903. }
  904. }
  905. public void to(String url) {
  906. get(url);
  907. }
  908. public void to(URL url) {
  909. get(url);
  910. }
  911. public void refresh() {
  912. if (lastPage() instanceof HtmlPage) {
  913. try {
  914. ((HtmlPage) lastPage()).refresh();
  915. } catch (IOException e) {
  916. throw new WebDriverException(e);
  917. }
  918. }
  919. }
  920. }
  921. public Options manage() {
  922. return new HtmlUnitOptions();
  923. }
  924. private class HtmlUnitOptions implements Options {
  925. public Logs logs() {
  926. throw new UnsupportedOperationException("Driver does not support this operation.");
  927. }
  928. public void addCookie(Cookie cookie) {
  929. Page page = lastPage();
  930. if (!(page instanceof HtmlPage)) {
  931. throw new UnableToSetCookieException("You may not set cookies on a page that is not HTML");
  932. }
  933. String domain = getDomainForCookie();
  934. verifyDomain(cookie, domain);
  935. webClient.getCookieManager().addCookie(
  936. new com.gargoylesoftware.htmlunit.util.Cookie(domain, cookie.getName(),
  937. cookie.getValue(),
  938. cookie.getPath(), cookie.getExpiry(), cookie.isSecure()));
  939. }
  940. private void verifyDomain(Cookie cookie, String expectedDomain) {
  941. String domain = cookie.getDomain();
  942. if (domain == null) {
  943. return;
  944. }
  945. if ("".equals(domain)) {
  946. throw new InvalidCookieDomainException(
  947. "Domain must not be an empty string. Consider using null instead");
  948. }
  949. // Line-noise-tastic
  950. if (domain.matches(".*[^:]:\\d+$")) {
  951. domain = domain.replaceFirst(":\\d+$", "");
  952. }
  953. expectedDomain = expectedDomain.startsWith(".") ? expectedDomain : "." + expectedDomain;
  954. domain = domain.startsWith(".") ? domain : "." + domain;
  955. if (!expectedDomain.endsWith(domain)) {
  956. throw new InvalidCookieDomainException(
  957. String.format(
  958. "You may only add cookies that would be visible to the current domain: %s => %s",
  959. domain, expectedDomain));
  960. }
  961. }
  962. public Cookie getCookieNamed(String name) {
  963. Set<Cookie> allCookies = getCookies();
  964. for (Cookie cookie : allCookies) {
  965. if (name.equals(cookie.getName())) {
  966. return cookie;
  967. }
  968. }
  969. return null;
  970. }
  971. public void deleteCookieNamed(String name) {
  972. CookieManager cookieManager = webClient.getCookieManager();
  973. URL url = getRawUrl();
  974. Set<com.gargoylesoftware.htmlunit.util.Cookie> rawCookies =
  975. webClient.getCookieManager().getCookies(url);
  976. for (com.gargoylesoftware.htmlunit.util.Cookie cookie : rawCookies) {
  977. if (name.equals(cookie.getName())) {
  978. cookieManager.removeCookie(cookie);
  979. }
  980. }
  981. }
  982. public void deleteCookie(Cookie cookie) {
  983. deleteCookieNamed(cookie.getName());
  984. }
  985. public void deleteAllCookies() {
  986. webClient.getCookieManager().clearCookies();
  987. }
  988. public Set<Cookie> getCookies() {
  989. URL url = getRawUrl();
  990. // The about:blank URL (the default in case no navigation took place)
  991. // does not have a valid 'hostname' part and cannot be used for creating
  992. // cookies based on it - return an empty set.
  993. if (!url.toString().startsWith("http")) {
  994. return Sets.newHashSet();
  995. }
  996. return ImmutableSet.copyOf(Collections2.transform(
  997. webClient.getCookieManager().getCookies(url),
  998. htmlUnitCookieToSeleniumCookieTransformer));
  999. }
  1000. private final com.google.common.base.Function<? super com.gargoylesoftware.htmlunit.util.Cookie, org.openqa.selenium.Cookie> htmlUnitCookieToSeleniumCookieTransformer =
  1001. new com.google.common.base.Function<com.gargoylesoftware.htmlunit.util.Cookie, org.openqa.selenium.Cookie>() {
  1002. public org.openqa.selenium.Cookie apply(com.gargoylesoftware.htmlunit.util.Cookie c) {
  1003. return new Cookie.Builder(c.getName(), c.getValue())
  1004. .domain(c.getDomain())
  1005. .path(c.getPath())
  1006. .expiresOn(c.getExpires())
  1007. .isSecure(c.isSecure())
  1008. .build();
  1009. }
  1010. };
  1011. private String getDomainForCookie() {
  1012. URL current = getRawUrl();
  1013. return current.getHost();
  1014. }
  1015. public Timeouts timeouts() {
  1016. return new HtmlUnitTimeouts();
  1017. }
  1018. public ImeHandler ime() {
  1019. throw new UnsupportedOperationException("Cannot input IME using HtmlUnit.");
  1020. }
  1021. public Window window() {
  1022. throw new UnsupportedOperationException("Window handling not yet implemented in HtmlUnit");
  1023. }
  1024. }
  1025. class HtmlUnitTimeouts implements Timeouts {
  1026. public Timeouts implicitlyWait(long time, TimeUnit unit) {
  1027. HtmlUnitDriver.this.implicitWait =
  1028. TimeUnit.MILLISECONDS.convert(Math.max(0, time), unit);
  1029. return this;
  1030. }
  1031. public Timeouts setScriptTimeout(long time, TimeUnit unit) {
  1032. HtmlUnitDriver.this.scriptTimeout = TimeUnit.MILLISECONDS.convert(time, unit);
  1033. return this;
  1034. }
  1035. public Timeouts pageLoadTimeout(long time, TimeUnit unit) {
  1036. throw new UnsupportedOperationException("pageLoadTimeout");
  1037. }
  1038. }
  1039. public WebElement findElementByPartialLinkText(String using) {
  1040. if (!(lastPage() instanceof HtmlPage)) {
  1041. throw new IllegalStateException("Cannot find links for " + lastPage());
  1042. }
  1043. List<HtmlAnchor> anchors = ((HtmlPage) lastPage()).getAnchors();
  1044. for (HtmlAnchor anchor : anchors) {
  1045. if (anchor.asText().contains(using)) {
  1046. return newHtmlUnitWebElement(anchor);
  1047. }
  1048. }
  1049. throw new NoSuchElementException("No link found with text: " + using);
  1050. }
  1051. public List<WebElement> findElementsByPartialLinkText(String using) {
  1052. List<HtmlAnchor> anchors = ((HtmlPage) lastPage()).getAnchors();
  1053. List<WebElement> elements = new ArrayList<WebElement>();
  1054. for (HtmlAnchor anchor : anchors) {
  1055. if (anchor.asText().contains(using)) {
  1056. elements.add(newHtmlUnitWebElement(anchor));
  1057. }
  1058. }
  1059. return elements;
  1060. }
  1061. WebElement findElement(final By locator, final SearchContext context) {
  1062. return implicitlyWaitFor(new Callable<WebElement>() {
  1063. public WebElement call() throws Exception {
  1064. return locator.findElement(context);
  1065. }
  1066. });
  1067. }
  1068. List<WebElement> findElements(final By by, final SearchContext context) {
  1069. long end = System.currentTimeMillis() + implicitWait;
  1070. List<WebElement> found;
  1071. do {
  1072. found = by.findElements(context);
  1073. if (!found.isEmpty()) {
  1074. return found;
  1075. }
  1076. } while (System.currentTimeMillis() < end);
  1077. return found;
  1078. }
  1079. private static void sleepQuietly(long ms) {
  1080. try {
  1081. Thread.sleep(ms);
  1082. } catch (InterruptedException ignored) {
  1083. }
  1084. }
  1085. }