| // Copyright 2019 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_actions_app_interface.h" |
| |
| #import "base/apple/foundation_util.h" |
| #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_switch_cell.h" |
| #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_switch_item.h" |
| #import "ios/chrome/test/app/tab_test_util.h" |
| #import "ios/testing/earl_grey/earl_grey_app.h" |
| #import "ios/web/public/test/earl_grey/web_view_actions.h" |
| #import "ios/web/public/test/element_selector.h" |
| |
| namespace { |
| |
| // Action to swipe left on 150pt. |
| NSArray<NSValue*>* SwipeLeft(CGPoint startPoint) { |
| const CGFloat total_length = 150; |
| const int number_of_frames = 30; |
| const CGFloat deltaX = total_length / number_of_frames; |
| // Initial displacement to trigger a swipe. |
| const int initial_displacement = 10; |
| const int beginning = ceil(initial_displacement / deltaX); |
| |
| NSMutableArray* touchPath = [[NSMutableArray alloc] init]; |
| [touchPath addObject:[NSValue valueWithCGPoint:startPoint]]; |
| |
| for (int i = beginning; i < number_of_frames; i++) { |
| CGPoint point = CGPointMake(startPoint.x - i * deltaX, startPoint.y); |
| [touchPath addObject:[NSValue valueWithCGPoint:point]]; |
| } |
| |
| [touchPath addObject:[NSValue valueWithCGPoint:CGPointMake(startPoint.x - |
| total_length, |
| startPoint.y)]]; |
| |
| return touchPath; |
| } |
| |
| NSString* kChromeActionsErrorDomain = @"ChromeActionsError"; |
| |
| } // namespace |
| |
| @implementation ChromeActionsAppInterface : NSObject |
| |
| + (id<GREYAction>)longPressElement:(ElementSelector*)selector |
| triggerContextMenu:(BOOL)triggerContextMenu { |
| return WebViewLongPressElementForContextMenu( |
| chrome_test_util::GetCurrentWebState(), selector, triggerContextMenu); |
| } |
| |
| + (id<GREYAction>)scrollElementToVisible:(ElementSelector*)selector { |
| return WebViewScrollElementToVisible(chrome_test_util::GetCurrentWebState(), |
| selector); |
| } |
| |
| + (id<GREYAction>)turnTableViewSwitchOn:(BOOL)on { |
| id<GREYMatcher> constraints = grey_not(grey_systemAlertViewShown()); |
| NSString* actionName = |
| [NSString stringWithFormat:@"Turn table view switch to %@ state", |
| on ? @"ON" : @"OFF"]; |
| return [GREYActionBlock |
| actionWithName:actionName |
| constraints:constraints |
| performBlock:^BOOL(id collectionViewCell, |
| __strong NSError** errorOrNil) { |
| // EG2 executes actions on a background thread by default. Since this |
| // action interacts with UI, kick it over to the main thread. |
| __block BOOL success = NO; |
| grey_dispatch_sync_on_main_thread(^{ |
| TableViewSwitchCell* switchCell = |
| base::apple::ObjCCast<TableViewSwitchCell>(collectionViewCell); |
| if (!switchCell) { |
| NSString* description = @"The element isn't of the expected type " |
| @"(TableViewSwitchCell)."; |
| *errorOrNil = [NSError |
| errorWithDomain:kChromeActionsErrorDomain |
| code:0 |
| userInfo:@{NSLocalizedDescriptionKey : description}]; |
| success = NO; |
| return; |
| } |
| UISwitch* switchView = switchCell.switchView; |
| if (switchView.on != on) { |
| id<GREYAction> action = [GREYActions actionForTurnSwitchOn:on]; |
| success = [action perform:switchView error:errorOrNil]; |
| return; |
| } |
| success = YES; |
| }); |
| return success; |
| }]; |
| } |
| |
| + (id<GREYAction>)tapWebElement:(ElementSelector*)selector { |
| return web::WebViewTapElement(chrome_test_util::GetCurrentWebState(), |
| selector, /*verified*/ true); |
| } |
| |
| + (id<GREYAction>)tapWebElementUnverified:(ElementSelector*)selector { |
| return web::WebViewTapElement(chrome_test_util::GetCurrentWebState(), |
| selector, /*verified*/ false); |
| } |
| |
| + (id<GREYAction>)scrollToTop { |
| GREYPerformBlock scrollToTopBlock = ^BOOL(id element, |
| __strong NSError** error) { |
| grey_dispatch_sync_on_main_thread(^{ |
| UIScrollView* view = base::apple::ObjCCast<UIScrollView>(element); |
| if (!view) { |
| *error = [NSError |
| errorWithDomain:kChromeActionsErrorDomain |
| code:0 |
| userInfo:@{ |
| NSLocalizedDescriptionKey : @"View is not a UIScrollView" |
| }]; |
| } |
| view.contentOffset = CGPointZero; |
| }); |
| return YES; |
| }; |
| return [GREYActionBlock actionWithName:@"Scroll to top" |
| performBlock:scrollToTopBlock]; |
| } |
| |
| + (id<GREYAction>)tapAtPointAtxOriginStartPercentage:(CGFloat)x |
| yOriginStartPercentage:(CGFloat)y { |
| DCHECK(0 <= x && x <= 1); |
| DCHECK(0 <= y && y <= 1); |
| |
| id<GREYMatcher> constraints = grey_notNil(); |
| NSString* actionName = |
| [NSString stringWithFormat:@"Tap at point at percentage"]; |
| |
| GREYPerformBlock actionBlock = ^BOOL(id view, __strong NSError** errorOrNil) { |
| __block BOOL success = NO; |
| grey_dispatch_sync_on_main_thread(^{ |
| CGRect rect = [view accessibilityFrame]; |
| CGPoint pointToTap = |
| CGPointMake(rect.size.width * x, rect.size.height * y); |
| success = |
| [[GREYActions actionForTapAtPoint:pointToTap] perform:view |
| error:errorOrNil]; |
| }); |
| return success; |
| }; |
| return [GREYActionBlock actionWithName:actionName |
| constraints:constraints |
| performBlock:actionBlock]; |
| } |
| |
| + (id<GREYAction>)swipeToShowDeleteButton { |
| return [GREYActionBlock |
| actionWithName:@"Swipe to display delete button" |
| constraints:nil |
| performBlock:^(UIView* element, NSError* __strong* errorOrNil) { |
| if ([element window] == nil) { |
| NSString* errorDescription = [NSString |
| stringWithFormat: |
| @"Cannot swipe on this view as it has no window and " |
| @"isn't a window itself:\n%@", |
| [element grey_description]]; |
| *errorOrNil = [NSError |
| errorWithDomain:@"No window available" |
| code:0 |
| userInfo:@{@"Failure Reason" : (errorDescription)}]; |
| // Indicates that the action failed. |
| return NO; |
| } |
| CGRect accessibilityFrame = element.accessibilityFrame; |
| CGPoint startPoint = CGPointMake( |
| accessibilityFrame.origin.x + accessibilityFrame.size.width * 0.5, |
| accessibilityFrame.origin.y + |
| accessibilityFrame.size.height * 0.5); |
| // Invoke a custom selector that animates the window of the element. |
| [GREYSyntheticEvents touchAlongPath:SwipeLeft(startPoint) |
| relativeToWindow:[element window] |
| forDuration:1 |
| timeout:10]; |
| // Indicates that the action was executed successfully. |
| return YES; |
| }]; |
| } |
| |
| @end |