diff --git a/WebDriverAgentLib/FBApplication.h b/WebDriverAgentLib/FBApplication.h index 00adb527d..2aaa3d888 100644 --- a/WebDriverAgentLib/FBApplication.h +++ b/WebDriverAgentLib/FBApplication.h @@ -44,6 +44,16 @@ NS_ASSUME_NONNULL_BEGIN */ + (BOOL)fb_switchToSystemApplicationWithError:(NSError **)error; +/** + Returns the bundle indentifier of the system app (ususally Springboard) + */ ++ (NSString *)fb_systemApplicationBundleID; + +/** + Checks if the app is installed on the device under test. + */ +- (BOOL)fb_isInstalled; + @end NS_ASSUME_NONNULL_END diff --git a/WebDriverAgentLib/FBApplication.m b/WebDriverAgentLib/FBApplication.m index 8a9a48ba0..095620118 100644 --- a/WebDriverAgentLib/FBApplication.m +++ b/WebDriverAgentLib/FBApplication.m @@ -9,6 +9,7 @@ #import "FBApplication.h" +#import "FBExceptions.h" #import "FBXCAccessibilityElement.h" #import "FBLogger.h" #import "FBRunLoopSpinner.h" @@ -16,6 +17,7 @@ #import "FBActiveAppDetectionPoint.h" #import "FBXCodeCompatibility.h" #import "FBXCTestDaemonsProxy.h" +#import "LSApplicationWorkspace.h" #import "XCUIApplication.h" #import "XCUIApplication+FBHelpers.h" #import "XCUIApplicationImpl.h" @@ -133,6 +135,16 @@ + (instancetype)fb_systemApplication [[FBXCAXClientProxy.sharedClient systemApplication] processIdentifier]]; } ++ (NSString *)fb_systemApplicationBundleID +{ + static dispatch_once_t onceToken; + static NSString *systemAppBundleID; + dispatch_once(&onceToken, ^{ + systemAppBundleID = [[self.class fb_systemApplication] bundleID]; + }); + return systemAppBundleID; +} + + (instancetype)applicationWithPID:(pid_t)processID { if ([NSProcessInfo processInfo].processIdentifier == processID) { @@ -141,14 +153,33 @@ + (instancetype)applicationWithPID:(pid_t)processID return (FBApplication *)[FBXCAXClientProxy.sharedClient monitoredApplicationWithProcessIdentifier:processID]; } +/** + https://github.com/appium/WebDriverAgent/issues/702 + */ +- (void)fb_assertInstalledByAction:(NSString *)action +{ + if (![self.bundleID isEqualToString:[self.class fb_systemApplicationBundleID]] && !self.fb_isInstalled) { + NSString *message = [NSString stringWithFormat:@"The application '%@' cannot be %@ because it is not installed on the device under test", + self.bundleID, action]; + [[NSException exceptionWithName:FBApplicationMissingException reason:message userInfo:nil] raise]; + } +} + - (void)launch { + [self fb_assertInstalledByAction:@"launched"]; [super launch]; if (![self fb_waitForAppElement:APP_STATE_CHANGE_TIMEOUT]) { [FBLogger logFmt:@"The application '%@' is not running in foreground after %.2f seconds", self.bundleID, APP_STATE_CHANGE_TIMEOUT]; } } +- (void)activate +{ + [self fb_assertInstalledByAction:@"activated"]; + [super activate]; +} + - (void)terminate { [super terminate]; @@ -181,4 +212,9 @@ + (BOOL)fb_switchToSystemApplicationWithError:(NSError **)error error:error]; } +- (BOOL)fb_isInstalled +{ + return [[LSApplicationWorkspace defaultWorkspace] applicationIsInstalled:self.bundleID]; +} + @end diff --git a/WebDriverAgentLib/Routing/FBExceptionHandler.m b/WebDriverAgentLib/Routing/FBExceptionHandler.m index 203fcf0e7..7b7a8263f 100644 --- a/WebDriverAgentLib/Routing/FBExceptionHandler.m +++ b/WebDriverAgentLib/Routing/FBExceptionHandler.m @@ -24,7 +24,8 @@ - (void)handleException:(NSException *)exception forResponse:(RouteResponse *)re commandStatus = [FBCommandStatus noSuchDriverErrorWithMessage:exception.reason traceback:traceback]; } else if ([exception.name isEqualToString:FBInvalidArgumentException] - || [exception.name isEqualToString:FBElementAttributeUnknownException]) { + || [exception.name isEqualToString:FBElementAttributeUnknownException] + || [exception.name isEqualToString:FBApplicationMissingException]) { commandStatus = [FBCommandStatus invalidArgumentErrorWithMessage:exception.reason traceback:traceback]; } else if ([exception.name isEqualToString:FBApplicationCrashedException] diff --git a/WebDriverAgentLib/Routing/FBExceptions.h b/WebDriverAgentLib/Routing/FBExceptions.h index 4cbc3e5c4..1c9507a19 100644 --- a/WebDriverAgentLib/Routing/FBExceptions.h +++ b/WebDriverAgentLib/Routing/FBExceptions.h @@ -49,4 +49,7 @@ extern NSString *const FBClassChainQueryParseException; /*! Exception used to notify about application crash */ extern NSString *const FBApplicationCrashedException; +/*! Exception used to notify about the application is not installed */ +extern NSString *const FBApplicationMissingException; + NS_ASSUME_NONNULL_END diff --git a/WebDriverAgentLib/Routing/FBExceptions.m b/WebDriverAgentLib/Routing/FBExceptions.m index 5ff6fed85..571cea4fb 100644 --- a/WebDriverAgentLib/Routing/FBExceptions.m +++ b/WebDriverAgentLib/Routing/FBExceptions.m @@ -20,3 +20,4 @@ NSString *const FBXPathQueryEvaluationException = @"FBXPathQueryEvaluationException"; NSString *const FBClassChainQueryParseException = @"FBClassChainQueryParseException"; NSString *const FBApplicationCrashedException = @"FBApplicationCrashedException"; +NSString *const FBApplicationMissingException = @"FBApplicationMissingException"; diff --git a/WebDriverAgentTests/IntegrationTests/FBSessionIntegrationTests.m b/WebDriverAgentTests/IntegrationTests/FBSessionIntegrationTests.m index dc9a9b1af..655d91b85 100644 --- a/WebDriverAgentTests/IntegrationTests/FBSessionIntegrationTests.m +++ b/WebDriverAgentTests/IntegrationTests/FBSessionIntegrationTests.m @@ -11,6 +11,7 @@ #import "FBIntegrationTestCase.h" #import "FBApplication.h" +#import "FBExceptions.h" #import "FBMacros.h" #import "FBSession.h" #import "FBXCodeCompatibility.h" @@ -100,6 +101,34 @@ - (void)testMainAppCanBeRestartedInScopeOfTheCurrentSession FBAssertWaitTillBecomesTrue([self.session.activeApplication.bundleID isEqualToString:testedApp.bundleID]); } +- (void)testAppWithInvalidBundleIDCannotBeStarted +{ + FBApplication *testedApp = [[FBApplication alloc] initWithBundleIdentifier:@"yolo"]; + @try { + [testedApp launch]; + XCTFail(@"An exception is expected to be thrown"); + } @catch (NSException *exception) { + XCTAssertEqualObjects(FBApplicationMissingException, exception.name); + } +} + +- (void)testAppWithInvalidBundleIDCannotBeActivated +{ + FBApplication *testedApp = [[FBApplication alloc] initWithBundleIdentifier:@"yolo"]; + @try { + [testedApp activate]; + XCTFail(@"An exception is expected to be thrown"); + } @catch (NSException *exception) { + XCTAssertEqualObjects(FBApplicationMissingException, exception.name); + } +} + +- (void)testAppWithInvalidBundleIDCannotBeTerminated +{ + FBApplication *testedApp = [[FBApplication alloc] initWithBundleIdentifier:@"yolo"]; + [testedApp terminate]; +} + - (void)testLaunchUnattachedApp { [FBUnattachedAppLauncher launchAppWithBundleId:SETTINGS_BUNDLE_ID];