appium-webdriveragent 16.2.2 → 16.3.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.
Files changed (64) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/PrivateHeaders/XCTest/XCSynthesizedEventRecord.h +3 -3
  3. package/PrivateHeaders/XCTest/XCUIApplication.h +1 -1
  4. package/PrivateHeaders/XCTest/XCUIElement.h +1 -1
  5. package/Scripts/build.sh +6 -0
  6. package/WebDriverAgent.xcodeproj/project.pbxproj +1482 -2
  7. package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/IntegrationApp_watchOS.xcscheme +76 -0
  8. package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/IntegrationTests_watchOS.xcscheme +86 -0
  9. package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentLib_watchOS.xcscheme +85 -0
  10. package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentRunner.xcscheme +1 -1
  11. package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentRunner_watchOS.xcscheme +128 -0
  12. package/WebDriverAgentLib/Categories/XCUIApplication+FBAlert.m +5 -0
  13. package/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m +2 -0
  14. package/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.m +1 -1
  15. package/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m +4 -4
  16. package/WebDriverAgentLib/Categories/XCUIDevice+FBRotation.h +1 -1
  17. package/WebDriverAgentLib/Categories/XCUIDevice+FBRotation.m +1 -1
  18. package/WebDriverAgentLib/Categories/XCUIElement+FBPickerWheel.m +1 -1
  19. package/WebDriverAgentLib/Categories/XCUIElement+FBTyping.m +11 -0
  20. package/WebDriverAgentLib/Commands/FBCustomCommands.m +19 -4
  21. package/WebDriverAgentLib/Commands/FBElementCommands.m +6 -5
  22. package/WebDriverAgentLib/Commands/FBOrientationCommands.m +1 -1
  23. package/WebDriverAgentLib/Commands/FBSessionCommands.m +29 -5
  24. package/WebDriverAgentLib/Info.plist +2 -2
  25. package/WebDriverAgentLib/Routing/FBTCPSocket.h +70 -0
  26. package/WebDriverAgentLib/Routing/FBTCPSocket.m +199 -0
  27. package/WebDriverAgentLib/Routing/FBWebServer.m +33 -3
  28. package/WebDriverAgentLib/Routing/WatchOS/FBWatchHTTPServer.h +68 -0
  29. package/WebDriverAgentLib/Routing/WatchOS/FBWatchHTTPServer.m +376 -0
  30. package/WebDriverAgentLib/Routing/WatchOS/RouteRequest.h +30 -0
  31. package/WebDriverAgentLib/Routing/WatchOS/RouteRequest.m +25 -0
  32. package/WebDriverAgentLib/Routing/WatchOS/RouteResponse.h +30 -0
  33. package/WebDriverAgentLib/Routing/WatchOS/RouteResponse.m +54 -0
  34. package/WebDriverAgentLib/Utilities/FBActiveAppDetectionPoint.m +8 -0
  35. package/WebDriverAgentLib/Utilities/FBBaseActionsSynthesizer.h +1 -1
  36. package/WebDriverAgentLib/Utilities/FBBaseActionsSynthesizer.m +1 -1
  37. package/WebDriverAgentLib/Utilities/FBConfiguration.h +1 -1
  38. package/WebDriverAgentLib/Utilities/FBConfiguration.m +3 -3
  39. package/WebDriverAgentLib/Utilities/FBImageProcessor.m +7 -2
  40. package/WebDriverAgentLib/Utilities/FBMacros.h +14 -5
  41. package/WebDriverAgentLib/Utilities/FBMathUtils.h +1 -1
  42. package/WebDriverAgentLib/Utilities/FBMathUtils.m +1 -1
  43. package/WebDriverAgentLib/Utilities/FBPasteboard.h +1 -1
  44. package/WebDriverAgentLib/Utilities/FBPasteboard.m +1 -1
  45. package/WebDriverAgentLib/Utilities/FBSettingsHandler.m +2 -2
  46. package/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.h +1 -1
  47. package/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.m +1 -1
  48. package/WebDriverAgentTests/IntegrationApp_watchOS/ContentView.swift +31 -0
  49. package/WebDriverAgentTests/IntegrationApp_watchOS/WatchSpikeApp.swift +18 -0
  50. package/WebDriverAgentTests/IntegrationTests/FBForceTouchTests.m +3 -0
  51. package/WebDriverAgentTests/IntegrationTests/FBPasteboardTests.m +3 -2
  52. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAAlertIntegrationTests.swift +23 -0
  53. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAAppLifecycleIntegrationTests.swift +57 -0
  54. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAClickIntegrationTests.swift +31 -0
  55. package/WebDriverAgentTests/IntegrationTests_watchOS/WDADeviceIntegrationTests.swift +50 -0
  56. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAElementAttributeIntegrationTests.swift +57 -0
  57. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAFindIntegrationTests.swift +126 -0
  58. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAScreenshotAndSourceIntegrationTests.swift +52 -0
  59. package/WebDriverAgentTests/IntegrationTests_watchOS/WDASessionIntegrationTests.swift +72 -0
  60. package/WebDriverAgentTests/IntegrationTests_watchOS/WDATypingIntegrationTests.swift +57 -0
  61. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAUnknownCommandIntegrationTests.swift +24 -0
  62. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAWatchHTTPClient.swift +95 -0
  63. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAWatchIntegrationTestCase.swift +61 -0
  64. package/package.json +1 -1
@@ -0,0 +1,376 @@
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 "FBWatchHTTPServer.h"
10
+
11
+ #import "FBConfiguration.h"
12
+ #import "FBTCPSocket.h"
13
+
14
+ static NSData *FBCRLFCRLFData(void)
15
+ {
16
+ static NSData *data;
17
+ static dispatch_once_t onceToken;
18
+ dispatch_once(&onceToken, ^{
19
+ data = [@"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding];
20
+ });
21
+ return data;
22
+ }
23
+
24
+ // -dataUsingEncoding:NSUTF8StringEncoding never actually returns nil; this just keeps the cast
25
+ // out of every call site below.
26
+ static NSData * _Nonnull FBUTF8Data(NSString *string)
27
+ {
28
+ return (NSData * _Nonnull)[string dataUsingEncoding:NSUTF8StringEncoding];
29
+ }
30
+
31
+ @interface FBWatchHTTPRoute : NSObject
32
+ @property (nonatomic, copy) NSString *verb;
33
+ @property (nonatomic, strong) NSRegularExpression *regex;
34
+ @property (nonatomic, copy, nullable) NSArray<NSString *> *keys;
35
+ @property (nonatomic, copy) void (^block)(RouteRequest *request, RouteResponse *response);
36
+ @end
37
+
38
+ @implementation FBWatchHTTPRoute
39
+ @end
40
+
41
+
42
+ @interface FBWatchHTTPServer () <FBTCPSocketDelegate>
43
+
44
+ @property (nonatomic, nullable, strong) FBTCPSocket *socket;
45
+ @property (nonatomic, strong) NSMutableArray<FBWatchHTTPRoute *> *routes;
46
+ @property (nonatomic, strong) NSMutableDictionary<NSString *, NSString *> *defaultHeaders;
47
+ @property (nonatomic, nullable) dispatch_queue_t routeQueue;
48
+ // nw_connection_t isn't NSCopying, so it can't be an NSDictionary key - use NSMapTable instead.
49
+ @property (nonatomic, strong) NSMapTable<id, NSMutableData *> *connectionBuffers;
50
+
51
+ @end
52
+
53
+ @implementation FBWatchHTTPServer
54
+
55
+ - (instancetype)init
56
+ {
57
+ if ((self = [super init])) {
58
+ _routes = [NSMutableArray array];
59
+ _defaultHeaders = [NSMutableDictionary dictionary];
60
+ _connectionBuffers = [NSMapTable mapTableWithKeyOptions:(NSPointerFunctionsOptions)(NSMapTableObjectPointerPersonality | NSMapTableStrongMemory)
61
+ valueOptions:(NSPointerFunctionsOptions)NSMapTableStrongMemory];
62
+ }
63
+ return self;
64
+ }
65
+
66
+ - (void)setRouteQueue:(nullable dispatch_queue_t)queue
67
+ {
68
+ _routeQueue = queue;
69
+ }
70
+
71
+ - (void)setDefaultHeader:(NSString *)field value:(NSString *)value
72
+ {
73
+ self.defaultHeaders[field] = value;
74
+ }
75
+
76
+ #pragma mark - Route registration
77
+
78
+ - (FBWatchHTTPRoute *)compiledRouteWithPath:(NSString *)path
79
+ {
80
+ FBWatchHTTPRoute *route = [FBWatchHTTPRoute new];
81
+ NSMutableArray<NSString *> *keys = [NSMutableArray array];
82
+
83
+ // Escape regex-significant characters before substituting :param placeholders, like
84
+ // RoutingHTTPServer.m does.
85
+ NSRegularExpression *escapeRegex = [NSRegularExpression regularExpressionWithPattern:@"[.+()]"
86
+ options:(NSRegularExpressionOptions)0
87
+ error:nil];
88
+ NSString *escapedPath = [escapeRegex stringByReplacingMatchesInString:path
89
+ options:(NSMatchingOptions)0
90
+ range:NSMakeRange(0, path.length)
91
+ withTemplate:@"\\\\$0"];
92
+
93
+ NSRegularExpression *paramRegex = [NSRegularExpression regularExpressionWithPattern:@"(:(\\w+)|\\*)"
94
+ options:(NSRegularExpressionOptions)0
95
+ error:nil];
96
+ NSMutableString *regexPath = [NSMutableString stringWithString:escapedPath];
97
+ __block NSInteger diff = 0;
98
+ [paramRegex enumerateMatchesInString:escapedPath
99
+ options:(NSMatchingOptions)0
100
+ range:NSMakeRange(0, escapedPath.length)
101
+ usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
102
+ NSRange replacementRange = NSMakeRange(diff + result.range.location, result.range.length);
103
+ NSString *capturedString = [escapedPath substringWithRange:result.range];
104
+ NSString *replacementString;
105
+ if ([capturedString isEqualToString:@"*"]) {
106
+ [keys addObject:@"wildcards"];
107
+ replacementString = @"(.*?)";
108
+ } else {
109
+ NSString *keyString = [escapedPath substringWithRange:[result rangeAtIndex:2]];
110
+ [keys addObject:keyString];
111
+ replacementString = @"([^/]+)";
112
+ }
113
+ [regexPath replaceCharactersInRange:replacementRange withString:replacementString];
114
+ diff += replacementString.length - result.range.length;
115
+ }];
116
+
117
+ NSString *anchoredPattern = [NSString stringWithFormat:@"^%@$", regexPath];
118
+ route.regex = [NSRegularExpression regularExpressionWithPattern:anchoredPattern
119
+ options:NSRegularExpressionCaseInsensitive
120
+ error:nil];
121
+ route.keys = keys.count > 0 ? keys.copy : nil;
122
+ return route;
123
+ }
124
+
125
+ - (void)handleMethod:(NSString *)method
126
+ withPath:(NSString *)path
127
+ block:(void (^)(RouteRequest *request, RouteResponse *response))block
128
+ {
129
+ FBWatchHTTPRoute *route = [self compiledRouteWithPath:path];
130
+ route.verb = method.uppercaseString;
131
+ route.block = block;
132
+ [self.routes addObject:route];
133
+ }
134
+
135
+ - (void)get:(NSString *)path withBlock:(void (^)(RouteRequest *request, RouteResponse *response))block
136
+ {
137
+ [self handleMethod:@"GET" withPath:path block:block];
138
+ }
139
+
140
+ #pragma mark - Lifecycle
141
+
142
+ - (BOOL)start:(NSError **)error
143
+ {
144
+ FBTCPSocket *socket = [[FBTCPSocket alloc] initWithPort:self.port];
145
+ socket.delegate = self;
146
+ if (![socket startWithError:error]) {
147
+ return NO;
148
+ }
149
+ self.socket = socket;
150
+ _isRunning = YES;
151
+ return YES;
152
+ }
153
+
154
+ - (void)stop:(BOOL)immediately
155
+ {
156
+ [self.socket stop];
157
+ self.socket = nil;
158
+ @synchronized (self.connectionBuffers) {
159
+ [self.connectionBuffers removeAllObjects];
160
+ }
161
+ _isRunning = NO;
162
+ }
163
+
164
+ #pragma mark - FBTCPSocketDelegate
165
+
166
+ - (void)didClientConnect:(nw_connection_t)newClient
167
+ {
168
+ @synchronized (self.connectionBuffers) {
169
+ [self.connectionBuffers setObject:[NSMutableData data] forKey:newClient];
170
+ }
171
+ }
172
+
173
+ - (void)didClientDisconnect:(nw_connection_t)client
174
+ {
175
+ @synchronized (self.connectionBuffers) {
176
+ [self.connectionBuffers removeObjectForKey:client];
177
+ }
178
+ }
179
+
180
+ - (void)client:(nw_connection_t)client didReceiveData:(NSData *)data
181
+ {
182
+ NSMutableData *buffer;
183
+ @synchronized (self.connectionBuffers) {
184
+ buffer = [self.connectionBuffers objectForKey:client];
185
+ if (nil == buffer) {
186
+ return;
187
+ }
188
+ [buffer appendData:data];
189
+ }
190
+ [self processBufferForClient:client];
191
+ }
192
+
193
+ #pragma mark - HTTP parsing
194
+
195
+ - (void)processBufferForClient:(nw_connection_t)client
196
+ {
197
+ while (YES) {
198
+ NSMutableData *buffer;
199
+ @synchronized (self.connectionBuffers) {
200
+ buffer = [self.connectionBuffers objectForKey:client];
201
+ }
202
+ if (nil == buffer) {
203
+ return;
204
+ }
205
+
206
+ NSRange headerEndRange = [buffer rangeOfData:FBCRLFCRLFData() options:(NSDataSearchOptions)0 range:NSMakeRange(0, buffer.length)];
207
+ if (NSNotFound == headerEndRange.location) {
208
+ return;
209
+ }
210
+
211
+ NSData *headerData = [buffer subdataWithRange:NSMakeRange(0, headerEndRange.location)];
212
+ NSString *headerString = [[NSString alloc] initWithData:headerData encoding:NSUTF8StringEncoding];
213
+ NSArray<NSString *> *lines = [headerString componentsSeparatedByString:@"\r\n"];
214
+ if (lines.count < 1) {
215
+ [self closeClient:client];
216
+ return;
217
+ }
218
+
219
+ NSArray<NSString *> *requestLineParts = [lines.firstObject componentsSeparatedByString:@" "];
220
+ if (requestLineParts.count < 2) {
221
+ [self closeClient:client];
222
+ return;
223
+ }
224
+ NSString *method = requestLineParts[0].uppercaseString;
225
+ NSString *pathAndQuery = requestLineParts[1];
226
+
227
+ NSMutableDictionary<NSString *, NSString *> *requestHeaders = [NSMutableDictionary dictionary];
228
+ for (NSUInteger i = 1; i < lines.count; i++) {
229
+ NSString *line = lines[i];
230
+ NSRange colonRange = [line rangeOfString:@":"];
231
+ if (NSNotFound == colonRange.location) {
232
+ continue;
233
+ }
234
+ NSString *name = [line substringToIndex:colonRange.location];
235
+ NSString *value = [[line substringFromIndex:colonRange.location + 1]
236
+ stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet];
237
+ requestHeaders[name.lowercaseString] = value;
238
+ }
239
+
240
+ NSUInteger contentLength = (NSUInteger)requestHeaders[@"content-length"].integerValue;
241
+ if (contentLength > FBConfiguration.sharedInstance.httpRequestBodySizeLimit) {
242
+ // Mirrors FBHTTPConnection's maxRequestBodySize enforcement on iOS/tvOS. Closes the
243
+ // connection after responding, since the rest of the oversized body is still incoming.
244
+ RouteResponse *tooLarge = [RouteResponse new];
245
+ tooLarge.statusCode = kHTTPStatusCodeRequestEntityTooLarge;
246
+ [tooLarge respondWithString:@"Request Entity Too Large"];
247
+ [self writeResponse:tooLarge toClient:client thenCloseConnection:YES];
248
+ return;
249
+ }
250
+ NSUInteger bodyStart = headerEndRange.location + headerEndRange.length;
251
+ NSUInteger totalRequestLength = bodyStart + contentLength;
252
+ if (buffer.length < totalRequestLength) {
253
+ // Wait for the rest of the body to arrive.
254
+ return;
255
+ }
256
+
257
+ NSData *body = contentLength > 0 ? [buffer subdataWithRange:NSMakeRange(bodyStart, contentLength)] : [NSData data];
258
+
259
+ @synchronized (self.connectionBuffers) {
260
+ [buffer replaceBytesInRange:NSMakeRange(0, totalRequestLength) withBytes:NULL length:0];
261
+ }
262
+
263
+ [self dispatchMethod:method pathAndQuery:pathAndQuery body:body client:client];
264
+ }
265
+ }
266
+
267
+ - (void)dispatchMethod:(NSString *)method pathAndQuery:(NSString *)pathAndQuery body:(NSData *)body client:(nw_connection_t)client
268
+ {
269
+ NSURLComponents *requestTarget = [NSURLComponents componentsWithString:pathAndQuery];
270
+ NSString *path = requestTarget.path ?: pathAndQuery;
271
+
272
+ for (FBWatchHTTPRoute *route in self.routes) {
273
+ if (![route.verb isEqualToString:method]) {
274
+ continue;
275
+ }
276
+ NSTextCheckingResult *result = [route.regex firstMatchInString:path options:(NSMatchingOptions)0 range:NSMakeRange(0, path.length)];
277
+ if (nil == result) {
278
+ continue;
279
+ }
280
+
281
+ NSMutableDictionary<NSString *, NSString *> *params = [NSMutableDictionary dictionary];
282
+ for (NSURLQueryItem *queryItem in requestTarget.queryItems) {
283
+ params[queryItem.name] = queryItem.value ?: @"";
284
+ }
285
+ if (route.keys.count > 0 && result.numberOfRanges == route.keys.count + 1) {
286
+ NSUInteger index = 1;
287
+ for (NSString *key in route.keys) {
288
+ params[key] = [path substringWithRange:[result rangeAtIndex:index]];
289
+ index++;
290
+ }
291
+ }
292
+
293
+ NSURL *url = [NSURL URLWithString:path] ?: [NSURL URLWithString:@"/"];
294
+ RouteRequest *request = [[RouteRequest alloc] initWithURL:url params:params.copy body:body];
295
+ RouteResponse *response = [RouteResponse new];
296
+ [self.defaultHeaders enumerateKeysAndObjectsUsingBlock:^(NSString *field, NSString *value, BOOL *stop) {
297
+ [response setHeader:field value:value];
298
+ }];
299
+
300
+ void (^invoke)(void) = ^{
301
+ route.block(request, response);
302
+ [self writeResponse:response toClient:client];
303
+ };
304
+ dispatch_queue_t routeQueue = self.routeQueue;
305
+ if (routeQueue) {
306
+ dispatch_async((dispatch_queue_t _Nonnull)routeQueue, invoke);
307
+ } else {
308
+ invoke();
309
+ }
310
+ return;
311
+ }
312
+
313
+ RouteResponse *notFound = [RouteResponse new];
314
+ notFound.statusCode = kHTTPStatusCodeNotFound;
315
+ [notFound respondWithString:@"Not Found"];
316
+ [self writeResponse:notFound toClient:client];
317
+ }
318
+
319
+ - (void)writeResponse:(RouteResponse *)response toClient:(nw_connection_t)client
320
+ {
321
+ [self writeResponse:response toClient:client thenCloseConnection:NO];
322
+ }
323
+
324
+ - (void)writeResponse:(RouteResponse *)response toClient:(nw_connection_t)client thenCloseConnection:(BOOL)shouldClose
325
+ {
326
+ NSMutableData *payload = [NSMutableData data];
327
+ NSString *statusLine = [NSString stringWithFormat:@"HTTP/1.1 %ld %@\r\n",
328
+ (long)response.statusCode, [self reasonPhraseForStatusCode:response.statusCode]];
329
+ [payload appendData:FBUTF8Data(statusLine)];
330
+
331
+ NSData *body = response.responseData ?: [NSData data];
332
+ NSMutableDictionary<NSString *, NSString *> *headers = response.headers.mutableCopy;
333
+ if (nil == headers[@"Content-Length"]) {
334
+ headers[@"Content-Length"] = [NSString stringWithFormat:@"%lu", (unsigned long)body.length];
335
+ }
336
+ [headers enumerateKeysAndObjectsUsingBlock:^(NSString *field, NSString *value, BOOL *stop) {
337
+ NSString *headerLine = [NSString stringWithFormat:@"%@: %@\r\n", field, value];
338
+ [payload appendData:FBUTF8Data(headerLine)];
339
+ }];
340
+ [payload appendData:FBUTF8Data(@"\r\n")];
341
+ [payload appendData:body];
342
+
343
+ if (shouldClose) {
344
+ __weak typeof(self) weakSelf = self;
345
+ [self.socket writeData:payload toClient:client completion:^{
346
+ [weakSelf closeClient:client];
347
+ }];
348
+ } else {
349
+ [self.socket writeData:payload toClient:client];
350
+ }
351
+ }
352
+
353
+ - (void)closeClient:(nw_connection_t)client
354
+ {
355
+ @synchronized (self.connectionBuffers) {
356
+ [self.connectionBuffers removeObjectForKey:client];
357
+ }
358
+ nw_connection_cancel(client);
359
+ }
360
+
361
+ - (NSString *)reasonPhraseForStatusCode:(HTTPStatusCode)statusCode
362
+ {
363
+ // if/else, not switch, to avoid having to list all ~90 HTTPStatusCode cases for -Wswitch-enum.
364
+ if (kHTTPStatusCodeOK == statusCode) {
365
+ return @"OK";
366
+ } else if (kHTTPStatusCodeBadRequest == statusCode) {
367
+ return @"Bad Request";
368
+ } else if (kHTTPStatusCodeNotFound == statusCode) {
369
+ return @"Not Found";
370
+ } else if (kHTTPStatusCodeInternalServerError == statusCode) {
371
+ return @"Internal Server Error";
372
+ }
373
+ return @"Status";
374
+ }
375
+
376
+ @end
@@ -0,0 +1,30 @@
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
+ // Minimal, watchOS-only stand-in for Vendor/RoutingHTTPServer/RouteRequest.h, which is not
10
+ // available on watchOS because RoutingHTTPServer/CocoaHTTPServer/CocoaAsyncSocket cannot be
11
+ // built there (see FBWatchHTTPServer.h). Exposes just the surface FBWebServer's route blocks
12
+ // and FBRoute.decorateRequest: read.
13
+
14
+ @import Foundation;
15
+
16
+ NS_ASSUME_NONNULL_BEGIN
17
+
18
+ @interface RouteRequest : NSObject
19
+
20
+ @property (nonatomic, copy, readonly) NSURL *url;
21
+ @property (nonatomic, copy, readonly) NSDictionary<NSString *, NSString *> *params;
22
+ @property (nonatomic, copy, readonly) NSData *body;
23
+
24
+ - (instancetype)initWithURL:(NSURL *)url
25
+ params:(NSDictionary<NSString *, NSString *> *)params
26
+ body:(NSData *)body;
27
+
28
+ @end
29
+
30
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,25 @@
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 "RouteRequest.h"
10
+
11
+ @implementation RouteRequest
12
+
13
+ - (instancetype)initWithURL:(NSURL *)url
14
+ params:(NSDictionary<NSString *, NSString *> *)params
15
+ body:(NSData *)body
16
+ {
17
+ if ((self = [super init])) {
18
+ _url = url.copy;
19
+ _params = params.copy;
20
+ _body = body.copy;
21
+ }
22
+ return self;
23
+ }
24
+
25
+ @end
@@ -0,0 +1,30 @@
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
+ // Minimal, watchOS-only stand-in for Vendor/RoutingHTTPServer/RouteResponse.h, reproducing just
10
+ // what FBRoute.m/FBResponseJSONPayload.m call on it. See FBWatchHTTPServer.h.
11
+
12
+ @import Foundation;
13
+ #import <WebDriverAgentLib/FBHTTPStatusCodes.h>
14
+
15
+ NS_ASSUME_NONNULL_BEGIN
16
+
17
+ @interface RouteResponse : NSObject
18
+
19
+ @property (nonatomic) HTTPStatusCode statusCode;
20
+ @property (nonatomic, copy, readonly) NSDictionary<NSString *, NSString *> *headers;
21
+ @property (nonatomic, copy, readonly, nullable) NSData *responseData;
22
+
23
+ - (void)setHeader:(NSString *)field value:(NSString *)value;
24
+ - (void)respondWithString:(NSString *)string;
25
+ - (void)respondWithString:(NSString *)string encoding:(NSStringEncoding)encoding;
26
+ - (void)respondWithData:(NSData *)data;
27
+
28
+ @end
29
+
30
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,54 @@
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 "RouteResponse.h"
10
+
11
+ @interface RouteResponse ()
12
+ @property (nonatomic, copy) NSMutableDictionary<NSString *, NSString *> *mutableHeaders;
13
+ @end
14
+
15
+ @implementation RouteResponse
16
+
17
+ - (instancetype)init
18
+ {
19
+ if ((self = [super init])) {
20
+ _statusCode = kHTTPStatusCodeOK;
21
+ _mutableHeaders = [NSMutableDictionary dictionary];
22
+ }
23
+ return self;
24
+ }
25
+
26
+ - (NSDictionary<NSString *, NSString *> *)headers
27
+ {
28
+ return self.mutableHeaders.copy;
29
+ }
30
+
31
+ - (void)setHeader:(NSString *)field value:(NSString *)value
32
+ {
33
+ self.mutableHeaders[field] = value;
34
+ }
35
+
36
+ - (void)respondWithData:(NSData *)data
37
+ {
38
+ _responseData = data.copy;
39
+ if (nil == self.mutableHeaders[@"Content-Length"]) {
40
+ self.mutableHeaders[@"Content-Length"] = [NSString stringWithFormat:@"%lu", (unsigned long)data.length];
41
+ }
42
+ }
43
+
44
+ - (void)respondWithString:(NSString *)string
45
+ {
46
+ [self respondWithString:string encoding:NSUTF8StringEncoding];
47
+ }
48
+
49
+ - (void)respondWithString:(NSString *)string encoding:(NSStringEncoding)encoding
50
+ {
51
+ [self respondWithData:[string dataUsingEncoding:encoding] ?: [NSData data]];
52
+ }
53
+
54
+ @end
@@ -8,6 +8,10 @@
8
8
 
9
9
  #import "FBActiveAppDetectionPoint.h"
10
10
 
11
+ #if TARGET_OS_WATCH
12
+ @import WatchKit;
13
+ #endif
14
+
11
15
  #import "FBErrorBuilder.h"
12
16
  #import "FBLogger.h"
13
17
  #import "FBXCTestDaemonsProxy.h"
@@ -17,7 +21,11 @@
17
21
 
18
22
  - (instancetype)init {
19
23
  if ((self = [super init])) {
24
+ #if TARGET_OS_WATCH
25
+ CGSize screenSize = WKInterfaceDevice.currentDevice.screenBounds.size;
26
+ #else
20
27
  CGSize screenSize = [UIScreen mainScreen].bounds.size;
28
+ #endif
21
29
  // Consider the element, which is located close to the top left corner of the screen the on-screen one.
22
30
  CGFloat pointDistance = MIN(screenSize.width, screenSize.height) * (CGFloat) 0.2;
23
31
  _coordinates = CGPointMake(pointDistance, pointDistance);
@@ -14,7 +14,7 @@
14
14
 
15
15
  NS_ASSUME_NONNULL_BEGIN
16
16
 
17
- #if !TARGET_OS_TV
17
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
18
18
  @interface FBBaseActionItem : NSObject
19
19
 
20
20
  /*! Raw JSON representation of the corresponding action item */
@@ -21,7 +21,7 @@
21
21
  #import "XCSynthesizedEventRecord.h"
22
22
  #import "XCUIElement+FBUtilities.h"
23
23
 
24
- #if !TARGET_OS_TV
24
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
25
25
  @implementation FBBaseActionItem
26
26
 
27
27
  + (NSString *)actionName
@@ -262,7 +262,7 @@ typedef NS_ENUM(NSInteger, FBConfigurationKeyboardPreference) {
262
262
  */
263
263
  @property (atomic, assign) BOOL useClearTextShortcut;
264
264
 
265
- #if !TARGET_OS_TV
265
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
266
266
  /**
267
267
  Set the screenshot orientation for iOS
268
268
 
@@ -37,7 +37,7 @@ static NSString *const axSettingsClassName = @"AXSettings";
37
37
  @interface FBConfiguration ()
38
38
 
39
39
  @property (atomic, strong) NSNumber *maxTypingFrequencyOverride;
40
- #if !TARGET_OS_TV
40
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
41
41
  @property (atomic, assign) UIInterfaceOrientation screenshotOrientationStorage;
42
42
  #endif
43
43
 
@@ -292,7 +292,7 @@ static NSString *const axSettingsClassName = @"AXSettings";
292
292
  return [FBGetCustomParameterForElementSnapshot(FBSnapshotMaxChildrenKey) intValue];
293
293
  }
294
294
 
295
- #if !TARGET_OS_TV
295
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
296
296
  - (BOOL)setScreenshotOrientation:(NSString *)orientation error:(NSError **)error
297
297
  {
298
298
  // Only UIInterfaceOrientationUnknown is over iOS 8. Others are over iOS 2.
@@ -359,7 +359,7 @@ static NSString *const axSettingsClassName = @"AXSettings";
359
359
  FBSetCustomParameterForElementSnapshot(FBSnapshotMaxChildrenKey, @INT_MAX);
360
360
  self.useClearTextShortcut = YES;
361
361
  self.limitXpathContextScope = YES;
362
- #if !TARGET_OS_TV
362
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
363
363
  self.screenshotOrientationStorage = UIInterfaceOrientationUnknown;
364
364
  #endif
365
365
  }
@@ -109,8 +109,12 @@ const CGFloat FBMaxCompressionQuality = 1.0f;
109
109
  desiredOrientation:(nullable NSNumber *)orientation
110
110
  {
111
111
  scalingFactor = MAX(FBMinScalingFactor, MIN(FBMaxScalingFactor, scalingFactor));
112
- BOOL usesScaling = scalingFactor > 0.0 && scalingFactor < FBMaxScalingFactor;
113
112
  @autoreleasepool {
113
+ #if TARGET_OS_WATCH
114
+ // UIGraphicsImageRenderer is unavailable and the watch screen doesn't rotate, so skip both.
115
+ return [uti conformsToType:UTTypePNG] ? FBToPngData(imageData) : FBToJpegData(imageData, compressionQuality);
116
+ #else
117
+ BOOL usesScaling = scalingFactor > 0.0 && scalingFactor < FBMaxScalingFactor;
114
118
  if (!usesScaling && !fixOrientation) {
115
119
  return [uti conformsToType:UTTypePNG] ? FBToPngData(imageData) : FBToJpegData(imageData, compressionQuality);
116
120
  }
@@ -157,6 +161,7 @@ const CGFloat FBMaxCompressionQuality = 1.0f;
157
161
  actions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
158
162
  [uiImage drawInRect:CGRectMake(0, 0, scaledSize.width, scaledSize.height)];
159
163
  }];
164
+ #endif
160
165
  }
161
166
  }
162
167
 
@@ -167,7 +172,7 @@ const CGFloat FBMaxCompressionQuality = 1.0f;
167
172
  error:(NSError **)error
168
173
  {
169
174
  NSNumber *orientation = nil;
170
- #if !TARGET_OS_TV
175
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
171
176
  if (FBConfiguration.sharedInstance.screenshotOrientation == UIInterfaceOrientationPortrait) {
172
177
  orientation = @(UIImageOrientationUp);
173
178
  } else if (FBConfiguration.sharedInstance.screenshotOrientation == UIInterfaceOrientationPortraitUpsideDown) {
@@ -42,12 +42,21 @@
42
42
  typeof(arg) arg = wda_weak_##arg \
43
43
  _Pragma("clang diagnostic pop")
44
44
 
45
+ /*! Current OS version as a dotted string, e.g. "17.4". UIDevice.systemVersion is unavailable on
46
+ watchOS, so this is sourced from NSProcessInfo, which works identically on every platform. */
47
+ NS_INLINE NSString *FBSystemVersion(void) {
48
+ NSOperatingSystemVersion v = NSProcessInfo.processInfo.operatingSystemVersion;
49
+ return 0 == v.patchVersion
50
+ ? [NSString stringWithFormat:@"%ld.%ld", (long)v.majorVersion, (long)v.minorVersion]
51
+ : [NSString stringWithFormat:@"%ld.%ld.%ld", (long)v.majorVersion, (long)v.minorVersion, (long)v.patchVersion];
52
+ }
53
+
45
54
  /*! Returns YES if current system version satisfies the given codition */
46
- #define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
47
- #define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
48
- #define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
49
- #define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
50
- #define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)
55
+ #define SYSTEM_VERSION_EQUAL_TO(v) ([FBSystemVersion() compare:v options:NSNumericSearch] == NSOrderedSame)
56
+ #define SYSTEM_VERSION_GREATER_THAN(v) ([FBSystemVersion() compare:v options:NSNumericSearch] == NSOrderedDescending)
57
+ #define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([FBSystemVersion() compare:v options:NSNumericSearch] != NSOrderedAscending)
58
+ #define SYSTEM_VERSION_LESS_THAN(v) ([FBSystemVersion() compare:v options:NSNumericSearch] == NSOrderedAscending)
59
+ #define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([FBSystemVersion() compare:v options:NSNumericSearch] != NSOrderedDescending)
51
60
 
52
61
  /*! Converts the given number of milliseconds into seconds */
53
62
  #define FBMillisToSeconds(ms) ((ms) / 1000.0)
@@ -30,7 +30,7 @@ BOOL FBSizeFuzzyEqualToSize(CGSize size1, CGSize size2, CGFloat threshold);
30
30
  /*! Returns whether rect are equal within given threshold */
31
31
  BOOL FBRectFuzzyEqualToRect(CGRect rect1, CGRect rect2, CGFloat threshold);
32
32
 
33
- #if !TARGET_OS_TV
33
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
34
34
  /*! Inverts size if necessary to match current screen orientation */
35
35
  CGSize FBAdjustDimensionsForApplication(CGSize actualSize, UIInterfaceOrientation orientation);
36
36
  #endif
@@ -44,7 +44,7 @@ BOOL FBRectFuzzyEqualToRect(CGRect rect1, CGRect rect2, CGFloat threshold)
44
44
  FBSizeFuzzyEqualToSize(rect1.size, rect2.size, threshold);
45
45
  }
46
46
 
47
- #if !TARGET_OS_TV
47
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
48
48
 
49
49
  CGSize FBAdjustDimensionsForApplication(CGSize actualSize, UIInterfaceOrientation orientation)
50
50
  {