Home | History | Annotate | Download | only in test
      1 // Copyright 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 package org.chromium.android_webview.test;
      6 
      7 import android.app.Instrumentation;
      8 import android.content.Context;
      9 import android.test.ActivityInstrumentationTestCase2;
     10 import android.util.Log;
     11 
     12 import static org.chromium.base.test.util.ScalableTimeout.scaleTimeout;
     13 
     14 import org.chromium.android_webview.AwBrowserContext;
     15 import org.chromium.android_webview.AwBrowserProcess;
     16 import org.chromium.android_webview.AwContents;
     17 import org.chromium.android_webview.AwContentsClient;
     18 import org.chromium.android_webview.AwSettings;
     19 import org.chromium.android_webview.test.util.JSUtils;
     20 import org.chromium.base.test.util.InMemorySharedPreferences;
     21 import org.chromium.content.browser.ContentSettings;
     22 import org.chromium.content.browser.test.util.CallbackHelper;
     23 import org.chromium.content.browser.test.util.Criteria;
     24 import org.chromium.content.browser.test.util.CriteriaHelper;
     25 import org.chromium.content_public.browser.LoadUrlParams;
     26 
     27 import java.util.Map;
     28 import java.util.concurrent.Callable;
     29 import java.util.concurrent.FutureTask;
     30 import java.util.concurrent.TimeUnit;
     31 import java.util.concurrent.atomic.AtomicReference;
     32 
     33 /**
     34  * A base class for android_webview tests.
     35  */
     36 public class AwTestBase
     37         extends ActivityInstrumentationTestCase2<AwTestRunnerActivity> {
     38     public static final long WAIT_TIMEOUT_MS = scaleTimeout(15000);
     39     public static final int CHECK_INTERVAL = 100;
     40     private static final String TAG = "AwTestBase";
     41 
     42     public AwTestBase() {
     43         super(AwTestRunnerActivity.class);
     44     }
     45 
     46     @Override
     47     protected void setUp() throws Exception {
     48         super.setUp();
     49         if (needsBrowserProcessStarted()) {
     50             final Context context = getActivity();
     51             getInstrumentation().runOnMainSync(new Runnable() {
     52                 @Override
     53                 public void run() {
     54                     AwBrowserProcess.start(context);
     55                 }
     56             });
     57         }
     58     }
     59 
     60     /* Override this to return false if the test doesn't want the browser startup sequence to
     61      * be run automatically.
     62      */
     63     protected boolean needsBrowserProcessStarted() {
     64         return true;
     65     }
     66 
     67     /**
     68      * Runs a {@link Callable} on the main thread, blocking until it is
     69      * complete, and returns the result. Calls
     70      * {@link Instrumentation#waitForIdleSync()} first to help avoid certain
     71      * race conditions.
     72      *
     73      * @param <R> Type of result to return
     74      */
     75     public <R> R runTestOnUiThreadAndGetResult(Callable<R> callable)
     76             throws Exception {
     77         FutureTask<R> task = new FutureTask<R>(callable);
     78         getInstrumentation().waitForIdleSync();
     79         getInstrumentation().runOnMainSync(task);
     80         return task.get();
     81     }
     82 
     83     public void enableJavaScriptOnUiThread(final AwContents awContents) {
     84         getInstrumentation().runOnMainSync(new Runnable() {
     85             @Override
     86             public void run() {
     87                 awContents.getSettings().setJavaScriptEnabled(true);
     88             }
     89         });
     90     }
     91 
     92     public void setNetworkAvailableOnUiThread(final AwContents awContents,
     93             final boolean networkUp) {
     94         getInstrumentation().runOnMainSync(new Runnable() {
     95             @Override
     96             public void run() {
     97                 awContents.setNetworkAvailable(networkUp);
     98             }
     99         });
    100     }
    101 
    102     /**
    103      * Loads url on the UI thread and blocks until onPageFinished is called.
    104      */
    105     public void loadUrlSync(final AwContents awContents,
    106                                CallbackHelper onPageFinishedHelper,
    107                                final String url) throws Exception {
    108         loadUrlSync(awContents, onPageFinishedHelper, url, null);
    109     }
    110 
    111     public void loadUrlSync(final AwContents awContents,
    112                                CallbackHelper onPageFinishedHelper,
    113                                final String url,
    114                                final Map<String, String> extraHeaders) throws Exception {
    115         int currentCallCount = onPageFinishedHelper.getCallCount();
    116         loadUrlAsync(awContents, url, extraHeaders);
    117         onPageFinishedHelper.waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_MS,
    118                 TimeUnit.MILLISECONDS);
    119     }
    120 
    121     public void loadUrlSyncAndExpectError(final AwContents awContents,
    122             CallbackHelper onPageFinishedHelper,
    123             CallbackHelper onReceivedErrorHelper,
    124             final String url) throws Exception {
    125         int onErrorCallCount = onReceivedErrorHelper.getCallCount();
    126         int onFinishedCallCount = onPageFinishedHelper.getCallCount();
    127         loadUrlAsync(awContents, url);
    128         onReceivedErrorHelper.waitForCallback(onErrorCallCount, 1, WAIT_TIMEOUT_MS,
    129                 TimeUnit.MILLISECONDS);
    130         onPageFinishedHelper.waitForCallback(onFinishedCallCount, 1, WAIT_TIMEOUT_MS,
    131                 TimeUnit.MILLISECONDS);
    132     }
    133 
    134     /**
    135      * Loads url on the UI thread but does not block.
    136      */
    137     public void loadUrlAsync(final AwContents awContents,
    138                                 final String url) throws Exception {
    139         loadUrlAsync(awContents, url, null);
    140     }
    141 
    142     public void loadUrlAsync(final AwContents awContents,
    143                                 final String url,
    144                                 final Map<String, String> extraHeaders) {
    145         getInstrumentation().runOnMainSync(new Runnable() {
    146             @Override
    147             public void run() {
    148                 LoadUrlParams params = new LoadUrlParams(url);
    149                 params.setExtraHeaders(extraHeaders);
    150                 awContents.loadUrl(params);
    151             }
    152         });
    153     }
    154 
    155     /**
    156      * Posts url on the UI thread and blocks until onPageFinished is called.
    157      */
    158     public void postUrlSync(final AwContents awContents,
    159             CallbackHelper onPageFinishedHelper, final String url,
    160             byte[] postData) throws Exception {
    161         int currentCallCount = onPageFinishedHelper.getCallCount();
    162         postUrlAsync(awContents, url, postData);
    163         onPageFinishedHelper.waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_MS,
    164                 TimeUnit.MILLISECONDS);
    165     }
    166 
    167     /**
    168      * Loads url on the UI thread but does not block.
    169      */
    170     public void postUrlAsync(final AwContents awContents,
    171             final String url, byte[] postData) throws Exception {
    172         class PostUrl implements Runnable {
    173             byte[] mPostData;
    174             public PostUrl(byte[] postData) {
    175                 mPostData = postData;
    176             }
    177             @Override
    178             public void run() {
    179                 awContents.loadUrl(LoadUrlParams.createLoadHttpPostParams(url,
    180                         mPostData));
    181             }
    182         }
    183         getInstrumentation().runOnMainSync(new PostUrl(postData));
    184     }
    185 
    186     /**
    187      * Loads data on the UI thread and blocks until onPageFinished is called.
    188      */
    189     public void loadDataSync(final AwContents awContents,
    190                                 CallbackHelper onPageFinishedHelper,
    191                                 final String data, final String mimeType,
    192                                 final boolean isBase64Encoded) throws Exception {
    193         int currentCallCount = onPageFinishedHelper.getCallCount();
    194         loadDataAsync(awContents, data, mimeType, isBase64Encoded);
    195         onPageFinishedHelper.waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_MS,
    196                 TimeUnit.MILLISECONDS);
    197     }
    198 
    199     public void loadDataSyncWithCharset(final AwContents awContents,
    200                                            CallbackHelper onPageFinishedHelper,
    201                                            final String data, final String mimeType,
    202                                            final boolean isBase64Encoded, final String charset)
    203             throws Exception {
    204         int currentCallCount = onPageFinishedHelper.getCallCount();
    205         getInstrumentation().runOnMainSync(new Runnable() {
    206             @Override
    207             public void run() {
    208                 awContents.loadUrl(LoadUrlParams.createLoadDataParams(
    209                         data, mimeType, isBase64Encoded, charset));
    210             }
    211         });
    212         onPageFinishedHelper.waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_MS,
    213                 TimeUnit.MILLISECONDS);
    214     }
    215 
    216     /**
    217      * Loads data on the UI thread but does not block.
    218      */
    219     public void loadDataAsync(final AwContents awContents, final String data,
    220                                  final String mimeType, final boolean isBase64Encoded)
    221             throws Exception {
    222         getInstrumentation().runOnMainSync(new Runnable() {
    223             @Override
    224             public void run() {
    225                 awContents.loadUrl(LoadUrlParams.createLoadDataParams(
    226                         data, mimeType, isBase64Encoded));
    227             }
    228         });
    229     }
    230 
    231     public void loadDataWithBaseUrlSync(final AwContents awContents,
    232             CallbackHelper onPageFinishedHelper, final String data, final String mimeType,
    233             final boolean isBase64Encoded, final String baseUrl,
    234             final String historyUrl) throws Throwable {
    235         int currentCallCount = onPageFinishedHelper.getCallCount();
    236         loadDataWithBaseUrlAsync(awContents, data, mimeType, isBase64Encoded, baseUrl, historyUrl);
    237         onPageFinishedHelper.waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_MS,
    238                 TimeUnit.MILLISECONDS);
    239     }
    240 
    241     public void loadDataWithBaseUrlAsync(final AwContents awContents,
    242             final String data, final String mimeType, final boolean isBase64Encoded,
    243             final String baseUrl, final String historyUrl) throws Throwable {
    244         runTestOnUiThread(new Runnable() {
    245             @Override
    246             public void run() {
    247                 awContents.loadUrl(LoadUrlParams.createLoadDataParamsWithBaseUrl(
    248                         data, mimeType, isBase64Encoded, baseUrl, historyUrl));
    249             }
    250         });
    251     }
    252 
    253     /**
    254      * Reloads the current page synchronously.
    255      */
    256     public void reloadSync(final AwContents awContents,
    257                               CallbackHelper onPageFinishedHelper) throws Exception {
    258         int currentCallCount = onPageFinishedHelper.getCallCount();
    259         getInstrumentation().runOnMainSync(new Runnable() {
    260             @Override
    261             public void run() {
    262                 awContents.getNavigationController().reload(true);
    263             }
    264         });
    265         onPageFinishedHelper.waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_MS,
    266                 TimeUnit.MILLISECONDS);
    267     }
    268 
    269     /**
    270      * Factory class used in creation of test AwContents instances.
    271      *
    272      * Test cases can provide subclass instances to the createAwTest* methods in order to create an
    273      * AwContents instance with injected test dependencies.
    274      */
    275     public static class TestDependencyFactory extends AwContents.DependencyFactory {
    276         public AwTestContainerView createAwTestContainerView(AwTestRunnerActivity activity) {
    277             return new AwTestContainerView(activity, false);
    278         }
    279         public AwSettings createAwSettings(Context context, boolean supportsLegacyQuirks) {
    280             return new AwSettings(context, false, supportsLegacyQuirks);
    281         }
    282     }
    283 
    284     protected TestDependencyFactory createTestDependencyFactory() {
    285         return new TestDependencyFactory();
    286     }
    287 
    288     public AwTestContainerView createAwTestContainerView(
    289             final AwContentsClient awContentsClient) {
    290         return createAwTestContainerView(awContentsClient, false);
    291     }
    292 
    293     public AwTestContainerView createAwTestContainerView(
    294             final AwContentsClient awContentsClient, boolean supportsLegacyQuirks) {
    295         AwTestContainerView testContainerView =
    296                 createDetachedAwTestContainerView(awContentsClient, supportsLegacyQuirks);
    297         getActivity().addView(testContainerView);
    298         testContainerView.requestFocus();
    299         return testContainerView;
    300     }
    301 
    302     // The browser context needs to be a process-wide singleton.
    303     private AwBrowserContext mBrowserContext =
    304             new AwBrowserContext(new InMemorySharedPreferences());
    305 
    306     public AwTestContainerView createDetachedAwTestContainerView(
    307             final AwContentsClient awContentsClient) {
    308         return createDetachedAwTestContainerView(awContentsClient, false);
    309     }
    310 
    311     public AwTestContainerView createDetachedAwTestContainerView(
    312             final AwContentsClient awContentsClient, boolean supportsLegacyQuirks) {
    313         final TestDependencyFactory testDependencyFactory = createTestDependencyFactory();
    314         final AwTestContainerView testContainerView =
    315             testDependencyFactory.createAwTestContainerView(getActivity());
    316         AwSettings awSettings = testDependencyFactory.createAwSettings(getActivity(),
    317                 supportsLegacyQuirks);
    318         testContainerView.initialize(new AwContents(
    319                 mBrowserContext, testContainerView, testContainerView.getContext(),
    320                 testContainerView.getInternalAccessDelegate(),
    321                 testContainerView.getNativeGLDelegate(), awContentsClient,
    322                 awSettings, testDependencyFactory));
    323         return testContainerView;
    324     }
    325 
    326     public AwTestContainerView createAwTestContainerViewOnMainSync(
    327             final AwContentsClient client) throws Exception {
    328         return createAwTestContainerViewOnMainSync(client, false);
    329     }
    330 
    331     public AwTestContainerView createAwTestContainerViewOnMainSync(
    332             final AwContentsClient client, final boolean supportsLegacyQuirks) throws Exception {
    333         final AtomicReference<AwTestContainerView> testContainerView =
    334                 new AtomicReference<AwTestContainerView>();
    335         getInstrumentation().runOnMainSync(new Runnable() {
    336             @Override
    337             public void run() {
    338                 testContainerView.set(createAwTestContainerView(client, supportsLegacyQuirks));
    339             }
    340         });
    341         return testContainerView.get();
    342     }
    343 
    344     public void destroyAwContentsOnMainSync(final AwContents awContents) {
    345         if (awContents == null) return;
    346         getInstrumentation().runOnMainSync(new Runnable() {
    347             @Override
    348             public void run() {
    349                 awContents.destroy();
    350             }
    351         });
    352     }
    353 
    354     public String getTitleOnUiThread(final AwContents awContents) throws Exception {
    355         return runTestOnUiThreadAndGetResult(new Callable<String>() {
    356             @Override
    357             public String call() throws Exception {
    358                 return awContents.getTitle();
    359             }
    360         });
    361     }
    362 
    363     public ContentSettings getContentSettingsOnUiThread(
    364             final AwContents awContents) throws Exception {
    365         return runTestOnUiThreadAndGetResult(new Callable<ContentSettings>() {
    366             @Override
    367             public ContentSettings call() throws Exception {
    368                 return awContents.getContentViewCore().getContentSettings();
    369             }
    370         });
    371     }
    372 
    373     public AwSettings getAwSettingsOnUiThread(
    374             final AwContents awContents) throws Exception {
    375         return runTestOnUiThreadAndGetResult(new Callable<AwSettings>() {
    376             @Override
    377             public AwSettings call() throws Exception {
    378                 return awContents.getSettings();
    379             }
    380         });
    381     }
    382 
    383     /**
    384      * Executes the given snippet of JavaScript code within the given ContentView. Returns the
    385      * result of its execution in JSON format.
    386      */
    387     public String executeJavaScriptAndWaitForResult(final AwContents awContents,
    388             TestAwContentsClient viewClient, final String code) throws Exception {
    389         return JSUtils.executeJavaScriptAndWaitForResult(this, awContents,
    390                 viewClient.getOnEvaluateJavaScriptResultHelper(),
    391                 code);
    392     }
    393 
    394     /**
    395      * Wrapper around CriteriaHelper.pollForCriteria. This uses AwTestBase-specifc timeouts and
    396      * treats timeouts and exceptions as test failures automatically.
    397      */
    398     public static void poll(final Callable<Boolean> callable) throws Exception {
    399         assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
    400             @Override
    401             public boolean isSatisfied() {
    402                 try {
    403                     return callable.call();
    404                 } catch (Throwable e) {
    405                     Log.e(TAG, "Exception while polling.", e);
    406                     return false;
    407                 }
    408             }
    409         }, WAIT_TIMEOUT_MS, CHECK_INTERVAL));
    410     }
    411 
    412     /**
    413      * Wrapper around {@link AwTestBase#poll()} but runs the callable on the UI thread.
    414      */
    415     public void pollOnUiThread(final Callable<Boolean> callable) throws Exception {
    416         poll(new Callable<Boolean>() {
    417             @Override
    418             public Boolean call() throws Exception {
    419                 return runTestOnUiThreadAndGetResult(callable);
    420             }
    421         });
    422     }
    423 
    424     /**
    425      * Clears the resource cache. Note that the cache is per-application, so this will clear the
    426      * cache for all WebViews used.
    427      */
    428     public void clearCacheOnUiThread(
    429             final AwContents awContents,
    430             final boolean includeDiskFiles) throws Exception {
    431         getInstrumentation().runOnMainSync(new Runnable() {
    432             @Override
    433             public void run() {
    434                 awContents.clearCache(includeDiskFiles);
    435             }
    436         });
    437     }
    438 
    439     /**
    440      * Returns pure page scale.
    441      */
    442     public float getScaleOnUiThread(final AwContents awContents) throws Exception {
    443         return runTestOnUiThreadAndGetResult(new Callable<Float>() {
    444             @Override
    445             public Float call() throws Exception {
    446                 return awContents.getPageScaleFactor();
    447             }
    448         });
    449     }
    450 
    451     /**
    452      * Returns page scale multiplied by the screen density.
    453      */
    454     public float getPixelScaleOnUiThread(final AwContents awContents) throws Exception {
    455         return runTestOnUiThreadAndGetResult(new Callable<Float>() {
    456             @Override
    457             public Float call() throws Exception {
    458                 return awContents.getScale();
    459             }
    460         });
    461     }
    462 
    463     /**
    464      * Returns whether a user can zoom the page in.
    465      */
    466     public boolean canZoomInOnUiThread(final AwContents awContents) throws Exception {
    467         return runTestOnUiThreadAndGetResult(new Callable<Boolean>() {
    468             @Override
    469             public Boolean call() throws Exception {
    470                 return awContents.canZoomIn();
    471             }
    472         });
    473     }
    474 
    475     /**
    476      * Returns whether a user can zoom the page out.
    477      */
    478     public boolean canZoomOutOnUiThread(final AwContents awContents) throws Exception {
    479         return runTestOnUiThreadAndGetResult(new Callable<Boolean>() {
    480             @Override
    481             public Boolean call() throws Exception {
    482                 return awContents.canZoomOut();
    483             }
    484         });
    485     }
    486 
    487 }
    488