chromium/ios/chrome/browser/metrics/model/tab_usage_recorder_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 <memory>

#import "base/functional/bind.h"
#import "base/strings/stringprintf.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "base/time/time.h"
#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/metrics/model/metrics_app_interface.h"
#import "ios/chrome/browser/metrics/model/tab_usage_recorder_metrics.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_matchers.h"
#import "ios/chrome/test/earl_grey/web_http_server_chrome_test_case.h"
#import "ios/chrome/test/scoped_eg_synchronization_disabler.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "ios/web/public/test/element_selector.h"
#import "ios/web/public/test/http_server/delayed_response_provider.h"
#import "ios/web/public/test/http_server/html_response_provider.h"
#import "ios/web/public/test/http_server/http_server.h"
#import "ios/web/public/test/http_server/http_server_util.h"
#import "ui/base/l10n/l10n_util_mac.h"

using chrome_test_util::OpenLinkInNewTabButton;
using chrome_test_util::SettingsDestinationButton;
using chrome_test_util::SettingsDoneButton;
using chrome_test_util::SettingsMenuPrivacyButton;
using chrome_test_util::TabGridCellAtIndex;
using chrome_test_util::WebViewMatcher;

namespace {

const char kTestUrl1[] =
    "http://ios/testing/data/http_server_files/memory_usage.html";
const char kURL1FirstWord[] = "Page";
const char kTestUrl2[] =
    "http://ios/testing/data/http_server_files/fullscreen.html";
const char kURL2FirstWord[] = "Rugby";
NSString* const kClearPageScript = @"document.body.innerHTML='';";

// The delay to use to serve slow URLs.
constexpr base::TimeDelta kSlowURLDelay = base::Seconds(3);

// The delay to use to wait for pate starting loading.
constexpr base::TimeDelta kWaitForPageLoadTimeout = base::Seconds(3);

// The delay to use to serve very slow URLS -- tests using this delay expect the
// page to never load.
constexpr base::TimeDelta kVerySlowURLDelay = base::Seconds(20);

// The delay to wait for an element to appear before tapping on it.
constexpr base::TimeDelta kWaitElementTimeout = base::Seconds(3);

// Wait until `matcher` is accessible (not nil).
void Wait(id<GREYMatcher> matcher, NSString* name) {
  ConditionBlock condition = ^{
    NSError* error = nil;
    [[EarlGrey selectElementWithMatcher:matcher] assertWithMatcher:grey_notNil()
                                                             error:&error];
    return error == nil;
  };
  GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(kWaitElementTimeout,
                                                          condition),
             @"Waiting for matcher %@ failed.", name);
}

// Creates a new main tab and load `url`. Wait until `word` is visible on the
// page.
void NewMainTabWithURL(const GURL& url, const std::string& word) {
  int number_of_tabs = [ChromeEarlGrey mainTabCount];
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:url];
  [ChromeEarlGrey waitForWebStateContainingText:word];
  [ChromeEarlGrey waitForMainTabCount:(number_of_tabs + 1)];
}

// Opens 2 new tabs with different URLs.
void OpenTwoTabs() {
  [ChromeEarlGrey closeAllTabsInCurrentMode];
  const GURL url1 = web::test::HttpServer::MakeUrl(kTestUrl1);
  const GURL url2 = web::test::HttpServer::MakeUrl(kTestUrl2);
  NewMainTabWithURL(url1, kURL1FirstWord);
  NewMainTabWithURL(url2, kURL2FirstWord);
}

// Closes a tab in the current tab model. Synchronize on tab number afterwards.
void CloseTabAtIndexAndSync(NSUInteger i) {
  NSUInteger nb_main_tab = [ChromeEarlGrey mainTabCount];
  [ChromeEarlGrey closeTabAtIndex:i];
  ConditionBlock condition = ^{
    return [ChromeEarlGrey mainTabCount] == (nb_main_tab - 1);
  };
  GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(kWaitElementTimeout,
                                                          condition),
             @"Waiting for tab to close");
}

void SwitchToNormalMode() {
  GREYAssertTrue([ChromeEarlGrey isIncognitoMode],
                 @"Switching to normal mode is only allowed from Incognito.");

  // Enter the tab grid to switch modes.
  [ChromeEarlGrey showTabSwitcher];

  // Switch modes and exit the tab grid.
  const int tab_index = [ChromeEarlGrey indexOfActiveNormalTab];
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::TabGridOpenTabsPanelButton()]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:TabGridCellAtIndex(tab_index)]
      performAction:grey_tap()];

  BOOL success = NO;
  // Turn off synchronization of GREYAssert to test the pending states.
  {
    ScopedSynchronizationDisabler disabler;
    success =
        base::test::ios::WaitUntilConditionOrTimeout(kWaitElementTimeout, ^{
          return ![ChromeEarlGrey isIncognitoMode];
        });
  }

  if (!success) {
    // TODO(crbug.com/40622599): Avoid asserting directly unless the test fails,
    // due to timing issues.
    GREYFail(@"Failed to switch to normal mode.");
  }
}

}  // namespace

// Test for the TabUsageRecorder class.
@interface TabUsageRecorderTestCase : WebHttpServerChromeTestCase
@end

@implementation TabUsageRecorderTestCase

- (void)setUp {
  [super setUp];
  GREYAssertNil([MetricsAppInterface setupHistogramTester],
                @"Cannot setup histogram tester.");
  [ChromeEarlGrey removeBrowsingCache];
}

- (void)tearDown {
  GREYAssertNil([MetricsAppInterface releaseHistogramTester],
                @"Cannot reset histogram tester.");
  [super tearDown];
}

// Tests that the recorder actual recorde tab state.
// TODO(crbug.com/41442581) The test is flaky.
- (void)DISABLED_testTabSwitchRecorder {
  [ChromeEarlGrey resetTabUsageRecorder];

  // Open two tabs with urls.
  OpenTwoTabs();
  [ChromeEarlGrey waitForMainTabCount:2];
  // Switch between the two tabs.  Both are currently in memory.
  [ChromeEarlGrey selectTabAtIndex:0];

  // Verify that one in-memory tab switch has been recorded.
  // histogramTester.ExpectTotalCount(kSelectedTabHistogramName, 1,
  // failureBlock);
  NSError* error = [MetricsAppInterface
      expectTotalCount:1
          forHistogram:@(tab_usage_recorder::kSelectedTabHistogramName)];
  if (error) {
    GREYFail([error description]);
  }
  error = [MetricsAppInterface
      expectUniqueSampleWithCount:1
                        forBucket:tab_usage_recorder::IN_MEMORY
                     forHistogram:
                         @(tab_usage_recorder::kSelectedTabHistogramName)];
  if (error) {
    GREYFail([error description]);
  }

  // Evict the tab.
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey evictOtherBrowserTabs];

  GREYAssertTrue([ChromeEarlGrey isIncognitoMode],
                 @"Failed to switch to incognito mode");

  // Switch back to the normal tabs. Should be on tab one.
  SwitchToNormalMode();

  [ChromeEarlGrey waitForWebStateContainingText:kURL1FirstWord];

  error = [MetricsAppInterface
      expectTotalCount:2
          forHistogram:@(tab_usage_recorder::kSelectedTabHistogramName)];
  if (error) {
    GREYFail([error description]);
  }
  error = [MetricsAppInterface
       expectCount:1
         forBucket:tab_usage_recorder::EVICTED
      forHistogram:@(tab_usage_recorder::kSelectedTabHistogramName)];
  if (error) {
    GREYFail([error description]);
  }
}

// Verifies the UMA metric for page loads before a tab eviction by loading
// some tabs, forcing a tab eviction, then checking the histogram.
- (void)testPageLoadCountBeforeEvictedTab {
  [ChromeEarlGrey resetTabUsageRecorder];
  const GURL url1 = web::test::HttpServer::MakeUrl(kTestUrl1);
  // This test opens three tabs.
  const int numberOfTabs = 3;
  [ChromeEarlGrey closeAllTabsInCurrentMode];

  // Open three tabs with http:// urls.
  for (NSUInteger i = 0; i < numberOfTabs; i++) {
    [ChromeEarlGrey openNewTab];
    [ChromeEarlGrey loadURL:url1];
    [ChromeEarlGrey waitForWebStateContainingText:kURL1FirstWord];
    [ChromeEarlGrey waitForMainTabCount:(i + 1)];
  }

  // Switch between the tabs. They are currently in memory.
  [ChromeEarlGrey selectTabAtIndex:0];

  // Verify that no page-load count has been recorded.
  NSError* error = [MetricsAppInterface
      expectTotalCount:0
          forHistogram:
              @(tab_usage_recorder::kPageLoadsBeforeEvictedTabSelected)];

  if (error) {
    GREYFail([error description]);
  }

  // Reload each tab.
  for (NSUInteger i = 0; i < numberOfTabs; i++) {
    [ChromeEarlGrey selectTabAtIndex:i];
    // Clear the page so that we can check when page reload is complete.
    [ChromeEarlGrey evaluateJavaScriptForSideEffect:kClearPageScript];

    [ChromeEarlGrey reload];
    [ChromeEarlGrey waitForWebStateContainingText:kURL1FirstWord];
  }

  // Evict the tab. Create a dummy tab so that switching back to normal mode
  // does not trigger a reload immediately.
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey evictOtherBrowserTabs];
  [ChromeEarlGrey waitForIncognitoTabCount:1];

  // Switch back to the normal tabs. Should be on tab one.
  SwitchToNormalMode();

  [ChromeEarlGrey selectTabAtIndex:0];
  [ChromeEarlGrey waitForWebStateContainingText:kURL1FirstWord];

  // Verify that one page-load count has been recorded. It should contain two
  // page loads for each tab created.
  error = [MetricsAppInterface
      expectTotalCount:1
          forHistogram:
              @(tab_usage_recorder::kPageLoadsBeforeEvictedTabSelected)];

  if (error) {
    GREYFail([error description]);
  }

  error = [MetricsAppInterface
         expectSum:numberOfTabs * 2
      forHistogram:@(tab_usage_recorder::kPageLoadsBeforeEvictedTabSelected)];
  if (error) {
    GREYFail([error description]);
  }
}

// Tests that tabs reloaded on cold start are reported as
// EVICTED_DUE_TO_COLD_START.
// TODO(crbug.com/41442581) The test is disabled due to flakiness.
- (void)DISABLED_testColdLaunchReloadCount {
  [ChromeEarlGrey resetTabUsageRecorder];

  // Open two tabs with urls.
  OpenTwoTabs();
  [ChromeEarlGrey waitForMainTabCount:2];

  // Set the normal tabs as 'cold start' tabs.
  [ChromeEarlGrey setCurrentTabsToBeColdStartTabs];

  // Open two incognito tabs with urls, clearing normal tabs from memory.
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey evictOtherBrowserTabs];
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey evictOtherBrowserTabs];

  [ChromeEarlGrey waitForIncognitoTabCount:2];

  // Switch back to the normal tabs.
  SwitchToNormalMode();
  {
    ScopedSynchronizationDisabler disabler;
    Wait(chrome_test_util::ToolsMenuButton(), @"Tool Menu");
  }
  [ChromeEarlGrey waitForWebStateContainingText:kURL2FirstWord];

  // Select the other one so it also reloads.
  [ChromeEarlGrey selectTabAtIndex:0];
  [ChromeEarlGrey waitForWebStateContainingText:kURL1FirstWord];

  // Make sure that one of the 2 tab loads (excluding the selected tab) is
  // counted as a cold start eviction.
  NSError* error = [MetricsAppInterface
       expectCount:1
         forBucket:tab_usage_recorder::EVICTED_DUE_TO_COLD_START
      forHistogram:@(tab_usage_recorder::kSelectedTabHistogramName)];
  if (error) {
    GREYFail([error description]);
  }
  error = [MetricsAppInterface
       expectCount:0
         forBucket:tab_usage_recorder::IN_MEMORY
      forHistogram:@(tab_usage_recorder::kSelectedTabHistogramName)];
  if (error) {
    GREYFail([error description]);
  }

  // Re-select the same tab and make sure it is not counted again as evicted.
  [ChromeEarlGrey selectTabAtIndex:1];
  [ChromeEarlGrey selectTabAtIndex:0];

  [ChromeEarlGrey waitForWebStateContainingText:kURL1FirstWord];
  error = [MetricsAppInterface
       expectCount:1
         forBucket:tab_usage_recorder::EVICTED_DUE_TO_COLD_START
      forHistogram:@(tab_usage_recorder::kSelectedTabHistogramName)];
  if (error) {
    GREYFail([error description]);
  }
  error = [MetricsAppInterface
       expectCount:2
         forBucket:tab_usage_recorder::IN_MEMORY
      forHistogram:@(tab_usage_recorder::kSelectedTabHistogramName)];
  if (error) {
    GREYFail([error description]);
  }
}

// Tests that tabs reloads after backgrounding and eviction.
// TODO(crbug.com/41442581) The test is flaky.
- (void)DISABLED_testBackgroundingReloadCount {
  [ChromeEarlGrey resetTabUsageRecorder];

  // Open two tabs with urls.
  OpenTwoTabs();
  [ChromeEarlGrey waitForMainTabCount:2];

  // Simulate going into the background.
  [ChromeEarlGrey simulateTabsBackgrounding];

  // Open incognito and clear normal tabs from memory.
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey evictOtherBrowserTabs];
  GREYAssertTrue([ChromeEarlGrey isIncognitoMode],
                 @"Failed to switch to incognito mode");

  // Switch back to the normal tabs.
  SwitchToNormalMode();
  // Wait for the page starting to load. It is possible that the page finish
  // loading before this test. In that case the wait will timeout. Ignore the
  // result.
  bool unused =
      base::test::ios::WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
        return [ChromeEarlGrey isLoading];
      });
  (void)unused;

  [ChromeEarlGrey waitForWebStateContainingText:kURL2FirstWord];

  const GURL url1 = web::test::HttpServer::MakeUrl(kTestUrl1);
  const GURL url2 = web::test::HttpServer::MakeUrl(kTestUrl2);
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::OmniboxText(url2.GetContent())]
      assertWithMatcher:grey_notNil()];

  [ChromeEarlGrey selectTabAtIndex:0];
  [ChromeEarlGrey waitForWebStateContainingText:kURL1FirstWord];
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::OmniboxText(url1.GetContent())]
      assertWithMatcher:grey_notNil()];
}

// Test that USER_DID_NOT_WAIT is reported if the user does not wait for the
// reload to be complete after eviction.
- (void)testEvictedTabSlowReload {
  std::map<GURL, std::string> responses;
  const GURL slowURL = web::test::HttpServer::MakeUrl("http://slow");
  responses[slowURL] = "Slow Page";
  web::test::SetUpHttpServer(std::make_unique<web::DelayedResponseProvider>(
      std::make_unique<HtmlResponseProvider>(responses), kSlowURLDelay));

  // A blank tab needed to switch to it after reloading.
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:slowURL waitForCompletion:YES];
  // Wait for the page starting to load. It is possible that the page finish
  // loading before this test. In that case the wait will timeout. Ignore the
  // result.
  bool unused =
      base::test::ios::WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
        return [ChromeEarlGrey isLoading];
      });
  (void)unused;
  [ChromeEarlGrey waitForPageToFinishLoading];

  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey evictOtherBrowserTabs];
  [ChromeEarlGrey removeBrowsingCache];

  SwitchToNormalMode();
  // TODO(crbug.com/41271925): EarlGrey synchronize on some animations when a
  // page is loading. Need to handle synchronization manually for this test.
  {
    ScopedSynchronizationDisabler disabler;
    Wait(chrome_test_util::ToolsMenuButton(), @"Tool Menu");
    // This method is not synced on EarlGrey.
    [ChromeEarlGrey selectTabAtIndex:0];
    // Wait for the page starting to load. It is possible that the page finish
    // loading before this test. In that case the wait will timeout. Ignore the
    // result.
    unused =
        base::test::ios::WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
          return [ChromeEarlGrey isLoading];
        });
    (void)unused;
  }
}

// Test that the USER_DID_NOT_WAIT metric is logged when the user opens an NTP
// while the evicted tab is still reloading.
- (void)testEvictedTabReloadSwitchToNTP {
  std::map<GURL, std::string> responses;
  const GURL slowURL = web::test::HttpServer::MakeUrl("http://slow");
  responses[slowURL] = "Slow Page";

  web::test::SetUpHttpServer(std::make_unique<web::DelayedResponseProvider>(
      std::make_unique<HtmlResponseProvider>(responses), kSlowURLDelay));

  NewMainTabWithURL(slowURL, "Slow");
  // Wait for the page starting to load. It is possible that the page finish
  // loading before this test. In that case the wait will timeout. Ignore the
  // result.
  bool unused =
      base::test::ios::WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
        return [ChromeEarlGrey isLoading];
      });
  (void)unused;
  [ChromeEarlGrey waitForPageToFinishLoading];
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey evictOtherBrowserTabs];

  [ChromeEarlGrey removeBrowsingCache];

  SwitchToNormalMode();
  // Wait for the page starting to load. It is possible that the page finish
  // loading before this test. In that case the wait will timeout. Ignore the
  // result.
  unused =
      base::test::ios::WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
        return [ChromeEarlGrey isLoading];
      });
  (void)unused;
  // TODO(crbug.com/41271925): EarlGrey synchronize on some animations when a
  // page is loading. Need to handle synchronization manually for this test.
  {
    ScopedSynchronizationDisabler disabler;
    Wait(chrome_test_util::ToolsMenuButton(), @"Tool Menu");
  }

  [ChromeEarlGrey openNewTab];
}

// Test that the USER_DID_NOT_WAIT metric is not logged when the user opens
// and closes the settings UI while the evicted tab is still reloading.
- (void)testEvictedTabReloadSettingsAndBack {
  std::map<GURL, std::string> responses;
  const GURL slowURL = web::test::HttpServer::MakeUrl("http://slow");
  responses[slowURL] = "Slow Page";

  web::test::SetUpHttpServer(std::make_unique<web::DelayedResponseProvider>(
      std::make_unique<HtmlResponseProvider>(responses), kSlowURLDelay));
  NewMainTabWithURL(slowURL, responses[slowURL]);
  // Wait for the page starting to load. It is possible that the page finish
  // loading before this test. In that case the wait will timeout. Ignore the
  // result.
  bool unused =
      base::test::ios::WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
        return [ChromeEarlGrey isLoading];
      });
  (void)unused;
  [ChromeEarlGrey waitForPageToFinishLoading];
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey evictOtherBrowserTabs];

  [ChromeEarlGrey removeBrowsingCache];
  SwitchToNormalMode();
  // Wait for the page starting to load. It is possible that the page finish
  // loading before this test. In that case the wait will timeout. Ignore the
  // result.
  unused =
      base::test::ios::WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
        return [ChromeEarlGrey isLoading];
      });
  (void)unused;
  [ChromeEarlGreyUI openSettingsMenu];

  [ChromeEarlGreyUI tapSettingsMenuButton:SettingsMenuPrivacyButton()];
  [[EarlGrey selectElementWithMatcher:SettingsDoneButton()]
      performAction:grey_tap()];
  [ChromeEarlGrey waitForWebStateContainingText:responses[slowURL]];
}

// Tests that leaving Chrome while an evicted tab is reloading triggers the
// recording of the USER_LEFT_CHROME metric.
- (void)testEvictedTabReloadBackgrounded {
  std::map<GURL, std::string> responses;
  const GURL slowURL = web::test::HttpServer::MakeUrl("http://slow");
  responses[slowURL] = "Slow Page";

  web::test::SetUpHttpServer(std::make_unique<HtmlResponseProvider>(responses));

  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:slowURL waitForCompletion:YES];
  // Wait for the page starting to load. It is possible that the page finish
  // loading before this test. In that case the wait will timeout. Ignore the
  // result.
  bool unused =
      base::test::ios::WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
        return [ChromeEarlGrey isLoading];
      });
  (void)unused;
  [ChromeEarlGrey waitForPageToFinishLoading];

  int nb_incognito_tab = [ChromeEarlGrey incognitoTabCount];
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey waitForIncognitoTabCount:(nb_incognito_tab + 1)];
  [ChromeEarlGrey evictOtherBrowserTabs];
  GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(
                 kWaitElementTimeout,
                 ^{
                   return [ChromeEarlGrey isIncognitoMode];
                 }),
             @"Fail to switch to incognito mode.");

  web::test::SetUpHttpServer(std::make_unique<web::DelayedResponseProvider>(
      std::make_unique<HtmlResponseProvider>(responses), kVerySlowURLDelay));

  [ChromeEarlGrey removeBrowsingCache];

  SwitchToNormalMode();
  // Wait for the page starting to load. It is possible that the page finish
  // loading before this test. In that case the wait will timeout. Ignore the
  // result.
  unused =
      base::test::ios::WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
        return [ChromeEarlGrey isLoading];
      });
  (void)unused;

  {
    ScopedSynchronizationDisabler disabler;
    Wait(chrome_test_util::ToolsMenuButton(), @"Tool Menu");

    [ChromeEarlGrey simulateTabsBackgrounding];
  }
}

// Tests that redirecting pages are not reloaded after eviction.
- (void)testPageRedirect {
  GURL redirectURL = web::test::HttpServer::MakeUrl(
      "http://ios/testing/data/http_server_files/redirect_refresh.html");
  GURL destinationURL = web::test::HttpServer::MakeUrl(
      "http://ios/testing/data/http_server_files/destination.html");
  [ChromeEarlGrey resetTabUsageRecorder];

  NewMainTabWithURL(redirectURL, "arrived");

  [[EarlGrey selectElementWithMatcher:chrome_test_util::OmniboxText(
                                          destinationURL.GetContent())]
      assertWithMatcher:grey_notNil()];

  NSUInteger tabIndex = [ChromeEarlGrey mainTabCount] - 1;
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey evictOtherBrowserTabs];

  SwitchToNormalMode();

  [ChromeEarlGrey selectTabAtIndex:tabIndex];
  [ChromeEarlGrey waitForWebStateContainingText:"arrived"];

  // Verify that one page-load count has been recorded.  It should contain a
  // sum of 1 - one sample with 1 page load.
  NSError* error = [MetricsAppInterface
      expectTotalCount:1
          forHistogram:
              @(tab_usage_recorder::kPageLoadsBeforeEvictedTabSelected)];
  if (error) {
    GREYFail([error description]);
  }

  error = [MetricsAppInterface
         expectSum:1
      forHistogram:@(tab_usage_recorder::kPageLoadsBeforeEvictedTabSelected)];
  if (error) {
    GREYFail([error description]);
  }
}

// Tests that navigations are correctly reported in
// Tab.PageLoadsSinceLastSwitchToEvictedTab histogram.
- (void)testLinkClickNavigation {
  // Create map of canned responses and set up the test HTML server.
  std::map<GURL, std::string> responses;
  const GURL initialURL =
      web::test::HttpServer::MakeUrl("http://scenarioTestLinkClickNavigation");
  const GURL destinationURL =
      web::test::HttpServer::MakeUrl("http://destination");
  responses[initialURL] = base::StringPrintf(
      "<body><a style='margin-left:50px' href='%s' id='link'>link</a></body>",
      destinationURL.spec().c_str());
  responses[destinationURL] = "Whee!";
  web::test::SetUpHttpServer(std::make_unique<HtmlResponseProvider>(responses));
  [ChromeEarlGrey resetTabUsageRecorder];

  // Open a tab with a link to click.
  NewMainTabWithURL(initialURL, "link");
  // Click the link.
  [ChromeEarlGrey tapWebStateElementWithID:@"link"];

  [ChromeEarlGrey waitForWebStateContainingText:"Whee"];
  NSUInteger tabIndex = [ChromeEarlGrey mainTabCount] - 1;

  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey evictOtherBrowserTabs];
  SwitchToNormalMode();

  [ChromeEarlGrey selectTabAtIndex:tabIndex];
  // Wait for the page starting to load. It is possible that the page finish
  // loading before this test. In that case the wait will timeout. Ignore the
  // result.
  bool unused =
      base::test::ios::WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
        return [ChromeEarlGrey isLoading];
      });
  (void)unused;
  [ChromeEarlGrey waitForWebStateContainingText:"Whee"];

  // Verify that the page-load count has been recorded.  It should contain a
  // sum of 2 - one sample with 2 page loads.
  NSError* error = [MetricsAppInterface
         expectSum:2
      forHistogram:@(tab_usage_recorder::kPageLoadsBeforeEvictedTabSelected)];
  if (error) {
    GREYFail([error description]);
  }

  // Verify that only one evicted tab was selected.  This is to make sure the
  // link click did not generate an evicted-tab-reload count.
  error = [MetricsAppInterface
       expectCount:1
         forBucket:tab_usage_recorder::EVICTED
      forHistogram:@(tab_usage_recorder::kSelectedTabHistogramName)];
  if (error) {
    GREYFail([error description]);
  }
}

// Tests that opening links in a new tab will not evict the source tab.
- (void)testOpenLinkInNewTab {
  // Create map of canned responses and set up the test HTML server.
  std::map<GURL, std::string> responses;
  const GURL initialURL =
      web::test::HttpServer::MakeUrl("http://scenarioTestOpenLinkInNewTab");
  const GURL destinationURL =
      web::test::HttpServer::MakeUrl("http://destination");
  // Make the link that cover the whole page so that long pressing the web view
  // will trigger the link context menu.
  responses[initialURL] =
      base::StringPrintf("<body style='width:auto; height:auto;'><a href='%s' "
                         "id='link'><div style='width:100%%; "
                         "height:100%%;'>link</div></a></body>",
                         destinationURL.spec().c_str());
  responses[destinationURL] = "Whee!";
  web::test::SetUpHttpServer(std::make_unique<HtmlResponseProvider>(responses));
  [ChromeEarlGrey resetTabUsageRecorder];

  // Open a tab with a link to click.
  NewMainTabWithURL(initialURL, "link");

  int numberOfTabs = [ChromeEarlGrey mainTabCount];
  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
      performAction:chrome_test_util::LongPressElementForContextMenu(
                        [ElementSelector selectorWithElementID:"link"],
                        true /* menu should appear */)];

  [[EarlGrey selectElementWithMatcher:OpenLinkInNewTabButton()]
      performAction:grey_tap()];
  [ChromeEarlGrey waitForMainTabCount:(numberOfTabs + 1)];

  [ChromeEarlGrey selectTabAtIndex:numberOfTabs];

  [ChromeEarlGreyUI waitForAppToIdle];
  [ChromeEarlGrey waitForWebStateContainingText:"Whee"];

  NSError* error = [MetricsAppInterface
      expectTotalCount:1
          forHistogram:@(tab_usage_recorder::kSelectedTabHistogramName)];
  if (error) {
    GREYFail([error description]);
  }
  error = [MetricsAppInterface
       expectCount:1
         forBucket:tab_usage_recorder::IN_MEMORY
      forHistogram:@(tab_usage_recorder::kSelectedTabHistogramName)];
  if (error) {
    GREYFail([error description]);
  }
}

// Tests that opening tabs from external app will not cause tab eviction.
- (void)testOpenFromApp {
  [ChromeEarlGrey resetTabUsageRecorder];

  [ChromeEarlGrey openNewTab];
  GURL url(kTestUrl1);

  [ChromeEarlGrey openURLFromExternalApp:url];

  // Add a delay to ensure the tab has fully opened.  Because the check below
  // is for zero metrics recorded, it adds no flakiness.  However, this pause
  // makes the step more likely to fail in failure cases.  I.e. without it, this
  // test would sometimes pass even when it should fail.
  base::test::ios::SpinRunLoopWithMaxDelay(base::Milliseconds(500));

  // Verify that zero Tab.StatusWhenSwitchedBackToForeground metrics were
  // recorded.  Tabs created at the time the user switches to them should not
  // be counted in this metric.
  NSError* error = [MetricsAppInterface
      expectTotalCount:0
          forHistogram:@(tab_usage_recorder::kSelectedTabHistogramName)];
  if (error) {
    GREYFail([error description]);
  }
}

// Verify that evicted tabs that are deleted are removed from the evicted tabs
// map.
- (void)testTabDeletion {
  [ChromeEarlGrey resetTabUsageRecorder];
  // Add an autorelease pool to delete the closed tabs before the end of the
  // test.
  @autoreleasepool {
    // Open two tabs with urls.
    OpenTwoTabs();
    // Set the normal tabs as 'cold start' tabs.
    [ChromeEarlGrey setCurrentTabsToBeColdStartTabs];
    // One more tab.
    const GURL url1 = web::test::HttpServer::MakeUrl(kTestUrl1);
    NewMainTabWithURL(url1, kURL1FirstWord);

    GREYAssertEqual([ChromeEarlGrey mainTabCount], 3,
                    @"Check number of normal tabs");
    // The cold start tab which was not active will still be evicted.
    GREYAssertEqual([ChromeEarlGrey evictedMainTabCount], 1,
                    @"Check number of evicted tabs");

    // Close two of the three open tabs without selecting them first.
    // This should delete the tab objects, even though they're still being
    // tracked by the tab usage recorder in its `evicted_tabs_` map.
    CloseTabAtIndexAndSync(1);

    GREYAssertEqual([ChromeEarlGrey mainTabCount], 2,
                    @"Check number of normal tabs");
    CloseTabAtIndexAndSync(0);
    GREYAssertEqual([ChromeEarlGrey mainTabCount], 1,
                    @"Check number of normal tabs");
    [ChromeEarlGreyUI waitForAppToIdle];
  }
  // The deleted tabs are purged during foregrounding and backgrounding.
  [ChromeEarlGrey simulateTabsBackgrounding];
  // Make sure `evicted_tabs_` purged the deleted tabs.
  int evicted = [ChromeEarlGrey evictedMainTabCount];
  GREYAssertEqual(evicted, 0, @"Check number of evicted tabs");
}

@end