blob: 490a1498535d274f5ebad0068b85143bd94104f0 [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>
#include "base/bind.h"
#import "base/test/ios/wait_util.h"
#include "components/strings/grit/components_strings.h"
#include "ios/chrome/browser/ui/util/ui_util.h"
#include "ios/chrome/grit/ios_strings.h"
#import "ios/chrome/test/app/chrome_test_util.h"
#import "ios/chrome/test/app/histogram_test_util.h"
#import "ios/chrome/test/app/tab_test_util.h"
#import "ios/chrome/test/earl_grey/chrome_actions.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_error_util.h"
#import "ios/chrome/test/earl_grey/chrome_matchers.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h"
#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
#import "ios/public/provider/chrome/browser/ui/fullscreen_provider.h"
#import "ios/testing/earl_grey/disabled_test_macros.h"
#import "ios/web/public/test/earl_grey/web_view_matchers.h"
#include "ios/web/public/test/element_selector.h"
#import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
#import "ios/web/public/web_state/web_state.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "url/gurl.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::ContextMenuCopyButton;
using chrome_test_util::OmniboxText;
using chrome_test_util::OpenLinkInNewTabButton;
using chrome_test_util::SystemSelectionCallout;
using chrome_test_util::SystemSelectionCalloutCopyButton;
namespace {
// Directory containing the |kLogoPagePath| and |kLogoPageImageSourcePath|
// resources.
const char kServerFilesDir[] = "ios/testing/data/http_server_files/";
// Path to a page containing the chromium logo and the text |kLogoPageText|.
const char kLogoPagePath[] = "/chromium_logo_page.html";
// Path to the chromium logo.
const char kLogoPageImageSourcePath[] = "/chromium_logo.png";
// The DOM element ID of the chromium image on the logo page.
const char kLogoPageChromiumImageId[] = "chromium_image";
// The text of the message on the logo page.
const char kLogoPageText[] = "Page with some text and the chromium logo image.";
// URL to a page with a static message.
const char kDestinationPageUrl[] = "/destination";
// HTML content of the destination page.
const char kDestinationHtml[] =
"<html><body><script>document.title='new doc'</script>"
"<center><span id=\"message\">You made it!</span></center>"
"</body></html>";
// The DOM element ID of the message on the destination page.
const char kDestinationPageTextId[] = "message";
// The text of the message on the destination page.
const char kDestinationPageText[] = "You made it!";
// URL to a page with a link to the destination page.
const char kInitialPageUrl[] = "/scenarioContextMenuOpenInNewTab";
// HTML content of a page with a link to the destination page.
const char kInitialPageHtml[] =
"<html><body><a style='margin-left:150px' href='/destination' id='link'>"
"link</a></body></html>";
// The DOM element ID of the link to the destination page.
const char kInitialPageDestinationLinkId[] = "link";
// The text of the link to the destination page.
const char kInitialPageDestinationLinkText[] = "link";
// Matcher for the open image button in the context menu.
id<GREYMatcher> OpenImageButton() {
return ButtonWithAccessibilityLabelId(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
}
// Matcher for the open image in new tab button in the context menu.
id<GREYMatcher> OpenImageInNewTabButton() {
return ButtonWithAccessibilityLabelId(
IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
}
// Provides responses for initial page and destination URLs.
std::unique_ptr<net::test_server::HttpResponse> StandardResponse(
const net::test_server::HttpRequest& request) {
std::unique_ptr<net::test_server::BasicHttpResponse> http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
if (request.relative_url == kInitialPageUrl) {
// The initial page contains a link to the destination page.
http_response->set_content(kInitialPageHtml);
} else if (request.relative_url == kDestinationPageUrl) {
http_response->set_content(kDestinationHtml);
} else {
return nullptr;
}
return std::move(http_response);
}
// Waits for the context menu item to disappear. TODO(crbug.com/682871): Remove
// this once EarlGrey is synchronized with context menu.
void WaitForContextMenuItemDisappeared(
id<GREYMatcher> context_menu_item_button) {
ConditionBlock condition = ^{
NSError* error = nil;
[[EarlGrey selectElementWithMatcher:context_menu_item_button]
assertWithMatcher:grey_nil()
error:&error];
return error == nil;
};
GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(
base::test::ios::kWaitForUIElementTimeout, condition),
@"Waiting for matcher %@ failed.", context_menu_item_button);
}
// Long press on |element_id| to trigger context menu.
void LongPressElement(const char* element_id) {
id<GREYMatcher> web_view_matcher =
web::WebViewInWebState(chrome_test_util::GetCurrentWebState());
[[EarlGrey selectElementWithMatcher:web_view_matcher]
performAction:chrome_test_util::LongPressElementForContextMenu(
[ElementSelector selectorWithElementID:element_id],
true /* menu should appear */)];
}
// Tap on |context_menu_item_button| context menu item.
void TapOnContextMenuButton(id<GREYMatcher> context_menu_item_button) {
[[EarlGrey selectElementWithMatcher:context_menu_item_button]
assertWithMatcher:grey_notNil()];
[[EarlGrey selectElementWithMatcher:context_menu_item_button]
performAction:grey_tap()];
WaitForContextMenuItemDisappeared(context_menu_item_button);
}
// A simple wrapper that sleeps for 1s to wait for the animation, triggered from
// opening a new tab through context menu, to finish before selecting tab.
// TODO(crbug.com/643792): Remove this function when the bug is fixed.
void SelectTabAtIndexInCurrentMode(NSUInteger index) {
// Delay for 1 second.
GREYCondition* myCondition = [GREYCondition conditionWithName:@"delay"
block:^BOOL {
return NO;
}];
[myCondition waitWithTimeout:1];
chrome_test_util::SelectTabAtIndexInCurrentMode(index);
}
} // namespace
// Context menu tests for Chrome.
@interface ContextMenuTestCase : ChromeTestCase
@end
@implementation ContextMenuTestCase
+ (void)setUp {
[super setUp];
CHROME_EG_ASSERT_NO_ERROR(
[ChromeEarlGrey setContentSettings:CONTENT_SETTING_ALLOW]);
}
+ (void)tearDown {
CHROME_EG_ASSERT_NO_ERROR(
[ChromeEarlGrey setContentSettings:CONTENT_SETTING_DEFAULT]);
[super tearDown];
}
- (void)setUp {
[super setUp];
self.testServer->RegisterRequestHandler(
base::BindRepeating(&StandardResponse));
self.testServer->ServeFilesFromSourceDirectory(
base::FilePath(kServerFilesDir));
GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
}
// Tests that selecting "Open Image" from the context menu properly opens the
// image in the current tab.
- (void)testOpenImageInCurrentTabFromContextMenu {
const GURL pageURL = self.testServer->GetURL(kLogoPagePath);
CHROME_EG_ASSERT_NO_ERROR([ChromeEarlGrey loadURL:pageURL]);
CHROME_EG_ASSERT_NO_ERROR(
[ChromeEarlGrey waitForWebStateContainingText:kLogoPageText]);
LongPressElement(kLogoPageChromiumImageId);
TapOnContextMenuButton(OpenImageButton());
CHROME_EG_ASSERT_NO_ERROR([ChromeEarlGrey waitForPageToFinishLoading]);
// Verify url.
const GURL imageURL = self.testServer->GetURL(kLogoPageImageSourcePath);
[[EarlGrey selectElementWithMatcher:OmniboxText(imageURL.GetContent())]
assertWithMatcher:grey_notNil()];
}
// Tests that selecting "Open Image in New Tab" from the context menu properly
// opens the image in a new background tab.
- (void)testOpenImageInNewTabFromContextMenu {
const GURL pageURL = self.testServer->GetURL(kLogoPagePath);
CHROME_EG_ASSERT_NO_ERROR([ChromeEarlGrey loadURL:pageURL]);
CHROME_EG_ASSERT_NO_ERROR(
[ChromeEarlGrey waitForWebStateContainingText:kLogoPageText]);
LongPressElement(kLogoPageChromiumImageId);
TapOnContextMenuButton(OpenImageInNewTabButton());
CHROME_EG_ASSERT_NO_ERROR([ChromeEarlGrey waitForMainTabCount:2]);
SelectTabAtIndexInCurrentMode(1U);
CHROME_EG_ASSERT_NO_ERROR([ChromeEarlGrey waitForPageToFinishLoading]);
// Verify url.
const GURL imageURL = self.testServer->GetURL(kLogoPageImageSourcePath);
[[EarlGrey selectElementWithMatcher:OmniboxText(imageURL.GetContent())]
assertWithMatcher:grey_notNil()];
}
// Tests "Open in New Tab" on context menu.
- (void)testContextMenuOpenInNewTab {
const GURL initialURL = self.testServer->GetURL(kInitialPageUrl);
CHROME_EG_ASSERT_NO_ERROR([ChromeEarlGrey loadURL:initialURL]);
CHROME_EG_ASSERT_NO_ERROR([ChromeEarlGrey
waitForWebStateContainingText:kInitialPageDestinationLinkText]);
LongPressElement(kInitialPageDestinationLinkId);
TapOnContextMenuButton(OpenLinkInNewTabButton());
CHROME_EG_ASSERT_NO_ERROR([ChromeEarlGrey waitForMainTabCount:2]);
SelectTabAtIndexInCurrentMode(1U);
CHROME_EG_ASSERT_NO_ERROR(
[ChromeEarlGrey waitForWebStateContainingText:kDestinationPageText]);
// Verify url.
const GURL destinationURL = self.testServer->GetURL(kDestinationPageUrl);
[[EarlGrey selectElementWithMatcher:OmniboxText(destinationURL.GetContent())]
assertWithMatcher:grey_notNil()];
}
// Tests that the context menu is displayed for an image url.
- (void)testContextMenuDisplayedOnImage {
const GURL imageURL = self.testServer->GetURL(kLogoPageImageSourcePath);
CHROME_EG_ASSERT_NO_ERROR([ChromeEarlGrey loadURL:imageURL]);
// Calculate a point inside the displayed image. Javascript can not be used to
// find the element because no DOM exists. If the viewport is adjusted using
// the contentInset, the top inset needs to be added to the touch point.
id<CRWWebViewProxy> webViewProxy =
chrome_test_util::GetCurrentWebState()->GetWebViewProxy();
BOOL usesContentInset =
webViewProxy.shouldUseViewContentInset ||
ios::GetChromeBrowserProvider()->GetFullscreenProvider()->IsInitialized();
CGFloat topInset = usesContentInset ? webViewProxy.contentInset.top : 0.0;
CGPoint point = CGPointMake(
CGRectGetMidX([chrome_test_util::GetActiveViewController() view].bounds),
topInset + 20.0);
id<GREYMatcher> web_view_matcher =
web::WebViewInWebState(chrome_test_util::GetCurrentWebState());
[[EarlGrey selectElementWithMatcher:web_view_matcher]
performAction:grey_longPressAtPointWithDuration(
point, kGREYLongPressDefaultDuration)];
TapOnContextMenuButton(OpenImageInNewTabButton());
CHROME_EG_ASSERT_NO_ERROR([ChromeEarlGrey waitForMainTabCount:2]);
SelectTabAtIndexInCurrentMode(1U);
CHROME_EG_ASSERT_NO_ERROR([ChromeEarlGrey waitForPageToFinishLoading]);
// Verify url.
[[EarlGrey selectElementWithMatcher:OmniboxText(imageURL.GetContent())]
assertWithMatcher:grey_notNil()];
}
// Tests that the element fetch duration is logged once.
- (void)testContextMenuElementFetchDurationMetric {
chrome_test_util::HistogramTester histogramTester;
const GURL pageURL = self.testServer->GetURL(kLogoPagePath);
CHROME_EG_ASSERT_NO_ERROR([ChromeEarlGrey loadURL:pageURL]);
CHROME_EG_ASSERT_NO_ERROR(
[ChromeEarlGrey waitForWebStateContainingText:kLogoPageText]);
LongPressElement(kLogoPageChromiumImageId);
TapOnContextMenuButton(OpenImageButton());
CHROME_EG_ASSERT_NO_ERROR([ChromeEarlGrey waitForPageToFinishLoading]);
histogramTester.ExpectTotalCount("ContextMenu.DOMElementFetchDuration", 1,
^(NSString* error) {
GREYFail(error);
});
}
// Tests that system touches are cancelled when the context menu is shown.
- (void)testContextMenuCancelSystemTouchesMetric {
chrome_test_util::HistogramTester histogramTester;
const GURL pageURL = self.testServer->GetURL(kLogoPagePath);
CHROME_EG_ASSERT_NO_ERROR([ChromeEarlGrey loadURL:pageURL]);
CHROME_EG_ASSERT_NO_ERROR(
[ChromeEarlGrey waitForWebStateContainingText:kLogoPageText]);
LongPressElement(kLogoPageChromiumImageId);
TapOnContextMenuButton(OpenImageButton());
CHROME_EG_ASSERT_NO_ERROR([ChromeEarlGrey waitForPageToFinishLoading]);
// Verify that system touches were cancelled.
histogramTester.ExpectTotalCount("ContextMenu.CancelSystemTouches", 1,
^(NSString* error) {
GREYFail(error);
});
}
// Tests that the system selected text callout is displayed instead of the
// context menu when user long presses on plain text.
- (void)testContextMenuSelectedTextCallout {
chrome_test_util::HistogramTester histogramTester;
// Load the destination page directly because it has a plain text message on
// it.
const GURL destinationURL = self.testServer->GetURL(kDestinationPageUrl);
CHROME_EG_ASSERT_NO_ERROR([ChromeEarlGrey loadURL:destinationURL]);
CHROME_EG_ASSERT_NO_ERROR(
[ChromeEarlGrey waitForWebStateContainingText:kDestinationPageText]);
LongPressElement(kDestinationPageTextId);
// Verify that context menu is not shown.
[[EarlGrey selectElementWithMatcher:ContextMenuCopyButton()]
assertWithMatcher:grey_nil()];
// Verify that system text selection callout is displayed.
[[[EarlGrey selectElementWithMatcher:SystemSelectionCalloutCopyButton()]
inRoot:SystemSelectionCallout()] assertWithMatcher:grey_notNil()];
// Verify that system touches were not cancelled.
histogramTester.ExpectTotalCount("ContextMenu.CancelSystemTouches", 0,
^(NSString* error) {
GREYFail(error);
});
}
// Tests cancelling the context menu.
- (void)testDismissContextMenu {
const GURL initialURL = self.testServer->GetURL(kInitialPageUrl);
CHROME_EG_ASSERT_NO_ERROR([ChromeEarlGrey loadURL:initialURL]);
CHROME_EG_ASSERT_NO_ERROR([ChromeEarlGrey
waitForWebStateContainingText:kInitialPageDestinationLinkText]);
// Display the context menu twice.
for (NSInteger i = 0; i < 2; i++) {
LongPressElement(kInitialPageDestinationLinkId);
// Make sure the context menu appeared.
[[EarlGrey selectElementWithMatcher:OpenLinkInNewTabButton()]
assertWithMatcher:grey_notNil()];
if (IsIPadIdiom()) {
// Tap the tools menu to dismiss the popover.
[[EarlGrey selectElementWithMatcher:chrome_test_util::ToolsMenuButton()]
performAction:grey_tap()];
} else {
TapOnContextMenuButton(chrome_test_util::CancelButton());
}
// Make sure the context menu disappeared.
[[EarlGrey selectElementWithMatcher:OpenLinkInNewTabButton()]
assertWithMatcher:grey_nil()];
}
// Display the context menu one last time.
LongPressElement(kInitialPageDestinationLinkId);
// Make sure the context menu appeared.
[[EarlGrey selectElementWithMatcher:OpenLinkInNewTabButton()]
assertWithMatcher:grey_notNil()];
}
// Checks that all the options are displayed in the context menu.
- (void)testAppropriateContextMenu {
const GURL initialURL = self.testServer->GetURL(kInitialPageUrl);
CHROME_EG_ASSERT_NO_ERROR([ChromeEarlGrey loadURL:initialURL]);
CHROME_EG_ASSERT_NO_ERROR([ChromeEarlGrey
waitForWebStateContainingText:kInitialPageDestinationLinkText]);
LongPressElement(kInitialPageDestinationLinkId);
// Check the different buttons.
[[EarlGrey
selectElementWithMatcher:chrome_test_util::ButtonWithAccessibilityLabelId(
IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB)]
assertWithMatcher:grey_sufficientlyVisible()];
[[EarlGrey selectElementWithMatcher:
chrome_test_util::ButtonWithAccessibilityLabelId(
IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB)]
assertWithMatcher:grey_sufficientlyVisible()];
[[EarlGrey
selectElementWithMatcher:chrome_test_util::ButtonWithAccessibilityLabelId(
IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST)]
assertWithMatcher:grey_sufficientlyVisible()];
[[EarlGrey
selectElementWithMatcher:chrome_test_util::ButtonWithAccessibilityLabelId(
IDS_IOS_CONTENT_CONTEXT_COPY)]
assertWithMatcher:grey_sufficientlyVisible()];
if (!IsIPadIdiom()) {
[[EarlGrey selectElementWithMatcher:
chrome_test_util::ButtonWithAccessibilityLabelId(IDS_CANCEL)]
assertWithMatcher:grey_sufficientlyVisible()];
}
}
@end