appium-webdriveragent 16.5.0 → 16.6.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 CHANGED
@@ -1,3 +1,15 @@
1
+ ## [16.6.0](https://github.com/appium/WebDriverAgent/compare/v16.5.1...v16.6.0) (2026-08-22)
2
+
3
+ ### Features
4
+
5
+ * Add MJPEG screenshot streaming support to watchOS ([#1220](https://github.com/appium/WebDriverAgent/issues/1220)) ([c6dcf03](https://github.com/appium/WebDriverAgent/commit/c6dcf03096dd5faab83398f16fb772724fcc5220))
6
+
7
+ ## [16.5.1](https://github.com/appium/WebDriverAgent/compare/v16.5.0...v16.5.1) (2026-08-20)
8
+
9
+ ### Bug Fixes
10
+
11
+ * add watchOS assets to GitHub release artifacts ([#1219](https://github.com/appium/WebDriverAgent/issues/1219)) ([ce5a9e8](https://github.com/appium/WebDriverAgent/commit/ce5a9e8af36747a72669c34b38bd39e495b57f5b))
12
+
1
13
  ## [16.5.0](https://github.com/appium/WebDriverAgent/compare/v16.4.0...v16.5.0) (2026-08-20)
2
14
 
3
15
  ### Features
@@ -310,6 +310,7 @@
310
310
  641EE5F62240C5CA00173FCB /* FBRouteRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB7881CAEDF0C008C271F /* FBRouteRequest.m */; };
311
311
  641EE5F72240C5CA00173FCB /* FBResponseJSONPayload.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB7811CAEDF0C008C271F /* FBResponseJSONPayload.m */; };
312
312
  641EE5F92240C5CA00173FCB /* FBMjpegServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7155D702211DCEF400166C20 /* FBMjpegServer.m */; };
313
+ AA11BB22CC33DD44EE55FF02 /* FBMjpegServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7155D702211DCEF400166C20 /* FBMjpegServer.m */; };
313
314
  641EE5FA2240C5CA00173FCB /* XCUIDevice+FBHealthCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = EEDFE1201D9C06F800E6FFE5 /* XCUIDevice+FBHealthCheck.m */; };
314
315
  641EE5FD2240C5CA00173FCB /* FBBaseActionsSynthesizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7140974D1FAE20EE008FB2C5 /* FBBaseActionsSynthesizer.m */; };
315
316
  641EE5FE2240C5CA00173FCB /* XCUIElement+FBWebDriverAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = EEE376481D59FAE900ED88DD /* XCUIElement+FBWebDriverAttributes.m */; };
@@ -1320,6 +1321,7 @@
1320
1321
  FBDF04E5916B91FCEC0190CB /* Route.h in Headers */ = {isa = PBXBuildFile; fileRef = E444DCA324913C210060D7EB /* Route.h */; };
1321
1322
  FBF064E4D96CFCD08E1F4EF7 /* XCUIDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = EE35ACFD1E3B77D600A02D78 /* XCUIDevice.h */; };
1322
1323
  FBFEC05ED2C01D1EAE34BE9A /* WDAScreenshotAndSourceIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75F677B5B7737E7E1F321C20 /* WDAScreenshotAndSourceIntegrationTests.swift */; };
1324
+ AA11BB22CC33DD44EE55FF04 /* WDAMjpegStreamingIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA11BB22CC33DD44EE55FF03 /* WDAMjpegStreamingIntegrationTests.swift */; };
1323
1325
  FC607DB5132FEF425237267B /* XCTMessagingRole_MemoryTesting-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 13D863EAE7F6F8B7E42D99B9 /* XCTMessagingRole_MemoryTesting-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
1324
1326
  FC73EE781D24A598B44C3337 /* XCUIElement+FBScrolling.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB74A1CAEDF0C008C271F /* XCUIElement+FBScrolling.m */; };
1325
1327
  FD89236D119E129E8CEBDBCD /* XCTMessagingRole_HIDEventRecording-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 94D7F0C1E30FBDB5D2583908 /* XCTMessagingRole_HIDEventRecording-Protocol.h */; };
@@ -1739,6 +1741,7 @@
1739
1741
  75438FC693C39052C82C27DB /* XCUIApplicationRegistry.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = XCUIApplicationRegistry.h; sourceTree = "<group>"; };
1740
1742
  758FC0D745C185A2C28BEDA1 /* IntegrationApp_watchOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IntegrationApp_watchOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
1741
1743
  75F677B5B7737E7E1F321C20 /* WDAScreenshotAndSourceIntegrationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WDAScreenshotAndSourceIntegrationTests.swift; sourceTree = "<group>"; };
1744
+ AA11BB22CC33DD44EE55FF03 /* WDAMjpegStreamingIntegrationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WDAMjpegStreamingIntegrationTests.swift; sourceTree = "<group>"; };
1742
1745
  7AA21CEBA6E92AAC73FB6A48 /* XCTAggregateSuiteRunStatistics.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = XCTAggregateSuiteRunStatistics.h; sourceTree = "<group>"; };
1743
1746
  7CBAA574F7786985E01D0B6F /* XCTHarnessEventReporting-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCTHarnessEventReporting-Protocol.h"; sourceTree = "<group>"; };
1744
1747
  7E079E00FE148476F94BC42F /* XCTTestIdentifierSet.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = XCTTestIdentifierSet.h; sourceTree = "<group>"; };
@@ -2503,6 +2506,7 @@
2503
2506
  C878996F07A9B26E66FC4EAC /* WDAClickIntegrationTests.swift */,
2504
2507
  912F32C353FE7C3E7C841405 /* WDATypingIntegrationTests.swift */,
2505
2508
  75F677B5B7737E7E1F321C20 /* WDAScreenshotAndSourceIntegrationTests.swift */,
2509
+ AA11BB22CC33DD44EE55FF03 /* WDAMjpegStreamingIntegrationTests.swift */,
2506
2510
  EB1B9A793C9EFB7853C1AA13 /* WDAAppLifecycleIntegrationTests.swift */,
2507
2511
  646D714BC17FE9258CFBDF55 /* WDADeviceIntegrationTests.swift */,
2508
2512
  28F75AEC6442F32A3C4394AD /* WDAAlertIntegrationTests.swift */,
@@ -4741,6 +4745,7 @@
4741
4745
  6AF8892AEF5C6BF01896B6DA /* WDAAppLifecycleIntegrationTests.swift in Sources */,
4742
4746
  6EDF83DA0A6C7A7E67C30AF6 /* WDADeviceIntegrationTests.swift in Sources */,
4743
4747
  FBFEC05ED2C01D1EAE34BE9A /* WDAScreenshotAndSourceIntegrationTests.swift in Sources */,
4748
+ AA11BB22CC33DD44EE55FF04 /* WDAMjpegStreamingIntegrationTests.swift in Sources */,
4744
4749
  D22FED0B3856919DA4BCFA67 /* WDATypingIntegrationTests.swift in Sources */,
4745
4750
  68EB0B8D53270CB68C53D028 /* WDAAlertIntegrationTests.swift in Sources */,
4746
4751
  );
@@ -4862,6 +4867,7 @@
4862
4867
  C2D22426DB9FCE6AD84FA16A /* RouteRequest.m in Sources */,
4863
4868
  3FEF512914A962C5E30579FC /* RouteResponse.m in Sources */,
4864
4869
  91FF05D51401D1F1A88FB0B0 /* FBWatchHTTPServer.m in Sources */,
4870
+ AA11BB22CC33DD44EE55FF02 /* FBMjpegServer.m in Sources */,
4865
4871
  );
4866
4872
  runOnlyForDeploymentPostprocessing = 0;
4867
4873
  };
@@ -15,11 +15,11 @@
15
15
  <key>CFBundlePackageType</key>
16
16
  <string>FMWK</string>
17
17
  <key>CFBundleShortVersionString</key>
18
- <string>16.5.0</string>
18
+ <string>16.6.0</string>
19
19
  <key>CFBundleSignature</key>
20
20
  <string>????</string>
21
21
  <key>CFBundleVersion</key>
22
- <string>16.5.0</string>
22
+ <string>16.6.0</string>
23
23
  <key>NSPrincipalClass</key>
24
24
  <string/>
25
25
  </dict>
@@ -92,6 +92,13 @@ NS_ASSUME_NONNULL_BEGIN
92
92
  @property (nullable, nonatomic, assign) id<FBTCPSocketDelegate> delegate;
93
93
  #endif
94
94
 
95
+ /**
96
+ The port this socket is listening on. Equal to the port passed to -initWithPort: unless that was
97
+ 0 ("let the system assign a port"), in which case this reflects the actually assigned port once
98
+ -startWithError: has returned successfully.
99
+ */
100
+ @property (nonatomic, readonly) uint16_t port;
101
+
95
102
  /**
96
103
  Creates TCP socket isntance which is going to be started on the specified port
97
104
 
@@ -14,7 +14,6 @@
14
14
  @property (readonly, nonatomic) dispatch_queue_t socketQueue;
15
15
  @property (nullable, nonatomic) nw_listener_t listener;
16
16
  @property (readonly, nonatomic) NSMutableArray<nw_connection_t> *connectedClients;
17
- @property (readonly, nonatomic) uint16_t port;
18
17
  @end
19
18
 
20
19
 
@@ -61,6 +60,10 @@
61
60
  // if/else, not switch: -Wswitch-enum, -Wswitch-default, and -Wcovered-switch-default can't
62
61
  // all be satisfied by one switch statement at once.
63
62
  if (nw_listener_state_ready == state) {
63
+ __strong typeof(weakSelf) strongSelf = weakSelf;
64
+ if (strongSelf) {
65
+ strongSelf->_port = nw_listener_get_port(listener);
66
+ }
64
67
  dispatch_semaphore_signal(startupSemaphore);
65
68
  } else if (nw_listener_state_failed == state || nw_listener_state_cancelled == state) {
66
69
  // NSLocalizedDescriptionKey must be a string, not the underlying NSError itself, or
@@ -210,7 +213,6 @@
210
213
  @property (readonly, nonatomic) dispatch_queue_t socketQueue;
211
214
  @property (readonly, nonatomic) GCDAsyncSocket *listeningSocket;
212
215
  @property (readonly, nonatomic) NSMutableArray *connectedClients;
213
- @property (readonly, nonatomic) uint16_t port;
214
216
  @end
215
217
 
216
218
 
@@ -239,6 +241,7 @@
239
241
  return NO;
240
242
  }
241
243
 
244
+ _port = self.listeningSocket.localPort;
242
245
  return YES;
243
246
  }
244
247
 
@@ -13,9 +13,9 @@
13
13
  #else
14
14
  #import "RoutingConnection.h"
15
15
  #import "RoutingHTTPServer.h"
16
+ #endif
16
17
  #import "FBMjpegServer.h"
17
18
  #import "FBTCPSocket.h"
18
- #endif
19
19
 
20
20
  #import "FBCommandHandler.h"
21
21
  #import "FBErrorBuilder.h"
@@ -59,9 +59,9 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
59
59
  @property (nonatomic, strong) FBWatchHTTPServer *server;
60
60
  #else
61
61
  @property (nonatomic, strong) RoutingHTTPServer *server;
62
+ #endif
62
63
  @property (nonatomic, nullable) FBTCPSocket *screenshotsBroadcaster;
63
64
  @property (nonatomic, nullable, strong) FBMjpegServer *mjpegServer;
64
- #endif
65
65
  @property (atomic, assign) BOOL keepAlive;
66
66
  @end
67
67
 
@@ -69,9 +69,7 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
69
69
 
70
70
  - (void)dealloc
71
71
  {
72
- #if !TARGET_OS_WATCH
73
72
  [self stopScreenshotsBroadcaster];
74
- #endif
75
73
  }
76
74
 
77
75
  + (NSArray<Class<FBCommandHandler>> *)collectCommandHandlerClasses
@@ -96,9 +94,7 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
96
94
  if (![self startHTTPServer]) {
97
95
  return;
98
96
  }
99
- #if !TARGET_OS_WATCH
100
97
  [self initScreenshotsBroadcaster];
101
- #endif
102
98
 
103
99
  self.keepAlive = YES;
104
100
  NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
@@ -163,13 +159,15 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
163
159
  return YES;
164
160
  }
165
161
 
166
- #if !TARGET_OS_WATCH
167
162
  - (void)initScreenshotsBroadcaster
168
163
  {
169
164
  [self readMjpegSettingsFromEnv];
170
165
  self.mjpegServer = [[FBMjpegServer alloc] init];
171
166
  self.screenshotsBroadcaster = [[FBTCPSocket alloc]
172
167
  initWithPort:(uint16_t)FBConfiguration.sharedInstance.mjpegServerPort];
168
+ #if TARGET_OS_WATCH
169
+ self.mjpegServer.socket = self.screenshotsBroadcaster;
170
+ #endif
173
171
  self.screenshotsBroadcaster.delegate = self.mjpegServer;
174
172
  NSError *error;
175
173
  if (![self.screenshotsBroadcaster startWithError:&error]) {
@@ -209,14 +207,11 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
209
207
  FBConfiguration.sharedInstance.mjpegServerScreenshotQuality = [screenshotQuality integerValue];
210
208
  }
211
209
  }
212
- #endif
213
210
 
214
211
  - (void)stopServing
215
212
  {
216
213
  [FBSession.activeSession kill];
217
- #if !TARGET_OS_WATCH
218
214
  [self stopScreenshotsBroadcaster];
219
- #endif
220
215
  if (self.server.isRunning) {
221
216
  [self.server stop:NO];
222
217
  }
@@ -19,6 +19,15 @@ NS_ASSUME_NONNULL_BEGIN
19
19
  */
20
20
  - (instancetype)init;
21
21
 
22
+ #if TARGET_OS_WATCH
23
+ /**
24
+ The socket that owns this instance as its delegate. watchOS clients are bare nw_connection_t
25
+ values with no write method of their own (unlike GCDAsyncSocket on other platforms), so frame
26
+ writes are routed through -[FBTCPSocket writeData:toClient:]. Must be set before streaming starts.
27
+ */
28
+ @property (nonatomic, weak, nullable) FBTCPSocket *socket;
29
+ #endif
30
+
22
31
  /**
23
32
  Stops screenshot broadcasting and prevents future scheduling.
24
33
  */
@@ -11,7 +11,12 @@
11
11
  #import <mach/mach_time.h>
12
12
  @import UniformTypeIdentifiers;
13
13
 
14
+ #if TARGET_OS_WATCH
15
+ // Textual import, not `@import Network;` - see the comment in FBTCPSocket.h.
16
+ #import <Network/Network.h>
17
+ #else
14
18
  #import "GCDAsyncSocket.h"
19
+ #endif
15
20
  #import "FBConfiguration.h"
16
21
  #import "FBLogger.h"
17
22
  #import "FBScreenshot.h"
@@ -36,7 +41,11 @@ static NSUInteger FBNormalizedMjpegFramerate(NSUInteger framerate)
36
41
  @interface FBMjpegServer()
37
42
 
38
43
  @property (nonatomic, readonly) dispatch_queue_t backgroundQueue;
44
+ #if TARGET_OS_WATCH
45
+ @property (nonatomic, readonly) NSMutableArray<nw_connection_t> *listeningClients;
46
+ #else
39
47
  @property (nonatomic, readonly) NSMutableArray<GCDAsyncSocket *> *listeningClients;
48
+ #endif
40
49
  @property (nonatomic, readonly) FBImageProcessor *imageProcessor;
41
50
  @property (nonatomic, readonly) long long mainScreenID;
42
51
  @property (nonatomic, assign) NSUInteger consecutiveScreenshotFailures;
@@ -148,10 +157,16 @@ static NSUInteger FBNormalizedMjpegFramerate(NSUInteger framerate)
148
157
  return;
149
158
  }
150
159
  NSUInteger clientCount = self.listeningClients.count;
160
+ #if TARGET_OS_WATCH
161
+ for (nw_connection_t client in self.listeningClients) {
162
+ [self.socket writeData:chunk toClient:client];
163
+ }
164
+ #else
151
165
  for (GCDAsyncSocket *client in self.listeningClients) {
152
166
  // Slow clients should fail/close instead of buffering indefinitely.
153
167
  [client writeData:chunk withTimeout:FRAME_TIMEOUT tag:0];
154
168
  }
169
+ #endif
155
170
  self.sentFramesCount++;
156
171
  self.sentBytesCount += chunk.length * clientCount;
157
172
  NSUInteger framerate = FBNormalizedMjpegFramerate(FBConfiguration.sharedInstance.mjpegServerFramerate);
@@ -164,6 +179,53 @@ static NSUInteger FBNormalizedMjpegFramerate(NSUInteger framerate)
164
179
  }
165
180
  }
166
181
 
182
+ #if TARGET_OS_WATCH
183
+
184
+ - (void)didClientConnect:(nw_connection_t)newClient
185
+ {
186
+ [FBLogger log:@"Got screenshots broadcast client connection"];
187
+ // FBTCPSocket already schedules the receive that -client:didReceiveData: relies on below;
188
+ // unlike GCDAsyncSocket, there is nothing to arm here.
189
+ }
190
+
191
+ - (void)client:(nw_connection_t)client didReceiveData:(NSData *)data
192
+ {
193
+ @synchronized (self.listeningClients) {
194
+ if ([self.listeningClients containsObject:client]) {
195
+ return;
196
+ }
197
+ }
198
+
199
+ [FBLogger log:@"Starting screenshots broadcast for the client"];
200
+ NSString *streamHeader = [NSString stringWithFormat:@"HTTP/1.0 200 OK\r\nServer: %@\r\nConnection: close\r\nMax-Age: 0\r\nExpires: 0\r\nCache-Control: no-cache, private\r\nPragma: no-cache\r\nContent-Type: multipart/x-mixed-replace; boundary=--BoundaryString\r\n\r\n", SERVER_NAME];
201
+ [self.socket writeData:(id)[streamHeader dataUsingEncoding:NSUTF8StringEncoding] toClient:client];
202
+ @synchronized (self.listeningClients) {
203
+ [self.listeningClients addObject:client];
204
+ }
205
+ }
206
+
207
+ - (void)didClientDisconnect:(nw_connection_t)client
208
+ {
209
+ @synchronized (self.listeningClients) {
210
+ [self.listeningClients removeObject:client];
211
+ }
212
+ [FBLogger log:@"Disconnected a client from screenshots broadcast"];
213
+ }
214
+
215
+ - (void)stopStreaming
216
+ {
217
+ self.isStreaming = NO;
218
+ @synchronized (self.listeningClients) {
219
+ NSArray<nw_connection_t> *clients = self.listeningClients.copy;
220
+ [self.listeningClients removeAllObjects];
221
+ for (nw_connection_t client in clients) {
222
+ nw_connection_cancel(client);
223
+ }
224
+ }
225
+ }
226
+
227
+ #else
228
+
167
229
  - (void)didClientConnect:(GCDAsyncSocket *)newClient
168
230
  {
169
231
  [FBLogger logFmt:@"Got screenshots broadcast client connection at %@:%d", newClient.connectedHost, newClient.connectedPort];
@@ -207,6 +269,8 @@ static NSUInteger FBNormalizedMjpegFramerate(NSUInteger framerate)
207
269
  }
208
270
  }
209
271
 
272
+ #endif
273
+
210
274
  - (void)dealloc
211
275
  {
212
276
  [self stopStreaming];
@@ -19,3 +19,5 @@
19
19
  #import "XCUIDevice+FBHelpers.h"
20
20
  #import "FBAlert.h"
21
21
  #import "FBElement.h"
22
+ #import "FBMjpegServer.h"
23
+ #import "FBTCPSocket.h"
@@ -0,0 +1,81 @@
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 Network
10
+ import XCTest
11
+
12
+ /// Exercises FBMjpegServer+FBTCPSocket the same way FBWebServer wires them together in
13
+ /// -initScreenshotsBroadcaster, but standalone (no full FBWebServer/HTTP command server needed).
14
+ /// A client NWConnection reads the raw wire bytes back to confirm the multipart/x-mixed-replace
15
+ /// stream and its JPEG frames actually arrive, since watchOS has no BSD sockets/GCDAsyncSocket -
16
+ /// the production implementation is Network.framework-backed and worth covering for real.
17
+ final class WDAMjpegStreamingIntegrationTests: WDAWatchInProcessTestCase {
18
+ override class var relaunchForEachTest: Bool { false }
19
+
20
+ private static let jpegMagicBytes: [UInt8] = [0xFF, 0xD8, 0xFF]
21
+
22
+ func testMjpegStreamingProducesJpegFrames() throws {
23
+ // Port 0 asks the system to assign an ephemeral port; -[FBTCPSocket port] then reflects the
24
+ // actual bound port once -start returns, per nw_listener_get_port's documented behavior.
25
+ let socket = FBTCPSocket(port: 0)
26
+ let server = FBMjpegServer()
27
+ server.socket = socket
28
+ socket.delegate = server
29
+ defer {
30
+ server.stopStreaming()
31
+ socket.stop()
32
+ }
33
+ try socket.start()
34
+ let boundPort = socket.port
35
+ XCTAssertNotEqual(boundPort, 0, "FBTCPSocket did not report its bound ephemeral port")
36
+
37
+ let connection = NWConnection(host: "127.0.0.1", port: NWEndpoint.Port(rawValue: boundPort)!, using: .tcp)
38
+ defer { connection.cancel() }
39
+
40
+ let connected = expectation(description: "client connected to the mjpeg broadcaster")
41
+ connection.stateUpdateHandler = { state in
42
+ if case .ready = state {
43
+ connected.fulfill()
44
+ }
45
+ }
46
+ connection.start(queue: .main)
47
+ wait(for: [connected], timeout: 15)
48
+
49
+ // FBMjpegServer only starts broadcasting once it has seen any data from the client - mimic the
50
+ // request line a real HTTP client would send.
51
+ let sent = expectation(description: "sent the request line")
52
+ connection.send(content: Data("GET / HTTP/1.0\r\n\r\n".utf8), completion: .contentProcessed { _ in
53
+ sent.fulfill()
54
+ })
55
+ wait(for: [sent], timeout: 15)
56
+
57
+ var received = Data()
58
+ let jpegMagicData = Data(Self.jpegMagicBytes)
59
+ let sawJpegFrame = expectation(description: "received at least one jpeg frame")
60
+ func receiveMore() {
61
+ connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { data, _, _, error in
62
+ if let data = data {
63
+ received.append(data)
64
+ if received.range(of: jpegMagicData) != nil {
65
+ sawJpegFrame.fulfill()
66
+ return
67
+ }
68
+ }
69
+ if error == nil {
70
+ receiveMore()
71
+ }
72
+ }
73
+ }
74
+ receiveMore()
75
+ wait(for: [sawJpegFrame], timeout: 15)
76
+
77
+ XCTAssertNotNil(received.range(of: Data("multipart/x-mixed-replace".utf8)), "Response is not a multipart stream")
78
+ XCTAssertNotNil(received.range(of: Data("Content-type: image/jpeg".utf8)), "No JPEG part header found")
79
+ XCTAssertNotNil(received.range(of: jpegMagicData), "No JPEG frame data found")
80
+ }
81
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appium-webdriveragent",
3
- "version": "16.5.0",
3
+ "version": "16.6.0",
4
4
  "description": "Package bundling WebDriverAgent",
5
5
  "keywords": [
6
6
  "Appium",