appium-mac2-driver 4.0.7 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/WebDriverAgentMac/IntegrationTests/AMElementAttributesTests.m +14 -0
- package/WebDriverAgentMac/IntegrationTests/AMFindElementTests.m +20 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Categories/XCUIElement+AMAttributes.h +10 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Categories/XCUIElement+AMAttributes.m +12 -1
- package/WebDriverAgentMac/WebDriverAgentLib/Categories/XCUIElement+FBFind.m +64 -1
- package/WebDriverAgentMac/WebDriverAgentLib/Commands/FBSessionCommands.m +5 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMSettings.h +3 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMSettings.m +1 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMSnapshotUtils.h +27 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMSnapshotUtils.m +135 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/FBConfiguration.h +3 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/FBConfiguration.m +11 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/FBXPath.m +6 -17
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [4.1.0](https://github.com/appium/appium-mac2-driver/compare/v4.0.7...v4.1.0) (2026-08-07)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* locate WKWebView web elements by DOM id via AXDOMIdentifier (opt-in) ([#405](https://github.com/appium/appium-mac2-driver/issues/405)) ([9f7cf35](https://github.com/appium/appium-mac2-driver/commit/9f7cf3570fef0c8a449dcef038563427807e8cd7))
|
|
6
|
+
|
|
1
7
|
## [4.0.7](https://github.com/appium/appium-mac2-driver/compare/v4.0.6...v4.0.7) (2026-08-06)
|
|
2
8
|
|
|
3
9
|
### Miscellaneous Chores
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
#import <XCTest/XCTest.h>
|
|
18
18
|
|
|
19
19
|
#import "AMIntegrationTestCase.h"
|
|
20
|
+
#import "FBConfiguration.h"
|
|
20
21
|
#import "FBTestMacros.h"
|
|
21
22
|
#import "XCUIElement+AMAttributes.h"
|
|
22
23
|
#import "XCUIElement+AMEditable.h"
|
|
@@ -76,6 +77,19 @@
|
|
|
76
77
|
XCTAssertEqual(type, XCUIElementTypeButton);
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
- (void)testIdentifierAttributeWhileDomIdFallbackIsEnabled
|
|
81
|
+
{
|
|
82
|
+
XCUIElement *closeButton = [self.testedApplication.buttons matchingIdentifier:@"_XCUI:CloseWindow"].firstMatch;
|
|
83
|
+
FBConfiguration.sharedConfiguration.useDomIdAsAccessibilityId = YES;
|
|
84
|
+
@try {
|
|
85
|
+
// A native identifier must never be replaced by a DOM one
|
|
86
|
+
NSString *identifier = [closeButton am_wdAttributeValueWithName:@"identifier"];
|
|
87
|
+
XCTAssertEqualObjects(identifier, @"_XCUI:CloseWindow");
|
|
88
|
+
} @finally {
|
|
89
|
+
FBConfiguration.sharedConfiguration.useDomIdAsAccessibilityId = NO;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
79
93
|
- (void)testDisabledAttribute
|
|
80
94
|
{
|
|
81
95
|
NSString *buttonTitle = @"I Am Disabled";
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
#import <XCTest/XCTest.h>
|
|
18
18
|
|
|
19
19
|
#import "AMIntegrationTestCase.h"
|
|
20
|
+
#import "FBConfiguration.h"
|
|
20
21
|
#import "XCUIElement+FBFind.h"
|
|
21
22
|
#import "XCUIElement+FBClassChain.h"
|
|
22
23
|
|
|
@@ -43,6 +44,25 @@
|
|
|
43
44
|
XCTAssertEqualObjects(matches.firstObject.identifier, @"_XCUI:CloseWindow");
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
- (void)testSingleDescendantWithIdentifierWhileDomIdFallbackIsEnabled
|
|
48
|
+
{
|
|
49
|
+
FBConfiguration.sharedConfiguration.useDomIdAsAccessibilityId = YES;
|
|
50
|
+
@try {
|
|
51
|
+
// The application under test hosts no web content, so the fallback must find
|
|
52
|
+
// nothing extra and native identifiers must keep resolving as usual
|
|
53
|
+
NSArray<XCUIElement *> *matches = [self.testedApplication fb_descendantsMatchingIdentifier:@"_XCUI:CloseWindow"
|
|
54
|
+
shouldReturnAfterFirstMatch:NO];
|
|
55
|
+
XCTAssertEqual(matches.count, 1);
|
|
56
|
+
XCTAssertEqualObjects(matches.firstObject.identifier, @"_XCUI:CloseWindow");
|
|
57
|
+
|
|
58
|
+
NSArray<XCUIElement *> *noMatches = [self.testedApplication fb_descendantsMatchingIdentifier:@"there_is_no_such_dom_id"
|
|
59
|
+
shouldReturnAfterFirstMatch:NO];
|
|
60
|
+
XCTAssertEqual(noMatches.count, 0);
|
|
61
|
+
} @finally {
|
|
62
|
+
FBConfiguration.sharedConfiguration.useDomIdAsAccessibilityId = NO;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
46
66
|
- (void)testSingleDescendantWithClassName
|
|
47
67
|
{
|
|
48
68
|
NSArray<XCUIElement *> *matches = [self.testedApplication fb_descendantsMatchingClassName:@"XCUIElementTypeButton"
|
|
@@ -50,6 +50,16 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
50
50
|
*/
|
|
51
51
|
- (BOOL)am_hasKeyboardInputFocus;
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
The element's identifier as WebDriver reports it: the standard accessibility
|
|
55
|
+
identifier, falling back to the WebKit DOM identifier for web nodes, which leave the
|
|
56
|
+
standard one empty. The fallback only applies while useDomIdAsAccessibilityId is
|
|
57
|
+
enabled, and only takes a snapshot when it is actually needed.
|
|
58
|
+
|
|
59
|
+
@return The identifier. Could be an empty string
|
|
60
|
+
*/
|
|
61
|
+
- (NSString *)am_wdIdentifier;
|
|
62
|
+
|
|
53
63
|
@end
|
|
54
64
|
|
|
55
65
|
NS_ASSUME_NONNULL_END
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
#import "XCUIElement+AMAttributes.h"
|
|
18
18
|
|
|
19
19
|
#import "AMGeometryUtils.h"
|
|
20
|
+
#import "AMSnapshotUtils.h"
|
|
20
21
|
#import "FBConfiguration.h"
|
|
21
22
|
#import "FBElementTypeTransformer.h"
|
|
22
23
|
#import "FBElementUtils.h"
|
|
@@ -50,7 +51,7 @@
|
|
|
50
51
|
} else if ([wdAttributeName isEqualToString:FBStringify(XCUIElement, value)]) {
|
|
51
52
|
return [FBElementUtils stringValueWithValue:self.value];
|
|
52
53
|
} else if ([wdAttributeName isEqualToString:FBStringify(XCUIElement, identifier)]) {
|
|
53
|
-
return self.
|
|
54
|
+
return self.am_wdIdentifier;
|
|
54
55
|
}
|
|
55
56
|
// This should not happen
|
|
56
57
|
NSString *description = [NSString stringWithFormat:@"The attribute '%@' is unknown", wdAttributeName];
|
|
@@ -59,6 +60,16 @@
|
|
|
59
60
|
userInfo:@{}];
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
- (NSString *)am_wdIdentifier
|
|
64
|
+
{
|
|
65
|
+
NSString *identifier = self.identifier;
|
|
66
|
+
// Snapshotting is not free, so only pay for it when the fallback can apply.
|
|
67
|
+
if (identifier.length > 0 || !FBConfiguration.sharedConfiguration.useDomIdAsAccessibilityId) {
|
|
68
|
+
return identifier;
|
|
69
|
+
}
|
|
70
|
+
return [AMSnapshotUtils wdIdentifierWithSnapshot:[self snapshotWithError:nil]] ?: identifier;
|
|
71
|
+
}
|
|
72
|
+
|
|
62
73
|
- (NSDictionary<NSString *, NSNumber *> *)am_rect
|
|
63
74
|
{
|
|
64
75
|
return AMCGRectToDict(self.frame);
|
|
@@ -9,12 +9,50 @@
|
|
|
9
9
|
|
|
10
10
|
#import "XCUIElement+FBFind.h"
|
|
11
11
|
|
|
12
|
+
#import "AMSnapshotUtils.h"
|
|
13
|
+
|
|
14
|
+
#import "FBConfiguration.h"
|
|
12
15
|
#import "FBElementTypeTransformer.h"
|
|
13
16
|
#import "FBElementUtils.h"
|
|
14
17
|
#import "FBXPath.h"
|
|
15
18
|
#import "NSPredicate+FBFormat.h"
|
|
16
19
|
#import "XCUIElementQuery+AMHelpers.h"
|
|
17
20
|
|
|
21
|
+
/**
|
|
22
|
+
Collects the hashes of snapshot tree nodes whose identifier equals the given value.
|
|
23
|
+
|
|
24
|
+
@return YES if the caller should stop descending, which happens as soon as a match is
|
|
25
|
+
found while only the first one is wanted
|
|
26
|
+
*/
|
|
27
|
+
static BOOL AMCollectIdentifierMatches(id<XCUIElementSnapshot> snapshot,
|
|
28
|
+
NSString *accessibilityId,
|
|
29
|
+
BOOL firstMatchOnly,
|
|
30
|
+
NSMutableSet<NSString *> *matchedHashes)
|
|
31
|
+
{
|
|
32
|
+
if (nil == snapshot) {
|
|
33
|
+
return NO;
|
|
34
|
+
}
|
|
35
|
+
if ([[AMSnapshotUtils wdIdentifierWithSnapshot:snapshot] isEqualToString:accessibilityId]) {
|
|
36
|
+
[matchedHashes addObject:[AMSnapshotUtils hashWithSnapshot:snapshot]];
|
|
37
|
+
if (firstMatchOnly) {
|
|
38
|
+
return YES;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (id<XCUIElementSnapshot> child in snapshot.children) {
|
|
42
|
+
if (AMCollectIdentifierMatches(child, accessibilityId, firstMatchOnly, matchedHashes)) {
|
|
43
|
+
return YES;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return NO;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@interface XCUIElement (FBFindPrivate)
|
|
50
|
+
|
|
51
|
+
- (NSArray<XCUIElement *> *)am_descendantsMatchingDomIdentifier:(NSString *)accessibilityId
|
|
52
|
+
shouldReturnAfterFirstMatch:(BOOL)shouldReturnAfterFirstMatch;
|
|
53
|
+
|
|
54
|
+
@end
|
|
55
|
+
|
|
18
56
|
@implementation XCUIElement (FBFind)
|
|
19
57
|
|
|
20
58
|
+ (NSArray<XCUIElement *> *)fb_extractMatchingElementsFromQuery:(XCUIElementQuery *)query
|
|
@@ -95,7 +133,32 @@
|
|
|
95
133
|
XCUIElementQuery *query = [[self descendantsMatchingType:XCUIElementTypeAny] matchingIdentifier:accessibilityId];
|
|
96
134
|
[result addObjectsFromArray:[self.class fb_extractMatchingElementsFromQuery:query
|
|
97
135
|
shouldReturnAfterFirstMatch:shouldReturnAfterFirstMatch]];
|
|
98
|
-
|
|
136
|
+
if (result.count > 0
|
|
137
|
+
|| !FBConfiguration.sharedConfiguration.useDomIdAsAccessibilityId) {
|
|
138
|
+
return result.copy;
|
|
139
|
+
}
|
|
140
|
+
// WebKit leaves the standard accessibility identifier of web nodes empty, so the
|
|
141
|
+
// above query cannot match them. Retry through their DOM identifiers.
|
|
142
|
+
return [self am_descendantsMatchingDomIdentifier:accessibilityId
|
|
143
|
+
shouldReturnAfterFirstMatch:shouldReturnAfterFirstMatch];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
- (NSArray<XCUIElement *> *)am_descendantsMatchingDomIdentifier:(NSString *)accessibilityId
|
|
147
|
+
shouldReturnAfterFirstMatch:(BOOL)shouldReturnAfterFirstMatch
|
|
148
|
+
{
|
|
149
|
+
// Walk a single root snapshot in memory and only resolve the matching nodes back
|
|
150
|
+
// to elements. Binding every descendant up front and snapshotting each one
|
|
151
|
+
// separately exceeds the request timeout on large web trees.
|
|
152
|
+
id<XCUIElementSnapshot> rootSnapshot = [self snapshotWithError:nil];
|
|
153
|
+
if (nil == rootSnapshot) {
|
|
154
|
+
return @[];
|
|
155
|
+
}
|
|
156
|
+
NSMutableSet<NSString *> *matchedHashes = [NSMutableSet set];
|
|
157
|
+
AMCollectIdentifierMatches(rootSnapshot, accessibilityId, shouldReturnAfterFirstMatch, matchedHashes);
|
|
158
|
+
return [AMSnapshotUtils elementsWithHashes:matchedHashes.copy
|
|
159
|
+
rootElement:self
|
|
160
|
+
rootSnapshot:rootSnapshot
|
|
161
|
+
includeOnlyFirstMatch:shouldReturnAfterFirstMatch];
|
|
99
162
|
}
|
|
100
163
|
|
|
101
164
|
@end
|
|
@@ -238,6 +238,7 @@ const static NSString *CAPABILITIES_KEY = @"capabilities";
|
|
|
238
238
|
AM_BOUND_ELEMENTS_BY_INDEX_SETTING: @(FBSession.activeSession.boundElementsByIndex),
|
|
239
239
|
AM_USE_DEFAULT_UI_INTERRUPTIONS_HANDLING_SETTING: @(!application.am_doesNotHandleUIInterruptions),
|
|
240
240
|
AM_FETCH_FULL_TEXT: @(FBConfiguration.sharedConfiguration.fetchFullText),
|
|
241
|
+
AM_USE_DOM_ID_AS_ACCESSIBILITY_ID: @(FBConfiguration.sharedConfiguration.useDomIdAsAccessibilityId),
|
|
241
242
|
}
|
|
242
243
|
);
|
|
243
244
|
}
|
|
@@ -256,6 +257,10 @@ const static NSString *CAPABILITIES_KEY = @"capabilities";
|
|
|
256
257
|
if (nil != [settings objectForKey:AM_FETCH_FULL_TEXT]) {
|
|
257
258
|
FBConfiguration.sharedConfiguration.fetchFullText = [settings objectForKey:AM_FETCH_FULL_TEXT];
|
|
258
259
|
}
|
|
260
|
+
if (nil != [settings objectForKey:AM_USE_DOM_ID_AS_ACCESSIBILITY_ID]) {
|
|
261
|
+
FBConfiguration.sharedConfiguration.useDomIdAsAccessibilityId =
|
|
262
|
+
[[settings objectForKey:AM_USE_DOM_ID_AS_ACCESSIBILITY_ID] boolValue];
|
|
263
|
+
}
|
|
259
264
|
|
|
260
265
|
return [self handleGetSettings:request];
|
|
261
266
|
}
|
|
@@ -28,4 +28,7 @@ extern NSString* const AM_USE_DEFAULT_UI_INTERRUPTIONS_HANDLING_SETTING;
|
|
|
28
28
|
/*! Whether to use custom snapshotting mechanism to fetch full element's text payload instead of the first 512 chars */
|
|
29
29
|
extern NSString* const AM_FETCH_FULL_TEXT;
|
|
30
30
|
|
|
31
|
+
/*! Whether to expose a WKWebView web element's DOM id as its accessibility id */
|
|
32
|
+
extern NSString* const AM_USE_DOM_ID_AS_ACCESSIBILITY_ID;
|
|
33
|
+
|
|
31
34
|
NS_ASSUME_NONNULL_END
|
|
@@ -19,3 +19,4 @@
|
|
|
19
19
|
NSString* const AM_BOUND_ELEMENTS_BY_INDEX_SETTING = @"boundElementsByIndex";
|
|
20
20
|
NSString* const AM_USE_DEFAULT_UI_INTERRUPTIONS_HANDLING_SETTING = @"useDefaultUiInterruptionsHandling";
|
|
21
21
|
NSString* const AM_FETCH_FULL_TEXT = @"fetchFullText";
|
|
22
|
+
NSString* const AM_USE_DOM_ID_AS_ACCESSIBILITY_ID = @"useDomIdAsAccessibilityId";
|
|
@@ -30,6 +30,33 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
30
30
|
*/
|
|
31
31
|
+ (NSString *)hashWithSnapshot:(id)snapshot;
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
Retrieves the identifier WebDriver clients should observe for the given snapshot.
|
|
35
|
+
This is the standard accessibility identifier, except for WebKit (WKWebView) web
|
|
36
|
+
nodes, which leave it empty and publish their HTML `id` through the non-standard
|
|
37
|
+
AXDOMIdentifier attribute instead. That one is only consulted while the
|
|
38
|
+
`useDomIdAsAccessibilityId` setting is enabled, so a native identifier is never
|
|
39
|
+
overridden.
|
|
40
|
+
|
|
41
|
+
@param snapshot snapshot instance to retrieve the identifier for
|
|
42
|
+
@return The identifier value or nil
|
|
43
|
+
*/
|
|
44
|
+
+ (nullable NSString *)wdIdentifierWithSnapshot:(nullable id<XCUIElementSnapshot>)snapshot;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
Resolves snapshot hashes back to live elements below the given root element.
|
|
48
|
+
|
|
49
|
+
@param hashes snapshot hashes to resolve, as returned by hashWithSnapshot:
|
|
50
|
+
@param rootElement the element the hashes were collected from
|
|
51
|
+
@param rootSnapshot the snapshot of rootElement
|
|
52
|
+
@param firstMatch whether to only return the first matching element
|
|
53
|
+
@return The matching elements. Could be empty
|
|
54
|
+
*/
|
|
55
|
+
+ (NSArray<XCUIElement *> *)elementsWithHashes:(NSSet<NSString *> *)hashes
|
|
56
|
+
rootElement:(XCUIElement *)rootElement
|
|
57
|
+
rootSnapshot:(id<XCUIElementSnapshot>)rootSnapshot
|
|
58
|
+
includeOnlyFirstMatch:(BOOL)firstMatch;
|
|
59
|
+
|
|
33
60
|
@end
|
|
34
61
|
|
|
35
62
|
NS_ASSUME_NONNULL_END
|
|
@@ -16,6 +16,66 @@
|
|
|
16
16
|
|
|
17
17
|
#import "AMSnapshotUtils.h"
|
|
18
18
|
|
|
19
|
+
#import <ApplicationServices/ApplicationServices.h>
|
|
20
|
+
#import <dlfcn.h>
|
|
21
|
+
|
|
22
|
+
#import "FBConfiguration.h"
|
|
23
|
+
#import "FBLogger.h"
|
|
24
|
+
#import "XCUIElementQuery+AMHelpers.h"
|
|
25
|
+
|
|
26
|
+
static NSString *const AM_DOM_IDENTIFIER_ATTRIBUTE = @"AXDOMIdentifier";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
Creates an AXUIElementRef from an accessibility element's remote token. This entry
|
|
30
|
+
point is not exported in the public link stubs, so it is resolved dynamically. The
|
|
31
|
+
underscore-prefixed name is the one actually present in the framework.
|
|
32
|
+
*/
|
|
33
|
+
typedef AXUIElementRef _Nullable (*AMCreateWithRemoteTokenFn)(CFDataRef);
|
|
34
|
+
|
|
35
|
+
static AMCreateWithRemoteTokenFn AMRemoteTokenFn(void)
|
|
36
|
+
{
|
|
37
|
+
static AMCreateWithRemoteTokenFn fn = NULL;
|
|
38
|
+
static dispatch_once_t onceToken;
|
|
39
|
+
dispatch_once(&onceToken, ^{
|
|
40
|
+
fn = (AMCreateWithRemoteTokenFn)(dlsym(RTLD_DEFAULT, "_AXUIElementCreateWithRemoteToken")
|
|
41
|
+
?: dlsym(RTLD_DEFAULT, "AXUIElementCreateWithRemoteToken"));
|
|
42
|
+
});
|
|
43
|
+
return fn;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
Whether this process may use the public Accessibility API. XCUITest itself does not
|
|
48
|
+
need it, so an untrusted runner keeps automating native content normally while every
|
|
49
|
+
AXDOMIdentifier read fails with kAXErrorAPIDisabled. Guarding the read turns a failing
|
|
50
|
+
syscall per element into a cached boolean. TCC trust is fixed at process start, so the
|
|
51
|
+
answer cannot change under us.
|
|
52
|
+
*/
|
|
53
|
+
static BOOL AMIsAccessibilityTrusted(void)
|
|
54
|
+
{
|
|
55
|
+
static BOOL isTrusted = NO;
|
|
56
|
+
static dispatch_once_t onceToken;
|
|
57
|
+
dispatch_once(&onceToken, ^{
|
|
58
|
+
isTrusted = AXIsProcessTrusted();
|
|
59
|
+
if (!isTrusted) {
|
|
60
|
+
[FBLogger log:@"This process is not trusted for the Accessibility API, so WebKit DOM "
|
|
61
|
+
@"identifiers cannot be resolved. Grant Accessibility permission to the "
|
|
62
|
+
@"WebDriverAgentRunner app and restart it to enable useDomIdAsAccessibilityId."];
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
return isTrusted;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@interface AMSnapshotUtils ()
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
Reads the non-standard AXDOMIdentifier attribute, where WebKit publishes a web
|
|
72
|
+
node's HTML `id`. Returns nil for native elements, which report
|
|
73
|
+
kAXErrorAttributeUnsupported for it.
|
|
74
|
+
*/
|
|
75
|
+
+ (nullable NSString *)domIdentifierWithSnapshot:(nullable id)snapshot;
|
|
76
|
+
|
|
77
|
+
@end
|
|
78
|
+
|
|
19
79
|
@implementation AMSnapshotUtils
|
|
20
80
|
|
|
21
81
|
+ (NSString *)hashWithSnapshot:(id)snapshot
|
|
@@ -24,4 +84,79 @@
|
|
|
24
84
|
return [token base64EncodedStringWithOptions:0];
|
|
25
85
|
}
|
|
26
86
|
|
|
87
|
+
+ (nullable NSString *)wdIdentifierWithSnapshot:(nullable id<XCUIElementSnapshot>)snapshot
|
|
88
|
+
{
|
|
89
|
+
NSString *identifier = snapshot.identifier;
|
|
90
|
+
if (identifier.length > 0
|
|
91
|
+
|| !FBConfiguration.sharedConfiguration.useDomIdAsAccessibilityId) {
|
|
92
|
+
return identifier;
|
|
93
|
+
}
|
|
94
|
+
return [self domIdentifierWithSnapshot:snapshot] ?: identifier;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
+ (NSArray<XCUIElement *> *)elementsWithHashes:(NSSet<NSString *> *)hashes
|
|
98
|
+
rootElement:(XCUIElement *)rootElement
|
|
99
|
+
rootSnapshot:(id<XCUIElementSnapshot>)rootSnapshot
|
|
100
|
+
includeOnlyFirstMatch:(BOOL)firstMatch
|
|
101
|
+
{
|
|
102
|
+
if (0 == hashes.count) {
|
|
103
|
+
return @[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
NSMutableArray<XCUIElement *> *matchingElements = [NSMutableArray array];
|
|
107
|
+
if ([hashes containsObject:[self hashWithSnapshot:rootSnapshot]]) {
|
|
108
|
+
[matchingElements addObject:rootElement];
|
|
109
|
+
if (firstMatch) {
|
|
110
|
+
return matchingElements.copy;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id snapshot, NSDictionary *bindings) {
|
|
114
|
+
return [hashes containsObject:[self hashWithSnapshot:snapshot]];
|
|
115
|
+
}];
|
|
116
|
+
[matchingElements addObjectsFromArray:[[rootElement descendantsMatchingType:XCUIElementTypeAny]
|
|
117
|
+
matchingPredicate:predicate].am_allMatches];
|
|
118
|
+
return firstMatch && matchingElements.count > 0
|
|
119
|
+
? @[matchingElements.firstObject]
|
|
120
|
+
: matchingElements.copy;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
+ (nullable NSString *)domIdentifierWithSnapshot:(nullable id)snapshot
|
|
124
|
+
{
|
|
125
|
+
if (nil == snapshot || !AMIsAccessibilityTrusted()) {
|
|
126
|
+
return nil;
|
|
127
|
+
}
|
|
128
|
+
AMCreateWithRemoteTokenFn createRef = AMRemoteTokenFn();
|
|
129
|
+
if (NULL == createRef) {
|
|
130
|
+
return nil;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@try {
|
|
134
|
+
NSData *token = [[snapshot valueForKey:@"_accessibilityElement"] valueForKey:@"_token"];
|
|
135
|
+
if (![token isKindOfClass:NSData.class]) {
|
|
136
|
+
return nil;
|
|
137
|
+
}
|
|
138
|
+
AXUIElementRef ref = createRef((__bridge CFDataRef)token);
|
|
139
|
+
if (NULL == ref) {
|
|
140
|
+
return nil;
|
|
141
|
+
}
|
|
142
|
+
CFTypeRef value = NULL;
|
|
143
|
+
AXError err = AXUIElementCopyAttributeValue(ref,
|
|
144
|
+
(__bridge CFStringRef)AM_DOM_IDENTIFIER_ATTRIBUTE,
|
|
145
|
+
&value);
|
|
146
|
+
CFRelease(ref);
|
|
147
|
+
if (kAXErrorSuccess != err || NULL == value) {
|
|
148
|
+
return nil;
|
|
149
|
+
}
|
|
150
|
+
NSString *result = [(__bridge id)value isKindOfClass:NSString.class]
|
|
151
|
+
? [(__bridge NSString *)value copy]
|
|
152
|
+
: nil;
|
|
153
|
+
CFRelease(value);
|
|
154
|
+
return result.length > 0 ? result : nil;
|
|
155
|
+
} @catch (NSException *e) {
|
|
156
|
+
// Only the private _accessibilityElement._token key path may throw here
|
|
157
|
+
[FBLogger logFmt:@"Cannot retrieve the DOM identifier of a snapshot. Original error: %@", e.reason];
|
|
158
|
+
return nil;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
27
162
|
@end
|
|
@@ -33,6 +33,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
33
33
|
/*! Whether to use custom snapshotting mechanism to fetch full element's text payload instead of the first 512 chars */
|
|
34
34
|
@property BOOL fetchFullText;
|
|
35
35
|
|
|
36
|
+
/*! Whether to expose a WKWebView web element's DOM id as its accessibility id */
|
|
37
|
+
@property BOOL useDomIdAsAccessibilityId;
|
|
38
|
+
|
|
36
39
|
/**
|
|
37
40
|
The range of ports that the HTTP Server should attempt to bind on launch
|
|
38
41
|
*/
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
static NSUInteger const DefaultStartingPort = 10100;
|
|
13
13
|
static NSUInteger const DefaultPortRange = 100;
|
|
14
14
|
static BOOL FBFetchFullText = NO;
|
|
15
|
+
static BOOL FBUseDomIdAsAccessibilityId = NO;
|
|
15
16
|
|
|
16
17
|
@implementation FBConfiguration
|
|
17
18
|
|
|
@@ -86,6 +87,16 @@ static FBConfiguration *instance;
|
|
|
86
87
|
FBFetchFullText = fetchFullText;
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
- (BOOL)useDomIdAsAccessibilityId
|
|
91
|
+
{
|
|
92
|
+
return FBUseDomIdAsAccessibilityId;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
- (void)setUseDomIdAsAccessibilityId:(BOOL)useDomIdAsAccessibilityId
|
|
96
|
+
{
|
|
97
|
+
FBUseDomIdAsAccessibilityId = useDomIdAsAccessibilityId;
|
|
98
|
+
}
|
|
99
|
+
|
|
89
100
|
- (NSRange)bindingPortRange
|
|
90
101
|
{
|
|
91
102
|
// 'WebDriverAgent --port 8080' can be passed via the arguments to the process
|
|
@@ -184,7 +184,7 @@ static NSString *const kXMLIndexPathKey = @"private_indexPath";
|
|
|
184
184
|
return @[];
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
|
|
187
|
+
NSMutableSet<NSString *> *hashes = [NSMutableSet set];
|
|
188
188
|
for (NSXMLNode *node in nodes) {
|
|
189
189
|
if (![node isKindOfClass:NSXMLElement.class]) {
|
|
190
190
|
continue;
|
|
@@ -195,21 +195,10 @@ static NSString *const kXMLIndexPathKey = @"private_indexPath";
|
|
|
195
195
|
}
|
|
196
196
|
[hashes addObject:attrValue];
|
|
197
197
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (firstMatch) {
|
|
203
|
-
return matchingElements.copy;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id snapshot, NSDictionary *bindings) {
|
|
207
|
-
return [hashes containsObject:[AMSnapshotUtils hashWithSnapshot:snapshot]];
|
|
208
|
-
}];
|
|
209
|
-
[matchingElements addObjectsFromArray:[[rootElement descendantsMatchingType:XCUIElementTypeAny] matchingPredicate:predicate].am_allMatches];
|
|
210
|
-
return firstMatch && matchingElements.count > 0
|
|
211
|
-
? @[matchingElements.firstObject]
|
|
212
|
-
: matchingElements.copy;
|
|
198
|
+
return [AMSnapshotUtils elementsWithHashes:hashes.copy
|
|
199
|
+
rootElement:rootElement
|
|
200
|
+
rootSnapshot:rootSnapshot
|
|
201
|
+
includeOnlyFirstMatch:firstMatch];
|
|
213
202
|
}
|
|
214
203
|
|
|
215
204
|
+ (NSXMLDocument *)xmlRepresentationWithSnapshot:(id<XCUIElementSnapshot>)root
|
|
@@ -366,7 +355,7 @@ static NSString *const FBAbstractMethodInvocationException = @"AbstractMethodInv
|
|
|
366
355
|
|
|
367
356
|
+ (NSString *)valueForElement:(id<XCUIElementSnapshot>)element
|
|
368
357
|
{
|
|
369
|
-
return element
|
|
358
|
+
return [AMSnapshotUtils wdIdentifierWithSnapshot:element];
|
|
370
359
|
}
|
|
371
360
|
|
|
372
361
|
@end
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appium-mac2-driver",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "appium-mac2-driver",
|
|
9
|
-
"version": "4.0
|
|
9
|
+
"version": "4.1.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@appium/strongbox": "^1.0.0-rc.1",
|