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 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
- NSString *hintText = @"Make sure the application UI has the expected state";
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.8.0</string>
18
+ <string>9.10.0</string>
19
19
  <key>CFBundleSignature</key>
20
20
  <string>????</string>
21
21
  <key>CFBundleVersion</key>
22
- <string>9.8.0</string>
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
- rc = [self xmlRepresentationWithRootElement:[self snapshotWithRoot:root]
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 fb_customSnapshot]);
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
- // If the app is not idle state while we retrieve the visiblity state
492
- // then the snapshot retrieval operation might freeze and time out
493
- [[(XCUIElement *)root application] fb_waitUntilStableWithTimeout:FBConfiguration.animationCoolOffTimeout];
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
- XCTAssertTrue(element.wdHittable);
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 = CGRectMake(0, 0, 0, 0);
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appium-webdriveragent",
3
- "version": "9.8.0",
3
+ "version": "9.10.0",
4
4
  "description": "Package bundling WebDriverAgent",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",