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 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
- @try {
34
+ [FBXCAXClientProxy withApplicationStateTimeout:desiredTimeout do:^{
37
35
  original_waitForQuiescenceIncludingAnimationsIdle(self, _cmd, includingAnimations);
38
- } @finally {
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
- @try {
51
+ [FBXCAXClientProxy withApplicationStateTimeout:desiredTimeout do:^{
58
52
  original_waitForQuiescenceIncludingAnimationsIdlePreEvent(self, _cmd, includingAnimations, isPreEvent);
59
- } @finally {
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
- NSTimeInterval defaultTimeout = _XCTApplicationStateTimeout();
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
- _XCTSetApplicationStateTimeout([capabilities[FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC] doubleValue]);
389
+ [FBXCAXClientProxy withApplicationStateTimeout:[capabilities[FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC] doubleValue]
390
+ do:launchBlock];
391
+ } else {
392
+ launchBlock();
382
393
  }
383
- @try {
384
- [app launch];
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
- NSError *openError;
490
- NSTimeInterval defaultTimeout = _XCTApplicationStateTimeout();
491
- if (nil != timeout) {
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 nil;
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
- return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:errorMsg traceback:nil]);
507
- } @finally {
508
- if (nil != timeout) {
509
- _XCTSetApplicationStateTimeout(defaultTimeout);
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.10.0</string>
18
+ <string>16.11.1</string>
19
19
  <key>CFBundleSignature</key>
20
20
  <string>????</string>
21
21
  <key>CFBundleVersion</key>
22
- <string>16.10.0</string>
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)setAXTimeout:(NSTimeInterval)timeout error:(NSError **)error
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
- return [FBAXClient _setAXTimeout:timeout error:error];
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appium-webdriveragent",
3
- "version": "16.10.0",
3
+ "version": "16.11.1",
4
4
  "description": "Package bundling WebDriverAgent",
5
5
  "keywords": [
6
6
  "Appium",