appium-webdriveragent 16.7.2 → 16.8.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/README.md +0 -10
- package/WebDriverAgentLib/Categories/XCAXClient_iOS+FBSnapshotReqParams.m +90 -0
- package/WebDriverAgentLib/Commands/FBCustomCommands.m +4 -1
- package/WebDriverAgentLib/Commands/FBScreenshotCommands.m +2 -2
- package/WebDriverAgentLib/Commands/FBSessionCommands.m +6 -5
- package/WebDriverAgentLib/Info.plist +2 -2
- package/WebDriverAgentLib/Routing/FBHTTPServer.h +25 -1
- package/WebDriverAgentLib/Routing/FBHTTPServer.m +278 -92
- package/WebDriverAgentLib/Routing/FBRoute.h +9 -0
- package/WebDriverAgentLib/Routing/FBRoute.m +9 -0
- package/WebDriverAgentLib/Routing/FBSession.h +13 -0
- package/WebDriverAgentLib/Routing/FBSession.m +153 -22
- package/WebDriverAgentLib/Routing/FBTCPSocket.m +2 -1
- package/WebDriverAgentLib/Routing/FBWebServer.m +26 -1
- package/WebDriverAgentLib/Routing/RouteResponse.m +1 -1
- package/WebDriverAgentLib/Utilities/FBConfiguration.h +11 -0
- package/WebDriverAgentLib/Utilities/FBConfiguration.m +8 -10
- 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 +26 -18
- package/WebDriverAgentLib/Utilities/FBXCTestDaemonsProxy.m +10 -7
- package/WebDriverAgentLib/Utilities/FBXCodeCompatibility.m +36 -15
- 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.8.0](https://github.com/appium/WebDriverAgent/compare/v16.7.3...v16.8.0) (2026-08-24)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* bound accessibility snapshot requests to avoid indefinite hangs ([#1214](https://github.com/appium/WebDriverAgent/issues/1214)) ([cd829eb](https://github.com/appium/WebDriverAgent/commit/cd829eb9725f57efbfb9538a073de059020ddd38))
|
|
6
|
+
|
|
7
|
+
## [16.7.3](https://github.com/appium/WebDriverAgent/compare/v16.7.2...v16.7.3) (2026-08-24)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* let /status, /screenshot, and DELETE /session API methods to bypass the dispatch queue ([#1222](https://github.com/appium/WebDriverAgent/issues/1222)) ([f99b011](https://github.com/appium/WebDriverAgent/commit/f99b0111bba6f5cceabbddf1f9f0144d69d8f168))
|
|
12
|
+
|
|
1
13
|
## [16.7.2](https://github.com/appium/WebDriverAgent/compare/v16.7.1...v16.7.2) (2026-08-24)
|
|
2
14
|
|
|
3
15
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -42,15 +42,5 @@ Then, you find `WebDriverAgentRunner-Runner-sim-<version>.zip` for iOS and `Web
|
|
|
42
42
|
|
|
43
43
|
[`WebDriverAgent` is BSD-licensed](LICENSE).
|
|
44
44
|
|
|
45
|
-
## Third Party Sources
|
|
46
|
-
|
|
47
|
-
WebDriverAgent depends on the following third-party frameworks:
|
|
48
|
-
- [CocoaHTTPServer](https://github.com/robbiehanson/CocoaHTTPServer)
|
|
49
|
-
- [RoutingHTTPServer](https://github.com/mattstevens/RoutingHTTPServer)
|
|
50
|
-
|
|
51
|
-
These projects haven't been maintained in a while. That's why the source code of these
|
|
52
|
-
projects has been integrated directly in the WebDriverAgent source tree.
|
|
53
|
-
|
|
54
|
-
You can find the source files and their licenses in the `WebDriverAgentLib/Vendor` directory.
|
|
55
45
|
|
|
56
46
|
Have fun!
|
|
@@ -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
|
}
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
{
|
|
19
19
|
return
|
|
20
20
|
@[
|
|
21
|
-
[[FBRoute GET:@"/screenshot"].withoutSession respondWithTarget:self action:@selector(handleGetScreenshot:)],
|
|
22
|
-
[[FBRoute GET:@"/screenshot"] respondWithTarget:self action:@selector(handleGetScreenshot:)],
|
|
21
|
+
[[FBRoute GET:@"/screenshot"].withoutSession.standalone respondWithTarget:self action:@selector(handleGetScreenshot:)],
|
|
22
|
+
[[FBRoute GET:@"/screenshot"].standalone respondWithTarget:self action:@selector(handleGetScreenshot:)],
|
|
23
23
|
];
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
[[FBRoute POST:@"/wda/apps/state"] respondWithTarget:self action:@selector(handleSessionAppState:)],
|
|
48
48
|
[[FBRoute GET:@"/wda/apps/list"] respondWithTarget:self action:@selector(handleGetActiveAppsList:)],
|
|
49
49
|
[[FBRoute GET:@""] respondWithTarget:self action:@selector(handleGetActiveSession:)],
|
|
50
|
-
[[FBRoute DELETE:@""] respondWithTarget:self action:@selector(handleDeleteSession:)],
|
|
51
|
-
[[FBRoute GET:@"/status"].withoutSession respondWithTarget:self action:@selector(handleGetStatus:)],
|
|
50
|
+
[[FBRoute DELETE:@""].standalone respondWithTarget:self action:@selector(handleDeleteSession:)],
|
|
51
|
+
[[FBRoute GET:@"/status"].withoutSession.standalone respondWithTarget:self action:@selector(handleGetStatus:)],
|
|
52
52
|
|
|
53
53
|
// Health check might modify simulator state so it should only be called in-between testing sessions
|
|
54
54
|
[[FBRoute GET:@"/wda/healthcheck"].withoutSession respondWithTarget:self action:@selector(handleGetHealthCheck:)],
|
|
@@ -89,9 +89,7 @@
|
|
|
89
89
|
|
|
90
90
|
+ (id<FBResponsePayload>)handleCreateSession:(FBRouteRequest *)request
|
|
91
91
|
{
|
|
92
|
-
|
|
93
|
-
[FBSession.activeSession kill];
|
|
94
|
-
}
|
|
92
|
+
[FBSession killActiveSessionAndWaitForTeardown];
|
|
95
93
|
|
|
96
94
|
NSDictionary<NSString *, id> *capabilities;
|
|
97
95
|
id<FBResponsePayload> errorResponse = [self capabilitiesFromCreateSessionRequest:request
|
|
@@ -311,6 +309,9 @@
|
|
|
311
309
|
if (nil != capabilities[FB_SETTING_WAIT_FOR_IDLE_TIMEOUT]) {
|
|
312
310
|
FBConfiguration.sharedInstance.waitForIdleTimeout = [capabilities[FB_SETTING_WAIT_FOR_IDLE_TIMEOUT] doubleValue];
|
|
313
311
|
}
|
|
312
|
+
if (nil != capabilities[FB_SETTING_ACCESSIBILITY_DEADLINE]) {
|
|
313
|
+
FBConfiguration.sharedInstance.accessibilityDeadline = [capabilities[FB_SETTING_ACCESSIBILITY_DEADLINE] doubleValue];
|
|
314
|
+
}
|
|
314
315
|
if (nil == capabilities[FB_CAP_FORCE_SIMULATOR_SOFTWARE_KEYBOARD_PRESENCE] ||
|
|
315
316
|
[capabilities[FB_CAP_FORCE_SIMULATOR_SOFTWARE_KEYBOARD_PRESENCE] boolValue]) {
|
|
316
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.8.0</string>
|
|
19
19
|
<key>CFBundleSignature</key>
|
|
20
20
|
<string>????</string>
|
|
21
21
|
<key>CFBundleVersion</key>
|
|
22
|
-
<string>16.
|
|
22
|
+
<string>16.8.0</string>
|
|
23
23
|
<key>NSPrincipalClass</key>
|
|
24
24
|
<string/>
|
|
25
25
|
</dict>
|
|
@@ -47,17 +47,41 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
Registers a route handler for the given HTTP method and path pattern (":param" segments are
|
|
50
|
-
captured into the request's `params`).
|
|
50
|
+
captured into the request's `params`). Equivalent to -handleMethod:withPath:standalone:block:
|
|
51
|
+
with standalone:NO.
|
|
51
52
|
*/
|
|
52
53
|
- (void)handleMethod:(NSString *)method
|
|
53
54
|
withPath:(NSString *)path
|
|
54
55
|
block:(void (^)(RouteRequest *request, RouteResponse *response))block;
|
|
55
56
|
|
|
57
|
+
/**
|
|
58
|
+
Registers a route handler that, when `standalone` is YES, bypasses -routeQueue entirely so a
|
|
59
|
+
handler stuck on that queue can never block it. Concurrent requests to the same method+path are
|
|
60
|
+
coalesced into a single in-flight execution, whose response is delivered to all of them; anything
|
|
61
|
+
else runs on its own queue, so distinct standalone endpoints always execute in parallel with each
|
|
62
|
+
other and with whatever is stuck on -routeQueue.
|
|
63
|
+
*/
|
|
64
|
+
- (void)handleMethod:(NSString *)method
|
|
65
|
+
withPath:(NSString *)path
|
|
66
|
+
standalone:(BOOL)standalone
|
|
67
|
+
block:(void (^)(RouteRequest *request, RouteResponse *response))block;
|
|
68
|
+
|
|
56
69
|
/**
|
|
57
70
|
Convenience for -handleMethod:@"GET" withPath:path block:block.
|
|
58
71
|
*/
|
|
59
72
|
- (void)get:(NSString *)path withBlock:(void (^)(RouteRequest *request, RouteResponse *response))block;
|
|
60
73
|
|
|
74
|
+
/**
|
|
75
|
+
Immediately sends `response` to every non-standalone request currently pending for the given
|
|
76
|
+
"sessionID" path param - whether still queued on -routeQueue or already executing - instead of
|
|
77
|
+
leaving their HTTP clients waiting on a session that no longer exists. A request that has already
|
|
78
|
+
started executing keeps running to completion in the background regardless (GCD gives no way to
|
|
79
|
+
abort a block once it starts), but its eventual result is discarded rather than ever reaching a
|
|
80
|
+
client. `response` is written as-is to every pending client, so the caller is expected to supply
|
|
81
|
+
a fully-populated, protocol-correct error response (e.g. a W3C-shaped JSON body).
|
|
82
|
+
*/
|
|
83
|
+
- (void)abandonPendingRequestsForSessionID:(NSString *)sessionID withResponse:(RouteResponse *)response;
|
|
84
|
+
|
|
61
85
|
/**
|
|
62
86
|
Starts listening on `port`.
|
|
63
87
|
*/
|