appium-webdriveragent 14.0.0 → 14.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [14.1.0](https://github.com/appium/WebDriverAgent/compare/v14.0.0...v14.1.0) (2026-06-10)
2
+
3
+ ### Features
4
+
5
+ * Add VoiceOver automation support ([#1153](https://github.com/appium/WebDriverAgent/issues/1153)) ([6b3631a](https://github.com/appium/WebDriverAgent/commit/6b3631aed7a95439b01a9a3bb87189df384dcf06))
6
+
1
7
  ## [14.0.0](https://github.com/appium/WebDriverAgent/compare/v13.3.0...v14.0.0) (2026-06-09)
2
8
 
3
9
  ### ⚠ BREAKING CHANGES
@@ -535,6 +535,11 @@
535
535
  71F5BE50252F14EB00EE9EBA /* FBExceptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 71F5BE4D252F14EB00EE9EBA /* FBExceptions.h */; };
536
536
  71F5BE51252F14EB00EE9EBA /* FBExceptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 71F5BE4E252F14EB00EE9EBA /* FBExceptions.m */; };
537
537
  71F5BE52252F14EB00EE9EBA /* FBExceptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 71F5BE4E252F14EB00EE9EBA /* FBExceptions.m */; };
538
+ A1B2C3D41F001A00A1B0004 /* XCUIDevice+FBVoiceOver.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B2C3D41F001A00A1B0001 /* XCUIDevice+FBVoiceOver.h */; settings = {ATTRIBUTES = (Public, ); }; };
539
+ A1B2C3D41F001A00A1B0005 /* XCUIDevice+FBVoiceOver.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D41F001A00A1B0002 /* XCUIDevice+FBVoiceOver.m */; };
540
+ A1B2C3D41F001A00A1B0006 /* XCUIDevice+FBVoiceOver.h in Headers */ = {isa = PBXBuildFile; fileRef = A1B2C3D41F001A00A1B0001 /* XCUIDevice+FBVoiceOver.h */; settings = {ATTRIBUTES = (Public, ); }; };
541
+ A1B2C3D41F001A00A1B0007 /* XCUIDevice+FBVoiceOver.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D41F001A00A1B0002 /* XCUIDevice+FBVoiceOver.m */; };
542
+ A1B2C3D41F001A00A1B0008 /* FBVoiceOverTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D41F001A00A1B0003 /* FBVoiceOverTests.m */; };
538
543
  A1B2C3D4E5F600000000001B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F600000000001A /* Assets.xcassets */; };
539
544
  AABBCCDDEEFF001122334457 /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AABBCCDDEEFF001122334456 /* SceneDelegate.m */; };
540
545
  AD35D06C1CF1C35500870A75 /* WebDriverAgentLib.framework in Copy frameworks */ = {isa = PBXBuildFile; fileRef = EE158A991CBD452B00A3E3F0 /* WebDriverAgentLib.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -1125,6 +1130,9 @@
1125
1130
  71F5BE33252E5B2200EE9EBA /* FBElementSwipingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBElementSwipingTests.m; sourceTree = "<group>"; };
1126
1131
  71F5BE4D252F14EB00EE9EBA /* FBExceptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBExceptions.h; sourceTree = "<group>"; };
1127
1132
  71F5BE4E252F14EB00EE9EBA /* FBExceptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBExceptions.m; sourceTree = "<group>"; };
1133
+ A1B2C3D41F001A00A1B0001 /* XCUIDevice+FBVoiceOver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCUIDevice+FBVoiceOver.h"; sourceTree = "<group>"; };
1134
+ A1B2C3D41F001A00A1B0002 /* XCUIDevice+FBVoiceOver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "XCUIDevice+FBVoiceOver.m"; sourceTree = "<group>"; };
1135
+ A1B2C3D41F001A00A1B0003 /* FBVoiceOverTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBVoiceOverTests.m; sourceTree = "<group>"; };
1128
1136
  A1B2C3D4E5F600000000001A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = WebDriverAgentRunner/Assets.xcassets; sourceTree = SOURCE_ROOT; };
1129
1137
  AABBCCDDEEFF001122334455 /* SceneDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SceneDelegate.h; sourceTree = "<group>"; };
1130
1138
  AABBCCDDEEFF001122334456 /* SceneDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SceneDelegate.m; sourceTree = "<group>"; };
@@ -1798,6 +1806,8 @@
1798
1806
  EEDFE1201D9C06F800E6FFE5 /* XCUIDevice+FBHealthCheck.m */,
1799
1807
  AD6C26961CF2481700F8B5FF /* XCUIDevice+FBHelpers.h */,
1800
1808
  AD6C26971CF2481700F8B5FF /* XCUIDevice+FBHelpers.m */,
1809
+ A1B2C3D41F001A00A1B0001 /* XCUIDevice+FBVoiceOver.h */,
1810
+ A1B2C3D41F001A00A1B0002 /* XCUIDevice+FBVoiceOver.m */,
1801
1811
  EEE3763D1D59F81400ED88DD /* XCUIDevice+FBRotation.h */,
1802
1812
  EEE3763E1D59F81400ED88DD /* XCUIDevice+FBRotation.m */,
1803
1813
  EE9AB7451CAEDF0C008C271F /* XCUIElement+FBAccessibility.h */,
@@ -2070,6 +2080,7 @@
2070
2080
  AD76723F1D6B826F00610457 /* FBTypingTest.m */,
2071
2081
  714CA3C61DC23186000F12C9 /* FBXPathIntegrationTests.m */,
2072
2082
  71BB58DD2B9631B700CB9BFE /* FBVideoRecordingTests.m */,
2083
+ A1B2C3D41F001A00A1B0003 /* FBVoiceOverTests.m */,
2073
2084
  71241D7D1FAF084E00B9559F /* FBW3CTouchActionsIntegrationTests.m */,
2074
2085
  71241D7F1FAF087500B9559F /* FBW3CMultiTouchActionsIntegrationTests.m */,
2075
2086
  7136C0F8243A182400921C76 /* FBW3CTypeActionsTests.m */,
@@ -2513,6 +2524,7 @@
2513
2524
  71822777258744CE00661B83 /* DDNumber.h in Headers */,
2514
2525
  641EE6C82240C5CA00173FCB /* XCTKVOExpectation.h in Headers */,
2515
2526
  641EE6C92240C5CA00173FCB /* XCUIDevice+FBRotation.h in Headers */,
2527
+ A1B2C3D41F001A00A1B0004 /* XCUIDevice+FBVoiceOver.h in Headers */,
2516
2528
  641EE6CA2240C5CA00173FCB /* XCEventGenerator.h in Headers */,
2517
2529
  719DCF162601EAFB000E765F /* FBNotificationsHelper.h in Headers */,
2518
2530
  71414ED52670A1EE003A8C5D /* LRUCache.h in Headers */,
@@ -2760,6 +2772,7 @@
2760
2772
  EE35AD591E3B77D600A02D78 /* XCTKVOExpectation.h in Headers */,
2761
2773
  13DE7A43287C2A8D003243C6 /* FBXCAccessibilityElement.h in Headers */,
2762
2774
  EEE376431D59F81400ED88DD /* XCUIDevice+FBRotation.h in Headers */,
2775
+ A1B2C3D41F001A00A1B0006 /* XCUIDevice+FBVoiceOver.h in Headers */,
2763
2776
  EE35AD2E1E3B77D600A02D78 /* XCEventGenerator.h in Headers */,
2764
2777
  EE9B76A61CF7A43900275851 /* FBConfiguration.h in Headers */,
2765
2778
  EE35AD571E3B77D600A02D78 /* XCTestSuiteRun.h in Headers */,
@@ -3206,6 +3219,7 @@
3206
3219
  641EE5F22240C5CA00173FCB /* NSPredicate+FBFormat.m in Sources */,
3207
3220
  718F49CA23087AD30045FE8B /* FBProtocolHelpers.m in Sources */,
3208
3221
  641EE5F42240C5CA00173FCB /* XCUIDevice+FBRotation.m in Sources */,
3222
+ A1B2C3D41F001A00A1B0005 /* XCUIDevice+FBVoiceOver.m in Sources */,
3209
3223
  13815F722328D20400CDAB61 /* FBActiveAppDetectionPoint.m in Sources */,
3210
3224
  71D475C52538F5A8008D9401 /* XCUIApplicationProcess+FBQuiescence.m in Sources */,
3211
3225
  641EE5F52240C5CA00173FCB /* XCUIElement+FBUID.m in Sources */,
@@ -3333,6 +3347,7 @@
3333
3347
  71A224E61DE2F56600844D55 /* NSPredicate+FBFormat.m in Sources */,
3334
3348
  E444DC85249131B10060D7EB /* DDNumber.m in Sources */,
3335
3349
  EEE376441D59F81400ED88DD /* XCUIDevice+FBRotation.m in Sources */,
3350
+ A1B2C3D41F001A00A1B0007 /* XCUIDevice+FBVoiceOver.m in Sources */,
3336
3351
  13815F712328D20400CDAB61 /* FBActiveAppDetectionPoint.m in Sources */,
3337
3352
  71B49EC81ED1A58100D51AD6 /* XCUIElement+FBUID.m in Sources */,
3338
3353
  EE158AE21CBD456F00A3E3F0 /* FBRouteRequest.m in Sources */,
@@ -3422,6 +3437,7 @@
3422
3437
  files = (
3423
3438
  71241D801FAF087500B9559F /* FBW3CMultiTouchActionsIntegrationTests.m in Sources */,
3424
3439
  71BB58DE2B9631B700CB9BFE /* FBVideoRecordingTests.m in Sources */,
3440
+ A1B2C3D41F001A00A1B0008 /* FBVoiceOverTests.m in Sources */,
3425
3441
  71241D7E1FAF084E00B9559F /* FBW3CTouchActionsIntegrationTests.m in Sources */,
3426
3442
  63FD950221F9D06100A3E356 /* FBImageProcessorTests.m in Sources */,
3427
3443
  719CD8FF2126C90200C7D0C2 /* FBAutoAlertsHandlerTests.m in Sources */,
@@ -4300,7 +4316,7 @@
4300
4316
  CLANG_ANALYZER_NONNULL = YES;
4301
4317
  DEBUG_INFORMATION_FORMAT = dwarf;
4302
4318
  INFOPLIST_FILE = WebDriverAgentTests/IntegrationApp/Info.plist;
4303
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
4319
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
4304
4320
  LD_RUNPATH_SEARCH_PATHS = (
4305
4321
  "$(inherited)",
4306
4322
  "@executable_path/Frameworks",
@@ -4317,7 +4333,7 @@
4317
4333
  ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
4318
4334
  CLANG_ANALYZER_NONNULL = YES;
4319
4335
  INFOPLIST_FILE = WebDriverAgentTests/IntegrationApp/Info.plist;
4320
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
4336
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
4321
4337
  LD_RUNPATH_SEARCH_PATHS = (
4322
4338
  "$(inherited)",
4323
4339
  "@executable_path/Frameworks",
@@ -0,0 +1,66 @@
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/XCTest.h>
10
+
11
+ NS_ASSUME_NONNULL_BEGIN
12
+
13
+ @interface XCUIDevice (FBVoiceOver)
14
+
15
+ /**
16
+ Whether VoiceOver control APIs are available in the current Xcode SDK.
17
+
18
+ @return YES if the VoiceOver service is exposed by XCUIDevice
19
+ */
20
+ - (BOOL)fb_isVoiceOverServiceAvailable;
21
+
22
+ /**
23
+ Enable VoiceOver. Only works since Xcode 27/iOS 27.
24
+
25
+ @param error If there is an error, upon return contains an NSError object that describes the problem.
26
+ @return YES if VoiceOver has been successfully enabled
27
+ */
28
+ - (BOOL)fb_enableVoiceOver:(NSError **)error;
29
+
30
+ /**
31
+ Disable VoiceOver. Only works since Xcode 27/iOS 27.
32
+
33
+ @param error If there is an error, upon return contains an NSError object that describes the problem.
34
+ @return YES if VoiceOver has been successfully disabled
35
+ */
36
+ - (BOOL)fb_disableVoiceOver:(NSError **)error;
37
+
38
+ /**
39
+ Whether VoiceOver is currently enabled. Only works since Xcode 27/iOS 27.
40
+
41
+ @param error If there is an error, upon return contains an NSError object that describes the problem.
42
+ @return YES if VoiceOver is enabled
43
+ */
44
+ - (BOOL)fb_isVoiceOverEnabled:(NSError **)error;
45
+
46
+ /**
47
+ Move VoiceOver focus and return speech for the newly focused element.
48
+ Only works since Xcode 27/iOS 27.
49
+
50
+ @param direction One of: forward, backward, in (iOS only), out (iOS only)
51
+ @param error If there is an error, upon return contains an NSError object that describes the problem.
52
+ @return The spoken utterance or nil in case of failure
53
+ */
54
+ - (nullable NSString *)fb_voiceOverMove:(NSString *)direction error:(NSError **)error;
55
+
56
+ /**
57
+ Return the speech for the currently focused element. Only works since Xcode 27/iOS 27.
58
+
59
+ @param error If there is an error, upon return contains an NSError object that describes the problem.
60
+ @return The spoken utterance or nil in case of failure
61
+ */
62
+ - (nullable NSString *)fb_voiceOverCurrentSpeech:(NSError **)error;
63
+
64
+ @end
65
+
66
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,216 @@
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 "XCUIDevice+FBVoiceOver.h"
10
+
11
+ #import "FBErrorBuilder.h"
12
+
13
+ static NSString *const FBVoiceOverSDKUnsupportedError =
14
+ @"The current Xcode SDK does not support VoiceOver control. Consider upgrading to Xcode 27+/iOS 27+";
15
+
16
+ static BOOL FBVoiceOverBuildSDKUnsupportedError(NSError **error)
17
+ {
18
+ return [[[FBErrorBuilder builder]
19
+ withDescription:FBVoiceOverSDKUnsupportedError]
20
+ buildError:error];
21
+ }
22
+
23
+ static BOOL FBIsVoiceOverServiceAvailable(void)
24
+ {
25
+ static BOOL isAvailable = NO;
26
+ static dispatch_once_t onceToken;
27
+ dispatch_once(&onceToken, ^{
28
+ isAvailable = [XCUIDevice.sharedDevice respondsToSelector:NSSelectorFromString(@"voiceOverService")];
29
+ });
30
+ return isAvailable;
31
+ }
32
+
33
+ static id FBVoiceOverService(NSError **error)
34
+ {
35
+ if (!FBIsVoiceOverServiceAvailable()) {
36
+ FBVoiceOverBuildSDKUnsupportedError(error);
37
+ return nil;
38
+ }
39
+ return [XCUIDevice.sharedDevice valueForKey:@"voiceOverService"];
40
+ }
41
+
42
+ static BOOL FBInvokeVoiceOverBoolMethod(id voiceOverService,
43
+ SEL selector,
44
+ NSError **error)
45
+ {
46
+ if (![voiceOverService respondsToSelector:selector]) {
47
+ return FBVoiceOverBuildSDKUnsupportedError(error);
48
+ }
49
+
50
+ NSMethodSignature *signature = [voiceOverService methodSignatureForSelector:selector];
51
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
52
+ [invocation setSelector:selector];
53
+ [invocation setTarget:voiceOverService];
54
+ NSError *invokeError = nil;
55
+ [invocation setArgument:&invokeError atIndex:2];
56
+ [invocation invoke];
57
+ if (nil != invokeError) {
58
+ if (error) {
59
+ *error = invokeError;
60
+ }
61
+ return NO;
62
+ }
63
+
64
+ BOOL result = NO;
65
+ [invocation getReturnValue:&result];
66
+ return result;
67
+ }
68
+
69
+ static id FBInvokeVoiceOverOutputMethod(id voiceOverService,
70
+ SEL selector,
71
+ NSError **error)
72
+ {
73
+ if (![voiceOverService respondsToSelector:selector]) {
74
+ FBVoiceOverBuildSDKUnsupportedError(error);
75
+ return nil;
76
+ }
77
+
78
+ NSMethodSignature *signature = [voiceOverService methodSignatureForSelector:selector];
79
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
80
+ [invocation setSelector:selector];
81
+ [invocation setTarget:voiceOverService];
82
+ NSError *invokeError = nil;
83
+ [invocation setArgument:&invokeError atIndex:2];
84
+ [invocation invoke];
85
+ if (nil != invokeError) {
86
+ if (error) {
87
+ *error = invokeError;
88
+ }
89
+ return nil;
90
+ }
91
+
92
+ id __unsafe_unretained output = nil;
93
+ [invocation getReturnValue:&output];
94
+ return output;
95
+ }
96
+
97
+ static NSString *FBUtteranceFromVoiceOverOutput(id output, NSError **error)
98
+ {
99
+ if (nil == output) {
100
+ return nil;
101
+ }
102
+
103
+ if (![output respondsToSelector:NSSelectorFromString(@"utterance")]) {
104
+ [[[FBErrorBuilder builder]
105
+ withDescription:@"VoiceOver output does not provide an utterance"]
106
+ buildError:error];
107
+ return nil;
108
+ }
109
+
110
+ id utterance = [output valueForKey:@"utterance"];
111
+ return [utterance isKindOfClass:NSString.class] ? utterance : nil;
112
+ }
113
+
114
+ static NSString *FBVoiceOverSpeechFromSelector(SEL selector, NSError **error)
115
+ {
116
+ id service = FBVoiceOverService(error);
117
+ if (nil == service) {
118
+ return nil;
119
+ }
120
+
121
+ id output = FBInvokeVoiceOverOutputMethod(service, selector, error);
122
+ if (nil != error && nil != *error) {
123
+ return nil;
124
+ }
125
+ return FBUtteranceFromVoiceOverOutput(output, error);
126
+ }
127
+
128
+ static NSDictionary<NSString *, NSString *> *FBVoiceOverMoveSelectors(void)
129
+ {
130
+ static NSDictionary<NSString *, NSString *> *selectors = nil;
131
+ static dispatch_once_t onceToken;
132
+ dispatch_once(&onceToken, ^{
133
+ NSMutableDictionary<NSString *, NSString *> *mapping = [@{
134
+ @"forward": @"moveForwardAndReturnError:",
135
+ @"backward": @"moveBackwardAndReturnError:",
136
+ } mutableCopy];
137
+ #if TARGET_OS_IOS
138
+ mapping[@"in"] = @"moveInAndReturnError:";
139
+ mapping[@"out"] = @"moveOutAndReturnError:";
140
+ #endif
141
+ selectors = mapping.copy;
142
+ });
143
+ return selectors;
144
+ }
145
+
146
+ @implementation XCUIDevice (FBVoiceOver)
147
+
148
+ - (BOOL)fb_isVoiceOverServiceAvailable
149
+ {
150
+ return FBIsVoiceOverServiceAvailable();
151
+ }
152
+
153
+ - (BOOL)fb_enableVoiceOver:(NSError **)error
154
+ {
155
+ id service = FBVoiceOverService(error);
156
+ if (nil == service) {
157
+ return NO;
158
+ }
159
+ return FBInvokeVoiceOverBoolMethod(service,
160
+ NSSelectorFromString(@"enableAndReturnError:"),
161
+ error);
162
+ }
163
+
164
+ - (BOOL)fb_disableVoiceOver:(NSError **)error
165
+ {
166
+ id service = FBVoiceOverService(error);
167
+ if (nil == service) {
168
+ return NO;
169
+ }
170
+ return FBInvokeVoiceOverBoolMethod(service,
171
+ NSSelectorFromString(@"disableAndReturnError:"),
172
+ error);
173
+ }
174
+
175
+ - (BOOL)fb_isVoiceOverEnabled:(NSError **)error
176
+ {
177
+ id service = FBVoiceOverService(error);
178
+ if (nil == service) {
179
+ return NO;
180
+ }
181
+
182
+ if (![service respondsToSelector:NSSelectorFromString(@"isEnabled")] &&
183
+ ![service respondsToSelector:NSSelectorFromString(@"enabled")]) {
184
+ return FBVoiceOverBuildSDKUnsupportedError(error);
185
+ }
186
+
187
+ return [[service valueForKey:@"enabled"] boolValue];
188
+ }
189
+
190
+ - (nullable NSString *)fb_voiceOverMove:(NSString *)direction error:(NSError **)error
191
+ {
192
+ if (![direction isKindOfClass:NSString.class] || 0 == direction.length) {
193
+ return [[[FBErrorBuilder builder]
194
+ withDescription:@"VoiceOver move direction must be a non-empty string"]
195
+ buildError:error], nil;
196
+ }
197
+
198
+ NSString *normalizedDirection = direction.lowercaseString;
199
+ NSString *selectorName = FBVoiceOverMoveSelectors()[normalizedDirection];
200
+ if (nil == selectorName) {
201
+ NSArray *supportedDirections = [FBVoiceOverMoveSelectors().allKeys sortedArrayUsingSelector:@selector(compare:)];
202
+ return [[[FBErrorBuilder builder]
203
+ withDescriptionFormat:@"Unsupported VoiceOver move direction '%@'. Supported directions: %@",
204
+ direction, supportedDirections]
205
+ buildError:error], nil;
206
+ }
207
+
208
+ return FBVoiceOverSpeechFromSelector(NSSelectorFromString(selectorName), error);
209
+ }
210
+
211
+ - (nullable NSString *)fb_voiceOverCurrentSpeech:(NSError **)error
212
+ {
213
+ return FBVoiceOverSpeechFromSelector(NSSelectorFromString(@"currentSpeechAndReturnError:"), error);
214
+ }
215
+
216
+ @end
@@ -26,6 +26,7 @@
26
26
  #import "XCUIApplication.h"
27
27
  #import "XCUIApplication+FBHelpers.h"
28
28
  #import "XCUIDevice+FBHelpers.h"
29
+ #import "XCUIDevice+FBVoiceOver.h"
29
30
  #import "XCUIElement.h"
30
31
  #import "XCUIElement+FBIsVisible.h"
31
32
  #import "XCUIElementQuery.h"
@@ -81,6 +82,16 @@
81
82
  [[FBRoute DELETE:@"/wda/simulatedLocation"] respondWithTarget:self action:@selector(handleClearSimulatedLocation:)],
82
83
  [[FBRoute DELETE:@"/wda/simulatedLocation"].withoutSession respondWithTarget:self action:@selector(handleClearSimulatedLocation:)],
83
84
  #endif
85
+ [[FBRoute POST:@"/wda/voiceOver/enable"] respondWithTarget:self action:@selector(handleVoiceOverEnable:)],
86
+ [[FBRoute POST:@"/wda/voiceOver/enable"].withoutSession respondWithTarget:self action:@selector(handleVoiceOverEnable:)],
87
+ [[FBRoute POST:@"/wda/voiceOver/disable"] respondWithTarget:self action:@selector(handleVoiceOverDisable:)],
88
+ [[FBRoute POST:@"/wda/voiceOver/disable"].withoutSession respondWithTarget:self action:@selector(handleVoiceOverDisable:)],
89
+ [[FBRoute GET:@"/wda/voiceOver/enabled"] respondWithTarget:self action:@selector(handleVoiceOverEnabled:)],
90
+ [[FBRoute GET:@"/wda/voiceOver/enabled"].withoutSession respondWithTarget:self action:@selector(handleVoiceOverEnabled:)],
91
+ [[FBRoute POST:@"/wda/voiceOver/move"] respondWithTarget:self action:@selector(handleVoiceOverMove:)],
92
+ [[FBRoute POST:@"/wda/voiceOver/move"].withoutSession respondWithTarget:self action:@selector(handleVoiceOverMove:)],
93
+ [[FBRoute GET:@"/wda/voiceOver/currentSpeech"] respondWithTarget:self action:@selector(handleVoiceOverCurrentSpeech:)],
94
+ [[FBRoute GET:@"/wda/voiceOver/currentSpeech"].withoutSession respondWithTarget:self action:@selector(handleVoiceOverCurrentSpeech:)],
84
95
  [[FBRoute OPTIONS:@"/*"].withoutSession respondWithTarget:self action:@selector(handlePingCommand:)],
85
96
  ];
86
97
  }
@@ -611,6 +622,75 @@
611
622
  #endif
612
623
  #endif
613
624
 
625
+ + (id<FBResponsePayload>)fb_handleVoiceOverSpeechResponse:(nullable NSString *)utterance
626
+ error:(NSError *)error
627
+ {
628
+ if (nil != error) {
629
+ return FBResponseWithStatus([FBCommandStatus unknownErrorWithMessage:error.description
630
+ traceback:nil]);
631
+ }
632
+ return FBResponseWithObject(@{
633
+ @"utterance": utterance ?: NSNull.null,
634
+ });
635
+ }
636
+
637
+ + (id<FBResponsePayload>)handleVoiceOverEnable:(FBRouteRequest *)request
638
+ {
639
+ NSError *error;
640
+ if (![XCUIDevice.sharedDevice fb_enableVoiceOver:&error]) {
641
+ return FBResponseWithStatus([FBCommandStatus unknownErrorWithMessage:error.description
642
+ traceback:nil]);
643
+ }
644
+ return FBResponseWithOK();
645
+ }
646
+
647
+ + (id<FBResponsePayload>)handleVoiceOverDisable:(FBRouteRequest *)request
648
+ {
649
+ NSError *error;
650
+ if (![XCUIDevice.sharedDevice fb_disableVoiceOver:&error]) {
651
+ return FBResponseWithStatus([FBCommandStatus unknownErrorWithMessage:error.description
652
+ traceback:nil]);
653
+ }
654
+ return FBResponseWithOK();
655
+ }
656
+
657
+ + (id<FBResponsePayload>)handleVoiceOverEnabled:(FBRouteRequest *)request
658
+ {
659
+ NSError *error;
660
+ BOOL isEnabled = [XCUIDevice.sharedDevice fb_isVoiceOverEnabled:&error];
661
+ if (nil != error) {
662
+ return FBResponseWithStatus([FBCommandStatus unknownErrorWithMessage:error.description
663
+ traceback:nil]);
664
+ }
665
+ return FBResponseWithObject(@{@"enabled": @(isEnabled)});
666
+ }
667
+
668
+ + (id<FBResponsePayload>)handleVoiceOverMove:(FBRouteRequest *)request
669
+ {
670
+ NSString *direction = request.arguments[@"direction"];
671
+ if (nil == direction) {
672
+ return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:@"The 'direction' argument must be provided"
673
+ traceback:nil]);
674
+ }
675
+
676
+ NSError *error;
677
+ NSString *utterance = [XCUIDevice.sharedDevice fb_voiceOverMove:direction error:&error];
678
+ if (nil != error) {
679
+ FBCommandStatus *status = [error.localizedDescription containsString:@"Unsupported VoiceOver move direction"]
680
+ ? [FBCommandStatus invalidArgumentErrorWithMessage:error.description traceback:nil]
681
+ : [FBCommandStatus unknownErrorWithMessage:error.description traceback:nil];
682
+ return FBResponseWithStatus(status);
683
+ }
684
+ return [self fb_handleVoiceOverSpeechResponse:utterance error:nil];
685
+ }
686
+
687
+ + (id<FBResponsePayload>)handleVoiceOverCurrentSpeech:(FBRouteRequest *)request
688
+ {
689
+ NSError *error;
690
+ NSString *utterance = [XCUIDevice.sharedDevice fb_voiceOverCurrentSpeech:&error];
691
+ return [self fb_handleVoiceOverSpeechResponse:utterance error:error];
692
+ }
693
+
614
694
  + (id<FBResponsePayload>)handlePerformAccessibilityAudit:(FBRouteRequest *)request
615
695
  {
616
696
  NSError *error;
@@ -15,11 +15,11 @@
15
15
  <key>CFBundlePackageType</key>
16
16
  <string>FMWK</string>
17
17
  <key>CFBundleShortVersionString</key>
18
- <string>14.0.0</string>
18
+ <string>14.1.0</string>
19
19
  <key>CFBundleSignature</key>
20
20
  <string>????</string>
21
21
  <key>CFBundleVersion</key>
22
- <string>14.0.0</string>
22
+ <string>14.1.0</string>
23
23
  <key>NSPrincipalClass</key>
24
24
  <string/>
25
25
  </dict>
@@ -48,6 +48,7 @@ FOUNDATION_EXPORT const unsigned char WebDriverAgentLib_VersionString[];
48
48
  #import <WebDriverAgentLib/XCUIApplication+FBHelpers.h>
49
49
  #import <WebDriverAgentLib/XCUIDevice+FBHelpers.h>
50
50
  #import <WebDriverAgentLib/XCUIDevice+FBRotation.h>
51
+ #import <WebDriverAgentLib/XCUIDevice+FBVoiceOver.h>
51
52
  #import <WebDriverAgentLib/XCUIElement.h>
52
53
  #import <WebDriverAgentLib/XCUIElement+FBAccessibility.h>
53
54
  #import <WebDriverAgentLib/XCUIElement+FBFind.h>
@@ -0,0 +1,103 @@
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/XCTest.h>
10
+
11
+ #import "FBIntegrationTestCase.h"
12
+ #import "FBMacros.h"
13
+ #import "FBTestMacros.h"
14
+ #import "XCUIDevice+FBVoiceOver.h"
15
+
16
+ @interface FBVoiceOverTests : FBIntegrationTestCase
17
+ @end
18
+
19
+ @implementation FBVoiceOverTests
20
+
21
+ - (void)tearDown
22
+ {
23
+ if ([XCUIDevice.sharedDevice fb_isVoiceOverServiceAvailable]) {
24
+ NSError *error = nil;
25
+ if ([XCUIDevice.sharedDevice fb_isVoiceOverEnabled:&error] && nil == error) {
26
+ [XCUIDevice.sharedDevice fb_disableVoiceOver:&error];
27
+ }
28
+ }
29
+ [super tearDown];
30
+ }
31
+
32
+ - (void)testVoiceOverUnavailableOnOlderSDK
33
+ {
34
+ if ([XCUIDevice.sharedDevice fb_isVoiceOverServiceAvailable]) {
35
+ return;
36
+ }
37
+
38
+ NSError *error = nil;
39
+ XCTAssertFalse([XCUIDevice.sharedDevice fb_enableVoiceOver:&error]);
40
+ XCTAssertNotNil(error);
41
+ XCTAssertTrue([error.localizedDescription containsString:@"Xcode 27"]);
42
+ }
43
+
44
+ - (void)testVoiceOverEnableDisableAndNavigation
45
+ {
46
+ if (SYSTEM_VERSION_LESS_THAN(@"27.0")) {
47
+ return;
48
+ }
49
+ if (![XCUIDevice.sharedDevice fb_isVoiceOverServiceAvailable]) {
50
+ return;
51
+ }
52
+
53
+ [self launchApplication];
54
+
55
+ NSError *error = nil;
56
+ XCTAssertTrue([XCUIDevice.sharedDevice fb_enableVoiceOver:&error]);
57
+ XCTAssertNil(error);
58
+ XCTAssertTrue([XCUIDevice.sharedDevice fb_isVoiceOverEnabled:&error]);
59
+ XCTAssertNil(error);
60
+
61
+ NSString *utterance = [XCUIDevice.sharedDevice fb_voiceOverMove:@"forward" error:&error];
62
+ XCTAssertNil(error);
63
+ XCTAssertNotNil(utterance);
64
+ XCTAssertTrue(utterance.length > 0);
65
+
66
+ NSString *currentSpeech = [XCUIDevice.sharedDevice fb_voiceOverCurrentSpeech:&error];
67
+ XCTAssertNil(error);
68
+ XCTAssertNotNil(currentSpeech);
69
+ XCTAssertEqualObjects(currentSpeech, utterance);
70
+
71
+ XCTAssertTrue([XCUIDevice.sharedDevice fb_disableVoiceOver:&error]);
72
+ XCTAssertNil(error);
73
+ XCTAssertFalse([XCUIDevice.sharedDevice fb_isVoiceOverEnabled:&error]);
74
+ XCTAssertNil(error);
75
+ }
76
+
77
+ #if TARGET_OS_IOS
78
+ - (void)testVoiceOverMoveBackward
79
+ {
80
+ if (SYSTEM_VERSION_LESS_THAN(@"27.0")) {
81
+ return;
82
+ }
83
+ if (![XCUIDevice.sharedDevice fb_isVoiceOverServiceAvailable]) {
84
+ return;
85
+ }
86
+
87
+ [self launchApplication];
88
+
89
+ NSError *error = nil;
90
+ XCTAssertTrue([XCUIDevice.sharedDevice fb_enableVoiceOver:&error]);
91
+ XCTAssertNil(error);
92
+
93
+ XCTAssertNotNil([XCUIDevice.sharedDevice fb_voiceOverMove:@"forward" error:&error]);
94
+ XCTAssertNil(error);
95
+
96
+ NSString *utterance = [XCUIDevice.sharedDevice fb_voiceOverMove:@"backward" error:&error];
97
+ XCTAssertNil(error);
98
+ XCTAssertNotNil(utterance);
99
+ XCTAssertTrue(utterance.length > 0);
100
+ }
101
+ #endif
102
+
103
+ @end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appium-webdriveragent",
3
- "version": "14.0.0",
3
+ "version": "14.1.0",
4
4
  "description": "Package bundling WebDriverAgent",
5
5
  "main": "./build/lib/index.js",
6
6
  "types": "./build/lib/index.d.ts",