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