appium-webdriveragent 16.7.3 → 16.9.0
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/WebDriverAgentLib/Categories/XCAXClient_iOS+FBSnapshotReqParams.m +90 -0
- package/WebDriverAgentLib/Commands/FBCustomCommands.m +4 -1
- package/WebDriverAgentLib/Commands/FBSessionCommands.m +3 -0
- package/WebDriverAgentLib/Info.plist +2 -2
- package/WebDriverAgentLib/Routing/FBWebServer.m +4 -0
- package/WebDriverAgentLib/Utilities/FBConfiguration.h +11 -0
- package/WebDriverAgentLib/Utilities/FBConfiguration.m +1 -0
- package/WebDriverAgentLib/Utilities/FBSettings.h +1 -0
- package/WebDriverAgentLib/Utilities/FBSettings.m +1 -0
- package/WebDriverAgentLib/Utilities/FBSettingsHandler.m +7 -0
- package/WebDriverAgentLib/Utilities/FBXCAXClientProxy.h +9 -0
- package/WebDriverAgentLib/Utilities/FBXCAXClientProxy.m +35 -19
- package/WebDriverAgentTests/IntegrationApp/Classes/ViewController.m +4 -3
- package/WebDriverAgentTests/IntegrationApp/Resources/Base.lproj/Main.storyboard +0 -1
- package/WebDriverAgentTests/IntegrationTests/FBConfigurationTests.m +34 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [16.9.0](https://github.com/appium/WebDriverAgent/compare/v16.8.0...v16.9.0) (2026-08-27)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* cache the system application resolution for WDA's process lifetime ([#1232](https://github.com/appium/WebDriverAgent/issues/1232)) ([21c98f2](https://github.com/appium/WebDriverAgent/commit/21c98f253561ee3dfc93edfb1c4de6afc3200b50))
|
|
6
|
+
|
|
7
|
+
## [16.8.0](https://github.com/appium/WebDriverAgent/compare/v16.7.3...v16.8.0) (2026-08-24)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* bound accessibility snapshot requests to avoid indefinite hangs ([#1214](https://github.com/appium/WebDriverAgent/issues/1214)) ([cd829eb](https://github.com/appium/WebDriverAgent/commit/cd829eb9725f57efbfb9538a073de059020ddd38))
|
|
12
|
+
|
|
1
13
|
## [16.7.3](https://github.com/appium/WebDriverAgent/compare/v16.7.2...v16.7.3) (2026-08-24)
|
|
2
14
|
|
|
3
15
|
### Bug Fixes
|
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
#import <objc/runtime.h>
|
|
12
12
|
|
|
13
|
+
#import "FBConfiguration.h"
|
|
14
|
+
#import "FBErrorBuilder.h"
|
|
15
|
+
#import "FBLogger.h"
|
|
16
|
+
#import "FBXCAccessibilityElement.h"
|
|
17
|
+
#import "FBXCAXClientProxy.h"
|
|
18
|
+
#import "XCUIApplication.h"
|
|
19
|
+
|
|
13
20
|
/**
|
|
14
21
|
Available parameters with their default values for XCTest:
|
|
15
22
|
@"maxChildren" : (int)2147483647
|
|
@@ -60,6 +67,82 @@ static id swizzledSnapshotParameters(id self, SEL _cmd)
|
|
|
60
67
|
return result;
|
|
61
68
|
}
|
|
62
69
|
|
|
70
|
+
static id (*original_requestSnapshotForElement)(id, SEL, id, id, id, NSError **);
|
|
71
|
+
|
|
72
|
+
// pid -> last-unresponsive-at. XCTest retries a failed snapshot request several
|
|
73
|
+
// times in a row; this lets retries fail fast within `timeout` of the last check
|
|
74
|
+
// instead of each re-running the full wait.
|
|
75
|
+
static NSMutableDictionary<NSNumber *, NSDate *> *unresponsiveApplicationPids;
|
|
76
|
+
static NSObject *unresponsiveApplicationPidsLock;
|
|
77
|
+
|
|
78
|
+
static NSError *FBBuildUnresponsiveApplicationError(int pid, NSTimeInterval timeout)
|
|
79
|
+
{
|
|
80
|
+
// https://github.com/appium/WebDriverAgent/issues/1210
|
|
81
|
+
NSString *description = [NSString stringWithFormat:
|
|
82
|
+
@"The application with process identifier %d did not confirm its main run loop is "
|
|
83
|
+
@"responsive within %.1f second(s) and is likely in an unresponsive state. "
|
|
84
|
+
@"Aborting the accessibility snapshot request instead of risking an indefinite "
|
|
85
|
+
@"hang.",
|
|
86
|
+
pid, timeout];
|
|
87
|
+
[FBLogger logFmt:@"%@", description];
|
|
88
|
+
NSError *error;
|
|
89
|
+
[[[FBErrorBuilder builder] withDescription:description] buildError:&error];
|
|
90
|
+
return error;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Guards -[XCAXClient_iOS requestSnapshotForElement:...] against hanging forever
|
|
94
|
+
// on an unresponsive app (#1210). If accessibilityDeadline > 0, checks run loop
|
|
95
|
+
// responsiveness first and aborts with an error instead of risking an unbounded
|
|
96
|
+
// wait; otherwise falls through to the original, unbounded behavior.
|
|
97
|
+
static id swizzledRequestSnapshotForElement(id self, SEL _cmd, id element, id attributes, id parameters, NSError **error)
|
|
98
|
+
{
|
|
99
|
+
NSTimeInterval timeout = FBConfiguration.sharedInstance.accessibilityDeadline;
|
|
100
|
+
if (timeout < DBL_EPSILON) {
|
|
101
|
+
return original_requestSnapshotForElement(self, _cmd, element, attributes, parameters, error);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
int pid = [(id<FBXCAccessibilityElement>)element processIdentifier];
|
|
105
|
+
XCUIApplication *application = [FBXCAXClientProxy.sharedClient monitoredApplicationWithProcessIdentifier:pid];
|
|
106
|
+
if (nil == application) {
|
|
107
|
+
// Nothing to confirm responsiveness for (e.g. the system element) - fall
|
|
108
|
+
// through to the original behavior.
|
|
109
|
+
return original_requestSnapshotForElement(self, _cmd, element, attributes, parameters, error);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
NSNumber *pidKey = @(pid);
|
|
113
|
+
@synchronized (unresponsiveApplicationPidsLock) {
|
|
114
|
+
NSDate *markedUnresponsiveAt = unresponsiveApplicationPids[pidKey];
|
|
115
|
+
if (nil != markedUnresponsiveAt && -markedUnresponsiveAt.timeIntervalSinceNow < timeout) {
|
|
116
|
+
if (nil != error) {
|
|
117
|
+
*error = FBBuildUnresponsiveApplicationError(pid, timeout);
|
|
118
|
+
}
|
|
119
|
+
return nil;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
|
|
124
|
+
__block BOOL isResponsive = NO;
|
|
125
|
+
[FBXCAXClientProxy.sharedClient notifyWhenEventLoopIsIdleForApplication:application
|
|
126
|
+
reply:^(id result, NSError *idleError) {
|
|
127
|
+
isResponsive = (nil == idleError);
|
|
128
|
+
dispatch_semaphore_signal(sem);
|
|
129
|
+
}];
|
|
130
|
+
BOOL didReplyInTime = 0 == dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)));
|
|
131
|
+
if (didReplyInTime && isResponsive) {
|
|
132
|
+
@synchronized (unresponsiveApplicationPidsLock) {
|
|
133
|
+
[unresponsiveApplicationPids removeObjectForKey:pidKey];
|
|
134
|
+
}
|
|
135
|
+
return original_requestSnapshotForElement(self, _cmd, element, attributes, parameters, error);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@synchronized (unresponsiveApplicationPidsLock) {
|
|
139
|
+
unresponsiveApplicationPids[pidKey] = [NSDate date];
|
|
140
|
+
}
|
|
141
|
+
if (nil != error) {
|
|
142
|
+
*error = FBBuildUnresponsiveApplicationError(pid, timeout);
|
|
143
|
+
}
|
|
144
|
+
return nil;
|
|
145
|
+
}
|
|
63
146
|
|
|
64
147
|
@implementation XCAXClient_iOS (FBSnapshotReqParams)
|
|
65
148
|
|
|
@@ -69,6 +152,9 @@ static id swizzledSnapshotParameters(id self, SEL _cmd)
|
|
|
69
152
|
|
|
70
153
|
+ (void)load
|
|
71
154
|
{
|
|
155
|
+
unresponsiveApplicationPids = [NSMutableDictionary new];
|
|
156
|
+
unresponsiveApplicationPidsLock = [NSObject new];
|
|
157
|
+
|
|
72
158
|
Method original_defaultParametersMethod = class_getInstanceMethod(self.class, @selector(defaultParameters));
|
|
73
159
|
IMP swizzledDefaultParametersImp = (IMP)swizzledDefaultParameters;
|
|
74
160
|
original_defaultParameters = (id (*)(id, SEL)) method_setImplementation(original_defaultParametersMethod, swizzledDefaultParametersImp);
|
|
@@ -76,6 +162,10 @@ static id swizzledSnapshotParameters(id self, SEL _cmd)
|
|
|
76
162
|
Method original_snapshotParametersMethod = class_getInstanceMethod(NSClassFromString(@"XCTElementQuery"), NSSelectorFromString(@"snapshotParameters"));
|
|
77
163
|
IMP swizzledSnapshotParametersImp = (IMP)swizzledSnapshotParameters;
|
|
78
164
|
original_snapshotParameters = (id (*)(id, SEL)) method_setImplementation(original_snapshotParametersMethod, swizzledSnapshotParametersImp);
|
|
165
|
+
|
|
166
|
+
Method original_requestSnapshotForElementMethod = class_getInstanceMethod(self.class, @selector(requestSnapshotForElement:attributes:parameters:error:));
|
|
167
|
+
IMP swizzledRequestSnapshotForElementImp = (IMP)swizzledRequestSnapshotForElement;
|
|
168
|
+
original_requestSnapshotForElement = (id (*)(id, SEL, id, id, id, NSError **)) method_setImplementation(original_requestSnapshotForElementMethod, swizzledRequestSnapshotForElementImp);
|
|
79
169
|
}
|
|
80
170
|
|
|
81
171
|
#pragma clang diagnostic pop
|
|
@@ -205,10 +205,13 @@
|
|
|
205
205
|
+ (id<FBResponsePayload>)handleActiveAppInfo:(FBRouteRequest *)request
|
|
206
206
|
{
|
|
207
207
|
XCUIApplication *app = request.session.activeApplication ?: XCUIApplication.fb_activeApplication;
|
|
208
|
+
// .identifier can be nil if the app stopped answering accessibility requests
|
|
209
|
+
// and accessibilityDeadline aborted the underlying snapshot fetch (#1210).
|
|
210
|
+
NSString *name = app.identifier ?: @"unknown";
|
|
208
211
|
return FBResponseWithObject(@{
|
|
209
212
|
@"pid": @(app.processID),
|
|
210
213
|
@"bundleId": app.bundleID,
|
|
211
|
-
@"name":
|
|
214
|
+
@"name": name,
|
|
212
215
|
@"processArguments": [self processArguments:app],
|
|
213
216
|
});
|
|
214
217
|
}
|
|
@@ -309,6 +309,9 @@
|
|
|
309
309
|
if (nil != capabilities[FB_SETTING_WAIT_FOR_IDLE_TIMEOUT]) {
|
|
310
310
|
FBConfiguration.sharedInstance.waitForIdleTimeout = [capabilities[FB_SETTING_WAIT_FOR_IDLE_TIMEOUT] doubleValue];
|
|
311
311
|
}
|
|
312
|
+
if (nil != capabilities[FB_SETTING_ACCESSIBILITY_DEADLINE]) {
|
|
313
|
+
FBConfiguration.sharedInstance.accessibilityDeadline = [capabilities[FB_SETTING_ACCESSIBILITY_DEADLINE] doubleValue];
|
|
314
|
+
}
|
|
312
315
|
if (nil == capabilities[FB_CAP_FORCE_SIMULATOR_SOFTWARE_KEYBOARD_PRESENCE] ||
|
|
313
316
|
[capabilities[FB_CAP_FORCE_SIMULATOR_SOFTWARE_KEYBOARD_PRESENCE] boolValue]) {
|
|
314
317
|
[FBConfiguration.sharedInstance forceSimulatorSoftwareKeyboardPresence];
|
|
@@ -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.9.0</string>
|
|
19
19
|
<key>CFBundleSignature</key>
|
|
20
20
|
<string>????</string>
|
|
21
21
|
<key>CFBundleVersion</key>
|
|
22
|
-
<string>16.
|
|
22
|
+
<string>16.9.0</string>
|
|
23
23
|
<key>NSPrincipalClass</key>
|
|
24
24
|
<string/>
|
|
25
25
|
</dict>
|
|
@@ -269,6 +269,9 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
|
|
|
269
269
|
[response respondWithString:@"<!DOCTYPE html><html><title>Health Check</title><body><p>I-AM-ALIVE</p></body></html>"];
|
|
270
270
|
}];
|
|
271
271
|
|
|
272
|
+
// Deprecated: no longer needed since appium-xcuitest-driver handles calibration
|
|
273
|
+
// itself (https://github.com/appium/appium-xcuitest-driver/pull/2948). Kept for
|
|
274
|
+
// backward compatibility; will be removed in a future major release.
|
|
272
275
|
NSString *calibrationPage = @"<html>"
|
|
273
276
|
"<title>{\"x\":null,\"y\":null}</title>"
|
|
274
277
|
"<header>"
|
|
@@ -276,6 +279,7 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
|
|
|
276
279
|
"</header>"
|
|
277
280
|
"</html>";
|
|
278
281
|
[self.server get:@"/calibrate" withBlock:^(RouteRequest *request, RouteResponse *response) {
|
|
282
|
+
[FBLogger logFmt:@"The /calibrate endpoint is deprecated and will be removed in a future release"];
|
|
279
283
|
[response respondWithString:calibrationPage];
|
|
280
284
|
}];
|
|
281
285
|
|
|
@@ -234,6 +234,17 @@ typedef NS_ENUM(NSInteger, FBConfigurationKeyboardPreference) {
|
|
|
234
234
|
*/
|
|
235
235
|
@property (atomic, assign) NSTimeInterval animationCoolOffTimeout;
|
|
236
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Maximum time to wait for the frontmost application to confirm its main run loop
|
|
239
|
+
* is responsive before an accessibility snapshot request (element attribute
|
|
240
|
+
* lookups, active app detection, etc). XCTest has no bounded timeout of its own
|
|
241
|
+
* here, so a frozen app could otherwise block WDA forever (#1210); past this
|
|
242
|
+
* timeout the request is aborted with an error instead.
|
|
243
|
+
* Set to zero or negative to disable, restoring unbounded behavior. Disabled (0)
|
|
244
|
+
* by default.
|
|
245
|
+
*/
|
|
246
|
+
@property (atomic, assign) NSTimeInterval accessibilityDeadline;
|
|
247
|
+
|
|
237
248
|
/**
|
|
238
249
|
Custom class chain locator for accept alert button location.
|
|
239
250
|
This might be useful if the default buttons detection algorithm fails to determine alert buttons properly
|
|
@@ -351,6 +351,7 @@ static NSString *const axSettingsClassName = @"AXSettings";
|
|
|
351
351
|
self.autoClickAlertSelector = @"";
|
|
352
352
|
self.waitForIdleTimeout = 10.;
|
|
353
353
|
self.animationCoolOffTimeout = 2.;
|
|
354
|
+
self.accessibilityDeadline = 0.;
|
|
354
355
|
// 50 should be enough for the majority of the cases. The performance is acceptable for values up to 100.
|
|
355
356
|
FBSetCustomParameterForElementSnapshot(FBSnapshotMaxDepthKey, @50);
|
|
356
357
|
FBSetCustomParameterForElementSnapshot(FBSnapshotMaxChildrenKey, @INT_MAX);
|
|
@@ -23,6 +23,7 @@ extern NSString* const FB_SETTING_KEYBOARD_AUTOCORRECTION;
|
|
|
23
23
|
extern NSString* const FB_SETTING_KEYBOARD_PREDICTION;
|
|
24
24
|
extern NSString* const FB_SETTING_SNAPSHOT_MAX_DEPTH;
|
|
25
25
|
extern NSString* const FB_SETTING_SNAPSHOT_MAX_CHILDREN;
|
|
26
|
+
extern NSString* const FB_SETTING_ACCESSIBILITY_DEADLINE;
|
|
26
27
|
extern NSString* const FB_SETTING_USE_FIRST_MATCH;
|
|
27
28
|
extern NSString* const FB_SETTING_BOUND_ELEMENTS_BY_INDEX;
|
|
28
29
|
extern NSString* const FB_SETTING_REDUCE_MOTION;
|
|
@@ -19,6 +19,7 @@ NSString* const FB_SETTING_KEYBOARD_AUTOCORRECTION = @"keyboardAutocorrection";
|
|
|
19
19
|
NSString* const FB_SETTING_KEYBOARD_PREDICTION = @"keyboardPrediction";
|
|
20
20
|
NSString* const FB_SETTING_SNAPSHOT_MAX_DEPTH = @"snapshotMaxDepth";
|
|
21
21
|
NSString* const FB_SETTING_SNAPSHOT_MAX_CHILDREN = @"snapshotMaxChildren";
|
|
22
|
+
NSString* const FB_SETTING_ACCESSIBILITY_DEADLINE = @"accessibilityDeadline";
|
|
22
23
|
NSString* const FB_SETTING_USE_FIRST_MATCH = @"useFirstMatch";
|
|
23
24
|
NSString* const FB_SETTING_BOUND_ELEMENTS_BY_INDEX = @"boundElementsByIndex";
|
|
24
25
|
NSString* const FB_SETTING_REDUCE_MOTION = @"reduceMotion";
|
|
@@ -142,6 +142,10 @@ static NSSet<NSString *> *FBNilClearableSettingKeys(void)
|
|
|
142
142
|
FBConfiguration.sharedInstance.animationCoolOffTimeout = [value doubleValue];
|
|
143
143
|
return nil;
|
|
144
144
|
};
|
|
145
|
+
map[FB_SETTING_ACCESSIBILITY_DEADLINE] = ^FBCommandStatus *(FBSession *session, id value) {
|
|
146
|
+
FBConfiguration.sharedInstance.accessibilityDeadline = [value doubleValue];
|
|
147
|
+
return nil;
|
|
148
|
+
};
|
|
145
149
|
map[FB_SETTING_DEFAULT_ALERT_ACTION] = ^FBCommandStatus *(FBSession *session, id value) {
|
|
146
150
|
if (nil == value) {
|
|
147
151
|
session.defaultAlertAction = nil;
|
|
@@ -249,6 +253,9 @@ static NSSet<NSString *> *FBNilClearableSettingKeys(void)
|
|
|
249
253
|
map[FB_SETTING_ANIMATION_COOL_OFF_TIMEOUT] = ^id(FBSession *session) {
|
|
250
254
|
return @(FBConfiguration.sharedInstance.animationCoolOffTimeout);
|
|
251
255
|
};
|
|
256
|
+
map[FB_SETTING_ACCESSIBILITY_DEADLINE] = ^id(FBSession *session) {
|
|
257
|
+
return @(FBConfiguration.sharedInstance.accessibilityDeadline);
|
|
258
|
+
};
|
|
252
259
|
map[FB_SETTING_BOUND_ELEMENTS_BY_INDEX] = ^id(FBSession *session) {
|
|
253
260
|
return @(FBConfiguration.sharedInstance.boundElementsByIndex);
|
|
254
261
|
};
|
|
@@ -38,6 +38,15 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
38
38
|
- (void)notifyWhenNoAnimationsAreActiveForApplication:(XCUIApplication *)application
|
|
39
39
|
reply:(void (^)(void))reply;
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
Wraps the private -[XCAXClient_iOS notifyWhenEventLoopIsIdleForApplication:reply:],
|
|
43
|
+
used to check run loop responsiveness before a snapshot request (#1210).
|
|
44
|
+
`reply` may fire more than once per call; `error` is non-nil only if monitoring
|
|
45
|
+
itself could not be started.
|
|
46
|
+
*/
|
|
47
|
+
- (void)notifyWhenEventLoopIsIdleForApplication:(XCUIApplication *)application
|
|
48
|
+
reply:(void (^)(id _Nullable result, NSError * _Nullable error))reply;
|
|
49
|
+
|
|
41
50
|
- (nullable NSDictionary *)attributesForElement:(id<FBXCAccessibilityElement>)element
|
|
42
51
|
attributes:(NSArray *)attributes
|
|
43
52
|
error:(NSError**)error;
|
|
@@ -20,6 +20,7 @@ static id FBAXClient = nil;
|
|
|
20
20
|
@interface FBXCAXClientProxy ()
|
|
21
21
|
|
|
22
22
|
@property (nonatomic) NSMutableDictionary<NSNumber *, XCUIApplication *> *appsCache;
|
|
23
|
+
@property (nonatomic, nullable) id<FBXCAccessibilityElement> cachedSystemApplication;
|
|
23
24
|
|
|
24
25
|
@end
|
|
25
26
|
|
|
@@ -67,7 +68,14 @@ static id FBAXClient = nil;
|
|
|
67
68
|
|
|
68
69
|
- (id<FBXCAccessibilityElement>)systemApplication
|
|
69
70
|
{
|
|
70
|
-
|
|
71
|
+
@synchronized (self) {
|
|
72
|
+
if (nil == self.cachedSystemApplication) {
|
|
73
|
+
// The system application's identity cannot change without it being killed,
|
|
74
|
+
// which takes WDA down with it, so it is safe to cache it forever.
|
|
75
|
+
self.cachedSystemApplication = [FBAXClient systemApplication];
|
|
76
|
+
}
|
|
77
|
+
return self.cachedSystemApplication;
|
|
78
|
+
}
|
|
71
79
|
}
|
|
72
80
|
|
|
73
81
|
- (NSDictionary *)defaultParameters
|
|
@@ -81,6 +89,12 @@ static id FBAXClient = nil;
|
|
|
81
89
|
[FBAXClient notifyWhenNoAnimationsAreActiveForApplication:application reply:reply];
|
|
82
90
|
}
|
|
83
91
|
|
|
92
|
+
- (void)notifyWhenEventLoopIsIdleForApplication:(XCUIApplication *)application
|
|
93
|
+
reply:(void (^)(id _Nullable result, NSError * _Nullable error))reply
|
|
94
|
+
{
|
|
95
|
+
[FBAXClient notifyWhenEventLoopIsIdleForApplication:application reply:reply];
|
|
96
|
+
}
|
|
97
|
+
|
|
84
98
|
- (NSDictionary *)attributesForElement:(id<FBXCAccessibilityElement>)element
|
|
85
99
|
attributes:(NSArray *)attributes
|
|
86
100
|
error:(NSError**)error;
|
|
@@ -92,28 +106,30 @@ static id FBAXClient = nil;
|
|
|
92
106
|
|
|
93
107
|
- (XCUIApplication *)monitoredApplicationWithProcessIdentifier:(int)pid
|
|
94
108
|
{
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
[
|
|
109
|
+
@synchronized (self) {
|
|
110
|
+
NSMutableSet *terminatedAppIds = [NSMutableSet set];
|
|
111
|
+
for (NSNumber *appPid in self.appsCache) {
|
|
112
|
+
if (![self.appsCache[appPid] running]) {
|
|
113
|
+
[terminatedAppIds addObject:appPid];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
for (NSNumber *appPid in terminatedAppIds) {
|
|
117
|
+
[self.appsCache removeObjectForKey:appPid];
|
|
99
118
|
}
|
|
100
|
-
}
|
|
101
|
-
for (NSNumber *appPid in terminatedAppIds) {
|
|
102
|
-
[self.appsCache removeObjectForKey:appPid];
|
|
103
|
-
}
|
|
104
119
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
120
|
+
XCUIApplication *result = [self.appsCache objectForKey:@(pid)];
|
|
121
|
+
if (nil != result) {
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
109
124
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
125
|
+
XCUIApplication *app = [[FBAXClient applicationProcessTracker]
|
|
126
|
+
monitoredApplicationWithProcessIdentifier:pid];
|
|
127
|
+
if (nil == app) {
|
|
128
|
+
return nil;
|
|
129
|
+
}
|
|
130
|
+
[self.appsCache setObject:app forKey:@(pid)];
|
|
131
|
+
return app;
|
|
114
132
|
}
|
|
115
|
-
[self.appsCache setObject:app forKey:@(pid)];
|
|
116
|
-
return app;
|
|
117
133
|
}
|
|
118
134
|
|
|
119
135
|
@end
|
|
@@ -38,9 +38,10 @@
|
|
|
38
38
|
|
|
39
39
|
- (IBAction)deadlockApp:(id)sender
|
|
40
40
|
{
|
|
41
|
-
dispatch_sync
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
// A self dispatch_sync would trip the OS watchdog and get the process
|
|
42
|
+
// killed outright. Sleeping instead simulates an app that stops answering
|
|
43
|
+
// accessibility requests while staying alive, per #1210.
|
|
44
|
+
[NSThread sleepForTimeInterval:20.0];
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
- (IBAction)didTapButton:(UIButton *)button
|
|
@@ -38,7 +38,6 @@
|
|
|
38
38
|
<state key="normal" title="Deadlock app"/>
|
|
39
39
|
<connections>
|
|
40
40
|
<action selector="deadlockApp:" destination="BYZ-38-t0r" eventType="touchUpInside" id="53X-DJ-KNY"/>
|
|
41
|
-
<action selector="showAlert:" destination="BYZ-38-t0r" eventType="touchUpInside" id="FEN-VX-MMc"/>
|
|
42
41
|
</connections>
|
|
43
42
|
</button>
|
|
44
43
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="M2N-Yn-ytb">
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
|
|
12
12
|
#import "FBConfiguration.h"
|
|
13
13
|
#import "FBRuntimeUtils.h"
|
|
14
|
+
#import "FBTestMacros.h"
|
|
15
|
+
#import "XCUIElement.h"
|
|
16
|
+
#import "XCUIElement+FBIsVisible.h"
|
|
14
17
|
|
|
15
18
|
@interface FBConfigurationTests : FBIntegrationTestCase
|
|
16
19
|
|
|
@@ -35,4 +38,35 @@
|
|
|
35
38
|
XCTAssertEqual(FBConfiguration.sharedInstance.reduceMotionEnabled, defaultReduceMotionEnabled);
|
|
36
39
|
}
|
|
37
40
|
|
|
41
|
+
- (void)testAccessibilityDeadlineAbortsSnapshotRequestForDeadlockedApp
|
|
42
|
+
{
|
|
43
|
+
if (nil != NSProcessInfo.processInfo.environment[@"CI"]) {
|
|
44
|
+
XCTSkip(@"Deliberately freezes the app for several seconds, too slow/flaky for CI");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
NSTimeInterval previousDeadline = FBConfiguration.sharedInstance.accessibilityDeadline;
|
|
48
|
+
// Also bounds any snapshot-based wait -tap itself may perform once the app is stuck.
|
|
49
|
+
FBConfiguration.sharedInstance.accessibilityDeadline = 3.0;
|
|
50
|
+
@try {
|
|
51
|
+
XCUIElement *deadlockButton = self.testedApplication.buttons[@"Deadlock app"];
|
|
52
|
+
FBAssertWaitTillBecomesTrue(deadlockButton.fb_isVisible);
|
|
53
|
+
// Freezes the app's main thread for 20s - see -[ViewController deadlockApp:].
|
|
54
|
+
[deadlockButton tap];
|
|
55
|
+
|
|
56
|
+
NSError *error;
|
|
57
|
+
NSDate *start = [NSDate date];
|
|
58
|
+
id snapshot = [self.testedApplication snapshotWithError:&error];
|
|
59
|
+
NSTimeInterval elapsed = -start.timeIntervalSinceNow;
|
|
60
|
+
|
|
61
|
+
XCTAssertNil(snapshot);
|
|
62
|
+
XCTAssertNotNil(error);
|
|
63
|
+
// Should abort close to accessibilityDeadline (plus XCTest's own internal
|
|
64
|
+
// retries), not hang indefinitely waiting for the frozen app (#1210).
|
|
65
|
+
XCTAssertLessThan(elapsed, 20.0);
|
|
66
|
+
} @finally {
|
|
67
|
+
FBConfiguration.sharedInstance.accessibilityDeadline = previousDeadline;
|
|
68
|
+
[self.testedApplication terminate];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
38
72
|
@end
|