| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- /**
- * Copyright (c) 2015-present, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- */
- #import "FBXCTestDaemonsProxy.h"
- #import <objc/runtime.h>
- #import "FBConfiguration.h"
- #import "FBErrorBuilder.h"
- #import "FBExceptions.h"
- #import "FBLogger.h"
- #import "FBRunLoopSpinner.h"
- #import "FBScreenRecordingPromise.h"
- #import "FBScreenRecordingRequest.h"
- #import "XCTestDriver.h"
- #import "XCTRunnerDaemonSession.h"
- #import "XCUIApplication.h"
- #import "XCUIDevice.h"
- #define LAUNCH_APP_TIMEOUT_SEC 300
- static void (*originalLaunchAppMethod)(id, SEL, NSString*, NSString*, NSArray*, NSDictionary*, void (^)(_Bool, NSError *));
- static void swizzledLaunchApp(id self, SEL _cmd, NSString *path, NSString *bundleID,
- NSArray *arguments, NSDictionary *environment,
- void (^reply)(_Bool, NSError *))
- {
- __block BOOL isSuccessful;
- __block NSError *error;
- dispatch_semaphore_t sem = dispatch_semaphore_create(0);
- originalLaunchAppMethod(self, _cmd, path, bundleID, arguments, environment, ^(BOOL passed, NSError *innerError) {
- isSuccessful = passed;
- error = innerError;
- dispatch_semaphore_signal(sem);
- });
- int64_t timeoutNs = (int64_t)(LAUNCH_APP_TIMEOUT_SEC * NSEC_PER_SEC);
- if (0 != dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, timeoutNs))) {
- NSString *message = [NSString stringWithFormat:@"The application '%@' cannot be launched within %d seconds timeout",
- bundleID ?: path, LAUNCH_APP_TIMEOUT_SEC];
- @throw [NSException exceptionWithName:FBTimeoutException reason:message userInfo:nil];
- }
- if (!isSuccessful || nil != error) {
- [FBLogger logFmt:@"%@", error.description];
- NSString *message = error.description ?: [NSString stringWithFormat:@"The application '%@' is not installed on the device under test",
- bundleID ?: path];
- @throw [NSException exceptionWithName:FBApplicationMissingException reason:message userInfo:nil];
- }
- reply(isSuccessful, error);
- }
- @implementation FBXCTestDaemonsProxy
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wobjc-load-method"
- + (void)load
- {
- [self.class swizzleLaunchApp];
- }
- #pragma clang diagnostic pop
- + (void)swizzleLaunchApp {
- Method original = class_getInstanceMethod([XCTRunnerDaemonSession class],
- @selector(launchApplicationWithPath:bundleID:arguments:environment:completion:));
- if (original == nil) {
- [FBLogger log:@"Could not find method -[XCTRunnerDaemonSession launchApplicationWithPath:]"];
- return;
- }
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wcast-function-type-strict"
- // Workaround for https://github.com/appium/WebDriverAgent/issues/702
- originalLaunchAppMethod = (void(*)(id, SEL, NSString*, NSString*, NSArray*, NSDictionary*, void (^)(_Bool, NSError *))) method_getImplementation(original);
- method_setImplementation(original, (IMP)swizzledLaunchApp);
- #pragma clang diagnostic pop
- }
- + (id<XCTestManager_ManagerInterface>)testRunnerProxy
- {
- static id<XCTestManager_ManagerInterface> proxy = nil;
- if ([FBConfiguration shouldUseSingletonTestManager]) {
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- [FBLogger logFmt:@"Using singleton test manager"];
- proxy = [self.class retrieveTestRunnerProxy];
- });
- } else {
- [FBLogger logFmt:@"Using general test manager"];
- proxy = [self.class retrieveTestRunnerProxy];
- }
- NSAssert(proxy != NULL, @"Could not determine testRunnerProxy", proxy);
- return proxy;
- }
- + (id<XCTestManager_ManagerInterface>)retrieveTestRunnerProxy
- {
- return ((XCTRunnerDaemonSession *)[XCTRunnerDaemonSession sharedSession]).daemonProxy;
- }
- + (BOOL)synthesizeEventWithRecord:(XCSynthesizedEventRecord *)record error:(NSError *__autoreleasing*)error
- {
- __block NSError *innerError = nil;
- [FBRunLoopSpinner spinUntilCompletion:^(void(^completion)(void)){
- void (^errorHandler)(NSError *) = ^(NSError *invokeError) {
- if (nil != invokeError) {
- innerError = invokeError;
- }
- completion();
- };
- XCEventGeneratorHandler handlerBlock = ^(XCSynthesizedEventRecord *innerRecord, NSError *invokeError) {
- errorHandler(invokeError);
- };
- [[XCUIDevice.sharedDevice eventSynthesizer] synthesizeEvent:record completion:(id)^(BOOL result, NSError *invokeError) {
- handlerBlock(record, invokeError);
- }];
- }];
- if (nil != innerError) {
- if (error) {
- *error = innerError;
- }
- return NO;
- }
- return YES;
- }
- + (BOOL)openURL:(NSURL *)url usingApplication:(NSString *)bundleId error:(NSError *__autoreleasing*)error
- {
- XCTRunnerDaemonSession *session = [XCTRunnerDaemonSession sharedSession];
- if (![session respondsToSelector:@selector(openURL:usingApplication:completion:)]) {
- return [[[FBErrorBuilder builder]
- withDescriptionFormat:@"The current Xcode SDK does not support opening of URLs with given application"]
- buildError:error];
- }
- __block NSError *innerError = nil;
- __block BOOL didSucceed = NO;
- [FBRunLoopSpinner spinUntilCompletion:^(void(^completion)(void)){
- [session openURL:url usingApplication:bundleId completion:^(bool result, NSError *invokeError) {
- if (nil != invokeError) {
- innerError = invokeError;
- } else {
- didSucceed = result;
- }
- completion();
- }];
- }];
- if (nil != innerError && error) {
- *error = innerError;
- }
- return didSucceed;
- }
- + (BOOL)openDefaultApplicationForURL:(NSURL *)url error:(NSError *__autoreleasing*)error
- {
- XCTRunnerDaemonSession *session = [XCTRunnerDaemonSession sharedSession];
- if (![session respondsToSelector:@selector(openDefaultApplicationForURL:completion:)]) {
- return [[[FBErrorBuilder builder]
- withDescriptionFormat:@"The current Xcode SDK does not support opening of URLs. Consider upgrading to Xcode 14.3+/iOS 16.4+"]
- buildError:error];
- }
- __block NSError *innerError = nil;
- __block BOOL didSucceed = NO;
- [FBRunLoopSpinner spinUntilCompletion:^(void(^completion)(void)){
- [session openDefaultApplicationForURL:url completion:^(bool result, NSError *invokeError) {
- if (nil != invokeError) {
- innerError = invokeError;
- } else {
- didSucceed = result;
- }
- completion();
- }];
- }];
- if (nil != innerError && error) {
- *error = innerError;
- }
- return didSucceed;
- }
- #if !TARGET_OS_TV
- + (BOOL)setSimulatedLocation:(CLLocation *)location error:(NSError *__autoreleasing*)error
- {
- XCTRunnerDaemonSession *session = [XCTRunnerDaemonSession sharedSession];
- if (![session respondsToSelector:@selector(setSimulatedLocation:completion:)]) {
- return [[[FBErrorBuilder builder]
- withDescriptionFormat:@"The current Xcode SDK does not support location simulation. Consider upgrading to Xcode 14.3+/iOS 16.4+"]
- buildError:error];
- }
- if (![session supportsLocationSimulation]) {
- return [[[FBErrorBuilder builder]
- withDescriptionFormat:@"Your device does not support location simulation"]
- buildError:error];
- }
- __block NSError *innerError = nil;
- __block BOOL didSucceed = NO;
- [FBRunLoopSpinner spinUntilCompletion:^(void(^completion)(void)){
- [session setSimulatedLocation:location completion:^(bool result, NSError *invokeError) {
- if (nil != invokeError) {
- innerError = invokeError;
- } else {
- didSucceed = result;
- }
- completion();
- }];
- }];
- if (nil != innerError && error) {
- *error = innerError;
- }
- return didSucceed;
- }
- + (nullable CLLocation *)getSimulatedLocation:(NSError *__autoreleasing*)error;
- {
- XCTRunnerDaemonSession *session = [XCTRunnerDaemonSession sharedSession];
- if (![session respondsToSelector:@selector(getSimulatedLocationWithReply:)]) {
- [[[FBErrorBuilder builder]
- withDescriptionFormat:@"The current Xcode SDK does not support location simulation. Consider upgrading to Xcode 14.3+/iOS 16.4+"]
- buildError:error];
- return nil;
- }
- if (![session supportsLocationSimulation]) {
- [[[FBErrorBuilder builder]
- withDescriptionFormat:@"Your device does not support location simulation"]
- buildError:error];
- return nil;
- }
- __block NSError *innerError = nil;
- __block CLLocation *location = nil;
- [FBRunLoopSpinner spinUntilCompletion:^(void(^completion)(void)){
- [session getSimulatedLocationWithReply:^(CLLocation *reply, NSError *invokeError) {
- if (nil != invokeError) {
- innerError = invokeError;
- } else {
- location = reply;
- }
- completion();
- }];
- }];
- if (nil != innerError && error) {
- *error = innerError;
- }
- return location;
- }
- + (BOOL)clearSimulatedLocation:(NSError *__autoreleasing*)error
- {
- XCTRunnerDaemonSession *session = [XCTRunnerDaemonSession sharedSession];
- if (![session respondsToSelector:@selector(clearSimulatedLocationWithReply:)]) {
- return [[[FBErrorBuilder builder]
- withDescriptionFormat:@"The current Xcode SDK does not support location simulation. Consider upgrading to Xcode 14.3+/iOS 16.4+"]
- buildError:error];
- }
- if (![session supportsLocationSimulation]) {
- return [[[FBErrorBuilder builder]
- withDescriptionFormat:@"Your device does not support location simulation"]
- buildError:error];
- }
- __block NSError *innerError = nil;
- __block BOOL didSucceed = NO;
- [FBRunLoopSpinner spinUntilCompletion:^(void(^completion)(void)){
- [session clearSimulatedLocationWithReply:^(bool result, NSError *invokeError) {
- if (nil != invokeError) {
- innerError = invokeError;
- } else {
- didSucceed = result;
- }
- completion();
- }];
- }];
- if (nil != innerError && error) {
- *error = innerError;
- }
- return didSucceed;
- }
- #endif
- + (FBScreenRecordingPromise *)startScreenRecordingWithRequest:(FBScreenRecordingRequest *)request
- error:(NSError *__autoreleasing*)error
- {
- XCTRunnerDaemonSession *session = [XCTRunnerDaemonSession sharedSession];
- if (![session respondsToSelector:@selector(startScreenRecordingWithRequest:withReply:)]) {
- [[[FBErrorBuilder builder]
- withDescriptionFormat:@"The current Xcode SDK does not support screen recording. Consider upgrading to Xcode 15+/iOS 17+"]
- buildError:error];
- return nil;
- }
- if (![session supportsScreenRecording]) {
- [[[FBErrorBuilder builder]
- withDescriptionFormat:@"Your device does not support screen recording"]
- buildError:error];
- return nil;
- }
- id nativeRequest = [request toNativeRequestWithError:error];
- if (nil == nativeRequest) {
- return nil;
- }
- __block id futureMetadata = nil;
- __block NSError *innerError = nil;
- [FBRunLoopSpinner spinUntilCompletion:^(void(^completion)(void)){
- [session startScreenRecordingWithRequest:nativeRequest withReply:^(id reply, NSError *invokeError) {
- if (nil == invokeError) {
- futureMetadata = reply;
- } else {
- innerError = invokeError;
- }
- completion();
- }];
- }];
- if (nil != innerError) {
- if (error) {
- *error = innerError;
- }
- return nil;
- }
- return [[FBScreenRecordingPromise alloc] initWithNativePromise:futureMetadata];
- }
- + (BOOL)stopScreenRecordingWithUUID:(NSUUID *)uuid error:(NSError *__autoreleasing*)error
- {
- XCTRunnerDaemonSession *session = [XCTRunnerDaemonSession sharedSession];
- if (![session respondsToSelector:@selector(stopScreenRecordingWithUUID:withReply:)]) {
- return [[[FBErrorBuilder builder]
- withDescriptionFormat:@"The current Xcode SDK does not support screen recording. Consider upgrading to Xcode 15+/iOS 17+"]
- buildError:error];
- }
- if (![session supportsScreenRecording]) {
- return [[[FBErrorBuilder builder]
- withDescriptionFormat:@"Your device does not support screen recording"]
- buildError:error];
- }
- __block NSError *innerError = nil;
- [FBRunLoopSpinner spinUntilCompletion:^(void(^completion)(void)){
- [session stopScreenRecordingWithUUID:uuid withReply:^(NSError *invokeError) {
- if (nil != invokeError) {
- innerError = invokeError;
- }
- completion();
- }];
- }];
- if (nil != innerError && error) {
- *error = innerError;
- }
- return nil == innerError;
- }
- @end
|