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
@@ -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,50 @@
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
+ }
@@ -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
+ }
@@ -0,0 +1,126 @@
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 FBFindElementCommands: find/sub-find/active-element, not-found errors, and every
12
+ /// locator strategy (accessibility id/name/id, class name, class chain, xpath, predicate
13
+ /// string, link text). getVisibleCells is skipped - no table/collection view to test it against.
14
+ final class WDAFindIntegrationTests: WDAWatchIntegrationTestCase {
15
+ func testFindElementByAccessibilityId() throws {
16
+ let elementId = try findElement(byAccessibilityId: "tapMeButton")
17
+ XCTAssertFalse(elementId.isEmpty)
18
+ }
19
+
20
+ func testFindElementByNameAndIdAliases() throws {
21
+ // "name" and "id" are aliases for "accessibility id".
22
+ for using in ["name", "id"] {
23
+ let response = try client.post("/session/\(sessionId!)/element", body: [
24
+ "using": using,
25
+ "value": "tapMeButton",
26
+ ])
27
+ XCTAssertEqual(response.statusCode, 200, "using: \(using)")
28
+ XCTAssertNotNil(response.valueDict?["ELEMENT"], "using: \(using)")
29
+ }
30
+ }
31
+
32
+ func testFindElementByPredicateString() throws {
33
+ let response = try client.post("/session/\(sessionId!)/element", body: [
34
+ "using": "predicate string",
35
+ "value": "label == \"Tap Me\"",
36
+ ])
37
+ XCTAssertEqual(response.statusCode, 200)
38
+ XCTAssertNotNil(response.valueDict?["ELEMENT"])
39
+ }
40
+
41
+ func testFindElementsByClassChain() throws {
42
+ let response = try client.post("/session/\(sessionId!)/elements", body: [
43
+ "using": "class chain",
44
+ "value": "**/XCUIElementTypeButton",
45
+ ])
46
+ XCTAssertEqual(response.statusCode, 200)
47
+ XCTAssertGreaterThanOrEqual((response.value as? [[String: Any]])?.count ?? 0, 1)
48
+ }
49
+
50
+ func testFindElementByXPath() throws {
51
+ let response = try client.post("/session/\(sessionId!)/element", body: [
52
+ "using": "xpath",
53
+ "value": "//XCUIElementTypeButton[@name=\"tapMeButton\"]",
54
+ ])
55
+ XCTAssertEqual(response.statusCode, 200)
56
+ XCTAssertNotNil(response.valueDict?["ELEMENT"])
57
+ }
58
+
59
+ func testFindElementByLinkTextAndPartialLinkText() throws {
60
+ // WDA matches (partial) link text against `name`, not an actual hyperlink search.
61
+ for using in ["link text", "partial link text"] {
62
+ let value = using == "link text" ? "tapMeButton" : "tapMe"
63
+ let response = try client.post("/session/\(sessionId!)/element", body: [
64
+ "using": using,
65
+ "value": value,
66
+ ])
67
+ XCTAssertEqual(response.statusCode, 200, "using: \(using)")
68
+ XCTAssertNotNil(response.valueDict?["ELEMENT"], "using: \(using)")
69
+ }
70
+ }
71
+
72
+ func testFindElementThatDoesNotExistReturnsAnError() throws {
73
+ let response = try client.post("/session/\(sessionId!)/element", body: [
74
+ "using": "accessibility id",
75
+ "value": "thisElementDoesNotExist",
76
+ ])
77
+ XCTAssertNotEqual(response.statusCode, 200)
78
+ }
79
+
80
+ func testFindElements() throws {
81
+ let response = try client.post("/session/\(sessionId!)/elements", body: [
82
+ "using": "class name",
83
+ "value": "XCUIElementTypeButton",
84
+ ])
85
+ XCTAssertEqual(response.statusCode, 200)
86
+ guard let elements = response.value as? [[String: Any]] else {
87
+ return XCTFail("Expected an array of elements: \(String(describing: response.json))")
88
+ }
89
+ XCTAssertGreaterThanOrEqual(elements.count, 1)
90
+ }
91
+
92
+ func testFindSubElementAndSubElements() throws {
93
+ let windowResponse = try client.post("/session/\(sessionId!)/element", body: [
94
+ "using": "class name",
95
+ "value": "XCUIElementTypeWindow",
96
+ ])
97
+ guard let windowId = windowResponse.valueDict?["ELEMENT"] as? String else {
98
+ return XCTFail("Could not find the root window element")
99
+ }
100
+
101
+ let subElementResponse = try client.post("/session/\(sessionId!)/element/\(windowId)/element", body: [
102
+ "using": "accessibility id",
103
+ "value": "tapMeButton",
104
+ ])
105
+ XCTAssertEqual(subElementResponse.statusCode, 200)
106
+ XCTAssertNotNil(subElementResponse.valueDict?["ELEMENT"])
107
+
108
+ let subElementsResponse = try client.post("/session/\(sessionId!)/element/\(windowId)/elements", body: [
109
+ "using": "class name",
110
+ "value": "XCUIElementTypeButton",
111
+ ])
112
+ XCTAssertEqual(subElementsResponse.statusCode, 200)
113
+ XCTAssertGreaterThanOrEqual((subElementsResponse.value as? [[String: Any]])?.count ?? 0, 1)
114
+ }
115
+
116
+ func testActiveElementResolvesToTheFocusedTextField() throws {
117
+ let fieldId = try findElement(byAccessibilityId: "typingField")
118
+ try client.post("/session/\(sessionId!)/element/\(fieldId)/click")
119
+ // Give the keyboard-sheet transition a moment to finish before checking focus.
120
+ Thread.sleep(forTimeInterval: 1)
121
+
122
+ let response = try client.get("/session/\(sessionId!)/element/active")
123
+ XCTAssertEqual(response.statusCode, 200)
124
+ XCTAssertNotNil(response.valueDict?["ELEMENT"])
125
+ }
126
+ }
@@ -0,0 +1,52 @@
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/element screenshots and /source, /wda/accessibleSource.
12
+ final class WDAScreenshotAndSourceIntegrationTests: WDAWatchIntegrationTestCase {
13
+ private static let pngMagicBytes: [UInt8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
14
+
15
+ private func assertLooksLikeAPNG(_ base64String: String?, file: StaticString = #filePath, line: UInt = #line) {
16
+ guard let base64String = base64String, let data = Data(base64Encoded: base64String) else {
17
+ return XCTFail("Response value was not base64 data", file: file, line: line)
18
+ }
19
+ XCTAssertGreaterThan(data.count, Self.pngMagicBytes.count, file: file, line: line)
20
+ XCTAssertEqual(Array(data.prefix(Self.pngMagicBytes.count)), Self.pngMagicBytes, "response was not a PNG", file: file, line: line)
21
+ }
22
+
23
+ func testDeviceScreenshot() throws {
24
+ let response = try client.get("/session/\(sessionId!)/screenshot")
25
+ XCTAssertEqual(response.statusCode, 200)
26
+ assertLooksLikeAPNG(response.valueString)
27
+ }
28
+
29
+ func testElementScreenshot() throws {
30
+ let buttonId = try findElement(byAccessibilityId: "tapMeButton")
31
+ let response = try client.get("/session/\(sessionId!)/element/\(buttonId)/screenshot")
32
+ XCTAssertEqual(response.statusCode, 200)
33
+ assertLooksLikeAPNG(response.valueString)
34
+ }
35
+
36
+ func testPageSourceContainsTheKnownElements() throws {
37
+ let response = try client.get("/session/\(sessionId!)/source?format=xml")
38
+ XCTAssertEqual(response.statusCode, 200)
39
+ guard let xml = response.valueString else {
40
+ return XCTFail("No XML source returned")
41
+ }
42
+ XCTAssertTrue(xml.contains("tapMeButton"))
43
+ XCTAssertTrue(xml.contains("resultLabel"))
44
+ XCTAssertTrue(xml.contains("typingField"))
45
+ }
46
+
47
+ func testAccessibleSource() throws {
48
+ let response = try client.get("/session/\(sessionId!)/wda/accessibleSource")
49
+ XCTAssertEqual(response.statusCode, 200)
50
+ XCTAssertNotNil(response.value)
51
+ }
52
+ }
@@ -0,0 +1,72 @@
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 FBSessionCommands. Manages its own session lifecycle per test, since that's what's
12
+ /// under test here, rather than using WDAWatchIntegrationTestCase.
13
+ final class WDASessionIntegrationTests: XCTestCase {
14
+ let client = WDAWatchHTTPClient()
15
+
16
+ override func setUp() {
17
+ super.setUp()
18
+ continueAfterFailure = false
19
+ }
20
+
21
+ func testStatusIsReadyWithoutASession() throws {
22
+ let response = try client.get("/status")
23
+ XCTAssertEqual(response.statusCode, 200)
24
+ XCTAssertEqual(response.valueDict?["ready"] as? Bool, true)
25
+ XCTAssertEqual(response.valueDict?["state"] as? String, "success")
26
+ }
27
+
28
+ func testHealthCheck() throws {
29
+ let response = try client.get("/wda/healthcheck")
30
+ XCTAssertEqual(response.statusCode, 200)
31
+ }
32
+
33
+ func testCreateGetActiveAndDeleteSessionLifecycle() throws {
34
+ let createResponse = try client.post("/session", body: [
35
+ "capabilities": ["alwaysMatch": ["bundleId": WDAWatchIntegrationTestCase.integrationAppBundleId]]
36
+ ])
37
+ XCTAssertEqual(createResponse.statusCode, 200)
38
+ guard let sessionId = createResponse.valueDict?["sessionId"] as? String else {
39
+ return XCTFail("No sessionId in create-session response: \(String(describing: createResponse.json))")
40
+ }
41
+ XCTAssertFalse(sessionId.isEmpty)
42
+
43
+ let activeResponse = try client.get("/session/\(sessionId)")
44
+ XCTAssertEqual(activeResponse.statusCode, 200)
45
+ XCTAssertEqual(activeResponse.valueDict?["sessionId"] as? String, sessionId)
46
+
47
+ let deleteResponse = try client.delete("/session/\(sessionId)")
48
+ XCTAssertEqual(deleteResponse.statusCode, 200)
49
+ }
50
+
51
+ func testTimeoutsAndAppiumSettingsRoundTrip() throws {
52
+ let createResponse = try client.post("/session", body: [
53
+ "capabilities": ["alwaysMatch": ["bundleId": WDAWatchIntegrationTestCase.integrationAppBundleId]]
54
+ ])
55
+ guard let sessionId = createResponse.valueDict?["sessionId"] as? String else {
56
+ return XCTFail("No sessionId in create-session response")
57
+ }
58
+ defer { _ = try? client.delete("/session/\(sessionId)") }
59
+
60
+ let timeoutsResponse = try client.post("/session/\(sessionId)/timeouts", body: ["implicit": 500])
61
+ XCTAssertEqual(timeoutsResponse.statusCode, 200)
62
+
63
+ let getSettingsResponse = try client.get("/session/\(sessionId)/appium/settings")
64
+ XCTAssertEqual(getSettingsResponse.statusCode, 200)
65
+ XCTAssertNotNil(getSettingsResponse.valueDict)
66
+
67
+ let setSettingsResponse = try client.post("/session/\(sessionId)/appium/settings", body: [
68
+ "settings": ["shouldUseCompactResponses": false]
69
+ ])
70
+ XCTAssertEqual(setSettingsResponse.statusCode, 200)
71
+ }
72
+ }