blob: 0be5b1267ef61b117d479c597c26c930f2c0f33c [file] [log] [blame]
// Copyright 2016 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.
#import <EarlGrey/EarlGrey.h>
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import "base/strings/sys_string_conversions.h"
#include "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/ui/dialogs/dialog_presenter.h"
#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_controller.h"
#include "ios/chrome/browser/ui/ui_util.h"
#include "ios/chrome/grit/ios_strings.h"
#include "ios/chrome/test/app/chrome_test_util.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
#import "ios/chrome/test/earl_grey/chrome_matchers.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h"
#import "ios/testing/earl_grey/disabled_test_macros.h"
#import "ios/testing/wait_util.h"
#import "ios/web/public/test/http_server.h"
#import "ios/web/public/test/http_server_util.h"
#include "ios/web/public/web_state/web_state.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/l10n_util_mac.h"
#import "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Enum specifying different types of JavaScript alerts:
// - JavaScriptAlertType::ALERT - Dialog with only one OK button.
// - JavaScriptAlertType::CONFIRMATION - Dialog with OK and Cancel button.
// - JavaScriptAlertType::PROMPT - Dialog with OK button, cancel button, and
// a text field.
enum class JavaScriptAlertType : NSUInteger { ALERT, CONFIRMATION, PROMPT };
// Script to inject that will show an alert. The document's body will be reset
// to |kAlertResultBody| after the dialog is dismissed.
const char kAlertMessage[] = "This is a JavaScript alert.";
const char kAlertResultBody[] = "JAVASCRIPT ALERT WAS DISMISSED";
const char kJavaScriptAlertTestScriptFormat[] =
"(function(){ "
" alert(\"%@\");"
" document.body.innerHTML = \"%@\";"
"})();";
NSString* GetJavaScriptAlertTestScript() {
return [NSString stringWithFormat:@(kJavaScriptAlertTestScriptFormat),
@(kAlertMessage), @(kAlertResultBody)];
}
// Script to inject that will show a confirmation dialog. The document's body
// will be reset to |kConfirmationResultBodyOK| or
// |kConfirmationResultBodyCancelled| depending on whether the OK or Cancel
// button was tapped.
const char kConfirmationMessage[] = "This is a JavaScript confirmation.";
const char kConfirmationResultBodyOK[] = "Okay";
const char kConfirmationResultBodyCancelled[] = "Cancelled";
const char kJavaScriptConfirmationScriptFormat[] =
"(function(){ "
" if (confirm(\"%@\") == true) {"
" document.body.innerHTML = \"%@\";"
" } else {"
" document.body.innerHTML = \"%@\";"
" }"
"})();";
NSString* GetJavaScriptConfirmationTestScript() {
return [NSString stringWithFormat:@(kJavaScriptConfirmationScriptFormat),
@(kConfirmationMessage),
@(kConfirmationResultBodyOK),
@(kConfirmationResultBodyCancelled)];
}
// Script to inject that will show a prompt dialog. The document's body will be
// reset to |kPromptResultBodyCancelled| or |kPromptTestUserInput| depending on
// whether the OK or Cancel button was tapped.
const char kPromptMessage[] = "This is a JavaScript prompt.";
const char kPromptResultBodyCancelled[] = "Cancelled";
const char kPromptTestUserInput[] = "test";
const char kJavaScriptPromptTestScriptFormat[] =
"(function(){ "
" var input = prompt(\"%@\");"
" if (input != null) {"
" document.body.innerHTML = input;"
" } else {"
" document.body.innerHTML = \"%@\";"
" }"
"})();";
NSString* GetJavaScriptPromptTestScript() {
return [NSString stringWithFormat:@(kJavaScriptPromptTestScriptFormat),
@(kPromptMessage),
@(kPromptResultBodyCancelled)];
}
// Script to inject that will show a JavaScript alert in a loop 20 times, then
// reset the document's HTML to |kAlertLoopFinishedText|.
const char kAlertLoopFinishedText[] = "Loop Finished";
const char kJavaScriptAlertLoopScriptFormat[] =
"(function(){ "
" for (i = 0; i < 20; ++i) {"
" alert(\"ALERT TEXT\");"
" }"
" document.body.innerHTML = \"%@\";"
"})();";
NSString* GetJavaScriptAlertLoopScript() {
return [NSString stringWithFormat:@(kJavaScriptAlertLoopScriptFormat),
@(kAlertLoopFinishedText)];
}
// Returns the message for a JavaScript alert with |type|.
NSString* GetMessageForAlertWithType(JavaScriptAlertType type) {
switch (type) {
case JavaScriptAlertType::ALERT:
return @(kAlertMessage);
case JavaScriptAlertType::CONFIRMATION:
return @(kConfirmationMessage);
case JavaScriptAlertType::PROMPT:
return @(kPromptMessage);
}
GREYFail(@"JavascriptAlertType not recognized.");
return nil;
}
// Returns the script to show a JavaScript alert with |type|.
NSString* GetScriptForAlertWithType(JavaScriptAlertType type) {
switch (type) {
case JavaScriptAlertType::ALERT:
return GetJavaScriptAlertTestScript();
case JavaScriptAlertType::CONFIRMATION:
return GetJavaScriptConfirmationTestScript();
case JavaScriptAlertType::PROMPT:
return GetJavaScriptPromptTestScript();
}
GREYFail(@"JavascriptAlertType not recognized.");
return nil;
}
// Const for an http server that returns a blank document.
const char* kJavaScriptTestURL = "http://jsalerts";
const char* kJavaScriptTestResponse =
"<!DOCTYPE html><html><body></body></html>";
// Waits until |string| is displayed on the web view.
void WaitForWebDisplay(const std::string& string) {
id<GREYMatcher> response1Matcher =
chrome_test_util::webViewContainingText(string);
[[EarlGrey selectElementWithMatcher:response1Matcher]
assertWithMatcher:grey_notNil()];
}
// Display the javascript alert.
void DisplayJavaScriptAlert(JavaScriptAlertType type) {
// Get the WebController.
web::WebState* webState = chrome_test_util::GetCurrentWebState();
// Evaluate JavaScript.
NSString* script = GetScriptForAlertWithType(type);
webState->ExecuteJavaScript(base::SysNSStringToUTF16(script));
}
// Assert that the javascript alert has been presented.
void WaitForAlertToBeShown(NSString* alert_label) {
// Wait for the alert to be shown by trying to get the alert title.
ConditionBlock condition = ^{
NSError* error = nil;
id<GREYMatcher> titleLabel =
chrome_test_util::staticTextWithAccessibilityLabel(alert_label);
[[EarlGrey selectElementWithMatcher:titleLabel]
assertWithMatcher:grey_notNil()
error:&error];
return !error;
};
GREYAssert(testing::WaitUntilConditionOrTimeout(
testing::kWaitForJSCompletionTimeout, condition),
@"Alert with title was not present: %@", alert_label);
}
void WaitForJavaScripDialogToBeShown() {
NSString* alert_label = [DialogPresenter
localizedTitleForJavaScriptAlertFromPage:web::test::HttpServer::MakeUrl(
kJavaScriptTestURL)];
WaitForAlertToBeShown(alert_label);
}
// Injects JavaScript to show a dialog with |type|, verifying that it was
// properly displayed.
void ShowJavaScriptDialog(JavaScriptAlertType type) {
DisplayJavaScriptAlert(type);
WaitForJavaScripDialogToBeShown();
// Check the message of the alert.
id<GREYMatcher> messageLabel =
chrome_test_util::staticTextWithAccessibilityLabel(
GetMessageForAlertWithType(type));
[[EarlGrey selectElementWithMatcher:messageLabel]
assertWithMatcher:grey_notNil()];
}
// Assert no javascript alert is visible.
void AssertJavaScriptAlertNotPresent() {
ConditionBlock condition = ^{
NSError* error = nil;
NSString* alertLabel = [DialogPresenter
localizedTitleForJavaScriptAlertFromPage:web::test::HttpServer::MakeUrl(
kJavaScriptTestURL)];
id<GREYMatcher> titleLabel =
chrome_test_util::staticTextWithAccessibilityLabel(alertLabel);
[[EarlGrey selectElementWithMatcher:titleLabel] assertWithMatcher:grey_nil()
error:&error];
return !error;
};
GREYAssert(testing::WaitUntilConditionOrTimeout(
testing::kWaitForJSCompletionTimeout, condition),
@"Javascript alert title was still present");
}
// Types |input| in the prompt.
void TypeInPrompt(NSString* input) {
[[[EarlGrey selectElementWithMatcher:
grey_accessibilityID(
kJavaScriptDialogTextFieldAccessibiltyIdentifier)]
assertWithMatcher:grey_sufficientlyVisible()] performAction:grey_tap()];
[[EarlGrey selectElementWithMatcher:
grey_accessibilityID(
kJavaScriptDialogTextFieldAccessibiltyIdentifier)]
performAction:grey_typeText(input)];
}
void TapOK() {
id<GREYMatcher> ok_button =
chrome_test_util::buttonWithAccessibilityLabelId(IDS_OK);
[[EarlGrey selectElementWithMatcher:ok_button] performAction:grey_tap()];
}
void TapCancel() {
[[EarlGrey selectElementWithMatcher:chrome_test_util::cancelButton()]
performAction:grey_tap()];
}
void TapSuppressDialogsButton() {
id<GREYMatcher> suppress_dialogs_button =
chrome_test_util::buttonWithAccessibilityLabelId(
IDS_IOS_JAVA_SCRIPT_DIALOG_BLOCKING_BUTTON_TEXT);
[[EarlGrey selectElementWithMatcher:suppress_dialogs_button]
performAction:grey_tap()];
}
} // namespace
@interface JavaScriptDialogTestCase : ChromeTestCase {
GURL _URL;
}
@end
@implementation JavaScriptDialogTestCase
- (void)setUp {
[super setUp];
_URL = web::test::HttpServer::MakeUrl(kJavaScriptTestURL);
std::map<GURL, std::string> responses;
responses[web::test::HttpServer::MakeUrl(kJavaScriptTestURL)] =
kJavaScriptTestResponse;
web::test::SetUpSimpleHttpServer(responses);
[ChromeEarlGrey loadURL:_URL];
id<GREYMatcher> response2Matcher =
chrome_test_util::webViewContainingText(std::string(""));
[[EarlGrey selectElementWithMatcher:response2Matcher]
assertWithMatcher:grey_notNil()];
}
- (void)tearDown {
NSError* errorOK = nil;
NSError* errorCancel = nil;
// Dismiss JavaScript alert by tapping Cancel.
[[EarlGrey selectElementWithMatcher:chrome_test_util::cancelButton()]
performAction:grey_tap()
error:&errorCancel];
// Dismiss JavaScript alert by tapping OK.
id<GREYMatcher> OKButton =
chrome_test_util::buttonWithAccessibilityLabelId(IDS_OK);
[[EarlGrey selectElementWithMatcher:OKButton] performAction:grey_tap()
error:&errorOK];
if (!errorOK || !errorCancel) {
GREYFail(@"There are still alerts");
}
[super tearDown];
}
#pragma mark - Tests
// Tests that an alert is shown, and that the completion block is called.
- (void)testShowJavaScriptAlert {
// TODO(crbug.com/663026): Reenable the test for devices.
#if !TARGET_IPHONE_SIMULATOR
EARL_GREY_TEST_DISABLED(@"Disabled for devices because existing system "
@"alerts would prevent app alerts to present "
@"correctly.");
#endif
// Show an alert.
ShowJavaScriptDialog(JavaScriptAlertType::ALERT);
// Tap the OK button.
TapOK();
// Wait for the html body to be reset to the correct value.
WaitForWebDisplay(kAlertResultBody);
}
// Tests that a confirmation dialog is shown, and that the completion block is
// called with the correct value when the OK buton is tapped.
- (void)testShowJavaScriptConfirmationOK {
// TODO(crbug.com/663026): Reenable the test for devices.
#if !TARGET_IPHONE_SIMULATOR
EARL_GREY_TEST_DISABLED(@"Disabled for devices because existing system "
@"alerts would prevent app alerts to present "
@"correctly.");
#endif
// Show a confirmation dialog.
ShowJavaScriptDialog(JavaScriptAlertType::CONFIRMATION);
// Tap the OK button.
TapOK();
// Wait for the html body to be reset to the correct value.
WaitForWebDisplay(kConfirmationResultBodyOK);
}
// Tests that a confirmation dialog is shown, and that the completion block is
// called with the correct value when the Cancel buton is tapped.
- (void)testShowJavaScriptConfirmationCancelled {
// TODO(crbug.com/663026): Reenable the test for devices.
#if !TARGET_IPHONE_SIMULATOR
EARL_GREY_TEST_DISABLED(@"Disabled for devices because existing system "
@"alerts would prevent app alerts to present "
@"correctly.");
#endif
// Show a confirmation dialog.
ShowJavaScriptDialog(JavaScriptAlertType::CONFIRMATION);
// Tap the Cancel button.
TapCancel();
// Wait for the html body to be reset to the correct value.
WaitForWebDisplay(kConfirmationResultBodyCancelled);
}
// Tests that a prompt dialog is shown, and that the completion block is called
// with the correct value when the OK buton is tapped.
- (void)testShowJavaScriptPromptOK {
// TODO(crbug.com/663026): Reenable the test for devices.
#if !TARGET_IPHONE_SIMULATOR
EARL_GREY_TEST_DISABLED(@"Disabled for devices because existing system "
@"alerts would prevent app alerts to present "
@"correctly.");
#endif
// Show a prompt dialog.
ShowJavaScriptDialog(JavaScriptAlertType::PROMPT);
// Enter text into text field.
TypeInPrompt(@(kPromptTestUserInput));
// Tap the OK button.
TapOK();
// Wait for the html body to be reset to the input text.
WaitForWebDisplay(kPromptTestUserInput);
}
// Tests that a prompt dialog is shown, and that the completion block is called
// with the correct value when the Cancel buton is tapped.
- (void)testShowJavaScriptPromptCancelled {
// TODO(crbug.com/663026): Reenable the test for devices.
#if !TARGET_IPHONE_SIMULATOR
EARL_GREY_TEST_DISABLED(@"Disabled for devices because existing system "
@"alerts would prevent app alerts to present "
@"correctly.");
#endif
// Show a prompt dialog.
ShowJavaScriptDialog(JavaScriptAlertType::PROMPT);
// Enter text into text field.
TypeInPrompt(@(kPromptTestUserInput));
// Tap the Cancel button.
TapCancel();
// Wait for the html body to be reset to the cancel text.
WaitForWebDisplay(kPromptResultBodyCancelled);
}
// Tests that JavaScript alerts that are shown in a loop can be suppressed.
- (void)testShowJavaScriptAlertLoop {
// Evaluate JavaScript to show alerts in an endless loop.
web::WebState* webState = chrome_test_util::GetCurrentWebState();
NSString* script = GetJavaScriptAlertLoopScript();
webState->ExecuteJavaScript(base::SysNSStringToUTF16(script));
WaitForJavaScripDialogToBeShown();
// Tap the OK button and wait for another dialog to be shown.
TapOK();
WaitForJavaScripDialogToBeShown();
// Tap the suppress dialogs button.
TapSuppressDialogsButton();
// Wait for confirmation action sheet to be shown.
NSString* alertLabel =
l10n_util::GetNSString(IDS_JAVASCRIPT_MESSAGEBOX_SUPPRESS_OPTION);
WaitForAlertToBeShown(alertLabel);
// Tap the suppress dialogs confirmation button.
TapSuppressDialogsButton();
// Wait for the html body to be reset to the loop finished text.
WaitForWebDisplay(kAlertLoopFinishedText);
}
// Tests to ensure crbug.com/658260 does not regress.
// Tests that if an alert should be called when settings are displays, the alert
// waits for the dismiss of the settings.
- (void)testShowJavaScriptBehindSettings {
// TODO(crbug.com/663026): Reenable the test for devices.
#if !TARGET_IPHONE_SIMULATOR
EARL_GREY_TEST_DISABLED(@"Disabled for devices because existing system "
@"alerts would prevent app alerts to present "
@"correctly.");
#endif
// Show settings.
[ChromeEarlGreyUI openToolsMenu];
[[EarlGrey
selectElementWithMatcher:grey_accessibilityID(kToolsMenuSettingsId)]
performAction:grey_tap()];
[[EarlGrey selectElementWithMatcher:chrome_test_util::
staticTextWithAccessibilityLabelId(
IDS_IOS_SETTINGS_TITLE)]
assertWithMatcher:grey_sufficientlyVisible()];
// Show an alert.
DisplayJavaScriptAlert(JavaScriptAlertType::ALERT);
// Make sure the alert is not present.
AssertJavaScriptAlertNotPresent();
// Close the settings.
[[EarlGrey
selectElementWithMatcher:chrome_test_util::buttonWithAccessibilityLabelId(
IDS_IOS_NAVIGATION_BAR_DONE_BUTTON)]
performAction:grey_tap()];
// Make sure the alert is present.
WaitForJavaScripDialogToBeShown();
// Tap the OK button.
TapOK();
// Wait for the html body to be reset to the correct value.
WaitForWebDisplay(kAlertResultBody);
}
// Tests that an alert is presented after displaying the share menu.
- (void)testShowJavaScriptAfterShareMenu {
// TODO(crbug.com/663026): Reenable the test for devices.
#if !TARGET_IPHONE_SIMULATOR
EARL_GREY_TEST_DISABLED(@"Disabled for devices because existing system "
@"alerts would prevent app alerts to present "
@"correctly.");
#endif
[ChromeEarlGreyUI openShareMenu];
// Copy URL, dismissing the share menu.
id<GREYMatcher> printButton =
grey_allOf(grey_accessibilityLabel(@"Copy"),
grey_accessibilityTrait(UIAccessibilityTraitButton), nil);
[[EarlGrey selectElementWithMatcher:printButton] performAction:grey_tap()];
// Show an alert and assert it is present.
ShowJavaScriptDialog(JavaScriptAlertType::ALERT);
// Tap the OK button.
TapOK();
// Wait for the html body to be reset to the correct value.
WaitForWebDisplay(kAlertResultBody);
}
@end