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

Commit 600780bb authored by Dounia Berrada's avatar Dounia Berrada
Browse files

First bits of WebDriver containing blocking function to load a page and

fetching the page source.

bug: 3457555
Change-Id: I5fbeb7f6103f1e1af04b7a6964cde5710338ee6e
parent f60cee57
Loading
Loading
Loading
Loading
+216 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.webkit.webdriver;

import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.webkit.ConsoleMessage;
import android.webkit.GeolocationPermissions;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebStorage;
import android.webkit.WebView;

/**
 * Drives a web application by controlling the WebView. This class
 * provides a DOM-like API allowing to get information about the page,
 * navigate, and interact with the web application. This is particularly useful
 * for testing a web application.
 *
 * <p/>{@link android.webkit.webdriver.WebDriver} should be created in the main
 * thread, and invoked from another thread. Here is a sample usage:
 *
 * public class WebDriverStubActivity extends Activity {
 *   private WebDriver mDriver;
 *
 *   public void onCreate(Bundle savedInstanceState) {
 *       super.onCreate(savedInstanceState);
 *       WebView view = new WebView(this);
 *       mDriver = new WebDriver(view);
 *       setContentView(view);
 *   }
 *
 *
 *   public WebDriver getDriver() {
 *       return mDriver;
 *   }
 *}
 *
 * public class WebDriverTest extends
 *       ActivityInstrumentationTestCase2<WebDriverStubActivity>{
 *   private WebDriver mDriver;
 *
 *   public WebDriverTest() {
 *       super(WebDriverStubActivity.class);
 *   }
 *
 *   protected void setUp() throws Exception {
 *       super.setUp();
 *       mDriver = getActivity().getDriver();
 *   }
 *
 *   public void testGetIsBlocking() {
 *       mDriver.get("http://google.com");
 *       assertTrue(mDriver.getPageSource().startsWith("<html"));
 *   }
 *}
 *
 * @hide
 */
public class WebDriver {
    // Timeout for page load in milliseconds
    private static final int LOADING_TIMEOUT = 30000;
    // Timeout for executing JavaScript in the WebView in milliseconds
    private static final int JS_EXECUTION_TIMEOUT = 10000;

    // Commands posted to the handler
    private static final int GET_URL = 1;
    private static final int EXECUTE_SCRIPT = 2;

    private static final long MAIN_THREAD = Thread.currentThread().getId();

    // This is updated by a callabck from JavaScript when the result is ready
    private String mJsResult;

    // Used for synchronization
    private final Object mSyncObject;

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

    private WebView mWebView;

    // This Handler runs in the main UI thread
    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == GET_URL) {
                final String url = (String) msg.obj;
                mWebView.loadUrl(url);
            } else if (msg.what == EXECUTE_SCRIPT) {
                executeScript((String) msg.obj);
            }
        }
    };

    public WebDriver(WebView webview) {
        if (!mWebView.getSettings().getJavaScriptEnabled()) {
            throw new RuntimeException("Javascript is disabled in the WebView. "
                    + "Enable it to use WebDriver");
        }
        shouldRunInMainThread(true);
        mSyncObject = new Object();
        this.mWebView = webview;
        WebchromeClientWrapper chromeWrapper = new WebchromeClientWrapper(
                webview.getWebChromeClient(), this);
        mWebView.setWebChromeClient(chromeWrapper);
        mWebView.addJavascriptInterface(new JavascriptResultReady(),
                "webdriver");
    }

    /**
     * Loads a URL in the WebView. This function is blocking and will return
     * when the page has finished loading.
     *
     * @param url The URL to load.
     */
    public void get(String url) {
        executeCommand(GET_URL, url, LOADING_TIMEOUT);
    }

    /**
     * @return The source page of the currently loaded page in WebView.
     */
    public String getPageSource() {
        executeCommand(EXECUTE_SCRIPT, "return (new XMLSerializer())"
                + ".serializeToString(document.documentElement);",
                JS_EXECUTION_TIMEOUT);
        return mJsResult;
    }

    private void executeScript(String script) {
        mWebView.loadUrl("javascript:" + script);
    }

    private void shouldRunInMainThread(boolean value) {
        assert (value == (MAIN_THREAD == Thread.currentThread().getId()));
    }

    /**
     * Interface called from JavaScript when the result is ready.
     */
    private class JavascriptResultReady {

        /**
         * A callback from JavaScript to Java that passes the result as a
         * parameter. This method is available from the WebView's
         * JavaScript DOM as window.webdriver.resultReady().
         *
         * @param result The result that should be sent to Java from Javascript.
         */
        public void resultReady(String result) {
            synchronized (mSyncObject) {
                mJsResult = result;
                mCommandDone = true;
                mSyncObject.notify();
            }
        }
    }

    /* package */ void notifyCommandDone() {
        synchronized (mSyncObject) {
            mCommandDone = true;
            mSyncObject.notify();
        }
    }

    /**
     * Executes the given command by posting a message to mHandler. This thread
     * will block until the command which runs in the main thread is done.
     *
     * @param command The command to run.
     * @param arg The argument for that command.
     * @param timeout A timeout in milliseconds.
     */
    private void executeCommand(int command, String arg, long timeout) {
        shouldRunInMainThread(false);

        synchronized (mSyncObject) {
            mCommandDone = false;
            Message msg = mHandler.obtainMessage(command);
            msg.obj = arg;
            mHandler.sendMessage(msg);

            long end = System.currentTimeMillis() + timeout;
            while (!mCommandDone) {
                if (System.currentTimeMillis() >= end) {
                    throw new RuntimeException("Timeout executing command.");
                }
                try {
                    mSyncObject.wait(timeout);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}
+193 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.webkit.webdriver;

import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Message;
import android.view.View;
import android.webkit.ConsoleMessage;
import android.webkit.GeolocationPermissions;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebStorage;
import android.webkit.WebView;

/* package */ class WebchromeClientWrapper extends WebChromeClient {

    private final WebChromeClient mDelegate;
    private final WebDriver mDriver;

    public WebchromeClientWrapper(WebChromeClient delegate, WebDriver driver) {
        if (delegate == null) {
            this.mDelegate = new WebChromeClient();
        } else {
            this.mDelegate = delegate;
        }
        this.mDriver = driver;
    }

    @Override
    public void onProgressChanged(WebView view, int newProgress) {
        if (newProgress == 100) {
            mDriver.notifyCommandDone();
        }
        mDelegate.onProgressChanged(view, newProgress);
    }

    @Override
    public void onReceivedTitle(WebView view, String title) {
        mDelegate.onReceivedTitle(view, title);
    }

    @Override
    public void onReceivedIcon(WebView view, Bitmap icon) {
        mDelegate.onReceivedIcon(view, icon);
    }

    @Override
    public void onReceivedTouchIconUrl(WebView view, String url,
            boolean precomposed) {
        mDelegate.onReceivedTouchIconUrl(view, url, precomposed);
    }

    @Override
    public void onShowCustomView(View view,
            CustomViewCallback callback) {
        mDelegate.onShowCustomView(view, callback);
    }

    @Override
    public void onHideCustomView() {
        mDelegate.onHideCustomView();
    }

    @Override
    public boolean onCreateWindow(WebView view, boolean dialog,
            boolean userGesture, Message resultMsg) {
        return mDelegate.onCreateWindow(view, dialog, userGesture, resultMsg);
    }

    @Override
    public void onRequestFocus(WebView view) {
        mDelegate.onRequestFocus(view);
    }

    @Override
    public void onCloseWindow(WebView window) {
        mDelegate.onCloseWindow(window);
    }

    @Override
    public boolean onJsAlert(WebView view, String url, String message,
            JsResult result) {
        return mDelegate.onJsAlert(view, url, message, result);
    }

    @Override
    public boolean onJsConfirm(WebView view, String url, String message,
            JsResult result) {
        return mDelegate.onJsConfirm(view, url, message, result);
    }

    @Override
    public boolean onJsPrompt(WebView view, String url, String message,
            String defaultValue, JsPromptResult result) {
        return mDelegate.onJsPrompt(view, url, message, defaultValue, result);
    }

    @Override
    public boolean onJsBeforeUnload(WebView view, String url, String message,
            JsResult result) {
        return mDelegate.onJsBeforeUnload(view, url, message, result);
    }

    @Override
    public void onExceededDatabaseQuota(String url, String databaseIdentifier,
            long currentQuota, long estimatedSize, long totalUsedQuota,
            WebStorage.QuotaUpdater quotaUpdater) {
        mDelegate.onExceededDatabaseQuota(url, databaseIdentifier, currentQuota,
                estimatedSize, totalUsedQuota, quotaUpdater);
    }

    @Override
    public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota,
            WebStorage.QuotaUpdater quotaUpdater) {
        mDelegate.onReachedMaxAppCacheSize(spaceNeeded, totalUsedQuota,
                quotaUpdater);
    }

    @Override
    public void onGeolocationPermissionsShowPrompt(String origin,
            GeolocationPermissions.Callback callback) {
        mDelegate.onGeolocationPermissionsShowPrompt(origin, callback);
    }

    @Override
    public void onGeolocationPermissionsHidePrompt() {
        mDelegate.onGeolocationPermissionsHidePrompt();
    }

    @Override
    public boolean onJsTimeout() {
        return mDelegate.onJsTimeout();
    }

    @Override
    public void onConsoleMessage(String message, int lineNumber,
            String sourceID) {
        mDelegate.onConsoleMessage(message, lineNumber, sourceID);
    }

    @Override
    public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
        return mDelegate.onConsoleMessage(consoleMessage);
    }

    @Override
    public Bitmap getDefaultVideoPoster() {
        return mDelegate.getDefaultVideoPoster();
    }

    @Override
    public View getVideoLoadingProgressView() {
        return mDelegate.getVideoLoadingProgressView();
    }

    @Override
    public void getVisitedHistory(ValueCallback<String[]> callback) {
        mDelegate.getVisitedHistory(callback);
    }

    @Override
    public void openFileChooser(ValueCallback<Uri> uploadFile,
            String acceptType) {
        mDelegate.openFileChooser(uploadFile, acceptType);
    }

    @Override
    public void setInstallableWebApp() {
        mDelegate.setInstallableWebApp();
    }

    @Override
    public void setupAutoFill(Message msg) {
        mDelegate.setupAutoFill(msg);
    }
}