Unverified Commit ee3e621f authored by Dan Field's avatar Dan Field Committed by GitHub

Remove timeout from add2app test for iOS (#28746)

parent 5e27ebbe
......@@ -6,16 +6,23 @@ interaction.
The following functionality is currently implemented:
1. A regular iOS view controller (UIViewController), similar to the default `flutter create` template.
1. A FlutterViewController subclass that takes over full screen. Demos showing this both from a cold/fresh engine state and a warm engine state.
1. A regular iOS view controller (UIViewController), similar to the default
`flutter create` template (NativeViewController.m).
1. A FlutterViewController subclass that takes over full screen. Demos showing
this both from a cold/fresh engine state and a warm engine state
(FullScreenViewController.m).
1. A demo of pushing a FlutterViewController on as a child view.
1. A demo of showing both the native and the Flutter views using a platform channel to to interact with each other.
1. A demo of showing two FlutterViewControllers simultaneously.
1. A demo of showing both the native and the Flutter views using a platform
channel to to interact with each other (HybridViewController.m).
1. A demo of showing two FlutterViewControllers simultaneously
(DualViewController.m).
A few key things are tested here:
A few key things are tested here (IntegrationTests.m):
1. The ability to pre-warm the engine and attach/detatch a ViewController from it.
1. The ability to simultaneously run two instances of the engine.
1. The ability to pre-warm the engine and attach/detatch a ViewController from
it.
1. The ability to use platform channels to communicate between views.
1. That a FlutterViewController can be freed when no longer in use.
1. The ability to simultaneously run two instances of the engine.
1. That a FlutterViewController can be freed when no longer in use (also tested
from FlutterViewControllerTests.m).
1. That a FlutterEngine can be freed when no longer in use.
\ No newline at end of file
......@@ -500,6 +500,7 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
......@@ -559,6 +560,7 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_BITCODE = NO;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
......
......@@ -8,6 +8,9 @@ NS_ASSUME_NONNULL_BEGIN
@interface DualFlutterViewController : UIViewController
@property (readonly, strong, nonatomic) FlutterViewController* topFlutterViewController;
@property (readonly, strong, nonatomic) FlutterViewController* bottomFlutterViewController;
@end
NS_ASSUME_NONNULL_END
......@@ -23,19 +23,18 @@
stackView.layoutMarginsRelativeArrangement = YES;
[self.view addSubview:stackView];
FlutterViewController* topFlutterViewController = [[FlutterViewController alloc] init];
FlutterViewController* bottomFlutterViewController= [[FlutterViewController alloc] init];
[topFlutterViewController setInitialRoute:@"marquee_green"];
[self addChildViewController:topFlutterViewController];
[stackView addArrangedSubview:topFlutterViewController.view];
[topFlutterViewController didMoveToParentViewController:self];
[bottomFlutterViewController setInitialRoute:@"marquee_purple"];
[self addChildViewController:bottomFlutterViewController];
[stackView addArrangedSubview:bottomFlutterViewController.view];
[topFlutterViewController didMoveToParentViewController:self];
_topFlutterViewController = [[FlutterViewController alloc] init];
_bottomFlutterViewController= [[FlutterViewController alloc] init];
[_topFlutterViewController setInitialRoute:@"marquee_green"];
[self addChildViewController:_topFlutterViewController];
[stackView addArrangedSubview:_topFlutterViewController.view];
[_topFlutterViewController didMoveToParentViewController:self];
[_bottomFlutterViewController setInitialRoute:@"marquee_purple"];
[self addChildViewController:_bottomFlutterViewController];
[stackView addArrangedSubview:_bottomFlutterViewController.view];
[_bottomFlutterViewController didMoveToParentViewController:self];
}
@end
......@@ -10,6 +10,8 @@ NS_ASSUME_NONNULL_BEGIN
@interface HybridViewController : UIViewController<NativeViewControllerDelegate>
@property (readonly, strong, nonatomic) FlutterViewController* flutterViewController;
@end
NS_ASSUME_NONNULL_END
......@@ -43,7 +43,7 @@ static NSString *_kPing = @"ping";
[stackView addArrangedSubview:nativeViewController.view];
[nativeViewController didMoveToParentViewController:self];
FlutterViewController *flutterViewController =
_flutterViewController =
[[FlutterViewController alloc] initWithEngine:[self engine]
nibName:nil
bundle:nil];
......@@ -51,11 +51,11 @@ static NSString *_kPing = @"ping";
_messageChannel = [[FlutterBasicMessageChannel alloc]
initWithName:_kChannel
binaryMessenger:flutterViewController
binaryMessenger:_flutterViewController
codec:[FlutterStringCodec sharedInstance]];
[self addChildViewController:flutterViewController];
[stackView addArrangedSubview:flutterViewController.view];
[flutterViewController didMoveToParentViewController:self];
[self addChildViewController:_flutterViewController];
[stackView addArrangedSubview:_flutterViewController.view];
[_flutterViewController didMoveToParentViewController:self];
__weak NativeViewController *weakNativeViewController = nativeViewController;
[_messageChannel setMessageHandler:^(id message, FlutterReply reply) {
......
......@@ -6,12 +6,39 @@
#import <XCTest/XCTest.h>
#import "../ios_add2app/AppDelegate.h"
#import "../ios_add2app/MainViewController.h"
#import "../ios_add2app/DualFlutterViewController.h"
#import "../ios_add2app/FullScreenViewController.h"
static void waitForInitialFlutterRender() {
// TODO(dnfield,jamesderlin): actually sync with Flutter rendering.
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);
#import "../ios_add2app/MainViewController.h"
#import "../ios_add2app/HybridViewController.h"
static void waitForFlutterSemanticsTree(FlutterViewController *viewController) {
int tries = 10;
double delay = 1.0;
// ensureSemanticsEnabled is a synchronous call, but only ensures that the
// semantics tree will be built on a subsequent frame (as opposed to being
// available at time it returns).
// To actually get the tree, we have to wait for the FlutterSemanticsUpdate
// notification, which lets us know that a semantics tree has been built;
// but we cannot block the main thread while waiting (so we use
// CFRunLoopRunInMode).
__block BOOL semanticsAvailable = NO;
__block id<NSObject> observer = [[NSNotificationCenter defaultCenter]
addObserverForName:@"FlutterSemanticsUpdate"
object:viewController
queue:nil
usingBlock:^(NSNotification *notification) {
semanticsAvailable = YES;
[[NSNotificationCenter defaultCenter] removeObserver:observer];
}];
[viewController.engine ensureSemanticsEnabled];
while (semanticsAvailable == NO && tries != 0) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, delay, false);
tries--;
[viewController.engine ensureSemanticsEnabled];
}
GREYAssertTrue(semanticsAvailable, @"Semantics Tree did not build!");
}
@interface FlutterTests : XCTestCase
......@@ -38,43 +65,67 @@ static void waitForInitialFlutterRender() {
[[EarlGrey selectElementWithMatcher:grey_buttonTitle(@"Full Screen (Cold)")]
performAction:grey_tap()];
waitForInitialFlutterRender();
__weak FlutterViewController* weakViewController;
__weak FlutterViewController *weakViewController;
@autoreleasepool {
UINavigationController* navController =
(UINavigationController*)((AppDelegate*)[
[UIApplication sharedApplication]
UINavigationController *navController =
(UINavigationController *)((AppDelegate *)
[[UIApplication sharedApplication]
delegate])
.window.rootViewController;
weakViewController =
(FullScreenViewController*)navController.visibleViewController;
GREYAssertNotNil(weakViewController, @"Expected non-nil FullScreenViewController.");
(FullScreenViewController *)navController.visibleViewController;
waitForFlutterSemanticsTree(weakViewController);
GREYAssertNotNil(weakViewController,
@"Expected non-nil FullScreenViewController.");
}
[[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"POP")] performAction:grey_tap()];
waitForInitialFlutterRender();
[[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"POP")]
performAction:grey_tap()];
// EarlGrey v1 isn't good at detecting this yet - 2.0 will be able to do it
int tries = 10;
double delay = 1.0;
while (weakViewController != nil && tries != 0) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, delay, false);
tries--;
}
[[EarlGrey selectElementWithMatcher:grey_buttonTitle(@"Native iOS View")]
assertWithMatcher:grey_sufficientlyVisible()];
GREYAssertNil(weakViewController, @"Expected FullScreenViewController to be deallocated.");
GREYAssertNil(weakViewController,
@"Expected FullScreenViewController to be deallocated.");
}
- (void)testDualFlutterView {
[[EarlGrey selectElementWithMatcher:grey_keyWindow()]
assertWithMatcher:grey_sufficientlyVisible()];
[[EarlGrey selectElementWithMatcher:grey_buttonTitle(@"Dual Flutter View (Cold)")]
[[EarlGrey
selectElementWithMatcher:grey_buttonTitle(@"Dual Flutter View (Cold)")]
performAction:grey_tap()];
waitForInitialFlutterRender();
@autoreleasepool {
UINavigationController *navController =
(UINavigationController *)((AppDelegate *)
[[UIApplication sharedApplication]
delegate])
.window.rootViewController;
DualFlutterViewController *viewController =
(DualFlutterViewController *)navController.visibleViewController;
GREYAssertNotNil(viewController,
@"Expected non-nil DualFlutterViewController.");
waitForFlutterSemanticsTree(viewController.topFlutterViewController);
waitForFlutterSemanticsTree(viewController.bottomFlutterViewController);
}
// Verify that there are two Flutter views with the expected marquee text.
[[[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"This is Marquee")] atIndex:0]
assertWithMatcher:grey_notNil()];
[[[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"This is Marquee")] atIndex:1]
assertWithMatcher:grey_notNil()];
[[EarlGrey selectElementWithMatcher:grey_buttonTitle(@"Back")] performAction:grey_tap()];
[[[EarlGrey
selectElementWithMatcher:grey_accessibilityLabel(@"This is Marquee")]
atIndex:0] assertWithMatcher:grey_notNil()];
[[[EarlGrey
selectElementWithMatcher:grey_accessibilityLabel(@"This is Marquee")]
atIndex:1] assertWithMatcher:grey_notNil()];
[[EarlGrey selectElementWithMatcher:grey_buttonTitle(@"Back")]
performAction:grey_tap()];
[[EarlGrey selectElementWithMatcher:grey_buttonTitle(@"Native iOS View")]
assertWithMatcher:grey_sufficientlyVisible()];
......@@ -84,9 +135,21 @@ static void waitForInitialFlutterRender() {
[[EarlGrey selectElementWithMatcher:grey_keyWindow()]
assertWithMatcher:grey_sufficientlyVisible()];
[[EarlGrey selectElementWithMatcher:grey_buttonTitle(@"Hybrid View (Warm)")] performAction:grey_tap()];
[[EarlGrey selectElementWithMatcher:grey_buttonTitle(@"Hybrid View (Warm)")]
performAction:grey_tap()];
waitForInitialFlutterRender();
@autoreleasepool {
UINavigationController *navController =
(UINavigationController *)((AppDelegate *)
[[UIApplication sharedApplication]
delegate])
.window.rootViewController;
HybridViewController *viewController =
(HybridViewController *)navController.visibleViewController;
GREYAssertNotNil(viewController.flutterViewController,
@"Expected non-nil FlutterViewController.");
waitForFlutterSemanticsTree(viewController.flutterViewController);
}
[self validateCountsFlutter:@"Platform" count:0];
[self validateCountsPlatform:@"Flutter" count:_flutterWarmEngineTaps];
......@@ -94,8 +157,10 @@ static void waitForInitialFlutterRender() {
static const int platformTapCount = 4;
static const int flutterTapCount = 6;
for (int i = _flutterWarmEngineTaps; i < flutterTapCount; i++, _flutterWarmEngineTaps++) {
[[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"Increment via Flutter")]
for (int i = _flutterWarmEngineTaps; i < flutterTapCount;
i++, _flutterWarmEngineTaps++) {
[[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(
@"Increment via Flutter")]
performAction:grey_tap()];
}
......@@ -103,36 +168,43 @@ static void waitForInitialFlutterRender() {
[self validateCountsPlatform:@"Flutter" count:_flutterWarmEngineTaps];
for (int i = 0; i < platformTapCount; i++) {
[[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"Increment via iOS")]
[[EarlGrey
selectElementWithMatcher:grey_accessibilityLabel(@"Increment via iOS")]
performAction:grey_tap()];
}
[self validateCountsFlutter:@"Platform" count:platformTapCount];
[self validateCountsPlatform:@"Flutter" count:_flutterWarmEngineTaps];
[[EarlGrey selectElementWithMatcher:grey_buttonTitle(@"Back")] performAction:grey_tap()];
[[EarlGrey selectElementWithMatcher:grey_buttonTitle(@"Back")]
performAction:grey_tap()];
[[EarlGrey selectElementWithMatcher:grey_buttonTitle(@"Native iOS View")]
assertWithMatcher:grey_sufficientlyVisible()];
}
/** Validates that the text labels showing the number of button taps match the expected counts. */
- (void)validateCountsFlutter:(NSString*)labelPrefix
count:(int)flutterCount {
NSString* flutterCountStr =
[NSString stringWithFormat:@"%@ button tapped %d times.", labelPrefix, flutterCount];
/** Validates that the text labels showing the number of button taps match the
* expected counts. */
- (void)validateCountsFlutter:(NSString *)labelPrefix count:(int)flutterCount {
NSString *flutterCountStr =
[NSString stringWithFormat:@"%@ button tapped %d times.", labelPrefix,
flutterCount];
// TODO(https://github.com/flutter/flutter/issues/17988): Flutter doesn't expose accessibility
// IDs, so the best we can do is to search for an element with the text we expect.
// TODO(https://github.com/flutter/flutter/issues/17988): Flutter doesn't
// expose accessibility IDs, so the best we can do is to search for an element
// with the text we expect.
[[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(flutterCountStr)]
assertWithMatcher:grey_sufficientlyVisible()];
}
- (void)validateCountsPlatform:(NSString*)labelPrefix count:(int)platformCount {
NSString* platformCountStr =
[NSString stringWithFormat:@"%@ button tapped %d times.", labelPrefix, platformCount];
- (void)validateCountsPlatform:(NSString *)labelPrefix
count:(int)platformCount {
NSString *platformCountStr =
[NSString stringWithFormat:@"%@ button tapped %d times.", labelPrefix,
platformCount];
[[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"counter_on_iOS")]
assertWithMatcher:grey_text(platformCountStr)] assertWithMatcher:grey_sufficientlyVisible()];
assertWithMatcher:grey_text(platformCountStr)]
assertWithMatcher:grey_sufficientlyVisible()];
}
@end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment