appium-webdriveragent 16.11.1 → 16.11.3

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.3](https://github.com/appium/WebDriverAgent/compare/v16.11.2...v16.11.3) (2026-08-30)
2
+
3
+ ### Miscellaneous Chores
4
+
5
+ * bump base-driver & support ([#1240](https://github.com/appium/WebDriverAgent/issues/1240)) ([b8ee694](https://github.com/appium/WebDriverAgent/commit/b8ee694adacb8b7a835becdbc36bdee1c5a6f684))
6
+
7
+ ## [16.11.2](https://github.com/appium/WebDriverAgent/compare/v16.11.1...v16.11.2) (2026-08-30)
8
+
9
+ ### Bug Fixes
10
+
11
+ * reject requests admitted after their session was abandoned ([#1229](https://github.com/appium/WebDriverAgent/issues/1229)) ([d6b4862](https://github.com/appium/WebDriverAgent/commit/d6b4862ea113083959a80e87c488fd555cbc8061))
12
+
1
13
  ## [16.11.1](https://github.com/appium/WebDriverAgent/compare/v16.11.0...v16.11.1) (2026-08-29)
2
14
 
3
15
  ### Bug Fixes
@@ -1191,6 +1191,7 @@
1191
1191
  EE8DDD7F20C5733C004D4925 /* XCUIElement+FBForceTouch.h in Headers */ = {isa = PBXBuildFile; fileRef = EE8DDD7D20C5733C004D4925 /* XCUIElement+FBForceTouch.h */; settings = {ATTRIBUTES = (Public, ); }; };
1192
1192
  EE9AB8011CAEE048008C271F /* UITestingUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB7FD1CAEE048008C271F /* UITestingUITests.m */; };
1193
1193
  EE9B76591CF7987800275851 /* FBRouteTests.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9B76571CF7987300275851 /* FBRouteTests.m */; };
1194
+ C312172CE7EE9B10B7A3034B /* FBHTTPServerSessionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FB741D78562232A78902B374 /* FBHTTPServerSessionTests.m */; };
1194
1195
  DF82F4A8758B79DD91FA20CC /* FBHTTPServerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 76B7399DDF52C85A21433C1D /* FBHTTPServerTests.m */; };
1195
1196
  EE9B768E1CF7997600275851 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9B76831CF7997600275851 /* AppDelegate.m */; };
1196
1197
  EE9B768F1CF7997600275851 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9B76851CF7997600275851 /* ViewController.m */; };
@@ -1908,6 +1909,7 @@
1908
1909
  EE9B75D41CF7956C00275851 /* IntegrationApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IntegrationApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
1909
1910
  EE9B75EC1CF7956C00275851 /* IntegrationTests_1.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = IntegrationTests_1.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
1910
1911
  EE9B76571CF7987300275851 /* FBRouteTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBRouteTests.m; sourceTree = "<group>"; };
1912
+ FB741D78562232A78902B374 /* FBHTTPServerSessionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBHTTPServerSessionTests.m; sourceTree = "<group>"; };
1911
1913
  76B7399DDF52C85A21433C1D /* FBHTTPServerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBHTTPServerTests.m; sourceTree = "<group>"; };
1912
1914
  EE9B76581CF7987300275851 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
1913
1915
  EE9B76821CF7997600275851 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
@@ -2679,6 +2681,7 @@
2679
2681
  713352FC26CEF31D00523CBC /* FBLRUCacheTests.m */,
2680
2682
  EE18883C1DA663EB00307AA8 /* FBMathUtilsTests.m */,
2681
2683
  718F49C7230844330045FE8B /* FBProtocolHelpersTests.m */,
2684
+ FB741D78562232A78902B374 /* FBHTTPServerSessionTests.m */,
2682
2685
  76B7399DDF52C85A21433C1D /* FBHTTPServerTests.m */,
2683
2686
  EE9B76571CF7987300275851 /* FBRouteTests.m */,
2684
2687
  EE3F8CFD1D08AA17006F02CE /* FBRunLoopSpinnerTests.m */,
@@ -4816,6 +4819,7 @@
4816
4819
  719FF5B91DAD21F5008E0099 /* FBElementUtilitiesTests.m in Sources */,
4817
4820
  716E0BD11E917F260087A825 /* FBXMLSafeStringTests.m in Sources */,
4818
4821
  ADEF63AF1D09DEBE0070A7E3 /* FBRuntimeUtilsTests.m in Sources */,
4822
+ C312172CE7EE9B10B7A3034B /* FBHTTPServerSessionTests.m in Sources */,
4819
4823
  DF82F4A8758B79DD91FA20CC /* FBHTTPServerTests.m in Sources */,
4820
4824
  EE9B76591CF7987800275851 /* FBRouteTests.m in Sources */,
4821
4825
  7139145C1DF01A12005896C2 /* NSExpressionFBFormatTests.m in Sources */,
@@ -15,11 +15,11 @@
15
15
  <key>CFBundlePackageType</key>
16
16
  <string>FMWK</string>
17
17
  <key>CFBundleShortVersionString</key>
18
- <string>16.11.1</string>
18
+ <string>16.11.3</string>
19
19
  <key>CFBundleSignature</key>
20
20
  <string>????</string>
21
21
  <key>CFBundleVersion</key>
22
- <string>16.11.1</string>
22
+ <string>16.11.3</string>
23
23
  <key>NSPrincipalClass</key>
24
24
  <string/>
25
25
  </dict>
@@ -131,6 +131,10 @@ static BOOL FBParseContentLength(NSString *value, NSUInteger *outLength)
131
131
  // standalone or not (except DELETE /session itself - see -dispatchMethod:). See
132
132
  // -abandonPendingRequestsForSessionID:. Guarded by @synchronized(self.pendingSessionRequests).
133
133
  @property (nonatomic, strong) NSMutableDictionary<NSString *, NSMutableSet<FBPendingRequest *> *> *pendingSessionRequests;
134
+ // Already-abandoned sessions mapped to the response they were abandoned with, so a request parsed
135
+ // after that point is answered at once instead of queueing for a session that is gone. Kept for
136
+ // the server's lifetime; ids are UUIDs. Guarded by @synchronized(self.pendingSessionRequests).
137
+ @property (nonatomic, strong) NSMutableDictionary<NSString *, RouteResponse *> *abandonedSessionResponses;
134
138
  // When each connection started waiting for its current request. The reaper closes connections
135
139
  // whose entry outlives FBIncompleteRequestTimeout; idle keep-alive connections have no entry and
136
140
  // are exempt. Guarded by @synchronized(self.connectionBuffers).
@@ -159,6 +163,7 @@ static const int64_t FBStaleConnectionSweepIntervalSec = 10;
159
163
  _connectionsAwaitingResponse = [NSMutableSet set];
160
164
  _standaloneWaiters = [NSMutableDictionary dictionary];
161
165
  _pendingSessionRequests = [NSMutableDictionary dictionary];
166
+ _abandonedSessionResponses = [NSMutableDictionary dictionary];
162
167
  _incompleteRequestStarts = [NSMapTable mapTableWithKeyOptions:(NSPointerFunctionsOptions)(NSMapTableObjectPointerPersonality | NSMapTableStrongMemory)
163
168
  valueOptions:(NSPointerFunctionsOptions)NSMapTableStrongMemory];
164
169
  }
@@ -678,7 +683,11 @@ static const int64_t FBStaleConnectionSweepIntervalSec = 10;
678
683
  FBPendingRequest *pendingRequest = nil;
679
684
  if (nil != sessionID) {
680
685
  pendingRequest = [[FBPendingRequest alloc] initWithClient:client];
681
- [self trackPendingRequest:pendingRequest forSessionID:sessionID];
686
+ RouteResponse *abandonedResponse = [self trackPendingRequest:pendingRequest forSessionID:sessionID];
687
+ if (nil != abandonedResponse) {
688
+ [self writeResponse:abandonedResponse toClient:client];
689
+ return;
690
+ }
682
691
  }
683
692
 
684
693
  void (^invoke)(void) = ^{
@@ -709,15 +718,22 @@ static const int64_t FBStaleConnectionSweepIntervalSec = 10;
709
718
 
710
719
  #pragma mark - Session-scoped request cancellation
711
720
 
712
- - (void)trackPendingRequest:(FBPendingRequest *)pendingRequest forSessionID:(NSString *)sessionID
721
+ // Returns nil once `pendingRequest` is tracked, or the response an already-abandoned session was
722
+ // abandoned with, which the caller must deliver instead of dispatching.
723
+ - (nullable RouteResponse *)trackPendingRequest:(FBPendingRequest *)pendingRequest forSessionID:(NSString *)sessionID
713
724
  {
714
725
  @synchronized (self.pendingSessionRequests) {
726
+ RouteResponse *abandonedResponse = self.abandonedSessionResponses[sessionID];
727
+ if (nil != abandonedResponse) {
728
+ return abandonedResponse;
729
+ }
715
730
  NSMutableSet<FBPendingRequest *> *pendingRequests = self.pendingSessionRequests[sessionID];
716
731
  if (nil == pendingRequests) {
717
732
  pendingRequests = [NSMutableSet set];
718
733
  self.pendingSessionRequests[sessionID] = pendingRequests;
719
734
  }
720
735
  [pendingRequests addObject:pendingRequest];
736
+ return nil;
721
737
  }
722
738
  }
723
739
 
@@ -744,6 +760,8 @@ static const int64_t FBStaleConnectionSweepIntervalSec = 10;
744
760
  @synchronized (self.pendingSessionRequests) {
745
761
  pendingRequests = [self.pendingSessionRequests[sessionID] copy];
746
762
  [self.pendingSessionRequests removeObjectForKey:sessionID];
763
+ // Recorded before the lock is dropped, so requests admitted from here on are rejected.
764
+ self.abandonedSessionResponses[sessionID] = response;
747
765
  }
748
766
  for (FBPendingRequest *pendingRequest in pendingRequests) {
749
767
  [self writeResponse:response toClient:pendingRequest.client];
@@ -764,7 +782,11 @@ static const int64_t FBStaleConnectionSweepIntervalSec = 10;
764
782
  NSString *key = [NSString stringWithFormat:@"%@ %@", method, pathAndQuery];
765
783
  FBPendingRequest *waiter = [[FBPendingRequest alloc] initWithClient:client];
766
784
  if (nil != sessionID) {
767
- [self trackPendingRequest:waiter forSessionID:sessionID];
785
+ RouteResponse *abandonedResponse = [self trackPendingRequest:waiter forSessionID:sessionID];
786
+ if (nil != abandonedResponse) {
787
+ [self writeResponse:abandonedResponse toClient:client];
788
+ return;
789
+ }
768
790
  }
769
791
 
770
792
  BOOL isInFlight = NO;
@@ -0,0 +1,133 @@
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 <arpa/inet.h>
12
+ #import <stdatomic.h>
13
+ #import <sys/socket.h>
14
+
15
+ #import "FBHTTPServer.h"
16
+
17
+ static atomic_int gSessionProbeHits;
18
+
19
+ @interface FBHTTPServerSessionTests : XCTestCase
20
+ @property (nonatomic, strong) FBHTTPServer *server;
21
+ @property (nonatomic, assign) uint16_t port;
22
+ @end
23
+
24
+ @implementation FBHTTPServerSessionTests
25
+
26
+ - (void)setUp
27
+ {
28
+ [super setUp];
29
+ atomic_store(&gSessionProbeHits, 0);
30
+ self.server = [FBHTTPServer new];
31
+ [self.server get:@"/session/:sessionID/probe" withBlock:^(RouteRequest *request, RouteResponse *response) {
32
+ atomic_fetch_add(&gSessionProbeHits, 1);
33
+ [response respondWithString:@"session-probe-ok"];
34
+ }];
35
+ self.server.port = 0;
36
+ NSError *error;
37
+ XCTAssertTrue([self.server start:&error], @"%@", error);
38
+ self.port = [[self.server valueForKeyPath:@"socket.port"] unsignedShortValue];
39
+ }
40
+
41
+ - (void)tearDown
42
+ {
43
+ [self.server stop:NO];
44
+ self.server = nil;
45
+ [super tearDown];
46
+ }
47
+
48
+ - (NSString *)responseForRawPayload:(NSData *)payload timeout:(NSTimeInterval)timeout
49
+ {
50
+ int fd = socket(AF_INET, SOCK_STREAM, 0);
51
+ if (fd < 0) {
52
+ return nil;
53
+ }
54
+ int noSigpipe = 1;
55
+ setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &noSigpipe, sizeof(noSigpipe));
56
+ struct timeval tv = { .tv_sec = (long)timeout, .tv_usec = 0 };
57
+ setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
58
+ struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(self.port) };
59
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
60
+ if (0 != connect(fd, (struct sockaddr *)&addr, sizeof(addr))) {
61
+ close(fd);
62
+ return nil;
63
+ }
64
+ send(fd, payload.bytes, payload.length, 0);
65
+ NSMutableData *received = [NSMutableData data];
66
+ char chunk[4096];
67
+ NSDate *deadline = [NSDate dateWithTimeIntervalSinceNow:timeout];
68
+ while (deadline.timeIntervalSinceNow > 0) {
69
+ ssize_t n = recv(fd, chunk, sizeof(chunk), 0);
70
+ if (n > 0) {
71
+ [received appendBytes:chunk length:(NSUInteger)n];
72
+ // The server keeps the connection open after a success, so don't wait the full timeout
73
+ // for an EOF that never comes.
74
+ struct timeval drainTv = { .tv_sec = 0, .tv_usec = 200000 };
75
+ setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &drainTv, sizeof(drainTv));
76
+ } else {
77
+ break;
78
+ }
79
+ }
80
+ close(fd);
81
+ return [[NSString alloc] initWithData:received encoding:NSUTF8StringEncoding] ?: @"";
82
+ }
83
+
84
+ - (void)testRequestForAlreadyAbandonedSessionIsRejectedImmediately
85
+ {
86
+ // A request parsed *after* DELETE /session tore the session down never receives an abandonment
87
+ // notification of its own, so before this was tracked it queued on the route queue -
88
+ // potentially forever, if that queue is wedged behind the very request that made the client
89
+ // delete the session in the first place.
90
+ RouteResponse *abandonedResponse = [RouteResponse new];
91
+ [abandonedResponse respondWithString:@"session-was-deleted"];
92
+ [self.server abandonPendingRequestsForSessionID:@"dead-session" withResponse:abandonedResponse];
93
+
94
+ NSString *response = [self responseForRawPayload:(NSData * _Nonnull)[@"GET /session/dead-session/probe HTTP/1.1\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]
95
+ timeout:5.0];
96
+ XCTAssertTrue([response containsString:@"session-was-deleted"], @"%@", response);
97
+ XCTAssertEqual(atomic_load(&gSessionProbeHits), 0, @"the route must not run for a deleted session");
98
+ }
99
+
100
+ - (void)testAbandonedSessionIsRememberedAfterManyLaterAbandonments
101
+ {
102
+ // Abandoned ids are kept for the server's lifetime; evicting them would let a stale request
103
+ // queue on a possibly wedged route queue again, which is the hang this rejection prevents.
104
+ RouteResponse *abandonedResponse = [RouteResponse new];
105
+ [abandonedResponse respondWithString:@"session-was-deleted"];
106
+ [self.server abandonPendingRequestsForSessionID:@"dead-session" withResponse:abandonedResponse];
107
+ for (NSUInteger index = 0; index < 64; ++index) {
108
+ RouteResponse *otherResponse = [RouteResponse new];
109
+ [otherResponse respondWithString:@"other-session-was-deleted"];
110
+ [self.server abandonPendingRequestsForSessionID:[NSString stringWithFormat:@"other-session-%lu", (unsigned long)index]
111
+ withResponse:otherResponse];
112
+ }
113
+
114
+ NSString *response = [self responseForRawPayload:(NSData * _Nonnull)[@"GET /session/dead-session/probe HTTP/1.1\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]
115
+ timeout:5.0];
116
+ XCTAssertTrue([response containsString:@"session-was-deleted"], @"%@", response);
117
+ XCTAssertEqual(atomic_load(&gSessionProbeHits), 0, @"the route must not run for a deleted session");
118
+ }
119
+
120
+ - (void)testRequestForLiveSessionIsStillServed
121
+ {
122
+ // The rejection above must be scoped to the abandoned identifier only.
123
+ RouteResponse *abandonedResponse = [RouteResponse new];
124
+ [abandonedResponse respondWithString:@"session-was-deleted"];
125
+ [self.server abandonPendingRequestsForSessionID:@"dead-session" withResponse:abandonedResponse];
126
+
127
+ NSString *response = [self responseForRawPayload:(NSData * _Nonnull)[@"GET /session/live-session/probe HTTP/1.1\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]
128
+ timeout:5.0];
129
+ XCTAssertTrue([response containsString:@"session-probe-ok"], @"%@", response);
130
+ XCTAssertEqual(atomic_load(&gSessionProbeHits), 1);
131
+ }
132
+
133
+ @end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appium-webdriveragent",
3
- "version": "16.11.1",
3
+ "version": "16.11.3",
4
4
  "description": "Package bundling WebDriverAgent",
5
5
  "keywords": [
6
6
  "Appium",
@@ -64,9 +64,9 @@
64
64
  "sync-wda-version": "node ./Scripts/update-wda-version.mjs --package-version=${npm_package_version} && git add WebDriverAgentLib/Info.plist"
65
65
  },
66
66
  "dependencies": {
67
- "@appium/base-driver": "^10.3.0",
67
+ "@appium/base-driver": "^10.8.0",
68
68
  "@appium/strongbox": "^2.0.0",
69
- "@appium/support": "^7.2.1",
69
+ "@appium/support": "^7.2.6",
70
70
  "appium-ios-simulator": "^9.0.0",
71
71
  "async-lock": "^1.0.0",
72
72
  "asyncbox": "^6.1.0",