PageRenderTime 57ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/java/client/src/org/openqa/selenium/android/library/AndroidWebDriver.java

https://bitbucket.org/abahdanovich/selenium
Java | 1339 lines | 1109 code | 140 blank | 90 comment | 110 complexity | 890ebd5fd27a50e61ed81bf1d32cbc8f 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 2011 Selenium committers
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package org.openqa.selenium.android.library;
  14. import android.app.Activity;
  15. import android.content.Context;
  16. import android.content.Intent;
  17. import android.graphics.Bitmap;
  18. import android.graphics.Canvas;
  19. import android.graphics.Picture;
  20. import android.location.LocationListener;
  21. import android.location.LocationManager;
  22. import android.os.Bundle;
  23. import android.os.Environment;
  24. import android.os.Looper;
  25. import android.provider.Settings;
  26. import android.view.View;
  27. import android.webkit.CookieManager;
  28. import android.webkit.CookieSyncManager;
  29. import android.webkit.WebView;
  30. import com.google.common.base.Supplier;
  31. import com.google.common.collect.Lists;
  32. import com.google.common.collect.Maps;
  33. import org.json.JSONArray;
  34. import org.json.JSONException;
  35. import org.json.JSONObject;
  36. import org.openqa.selenium.Alert;
  37. import org.openqa.selenium.Beta;
  38. import org.openqa.selenium.By;
  39. import org.openqa.selenium.Cookie;
  40. import org.openqa.selenium.HasTouchScreen;
  41. import org.openqa.selenium.JavascriptExecutor;
  42. import org.openqa.selenium.NoAlertPresentException;
  43. import org.openqa.selenium.NoSuchElementException;
  44. import org.openqa.selenium.NoSuchFrameException;
  45. import org.openqa.selenium.NoSuchWindowException;
  46. import org.openqa.selenium.OutputType;
  47. import org.openqa.selenium.Rotatable;
  48. import org.openqa.selenium.ScreenOrientation;
  49. import org.openqa.selenium.SearchContext;
  50. import org.openqa.selenium.StaleElementReferenceException;
  51. import org.openqa.selenium.TakesScreenshot;
  52. import org.openqa.selenium.TouchScreen;
  53. import org.openqa.selenium.WebDriver;
  54. import org.openqa.selenium.WebDriverException;
  55. import org.openqa.selenium.WebElement;
  56. import org.openqa.selenium.html5.AppCacheStatus;
  57. import org.openqa.selenium.html5.ApplicationCache;
  58. import org.openqa.selenium.html5.BrowserConnection;
  59. import org.openqa.selenium.html5.LocalStorage;
  60. import org.openqa.selenium.html5.Location;
  61. import org.openqa.selenium.html5.LocationContext;
  62. import org.openqa.selenium.html5.SessionStorage;
  63. import org.openqa.selenium.html5.WebStorage;
  64. import org.openqa.selenium.internal.Base64Encoder;
  65. import org.openqa.selenium.internal.FindsByClassName;
  66. import org.openqa.selenium.internal.FindsByCssSelector;
  67. import org.openqa.selenium.internal.FindsById;
  68. import org.openqa.selenium.internal.FindsByLinkText;
  69. import org.openqa.selenium.internal.FindsByName;
  70. import org.openqa.selenium.internal.FindsByTagName;
  71. import org.openqa.selenium.internal.FindsByXPath;
  72. import org.openqa.selenium.internal.WrapsElement;
  73. import org.openqa.selenium.io.IOUtils;
  74. import org.openqa.selenium.logging.Logs;
  75. import org.openqa.selenium.remote.ErrorCodes;
  76. import java.io.ByteArrayOutputStream;
  77. import java.io.File;
  78. import java.io.FileNotFoundException;
  79. import java.io.FileWriter;
  80. import java.io.IOException;
  81. import java.net.URL;
  82. import java.util.ArrayList;
  83. import java.util.Iterator;
  84. import java.util.List;
  85. import java.util.Map;
  86. import java.util.Set;
  87. import java.util.concurrent.TimeUnit;
  88. import java.util.logging.Level;
  89. public class AndroidWebDriver implements WebDriver, SearchContext, JavascriptExecutor,
  90. TakesScreenshot, Rotatable, BrowserConnection, HasTouchScreen,
  91. WebStorage, LocationContext, LocationListener, ApplicationCache {
  92. private static final String ELEMENT_KEY = "ELEMENT";
  93. private static final String WINDOW_KEY = "WINDOW";
  94. private static final String STATUS = "status";
  95. private static final String VALUE = "value";
  96. private AndroidWebElement element;
  97. private DomWindow currentWindowOrFrame;
  98. private long implicitWait = 0;
  99. // Maps the element ID to the AndroidWebElement
  100. private Map<String, AndroidWebElement> store;
  101. private AndroidTouchScreen touchScreen;
  102. private AndroidNavigation navigation;
  103. private AndroidOptions options;
  104. private AndroidLocalStorage localStorage;
  105. private AndroidSessionStorage sessionStorage;
  106. private AndroidTargetLocator targetLocator;
  107. private AndroidFindBy findBy;
  108. private AndroidLogs logs;
  109. // Use for control redirect, contains the last url loaded (updated after each redirect)
  110. private volatile String lastUrlLoaded;
  111. private SessionCookieManager sessionCookieManager;
  112. private ViewAdapter view;
  113. private WebDriverViewManager viewManager;
  114. private final Object syncObject = new Object();
  115. private volatile boolean pageDoneLoading;
  116. private NetworkStateHandler networkHandler;
  117. private Activity activity;
  118. private volatile boolean editAreaHasFocus;
  119. private volatile String result;
  120. private volatile boolean resultReady;
  121. // Timeouts in milliseconds
  122. private static final long LOADING_TIMEOUT = 30000L;
  123. private static final long START_LOADING_TIMEOUT = 700L;
  124. static final long RESPONSE_TIMEOUT = 10000L;
  125. private static final long FOCUS_TIMEOUT = 1000L;
  126. private static final long POLLING_INTERVAL = 50L;
  127. static final long UI_TIMEOUT = 3000L;
  128. private boolean acceptSslCerts;
  129. private volatile boolean pageStartedLoading;
  130. private boolean done = false;
  131. private Supplier<LocationManager> locManagerSupplier;
  132. private String locationProvider;
  133. private JavascriptResultNotifier notifier = new JavascriptResultNotifier() {
  134. public void notifyResultReady(String updated) {
  135. synchronized (syncObject) {
  136. result = updated;
  137. resultReady = true;
  138. syncObject.notify();
  139. }
  140. }
  141. };
  142. private AndroidWebElement getOrCreateWebElement(String id) {
  143. if (store.get(id) != null) {
  144. return store.get(id);
  145. } else {
  146. AndroidWebElement toReturn = new AndroidWebElement(this, id);
  147. store.put(id, toReturn);
  148. return toReturn;
  149. }
  150. }
  151. public void setAcceptSslCerts(boolean accept) {
  152. acceptSslCerts = accept;
  153. }
  154. public boolean getAcceptSslCerts() {
  155. return acceptSslCerts;
  156. }
  157. private void initDriverState() {
  158. store = Maps.newHashMap();
  159. findBy = new AndroidFindBy();
  160. currentWindowOrFrame = new DomWindow("");
  161. store = Maps.newHashMap();
  162. touchScreen = new AndroidTouchScreen(this);
  163. navigation = new AndroidNavigation();
  164. options = new AndroidOptions();
  165. element = getOrCreateWebElement("");
  166. localStorage = new AndroidLocalStorage(this);
  167. sessionStorage = new AndroidSessionStorage(this);
  168. targetLocator = new AndroidTargetLocator();
  169. viewManager = new WebDriverViewManager();
  170. logs = new AndroidLogs();
  171. Looper.prepare();
  172. try {
  173. locationProvider = LocationManager.GPS_PROVIDER;
  174. final LocationManager locManager =
  175. (LocationManager)activity.getSystemService(Context.LOCATION_SERVICE);
  176. locManagerSupplier = new Supplier<LocationManager>() {
  177. public LocationManager get() {
  178. return locManager;
  179. }
  180. };
  181. locManager.addTestProvider(locationProvider,
  182. true, true, true, true, true, true, true, 0, 5);
  183. locManager.setTestProviderEnabled(locationProvider, true);
  184. locManager.requestLocationUpdates(locationProvider, 0, 0, this);
  185. } catch (SecurityException e) {
  186. // Devices require manually setting up to allow location, 99% of tests don't need location,
  187. // ignore the relevant exception here
  188. locManagerSupplier = new Supplier<LocationManager>() {
  189. public LocationManager get() {
  190. throw new IllegalStateException(
  191. "The permission to ALLOW_MOCK_LOCATION needs to be set on your android device, " +
  192. "but currently is not. Cannot perform location actions without this permission.");
  193. }
  194. };
  195. }
  196. }
  197. private void initCookiesState() {
  198. // Needs to be called before CookieMAnager::getInstance()
  199. CookieSyncManager.createInstance(activity);
  200. sessionCookieManager = new SessionCookieManager();
  201. CookieManager cookieManager = CookieManager.getInstance();
  202. cookieManager.removeAllCookie();
  203. }
  204. /**
  205. * Use this contructor to use WebDriver with a WebView that has the same settings as
  206. * the Android browser.
  207. *
  208. * @param activity the activity context where the WebView will be created.
  209. */
  210. public AndroidWebDriver(Activity activity) {
  211. this.activity = activity;
  212. initDriverState();
  213. ChromeClientWrapper chromeWrapper = new ChromeClientWrapper("android.webkit.WebChromeClient",
  214. new DefaultChromeClient());
  215. ViewClientWrapper viewClientWrapper = new ViewClientWrapper("android.webkit.WebViewClient",
  216. new DefaultViewClient());
  217. WebDriverView wdview = new WebDriverView(this, new DefaultWebViewFactory(),
  218. viewClientWrapper, chromeWrapper, null);
  219. // Create a new view and delete existing windows.
  220. newWebView( /*Delete existing windows*/true, wdview);
  221. initCookiesState();
  222. networkHandler = new NetworkStateHandler(activity, view);
  223. }
  224. /**
  225. * Use this constructor to use WebDriver with a custom view.
  226. *
  227. * @param activity the activity context where the view will be displayed.
  228. * @param viewFactory a implementation of the ViewFactory interface. WebDriver will
  229. * use this creation mechanism to create views when needed (e.g. when clicking on a link
  230. * that opens a new window).
  231. * @param viewClient the ViewClientWrapper used by the custom WebView.
  232. * @param chromeClient the ChromeClientWrapper used by the custom WebView.
  233. */
  234. public AndroidWebDriver(Activity activity, ViewFactory viewFactory,
  235. ViewClientWrapper viewClient, ChromeClientWrapper chromeClient) {
  236. this.activity = activity;
  237. initDriverState();
  238. WebDriverView wdview = new WebDriverView(this, viewFactory, viewClient, chromeClient,
  239. null);
  240. newWebView(/*Delete existing windows*/true, wdview);
  241. initCookiesState();
  242. networkHandler = new NetworkStateHandler(activity, view);
  243. }
  244. /**
  245. * Use this constructor to use WebDriver with a custom view and a custom
  246. * View.OnFocusChangeListener for that view..
  247. *
  248. * @param activity the activity context where the view will be displayed.
  249. * @param viewFactory a implementation of the ViewFactory interface. WebDriver will
  250. * use this creation mechanism to create views when needed (e.g. when clicking on a link
  251. * that opens a new window).
  252. * @param viewClient the ViewClientWrapper used by the custom WebView.
  253. * @param chromeClient the ChromeClientWrapper used by the custom WebView.
  254. * @param focusListener the listener used by the view that will be created by the viewFactory.
  255. */
  256. public AndroidWebDriver(Activity activity, ViewFactory viewFactory,
  257. ViewClientWrapper viewClient, ChromeClientWrapper chromeClient,
  258. View.OnFocusChangeListener focusListener) {
  259. this.activity = activity;
  260. initDriverState();
  261. WebDriverView wdview = new WebDriverView(this, viewFactory, viewClient, chromeClient,
  262. focusListener);
  263. newWebView(/*Delete existing windows*/true, wdview);
  264. initCookiesState();
  265. networkHandler = new NetworkStateHandler(activity, view);
  266. }
  267. String getLastUrlLoaded() {
  268. return lastUrlLoaded;
  269. }
  270. void setLastUrlLoaded(String url) {
  271. this.lastUrlLoaded = url;
  272. }
  273. void setEditAreaHasFocus(boolean focused) {
  274. editAreaHasFocus = focused;
  275. }
  276. boolean getEditAreaHasFocus() {
  277. return editAreaHasFocus;
  278. }
  279. void resetPageIsLoading() {
  280. pageStartedLoading = false;
  281. pageDoneLoading = false;
  282. }
  283. void notifyPageStartedLoading() {
  284. synchronized (syncObject) {
  285. pageStartedLoading = true;
  286. pageDoneLoading = false;
  287. syncObject.notify();
  288. }
  289. }
  290. void notifyPageDoneLoading() {
  291. synchronized (syncObject) {
  292. pageDoneLoading = true;
  293. syncObject.notify();
  294. }
  295. }
  296. void waitForPageToLoad() {
  297. synchronized (syncObject) {
  298. long timeout = System.currentTimeMillis() + START_LOADING_TIMEOUT;
  299. while (!pageStartedLoading && (System.currentTimeMillis() < timeout)) {
  300. try {
  301. syncObject.wait(POLLING_INTERVAL);
  302. } catch (InterruptedException e) {
  303. throw new RuntimeException();
  304. }
  305. }
  306. long end = System.currentTimeMillis() + LOADING_TIMEOUT;
  307. while (!pageDoneLoading && pageStartedLoading && (System.currentTimeMillis() < end)) {
  308. try {
  309. syncObject.wait(LOADING_TIMEOUT);
  310. } catch (InterruptedException e) {
  311. throw new RuntimeException(e);
  312. }
  313. }
  314. }
  315. }
  316. void waitUntilEditAreaHasFocus() {
  317. long timeout = System.currentTimeMillis() + FOCUS_TIMEOUT;
  318. while (!editAreaHasFocus && (System.currentTimeMillis() < timeout)) {
  319. try {
  320. Thread.sleep(POLLING_INTERVAL);
  321. } catch (InterruptedException e) {
  322. throw new RuntimeException(e);
  323. }
  324. }
  325. }
  326. public WebView getWebView() {
  327. if (view.getUnderlyingView() instanceof WebView) {
  328. return (WebView) view.getUnderlyingView();
  329. }
  330. throw new WebDriverException("This WebDriver instance is not using a WebView!");
  331. }
  332. public Object getView() {
  333. return view.getUnderlyingView();
  334. }
  335. void newWebView(boolean newDriver, final WebDriverView wdview) {
  336. // If we are requesting a new driver, then close all
  337. // existing window before opening a new one.
  338. if (newDriver) {
  339. quit();
  340. }
  341. long start = System.currentTimeMillis();
  342. long end = start + UI_TIMEOUT;
  343. done = false;
  344. activity.runOnUiThread(new Runnable() {
  345. public void run() {
  346. synchronized (syncObject) {
  347. final ViewAdapter newView = wdview.create();
  348. view = newView;
  349. viewManager.addView(view);
  350. activity.setContentView((View) view.getUnderlyingView());
  351. done = true;
  352. syncObject.notify();
  353. }
  354. }
  355. });
  356. waitForDone(end, UI_TIMEOUT, "Failed to create WebView.");
  357. }
  358. private void waitForDone(long end, long timeout, String error) {
  359. synchronized (syncObject) {
  360. while (!done && System.currentTimeMillis() < end) {
  361. try {
  362. syncObject.wait(timeout);
  363. } catch (InterruptedException e) {
  364. throw new WebDriverException(error, e);
  365. }
  366. }
  367. }
  368. }
  369. WebDriverViewManager getViewManager() {
  370. return viewManager;
  371. }
  372. public Activity getActivity() {
  373. return activity;
  374. }
  375. public String getCurrentUrl() {
  376. if (view == null) {
  377. throw new WebDriverException("No open windows.");
  378. }
  379. done = false;
  380. long end = System.currentTimeMillis() + UI_TIMEOUT;
  381. final String[] url = new String[1];
  382. activity.runOnUiThread(new Runnable() {
  383. public void run() {
  384. synchronized (syncObject) {
  385. url[0] = view.getUrl();
  386. done = true;
  387. syncObject.notify();
  388. }
  389. }
  390. });
  391. waitForDone(end, UI_TIMEOUT, "Failed to get current url.");
  392. return url[0];
  393. }
  394. public String getTitle() {
  395. if (view == null) {
  396. throw new WebDriverException("No open windows.");
  397. }
  398. long end = System.currentTimeMillis() + UI_TIMEOUT;
  399. final String[] title = new String[1];
  400. done = false;
  401. activity.runOnUiThread(new Runnable() {
  402. public void run() {
  403. synchronized (syncObject) {
  404. title[0] = view.getTitle();
  405. done = true;
  406. syncObject.notify();
  407. }
  408. }
  409. });
  410. waitForDone(end, UI_TIMEOUT, "Failed to get title");
  411. return title[0];
  412. }
  413. public void get(String url) {
  414. navigation.to(url);
  415. }
  416. public String getPageSource() {
  417. return (String) executeScript(
  418. "return (new XMLSerializer()).serializeToString(document.documentElement);");
  419. }
  420. public void close() {
  421. if (view == null) {
  422. throw new WebDriverException("No open windows.");
  423. }
  424. // Dispose of existing alerts (if any) for this view.
  425. AlertManager.removeAlertForView(view);
  426. done = false;
  427. long end = System.currentTimeMillis() + RESPONSE_TIMEOUT;
  428. activity.runOnUiThread(new Runnable() {
  429. public void run() {
  430. synchronized (syncObject) {
  431. view.destroy();
  432. viewManager.removeView(view);
  433. done = true;
  434. syncObject.notify();
  435. }
  436. }
  437. });
  438. waitForDone(end, RESPONSE_TIMEOUT, "Failed to close window.");
  439. view = null;
  440. }
  441. public void quit() {
  442. AlertManager.removeAllAlerts();
  443. activity.runOnUiThread(new Runnable() {
  444. public void run() {
  445. viewManager.closeAll();
  446. view = null;
  447. }
  448. });
  449. }
  450. public WebElement findElement(By by) {
  451. long start = System.currentTimeMillis();
  452. while (true) {
  453. try {
  454. return by.findElement(findBy);
  455. } catch (NoSuchElementException e) {
  456. if (System.currentTimeMillis() - start > implicitWait) {
  457. throw e;
  458. }
  459. sleepQuietly(100);
  460. }
  461. }
  462. }
  463. public List<WebElement> findElements(By by) {
  464. long start = System.currentTimeMillis();
  465. List<WebElement> found = by.findElements(findBy);
  466. while (found.isEmpty() && (System.currentTimeMillis() - start <= implicitWait)) {
  467. sleepQuietly(100);
  468. found = by.findElements(findBy);
  469. }
  470. return found;
  471. }
  472. public AppCacheStatus getStatus() {
  473. Long scriptRes = (Long) executeRawScript("(" + AndroidAtoms.GET_APPCACHE_STATUS.getValue() + ")()");
  474. return AppCacheStatus.getEnum(scriptRes.intValue());
  475. }
  476. private class AndroidFindBy implements SearchContext, FindsByTagName, FindsById,
  477. FindsByLinkText, FindsByName, FindsByXPath, FindsByCssSelector, FindsByClassName {
  478. public WebElement findElement(By by) {
  479. long start = System.currentTimeMillis();
  480. while (true) {
  481. try {
  482. return by.findElement(findBy);
  483. } catch (NoSuchElementException e) {
  484. if (System.currentTimeMillis() - start > implicitWait) {
  485. throw e;
  486. }
  487. sleepQuietly(100);
  488. }
  489. }
  490. }
  491. public List<WebElement> findElements(By by) {
  492. long start = System.currentTimeMillis();
  493. List<WebElement> found = by.findElements(findBy);
  494. while (found.isEmpty() && (System.currentTimeMillis() - start <= implicitWait)) {
  495. sleepQuietly(100);
  496. found = by.findElements(this);
  497. }
  498. return found;
  499. }
  500. public WebElement findElementByLinkText(String using) {
  501. return element.getFinder().findElementByLinkText(using);
  502. }
  503. public List<WebElement> findElementsByLinkText(String using) {
  504. return element.getFinder().findElementsByLinkText(using);
  505. }
  506. public WebElement findElementById(String id) {
  507. return element.getFinder().findElementById(id);
  508. }
  509. public List<WebElement> findElementsById(String id) {
  510. return findElementsByXPath("//*[@id='" + id + "']");
  511. }
  512. public WebElement findElementByName(String using) {
  513. return element.getFinder().findElementByName(using);
  514. }
  515. public List<WebElement> findElementsByName(String using) {
  516. return element.getFinder().findElementsByName(using);
  517. }
  518. public WebElement findElementByTagName(String using) {
  519. return element.getFinder().findElementByTagName(using);
  520. }
  521. public List<WebElement> findElementsByTagName(String using) {
  522. return element.getFinder().findElementsByTagName(using);
  523. }
  524. public WebElement findElementByXPath(String using) {
  525. return element.getFinder().findElementByXPath(using);
  526. }
  527. public List<WebElement> findElementsByXPath(String using) {
  528. return element.getFinder().findElementsByXPath(using);
  529. }
  530. public WebElement findElementByPartialLinkText(String using) {
  531. return element.getFinder().findElementByPartialLinkText(using);
  532. }
  533. public List<WebElement> findElementsByPartialLinkText(String using) {
  534. return element.getFinder().findElementsByPartialLinkText(using);
  535. }
  536. public WebElement findElementByCssSelector(String using) {
  537. return element.getFinder().findElementByCssSelector(using);
  538. }
  539. public List<WebElement> findElementsByCssSelector(String using) {
  540. return element.getFinder().findElementsByCssSelector(using);
  541. }
  542. public WebElement findElementByClassName(String using) {
  543. return element.getFinder().findElementByClassName(using);
  544. }
  545. public List<WebElement> findElementsByClassName(String using) {
  546. return element.getFinder().findElementsByClassName(using);
  547. }
  548. }
  549. private static void sleepQuietly(long ms) {
  550. try {
  551. Thread.sleep(ms);
  552. } catch (InterruptedException cause) {
  553. Thread.currentThread().interrupt();
  554. throw new WebDriverException(cause);
  555. }
  556. }
  557. public Set<String> getWindowHandles() {
  558. return viewManager.getAllHandles();
  559. }
  560. public String getWindowHandle() {
  561. String r = viewManager.getWindowHandle(view);
  562. if (r == null) {
  563. throw new WebDriverException("FATAL ERROR HANDLE IS NULL");
  564. }
  565. return r;
  566. }
  567. public TargetLocator switchTo() {
  568. return targetLocator;
  569. }
  570. public LocalStorage getLocalStorage() {
  571. return localStorage;
  572. }
  573. public SessionStorage getSessionStorage() {
  574. return sessionStorage;
  575. }
  576. private class AndroidTargetLocator implements TargetLocator {
  577. public WebElement activeElement() {
  578. return (WebElement) executeRawScript("(" + AndroidAtoms.ACTIVE_ELEMENT.getValue() + ")()");
  579. }
  580. public WebDriver defaultContent() {
  581. executeRawScript("(" + AndroidAtoms.DEFAULT_CONTENT.getValue() + ")()");
  582. return AndroidWebDriver.this;
  583. }
  584. public WebDriver frame(int index) {
  585. DomWindow window = (DomWindow) executeRawScript(
  586. "(" + AndroidAtoms.FRAME_BY_INDEX.getValue() + ")(" + index + ")");
  587. if (window == null) {
  588. throw new NoSuchFrameException("Frame with index '" + index + "' does not exists.");
  589. }
  590. currentWindowOrFrame = window;
  591. return AndroidWebDriver.this;
  592. }
  593. public WebDriver frame(String frameNameOrId) {
  594. DomWindow window = (DomWindow) executeRawScript(
  595. "(" + AndroidAtoms.FRAME_BY_ID_OR_NAME.getValue() + ")('" + frameNameOrId + "')");
  596. if (window == null) {
  597. throw new NoSuchFrameException("Frame with ID or name '" + frameNameOrId
  598. + "' does not exists.");
  599. }
  600. currentWindowOrFrame = window;
  601. return AndroidWebDriver.this;
  602. }
  603. public WebDriver frame(WebElement frameElement) {
  604. DomWindow window = (DomWindow) executeScript("return arguments[0].contentWindow;",
  605. ((AndroidWebElement) ((WrapsElement) frameElement).getWrappedElement()));
  606. if (window == null) {
  607. throw new NoSuchFrameException("Frame does not exists.");
  608. }
  609. currentWindowOrFrame = window;
  610. return AndroidWebDriver.this;
  611. }
  612. public WebDriver window(final String nameOrHandle) {
  613. final boolean[] shouldhTrow = new boolean[1];
  614. shouldhTrow[0] = false;
  615. done = false;
  616. long end = System.currentTimeMillis() + RESPONSE_TIMEOUT;
  617. activity.runOnUiThread(new Runnable() {
  618. public void run() {
  619. synchronized (syncObject) {
  620. ViewAdapter v = viewManager.getView(nameOrHandle);
  621. if (v != null) {
  622. view = v;
  623. } else {
  624. // Can't throw an exception in the UI thread
  625. // Or the App crashes
  626. shouldhTrow[0] = true;
  627. }
  628. activity.setContentView((View) view.getUnderlyingView());
  629. done = true;
  630. syncObject.notify();
  631. }
  632. }
  633. });
  634. waitForDone(end, RESPONSE_TIMEOUT, "Failed to switch to window: " + nameOrHandle);
  635. if (shouldhTrow[0]) {
  636. throw new NoSuchWindowException(
  637. "Window '" + nameOrHandle + "' does not exist.");
  638. }
  639. return AndroidWebDriver.this;
  640. }
  641. public Alert alert() {
  642. if (view == null) {
  643. // An alert may have popped up when the window was closed.
  644. // If there is an alert, just return it.
  645. throw new WebDriverException("Asked for an alert without a window context. " +
  646. "switchTo().window(...) first.");
  647. }
  648. Alert foundAlert = AlertManager.getAlertForView(view);
  649. if (foundAlert == null) {
  650. throw new NoAlertPresentException("No alert in current view.");
  651. }
  652. return foundAlert;
  653. }
  654. }
  655. public Navigation navigate() {
  656. return navigation;
  657. }
  658. public boolean isJavascriptEnabled() {
  659. return true;
  660. }
  661. public Object executeScript(String script, Object... args) {
  662. return injectJavascript(script, false, args);
  663. }
  664. public Object executeAsyncScript(String script, Object... args) {
  665. throw new UnsupportedOperationException("This is feature will be implemented soon!");
  666. }
  667. /**
  668. * Converts the arguments passed to a JavaScript friendly format.
  669. *
  670. * @param args The arguments to convert.
  671. * @return Comma separated Strings containing the arguments.
  672. */
  673. private String convertToJsArgs(final Object... args) {
  674. StringBuilder toReturn = new StringBuilder();
  675. int length = args.length;
  676. for (int i = 0; i < length; i++) {
  677. toReturn.append((i > 0) ? "," : "");
  678. if (args[i] instanceof List<?>) {
  679. toReturn.append("[");
  680. List<Object> aList = (List<Object>) args[i];
  681. for (int j = 0; j < aList.size(); j++) {
  682. String comma = ((j == 0) ? "" : ",");
  683. toReturn.append(comma + convertToJsArgs(aList.get(j)));
  684. }
  685. toReturn.append("]");
  686. } else if (args[i] instanceof Map<?, ?>) {
  687. Map<Object, Object> aMap = (Map<Object, Object>) args[i];
  688. String toAdd = "{";
  689. for (Object key : aMap.keySet()) {
  690. toAdd += key + ":"
  691. + convertToJsArgs(aMap.get(key)) + ",";
  692. }
  693. toReturn.append(toAdd.substring(0, toAdd.length() - 1) + "}");
  694. } else if (args[i] instanceof WebElement) {
  695. // A WebElement is represented in JavaScript by an Object as
  696. // follow: {"ELEMENT":"id"} where "id" refers to the id
  697. // of the HTML element in the javascript cache that can
  698. // be accessed throught bot.inject.cache.getCache_()
  699. toReturn.append("{\"" + ELEMENT_KEY + "\":\""
  700. + ((AndroidWebElement) args[i]).getId() + "\"}");
  701. } else if (args[i] instanceof DomWindow) {
  702. // A DomWindow is represented in JavaScript by an Object as
  703. // follow {"WINDOW":"id"} where "id" refers to the id of the
  704. // DOM window in the cache.
  705. toReturn.append("{\"" + WINDOW_KEY + "\":\"" + ((DomWindow) args[i]).getKey() + "\"}");
  706. } else if (args[i] instanceof Number || args[i] instanceof Boolean) {
  707. toReturn.append(String.valueOf(args[i]));
  708. } else if (args[i] instanceof String) {
  709. toReturn.append(escapeAndQuote((String) args[i]));
  710. } else {
  711. throw new IllegalArgumentException(
  712. "Javascript arguments can be "
  713. + "a Number, a Boolean, a String, a WebElement, "
  714. + "or a List or a Map of those. Got: "
  715. + ((args[i] == null) ? "null" : args[i].getClass()
  716. + ", value: " + args[i].toString()));
  717. }
  718. }
  719. return toReturn.toString();
  720. }
  721. /**
  722. * Wraps the given string into quotes and escape existing quotes and backslashes. "foo" ->
  723. * "\"foo\"" "foo\"" -> "\"foo\\\"\"" "fo\o" -> "\"fo\\o\""
  724. *
  725. * @param toWrap The String to wrap in quotes
  726. * @return a String wrapping the original String in quotes
  727. */
  728. private static String escapeAndQuote(final String toWrap) {
  729. StringBuilder toReturn = new StringBuilder("\"");
  730. for (int i = 0; i < toWrap.length(); i++) {
  731. char c = toWrap.charAt(i);
  732. if (c == '\"') {
  733. toReturn.append("\\\"");
  734. } else if (c == '\\') {
  735. toReturn.append("\\\\");
  736. } else {
  737. toReturn.append(c);
  738. }
  739. }
  740. toReturn.append("\"");
  741. return toReturn.toString();
  742. }
  743. void writeTo(String name, String toWrite) {
  744. try {
  745. File f = new File(Environment.getExternalStorageDirectory(),
  746. name);
  747. FileWriter w = new FileWriter(f);
  748. w.append(toWrite);
  749. w.flush();
  750. w.close();
  751. } catch (FileNotFoundException e) {
  752. e.printStackTrace();
  753. } catch (IOException e) {
  754. e.printStackTrace();
  755. }
  756. }
  757. private Object executeRawScript(String toExecute) {
  758. String result = null;
  759. result = executeJavascriptInWebView("window.webdriver.resultMethod(" + toExecute + ")");
  760. if (result == null || "undefined".equals(result)) {
  761. return null;
  762. }
  763. try {
  764. JSONObject json = new JSONObject(result);
  765. throwIfError(json);
  766. Object value = json.get(VALUE);
  767. return convertJsonToJavaObject(value);
  768. } catch (JSONException e) {
  769. throw new RuntimeException("Failed to parse JavaScript result: "
  770. + result.toString(), e);
  771. }
  772. }
  773. Object executeAtom(String toExecute, Object... args) {
  774. String scriptInWindow =
  775. "(function(){ "
  776. + " var win; try{win=" + getWindowString() + "}catch(e){win=window;}"
  777. + "with(win){return ("
  778. + toExecute + ")(" + convertToJsArgs(args) + ")}})()";
  779. return executeRawScript(scriptInWindow);
  780. }
  781. private String getWindowString() {
  782. String window = "";
  783. if (!currentWindowOrFrame.getKey().equals("")) {
  784. window = "document['$wdc_']['" + currentWindowOrFrame.getKey() + "'] ||";
  785. }
  786. return (window += "window;");
  787. }
  788. Object injectJavascript(String toExecute, boolean isAsync, Object... args) {
  789. String executeScript = AndroidAtoms.EXECUTE_SCRIPT.getValue();
  790. toExecute = "var win_context; try{win_context= " + getWindowString() + "}catch(e){"
  791. + "win_context=window;}with(win_context){" + toExecute + "}";
  792. String wrappedScript =
  793. "(function(){"
  794. + "var win; try{win=" + getWindowString() + "}catch(e){win=window}"
  795. + "with(win){return (" + executeScript + ")("
  796. + escapeAndQuote(toExecute) + ", [" + convertToJsArgs(args) + "], true)}})()";
  797. return executeRawScript(wrappedScript);
  798. }
  799. private Object convertJsonToJavaObject(final Object toConvert) {
  800. try {
  801. if (toConvert == null
  802. || toConvert.equals(null)
  803. || "undefined".equals(toConvert)
  804. || "null".equals(toConvert)) {
  805. return null;
  806. } else if (toConvert instanceof Boolean) {
  807. return toConvert;
  808. } else if (toConvert instanceof Double
  809. || toConvert instanceof Float) {
  810. return Double.valueOf(String.valueOf(toConvert));
  811. } else if (toConvert instanceof Integer
  812. || toConvert instanceof Long) {
  813. return Long.valueOf(String.valueOf(toConvert));
  814. } else if (toConvert instanceof JSONArray) { // List
  815. return convertJsonArrayToList((JSONArray) toConvert);
  816. } else if (toConvert instanceof JSONObject) { // Map or WebElment
  817. JSONObject map = (JSONObject) toConvert;
  818. if (map.opt(ELEMENT_KEY) != null) { // WebElement
  819. return getOrCreateWebElement((String) map.get(ELEMENT_KEY));
  820. } else if (map.opt(WINDOW_KEY) != null) { // DomWindow
  821. return new DomWindow((String) map.get(WINDOW_KEY));
  822. } else { // Map
  823. return convertJsonObjectToMap(map);
  824. }
  825. } else {
  826. return toConvert.toString();
  827. }
  828. } catch (JSONException e) {
  829. throw new RuntimeException("Failed to parse JavaScript result: "
  830. + toConvert.toString(), e);
  831. }
  832. }
  833. private List<Object> convertJsonArrayToList(final JSONArray json) {
  834. List<Object> toReturn = Lists.newArrayList();
  835. for (int i = 0; i < json.length(); i++) {
  836. try {
  837. toReturn.add(convertJsonToJavaObject(json.get(i)));
  838. } catch (JSONException e) {
  839. throw new RuntimeException("Failed to parse JSON: "
  840. + json.toString(), e);
  841. }
  842. }
  843. return toReturn;
  844. }
  845. private Map<Object, Object> convertJsonObjectToMap(final JSONObject json) {
  846. Map<Object, Object> toReturn = Maps.newHashMap();
  847. for (Iterator it = json.keys(); it.hasNext();) {
  848. String key = (String) it.next();
  849. try {
  850. Object value = json.get(key);
  851. toReturn.put(convertJsonToJavaObject(key),
  852. convertJsonToJavaObject(value));
  853. } catch (JSONException e) {
  854. throw new RuntimeException("Failed to parse JSON:"
  855. + json.toString(), e);
  856. }
  857. }
  858. return toReturn;
  859. }
  860. private void throwIfError(final JSONObject jsonObject) {
  861. int status;
  862. String errorMsg;
  863. try {
  864. status = (Integer) jsonObject.get(STATUS);
  865. errorMsg = String.valueOf(jsonObject.get(VALUE));
  866. } catch (JSONException e) {
  867. throw new RuntimeException("Failed to parse JSON Object: "
  868. + jsonObject, e);
  869. }
  870. switch (status) {
  871. case ErrorCodes.SUCCESS:
  872. return;
  873. case ErrorCodes.NO_SUCH_ELEMENT:
  874. throw new NoSuchElementException("Could not find "
  875. + "WebElement.");
  876. case ErrorCodes.STALE_ELEMENT_REFERENCE:
  877. throw new StaleElementReferenceException("WebElement is stale.");
  878. default:
  879. if (jsonObject.toString().contains("Result of expression 'd.evaluate' [undefined] is"
  880. + " not a function.")) {
  881. throw new WebDriverException("You are using a version of Android WebDriver APK"
  882. + " compatible with ICS SDKs or more recent SDKs. For more info take a look at"
  883. + " http://code.google.com/p/selenium/wiki/AndroidDriver#Supported_Platforms. Error:"
  884. + " " + jsonObject.toString());
  885. }
  886. throw new WebDriverException("Error: " + errorMsg);
  887. }
  888. }
  889. /**
  890. * Executes the given Javascript in the WebView and wait until it is done executing. If the
  891. * Javascript executed returns a value, the later is updated in the class variable jsResult when
  892. * the event is broadcasted.
  893. *
  894. * @param script the Javascript to be executed
  895. */
  896. private String executeJavascriptInWebView(final String script) {
  897. if (view == null) {
  898. throw new WebDriverException("No open windows.");
  899. }
  900. result = null;
  901. resultReady = false;
  902. activity.runOnUiThread(new Runnable() {
  903. public void run() {
  904. org.openqa.selenium.android.library.JavascriptExecutor.executeJs(
  905. view, notifier, script);
  906. }
  907. });
  908. long timeout = System.currentTimeMillis() + RESPONSE_TIMEOUT;
  909. synchronized (syncObject) {
  910. while (!resultReady && (System.currentTimeMillis() < timeout)) {
  911. try {
  912. syncObject.wait(RESPONSE_TIMEOUT);
  913. } catch (InterruptedException e) {
  914. throw new WebDriverException(e);
  915. }
  916. }
  917. return result;
  918. }
  919. }
  920. protected Object processJsonObject(Object res) throws JSONException {
  921. if (res instanceof JSONArray) {
  922. return convertJsonArray2List((JSONArray) res);
  923. } else if ("undefined".equals(res)) {
  924. return null;
  925. }
  926. return res;
  927. }
  928. private List<Object> convertJsonArray2List(JSONArray arr) throws JSONException {
  929. List<Object> list = new ArrayList<Object>();
  930. for (int i = 0; i < arr.length(); i++) {
  931. list.add(processJsonObject(arr.get(i)));
  932. }
  933. return list;
  934. }
  935. public void setProxy(String host, int port) {
  936. if ((host != null) && (host.length() > 0)) {
  937. System.getProperties().put("proxySet", "true");
  938. System.getProperties().put("proxyHost", host);
  939. System.getProperties().put("proxyPort", port);
  940. }
  941. }
  942. public Options manage() {
  943. return options;
  944. }
  945. private class AndroidOptions implements Options {
  946. public Logs logs() {
  947. return logs;
  948. }
  949. public void addCookie(Cookie cookie) {
  950. if (view == null) {
  951. throw new WebDriverException("No open windows.");
  952. }
  953. sessionCookieManager.addCookie(getCurrentUrl(), cookie);
  954. }
  955. public void deleteCookieNamed(String name) {
  956. if (view == null) {
  957. throw new WebDriverException("No open windows.");
  958. }
  959. sessionCookieManager.remove(getCurrentUrl(), name);
  960. }
  961. public void deleteCookie(Cookie cookie) {
  962. if (view == null) {
  963. throw new WebDriverException("No open windows.");
  964. }
  965. sessionCookieManager.remove(getCurrentUrl(), cookie.getName());
  966. }
  967. public void deleteAllCookies() {
  968. if (view == null) {
  969. throw new WebDriverException("No open windows.");
  970. }
  971. sessionCookieManager.removeAllCookies(getCurrentUrl());
  972. }
  973. public Set<Cookie> getCookies() {
  974. if (view == null) {
  975. throw new WebDriverException("No open windows.");
  976. }
  977. return sessionCookieManager.getAllCookies(getCurrentUrl());
  978. }
  979. public Cookie getCookieNamed(String name) {
  980. if (view == null) {
  981. throw new WebDriverException("No open windows.");
  982. }
  983. return sessionCookieManager.getCookie(getCurrentUrl(), name);
  984. }
  985. public Timeouts timeouts() {
  986. return new AndroidTimeouts();
  987. }
  988. public ImeHandler ime() {
  989. throw new UnsupportedOperationException("Not implementing IME input just yet.");
  990. }
  991. @Beta
  992. public Window window() {
  993. throw new UnsupportedOperationException("Window handling not supported on Android");
  994. }
  995. }
  996. private class AndroidTimeouts implements Timeouts {
  997. public Timeouts implicitlyWait(long time, TimeUnit unit) {
  998. implicitWait = TimeUnit.MILLISECONDS.convert(Math.max(0, time), unit);
  999. return this;
  1000. }
  1001. public Timeouts setScriptTimeout(long time, TimeUnit unit) {
  1002. //asyncScriptTimeout = TimeUnit.MILLISECONDS.convert(Math.max(0, time), unit);
  1003. return this;
  1004. }
  1005. public Timeouts pageLoadTimeout(long time, TimeUnit unit) {
  1006. throw new UnsupportedOperationException("pageLoadTimeout");
  1007. }
  1008. }
  1009. public Location location() {
  1010. android.location.Location loc = locManagerSupplier.get().getLastKnownLocation(locationProvider);
  1011. return new Location(loc.getLatitude(), loc.getLongitude(), loc.getAltitude());
  1012. }
  1013. public void setLocation(Location loc) {
  1014. android.location.Location location =
  1015. new android.location.Location(locationProvider);
  1016. location.setLatitude(loc.getLatitude());
  1017. location.setLongitude(loc.getLongitude());
  1018. location.setAltitude(loc.getAltitude());
  1019. // set the time so it's not ignored!
  1020. location.setTime(System.currentTimeMillis());
  1021. locManagerSupplier.get().setTestProviderLocation(locationProvider, location);
  1022. }
  1023. public void onLocationChanged(android.location.Location location) {
  1024. Logger.log(Level.WARNING, AndroidWebDriver.class.getName(), "onLocationChanged",
  1025. "New location: " + location.toString());
  1026. }
  1027. public void onStatusChanged(String s, int i, Bundle bundle) {
  1028. }
  1029. public void onProviderEnabled(String s) {
  1030. }
  1031. public void onProviderDisabled(String s) {
  1032. }
  1033. private byte[] takeScreenshot() {
  1034. if (view == null) {
  1035. throw new WebDriverException("No open windows.");
  1036. }
  1037. done = false;
  1038. long end = System.currentTimeMillis() + RESPONSE_TIMEOUT;
  1039. final byte[][] rawPng = new byte[1][1];
  1040. activity.runOnUiThread(new Runnable() {
  1041. public void run() {
  1042. synchronized (syncObject) {
  1043. Picture pic = view.capturePicture();
  1044. // Bitmap of the entire document
  1045. Bitmap raw = Bitmap.createBitmap(
  1046. pic.getWidth(),
  1047. pic.getHeight(),
  1048. Bitmap.Config.RGB_565);
  1049. // Drawing on a canvas
  1050. Canvas cv = new Canvas(raw);
  1051. cv.drawPicture(pic);
  1052. ByteArrayOutputStream stream = new ByteArrayOutputStream();
  1053. if (!raw.compress(Bitmap.CompressFormat.PNG, 100, stream)) {
  1054. throw new RuntimeException(
  1055. "Error while compressing screenshot image.");
  1056. }
  1057. try {
  1058. stream.flush();
  1059. stream.close();
  1060. } catch (IOException e) {
  1061. throw new RuntimeException(
  1062. "I/O Error while capturing screenshot: " + e.getMessage());
  1063. } finally {
  1064. IOUtils.closeQuietly(stream);
  1065. }
  1066. rawPng[0] = stream.toByteArray();
  1067. done = true;
  1068. syncObject.notify();
  1069. }
  1070. }
  1071. });
  1072. waitForDone(end, RESPONSE_TIMEOUT, "Failed to take screenshot.");
  1073. return rawPng[0];
  1074. }
  1075. public <X> X getScreenshotAs(OutputType<X> target) throws WebDriverException {
  1076. byte[] rawPng = takeScreenshot();
  1077. String base64Png = new Base64Encoder().encode(rawPng);
  1078. return target.convertFromBase64Png(base64Png);
  1079. }
  1080. public ScreenOrientation getOrientation() {
  1081. int value = activity.getRequestedOrientation();
  1082. if (value == 0) {
  1083. return ScreenOrientation.LANDSCAPE;
  1084. }
  1085. return ScreenOrientation.PORTRAIT;
  1086. }
  1087. public void rotate(ScreenOrientation orientation) {
  1088. activity.setRequestedOrientation(getAndroidScreenOrientation(orientation));
  1089. }
  1090. private int getAndroidScreenOrientation(ScreenOrientation orientation) {
  1091. if (ScreenOrientation.LANDSCAPE.equals(orientation)) {
  1092. return 0;
  1093. }
  1094. return 1;
  1095. }
  1096. public boolean isOnline() {
  1097. return Settings.System.getInt(getActivity().getContentResolver(),
  1098. Settings.System.AIRPLANE_MODE_ON, 0) != 1;
  1099. }
  1100. public void setOnline(boolean online) throws WebDriverException {
  1101. Settings.System.putInt(getActivity().getContentResolver(),
  1102. Settings.System.AIRPLANE_MODE_ON, online ? 0 : 1);
  1103. Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
  1104. intent.putExtra("state", online);
  1105. getActivity().sendBroadcast(intent);
  1106. }
  1107. public TouchScreen getTouch() {
  1108. return touchScreen;
  1109. }
  1110. private class AndroidNavigation implements Navigation {
  1111. public void back() {
  1112. if (view == null) {
  1113. throw new WebDriverException("No open windows.");
  1114. }
  1115. pageDoneLoading = false;
  1116. activity.runOnUiThread(new Runnable() {
  1117. public void run() {
  1118. view.goBack();
  1119. }
  1120. });
  1121. waitForPageLoadToComplete();
  1122. }
  1123. public void forward() {
  1124. if (view == null) {
  1125. throw new WebDriverException("No open windows.");
  1126. }
  1127. pageDoneLoading = false;
  1128. activity.runOnUiThread(new Runnable() {
  1129. public void run() {
  1130. view.goForward();
  1131. }
  1132. });
  1133. waitForPageLoadToComplete();
  1134. }
  1135. public void to(final String url) {
  1136. if (url == null) {
  1137. return;
  1138. }
  1139. if (view == null) {
  1140. throw new WebDriverException("No open windows.");
  1141. }
  1142. pageDoneLoading = false;
  1143. activity.runOnUiThread(new Runnable() {
  1144. public void run() {
  1145. try {
  1146. view.loadUrl(url);
  1147. } catch (Exception e) {
  1148. // For some dark reason WebView sometimes throws an
  1149. // NPE here.
  1150. }
  1151. }
  1152. });
  1153. waitForPageLoadToComplete();
  1154. }
  1155. public void to(URL url) {
  1156. to(url.toString());
  1157. }
  1158. public void refresh() {
  1159. if (view == null) {
  1160. throw new WebDriverException("No open windows.");
  1161. }
  1162. pageDoneLoading = false;
  1163. activity.runOnUiThread(new Runnable() {
  1164. public void run() {
  1165. view.reload();
  1166. }
  1167. });
  1168. waitForPageLoadToComplete();
  1169. }
  1170. private void waitForPageLoadToComplete() {
  1171. long timeout = System.currentTimeMillis() + LOADING_TIMEOUT;
  1172. synchronized (syncObject) {
  1173. while (!pageDoneLoading && (System.currentTimeMillis() < timeout)) {
  1174. try {
  1175. syncObject.wait(LOADING_TIMEOUT);
  1176. } catch (InterruptedException e) {
  1177. throw new RuntimeException(e);
  1178. }
  1179. }
  1180. }
  1181. }
  1182. }
  1183. }