appium-webdriveragent 16.10.0 → 16.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/PrivateHeaders/XCTest/CDStructures.h +2 -0
- package/WebDriverAgent.xcodeproj/project.pbxproj +4 -0
- package/WebDriverAgentLib/Categories/XCUIApplicationProcess+FBQuiescence.m +5 -13
- package/WebDriverAgentLib/Commands/FBSessionCommands.m +26 -22
- package/WebDriverAgentLib/Info.plist +2 -2
- package/WebDriverAgentLib/Utilities/FBMjpegServer.m +1 -1
- package/WebDriverAgentLib/Utilities/FBXCAXClientProxy.h +115 -2
- package/WebDriverAgentLib/Utilities/FBXCAXClientProxy.m +107 -2
- package/WebDriverAgentTests/UnitTests/FBXCAXClientProxyTests.m +90 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [16.11.1](https://github.com/appium/WebDriverAgent/compare/v16.11.0...v16.11.1) (2026-08-29)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
* build of FBMjpegServer ([#1239](https://github.com/appium/WebDriverAgent/issues/1239)) ([a3b8650](https://github.com/appium/WebDriverAgent/commit/a3b8650670b9b30c5fc0cd5aa4834a6774d4a828))
|
|
6
|
+
|
|
7
|
+
## [16.11.0](https://github.com/appium/WebDriverAgent/compare/v16.10.0...v16.11.0) (2026-08-29)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* expose AXTimeout and XCTest XPC request timeout wrappers ([#1233](https://github.com/appium/WebDriverAgent/issues/1233)) ([d5c8571](https://github.com/appium/WebDriverAgent/commit/d5c85718c32bc495a0f73518d3e40eee624b3582))
|
|
12
|
+
|
|
1
13
|
## [16.10.0](https://github.com/appium/WebDriverAgent/compare/v16.9.4...v16.10.0) (2026-08-29)
|
|
2
14
|
|
|
3
15
|
### Features
|
|
@@ -2,4 +2,6 @@
|
|
|
2
2
|
#pragma clang diagnostic ignored "-Wreserved-identifier"
|
|
3
3
|
int _XCTSetApplicationStateTimeout(double timeout);
|
|
4
4
|
double _XCTApplicationStateTimeout(void);
|
|
5
|
+
void _XCTSetXPCRequestTimeout(double timeout);
|
|
6
|
+
double _XCTXPCRequestTimeout(void);
|
|
5
7
|
#pragma clang diagnostic pop
|
|
@@ -1158,6 +1158,7 @@
|
|
|
1158
1158
|
EE3A18661CDE734B00DE4205 /* FBKeyboard.h in Headers */ = {isa = PBXBuildFile; fileRef = EE3A18641CDE734B00DE4205 /* FBKeyboard.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
|
1159
1159
|
EE3A18671CDE734B00DE4205 /* FBKeyboard.m in Sources */ = {isa = PBXBuildFile; fileRef = EE3A18651CDE734B00DE4205 /* FBKeyboard.m */; };
|
|
1160
1160
|
EE3F8CFE1D08AA17006F02CE /* FBRunLoopSpinnerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = EE3F8CFD1D08AA17006F02CE /* FBRunLoopSpinnerTests.m */; };
|
|
1161
|
+
A09D847635CA4C155583B967 /* FBXCAXClientProxyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6231A764C3FAAB29B87CD987 /* FBXCAXClientProxyTests.m */; };
|
|
1161
1162
|
EE3F8D001D08B05F006F02CE /* FBElementTypeTransformerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = EE3F8CFF1D08B05F006F02CE /* FBElementTypeTransformerTests.m */; };
|
|
1162
1163
|
EE5095E51EBCC9090028E2FE /* FBTypingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AD76723F1D6B826F00610457 /* FBTypingTest.m */; };
|
|
1163
1164
|
EE5095EB1EBCC9090028E2FE /* XCElementSnapshotHitPointTests.m in Sources */ = {isa = PBXBuildFile; fileRef = EE006EB21EBA1C7B006900A4 /* XCElementSnapshotHitPointTests.m */; };
|
|
@@ -1827,6 +1828,7 @@
|
|
|
1827
1828
|
EE3A18641CDE734B00DE4205 /* FBKeyboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FBKeyboard.h; path = WebDriverAgentLib/Utilities/FBKeyboard.h; sourceTree = SOURCE_ROOT; };
|
|
1828
1829
|
EE3A18651CDE734B00DE4205 /* FBKeyboard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FBKeyboard.m; path = WebDriverAgentLib/Utilities/FBKeyboard.m; sourceTree = SOURCE_ROOT; };
|
|
1829
1830
|
EE3F8CFD1D08AA17006F02CE /* FBRunLoopSpinnerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBRunLoopSpinnerTests.m; sourceTree = "<group>"; };
|
|
1831
|
+
6231A764C3FAAB29B87CD987 /* FBXCAXClientProxyTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBXCAXClientProxyTests.m; sourceTree = "<group>"; };
|
|
1830
1832
|
EE3F8CFF1D08B05F006F02CE /* FBElementTypeTransformerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBElementTypeTransformerTests.m; sourceTree = "<group>"; };
|
|
1831
1833
|
EE5095FE1EBCC9090028E2FE /* IntegrationTests_2.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = IntegrationTests_2.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
1832
1834
|
EE55B3221D1D5388003AAAEC /* FBTableDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBTableDataSource.h; sourceTree = "<group>"; };
|
|
@@ -2683,6 +2685,7 @@
|
|
|
2683
2685
|
ADEF63AE1D09DEBE0070A7E3 /* FBRuntimeUtilsTests.m */,
|
|
2684
2686
|
714801D01FA9D9FA00DC5997 /* FBSDKVersionTests.m */,
|
|
2685
2687
|
EE6A89251D0B19E60083E92B /* FBSessionTests.m */,
|
|
2688
|
+
6231A764C3FAAB29B87CD987 /* FBXCAXClientProxyTests.m */,
|
|
2686
2689
|
716E0BD01E917F260087A825 /* FBXMLSafeStringTests.m */,
|
|
2687
2690
|
712A0C841DA3E459007D02E5 /* FBXPathTests.m */,
|
|
2688
2691
|
EE9B76581CF7987300275851 /* Info.plist */,
|
|
@@ -4802,6 +4805,7 @@
|
|
|
4802
4805
|
files = (
|
|
4803
4806
|
713352FD26CEF31D00523CBC /* FBLRUCacheTests.m in Sources */,
|
|
4804
4807
|
EE3F8CFE1D08AA17006F02CE /* FBRunLoopSpinnerTests.m in Sources */,
|
|
4808
|
+
A09D847635CA4C155583B967 /* FBXCAXClientProxyTests.m in Sources */,
|
|
4805
4809
|
714801D11FA9D9FA00DC5997 /* FBSDKVersionTests.m in Sources */,
|
|
4806
4810
|
EE3F8D001D08B05F006F02CE /* FBElementTypeTransformerTests.m in Sources */,
|
|
4807
4811
|
13FFF2F2287DBEE600E561E4 /* XCElementSnapshotDouble.m in Sources */,
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
|
|
11
11
|
#import <objc/runtime.h>
|
|
12
12
|
|
|
13
|
-
#import "CDStructures.h"
|
|
14
13
|
#import "FBConfiguration.h"
|
|
15
14
|
#import "FBExceptions.h"
|
|
16
15
|
#import "FBLogger.h"
|
|
17
16
|
#import "FBSettings.h"
|
|
17
|
+
#import "FBXCAXClientProxy.h"
|
|
18
18
|
|
|
19
19
|
static void (*original_waitForQuiescenceIncludingAnimationsIdle)(id, SEL, BOOL);
|
|
20
20
|
static void (*original_waitForQuiescenceIncludingAnimationsIdlePreEvent)(id, SEL, BOOL, BOOL);
|
|
@@ -29,15 +29,11 @@ static void swizzledWaitForQuiescenceIncludingAnimationsIdle(id self, SEL _cmd,
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
NSTimeInterval desiredTimeout = FBConfiguration.sharedInstance.waitForIdleTimeout;
|
|
32
|
-
NSTimeInterval previousTimeout = _XCTApplicationStateTimeout();
|
|
33
|
-
_XCTSetApplicationStateTimeout(desiredTimeout);
|
|
34
32
|
[FBLogger logFmt:@"Waiting up to %@s until %@ is in idle state (%@ animations)",
|
|
35
33
|
@(desiredTimeout), bundleId, includingAnimations ? @"including" : @"excluding"];
|
|
36
|
-
|
|
34
|
+
[FBXCAXClientProxy withApplicationStateTimeout:desiredTimeout do:^{
|
|
37
35
|
original_waitForQuiescenceIncludingAnimationsIdle(self, _cmd, includingAnimations);
|
|
38
|
-
}
|
|
39
|
-
_XCTSetApplicationStateTimeout(previousTimeout);
|
|
40
|
-
}
|
|
36
|
+
}];
|
|
41
37
|
}
|
|
42
38
|
|
|
43
39
|
static void swizzledWaitForQuiescenceIncludingAnimationsIdlePreEvent(id self, SEL _cmd, BOOL includingAnimations, BOOL isPreEvent)
|
|
@@ -50,15 +46,11 @@ static void swizzledWaitForQuiescenceIncludingAnimationsIdlePreEvent(id self, SE
|
|
|
50
46
|
}
|
|
51
47
|
|
|
52
48
|
NSTimeInterval desiredTimeout = FBConfiguration.sharedInstance.waitForIdleTimeout;
|
|
53
|
-
NSTimeInterval previousTimeout = _XCTApplicationStateTimeout();
|
|
54
|
-
_XCTSetApplicationStateTimeout(desiredTimeout);
|
|
55
49
|
[FBLogger logFmt:@"Waiting up to %@s until %@ is in idle state (%@ animations)",
|
|
56
50
|
@(desiredTimeout), bundleId, includingAnimations ? @"including" : @"excluding"];
|
|
57
|
-
|
|
51
|
+
[FBXCAXClientProxy withApplicationStateTimeout:desiredTimeout do:^{
|
|
58
52
|
original_waitForQuiescenceIncludingAnimationsIdlePreEvent(self, _cmd, includingAnimations, isPreEvent);
|
|
59
|
-
}
|
|
60
|
-
_XCTSetApplicationStateTimeout(previousTimeout);
|
|
61
|
-
}
|
|
53
|
+
}];
|
|
62
54
|
}
|
|
63
55
|
|
|
64
56
|
@implementation XCUIApplicationProcess (FBQuiescence)
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
#import "FBSettings.h"
|
|
23
23
|
#import "FBSettingsHandler.h"
|
|
24
24
|
#import "FBRuntimeUtils.h"
|
|
25
|
+
#import "FBXCAXClientProxy.h"
|
|
25
26
|
#import "FBXCodeCompatibility.h"
|
|
26
27
|
#import "XCUIApplication+FBHelpers.h"
|
|
27
28
|
#import "XCUIApplication+FBQuiescence.h"
|
|
@@ -376,18 +377,22 @@
|
|
|
376
377
|
return errorResponse;
|
|
377
378
|
}
|
|
378
379
|
} else {
|
|
379
|
-
|
|
380
|
+
__block id<FBResponsePayload> launchErrorResponse;
|
|
381
|
+
void (^launchBlock)(void) = ^{
|
|
382
|
+
@try {
|
|
383
|
+
[app launch];
|
|
384
|
+
} @catch (NSException *e) {
|
|
385
|
+
launchErrorResponse = FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:e.reason traceback:nil]);
|
|
386
|
+
}
|
|
387
|
+
};
|
|
380
388
|
if (nil != capabilities[FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC]) {
|
|
381
|
-
|
|
389
|
+
[FBXCAXClientProxy withApplicationStateTimeout:[capabilities[FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC] doubleValue]
|
|
390
|
+
do:launchBlock];
|
|
391
|
+
} else {
|
|
392
|
+
launchBlock();
|
|
382
393
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
} @catch (NSException *e) {
|
|
386
|
-
return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:e.reason traceback:nil]);
|
|
387
|
-
} @finally {
|
|
388
|
-
if (nil != capabilities[FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC]) {
|
|
389
|
-
_XCTSetApplicationStateTimeout(defaultTimeout);
|
|
390
|
-
}
|
|
394
|
+
if (nil != launchErrorResponse) {
|
|
395
|
+
return launchErrorResponse;
|
|
391
396
|
}
|
|
392
397
|
}
|
|
393
398
|
|
|
@@ -486,12 +491,9 @@
|
|
|
486
491
|
withApplication:(nullable NSString *)bundleID
|
|
487
492
|
timeout:(nullable NSNumber *)timeout
|
|
488
493
|
{
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
_XCTSetApplicationStateTimeout([timeout doubleValue]);
|
|
493
|
-
}
|
|
494
|
-
@try {
|
|
494
|
+
__block id<FBResponsePayload> response;
|
|
495
|
+
void (^openBlock)(void) = ^{
|
|
496
|
+
NSError *openError;
|
|
495
497
|
BOOL result = nil == bundleID
|
|
496
498
|
? [XCUIDevice.sharedDevice fb_openUrl:initialUrl
|
|
497
499
|
error:&openError]
|
|
@@ -499,16 +501,18 @@
|
|
|
499
501
|
withApplication:(id)bundleID
|
|
500
502
|
error:&openError];
|
|
501
503
|
if (result) {
|
|
502
|
-
return
|
|
504
|
+
return;
|
|
503
505
|
}
|
|
504
506
|
NSString *errorMsg = [NSString stringWithFormat:@"Cannot open the URL %@ with the %@ application. Original error: %@",
|
|
505
507
|
initialUrl, bundleID ?: @"default", openError.localizedDescription];
|
|
506
|
-
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
508
|
+
response = FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:errorMsg traceback:nil]);
|
|
509
|
+
};
|
|
510
|
+
if (nil != timeout) {
|
|
511
|
+
[FBXCAXClientProxy withApplicationStateTimeout:[timeout doubleValue] do:openBlock];
|
|
512
|
+
} else {
|
|
513
|
+
openBlock();
|
|
511
514
|
}
|
|
515
|
+
return response;
|
|
512
516
|
}
|
|
513
517
|
|
|
514
518
|
@end
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
<key>CFBundlePackageType</key>
|
|
16
16
|
<string>FMWK</string>
|
|
17
17
|
<key>CFBundleShortVersionString</key>
|
|
18
|
-
<string>16.
|
|
18
|
+
<string>16.11.1</string>
|
|
19
19
|
<key>CFBundleSignature</key>
|
|
20
20
|
<string>????</string>
|
|
21
21
|
<key>CFBundleVersion</key>
|
|
22
|
-
<string>16.
|
|
22
|
+
<string>16.11.1</string>
|
|
23
23
|
<key>NSPrincipalClass</key>
|
|
24
24
|
<string/>
|
|
25
25
|
</dict>
|
|
@@ -165,7 +165,7 @@ static NSUInteger FBNormalizedMjpegFramerate(NSUInteger framerate)
|
|
|
165
165
|
continue;
|
|
166
166
|
}
|
|
167
167
|
[self.pendingFrameCounts setObject:@(pendingFrames + 1) forKey:client];
|
|
168
|
-
[self.socket writeData:chunk toClient:client completion:^{
|
|
168
|
+
[self.socket writeData:chunk toClient:client completion:^(BOOL didSucceed) {
|
|
169
169
|
__strong typeof(weakSelf) strongSelf = weakSelf;
|
|
170
170
|
if (nil == strongSelf) {
|
|
171
171
|
return;
|
|
@@ -22,8 +22,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
22
22
|
|
|
23
23
|
+ (instancetype)sharedClient;
|
|
24
24
|
|
|
25
|
-
- (BOOL)setAXTimeout:(NSTimeInterval)timeout error:(NSError **)error;
|
|
26
|
-
|
|
27
25
|
- (nullable id<FBXCElementSnapshot>)snapshotForElement:(id<FBXCAccessibilityElement>)element
|
|
28
26
|
attributes:(nullable NSArray<NSString *> *)attributes
|
|
29
27
|
inDepth:(BOOL)inDepth
|
|
@@ -53,6 +51,121 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
53
51
|
|
|
54
52
|
- (nullable XCUIApplication *)monitoredApplicationWithProcessIdentifier:(int)pid;
|
|
55
53
|
|
|
54
|
+
/**
|
|
55
|
+
Runs `block` synchronously with AXTimeout (the "AXTimeout" property of
|
|
56
|
+
XCAXClient_iOS/XCUIAccessibilityInterface, backed by the private `_XCTAXIPCTimeout`
|
|
57
|
+
global) temporarily set to `timeout`, restoring the previous value once `block`
|
|
58
|
+
returns (even if it throws). Bounds how long a single accessibility (AX) request
|
|
59
|
+
issued by this process is allowed to wait for a reply from the AX server. Every
|
|
60
|
+
AX-backed call funneled through this proxy - systemApplication, activeApplications,
|
|
61
|
+
snapshotForElement:..., attributesForElement:... - is an in-process call into
|
|
62
|
+
XCAXClient_iOS and is bounded by this single, process-wide value; there is no
|
|
63
|
+
per-call override. Defaults to 60 seconds.
|
|
64
|
+
|
|
65
|
+
These XCAXClient_iOS calls are themselves wrapped in
|
|
66
|
+
`+[XCTFuture futureWithTimeout:description:block:]`, but that wrapper's own timeout
|
|
67
|
+
is `_XCTAXClientWrapperTimeout()` - AXTimeout plus a fixed 5-second margin, not
|
|
68
|
+
+withXPCRequestTimeout:do:'s `_XCTXPCRequestTimeout`. That margin exists so the AX
|
|
69
|
+
layer's own timeout has a chance to fire and produce a clean error before XCTFuture's
|
|
70
|
+
wrapper would cut if off anyway; it is not independently tunable. AXTimeout is
|
|
71
|
+
therefore the only knob that matters for every call this proxy wraps.
|
|
72
|
+
|
|
73
|
+
Important: this only bounds how long the CALLING thread waits for a reply. The AX
|
|
74
|
+
server itself is not told to cancel the request when this timeout elapses - the
|
|
75
|
+
request keeps running/queued on the AX side regardless of whether this process gave
|
|
76
|
+
up waiting on it. All AX requests from this process share one serial channel to the
|
|
77
|
+
AX server, so if the target app's UI is genuinely unresponsive, lowering this value
|
|
78
|
+
does not reduce the amount of queued work or make the server itself more responsive -
|
|
79
|
+
it only makes each individual caller give up sooner, while requests already abandoned
|
|
80
|
+
by their callers keep occupying the channel and can still delay whatever is queued
|
|
81
|
+
behind them by their original, un-shortened duration.
|
|
82
|
+
|
|
83
|
+
The whole scope is serialized behind a dedicated lock, so overlapping calls from
|
|
84
|
+
different threads nest/queue instead of racing to restore the global.
|
|
85
|
+
|
|
86
|
+
If installing `timeout` fails, `block` is not run, this returns NO, and `error` (if
|
|
87
|
+
given) is populated with the underlying failure. A failure while restoring the
|
|
88
|
+
previous value is logged and reported via `error` independently of the return value,
|
|
89
|
+
which always reflects `block`'s completion, sampled immediately after it returns and
|
|
90
|
+
before the restore attempt.
|
|
91
|
+
*/
|
|
92
|
+
- (BOOL)withAXTimeout:(NSTimeInterval)timeout do:(void (^)(void))block error:(NSError **)error;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
Runs `block` synchronously with the XCTest automation-session XPC request timeout
|
|
96
|
+
(the private `_XCTXPCRequestTimeout`/`_XCTSetXPCRequestTimeout` globals) temporarily
|
|
97
|
+
set to `timeout`, restoring the previous value once `block` returns (even if it
|
|
98
|
+
throws). Defaults to 30 seconds.
|
|
99
|
+
|
|
100
|
+
This does NOT bound anything else in this proxy - despite the similar shape, it is
|
|
101
|
+
not the XCTest-level analog of -withAXTimeout:do: for the calls above. AXTimeout and
|
|
102
|
+
the XPC request timeout gate two structurally different, non-nested call paths:
|
|
103
|
+
XCAXClient_iOS (what this proxy wraps) makes its AX-server round trips in-process,
|
|
104
|
+
bounded solely by AXTimeout (see -withAXTimeout:do:); XCTRunnerAutomationSession -
|
|
105
|
+
a separate class WDA does not go through here - makes its calls over a real
|
|
106
|
+
NSXPCConnection (`remoteObjectProxyWithErrorHandler:`) to another process, bounded by
|
|
107
|
+
this timeout instead. `-[XCTRunnerAutomationSession matchesForQuery:error:]` (the
|
|
108
|
+
primitive behind XCUIElementQuery/most element-finding lookups) and that class's own,
|
|
109
|
+
identically-named `attributesForElement:attributes:error:` are the calls this timeout
|
|
110
|
+
actually bounds - not -[XCAXClient_iOS attributesForElement:attributes:error:] above.
|
|
111
|
+
|
|
112
|
+
`futureWithTimeout:description:block:` is a synchronous wait wrapper: it starts the
|
|
113
|
+
real (asynchronous) XPC request and blocks the calling thread until either the reply
|
|
114
|
+
arrives or this timeout elapses, then returns either way - but elapsing the timeout
|
|
115
|
+
does NOT cancel the underlying XPC request. It keeps running to completion on the
|
|
116
|
+
same serial channel regardless of whether anyone is still waiting on it.
|
|
117
|
+
|
|
118
|
+
Practical consequence: all XPC-bounded requests from this process share one queue to
|
|
119
|
+
the automation session. If the target app's main thread/run loop is genuinely stuck,
|
|
120
|
+
lowering this timeout does not shrink the backlog or make the target more responsive
|
|
121
|
+
- it only makes the CALLER give up sooner. A request issued right after an earlier
|
|
122
|
+
one "times out" still has to wait behind that earlier request's real completion (which
|
|
123
|
+
keeps consuming the channel in the background), so it can take just as long, or longer,
|
|
124
|
+
to be serviced - repeatedly retrying after a timeout adds more queued work rather than
|
|
125
|
+
freeing up the channel, and can never be used to reliably bound end-to-end latency
|
|
126
|
+
while the target is unresponsive.
|
|
127
|
+
|
|
128
|
+
A class-level method: it only touches the XPC request timeout global, never the AX
|
|
129
|
+
client, so calling it does not trigger AX subsystem initialization. The scope is
|
|
130
|
+
serialized behind a dedicated lock, so overlapping calls nest/queue instead of racing
|
|
131
|
+
to restore the global. The returned completion result is sampled immediately after
|
|
132
|
+
`block` returns and before the previous value is restored.
|
|
133
|
+
|
|
134
|
+
Returns YES if `block` returned within `timeout`, NO otherwise.
|
|
135
|
+
*/
|
|
136
|
+
+ (BOOL)withXPCRequestTimeout:(NSTimeInterval)timeout do:(void (^)(void))block;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
Runs `block` synchronously with the XCTest application-state timeout (the private
|
|
140
|
+
`_XCTApplicationStateTimeout`/`_XCTSetApplicationStateTimeout` globals) temporarily
|
|
141
|
+
set to `timeout`, restoring the previous value once `block` returns (even if it
|
|
142
|
+
throws). Defaults to 60 seconds, though it may be pre-seeded once from a
|
|
143
|
+
NSUserDefaults override the first time it is read, before any of this process's own
|
|
144
|
+
+withApplicationStateTimeout:do: calls run.
|
|
145
|
+
|
|
146
|
+
Unlike AXTimeout/the XPC request timeout above, this one is not scoped to a single
|
|
147
|
+
request/response round trip - it bounds `[XCTWaiter waitForExpectations:timeout:]`
|
|
148
|
+
inside `-[XCUIApplicationProcess waitForQuiescenceIncludingAnimationsIdle:...]`,
|
|
149
|
+
which WDA's own XCUIApplicationProcess+FBQuiescence.m swizzle already targets for the
|
|
150
|
+
per-tap pre/post-event quiescence wait. That wait is gated by a *compound OR*
|
|
151
|
+
expectation over two independently-notified flags - `eventLoopHasIdled` and (when
|
|
152
|
+
requested) `animationsHaveFinished` - so it can return as soon as either one changes,
|
|
153
|
+
not necessarily both; this timeout only bounds how long that race is allowed to run
|
|
154
|
+
before giving up on both. The same global also bounds XCTest's app-launch/foreground
|
|
155
|
+
state-transition waits (see -[FBSessionCommands launchApplication:...],
|
|
156
|
+
+[FBSessionCommands openDeepLink:withApplication:timeout:]), which are a different,
|
|
157
|
+
non-quiescence consumer of this same timeout.
|
|
158
|
+
|
|
159
|
+
A class-level method: it only touches the application-state timeout global, never the
|
|
160
|
+
AX client, so calling it does not trigger AX subsystem initialization. The scope is
|
|
161
|
+
serialized behind a dedicated lock, so overlapping calls nest/queue instead of racing
|
|
162
|
+
to restore the global. The returned completion result is sampled immediately after
|
|
163
|
+
`block` returns and before the previous value is restored.
|
|
164
|
+
|
|
165
|
+
Returns YES if `block` returned within `timeout`, NO otherwise.
|
|
166
|
+
*/
|
|
167
|
+
+ (BOOL)withApplicationStateTimeout:(NSTimeInterval)timeout do:(void (^)(void))block;
|
|
168
|
+
|
|
56
169
|
@end
|
|
57
170
|
|
|
58
171
|
NS_ASSUME_NONNULL_END
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
#import "FBXCAXClientProxy.h"
|
|
10
10
|
|
|
11
|
+
#import "CDStructures.h"
|
|
11
12
|
#import "FBXCAccessibilityElement.h"
|
|
12
13
|
#import "FBLogger.h"
|
|
13
14
|
#import "FBMacros.h"
|
|
@@ -17,6 +18,39 @@
|
|
|
17
18
|
|
|
18
19
|
static id FBAXClient = nil;
|
|
19
20
|
|
|
21
|
+
// Guards -withAXTimeout:do:'s save/set/restore of the process-wide AXTimeout global.
|
|
22
|
+
static NSRecursiveLock *FBAXTimeoutLock(void)
|
|
23
|
+
{
|
|
24
|
+
static NSRecursiveLock *lock;
|
|
25
|
+
static dispatch_once_t onceToken;
|
|
26
|
+
dispatch_once(&onceToken, ^{
|
|
27
|
+
lock = [NSRecursiveLock new];
|
|
28
|
+
});
|
|
29
|
+
return lock;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Guards +withXPCRequestTimeout:do:'s save/set/restore of the process-wide XPC request timeout global.
|
|
33
|
+
static NSRecursiveLock *FBXPCRequestTimeoutLock(void)
|
|
34
|
+
{
|
|
35
|
+
static NSRecursiveLock *lock;
|
|
36
|
+
static dispatch_once_t onceToken;
|
|
37
|
+
dispatch_once(&onceToken, ^{
|
|
38
|
+
lock = [NSRecursiveLock new];
|
|
39
|
+
});
|
|
40
|
+
return lock;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Guards +withApplicationStateTimeout:do:'s save/set/restore of the process-wide application-state timeout global.
|
|
44
|
+
static NSRecursiveLock *FBApplicationStateTimeoutLock(void)
|
|
45
|
+
{
|
|
46
|
+
static NSRecursiveLock *lock;
|
|
47
|
+
static dispatch_once_t onceToken;
|
|
48
|
+
dispatch_once(&onceToken, ^{
|
|
49
|
+
lock = [NSRecursiveLock new];
|
|
50
|
+
});
|
|
51
|
+
return lock;
|
|
52
|
+
}
|
|
53
|
+
|
|
20
54
|
@interface FBXCAXClientProxy ()
|
|
21
55
|
|
|
22
56
|
@property (nonatomic) NSMutableDictionary<NSNumber *, XCUIApplication *> *appsCache;
|
|
@@ -38,9 +72,80 @@ static id FBAXClient = nil;
|
|
|
38
72
|
return instance;
|
|
39
73
|
}
|
|
40
74
|
|
|
41
|
-
- (BOOL)
|
|
75
|
+
- (BOOL)withAXTimeout:(NSTimeInterval)timeout do:(void (^)(void))block error:(NSError **)error
|
|
76
|
+
{
|
|
77
|
+
NSRecursiveLock *lock = FBAXTimeoutLock();
|
|
78
|
+
[lock lock];
|
|
79
|
+
@try {
|
|
80
|
+
NSTimeInterval previousTimeout = [FBAXClient AXTimeout];
|
|
81
|
+
NSError *setError;
|
|
82
|
+
if (![FBAXClient _setAXTimeout:timeout error:&setError]) {
|
|
83
|
+
[FBLogger logFmt:@"Failed to set AXTimeout to %@: %@", @(timeout), setError];
|
|
84
|
+
if (nil != error) {
|
|
85
|
+
*error = setError;
|
|
86
|
+
}
|
|
87
|
+
return NO;
|
|
88
|
+
}
|
|
89
|
+
NSTimeInterval startTime = NSProcessInfo.processInfo.systemUptime;
|
|
90
|
+
BOOL completedInTime = NO;
|
|
91
|
+
@try {
|
|
92
|
+
block();
|
|
93
|
+
completedInTime = (NSProcessInfo.processInfo.systemUptime - startTime) < timeout;
|
|
94
|
+
} @finally {
|
|
95
|
+
NSError *restoreError;
|
|
96
|
+
if (![FBAXClient _setAXTimeout:previousTimeout error:&restoreError]) {
|
|
97
|
+
[FBLogger logFmt:@"Failed to restore AXTimeout to %@: %@", @(previousTimeout), restoreError];
|
|
98
|
+
if (nil != error) {
|
|
99
|
+
*error = restoreError;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return completedInTime;
|
|
104
|
+
} @finally {
|
|
105
|
+
[lock unlock];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
+ (BOOL)withXPCRequestTimeout:(NSTimeInterval)timeout do:(void (^)(void))block
|
|
110
|
+
{
|
|
111
|
+
NSRecursiveLock *lock = FBXPCRequestTimeoutLock();
|
|
112
|
+
[lock lock];
|
|
113
|
+
@try {
|
|
114
|
+
NSTimeInterval previousTimeout = _XCTXPCRequestTimeout();
|
|
115
|
+
_XCTSetXPCRequestTimeout(timeout);
|
|
116
|
+
NSTimeInterval startTime = NSProcessInfo.processInfo.systemUptime;
|
|
117
|
+
BOOL completedInTime = NO;
|
|
118
|
+
@try {
|
|
119
|
+
block();
|
|
120
|
+
completedInTime = (NSProcessInfo.processInfo.systemUptime - startTime) < timeout;
|
|
121
|
+
} @finally {
|
|
122
|
+
_XCTSetXPCRequestTimeout(previousTimeout);
|
|
123
|
+
}
|
|
124
|
+
return completedInTime;
|
|
125
|
+
} @finally {
|
|
126
|
+
[lock unlock];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
+ (BOOL)withApplicationStateTimeout:(NSTimeInterval)timeout do:(void (^)(void))block
|
|
42
131
|
{
|
|
43
|
-
|
|
132
|
+
NSRecursiveLock *lock = FBApplicationStateTimeoutLock();
|
|
133
|
+
[lock lock];
|
|
134
|
+
@try {
|
|
135
|
+
NSTimeInterval previousTimeout = _XCTApplicationStateTimeout();
|
|
136
|
+
_XCTSetApplicationStateTimeout(timeout);
|
|
137
|
+
NSTimeInterval startTime = NSProcessInfo.processInfo.systemUptime;
|
|
138
|
+
BOOL completedInTime = NO;
|
|
139
|
+
@try {
|
|
140
|
+
block();
|
|
141
|
+
completedInTime = (NSProcessInfo.processInfo.systemUptime - startTime) < timeout;
|
|
142
|
+
} @finally {
|
|
143
|
+
_XCTSetApplicationStateTimeout(previousTimeout);
|
|
144
|
+
}
|
|
145
|
+
return completedInTime;
|
|
146
|
+
} @finally {
|
|
147
|
+
[lock unlock];
|
|
148
|
+
}
|
|
44
149
|
}
|
|
45
150
|
|
|
46
151
|
- (id<FBXCElementSnapshot>)snapshotForElement:(id<FBXCAccessibilityElement>)element
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2015-present, Facebook, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the BSD-style license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#import <XCTest/XCTest.h>
|
|
10
|
+
|
|
11
|
+
#import "CDStructures.h"
|
|
12
|
+
#import "FBXCAXClientProxy.h"
|
|
13
|
+
|
|
14
|
+
@interface FBXCAXClientProxyTests : XCTestCase
|
|
15
|
+
@end
|
|
16
|
+
|
|
17
|
+
@implementation FBXCAXClientProxyTests
|
|
18
|
+
|
|
19
|
+
- (void)testApplicationStateTimeoutSetsValueDuringBlockAndRestoresAfter
|
|
20
|
+
{
|
|
21
|
+
double original = _XCTApplicationStateTimeout();
|
|
22
|
+
__block double observed = -1;
|
|
23
|
+
BOOL completed = [FBXCAXClientProxy withApplicationStateTimeout:12.5 do:^{
|
|
24
|
+
observed = _XCTApplicationStateTimeout();
|
|
25
|
+
}];
|
|
26
|
+
XCTAssertTrue(completed);
|
|
27
|
+
XCTAssertEqual(observed, 12.5);
|
|
28
|
+
XCTAssertEqual(_XCTApplicationStateTimeout(), original);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
- (void)testApplicationStateTimeoutNestedCallsRestoreCorrectly
|
|
32
|
+
{
|
|
33
|
+
double original = _XCTApplicationStateTimeout();
|
|
34
|
+
[FBXCAXClientProxy withApplicationStateTimeout:20 do:^{
|
|
35
|
+
XCTAssertEqual(_XCTApplicationStateTimeout(), 20);
|
|
36
|
+
[FBXCAXClientProxy withApplicationStateTimeout:5 do:^{
|
|
37
|
+
XCTAssertEqual(_XCTApplicationStateTimeout(), 5);
|
|
38
|
+
}];
|
|
39
|
+
XCTAssertEqual(_XCTApplicationStateTimeout(), 20);
|
|
40
|
+
}];
|
|
41
|
+
XCTAssertEqual(_XCTApplicationStateTimeout(), original);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
- (void)testApplicationStateTimeoutOverlappingCallsRestoreOriginalValue
|
|
45
|
+
{
|
|
46
|
+
double original = _XCTApplicationStateTimeout();
|
|
47
|
+
NSInteger iterations = 50;
|
|
48
|
+
dispatch_group_t group = dispatch_group_create();
|
|
49
|
+
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
|
|
50
|
+
for (NSInteger i = 0; i < iterations; i++) {
|
|
51
|
+
dispatch_group_async(group, queue, ^{
|
|
52
|
+
[FBXCAXClientProxy withApplicationStateTimeout:10 + (double)i do:^{
|
|
53
|
+
usleep(500);
|
|
54
|
+
}];
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
|
|
58
|
+
XCTAssertEqual(_XCTApplicationStateTimeout(), original);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
- (void)testXPCRequestTimeoutSetsValueDuringBlockAndRestoresAfter
|
|
62
|
+
{
|
|
63
|
+
double original = _XCTXPCRequestTimeout();
|
|
64
|
+
__block double observed = -1;
|
|
65
|
+
BOOL completed = [FBXCAXClientProxy withXPCRequestTimeout:7.5 do:^{
|
|
66
|
+
observed = _XCTXPCRequestTimeout();
|
|
67
|
+
}];
|
|
68
|
+
XCTAssertTrue(completed);
|
|
69
|
+
XCTAssertEqual(observed, 7.5);
|
|
70
|
+
XCTAssertEqual(_XCTXPCRequestTimeout(), original);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
- (void)testXPCRequestTimeoutOverlappingCallsRestoreOriginalValue
|
|
74
|
+
{
|
|
75
|
+
double original = _XCTXPCRequestTimeout();
|
|
76
|
+
NSInteger iterations = 50;
|
|
77
|
+
dispatch_group_t group = dispatch_group_create();
|
|
78
|
+
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
|
|
79
|
+
for (NSInteger i = 0; i < iterations; i++) {
|
|
80
|
+
dispatch_group_async(group, queue, ^{
|
|
81
|
+
[FBXCAXClientProxy withXPCRequestTimeout:10 + (double)i do:^{
|
|
82
|
+
usleep(500);
|
|
83
|
+
}];
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
|
|
87
|
+
XCTAssertEqual(_XCTXPCRequestTimeout(), original);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@end
|