appium-webdriveragent 16.2.2 → 16.4.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 (66) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/PrivateHeaders/XCTest/XCSynthesizedEventRecord.h +3 -3
  3. package/PrivateHeaders/XCTest/XCUIApplication.h +1 -1
  4. package/PrivateHeaders/XCTest/XCUIDevice.h +0 -1
  5. package/PrivateHeaders/XCTest/XCUIElement.h +1 -1
  6. package/Scripts/build.sh +6 -0
  7. package/WebDriverAgent.xcodeproj/project.pbxproj +1482 -2
  8. package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/IntegrationApp_watchOS.xcscheme +76 -0
  9. package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/IntegrationTests_watchOS.xcscheme +86 -0
  10. package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentLib_watchOS.xcscheme +85 -0
  11. package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentRunner.xcscheme +1 -1
  12. package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentRunner_watchOS.xcscheme +128 -0
  13. package/WebDriverAgentLib/Categories/XCUIApplication+FBAlert.m +5 -0
  14. package/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m +2 -0
  15. package/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.m +1 -1
  16. package/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h +34 -0
  17. package/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m +76 -4
  18. package/WebDriverAgentLib/Categories/XCUIDevice+FBRotation.h +1 -1
  19. package/WebDriverAgentLib/Categories/XCUIDevice+FBRotation.m +1 -1
  20. package/WebDriverAgentLib/Categories/XCUIElement+FBPickerWheel.m +1 -1
  21. package/WebDriverAgentLib/Categories/XCUIElement+FBTyping.m +11 -0
  22. package/WebDriverAgentLib/Commands/FBCustomCommands.m +55 -4
  23. package/WebDriverAgentLib/Commands/FBElementCommands.m +6 -5
  24. package/WebDriverAgentLib/Commands/FBOrientationCommands.m +1 -1
  25. package/WebDriverAgentLib/Commands/FBSessionCommands.m +29 -5
  26. package/WebDriverAgentLib/Info.plist +2 -2
  27. package/WebDriverAgentLib/Routing/FBTCPSocket.h +70 -0
  28. package/WebDriverAgentLib/Routing/FBTCPSocket.m +199 -0
  29. package/WebDriverAgentLib/Routing/FBWebServer.m +33 -3
  30. package/WebDriverAgentLib/Routing/WatchOS/FBWatchHTTPServer.h +68 -0
  31. package/WebDriverAgentLib/Routing/WatchOS/FBWatchHTTPServer.m +376 -0
  32. package/WebDriverAgentLib/Routing/WatchOS/RouteRequest.h +30 -0
  33. package/WebDriverAgentLib/Routing/WatchOS/RouteRequest.m +25 -0
  34. package/WebDriverAgentLib/Routing/WatchOS/RouteResponse.h +30 -0
  35. package/WebDriverAgentLib/Routing/WatchOS/RouteResponse.m +54 -0
  36. package/WebDriverAgentLib/Utilities/FBActiveAppDetectionPoint.m +8 -0
  37. package/WebDriverAgentLib/Utilities/FBBaseActionsSynthesizer.h +1 -1
  38. package/WebDriverAgentLib/Utilities/FBBaseActionsSynthesizer.m +1 -1
  39. package/WebDriverAgentLib/Utilities/FBConfiguration.h +1 -1
  40. package/WebDriverAgentLib/Utilities/FBConfiguration.m +3 -3
  41. package/WebDriverAgentLib/Utilities/FBImageProcessor.m +7 -2
  42. package/WebDriverAgentLib/Utilities/FBMacros.h +14 -5
  43. package/WebDriverAgentLib/Utilities/FBMathUtils.h +1 -1
  44. package/WebDriverAgentLib/Utilities/FBMathUtils.m +1 -1
  45. package/WebDriverAgentLib/Utilities/FBPasteboard.h +1 -1
  46. package/WebDriverAgentLib/Utilities/FBPasteboard.m +1 -1
  47. package/WebDriverAgentLib/Utilities/FBSettingsHandler.m +2 -2
  48. package/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.h +1 -1
  49. package/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.m +1 -1
  50. package/WebDriverAgentTests/IntegrationApp_watchOS/ContentView.swift +31 -0
  51. package/WebDriverAgentTests/IntegrationApp_watchOS/WatchSpikeApp.swift +18 -0
  52. package/WebDriverAgentTests/IntegrationTests/FBForceTouchTests.m +3 -0
  53. package/WebDriverAgentTests/IntegrationTests/FBPasteboardTests.m +3 -2
  54. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAAlertIntegrationTests.swift +23 -0
  55. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAAppLifecycleIntegrationTests.swift +57 -0
  56. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAClickIntegrationTests.swift +31 -0
  57. package/WebDriverAgentTests/IntegrationTests_watchOS/WDADeviceIntegrationTests.swift +113 -0
  58. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAElementAttributeIntegrationTests.swift +57 -0
  59. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAFindIntegrationTests.swift +126 -0
  60. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAScreenshotAndSourceIntegrationTests.swift +52 -0
  61. package/WebDriverAgentTests/IntegrationTests_watchOS/WDASessionIntegrationTests.swift +72 -0
  62. package/WebDriverAgentTests/IntegrationTests_watchOS/WDATypingIntegrationTests.swift +57 -0
  63. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAUnknownCommandIntegrationTests.swift +24 -0
  64. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAWatchHTTPClient.swift +95 -0
  65. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAWatchIntegrationTestCase.swift +61 -0
  66. package/package.json +1 -1
@@ -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
  {
@@ -10,7 +10,7 @@
10
10
 
11
11
  NS_ASSUME_NONNULL_BEGIN
12
12
 
13
- #if !TARGET_OS_TV
13
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
14
14
  @interface FBPasteboard : NSObject
15
15
 
16
16
  /**
@@ -20,7 +20,7 @@
20
20
  // Must not be less than FB_MONTORING_INTERVAL in FBAlertsMonitor
21
21
  #define ALERT_CHECK_INTERVAL_SEC 2
22
22
 
23
- #if !TARGET_OS_TV
23
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
24
24
  @implementation FBPasteboard
25
25
 
26
26
  + (BOOL)setData:(NSData *)data forType:(NSString *)type error:(NSError **)error
@@ -186,7 +186,7 @@ static NSSet<NSString *> *FBNilClearableSettingKeys(void)
186
186
  FBConfiguration.sharedInstance.limitXpathContextScope = [value boolValue];
187
187
  return nil;
188
188
  };
189
- #if !TARGET_OS_TV
189
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
190
190
  map[FB_SETTING_SCREENSHOT_ORIENTATION] = ^FBCommandStatus *(FBSession *session, id value) {
191
191
  NSError *error;
192
192
  if (![FBConfiguration.sharedInstance setScreenshotOrientation:(NSString *)value error:&error]) {
@@ -303,7 +303,7 @@ static NSSet<NSString *> *FBNilClearableSettingKeys(void)
303
303
  map[FB_SETTING_LIMIT_XPATH_CONTEXT_SCOPE] = ^id(FBSession *session) {
304
304
  return @(FBConfiguration.sharedInstance.limitXpathContextScope);
305
305
  };
306
- #if !TARGET_OS_TV
306
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
307
307
  map[FB_SETTING_SCREENSHOT_ORIENTATION] = ^id(FBSession *session) {
308
308
  return FBConfiguration.sharedInstance.humanReadableScreenshotOrientation;
309
309
  };
@@ -10,7 +10,7 @@
10
10
 
11
11
  NS_ASSUME_NONNULL_BEGIN
12
12
 
13
- #if !TARGET_OS_TV
13
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
14
14
  @interface FBW3CActionsSynthesizer : FBBaseActionsSynthesizer
15
15
 
16
16
  @end
@@ -63,7 +63,7 @@ static NSString *const FB_KEY_PARAMETERS = @"parameters";
63
63
  static NSString *const FB_KEY_ACTIONS = @"actions";
64
64
 
65
65
 
66
- #if !TARGET_OS_TV
66
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
67
67
  @interface FBW3CGestureItem : FBBaseGestureItem
68
68
 
69
69
  @property (nullable, readonly, nonatomic) FBBaseGestureItem *previousItem;
@@ -0,0 +1,31 @@
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 SwiftUI
10
+
11
+ struct ContentView: View {
12
+ @State private var resultText = "Idle"
13
+ @State private var typedText = ""
14
+
15
+ var body: some View {
16
+ // ScrollView keeps content reachable - WDA's test-launch path shifts the scene down
17
+ // vs. a plain launch, which can push a plain VStack off-screen.
18
+ ScrollView {
19
+ VStack(spacing: 8) {
20
+ Text(resultText)
21
+ .accessibilityIdentifier("resultLabel")
22
+ Button("Tap Me") {
23
+ resultText = "Tapped"
24
+ }
25
+ .accessibilityIdentifier("tapMeButton")
26
+ TextField("Type here", text: $typedText)
27
+ .accessibilityIdentifier("typingField")
28
+ }
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,18 @@
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 SwiftUI
10
+
11
+ @main
12
+ struct WatchSpikeApp: App {
13
+ var body: some Scene {
14
+ WindowGroup {
15
+ ContentView()
16
+ }
17
+ }
18
+ }
@@ -57,6 +57,9 @@
57
57
 
58
58
  - (void)testForceTap
59
59
  {
60
+ XCTSkipIf(SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"27.0"),
61
+ @"FIXME: Unstable on platform version 27.");
62
+
60
63
  if (![XCUIDevice sharedDevice].supportsPressureInteraction) {
61
64
  return;
62
65
  }
@@ -51,6 +51,9 @@
51
51
 
52
52
  - (void)testGetPasteboard
53
53
  {
54
+ XCTSkipIf(SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"27.0"),
55
+ @"FIXME: Pasteboard retrieval has opposite button behavior on platform version 27.");
56
+
54
57
  NSString *text = @"Happy copying";
55
58
  XCUIElement *textField = self.testedApplication.textFields[@"aIdentifier"];
56
59
  NSError *error;
@@ -87,5 +90,3 @@
87
90
  }
88
91
 
89
92
  @end
90
-
91
-
@@ -0,0 +1,23 @@
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
10
+
11
+ /// Covers FBAlertViewCommands' error path - IntegrationApp_watchOS never presents a real alert,
12
+ /// so this only exercises the "no alert open" error, not the success path.
13
+ final class WDAAlertIntegrationTests: WDAWatchIntegrationTestCase {
14
+ func testAlertTextWithNoAlertPresentReturnsAnError() throws {
15
+ let response = try client.get("/session/\(sessionId!)/alert/text")
16
+ XCTAssertNotEqual(response.statusCode, 200)
17
+ }
18
+
19
+ func testAlertButtonsWithNoAlertPresentReturnsAnError() throws {
20
+ let response = try client.get("/session/\(sessionId!)/wda/alert/buttons")
21
+ XCTAssertNotEqual(response.statusCode, 200)
22
+ }
23
+ }
@@ -0,0 +1,57 @@
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
10
+
11
+ /// Covers the app lifecycle routes (launch/activate/terminate/state/list). Unlike
12
+ /// /wda/homescreen or /wda/lock, these only ever target IntegrationApp_watchOS, so they're
13
+ /// safe to run repeatedly without disrupting shared simulator state.
14
+ final class WDAAppLifecycleIntegrationTests: WDAWatchIntegrationTestCase {
15
+ // XCUIApplicationState raw values: NotRunning = 1, RunningForeground = 4.
16
+ private static let notRunningState = 1
17
+ private static let runningForegroundState = 4
18
+
19
+ func testAppsListIncludesTheAppUnderTest() throws {
20
+ let response = try client.get("/session/\(sessionId!)/wda/apps/list")
21
+ XCTAssertEqual(response.statusCode, 200)
22
+ guard let apps = response.value as? [[String: Any]] else {
23
+ return XCTFail("Expected an array of active apps: \(String(describing: response.json))")
24
+ }
25
+ XCTAssertTrue(apps.contains { $0["bundleId"] as? String == Self.integrationAppBundleId })
26
+ }
27
+
28
+ func testTerminateThenRelaunchLifecycle() throws {
29
+ let bundleId = Self.integrationAppBundleId
30
+
31
+ let terminateResponse = try client.post("/session/\(sessionId!)/wda/apps/terminate", body: ["bundleId": bundleId])
32
+ XCTAssertEqual(terminateResponse.statusCode, 200)
33
+ XCTAssertEqual(terminateResponse.value as? Bool, true)
34
+
35
+ let stateAfterTerminate = try client.post("/session/\(sessionId!)/wda/apps/state", body: ["bundleId": bundleId])
36
+ XCTAssertEqual(stateAfterTerminate.value as? Int, Self.notRunningState)
37
+
38
+ let launchResponse = try client.post("/session/\(sessionId!)/wda/apps/launch", body: ["bundleId": bundleId])
39
+ XCTAssertEqual(launchResponse.statusCode, 200)
40
+ Thread.sleep(forTimeInterval: 1)
41
+
42
+ let stateAfterLaunch = try client.post("/session/\(sessionId!)/wda/apps/state", body: ["bundleId": bundleId])
43
+ XCTAssertEqual(stateAfterLaunch.value as? Int, Self.runningForegroundState)
44
+
45
+ // The app relaunched fresh, so its accessibility tree should be back too.
46
+ XCTAssertNoThrow(try findElement(byAccessibilityId: "tapMeButton"))
47
+ }
48
+
49
+ func testActivateBringsTheAppToTheForeground() throws {
50
+ let bundleId = Self.integrationAppBundleId
51
+ let activateResponse = try client.post("/session/\(sessionId!)/wda/apps/activate", body: ["bundleId": bundleId])
52
+ XCTAssertEqual(activateResponse.statusCode, 200)
53
+
54
+ let stateResponse = try client.post("/session/\(sessionId!)/wda/apps/state", body: ["bundleId": bundleId])
55
+ XCTAssertEqual(stateResponse.value as? Int, Self.runningForegroundState)
56
+ }
57
+ }
@@ -0,0 +1,31 @@
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
10
+
11
+ /// Covers /element/:uuid/click - the only tap-like interaction registered on watchOS
12
+ /// (gesture synthesis routes aren't).
13
+ final class WDAClickIntegrationTests: WDAWatchIntegrationTestCase {
14
+ func testClickingButtonUpdatesTheResultLabel() throws {
15
+ let labelId = try findElement(byAccessibilityId: "resultLabel")
16
+ XCTAssertEqual(try attributeValue(labelId, "label"), "Idle")
17
+
18
+ let buttonId = try findElement(byAccessibilityId: "tapMeButton")
19
+ let clickResponse = try client.post("/session/\(sessionId!)/element/\(buttonId)/click")
20
+ XCTAssertEqual(clickResponse.statusCode, 200)
21
+
22
+ // Re-find: element UUIDs aren't stable across snapshots, and the label just changed.
23
+ let updatedLabelId = try findElement(byAccessibilityId: "resultLabel")
24
+ XCTAssertEqual(try attributeValue(updatedLabelId, "label"), "Tapped")
25
+ }
26
+
27
+ func testClickingAnElementThatDoesNotExistReturnsAnError() throws {
28
+ let response = try client.post("/session/\(sessionId!)/element/00000000-0000-0000-0000-000000000000/click")
29
+ XCTAssertNotEqual(response.statusCode, 200)
30
+ }
31
+ }
@@ -0,0 +1,113 @@
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
10
+
11
+ /// Covers device-info and location routes. These only read device state or set a simulated
12
+ /// GPS fix, so unlike /wda/lock or /wda/voiceOver/enable, they won't disrupt later tests.
13
+ final class WDADeviceIntegrationTests: WDAWatchIntegrationTestCase {
14
+ func testDeviceInfo() throws {
15
+ let response = try client.get("/session/\(sessionId!)/wda/device/info")
16
+ XCTAssertEqual(response.statusCode, 200)
17
+ XCTAssertFalse((response.valueDict?["name"] as? String ?? "").isEmpty)
18
+ XCTAssertFalse((response.valueDict?["model"] as? String ?? "").isEmpty)
19
+ }
20
+
21
+ func testActiveAppInfo() throws {
22
+ let response = try client.get("/session/\(sessionId!)/wda/activeAppInfo")
23
+ XCTAssertEqual(response.statusCode, 200)
24
+ XCTAssertEqual(response.valueDict?["bundleId"] as? String, Self.integrationAppBundleId)
25
+ XCTAssertGreaterThan(response.valueDict?["pid"] as? Int ?? 0, 0)
26
+ }
27
+
28
+ func testDeviceLocation() throws {
29
+ let response = try client.get("/session/\(sessionId!)/wda/device/location")
30
+ XCTAssertEqual(response.statusCode, 200)
31
+ XCTAssertNotNil(response.valueDict?["latitude"])
32
+ XCTAssertNotNil(response.valueDict?["authorizationStatus"])
33
+ }
34
+
35
+ func testSimulatedLocationRoundTrip() throws {
36
+ let setResponse = try client.post("/session/\(sessionId!)/wda/simulatedLocation", body: [
37
+ "latitude": 37.3230,
38
+ "longitude": -122.0322,
39
+ ])
40
+ XCTAssertEqual(setResponse.statusCode, 200)
41
+
42
+ let getResponse = try client.get("/session/\(sessionId!)/wda/simulatedLocation")
43
+ XCTAssertEqual(getResponse.statusCode, 200)
44
+ XCTAssertEqual(getResponse.valueDict?["latitude"] as? Double ?? 0, 37.3230, accuracy: 0.001)
45
+ XCTAssertEqual(getResponse.valueDict?["longitude"] as? Double ?? 0, -122.0322, accuracy: 0.001)
46
+
47
+ let clearResponse = try client.delete("/session/\(sessionId!)/wda/simulatedLocation")
48
+ XCTAssertEqual(clearResponse.statusCode, 200)
49
+ }
50
+
51
+ /// rotateDigitalCrownByDelta:/performHandGesture: were only added to the SDK in Xcode 16.3 - WDA calls
52
+ /// them dynamically (NSInvocation) so it keeps building on older Xcode too, but they only actually work
53
+ /// when the toolchain that built this very test bundle (same build as the runner) is new enough. Checked
54
+ /// the same way WDA itself does, via responds(to:), rather than hardcoding an Xcode/OS version number here.
55
+ private var supportsDigitalCrownAndHandGesture: Bool {
56
+ XCUIDevice.shared.responds(to: Selector(("rotateDigitalCrownByDelta:")))
57
+ }
58
+
59
+ func testRotateDigitalCrown() throws {
60
+ let response = try client.post("/session/\(sessionId!)/wda/rotateDigitalCrown", body: [
61
+ "delta": 0.2,
62
+ "velocity": 1.0,
63
+ ])
64
+ XCTAssertEqual(response.statusCode, supportsDigitalCrownAndHandGesture ? 200 : 500)
65
+ }
66
+
67
+ func testRotateDigitalCrownDefaultVelocity() throws {
68
+ let response = try client.post("/session/\(sessionId!)/wda/rotateDigitalCrown", body: [
69
+ "delta": -0.2,
70
+ ])
71
+ XCTAssertEqual(response.statusCode, supportsDigitalCrownAndHandGesture ? 200 : 500)
72
+ }
73
+
74
+ func testRotateDigitalCrownMissingDelta() throws {
75
+ // Argument validation happens before the dynamic dispatch check, so this is always a 400.
76
+ let response = try client.post("/session/\(sessionId!)/wda/rotateDigitalCrown")
77
+ XCTAssertEqual(response.statusCode, 400)
78
+ }
79
+
80
+ func testPerformHandGestureDoubleTap() throws {
81
+ let response = try client.post("/session/\(sessionId!)/wda/performHandGesture", body: [
82
+ "name": "doubleTap",
83
+ ])
84
+ XCTAssertEqual(response.statusCode, supportsDigitalCrownAndHandGesture ? 200 : 500)
85
+ }
86
+
87
+ func testPerformHandGestureFlick() throws {
88
+ let response = try client.post("/session/\(sessionId!)/wda/performHandGesture", body: [
89
+ "name": "flick",
90
+ ])
91
+ // flick additionally needs watchOS 26+ (still internally versioned 12.0 pre-rename) on top of the
92
+ // Xcode 16.3+ toolchain floor - below that OS version it isn't even advertised as a supported name,
93
+ // so the server rejects it the same way it would reject any other unknown gesture name.
94
+ if #available(watchOS 12.0, *), supportsDigitalCrownAndHandGesture {
95
+ XCTAssertEqual(response.statusCode, 200)
96
+ } else {
97
+ XCTAssertEqual(response.statusCode, 500)
98
+ }
99
+ }
100
+
101
+ func testPerformHandGestureUnsupportedName() throws {
102
+ // Not a real gesture name in any environment, so always rejected regardless of toolchain/OS version.
103
+ let response = try client.post("/session/\(sessionId!)/wda/performHandGesture", body: [
104
+ "name": "clench",
105
+ ])
106
+ XCTAssertEqual(response.statusCode, 500)
107
+ }
108
+
109
+ func testPerformHandGestureMissingName() throws {
110
+ let response = try client.post("/session/\(sessionId!)/wda/performHandGesture")
111
+ XCTAssertEqual(response.statusCode, 400)
112
+ }
113
+ }
@@ -0,0 +1,57 @@
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
10
+
11
+ /// Covers FBElementCommands' read-only element/window attribute routes.
12
+ final class WDAElementAttributeIntegrationTests: WDAWatchIntegrationTestCase {
13
+ func testWindowSizeAndRect() throws {
14
+ let sizeResponse = try client.get("/session/\(sessionId!)/window/size")
15
+ XCTAssertEqual(sizeResponse.statusCode, 200)
16
+ XCTAssertGreaterThan(sizeResponse.valueDict?["width"] as? Double ?? 0, 0)
17
+ XCTAssertGreaterThan(sizeResponse.valueDict?["height"] as? Double ?? 0, 0)
18
+
19
+ let rectResponse = try client.get("/session/\(sessionId!)/window/rect")
20
+ XCTAssertEqual(rectResponse.statusCode, 200)
21
+ XCTAssertGreaterThan(rectResponse.valueDict?["width"] as? Double ?? 0, 0)
22
+ }
23
+
24
+ func testButtonAttributes() throws {
25
+ let buttonId = try findElement(byAccessibilityId: "tapMeButton")
26
+
27
+ let enabledResponse = try client.get("/session/\(sessionId!)/element/\(buttonId)/enabled")
28
+ XCTAssertEqual(enabledResponse.value as? Bool, true)
29
+
30
+ let displayedResponse = try client.get("/session/\(sessionId!)/element/\(buttonId)/displayed")
31
+ XCTAssertEqual(displayedResponse.value as? Bool, true)
32
+
33
+ let rectResponse = try client.get("/session/\(sessionId!)/element/\(buttonId)/rect")
34
+ XCTAssertEqual(rectResponse.statusCode, 200)
35
+ XCTAssertGreaterThan(rectResponse.valueDict?["width"] as? Double ?? 0, 0)
36
+
37
+ let nameResponse = try client.get("/session/\(sessionId!)/element/\(buttonId)/name")
38
+ XCTAssertEqual(nameResponse.valueString, "XCUIElementTypeButton")
39
+
40
+ let labelResponse = try client.get("/session/\(sessionId!)/element/\(buttonId)/attribute/label")
41
+ XCTAssertEqual(labelResponse.valueString, "Tap Me")
42
+
43
+ let accessibleResponse = try client.get("/session/\(sessionId!)/wda/element/\(buttonId)/accessible")
44
+ XCTAssertEqual(accessibleResponse.statusCode, 200)
45
+ }
46
+
47
+ func testResultLabelTextAndSelectedState() throws {
48
+ let labelId = try findElement(byAccessibilityId: "resultLabel")
49
+
50
+ let textResponse = try client.get("/session/\(sessionId!)/element/\(labelId)/text")
51
+ XCTAssertEqual(textResponse.valueString, "Idle")
52
+
53
+ let selectedResponse = try client.get("/session/\(sessionId!)/element/\(labelId)/selected")
54
+ XCTAssertEqual(selectedResponse.statusCode, 200)
55
+ XCTAssertEqual(selectedResponse.value as? Bool, false)
56
+ }
57
+ }