blob: e16fe46ecac79fa522548c25d7e40cae0087e508 [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
11are created. The first window will be `@”0”`, the second window will be `@”1”`,
12etc. 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
20[`[BrowserViewControllerTestCase testMultiWindowURLLoading]`](https://source.chromium.org/chromium/chromium/src/+/master:ios/chrome/browser/ui/browser_view/browser_view_controller_egtest.mm;l=209)
21as 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
31[ios/chrome/test/earl_grey/chrome_earl_grey.h](https://source.chromium.org/chromium/chromium/src/+/master:ios/chrome/test/earl_grey/chrome_earl_grey.h)
32
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
185[ios/chrome/test/earl_grey/chrome_matchers.h](https://source.chromium.org/chromium/chromium/src/+/master:ios/chrome/test/earl_grey/chrome_matchers.h)
186
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
211## Actions
212
213There are actions that cannot be done using `EarlGrey` (yet?). One of those is
214Drag and drop. But XCUI is good at that, so there are two new
215client-side-triggered actions that can be used, that work across multiple
216windows:
217
218[ios/chrome/test/earl_grey/chrome_xcui_actions.h](https://source.chromium.org/chromium/chromium/src/+/master:ios/chrome/test/earl_grey/chrome_xcui_actions.h)
219
220```
221// Action (XCUI, hence local) to long press a cell item with
222// |accessibility_identifier| in |window_number| and drag it to the given |edge|
223// of the app screen (can trigger a new window) before dropping it. Returns YES
224// on success (finding the item).
225BOOL LongPressCellAndDragToEdge(NSString* accessibility_identifier,
226 GREYContentEdge edge,
227 int window_number);
228
229// Action (XCUI, hence local) to long press a cell item with
230// |src_accessibility_identifier| in |src_window_number| and drag it to the
231// given normalized offset of the cell or window with
232// |dst_accessibility_identifier| in |dst_window_number| before dropping it. To
233// target a window, pass nil as |dst_accessibility_identifier|. Returns YES on
234// success (finding both items).
235BOOL LongPressCellAndDragToOffsetOf(NSString* src_accessibility_identifier,
236 int src_window_number,
237 NSString* dst_accessibility_identifier,
238 int dst_window_number,
239 CGVector dst_normalized_offset);
240```
241
242Also there’s a bug in `EarlGrey` that makes some actions fail without a reason
243on a second or third window due to a false negative visibility computation.
244To palliate this for now, this XCUI action helper can be used:
245
246```
247// Action (XCUI, hence local) to tap item with |accessibility_identifier| in
248// |window_number|.
249BOOL TapAtOffsetOf(NSString* accessibility_identifier,
250 int window_number,
251 CGVector normalized_offset);
252```
253
254It’s hard to know when you will need it, but unexpected and unexplainable
255failures in the simulator are a good clue...
256
257If you need window resizing, the following allows you to:
258
259```
260// Action (XCUI, hence local) to resize split windows by dragging the splitter.
261// This action requires two windows (|first_window_number| and
262// |second_window_number|, in any order) to find where the splitter is located.
263// A given |first_window_normalized_screen_size| defines the normalized size
264// [0.0 - 1.0] wanted for the |first_window_number|. Returns NO if any window
265// is not found or if one of them is a floating window.
266// Notes: The size requested
267// will be matched by the OS to the closest available multiwindow layout. This
268// function works with any device orientation and with either LTR or RTL
269// languages. Example of use:
270// [ChromeEarlGrey openNewWindow];
271// [ChromeEarlGrey waitForForegroundWindowCount:2];
272// chrome_test_util::DragWindowSplitterToSize(0, 1, 0.25);
273// Starting with window sizes 438 and 320 pts, this will resize
274// them to 320pts and 438 pts respectively.
275BOOL DragWindowSplitterToSize(int first_window_number,
276 int second_window_number,
277 CGFloat first_window_normalized_screen_size);
278```
279
280## setUp/tearDown/test
281
David Jean0874f712021-03-11 15:37:29282In multiwindow tests, a failure to clear extra windows and root matcher at the
283end of a test, would mean a more than likely failure on the next one. To do so,
284the setUp/tearDown methods do not need any changes. ```closeAllExtraWindows```
285and ```[EarlGrey setRootMatcherForSubsequentInteractions:nil]``` are called on
286```[ChromeTestCase tearDown]``` and ```[ChromeTestCase setUpForTestCase]```.
David Jean4ddad6b2021-02-26 12:46:49287
288Tests should check if multiwindow is available on their first lines, to avoid
289failing on iPhones:
290
291```
292- (void)testDragAndDropAtEdgeToCreateNewWindow {
293 if (![ChromeEarlGrey areMultipleWindowsSupported])
294 EARL_GREY_TEST_DISABLED(@"Multiple windows can't be opened.");
295 ...
296```