appium-webdriveragent 16.1.2 → 16.1.3
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/WebDriverAgentLib/Categories/XCUIApplication+FBAlert.h +20 -8
- package/WebDriverAgentLib/Categories/XCUIApplication+FBAlert.m +116 -60
- package/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.h +15 -0
- package/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m +16 -0
- package/WebDriverAgentLib/Commands/FBAlertViewCommands.m +10 -36
- package/WebDriverAgentLib/FBAlert.h +43 -30
- package/WebDriverAgentLib/FBAlert.m +257 -91
- package/WebDriverAgentLib/Info.plist +2 -2
- package/WebDriverAgentLib/Routing/FBExceptionHandler.m +8 -1
- package/WebDriverAgentLib/Routing/FBExceptions.h +9 -0
- package/WebDriverAgentLib/Routing/FBExceptions.m +3 -0
- package/WebDriverAgentLib/Routing/FBSession.m +11 -13
- package/WebDriverAgentLib/Utilities/FBAlertsMonitor.m +11 -9
- package/WebDriverAgentLib/Utilities/FBPasteboard.m +8 -4
- package/WebDriverAgentTests/IntegrationTests/FBAlertTests.m +32 -51
- package/WebDriverAgentTests/IntegrationTests/FBIntegrationTestCase.m +9 -1
- package/WebDriverAgentTests/IntegrationTests/FBSafariAlertTests.m +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [16.1.3](https://github.com/appium/WebDriverAgent/compare/v16.1.2...v16.1.3) (2026-08-03)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
* Optimize alert operations ([#1193](https://github.com/appium/WebDriverAgent/issues/1193)) ([2bc1c40](https://github.com/appium/WebDriverAgent/commit/2bc1c40d20883a9b924b8a177be4d43ffd497901))
|
|
6
|
+
|
|
1
7
|
## [16.1.2](https://github.com/appium/WebDriverAgent/compare/v16.1.1...v16.1.2) (2026-08-03)
|
|
2
8
|
|
|
3
9
|
### Bug Fixes
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
#import <XCTest/XCTest.h>
|
|
10
10
|
|
|
11
|
+
#import "FBXCElementSnapshot.h"
|
|
12
|
+
|
|
11
13
|
NS_ASSUME_NONNULL_BEGIN
|
|
12
14
|
|
|
13
15
|
@interface XCUIApplication (FBAlert)
|
|
@@ -16,19 +18,29 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
16
18
|
extern NSString *const FB_SAFARI_APP_NAME;
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
|
-
Retrieve the
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
Retrieve the currently displayed alert element, if any, using a single
|
|
22
|
+
predicate-filtered query (Alert, Sheet, or ScrollView type) instead of
|
|
23
|
+
snapshotting and walking the whole application tree - the cost stays
|
|
24
|
+
proportional to the number of matching elements rather than the
|
|
25
|
+
size/depth of the whole app.
|
|
26
|
+
|
|
27
|
+
@param snapshotOut On return, set to the element's snapshot if resolving it
|
|
28
|
+
already required taking one as a side effect (the iPad sheet popover check,
|
|
29
|
+
or the Safari web-alert scan) - left untouched if no snapshot was taken, so
|
|
30
|
+
callers should seed it with nil beforehand. Pass NULL if not needed.
|
|
31
|
+
@return Alert element instance, or nil if no alert is present
|
|
22
32
|
*/
|
|
23
|
-
- (nullable XCUIElement *)
|
|
33
|
+
- (nullable XCUIElement *)fb_alertElementWithSnapshot:(id<FBXCElementSnapshot> _Nullable * _Nullable)snapshotOut;
|
|
24
34
|
|
|
25
35
|
/**
|
|
26
|
-
Retrieve
|
|
27
|
-
|
|
36
|
+
Retrieve the application hosting the iOS 18+ limited access permission prompt,
|
|
37
|
+
cheaply gated on its running state so callers can avoid resolving its alert
|
|
38
|
+
snapshot when the prompt process isn't in the foreground.
|
|
39
|
+
See https://github.com/appium/appium/issues/20591
|
|
28
40
|
|
|
29
|
-
@return
|
|
41
|
+
@return The prompt application if it is running in the foreground, otherwise nil
|
|
30
42
|
*/
|
|
31
|
-
+ (nullable
|
|
43
|
+
+ (nullable XCUIApplication *)fb_limitedAccessPromptApplication;
|
|
32
44
|
|
|
33
45
|
@end
|
|
34
46
|
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
|
|
9
9
|
#import "XCUIApplication+FBAlert.h"
|
|
10
10
|
|
|
11
|
-
#import "FBMacros.h"
|
|
12
11
|
#import "FBXCElementSnapshotWrapper+Helpers.h"
|
|
13
12
|
#import "FBXCodeCompatibility.h"
|
|
14
13
|
#import "XCUIElement+FBUtilities.h"
|
|
@@ -24,95 +23,152 @@ static NSString *const FB_LIMITED_ACCESS_PROMPT_BUNDLE_ID = @"com.apple.Contacts
|
|
|
24
23
|
|
|
25
24
|
@implementation XCUIApplication (FBAlert)
|
|
26
25
|
|
|
27
|
-
+ (nullable
|
|
26
|
+
+ (nullable XCUIApplication *)fb_limitedAccessPromptApplication
|
|
28
27
|
{
|
|
29
28
|
XCUIApplication *promptApp = [[XCUIApplication alloc] initWithBundleIdentifier:FB_LIMITED_ACCESS_PROMPT_BUNDLE_ID];
|
|
30
|
-
|
|
31
|
-
return nil;
|
|
32
|
-
}
|
|
33
|
-
return promptApp.fb_alertElement;
|
|
29
|
+
return promptApp.state < XCUIApplicationStateRunningForeground ? nil : promptApp;
|
|
34
30
|
}
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
viewSnapshot:(id<FBXCElementSnapshot>)viewSnapshot
|
|
32
|
+
+ (nullable id<FBXCElementSnapshot>)fb_findSafariAlertSnapshotInScrollView:(id<FBXCElementSnapshot>)scrollViewSnapshot
|
|
38
33
|
{
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
34
|
+
if (nil == scrollViewSnapshot) {
|
|
35
|
+
return nil;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
CGRect appFrame = scrollViewSnapshot.frame;
|
|
39
|
+
|
|
40
|
+
__block id<FBXCElementSnapshot> webView = nil;
|
|
41
|
+
[scrollViewSnapshot enumerateDescendantsUsingBlock:^(id<FBXCElementSnapshot> descendant) {
|
|
42
|
+
if (nil == webView && nil != descendant.identifier && [descendant.identifier isEqualToString:@"WebView"]) {
|
|
43
|
+
webView = descendant;
|
|
46
44
|
}
|
|
47
|
-
return NO;
|
|
48
45
|
}];
|
|
49
|
-
|
|
50
|
-
NSPredicate *dstViewContainPredicate2 = [NSPredicate predicateWithFormat:@"elementType == %lu", XCUIElementTypeButton];
|
|
51
|
-
// Find the first XCUIElementTypeOther which is the grandchild of the web view
|
|
52
|
-
// and is horizontally aligned to the center of the screen
|
|
53
|
-
XCUIElement *candidate = [[[[[[scrollView descendantsMatchingType:XCUIElementTypeAny]
|
|
54
|
-
matchingIdentifier:@"WebView"]
|
|
55
|
-
descendantsMatchingType:XCUIElementTypeOther]
|
|
56
|
-
matchingPredicate:dstViewMatchPredicate]
|
|
57
|
-
containingPredicate:dstViewContainPredicate1]
|
|
58
|
-
containingPredicate:dstViewContainPredicate2].allElementsBoundByIndex.firstObject;
|
|
59
|
-
|
|
60
|
-
if (nil == candidate) {
|
|
46
|
+
if (nil == webView) {
|
|
61
47
|
return nil;
|
|
62
48
|
}
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
id<FBXCElementSnapshot>
|
|
68
|
-
[
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
49
|
+
|
|
50
|
+
// Find the first XCUIElementTypeOther which is the grandchild of the web view
|
|
51
|
+
// and is horizontally aligned to the center of the screen, and contains one
|
|
52
|
+
// to two buttons and at least one text view.
|
|
53
|
+
__block id<FBXCElementSnapshot> candidate = nil;
|
|
54
|
+
[webView enumerateDescendantsUsingBlock:^(id<FBXCElementSnapshot> descendant) {
|
|
55
|
+
if (nil != candidate || descendant.elementType != XCUIElementTypeOther) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
CGRect curFrame = descendant.frame;
|
|
59
|
+
if (CGRectEqualToRect(appFrame, curFrame)
|
|
60
|
+
|| curFrame.origin.x <= 0
|
|
61
|
+
|| curFrame.size.width >= appFrame.size.width) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
CGFloat possibleCenterX = (appFrame.size.width - curFrame.size.width) / 2;
|
|
65
|
+
if (fabs(possibleCenterX - curFrame.origin.x) >= MAX_CENTER_DELTA) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
__block NSUInteger buttonsCount = 0;
|
|
70
|
+
__block NSUInteger textViewsCount = 0;
|
|
71
|
+
[descendant enumerateDescendantsUsingBlock:^(id<FBXCElementSnapshot> innerDescendant) {
|
|
72
|
+
XCUIElementType curType = innerDescendant.elementType;
|
|
73
|
+
if (curType == XCUIElementTypeButton) {
|
|
74
|
+
buttonsCount++;
|
|
75
|
+
} else if (curType == XCUIElementTypeTextView) {
|
|
76
|
+
textViewsCount++;
|
|
77
|
+
}
|
|
78
|
+
}];
|
|
79
|
+
if (buttonsCount >= 1 && buttonsCount <= 2 && textViewsCount > 0) {
|
|
80
|
+
candidate = descendant;
|
|
74
81
|
}
|
|
75
82
|
}];
|
|
76
|
-
return
|
|
83
|
+
return candidate;
|
|
77
84
|
}
|
|
78
85
|
|
|
79
|
-
|
|
86
|
+
// Resolving a query (e.g. allElementsBoundByIndex) is itself
|
|
87
|
+
// as expensive as taking a snapshot - it has to walk/resolve the matching
|
|
88
|
+
// subtree either way. So this issues exactly ONE such query, matching all
|
|
89
|
+
// three candidate types at once, instead of one query per type: querying
|
|
90
|
+
// Alert, then Sheet, then ScrollView separately would pay that cost up to
|
|
91
|
+
// three times over, which is worse than a single whole-app snapshot in the
|
|
92
|
+
// common case where no alert is present at all (the query comes back
|
|
93
|
+
// empty on the very first attempt). Priority is then resolved in memory
|
|
94
|
+
// over the (typically 0-1 element) result: an Alert always wins outright,
|
|
95
|
+
// a Sheet only loses to an Alert, and a ScrollView (the Safari web-alert
|
|
96
|
+
// case) is the last resort. Per-candidate ancestor/subtree checks (the
|
|
97
|
+
// iPad popover check, the Safari web-alert walk) only run for candidates
|
|
98
|
+
// that actually matched, not for every possible type.
|
|
99
|
+
- (nullable XCUIElement *)fb_alertElementWithSnapshot:(id<FBXCElementSnapshot> _Nullable * _Nullable)snapshotOut
|
|
80
100
|
{
|
|
81
|
-
NSPredicate *
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
101
|
+
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"elementType IN {%lu,%lu,%lu}",
|
|
102
|
+
XCUIElementTypeAlert, XCUIElementTypeSheet, XCUIElementTypeScrollView];
|
|
103
|
+
// allElementsBoundByAccessibilityElement resolves all matches in one
|
|
104
|
+
// round trip; allElementsBoundByIndex pays one extra round trip per
|
|
105
|
+
// match, costly while the target is JS-blocked inside alert() (~5s per
|
|
106
|
+
// hop).
|
|
107
|
+
NSArray<XCUIElement *> *candidates = [[self descendantsMatchingType:XCUIElementTypeAny]
|
|
108
|
+
matchingPredicate:predicate].allElementsBoundByAccessibilityElement;
|
|
109
|
+
if (0 == candidates.count) {
|
|
86
110
|
return nil;
|
|
87
111
|
}
|
|
88
|
-
id<FBXCElementSnapshot> alertSnapshot = alert.fb_cachedSnapshot ?: [alert fb_customSnapshot];
|
|
89
112
|
|
|
90
|
-
|
|
91
|
-
|
|
113
|
+
NSMutableArray<XCUIElement *> *sheets = [NSMutableArray array];
|
|
114
|
+
NSMutableArray<XCUIElement *> *scrollViews = [NSMutableArray array];
|
|
115
|
+
for (XCUIElement *candidate in candidates) {
|
|
116
|
+
switch (candidate.elementType) {
|
|
117
|
+
case XCUIElementTypeAlert:
|
|
118
|
+
return candidate;
|
|
119
|
+
case XCUIElementTypeSheet:
|
|
120
|
+
[sheets addObject:candidate];
|
|
121
|
+
break;
|
|
122
|
+
case XCUIElementTypeScrollView:
|
|
123
|
+
[scrollViews addObject:candidate];
|
|
124
|
+
break;
|
|
125
|
+
default:
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
92
128
|
}
|
|
93
129
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
130
|
+
BOOL isPhone = [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone;
|
|
131
|
+
for (XCUIElement *sheet in sheets) {
|
|
132
|
+
if (isPhone) {
|
|
133
|
+
return sheet;
|
|
97
134
|
}
|
|
98
135
|
|
|
99
136
|
// In case of iPad we want to check if sheet isn't contained by popover.
|
|
100
137
|
// In that case we ignore it.
|
|
101
|
-
id<FBXCElementSnapshot>
|
|
138
|
+
id<FBXCElementSnapshot> sheetSnapshot = sheet.lastSnapshot ?: sheet.fb_cachedSnapshot ?: [sheet fb_customSnapshot];
|
|
139
|
+
BOOL isInsidePopover = NO;
|
|
140
|
+
id<FBXCElementSnapshot> ancestor = sheetSnapshot.parent;
|
|
102
141
|
while (nil != ancestor) {
|
|
103
142
|
if (nil != ancestor.identifier && [ancestor.identifier isEqualToString:@"PopoverDismissRegion"]) {
|
|
104
|
-
|
|
143
|
+
isInsidePopover = YES;
|
|
144
|
+
break;
|
|
105
145
|
}
|
|
106
146
|
ancestor = ancestor.parent;
|
|
107
147
|
}
|
|
108
|
-
|
|
148
|
+
if (!isInsidePopover) {
|
|
149
|
+
if (NULL != snapshotOut) {
|
|
150
|
+
*snapshotOut = sheetSnapshot;
|
|
151
|
+
}
|
|
152
|
+
return sheet;
|
|
153
|
+
}
|
|
109
154
|
}
|
|
110
155
|
|
|
111
|
-
|
|
112
|
-
id<FBXCElementSnapshot>
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
156
|
+
for (XCUIElement *scrollView in scrollViews) {
|
|
157
|
+
id<FBXCElementSnapshot> scrollViewSnapshot = scrollView.lastSnapshot ?: scrollView.fb_cachedSnapshot ?: [scrollView fb_customSnapshot];
|
|
158
|
+
id<FBXCElementSnapshot> app = [[FBXCElementSnapshotWrapper ensureWrapped:scrollViewSnapshot] fb_parentMatchingType:XCUIElementTypeApplication];
|
|
159
|
+
if (nil == app || ![app.label isEqualToString:FB_SAFARI_APP_NAME]) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
// Check alert presence in Safari web view
|
|
163
|
+
id<FBXCElementSnapshot> safariAlertSnapshot = [self.class fb_findSafariAlertSnapshotInScrollView:scrollViewSnapshot];
|
|
164
|
+
if (nil != safariAlertSnapshot) {
|
|
165
|
+
// Not resolving safariAlertSnapshot to a live element here (another
|
|
166
|
+
// round trip) - scrollView is already live and is a valid ancestor
|
|
167
|
+
// for callers to resolve buttons/fields from later.
|
|
168
|
+
if (NULL != snapshotOut) {
|
|
169
|
+
*snapshotOut = safariAlertSnapshot;
|
|
170
|
+
}
|
|
171
|
+
return scrollView;
|
|
116
172
|
}
|
|
117
173
|
}
|
|
118
174
|
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
#import <XCTest/XCTest.h>
|
|
10
10
|
|
|
11
|
+
#import "FBXCElementSnapshot.h"
|
|
12
|
+
|
|
11
13
|
@class XCElementSnapshot;
|
|
12
14
|
@protocol FBXCAccessibilityElement;
|
|
13
15
|
@class FBXMLGenerationOptions;
|
|
@@ -166,6 +168,19 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
166
168
|
*/
|
|
167
169
|
- (BOOL)fb_isSameAppAs:(nullable XCUIApplication *)otherApp;
|
|
168
170
|
|
|
171
|
+
/**
|
|
172
|
+
Resolves the live element that corresponds to an already-known snapshot
|
|
173
|
+
found somewhere under rootElement, by matching on its stable uid, instead
|
|
174
|
+
of re-running a fresh attribute/type-based query - a single targeted
|
|
175
|
+
accessibility round trip regardless of how deep the snapshot sits.
|
|
176
|
+
|
|
177
|
+
@param snapshot The snapshot to resolve a live element for
|
|
178
|
+
@param rootElement The element to scope the uid lookup query to
|
|
179
|
+
@return The live element matching the snapshot's uid, or nil if it could not be resolved
|
|
180
|
+
*/
|
|
181
|
+
+ (nullable XCUIElement *)fb_elementForSnapshot:(id<FBXCElementSnapshot>)snapshot
|
|
182
|
+
underElement:(XCUIElement *)rootElement;
|
|
183
|
+
|
|
169
184
|
@end
|
|
170
185
|
|
|
171
186
|
NS_ASSUME_NONNULL_END
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
#import "XCUIElement.h"
|
|
34
34
|
#import "XCUIElement+FBCaching.h"
|
|
35
35
|
#import "XCUIElement+FBIsVisible.h"
|
|
36
|
+
#import "XCUIElement+FBUID.h"
|
|
36
37
|
#import "XCUIElement+FBUtilities.h"
|
|
37
38
|
#import "XCUIElement+FBWebDriverAttributes.h"
|
|
38
39
|
#import "XCUIElementQuery.h"
|
|
@@ -653,4 +654,19 @@ NSDictionary<NSString *, NSString *> *customExclusionAttributesMap(void) {
|
|
|
653
654
|
return self == otherApp || [self.bundleID isEqualToString:(NSString *)otherApp.bundleID];
|
|
654
655
|
}
|
|
655
656
|
|
|
657
|
+
+ (nullable XCUIElement *)fb_elementForSnapshot:(id<FBXCElementSnapshot>)snapshot
|
|
658
|
+
underElement:(XCUIElement *)rootElement
|
|
659
|
+
{
|
|
660
|
+
NSString *uid = [FBXCElementSnapshotWrapper wdUIDWithSnapshot:snapshot];
|
|
661
|
+
if (nil == uid) {
|
|
662
|
+
return nil;
|
|
663
|
+
}
|
|
664
|
+
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K = %@",
|
|
665
|
+
FBStringify(FBXCElementSnapshotWrapper, fb_uid), uid];
|
|
666
|
+
// Filtering by the snapshot's own type (instead of XCUIElementTypeAny) lets
|
|
667
|
+
// the query narrow down before the uid predicate is even applied.
|
|
668
|
+
return [[rootElement.fb_query descendantsMatchingType:snapshot.elementType]
|
|
669
|
+
matchingPredicate:predicate].allElementsBoundByIndex.firstObject;
|
|
670
|
+
}
|
|
671
|
+
|
|
656
672
|
@end
|
|
@@ -54,19 +54,11 @@
|
|
|
54
54
|
return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:@"Missing 'value' parameter" traceback:nil]);
|
|
55
55
|
}
|
|
56
56
|
FBAlert *alert = [FBAlert alertWithApplication:session.activeApplication];
|
|
57
|
-
if (!alert.isPresent) {
|
|
58
|
-
return FBResponseWithStatus([FBCommandStatus noAlertOpenErrorWithMessage:nil
|
|
59
|
-
traceback:nil]);
|
|
60
|
-
}
|
|
61
57
|
NSString *textToType = value;
|
|
62
58
|
if ([value isKindOfClass:[NSArray class]]) {
|
|
63
59
|
textToType = [value componentsJoinedByString:@""];
|
|
64
60
|
}
|
|
65
|
-
|
|
66
|
-
if (![alert typeText:textToType error:&error]) {
|
|
67
|
-
return FBResponseWithStatus([FBCommandStatus unsupportedOperationErrorWithMessage:error.description
|
|
68
|
-
traceback:[NSString stringWithFormat:@"%@", NSThread.callStackSymbols]]);
|
|
69
|
-
}
|
|
61
|
+
[alert typeText:textToType];
|
|
70
62
|
return FBResponseWithOK();
|
|
71
63
|
}
|
|
72
64
|
|
|
@@ -75,20 +67,11 @@
|
|
|
75
67
|
XCUIApplication *application = request.session.activeApplication ?: XCUIApplication.fb_activeApplication;
|
|
76
68
|
NSString *name = request.arguments[@"name"];
|
|
77
69
|
FBAlert *alert = [FBAlert alertWithApplication:application];
|
|
78
|
-
NSError *error;
|
|
79
70
|
|
|
80
|
-
if (!alert.isPresent) {
|
|
81
|
-
return FBResponseWithStatus([FBCommandStatus noAlertOpenErrorWithMessage:nil
|
|
82
|
-
traceback:nil]);
|
|
83
|
-
}
|
|
84
71
|
if (name) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
} else if (![alert acceptWithError:&error]) {
|
|
90
|
-
return FBResponseWithStatus([FBCommandStatus invalidElementStateErrorWithMessage:error.description
|
|
91
|
-
traceback:[NSString stringWithFormat:@"%@", NSThread.callStackSymbols]]);
|
|
72
|
+
[alert clickAlertButton:name];
|
|
73
|
+
} else {
|
|
74
|
+
[alert accept];
|
|
92
75
|
}
|
|
93
76
|
return FBResponseWithOK();
|
|
94
77
|
}
|
|
@@ -98,20 +81,11 @@
|
|
|
98
81
|
XCUIApplication *application = request.session.activeApplication ?: XCUIApplication.fb_activeApplication;
|
|
99
82
|
NSString *name = request.arguments[@"name"];
|
|
100
83
|
FBAlert *alert = [FBAlert alertWithApplication:application];
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (!alert.isPresent) {
|
|
104
|
-
return FBResponseWithStatus([FBCommandStatus noAlertOpenErrorWithMessage:nil
|
|
105
|
-
traceback:nil]);
|
|
106
|
-
}
|
|
84
|
+
|
|
107
85
|
if (name) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
} else if (![alert dismissWithError:&error]) {
|
|
113
|
-
return FBResponseWithStatus([FBCommandStatus invalidElementStateErrorWithMessage:error.description
|
|
114
|
-
traceback:[NSString stringWithFormat:@"%@", NSThread.callStackSymbols]]);
|
|
86
|
+
[alert clickAlertButton:name];
|
|
87
|
+
} else {
|
|
88
|
+
[alert dismiss];
|
|
115
89
|
}
|
|
116
90
|
return FBResponseWithOK();
|
|
117
91
|
}
|
|
@@ -120,11 +94,11 @@
|
|
|
120
94
|
FBSession *session = request.session;
|
|
121
95
|
FBAlert *alert = [FBAlert alertWithApplication:session.activeApplication];
|
|
122
96
|
|
|
123
|
-
|
|
97
|
+
NSArray *labels = alert.buttonLabels;
|
|
98
|
+
if (!labels) {
|
|
124
99
|
return FBResponseWithStatus([FBCommandStatus noAlertOpenErrorWithMessage:nil
|
|
125
100
|
traceback:nil]);
|
|
126
101
|
}
|
|
127
|
-
NSArray *labels = alert.buttonLabels;
|
|
128
102
|
return FBResponseWithObject(labels);
|
|
129
103
|
}
|
|
130
104
|
@end
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
#import <Foundation/Foundation.h>
|
|
10
10
|
|
|
11
11
|
@class XCUIApplication;
|
|
12
|
-
@class XCUIElement;
|
|
13
12
|
|
|
14
13
|
NS_ASSUME_NONNULL_BEGIN
|
|
15
14
|
|
|
@@ -26,65 +25,79 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
26
25
|
+ (instancetype)alertWithApplication:(XCUIApplication *)application;
|
|
27
26
|
|
|
28
27
|
/**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
Determines whether alert is present.
|
|
29
|
+
|
|
30
|
+
An FBAlert instance resolves the alert (and, if found, its snapshot) at
|
|
31
|
+
most once, lazily, on the first call to isPresent, text, buttonLabels,
|
|
32
|
+
accept, dismiss, clickAlertButton:, clickElementMatchingClassChain:, or
|
|
33
|
+
typeText: - every subsequent call on the same instance reuses that result
|
|
34
|
+
rather than re-querying the live UI. This makes an isPresent check
|
|
35
|
+
immediately followed by an action (e.g. the auto-accept flow) act on the
|
|
36
|
+
exact same alert it just observed. Create a fresh FBAlert instance (via
|
|
37
|
+
alertWithApplication:) to observe the current UI state again.
|
|
37
38
|
*/
|
|
38
39
|
- (BOOL)isPresent;
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
|
-
Gets the labels of the buttons visible in the alert
|
|
42
|
+
Gets the labels of the buttons visible in the alert.
|
|
43
|
+
See isPresent for how presence is resolved.
|
|
42
44
|
*/
|
|
43
45
|
- (nullable NSArray *)buttonLabels;
|
|
44
46
|
|
|
45
47
|
/**
|
|
46
|
-
Returns alert's title and description separated by new lines
|
|
48
|
+
Returns alert's title and description separated by new lines.
|
|
49
|
+
See isPresent for how presence is resolved.
|
|
47
50
|
*/
|
|
48
51
|
- (nullable NSString *)text;
|
|
49
52
|
|
|
50
53
|
/**
|
|
51
|
-
Accepts alert, if present
|
|
54
|
+
Accepts alert, if present.
|
|
55
|
+
See isPresent for how presence is resolved.
|
|
52
56
|
|
|
53
|
-
@
|
|
54
|
-
@
|
|
57
|
+
@throws FBAlertNotPresentException if no alert is present.
|
|
58
|
+
@throws FBAlertActionFailedException if the accept button could not be found.
|
|
55
59
|
*/
|
|
56
|
-
- (
|
|
60
|
+
- (void)accept;
|
|
57
61
|
|
|
58
62
|
/**
|
|
59
|
-
Dismisses alert, if present
|
|
63
|
+
Dismisses alert, if present.
|
|
64
|
+
See isPresent for how presence is resolved.
|
|
60
65
|
|
|
61
|
-
@
|
|
62
|
-
@
|
|
66
|
+
@throws FBAlertNotPresentException if no alert is present.
|
|
67
|
+
@throws FBAlertActionFailedException if the dismiss button could not be found.
|
|
63
68
|
*/
|
|
64
|
-
- (
|
|
69
|
+
- (void)dismiss;
|
|
65
70
|
|
|
66
71
|
/**
|
|
67
|
-
Clicks on an alert button, if present
|
|
68
|
-
|
|
72
|
+
Clicks on an alert button, if present.
|
|
73
|
+
See isPresent for how presence is resolved.
|
|
74
|
+
|
|
69
75
|
@param label The label of the button on which to click.
|
|
70
|
-
@
|
|
71
|
-
@
|
|
76
|
+
@throws FBAlertNotPresentException if no alert is present.
|
|
77
|
+
@throws FBAlertActionFailedException if no button with the given label could be found.
|
|
72
78
|
*/
|
|
73
|
-
- (
|
|
79
|
+
- (void)clickAlertButton:(NSString *)label;
|
|
74
80
|
|
|
75
81
|
/**
|
|
76
|
-
|
|
82
|
+
Taps the first descendant of the alert matching the given class chain
|
|
83
|
+
selector, if present.
|
|
84
|
+
See isPresent for how presence is resolved.
|
|
85
|
+
|
|
86
|
+
@param classChain The class chain selector to match against the alert's descendants.
|
|
87
|
+
@throws FBAlertNotPresentException if no alert is present.
|
|
88
|
+
@throws FBAlertActionFailedException if no matching element could be found.
|
|
77
89
|
*/
|
|
78
|
-
- (
|
|
90
|
+
- (void)clickElementMatchingClassChain:(NSString *)classChain;
|
|
79
91
|
|
|
80
92
|
/**
|
|
81
|
-
Types a text into an input inside the alert container, if it is present
|
|
93
|
+
Types a text into an input inside the alert container, if it is present.
|
|
94
|
+
See isPresent for how presence is resolved.
|
|
82
95
|
|
|
83
96
|
@param text the text to type
|
|
84
|
-
@
|
|
85
|
-
@
|
|
97
|
+
@throws FBAlertNotPresentException if no alert is present.
|
|
98
|
+
@throws FBAlertSetTextFailedException if there is no single input field to type into, or typing itself fails.
|
|
86
99
|
*/
|
|
87
|
-
- (
|
|
100
|
+
- (void)typeText:(NSString *)text;
|
|
88
101
|
|
|
89
102
|
@end
|
|
90
103
|
|