1// Copyright 2018 The Flutter Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#import <EarlGrey/EarlGrey.h> 6#import <XCTest/XCTest.h> 7 8#import "../ios_add2app/AppDelegate.h" 9#import "../ios_add2app/DualFlutterViewController.h" 10#import "../ios_add2app/FullScreenViewController.h" 11#import "../ios_add2app/MainViewController.h" 12#import "../ios_add2app/HybridViewController.h" 13 14@interface FlutterTests : XCTestCase 15@end 16 17@implementation FlutterTests { 18 int _flutterWarmEngineTaps; 19} 20 21- (instancetype)init { 22 self = [super init]; 23 24 if (self) { 25 _flutterWarmEngineTaps = 0; 26 } 27 28 return self; 29} 30 31- (void)expectSemanticsNotification:(FlutterViewController*)viewController { 32 [self expectationForNotification:FlutterSemanticsUpdateNotification object:viewController handler:nil]; 33 [viewController.engine ensureSemanticsEnabled]; 34 [self waitForExpectationsWithTimeout:30.0 handler:nil]; 35} 36 37- (void)testFullScreenCanPop { 38 [[EarlGrey selectElementWithMatcher:grey_keyWindow()] 39 assertWithMatcher:grey_sufficientlyVisible()]; 40 41 [[EarlGrey selectElementWithMatcher:grey_buttonTitle(@"Full Screen (Cold)")] 42 performAction:grey_tap()]; 43 44 __weak FlutterViewController *weakViewController; 45 @autoreleasepool { 46 UINavigationController *navController = 47 (UINavigationController *)((AppDelegate *) 48 [[UIApplication sharedApplication] 49 delegate]) 50 .window.rootViewController; 51 weakViewController = 52 (FullScreenViewController *)navController.visibleViewController; 53 [self expectSemanticsNotification:weakViewController]; 54 GREYAssertNotNil(weakViewController, 55 @"Expected non-nil FullScreenViewController."); 56 } 57 58 [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"POP")] 59 performAction:grey_tap()]; 60 // EarlGrey v1 isn't good at detecting this yet - 2.0 will be able to do it 61 int tries = 10; 62 double delay = 1.0; 63 while (weakViewController != nil && tries != 0) { 64 CFRunLoopRunInMode(kCFRunLoopDefaultMode, delay, false); 65 tries--; 66 } 67 [[EarlGrey selectElementWithMatcher:grey_buttonTitle(@"Native iOS View")] 68 assertWithMatcher:grey_sufficientlyVisible()]; 69 GREYAssertNil(weakViewController, 70 @"Expected FullScreenViewController to be deallocated."); 71} 72 73- (void)testDualFlutterView { 74 [[EarlGrey selectElementWithMatcher:grey_keyWindow()] 75 assertWithMatcher:grey_sufficientlyVisible()]; 76 77 [[EarlGrey 78 selectElementWithMatcher:grey_buttonTitle(@"Dual Flutter View (Cold)")] 79 performAction:grey_tap()]; 80 81 @autoreleasepool { 82 UINavigationController *navController = 83 (UINavigationController *)((AppDelegate *) 84 [[UIApplication sharedApplication] 85 delegate]) 86 .window.rootViewController; 87 DualFlutterViewController *viewController = 88 (DualFlutterViewController *)navController.visibleViewController; 89 GREYAssertNotNil(viewController, 90 @"Expected non-nil DualFlutterViewController."); 91 [self expectSemanticsNotification:viewController.topFlutterViewController]; 92 [self expectSemanticsNotification:viewController.bottomFlutterViewController]; 93 } 94 95 // Verify that there are two Flutter views with the expected marquee text. 96 [[[EarlGrey 97 selectElementWithMatcher:grey_accessibilityLabel(@"This is Marquee")] 98 atIndex:0] assertWithMatcher:grey_notNil()]; 99 [[[EarlGrey 100 selectElementWithMatcher:grey_accessibilityLabel(@"This is Marquee")] 101 atIndex:1] assertWithMatcher:grey_notNil()]; 102 103 [[EarlGrey selectElementWithMatcher:grey_buttonTitle(@"Back")] 104 performAction:grey_tap()]; 105 106 [[EarlGrey selectElementWithMatcher:grey_buttonTitle(@"Native iOS View")] 107 assertWithMatcher:grey_sufficientlyVisible()]; 108} 109 110- (void)testHybridView { 111 [[EarlGrey selectElementWithMatcher:grey_keyWindow()] 112 assertWithMatcher:grey_sufficientlyVisible()]; 113 114 [[EarlGrey selectElementWithMatcher:grey_buttonTitle(@"Hybrid View (Warm)")] 115 performAction:grey_tap()]; 116 117 @autoreleasepool { 118 UINavigationController *navController = 119 (UINavigationController *)((AppDelegate *) 120 [[UIApplication sharedApplication] 121 delegate]) 122 .window.rootViewController; 123 HybridViewController *viewController = 124 (HybridViewController *)navController.visibleViewController; 125 GREYAssertNotNil(viewController.flutterViewController, 126 @"Expected non-nil FlutterViewController."); 127 [self expectSemanticsNotification:viewController.flutterViewController]; 128 } 129 130 [self validateCountsFlutter:@"Platform" count:0]; 131 [self validateCountsPlatform:@"Flutter" count:_flutterWarmEngineTaps]; 132 133 static const int platformTapCount = 4; 134 static const int flutterTapCount = 6; 135 136 for (int i = _flutterWarmEngineTaps; i < flutterTapCount; 137 i++, _flutterWarmEngineTaps++) { 138 [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel( 139 @"Increment via Flutter")] 140 performAction:grey_tap()]; 141 } 142 143 [self validateCountsFlutter:@"Platform" count:0]; 144 [self validateCountsPlatform:@"Flutter" count:_flutterWarmEngineTaps]; 145 146 for (int i = 0; i < platformTapCount; i++) { 147 [[EarlGrey 148 selectElementWithMatcher:grey_accessibilityLabel(@"Increment via iOS")] 149 performAction:grey_tap()]; 150 } 151 152 [self validateCountsFlutter:@"Platform" count:platformTapCount]; 153 [self validateCountsPlatform:@"Flutter" count:_flutterWarmEngineTaps]; 154 155 [[EarlGrey selectElementWithMatcher:grey_buttonTitle(@"Back")] 156 performAction:grey_tap()]; 157 [[EarlGrey selectElementWithMatcher:grey_buttonTitle(@"Native iOS View")] 158 assertWithMatcher:grey_sufficientlyVisible()]; 159} 160 161/** Validates that the text labels showing the number of button taps match the 162 * expected counts. */ 163- (void)validateCountsFlutter:(NSString *)labelPrefix count:(int)flutterCount { 164 NSString *flutterCountStr = 165 [NSString stringWithFormat:@"%@ button tapped %d times.", labelPrefix, 166 flutterCount]; 167 168 // TODO(https://github.com/flutter/flutter/issues/17988): Flutter doesn't 169 // expose accessibility IDs, so the best we can do is to search for an element 170 // with the text we expect. 171 [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(flutterCountStr)] 172 assertWithMatcher:grey_sufficientlyVisible()]; 173} 174 175- (void)validateCountsPlatform:(NSString *)labelPrefix 176 count:(int)platformCount { 177 NSString *platformCountStr = 178 [NSString stringWithFormat:@"%@ button tapped %d times.", labelPrefix, 179 platformCount]; 180 181 [[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"counter_on_iOS")] 182 assertWithMatcher:grey_text(platformCountStr)] 183 assertWithMatcher:grey_sufficientlyVisible()]; 184} 185 186@end 187