Index: ios/chrome/browser/web/push_and_replace_state_navigation_egtest.mm |
diff --git a/ios/chrome/browser/web/push_and_replace_state_navigation_egtest.mm b/ios/chrome/browser/web/push_and_replace_state_navigation_egtest.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..17997ae5f1417a7b87d7cfb2daa69b573751f28b |
--- /dev/null |
+++ b/ios/chrome/browser/web/push_and_replace_state_navigation_egtest.mm |
@@ -0,0 +1,354 @@ |
+// 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/strings/sys_string_conversions.h" |
+#include "components/strings/grit/components_strings.h" |
+#include "ios/chrome/browser/ui/ui_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/web/public/test/http_server.h" |
+#include "ios/web/public/test/http_server_util.h" |
+ |
+namespace { |
+ |
+const char* kHistoryTestUrl = |
+ "http://ios/testing/data/http_server_files/history.html"; |
+const char* kNonPushedUrl = |
+ "http://ios/testing/data/http_server_files/pony.html"; |
+const char* kReplaceStateHashWithObjectURL = |
+ "http://ios/testing/data/http_server_files/history.html#replaceWithObject"; |
+const char* kPushStateHashStringURL = |
+ "http://ios/testing/data/http_server_files/history.html#string"; |
+const char* kReplaceStateHashStringURL = |
+ "http://ios/testing/data/http_server_files/history.html#replace"; |
+const char* kPushStatePathURL = |
+ "http://ios/testing/data/http_server_files/path"; |
+const char* kReplaceStateRootPathSpaceURL = "http://ios/rep lace"; |
+ |
+// Matcher for the navigate forward button. |
+id<GREYMatcher> forwardButton() { |
+ return chrome_test_util::buttonWithAccessibilityLabelId(IDS_ACCNAME_FORWARD); |
+} |
+// Matcher for the navigate backward button. |
+id<GREYMatcher> backButton() { |
+ return chrome_test_util::buttonWithAccessibilityLabelId(IDS_ACCNAME_BACK); |
+} |
+} // namespace |
+ |
+// Tests for pushState/replaceState navigations. |
+@interface PushAndReplaceStateNavigationTestCase : ChromeTestCase |
+@end |
+ |
+@implementation PushAndReplaceStateNavigationTestCase |
+ |
+// Tests calling history.pushState() multiple times and navigating back/forward. |
+- (void)testHtml5HistoryPushStateThenGoBackAndForward { |
+ const GURL pushStateHashWithObjectURL = web::test::HttpServer::MakeUrl( |
+ "http://ios/testing/data/http_server_files/history.html#pushWithObject"); |
+ const GURL pushStateRootPathURL = |
+ web::test::HttpServer::MakeUrl("http://ios/rootpath"); |
+ const GURL pushStatePathSpaceURL = |
+ web::test::HttpServer::MakeUrl("http://ios/pa%20th"); |
+ web::test::SetUpFileBasedHttpServer(); |
+ [ChromeEarlGrey loadURL:web::test::HttpServer::MakeUrl(kHistoryTestUrl)]; |
+ |
+ // Push 3 URLs. Verify that the URL changed and the status was updated. |
+ [ChromeEarlGrey tapWebViewElementWithID:@"pushStateHashWithObject"]; |
+ [self assertStatusText:@"pushStateHashWithObject" |
+ withURL:pushStateHashWithObjectURL |
+ pageLoaded:NO]; |
+ |
+ [ChromeEarlGrey tapWebViewElementWithID:@"pushStateRootPath"]; |
+ [self assertStatusText:@"pushStateRootPath" |
+ withURL:pushStateRootPathURL |
+ pageLoaded:NO]; |
+ |
+ [ChromeEarlGrey tapWebViewElementWithID:@"pushStatePathSpace"]; |
+ [self assertStatusText:@"pushStatePathSpace" |
+ withURL:pushStatePathSpaceURL |
+ pageLoaded:NO]; |
+ |
+ // Go back and check that the page doesn't load and the status text is updated |
+ // by the popstate event. |
+ [[EarlGrey selectElementWithMatcher:backButton()] performAction:grey_tap()]; |
+ [self assertStatusText:@"pushStateRootPath" |
+ withURL:pushStateRootPathURL |
+ pageLoaded:NO]; |
+ |
+ [[EarlGrey selectElementWithMatcher:backButton()] performAction:grey_tap()]; |
+ [self assertStatusText:@"pushStateHashWithObject" |
+ withURL:pushStateHashWithObjectURL |
+ pageLoaded:NO]; |
+ |
+ [ChromeEarlGrey tapWebViewElementWithID:@"goBack"]; |
+ const GURL historyTestURL = web::test::HttpServer::MakeUrl(kHistoryTestUrl); |
+ [self assertStatusText:NULL withURL:historyTestURL pageLoaded:NO]; |
+ |
+ // Go forward 2 pages and check that the page doesn't load and the status text |
+ // is updated by the popstate event. |
+ [ChromeEarlGrey tapWebViewElementWithID:@"goForward2"]; |
+ [self assertStatusText:@"pushStateRootPath" |
+ withURL:pushStateRootPathURL |
+ pageLoaded:NO]; |
+} |
+ |
+// Tests that calling replaceState() changes the current history entry. |
+- (void)testHtml5HistoryReplaceStateThenGoBackAndForward { |
+ web::test::SetUpFileBasedHttpServer(); |
+ const GURL initialURL = web::test::HttpServer::MakeUrl(kNonPushedUrl); |
+ [ChromeEarlGrey loadURL:initialURL]; |
+ [ChromeEarlGrey loadURL:web::test::HttpServer::MakeUrl(kHistoryTestUrl)]; |
+ |
+ // Replace the URL and go back then forward. |
+ const GURL replaceStateHashWithObjectURL = |
+ web::test::HttpServer::MakeUrl(kReplaceStateHashWithObjectURL); |
+ [ChromeEarlGrey tapWebViewElementWithID:@"replaceStateHashWithObject"]; |
+ [self assertStatusText:@"replaceStateHashWithObject" |
+ withURL:replaceStateHashWithObjectURL |
+ pageLoaded:NO]; |
+ |
+ [[EarlGrey selectElementWithMatcher:backButton()] performAction:grey_tap()]; |
+ [[EarlGrey selectElementWithMatcher:chrome_test_util::omniboxText( |
+ initialURL.GetContent())] |
+ assertWithMatcher:grey_notNil()]; |
+ |
+ [[EarlGrey selectElementWithMatcher:forwardButton()] |
+ performAction:grey_tap()]; |
+ [self assertStatusText:@"replaceStateHashWithObject" |
+ withURL:replaceStateHashWithObjectURL |
+ pageLoaded:YES]; |
+ |
+ // Push URL then replace it. Do this twice. |
+ const GURL pushStateHashStringURL = |
+ web::test::HttpServer::MakeUrl(kPushStateHashStringURL); |
+ [ChromeEarlGrey tapWebViewElementWithID:@"pushStateHashString"]; |
+ [self assertStatusText:@"pushStateHashString" |
+ withURL:pushStateHashStringURL |
+ pageLoaded:NO]; |
+ |
+ const GURL replaceStateHashStringURL = |
+ web::test::HttpServer::MakeUrl(kReplaceStateHashStringURL); |
+ [ChromeEarlGrey tapWebViewElementWithID:@"replaceStateHashString"]; |
+ [self assertStatusText:@"replaceStateHashString" |
+ withURL:replaceStateHashStringURL |
+ pageLoaded:NO]; |
+ |
+ const GURL pushStatePathURL = |
+ web::test::HttpServer::MakeUrl(kPushStatePathURL); |
+ [ChromeEarlGrey tapWebViewElementWithID:@"pushStatePath"]; |
+ [self assertStatusText:@"pushStatePath" |
+ withURL:pushStatePathURL |
+ pageLoaded:NO]; |
+ |
+ const GURL replaceStateRootPathSpaceURL = |
+ web::test::HttpServer::MakeUrl(kReplaceStateRootPathSpaceURL); |
+ [ChromeEarlGrey tapWebViewElementWithID:@"replaceStateRootPathSpace"]; |
+ [self assertStatusText:@"replaceStateRootPathSpace" |
+ withURL:replaceStateRootPathSpaceURL |
+ pageLoaded:NO]; |
+ |
+ // Go back and check URLs. |
+ [[EarlGrey selectElementWithMatcher:backButton()] performAction:grey_tap()]; |
+ [self assertStatusText:@"replaceStateHashString" |
+ withURL:replaceStateHashStringURL |
+ pageLoaded:NO]; |
+ [[EarlGrey selectElementWithMatcher:backButton()] performAction:grey_tap()]; |
+ [self assertStatusText:@"replaceStateHashWithObject" |
+ withURL:replaceStateHashWithObjectURL |
+ pageLoaded:NO]; |
+ |
+ // Go forward and check URL. |
+ [ChromeEarlGrey tapWebViewElementWithID:@"goForward2"]; |
+ [self assertStatusText:@"replaceStateRootPathSpace" |
+ withURL:replaceStateRootPathSpaceURL |
+ pageLoaded:NO]; |
+} |
+ |
+// Tests that page loads occur when navigating to or past a non-pushed URL. |
+- (void)testHtml5HistoryNavigatingPastNonPushedURL { |
+ GURL nonPushedURL = web::test::HttpServer::MakeUrl(kNonPushedUrl); |
+ web::test::SetUpFileBasedHttpServer(); |
+ const GURL historyTestURL = web::test::HttpServer::MakeUrl(kHistoryTestUrl); |
+ [ChromeEarlGrey loadURL:historyTestURL]; |
+ |
+ // Push same URL twice. Verify that URL changed and the status was updated. |
+ const GURL pushStateHashStringURL = |
+ web::test::HttpServer::MakeUrl(kPushStateHashStringURL); |
+ [ChromeEarlGrey tapWebViewElementWithID:@"pushStateHashString"]; |
+ [self assertStatusText:@"pushStateHashString" |
+ withURL:pushStateHashStringURL |
+ pageLoaded:NO]; |
+ [ChromeEarlGrey tapWebViewElementWithID:@"pushStateHashString"]; |
+ [self assertStatusText:@"pushStateHashString" |
+ withURL:pushStateHashStringURL |
+ pageLoaded:NO]; |
+ |
+ // Load a non-pushed URL. |
+ [ChromeEarlGrey loadURL:nonPushedURL]; |
+ |
+ // Load history.html and push another URL. |
+ [ChromeEarlGrey loadURL:historyTestURL]; |
+ [ChromeEarlGrey tapWebViewElementWithID:@"pushStateHashString"]; |
+ [self assertStatusText:@"pushStateHashString" |
+ withURL:pushStateHashStringURL |
+ pageLoaded:NO]; |
+ |
+ // At this point the history looks like this: |
+ // [NTP, history.html, #string, #string, nonPushedURL, history.html, #string] |
+ |
+ // Go back (to second history.html) and verify page did not load. |
+ [[EarlGrey selectElementWithMatcher:backButton()] performAction:grey_tap()]; |
+ [self assertStatusText:nil withURL:historyTestURL pageLoaded:NO]; |
+ |
+ // Go back twice (to second #string) and verify page did load. |
+ [[EarlGrey selectElementWithMatcher:backButton()] performAction:grey_tap()]; |
+ [[EarlGrey selectElementWithMatcher:backButton()] performAction:grey_tap()]; |
+ [self assertStatusText:nil withURL:pushStateHashStringURL pageLoaded:YES]; |
+ |
+ // Go back once (to first #string) and verify page did not load. |
+ [[EarlGrey selectElementWithMatcher:backButton()] performAction:grey_tap()]; |
+ [self assertStatusText:@"pushStateHashString" |
+ withURL:pushStateHashStringURL |
+ pageLoaded:NO]; |
+ |
+ // Go forward 4 entries at once (to third #string) and verify page did load. |
+ [ChromeEarlGrey tapWebViewElementWithID:@"goForward4"]; |
+ |
+ [self assertStatusText:nil withURL:pushStateHashStringURL pageLoaded:YES]; |
+ |
+ // Go back 4 entries at once (to first #string) and verify page did load. |
+ [ChromeEarlGrey tapWebViewElementWithID:@"goBack4"]; |
+ |
+ [self assertStatusText:NULL withURL:pushStateHashStringURL pageLoaded:YES]; |
+} |
+ |
+// Tests calling pushState with unicode characters. |
+- (void)testHtml5HistoryPushUnicodeCharacters { |
+ const GURL pushStateUnicodeURLEncoded = web::test::HttpServer::MakeUrl( |
+ "http://ios/testing/data/http_server_files/" |
+ "history.html#unicode%E1%84%91"); |
+ const GURL pushStateUnicode2URLEncoded = web::test::HttpServer::MakeUrl( |
+ "http://ios/testing/data/http_server_files/" |
+ "history.html#unicode2%E2%88%A2"); |
+ std::string pushStateUnicodeLabel = "Action: pushStateUnicodeᄑ"; |
+ NSString* pushStateUnicodeStatus = @"pushStateUnicodeᄑ"; |
+ std::string pushStateUnicode2Label = "Action: pushStateUnicode2∢"; |
+ NSString* pushStateUnicode2Status = @"pushStateUnicode2∢"; |
+ |
+ web::test::SetUpFileBasedHttpServer(); |
+ [ChromeEarlGrey loadURL:web::test::HttpServer::MakeUrl(kHistoryTestUrl)]; |
+ |
+ // TODO(crbug.com/643458): The fact that the URL shows %-escaped is due to |
+ // NSURL escaping to make UIWebView/JS happy. See if it's possible to |
+ // represent differently such that it displays unescaped. |
+ // Do 2 push states with unicode characters. |
+ [ChromeEarlGrey tapWebViewElementWithID:@"pushStateUnicode"]; |
+ [[EarlGrey |
+ selectElementWithMatcher:chrome_test_util::omniboxText( |
+ pushStateUnicodeURLEncoded.GetContent())] |
+ assertWithMatcher:grey_notNil()]; |
+ [[EarlGrey selectElementWithMatcher:chrome_test_util::webViewContainingText( |
+ pushStateUnicodeLabel)] |
+ assertWithMatcher:grey_notNil()]; |
+ |
+ [ChromeEarlGrey tapWebViewElementWithID:@"pushStateUnicode2"]; |
+ [[EarlGrey |
+ selectElementWithMatcher:chrome_test_util::omniboxText( |
+ pushStateUnicode2URLEncoded.GetContent())] |
+ assertWithMatcher:grey_notNil()]; |
+ [[EarlGrey selectElementWithMatcher:chrome_test_util::webViewContainingText( |
+ pushStateUnicode2Label)] |
+ assertWithMatcher:grey_notNil()]; |
+ |
+ // Do a push state without a unicode character. |
+ const GURL pushStatePathURL = |
+ web::test::HttpServer::MakeUrl(kPushStatePathURL); |
+ [ChromeEarlGrey tapWebViewElementWithID:@"pushStatePath"]; |
+ |
+ [self assertStatusText:@"pushStatePath" |
+ withURL:pushStatePathURL |
+ pageLoaded:NO]; |
+ |
+ // Go back and check the unicode in the URL and status. |
+ [[EarlGrey selectElementWithMatcher:backButton()] performAction:grey_tap()]; |
+ [self assertStatusText:pushStateUnicode2Status |
+ withURL:pushStateUnicode2URLEncoded |
+ pageLoaded:NO]; |
+ |
+ [[EarlGrey selectElementWithMatcher:backButton()] performAction:grey_tap()]; |
+ [self assertStatusText:pushStateUnicodeStatus |
+ withURL:pushStateUnicodeURLEncoded |
+ pageLoaded:NO]; |
+} |
+ |
+// Tests that pushState/replaceState handling properly handles <base>. |
+- (void)testHtml5HistoryWithBase { |
+ std::map<GURL, std::string> responses; |
+ GURL originURL = |
+ web::test::HttpServer::MakeUrl("http://foo.com/foo/bar.html"); |
+ GURL pushResultURL = originURL.GetOrigin().Resolve("pushed/relative/url"); |
+ GURL replaceResultURL = |
+ originURL.GetOrigin().Resolve("replaced/relative/url"); |
+ |
+ // A simple HTML page with a base tag that makes all relative URLs |
+ // domain-relative, a button to trigger a relative pushState, and a button |
+ // to trigger a relative replaceState. |
+ NSString* baseTag = @"<base href=\"/\">"; |
+ NSString* pushAndReplaceButtons = |
+ @"<input type=\"button\" value=\"pushState\" " |
+ "id=\"pushState\" onclick=\"history.pushState(" |
+ "{}, 'Foo', './pushed/relative/url');\"><br>" |
+ "<input type=\"button\" value=\"replaceState\" " |
+ "id=\"replaceState\" onclick=\"history.replaceState(" |
+ "{}, 'Foo', './replaced/relative/url');\"><br>"; |
+ NSString* simplePage = |
+ @"<!doctype html><html><head>%@</head><body>%@</body></html>"; |
+ responses[originURL] = base::SysNSStringToUTF8( |
+ [NSString stringWithFormat:simplePage, baseTag, pushAndReplaceButtons]); |
+ web::test::SetUpSimpleHttpServer(responses); |
+ |
+ [ChromeEarlGrey loadURL:originURL]; |
+ [ChromeEarlGrey tapWebViewElementWithID:@"pushState"]; |
+ [[EarlGrey selectElementWithMatcher:chrome_test_util::omniboxText( |
+ pushResultURL.GetContent())] |
+ assertWithMatcher:grey_notNil()]; |
+ |
+ [ChromeEarlGrey tapWebViewElementWithID:@"replaceState"]; |
+ [[EarlGrey selectElementWithMatcher:chrome_test_util::omniboxText( |
+ replaceResultURL.GetContent())] |
+ assertWithMatcher:grey_notNil()]; |
+} |
+ |
+#pragma mark - Utility methods |
+ |
+// Assert that status text |status| is displayed in the webview, that "onloaded" |
+// text is displayed if pageLoaded is YES, and that the URL is as expected. |
+- (void)assertStatusText:(NSString*)status |
+ withURL:(const GURL&)urlToVerify |
+ pageLoaded:(BOOL)pageLoaded { |
+ id<GREYMatcher> pageLoadedMatcher = |
+ pageLoaded ? chrome_test_util::webViewContainingText("onload") |
+ : chrome_test_util::webViewNotContainingText("onload"); |
+ [[EarlGrey selectElementWithMatcher:pageLoadedMatcher] |
+ assertWithMatcher:grey_notNil()]; |
+ |
+ if (status != NULL) { |
+ NSString* statusLabel = [NSString stringWithFormat:@"Action: %@", status]; |
+ [[EarlGrey |
+ selectElementWithMatcher:chrome_test_util::webViewContainingText( |
+ base::SysNSStringToUTF8(statusLabel))] |
+ assertWithMatcher:grey_notNil()]; |
+ } |
+ |
+ [[EarlGrey selectElementWithMatcher:chrome_test_util::omniboxText( |
+ urlToVerify.GetContent())] |
+ assertWithMatcher:grey_notNil()]; |
+} |
+ |
+@end |