blob: 6ba882ee4ad9bd1fb00cf596da48d8845a9b67cf [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 <XCTest/XCTest.h>
#include "base/memory/ptr_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "components/strings/grit/components_strings.h"
#include "ios/chrome/browser/ui/ui_util.h"
#import "ios/chrome/test/app/chrome_test_util.h"
#include "ios/chrome/test/app/navigation_test_util.h"
#include "ios/chrome/test/app/web_view_interaction_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/wait_util.h"
#import "ios/web/public/test/http_server.h"
#import "ios/web/public/test/http_server_util.h"
#include "ios/web/public/test/response_providers/data_response_provider.h"
#include "ios/web/public/test/url_test_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using chrome_test_util::ButtonWithAccessibilityLabelId;
using chrome_test_util::OmniboxText;
using chrome_test_util::WebViewContainingText;
namespace {
// Response shown on the page of |GetDestinationUrl|.
const char kDestinationText[] = "bar!";
// Label for the button in the form.
const char kSubmitButtonLabel[] = "submit";
// Html form template with a submission button named "submit".
const char* kFormHtmlTemplate =
"<form method='post' action='%s'> submit: "
"<input value='textfield' id='textfield' type='text'></label>"
"<input type='submit' value='submit' id='submit'>"
"</form>";
// GURL of a generic website in the user navigation flow.
const GURL GetGenericUrl() {
return web::test::HttpServer::MakeUrl("http://generic");
}
// GURL of a page with a form that posts data to |GetDestinationUrl|.
const GURL GetFormUrl() {
return web::test::HttpServer::MakeUrl("http://form");
}
// GURL of the page to which the |GetFormUrl| posts data to.
const GURL GetDestinationUrl() {
return web::test::HttpServer::MakeUrl("http://destination");
}
#pragma mark - TestFormRedirectResponseProvider
// URL that redirects to |GetDestinationUrl| with a 302.
const GURL GetRedirectUrl() {
return web::test::HttpServer::MakeUrl("http://redirect");
}
// URL to return a page that posts to |GetRedirectUrl|.
const GURL GetRedirectFormUrl() {
return web::test::HttpServer::MakeUrl("http://formRedirect");
}
// A ResponseProvider that provides html response or a redirect.
class TestFormRedirectResponseProvider : public web::DataResponseProvider {
public:
// TestResponseProvider implementation.
bool CanHandleRequest(const Request& request) override;
void GetResponseHeadersAndBody(
const Request& request,
scoped_refptr<net::HttpResponseHeaders>* headers,
std::string* response_body) override;
};
bool TestFormRedirectResponseProvider::CanHandleRequest(
const Request& request) {
const GURL& url = request.url;
return url == GetDestinationUrl() || url == GetRedirectUrl() ||
url == GetRedirectFormUrl();
}
void TestFormRedirectResponseProvider::GetResponseHeadersAndBody(
const Request& request,
scoped_refptr<net::HttpResponseHeaders>* headers,
std::string* response_body) {
const GURL& url = request.url;
if (url == GetRedirectUrl()) {
*headers = web::ResponseProvider::GetRedirectResponseHeaders(
GetDestinationUrl().spec(), net::HTTP_FOUND);
return;
}
*headers = web::ResponseProvider::GetDefaultResponseHeaders();
if (url == GetRedirectFormUrl()) {
*response_body =
base::StringPrintf(kFormHtmlTemplate, GetRedirectUrl().spec().c_str());
return;
} else if (url == GetDestinationUrl()) {
*response_body = request.method + std::string(" ") + request.body;
return;
}
NOTREACHED();
}
} // namespace
// Tests submition of HTTP forms POST data including cases involving navigation.
@interface FormsTestCase : ChromeTestCase
@end
@implementation FormsTestCase
// Waits for view with Tab History accessibility ID.
- (void)waitForTabHistoryView {
GREYCondition* condition = [GREYCondition
conditionWithName:@"Waiting for Tab History to display."
block:^BOOL {
NSError* error = nil;
id<GREYMatcher> tabHistory =
grey_accessibilityID(@"Tab History");
[[EarlGrey selectElementWithMatcher:tabHistory]
assertWithMatcher:grey_notNil()
error:&error];
return error == nil;
}];
GREYAssert([condition waitWithTimeout:testing::kWaitForUIElementTimeout],
@"Tab History View not displayed.");
}
// Open back navigation history.
- (void)openBackHistory {
[[EarlGrey selectElementWithMatcher:chrome_test_util::BackButton()]
performAction:grey_longPress()];
}
// Accepts the warning that the form POST data will be reposted.
- (void)confirmResendWarning {
id<GREYMatcher> resendWarning =
chrome_test_util::ButtonWithAccessibilityLabelId(
IDS_HTTP_POST_WARNING_RESEND);
[[EarlGrey selectElementWithMatcher:resendWarning]
performAction:grey_longPress()];
}
// Sets up a basic simple http server for form test with a form located at
// |GetFormUrl|, and posts data to |GetDestinationUrl| upon submission.
- (void)setUpFormTestSimpleHttpServer {
std::map<GURL, std::string> responses;
responses[GetGenericUrl()] = "A generic page";
responses[GetFormUrl()] =
base::StringPrintf(kFormHtmlTemplate, GetDestinationUrl().spec().c_str());
responses[GetDestinationUrl()] = kDestinationText;
web::test::SetUpSimpleHttpServer(responses);
}
// Tests that a POST followed by reloading the destination page resends data.
- (void)testRepostFormAfterReload {
[self setUpFormTestSimpleHttpServer];
const GURL destinationURL = GetDestinationUrl();
[ChromeEarlGrey loadURL:GetFormUrl()];
chrome_test_util::TapWebViewElementWithId(kSubmitButtonLabel);
[[EarlGrey selectElementWithMatcher:WebViewContainingText(kDestinationText)]
assertWithMatcher:grey_notNil()];
[[EarlGrey selectElementWithMatcher:OmniboxText(destinationURL.GetContent())]
assertWithMatcher:grey_notNil()];
[ChromeEarlGrey reload];
[self confirmResendWarning];
[[EarlGrey selectElementWithMatcher:WebViewContainingText(kDestinationText)]
assertWithMatcher:grey_notNil()];
[[EarlGrey selectElementWithMatcher:OmniboxText(destinationURL.GetContent())]
assertWithMatcher:grey_notNil()];
}
// Tests that a POST followed by navigating to a new page and then tapping back
// to the form result page resends data.
- (void)testRepostFormAfterTappingBack {
[self setUpFormTestSimpleHttpServer];
const GURL destinationURL = GetDestinationUrl();
[ChromeEarlGrey loadURL:GetFormUrl()];
chrome_test_util::TapWebViewElementWithId(kSubmitButtonLabel);
[[EarlGrey selectElementWithMatcher:WebViewContainingText(kDestinationText)]
assertWithMatcher:grey_notNil()];
[[EarlGrey selectElementWithMatcher:OmniboxText(destinationURL.GetContent())]
assertWithMatcher:grey_notNil()];
// Go to a new page and go back and check that the data is reposted.
[ChromeEarlGrey loadURL:GetGenericUrl()];
[ChromeEarlGrey goBack];
[self confirmResendWarning];
[[EarlGrey selectElementWithMatcher:WebViewContainingText(kDestinationText)]
assertWithMatcher:grey_notNil()];
[[EarlGrey selectElementWithMatcher:OmniboxText(destinationURL.GetContent())]
assertWithMatcher:grey_notNil()];
}
// Tests that a POST followed by tapping back to the form page and then tapping
// forward to the result page resends data.
- (void)testRepostFormAfterTappingBackAndForward {
[self setUpFormTestSimpleHttpServer];
const GURL destinationURL = GetDestinationUrl();
[ChromeEarlGrey loadURL:GetFormUrl()];
chrome_test_util::TapWebViewElementWithId(kSubmitButtonLabel);
[[EarlGrey selectElementWithMatcher:WebViewContainingText(kDestinationText)]
assertWithMatcher:grey_notNil()];
[[EarlGrey selectElementWithMatcher:OmniboxText(destinationURL.GetContent())]
assertWithMatcher:grey_notNil()];
[ChromeEarlGrey goBack];
[ChromeEarlGrey goForward];
[self confirmResendWarning];
[[EarlGrey selectElementWithMatcher:WebViewContainingText(kDestinationText)]
assertWithMatcher:grey_notNil()];
[[EarlGrey selectElementWithMatcher:OmniboxText(destinationURL.GetContent())]
assertWithMatcher:grey_notNil()];
}
// Tests that a POST followed by a new request and then index navigation to get
// back to the result page resends data.
- (void)testRepostFormAfterIndexNavigation {
[self setUpFormTestSimpleHttpServer];
const GURL destinationURL = GetDestinationUrl();
[ChromeEarlGrey loadURL:GetFormUrl()];
chrome_test_util::TapWebViewElementWithId(kSubmitButtonLabel);
[[EarlGrey selectElementWithMatcher:WebViewContainingText(kDestinationText)]
assertWithMatcher:grey_notNil()];
[[EarlGrey selectElementWithMatcher:OmniboxText(destinationURL.GetContent())]
assertWithMatcher:grey_notNil()];
// Go to a new page and go back to destination through back history.
[ChromeEarlGrey loadURL:GetGenericUrl()];
[self openBackHistory];
[self waitForTabHistoryView];
id<GREYMatcher> historyItem = grey_text(
base::SysUTF16ToNSString(web::GetDisplayTitleForUrl(destinationURL)));
[[EarlGrey selectElementWithMatcher:historyItem] performAction:grey_tap()];
[ChromeEarlGrey waitForPageToFinishLoading];
[self confirmResendWarning];
[[EarlGrey selectElementWithMatcher:WebViewContainingText(kDestinationText)]
assertWithMatcher:grey_notNil()];
[[EarlGrey selectElementWithMatcher:OmniboxText(destinationURL.GetContent())]
assertWithMatcher:grey_notNil()];
}
// When data is not reposted, the request is canceled.
- (void)testRepostFormCancelling {
[self setUpFormTestSimpleHttpServer];
const GURL destinationURL = GetDestinationUrl();
[ChromeEarlGrey loadURL:GetFormUrl()];
chrome_test_util::TapWebViewElementWithId(kSubmitButtonLabel);
[[EarlGrey selectElementWithMatcher:WebViewContainingText(kDestinationText)]
assertWithMatcher:grey_notNil()];
[[EarlGrey selectElementWithMatcher:OmniboxText(destinationURL.GetContent())]
assertWithMatcher:grey_notNil()];
[ChromeEarlGrey goBack];
[ChromeEarlGrey goForward];
// Abort the reload.
// TODO (crbug.com/705020): Use something like ElementToDismissContextMenu
// to cancel form repost.
if (IsIPadIdiom()) {
// On tablet, dismiss the popover.
GREYElementMatcherBlock* matcher = [[GREYElementMatcherBlock alloc]
initWithMatchesBlock:^BOOL(UIView* view) {
return [NSStringFromClass([view class]) hasPrefix:@"UIDimmingView"];
}
descriptionBlock:^(id<GREYDescription> description) {
[description appendText:@"class prefixed with UIDimmingView"];
}];
[[EarlGrey selectElementWithMatcher:matcher]
performAction:grey_tapAtPoint(CGPointMake(50.0f, 50.0f))];
} else {
// On handset, dismiss via the cancel button.
[[EarlGrey selectElementWithMatcher:chrome_test_util::CancelButton()]
performAction:grey_tap()];
}
[ChromeEarlGrey waitForPageToFinishLoading];
// Verify that navigation was cancelled, and forward navigation is possible.
[[EarlGrey selectElementWithMatcher:WebViewContainingText(kSubmitButtonLabel)]
assertWithMatcher:grey_notNil()];
[[EarlGrey selectElementWithMatcher:OmniboxText(GetFormUrl().GetContent())]
assertWithMatcher:grey_notNil()];
[[EarlGrey selectElementWithMatcher:chrome_test_util::ForwardButton()]
assertWithMatcher:grey_interactable()];
}
// Tests that pressing the button on a POST-based form changes the page and that
// the back button works as expected afterwards.
- (void)testGoBackButtonAfterFormSubmission {
[self setUpFormTestSimpleHttpServer];
GURL destinationURL = GetDestinationUrl();
[ChromeEarlGrey loadURL:GetFormUrl()];
chrome_test_util::TapWebViewElementWithId(kSubmitButtonLabel);
[[EarlGrey selectElementWithMatcher:WebViewContainingText(kDestinationText)]
assertWithMatcher:grey_notNil()];
[[EarlGrey selectElementWithMatcher:OmniboxText(destinationURL.GetContent())]
assertWithMatcher:grey_notNil()];
// Go back and verify the browser navigates to the original URL.
[ChromeEarlGrey goBack];
[[EarlGrey selectElementWithMatcher:WebViewContainingText(kSubmitButtonLabel)]
assertWithMatcher:grey_notNil()];
[[EarlGrey selectElementWithMatcher:OmniboxText(GetFormUrl().GetContent())]
assertWithMatcher:grey_notNil()];
}
// Tests that a POST followed by a redirect does not show the popup.
- (void)testRepostFormCancellingAfterRedirect {
web::test::SetUpHttpServer(
base::MakeUnique<TestFormRedirectResponseProvider>());
const GURL destinationURL = GetDestinationUrl();
[ChromeEarlGrey loadURL:GetRedirectFormUrl()];
// Submit the form, which redirects before printing the data.
chrome_test_util::TapWebViewElementWithId(kSubmitButtonLabel);
// Check that the redirect changes the POST to a GET.
[[EarlGrey selectElementWithMatcher:WebViewContainingText("GET")]
assertWithMatcher:grey_notNil()];
[[EarlGrey selectElementWithMatcher:OmniboxText(destinationURL.GetContent())]
assertWithMatcher:grey_notNil()];
[ChromeEarlGrey reload];
// Check that the popup did not show
id<GREYMatcher> resendWarning =
ButtonWithAccessibilityLabelId(IDS_HTTP_POST_WARNING_RESEND);
[[EarlGrey selectElementWithMatcher:resendWarning]
assertWithMatcher:grey_nil()];
[[EarlGrey selectElementWithMatcher:WebViewContainingText("GET")]
assertWithMatcher:grey_notNil()];
[[EarlGrey selectElementWithMatcher:OmniboxText(destinationURL.GetContent())]
assertWithMatcher:grey_notNil()];
}
@end