diff --git a/WebDriverAgent.xcodeproj/project.pbxproj b/WebDriverAgent.xcodeproj/project.pbxproj index 45408564e..31b6d6343 100644 --- a/WebDriverAgent.xcodeproj/project.pbxproj +++ b/WebDriverAgent.xcodeproj/project.pbxproj @@ -462,6 +462,7 @@ 1FC3B2E12121EC8C00B61EE0 /* FBApplicationProcessProxyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBApplicationProcessProxyTests.m; sourceTree = ""; }; 44757A831D42CE8300ECF35E /* XCUIDeviceRotationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XCUIDeviceRotationTests.m; sourceTree = ""; }; 631B523421F6174300625362 /* FBImageIOScalerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBImageIOScalerTests.m; sourceTree = ""; }; + 633E904A220DEE7F007CADF9 /* XCUIApplicationProcessDelay.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCUIApplicationProcessDelay.h; sourceTree = ""; }; 6385F4A5220A40760095BBDB /* XCUIApplicationProcessDelay.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCUIApplicationProcessDelay.m; sourceTree = ""; }; 63CCF91021ECE4C700E94ABD /* FBImageIOScaler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBImageIOScaler.h; sourceTree = ""; }; 63CCF91121ECE4C700E94ABD /* FBImageIOScaler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBImageIOScaler.m; sourceTree = ""; }; @@ -1172,6 +1173,7 @@ EE6B64FC1D0F86EF00E85F5D /* XCTestPrivateSymbols.m */, 63CCF91021ECE4C700E94ABD /* FBImageIOScaler.h */, 63CCF91121ECE4C700E94ABD /* FBImageIOScaler.m */, + 633E904A220DEE7F007CADF9 /* XCUIApplicationProcessDelay.h */, 6385F4A5220A40760095BBDB /* XCUIApplicationProcessDelay.m */, ); name = Utilities; diff --git a/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentRunner.xcscheme b/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentRunner.xcscheme index c91a57151..2c143c116 100644 --- a/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentRunner.xcscheme +++ b/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentRunner.xcscheme @@ -85,11 +85,6 @@ value = "$(WDA_PRODUCT_BUNDLE_IDENTIFIER)" isEnabled = "YES"> - - diff --git a/WebDriverAgentLib/Commands/FBSessionCommands.m b/WebDriverAgentLib/Commands/FBSessionCommands.m index 09521e347..e51bd5395 100644 --- a/WebDriverAgentLib/Commands/FBSessionCommands.m +++ b/WebDriverAgentLib/Commands/FBSessionCommands.m @@ -18,6 +18,7 @@ #import "XCUIDevice.h" #import "XCUIDevice+FBHealthCheck.h" #import "XCUIDevice+FBHelpers.h" +#import "XCUIApplicationProcessDelay.h" static NSString* const USE_COMPACT_RESPONSES = @"shouldUseCompactResponses"; static NSString* const ELEMENT_RESPONSE_ATTRIBUTES = @"elementResponseAttributes"; @@ -92,6 +93,12 @@ + (NSArray *)routes if (requirements[@"shouldUseSingletonTestManager"]) { [FBConfiguration setShouldUseSingletonTestManager:[requirements[@"shouldUseSingletonTestManager"] boolValue]]; } + NSNumber *delay = requirements[@"eventloopIdleDelaySec"]; + if ([delay doubleValue] > 0.0) { + [XCUIApplicationProcessDelay setEventLoopHasIdledDelay:[delay doubleValue]]; + } else { + [XCUIApplicationProcessDelay disableEventLoopDelay]; + } [FBConfiguration setShouldWaitForQuiescence:[requirements[@"shouldWaitForQuiescence"] boolValue]]; diff --git a/WebDriverAgentLib/Utilities/XCUIApplicationProcessDelay.h b/WebDriverAgentLib/Utilities/XCUIApplicationProcessDelay.h new file mode 100644 index 000000000..ed11a9a51 --- /dev/null +++ b/WebDriverAgentLib/Utilities/XCUIApplicationProcessDelay.h @@ -0,0 +1,38 @@ +/** + * 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 + +NS_ASSUME_NONNULL_BEGIN + +/** + In certain cases WebDriverAgent fails to create a session because -[XCUIApplication launch] doesn't return + since it waits for the target app to be quiescenced. + The reason for this seems to be that 'testmanagerd' doesn't send the events WebDriverAgent is waiting for. + The expected events would trigger calls to '-[XCUIApplicationProcess setEventLoopHasIdled:]' and + '-[XCUIApplicationProcess setAnimationsHaveFinished:]', which are the properties that are checked to + determine whether an app has quiescenced or not. + Delaying the call to on of the setters can fix this issue. + */ +@interface XCUIApplicationProcessDelay : NSObject + +/** + Delays the invocation of '-[XCUIApplicationProcess setEventLoopHasIdled:]' by the timer interval passed + @param delay The duration of the sleep before the original method is called + */ ++ (void)setEventLoopHasIdledDelay:(NSTimeInterval)delay; + +/** + Disables the delayed invocation of '-[XCUIApplicationProcess setEventLoopHasIdled:]'. + */ ++ (void)disableEventLoopDelay; + +@end + +NS_ASSUME_NONNULL_END diff --git a/WebDriverAgentLib/Utilities/XCUIApplicationProcessDelay.m b/WebDriverAgentLib/Utilities/XCUIApplicationProcessDelay.m index a59d9adcb..cd1d3fff2 100644 --- a/WebDriverAgentLib/Utilities/XCUIApplicationProcessDelay.m +++ b/WebDriverAgentLib/Utilities/XCUIApplicationProcessDelay.m @@ -7,44 +7,49 @@ * of patent rights can be found in the PATENTS file in the same directory. */ +#import "XCUIApplicationProcessDelay.h" #import #import "XCUIApplicationProcess.h" #import "FBLogger.h" -/** - In certain cases WebDriverAgent fails to create a session because -[XCUIApplication launch] doesn't return - since it waits for the target app to be quiescenced. - The reason for this seems to be that 'testmanagerd' doesn't send the events WebDriverAgent is waiting for. - The expected events would trigger calls to '-[XCUIApplicationProcess setEventLoopHasIdled:]' and - '-[XCUIApplicationProcess setAnimationsHaveFinished:]', which are the properties that are checked to - determine whether an app has quiescenced or not. - Delaying the call to on of the setters can fix this issue. Setting the environment variable - 'EVENTLOOP_IDLE_DELAY_SEC' will swizzle the method '-[XCUIApplicationProcess setEventLoopHasIdled:]' - and add a thread sleep of the value specified in the environment variable in seconds. - */ -@interface XCUIApplicationProcessDelay : NSObject - -@end - -static NSString *const EVENTLOOP_IDLE_DELAY_SEC = @"EVENTLOOP_IDLE_DELAY_SEC"; static void (*orig_set_event_loop_has_idled)(id, SEL, BOOL); -static NSTimeInterval delay = 0; +static NSTimeInterval eventloopIdleDelay = 0; +static BOOL isSwizzled = NO; +// '-[XCUIApplicationProcess setEventLoopHasIdled:]' can be called from different queues. +// Lets lock the setup and access to the 'eventloopIdleDelay' variable +static NSLock * lock = nil; @implementation XCUIApplicationProcessDelay + (void)load { - NSDictionary *env = [[NSProcessInfo processInfo] environment]; - NSString *setEventLoopIdleDelay = [env objectForKey:EVENTLOOP_IDLE_DELAY_SEC]; - if (!setEventLoopIdleDelay || [setEventLoopIdleDelay length] == 0) { - [FBLogger verboseLog:@"don't delay -[XCUIApplicationProcess setEventLoopHasIdled:]"]; + lock = [[NSLock alloc] init]; +} + ++ (void)setEventLoopHasIdledDelay:(NSTimeInterval)delay +{ + [lock lock]; + if (!isSwizzled && delay < DBL_EPSILON) { + // don't swizzle methods until we need to + [lock unlock]; return; } - delay = [setEventLoopIdleDelay doubleValue]; - if (delay < DBL_EPSILON) { - [FBLogger log:[NSString stringWithFormat:@"Value of '%@' has to be greater than zero to delay -[XCUIApplicationProcess setEventLoopHasIdled:]", - EVENTLOOP_IDLE_DELAY_SEC]]; + eventloopIdleDelay = delay; + if (isSwizzled) { + [lock unlock]; return; } + [self swizzleSetEventLoopHasIdled]; + [lock unlock]; +} + ++ (void)disableEventLoopDelay +{ + // Once the methods were swizzled they stay like that since the only change in the implementation + // is the thread sleep, which is skipped on setting it to zero. + [self setEventLoopHasIdledDelay:0]; +} + ++ (void)swizzleSetEventLoopHasIdled { Method original = class_getInstanceMethod([XCUIApplicationProcess class], @selector(setEventLoopHasIdled:)); if (original == nil) { [FBLogger log:@"Could not find method -[XCUIApplicationProcess setEventLoopHasIdled:]"]; @@ -53,11 +58,17 @@ + (void)load { orig_set_event_loop_has_idled = (void(*)(id, SEL, BOOL)) method_getImplementation(original); Method replace = class_getClassMethod([XCUIApplicationProcessDelay class], @selector(setEventLoopHasIdled:)); method_setImplementation(original, method_getImplementation(replace)); + isSwizzled = YES; } + (void)setEventLoopHasIdled:(BOOL)idled { - [FBLogger verboseLog:[NSString stringWithFormat:@"Delaying -[XCUIApplicationProcess setEventLoopHasIdled:] by %.2f seconds", delay]]; - [NSThread sleepForTimeInterval:delay]; + [lock lock]; + NSTimeInterval delay = eventloopIdleDelay; + [lock unlock]; + if (delay > 0.0) { + [FBLogger verboseLog:[NSString stringWithFormat:@"Delaying -[XCUIApplicationProcess setEventLoopHasIdled:] by %.2f seconds", delay]]; + [NSThread sleepForTimeInterval:delay]; + } orig_set_event_loop_has_idled(self, _cmd, idled); }