appium-webdriveragent 14.0.0 → 14.1.1
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 +12 -0
- package/WebDriverAgent.xcodeproj/project.pbxproj +18 -2
- package/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h +3 -3
- package/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m +1 -1
- package/WebDriverAgentLib/Categories/XCUIDevice+FBVoiceOver.h +66 -0
- package/WebDriverAgentLib/Categories/XCUIDevice+FBVoiceOver.m +216 -0
- package/WebDriverAgentLib/Commands/FBCustomCommands.m +80 -0
- package/WebDriverAgentLib/Info.plist +2 -2
- package/WebDriverAgentLib/Utilities/FBCapabilities.h +1 -1
- package/WebDriverAgentLib/Utilities/FBXCTestDaemonsProxy.m +7 -7
- package/WebDriverAgentLib/WebDriverAgentLib.h +1 -0
- package/WebDriverAgentTests/IntegrationTests/FBVoiceOverTests.m +103 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [14.1.1](https://github.com/appium/WebDriverAgent/compare/v14.1.0...v14.1.1) (2026-06-10)
|
|
2
|
+
|
|
3
|
+
### Miscellaneous Chores
|
|
4
|
+
|
|
5
|
+
* address runtime version rather than Xcode for selector based methods ([#1154](https://github.com/appium/WebDriverAgent/issues/1154)) ([6618b0b](https://github.com/appium/WebDriverAgent/commit/6618b0b6ccd06c69fc9e4a0947ef0c88c89b1e48))
|
|
6
|
+
|
|
7
|
+
## [14.1.0](https://github.com/appium/WebDriverAgent/compare/v14.0.0...v14.1.0) (2026-06-10)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Add VoiceOver automation support ([#1153](https://github.com/appium/WebDriverAgent/issues/1153)) ([6b3631a](https://github.com/appium/WebDriverAgent/commit/6b3631aed7a95439b01a9a3bb87189df384dcf06))
|
|
12
|
+
|
|
1
13
|
## [14.0.0](https://github.com/appium/WebDriverAgent/compare/v13.3.0...v14.0.0) (2026-06-09)
|
|
2
14
|
|
|
3
15
|
### ⚠ 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 =
|
|
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 =
|
|
4336
|
+
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
|
4321
4337
|
LD_RUNPATH_SEARCH_PATHS = (
|
|
4322
4338
|
"$(inherited)",
|
|
4323
4339
|
"@executable_path/Frameworks",
|
|
@@ -169,7 +169,7 @@ typedef NS_ENUM(NSUInteger, FBUIInterfaceAppearance) {
|
|
|
169
169
|
#if !TARGET_OS_TV
|
|
170
170
|
/**
|
|
171
171
|
Allows to set a simulated geolocation coordinates.
|
|
172
|
-
Only works since
|
|
172
|
+
Only works since iOS 16.4 runtime
|
|
173
173
|
|
|
174
174
|
@param location The simlated location coordinates to set
|
|
175
175
|
@param error If there is an error, upon return contains an NSError object that describes the problem.
|
|
@@ -179,7 +179,7 @@ typedef NS_ENUM(NSUInteger, FBUIInterfaceAppearance) {
|
|
|
179
179
|
|
|
180
180
|
/**
|
|
181
181
|
Allows to get a simulated geolocation coordinates.
|
|
182
|
-
Only works since
|
|
182
|
+
Only works since iOS 16.4 runtime
|
|
183
183
|
|
|
184
184
|
@param error If there is an error, upon return contains an NSError object that describes the problem.
|
|
185
185
|
@return The current simulated location or nil in case of failure or if no location has previously been seet
|
|
@@ -189,7 +189,7 @@ typedef NS_ENUM(NSUInteger, FBUIInterfaceAppearance) {
|
|
|
189
189
|
|
|
190
190
|
/**
|
|
191
191
|
Allows to clear a previosuly set simulated geolocation coordinates.
|
|
192
|
-
Only works since
|
|
192
|
+
Only works since iOS 16.4 runtime
|
|
193
193
|
|
|
194
194
|
@param error If there is an error, upon return contains an NSError object that describes the problem.
|
|
195
195
|
@return YES if the simulated location has been successfully cleared
|
|
@@ -229,7 +229,7 @@ static bool fb_isLocked;
|
|
|
229
229
|
return [self fb_activateSiriVoiceRecognitionWithText:[NSString stringWithFormat:@"Open {%@}", url] error:error];
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
NSString *description = [NSString stringWithFormat:@"Cannot open '%@' with the default application assigned for it.
|
|
232
|
+
NSString *description = [NSString stringWithFormat:@"Cannot open '%@' with the default application assigned for it. This API requires an iOS 16.4+ runtime", url];
|
|
233
233
|
return [[[FBErrorBuilder builder]
|
|
234
234
|
withDescriptionFormat:@"%@", description]
|
|
235
235
|
buildError:error];
|
|
@@ -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 OS runtime.
|
|
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 iOS 27 runtime.
|
|
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 iOS 27 runtime.
|
|
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 iOS 27 runtime.
|
|
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 iOS 27 runtime.
|
|
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 iOS 27 runtime.
|
|
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 OS runtime does not support VoiceOver control. This API requires an iOS 27+ runtime";
|
|
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.
|
|
18
|
+
<string>14.1.1</string>
|
|
19
19
|
<key>CFBundleSignature</key>
|
|
20
20
|
<string>????</string>
|
|
21
21
|
<key>CFBundleVersion</key>
|
|
22
|
-
<string>14.
|
|
22
|
+
<string>14.1.1</string>
|
|
23
23
|
<key>NSPrincipalClass</key>
|
|
24
24
|
<string/>
|
|
25
25
|
</dict>
|
|
@@ -24,7 +24,7 @@ extern NSString* const FB_CAP_BUNDLE_ID;
|
|
|
24
24
|
Usually an URL used as initial link to run Mobile Safari, but could be any other deep link.
|
|
25
25
|
This might also work together with `FB_CAP_BUNLDE_ID`, which tells XCTest to open
|
|
26
26
|
the given deep link in the particular app.
|
|
27
|
-
Only works since iOS 16.4
|
|
27
|
+
Only works since iOS 16.4 runtime
|
|
28
28
|
*/
|
|
29
29
|
extern NSString* const FB_CAP_INITIAL_URL;
|
|
30
30
|
/** Whether to enforrce (re)start of the application under test on session startup */
|
|
@@ -134,7 +134,7 @@ static void swizzledLaunchApp(id self, SEL _cmd, NSString *path, NSString *bundl
|
|
|
134
134
|
XCTRunnerDaemonSession *session = [XCTRunnerDaemonSession sharedSession];
|
|
135
135
|
if (![session respondsToSelector:@selector(openURL:usingApplication:completion:)]) {
|
|
136
136
|
return [[[FBErrorBuilder builder]
|
|
137
|
-
withDescriptionFormat:@"The current
|
|
137
|
+
withDescriptionFormat:@"The current OS runtime does not support opening URLs with a given application"]
|
|
138
138
|
buildError:error];
|
|
139
139
|
}
|
|
140
140
|
|
|
@@ -161,7 +161,7 @@ static void swizzledLaunchApp(id self, SEL _cmd, NSString *path, NSString *bundl
|
|
|
161
161
|
XCTRunnerDaemonSession *session = [XCTRunnerDaemonSession sharedSession];
|
|
162
162
|
if (![session respondsToSelector:@selector(openDefaultApplicationForURL:completion:)]) {
|
|
163
163
|
return [[[FBErrorBuilder builder]
|
|
164
|
-
withDescriptionFormat:@"The current
|
|
164
|
+
withDescriptionFormat:@"The current OS runtime does not support opening URLs. This API requires an iOS 16.4+ runtime"]
|
|
165
165
|
buildError:error];
|
|
166
166
|
}
|
|
167
167
|
|
|
@@ -189,7 +189,7 @@ static void swizzledLaunchApp(id self, SEL _cmd, NSString *path, NSString *bundl
|
|
|
189
189
|
XCTRunnerDaemonSession *session = [XCTRunnerDaemonSession sharedSession];
|
|
190
190
|
if (![session respondsToSelector:@selector(setSimulatedLocation:completion:)]) {
|
|
191
191
|
return [[[FBErrorBuilder builder]
|
|
192
|
-
withDescriptionFormat:@"The current
|
|
192
|
+
withDescriptionFormat:@"The current OS runtime does not support location simulation. This API requires an iOS 16.4+ runtime"]
|
|
193
193
|
buildError:error];
|
|
194
194
|
}
|
|
195
195
|
if (![session supportsLocationSimulation]) {
|
|
@@ -221,7 +221,7 @@ static void swizzledLaunchApp(id self, SEL _cmd, NSString *path, NSString *bundl
|
|
|
221
221
|
XCTRunnerDaemonSession *session = [XCTRunnerDaemonSession sharedSession];
|
|
222
222
|
if (![session respondsToSelector:@selector(getSimulatedLocationWithReply:)]) {
|
|
223
223
|
[[[FBErrorBuilder builder]
|
|
224
|
-
withDescriptionFormat:@"The current
|
|
224
|
+
withDescriptionFormat:@"The current OS runtime does not support location simulation. This API requires an iOS 16.4+ runtime"]
|
|
225
225
|
buildError:error];
|
|
226
226
|
return nil;
|
|
227
227
|
}
|
|
@@ -255,7 +255,7 @@ static void swizzledLaunchApp(id self, SEL _cmd, NSString *path, NSString *bundl
|
|
|
255
255
|
XCTRunnerDaemonSession *session = [XCTRunnerDaemonSession sharedSession];
|
|
256
256
|
if (![session respondsToSelector:@selector(clearSimulatedLocationWithReply:)]) {
|
|
257
257
|
return [[[FBErrorBuilder builder]
|
|
258
|
-
withDescriptionFormat:@"The current
|
|
258
|
+
withDescriptionFormat:@"The current OS runtime does not support location simulation. This API requires an iOS 16.4+ runtime"]
|
|
259
259
|
buildError:error];
|
|
260
260
|
}
|
|
261
261
|
if (![session supportsLocationSimulation]) {
|
|
@@ -289,7 +289,7 @@ static void swizzledLaunchApp(id self, SEL _cmd, NSString *path, NSString *bundl
|
|
|
289
289
|
XCTRunnerDaemonSession *session = [XCTRunnerDaemonSession sharedSession];
|
|
290
290
|
if (![session respondsToSelector:@selector(startScreenRecordingWithRequest:withReply:)]) {
|
|
291
291
|
[[[FBErrorBuilder builder]
|
|
292
|
-
withDescriptionFormat:@"The current
|
|
292
|
+
withDescriptionFormat:@"The current OS runtime does not support screen recording. This API requires an iOS 17+ runtime"]
|
|
293
293
|
buildError:error];
|
|
294
294
|
return nil;
|
|
295
295
|
}
|
|
@@ -331,7 +331,7 @@ static void swizzledLaunchApp(id self, SEL _cmd, NSString *path, NSString *bundl
|
|
|
331
331
|
XCTRunnerDaemonSession *session = [XCTRunnerDaemonSession sharedSession];
|
|
332
332
|
if (![session respondsToSelector:@selector(stopScreenRecordingWithUUID:withReply:)]) {
|
|
333
333
|
return [[[FBErrorBuilder builder]
|
|
334
|
-
withDescriptionFormat:@"The current
|
|
334
|
+
withDescriptionFormat:@"The current OS runtime does not support screen recording. This API requires an iOS 17+ runtime"]
|
|
335
335
|
buildError:error];
|
|
336
336
|
|
|
337
337
|
}
|
|
@@ -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:@"iOS 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
|