Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 940a198f authored by Dounia Berrada's avatar Dounia Berrada
Browse files

Adding functions to query the state of a WebElement.

Change-Id: I85dbbfbf6b6172d6a8cf6e437f414ecef0872e36
parent c54ed966
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -7260,7 +7260,10 @@ public class WebView extends AbsoluteLayout
                cursorData(), 1000);
    }

    /* package */ synchronized WebViewCore getWebViewCore() {
    /**
     * @hide
     */
    public synchronized WebViewCore getWebViewCore() {
        return mWebViewCore;
    }

+13 −4
Original line number Diff line number Diff line
@@ -48,7 +48,10 @@ import java.util.Set;

import junit.framework.Assert;

final class WebViewCore {
/**
 * @hide
 */
public final class WebViewCore {

    private static final String LOGTAG = "webcore";

@@ -905,7 +908,10 @@ final class WebViewCore {
            "REMOVE_JS_INTERFACE", // = 149;
        };

    class EventHub {
    /**
     * @hide
     */
    public class EventHub {
        // Message Ids
        static final int REVEAL_SELECTION = 96;
        static final int REQUEST_LABEL = 97;
@@ -936,7 +942,7 @@ final class WebViewCore {
        static final int DELETE_SELECTION = 122;
        static final int LISTBOX_CHOICES = 123;
        static final int SINGLE_LISTBOX_CHOICE = 124;
        static final int MESSAGE_RELAY = 125;
        public static final int MESSAGE_RELAY = 125;
        static final int SET_BACKGROUND_COLOR = 126;
        static final int SET_MOVE_FOCUS = 127;
        static final int SAVE_DOCUMENT_STATE = 128;
@@ -1702,7 +1708,10 @@ final class WebViewCore {
    // If it needs WebCore, it has to send message.
    //-------------------------------------------------------------------------

    void sendMessage(Message msg) {
    /**
     * @hide
     */
    public void sendMessage(Message msg) {
        mEventHub.sendMessage(msg);
    }

+311 −19
Original line number Diff line number Diff line
@@ -16,15 +16,22 @@

package android.webkit.webdriver;

import android.graphics.Point;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.webkit.WebView;

import com.android.internal.R;
import android.webkit.WebViewCore;

import com.google.android.collect.Lists;
import com.google.android.collect.Maps;

import com.android.internal.R;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -91,10 +98,24 @@ public class WebDriver {
    private static final int LOADING_TIMEOUT = 30000;
    // Timeout for executing JavaScript in the WebView in milliseconds.
    private static final int JS_EXECUTION_TIMEOUT = 10000;
    // Timeout for the MotionEvent to be completely handled
    private static final int MOTION_EVENT_TIMEOUT = 1000;
    // Timeout for detecting a new page load
    private static final int PAGE_STARTED_LOADING = 500;
    // Timeout for handling KeyEvents
    private static final int KEY_EVENT_TIMEOUT = 2000;

    // Commands posted to the handler
    private static final int CMD_GET_URL = 1;
    private static final int CMD_EXECUTE_SCRIPT = 2;
    private static final int CMD_SEND_TOUCH = 3;
    private static final int CMD_SEND_KEYS = 4;
    private static final int CMD_NAV_REFRESH = 5;
    private static final int CMD_NAV_BACK = 6;
    private static final int CMD_NAV_FORWARD = 7;
    private static final int CMD_SEND_KEYCODE = 8;
    private static final int CMD_MOVE_CURSOR_RIGHTMOST_POS = 9;
    private static final int CMD_MESSAGE_RELAY_ECHO = 10;

    private static final String ELEMENT_KEY = "ELEMENT";
    private static final String STATUS = "status";
@@ -107,24 +128,62 @@ public class WebDriver {

    // Used for synchronization
    private final Object mSyncObject;
    private final Object mSyncPageLoad;

    // Updated when the command is done executing in the main thread.
    private volatile boolean mCommandDone;

    // Used by WebViewClientWrapper.onPageStarted() to notify that
    // a page started loading.
    private volatile boolean mPageStartedLoading;
    // Used by WebChromeClientWrapper.onProgressChanged to notify when
    // a page finished loading.
    private volatile boolean mPageFinishedLoading;
    private WebView mWebView;

    private Navigation mNavigation;
    // This WebElement represents the object document.documentElement
    private WebElement mDocumentElement;


    // This Handler runs in the main UI thread.
    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == CMD_GET_URL) {
            switch (msg.what) {
                case CMD_GET_URL:
                    final String url = (String) msg.obj;
                    mWebView.loadUrl(url);
            } else if (msg.what == CMD_EXECUTE_SCRIPT) {
                    break;
                case CMD_EXECUTE_SCRIPT:
                    mWebView.loadUrl("javascript:" + (String) msg.obj);
                    break;
                case CMD_MESSAGE_RELAY_ECHO:
                    notifyCommandDone();
                    break;
                case CMD_SEND_TOUCH:
                    touchScreen((Point) msg.obj);
                    notifyCommandDone();
                    break;
                case CMD_SEND_KEYS:
                    dispatchKeys((CharSequence[]) msg.obj);
                    notifyCommandDone();
                    break;
                case CMD_NAV_REFRESH:
                    mWebView.reload();
                    break;
                case CMD_NAV_BACK:
                    mWebView.goBack();
                    break;
                case CMD_NAV_FORWARD:
                    mWebView.goForward();
                    break;
                case CMD_SEND_KEYCODE:
                    dispatchKeyCodes((int[]) msg.obj);
                    notifyCommandDone();
                    break;
                case CMD_MOVE_CURSOR_RIGHTMOST_POS:
                    moveCursorToLeftMostPos((String) msg.obj);
                    notifyCommandDone();
                    break;
            }
        }
    };
@@ -169,13 +228,13 @@ public class WebDriver {
                    return values[i];
                }
            }
            throw new IllegalArgumentException(intValue
                    + " does not map to any ErrorCode.");
            return UNKNOWN_ERROR;
        }
    }

    public WebDriver(WebView webview) {
        this.mWebView = webview;
        mWebView.requestFocus();
        if (mWebView == null) {
            throw new IllegalArgumentException("WebView cannot be null");
        }
@@ -186,12 +245,25 @@ public class WebDriver {
        shouldRunInMainThread(true);

        mSyncObject = new Object();
        mSyncPageLoad = new Object();
        this.mWebView = webview;
        WebchromeClientWrapper chromeWrapper = new WebchromeClientWrapper(
        WebChromeClientWrapper chromeWrapper = new WebChromeClientWrapper(
                webview.getWebChromeClient(), this);
        mWebView.setWebChromeClient(chromeWrapper);
        WebViewClientWrapper viewWrapper = new WebViewClientWrapper(
                webview.getWebViewClient(), this);
        mWebView.setWebViewClient(viewWrapper);
        mWebView.addJavascriptInterface(new JavascriptResultReady(),
                "webdriver");
        mDocumentElement = new WebElement(this, "");
        mNavigation = new Navigation();
    }

    /**
     * @return The title of the current page, null if not set.
     */
    public String getTitle() {
        return mWebView.getTitle();
    }

    /**
@@ -201,8 +273,7 @@ public class WebDriver {
     * @param url The URL to load.
     */
    public void get(String url) {
        executeCommand(CMD_GET_URL, url, LOADING_TIMEOUT);
        mDocumentElement = (WebElement) executeScript("return document.body;");
        mNavigation.to(url);
    }

    /**
@@ -243,7 +314,7 @@ public class WebDriver {
    }

    /**
     * Clears the WebView.
     * Clears the WebView's state and closes associated views.
     */
    public void quit() {
        mWebView.clearCache(true);
@@ -251,6 +322,7 @@ public class WebDriver {
        mWebView.clearHistory();
        mWebView.clearSslPreferences();
        mWebView.clearView();
        mWebView.removeAllViewsInLayout();
    }

    /**
@@ -287,6 +359,44 @@ public class WebDriver {
                ")(" + escapeAndQuote(script) + ", " + scriptArgs + ", true)");
    }

    public Navigation navigate() {
        return mNavigation;
    }


    /**
     * @hide
     */
    public class Navigation {
        /* package */ Navigation () {}

        public void back() {
            navigate(CMD_NAV_BACK, null);
        }

        public void forward() {
            navigate(CMD_NAV_FORWARD, null);
        }

        public void to(String url) {
            navigate(CMD_GET_URL, url);
        }

        public void refresh() {
            navigate(CMD_NAV_REFRESH, null);
        }

        private void navigate(int command, String url) {
            synchronized (mSyncPageLoad) {
                mPageFinishedLoading = false;
                Message msg = mHandler.obtainMessage(command);
                msg.obj = url;
                mHandler.sendMessage(msg);
                waitForPageLoad();
            }
        }
    }

    /**
     * Converts the arguments passed to a JavaScript friendly format.
     *
@@ -338,9 +448,19 @@ public class WebDriver {
    }

    /* package */ Object executeRawJavascript(final String script) {
        if (mWebView.getUrl() == null) {
            throw new WebDriverException("Cannot operate on a blank page. "
                    + "Load a page using WebDriver.get().");
        }
        String result = executeCommand(CMD_EXECUTE_SCRIPT,
                "if (!window.webdriver || !window.webdriver.resultReady) {" +
                "  return;" +
                "}" +
                "window.webdriver.resultReady(" + script + ")",
                JS_EXECUTION_TIMEOUT);
        if (result == null || "undefined".equals(result)) {
            return null;
        }
        try {
            JSONObject json = new JSONObject(result);
            throwIfError(json);
@@ -369,6 +489,177 @@ public class WebDriver {
        return sb.toString();
    }

    /* package */ void sendTouchScreen(Point coords) {
        // Reset state
        resetPageLoadState();
        executeCommand(CMD_SEND_TOUCH, coords,LOADING_TIMEOUT);
        // Wait for the events to be fully handled
        waitForMessageRelay(MOTION_EVENT_TIMEOUT);

        // If a page started loading, block until page finishes loading
        waitForPageLoadIfNeeded();
    }

    /* package */ void resetPageLoadState() {
        synchronized (mSyncPageLoad) {
            mPageStartedLoading = false;
            mPageFinishedLoading = false;
        }
    }

    /* package */ void waitForPageLoadIfNeeded() {
        synchronized (mSyncPageLoad) {
            Long end = System.currentTimeMillis() + PAGE_STARTED_LOADING;
            // Wait PAGE_STARTED_LOADING milliseconds to see if we detect a
            // page load.
            while (!mPageStartedLoading && (System.currentTimeMillis() <= end)) {
                try {
                    // This is notified by WebChromeClientWrapper#onProgressChanged
                    // when the page finished loading.
                    mSyncPageLoad.wait(PAGE_STARTED_LOADING);
                } catch (InterruptedException e) {
                    new RuntimeException(e);
                }
            }
            if (mPageStartedLoading) {
                waitForPageLoad();
            }
        }
    }

    private void touchScreen(Point coords) {
        // Convert to screen coords
        // screen = JS x zoom - offset
        float zoom = mWebView.getScale();
        float xOffset = mWebView.getX();
        float yOffset = mWebView.getY();
        Point screenCoords = new Point( (int)(coords.x*zoom - xOffset),
                (int)(coords.y*zoom - yOffset));

        long downTime = SystemClock.uptimeMillis();
        MotionEvent down = MotionEvent.obtain(downTime,
                SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, screenCoords.x,
                screenCoords.y, 0);
        down.setSource(InputDevice.SOURCE_TOUCHSCREEN);
        MotionEvent up = MotionEvent.obtain(downTime,
                SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, screenCoords.x,
                screenCoords.y, 0);
        up.setSource(InputDevice.SOURCE_TOUCHSCREEN);
        // Dispatch the events to WebView
        mWebView.dispatchTouchEvent(down);
        mWebView.dispatchTouchEvent(up);
    }

    /* package */ void notifyPageStartedLoading() {
        synchronized (mSyncPageLoad) {
            mPageStartedLoading = true;
            mSyncPageLoad.notify();
        }
    }

    /* package */ void notifyPageFinishedLoading() {
        synchronized (mSyncPageLoad) {
            mPageFinishedLoading = true;
            mSyncPageLoad.notify();
        }
    }

    /**
     *
     * @param keys The first element of the CharSequence should be the
     * existing value in the text input, or the empty string if none.
     */
    /* package */ void sendKeys(CharSequence[] keys) {
        executeCommand(CMD_SEND_KEYS, keys, KEY_EVENT_TIMEOUT);
        // Wait for all KeyEvents to be handled
        waitForMessageRelay(KEY_EVENT_TIMEOUT);
    }

    /* package */ void sendKeyCodes(int[] keycodes) {
        executeCommand(CMD_SEND_KEYCODE, keycodes, KEY_EVENT_TIMEOUT);
        // Wait for all KeyEvents to be handled
        waitForMessageRelay(KEY_EVENT_TIMEOUT);
    }

    /* package */ void moveCursorToRightMostPosition(String value) {
        executeCommand(CMD_MOVE_CURSOR_RIGHTMOST_POS, value, KEY_EVENT_TIMEOUT);
        waitForMessageRelay(KEY_EVENT_TIMEOUT);
    }

    private void moveCursorToLeftMostPos(String value) {
        // If there is text, move the cursor to the rightmost position
        if (value != null && !value.equals("")) {
            long downTime = SystemClock.uptimeMillis();
            KeyEvent down = new KeyEvent(downTime, SystemClock.uptimeMillis(),
                    KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT, 0);
            KeyEvent up = new KeyEvent(downTime, SystemClock.uptimeMillis(),
                    KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_RIGHT,
                    value.length());
            mWebView.dispatchKeyEvent(down);
            mWebView.dispatchKeyEvent(up);
        }
    }

    private void dispatchKeyCodes(int[] keycodes) {
        for (int i = 0; i < keycodes.length; i++) {
            KeyEvent down = new KeyEvent(KeyEvent.ACTION_DOWN, keycodes[i]);
            KeyEvent up = new KeyEvent(KeyEvent.ACTION_UP, keycodes[i]);
            mWebView.dispatchKeyEvent(down);
            mWebView.dispatchKeyEvent(up);
        }
    }

    private void dispatchKeys(CharSequence[] keys) {
        KeyCharacterMap chararcterMap = KeyCharacterMap.load(
                KeyCharacterMap.VIRTUAL_KEYBOARD);
        for (int i = 0; i < keys.length; i++) {
            CharSequence s = keys[i];
            for (int j = 0; j < s.length(); j++) {
                KeyEvent[] events =
                        chararcterMap.getEvents(new char[]{s.charAt(j)});
                for (KeyEvent e : events) {
                    mWebView.dispatchKeyEvent(e);
                }
            }
        }
    }

    private void waitForMessageRelay(long timeout) {
        synchronized (mSyncObject) {
            mCommandDone = false;
        }
        Message msg = Message.obtain();
        msg.what = WebViewCore.EventHub.MESSAGE_RELAY;
        Message echo = mHandler.obtainMessage(CMD_MESSAGE_RELAY_ECHO);
        msg.obj = echo;

        mWebView.getWebViewCore().sendMessage(msg);
        synchronized (mSyncObject) {
            long end  = System.currentTimeMillis() + timeout;
            while (!mCommandDone && (System.currentTimeMillis() <= end)) {
                try {
                    // This is notifed by the mHandler when it receives the
                    // MESSAGE_RELAY back
                    mSyncObject.wait(timeout);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    private void waitForPageLoad() {
        long endLoad = System.currentTimeMillis() + LOADING_TIMEOUT;
        while (!mPageFinishedLoading
                && (System.currentTimeMillis() <= endLoad)) {
            try {
                mSyncPageLoad.wait(LOADING_TIMEOUT);
            } catch (InterruptedException e) {
                throw new RuntimeException();
            }
        }
    }

    /**
     * Wraps the given string into quotes and escape existing quotes
     * and backslashes.
@@ -531,7 +822,8 @@ public class WebDriver {
            long end = System.currentTimeMillis() + timeout;
            while (!mCommandDone) {
                if (System.currentTimeMillis() >= end) {
                    throw new RuntimeException("Timeout executing command.");
                    throw new RuntimeException("Timeout executing command: "
                            + command);
                }
                try {
                    mSyncObject.wait(timeout);
+137 −4
Original line number Diff line number Diff line
@@ -16,9 +16,13 @@

package android.webkit.webdriver;

import android.graphics.Point;
import android.view.KeyEvent;

import com.android.internal.R;

import java.util.List;
import java.util.Map;

/**
 * Represents an HTML element. Typically most interactions with a web page
@@ -158,6 +162,124 @@ public class WebElement {
        return (Boolean) executeAtom(toggle, this);
    }

    /**
     * Sends the KeyEvents for the given sequence of characters to the
     * WebElement to simulate typing. The KeyEvents are generated using the
     * device's {@link android.view.KeyCharacterMap.VIRTUAL_KEYBOARD}.
     *
     * @param keys The keys to send to this WebElement
     */
    public void sendKeys(CharSequence... keys) {
        if (keys == null || keys.length == 0) {
            return;
        }
        click();
        mDriver.moveCursorToRightMostPosition(getAttribute("value"));
        mDriver.sendKeys(keys);
    }

    /**
     * Use this to send one of the key code constants defined in
     * {@link android.view.KeyEvent}
     *
     * @param keys
     */
    public void sendKeyCodes(int... keys) {
        if (keys == null || keys.length == 0) {
            return;
        }
        click();
        mDriver.moveCursorToRightMostPosition(getAttribute("value"));
        mDriver.sendKeyCodes(keys);
    }

    /**
     * Sends a touch event to the center coordinates of this WebElement.
     */
    public void click() {
        Point topLeft = getLocation();
        Point size = getSize();
        int jsX = topLeft.x + size.x/2;
        int jsY = topLeft.y + size.y/2;
        Point center = new Point(jsX, jsY);
        mDriver.sendTouchScreen(center);
    }

    /**
     * Submits the form containing this WebElement.
     */
    public void submit() {
        mDriver.resetPageLoadState();
        String submit = mDriver.getResourceAsString(R.raw.submit_android);
        executeAtom(submit, this);
        mDriver.waitForPageLoadIfNeeded();
    }

    /**
     * Clears the text value if this is a text entry element. Does nothing
     * otherwise.
     */
    public void clear() {
        String value = getAttribute("value");
        if (value == null || value.equals("")) {
            return;
        }
        int length = value.length();
        int[] keys = new int[length];
        for (int i = 0; i < length; i++) {
            keys[i] = KeyEvent.KEYCODE_DEL;
        }
        sendKeyCodes(keys);
    }

    /**
     * @return the value of the given CSS property if found, null otherwise.
     */
    public String getCssValue(String cssProperty) {
        String getCssProp = mDriver.getResourceAsString(
                R.raw.get_value_of_css_property_android);
        return (String) executeAtom(getCssProp, this, cssProperty);
    }

    /**
     * Gets the width and height of the rendered element.
     *
     * @return a {@link android.graphics.Point}, where Point.x represents the
     * width, and Point.y represents the height of the element.
     */
    public Point getSize() {
        String getSize = mDriver.getResourceAsString(R.raw.get_size_android);
        Map<String, Long> map = (Map<String, Long>) executeAtom(getSize, this);
        return new Point(map.get("width").intValue(),
                map.get("height").intValue());
    }

    /**
     * Gets the location of the top left corner of this element on the screen.
     * If the element is not visisble, this will scroll to get the element into
     * the visisble screen.
     *
     * @return a {@link android.graphics.Point} containing the x and y
     * coordinates of the top left corner of this element.
     */
    public Point getLocation() {
        String getLocation = mDriver.getResourceAsString(
                R.raw.get_top_left_coordinates_android);
        Map<String,Long> map = (Map<String, Long>)  executeAtom(getLocation,
                this);
        return new Point(map.get("x").intValue(), map.get("y").intValue());
    }

    /**
     * @return True if the WebElement is displayed on the screen,
     * false otherwise.
     */
    public boolean isDisplayed() {
        String isDisplayed = mDriver.getResourceAsString(
                R.raw.is_displayed_android);
        return (Boolean) executeAtom(isDisplayed, this);
    }

    /*package*/ String getId() {
        return mId;
    }
@@ -237,15 +359,26 @@ public class WebElement {
    private List<WebElement> findElements(String strategy, String locator) {
        String findElements = mDriver.getResourceAsString(
                R.raw.find_elements_android);
        if (mId.equals("")) {
            return (List<WebElement>) executeAtom(findElements,
                    strategy, locator);
        } else {
            return (List<WebElement>) executeAtom(findElements,
                    strategy, locator, this);
        }
    }

    private WebElement findElement(String strategy, String locator) {
        String findElement = mDriver.getResourceAsString(
                R.raw.find_element_android);
        WebElement el = (WebElement) executeAtom(findElement,
        WebElement el;
        if (mId.equals("")) {
            el = (WebElement) executeAtom(findElement,
                    strategy, locator);
        } else {
            el = (WebElement) executeAtom(findElement,
                    strategy, locator, this);
        }
        if (el == null) {
            throw new WebElementNotFoundException("Could not find element "
                    + "with " + strategy + ": " + locator);
+125 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading