// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.vr;
import android.os.Build;
import android.view.View;
import androidx.annotation.IntDef;
import org.junit.Assert;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.chrome.browser.ZoomController;
import org.chromium.chrome.browser.tab.SadTab;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.Tab.LoadUrlResult;
import org.chromium.chrome.browser.tab.TabLaunchType;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.util.ChromeTabUtils;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.test.RenderFrameHostTestExt;
import org.chromium.content_public.browser.test.util.JavaScriptUtils;
import org.chromium.content_public.browser.test.util.WebContentsUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.HashSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
/**
* Class containing the core framework for all XR (VR and AR) testing, which requires back-and-forth
* communication with JavaScript running in the browser. Feature-specific behavior can be found in
* the *TestFramework subclasses. Additional utility methods that don't relate to the core framework
* can be found in the util/ directory.
*
* <p>The general test flow is: - Load the HTML file containing the test, which: - Loads the WebVR
* boilerplate code and some test functions - Sets up common elements like the canvas and
* synchronization variable - Sets up any steps that need to be triggered by the Java code - Check
* if any VRDisplay objects were found and fail the test if it doesn't match what we expect for that
* test - Repeat: - Run any necessary Java-side code, e.g. trigger a user action - Trigger the next
* JavaScript test step and wait for it to finish
*
* <p>The JavaScript code will automatically process test results once all testharness.js tests are
* done, just like in layout tests. Once the results are processed, the JavaScript code will
* automatically signal the Java code, which can then grab the results and pass/fail the
* instrumentation test.
*/
public abstract class XrTestFramework {
public static final HashSet<String> OLD_DEVICE_BOARDS =
new HashSet(Arrays.asList("bullhead" /* Nexus 5X */, "marlin" /* Pixel 1 */));
public static final int PAGE_LOAD_TIMEOUT_S = 10;
// These two were originally different values, but the short one was bumped up to increase
// test harness reliability. The long version might also want to be bumped up at some point, or
// the two could be merged.
public static final int POLL_CHECK_INTERVAL_SHORT_MS = 100;
public static final int POLL_CHECK_INTERVAL_LONG_MS = 100;
public static final int POLL_TIMEOUT_SHORT_MS = getShortPollTimeout();
public static final int POLL_TIMEOUT_LONG_MS = getLongPollTimeout();
public static final boolean DEBUG_LOGS = false;
// The "3" corresponds to the "Mobile Bookmarks" folder - omitting a particular folder
// automatically redirects to that folder, and not having it in the URL causes issues with the
// URL we expect to be loaded being different than the actual URL.
public static final String[] NATIVE_URLS_OF_INTEREST = {
UrlConstants.BOOKMARKS_FOLDER_URL + "3",
UrlConstants.BOOKMARKS_UNCATEGORIZED_URL,
UrlConstants.BOOKMARKS_URL,
UrlConstants.DOWNLOADS_URL,
UrlConstants.NATIVE_HISTORY_URL,
UrlConstants.NTP_URL,
UrlConstants.RECENT_TABS_URL
};
private static final String TAG = "XrTestFramework";
static final String TEST_DIR = "chrome/test/data/xr/e2e_test_files";
// Test status enum
@IntDef({TestStatus.RUNNING, TestStatus.PASSED, TestStatus.FAILED})
@Retention(RetentionPolicy.SOURCE)
private @interface TestStatus {
int RUNNING = 0;
int PASSED = 1;
int FAILED = 2;
}
private ChromeActivityTestRule mRule;
static final int getShortPollTimeout() {
return getPollTimeout(1000);
}
static final int getLongPollTimeout() {
return getPollTimeout(10000);
}
static final int getPollTimeout(int baseTimeout) {
// Increase the timeouts on older devices, as the tests can be rather slow on them.
if (OLD_DEVICE_BOARDS.contains(Build.BOARD)) {
baseTimeout *= 2;
}
return baseTimeout;
}
/**
* Gets the file:// URL to the test file.
*
* @param testName The name of the test whose file will be retrieved.
* @return The file:// URL to the specified test file.
*/
public static String getFileUrlForHtmlTestFile(String testName) {
return "file://"
+ UrlUtils.getIsolatedTestFilePath(TEST_DIR)
+ "/html/"
+ testName
+ ".html";
}
/**
* Checks whether a request for the given permission would trigger a permission prompt.
*
* @param permission The name of the permission to check. Must come from PermissionName enum
* (see third_party/blink/renderer/modules/permissions/permission_descriptor.idl).
* @param webContents The WebContents to run the JavaScript in.
* @return True if the permission request would trigger a prompt, false otherwise.
*/
public static boolean permissionRequestWouldTriggerPrompt(
String permission, WebContents webContents) {
runJavaScriptOrFail(
"checkPermissionRequestWouldTriggerPrompt('" + permission + "')",
POLL_TIMEOUT_SHORT_MS,
webContents);
pollJavaScriptBooleanOrFail("wouldPrompt !== null", POLL_TIMEOUT_SHORT_MS, webContents);
return Boolean.valueOf(
runJavaScriptOrFail("wouldPrompt", POLL_TIMEOUT_SHORT_MS, webContents));
}
/**
* Helper function to run the given JavaScript, return the return value, and fail if a
* timeout/interrupt occurs so we don't have to catch or declare exceptions all the time.
*
* @param js The JavaScript to run.
* @param timeout The timeout in milliseconds before a failure.
* @param webContents The WebContents object to run the JavaScript in.
* @return The return value of the JavaScript.
*/
public static String runJavaScriptOrFail(String js, int timeout, WebContents webContents) {
if (DEBUG_LOGS) Log.i(TAG, "runJavaScriptOrFail " + js);
try {
String ret =
JavaScriptUtils.executeJavaScriptAndWaitForResult(
webContents, js, timeout, TimeUnit.MILLISECONDS);
if (DEBUG_LOGS) Log.i(TAG, "runJavaScriptOrFail result=" + ret);
return ret;
} catch (TimeoutException e) {
Assert.fail(
"Fatal interruption or timeout running JavaScript '"
+ js
+ "': "
+ e.toString());
}
return "Not reached";
}
/**
* Runs the given JavaScript in the focused frame, failing if a timeout/interrupt occurs.
*
* @param js The JavaScript to run.
* @param timeout The timeout in milliseconds before failure.
* @param webContents The WebContents object to get the focused frame from.
* @return The return value of the JavaScript.
*/
public static String runJavaScriptInFrameOrFail(
String js, int timeout, final WebContents webContents) {
return runJavaScriptInFrameInternal(js, timeout, webContents, /* failOnTimeout= */ true);
}
/**
* Polls the provided JavaScript boolean expression until the timeout is reached or the boolean
* is true.
*
* @param boolExpression The JavaScript boolean expression to poll.
* @param timeoutMs The polling timeout in milliseconds.
* @param webContents The WebContents to run the JavaScript through.
* @return True if the boolean evaluated to true, false if timed out.
*/
public static boolean pollJavaScriptBoolean(
final String boolExpression, int timeoutMs, final WebContents webContents) {
if (DEBUG_LOGS) {
Log.i(TAG, "pollJavaScriptBoolean " + boolExpression + ", timeoutMs=" + timeoutMs);
}
try {
CriteriaHelper.pollInstrumentationThread(
() -> {
String result = "false";
try {
result =
JavaScriptUtils.executeJavaScriptAndWaitForResult(
webContents,
boolExpression,
POLL_CHECK_INTERVAL_SHORT_MS,
TimeUnit.MILLISECONDS);
if (DEBUG_LOGS) {
Log.i(
TAG,
"pollJavaScriptBoolean "
+ boolExpression
+ " => "
+ result);
}
} catch (TimeoutException e) {
// Expected to happen regularly, do nothing
}
return Boolean.parseBoolean(result);
},
"Polling timed out",
timeoutMs,
POLL_CHECK_INTERVAL_LONG_MS);
} catch (CriteriaHelper.TimeoutException e) {
Log.d(TAG, "pollJavaScriptBoolean() timed out: " + e.toString());
return false;
}
return true;
}
/**
* Polls the provided JavaScript boolean expression in the focused frame until the timeout is
* reached or the boolean is true.
*
* @param boolExpression The JavaScript boolean expression to poll.
* @param timeoutMs The polling timeout in milliseconds.
* @param webContents The WebContents to get the focused frame from.
* @return True if the boolean evaluated to true, false if timed out.
*/
public static boolean pollJavaScriptBooleanInFrame(
final String boolExpression, int timeoutMs, final WebContents webContents) {
if (DEBUG_LOGS) Log.i(TAG, "pollJavaScriptBooleanInFrame " + boolExpression);
try {
CriteriaHelper.pollInstrumentationThread(
() -> {
String result = "false";
result =
runJavaScriptInFrameInternal(
boolExpression,
POLL_CHECK_INTERVAL_SHORT_MS,
webContents,
/* failOnTimeout= */ false);
if (DEBUG_LOGS) {
Log.i(
TAG,
"pollJavaScriptBooleanInFrame "
+ boolExpression
+ " => "
+ result);
}
return Boolean.parseBoolean(result);
},
"Polling timed out",
timeoutMs,
POLL_CHECK_INTERVAL_LONG_MS);
} catch (CriteriaHelper.TimeoutException e) {
Log.d(TAG, "pollJavaScriptBooleanInFrame() timed out: " + e.toString());
return false;
}
return true;
}
/**
* Polls the provided JavaScript boolean expression, failing the test if it does not evaluate to
* true within the provided timeout.
*
* @param boolExpression The JavaScript boolean expression to poll.
* @param timeoutMs The polling timeout in milliseconds.
* @param webContents The Webcontents to run the JavaScript through.
*/
public static void pollJavaScriptBooleanOrFail(
String boolExpression, int timeoutMs, WebContents webContents) {
Assert.assertTrue(
"Timed out polling JavaScript boolean expression: " + boolExpression,
pollJavaScriptBoolean(boolExpression, timeoutMs, webContents));
}
/**
* Polls the provided JavaScript boolean expression in the focused frame, failing the test if it
* does not evaluate to true within the provided timeout.
*
* @param boolExpression The JavaScript boolean expression to poll.
* @param timeoutMs The polling timeout in milliseconds.
* @param webContents The WebContents to get the focused frame from.
*/
public static void pollJavaScriptBooleanInFrameOrFail(
String boolExpression, int timeoutMs, WebContents webContents) {
Assert.assertTrue(
"Timed out polling JavaScript boolean expression in focused frame: "
+ boolExpression,
pollJavaScriptBooleanInFrame(boolExpression, timeoutMs, webContents));
}
/**
* Executes a JavaScript step function using the given WebContents.
*
* @param stepFunction The JavaScript step function to call.
* @param webContents The WebContents for the tab the JavaScript is in.
*/
public static void executeStepAndWait(String stepFunction, WebContents webContents) {
executeStepAndWait(stepFunction, webContents, POLL_TIMEOUT_LONG_MS);
}
/**
* Executes a JavaScript step function using the given WebContents.
*
* @param stepFunction The JavaScript step function to call.
* @param webContents The WebContents for the tab the JavaScript is in.
* @param timeoutMs Timeout (in milliseconds) to wait for the JavaScript step.
*/
public static void executeStepAndWait(
String stepFunction, WebContents webContents, int timeoutMs) {
// Run the step and block
if (DEBUG_LOGS) Log.i(TAG, "executeStepAndWait " + stepFunction);
JavaScriptUtils.executeJavaScript(webContents, stepFunction);
if (DEBUG_LOGS) Log.i(TAG, "executeStepAndWait ...wait");
waitOnJavaScriptStep(webContents, timeoutMs);
if (DEBUG_LOGS) Log.i(TAG, "executeStepAndWait ...done");
}
/**
* Waits for a JavaScript step to finish, asserting that the step finished instead of timing
* out.
*
* @param webContents The WebContents for the tab the JavaScript step is in.
*/
public static void waitOnJavaScriptStep(WebContents webContents) {
waitOnJavaScriptStep(webContents, POLL_TIMEOUT_LONG_MS);
}
/**
* Waits for a JavaScript step to finish, asserting that the step finished instead of timing
* out.
*
* @param webContents The WebContents for the tab the JavaScript step is in.
* @param timeoutMs Timeout (in milliseconds) to wait for the JavaScript step.
*/
public static void waitOnJavaScriptStep(WebContents webContents, int timeoutMs) {
if (DEBUG_LOGS) Log.i(TAG, "waitOnJavaScriptStep, timeoutMs=" + timeoutMs);
// Make sure we aren't trying to wait on a JavaScript test step without the code to do so.
Assert.assertTrue(
"Attempted to wait on a JavaScript step without the code to do so. You "
+ "either forgot to import webxr_e2e.js or are incorrectly using a "
+ "Java method.",
Boolean.parseBoolean(
runJavaScriptOrFail(
"typeof javascriptDone !== 'undefined'",
POLL_TIMEOUT_SHORT_MS,
webContents)));
// Actually wait for the step to finish
boolean success = pollJavaScriptBoolean("javascriptDone", timeoutMs, webContents);
// Check what state we're in to make sure javascriptDone wasn't called because the test
// failed.
@TestStatus int testStatus = checkTestStatus(webContents);
if (!success || testStatus == TestStatus.FAILED) {
// Failure states: Either polling failed or polling succeeded, but because the test
// failed.
String reason;
if (!success) {
reason = "Timed out waiting for JavaScript step to finish.";
} else {
reason =
"JavaScript testharness reported failure while waiting for JavaScript "
+ "step to finish";
}
String resultString =
runJavaScriptOrFail("resultString", POLL_TIMEOUT_SHORT_MS, webContents);
if (resultString.equals("\"\"")) {
reason += " Did not obtain specific failure reason from JavaScript testharness.";
} else {
reason += " JavaScript testharness reported failure reason: " + resultString;
}
Assert.fail(reason);
}
// Reset the synchronization boolean
runJavaScriptOrFail("javascriptDone = false", POLL_TIMEOUT_SHORT_MS, webContents);
}
/**
* Retrieves the current status of the JavaScript test and returns an enum corresponding to it.
*
* @param webContents The WebContents for the tab to check the status in.
* @return A TestStatus integer corresponding to the current state of the JavaScript test.
*/
public static @TestStatus int checkTestStatus(WebContents webContents) {
String resultString =
runJavaScriptOrFail("resultString", POLL_TIMEOUT_SHORT_MS, webContents);
boolean testPassed =
Boolean.parseBoolean(
runJavaScriptOrFail("testPassed", POLL_TIMEOUT_SHORT_MS, webContents));
if (testPassed) {
return TestStatus.PASSED;
} else if (!testPassed && resultString.equals("\"\"")) {
return TestStatus.RUNNING;
} else {
// !testPassed && !resultString.equals("\"\"")
return TestStatus.FAILED;
}
}
/**
* Helper function to end the test harness test and assert that it passed, setting the failure
* reason as the description if it didn't.
*
* @param webContents The WebContents for the tab to check test results in.
*/
public static void endTest(WebContents webContents) {
switch (checkTestStatus(webContents)) {
case TestStatus.PASSED:
break;
case TestStatus.FAILED:
String resultString =
runJavaScriptOrFail("resultString", POLL_TIMEOUT_SHORT_MS, webContents);
Assert.fail("JavaScript testharness failed with reason: " + resultString);
break;
case TestStatus.RUNNING:
Assert.fail("Attempted to end test in Java without finishing in JavaScript.");
break;
default:
Assert.fail("Received unknown test status.");
}
}
/**
* Helper function to make sure that the JavaScript test harness did not detect any failures.
* Similar to endTest, but does not fail if the test is still detected as running. This is
* useful because not all tests make use of the test harness' test/assert features (particularly
* simple enter/exit tests), but may still want to ensure that no unexpected JavaScript errors
* were encountered.
*
* @param webContents The Webcontents for the tab to check for failures in.
*/
public static void assertNoJavaScriptErrors(WebContents webContents) {
if (checkTestStatus(webContents) == TestStatus.FAILED) {
String resultString =
runJavaScriptOrFail("resultString", POLL_TIMEOUT_SHORT_MS, webContents);
Assert.fail("JavaScript testharness failed with reason: " + resultString);
}
}
private static String runJavaScriptInFrameInternal(
String js, int timeout, final WebContents webContents, boolean failOnTimeout) {
RenderFrameHostTestExt rfh =
ThreadUtils.runOnUiThreadBlocking(
() ->
new RenderFrameHostTestExt(
WebContentsUtils.getFocusedFrame(webContents)));
Assert.assertTrue("Did not get a focused frame", rfh != null);
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<String> result = new AtomicReference<String>();
// The JS execution needs to be started on the UI thread to avoid hitting a DCHECK.
ThreadUtils.runOnUiThreadBlocking(
() -> {
rfh.executeJavaScript(
js,
(String r) -> {
result.set(r);
latch.countDown();
});
});
try {
if (!latch.await(timeout, TimeUnit.MILLISECONDS) && failOnTimeout) {
Assert.fail("Timed out running JavaScript in focused frame: " + js);
}
} catch (InterruptedException e) {
Assert.fail("Waiting for latch was interrupted: " + e.toString());
}
return result.get();
}
/**
* Must be constructed after the rule has been applied (e.g. in whatever method is tagged
* with @Before).
*/
public XrTestFramework(ChromeActivityTestRule rule) {
mRule = rule;
// WebXr requires HTTPS, so configure the server to by default use it.
mRule.getEmbeddedTestServerRule().setServerUsesHttps(true);
}
/**
* Gets the URL that loads the given test file from the embedded test server Note that because
* sessions may cause permissions prompts to appear, this uses the embedded server, as granting
* permissions to file:// URLs results in DCHECKs.
*
* @param testName The name of the test whose file will be retrieved.
*/
public String getUrlForFile(String testName) {
return mRule.getTestServer().getURL("/" + TEST_DIR + "/html/" + testName + ".html");
}
/**
* Loads the given file on an embedded server with the given timeout then waits for JavaScript
* to signal that it's ready for testing. Throws an assertion error if an improper page load is
* detected.
*
* @param file The name of the page to load.
* @param timeoutSec The timeout of the page load in seconds.
* @return The return value of ChromeActivityTestRule.loadUrl().
*/
public LoadUrlResult loadFileAndAwaitInitialization(String url, int timeoutSec) {
LoadUrlResult result = mRule.loadUrl(getUrlForFile(url), timeoutSec);
Assert.assertEquals(
"Page did not load correctly. Load result enum: "
+ String.valueOf(result.tabLoadStatus),
result.tabLoadStatus,
Tab.TabLoadStatus.DEFAULT_PAGE_LOAD);
if (!pollJavaScriptBoolean(
"isInitializationComplete()", POLL_TIMEOUT_LONG_MS, mRule.getWebContents())) {
Log.e(
TAG,
"Timed out waiting for JavaScript test initialization, attempting to get "
+ "additional debug information");
String initSteps =
runJavaScriptOrFail(
"initializationSteps", POLL_TIMEOUT_SHORT_MS, mRule.getWebContents());
Assert.fail(
"Timed out waiting for JavaScript test initialization. Initialization steps "
+ "object: "
+ initSteps);
}
// It is possible, particularly with multiple sessions and navigations within a single test,
// for the page to get zoomed in on navigation. So, ensure that we are always zoomed out
// enough to see all page content after we do a page load.
ThreadUtils.runOnUiThreadBlocking(() -> ZoomController.zoomReset(mRule.getWebContents()));
return result;
}
/**
* Helper method to run permissionRequestWouldTriggerPrompt with the current tab's WebContents.
*
* @param permission The name of the permission to check. Must come from PermissionName enum
* (see third_party/blink/renderer/modules/permissions/permission_descriptor.idl).
* @return True if the permission request would trigger a prompt, false otherwise.
*/
public boolean permissionRequestWouldTriggerPrompt(String permission) {
return permissionRequestWouldTriggerPrompt(permission, getCurrentWebContents());
}
/**
* Helper method to run runJavaScriptOrFail with the current tab's WebContents.
*
* @param js The JavaScript to run.
* @param timeout The timeout in milliseconds before a failure.
* @return The return value of the JavaScript.
*/
public String runJavaScriptOrFail(String js, int timeout) {
return runJavaScriptOrFail(js, timeout, getCurrentWebContents());
}
/**
* Helper method to run runJavaScriptInFrameOrFail with the current tab's WebContents.
*
* @param js The JavaScript to run.
* @param timeout The timeout in milliseconds before a failure.
* @return The return value of the JavaScript.
*/
public String runJavaScriptInFrameOrFail(String js, int timeout) {
return runJavaScriptInFrameOrFail(js, timeout, getCurrentWebContents());
}
/**
* Helper function to run pollJavaScriptBoolean with the current tab's WebContents.
*
* @param boolExpression The JavaScript boolean expression to poll.
* @param timeoutMs The polling timeout in milliseconds.
* @return True if the boolean evaluated to true, false if timed out.
*/
public boolean pollJavaScriptBoolean(String boolExpression, int timeoutMs) {
return pollJavaScriptBoolean(boolExpression, timeoutMs, getCurrentWebContents());
}
/**
* Helper function to run pollJavaScriptBooleanInFrame with the current tab's WebContents.
*
* @param boolExpression The JavaScript boolean expression to poll.
* @param timeoutMs The polling timeout in milliseconds.
* @return True if the boolean evaluated to true, false if timed out.
*/
public boolean pollJavaScriptInFrameBoolean(String boolExpression, int timeoutMs) {
return pollJavaScriptBooleanInFrame(boolExpression, timeoutMs, getCurrentWebContents());
}
/**
* Helper function to run pollJavaScriptBooleanOrFail with the current tab's WebContents.
*
* @param boolExpression The JavaScript boolean expression to poll.
* @param timeoutMs The polling timeout in milliseconds.
*/
public void pollJavaScriptBooleanOrFail(String boolExpression, int timeoutMs) {
pollJavaScriptBooleanOrFail(boolExpression, timeoutMs, getCurrentWebContents());
}
/**
* Helper function to run pollJavaScriptBooleanInFrameOrFail with the current tab's WebContents.
*
* @param boolExpression The JavaScript boolean expression to poll.
* @param timeoutMs The polling timeout in milliseconds.
*/
public void pollJavaScriptBooleanInFrameOrFail(String boolExpression, int timeoutMs) {
pollJavaScriptBooleanInFrameOrFail(boolExpression, timeoutMs, getCurrentWebContents());
}
/**
* Helper function to run executeStepAndWait using the current tab's WebContents.
*
* @param stepFunction The JavaScript step function to call.
*/
public void executeStepAndWait(String stepFunction) {
executeStepAndWait(stepFunction, POLL_TIMEOUT_LONG_MS);
}
/**
* Helper function to run executeStepAndWait using the current tab's WebContents.
*
* @param stepFunction The JavaScript step function to call.
* @param timeoutMs Timeout (in milliseconds) to wait for the JavaScript step.
*/
public void executeStepAndWait(String stepFunction, int timeoutMs) {
executeStepAndWait(stepFunction, getCurrentWebContents(), timeoutMs);
}
/** Helper function to run waitOnJavaScriptStep with current current tab's WebContents. */
public void waitOnJavaScriptStep() {
waitOnJavaScriptStep(getCurrentWebContents());
}
/**
* Helper method to run checkTestSTatus with the current tab's WebContents.
*
* @return A TestStatus integer corresponding to the current state of the JavaScript test.
*/
public @TestStatus int checkTestStatus() {
return checkTestStatus(getCurrentWebContents());
}
/** Helper function to run endTest with the current tab's WebContents. */
public void endTest() {
endTest(getCurrentWebContents());
}
/** Helper function to run assertNoJavaScriptErrors with the current tab's WebContents. */
public void assertNoJavaScriptErrors() {
assertNoJavaScriptErrors(getCurrentWebContents());
}
public View getCurrentContentView() {
return mRule.getActivity().getActivityTab().getContentView();
}
public WebContents getCurrentWebContents() {
return mRule.getWebContents();
}
public ChromeActivityTestRule getRule() {
return mRule;
}
public void simulateRendererKilled() {
final Tab tab = getRule().getActivity().getActivityTab();
ThreadUtils.runOnUiThreadBlocking(
() -> ChromeTabUtils.simulateRendererKilledForTesting(tab));
CriteriaHelper.pollUiThread(
() -> SadTab.isShowing(tab), "Renderer killed, but sad tab not shown");
}
public void openIncognitoTab(final String url) {
ThreadUtils.runOnUiThreadBlocking(
() -> {
mRule.getActivity()
.getTabCreator(/* incognito= */ true)
.launchUrl(url, TabLaunchType.FROM_LINK);
});
}
}