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.LoadUrlParams; 23 import org.chromium.content.browser.test.util.CallbackHelper; 24 import org.chromium.content.browser.test.util.Criteria; 25 import org.chromium.content.browser.test.util.CriteriaHelper; 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 AwTestBase()42 public AwTestBase() { 43 super(AwTestRunnerActivity.class); 44 } 45 46 @Override setUp()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 */ needsBrowserProcessStarted()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 */ runTestOnUiThreadAndGetResult(Callable<R> callable)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 enableJavaScriptOnUiThread(final AwContents awContents)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 setNetworkAvailableOnUiThread(final AwContents awContents, final boolean networkUp)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 */ loadUrlSync(final AwContents awContents, CallbackHelper onPageFinishedHelper, final String url)105 public void loadUrlSync(final AwContents awContents, 106 CallbackHelper onPageFinishedHelper, 107 final String url) throws Exception { 108 loadUrlSync(awContents, onPageFinishedHelper, url, null); 109 } 110 loadUrlSync(final AwContents awContents, CallbackHelper onPageFinishedHelper, final String url, final Map<String, String> extraHeaders)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 loadUrlSyncAndExpectError(final AwContents awContents, CallbackHelper onPageFinishedHelper, CallbackHelper onReceivedErrorHelper, final String url)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 */ loadUrlAsync(final AwContents awContents, final String url)137 public void loadUrlAsync(final AwContents awContents, 138 final String url) throws Exception { 139 loadUrlAsync(awContents, url, null); 140 } 141 loadUrlAsync(final AwContents awContents, final String url, final Map<String, String> extraHeaders)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 */ postUrlSync(final AwContents awContents, CallbackHelper onPageFinishedHelper, final String url, byte[] postData)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 */ postUrlAsync(final AwContents awContents, final String url, byte[] postData)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 */ loadDataSync(final AwContents awContents, CallbackHelper onPageFinishedHelper, final String data, final String mimeType, final boolean isBase64Encoded)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 loadDataSyncWithCharset(final AwContents awContents, CallbackHelper onPageFinishedHelper, final String data, final String mimeType, final boolean isBase64Encoded, final String charset)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 */ loadDataAsync(final AwContents awContents, final String data, final String mimeType, final boolean isBase64Encoded)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 loadDataWithBaseUrlSync(final AwContents awContents, CallbackHelper onPageFinishedHelper, final String data, final String mimeType, final boolean isBase64Encoded, final String baseUrl, final String historyUrl)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 loadDataWithBaseUrlAsync(final AwContents awContents, final String data, final String mimeType, final boolean isBase64Encoded, final String baseUrl, final String historyUrl)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 */ reloadSync(final AwContents awContents, CallbackHelper onPageFinishedHelper)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.getContentViewCore().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 { createAwTestContainerView(AwTestRunnerActivity activity)276 public AwTestContainerView createAwTestContainerView(AwTestRunnerActivity activity) { 277 return new AwTestContainerView(activity); 278 } createAwSettings(Context context, boolean supportsLegacyQuirks)279 public AwSettings createAwSettings(Context context, boolean supportsLegacyQuirks) { 280 return new AwSettings(context, false, supportsLegacyQuirks); 281 } 282 } 283 createTestDependencyFactory()284 protected TestDependencyFactory createTestDependencyFactory() { 285 return new TestDependencyFactory(); 286 } 287 createAwTestContainerView( final AwContentsClient awContentsClient)288 public AwTestContainerView createAwTestContainerView( 289 final AwContentsClient awContentsClient) { 290 return createAwTestContainerView(awContentsClient, false); 291 } 292 createAwTestContainerView( final AwContentsClient awContentsClient, boolean supportsLegacyQuirks)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 createDetachedAwTestContainerView( final AwContentsClient awContentsClient)306 public AwTestContainerView createDetachedAwTestContainerView( 307 final AwContentsClient awContentsClient) { 308 return createDetachedAwTestContainerView(awContentsClient, false); 309 } 310 createDetachedAwTestContainerView( final AwContentsClient awContentsClient, boolean supportsLegacyQuirks)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 createAwTestContainerViewOnMainSync( final AwContentsClient client)326 public AwTestContainerView createAwTestContainerViewOnMainSync( 327 final AwContentsClient client) throws Exception { 328 return createAwTestContainerViewOnMainSync(client, false); 329 } 330 createAwTestContainerViewOnMainSync( final AwContentsClient client, final boolean supportsLegacyQuirks)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 destroyAwContentsOnMainSync(final AwContents awContents)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 getTitleOnUiThread(final AwContents awContents)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.getContentViewCore().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