blob: aab146a0b0e841312261a2a58f404da755ec1184 [file] [log] [blame] [view]
David Jean4ddad6b2021-02-26 12:46:491# Adding Multiwindow EG Tests
2
3## Overview
4
5This document is a simple guidance on adding Multiwindow EG tests, using a
6limited set of helper functions.
7
8## Window Number
9
10Windows are numbered, through their `accessibilityIdentifier`, in the order they
Arthur Milchior192120b2022-02-15 15:32:0711are created. The first window will be `@"0"`, the second window will be `@"1"`,
David Jean4ddad6b2021-02-26 12:46:4912etc. In most helper functions, the integer is used to identify windows.
13
14Windows are not automatically renumbered so it is possible to end up with two
15windows with the same number. You can use
16`[ChromeEarlGrey changeWindowWithNumber:toNewNumber:]` to fix that in the
17unlikely case it is needed. Depending on the needs of the test, you can decide
18on how to proceed, for example wanting to keep the left window as 0 and the
19right window as 1. See
John Palmer046f9872021-05-24 01:24:5620[`[BrowserViewControllerTestCase testMultiWindowURLLoading]`](https://source.chromium.org/chromium/chromium/src/+/main:ios/chrome/browser/ui/browser_view/browser_view_controller_egtest.mm;l=209)
David Jean4ddad6b2021-02-26 12:46:4921as an example of this.
22
23## Helpers
24
25Multiwindow helpers are divided into two groups: window management functions and
26tabs management functions, the latter being similar to their previously existing
27single window counterpart versions but with an extra `inWindowWithNumber`
28parameter.
29
30The helpers all live in
John Palmer046f9872021-05-24 01:24:5631[ios/chrome/test/earl_grey/chrome_earl_grey.h](https://source.chromium.org/chromium/chromium/src/+/main:ios/chrome/test/earl_grey/chrome_earl_grey.h)
David Jean4ddad6b2021-02-26 12:46:4932
33### Window Management
34
35#### Window Creation
36
37You can artificially create a new window, as if the user had done it through
38the dock:
39
40```
41// Opens a new window.
42- (void)openNewWindow;
43```
44
45Or by triggering any chrome function that opens one. Either way, it is strongly
46recommended to call the following function to wait and verify that the action
47worked (It can also be a good idea to call it before as well as after the
48action):
49
50```
51// Waits for there to be |count| number of browsers within a timeout,
52// or a GREYAssert is induced.
53- (void)waitForForegroundWindowCount:(NSUInteger)count;
54````
55
56Two helpers allow getting the current window counts. Note that calling
57`[ChromeEarlGrey waitForForegroundWindowCount:]` above is probably a better
58choice than asserting on these counts.
59
60```
61// Returns the number of windows, including background and disconnected or
62// archived windows.
63- (NSUInteger)windowCount WARN_UNUSED_RESULT;
64
65// Returns the number of foreground (visible on screen) windows.
66- (NSUInteger)foregroundWindowCount WARN_UNUSED_RESULT;
67```
68
69#### Window destruction
70
71You can manually destroy a window:
72
73```
74// Closes the window with given number. Note that numbering doesn't change and
75// if a new window is to be added in a test, a renumbering might be needed.
76- (void)closeWindowWithNumber:(int)windowNumber;
77```
78
79Or destroy all but one, leaving the test application ready for the next test.
80
81```
82// Closes all but one window, including all non-foreground windows. No-op if
83// only one window presents.
84- (void)closeAllExtraWindows;
85```
86
87#### Window renumbering
88
89As discussed before, it is possible to renumber a window. Note that if more
90than one window has the same number, only the first one found will be renamed.
91
92```
93// Renumbers given window with current number to new number. For example, if
94// you have windows 0 and 1, close window 0, then add new window, then both
95// windows would be 1. Therefore you should renumber the remaining ones
96// before adding new ones.
97- (void)changeWindowWithNumber:(int)windowNumber
98 toNewNumber:(int)newWindowNumber;
99```
100
101### Tab Management
102
103All the following functions work like their non multiwindow counterparts. Note
104that those non multiwindow counterparts can still be called in a multiwindow
105test when only one window is visible, but their result becomes undefined if
106more than one window exists.
107
108```
109// Opens a new tab in window with given number and waits for the new tab
110// animation to complete within a timeout, or a GREYAssert is induced.
111- (void)openNewTabInWindowWithNumber:(int)windowNumber;
112
113// Loads |URL| in the current WebState for window with given number, with
114// transition type ui::PAGE_TRANSITION_TYPED, and if waitForCompletion is YES
115// waits for the loading to complete within a timeout.
116// Returns nil on success, or else an NSError indicating why the operation
117// failed.
118- (void)loadURL:(const GURL&)URL
119 inWindowWithNumber:(int)windowNumber
120 waitForCompletion:(BOOL)wait;
121
122// Loads |URL| in the current WebState for window with given number, with
123// transition type ui::PAGE_TRANSITION_TYPED, and waits for the loading to
124// complete within a timeout. If the condition is not met within a timeout
125// returns an NSError indicating why the operation failed, otherwise nil.
126- (void)loadURL:(const GURL&)URL inWindowWithNumber:(int)windowNumber;
127
128// Waits for the page to finish loading for window with given number, within a
129// timeout, or a GREYAssert is induced.
130- (void)waitForPageToFinishLoadingInWindowWithNumber:(int)windowNumber;
131
132// Returns YES if the window with given number's current WebState is loading.
133- (BOOL)isLoadingInWindowWithNumber:(int)windowNumber WARN_UNUSED_RESULT;
134
135// Waits for the current web state for window with given number, to contain
136// |UTF8Text|. If the condition is not met within a timeout a GREYAssert is
137// induced.
138- (void)waitForWebStateContainingText:(const std::string&)UTF8Text
139 inWindowWithNumber:(int)windowNumber;
140
141// Waits for the current web state for window with given number, to contain
142// |UTF8Text|. If the condition is not met within the given |timeout| a
143// GREYAssert is induced.
144- (void)waitForWebStateContainingText:(const std::string&)UTF8Text
145 timeout:(NSTimeInterval)timeout
146 inWindowWithNumber:(int)windowNumber;
147
148// Waits for there to be |count| number of non-incognito tabs within a timeout,
149// or a GREYAssert is induced.
150- (void)waitForMainTabCount:(NSUInteger)count
151 inWindowWithNumber:(int)windowNumber;
152
153// Waits for there to be |count| number of incognito tabs within a timeout, or a
154// GREYAssert is induced.
155- (void)waitForIncognitoTabCount:(NSUInteger)count
156 inWindowWithNumber:(int)windowNumber;
157```
158
159## Matchers
160
161Most existing matchers can be used when multiple windows are present by setting
162a global root matcher with
163`[EarlGrey setRootMatcherForSubsequentInteractions:]`. For example, in the
164following blurb, the `BackButton` is tapped in window 0, then later the
165`TabGridDoneButton` is tapped in window 1:
166
167```
168 [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(1)];
169 [[EarlGrey selectElementWithMatcher:chrome_test_util::BackButton()]
170 performAction:grey_tap()];
171 [ChromeEarlGrey waitForWebStateContainingText:kResponse1
172 inWindowWithNumber:1];
173
174 [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
175 [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
176 performAction:grey_tap()];
177```
178
179If `grey_tap()` fails unexpectedly and unexplainably, see Actions section below.
180
181Thanks to the root matcher, a limited number of matchers, require the window
182number to be specified. `WindowWithNumber` is useful as a root matcher and
183`MatchInWindowWithNumber` if you want to match without using a root matcher:
184
John Palmer046f9872021-05-24 01:24:56185[ios/chrome/test/earl_grey/chrome_matchers.h](https://source.chromium.org/chromium/chromium/src/+/main:ios/chrome/test/earl_grey/chrome_matchers.h)
David Jean4ddad6b2021-02-26 12:46:49186
187```
188// Matcher for a window with a given number.
189// Window numbers are assigned at scene creation. Normally, each EGTest will
190// start with exactly one window with number 0. Each time a window is created,
191// it is assigned an accessibility identifier equal to the number of connected
192// scenes (stored as NSString). This means typically any windows created in a
193// test will have consecutive numbers.
194id<GREYMatcher> WindowWithNumber(int window_number);
195
196// Shorthand matcher for creating a matcher that ensures the given matcher
197// matches elements under the given window.
198id<GREYMatcher> MatchInWindowWithNumber(int window_number,
199 id<GREYMatcher> matcher);
200
201The settings back button is the hardest matcher to get right. Their
202characteristics change based on iOS versions, on device types and on an
203unexplainable situation where the label appears or not. For this reason,
204theres a special matcher for Multiwindow:
205
206// Returns matcher for the back button on a settings menu in given window
207// number.
208id<GREYMatcher> SettingsMenuBackButton(int window_number);
209```
210
David Jean08027bb2021-05-27 08:10:04211## Chrome Matchers
212Some chrome matchers have a version where the window number needs to be
213specified. On those, the root matcher will be set and left set on return to
214allow less verbosity at call site.
215
216```
217// Makes the toolbar visible by swiping downward, if necessary. Then taps on
218// the Tools menu button. At least one tab needs to be open and visible when
219// calling this method.
220// Sets and Leaves the root matcher to the given window with |windowNumber|.
221- (void)openToolsMenuInWindowWithNumber:(int)windowNumber;
222
223// Opens the settings menu by opening the tools menu, and then tapping the
224// Settings button. There will be a GREYAssert if the tools menu is open when
225// calling this method.
226// Sets and Leaves the root matcher to the given window with |windowNumber|.
227- (void)openSettingsMenuInWindowWithNumber:(int)windowNumber;
228```
229
230For example, the following code:
231
232```
233 [EarlGrey setRootMatcherForSubsequentInteractions:
234 chrome_test_util::WindowWithNumber(windowNumber)];
235 [ChromeEarlGreyUI openToolsMenu];
Benjamin Williams26d955d2022-06-27 13:55:52236 [ChromeEarlGreyUI tapToolsMenuButton:HistoryDestinationButton()];
David Jean08027bb2021-05-27 08:10:04237```
238
239Can be reduced to:
240
241```
242 [ChromeEarlGreyUI openToolsMenuInWindowWithNumber:windowNumber];
Benjamin Williams26d955d2022-06-27 13:55:52243 [ChromeEarlGreyUI tapToolsMenuButton:HistoryDestinationButton()];
David Jean08027bb2021-05-27 08:10:04244```
245
David Jean4ddad6b2021-02-26 12:46:49246## Actions
247
248There are actions that cannot be done using `EarlGrey` (yet?). One of those is
249Drag and drop. But XCUI is good at that, so there are two new
250client-side-triggered actions that can be used, that work across multiple
251windows:
252
John Palmer046f9872021-05-24 01:24:56253[ios/chrome/test/earl_grey/chrome_xcui_actions.h](https://source.chromium.org/chromium/chromium/src/+/main:ios/chrome/test/earl_grey/chrome_xcui_actions.h)
David Jean4ddad6b2021-02-26 12:46:49254
255```
256// Action (XCUI, hence local) to long press a cell item with
257// |accessibility_identifier| in |window_number| and drag it to the given |edge|
258// of the app screen (can trigger a new window) before dropping it. Returns YES
259// on success (finding the item).
260BOOL LongPressCellAndDragToEdge(NSString* accessibility_identifier,
261 GREYContentEdge edge,
262 int window_number);
263
264// Action (XCUI, hence local) to long press a cell item with
265// |src_accessibility_identifier| in |src_window_number| and drag it to the
266// given normalized offset of the cell or window with
267// |dst_accessibility_identifier| in |dst_window_number| before dropping it. To
268// target a window, pass nil as |dst_accessibility_identifier|. Returns YES on
269// success (finding both items).
270BOOL LongPressCellAndDragToOffsetOf(NSString* src_accessibility_identifier,
271 int src_window_number,
272 NSString* dst_accessibility_identifier,
273 int dst_window_number,
274 CGVector dst_normalized_offset);
275```
276
277Also there’s a bug in `EarlGrey` that makes some actions fail without a reason
278on a second or third window due to a false negative visibility computation.
279To palliate this for now, this XCUI action helper can be used:
280
281```
282// Action (XCUI, hence local) to tap item with |accessibility_identifier| in
283// |window_number|.
284BOOL TapAtOffsetOf(NSString* accessibility_identifier,
285 int window_number,
286 CGVector normalized_offset);
287```
288
289It’s hard to know when you will need it, but unexpected and unexplainable
290failures in the simulator are a good clue...
291
292If you need window resizing, the following allows you to:
293
294```
295// Action (XCUI, hence local) to resize split windows by dragging the splitter.
296// This action requires two windows (|first_window_number| and
297// |second_window_number|, in any order) to find where the splitter is located.
298// A given |first_window_normalized_screen_size| defines the normalized size
299// [0.0 - 1.0] wanted for the |first_window_number|. Returns NO if any window
300// is not found or if one of them is a floating window.
301// Notes: The size requested
302// will be matched by the OS to the closest available multiwindow layout. This
303// function works with any device orientation and with either LTR or RTL
304// languages. Example of use:
305// [ChromeEarlGrey openNewWindow];
306// [ChromeEarlGrey waitForForegroundWindowCount:2];
307// chrome_test_util::DragWindowSplitterToSize(0, 1, 0.25);
308// Starting with window sizes 438 and 320 pts, this will resize
309// them to 320pts and 438 pts respectively.
310BOOL DragWindowSplitterToSize(int first_window_number,
311 int second_window_number,
312 CGFloat first_window_normalized_screen_size);
313```
314
315## setUp/tearDown/test
316
David Jean0874f712021-03-11 15:37:29317In multiwindow tests, a failure to clear extra windows and root matcher at the
318end of a test, would mean a more than likely failure on the next one. To do so,
319the setUp/tearDown methods do not need any changes. ```closeAllExtraWindows```
320and ```[EarlGrey setRootMatcherForSubsequentInteractions:nil]``` are called on
321```[ChromeTestCase tearDown]``` and ```[ChromeTestCase setUpForTestCase]```.
David Jean4ddad6b2021-02-26 12:46:49322
323Tests should check if multiwindow is available on their first lines, to avoid
324failing on iPhones:
325
326```
327- (void)testDragAndDropAtEdgeToCreateNewWindow {
328 if (![ChromeEarlGrey areMultipleWindowsSupported])
329 EARL_GREY_TEST_DISABLED(@"Multiple windows can't be opened.");
330 ...
331```