Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ NS_ASSUME_NONNULL_BEGIN

@interface XCUIApplication (FBHelpers)

/** Activates the application and waits for the system transition when resuming on iOS. */
- (void)fb_activate;

/**
Deactivates application for given time

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ _Nullable id extractIssueProperty(id issue, NSString *propertyName) {
return result;
}

@interface XCUIApplication (FBActivationPrivate)
- (void)fb_activateWaitingForSystemTransition:(BOOL)shouldWait;
@end

@implementation XCUIApplication (FBHelpers)

- (BOOL)fb_waitForAppElement:(NSTimeInterval)timeout
Expand Down Expand Up @@ -172,13 +176,38 @@ - (BOOL)fb_waitForAppElement:(NSTimeInterval)timeout
return [self fb_appsInfoWithAxElements:[FBXCAXClientProxy.sharedClient activeApplications]];
}

- (void)fb_activate
{
BOOL shouldWaitForSystemTransition = NO;
#if !TARGET_OS_TV && !TARGET_OS_WATCH
XCUIApplicationState previousState = self.state;
shouldWaitForSystemTransition = previousState == XCUIApplicationStateRunningBackground
|| previousState == XCUIApplicationStateRunningBackgroundSuspended;
#endif
[self fb_activateWaitingForSystemTransition:shouldWaitForSystemTransition];
}

- (void)fb_activateWaitingForSystemTransition:(BOOL)shouldWait
{
[self activate];
#if !TARGET_OS_TV && !TARGET_OS_WATCH
if (shouldWait) {
// App quiescence alone can miss the system resume transition.
// https://github.com/appium/appium/issues/22857
[XCUIApplication.fb_systemApplication fb_waitUntilStableWithTimeout:
FBConfiguration.sharedInstance.animationCoolOffTimeout];
}
#endif
}

- (BOOL)fb_deactivateWithDuration:(NSTimeInterval)duration error:(NSError **)error
{
if(![[XCUIDevice sharedDevice] fb_goToHomescreenWithError:error]) {
return NO;
}
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:MAX(duration, .0)]];
[self activate];
// A zero-duration background transition may still report the app as foreground.
[self fb_activateWaitingForSystemTransition:YES];
return YES;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ + (void)applyConfigurationFromCapabilities:(NSDictionary<NSString *, id> *)capab
if (nil != initialUrl) {
return [self openDeepLink:initialUrl withApplication:bundleID timeout:nil];
}
[app activate];
[app fb_activate];
return nil;
}

Expand Down
5 changes: 3 additions & 2 deletions packages/webdriveragent/WebDriverAgentLib/Routing/FBSession.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#import "FBXCTestDaemonsProxy.h"
#import "XCUIApplication+FBQuiescence.h"
#import "XCUIElement.h"
#import "XCUIElement+FBUtilities.h"

/*!
The intial value for the default application property.
Expand Down Expand Up @@ -432,7 +433,7 @@ - (XCUIApplication *)launchApplicationWithBundleId:(NSString *)bundleIdentifier
app.launchEnvironment = environment ?: @{};
[app launch];
} else {
[app activate];
[app fb_activate];
}
if (asApplicationUnderTest) {
BOOL waitForQuiescence = app.fb_shouldWaitForQuiescence;
Expand Down Expand Up @@ -475,7 +476,7 @@ - (XCUIApplication *)launchApplicationWithBundleId:(NSString *)bundleIdentifier
- (XCUIApplication *)activateApplicationWithBundleId:(NSString *)bundleIdentifier
{
XCUIApplication *app = [self makeApplicationWithBundleId:bundleIdentifier];
[app activate];
[app fb_activate];
return app;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,29 @@
#import <XCTest/XCTest.h>

#import <mach/mach_time.h>
#import <math.h>

#import "FBIntegrationTestCase.h"
#import "FBElement.h"
#import "FBSession.h"
#import "FBSessionCommands.h"
#import "FBMacros.h"
#import "FBTestMacros.h"
#import "XCUIApplication.h"
#import "XCUIApplication+FBHelpers.h"
#import "XCUIElement+FBIsVisible.h"
#import "FBXCodeCompatibility.h"
#import "FBConfiguration.h"
#import "FBScreen.h"
#import "FBRunLoopSpinner.h"
#import "XCUIDevice+FBHinge.h"

// Exercise the session-start resume path without creating a second session.
@interface FBSessionCommands (FBActivationTests)
+ (id)activateBackgroundApplication:(XCUIApplication *)app
bundleID:(NSString *)bundleID
initialUrl:(nullable NSString *)initialUrl;
@end

void calculateMaxTreeDepth(NSDictionary *tree, NSNumber *currentDepth, NSNumber** maxDepth) {
if (nil == maxDepth) {
Expand Down Expand Up @@ -83,6 +97,103 @@ - (void)testDeactivateApplication
XCTAssertTrue(self.testedApplication.buttons[@"Alerts"].exists);
}

- (void)testInputAfterDeactivate
{
[self verifyInputAfterBackgroundWithMode:0];
}

- (void)testInputAfterSessionActivate
{
[self verifyInputAfterBackgroundWithMode:1];
}

- (void)testInputAfterSessionLaunch
{
[self verifyInputAfterBackgroundWithMode:2];
}

- (void)testInputAfterSessionStartActivate
{
[self verifyInputAfterBackgroundWithMode:3];
}

- (void)verifyInputAfterBackgroundWithMode:(NSUInteger)mode
{
XCTSkipIf(self.class.isRunningInCI, @"Immediate input after activation is unreliable on slow CI hosts");
XCUIDevice *device = XCUIDevice.sharedDevice;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we skip this test in CI?

BOOL canFold = NO;
#if TARGET_OS_SIMULATOR
canFold = device.fb_canAttemptSimulatedHingeAngleInjection;
#endif
NSNumber *originalAngle = canFold ? [device fb_getSimulatedHingeAngle:nil] : nil;
NSNumber *originalDisplay = FBConfiguration.sharedInstance.currentDisplayId;
if (canFold) {
XCTAssertNotNil(originalAngle);
}
BOOL (^setAngleAndWait)(NSNumber *) = ^BOOL(NSNumber *angle) {
if (![device fb_setSimulatedHingeAngle:angle.doubleValue error:nil]) {
return NO;
}
return [[[FBRunLoopSpinner new] timeout:5] spinUntilTrue:^BOOL{
NSNumber *reading = [device fb_getSimulatedHingeAngle:nil];
return nil != reading && fabs(reading.doubleValue - angle.doubleValue) < 1;
}];
};
FBSession *session = mode != 0 ? [FBSession initWithApplication:self.testedApplication] : nil;
@try {
for (NSNumber *angle in (canFold ? @[@0, @90, @180] : @[@0])) {
if (canFold) {
XCTAssertTrue(setAngleAndWait(angle), @"Hinge did not reach %@ degrees", angle);
for (NSDictionary *screen in [FBScreen screensWithError:nil]) {
if ([screen[@"isMain"] boolValue] == (angle.intValue == 0)) {
FBConfiguration.sharedInstance.currentDisplayId = screen[@"displayId"];
}
}
}
for (NSNumber *duration in @[@0, @2]) {
[self launchApplication];
NSError *error = nil;
if (mode != 0) {
XCTAssertTrue([device fb_goToHomescreenWithError:&error]);
XCTAssertTrue([[[FBRunLoopSpinner new] timeout:5] spinUntilTrue:^BOOL{
XCUIApplicationState state = self.testedApplication.state;
return state == XCUIApplicationStateRunningBackground
|| state == XCUIApplicationStateRunningBackgroundSuspended;
}]);
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:duration.doubleValue]];
XCUIApplicationState state = self.testedApplication.state;
XCTAssertTrue(state == XCUIApplicationStateRunningBackground
|| state == XCUIApplicationStateRunningBackgroundSuspended,
@"Expected a background app before activation (angle %@, duration %@), got %lu",
angle, duration, (unsigned long)state);
if (mode == 1) {
[session activateApplicationWithBundleId:self.testedApplication.bundleID];
} else if (mode == 2) {
[session launchApplicationWithBundleId:self.testedApplication.bundleID
shouldWaitForQuiescence:nil arguments:nil environment:nil];
} else {
XCTAssertNil([FBSessionCommands activateBackgroundApplication:self.testedApplication
bundleID:self.testedApplication.bundleID
initialUrl:nil]);
}
} else {
XCTAssertTrue([self.testedApplication fb_deactivateWithDuration:duration.doubleValue error:&error]);
}
XCTAssertNil(error);
// Existence/foreground checks alone miss lost input. Do not add a
// delay or retry: this first tap must actually open the alerts page.
[self.testedApplication.buttons[@"Alerts"] tap];
XCTAssertTrue([self.testedApplication.buttons[FBShowAlertButtonName] waitForExistenceWithTimeout:2]);
}
}
} @finally {
[session kill];
BOOL restored = nil == originalAngle || setAngleAndWait(originalAngle);
FBConfiguration.sharedInstance.currentDisplayId = originalDisplay;
XCTAssertTrue(restored, @"Hinge did not return to %@ degrees", originalAngle);
}
}

- (void)testActiveApplication
{
XCUIApplication *systemApp = XCUIApplication.fb_systemApplication;
Expand Down
Loading