Index: chrome/android/javatests/src/org/chromium/chrome/browser/test/ModalDialogTest.java |
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/test/ModalDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/test/ModalDialogTest.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..0a29bd7bde14851d78f895ac7e05a87f1e1606d6 |
--- /dev/null |
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/test/ModalDialogTest.java |
@@ -0,0 +1,427 @@ |
+// Copyright (c) 2013 The Chromium Authors. All rights reserved. |
+// 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.test; |
+ |
+import android.app.AlertDialog; |
+import android.content.DialogInterface; |
+import android.test.suitebuilder.annotation.MediumTest; |
+import android.util.Log; |
+import android.view.View; |
+import android.widget.Button; |
+import android.widget.CheckBox; |
+import android.widget.EditText; |
+ |
+import org.chromium.base.ThreadUtils; |
+import org.chromium.base.test.util.Feature; |
+import org.chromium.chrome.browser.JavascriptAppModalDialog; |
+import org.chromium.chrome.browser.TabBase; |
+import org.chromium.chrome.browser.test.util.TabBaseUtils; |
+import org.chromium.chrome.browser.test.util.TabBaseUtils.TestCallbackHelperContainerForTab; |
+import org.chromium.chrome.testshell.ChromiumTestShellTestBase; |
+import org.chromium.content.browser.ContentViewClient; |
+import org.chromium.content.browser.test.util.Criteria; |
+import org.chromium.content.browser.test.util.CriteriaHelper; |
+import org.chromium.content.browser.test.util.TestCallbackHelperContainer; |
+import org.chromium.content.browser.test.util.TestContentViewClient; |
+import org.chromium.content.browser.test.util.TestContentViewClientWrapper; |
+import org.chromium.content.browser.test.util.TestWebContentsObserver; |
+import org.chromium.content.browser.test.util.TestCallbackHelperContainer.OnEvaluateJavaScriptResultHelper; |
+ |
+import java.util.concurrent.Callable; |
+import java.util.concurrent.ExecutionException; |
+import java.util.concurrent.TimeoutException; |
+ |
+/** |
+ * Test suite for displaying and functioning of modal dialogs. |
+ */ |
+public class ModalDialogTest extends ChromiumTestShellTestBase { |
+ private final static String TAG = ModalDialogTest.class.getSimpleName(); |
+ private final static String EMPTY_PAGE = |
+ "data:text/html;utf-8,<!doctype html>" + |
+ "<title>Modal Dialog Test</title><p>Testcase.</p>"; |
+ private final static String BEFORE_UNLOAD_URL = |
+ "data:text/html;utf-8,<!doctype html>" + |
+ "<head><script>window.onbeforeunload=function() {" + |
+ "return 'Are you sure?';" + |
+ "};</script></head></body>"; |
+ |
+ @Override |
+ public void setUp() throws Exception { |
+ super.setUp(); |
+ launchChromiumTestShellWithUrl(EMPTY_PAGE); |
+ assertTrue("Page failed to load", waitForActiveShellToBeDoneLoading()); |
+ } |
+ |
+ /** |
+ * Verifies modal alert-dialog appearance and that JavaScript execution is |
+ * able to continue after dismissal. |
+ */ |
+ @MediumTest |
+ @Feature({"Browser", "Main"}) |
+ public void testAlertModalDialog() |
+ throws InterruptedException, TimeoutException, ExecutionException { |
+ final OnEvaluateJavaScriptResultHelper scriptEvent = |
+ executeJavaScriptAndWaitForDialog("alert('Hello Android!');"); |
+ |
+ JavascriptAppModalDialog jsDialog = getCurrentDialog(); |
+ assertNotNull("No dialog showing.", jsDialog); |
+ |
+ clickOk(jsDialog); |
+ assertTrue("JavaScript execution should continue after closing prompt.", |
+ scriptEvent.waitUntilHasValue()); |
+ } |
+ |
+ /** |
+ * Verifies that clicking on a button twice doesn't crash. |
+ */ |
+ @MediumTest |
+ @Feature({"Browser", "Main"}) |
+ public void testAlertModalDialogWithTwoClicks() |
+ throws InterruptedException, TimeoutException, ExecutionException { |
+ OnEvaluateJavaScriptResultHelper scriptEvent = |
+ executeJavaScriptAndWaitForDialog("alert('Hello Android');"); |
+ JavascriptAppModalDialog jsDialog = getCurrentDialog(); |
+ assertNotNull("No dialog showing.", jsDialog); |
+ |
+ clickOk(jsDialog); |
+ clickOk(jsDialog); |
+ |
+ assertTrue("JavaScript execution should continue after closing prompt.", |
+ scriptEvent.waitUntilHasValue()); |
+ } |
+ |
+ /** |
+ * Verifies that modal confirm-dialogs display, two buttons are visible and |
+ * the return value of [Ok] equals true, [Cancel] equals false. |
+ */ |
+ @MediumTest |
+ @Feature({"Browser", "Main"}) |
+ public void testConfirmModalDialog() |
+ throws InterruptedException, TimeoutException, ExecutionException { |
+ OnEvaluateJavaScriptResultHelper scriptEvent = |
+ executeJavaScriptAndWaitForDialog("confirm('Android');"); |
+ |
+ JavascriptAppModalDialog jsDialog = getCurrentDialog(); |
+ assertNotNull("No dialog showing.", jsDialog); |
+ |
+ Button[] buttons = getAlertDialogButtons(jsDialog.getDialogForTest()); |
+ assertNotNull("No cancel button in confirm dialog.", buttons[0]); |
+ assertEquals("Cancel button is not visible.", View.VISIBLE, buttons[0].getVisibility()); |
+ if (buttons[1] != null) { |
+ assertNotSame("Neutral button visible when it should not.", |
+ View.VISIBLE, buttons[1].getVisibility()); |
+ } |
+ assertNotNull("No OK button in confirm dialog.", buttons[2]); |
+ assertEquals("OK button is not visible.", View.VISIBLE, buttons[2].getVisibility()); |
+ |
+ clickOk(jsDialog); |
+ assertTrue("JavaScript execution should continue after closing dialog.", |
+ scriptEvent.waitUntilHasValue()); |
+ |
+ String resultString = scriptEvent.getJsonResultAndClear(); |
+ assertEquals("Invalid return value.", "true", resultString); |
+ |
+ // Try again, pressing cancel this time. |
+ scriptEvent = executeJavaScriptAndWaitForDialog("confirm('Android');"); |
+ jsDialog = getCurrentDialog(); |
+ assertNotNull("No dialog showing.", jsDialog); |
+ |
+ clickCancel(jsDialog); |
+ assertTrue("JavaScript execution should continue after closing dialog.", |
+ scriptEvent.waitUntilHasValue()); |
+ |
+ resultString = scriptEvent.getJsonResultAndClear(); |
+ assertEquals("Invalid return value.", "false", resultString); |
+ } |
+ |
+ /** |
+ * Verifies that modal prompt-dialogs display and the result is returned. |
+ */ |
+ @MediumTest |
+ @Feature({"Browser", "Main"}) |
+ public void testPromptModalDialog() |
+ throws InterruptedException, TimeoutException, ExecutionException { |
+ final String promptText = "Hello Android!"; |
+ final OnEvaluateJavaScriptResultHelper scriptEvent = |
+ executeJavaScriptAndWaitForDialog("prompt('Android', 'default');"); |
+ |
+ final JavascriptAppModalDialog jsDialog = getCurrentDialog(); |
+ assertNotNull("No dialog showing.", jsDialog); |
+ |
+ // Set the text in the prompt field of the dialog. |
+ boolean result = ThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() { |
+ @Override |
+ public Boolean call() { |
+ EditText prompt = (EditText) jsDialog.getDialogForTest().findViewById( |
+ org.chromium.chrome.R.id.js_modal_dialog_prompt); |
+ if (prompt == null) return false; |
+ prompt.setText(promptText); |
+ return true; |
+ } |
+ }); |
+ assertTrue("Failed to find prompt view in prompt dialog.", result); |
+ |
+ clickOk(jsDialog); |
+ assertTrue("JavaScript execution should continue after closing prompt.", |
+ scriptEvent.waitUntilHasValue()); |
+ |
+ String resultString = scriptEvent.getJsonResultAndClear(); |
+ assertEquals("Invalid return value.", '"' + promptText + '"', resultString); |
+ } |
+ |
+ /** |
+ * Verifies beforeunload dialogs are shown and they block/allow navigation |
+ * as appropriate. |
+ */ |
+ @MediumTest |
+ @Feature({"Browser", "Main"}) |
+ public void testBeforeUnloadDialog() |
+ throws InterruptedException, TimeoutException, ExecutionException { |
+ loadUrlWithSanitization(BEFORE_UNLOAD_URL); |
+ executeJavaScriptAndWaitForDialog("history.back();"); |
+ |
+ JavascriptAppModalDialog jsDialog = getCurrentDialog(); |
+ assertNotNull("No dialog showing.", jsDialog); |
+ checkButtonPresenceVisibilityText( |
+ jsDialog, 0, org.chromium.content.R.string.stay_on_this_page, |
+ "Stay on this page"); |
+ clickCancel(jsDialog); |
+ |
+ assertEquals(BEFORE_UNLOAD_URL, getActivity().getActiveContentView().getUrl()); |
+ executeJavaScriptAndWaitForDialog("history.back();"); |
+ |
+ jsDialog = getCurrentDialog(); |
+ assertNotNull("No dialog showing.", jsDialog); |
+ checkButtonPresenceVisibilityText( |
+ jsDialog, 2, org.chromium.content.R.string.leave_this_page, |
+ "Leave this page"); |
+ |
+ final TestCallbackHelperContainer.OnPageFinishedHelper onPageLoaded = |
+ getActiveTabTestCallbackHelperContainer().getOnPageFinishedHelper(); |
+ int callCount = onPageLoaded.getCallCount(); |
+ clickOk(jsDialog); |
+ onPageLoaded.waitForCallback(callCount); |
+ assertEquals(EMPTY_PAGE, getActivity().getActiveContentView().getUrl()); |
+ } |
+ |
+ /** |
+ * Verifies that when showing a beforeunload dialogs as a result of a page |
+ * reload, the correct UI strings are used. |
+ */ |
+ @MediumTest |
+ @Feature({"Browser", "Main"}) |
+ public void testBeforeUnloadOnReloadDialog() |
+ throws InterruptedException, TimeoutException, ExecutionException { |
+ loadUrlWithSanitization(BEFORE_UNLOAD_URL); |
+ executeJavaScriptAndWaitForDialog("window.location.reload();"); |
+ |
+ JavascriptAppModalDialog jsDialog = getCurrentDialog(); |
+ assertNotNull("No dialog showing.", jsDialog); |
+ |
+ checkButtonPresenceVisibilityText( |
+ jsDialog, 0, org.chromium.content.R.string.dont_reload_this_page, |
+ "Don't reload this page"); |
+ checkButtonPresenceVisibilityText( |
+ jsDialog, 2, org.chromium.content.R.string.reload_this_page, |
+ "Reload this page"); |
+ } |
+ |
+ /** |
+ * Verifies that repeated dialogs give the option to disable dialogs |
+ * altogether and then that disabling them works. |
+ */ |
+ @MediumTest |
+ @Feature({"Browser", "Main"}) |
+ public void testDisableRepeatedDialogs() |
+ throws InterruptedException, TimeoutException, ExecutionException { |
+ OnEvaluateJavaScriptResultHelper scriptEvent = |
+ executeJavaScriptAndWaitForDialog("alert('Android');"); |
+ |
+ // Show a dialog once. |
+ JavascriptAppModalDialog jsDialog = getCurrentDialog(); |
+ assertNotNull("No dialog showing.", jsDialog); |
+ |
+ clickCancel(jsDialog); |
+ scriptEvent.waitUntilHasValue(); |
+ |
+ // Show it again, it should have the option to suppress subsequent dialogs. |
+ scriptEvent = executeJavaScriptAndWaitForDialog("alert('Android');"); |
+ jsDialog = getCurrentDialog(); |
+ assertNotNull("No dialog showing.", jsDialog); |
+ final AlertDialog dialog = jsDialog.getDialogForTest(); |
+ String errorMessage = ThreadUtils.runOnUiThreadBlocking(new Callable<String>() { |
+ @Override |
+ public String call() { |
+ final CheckBox suppress = (CheckBox) dialog.findViewById( |
+ org.chromium.chrome.R.id.suppress_js_modal_dialogs); |
+ if (suppress == null) return "Suppress checkbox not found."; |
+ if (suppress.getVisibility() != View.VISIBLE) { |
+ return "Suppress checkbox is not visible."; |
+ } |
+ suppress.setChecked(true); |
+ return null; |
+ } |
+ }); |
+ assertNull(errorMessage, errorMessage); |
+ clickCancel(jsDialog); |
+ scriptEvent.waitUntilHasValue(); |
+ |
+ scriptEvent.evaluateJavaScript(getActivity().getActiveContentView().getContentViewCore(), |
+ "alert('Android');"); |
+ assertTrue("No further dialog boxes should be shown.", scriptEvent.waitUntilHasValue()); |
+ } |
+ |
+ /** |
+ * Displays a dialog and closes the tab in the background before attempting |
+ * to accept the dialog. Verifies that the dialog is dismissed when the tab |
+ * is closed. |
+ */ |
+ @MediumTest |
+ @Feature({"Browser", "Main"}) |
+ public void testDialogDismissedAfterClosingTab() |
+ throws InterruptedException, TimeoutException, ExecutionException { |
+ executeJavaScriptAndWaitForDialog("alert('Android')"); |
+ |
+ final TestCallbackHelperContainerForTab.OnCloseTabHelper onTabClosed = |
+ getActiveTabTestCallbackHelperContainer().getOnCloseTabHelper(); |
+ int callCount = onTabClosed.getCallCount(); |
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
+ @Override |
+ public void run() { |
+ getActivity().createTab(EMPTY_PAGE); |
+ } |
+ }); |
+ onTabClosed.waitForCallback(callCount); |
+ |
+ // Closing the tab should have dismissed the dialog. |
+ boolean criteriaSatisfied = CriteriaHelper.pollForCriteria( |
+ new JavascriptAppModalDialogShownCriteria(false)); |
+ assertTrue("The dialog should have been dismissed when its tab was closed.", |
+ criteriaSatisfied); |
+ } |
+ |
+ /** |
+ * Asynchronously executes the given code for spawning a dialog and waits |
+ * for the dialog to be visible. |
+ */ |
+ private OnEvaluateJavaScriptResultHelper executeJavaScriptAndWaitForDialog(String script) |
+ throws InterruptedException { |
+ final OnEvaluateJavaScriptResultHelper helper = |
+ getActiveTabTestCallbackHelperContainer().getOnEvaluateJavaScriptResultHelper(); |
+ return executeJavaScriptAndWaitForDialog(helper, script); |
+ } |
+ |
+ /** |
+ * Given a JavaScript evaluation helper, asynchronously executes the given |
+ * code for spawning a dialog and waits for the dialog to be visible. |
+ */ |
+ private OnEvaluateJavaScriptResultHelper executeJavaScriptAndWaitForDialog( |
+ final OnEvaluateJavaScriptResultHelper helper, String script) |
+ throws InterruptedException { |
+ helper.evaluateJavaScript(getActivity().getActiveContentView().getContentViewCore(), |
+ script); |
+ boolean criteriaSatisfied = CriteriaHelper.pollForCriteria( |
+ new JavascriptAppModalDialogShownCriteria(true)); |
+ assertTrue("Could not spawn or locate a modal dialog.", criteriaSatisfied); |
+ return helper; |
+ } |
+ |
+ /** |
+ * Returns an array of the 3 buttons for this dialog, in the order |
+ * BUTTON_NEGATIVE, BUTTON_NEUTRAL and BUTTON_POSITIVE. Any of these values |
+ * can be null. |
+ */ |
+ private Button[] getAlertDialogButtons(final AlertDialog dialog) throws ExecutionException { |
+ return ThreadUtils.runOnUiThreadBlocking(new Callable<Button[]>() { |
+ @Override |
+ public Button[] call() { |
+ final Button[] buttons = new Button[3]; |
+ buttons[0] = dialog.getButton(DialogInterface.BUTTON_NEGATIVE); |
+ buttons[1] = dialog.getButton(DialogInterface.BUTTON_NEUTRAL); |
+ buttons[2] = dialog.getButton(DialogInterface.BUTTON_POSITIVE); |
+ return buttons; |
+ } |
+ }); |
+ } |
+ |
+ /** |
+ * Returns the current JavaScript modal dialog showing or null if no such dialog is currently |
+ * showing. |
+ */ |
+ private JavascriptAppModalDialog getCurrentDialog() throws ExecutionException { |
+ return ThreadUtils.runOnUiThreadBlocking(new Callable<JavascriptAppModalDialog>() { |
+ @Override |
+ public JavascriptAppModalDialog call() { |
+ return JavascriptAppModalDialog.getCurrentDialogForTest(); |
+ } |
+ }); |
+ } |
+ |
+ private static class JavascriptAppModalDialogShownCriteria implements Criteria { |
+ private final boolean mShouldBeShown; |
+ |
+ public JavascriptAppModalDialogShownCriteria(boolean shouldBeShown) { |
+ mShouldBeShown = shouldBeShown; |
+ } |
+ |
+ @Override |
+ public boolean isSatisfied() { |
+ try { |
+ return ThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() { |
+ @Override |
+ public Boolean call() { |
+ final boolean isShown = |
+ JavascriptAppModalDialog.getCurrentDialogForTest() != null; |
+ return mShouldBeShown == isShown; |
+ } |
+ }); |
+ } catch (ExecutionException e) { |
+ Log.e(TAG, "Failed to getCurrentDialog", e); |
+ return false; |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * Simulates pressing the OK button of the passed dialog. |
+ */ |
+ private void clickOk(JavascriptAppModalDialog dialog) { |
+ clickButton(dialog, DialogInterface.BUTTON_POSITIVE); |
+ } |
+ |
+ /** |
+ * Simulates pressing the Cancel button of the passed dialog. |
+ */ |
+ private void clickCancel(JavascriptAppModalDialog dialog) { |
+ clickButton(dialog, DialogInterface.BUTTON_NEGATIVE); |
+ } |
+ |
+ private void clickButton(final JavascriptAppModalDialog dialog, final int whichButton) { |
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
+ @Override |
+ public void run() { |
+ dialog.onClick(null, whichButton); |
+ } |
+ }); |
+ } |
+ |
+ private void checkButtonPresenceVisibilityText( |
+ JavascriptAppModalDialog jsDialog, int buttonIndex, |
+ int expectedTextResourceId, String readableName) throws ExecutionException { |
+ final Button[] buttons = getAlertDialogButtons(jsDialog.getDialogForTest()); |
+ final Button button = buttons[buttonIndex]; |
+ assertNotNull("No '" + readableName + "' button in confirm dialog.", button); |
+ assertEquals("'" + readableName + "' button is not visible.", |
+ View.VISIBLE, |
+ button.getVisibility()); |
+ assertEquals("'" + readableName + "' button has wrong text", |
+ getActivity().getResources().getString(expectedTextResourceId), |
+ button.getText().toString()); |
+ } |
+ |
+ private TestCallbackHelperContainerForTab getActiveTabTestCallbackHelperContainer() { |
+ return TabBaseUtils.getTestCallbackHelperContainer(getActivity().getActiveTab()); |
+ } |
+} |