appium-mac2-driver 4.1.1 → 4.3.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/IntegrationTests/AMW3CActionsTests.m +7 -9
- 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/Routing/AMHTTPServer.h +84 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Routing/AMHTTPServer.m +493 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Routing/AMTCPSocket.h +109 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Routing/AMTCPSocket.m +235 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Routing/FBWebServer.h +1 -1
- package/WebDriverAgentMac/WebDriverAgentLib/Routing/FBWebServer.m +9 -21
- package/WebDriverAgentMac/WebDriverAgentLib/Routing/RouteRequest.h +36 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Routing/RouteRequest.m +33 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Routing/RouteResponse.h +37 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Routing/RouteResponse.m +62 -0
- 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 +46 -180
- package/npm-shrinkwrap.json +75 -146
- package/package.json +2 -1
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.h +0 -1226
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.m +0 -8513
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.h +0 -1036
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.m +0 -5632
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDNumber.h +0 -12
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDNumber.m +0 -88
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDRange.h +0 -56
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDRange.m +0 -106
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPConnection.h +0 -107
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPConnection.m +0 -2238
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPLogging.h +0 -122
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPMessage.h +0 -48
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPMessage.m +0 -114
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPResponse.h +0 -149
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPServer.h +0 -200
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPServer.m +0 -720
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPDataResponse.h +0 -13
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPDataResponse.m +0 -83
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPErrorResponse.h +0 -9
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPErrorResponse.m +0 -40
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/RoutingHTTPServer/HTTPResponseProxy.h +0 -13
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/RoutingHTTPServer/HTTPResponseProxy.m +0 -84
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/RoutingHTTPServer/Route.h +0 -18
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/RoutingHTTPServer/Route.m +0 -11
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteRequest.h +0 -16
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteRequest.m +0 -50
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteResponse.h +0 -20
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteResponse.m +0 -66
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingConnection.h +0 -5
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingConnection.m +0 -142
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingHTTPServer.h +0 -55
- package/WebDriverAgentMac/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingHTTPServer.m +0 -288
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [4.3.0](https://github.com/appium/appium-mac2-driver/compare/v4.2.0...v4.3.0) (2026-08-26)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* port WebDriverAgentMac's HTTP server to Network.framework ([#415](https://github.com/appium/appium-mac2-driver/issues/415)) ([f4ff684](https://github.com/appium/appium-mac2-driver/commit/f4ff68477925a0839c004a280bdda447cf9a4e17))
|
|
6
|
+
|
|
7
|
+
## [4.2.0](https://github.com/appium/appium-mac2-driver/compare/v4.1.1...v4.2.0) (2026-08-10)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* 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))
|
|
12
|
+
|
|
1
13
|
## [4.1.1](https://github.com/appium/appium-mac2-driver/compare/v4.1.0...v4.1.1) (2026-08-08)
|
|
2
14
|
|
|
3
15
|
### Bug Fixes
|
|
@@ -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
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
#import "AMIntegrationTestCase.h"
|
|
20
20
|
#import "XCUIApplication+FBW3CActions.h"
|
|
21
|
+
#import "XCUIElement+AMEditable.h"
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
@interface AMW3CActionsTests : AMIntegrationTestCase
|
|
@@ -49,15 +50,6 @@
|
|
|
49
50
|
},
|
|
50
51
|
],
|
|
51
52
|
|
|
52
|
-
// Chain element with empty 'actions'
|
|
53
|
-
@[@{
|
|
54
|
-
@"type": @"pointer",
|
|
55
|
-
@"id": @"finger1",
|
|
56
|
-
@"parameters": @{@"pointerType": @"mouse"},
|
|
57
|
-
@"actions": @[],
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
|
|
61
53
|
// Chain element without type
|
|
62
54
|
@[@{
|
|
63
55
|
@"id": @"finger1",
|
|
@@ -343,6 +335,9 @@
|
|
|
343
335
|
[self switchToEditsTab];
|
|
344
336
|
XCUIElement *edit = self.testedApplication.textFields.firstMatch;
|
|
345
337
|
[edit click];
|
|
338
|
+
NSError *clearError;
|
|
339
|
+
XCTAssertTrue([edit am_clearTextWithError:&clearError]);
|
|
340
|
+
XCTAssertNil(clearError);
|
|
346
341
|
|
|
347
342
|
NSArray<NSDictionary<NSString *, id> *> *gesture =
|
|
348
343
|
@[@{
|
|
@@ -375,6 +370,9 @@
|
|
|
375
370
|
[self switchToEditsTab];
|
|
376
371
|
XCUIElement *edit = self.testedApplication.textFields.firstMatch;
|
|
377
372
|
[edit click];
|
|
373
|
+
NSError *clearError;
|
|
374
|
+
XCTAssertTrue([edit am_clearTextWithError:&clearError]);
|
|
375
|
+
XCTAssertNil(clearError);
|
|
378
376
|
|
|
379
377
|
NSArray<NSDictionary<NSString *, id> *> *gesture =
|
|
380
378
|
@[@{
|
|
@@ -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
|
|