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 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 current alert element
20
-
21
- @return Alert element instance
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 *)fb_alertElement;
33
+ - (nullable XCUIElement *)fb_alertElementWithSnapshot:(id<FBXCElementSnapshot> _Nullable * _Nullable)snapshotOut;
24
34
 
25
35
  /**
26
- Retrieve an alert element hosted by the iOS 18+ limited access permission prompt
27
- process. See https://github.com/appium/appium/issues/20591
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 Alert element instance if the prompt is present, otherwise nil
41
+ @return The prompt application if it is running in the foreground, otherwise nil
30
42
  */
31
- + (nullable XCUIElement *)fb_limitedAccessPromptAlertElement;
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 XCUIElement *)fb_limitedAccessPromptAlertElement
26
+ + (nullable XCUIApplication *)fb_limitedAccessPromptApplication
28
27
  {
29
28
  XCUIApplication *promptApp = [[XCUIApplication alloc] initWithBundleIdentifier:FB_LIMITED_ACCESS_PROMPT_BUNDLE_ID];
30
- if (promptApp.state < XCUIApplicationStateRunningForeground) {
31
- return nil;
32
- }
33
- return promptApp.fb_alertElement;
29
+ return promptApp.state < XCUIApplicationStateRunningForeground ? nil : promptApp;
34
30
  }
35
31
 
36
- - (nullable XCUIElement *)fb_alertElementFromSafariWithScrollView:(XCUIElement *)scrollView
37
- viewSnapshot:(id<FBXCElementSnapshot>)viewSnapshot
32
+ + (nullable id<FBXCElementSnapshot>)fb_findSafariAlertSnapshotInScrollView:(id<FBXCElementSnapshot>)scrollViewSnapshot
38
33
  {
39
- CGRect appFrame = viewSnapshot.frame;
40
- NSPredicate *dstViewMatchPredicate = [NSPredicate predicateWithBlock:^BOOL(id<FBXCElementSnapshot> snapshot, NSDictionary *bindings) {
41
- CGRect curFrame = snapshot.frame;
42
- if (!CGRectEqualToRect(appFrame, curFrame)
43
- && curFrame.origin.x > 0 && curFrame.size.width < appFrame.size.width) {
44
- CGFloat possibleCenterX = (appFrame.size.width - curFrame.size.width) / 2;
45
- return fabs(possibleCenterX - curFrame.origin.x) < MAX_CENTER_DELTA;
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
- NSPredicate *dstViewContainPredicate1 = [NSPredicate predicateWithFormat:@"elementType == %lu", XCUIElementTypeTextView];
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
- // ...and contains one to two buttons
64
- // and conatins at least one text view
65
- __block NSUInteger buttonsCount = 0;
66
- __block NSUInteger textViewsCount = 0;
67
- id<FBXCElementSnapshot> snapshot = candidate.fb_cachedSnapshot ?: [candidate fb_customSnapshot];
68
- [snapshot enumerateDescendantsUsingBlock:^(id<FBXCElementSnapshot> descendant) {
69
- XCUIElementType curType = descendant.elementType;
70
- if (curType == XCUIElementTypeButton) {
71
- buttonsCount++;
72
- } else if (curType == XCUIElementTypeTextView) {
73
- textViewsCount++;
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 (buttonsCount >= 1 && buttonsCount <= 2 && textViewsCount > 0) ? candidate : nil;
83
+ return candidate;
77
84
  }
78
85
 
79
- - (XCUIElement *)fb_alertElement
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 *alertCollectorPredicate = [NSPredicate predicateWithFormat:@"elementType IN {%lu,%lu,%lu}",
82
- XCUIElementTypeAlert, XCUIElementTypeSheet, XCUIElementTypeScrollView];
83
- XCUIElement *alert = [[self descendantsMatchingType:XCUIElementTypeAny]
84
- matchingPredicate:alertCollectorPredicate].allElementsBoundByIndex.firstObject;
85
- if (nil == alert) {
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
- if (alertSnapshot.elementType == XCUIElementTypeAlert) {
91
- return alert;
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
- if (alertSnapshot.elementType == XCUIElementTypeSheet) {
95
- if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
96
- return alert;
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> ancestor = alertSnapshot.parent;
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
- return nil;
143
+ isInsidePopover = YES;
144
+ break;
105
145
  }
106
146
  ancestor = ancestor.parent;
107
147
  }
108
- return alert;
148
+ if (!isInsidePopover) {
149
+ if (NULL != snapshotOut) {
150
+ *snapshotOut = sheetSnapshot;
151
+ }
152
+ return sheet;
153
+ }
109
154
  }
110
155
 
111
- if (alertSnapshot.elementType == XCUIElementTypeScrollView) {
112
- id<FBXCElementSnapshot> app = [[FBXCElementSnapshotWrapper ensureWrapped:alertSnapshot] fb_parentMatchingType:XCUIElementTypeApplication];
113
- if (nil != app && [app.label isEqualToString:FB_SAFARI_APP_NAME]) {
114
- // Check alert presence in Safari web view
115
- return [self fb_alertElementFromSafariWithScrollView:alert viewSnapshot:alertSnapshot];
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
- NSError *error;
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
- if (![alert clickAlertButton:name error:&error]) {
86
- return FBResponseWithStatus([FBCommandStatus invalidElementStateErrorWithMessage:error.description
87
- traceback:[NSString stringWithFormat:@"%@", NSThread.callStackSymbols]]);
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
- NSError *error;
102
-
103
- if (!alert.isPresent) {
104
- return FBResponseWithStatus([FBCommandStatus noAlertOpenErrorWithMessage:nil
105
- traceback:nil]);
106
- }
84
+
107
85
  if (name) {
108
- if (![alert clickAlertButton:name error:&error]) {
109
- return FBResponseWithStatus([FBCommandStatus invalidElementStateErrorWithMessage:error.description
110
- traceback:[NSString stringWithFormat:@"%@", NSThread.callStackSymbols]]);
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
- if (!alert.isPresent) {
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
- Creates alert helper for given application
30
-
31
- @param element The element which represents the alert
32
- */
33
- + (instancetype)alertWithElement:(XCUIElement *)element;
34
-
35
- /**
36
- Determines whether alert is present
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
- @param error If there is an error, upon return contains an NSError object that describes the problem.
54
- @return YES if the operation succeeds, otherwise NO.
57
+ @throws FBAlertNotPresentException if no alert is present.
58
+ @throws FBAlertActionFailedException if the accept button could not be found.
55
59
  */
56
- - (BOOL)acceptWithError:(NSError **)error;
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
- @param error If there is an error, upon return contains an NSError object that describes the problem.
62
- @return YES if the operation succeeds, otherwise NO.
66
+ @throws FBAlertNotPresentException if no alert is present.
67
+ @throws FBAlertActionFailedException if the dismiss button could not be found.
63
68
  */
64
- - (BOOL)dismissWithError:(NSError **)error;
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
- @param error If there is an error, upon return contains an NSError object that describes the problem.
71
- @return YES if the operation suceeds, otherwise NO.
76
+ @throws FBAlertNotPresentException if no alert is present.
77
+ @throws FBAlertActionFailedException if no button with the given label could be found.
72
78
  */
73
- - (BOOL)clickAlertButton:(NSString *)label error:(NSError **)error;
79
+ - (void)clickAlertButton:(NSString *)label;
74
80
 
75
81
  /**
76
- XCUElement that represents alert
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
- - (nullable XCUIElement *)alertElement;
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
- @param error If there is an error, upon return contains an NSError object that describes the problem.
85
- @return YES if the operation succeeds, otherwise NO.
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
- - (BOOL)typeText:(NSString *)text error:(NSError **)error;
100
+ - (void)typeText:(NSString *)text;
88
101
 
89
102
  @end
90
103