diff --git a/WebDriverAgent.xcodeproj/project.pbxproj b/WebDriverAgent.xcodeproj/project.pbxproj index c52bdd5cd..91dc59494 100644 --- a/WebDriverAgent.xcodeproj/project.pbxproj +++ b/WebDriverAgent.xcodeproj/project.pbxproj @@ -314,6 +314,10 @@ 64B2650B228CE4FF002A5025 /* FBTVNavigationTracker-Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 64B26509228CE4FF002A5025 /* FBTVNavigationTracker-Private.h */; }; 64E3502E2AC0B6EB005F3ACB /* NSDictionary+FBUtf8SafeDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 716F0DA02A16CA1000CDD977 /* NSDictionary+FBUtf8SafeDictionary.m */; }; 64E3502F2AC0B6FE005F3ACB /* NSDictionary+FBUtf8SafeDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 716F0D9F2A16CA1000CDD977 /* NSDictionary+FBUtf8SafeDictionary.h */; }; + 6F2059772E4E0E3500E9D188 /* NSRunLoop+Monotonic.m in Sources */ = {isa = PBXBuildFile; fileRef = 6F2059762E4E0E3500E9D188 /* NSRunLoop+Monotonic.m */; }; + 6F2059782E4E0E3500E9D188 /* NSRunLoop+Monotonic.h in Headers */ = {isa = PBXBuildFile; fileRef = 6F2059752E4E0E3500E9D188 /* NSRunLoop+Monotonic.h */; }; + 6F2059792E4E0E3500E9D188 /* NSRunLoop+Monotonic.h in Headers */ = {isa = PBXBuildFile; fileRef = 6F2059752E4E0E3500E9D188 /* NSRunLoop+Monotonic.h */; }; + 6F20597A2E4E0E3500E9D188 /* NSRunLoop+Monotonic.m in Sources */ = {isa = PBXBuildFile; fileRef = 6F2059762E4E0E3500E9D188 /* NSRunLoop+Monotonic.m */; }; 711084441DA3AA7500F913D6 /* FBXPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 711084421DA3AA7500F913D6 /* FBXPath.h */; settings = {ATTRIBUTES = (Public, ); }; }; 711084451DA3AA7500F913D6 /* FBXPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 711084431DA3AA7500F913D6 /* FBXPath.m */; }; 7119097C2152580600BA3C7E /* XCUIScreen.h in Headers */ = {isa = PBXBuildFile; fileRef = 7119097B2152580600BA3C7E /* XCUIScreen.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -968,6 +972,8 @@ 64B26506228C54F2002A5025 /* XCUIElementDouble.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCUIElementDouble.h; sourceTree = ""; }; 64B26507228C5514002A5025 /* XCUIElementDouble.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCUIElementDouble.m; sourceTree = ""; }; 64B26509228CE4FF002A5025 /* FBTVNavigationTracker-Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FBTVNavigationTracker-Private.h"; sourceTree = ""; }; + 6F2059752E4E0E3500E9D188 /* NSRunLoop+Monotonic.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSRunLoop+Monotonic.h"; sourceTree = ""; }; + 6F2059762E4E0E3500E9D188 /* NSRunLoop+Monotonic.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSRunLoop+Monotonic.m"; sourceTree = ""; }; 711084421DA3AA7500F913D6 /* FBXPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBXPath.h; sourceTree = ""; }; 711084431DA3AA7500F913D6 /* FBXPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBXPath.m; sourceTree = ""; }; 7119097B2152580600BA3C7E /* XCUIScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCUIScreen.h; sourceTree = ""; }; @@ -1747,6 +1753,8 @@ EE9AB73E1CAEDF0C008C271F /* Categories */ = { isa = PBXGroup; children = ( + 6F2059752E4E0E3500E9D188 /* NSRunLoop+Monotonic.h */, + 6F2059762E4E0E3500E9D188 /* NSRunLoop+Monotonic.m */, 716F0D9F2A16CA1000CDD977 /* NSDictionary+FBUtf8SafeDictionary.h */, 716F0DA02A16CA1000CDD977 /* NSDictionary+FBUtf8SafeDictionary.m */, 71555A3B1DEC460A007D4A8B /* NSExpression+FBFormat.h */, @@ -2396,6 +2404,7 @@ 641EE6802240C5CA00173FCB /* FBElementTypeTransformer.h in Headers */, 641EE6812240C5CA00173FCB /* FBXCAXClientProxy.h in Headers */, 641EE6822240C5CA00173FCB /* FBElementCache.h in Headers */, + 6F2059782E4E0E3500E9D188 /* NSRunLoop+Monotonic.h in Headers */, 641EE6832240C5CA00173FCB /* XCTMetric.h in Headers */, 641EE6842240C5CA00173FCB /* XCTestContextScope.h in Headers */, 7182271D258744AB00661B83 /* RouteResponse.h in Headers */, @@ -2712,6 +2721,7 @@ EE35AD261E3B77D600A02D78 /* XCApplicationMonitor_iOS.h in Headers */, 0E04133B2DF1E15900AF007C /* XCUIElement+FBMinMax.h in Headers */, EE3A18661CDE734B00DE4205 /* FBKeyboard.h in Headers */, + 6F2059792E4E0E3500E9D188 /* NSRunLoop+Monotonic.h in Headers */, AD6C269C1CF2494200F8B5FF /* XCUIApplication+FBHelpers.h in Headers */, 714D88CC2733FB970074A925 /* FBXMLGenerationOptions.h in Headers */, EE35AD101E3B77D600A02D78 /* _XCTestObservationCenterImplementation.h in Headers */, @@ -3225,6 +3235,7 @@ 71BB58F22B96511800CB9BFE /* FBVideoCommands.m in Sources */, 641EE61F2240C5CA00173FCB /* FBXCodeCompatibility.m in Sources */, 71E75E70254824230099FC87 /* XCUIElementQuery+FBHelpers.m in Sources */, + 6F2059772E4E0E3500E9D188 /* NSRunLoop+Monotonic.m in Sources */, 641EE6212240C5CA00173FCB /* FBElementTypeTransformer.m in Sources */, 13DE7A5E287CA444003243C6 /* FBXCElementSnapshotWrapper+Helpers.m in Sources */, 641EE6232240C5CA00173FCB /* FBScreen.m in Sources */, @@ -3268,6 +3279,7 @@ 71555A3E1DEC460A007D4A8B /* NSExpression+FBFormat.m in Sources */, AD6C269D1CF2494200F8B5FF /* XCUIApplication+FBHelpers.m in Sources */, EE3A18671CDE734B00DE4205 /* FBKeyboard.m in Sources */, + 6F20597A2E4E0E3500E9D188 /* NSRunLoop+Monotonic.m in Sources */, 719DCF172601EAFB000E765F /* FBNotificationsHelper.m in Sources */, E444DCAC24913C220060D7EB /* Route.m in Sources */, 713C6DD01DDC772A00285B92 /* FBElementUtils.m in Sources */, diff --git a/WebDriverAgentLib/Categories/NSRunLoop+Monotonic.h b/WebDriverAgentLib/Categories/NSRunLoop+Monotonic.h new file mode 100644 index 000000000..04cd71ef7 --- /dev/null +++ b/WebDriverAgentLib/Categories/NSRunLoop+Monotonic.h @@ -0,0 +1,14 @@ +#import + +@interface NSRunLoop (Monotonic) +/** + Runs the current run loop in the default mode for the specified monotonic time interval. + + This method uses a monotonic clock to ensure the interval is not affected + by changes to the system clock. The run loop will repeatedly process events + until the given number of seconds has elapsed. + + @param seconds The duration, in seconds, to run the run loop. + */ +- (void)runForMonotonicInterval:(NSTimeInterval)seconds; +@end \ No newline at end of file diff --git a/WebDriverAgentLib/Categories/NSRunLoop+Monotonic.m b/WebDriverAgentLib/Categories/NSRunLoop+Monotonic.m new file mode 100644 index 000000000..d3c462763 --- /dev/null +++ b/WebDriverAgentLib/Categories/NSRunLoop+Monotonic.m @@ -0,0 +1,19 @@ +#import "NSRunLoop+Monotonic.h" +#import + +@implementation NSRunLoop (Monotonic) + +/** + * Runs the current run loop for a specified monotonic interval (in seconds), + * using CACurrentMediaTime for time measurement. This method is not affected by system time changes. + * + * @param seconds The duration to run the run loop, measured in monotonic time. + */ +- (void)runForMonotonicInterval:(NSTimeInterval)seconds { + CFTimeInterval end = CACurrentMediaTime() + seconds; + while (CACurrentMediaTime() < end) { + [self runMode:NSRunLoopCommonModes beforeDate:[NSDate distantFuture]]; + } +} + +@end diff --git a/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m b/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m index 804aaff8c..511e3e267 100644 --- a/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m +++ b/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m @@ -38,6 +38,7 @@ #import "XCUIElement+FBWebDriverAttributes.h" #import "XCUIElementQuery.h" #import "FBElementHelpers.h" +#import "NSRunLoop+Monotonic.h" static NSString* const FBUnknownBundleId = @"unknown"; @@ -169,7 +170,7 @@ - (BOOL)fb_deactivateWithDuration:(NSTimeInterval)duration error:(NSError **)err if(![[XCUIDevice sharedDevice] fb_goToHomescreenWithError:error]) { return NO; } - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:MAX(duration, .0)]]; + [[NSRunLoop currentRunLoop] runForMonotonicInterval:MAX(duration, .0)]; [self activate]; return YES; } diff --git a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m index ef8664fb6..b72df6242 100644 --- a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m +++ b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m @@ -23,6 +23,7 @@ #import "FBXCodeCompatibility.h" #import "FBXCTestDaemonsProxy.h" #import "XCUIDevice.h" +#import "NSRunLoop+Monotonic.h" static const NSTimeInterval FBHomeButtonCoolOffTime = 1.; static const NSTimeInterval FBScreenLockTimeout = 5.; @@ -84,13 +85,13 @@ - (BOOL)fb_unlockScreen:(NSError **)error return YES; } [self pressButton:XCUIDeviceButtonHome]; - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:FBHomeButtonCoolOffTime]]; + [[NSRunLoop currentRunLoop] runForMonotonicInterval:FBHomeButtonCoolOffTime]; #if !TARGET_OS_TV [self pressButton:XCUIDeviceButtonHome]; #else [self pressButton:XCUIDeviceButtonHome]; #endif - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:FBHomeButtonCoolOffTime]]; + [[NSRunLoop currentRunLoop] runForMonotonicInterval:FBHomeButtonCoolOffTime]; return [[[[FBRunLoopSpinner new] timeout:FBScreenLockTimeout] timeoutErrorMessage:@"Timed out while waiting until the screen gets unlocked"] diff --git a/WebDriverAgentLib/Utilities/FBPasteboard.m b/WebDriverAgentLib/Utilities/FBPasteboard.m index 0171b259c..6424b2262 100644 --- a/WebDriverAgentLib/Utilities/FBPasteboard.m +++ b/WebDriverAgentLib/Utilities/FBPasteboard.m @@ -15,6 +15,7 @@ #import "FBMacros.h" #import "XCUIApplication+FBHelpers.h" #import "XCUIApplication+FBAlert.h" +#import "NSRunLoop+Monotonic.h" #define ALERT_TIMEOUT_SEC 30 // Must not be less than FB_MONTORING_INTERVAL in FBAlertsMonitor @@ -96,7 +97,7 @@ + (nullable id)pasteboardContentForItem:(NSString *)item }); uint64_t timeStarted = clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW); while (!didFinishGetPasteboard) { - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:ALERT_CHECK_INTERVAL_SEC]]; + [[NSRunLoop currentRunLoop] runForMonotonicInterval:ALERT_CHECK_INTERVAL_SEC]; if (didFinishGetPasteboard) { break; } diff --git a/WebDriverAgentLib/Utilities/FBRunLoopSpinner.m b/WebDriverAgentLib/Utilities/FBRunLoopSpinner.m index 386238734..9febe41ce 100644 --- a/WebDriverAgentLib/Utilities/FBRunLoopSpinner.m +++ b/WebDriverAgentLib/Utilities/FBRunLoopSpinner.m @@ -12,6 +12,7 @@ #import #import "FBErrorBuilder.h" +#import "NSRunLoop+Monotonic.h" static const NSTimeInterval FBWaitInterval = 0.1; @@ -30,7 +31,7 @@ + (void)spinUntilCompletion:(void (^)(void(^completion)(void)))block atomic_fetch_or(&didFinish, true); }); while (!atomic_fetch_and(&didFinish, false)) { - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:FBWaitInterval]]; + [[NSRunLoop currentRunLoop] runForMonotonicInterval:FBWaitInterval]; } } @@ -71,7 +72,7 @@ - (BOOL)spinUntilTrue:(FBRunLoopSpinnerBlock)untilTrue error:(NSError **)error { NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:self.timeout]; while (!untilTrue()) { - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:self.interval]]; + [[NSRunLoop currentRunLoop] runForMonotonicInterval:self.interval]; if (timeoutDate.timeIntervalSinceNow < 0) { return [[[FBErrorBuilder builder] diff --git a/WebDriverAgentTests/IntegrationTests/FBTestMacros.h b/WebDriverAgentTests/IntegrationTests/FBTestMacros.h index 2b2ae2970..9a67aa724 100644 --- a/WebDriverAgentTests/IntegrationTests/FBTestMacros.h +++ b/WebDriverAgentTests/IntegrationTests/FBTestMacros.h @@ -8,6 +8,7 @@ */ #import +#import "NSRunLoop+Monotonic.h" /** Macro used to wait till certain condition is true. @@ -26,7 +27,7 @@ #define FBWaitExact(timeoutSeconds) \ ({ \ - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:(timeoutSeconds)]]; \ + [[NSRunLoop currentRunLoop] runForMonotonicInterval:(timeoutSeconds)]; \ }) #define FBCellElementWithLabel(label) ([self.testedApplication descendantsMatchingType:XCUIElementTypeAny][label]) diff --git a/WebDriverAgentTests/IntegrationTests/FBVideoRecordingTests.m b/WebDriverAgentTests/IntegrationTests/FBVideoRecordingTests.m index 191af3420..446b86a7e 100644 --- a/WebDriverAgentTests/IntegrationTests/FBVideoRecordingTests.m +++ b/WebDriverAgentTests/IntegrationTests/FBVideoRecordingTests.m @@ -17,6 +17,7 @@ #import "FBScreenRecordingRequest.h" #import "FBScreenRecordingContainer.h" #import "FBXCTestDaemonsProxy.h" +#import "NSRunLoop+Monotonic.h" @interface FBVideoRecordingTests : FBIntegrationTestCase @end @@ -52,7 +53,7 @@ - (void)testStartingAndStoppingVideoRecording codec:0]; XCTAssertEqual(FBScreenRecordingContainer.sharedInstance.screenRecordingPromise, promise); - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.]]; + [[NSRunLoop currentRunLoop] runForMonotonicInterval:2.]; BOOL isSuccessfull = [FBXCTestDaemonsProxy stopScreenRecordingWithUUID:promise.identifier error:&error]; XCTAssertTrue(isSuccessfull);