Skip to content

Commit

Permalink
feat: Add a possibility to work with absolute app paths
Browse files Browse the repository at this point in the history
  • Loading branch information
mykola-mokhnach committed Jan 12, 2024
1 parent 2da4090 commit e08be16
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 31 deletions.
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ keys | array | yes | Array of keys to type. Each item could either be a string,
> [!NOTE]
> The `modifierFlags` argument is of `unsigned long` type and defines the bitmask with depressed modifier keys for the given key.
> XCTest defines the following possible bitmasks for modifier keys:
>
>
> <pre>
> typedef NS_OPTIONS(NSUInteger, XCUIKeyModifierFlags) {
> XCUIKeyModifierNone = 0,
Expand All @@ -428,7 +428,7 @@ keys | array | yes | Array of keys to type. Each item could either be a string,
> XCUIKeyModifierAlternate = XCUIKeyModifierOption,
> };
> </pre>
>
>
> So, for example, if you want Ctrl and Shift to be depressed while entering your key then `modifierFlags` should be set to
> `(1 << 1) | (1 << 2)`, where the first constant defines `XCUIKeyModifierShift` and the seconds
> one - `XCUIKeyModifierControl`. We apply the [bitwise or](https://www.programiz.com/c-programming/bitwise-operators#or)
Expand Down Expand Up @@ -464,7 +464,8 @@ This API influences the state of the [Application Under Test](#application-under

Name | Type | Required | Description | Example
--- | --- | --- | --- | ---
bundleId | string | yes | bundle identifier of the app to be launched or activated | com.apple.TextEdit
bundleId | string | no | Bundle identifier of the app to be launched or activated. Either this argument or the `path` one must be provided | com.apple.TextEdit
path | string | no | Full path to the app bundle. Either this argument or the `bundleId` one must be provided | /Applications/Xcode.app
arguments | array | no | the list of command line arguments for the app to be be launched with. This argument is ignored if the app is already running. | ['--help']
environment | dictionary | no | Environment variables mapping. Custom variables are added to the default process environment. This argument is ignored if the app is already running. | { myEnvVar: 'value' }

Expand All @@ -477,7 +478,8 @@ This API influences the state of the [Application Under Test](#application-under

Name | Type | Required | Description | Example
--- | --- | --- | --- | ---
bundleId | string | yes | bundle identifier of the app to be activated | com.apple.Finder
bundleId | string | no | Bundle identifier of the app to be activated. Either this argument or the `path` one must be provided | com.apple.Finder
path | string | no | Full path to the app bundle. Either this argument or the `bundleId` one must be provided | /Applications/Xcode.app

### macos: terminateApp

Expand All @@ -488,7 +490,8 @@ This API influences the state of the [Application Under Test](#application-under

Name | Type | Required | Description | Example
--- | --- | --- | --- | ---
bundleId | string | yes | bundle identifier of the app to be terminated | com.apple.Finder
bundleId | string | no | Bundle identifier of the app to be terminated. Either this argument or the `path` one must be provided | com.apple.Finder
path | string | no | Full path to the app bundle. Either this argument or the `bundleId` one must be provided | /Applications/Xcode.app

#### Returns

Expand All @@ -502,7 +505,8 @@ Query an app state with given bundle identifier. An exception is thrown if the p

Name | Type | Required | Description | Example
--- | --- | --- | --- | ---
bundleId | string | yes | bundle identifier of the app to be queried | com.apple.TextEdit
bundleId | string | no | Bundle identifier of the app to be queried. Either this argument or the `path` one must be provided | com.apple.TextEdit
path | string | no | Full path to the app bundle. Either this argument or the `bundleId` one must be provided | /Applications/Xcode.app

#### Returns

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (NSString *)am_bundleID;

/**
Retrieves the full path to the app bundle
@returns full path to the app bundle
*/
- (NSString *)am_path;

/**
Retrieves the main screen rect
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ - (NSString *)am_bundleID
return [[self valueForKey:@"_applicationImpl"] valueForKey:@"_bundleID"];
}

- (NSString *)am_path
{
return [[self valueForKey:@"_applicationImpl"] valueForKey:@"_path"];
}

- (CGRect)am_screenRect
{
return NSScreen.mainScreen.frame;
Expand Down
12 changes: 8 additions & 4 deletions WebDriverAgentMac/WebDriverAgentLib/Commands/FBSessionCommands.m
Original file line number Diff line number Diff line change
Expand Up @@ -121,27 +121,31 @@ + (NSArray *)routes

+ (id<FBResponsePayload>)handleSessionAppLaunch:(FBRouteRequest *)request
{
[request.session launchApplicationWithBundleId:[request requireStringArgumentWithName:@"bundleId"]
[request.session launchApplicationWithBundleId:request.arguments[@"bundleId"]
path:request.arguments[@"path"]
arguments:request.arguments[@"arguments"]
environment:request.arguments[@"environment"]];
return FBResponseWithOK();
}

+ (id<FBResponsePayload>)handleSessionAppActivate:(FBRouteRequest *)request
{
[request.session activateApplicationWithBundleId:[request requireStringArgumentWithName:@"bundleId"]];
[request.session activateApplicationWithBundleId:request.arguments[@"bundleId"]
path:request.arguments[@"path"]];
return FBResponseWithOK();
}

+ (id<FBResponsePayload>)handleSessionAppTerminate:(FBRouteRequest *)request
{
BOOL result = [request.session terminateApplicationWithBundleId:[request requireStringArgumentWithName:@"bundleId"]];
BOOL result = [request.session terminateApplicationWithBundleId:request.arguments[@"bundleId"]
path:request.arguments[@"path"]];
return FBResponseWithObject(@(result));
}

+ (id<FBResponsePayload>)handleSessionAppState:(FBRouteRequest *)request
{
NSUInteger state = [request.session applicationStateWithBundleId:[request requireStringArgumentWithName:@"bundleId"]];
NSUInteger state = [request.session applicationStateWithBundleId:request.arguments[@"bundleId"]
path:request.arguments[@"path"]];
return FBResponseWithObject(@(state));
}

Expand Down
16 changes: 12 additions & 4 deletions WebDriverAgentMac/WebDriverAgentLib/Routing/FBSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,14 @@ extern NSString *const FINDER_BUNDLE_ID;
Launch an application with given bundle identifier in scope of current session.
@param bundleIdentifier Valid bundle identifier of the application to be launched
@param path Full path to the app bundle
@param arguments The optional array of application command line arguments. The arguments are going to be applied if the application was not running before.
@param environment The optional dictionary of environment variables for the application, which is going to be executed. The environment variables are going to be applied if the application was not running before.
@return The application instance
@throws FBApplicationMethodNotSupportedException if the method is not supported with the current XCTest SDK
*/
- (XCUIApplication *)launchApplicationWithBundleId:(NSString *)bundleIdentifier
- (XCUIApplication *)launchApplicationWithBundleId:(nullable NSString *)bundleIdentifier
path:(nullable NSString *)path
arguments:(nullable NSArray<NSString *> *)arguments
environment:(nullable NSDictionary <NSString *, NSString *> *)environment;

Expand All @@ -83,30 +85,36 @@ extern NSString *const FINDER_BUNDLE_ID;
!This method is only available since Xcode9 SDK
@param bundleIdentifier Valid bundle identifier of the application to be activated
@param path Full path to the app bundle
@return The application instance
@throws FBApplicationMethodNotSupportedException if the method is not supported with the current XCTest SDK
*/
- (XCUIApplication *)activateApplicationWithBundleId:(NSString *)bundleIdentifier;
- (XCUIApplication *)activateApplicationWithBundleId:(nullable NSString *)bundleIdentifier
path:(nullable NSString *)path;

/**
Terminate an application with the given bundle id. The application should be previously
executed by launchApplicationWithBundleId method or passed to the init method.
@param bundleIdentifier Valid bundle identifier of the application to be terminated
@param path Full path to the app bundle
@return Either YES if the app has been successfully terminated or NO if it was not running
*/
- (BOOL)terminateApplicationWithBundleId:(NSString *)bundleIdentifier;
- (BOOL)terminateApplicationWithBundleId:(nullable NSString *)bundleIdentifier
path:(nullable NSString *)path;

/**
Get the state of the particular application in scope of the current session.
!This method is only returning reliable results since Xcode9 SDK
@param bundleIdentifier Valid bundle identifier of the application to get the state from
@param path Full path to the app bundle
@return Application state as integer number. See
https://developer.apple.com/documentation/xctest/xcuiapplicationstate?language=objc
for more details on possible enum values
*/
- (NSUInteger)applicationStateWithBundleId:(NSString *)bundleIdentifier;
- (NSUInteger)applicationStateWithBundleId:(nullable NSString *)bundleIdentifier
path:(nullable NSString *)path;

@end

Expand Down
54 changes: 42 additions & 12 deletions WebDriverAgentMac/WebDriverAgentLib/Routing/FBSession.m
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,12 @@ - (XCUIApplication *)currentApplication
return [[XCUIApplication alloc] initWithBundleIdentifier:FINDER_BUNDLE_ID];
}

- (XCUIApplication *)launchApplicationWithBundleId:(NSString *)bundleIdentifier
- (XCUIApplication *)launchApplicationWithBundleId:(nullable NSString *)bundleIdentifier
path:(nullable NSString *)path
arguments:(nullable NSArray<NSString *> *)arguments
environment:(nullable NSDictionary <NSString *, NSString *> *)environment
{
XCUIApplication *app = [[XCUIApplication alloc] initWithBundleIdentifier:bundleIdentifier];
XCUIApplication *app = [self applicationWithBundleId:bundleIdentifier orPath:path];
if (app.state <= XCUIApplicationStateNotRunning) {
app.launchArguments = arguments ?: @[];
app.launchEnvironment = environment ?: @{};
Expand All @@ -101,26 +102,27 @@ - (XCUIApplication *)launchApplicationWithBundleId:(NSString *)bundleIdentifier
return app;
}

- (XCUIApplication *)activateApplicationWithBundleId:(NSString *)bundleIdentifier
- (XCUIApplication *)activateApplicationWithBundleId:(nullable NSString *)bundleIdentifier
path:(nullable NSString *)path
{
BOOL isCurrentApp = nil != self.testedApplication
&& [self.testedApplication.am_bundleID isEqualToString:bundleIdentifier];
BOOL isCurrentApp = [self isCurrentApplicationForBundleId:bundleIdentifier orPath:path];
XCUIApplication *app = isCurrentApp
? self.testedApplication
: [[XCUIApplication alloc] initWithBundleIdentifier:bundleIdentifier];
: [self applicationWithBundleId:bundleIdentifier orPath:path];
[app activate];
if (!isCurrentApp) {
self.testedApplication = app;
}
return app;
}

- (BOOL)terminateApplicationWithBundleId:(NSString *)bundleIdentifier
- (BOOL)terminateApplicationWithBundleId:(nullable NSString *)bundleIdentifier
path:(nullable NSString *)path
{
BOOL isCurrentApp = nil != self.testedApplication && [self.testedApplication.am_bundleID isEqualToString:bundleIdentifier];
BOOL isCurrentApp = [self isCurrentApplicationForBundleId:bundleIdentifier orPath:path];
XCUIApplication *app = isCurrentApp
? self.testedApplication
: [[XCUIApplication alloc] initWithBundleIdentifier:bundleIdentifier];
: [self applicationWithBundleId:bundleIdentifier orPath:path];
BOOL result = NO;
if (app.state > XCUIApplicationStateNotRunning) {
[app terminate];
Expand All @@ -132,12 +134,40 @@ - (BOOL)terminateApplicationWithBundleId:(NSString *)bundleIdentifier
return result;
}

- (NSUInteger)applicationStateWithBundleId:(NSString *)bundleIdentifier
- (NSUInteger)applicationStateWithBundleId:(nullable NSString *)bundleIdentifier
path:(nullable NSString *)path
{
XCUIApplication *app = (nil != self.testedApplication && [self.testedApplication.am_bundleID isEqualToString:bundleIdentifier])
XCUIApplication *app = [self isCurrentApplicationForBundleId:bundleIdentifier orPath:path]
? self.testedApplication
: [[XCUIApplication alloc] initWithBundleIdentifier:bundleIdentifier];
: [self applicationWithBundleId:bundleIdentifier orPath:path];
return app.state;
}

- (BOOL)isCurrentApplicationForBundleId:(nullable NSString *)bundleId
orPath:(nullable NSString *)path
{
if (nil == self.testedApplication) {
return NO;
}
if (nil != path) {
NSURL *appUrl = [NSURL fileURLWithPath:self.testedApplication.am_path].URLByStandardizingPath;
NSURL *expectedUrl = [NSURL fileURLWithPath:path].URLByStandardizingPath;
return [appUrl.path compare:expectedUrl.path] == NSOrderedSame;
}
return nil != bundleId && [self.testedApplication.am_bundleID isEqualToString:bundleId];
}

- (XCUIApplication *)applicationWithBundleId:(nullable NSString *)bundleId
orPath:(nullable NSString *)path
{
if (nil == bundleId && nil == path) {
@throw [NSException exceptionWithName:FBInvalidArgumentException
reason:@"Either app bundle identifier or app path must be provided"
userInfo:@{}];
}
return nil != path
? [[XCUIApplication alloc] initWithURL:[NSURL fileURLWithPath:(id)path]]
: [[XCUIApplication alloc] initWithBundleIdentifier:(id)bundleId];
}

@end
21 changes: 16 additions & 5 deletions lib/commands/app-management.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ const commands = {};

/**
* @typedef {Object} LaunchAppOptions
* @property {string} bundleId bundle identifier of the app to be launched
* or activated
* @property {string} [bundleId] Bundle identifier of the app to be launched
* or activated. Either this property or `path` must be provided
* @property {string} [path] Full path to the app bundle. Either this property
* or `bundleId` must be provided
* @property {string[]} arguments the list of command line arguments
* for the app to be be launched with. This parameter is ignored if the app
* is already running.
Expand All @@ -29,7 +31,10 @@ commands.macosLaunchApp = async function macosLaunchApp (opts) {

/**
* @typedef {Object} ActivateAppOptions
* @property {string} bundleId bundle identifier of the app to be activated
* @property {string} [bundleId] Bundle identifier of the app to be activated.
* Either this property or `path` must be provided
* @property {string} [path] Full path to the app bundle. Either this property
* or `bundleId` must be provided
*/

/**
Expand All @@ -45,7 +50,10 @@ commands.macosActivateApp = async function macosActivateApp (opts) {

/**
* @typedef {Object} TerminateAppOptions
* @property {string} bundleId bundle identifier of the app to be terminated
* @property {string} [bundleId] Bundle identifier of the app to be terminated.
* Either this property or `path` must be provided
* @property {string} [path] Full path to the app bundle. Either this property
* or `bundleId` must be provided
*/

/**
Expand All @@ -63,7 +71,10 @@ commands.macosTerminateApp = async function macosTerminateApp (opts) {

/**
* @typedef {Object} QueryAppStateOptions
* @property {string} bundleId bundle identifier of the app whose state should be queried
* @property {string} [bundleId] Bundle identifier of the app whose state should be queried.
* Either this property or `path` must be provided
* @property {string} [path] Full path to the app bundle. Either this property
* or `bundleId` must be provided
*/

/**
Expand Down

0 comments on commit e08be16

Please sign in to comment.