-
Notifications
You must be signed in to change notification settings - Fork 395
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add the necessary primitives to be able to automate split-screen apps #209
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
#import "FBSpringboardApplication.h" | ||
#import "XCElementSnapshot.h" | ||
#import "FBElementTypeTransformer.h" | ||
#import "FBLogger.h" | ||
#import "FBMacros.h" | ||
#import "FBMathUtils.h" | ||
#import "FBXCodeCompatibility.h" | ||
|
@@ -39,7 +40,6 @@ + (XCAccessibilityElement *)fb_onScreenElement | |
dispatch_once(&oncePoint, ^{ | ||
CGSize screenSize = [UIScreen mainScreen].bounds.size; | ||
// Consider the element, which is located close to the top left corner of the screen the on-screen one. | ||
// FIXME: Improve this algorithm for split-screen automation | ||
CGFloat pointDistance = MIN(screenSize.width, screenSize.height) * 0.2; | ||
screenPoint = CGPointMake(pointDistance, pointDistance); | ||
}); | ||
|
@@ -50,6 +50,8 @@ + (XCAccessibilityElement *)fb_onScreenElement | |
reply:^(XCAccessibilityElement *element, NSError *error) { | ||
if (nil == error) { | ||
onScreenElement = element; | ||
} else { | ||
[FBLogger logFmt:@"Cannot request the screen point at %@: %@", [NSValue valueWithCGPoint:screenPoint], error.description]; | ||
} | ||
dispatch_semaphore_signal(sem); | ||
}]; | ||
|
@@ -69,6 +71,37 @@ - (BOOL)fb_waitForAppElement:(NSTimeInterval)timeout | |
}]; | ||
} | ||
|
||
+ (NSArray<NSDictionary<NSString *, id> *> *)fb_appsInfoWithAxElements:(NSArray<XCAccessibilityElement *> *)axElements | ||
{ | ||
NSMutableArray<NSDictionary<NSString *, id> *> *result = [NSMutableArray array]; | ||
id<XCTestManager_ManagerInterface> proxy = [FBXCTestDaemonsProxy testRunnerProxy]; | ||
for (XCAccessibilityElement *axElement in axElements) { | ||
NSMutableDictionary<NSString *, id> *appInfo = [NSMutableDictionary dictionary]; | ||
pid_t pid = axElement.processIdentifier; | ||
appInfo[@"pid"] = @(pid); | ||
__block NSString *bundleId = nil; | ||
dispatch_semaphore_t sem = dispatch_semaphore_create(0); | ||
[proxy _XCT_requestBundleIDForPID:pid | ||
reply:^(NSString *bundleID, NSError *error) { | ||
if (nil == error) { | ||
bundleId = bundleID; | ||
} else { | ||
[FBLogger logFmt:@"Cannot request the bundle ID for process ID %@: %@", @(pid), error.description]; | ||
} | ||
dispatch_semaphore_signal(sem); | ||
}]; | ||
dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC))); | ||
appInfo[@"bundleId"] = bundleId ?: @""; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we set it to Unknown instead of empty? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't have any strong opinion on that. Will set to "unknown" |
||
[result addObject:appInfo.copy]; | ||
} | ||
return result.copy; | ||
} | ||
|
||
+ (NSArray<NSDictionary<NSString *, id> *> *)fb_activeAppsInfo | ||
{ | ||
return [self fb_appsInfoWithAxElements:[FBXCAXClientProxy.sharedClient activeApplications]]; | ||
} | ||
|
||
- (BOOL)fb_deactivateWithDuration:(NSTimeInterval)duration error:(NSError **)error | ||
{ | ||
if(![[XCUIDevice sharedDevice] fb_goToHomescreenWithError:error]) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ | |
#import "FBSession.h" | ||
#import "FBApplication.h" | ||
#import "FBRuntimeUtils.h" | ||
#import "XCUIApplication+FBHelpers.h" | ||
#import "XCUIDevice.h" | ||
#import "XCUIDevice+FBHealthCheck.h" | ||
#import "XCUIDevice+FBHelpers.h" | ||
|
@@ -33,6 +34,7 @@ | |
static NSString* const SNAPSHOT_TIMEOUT = @"snapshotTimeout"; | ||
static NSString* const USE_FIRST_MATCH = @"useFirstMatch"; | ||
static NSString* const REDUCE_MOTION = @"reduceMotion"; | ||
static NSString* const DEFAULT_ACTIVE_APPLICATION = @"defaultActiveApplication"; | ||
|
||
@implementation FBSessionCommands | ||
|
||
|
@@ -48,6 +50,7 @@ + (NSArray *)routes | |
[[FBRoute POST:@"/wda/apps/activate"] respondWithTarget:self action:@selector(handleSessionAppActivate:)], | ||
[[FBRoute POST:@"/wda/apps/terminate"] respondWithTarget:self action:@selector(handleSessionAppTerminate:)], | ||
[[FBRoute POST:@"/wda/apps/state"] respondWithTarget:self action:@selector(handleSessionAppState:)], | ||
[[FBRoute POST:@"/wda/apps/list"] respondWithTarget:self action:@selector(handleActiveAppsList:)], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would keep POST, since we might want to add some additional filtering arguments to the call in the future. GET request does not allow this though There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 make sense There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be still get and we should pass the further filtering with query params There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unfortunately selenium API does not really support passing of query parameters. Although, this endpoint is going to be put under |
||
[[FBRoute GET:@""] respondWithTarget:self action:@selector(handleGetActiveSession:)], | ||
[[FBRoute DELETE:@""] respondWithTarget:self action:@selector(handleDeleteSession:)], | ||
[[FBRoute GET:@"/status"].withoutSession respondWithTarget:self action:@selector(handleGetStatus:)], | ||
|
@@ -162,6 +165,11 @@ + (NSArray *)routes | |
return FBResponseWithObject(@(state)); | ||
} | ||
|
||
+ (id<FBResponsePayload>)handleActiveAppsList:(FBRouteRequest *)request | ||
{ | ||
return FBResponseWithObject([XCUIApplication fb_activeAppsInfo]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WDA has an endpoint to return appinfo, bundleid, process id, name and environment variables. It has not been released yet. So, what do you think of modifying here like below and remove it? + (id<FBResponsePayload>)handleActiveAppsList:(FBRouteRequest *)request
{
NSMutableArray<NSDictionary<NSString *, id> *> *result = [[NSMutableArray alloc] init];
NSArray *appsInfo = [XCUIApplication fb_activeAppsInfo];
for (NSDictionary *appInfo in appsInfo) {
FBApplication *app = [FBApplication fb_activeApplicationWithDefaultBundleId:appInfo[@"bundleId"]];
NSDictionary *processArguments = @{};
if (app != nil) {
processArguments = @{
@"args": app.launchArguments,
@"env": app.launchEnvironment
};
}
[result addObject:@{
@"pid": appInfo[@"pid"] ?: @"",
@"bundleId": appInfo[@"bundleId"] ?: @"",
@"name": app.identifier ?: @"",
@"processArguments": processArguments,
}];
}
return FBResponseWithObject(result);
} If ^ sounds good, I'll drop https://github.com/appium/appium/pull/13028/files for 1.15.0 release There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wanted to reduce the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 make sense |
||
} | ||
|
||
+ (id<FBResponsePayload>)handleGetActiveSession:(FBRouteRequest *)request | ||
{ | ||
return FBResponseWithObject(FBSessionCommands.sessionInformation); | ||
|
@@ -235,6 +243,7 @@ + (NSArray *)routes | |
SNAPSHOT_TIMEOUT: @([FBConfiguration snapshotTimeout]), | ||
USE_FIRST_MATCH: @([FBConfiguration useFirstMatch]), | ||
REDUCE_MOTION: @([FBConfiguration reduceMotionEnabled]), | ||
DEFAULT_ACTIVE_APPLICATION: request.session.defaultActiveApplication, | ||
} | ||
); | ||
} | ||
|
@@ -278,6 +287,9 @@ + (NSArray *)routes | |
if ([settings objectForKey:REDUCE_MOTION]) { | ||
[FBConfiguration setReduceMotionEnabled:[[settings objectForKey:REDUCE_MOTION] boolValue]]; | ||
} | ||
if ([settings objectForKey:DEFAULT_ACTIVE_APPLICATION]) { | ||
request.session.defaultActiveApplication = [settings objectForKey:DEFAULT_ACTIVE_APPLICATION]; | ||
} | ||
|
||
return [self handleGetSettings:request]; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,21 +35,44 @@ @interface FBApplication () | |
@implementation FBApplication | ||
|
||
+ (instancetype)fb_activeApplication | ||
{ | ||
return [self fb_activeApplicationWithDefaultBundleId:nil]; | ||
} | ||
|
||
+ (instancetype)fb_activeApplicationWithDefaultBundleId:(nullable NSString *)bundleId | ||
{ | ||
NSArray<XCAccessibilityElement *> *activeApplicationElements = [FBXCAXClientProxy.sharedClient activeApplications]; | ||
XCAccessibilityElement *activeApplicationElement = [activeApplicationElements firstObject]; | ||
XCAccessibilityElement *activeApplicationElement = nil; | ||
if (activeApplicationElements.count > 1) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be now bigger than 0 instead of 1? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it is fine. If it equals to 1 then the first array element is set in |
||
XCAccessibilityElement *currentElement = self.class.fb_onScreenElement; | ||
if (nil != currentElement) { | ||
for (XCAccessibilityElement *appElement in activeApplicationElements) { | ||
if (appElement.processIdentifier == currentElement.processIdentifier) { | ||
activeApplicationElement = appElement; | ||
if (nil != bundleId) { | ||
// Try to select the desired application first | ||
NSArray<NSDictionary *> *appsInfo = [self fb_appsInfoWithAxElements:activeApplicationElements]; | ||
NSUInteger index = 0; | ||
for (NSDictionary *appInfo in appsInfo) { | ||
index++; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a bit confused here. When we are iterating over the appsInfo and we find an appInfo where bundleIds are equal, we will then get index + 1 object in the activeApplicationElements. Is this intended? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good catch, we need to increment the index after the condition |
||
if ([appInfo[@"bundleId"] isEqualToString:(id)bundleId]) { | ||
activeApplicationElement = [activeApplicationElements objectAtIndex:index]; | ||
break; | ||
} | ||
} | ||
} | ||
// Fall back to the "normal" algorithm if the desired application is either | ||
// not set or is not active | ||
if (nil == activeApplicationElement) { | ||
XCAccessibilityElement *currentElement = self.class.fb_onScreenElement; | ||
if (nil != currentElement) { | ||
for (XCAccessibilityElement *appElement in activeApplicationElements) { | ||
if (appElement.processIdentifier == currentElement.processIdentifier) { | ||
activeApplicationElement = appElement; | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
if (nil == activeApplicationElement) { | ||
if (nil == activeApplicationElement && activeApplicationElements.count > 0) { | ||
activeApplicationElement = [activeApplicationElements firstObject]; | ||
} else { | ||
NSString *errMsg = @"No applications are currently active"; | ||
@throw [NSException exceptionWithName:FBElementNotVisibleException reason:errMsg userInfo:nil]; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,18 +9,7 @@ | |
|
||
#import "FBSpringboardApplication.h" | ||
|
||
#import "FBErrorBuilder.h" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 deletions |
||
#import "FBMathUtils.h" | ||
#import "FBRunLoopSpinner.h" | ||
#import "XCElementSnapshot+FBHelpers.h" | ||
#import "XCElementSnapshot.h" | ||
#import "XCUIApplication+FBHelpers.h" | ||
#import "XCUIElement+FBIsVisible.h" | ||
#import "XCUIElement+FBUtilities.h" | ||
#import "XCUIElement+FBTap.h" | ||
#import "XCUIElement+FBScrolling.h" | ||
#import "XCUIElement.h" | ||
#import "XCUIElementQuery.h" | ||
#import "FBXCodeCompatibility.h" | ||
|
||
#if TARGET_OS_TV | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good typos :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:-P