appium-webdriveragent 16.4.0 → 16.5.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 (55) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/Scripts/build-webdriveragent.mjs +9 -9
  3. package/Scripts/build.sh +8 -1
  4. package/WebDriverAgent.xcodeproj/project.pbxproj +125 -107
  5. package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/IntegrationTests_watchOS.xcscheme +4 -4
  6. package/WebDriverAgentLib/FBAlert.m +8 -6
  7. package/WebDriverAgentLib/Info.plist +2 -2
  8. package/WebDriverAgentTests/IntegrationTests_watchOS/IntegrationTests_watchOS-Bridging-Header.h +21 -0
  9. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAAlertIntegrationTests.swift +11 -9
  10. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAAppLifecycleIntegrationTests.swift +18 -38
  11. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAClickIntegrationTests.swift +11 -17
  12. package/WebDriverAgentTests/IntegrationTests_watchOS/WDADeviceIntegrationTests.swift +50 -71
  13. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAElementAttributeIntegrationTests.swift +23 -41
  14. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAFindIntegrationTests.swift +70 -95
  15. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAScreenshotAndSourceIntegrationTests.swift +24 -22
  16. package/WebDriverAgentTests/IntegrationTests_watchOS/WDATypingIntegrationTests.swift +44 -28
  17. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAWatchInProcessTestCase.swift +51 -0
  18. package/build/lib/check-dependencies.d.ts.map +1 -1
  19. package/build/lib/check-dependencies.js +21 -7
  20. package/build/lib/check-dependencies.js.map +1 -1
  21. package/build/lib/constants.d.ts +1 -2
  22. package/build/lib/constants.d.ts.map +1 -1
  23. package/build/lib/constants.js +1 -2
  24. package/build/lib/constants.js.map +1 -1
  25. package/build/lib/types.d.ts +2 -2
  26. package/build/lib/types.d.ts.map +1 -1
  27. package/build/lib/utils/index.d.ts +1 -1
  28. package/build/lib/utils/index.d.ts.map +1 -1
  29. package/build/lib/utils/index.js +1 -1
  30. package/build/lib/utils/index.js.map +1 -1
  31. package/build/lib/utils/platform.d.ts +12 -0
  32. package/build/lib/utils/platform.d.ts.map +1 -1
  33. package/build/lib/utils/platform.js +23 -1
  34. package/build/lib/utils/platform.js.map +1 -1
  35. package/build/lib/utils/xctestrun.js +27 -3
  36. package/build/lib/utils/xctestrun.js.map +1 -1
  37. package/build/lib/webdriveragent.d.ts.map +1 -1
  38. package/build/lib/webdriveragent.js +4 -1
  39. package/build/lib/webdriveragent.js.map +1 -1
  40. package/build/lib/xcodebuild.d.ts.map +1 -1
  41. package/build/lib/xcodebuild.js +19 -9
  42. package/build/lib/xcodebuild.js.map +1 -1
  43. package/lib/check-dependencies.ts +23 -7
  44. package/lib/constants.ts +1 -2
  45. package/lib/types.ts +4 -2
  46. package/lib/utils/index.ts +1 -1
  47. package/lib/utils/platform.ts +25 -1
  48. package/lib/utils/xctestrun.ts +29 -3
  49. package/lib/webdriveragent.ts +5 -1
  50. package/lib/xcodebuild.ts +27 -12
  51. package/package.json +3 -2
  52. package/WebDriverAgentTests/IntegrationTests_watchOS/WDASessionIntegrationTests.swift +0 -72
  53. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAUnknownCommandIntegrationTests.swift +0 -24
  54. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAWatchHTTPClient.swift +0 -95
  55. package/WebDriverAgentTests/IntegrationTests_watchOS/WDAWatchIntegrationTestCase.swift +0 -61
@@ -14,7 +14,7 @@
14
14
  buildForAnalyzing = "NO">
15
15
  <BuildableReference
16
16
  BuildableIdentifier = "primary"
17
- BlueprintIdentifier = "4632809915F58C9095914E85"
17
+ BlueprintIdentifier = "5D9539D7055B5E6EED56D100"
18
18
  BuildableName = "IntegrationTests_watchOS.xctest"
19
19
  BlueprintName = "IntegrationTests_watchOS"
20
20
  ReferencedContainer = "container:WebDriverAgent.xcodeproj">
@@ -32,7 +32,7 @@
32
32
  skipped = "NO">
33
33
  <BuildableReference
34
34
  BuildableIdentifier = "primary"
35
- BlueprintIdentifier = "4632809915F58C9095914E85"
35
+ BlueprintIdentifier = "5D9539D7055B5E6EED56D100"
36
36
  BuildableName = "IntegrationTests_watchOS.xctest"
37
37
  BlueprintName = "IntegrationTests_watchOS"
38
38
  ReferencedContainer = "container:WebDriverAgent.xcodeproj">
@@ -53,7 +53,7 @@
53
53
  <MacroExpansion>
54
54
  <BuildableReference
55
55
  BuildableIdentifier = "primary"
56
- BlueprintIdentifier = "4632809915F58C9095914E85"
56
+ BlueprintIdentifier = "5D9539D7055B5E6EED56D100"
57
57
  BuildableName = "IntegrationTests_watchOS.xctest"
58
58
  BlueprintName = "IntegrationTests_watchOS"
59
59
  ReferencedContainer = "container:WebDriverAgent.xcodeproj">
@@ -69,7 +69,7 @@
69
69
  <MacroExpansion>
70
70
  <BuildableReference
71
71
  BuildableIdentifier = "primary"
72
- BlueprintIdentifier = "4632809915F58C9095914E85"
72
+ BlueprintIdentifier = "5D9539D7055B5E6EED56D100"
73
73
  BuildableName = "IntegrationTests_watchOS.xctest"
74
74
  BlueprintName = "IntegrationTests_watchOS"
75
75
  ReferencedContainer = "container:WebDriverAgent.xcodeproj">
@@ -236,17 +236,18 @@
236
236
  }
237
237
 
238
238
  XCUIElement *acceptButton = nil;
239
- if (FBConfiguration.sharedInstance.acceptAlertButtonSelector.length) {
239
+ NSString *acceptAlertButtonSelector = FBConfiguration.sharedInstance.acceptAlertButtonSelector ?: @"";
240
+ if (acceptAlertButtonSelector.length) {
240
241
  NSString *errorReason = nil;
241
242
  @try {
242
- acceptButton = [[alertElement fb_descendantsMatchingClassChain:FBConfiguration.sharedInstance.acceptAlertButtonSelector
243
+ acceptButton = [[alertElement fb_descendantsMatchingClassChain:acceptAlertButtonSelector
243
244
  shouldReturnAfterFirstMatch:YES] firstObject];
244
245
  } @catch (NSException *ex) {
245
246
  errorReason = ex.reason;
246
247
  }
247
248
  if (nil == acceptButton) {
248
249
  [FBLogger logFmt:@"Cannot find any match for Accept alert button using the class chain selector '%@'",
249
- FBConfiguration.sharedInstance.acceptAlertButtonSelector];
250
+ acceptAlertButtonSelector];
250
251
  if (nil != errorReason) {
251
252
  [FBLogger logFmt:@"Original error: %@", errorReason];
252
253
  }
@@ -293,17 +294,18 @@
293
294
  }
294
295
 
295
296
  XCUIElement *dismissButton = nil;
296
- if (FBConfiguration.sharedInstance.dismissAlertButtonSelector.length) {
297
+ NSString *dismissAlertButtonSelector = FBConfiguration.sharedInstance.dismissAlertButtonSelector ?: @"";
298
+ if (dismissAlertButtonSelector.length) {
297
299
  NSString *errorReason = nil;
298
300
  @try {
299
- dismissButton = [[alertElement fb_descendantsMatchingClassChain:FBConfiguration.sharedInstance.dismissAlertButtonSelector
301
+ dismissButton = [[alertElement fb_descendantsMatchingClassChain:dismissAlertButtonSelector
300
302
  shouldReturnAfterFirstMatch:YES] firstObject];
301
303
  } @catch (NSException *ex) {
302
304
  errorReason = ex.reason;
303
305
  }
304
306
  if (nil == dismissButton) {
305
307
  [FBLogger logFmt:@"Cannot find any match for Dismiss alert button using the class chain selector '%@'",
306
- FBConfiguration.sharedInstance.dismissAlertButtonSelector];
308
+ dismissAlertButtonSelector];
307
309
  if (nil != errorReason) {
308
310
  [FBLogger logFmt:@"Original error: %@", errorReason];
309
311
  }
@@ -15,11 +15,11 @@
15
15
  <key>CFBundlePackageType</key>
16
16
  <string>FMWK</string>
17
17
  <key>CFBundleShortVersionString</key>
18
- <string>16.4.0</string>
18
+ <string>16.5.0</string>
19
19
  <key>CFBundleSignature</key>
20
20
  <string>????</string>
21
21
  <key>CFBundleVersion</key>
22
- <string>16.4.0</string>
22
+ <string>16.5.0</string>
23
23
  <key>NSPrincipalClass</key>
24
24
  <string/>
25
25
  </dict>
@@ -0,0 +1,21 @@
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
+ // Exposes the WebDriverAgentLib_watchOS categories these tests call directly, in-process,
10
+ // instead of going through the HTTP server.
11
+
12
+ #import "XCUIElement+FBFind.h"
13
+ #import "XCUIElement+FBClassChain.h"
14
+ #import "XCUIElement+FBIsVisible.h"
15
+ #import "XCUIElement+FBUtilities.h"
16
+ #import "XCUIElement+FBWebDriverAttributes.h"
17
+ #import "XCUIElement+FBTyping.h"
18
+ #import "XCUIApplication+FBHelpers.h"
19
+ #import "XCUIDevice+FBHelpers.h"
20
+ #import "FBAlert.h"
21
+ #import "FBElement.h"
@@ -8,16 +8,18 @@
8
8
 
9
9
  import XCTest
10
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)
11
+ /// Covers FBAlert's no-alert-present behavior directly, in-process. IntegrationApp_watchOS never
12
+ /// presents a real alert, so this only exercises the "no alert open" path (`text`/`buttonLabels`
13
+ /// both just return nil - no HTTP-error-style throw at this layer), not the success path.
14
+ final class WDAAlertIntegrationTests: WDAWatchInProcessTestCase {
15
+ // All reads, no taps/typing - reuse the same running app across the whole class.
16
+ override class var relaunchForEachTest: Bool { false }
17
+
18
+ func testAlertTextWithNoAlertPresentReturnsNil() {
19
+ XCTAssertNil(FBAlert(application: app).text())
17
20
  }
18
21
 
19
- func testAlertButtonsWithNoAlertPresentReturnsAnError() throws {
20
- let response = try client.get("/session/\(sessionId!)/wda/alert/buttons")
21
- XCTAssertNotEqual(response.statusCode, 200)
22
+ func testAlertButtonsWithNoAlertPresentReturnsNil() {
23
+ XCTAssertNil(FBAlert(application: app).buttonLabels())
22
24
  }
23
25
  }
@@ -8,50 +8,30 @@
8
8
 
9
9
  import XCTest
10
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 })
11
+ /// Covers the app lifecycle routes (launch/activate/terminate/state/list). FBSession's
12
+ /// implementations of these are themselves thin wrappers over plain XCTest XCUIApplication
13
+ /// APIs (see FBSession.m), so this calls `.launch()`/`.activate()`/`.terminate()`/`.state`
14
+ /// directly instead of going through the HTTP server; only the apps-list route has a real WDA
15
+ /// category behind it (`+fb_activeAppsInfo`).
16
+ final class WDAAppLifecycleIntegrationTests: WDAWatchInProcessTestCase {
17
+ func testAppsListIncludesTheAppUnderTest() {
18
+ let apps = XCUIApplication.fb_activeAppsInfo()
19
+ XCTAssertTrue(apps.contains { $0["bundleId"] as? String == WDAWatchInProcessTestCase.integrationAppBundleId })
26
20
  }
27
21
 
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)
22
+ func testTerminateThenRelaunchLifecycle() {
23
+ app.terminate()
24
+ XCTAssertEqual(app.state, .notRunning)
37
25
 
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)
26
+ app.launch()
27
+ XCTAssertEqual(app.state, .runningForeground)
44
28
 
45
29
  // The app relaunched fresh, so its accessibility tree should be back too.
46
- XCTAssertNoThrow(try findElement(byAccessibilityId: "tapMeButton"))
30
+ XCTAssertTrue(app.buttons["tapMeButton"].waitForExistence(timeout: 15))
47
31
  }
48
32
 
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)
33
+ func testActivateBringsTheAppToTheForeground() {
34
+ app.activate()
35
+ XCTAssertEqual(app.state, .runningForeground)
56
36
  }
57
37
  }
@@ -8,24 +8,18 @@
8
8
 
9
9
  import XCTest
10
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")
11
+ /// Covers /element/:uuid/click, which on watchOS (like iOS) is just standard XCTest `-tap`
12
+ /// (see FBElementCommands.handleClick:) - so this just calls `.tap()` directly, the same way
13
+ /// the iOS/tvOS integration tests do. The "click a nonexistent element UUID" case isn't a `tap`
14
+ /// behavior at all (it's FBElementCache's UUID-lookup failure), so it's covered by the HTTP
15
+ /// end-to-end tests instead, where there's an actual UUID/route layer to miss against.
16
+ final class WDAClickIntegrationTests: WDAWatchInProcessTestCase {
17
+ func testClickingButtonUpdatesTheResultLabel() {
18
+ let label = app.staticTexts["resultLabel"]
19
+ XCTAssertEqual(label.wdValue ?? label.wdLabel, "Idle")
17
20
 
18
- let buttonId = try findElement(byAccessibilityId: "tapMeButton")
19
- let clickResponse = try client.post("/session/\(sessionId!)/element/\(buttonId)/click")
20
- XCTAssertEqual(clickResponse.statusCode, 200)
21
+ app.buttons["tapMeButton"].tap()
21
22
 
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)
23
+ XCTAssertEqual(label.wdValue ?? label.wdLabel, "Tapped")
30
24
  }
31
25
  }
@@ -6,46 +6,37 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
 
9
+ import CoreLocation
10
+ import WatchKit
9
11
  import XCTest
10
12
 
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)
13
+ /// Covers device-info, active-app-info, simulated location, digital crown rotation, and hand
14
+ /// gesture, calling the same underlying APIs FBCustomCommands/XCUIDevice+FBHelpers use directly
15
+ /// in-process. The real (non-simulated) GPS location route and the two pure argument-validation
16
+ /// cases (missing delta/gesture name, rejected before any category method is even called) aren't
17
+ /// WDA category behavior, so they're not covered here.
18
+ final class WDADeviceIntegrationTests: WDAWatchInProcessTestCase {
19
+ func testDeviceInfo() {
20
+ XCTAssertFalse(WKInterfaceDevice.current().name.isEmpty)
21
+ XCTAssertFalse(WKInterfaceDevice.current().model.isEmpty)
19
22
  }
20
23
 
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"])
24
+ func testActiveAppInfo() {
25
+ let apps = XCUIApplication.fb_activeAppsInfo()
26
+ let ourApp = apps.first { $0["bundleId"] as? String == WDAWatchInProcessTestCase.integrationAppBundleId }
27
+ XCTAssertNotNil(ourApp)
28
+ XCTAssertGreaterThan(ourApp?["pid"] as? Int ?? 0, 0)
33
29
  }
34
30
 
35
31
  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)
32
+ let location = CLLocation(latitude: 37.3230, longitude: -122.0322)
33
+ try XCUIDevice.shared.fb_setSimulatedLocation(location)
41
34
 
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)
35
+ let fetched = try XCUIDevice.shared.fb_getSimulatedLocation()
36
+ XCTAssertEqual(fetched.coordinate.latitude, 37.3230, accuracy: 0.001)
37
+ XCTAssertEqual(fetched.coordinate.longitude, -122.0322, accuracy: 0.001)
46
38
 
47
- let clearResponse = try client.delete("/session/\(sessionId!)/wda/simulatedLocation")
48
- XCTAssertEqual(clearResponse.statusCode, 200)
39
+ try XCUIDevice.shared.fb_clearSimulatedLocation()
49
40
  }
50
41
 
51
42
  /// rotateDigitalCrownByDelta:/performHandGesture: were only added to the SDK in Xcode 16.3 - WDA calls
@@ -53,61 +44,49 @@ final class WDADeviceIntegrationTests: WDAWatchIntegrationTestCase {
53
44
  /// when the toolchain that built this very test bundle (same build as the runner) is new enough. Checked
54
45
  /// the same way WDA itself does, via responds(to:), rather than hardcoding an Xcode/OS version number here.
55
46
  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)
47
+ XCUIDevice.shared.responds(to: NSSelectorFromString("rotateDigitalCrownByDelta:"))
65
48
  }
66
49
 
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)
50
+ private func assertSucceeds(_ expectedToSucceed: Bool, file: StaticString = #filePath, line: UInt = #line, _ body: () throws -> Void) {
51
+ do {
52
+ try body()
53
+ XCTAssertTrue(expectedToSucceed, "expected this to throw, but it didn't", file: file, line: line)
54
+ } catch {
55
+ XCTAssertFalse(expectedToSucceed, "expected this to succeed, but it threw \(error)", file: file, line: line)
56
+ }
72
57
  }
73
58
 
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)
59
+ func testRotateDigitalCrown() {
60
+ assertSucceeds(supportsDigitalCrownAndHandGesture) {
61
+ try XCUIDevice.shared.fb_rotateDigitalCrown(0.2, velocity: 1.0)
62
+ }
78
63
  }
79
64
 
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)
65
+ func testPerformHandGestureDoubleTap() {
66
+ assertSucceeds(supportsDigitalCrownAndHandGesture) {
67
+ try XCUIDevice.shared.fb_performHandGesture("doubleTap")
68
+ }
85
69
  }
86
70
 
87
- func testPerformHandGestureFlick() throws {
88
- let response = try client.post("/session/\(sessionId!)/wda/performHandGesture", body: [
89
- "name": "flick",
90
- ])
71
+ func testPerformHandGestureFlick() {
91
72
  // flick additionally needs watchOS 26+ (still internally versioned 12.0 pre-rename) on top of the
92
73
  // Xcode 16.3+ toolchain floor - below that OS version it isn't even advertised as a supported name,
93
74
  // 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)
75
+ let shouldSucceed: Bool
76
+ if #available(watchOS 12.0, *) {
77
+ shouldSucceed = supportsDigitalCrownAndHandGesture
96
78
  } else {
97
- XCTAssertEqual(response.statusCode, 500)
79
+ shouldSucceed = false
80
+ }
81
+ assertSucceeds(shouldSucceed) {
82
+ try XCUIDevice.shared.fb_performHandGesture("flick")
98
83
  }
99
84
  }
100
85
 
101
- func testPerformHandGestureUnsupportedName() throws {
86
+ func testPerformHandGestureUnsupportedName() {
102
87
  // 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)
88
+ assertSucceeds(false) {
89
+ try XCUIDevice.shared.fb_performHandGesture("clench")
90
+ }
112
91
  }
113
92
  }
@@ -8,50 +8,32 @@
8
8
 
9
9
  import XCTest
10
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)
11
+ /// Covers the FBElement attribute properties (wdEnabled/wdVisible/wdRect/wdType/wdLabel/
12
+ /// wdSelected/wdAccessible/fb_valueForWDAttributeName:) directly, in-process - the same
13
+ /// properties FBElementCommands' attribute routes read from.
14
+ final class WDAElementAttributeIntegrationTests: WDAWatchInProcessTestCase {
15
+ // All reads, no taps/typing - reuse the same running app across the whole class.
16
+ override class var relaunchForEachTest: Bool { false }
17
+
18
+ func testWindowSizeAndRect() {
19
+ XCTAssertGreaterThan(app.wdFrame.width, 0)
20
+ XCTAssertGreaterThan(app.wdFrame.height, 0)
22
21
  }
23
22
 
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)
23
+ func testButtonAttributes() {
24
+ let button = app.buttons["tapMeButton"]
25
+ XCTAssertTrue(button.isWDEnabled)
26
+ XCTAssertTrue(button.isWDVisible)
27
+ XCTAssertGreaterThan(button.wdRect["width"] as? Double ?? 0, 0)
28
+ XCTAssertEqual(button.wdType, "XCUIElementTypeButton")
29
+ XCTAssertEqual(button.wdLabel, "Tap Me")
30
+ XCTAssertEqual(button.fb_value(forWDAttributeName: "label") as? String, "Tap Me")
31
+ XCTAssertTrue(button.isWDAccessible)
45
32
  }
46
33
 
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)
34
+ func testResultLabelTextAndSelectedState() {
35
+ let label = app.staticTexts["resultLabel"]
36
+ XCTAssertEqual(label.wdValue ?? label.wdLabel, "Idle")
37
+ XCTAssertFalse(label.isWDSelected)
56
38
  }
57
39
  }