appium-webdriveragent 9.8.0 → 9.10.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 +16 -0
- package/WebDriverAgent.xcodeproj/project.pbxproj +12 -0
- package/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m +5 -0
- package/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.h +16 -1
- package/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.m +25 -10
- package/WebDriverAgentLib/Categories/XCUIElement+FBWebDriverAttributes.m +21 -0
- package/WebDriverAgentLib/Info.plist +2 -2
- package/WebDriverAgentLib/Routing/FBElement.h +3 -0
- package/WebDriverAgentLib/Utilities/FBAccessibilityTraits.h +21 -0
- package/WebDriverAgentLib/Utilities/FBAccessibilityTraits.m +62 -0
- package/WebDriverAgentLib/Utilities/FBXPath.m +24 -7
- package/WebDriverAgentTests/IntegrationTests/FBElementAttributeTests.m +47 -2
- package/WebDriverAgentTests/IntegrationTests/FBScrollingTests.m +4 -1
- package/WebDriverAgentTests/IntegrationTests/XCUIElementFBFindTests.m +9 -0
- package/WebDriverAgentTests/UnitTests/Doubles/XCUIElementDouble.h +2 -0
- package/WebDriverAgentTests/UnitTests/Doubles/XCUIElementDouble.m +8 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## [9.10.0](https://github.com/appium/WebDriverAgent/compare/v9.9.0...v9.10.0) (2025-05-27)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* Add accessibility traits of the element ([#1020](https://github.com/appium/WebDriverAgent/issues/1020)) ([9465aaf](https://github.com/appium/WebDriverAgent/commit/9465aafd5e81ef57be7f78e9f2e188d3c1ba1bee))
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
* Use native snapshots if hittable attribute is requested in xPath ([#1023](https://github.com/appium/WebDriverAgent/issues/1023)) ([49d26cb](https://github.com/appium/WebDriverAgent/commit/49d26cb02a8515d1a1b52b65b7cb65512dfd749b))
|
|
10
|
+
|
|
11
|
+
## [9.9.0](https://github.com/appium/WebDriverAgent/compare/v9.8.0...v9.9.0) (2025-05-26)
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* Use another snapshotting mechanism for the hittable attribute calculation ([#1022](https://github.com/appium/WebDriverAgent/issues/1022)) ([13c9f45](https://github.com/appium/WebDriverAgent/commit/13c9f453d890ad9b78fa7c47728ebae33880966a))
|
|
16
|
+
|
|
1
17
|
## [9.8.0](https://github.com/appium/WebDriverAgent/compare/v9.7.1...v9.8.0) (2025-05-21)
|
|
2
18
|
|
|
3
19
|
### Features
|
|
@@ -539,6 +539,10 @@
|
|
|
539
539
|
ADBC39981D07842800327304 /* XCUIElementDouble.m in Sources */ = {isa = PBXBuildFile; fileRef = ADBC39971D07842800327304 /* XCUIElementDouble.m */; };
|
|
540
540
|
ADDA07241D6BB2BF001700AC /* FBScrollViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = ADDA07231D6BB2BF001700AC /* FBScrollViewController.m */; };
|
|
541
541
|
ADEF63AF1D09DEBE0070A7E3 /* FBRuntimeUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = ADEF63AE1D09DEBE0070A7E3 /* FBRuntimeUtilsTests.m */; };
|
|
542
|
+
B316351C2DDF0CF5007D9317 /* FBAccessibilityTraits.m in Sources */ = {isa = PBXBuildFile; fileRef = B316351B2DDF0CF5007D9317 /* FBAccessibilityTraits.m */; };
|
|
543
|
+
B316351D2DDF0CF5007D9317 /* FBAccessibilityTraits.m in Sources */ = {isa = PBXBuildFile; fileRef = B316351B2DDF0CF5007D9317 /* FBAccessibilityTraits.m */; };
|
|
544
|
+
B316351F2DDF0D0B007D9317 /* FBAccessibilityTraits.h in Headers */ = {isa = PBXBuildFile; fileRef = B316351E2DDF0D0B007D9317 /* FBAccessibilityTraits.h */; };
|
|
545
|
+
B31635202DDF0D0B007D9317 /* FBAccessibilityTraits.h in Headers */ = {isa = PBXBuildFile; fileRef = B316351E2DDF0D0B007D9317 /* FBAccessibilityTraits.h */; };
|
|
542
546
|
C845206222D5E79400EA68CB /* FBUnattachedAppLauncher.h in Headers */ = {isa = PBXBuildFile; fileRef = C8FB547722D4C1FC00B69954 /* FBUnattachedAppLauncher.h */; };
|
|
543
547
|
C845206322D5E79700EA68CB /* FBUnattachedAppLauncher.m in Sources */ = {isa = PBXBuildFile; fileRef = C8FB547822D4C1FC00B69954 /* FBUnattachedAppLauncher.m */; };
|
|
544
548
|
C8FB547422D3949C00B69954 /* LSApplicationWorkspace.h in Headers */ = {isa = PBXBuildFile; fileRef = C8FB547322D3949C00B69954 /* LSApplicationWorkspace.h */; };
|
|
@@ -1115,6 +1119,8 @@
|
|
|
1115
1119
|
ADDA07221D6BB2BF001700AC /* FBScrollViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBScrollViewController.h; sourceTree = "<group>"; };
|
|
1116
1120
|
ADDA07231D6BB2BF001700AC /* FBScrollViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBScrollViewController.m; sourceTree = "<group>"; };
|
|
1117
1121
|
ADEF63AE1D09DEBE0070A7E3 /* FBRuntimeUtilsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBRuntimeUtilsTests.m; sourceTree = "<group>"; };
|
|
1122
|
+
B316351B2DDF0CF5007D9317 /* FBAccessibilityTraits.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBAccessibilityTraits.m; sourceTree = "<group>"; };
|
|
1123
|
+
B316351E2DDF0D0B007D9317 /* FBAccessibilityTraits.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBAccessibilityTraits.h; sourceTree = "<group>"; };
|
|
1118
1124
|
C8FB547322D3949C00B69954 /* LSApplicationWorkspace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LSApplicationWorkspace.h; sourceTree = "<group>"; };
|
|
1119
1125
|
C8FB547722D4C1FC00B69954 /* FBUnattachedAppLauncher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBUnattachedAppLauncher.h; sourceTree = "<group>"; };
|
|
1120
1126
|
C8FB547822D4C1FC00B69954 /* FBUnattachedAppLauncher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBUnattachedAppLauncher.m; sourceTree = "<group>"; };
|
|
@@ -1979,6 +1985,8 @@
|
|
|
1979
1985
|
EE6B64FC1D0F86EF00E85F5D /* XCTestPrivateSymbols.m */,
|
|
1980
1986
|
633E904A220DEE7F007CADF9 /* XCUIApplicationProcessDelay.h */,
|
|
1981
1987
|
6385F4A5220A40760095BBDB /* XCUIApplicationProcessDelay.m */,
|
|
1988
|
+
B316351B2DDF0CF5007D9317 /* FBAccessibilityTraits.m */,
|
|
1989
|
+
B316351E2DDF0D0B007D9317 /* FBAccessibilityTraits.h */,
|
|
1982
1990
|
);
|
|
1983
1991
|
name = Utilities;
|
|
1984
1992
|
path = WebDriverAgentLib/Utilities;
|
|
@@ -2420,6 +2428,7 @@
|
|
|
2420
2428
|
641EE6A62240C5CA00173FCB /* FBImageProcessor.h in Headers */,
|
|
2421
2429
|
641EE6A72240C5CA00173FCB /* FBSession-Private.h in Headers */,
|
|
2422
2430
|
641EE6A82240C5CA00173FCB /* NSString+FBXMLSafeString.h in Headers */,
|
|
2431
|
+
B316351F2DDF0D0B007D9317 /* FBAccessibilityTraits.h in Headers */,
|
|
2423
2432
|
64E3502F2AC0B6FE005F3ACB /* NSDictionary+FBUtf8SafeDictionary.h in Headers */,
|
|
2424
2433
|
641EE6A92240C5CA00173FCB /* FBCommandStatus.h in Headers */,
|
|
2425
2434
|
71822702258744A400661B83 /* HTTPResponseProxy.h in Headers */,
|
|
@@ -2712,6 +2721,7 @@
|
|
|
2712
2721
|
EE35AD571E3B77D600A02D78 /* XCTestSuiteRun.h in Headers */,
|
|
2713
2722
|
EE35AD701E3B77D600A02D78 /* XCUIElementAsynchronousHandlerWrapper.h in Headers */,
|
|
2714
2723
|
EE35AD4C1E3B77D600A02D78 /* XCTestLog.h in Headers */,
|
|
2724
|
+
B31635202DDF0D0B007D9317 /* FBAccessibilityTraits.h in Headers */,
|
|
2715
2725
|
71BB58E82B96328700CB9BFE /* FBScreenRecordingRequest.h in Headers */,
|
|
2716
2726
|
EE35AD231E3B77D600A02D78 /* UITapGestureRecognizer-RecordingAdditions.h in Headers */,
|
|
2717
2727
|
EE35AD2A1E3B77D600A02D78 /* XCDebugLogDelegate-Protocol.h in Headers */,
|
|
@@ -3173,6 +3183,7 @@
|
|
|
3173
3183
|
641EE6082240C5CA00173FCB /* FBRuntimeUtils.m in Sources */,
|
|
3174
3184
|
641EE6092240C5CA00173FCB /* XCUIElement+FBUtilities.m in Sources */,
|
|
3175
3185
|
641EE60A2240C5CA00173FCB /* FBLogger.m in Sources */,
|
|
3186
|
+
B316351D2DDF0CF5007D9317 /* FBAccessibilityTraits.m in Sources */,
|
|
3176
3187
|
641EE60B2240C5CA00173FCB /* FBCustomCommands.m in Sources */,
|
|
3177
3188
|
71BB58E42B9631F100CB9BFE /* FBScreenRecordingPromise.m in Sources */,
|
|
3178
3189
|
641EE60C2240C5CA00173FCB /* XCUIDevice+FBHelpers.m in Sources */,
|
|
@@ -3257,6 +3268,7 @@
|
|
|
3257
3268
|
71AE3CFA2D38EE8E0039FC36 /* XCUIElement+FBVisibleFrame.m in Sources */,
|
|
3258
3269
|
EEBBD48C1D47746D00656A81 /* XCUIElement+FBFind.m in Sources */,
|
|
3259
3270
|
EE158ADD1CBD456F00A3E3F0 /* FBResponsePayload.m in Sources */,
|
|
3271
|
+
B316351C2DDF0CF5007D9317 /* FBAccessibilityTraits.m in Sources */,
|
|
3260
3272
|
E444DCB524913C220060D7EB /* RouteRequest.m in Sources */,
|
|
3261
3273
|
C8FB547A22D4C1FC00B69954 /* FBUnattachedAppLauncher.m in Sources */,
|
|
3262
3274
|
EE158ADF1CBD456F00A3E3F0 /* FBRoute.m in Sources */,
|
|
@@ -48,6 +48,7 @@ static NSString* const FBExclusionAttributeAccessible = @"accessible";
|
|
|
48
48
|
static NSString* const FBExclusionAttributeFocused = @"focused";
|
|
49
49
|
static NSString* const FBExclusionAttributePlaceholderValue = @"placeholderValue";
|
|
50
50
|
static NSString* const FBExclusionAttributeNativeFrame = @"nativeFrame";
|
|
51
|
+
static NSString* const FBExclusionAttributeTraits = @"traits";
|
|
51
52
|
|
|
52
53
|
_Nullable id extractIssueProperty(id issue, NSString *propertyName) {
|
|
53
54
|
SEL selector = NSSelectorFromString(propertyName);
|
|
@@ -209,6 +210,7 @@ NSDictionary<NSString *, NSString *> *customExclusionAttributesMap(void) {
|
|
|
209
210
|
FBExclusionAttributeFrame,
|
|
210
211
|
FBExclusionAttributePlaceholderValue,
|
|
211
212
|
FBExclusionAttributeNativeFrame,
|
|
213
|
+
FBExclusionAttributeTraits,
|
|
212
214
|
nil];
|
|
213
215
|
|
|
214
216
|
for (NSString *key in attributeBlocks) {
|
|
@@ -265,6 +267,9 @@ NSDictionary<NSString *, NSString *> *customExclusionAttributesMap(void) {
|
|
|
265
267
|
},
|
|
266
268
|
FBExclusionAttributeFocused: ^{
|
|
267
269
|
return [@([wrappedSnapshot isWDFocused]) stringValue];
|
|
270
|
+
},
|
|
271
|
+
FBExclusionAttributeTraits: ^{
|
|
272
|
+
return wrappedSnapshot.wdTraits;
|
|
268
273
|
}
|
|
269
274
|
} mutableCopy];
|
|
270
275
|
|
|
@@ -36,7 +36,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
36
36
|
/**
|
|
37
37
|
Gets the most recent snapshot of the current element. The element will be
|
|
38
38
|
automatically resolved if the snapshot is not available yet.
|
|
39
|
-
Calls to this method mutate the `lastSnapshot` instance property
|
|
39
|
+
Calls to this method mutate the `lastSnapshot` instance property.
|
|
40
40
|
The maximum snapshot tree depth is set by `FBConfiguration.snapshotMaxDepth`
|
|
41
41
|
|
|
42
42
|
Snapshot specifics:
|
|
@@ -49,6 +49,21 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
49
49
|
*/
|
|
50
50
|
- (id<FBXCElementSnapshot>)fb_customSnapshot;
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
Gets the most recent snapshot of the current element. The element will be
|
|
54
|
+
automatically resolved if the snapshot is not available yet.
|
|
55
|
+
Calls to this method mutate the `lastSnapshot` instance property.
|
|
56
|
+
The maximum snapshot tree depth is set by `FBConfiguration.snapshotMaxDepth`
|
|
57
|
+
|
|
58
|
+
Snapshot specifics:
|
|
59
|
+
- Less performant in comparison to the standard one
|
|
60
|
+
- The `hittable` property calculation is aligned with the native calculation logic
|
|
61
|
+
|
|
62
|
+
@return The recent snapshot of the element
|
|
63
|
+
@throws FBStaleElementException if the element is not present in DOM and thus no snapshot could be made
|
|
64
|
+
*/
|
|
65
|
+
- (id<FBXCElementSnapshot>)fb_nativeSnapshot;
|
|
66
|
+
|
|
52
67
|
/**
|
|
53
68
|
Extracts the cached element snapshot from its query.
|
|
54
69
|
No requests to the accessiblity framework is made.
|
|
@@ -53,16 +53,7 @@
|
|
|
53
53
|
? [self.fb_query fb_uniqueSnapshotWithError:&error]
|
|
54
54
|
: (id<FBXCElementSnapshot>)[self snapshotWithError:&error];
|
|
55
55
|
if (nil == snapshot) {
|
|
56
|
-
|
|
57
|
-
if (nil != error && [error.localizedDescription containsString:@"Identity Binding"]) {
|
|
58
|
-
hintText = [NSString stringWithFormat:@"%@. You could also try to switch the binding strategy using the 'boundElementsByIndex' setting for the element lookup", hintText];
|
|
59
|
-
}
|
|
60
|
-
NSString *reason = [NSString stringWithFormat:@"The previously found element \"%@\" is not present in the current view anymore. %@",
|
|
61
|
-
self.description, hintText];
|
|
62
|
-
if (nil != error) {
|
|
63
|
-
reason = [NSString stringWithFormat:@"%@. Original error: %@", reason, error.localizedDescription];
|
|
64
|
-
}
|
|
65
|
-
@throw [NSException exceptionWithName:FBStaleElementException reason:reason userInfo:@{}];
|
|
56
|
+
[self fb_raiseStaleElementExceptionWithError:error];
|
|
66
57
|
}
|
|
67
58
|
}
|
|
68
59
|
self.lastSnapshot = snapshot;
|
|
@@ -79,6 +70,16 @@
|
|
|
79
70
|
return [self fb_takeSnapshot:YES];
|
|
80
71
|
}
|
|
81
72
|
|
|
73
|
+
- (id<FBXCElementSnapshot>)fb_nativeSnapshot
|
|
74
|
+
{
|
|
75
|
+
NSError *error = nil;
|
|
76
|
+
BOOL isSuccessful = [self resolveOrRaiseTestFailure:NO error:&error];
|
|
77
|
+
if (nil == self.lastSnapshot || !isSuccessful) {
|
|
78
|
+
[self fb_raiseStaleElementExceptionWithError:error];
|
|
79
|
+
}
|
|
80
|
+
return self.lastSnapshot;
|
|
81
|
+
}
|
|
82
|
+
|
|
82
83
|
- (id<FBXCElementSnapshot>)fb_cachedSnapshot
|
|
83
84
|
{
|
|
84
85
|
return [self.query fb_cachedSnapshot];
|
|
@@ -153,4 +154,18 @@
|
|
|
153
154
|
FBConfiguration.waitForIdleTimeout = previousTimeout;
|
|
154
155
|
}
|
|
155
156
|
|
|
157
|
+
- (void)fb_raiseStaleElementExceptionWithError:(NSError *)error __attribute__((noreturn))
|
|
158
|
+
{
|
|
159
|
+
NSString *hintText = @"Make sure the application UI has the expected state";
|
|
160
|
+
if (nil != error && [error.localizedDescription containsString:@"Identity Binding"]) {
|
|
161
|
+
hintText = [NSString stringWithFormat:@"%@. You could also try to switch the binding strategy using the 'boundElementsByIndex' setting for the element lookup", hintText];
|
|
162
|
+
}
|
|
163
|
+
NSString *reason = [NSString stringWithFormat:@"The previously found element \"%@\" is not present in the current view anymore. %@",
|
|
164
|
+
self.description, hintText];
|
|
165
|
+
if (nil != error) {
|
|
166
|
+
reason = [NSString stringWithFormat:@"%@. Original error: %@", reason, error.localizedDescription];
|
|
167
|
+
}
|
|
168
|
+
@throw [NSException exceptionWithName:FBStaleElementException reason:reason userInfo:@{}];
|
|
169
|
+
}
|
|
170
|
+
|
|
156
171
|
@end
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
#import "FBElementUtils.h"
|
|
23
23
|
#import "XCTestPrivateSymbols.h"
|
|
24
24
|
#import "XCUIHitPointResult.h"
|
|
25
|
+
#import "FBAccessibilityTraits.h"
|
|
25
26
|
|
|
26
27
|
#define BROKEN_RECT CGRectMake(-1, -1, 0, 0)
|
|
27
28
|
|
|
@@ -29,6 +30,10 @@
|
|
|
29
30
|
|
|
30
31
|
- (id<FBXCElementSnapshot>)fb_snapshotForAttributeName:(NSString *)name
|
|
31
32
|
{
|
|
33
|
+
// https://github.com/appium/appium-xcuitest-driver/pull/2565
|
|
34
|
+
if ([name isEqualToString:FBStringify(XCUIElement, isWDHittable)]) {
|
|
35
|
+
return [self fb_nativeSnapshot];
|
|
36
|
+
}
|
|
32
37
|
// https://github.com/appium/appium-xcuitest-driver/issues/2552
|
|
33
38
|
BOOL isValueRequest = [name isEqualToString:FBStringify(XCUIElement, wdValue)];
|
|
34
39
|
if ([self isKindOfClass:XCUIApplication.class] && !isValueRequest) {
|
|
@@ -155,6 +160,22 @@
|
|
|
155
160
|
return self.frame;
|
|
156
161
|
}
|
|
157
162
|
|
|
163
|
+
/**
|
|
164
|
+
Returns a comma-separated string of accessibility traits for the element.
|
|
165
|
+
This method converts the element's accessibility traits bitmask into human-readable strings
|
|
166
|
+
using FBAccessibilityTraitsToStringsArray. The traits represent various accessibility
|
|
167
|
+
characteristics of the element such as Button, Link, Image, etc.
|
|
168
|
+
You can find the list of possible traits in the Apple documentation:
|
|
169
|
+
https://developer.apple.com/documentation/uikit/uiaccessibilitytraits?language=objc
|
|
170
|
+
|
|
171
|
+
@return A comma-separated string of accessibility traits, or an empty string if no traits are set
|
|
172
|
+
*/
|
|
173
|
+
- (NSString *)wdTraits
|
|
174
|
+
{
|
|
175
|
+
NSArray<NSString *> *traits = FBAccessibilityTraitsToStringsArray(self.snapshot.traits);
|
|
176
|
+
return [traits componentsJoinedByString:@", "];
|
|
177
|
+
}
|
|
178
|
+
|
|
158
179
|
- (BOOL)isWDVisible
|
|
159
180
|
{
|
|
160
181
|
return self.fb_isVisible;
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
<key>CFBundlePackageType</key>
|
|
16
16
|
<string>FMWK</string>
|
|
17
17
|
<key>CFBundleShortVersionString</key>
|
|
18
|
-
<string>9.
|
|
18
|
+
<string>9.10.0</string>
|
|
19
19
|
<key>CFBundleSignature</key>
|
|
20
20
|
<string>????</string>
|
|
21
21
|
<key>CFBundleVersion</key>
|
|
22
|
-
<string>9.
|
|
22
|
+
<string>9.10.0</string>
|
|
23
23
|
<key>NSPrincipalClass</key>
|
|
24
24
|
<string/>
|
|
25
25
|
</dict>
|
|
@@ -38,6 +38,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
38
38
|
/*! Element's type */
|
|
39
39
|
@property (nonatomic, readonly, copy) NSString *wdType;
|
|
40
40
|
|
|
41
|
+
/*! Element's accessibility traits as a comma-separated string */
|
|
42
|
+
@property (nonatomic, readonly, copy) NSString *wdTraits;
|
|
43
|
+
|
|
41
44
|
/*! Element's value */
|
|
42
45
|
@property (nonatomic, readonly, strong, nullable) NSString *wdValue;
|
|
43
46
|
|
|
@@ -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. An additional grant
|
|
7
|
+
* of patent rights can be found in the PATENTS file in the same directory.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#import <XCTest/XCTest.h>
|
|
11
|
+
|
|
12
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
Converts accessibility traits bitmask to an array of string representations
|
|
16
|
+
@param traits The accessibility traits bitmask
|
|
17
|
+
@return Array of strings representing the accessibility traits
|
|
18
|
+
*/
|
|
19
|
+
NSArray<NSString *> *FBAccessibilityTraitsToStringsArray(unsigned long long traits);
|
|
20
|
+
|
|
21
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,62 @@
|
|
|
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. An additional grant
|
|
7
|
+
* of patent rights can be found in the PATENTS file in the same directory.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#import "FBAccessibilityTraits.h"
|
|
11
|
+
|
|
12
|
+
NSArray<NSString *> *FBAccessibilityTraitsToStringsArray(unsigned long long traits) {
|
|
13
|
+
NSMutableArray<NSString *> *traitStringsArray;
|
|
14
|
+
NSNumber *key;
|
|
15
|
+
|
|
16
|
+
static NSDictionary<NSNumber *, NSString *> *traitsMapping;
|
|
17
|
+
static dispatch_once_t onceToken;
|
|
18
|
+
|
|
19
|
+
dispatch_once(&onceToken, ^{
|
|
20
|
+
NSMutableDictionary<NSNumber *, NSString *> *mapping = [@{
|
|
21
|
+
@(UIAccessibilityTraitNone): @"None",
|
|
22
|
+
@(UIAccessibilityTraitButton): @"Button",
|
|
23
|
+
@(UIAccessibilityTraitLink): @"Link",
|
|
24
|
+
@(UIAccessibilityTraitHeader): @"Header",
|
|
25
|
+
@(UIAccessibilityTraitSearchField): @"SearchField",
|
|
26
|
+
@(UIAccessibilityTraitImage): @"Image",
|
|
27
|
+
@(UIAccessibilityTraitSelected): @"Selected",
|
|
28
|
+
@(UIAccessibilityTraitPlaysSound): @"PlaysSound",
|
|
29
|
+
@(UIAccessibilityTraitKeyboardKey): @"KeyboardKey",
|
|
30
|
+
@(UIAccessibilityTraitStaticText): @"StaticText",
|
|
31
|
+
@(UIAccessibilityTraitSummaryElement): @"SummaryElement",
|
|
32
|
+
@(UIAccessibilityTraitNotEnabled): @"NotEnabled",
|
|
33
|
+
@(UIAccessibilityTraitUpdatesFrequently): @"UpdatesFrequently",
|
|
34
|
+
@(UIAccessibilityTraitStartsMediaSession): @"StartsMediaSession",
|
|
35
|
+
@(UIAccessibilityTraitAdjustable): @"Adjustable",
|
|
36
|
+
@(UIAccessibilityTraitAllowsDirectInteraction): @"AllowsDirectInteraction",
|
|
37
|
+
@(UIAccessibilityTraitCausesPageTurn): @"CausesPageTurn",
|
|
38
|
+
@(UIAccessibilityTraitTabBar): @"TabBar"
|
|
39
|
+
} mutableCopy];
|
|
40
|
+
|
|
41
|
+
#if __clang_major__ >= 16
|
|
42
|
+
// Add iOS 17.0 specific traits if available
|
|
43
|
+
if (@available(iOS 17.0, *)) {
|
|
44
|
+
[mapping addEntriesFromDictionary:@{
|
|
45
|
+
@(UIAccessibilityTraitToggleButton): @"ToggleButton",
|
|
46
|
+
@(UIAccessibilityTraitSupportsZoom): @"SupportsZoom"
|
|
47
|
+
}];
|
|
48
|
+
}
|
|
49
|
+
#endif
|
|
50
|
+
|
|
51
|
+
traitsMapping = [mapping copy];
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
traitStringsArray = [NSMutableArray array];
|
|
55
|
+
for (key in traitsMapping) {
|
|
56
|
+
if (traits & [key unsignedLongLongValue] && nil != traitsMapping[key]) {
|
|
57
|
+
[traitStringsArray addObject:(id)traitsMapping[key]];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return [traitStringsArray copy];
|
|
62
|
+
}
|
|
@@ -146,7 +146,8 @@ static NSString *const topNodeIndexPath = @"top";
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
if (rc >= 0) {
|
|
149
|
-
|
|
149
|
+
[self waitUntilStableWithElement:root];
|
|
150
|
+
rc = [self xmlRepresentationWithRootElement:[self snapshotWithRoot:root useNative:NO]
|
|
150
151
|
writer:writer
|
|
151
152
|
elementStore:nil
|
|
152
153
|
query:nil
|
|
@@ -196,17 +197,23 @@ static NSString *const topNodeIndexPath = @"top";
|
|
|
196
197
|
int rc = xmlTextWriterStartDocument(writer, NULL, _UTF8Encoding, NULL);
|
|
197
198
|
id<FBXCElementSnapshot> lookupScopeSnapshot = nil;
|
|
198
199
|
id<FBXCElementSnapshot> contextRootSnapshot = nil;
|
|
200
|
+
BOOL useNativeSnapshot = nil == xpathQuery
|
|
201
|
+
? NO
|
|
202
|
+
: [[self.class elementAttributesWithXPathQuery:xpathQuery] containsObject:FBHittableAttribute.class];
|
|
199
203
|
if (rc < 0) {
|
|
200
204
|
[FBLogger logFmt:@"Failed to invoke libxml2>xmlTextWriterStartDocument. Error code: %d", rc];
|
|
201
205
|
} else {
|
|
206
|
+
[self waitUntilStableWithElement:root];
|
|
202
207
|
if (FBConfiguration.limitXpathContextScope) {
|
|
203
|
-
lookupScopeSnapshot = [self snapshotWithRoot:root];
|
|
208
|
+
lookupScopeSnapshot = [self snapshotWithRoot:root useNative:useNativeSnapshot];
|
|
204
209
|
} else {
|
|
205
210
|
if ([root isKindOfClass:XCUIElement.class]) {
|
|
206
|
-
lookupScopeSnapshot = [self snapshotWithRoot:[(XCUIElement *)root application]
|
|
211
|
+
lookupScopeSnapshot = [self snapshotWithRoot:[(XCUIElement *)root application]
|
|
212
|
+
useNative:useNativeSnapshot];
|
|
207
213
|
contextRootSnapshot = [root isKindOfClass:XCUIApplication.class]
|
|
208
214
|
? nil
|
|
209
|
-
: ([(XCUIElement *)root lastSnapshot] ?: [(XCUIElement *)root
|
|
215
|
+
: ([(XCUIElement *)root lastSnapshot] ?: [self snapshotWithRoot:(XCUIElement *)root
|
|
216
|
+
useNative:useNativeSnapshot]);
|
|
210
217
|
} else {
|
|
211
218
|
lookupScopeSnapshot = (id<FBXCElementSnapshot>)root;
|
|
212
219
|
contextRootSnapshot = nil == lookupScopeSnapshot.parent ? nil : (id<FBXCElementSnapshot>)root;
|
|
@@ -483,19 +490,29 @@ static NSString *const topNodeIndexPath = @"top";
|
|
|
483
490
|
}
|
|
484
491
|
|
|
485
492
|
+ (id<FBXCElementSnapshot>)snapshotWithRoot:(id<FBElement>)root
|
|
493
|
+
useNative:(BOOL)useNative
|
|
486
494
|
{
|
|
487
495
|
if (![root isKindOfClass:XCUIElement.class]) {
|
|
488
496
|
return (id<FBXCElementSnapshot>)root;
|
|
489
497
|
}
|
|
490
498
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
499
|
+
if (useNative) {
|
|
500
|
+
return [(XCUIElement *)root fb_nativeSnapshot];
|
|
501
|
+
}
|
|
494
502
|
return [root isKindOfClass:XCUIApplication.class]
|
|
495
503
|
? [(XCUIElement *)root fb_standardSnapshot]
|
|
496
504
|
: [(XCUIElement *)root fb_customSnapshot];
|
|
497
505
|
}
|
|
498
506
|
|
|
507
|
+
+ (void)waitUntilStableWithElement:(id<FBElement>)root
|
|
508
|
+
{
|
|
509
|
+
if ([root isKindOfClass:XCUIElement.class]) {
|
|
510
|
+
// If the app is not idle state while we retrieve the visiblity state
|
|
511
|
+
// then the snapshot retrieval operation might freeze and time out
|
|
512
|
+
[[(XCUIElement *)root application] fb_waitUntilStableWithTimeout:FBConfiguration.animationCoolOffTimeout];
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
499
516
|
@end
|
|
500
517
|
|
|
501
518
|
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
#import "XCUIElement+FBAccessibility.h"
|
|
17
17
|
#import "XCUIElement+FBIsVisible.h"
|
|
18
18
|
#import "XCUIElement+FBWebDriverAttributes.h"
|
|
19
|
-
#import "FBXCodeCompatibility.h"
|
|
20
19
|
|
|
21
20
|
@interface FBElementAttributeTests : FBIntegrationTestCase
|
|
22
21
|
@end
|
|
@@ -97,6 +96,52 @@
|
|
|
97
96
|
XCTAssertEqual(element2.wdIndex, 0);
|
|
98
97
|
}
|
|
99
98
|
|
|
99
|
+
- (void)testAccessibilityTraits
|
|
100
|
+
{
|
|
101
|
+
XCUIElement *button = self.testedApplication.buttons.firstMatch;
|
|
102
|
+
XCTAssertTrue(button.exists);
|
|
103
|
+
NSArray *buttonTraits = [button.wdTraits componentsSeparatedByString:@", "];
|
|
104
|
+
NSArray *expectedButtonTraits = @[@"Button"];
|
|
105
|
+
XCTAssertEqual(buttonTraits.count, expectedButtonTraits.count, @"Button should have exactly 1 trait");
|
|
106
|
+
XCTAssertEqualObjects(buttonTraits, expectedButtonTraits);
|
|
107
|
+
XCTAssertEqualObjects(button.wdType, @"XCUIElementTypeButton");
|
|
108
|
+
|
|
109
|
+
XCUIElement *toggle = self.testedApplication.switches.firstMatch;
|
|
110
|
+
XCTAssertTrue(toggle.exists);
|
|
111
|
+
|
|
112
|
+
// iOS 17.0 specific traits if available
|
|
113
|
+
NSArray *toggleTraits = [toggle.wdTraits componentsSeparatedByString:@", "];
|
|
114
|
+
NSArray *expectedToggleTraits;
|
|
115
|
+
|
|
116
|
+
#if __clang_major__ >= 16
|
|
117
|
+
if (@available(iOS 17.0, *)) {
|
|
118
|
+
expectedToggleTraits = @[@"ToggleButton", @"Button"];
|
|
119
|
+
XCTAssertEqual(toggleTraits.count, 2, @"Toggle should have exactly 2 traits on iOS 17+");
|
|
120
|
+
}
|
|
121
|
+
#else
|
|
122
|
+
expectedToggleTraits = @[@"Button"];
|
|
123
|
+
XCTAssertEqual(toggleTraits.count, 1, @"Toggle should have exactly 1 trait on iOS < 17");
|
|
124
|
+
#endif
|
|
125
|
+
XCTAssertEqualObjects(toggleTraits, expectedToggleTraits);
|
|
126
|
+
XCTAssertEqualObjects(toggle.wdType, @"XCUIElementTypeSwitch");
|
|
127
|
+
|
|
128
|
+
XCUIElement *slider = self.testedApplication.sliders.firstMatch;
|
|
129
|
+
XCTAssertTrue(slider.exists);
|
|
130
|
+
NSArray *sliderTraits = [slider.wdTraits componentsSeparatedByString:@", "];
|
|
131
|
+
NSArray *expectedSliderTraits = @[@"Adjustable"];
|
|
132
|
+
XCTAssertEqual(sliderTraits.count, expectedSliderTraits.count, @"Slider should have exactly 1 trait");
|
|
133
|
+
XCTAssertEqualObjects(sliderTraits, expectedSliderTraits);
|
|
134
|
+
XCTAssertEqualObjects(slider.wdType, @"XCUIElementTypeSlider");
|
|
135
|
+
|
|
136
|
+
XCUIElement *picker = self.testedApplication.pickerWheels.firstMatch;
|
|
137
|
+
XCTAssertTrue(picker.exists);
|
|
138
|
+
NSArray *pickerTraits = [picker.wdTraits componentsSeparatedByString:@", "];
|
|
139
|
+
NSArray *expectedPickerTraits = @[@"Adjustable"];
|
|
140
|
+
XCTAssertEqual(pickerTraits.count, expectedPickerTraits.count, @"Picker should have exactly 1 trait");
|
|
141
|
+
XCTAssertEqualObjects(pickerTraits, expectedPickerTraits);
|
|
142
|
+
XCTAssertEqualObjects(picker.wdType, @"XCUIElementTypePickerWheel");
|
|
143
|
+
}
|
|
144
|
+
|
|
100
145
|
- (void)testTextFieldAttributes
|
|
101
146
|
{
|
|
102
147
|
XCUIElement *element = self.testedApplication.textFields[@"Value"];
|
|
@@ -157,7 +202,7 @@
|
|
|
157
202
|
XCTAssertNil(element.wdPlaceholderValue);
|
|
158
203
|
XCTAssertEqualObjects(element.wdValue, @"1");
|
|
159
204
|
XCTAssertFalse(element.wdSelected);
|
|
160
|
-
|
|
205
|
+
XCTAssertEqual(element.wdHittable, element.hittable);
|
|
161
206
|
[element tap];
|
|
162
207
|
XCTAssertEqualObjects(element.wdValue, @"0");
|
|
163
208
|
XCTAssertFalse(element.wdSelected);
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
#import "FBMacros.h"
|
|
16
16
|
#import "XCUIElement+FBIsVisible.h"
|
|
17
17
|
#import "XCUIElement+FBScrolling.h"
|
|
18
|
-
|
|
19
18
|
#import "XCUIElement+FBClassChain.h"
|
|
20
19
|
#import "FBXCodeCompatibility.h"
|
|
21
20
|
|
|
@@ -43,8 +42,12 @@
|
|
|
43
42
|
{
|
|
44
43
|
FBAssertVisibleCell(@"0");
|
|
45
44
|
FBAssertVisibleCell(@"10");
|
|
45
|
+
XCUIElement *cell10 = FBCellElementWithLabel(@"10");
|
|
46
|
+
XCTAssertEqual([cell10 isWDHittable], [cell10 isHittable]);
|
|
46
47
|
FBAssertInvisibleCell(@"30");
|
|
47
48
|
FBAssertInvisibleCell(@"50");
|
|
49
|
+
XCUIElement *cell50 = FBCellElementWithLabel(@"50");
|
|
50
|
+
XCTAssertEqual([cell50 isWDHittable], [cell50 isHittable]);
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
- (void)testSimpleScroll
|
|
@@ -461,4 +461,13 @@
|
|
|
461
461
|
XCTAssertFalse(matchingSnapshots.lastObject.fb_isVisible);
|
|
462
462
|
}
|
|
463
463
|
|
|
464
|
+
- (void)testNonHittableDescendantWithXPathQuery
|
|
465
|
+
{
|
|
466
|
+
NSArray<XCUIElement *> *matchingSnapshots = [self.testedApplication fb_descendantsMatchingXPathQuery:@"//XCUIElementTypeStaticText[@hittable='false']"
|
|
467
|
+
shouldReturnAfterFirstMatch:NO];
|
|
468
|
+
XCTAssertGreaterThan(matchingSnapshots.count, 1);
|
|
469
|
+
XCTAssertEqual(matchingSnapshots.lastObject.elementType, XCUIElementTypeStaticText);
|
|
470
|
+
XCTAssertFalse(matchingSnapshots.lastObject.fb_isVisible);
|
|
471
|
+
}
|
|
472
|
+
|
|
464
473
|
@end
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
@property (nonatomic, strong, readwrite, nullable) NSString *wdValue;
|
|
29
29
|
@property (nonatomic, readwrite, getter=isWDEnabled) BOOL wdEnabled;
|
|
30
30
|
@property (nonatomic, readwrite, getter=isWDSelected) BOOL wdSelected;
|
|
31
|
+
@property (nonatomic, readwrite, assign) CGRect wdNativeFrame;
|
|
31
32
|
@property (nonatomic, readwrite) NSUInteger wdIndex;
|
|
32
33
|
@property (nonatomic, readwrite, getter=isWDVisible) BOOL wdVisible;
|
|
33
34
|
@property (nonatomic, readwrite, getter=isWDAccessible) BOOL wdAccessible;
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
@property (copy, nonnull) NSArray *children;
|
|
38
39
|
@property (nonatomic, readwrite, assign) XCUIElementType elementType;
|
|
39
40
|
@property (nonatomic, readwrite, getter=isWDAccessibilityContainer) BOOL wdAccessibilityContainer;
|
|
41
|
+
@property (nonatomic, copy, readwrite, nullable) NSString *wdTraits;
|
|
40
42
|
|
|
41
43
|
- (void)resolve;
|
|
42
44
|
- (id _Nonnull)fb_standardSnapshot;
|
|
@@ -19,11 +19,13 @@
|
|
|
19
19
|
{
|
|
20
20
|
self = [super init];
|
|
21
21
|
if (self) {
|
|
22
|
-
self.wdFrame =
|
|
22
|
+
self.wdFrame = CGRectZero;
|
|
23
|
+
self.wdNativeFrame = CGRectZero;
|
|
23
24
|
self.wdName = @"testName";
|
|
24
25
|
self.wdLabel = @"testLabel";
|
|
25
26
|
self.wdValue = @"magicValue";
|
|
26
27
|
self.wdPlaceholderValue = @"testPlaceholderValue";
|
|
28
|
+
self.wdTraits = @"testTraits";
|
|
27
29
|
self.wdVisible = YES;
|
|
28
30
|
self.wdAccessible = YES;
|
|
29
31
|
self.wdEnabled = YES;
|
|
@@ -94,4 +96,9 @@
|
|
|
94
96
|
return self.wdUID;
|
|
95
97
|
}
|
|
96
98
|
|
|
99
|
+
- (NSString *)wdTraits
|
|
100
|
+
{
|
|
101
|
+
return self.wdTraits;
|
|
102
|
+
}
|
|
103
|
+
|
|
97
104
|
@end
|