root/chrome/android/javatests/src/org/chromium/chrome/browser/test/ModalDialogTest.java

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. setUp
  2. Feature
  3. testAlertModalDialog
  4. Feature
  5. testAlertModalDialogWithTwoClicks
  6. Feature
  7. testConfirmModalDialog
  8. Feature
  9. testPromptModalDialog
  10. testBeforeUnloadDialog
  11. Feature
  12. testBeforeUnloadOnReloadDialog
  13. Feature
  14. testDisableRepeatedDialogs
  15. Feature
  16. testDialogDismissedAfterClosingTab
  17. executeJavaScriptAndWaitForDialog
  18. executeJavaScriptAndWaitForDialog
  19. getAlertDialogButtons
  20. getCurrentDialog
  21. isSatisfied
  22. clickOk
  23. clickCancel
  24. clickButton
  25. checkButtonPresenceVisibilityText
  26. getActiveTabTestCallbackHelperContainer

// Copyright 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.DisabledTest;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.chrome.browser.JavascriptAppModalDialog;
import org.chromium.chrome.shell.ChromeShellTestBase;
import org.chromium.chrome.test.util.TabUtils;
import org.chromium.chrome.test.util.TabUtils.TestCallbackHelperContainerForTab;
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.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 ChromeShellTestBase {
    private static final String TAG = "ModalDialogTest";
    private static final String EMPTY_PAGE = UrlUtils.encodeHtmlDataUri(
            "<html><title>Modal Dialog Test</title><p>Testcase.</p></title></html>");
    private static final String BEFORE_UNLOAD_URL = UrlUtils.encodeHtmlDataUri(
            "<html>" +
            "<head><script>window.onbeforeunload=function() {" +
            "return 'Are you sure?';" +
            "};</script></head></html>");

    @Override
    public void setUp() throws Exception {
        super.setUp();
        launchChromeShellWithUrl(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"})
    @DisabledTest //crbug/270593
    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.chrome.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.chrome.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.chrome.R.string.dont_reload_this_page,
                "Don't reload this page");
        checkButtonPresenceVisibilityText(
                jsDialog, 2, org.chromium.chrome.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 TabUtils.getTestCallbackHelperContainer(getActivity().getActiveTab());
    }
}

/* [<][>][^][v][top][bottom][index][help] */