chromium/ios/chrome/browser/tabs/ui_bundled/tab_strip_egtest.mm

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import <XCTest/XCTest.h>

#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/tabs/ui_bundled/tab_strip_constants.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/earl_grey_test.h"
#import "net/test/embedded_test_server/embedded_test_server.h"
#import "net/test/embedded_test_server/http_request.h"
#import "net/test/embedded_test_server/http_response.h"

namespace {

// URL to a destination page with a static message.
const char kDestinationPageUrl[] = "/destination";
// HTML content of the destination page.
const char kDestinationHtml[] =
    "<html><head><meta name='viewport' content='width=device-width, "
    "initial-scale=1.0, maximum-scale=1.0, user-scalable=no' "
    "/></head><body><script>document.title='new doc'</script>"
    "<center><span id=\"message\">You made it!</span></center>"
    "</body></html>";
// Visible content of the destination page.
const char kDestinationContent[] = "You made it!";

// URL to an initial page with a link to the destination page.
const char kInitialPageUrl[] = "/scenarioDragURLToTabStrip";
// HTML content of an initial page with a link to the destination page.
// Note that this string contains substrings that must exactly match the next
// two constants (`kInitialPageLinkId` and `kInitialPageDestinationLinkText`).
// If these were more complex or more liable to change, a sprintf template and
// runtime composition should be used instead.
const char kInitialPageHtml[] =
    "<html><head><meta name='viewport' content='width=device-width, "
    "initial-scale=1.0, maximum-scale=1.0, user-scalable=no' /></head><body><a "
    "style='margin-left:150px' href='/destination' id='link' aria-label='link'>"
    "link</a></body></html>";

// Accessibility ID of the link on the initial page. The `aria-label` attribute
// makes this visible to UIKit as an accessibility ID.
NSString* const kInitialPageLinkId = @"link";

// The text of the link to the destination page.
const char kInitialPageDestinationLinkText[] = "link";

// 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) {
    http_response->set_content(kInitialPageHtml);
  } else if (request.relative_url == kDestinationPageUrl) {
    http_response->set_content(kDestinationHtml);
  } else {
    return nullptr;
  }

  return std::move(http_response);
}

// Finds the element with the given `identifier` of given `type`.
XCUIElement* GetElementMatchingIdentifier(XCUIApplication* app,
                                          NSString* identifier,
                                          XCUIElementType type) {
  XCUIElementQuery* query = [[app.windows.firstMatch
      descendantsMatchingType:type] matchingIdentifier:identifier];
  return [query elementBoundByIndex:0];
}

// Drags and drops the element with the given `src_identifier` identifier in the
// tab strip with the given `tab_strip_identifier` identifier.
void DragDrop(NSString* src_identifier, NSString* tab_strip_identifier) {
  XCUIApplication* app = [[XCUIApplication alloc] init];
  XCUIElement* src_element =
      GetElementMatchingIdentifier(app, src_identifier, XCUIElementTypeAny);
  XCUICoordinate* start_point =
      [src_element coordinateWithNormalizedOffset:CGVectorMake(0.5, 0.5)];

  XCUIElement* dst_element = GetElementMatchingIdentifier(
      app, tab_strip_identifier, XCUIElementTypeAny);
  XCUICoordinate* end_point =
      [dst_element coordinateWithNormalizedOffset:CGVectorMake(0.5, 0.75)];

  [start_point pressForDuration:1.5
           thenDragToCoordinate:end_point
                   withVelocity:XCUIGestureVelocityDefault
            thenHoldForDuration:1.0];
}

}  // namespace

// Tests for the tab strip shown on iPad.
@interface LegacyTabStripTestCase : ChromeTestCase
@end

@implementation LegacyTabStripTestCase

- (AppLaunchConfiguration)appConfigurationForTestCase {
  AppLaunchConfiguration config;
  config.features_disabled.push_back(kModernTabStrip);
  return config;
}

- (void)setUp {
  [super setUp];
  self.testServer->RegisterRequestHandler(
      base::BindRepeating(&StandardResponse));
  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
}

// Test switching tabs using the tab strip.
- (void)testTabStripSwitchTabs {
  if ([ChromeEarlGrey isCompactWidth]) {
    EARL_GREY_TEST_SKIPPED(@"No tab strip on this device.");
  }

  // TODO(crbug.com/41010830):  Make this test also handle the 'collapsed' tab
  // case.
  const int kNumberOfTabs = 3;
  [ChromeEarlGreyUI openNewTab];
  [ChromeEarlGrey loadURL:GURL("chrome://about")];
  [ChromeEarlGreyUI openNewTab];
  [ChromeEarlGrey loadURL:GURL("chrome://version")];

  // Note that the tab ordering wraps.  E.g. if A, B, and C are open,
  // and C is the current tab, the 'next' tab is 'A'.
  for (int i = 0; i < kNumberOfTabs + 1; i++) {
    GREYAssertTrue([ChromeEarlGrey mainTabCount] > 1,
                   [ChromeEarlGrey mainTabCount] ? @"Only one tab open."
                                                 : @"No more tabs.");
    NSString* nextTabTitle = [ChromeEarlGrey nextTabTitle];

    [[EarlGrey
        selectElementWithMatcher:grey_allOf(grey_text(nextTabTitle),
                                            grey_sufficientlyVisible(), nil)]
        performAction:grey_tap()];

    GREYAssertEqualObjects([ChromeEarlGrey currentTabTitle], nextTabTitle,
                           @"The selected tab did not change to the next tab.");
  }
}

// Tests dragging URL into regular tab strip.
// TODO(crbug.com/330842850): Test is flaky.
#if TARGET_IPHONE_SIMULATOR
#define MAYBE_testDragAndDropURLIntoRegularTabStrip \
  FLAKY_testDragAndDropURLIntoRegularTabStrip
#else
#define MAYBE_testDragAndDropURLIntoRegularTabStrip \
  testDragAndDropURLIntoRegularTabStrip
#endif
- (void)MAYBE_testDragAndDropURLIntoRegularTabStrip {
  if ([ChromeEarlGrey isCompactWidth]) {
    EARL_GREY_TEST_SKIPPED(@"No tab strip on this device.");
  }

  const GURL initialURL = self.testServer->GetURL(kInitialPageUrl);
  [ChromeEarlGrey loadURL:initialURL];
  [ChromeEarlGrey
      waitForWebStateContainingText:kInitialPageDestinationLinkText];
  [ChromeEarlGrey waitForMainTabCount:1];

  // Drag and drop the link on the page to the tab strip. This should open
  // a new regular tab.
  DragDrop(kInitialPageLinkId, kRegularTabStripId);
  GREYWaitForAppToIdle(@"App failed to idle");

  [ChromeEarlGrey waitForWebStateContainingText:kDestinationContent];
  [ChromeEarlGrey waitForMainTabCount:2];
}

// Tests dragging URL into incognito tab strip.
- (void)testDragAndDropURLIntoIncognitoTabStrip {
  if ([ChromeEarlGrey isCompactWidth]) {
    EARL_GREY_TEST_SKIPPED(@"No tab strip on this device.");
  }

  [ChromeEarlGrey closeAllNormalTabs];
  [ChromeEarlGrey openNewIncognitoTab];

  const GURL initialURL = self.testServer->GetURL(kInitialPageUrl);
  [ChromeEarlGrey loadURL:initialURL];
  [ChromeEarlGrey
      waitForWebStateContainingText:kInitialPageDestinationLinkText];
  [ChromeEarlGrey waitForIncognitoTabCount:1];

  // Drag and drop the link on the page to the tab strip. This should open
  // a new incognito tab.
  DragDrop(kInitialPageLinkId, kIncognitoTabStripId);
  GREYWaitForAppToIdle(@"App failed to idle");

  [ChromeEarlGrey waitForWebStateContainingText:kDestinationContent];
  [ChromeEarlGrey waitForIncognitoTabCount:2];
}

@end