1 // Copyright 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.shell.ChromeShellTestBase;
22 import org.chromium.content.browser.test.util.Criteria;
23 import org.chromium.content.browser.test.util.CriteriaHelper;
24 import org.chromium.content.browser.test.util.TestCallbackHelperContainer;
25 import org.chromium.content.browser.test.util.TestCallbackHelperContainer.OnEvaluateJavaScriptResultHelper;
27 import java.util.concurrent.Callable;
28 import java.util.concurrent.ExecutionException;
29 import java.util.concurrent.TimeoutException;
32 * Test suite for displaying and functioning of modal dialogs.
34 public class ModalDialogTest extends ChromeShellTestBase {
35 private static final String TAG = "ModalDialogTest";
36 private static final String EMPTY_PAGE = UrlUtils.encodeHtmlDataUri(
37 "<html><title>Modal Dialog Test</title><p>Testcase.</p></title></html>");
38 private static final String BEFORE_UNLOAD_URL = UrlUtils.encodeHtmlDataUri(
40 "<head><script>window.onbeforeunload=function() {" +
41 "return 'Are you sure?';" +
42 "};</script></head></html>");
45 public void setUp() throws Exception {
47 launchChromeShellWithUrl(EMPTY_PAGE);
48 assertTrue("Page failed to load", waitForActiveShellToBeDoneLoading());
52 * Verifies modal alert-dialog appearance and that JavaScript execution is
53 * able to continue after dismissal.
56 @Feature({"Browser", "Main"})
57 public void testAlertModalDialog()
58 throws InterruptedException, TimeoutException, ExecutionException {
59 final OnEvaluateJavaScriptResultHelper scriptEvent =
60 executeJavaScriptAndWaitForDialog("alert('Hello Android!');");
62 JavascriptAppModalDialog jsDialog = getCurrentDialog();
63 assertNotNull("No dialog showing.", jsDialog);
66 assertTrue("JavaScript execution should continue after closing prompt.",
67 scriptEvent.waitUntilHasValue());
71 * Verifies that clicking on a button twice doesn't crash.
74 @Feature({"Browser", "Main"})
75 public void testAlertModalDialogWithTwoClicks()
76 throws InterruptedException, TimeoutException, ExecutionException {
77 OnEvaluateJavaScriptResultHelper scriptEvent =
78 executeJavaScriptAndWaitForDialog("alert('Hello Android');");
79 JavascriptAppModalDialog jsDialog = getCurrentDialog();
80 assertNotNull("No dialog showing.", jsDialog);
85 assertTrue("JavaScript execution should continue after closing prompt.",
86 scriptEvent.waitUntilHasValue());
90 * Verifies that modal confirm-dialogs display, two buttons are visible and
91 * the return value of [Ok] equals true, [Cancel] equals false.
94 @Feature({"Browser", "Main"})
95 public void testConfirmModalDialog()
96 throws InterruptedException, TimeoutException, ExecutionException {
97 OnEvaluateJavaScriptResultHelper scriptEvent =
98 executeJavaScriptAndWaitForDialog("confirm('Android');");
100 JavascriptAppModalDialog jsDialog = getCurrentDialog();
101 assertNotNull("No dialog showing.", jsDialog);
103 Button[] buttons = getAlertDialogButtons(jsDialog.getDialogForTest());
104 assertNotNull("No cancel button in confirm dialog.", buttons[0]);
105 assertEquals("Cancel button is not visible.", View.VISIBLE, buttons[0].getVisibility());
106 if (buttons[1] != null) {
107 assertNotSame("Neutral button visible when it should not.",
108 View.VISIBLE, buttons[1].getVisibility());
110 assertNotNull("No OK button in confirm dialog.", buttons[2]);
111 assertEquals("OK button is not visible.", View.VISIBLE, buttons[2].getVisibility());
114 assertTrue("JavaScript execution should continue after closing dialog.",
115 scriptEvent.waitUntilHasValue());
117 String resultString = scriptEvent.getJsonResultAndClear();
118 assertEquals("Invalid return value.", "true", resultString);
120 // Try again, pressing cancel this time.
121 scriptEvent = executeJavaScriptAndWaitForDialog("confirm('Android');");
122 jsDialog = getCurrentDialog();
123 assertNotNull("No dialog showing.", jsDialog);
125 clickCancel(jsDialog);
126 assertTrue("JavaScript execution should continue after closing dialog.",
127 scriptEvent.waitUntilHasValue());
129 resultString = scriptEvent.getJsonResultAndClear();
130 assertEquals("Invalid return value.", "false", resultString);
134 * Verifies that modal prompt-dialogs display and the result is returned.
137 @Feature({"Browser", "Main"})
138 public void testPromptModalDialog()
139 throws InterruptedException, TimeoutException, ExecutionException {
140 final String promptText = "Hello Android!";
141 final OnEvaluateJavaScriptResultHelper scriptEvent =
142 executeJavaScriptAndWaitForDialog("prompt('Android', 'default');");
144 final JavascriptAppModalDialog jsDialog = getCurrentDialog();
145 assertNotNull("No dialog showing.", jsDialog);
147 // Set the text in the prompt field of the dialog.
148 boolean result = ThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
150 public Boolean call() {
151 EditText prompt = (EditText) jsDialog.getDialogForTest().findViewById(
152 org.chromium.chrome.R.id.js_modal_dialog_prompt);
153 if (prompt == null) return false;
154 prompt.setText(promptText);
158 assertTrue("Failed to find prompt view in prompt dialog.", result);
161 assertTrue("JavaScript execution should continue after closing prompt.",
162 scriptEvent.waitUntilHasValue());
164 String resultString = scriptEvent.getJsonResultAndClear();
165 assertEquals("Invalid return value.", '"' + promptText + '"', resultString);
169 * Verifies beforeunload dialogs are shown and they block/allow navigation
173 //@Feature({"Browser", "Main"})
174 @DisabledTest //crbug/270593
175 public void testBeforeUnloadDialog()
176 throws InterruptedException, TimeoutException, ExecutionException {
177 loadUrlWithSanitization(BEFORE_UNLOAD_URL);
178 executeJavaScriptAndWaitForDialog("history.back();");
180 JavascriptAppModalDialog jsDialog = getCurrentDialog();
181 assertNotNull("No dialog showing.", jsDialog);
182 checkButtonPresenceVisibilityText(
183 jsDialog, 0, org.chromium.chrome.R.string.stay_on_this_page,
184 "Stay on this page");
185 clickCancel(jsDialog);
187 assertEquals(BEFORE_UNLOAD_URL, getActivity().getActiveContentViewCore().getUrl());
188 executeJavaScriptAndWaitForDialog("history.back();");
190 jsDialog = getCurrentDialog();
191 assertNotNull("No dialog showing.", jsDialog);
192 checkButtonPresenceVisibilityText(
193 jsDialog, 2, org.chromium.chrome.R.string.leave_this_page,
196 final TestCallbackHelperContainer.OnPageFinishedHelper onPageLoaded =
197 getActiveTabTestCallbackHelperContainer().getOnPageFinishedHelper();
198 int callCount = onPageLoaded.getCallCount();
200 onPageLoaded.waitForCallback(callCount);
201 assertEquals(EMPTY_PAGE, getActivity().getActiveContentViewCore().getUrl());
205 * Verifies that when showing a beforeunload dialogs as a result of a page
206 * reload, the correct UI strings are used.
209 @Feature({"Browser", "Main"})
210 public void testBeforeUnloadOnReloadDialog()
211 throws InterruptedException, TimeoutException, ExecutionException {
212 loadUrlWithSanitization(BEFORE_UNLOAD_URL);
213 executeJavaScriptAndWaitForDialog("window.location.reload();");
215 JavascriptAppModalDialog jsDialog = getCurrentDialog();
216 assertNotNull("No dialog showing.", jsDialog);
218 checkButtonPresenceVisibilityText(
219 jsDialog, 0, org.chromium.chrome.R.string.dont_reload_this_page,
220 "Don't reload this page");
221 checkButtonPresenceVisibilityText(
222 jsDialog, 2, org.chromium.chrome.R.string.reload_this_page,
227 * Verifies that repeated dialogs give the option to disable dialogs
228 * altogether and then that disabling them works.
231 @Feature({"Browser", "Main"})
232 public void testDisableRepeatedDialogs()
233 throws InterruptedException, TimeoutException, ExecutionException {
234 OnEvaluateJavaScriptResultHelper scriptEvent =
235 executeJavaScriptAndWaitForDialog("alert('Android');");
237 // Show a dialog once.
238 JavascriptAppModalDialog jsDialog = getCurrentDialog();
239 assertNotNull("No dialog showing.", jsDialog);
241 clickCancel(jsDialog);
242 scriptEvent.waitUntilHasValue();
244 // Show it again, it should have the option to suppress subsequent dialogs.
245 scriptEvent = executeJavaScriptAndWaitForDialog("alert('Android');");
246 jsDialog = getCurrentDialog();
247 assertNotNull("No dialog showing.", jsDialog);
248 final AlertDialog dialog = jsDialog.getDialogForTest();
249 String errorMessage = ThreadUtils.runOnUiThreadBlocking(new Callable<String>() {
251 public String call() {
252 final CheckBox suppress = (CheckBox) dialog.findViewById(
253 org.chromium.chrome.R.id.suppress_js_modal_dialogs);
254 if (suppress == null) return "Suppress checkbox not found.";
255 if (suppress.getVisibility() != View.VISIBLE) {
256 return "Suppress checkbox is not visible.";
258 suppress.setChecked(true);
262 assertNull(errorMessage, errorMessage);
263 clickCancel(jsDialog);
264 scriptEvent.waitUntilHasValue();
266 scriptEvent.evaluateJavaScript(getActivity().getActiveContentViewCore(),
267 "alert('Android');");
268 assertTrue("No further dialog boxes should be shown.", scriptEvent.waitUntilHasValue());
272 * Displays a dialog and closes the tab in the background before attempting
273 * to accept the dialog. Verifies that the dialog is dismissed when the tab
277 @Feature({"Browser", "Main"})
278 public void testDialogDismissedAfterClosingTab() throws InterruptedException {
279 executeJavaScriptAndWaitForDialog("alert('Android')");
281 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
284 getActivity().createTab(EMPTY_PAGE);
288 // Closing the tab should have dismissed the dialog.
289 boolean criteriaSatisfied = CriteriaHelper.pollForCriteria(
290 new JavascriptAppModalDialogShownCriteria(false));
291 assertTrue("The dialog should have been dismissed when its tab was closed.",
296 * Asynchronously executes the given code for spawning a dialog and waits
297 * for the dialog to be visible.
299 private OnEvaluateJavaScriptResultHelper executeJavaScriptAndWaitForDialog(String script)
300 throws InterruptedException {
301 return executeJavaScriptAndWaitForDialog(new OnEvaluateJavaScriptResultHelper(), script);
305 * Given a JavaScript evaluation helper, asynchronously executes the given
306 * code for spawning a dialog and waits for the dialog to be visible.
308 private OnEvaluateJavaScriptResultHelper executeJavaScriptAndWaitForDialog(
309 final OnEvaluateJavaScriptResultHelper helper, String script)
310 throws InterruptedException {
311 helper.evaluateJavaScript(getActivity().getActiveContentViewCore(),
313 boolean criteriaSatisfied = CriteriaHelper.pollForCriteria(
314 new JavascriptAppModalDialogShownCriteria(true));
315 assertTrue("Could not spawn or locate a modal dialog.", criteriaSatisfied);
320 * Returns an array of the 3 buttons for this dialog, in the order
321 * BUTTON_NEGATIVE, BUTTON_NEUTRAL and BUTTON_POSITIVE. Any of these values
324 private Button[] getAlertDialogButtons(final AlertDialog dialog) throws ExecutionException {
325 return ThreadUtils.runOnUiThreadBlocking(new Callable<Button[]>() {
327 public Button[] call() {
328 final Button[] buttons = new Button[3];
329 buttons[0] = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
330 buttons[1] = dialog.getButton(DialogInterface.BUTTON_NEUTRAL);
331 buttons[2] = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
338 * Returns the current JavaScript modal dialog showing or null if no such dialog is currently
341 private JavascriptAppModalDialog getCurrentDialog() throws ExecutionException {
342 return ThreadUtils.runOnUiThreadBlocking(new Callable<JavascriptAppModalDialog>() {
344 public JavascriptAppModalDialog call() {
345 return JavascriptAppModalDialog.getCurrentDialogForTest();
350 private static class JavascriptAppModalDialogShownCriteria implements Criteria {
351 private final boolean mShouldBeShown;
353 public JavascriptAppModalDialogShownCriteria(boolean shouldBeShown) {
354 mShouldBeShown = shouldBeShown;
358 public boolean isSatisfied() {
360 return ThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
362 public Boolean call() {
363 final boolean isShown =
364 JavascriptAppModalDialog.getCurrentDialogForTest() != null;
365 return mShouldBeShown == isShown;
368 } catch (ExecutionException e) {
369 Log.e(TAG, "Failed to getCurrentDialog", e);
376 * Simulates pressing the OK button of the passed dialog.
378 private void clickOk(JavascriptAppModalDialog dialog) {
379 clickButton(dialog, DialogInterface.BUTTON_POSITIVE);
383 * Simulates pressing the Cancel button of the passed dialog.
385 private void clickCancel(JavascriptAppModalDialog dialog) {
386 clickButton(dialog, DialogInterface.BUTTON_NEGATIVE);
389 private void clickButton(final JavascriptAppModalDialog dialog, final int whichButton) {
390 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
393 dialog.onClick(null, whichButton);
398 private void checkButtonPresenceVisibilityText(
399 JavascriptAppModalDialog jsDialog, int buttonIndex,
400 int expectedTextResourceId, String readableName) throws ExecutionException {
401 final Button[] buttons = getAlertDialogButtons(jsDialog.getDialogForTest());
402 final Button button = buttons[buttonIndex];
403 assertNotNull("No '" + readableName + "' button in confirm dialog.", button);
404 assertEquals("'" + readableName + "' button is not visible.",
406 button.getVisibility());
407 assertEquals("'" + readableName + "' button has wrong text",
408 getActivity().getResources().getString(expectedTextResourceId),
409 button.getText().toString());
412 private TestCallbackHelperContainer getActiveTabTestCallbackHelperContainer() {
413 return new TestCallbackHelperContainer(getActivity().getActiveTab().getContentViewCore());