appium-mac2-driver 4.1.0 → 4.2.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 +12 -0
- package/WebDriverAgentMac/IntegrationTests/AMFindElementTests.m +165 -2
- package/WebDriverAgentMac/IntegrationTests/AMIntegrationTestCase.h +11 -0
- package/WebDriverAgentMac/IntegrationTests/AMIntegrationTestCase.m +33 -0
- package/WebDriverAgentMac/WebDriverAgent/Base.lproj/Main.storyboard +16 -1
- package/WebDriverAgentMac/WebDriverAgent/WebViewController.h +21 -0
- package/WebDriverAgentMac/WebDriverAgent/WebViewController.m +49 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Categories/NSPredicate+FBFormat.m +80 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Categories/XCUIElement+AMAttributes.h +29 -1
- package/WebDriverAgentMac/WebDriverAgentLib/Categories/XCUIElement+AMAttributes.m +66 -15
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMSnapshotUtils.h +22 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMSnapshotUtils.m +5 -11
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/FBElementUtils.m +7 -1
- package/WebDriverAgentMac/WebDriverAgentMac.xcodeproj/project.pbxproj +16 -4
- package/npm-shrinkwrap.json +5 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [4.2.0](https://github.com/appium/appium-mac2-driver/compare/v4.1.1...v4.2.0) (2026-08-10)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* locate WKWebView web elements via predicate string and class chain ([#412](https://github.com/appium/appium-mac2-driver/issues/412)) ([42a49aa](https://github.com/appium/appium-mac2-driver/commit/42a49aa8c321a2b554facfcf330d61d4feff99ed))
|
|
6
|
+
|
|
7
|
+
## [4.1.1](https://github.com/appium/appium-mac2-driver/compare/v4.1.0...v4.1.1) (2026-08-08)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* support WebDriverAgent builds with Xcode 27 ([#411](https://github.com/appium/appium-mac2-driver/issues/411)) ([8cd23fc](https://github.com/appium/appium-mac2-driver/commit/8cd23fc7485b0c6f0a0711374df627376ad4657e))
|
|
12
|
+
|
|
1
13
|
## [4.1.0](https://github.com/appium/appium-mac2-driver/compare/v4.0.7...v4.1.0) (2026-08-07)
|
|
2
14
|
|
|
3
15
|
### Features
|
|
@@ -18,10 +18,15 @@
|
|
|
18
18
|
|
|
19
19
|
#import "AMIntegrationTestCase.h"
|
|
20
20
|
#import "FBConfiguration.h"
|
|
21
|
+
#import "XCUIElement+AMAttributes.h"
|
|
21
22
|
#import "XCUIElement+FBFind.h"
|
|
22
23
|
#import "XCUIElement+FBClassChain.h"
|
|
23
24
|
|
|
24
25
|
|
|
26
|
+
static NSString *const AMWebContainerDomId = @"am-web-container";
|
|
27
|
+
static NSString *const AMWebButtonDomId = @"am-dom-button";
|
|
28
|
+
static NSTimeInterval const AMWebContentTimeout = 10.0;
|
|
29
|
+
|
|
25
30
|
@interface AMFindElementTests : AMIntegrationTestCase
|
|
26
31
|
@end
|
|
27
32
|
|
|
@@ -36,6 +41,34 @@
|
|
|
36
41
|
});
|
|
37
42
|
}
|
|
38
43
|
|
|
44
|
+
- (void)tearDown
|
|
45
|
+
{
|
|
46
|
+
// Other test classes assume the app is on the Buttons tab by default and do not
|
|
47
|
+
// switch to it themselves - restore it so tests that dwell on the WebView tab here
|
|
48
|
+
// do not leak that state into whichever test class happens to run next.
|
|
49
|
+
[self switchToButtonsTab];
|
|
50
|
+
[super tearDown];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
WebKit builds a web view's accessibility subtree lazily, so the very first lookup
|
|
55
|
+
after the page appears may need a retry before the web content shows up. Polls the
|
|
56
|
+
given lookup until it finds something or the timeout elapses.
|
|
57
|
+
*/
|
|
58
|
+
- (NSArray<XCUIElement *> *)am_waitForWebContentUsingBlock:(NSArray<XCUIElement *> *(^)(void))block
|
|
59
|
+
{
|
|
60
|
+
NSDate *deadline = [NSDate dateWithTimeIntervalSinceNow:AMWebContentTimeout];
|
|
61
|
+
NSArray<XCUIElement *> *matches = @[];
|
|
62
|
+
do {
|
|
63
|
+
matches = block();
|
|
64
|
+
if (matches.count > 0) {
|
|
65
|
+
return matches;
|
|
66
|
+
}
|
|
67
|
+
[NSThread sleepForTimeInterval:0.5];
|
|
68
|
+
} while ([deadline timeIntervalSinceNow] > 0);
|
|
69
|
+
return matches;
|
|
70
|
+
}
|
|
71
|
+
|
|
39
72
|
- (void)testSingleDescendantWithIdentifier
|
|
40
73
|
{
|
|
41
74
|
NSArray<XCUIElement *> *matches = [self.testedApplication fb_descendantsMatchingIdentifier:@"_XCUI:CloseWindow"
|
|
@@ -46,10 +79,11 @@
|
|
|
46
79
|
|
|
47
80
|
- (void)testSingleDescendantWithIdentifierWhileDomIdFallbackIsEnabled
|
|
48
81
|
{
|
|
82
|
+
[self skipUnlessAccessibilityTrusted];
|
|
83
|
+
[self switchToWebViewTab];
|
|
49
84
|
FBConfiguration.sharedConfiguration.useDomIdAsAccessibilityId = YES;
|
|
50
85
|
@try {
|
|
51
|
-
//
|
|
52
|
-
// nothing extra and native identifiers must keep resolving as usual
|
|
86
|
+
// Native identifiers must keep resolving as usual
|
|
53
87
|
NSArray<XCUIElement *> *matches = [self.testedApplication fb_descendantsMatchingIdentifier:@"_XCUI:CloseWindow"
|
|
54
88
|
shouldReturnAfterFirstMatch:NO];
|
|
55
89
|
XCTAssertEqual(matches.count, 1);
|
|
@@ -58,11 +92,28 @@
|
|
|
58
92
|
NSArray<XCUIElement *> *noMatches = [self.testedApplication fb_descendantsMatchingIdentifier:@"there_is_no_such_dom_id"
|
|
59
93
|
shouldReturnAfterFirstMatch:NO];
|
|
60
94
|
XCTAssertEqual(noMatches.count, 0);
|
|
95
|
+
|
|
96
|
+
// The web button has no native accessibility identifier, only a DOM id - the
|
|
97
|
+
// fallback must resolve it
|
|
98
|
+
NSArray<XCUIElement *> *webMatches = [self am_waitForWebContentUsingBlock:^NSArray<XCUIElement *> * {
|
|
99
|
+
return [self.testedApplication fb_descendantsMatchingIdentifier:AMWebButtonDomId
|
|
100
|
+
shouldReturnAfterFirstMatch:NO];
|
|
101
|
+
}];
|
|
102
|
+
XCTAssertEqual(webMatches.count, 1);
|
|
103
|
+
XCTAssertEqual(webMatches.firstObject.elementType, XCUIElementTypeButton);
|
|
61
104
|
} @finally {
|
|
62
105
|
FBConfiguration.sharedConfiguration.useDomIdAsAccessibilityId = NO;
|
|
63
106
|
}
|
|
64
107
|
}
|
|
65
108
|
|
|
109
|
+
- (void)testSingleDescendantWithIdentifierFindsNothingForDomIdWhenFallbackIsDisabled
|
|
110
|
+
{
|
|
111
|
+
[self switchToWebViewTab];
|
|
112
|
+
NSArray<XCUIElement *> *matches = [self.testedApplication fb_descendantsMatchingIdentifier:AMWebButtonDomId
|
|
113
|
+
shouldReturnAfterFirstMatch:NO];
|
|
114
|
+
XCTAssertEqual(matches.count, 0);
|
|
115
|
+
}
|
|
116
|
+
|
|
66
117
|
- (void)testSingleDescendantWithClassName
|
|
67
118
|
{
|
|
68
119
|
NSArray<XCUIElement *> *matches = [self.testedApplication fb_descendantsMatchingClassName:@"XCUIElementTypeButton"
|
|
@@ -144,4 +195,116 @@
|
|
|
144
195
|
XCTAssertEqualObjects([matches objectAtIndex:2].identifier, @"_XCUI:MinimizeWindow");
|
|
145
196
|
}
|
|
146
197
|
|
|
198
|
+
- (void)testSingleDescendantWithPredicateMatchingAmIdentifierWhileDomIdFallbackIsEnabled
|
|
199
|
+
{
|
|
200
|
+
[self skipUnlessAccessibilityTrusted];
|
|
201
|
+
[self switchToWebViewTab];
|
|
202
|
+
FBConfiguration.sharedConfiguration.useDomIdAsAccessibilityId = YES;
|
|
203
|
+
@try {
|
|
204
|
+
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"amIdentifier == %@ AND elementType == %lu",
|
|
205
|
+
AMWebButtonDomId, XCUIElementTypeButton];
|
|
206
|
+
NSArray<XCUIElement *> *matches = [self am_waitForWebContentUsingBlock:^NSArray<XCUIElement *> * {
|
|
207
|
+
return [self.testedApplication fb_descendantsMatchingPredicate:predicate
|
|
208
|
+
shouldReturnAfterFirstMatch:NO];
|
|
209
|
+
}];
|
|
210
|
+
XCTAssertEqual(matches.count, 1);
|
|
211
|
+
XCTAssertEqual(matches.firstObject.elementType, XCUIElementTypeButton);
|
|
212
|
+
} @finally {
|
|
213
|
+
FBConfiguration.sharedConfiguration.useDomIdAsAccessibilityId = NO;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
- (void)testSingleDescendantWithPredicateAmIdentifierAliasesNativeIdentifierWhenFallbackIsDisabled
|
|
218
|
+
{
|
|
219
|
+
// amIdentifier mirrors am_identifier: with the setting off it is just the native
|
|
220
|
+
// identifier, same as the standard identifier attribute
|
|
221
|
+
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"amIdentifier == %@", @"_XCUI:CloseWindow"];
|
|
222
|
+
NSArray<XCUIElement *> *matches = [self.testedApplication fb_descendantsMatchingPredicate:predicate
|
|
223
|
+
shouldReturnAfterFirstMatch:NO];
|
|
224
|
+
XCTAssertEqual(matches.count, 1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
- (void)testSingleDescendantWithPredicateAmIdentifierFindsNothingForDomIdWhenFallbackIsDisabled
|
|
228
|
+
{
|
|
229
|
+
[self switchToWebViewTab];
|
|
230
|
+
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"amIdentifier == %@", AMWebButtonDomId];
|
|
231
|
+
NSArray<XCUIElement *> *matches = [self.testedApplication fb_descendantsMatchingPredicate:predicate
|
|
232
|
+
shouldReturnAfterFirstMatch:NO];
|
|
233
|
+
XCTAssertEqual(matches.count, 0);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
- (void)testSingleDescendantWithPredicateMatchingNestedAmRectKeyPath
|
|
237
|
+
{
|
|
238
|
+
// A nested key path into an am_* attribute's resolved value (amRect is a
|
|
239
|
+
// {x, y, width, height} dictionary) must resolve through the same block-predicate
|
|
240
|
+
// rewrite as a bare am_* reference, not fall through to native key path resolution.
|
|
241
|
+
NSArray<XCUIElement *> *matches = [self.testedApplication fb_descendantsMatchingIdentifier:@"_XCUI:CloseWindow"
|
|
242
|
+
shouldReturnAfterFirstMatch:YES];
|
|
243
|
+
XCTAssertEqual(matches.count, 1);
|
|
244
|
+
NSNumber *x = matches.firstObject.am_rect[@"x"];
|
|
245
|
+
XCTAssertNotNil(x);
|
|
246
|
+
|
|
247
|
+
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"identifier == %@ AND amRect.x == %@",
|
|
248
|
+
@"_XCUI:CloseWindow", x];
|
|
249
|
+
NSArray<XCUIElement *> *nestedMatches = [self.testedApplication fb_descendantsMatchingPredicate:predicate
|
|
250
|
+
shouldReturnAfterFirstMatch:NO];
|
|
251
|
+
XCTAssertEqual(nestedMatches.count, 1);
|
|
252
|
+
XCTAssertEqualObjects(nestedMatches.firstObject.identifier, @"_XCUI:CloseWindow");
|
|
253
|
+
|
|
254
|
+
NSPredicate *nonMatchingPredicate = [NSPredicate predicateWithFormat:@"identifier == %@ AND amRect.x == %@",
|
|
255
|
+
@"_XCUI:CloseWindow", @(x.doubleValue + 1000)];
|
|
256
|
+
NSArray<XCUIElement *> *noMatches = [self.testedApplication fb_descendantsMatchingPredicate:nonMatchingPredicate
|
|
257
|
+
shouldReturnAfterFirstMatch:NO];
|
|
258
|
+
XCTAssertEqual(noMatches.count, 0);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
- (void)testSingleDescendantWithClassChainMatchingAmIdentifierOnLastSegmentWhileDomIdFallbackIsEnabled
|
|
262
|
+
{
|
|
263
|
+
[self skipUnlessAccessibilityTrusted];
|
|
264
|
+
[self switchToWebViewTab];
|
|
265
|
+
FBConfiguration.sharedConfiguration.useDomIdAsAccessibilityId = YES;
|
|
266
|
+
@try {
|
|
267
|
+
NSString *query = [NSString stringWithFormat:@"**/XCUIElementTypeButton[`amIdentifier == '%@'`]", AMWebButtonDomId];
|
|
268
|
+
NSArray<XCUIElement *> *matches = [self am_waitForWebContentUsingBlock:^NSArray<XCUIElement *> * {
|
|
269
|
+
return [self.testedApplication fb_descendantsMatchingClassChain:query
|
|
270
|
+
shouldReturnAfterFirstMatch:NO];
|
|
271
|
+
}];
|
|
272
|
+
XCTAssertEqual(matches.count, 1);
|
|
273
|
+
XCTAssertEqual(matches.firstObject.elementType, XCUIElementTypeButton);
|
|
274
|
+
} @finally {
|
|
275
|
+
FBConfiguration.sharedConfiguration.useDomIdAsAccessibilityId = NO;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
- (void)testSingleDescendantWithClassChainMatchingAmIdentifierOnAncestorSegmentWhileDomIdFallbackIsEnabled
|
|
280
|
+
{
|
|
281
|
+
[self skipUnlessAccessibilityTrusted];
|
|
282
|
+
[self switchToWebViewTab];
|
|
283
|
+
FBConfiguration.sharedConfiguration.useDomIdAsAccessibilityId = YES;
|
|
284
|
+
@try {
|
|
285
|
+
// The amIdentifier predicate sits on a non-terminal (ancestor) segment here, not
|
|
286
|
+
// the last one - this works because every class chain segment's predicate is
|
|
287
|
+
// formatted through the same NSPredicate+FBFormat rewrite, regardless of position
|
|
288
|
+
NSString *query = [NSString stringWithFormat:@"**/XCUIElementTypeGroup[`amIdentifier == '%@'`]/XCUIElementTypeButton",
|
|
289
|
+
AMWebContainerDomId];
|
|
290
|
+
NSArray<XCUIElement *> *matches = [self am_waitForWebContentUsingBlock:^NSArray<XCUIElement *> * {
|
|
291
|
+
return [self.testedApplication fb_descendantsMatchingClassChain:query
|
|
292
|
+
shouldReturnAfterFirstMatch:NO];
|
|
293
|
+
}];
|
|
294
|
+
XCTAssertEqual(matches.count, 1);
|
|
295
|
+
XCTAssertEqual(matches.firstObject.elementType, XCUIElementTypeButton);
|
|
296
|
+
} @finally {
|
|
297
|
+
FBConfiguration.sharedConfiguration.useDomIdAsAccessibilityId = NO;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
- (void)testSingleDescendantWithClassChainAmIdentifierFindsNothingForDomIdWhenFallbackIsDisabled
|
|
302
|
+
{
|
|
303
|
+
[self switchToWebViewTab];
|
|
304
|
+
NSString *query = [NSString stringWithFormat:@"**/XCUIElementTypeButton[`amIdentifier == '%@'`]", AMWebButtonDomId];
|
|
305
|
+
NSArray<XCUIElement *> *matches = [self.testedApplication fb_descendantsMatchingClassChain:query
|
|
306
|
+
shouldReturnAfterFirstMatch:NO];
|
|
307
|
+
XCTAssertEqual(matches.count, 0);
|
|
308
|
+
}
|
|
309
|
+
|
|
147
310
|
@end
|
|
@@ -32,6 +32,17 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
32
32
|
|
|
33
33
|
- (void)switchToEditsTab;
|
|
34
34
|
|
|
35
|
+
- (void)switchToWebViewTab;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
Skips the current test unless this process is trusted for the Accessibility API.
|
|
39
|
+
Tests that rely on the useDomIdAsAccessibilityId fallback need this - without it,
|
|
40
|
+
WebKit DOM identifier resolution silently returns nothing, which otherwise surfaces
|
|
41
|
+
as a confusing "found 0 elements" test failure rather than a clear "grant
|
|
42
|
+
Accessibility permission and re-run" skip reason.
|
|
43
|
+
*/
|
|
44
|
+
- (void)skipUnlessAccessibilityTrusted;
|
|
45
|
+
|
|
35
46
|
@end
|
|
36
47
|
|
|
37
48
|
NS_ASSUME_NONNULL_END
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
#import "AMIntegrationTestCase.h"
|
|
18
18
|
|
|
19
|
+
#import "AMSnapshotUtils.h"
|
|
19
20
|
#import "FBConfiguration.h"
|
|
20
21
|
|
|
21
22
|
@interface AMIntegrationTestCase ()
|
|
@@ -53,4 +54,36 @@
|
|
|
53
54
|
[self.testedApplication.radioButtons[@"Edits"].firstMatch click];
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
- (void)switchToWebViewTab
|
|
58
|
+
{
|
|
59
|
+
[self.testedApplication.radioButtons[@"WebView"].firstMatch click];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
Reading AXDOMIdentifier (unlike ordinary XCTest snapshotting) goes through the macOS
|
|
64
|
+
Accessibility API, which requires the process to be trusted under System Settings > Privacy &
|
|
65
|
+
Security > Accessibility. Two things make that grant easy to lose track of while iterating
|
|
66
|
+
locally in Xcode:
|
|
67
|
+
|
|
68
|
+
- Ad-hoc code signing loses the grant on every rebuild. macOS ties an Accessibility grant to
|
|
69
|
+
the binary's signing identity (its CDHash), and the default ad-hoc signature
|
|
70
|
+
(Signature=adhoc, no Team ID) gets a new CDHash on every build. The permission then appears
|
|
71
|
+
to "not stick" - it was actually granted, just to a binary that no longer exists. Assigning
|
|
72
|
+
a stable signing identity (e.g. an "Apple Development" certificate under a real Team ID) in
|
|
73
|
+
the target's Signing & Capabilities keeps the CDHash constant across rebuilds, so the grant
|
|
74
|
+
persists. This has to be done for both the WebDriverAgent app target and the
|
|
75
|
+
IntegrationTests test bundle target - granting only one leaves the other still ad-hoc signed
|
|
76
|
+
and still losing trust every build.
|
|
77
|
+
- xcodebuild build alone does not regenerate the signed XCTRunner wrapper app that actually
|
|
78
|
+
hosts and runs the tests, so a plain build after a signing change can silently run against a
|
|
79
|
+
stale runner. Use build-for-testing (or test, or Xcode's own Test action) instead.
|
|
80
|
+
*/
|
|
81
|
+
- (void)skipUnlessAccessibilityTrusted
|
|
82
|
+
{
|
|
83
|
+
XCTSkipUnless(AMSnapshotUtils.isAccessibilityTrusted,
|
|
84
|
+
@"This process is not trusted for the Accessibility API. Grant Accessibility "
|
|
85
|
+
@"permission to it in System Settings > Privacy & Security > Accessibility "
|
|
86
|
+
@"(note: an ad-hoc-signed build's grant does not survive a rebuild), then re-run.");
|
|
87
|
+
}
|
|
88
|
+
|
|
56
89
|
@end
|
|
@@ -685,7 +685,7 @@
|
|
|
685
685
|
<scene sceneID="R2V-B0-nI4">
|
|
686
686
|
<objects>
|
|
687
687
|
<windowController id="B8D-0N-5wS" sceneMemberID="viewController">
|
|
688
|
-
<window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="IQv-IB-iLA">
|
|
688
|
+
<window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" restorable="NO" id="IQv-IB-iLA">
|
|
689
689
|
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
|
690
690
|
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
|
691
691
|
<rect key="contentRect" x="196" y="240" width="480" height="270"/>
|
|
@@ -709,6 +709,7 @@
|
|
|
709
709
|
<tabViewItems>
|
|
710
710
|
<tabViewItem identifier="" id="Mia-WI-iEB"/>
|
|
711
711
|
<tabViewItem identifier="" id="sIZ-jZ-jj9"/>
|
|
712
|
+
<tabViewItem identifier="" id="Nlk-3F-Wcb"/>
|
|
712
713
|
</tabViewItems>
|
|
713
714
|
<tabView key="tabView" type="noTabsNoBorder" id="Rch-4v-RJW">
|
|
714
715
|
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
|
@@ -722,6 +723,7 @@
|
|
|
722
723
|
<outlet property="tabView" destination="Rch-4v-RJW" id="1XV-Um-WgP"/>
|
|
723
724
|
<segue destination="Pkx-be-7b9" kind="relationship" relationship="tabItems" id="IJQ-4v-ZpO"/>
|
|
724
725
|
<segue destination="bnY-XE-5sK" kind="relationship" relationship="tabItems" id="MtI-PF-Enx"/>
|
|
726
|
+
<segue destination="Qzv-6y-WKw" kind="relationship" relationship="tabItems" id="Pma-8s-CkV"/>
|
|
725
727
|
</connections>
|
|
726
728
|
</tabViewController>
|
|
727
729
|
<customObject id="iEe-Sq-WC0" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
|
@@ -832,5 +834,18 @@
|
|
|
832
834
|
</objects>
|
|
833
835
|
<point key="canvasLocation" x="545" y="-408"/>
|
|
834
836
|
</scene>
|
|
837
|
+
<!--WebView-->
|
|
838
|
+
<scene sceneID="tGx-1M-hRz">
|
|
839
|
+
<objects>
|
|
840
|
+
<viewController title="WebView" id="Qzv-6y-WKw" customClass="WebViewController" sceneMemberID="viewController">
|
|
841
|
+
<view key="view" id="Fbv-Y8-Enh">
|
|
842
|
+
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
|
843
|
+
<autoresizingMask key="autoresizingMask"/>
|
|
844
|
+
</view>
|
|
845
|
+
</viewController>
|
|
846
|
+
<customObject id="Xdm-hz-1Yc" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
|
847
|
+
</objects>
|
|
848
|
+
<point key="canvasLocation" x="1075" y="-408"/>
|
|
849
|
+
</scene>
|
|
835
850
|
</scenes>
|
|
836
851
|
</document>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
* you may not use this file except in compliance with the License.
|
|
4
|
+
* See the NOTICE file distributed with this work for additional
|
|
5
|
+
* information regarding copyright ownership.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
#import <Cocoa/Cocoa.h>
|
|
18
|
+
|
|
19
|
+
@interface WebViewController : NSViewController
|
|
20
|
+
|
|
21
|
+
@end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
* you may not use this file except in compliance with the License.
|
|
4
|
+
* See the NOTICE file distributed with this work for additional
|
|
5
|
+
* information regarding copyright ownership.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
#import "WebViewController.h"
|
|
18
|
+
|
|
19
|
+
#import <WebKit/WebKit.h>
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
None of these elements set a native accessibility identifier, so WebKit leaves their
|
|
23
|
+
standard AXIdentifier empty and only exposes their DOM `id` through AXDOMIdentifier -
|
|
24
|
+
this mirrors real-world web content and is what useDomIdAsAccessibilityId is meant to
|
|
25
|
+
fall back onto.
|
|
26
|
+
*/
|
|
27
|
+
static NSString *const AMWebViewHTML =
|
|
28
|
+
@"<!DOCTYPE html><html><head><meta charset=\"utf-8\"></head><body>"
|
|
29
|
+
@"<div id=\"am-web-container\" role=\"group\">"
|
|
30
|
+
@"<button id=\"am-dom-button\">Web Button</button>"
|
|
31
|
+
@"<span id=\"am-dom-text\">Web Text</span>"
|
|
32
|
+
@"</div>"
|
|
33
|
+
@"</body></html>";
|
|
34
|
+
|
|
35
|
+
@implementation WebViewController
|
|
36
|
+
|
|
37
|
+
- (void)loadView
|
|
38
|
+
{
|
|
39
|
+
self.view = [[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 450, 300)];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
- (void)viewDidLoad
|
|
43
|
+
{
|
|
44
|
+
[super viewDidLoad];
|
|
45
|
+
|
|
46
|
+
[(WKWebView *)self.view loadHTMLString:AMWebViewHTML baseURL:nil];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@end
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
#import "NSPredicate+FBFormat.h"
|
|
11
11
|
|
|
12
12
|
#import "NSExpression+FBFormat.h"
|
|
13
|
+
#import "XCUIElement+AMAttributes.h"
|
|
13
14
|
|
|
14
15
|
@implementation NSPredicate (FBFormat)
|
|
15
16
|
|
|
@@ -33,9 +34,88 @@
|
|
|
33
34
|
return original;
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
/**
|
|
38
|
+
Whether the given expression is a key path referencing one of the am_* predicate
|
|
39
|
+
attributes (AM_IDENTIFIER_ATTRIBUTE_NAME and friends, see XCUIElement+AMAttributes),
|
|
40
|
+
either directly (`amRect`) or through a nested key path into its resolved value
|
|
41
|
+
(`amRect.x`).
|
|
42
|
+
|
|
43
|
+
@return The referenced am_* attribute name, or nil if it isn't one
|
|
44
|
+
*/
|
|
45
|
+
+ (nullable NSString *)fb_amAttributeNameForExpression:(NSExpression *)expression
|
|
46
|
+
{
|
|
47
|
+
if (NSKeyPathExpressionType != expression.expressionType) {
|
|
48
|
+
return nil;
|
|
49
|
+
}
|
|
50
|
+
NSString *keyPath = expression.keyPath;
|
|
51
|
+
NSUInteger dotPos = [keyPath rangeOfString:@"."].location;
|
|
52
|
+
NSString *attributeName = NSNotFound == dotPos ? keyPath : [keyPath substringToIndex:dotPos];
|
|
53
|
+
return [[XCUIElement am_predicateAttributeNames] containsObject:attributeName] ? attributeName : nil;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
Rebuilds a comparison predicate that references an am_* attribute on one side into a
|
|
58
|
+
block predicate that resolves that attribute directly off the snapshot node being
|
|
59
|
+
matched and compares it with the same operator/options as the original. Block
|
|
60
|
+
predicates are evaluated per-node by matchingPredicate:/containingPredicate: (this is
|
|
61
|
+
how the accessibility id DOM fallback already resolves elements), unlike plain
|
|
62
|
+
comparison predicates whose key path resolution WebKit content does not reliably
|
|
63
|
+
observe. A nested key path into the attribute's resolved value (e.g. `amRect.x`) is
|
|
64
|
+
resolved with a plain valueForKeyPath: lookup after the attribute value itself is
|
|
65
|
+
resolved.
|
|
66
|
+
|
|
67
|
+
@param cp the original comparison predicate, known to reference an am_* attribute
|
|
68
|
+
@param amAttributeName the am_* attribute name referenced
|
|
69
|
+
@param amAttributeOnLeft whether it is the left or the right expression
|
|
70
|
+
@return an equivalent block-based predicate
|
|
71
|
+
*/
|
|
72
|
+
+ (NSPredicate *)fb_amAttributePredicateForComparison:(NSComparisonPredicate *)cp
|
|
73
|
+
amAttributeName:(NSString *)amAttributeName
|
|
74
|
+
amAttributeOnLeft:(BOOL)amAttributeOnLeft
|
|
75
|
+
{
|
|
76
|
+
NSExpression *amAttributeExpression = amAttributeOnLeft ? cp.leftExpression : cp.rightExpression;
|
|
77
|
+
NSExpression *otherExpression = amAttributeOnLeft ? cp.rightExpression : cp.leftExpression;
|
|
78
|
+
NSString *amKeyPath = amAttributeExpression.keyPath;
|
|
79
|
+
NSUInteger dotPos = [amKeyPath rangeOfString:@"."].location;
|
|
80
|
+
NSString *amValueKeyPathSuffix = NSNotFound == dotPos ? nil : [amKeyPath substringFromIndex:(dotPos + 1)];
|
|
81
|
+
NSComparisonPredicateModifier modifier = cp.comparisonPredicateModifier;
|
|
82
|
+
NSPredicateOperatorType operatorType = cp.predicateOperatorType;
|
|
83
|
+
NSComparisonPredicateOptions options = cp.options;
|
|
84
|
+
return [NSPredicate predicateWithBlock:^BOOL(id snapshot, NSDictionary *bindings) {
|
|
85
|
+
id amValue = [XCUIElement am_valueForPredicateAttributeName:amAttributeName target:snapshot];
|
|
86
|
+
if (amValueKeyPathSuffix.length > 0) {
|
|
87
|
+
amValue = [amValue valueForKeyPath:amValueKeyPathSuffix];
|
|
88
|
+
}
|
|
89
|
+
amValue = amValue ?: @"";
|
|
90
|
+
NSMutableDictionary *context = bindings ? [bindings mutableCopy] : [NSMutableDictionary dictionary];
|
|
91
|
+
id otherValue = [otherExpression expressionValueWithObject:snapshot context:context];
|
|
92
|
+
NSExpression *amExpression = [NSExpression expressionForConstantValue:amValue];
|
|
93
|
+
NSExpression *otherConstantExpression = [NSExpression expressionForConstantValue:otherValue];
|
|
94
|
+
NSComparisonPredicate *rewritten = amAttributeOnLeft
|
|
95
|
+
? [NSComparisonPredicate predicateWithLeftExpression:amExpression
|
|
96
|
+
rightExpression:otherConstantExpression
|
|
97
|
+
modifier:modifier
|
|
98
|
+
type:operatorType
|
|
99
|
+
options:options]
|
|
100
|
+
: [NSComparisonPredicate predicateWithLeftExpression:otherConstantExpression
|
|
101
|
+
rightExpression:amExpression
|
|
102
|
+
modifier:modifier
|
|
103
|
+
type:operatorType
|
|
104
|
+
options:options];
|
|
105
|
+
return [rewritten evaluateWithObject:nil];
|
|
106
|
+
}];
|
|
107
|
+
}
|
|
108
|
+
|
|
36
109
|
+ (instancetype)fb_formatSearchPredicate:(NSPredicate *)input
|
|
37
110
|
{
|
|
38
111
|
return [self.class fb_predicateWithPredicate:input comparisonModifier:^NSPredicate *(NSComparisonPredicate *cp) {
|
|
112
|
+
NSString *leftAmAttribute = [self.class fb_amAttributeNameForExpression:cp.leftExpression];
|
|
113
|
+
NSString *rightAmAttribute = [self.class fb_amAttributeNameForExpression:cp.rightExpression];
|
|
114
|
+
if (nil != leftAmAttribute || nil != rightAmAttribute) {
|
|
115
|
+
return [self.class fb_amAttributePredicateForComparison:cp
|
|
116
|
+
amAttributeName:leftAmAttribute ?: rightAmAttribute
|
|
117
|
+
amAttributeOnLeft:nil != leftAmAttribute];
|
|
118
|
+
}
|
|
39
119
|
NSExpression *left = [NSExpression fb_wdExpressionWithExpression:[cp leftExpression]];
|
|
40
120
|
NSExpression *right = [NSExpression fb_wdExpressionWithExpression:[cp rightExpression]];
|
|
41
121
|
return [NSComparisonPredicate predicateWithLeftExpression:left
|
|
@@ -18,6 +18,17 @@
|
|
|
18
18
|
|
|
19
19
|
NS_ASSUME_NONNULL_BEGIN
|
|
20
20
|
|
|
21
|
+
/*!
|
|
22
|
+
Predicate string / class chain keypath names for the am_* attributes below. These are
|
|
23
|
+
non-standard, Mac2-driver-specific attributes, in addition to the standard
|
|
24
|
+
XCUIElementAttributes ones.
|
|
25
|
+
*/
|
|
26
|
+
extern NSString *const AM_IDENTIFIER_ATTRIBUTE_NAME;
|
|
27
|
+
extern NSString *const AM_RECT_ATTRIBUTE_NAME;
|
|
28
|
+
extern NSString *const AM_TEXT_ATTRIBUTE_NAME;
|
|
29
|
+
extern NSString *const AM_TYPE_ATTRIBUTE_NAME;
|
|
30
|
+
extern NSString *const AM_HAS_KEYBOARD_INPUT_FOCUS_ATTRIBUTE_NAME;
|
|
31
|
+
|
|
21
32
|
@interface XCUIElement (AMAttributes)
|
|
22
33
|
|
|
23
34
|
/**
|
|
@@ -58,7 +69,24 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
58
69
|
|
|
59
70
|
@return The identifier. Could be an empty string
|
|
60
71
|
*/
|
|
61
|
-
- (NSString *)
|
|
72
|
+
- (NSString *)am_identifier;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
The full set of am_* predicate attribute names (AM_IDENTIFIER_ATTRIBUTE_NAME and
|
|
76
|
+
friends) usable in predicate string / class chain locators.
|
|
77
|
+
*/
|
|
78
|
+
+ (NSArray<NSString *> *)am_predicateAttributeNames;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
Resolves the value of an am_* predicate attribute name directly against a target,
|
|
82
|
+
which may be a live XCUIElement or a raw id<XCUIElementSnapshot> - the latter keeps
|
|
83
|
+
per-node predicate evaluation cheap by not needing to resolve a live element first.
|
|
84
|
+
|
|
85
|
+
@param name one of AM_IDENTIFIER_ATTRIBUTE_NAME and friends
|
|
86
|
+
@param target the XCUIElement or id<XCUIElementSnapshot> to resolve the value against
|
|
87
|
+
@return The resolved value, or nil if name is not a known am_* attribute name
|
|
88
|
+
*/
|
|
89
|
+
+ (nullable id)am_valueForPredicateAttributeName:(NSString *)name target:(id)target;
|
|
62
90
|
|
|
63
91
|
@end
|
|
64
92
|
|
|
@@ -25,6 +25,12 @@
|
|
|
25
25
|
#import "FBMacros.h"
|
|
26
26
|
#import "XCUIElementQuery+AMHelpers.h"
|
|
27
27
|
|
|
28
|
+
NSString *const AM_IDENTIFIER_ATTRIBUTE_NAME = @"amIdentifier";
|
|
29
|
+
NSString *const AM_RECT_ATTRIBUTE_NAME = @"amRect";
|
|
30
|
+
NSString *const AM_TEXT_ATTRIBUTE_NAME = @"amText";
|
|
31
|
+
NSString *const AM_TYPE_ATTRIBUTE_NAME = @"amType";
|
|
32
|
+
NSString *const AM_HAS_KEYBOARD_INPUT_FOCUS_ATTRIBUTE_NAME = @"amHasKeyboardInputFocus";
|
|
33
|
+
|
|
28
34
|
@implementation XCUIElement (AMAttributes)
|
|
29
35
|
|
|
30
36
|
- (id)am_wdAttributeValueWithName:(NSString *)name
|
|
@@ -50,8 +56,17 @@
|
|
|
50
56
|
return self.title;
|
|
51
57
|
} else if ([wdAttributeName isEqualToString:FBStringify(XCUIElement, value)]) {
|
|
52
58
|
return [FBElementUtils stringValueWithValue:self.value];
|
|
53
|
-
} else if ([wdAttributeName isEqualToString:FBStringify(XCUIElement, identifier)]
|
|
54
|
-
|
|
59
|
+
} else if ([wdAttributeName isEqualToString:FBStringify(XCUIElement, identifier)]
|
|
60
|
+
|| [wdAttributeName isEqualToString:AM_IDENTIFIER_ATTRIBUTE_NAME]) {
|
|
61
|
+
return self.am_identifier;
|
|
62
|
+
} else if ([wdAttributeName isEqualToString:AM_RECT_ATTRIBUTE_NAME]) {
|
|
63
|
+
return self.am_rect;
|
|
64
|
+
} else if ([wdAttributeName isEqualToString:AM_TEXT_ATTRIBUTE_NAME]) {
|
|
65
|
+
return self.am_text;
|
|
66
|
+
} else if ([wdAttributeName isEqualToString:AM_TYPE_ATTRIBUTE_NAME]) {
|
|
67
|
+
return self.am_type;
|
|
68
|
+
} else if ([wdAttributeName isEqualToString:AM_HAS_KEYBOARD_INPUT_FOCUS_ATTRIBUTE_NAME]) {
|
|
69
|
+
return FBBoolToStr(self.am_hasKeyboardInputFocus);
|
|
55
70
|
}
|
|
56
71
|
// This should not happen
|
|
57
72
|
NSString *description = [NSString stringWithFormat:@"The attribute '%@' is unknown", wdAttributeName];
|
|
@@ -60,32 +75,69 @@
|
|
|
60
75
|
userInfo:@{}];
|
|
61
76
|
}
|
|
62
77
|
|
|
63
|
-
|
|
78
|
+
+ (NSArray<NSString *> *)am_predicateAttributeNames
|
|
79
|
+
{
|
|
80
|
+
return @[
|
|
81
|
+
AM_IDENTIFIER_ATTRIBUTE_NAME,
|
|
82
|
+
AM_RECT_ATTRIBUTE_NAME,
|
|
83
|
+
AM_TEXT_ATTRIBUTE_NAME,
|
|
84
|
+
AM_TYPE_ATTRIBUTE_NAME,
|
|
85
|
+
AM_HAS_KEYBOARD_INPUT_FOCUS_ATTRIBUTE_NAME,
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
+ (nullable id)am_valueForPredicateAttributeName:(NSString *)name target:(id)target
|
|
64
90
|
{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
91
|
+
if ([name isEqualToString:AM_IDENTIFIER_ATTRIBUTE_NAME]) {
|
|
92
|
+
NSString *identifier = [target valueForKey:@"identifier"];
|
|
93
|
+
// Snapshotting is not free, so only pay for it when the fallback can apply.
|
|
94
|
+
if (identifier.length > 0 || !FBConfiguration.sharedConfiguration.useDomIdAsAccessibilityId) {
|
|
95
|
+
return identifier;
|
|
96
|
+
}
|
|
97
|
+
// domIdentifierWithSnapshot: relies on snapshot-only key paths (_accessibilityElement._token),
|
|
98
|
+
// which a live XCUIElement does not have, so it must be snapshotted first.
|
|
99
|
+
id snapshotTarget = [target isKindOfClass:XCUIElement.class]
|
|
100
|
+
? [(XCUIElement *)target snapshotWithError:nil]
|
|
101
|
+
: target;
|
|
102
|
+
return [AMSnapshotUtils domIdentifierWithSnapshot:snapshotTarget] ?: identifier;
|
|
69
103
|
}
|
|
70
|
-
|
|
104
|
+
if ([name isEqualToString:AM_RECT_ATTRIBUTE_NAME]) {
|
|
105
|
+
return AMCGRectToDict([[target valueForKey:@"frame"] rectValue]);
|
|
106
|
+
}
|
|
107
|
+
if ([name isEqualToString:AM_TEXT_ATTRIBUTE_NAME]) {
|
|
108
|
+
return [self am_textWithSource:target];
|
|
109
|
+
}
|
|
110
|
+
if ([name isEqualToString:AM_TYPE_ATTRIBUTE_NAME]) {
|
|
111
|
+
return [FBElementTypeTransformer stringWithElementType:[[target valueForKey:@"elementType"] unsignedLongLongValue]];
|
|
112
|
+
}
|
|
113
|
+
if ([name isEqualToString:AM_HAS_KEYBOARD_INPUT_FOCUS_ATTRIBUTE_NAME]) {
|
|
114
|
+
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"hasKeyboardFocus == YES"];
|
|
115
|
+
return @([predicate evaluateWithObject:target]);
|
|
116
|
+
}
|
|
117
|
+
return nil;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
- (NSString *)am_identifier
|
|
121
|
+
{
|
|
122
|
+
return [self.class am_valueForPredicateAttributeName:AM_IDENTIFIER_ATTRIBUTE_NAME target:self];
|
|
71
123
|
}
|
|
72
124
|
|
|
73
125
|
- (NSDictionary<NSString *, NSNumber *> *)am_rect
|
|
74
126
|
{
|
|
75
|
-
return
|
|
127
|
+
return [self.class am_valueForPredicateAttributeName:AM_RECT_ATTRIBUTE_NAME target:self];
|
|
76
128
|
}
|
|
77
129
|
|
|
78
130
|
- (NSString *)am_text
|
|
79
131
|
{
|
|
80
132
|
if (!FBConfiguration.sharedConfiguration.fetchFullText) {
|
|
81
|
-
return [self
|
|
133
|
+
return [self.class am_valueForPredicateAttributeName:AM_TEXT_ATTRIBUTE_NAME target:self];
|
|
82
134
|
}
|
|
83
135
|
|
|
84
136
|
NSError *error;
|
|
85
137
|
XCUIElementQuery *query = [self valueForKey:@"query"];
|
|
86
138
|
id<XCUIElementAttributes> snapshot = [query am_uniqueSnapshotWithError:&error];
|
|
87
139
|
if (nil != snapshot) {
|
|
88
|
-
return [self
|
|
140
|
+
return [self.class am_valueForPredicateAttributeName:AM_TEXT_ATTRIBUTE_NAME target:snapshot];
|
|
89
141
|
}
|
|
90
142
|
|
|
91
143
|
NSString *reason = [NSString stringWithFormat:@"Cannot extract the full text of '%@' element. Original error: %@",
|
|
@@ -95,16 +147,15 @@
|
|
|
95
147
|
|
|
96
148
|
- (NSString *)am_type
|
|
97
149
|
{
|
|
98
|
-
return [
|
|
150
|
+
return [self.class am_valueForPredicateAttributeName:AM_TYPE_ATTRIBUTE_NAME target:self];
|
|
99
151
|
}
|
|
100
152
|
|
|
101
153
|
- (BOOL)am_hasKeyboardInputFocus
|
|
102
154
|
{
|
|
103
|
-
|
|
104
|
-
return [predicate evaluateWithObject:self];
|
|
155
|
+
return [[self.class am_valueForPredicateAttributeName:AM_HAS_KEYBOARD_INPUT_FOCUS_ATTRIBUTE_NAME target:self] boolValue];
|
|
105
156
|
}
|
|
106
157
|
|
|
107
|
-
|
|
158
|
+
+ (NSString *)am_textWithSource:(id<XCUIElementAttributes>)source
|
|
108
159
|
{
|
|
109
160
|
NSArray<NSString *> *candidates = @[
|
|
110
161
|
[FBElementUtils stringValueWithValue:source.value],
|
|
@@ -43,6 +43,17 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
43
43
|
*/
|
|
44
44
|
+ (nullable NSString *)wdIdentifierWithSnapshot:(nullable id<XCUIElementSnapshot>)snapshot;
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
Whether this process is trusted for the Accessibility API. WebKit DOM identifier
|
|
48
|
+
resolution silently yields no results without this, since AXDOMIdentifier reads
|
|
49
|
+
fail with kAXErrorAPIDisabled - useful for tests and diagnostics to tell "no DOM id
|
|
50
|
+
fallback happened because the setting is off" apart from "no DOM id fallback
|
|
51
|
+
happened because this process lacks Accessibility permission".
|
|
52
|
+
|
|
53
|
+
@return YES if AXDOMIdentifier reads are expected to work, NO otherwise
|
|
54
|
+
*/
|
|
55
|
+
+ (BOOL)isAccessibilityTrusted;
|
|
56
|
+
|
|
46
57
|
/**
|
|
47
58
|
Resolves snapshot hashes back to live elements below the given root element.
|
|
48
59
|
|
|
@@ -57,6 +68,17 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
57
68
|
rootSnapshot:(id<XCUIElementSnapshot>)rootSnapshot
|
|
58
69
|
includeOnlyFirstMatch:(BOOL)firstMatch;
|
|
59
70
|
|
|
71
|
+
/**
|
|
72
|
+
Reads the non-standard AXDOMIdentifier attribute, where WebKit publishes a web
|
|
73
|
+
node's HTML `id`. Returns nil for native elements, which report
|
|
74
|
+
kAXErrorAttributeUnsupported for it, or when this process lacks Accessibility
|
|
75
|
+
permission.
|
|
76
|
+
|
|
77
|
+
@param snapshot snapshot instance to read the DOM identifier from
|
|
78
|
+
@return The DOM identifier or nil
|
|
79
|
+
*/
|
|
80
|
+
+ (nullable NSString *)domIdentifierWithSnapshot:(nullable id)snapshot;
|
|
81
|
+
|
|
60
82
|
@end
|
|
61
83
|
|
|
62
84
|
NS_ASSUME_NONNULL_END
|
|
@@ -65,17 +65,6 @@ static BOOL AMIsAccessibilityTrusted(void)
|
|
|
65
65
|
return isTrusted;
|
|
66
66
|
}
|
|
67
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
|
-
|
|
79
68
|
@implementation AMSnapshotUtils
|
|
80
69
|
|
|
81
70
|
+ (NSString *)hashWithSnapshot:(id)snapshot
|
|
@@ -94,6 +83,11 @@ static BOOL AMIsAccessibilityTrusted(void)
|
|
|
94
83
|
return [self domIdentifierWithSnapshot:snapshot] ?: identifier;
|
|
95
84
|
}
|
|
96
85
|
|
|
86
|
+
+ (BOOL)isAccessibilityTrusted
|
|
87
|
+
{
|
|
88
|
+
return AMIsAccessibilityTrusted();
|
|
89
|
+
}
|
|
90
|
+
|
|
97
91
|
+ (NSArray<XCUIElement *> *)elementsWithHashes:(NSSet<NSString *> *)hashes
|
|
98
92
|
rootElement:(XCUIElement *)rootElement
|
|
99
93
|
rootSnapshot:(id<XCUIElementSnapshot>)rootSnapshot
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
#import "FBExceptions.h"
|
|
13
13
|
#import "FBMacros.h"
|
|
14
|
+
#import "XCUIElement+AMAttributes.h"
|
|
14
15
|
|
|
15
16
|
@implementation FBElementUtils
|
|
16
17
|
|
|
@@ -43,7 +44,12 @@
|
|
|
43
44
|
FBStringify(XCUIElement, title),
|
|
44
45
|
FBStringify(XCUIElement, value),
|
|
45
46
|
FBStringify(XCUIElement, frame),
|
|
46
|
-
FBStringify(XCUIElement, identifier)
|
|
47
|
+
FBStringify(XCUIElement, identifier),
|
|
48
|
+
AM_IDENTIFIER_ATTRIBUTE_NAME,
|
|
49
|
+
AM_RECT_ATTRIBUTE_NAME,
|
|
50
|
+
AM_TEXT_ATTRIBUTE_NAME,
|
|
51
|
+
AM_TYPE_ATTRIBUTE_NAME,
|
|
52
|
+
AM_HAS_KEYBOARD_INPUT_FOCUS_ATTRIBUTE_NAME
|
|
47
53
|
];
|
|
48
54
|
});
|
|
49
55
|
return attributeNames;
|
|
@@ -65,8 +65,6 @@
|
|
|
65
65
|
7109C0402565B5C4006BFD13 /* NSPredicate+FBFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 710527E82565731200130763 /* NSPredicate+FBFormat.m */; };
|
|
66
66
|
7109C0422565B5C7006BFD13 /* XCUIApplication+AMHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 710527FD2565802200130763 /* XCUIApplication+AMHelpers.h */; };
|
|
67
67
|
7109C0452565B5CB006BFD13 /* XCUIApplication+AMHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 710527FE2565802200130763 /* XCUIApplication+AMHelpers.m */; };
|
|
68
|
-
715AAB102F9C450000C90123 /* XCUIApplication+AMAccessibilityAudit.h in Headers */ = {isa = PBXBuildFile; fileRef = 715AAB122F9C450000C90123 /* XCUIApplication+AMAccessibilityAudit.h */; };
|
|
69
|
-
715AAB112F9C450000C90123 /* XCUIApplication+AMAccessibilityAudit.m in Sources */ = {isa = PBXBuildFile; fileRef = 715AAB132F9C450000C90123 /* XCUIApplication+AMAccessibilityAudit.m */; };
|
|
70
68
|
7109C0472565B5DB006BFD13 /* DDNumber.h in Headers */ = {isa = PBXBuildFile; fileRef = 7151AD1E2564EFAC008B8B2A /* DDNumber.h */; };
|
|
71
69
|
7109C0492565B5DD006BFD13 /* DDNumber.m in Sources */ = {isa = PBXBuildFile; fileRef = 7151AD1D2564EFAC008B8B2A /* DDNumber.m */; };
|
|
72
70
|
7109C04B2565B5E0006BFD13 /* DDRange.h in Headers */ = {isa = PBXBuildFile; fileRef = 7151AD1F2564EFAC008B8B2A /* DDRange.h */; };
|
|
@@ -145,6 +143,8 @@
|
|
|
145
143
|
715117522E8C452E00C90122 /* AMPasteboard.m in Sources */ = {isa = PBXBuildFile; fileRef = 715117512E8C452E00C90122 /* AMPasteboard.m */; };
|
|
146
144
|
715117532E8C452E00C90122 /* AMPasteboard.h in Headers */ = {isa = PBXBuildFile; fileRef = 715117502E8C452E00C90122 /* AMPasteboard.h */; };
|
|
147
145
|
715117552E8C4C3300C90122 /* AMPasteboardTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 715117542E8C4C3300C90122 /* AMPasteboardTests.m */; };
|
|
146
|
+
715AAB102F9C450000C90123 /* XCUIApplication+AMAccessibilityAudit.h in Headers */ = {isa = PBXBuildFile; fileRef = 715AAB122F9C450000C90123 /* XCUIApplication+AMAccessibilityAudit.h */; };
|
|
147
|
+
715AAB112F9C450000C90123 /* XCUIApplication+AMAccessibilityAudit.m in Sources */ = {isa = PBXBuildFile; fileRef = 715AAB132F9C450000C90123 /* XCUIApplication+AMAccessibilityAudit.m */; };
|
|
148
148
|
71688A98256461ED0007F55B /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 71688A97256461ED0007F55B /* AppDelegate.m */; };
|
|
149
149
|
71688A9B256461ED0007F55B /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 71688A9A256461ED0007F55B /* ViewController.m */; };
|
|
150
150
|
71688A9D256461F00007F55B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 71688A9C256461F00007F55B /* Assets.xcassets */; };
|
|
@@ -202,6 +202,8 @@
|
|
|
202
202
|
71A5C67A29A4FAF900421C37 /* FBFailureProofTestCase.h in Headers */ = {isa = PBXBuildFile; fileRef = 71A5C67829A4FAF900421C37 /* FBFailureProofTestCase.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
|
203
203
|
71AA30BF25EFF81900151CED /* AMKeyboardUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 71AA30BD25EFF81900151CED /* AMKeyboardUtils.h */; };
|
|
204
204
|
71AA30C025EFF81900151CED /* AMKeyboardUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 71AA30BE25EFF81900151CED /* AMKeyboardUtils.m */; };
|
|
205
|
+
71AB00012BD1345000997FF4 /* WebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 71AB00002BD1345000997FF4 /* WebViewController.m */; };
|
|
206
|
+
71AB00042BD1345000997FF4 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 71AB00032BD1345000997FF4 /* WebKit.framework */; };
|
|
205
207
|
71B00E8E2566D4BA0010DA73 /* AMIntegrationTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 71B00E8D2566D4BA0010DA73 /* AMIntegrationTestCase.m */; };
|
|
206
208
|
71B00E9B2566D7B00010DA73 /* WebDriverAgentLib.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7199B3AB2565B122000B5C51 /* WebDriverAgentLib.framework */; };
|
|
207
209
|
71B00EA02566D9570010DA73 /* AMFindElementTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 71B00E9F2566D9570010DA73 /* AMFindElementTests.m */; };
|
|
@@ -264,8 +266,6 @@
|
|
|
264
266
|
710527F22565744400130763 /* FBSession-Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "FBSession-Private.h"; sourceTree = "<group>"; };
|
|
265
267
|
710527FD2565802200130763 /* XCUIApplication+AMHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XCUIApplication+AMHelpers.h"; sourceTree = "<group>"; };
|
|
266
268
|
710527FE2565802200130763 /* XCUIApplication+AMHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "XCUIApplication+AMHelpers.m"; sourceTree = "<group>"; };
|
|
267
|
-
715AAB122F9C450000C90123 /* XCUIApplication+AMAccessibilityAudit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XCUIApplication+AMAccessibilityAudit.h"; sourceTree = "<group>"; };
|
|
268
|
-
715AAB132F9C450000C90123 /* XCUIApplication+AMAccessibilityAudit.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "XCUIApplication+AMAccessibilityAudit.m"; sourceTree = "<group>"; };
|
|
269
269
|
710528162565A09100130763 /* FBScreenshotCommands.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBScreenshotCommands.m; sourceTree = "<group>"; };
|
|
270
270
|
710528172565A09100130763 /* FBScreenshotCommands.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBScreenshotCommands.h; sourceTree = "<group>"; };
|
|
271
271
|
7105281C2565A33300130763 /* FBSessionCommands.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBSessionCommands.m; sourceTree = "<group>"; };
|
|
@@ -395,6 +395,8 @@
|
|
|
395
395
|
7151AE302564FA37008B8B2A /* FBRuntimeUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBRuntimeUtils.h; sourceTree = "<group>"; };
|
|
396
396
|
7151AE352564FAAC008B8B2A /* FBUnknownCommands.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBUnknownCommands.m; sourceTree = "<group>"; };
|
|
397
397
|
7151AE362564FAAC008B8B2A /* FBUnknownCommands.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBUnknownCommands.h; sourceTree = "<group>"; };
|
|
398
|
+
715AAB122F9C450000C90123 /* XCUIApplication+AMAccessibilityAudit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XCUIApplication+AMAccessibilityAudit.h"; sourceTree = "<group>"; };
|
|
399
|
+
715AAB132F9C450000C90123 /* XCUIApplication+AMAccessibilityAudit.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "XCUIApplication+AMAccessibilityAudit.m"; sourceTree = "<group>"; };
|
|
398
400
|
715C123827EB587100D6F63B /* XCTIssue+AMPatcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XCTIssue+AMPatcher.h"; sourceTree = "<group>"; };
|
|
399
401
|
715C123927EB587100D6F63B /* XCTIssue+AMPatcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "XCTIssue+AMPatcher.m"; sourceTree = "<group>"; };
|
|
400
402
|
71688A93256461ED0007F55B /* WebDriverAgent.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WebDriverAgent.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
@@ -460,6 +462,9 @@
|
|
|
460
462
|
71A5C67829A4FAF900421C37 /* FBFailureProofTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBFailureProofTestCase.h; sourceTree = "<group>"; };
|
|
461
463
|
71AA30BD25EFF81900151CED /* AMKeyboardUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AMKeyboardUtils.h; sourceTree = "<group>"; };
|
|
462
464
|
71AA30BE25EFF81900151CED /* AMKeyboardUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AMKeyboardUtils.m; sourceTree = "<group>"; };
|
|
465
|
+
71AB00002BD1345000997FF4 /* WebViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WebViewController.m; sourceTree = "<group>"; };
|
|
466
|
+
71AB00022BD1345000997FF4 /* WebViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WebViewController.h; sourceTree = "<group>"; };
|
|
467
|
+
71AB00032BD1345000997FF4 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };
|
|
463
468
|
71B00E8B2566D4B90010DA73 /* IntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = IntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
464
469
|
71B00E8D2566D4BA0010DA73 /* AMIntegrationTestCase.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AMIntegrationTestCase.m; sourceTree = "<group>"; };
|
|
465
470
|
71B00E8F2566D4BA0010DA73 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
|
@@ -482,6 +487,7 @@
|
|
|
482
487
|
isa = PBXFrameworksBuildPhase;
|
|
483
488
|
buildActionMask = 2147483647;
|
|
484
489
|
files = (
|
|
490
|
+
71AB00042BD1345000997FF4 /* WebKit.framework in Frameworks */,
|
|
485
491
|
);
|
|
486
492
|
runOnlyForDeploymentPostprocessing = 0;
|
|
487
493
|
};
|
|
@@ -611,6 +617,8 @@
|
|
|
611
617
|
71688A97256461ED0007F55B /* AppDelegate.m */,
|
|
612
618
|
71688A99256461ED0007F55B /* ViewController.h */,
|
|
613
619
|
71688A9A256461ED0007F55B /* ViewController.m */,
|
|
620
|
+
71AB00022BD1345000997FF4 /* WebViewController.h */,
|
|
621
|
+
71AB00002BD1345000997FF4 /* WebViewController.m */,
|
|
614
622
|
71688A9C256461F00007F55B /* Assets.xcassets */,
|
|
615
623
|
71688A9E256461F00007F55B /* Main.storyboard */,
|
|
616
624
|
71688AA1256461F00007F55B /* Info.plist */,
|
|
@@ -626,6 +634,7 @@
|
|
|
626
634
|
714CA732256668F600353B27 /* XCTAutomationSupport.framework */,
|
|
627
635
|
714CA709256648A100353B27 /* libxml2.tbd */,
|
|
628
636
|
71688AE2256466070007F55B /* XCTest.framework */,
|
|
637
|
+
71AB00032BD1345000997FF4 /* WebKit.framework */,
|
|
629
638
|
);
|
|
630
639
|
name = Frameworks;
|
|
631
640
|
sourceTree = "<group>";
|
|
@@ -1159,6 +1168,7 @@
|
|
|
1159
1168
|
buildActionMask = 2147483647;
|
|
1160
1169
|
files = (
|
|
1161
1170
|
71688A9B256461ED0007F55B /* ViewController.m in Sources */,
|
|
1171
|
+
71AB00012BD1345000997FF4 /* WebViewController.m in Sources */,
|
|
1162
1172
|
71688AA3256461F00007F55B /* main.m in Sources */,
|
|
1163
1173
|
71688A98256461ED0007F55B /* AppDelegate.m in Sources */,
|
|
1164
1174
|
);
|
|
@@ -1367,6 +1377,7 @@
|
|
|
1367
1377
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
|
1368
1378
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
|
1369
1379
|
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
|
1380
|
+
"MACOSX_DEPLOYMENT_TARGET[sdk=macosx27.*]" = 12.0;
|
|
1370
1381
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
|
1371
1382
|
MTL_FAST_MATH = YES;
|
|
1372
1383
|
ONLY_ACTIVE_ARCH = YES;
|
|
@@ -1420,6 +1431,7 @@
|
|
|
1420
1431
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
|
1421
1432
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
|
1422
1433
|
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
|
1434
|
+
"MACOSX_DEPLOYMENT_TARGET[sdk=macosx27.*]" = 12.0;
|
|
1423
1435
|
MTL_ENABLE_DEBUG_INFO = NO;
|
|
1424
1436
|
MTL_FAST_MATH = YES;
|
|
1425
1437
|
SDKROOT = macosx;
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appium-mac2-driver",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "appium-mac2-driver",
|
|
9
|
-
"version": "4.
|
|
9
|
+
"version": "4.2.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@appium/strongbox": "^1.0.0-rc.1",
|
|
@@ -492,9 +492,9 @@
|
|
|
492
492
|
}
|
|
493
493
|
},
|
|
494
494
|
"node_modules/bare-url": {
|
|
495
|
-
"version": "2.
|
|
496
|
-
"resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.
|
|
497
|
-
"integrity": "sha512-
|
|
495
|
+
"version": "2.5.1",
|
|
496
|
+
"resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.1.tgz",
|
|
497
|
+
"integrity": "sha512-cD5ciQuKlx+eumTCfqbfiL+fhQm+dHbVNB/cX4+d+I/nx4JsVop9VoFEPAs5RJ6I84QR6bZIBjpd2kjDOYeWcg==",
|
|
498
498
|
"license": "Apache-2.0",
|
|
499
499
|
"dependencies": {
|
|
500
500
|
"bare-path": "^3.0.0"
|