1 // Copyright (c) 2013 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.
5 package org.chromium.chrome.browser.test;
7 import android.app.AlertDialog;
8 import android.content.DialogInterface;
9 import android.test.suitebuilder.annotation.MediumTest;
10 import android.util.Log;
11 import android.view.View;
12 import android.widget.Button;
13 import android.widget.CheckBox;
14 import android.widget.EditText;
16 import org.chromium.base.ThreadUtils;
17 import org.chromium.base.test.util.DisabledTest;
18 import org.chromium.base.test.util.Feature;
19 import org.chromium.base.test.util.UrlUtils;
20 import org.chromium.chrome.browser.JavascriptAppModalDialog;
21 import org.chromium.chrome.testshell.ChromiumTestShellTestBase;
22 import org.chromium.chrome.testshell.TabShellTabUtils;
23 import org.chromium.chrome.testshell.TabShellTabUtils.TestCallbackHelperContainerForTab;
24 import org.chromium.content.browser.test.util.Criteria;
25 import org.chromium.content.browser.test.util.CriteriaHelper;
26 import org.chromium.content.browser.test.util.TestCallbackHelperContainer;
27 import org.chromium.content.browser.test.util.TestCallbackHelperContainer.OnEvaluateJavaScriptResultHelper;
29 import java.util.concurrent.Callable;
30 import java.util.concurrent.ExecutionException;
31 import java.util.concurrent.TimeoutException;
34 * Test suite for displaying and functioning of modal dialogs.
36 public class ModalDialogTest extends ChromiumTestShellTestBase {
37 private final static String TAG = "ModalDialogTest";
38 private final static String EMPTY_PAGE = UrlUtils.encodeHtmlDataUri(
39 "<html><title>Modal Dialog Test</title><p>Testcase.</p></title></html>");
40 private final static String BEFORE_UNLOAD_URL = UrlUtils.encodeHtmlDataUri(
42 "<head><script>window.onbeforeunload=function() {" +
43 "return 'Are you sure?';" +
44 "};</script></head></html>");
47 public void setUp() throws Exception {
49 launchChromiumTestShellWithUrl(EMPTY_PAGE);
50 assertTrue("Page failed to load", waitForActiveShellToBeDoneLoading());
54 * Verifies modal alert-dialog appearance and that JavaScript execution is
55 * able to continue after dismissal.
58 @Feature({"Browser", "Main"})
59 public void testAlertModalDialog()
60 throws InterruptedException, TimeoutException, ExecutionException {
61 final OnEvaluateJavaScriptResultHelper scriptEvent =
62 executeJavaScriptAndWaitForDialog("alert('Hello Android!');");
64 JavascriptAppModalDialog jsDialog = getCurrentDialog();
65 assertNotNull("No dialog showing.", jsDialog);
68 assertTrue("JavaScript execution should continue after closing prompt.",
69 scriptEvent.waitUntilHasValue());
73 * Verifies that clicking on a button twice doesn't crash.
76 @Feature({"Browser", "Main"})
77 public void testAlertModalDialogWithTwoClicks()
78 throws InterruptedException, TimeoutException, ExecutionException {
79 OnEvaluateJavaScriptResultHelper scriptEvent =
80 executeJavaScriptAndWaitForDialog("alert('Hello Android');");
81 JavascriptAppModalDialog jsDialog = getCurrentDialog();
82 assertNotNull("No dialog showing.", jsDialog);
87 assertTrue("JavaScript execution should continue after closing prompt.",
88 scriptEvent.waitUntilHasValue());
92 * Verifies that modal confirm-dialogs display, two buttons are visible and
93 * the return value of [Ok] equals true, [Cancel] equals false.
96 @Feature({"Browser", "Main"})
97 public void testConfirmModalDialog()
98 throws InterruptedException, TimeoutException, ExecutionException {
99 OnEvaluateJavaScriptResultHelper scriptEvent =
100 executeJavaScriptAndWaitForDialog("confirm('Android');");
102 JavascriptAppModalDialog jsDialog = getCurrentDialog();
103 assertNotNull("No dialog showing.", jsDialog);
105 Button[] buttons = getAlertDialogButtons(jsDialog.getDialogForTest());
106 assertNotNull("No cancel button in confirm dialog.", buttons[0]);
107 assertEquals("Cancel button is not visible.", View.VISIBLE, buttons[0].getVisibility());
108 if (buttons[1] != null) {
109 assertNotSame("Neutral button visible when it should not.",
110 View.VISIBLE, buttons[1].getVisibility());
112 assertNotNull("No OK button in confirm dialog.", buttons[2]);
113 assertEquals("OK button is not visible.", View.VISIBLE, buttons[2].getVisibility());
116 assertTrue("JavaScript execution should continue after closing dialog.",
117 scriptEvent.waitUntilHasValue());
119 String resultString = scriptEvent.getJsonResultAndClear();
120 assertEquals("Invalid return value.", "true", resultString);
122 // Try again, pressing cancel this time.
123 scriptEvent = executeJavaScriptAndWaitForDialog("confirm('Android');");
124 jsDialog = getCurrentDialog();
125 assertNotNull("No dialog showing.", jsDialog);
127 clickCancel(jsDialog);
128 assertTrue("JavaScript execution should continue after closing dialog.",
129 scriptEvent.waitUntilHasValue());
131 resultString = scriptEvent.getJsonResultAndClear();
132 assertEquals("Invalid return value.", "false", resultString);
136 * Verifies that modal prompt-dialogs display and the result is returned.
139 @Feature({"Browser", "Main"})
140 public void testPromptModalDialog()
141 throws InterruptedException, TimeoutException, ExecutionException {
142 final String promptText = "Hello Android!";
143 final OnEvaluateJavaScriptResultHelper scriptEvent =
144 executeJavaScriptAndWaitForDialog("prompt('Android', 'default');");
146 final JavascriptAppModalDialog jsDialog = getCurrentDialog();
147 assertNotNull("No dialog showing.", jsDialog);
149 // Set the text in the prompt field of the dialog.
150 boolean result = ThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
152 public Boolean call() {
153 EditText prompt = (EditText) jsDialog.getDialogForTest().findViewById(
154 org.chromium.chrome.R.id.js_modal_dialog_prompt);
155 if (prompt == null) return false;
156 prompt.setText(promptText);
160 assertTrue("Failed to find prompt view in prompt dialog.", result);
163 assertTrue("JavaScript execution should continue after closing prompt.",
164 scriptEvent.waitUntilHasValue());
166 String resultString = scriptEvent.getJsonResultAndClear();
167 assertEquals("Invalid return value.", '"' + promptText + '"', resultString);
171 * Verifies beforeunload dialogs are shown and they block/allow navigation
175 //@Feature({"Browser", "Main"})
176 @DisabledTest //crbug/270593
177 public void testBeforeUnloadDialog()
178 throws InterruptedException, TimeoutException, ExecutionException {
179 loadUrlWithSanitization(BEFORE_UNLOAD_URL);
180 executeJavaScriptAndWaitForDialog("history.back();");
182 JavascriptAppModalDialog jsDialog = getCurrentDialog();
183 assertNotNull("No dialog showing.", jsDialog);
184 checkButtonPresenceVisibilityText(
185 jsDialog, 0, org.chromium.chrome.R.string.stay_on_this_page,
186 "Stay on this page");
187 clickCancel(jsDialog);
189 assertEquals(BEFORE_UNLOAD_URL, getActivity().getActiveContentView().getUrl());
190 executeJavaScriptAndWaitForDialog("history.back();");
192 jsDialog = getCurrentDialog();
193 assertNotNull("No dialog showing.", jsDialog);
194 checkButtonPresenceVisibilityText(
195 jsDialog, 2, org.chromium.chrome.R.string.leave_this_page,
198 final TestCallbackHelperContainer.OnPageFinishedHelper onPageLoaded =
199 getActiveTabTestCallbackHelperContainer().getOnPageFinishedHelper();
200 int callCount = onPageLoaded.getCallCount();
202 onPageLoaded.waitForCallback(callCount);
203 assertEquals(EMPTY_PAGE, getActivity().getActiveContentView().getUrl());
207 * Verifies that when showing a beforeunload dialogs as a result of a page
208 * reload, the correct UI strings are used.
211 @Feature({"Browser", "Main"})
212 public void testBeforeUnloadOnReloadDialog()
213 throws InterruptedException, TimeoutException, ExecutionException {
214 loadUrlWithSanitization(BEFORE_UNLOAD_URL);
215 executeJavaScriptAndWaitForDialog("window.location.reload();");
217 JavascriptAppModalDialog jsDialog = getCurrentDialog();
218 assertNotNull("No dialog showing.", jsDialog);
220 checkButtonPresenceVisibilityText(
221 jsDialog, 0, org.chromium.chrome.R.string.dont_reload_this_page,
222 "Don't reload this page");
223 checkButtonPresenceVisibilityText(
224 jsDialog, 2, org.chromium.chrome.R.string.reload_this_page,
229 * Verifies that repeated dialogs give the option to disable dialogs
230 * altogether and then that disabling them works.
233 @Feature({"Browser", "Main"})
234 public void testDisableRepeatedDialogs()
235 throws InterruptedException, TimeoutException, ExecutionException {
236 OnEvaluateJavaScriptResultHelper scriptEvent =
237 executeJavaScriptAndWaitForDialog("alert('Android');");
239 // Show a dialog once.
240 JavascriptAppModalDialog jsDialog = getCurrentDialog();
241 assertNotNull("No dialog showing.", jsDialog);
243 clickCancel(jsDialog);
244 scriptEvent.waitUntilHasValue();
246 // Show it again, it should have the option to suppress subsequent dialogs.
247 scriptEvent = executeJavaScriptAndWaitForDialog("alert('Android');");
248 jsDialog = getCurrentDialog();
249 assertNotNull("No dialog showing.", jsDialog);
250 final AlertDialog dialog = jsDialog.getDialogForTest();
251 String errorMessage = ThreadUtils.runOnUiThreadBlocking(new Callable<String>() {
253 public String call() {
254 final CheckBox suppress = (CheckBox) dialog.findViewById(
255 org.chromium.chrome.R.id.suppress_js_modal_dialogs);
256 if (suppress == null) return "Suppress checkbox not found.";
257 if (suppress.getVisibility() != View.VISIBLE) {
258 return "Suppress checkbox is not visible.";
260 suppress.setChecked(true);
264 assertNull(errorMessage, errorMessage);
265 clickCancel(jsDialog);
266 scriptEvent.waitUntilHasValue();
268 scriptEvent.evaluateJavaScript(getActivity().getActiveContentView().getContentViewCore(),
269 "alert('Android');");
270 assertTrue("No further dialog boxes should be shown.", scriptEvent.waitUntilHasValue());
274 * Displays a dialog and closes the tab in the background before attempting
275 * to accept the dialog. Verifies that the dialog is dismissed when the tab
279 @Feature({"Browser", "Main"})
280 public void testDialogDismissedAfterClosingTab()
281 throws InterruptedException, TimeoutException, ExecutionException {
282 executeJavaScriptAndWaitForDialog("alert('Android')");
284 final TestCallbackHelperContainerForTab.OnCloseTabHelper onTabClosed =
285 getActiveTabTestCallbackHelperContainer().getOnCloseTabHelper();
286 int callCount = onTabClosed.getCallCount();
287 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
290 getActivity().createTab(EMPTY_PAGE);
293 onTabClosed.waitForCallback(callCount);
295 // Closing the tab should have dismissed the dialog.
296 boolean criteriaSatisfied = CriteriaHelper.pollForCriteria(
297 new JavascriptAppModalDialogShownCriteria(false));
298 assertTrue("The dialog should have been dismissed when its tab was closed.",
303 * Asynchronously executes the given code for spawning a dialog and waits
304 * for the dialog to be visible.
306 private OnEvaluateJavaScriptResultHelper executeJavaScriptAndWaitForDialog(String script)
307 throws InterruptedException {
308 final OnEvaluateJavaScriptResultHelper helper =
309 getActiveTabTestCallbackHelperContainer().getOnEvaluateJavaScriptResultHelper();
310 return executeJavaScriptAndWaitForDialog(helper, script);
314 * Given a JavaScript evaluation helper, asynchronously executes the given
315 * code for spawning a dialog and waits for the dialog to be visible.
317 private OnEvaluateJavaScriptResultHelper executeJavaScriptAndWaitForDialog(
318 final OnEvaluateJavaScriptResultHelper helper, String script)
319 throws InterruptedException {
320 helper.evaluateJavaScript(getActivity().getActiveContentView().getContentViewCore(),
322 boolean criteriaSatisfied = CriteriaHelper.pollForCriteria(
323 new JavascriptAppModalDialogShownCriteria(true));
324 assertTrue("Could not spawn or locate a modal dialog.", criteriaSatisfied);
329 * Returns an array of the 3 buttons for this dialog, in the order
330 * BUTTON_NEGATIVE, BUTTON_NEUTRAL and BUTTON_POSITIVE. Any of these values
333 private Button[] getAlertDialogButtons(final AlertDialog dialog) throws ExecutionException {
334 return ThreadUtils.runOnUiThreadBlocking(new Callable<Button[]>() {
336 public Button[] call() {
337 final Button[] buttons = new Button[3];
338 buttons[0] = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
339 buttons[1] = dialog.getButton(DialogInterface.BUTTON_NEUTRAL);
340 buttons[2] = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
347 * Returns the current JavaScript modal dialog showing or null if no such dialog is currently
350 private JavascriptAppModalDialog getCurrentDialog() throws ExecutionException {
351 return ThreadUtils.runOnUiThreadBlocking(new Callable<JavascriptAppModalDialog>() {
353 public JavascriptAppModalDialog call() {
354 return JavascriptAppModalDialog.getCurrentDialogForTest();
359 private static class JavascriptAppModalDialogShownCriteria implements Criteria {
360 private final boolean mShouldBeShown;
362 public JavascriptAppModalDialogShownCriteria(boolean shouldBeShown) {
363 mShouldBeShown = shouldBeShown;
367 public boolean isSatisfied() {
369 return ThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
371 public Boolean call() {
372 final boolean isShown =
373 JavascriptAppModalDialog.getCurrentDialogForTest() != null;
374 return mShouldBeShown == isShown;
377 } catch (ExecutionException e) {
378 Log.e(TAG, "Failed to getCurrentDialog", e);
385 * Simulates pressing the OK button of the passed dialog.
387 private void clickOk(JavascriptAppModalDialog dialog) {
388 clickButton(dialog, DialogInterface.BUTTON_POSITIVE);
392 * Simulates pressing the Cancel button of the passed dialog.
394 private void clickCancel(JavascriptAppModalDialog dialog) {
395 clickButton(dialog, DialogInterface.BUTTON_NEGATIVE);
398 private void clickButton(final JavascriptAppModalDialog dialog, final int whichButton) {
399 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
402 dialog.onClick(null, whichButton);
407 private void checkButtonPresenceVisibilityText(
408 JavascriptAppModalDialog jsDialog, int buttonIndex,
409 int expectedTextResourceId, String readableName) throws ExecutionException {
410 final Button[] buttons = getAlertDialogButtons(jsDialog.getDialogForTest());
411 final Button button = buttons[buttonIndex];
412 assertNotNull("No '" + readableName + "' button in confirm dialog.", button);
413 assertEquals("'" + readableName + "' button is not visible.",
415 button.getVisibility());
416 assertEquals("'" + readableName + "' button has wrong text",
417 getActivity().getResources().getString(expectedTextResourceId),
418 button.getText().toString());
421 private TestCallbackHelperContainerForTab getActiveTabTestCallbackHelperContainer() {
422 return TabShellTabUtils.getTestCallbackHelperContainer(getActivity().getActiveTab());