[iOS] Add New Window EG test.

Original CL by marq: crrev/2424125

- Adds a cleanup step to EarlGreyTestCase that closes all but one window.

- Adds test utilities for closing all (but one) windows, counting windows,
  and waiting for a specific window count.

- Adds a test utility to check for multiple window support.

- Adds a simple test of the tools menu "New Window" item.

- Updates & reenables the history and bookmarks EG tests to wait for a
  window count (and to skip the tab-closing step, which wouldn't have
  closed the window in any case).

Bug: 1126893
Change-Id: Id6b3ab02058e0db036045b0653a716fe19271b74
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2450096
Commit-Queue: Stepan Khapugin <stkhapugin@chromium.org>
Commit-Queue: Mark Cogan <marq@chromium.org>
Reviewed-by: Mark Cogan <marq@chromium.org>
Cr-Commit-Position: refs/heads/master@{#815179}
diff --git a/ios/chrome/browser/ui/bookmarks/bookmarks_entries_egtest.mm b/ios/chrome/browser/ui/bookmarks/bookmarks_entries_egtest.mm
index 03b44c6..71f28eb 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmarks_entries_egtest.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmarks_entries_egtest.mm
@@ -12,7 +12,6 @@
 #import "ios/chrome/browser/ui/bookmarks/bookmark_earl_grey_ui.h"
 #import "ios/chrome/browser/ui/bookmarks/bookmark_ui_constants.h"
 #import "ios/chrome/browser/ui/table_view/feature_flags.h"
-#import "ios/chrome/browser/ui/util/multi_window_support.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
 #import "ios/chrome/test/earl_grey/chrome_matchers.h"
@@ -48,11 +47,13 @@
 
   [ChromeEarlGrey waitForBookmarksToFinishLoading];
   [ChromeEarlGrey clearBookmarks];
+  [BookmarkEarlGrey clearBookmarksPositionCache];
 }
 
 // Tear down called once per test.
 - (void)tearDown {
   [super tearDown];
+  [ChromeEarlGrey closeAllExtraWindows];
   [ChromeEarlGrey clearBookmarks];
   [BookmarkEarlGrey clearBookmarksPositionCache];
 }
@@ -197,23 +198,21 @@
 
 // Tests display and selection of 'Open in New Window' in a context menu on a
 // bookmarks entry.
-// TODO(crbug.com/1126893): reenable this test once EG multiwindow support is
-// available.
-- (void)DISABLED_testContextMenuOpenInNewWindow {
+- (void)testContextMenuOpenInNewWindow {
   // TODO(crbug.com/1035764): EG1 Test fails on iOS 12.
   if (!base::ios::IsRunningOnIOS13OrLater()) {
     EARL_GREY_TEST_DISABLED(@"EG1 Fails on iOS 12.");
   }
 
-  if (!IsMultipleScenesSupported()) {
-    EARL_GREY_TEST_DISABLED(@"Multiple scenes can't be opened.");
+  if (![ChromeEarlGrey areMultipleWindowsSupported]) {
+    EARL_GREY_TEST_DISABLED(@"Multiple windows can't be opened.");
   }
 
   [BookmarkEarlGrey setupStandardBookmarks];
   [BookmarkEarlGreyUI openBookmarks];
   [BookmarkEarlGreyUI openMobileBookmarks];
 
-  [ChromeEarlGrey waitForBrowserCount:1];
+  [ChromeEarlGrey waitForForegroundWindowCount:1];
 
   // Open a bookmark in a new window (through a long press).
   [[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"First URL")]
@@ -224,10 +223,7 @@
   [[EarlGrey selectElementWithMatcher:chrome_test_util::OmniboxText(
                                           GetFirstUrl().GetContent())]
       assertWithMatcher:grey_notNil()];
-  [ChromeEarlGrey waitForBrowserCount:2];
-
-  [ChromeEarlGrey closeCurrentTab];
-  [ChromeEarlGrey waitForBrowserCount:1];
+  [ChromeEarlGrey waitForForegroundWindowCount:2];
 }
 
 // Verify Edit Text functionality on single URL selection.
diff --git a/ios/chrome/browser/ui/history/history_ui_egtest.mm b/ios/chrome/browser/ui/history/history_ui_egtest.mm
index f6d4ba34..e5ac5d4 100644
--- a/ios/chrome/browser/ui/history/history_ui_egtest.mm
+++ b/ios/chrome/browser/ui/history/history_ui_egtest.mm
@@ -15,7 +15,6 @@
 #import "ios/chrome/browser/ui/table_view/feature_flags.h"
 #import "ios/chrome/browser/ui/table_view/table_view_constants.h"
 #import "ios/chrome/browser/ui/ui_feature_flags.h"
-#import "ios/chrome/browser/ui/util/multi_window_support.h"
 #include "ios/chrome/common/string_util.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
@@ -123,6 +122,9 @@
 
 - (void)setUp {
   [super setUp];
+
+  [ChromeEarlGrey closeAllExtraWindows];
+
   self.testServer->RegisterRequestHandler(
       base::BindRepeating(&StandardResponse));
   GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
@@ -138,6 +140,8 @@
 }
 
 - (void)tearDown {
+  [ChromeEarlGrey closeAllExtraWindows];
+
   NSError* error = nil;
   // Dismiss search bar by pressing cancel, if present. Passing error prevents
   // failure if the element is not found.
@@ -395,16 +399,14 @@
 
 // Tests display and selection of 'Open in New Window' in a context menu on a
 // history entry.
-// TODO(crbug.com/1126893): reenable this test once EG multiwindow support is
-// available.
-- (void)DISABLED_testContextMenuOpenInNewWindow {
-  if (!IsMultipleScenesSupported())
-    return;
+- (void)testContextMenuOpenInNewWindow {
+  if (![ChromeEarlGrey areMultipleWindowsSupported])
+    EARL_GREY_TEST_DISABLED(@"Multiple windows can't be opened.");
 
   [self loadTestURLs];
   [self openHistoryPanel];
 
-  [ChromeEarlGrey waitForBrowserCount:1];
+  [ChromeEarlGrey waitForForegroundWindowCount:1];
 
   // Long press on the history element.
   [[EarlGrey
@@ -415,13 +417,10 @@
   // selected URL in the new window.
   [[EarlGrey selectElementWithMatcher:OpenLinkInNewWindowButton()]
       performAction:grey_tap()];
+  [ChromeEarlGrey waitForForegroundWindowCount:2];
   [[EarlGrey selectElementWithMatcher:chrome_test_util::OmniboxText(
                                           _URL1.GetContent())]
       assertWithMatcher:grey_notNil()];
-  [ChromeEarlGrey waitForBrowserCount:2];
-
-  [ChromeEarlGrey closeCurrentTab];
-  [ChromeEarlGrey waitForBrowserCount:1];
 }
 
 // Tests display and selection of 'Open in New Incognito Tab' in a context menu
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_constants.h b/ios/chrome/browser/ui/popup_menu/popup_menu_constants.h
index e935e2d..0c7c5af 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_constants.h
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_constants.h
@@ -21,7 +21,7 @@
 // New Tab item accessibility Identifier.
 extern NSString* const kToolsMenuNewTabId;
 // New Tab item accessibility Identifier.
-extern NSString* const kToolsMenuNewWindow;
+extern NSString* const kToolsMenuNewWindowId;
 // New incognito Tab item accessibility Identifier.
 extern NSString* const kToolsMenuNewIncognitoTabId;
 // Close all Tabs item accessibility Identifier.
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_constants.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_constants.mm
index 631d763..aa3ba5e 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_constants.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_constants.mm
@@ -17,7 +17,7 @@
 NSString* const kToolsMenuReload = @"kToolsMenuReload";
 NSString* const kToolsMenuStop = @"kToolsMenuStop";
 NSString* const kToolsMenuNewTabId = @"kToolsMenuNewTabId";
-NSString* const kToolsMenuNewWindow = @"kToolsMenuNewWindow";
+NSString* const kToolsMenuNewWindowId = @"kToolsMenuNewWindowId";
 NSString* const kToolsMenuNewIncognitoTabId = @"kToolsMenuNewIncognitoTabId";
 NSString* const kToolsMenuCloseAllTabsId = @"kToolsMenuCloseAllTabsId";
 NSString* const kToolsMenuCloseAllIncognitoTabsId =
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_egtest.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_egtest.mm
index 9cccd3e..9d2d49f 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_egtest.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_egtest.mm
@@ -147,6 +147,18 @@
       assertWithMatcher:grey_notVisible()];
 }
 
+- (void)testNewWindowFromToolsMenu {
+  if (![ChromeEarlGrey areMultipleWindowsSupported])
+    EARL_GREY_TEST_DISABLED(@"Multiple windows can't be opened.");
+
+  [ChromeEarlGreyUI openToolsMenu];
+  [ChromeEarlGreyUI
+      tapToolsMenuButton:chrome_test_util::OpenNewWindowMenuButton()];
+
+  // Verify the second window.
+  [ChromeEarlGrey waitForForegroundWindowCount:2];
+}
+
 // Navigates to a pdf page and verifies that the "Find in Page..." tool
 // is not enabled
 - (void)testNoSearchForPDF {
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_mediator.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_mediator.mm
index 196aea5..7465029 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_mediator.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_mediator.mm
@@ -893,7 +893,7 @@
   // Create the menu item -- hardcoded string and no accessibility ID.
   PopupMenuToolsItem* openNewWindowItem = CreateTableViewItem(
       IDS_IOS_TOOLS_MENU_NEW_WINDOW, PopupMenuActionOpenNewWindow,
-      @"popup_menu_new_window", kToolsMenuNewWindow);
+      @"popup_menu_new_window", kToolsMenuNewWindowId);
 
   return @[ openNewWindowItem ];
 }
diff --git a/ios/chrome/test/earl_grey/BUILD.gn b/ios/chrome/test/earl_grey/BUILD.gn
index 85b2f56d..2e215b35 100644
--- a/ios/chrome/test/earl_grey/BUILD.gn
+++ b/ios/chrome/test/earl_grey/BUILD.gn
@@ -148,6 +148,7 @@
     "//ios/chrome/browser/ui/toolbar/public",
     "//ios/chrome/browser/ui/util",
     "//ios/chrome/browser/ui/util:eg_app_support+eg2",
+    "//ios/chrome/browser/ui/util:multiwindow_util",
     "//ios/chrome/browser/unified_consent",
     "//ios/chrome/browser/web:eg_app_support+eg2",
     "//ios/chrome/browser/web:tab_id_tab_helper",
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.h b/ios/chrome/test/earl_grey/chrome_earl_grey.h
index 5bbb599..352e47e 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey.h
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey.h
@@ -142,10 +142,6 @@
 // GREYAssert is induced.
 - (void)waitForIncognitoTabCount:(NSUInteger)count;
 
-// Waits for there to be |count| number of browsers within a timeout,
-// or a GREYAssert is induced.
-- (void)waitForBrowserCount:(NSUInteger)count;
-
 // Loads |URL| as if it was opened from an external application.
 - (void)openURLFromExternalApp:(const GURL&)URL;
 
@@ -346,6 +342,22 @@
 // and tablet.
 - (void)showTabSwitcher;
 
+#pragma mark - Window utilities (EG2)
+
+// Returns the number of windows, including background and disconnected or
+// archived windows.
+- (NSUInteger)windowCount WARN_UNUSED_RESULT;
+
+// Returns the number of foreground (visible on screen) windows.
+- (NSUInteger)foregroundWindowCount WARN_UNUSED_RESULT;
+
+// Waits for there to be |count| number of browsers within a timeout,
+// or a GREYAssert is induced.
+- (void)waitForForegroundWindowCount:(NSUInteger)count;
+
+// Closes all but one window, including all non-foreground windows.
+- (void)closeAllExtraWindows;
+
 #pragma mark - SignIn Utilities (EG2)
 
 // Signs the user out, clears the known accounts entirely and checks whether the
@@ -523,6 +535,10 @@
 // Returns whether the native context menus feature is enabled or not.
 - (BOOL)isNativeContextMenusEnabled;
 
+// Returns whether the app is configured to, and running in an environment which
+// can, open multiple windows.
+- (BOOL)areMultipleWindowsSupported;
+
 #pragma mark - Popup Blocking
 
 // Gets the current value of the popup content setting preference for the
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.mm b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
index a7fe700f..66f1f81 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
@@ -483,30 +483,6 @@
   EG_TEST_HELPER_ASSERT_TRUE(tabCountEqual, errorString);
 }
 
-- (void)waitForBrowserCount:(NSUInteger)count {
-  __block NSUInteger actualCount = [ChromeEarlGreyAppInterface browserCount];
-  NSString* conditionName = [NSString
-      stringWithFormat:@"Waiting for window count to become %" PRIuNS, count];
-
-  // Allow the UI to become idle, in case any tabs are being opened or closed.
-  GREYWaitForAppToIdle(@"App failed to idle");
-
-  GREYCondition* browserCountCheck = [GREYCondition
-      conditionWithName:conditionName
-                  block:^{
-                    actualCount = [ChromeEarlGreyAppInterface browserCount];
-                    return actualCount == count;
-                  }];
-  bool browserCountEqual =
-      [browserCountCheck waitWithTimeout:kWaitForUIElementTimeout];
-
-  NSString* errorString = [NSString
-      stringWithFormat:@"Failed waiting for window count to become %" PRIuNS
-                        "; actual count: %" PRIuNS,
-                       count, actualCount];
-  EG_TEST_HELPER_ASSERT_TRUE(browserCountEqual, errorString);
-}
-
 - (NSUInteger)indexOfActiveNormalTab {
   return [ChromeEarlGreyAppInterface indexOfActiveNormalTab];
 }
@@ -788,6 +764,53 @@
   EG_TEST_HELPER_ASSERT_TRUE(success, errorString);
 }
 
+#pragma mark - Window utilities (EG2)
+
+// Returns the number of windows, including background and disconnected or
+// archived windows.
+- (NSUInteger)windowCount WARN_UNUSED_RESULT {
+  return [ChromeEarlGreyAppInterface windowCount];
+}
+
+// Returns the number of foreground (visible on screen) windows.
+- (NSUInteger)foregroundWindowCount WARN_UNUSED_RESULT {
+  return [ChromeEarlGreyAppInterface foregroundWindowCount];
+}
+
+// Closes all but one window, including all non-foreground windows.
+- (void)closeAllExtraWindows {
+  [ChromeEarlGreyAppInterface closeAllExtraWindows];
+  // Tab changes are initiated through |WebStateList|. Need to wait its
+  // obeservers to complete UI changes at app.
+  GREYWaitForAppToIdle(@"App failed to idle");
+}
+
+- (void)waitForForegroundWindowCount:(NSUInteger)count {
+  __block NSUInteger actualCount =
+      [ChromeEarlGreyAppInterface foregroundWindowCount];
+  NSString* conditionName = [NSString
+      stringWithFormat:@"Waiting for window count to become %" PRIuNS, count];
+
+  // Allow the UI to become idle, in case any tabs are being opened or closed.
+  GREYWaitForAppToIdle(@"App failed to idle");
+
+  GREYCondition* browserCountCheck = [GREYCondition
+      conditionWithName:conditionName
+                  block:^{
+                    actualCount =
+                        [ChromeEarlGreyAppInterface foregroundWindowCount];
+                    return actualCount == count;
+                  }];
+  bool browserCountEqual =
+      [browserCountCheck waitWithTimeout:kWaitForUIElementTimeout];
+
+  NSString* errorString = [NSString
+      stringWithFormat:@"Failed waiting for window count to become %" PRIuNS
+                        "; actual count: %" PRIuNS,
+                       count, actualCount];
+  EG_TEST_HELPER_ASSERT_TRUE(browserCountEqual, errorString);
+}
+
 #pragma mark - SignIn Utilities (EG2)
 
 - (void)signOutAndClearIdentities {
@@ -898,6 +921,10 @@
   return [ChromeEarlGreyAppInterface isNativeContextMenusEnabled];
 }
 
+- (BOOL)areMultipleWindowsSupported {
+  return [ChromeEarlGreyAppInterface areMultipleWindowsSupported];
+}
+
 #pragma mark - ScopedBlockPopupsPref
 
 - (ContentSetting)popupPrefValue {
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
index a9c60b56..8bf2192 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
@@ -162,6 +162,18 @@
 // Returns the index of active tab in normal mode.
 + (NSUInteger)indexOfActiveNormalTab;
 
+#pragma mark - Window utilities (EG2)
+
+// Returns the number of windows, including background and disconnected or
+// archived windows.
++ (NSUInteger)windowCount WARN_UNUSED_RESULT;
+
+// Returns the number of foreground (visible on screen) windows.
++ (NSUInteger)foregroundWindowCount WARN_UNUSED_RESULT;
+
+// Closes all but one window, including all non-foreground windows.
++ (void)closeAllExtraWindows;
+
 #pragma mark - WebState Utilities (EG2)
 
 // Attempts to tap the element with |element_id| within window.frames[0] of the
@@ -433,6 +445,10 @@
 // Returns whether the native context menus feature is enabled or not.
 + (BOOL)isNativeContextMenusEnabled;
 
+// Returns whether the app is configured to, and running in an environment which
+// can, open multiple windows.
++ (BOOL)areMultipleWindowsSupported;
+
 #pragma mark - Popup Blocking
 
 // Gets the current value of the popup content setting preference for the
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
index fd4f648f..8d26b22 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
@@ -30,6 +30,7 @@
 #import "ios/chrome/browser/ui/table_view/feature_flags.h"
 #import "ios/chrome/browser/ui/ui_feature_flags.h"
 #import "ios/chrome/browser/ui/util/menu_util.h"
+#import "ios/chrome/browser/ui/util/multi_window_support.h"
 #import "ios/chrome/browser/ui/util/named_guide.h"
 #import "ios/chrome/browser/unified_consent/unified_consent_service_factory.h"
 #import "ios/chrome/browser/web/tab_id_tab_helper.h"
@@ -298,6 +299,68 @@
   return chrome_test_util::GetIndexOfActiveNormalTab();
 }
 
+#pragma mark - Window utilities (EG2)
+
++ (NSUInteger)windowCount WARN_UNUSED_RESULT {
+  // If the scene API is in use, return the count of open sessions.
+  if (@available(iOS 13, *)) {
+    return UIApplication.sharedApplication.openSessions.count;
+  }
+
+  // Otherwise, there's always exectly one window;
+  return 1;
+}
+
++ (NSUInteger)foregroundWindowCount WARN_UNUSED_RESULT {
+  // If the scene API is in use, look at all the connected scenes and count
+  // those in the foreground.
+  if (@available(iOS 13, *)) {
+    NSUInteger count = 0;
+    for (UIScene* scene in UIApplication.sharedApplication.connectedScenes) {
+      if (scene.activationState == UISceneActivationStateForegroundActive ||
+          scene.activationState == UISceneActivationStateForegroundInactive) {
+        count++;
+      }
+    }
+    return count;
+  }
+
+  // Otherwise, there's always exectly one window;
+  return 1;
+}
+
++ (void)closeAllExtraWindows {
+  if (!IsMultipleScenesSupported())
+    return;
+
+  if (@available(iOS 13, *)) {
+    NSSet<UISceneSession*>* sessions =
+        UIApplication.sharedApplication.openSessions;
+    if (sessions.count <= 1)
+      return;
+    BOOL foundForegroundScene = NO;
+    for (UISceneSession* session in sessions) {
+      UIScene* scene = session.scene;
+      if (!foundForegroundScene && scene &&
+          (scene.activationState == UISceneActivationStateForegroundActive ||
+           scene.activationState == UISceneActivationStateForegroundInactive)) {
+        foundForegroundScene = YES;
+        // Leave the first foreground scene connected, so there's one open
+        // window left.
+        continue;
+      }
+      // If this isn't the first foreground scene, destroy it.
+      UIWindowSceneDestructionRequestOptions* options =
+          [[UIWindowSceneDestructionRequestOptions alloc] init];
+      options.windowDismissalAnimation =
+          UIWindowSceneDismissalAnimationStandard;
+      [UIApplication.sharedApplication requestSceneSessionDestruction:session
+                                                              options:options
+                                                         errorHandler:nil];
+    }
+  }
+}
+
 #pragma mark - WebState Utilities (EG2)
 
 + (NSError*)tapWebStateElementInIFrameWithID:(NSString*)elementID {
@@ -769,6 +832,10 @@
   return IsNativeContextMenuEnabled();
 }
 
++ (BOOL)areMultipleWindowsSupported {
+  return IsMultipleScenesSupported();
+}
+
 #pragma mark - ScopedBlockPopupsPref
 
 + (ContentSetting)popupPrefValue {
diff --git a/ios/chrome/test/earl_grey/chrome_matchers.h b/ios/chrome/test/earl_grey/chrome_matchers.h
index 8b101fa..23db1a7c 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers.h
+++ b/ios/chrome/test/earl_grey/chrome_matchers.h
@@ -310,6 +310,9 @@
 // Returns matcher for the payment request search bar.
 id<GREYMatcher> PaymentRequestPickerSearchBar();
 
+// Returns matcher for the New Window button on the Tools menu.
+id<GREYMatcher> OpenNewWindowMenuButton();
+
 // Returns matcher for the reading list on the Tools menu.
 id<GREYMatcher> ReadingListMenuButton();
 
diff --git a/ios/chrome/test/earl_grey/chrome_matchers.mm b/ios/chrome/test/earl_grey/chrome_matchers.mm
index 24c4dac..b1c49a6 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers.mm
+++ b/ios/chrome/test/earl_grey/chrome_matchers.mm
@@ -387,6 +387,10 @@
   return [ChromeMatchersAppInterface paymentRequestPickerSearchBar];
 }
 
+id<GREYMatcher> OpenNewWindowMenuButton() {
+  return [ChromeMatchersAppInterface openNewWindowMenuButton];
+}
+
 id<GREYMatcher> ReadingListMenuButton() {
   return [ChromeMatchersAppInterface readingListMenuButton];
 }
diff --git a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.h b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.h
index a381260..2e53cae 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.h
+++ b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.h
@@ -315,6 +315,9 @@
 // Returns matcher for the payment request search bar.
 + (id<GREYMatcher>)paymentRequestPickerSearchBar;
 
+// Returns matcher for the New Window button on the Tools menu.
++ (id<GREYMatcher>)openNewWindowMenuButton;
+
 // Returns matcher for the reading list button on the Tools menu.
 + (id<GREYMatcher>)readingListMenuButton;
 
diff --git a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
index 7e6879af..cb2cf6dcd 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
+++ b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
@@ -670,6 +670,10 @@
   return nil;
 }
 
++ (id<GREYMatcher>)openNewWindowMenuButton {
+  return grey_accessibilityID(kToolsMenuNewWindowId);
+}
+
 + (id<GREYMatcher>)readingListMenuButton {
   return grey_accessibilityID(kToolsMenuReadingListId);
 }
diff --git a/ios/chrome/test/earl_grey/chrome_test_case.mm b/ios/chrome/test/earl_grey/chrome_test_case.mm
index 3581fc9b..d34c05e39 100644
--- a/ios/chrome/test/earl_grey/chrome_test_case.mm
+++ b/ios/chrome/test/earl_grey/chrome_test_case.mm
@@ -194,6 +194,7 @@
   [[AppLaunchManager sharedManager] addObserver:self];
 
   [super setUp];
+  [[self class] closeAllWindows];
   [self resetAppState];
 
   ResetAuthentication();
@@ -220,6 +221,7 @@
 
   // Clean up any UI that may remain open so the next test starts in a clean
   // state.
+  [[self class] closeAllWindows];
   [[self class] removeAnyOpenMenusAndInfoBars];
   [[self class] closeAllTabs];
 
@@ -256,6 +258,12 @@
       drainUntilIdleWithTimeout:kDrainTimeout];
 }
 
++ (void)closeAllWindows {
+  [ChromeEarlGrey closeAllExtraWindows];
+  [[GREYUIThreadExecutor sharedInstance]
+      drainUntilIdleWithTimeout:kDrainTimeout];
+}
+
 - (void)disableMockAuthentication {
   [[self class] disableMockAuthentication];
 }