| 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
|
|
|