Index: ios/chrome/browser/ui/dialogs/javascript_dialog_egtest.mm |
diff --git a/ios/chrome/browser/ui/dialogs/javascript_dialog_egtest.mm b/ios/chrome/browser/ui/dialogs/javascript_dialog_egtest.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2dce5e7f42b33741ebaa9abeb9f09be9320008a2 |
--- /dev/null |
+++ b/ios/chrome/browser/ui/dialogs/javascript_dialog_egtest.mm |
@@ -0,0 +1,511 @@ |
+// 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() { |
+ id<GREYMatcher> cancel_button = |
+ chrome_test_util::buttonWithAccessibilityLabelId(IDS_CANCEL); |
+ [[EarlGrey selectElementWithMatcher:cancel_button] 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. |
+ id<GREYMatcher> CancelButton = |
+ chrome_test_util::buttonWithAccessibilityLabelId(IDS_CANCEL); |
+ [[EarlGrey selectElementWithMatcher: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 |
+ |
+ // Show the share menu. |
+ if (!IsIPadIdiom()) |
+ [ChromeEarlGreyUI openToolsMenu]; |
+ |
+ NSString* shareLabel = l10n_util::GetNSString(IDS_IOS_TOOLS_MENU_SHARE); |
+ [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(shareLabel)] |
+ performAction:grey_tap()]; |
+ |
+ // 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 |