| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/chrome/test/earl_grey/chrome_xcui_actions.h" |
| |
| #import "base/apple/foundation_util.h" |
| #import "ios/chrome/test/earl_grey/chrome_earl_grey.h" |
| #import "ios/testing/earl_grey/earl_grey_test.h" |
| |
| namespace { |
| |
| // The splitter is 10 points wide. |
| const CGFloat kSplitterWidth = 10.0; |
| |
| // Returns a normalized vector for the given edge. |
| CGVector GetNormalizedEdgeVector(GREYContentEdge edge) { |
| switch (edge) { |
| case kGREYContentEdgeLeft: |
| return CGVectorMake(0.02, 0.5); |
| case kGREYContentEdgeRight: |
| return CGVectorMake(.98, 0.5); |
| case kGREYContentEdgeTop: |
| return CGVectorMake(0.5, 0.02); |
| case kGREYContentEdgeBottom: |
| return CGVectorMake(0.5, 0.98); |
| default: |
| return CGVectorMake(0.5, 0.5); |
| } |
| } |
| |
| NSString* GetWindowAccessibilityIdentifier(int window_number) { |
| return [NSString stringWithFormat:@"%d", window_number]; |
| } |
| |
| // Finds the element with the given `identifier` of given `type` in given |
| // `window_number`. If `identitifer` is nil, this will return a element for the |
| // window with `window_number` itself. |
| XCUIElement* GetElementMatchingIdentifierInWindow(XCUIApplication* app, |
| NSString* identifier, |
| int window_number, |
| XCUIElementType type) { |
| NSString* window_id = GetWindowAccessibilityIdentifier(window_number); |
| XCUIElementQuery* query = nil; |
| if (identifier) { |
| // Check for matching descendants. |
| query = [[[app.windows matchingIdentifier:window_id] |
| descendantsMatchingType:type] matchingIdentifier:identifier]; |
| } else { |
| // Check for window itself. |
| query = [app.windows matchingIdentifier:window_id]; |
| } |
| |
| if (query.count == 0) |
| return nil; |
| |
| return [query elementBoundByIndex:0]; |
| } |
| |
| // Long press at `start_point` and drag to `end_point`, with fixed press and |
| // hold druations and drag velocity. |
| void LongPressAndDragBetweenCoordinates(XCUICoordinate* start_point, |
| XCUICoordinate* end_point) { |
| [start_point pressForDuration:1.5 |
| thenDragToCoordinate:end_point |
| withVelocity:XCUIGestureVelocityDefault |
| thenHoldForDuration:1.0]; |
| } |
| |
| // Long press on `src_element`'s center then drag to the point in `dst_element` |
| // defined by `dst_normalized_offset`. Returns NO if either element is nil, YES |
| // otherwise. |
| BOOL LongPressAndDragBetweenElements(XCUIElement* src_element, |
| XCUIElement* dst_element, |
| CGVector dst_normalized_offset) { |
| if (!src_element || !dst_element) |
| return NO; |
| |
| XCUICoordinate* start_point = |
| [src_element coordinateWithNormalizedOffset:CGVectorMake(0.5, 0.5)]; |
| XCUICoordinate* end_point = |
| [dst_element coordinateWithNormalizedOffset:dst_normalized_offset]; |
| |
| LongPressAndDragBetweenCoordinates(start_point, end_point); |
| return YES; |
| } |
| |
| } // namespace |
| |
| namespace chrome_test_util { |
| |
| BOOL LongPressCellAndDragToEdge(NSString* accessibility_identifier, |
| GREYContentEdge edge, |
| int window_number) { |
| XCUIApplication* app = [[XCUIApplication alloc] init]; |
| XCUIElement* drag_element = GetElementMatchingIdentifierInWindow( |
| app, accessibility_identifier, window_number, XCUIElementTypeCell); |
| |
| // `app` is still an element, so it can just be passed in directly here. |
| return LongPressAndDragBetweenElements(drag_element, app, |
| GetNormalizedEdgeVector(edge)); |
| } |
| |
| BOOL LongPressCellAndDragToOffsetOf(NSString* src_accessibility_identifier, |
| int src_window_number, |
| NSString* dst_accessibility_identifier, |
| int dst_window_number, |
| CGVector dst_normalized_offset) { |
| XCUIApplication* app = [[XCUIApplication alloc] init]; |
| XCUIElement* src_element = GetElementMatchingIdentifierInWindow( |
| app, src_accessibility_identifier, src_window_number, |
| XCUIElementTypeCell); |
| |
| XCUIElement* dst_element = GetElementMatchingIdentifierInWindow( |
| app, dst_accessibility_identifier, dst_window_number, |
| XCUIElementTypeCell); |
| |
| return LongPressAndDragBetweenElements(src_element, dst_element, |
| dst_normalized_offset); |
| } |
| |
| BOOL LongPressLinkAndDragToView(NSString* src_accessibility_identifier, |
| int src_window_number, |
| NSString* dst_accessibility_identifier, |
| int dst_window_number, |
| CGVector dst_normalized_offset) { |
| XCUIApplication* app = [[XCUIApplication alloc] init]; |
| XCUIElement* src_element = GetElementMatchingIdentifierInWindow( |
| app, src_accessibility_identifier, src_window_number, |
| XCUIElementTypeLink); |
| |
| XCUIElement* dst_element = GetElementMatchingIdentifierInWindow( |
| app, dst_accessibility_identifier, dst_window_number, XCUIElementTypeAny); |
| |
| return LongPressAndDragBetweenElements(src_element, dst_element, |
| dst_normalized_offset); |
| } |
| |
| BOOL LongPressLinkAndDragToView(NSString* src_accessibility_identifier, |
| NSString* dst_accessibility_identifier) { |
| return LongPressLinkAndDragToView(src_accessibility_identifier, 0, |
| dst_accessibility_identifier, 0, |
| CGVectorMake(0.5, 0.5)); |
| } |
| |
| BOOL DragWindowSplitterToSize(int first_window_number, |
| int second_window_number, |
| CGFloat first_window_normalized_screen_size) { |
| CGRect first_rect = |
| [ChromeEarlGrey screenPositionOfScreenWithNumber:first_window_number]; |
| if (CGRectIsEmpty(first_rect)) |
| return NO; |
| |
| CGRect second_rect = |
| [ChromeEarlGrey screenPositionOfScreenWithNumber:second_window_number]; |
| if (CGRectIsEmpty(second_rect)) |
| return NO; |
| |
| // Fail early if a floating window was passed in in one of the windows. |
| if (first_rect.origin.x != second_rect.origin.x && |
| first_rect.origin.y != second_rect.origin.y) |
| return NO; |
| |
| // Use device orientation to see if it is one of the modes where we need to |
| // invert ltr, because the two rects returned above are always related to |
| // UIDeviceOrientationPortrait, and always defined with x,y being the top |
| // left corner and width and height always positive. |
| UIDeviceOrientation orientation = |
| [[GREY_REMOTE_CLASS_IN_APP(UIDevice) currentDevice] orientation]; |
| BOOL inverted_display = orientation == UIDeviceOrientationLandscapeRight || |
| orientation == UIDeviceOrientationPortraitUpsideDown; |
| |
| // Select leftmost window as reference. |
| BOOL ltr = first_rect.origin.x < second_rect.origin.x; |
| BOOL landscape = NO; |
| if (first_rect.origin.x == second_rect.origin.x) { |
| ltr = first_rect.origin.y < second_rect.origin.y; |
| landscape = YES; |
| } |
| if (inverted_display) |
| ltr = !ltr; |
| int left_window = ltr ? first_window_number : second_window_number; |
| |
| XCUIApplication* app = [[XCUIApplication alloc] init]; |
| XCUIElementQuery* query = [app.windows |
| matchingIdentifier:GetWindowAccessibilityIdentifier(left_window)]; |
| |
| if (query.count == 0) |
| return NO; |
| XCUIElement* element = [query elementBoundByIndex:0]; |
| |
| // Start on center of splitter, right of leftmost window. |
| XCUICoordinate* start_point = |
| [[element coordinateWithNormalizedOffset:CGVectorMake(1.0, 0.5)] |
| coordinateWithOffset:CGVectorMake(kSplitterWidth / 2.0, 0.0)]; |
| |
| // Transform screen relative normalized size into offset relative to |
| // 'left_window'. |
| CGFloat offset; |
| if (landscape) { |
| CGFloat height = |
| first_rect.size.height + kSplitterWidth + second_rect.size.height; |
| if (ltr) { |
| offset = |
| first_window_normalized_screen_size * height / first_rect.size.height; |
| } else { |
| offset = (1.0 - first_window_normalized_screen_size) * height / |
| second_rect.size.height; |
| } |
| } else { |
| CGFloat width = |
| first_rect.size.width + kSplitterWidth + second_rect.size.width; |
| if (ltr) { |
| offset = |
| first_window_normalized_screen_size * width / first_rect.size.width; |
| } else { |
| offset = (1.0 - first_window_normalized_screen_size) * width / |
| second_rect.size.width; |
| } |
| } |
| |
| XCUICoordinate* end_point = |
| [element coordinateWithNormalizedOffset:CGVectorMake(offset, 0.5)]; |
| |
| [start_point pressForDuration:0.1 |
| thenDragToCoordinate:end_point |
| withVelocity:XCUIGestureVelocityFast |
| thenHoldForDuration:0.2]; |
| |
| return YES; |
| } |
| |
| BOOL TapAtOffsetOf(NSString* accessibility_identifier, |
| int window_number, |
| CGVector normalized_offset) { |
| XCUIApplication* app = [[XCUIApplication alloc] init]; |
| XCUIElement* element = GetElementMatchingIdentifierInWindow( |
| app, accessibility_identifier, window_number, XCUIElementTypeAny); |
| |
| if (!element) |
| return NO; |
| |
| XCUICoordinate* tap_point = |
| [element coordinateWithNormalizedOffset:normalized_offset]; |
| [tap_point tap]; |
| |
| return YES; |
| } |
| |
| BOOL TypeText(NSString* accessibility_identifier, |
| int window_number, |
| NSString* text) { |
| XCUIApplication* app = [[XCUIApplication alloc] init]; |
| |
| XCUIElement* element = GetElementMatchingIdentifierInWindow( |
| app, accessibility_identifier, window_number, XCUIElementTypeTextField); |
| |
| if (!element) |
| return NO; |
| |
| [element typeText:text]; |
| |
| return YES; |
| } |
| |
| } // namespace chrome_test_util |