appium-webdriveragent 16.1.1 → 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.
Files changed (26) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/WebDriverAgentLib/Categories/XCUIApplication+FBAlert.h +20 -8
  3. package/WebDriverAgentLib/Categories/XCUIApplication+FBAlert.m +116 -60
  4. package/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.h +15 -0
  5. package/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m +16 -0
  6. package/WebDriverAgentLib/Categories/XCUIElement+FBClassChain.m +146 -3
  7. package/WebDriverAgentLib/Commands/FBAlertViewCommands.m +10 -36
  8. package/WebDriverAgentLib/FBAlert.h +43 -30
  9. package/WebDriverAgentLib/FBAlert.m +257 -91
  10. package/WebDriverAgentLib/Info.plist +2 -2
  11. package/WebDriverAgentLib/Routing/FBExceptionHandler.m +8 -1
  12. package/WebDriverAgentLib/Routing/FBExceptions.h +9 -0
  13. package/WebDriverAgentLib/Routing/FBExceptions.m +3 -0
  14. package/WebDriverAgentLib/Routing/FBSession.m +11 -13
  15. package/WebDriverAgentLib/Utilities/FBAlertsMonitor.m +11 -9
  16. package/WebDriverAgentLib/Utilities/FBPasteboard.m +8 -4
  17. package/WebDriverAgentTests/IntegrationApp/Classes/ViewController.m +34 -0
  18. package/WebDriverAgentTests/IntegrationApp/Resources/Base.lproj/Main.storyboard +12 -0
  19. package/WebDriverAgentTests/IntegrationTests/FBAlertTests.m +32 -51
  20. package/WebDriverAgentTests/IntegrationTests/FBIntegrationTestCase.h +16 -0
  21. package/WebDriverAgentTests/IntegrationTests/FBIntegrationTestCase.m +28 -1
  22. package/WebDriverAgentTests/IntegrationTests/FBSafariAlertTests.m +1 -1
  23. package/WebDriverAgentTests/IntegrationTests/FBXPathIntegrationTests.m +3 -3
  24. package/WebDriverAgentTests/IntegrationTests/XCElementSnapshotHelperTests.m +1 -7
  25. package/WebDriverAgentTests/IntegrationTests/XCUIElementFBFindTests.m +76 -10
  26. package/package.json +1 -1
@@ -24,7 +24,6 @@
24
24
  #import "FBXCTestDaemonsProxy.h"
25
25
  #import "XCUIApplication+FBQuiescence.h"
26
26
  #import "XCUIElement.h"
27
- #import "XCUIElement+FBClassChain.h"
28
27
 
29
28
  /*!
30
29
  The intial value for the default application property.
@@ -56,14 +55,10 @@ NSString *const FB_SAFARI_BUNDLE_ID = @"com.apple.mobilesafari";
56
55
  NSString *autoClickAlertSelector = FBConfiguration.autoClickAlertSelector;
57
56
  if ([autoClickAlertSelector length] > 0) {
58
57
  @try {
59
- NSArray<XCUIElement*> *matches = [alert.alertElement fb_descendantsMatchingClassChain:autoClickAlertSelector
60
- shouldReturnAfterFirstMatch:YES];
61
- if (matches.count > 0) {
62
- [[matches objectAtIndex:0] tap];
63
- }
58
+ [alert clickElementMatchingClassChain:autoClickAlertSelector];
64
59
  } @catch (NSException *e) {
65
60
  [FBLogger logFmt:@"Could not click at the alert element '%@'. Original error: %@",
66
- autoClickAlertSelector, e.description];
61
+ autoClickAlertSelector, e.reason];
67
62
  }
68
63
  // This setting has priority over other settings if enabled
69
64
  return;
@@ -73,14 +68,17 @@ NSString *const FB_SAFARI_BUNDLE_ID = @"com.apple.mobilesafari";
73
68
  return;
74
69
  }
75
70
 
76
- NSError *error;
77
71
  if ([self.defaultAlertAction isEqualToString:@"accept"]) {
78
- if (![alert acceptWithError:&error]) {
79
- [FBLogger logFmt:@"Cannot accept the alert. Original error: %@", error.description];
72
+ @try {
73
+ [alert accept];
74
+ } @catch (NSException *e) {
75
+ [FBLogger logFmt:@"Cannot accept the alert. Original error: %@", e.reason];
80
76
  }
81
77
  } else if ([self.defaultAlertAction isEqualToString:@"dismiss"]) {
82
- if (![alert dismissWithError:&error]) {
83
- [FBLogger logFmt:@"Cannot dismiss the alert. Original error: %@", error.description];
78
+ @try {
79
+ [alert dismiss];
80
+ } @catch (NSException *e) {
81
+ [FBLogger logFmt:@"Cannot dismiss the alert. Original error: %@", e.reason];
84
82
  }
85
83
  } else {
86
84
  [FBLogger logFmt:@"'%@' default alert action is unsupported", self.defaultAlertAction];
@@ -213,7 +211,7 @@ static FBSession *_activeSession = nil;
213
211
  XCUIApplicationState testedAppState = self.testedApplication.state;
214
212
  if (testedAppState >= XCUIApplicationStateRunningForeground) {
215
213
  NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:@"%K == %@ OR %K IN {%@, %@}",
216
- @"elementType", @(XCUIElementTypeAlert),
214
+ @"elementType", @(XCUIElementTypeAlert),
217
215
  // To look for `SBTransientOverlayWindow` elements. See https://github.com/appium/WebDriverAgent/pull/946
218
216
  @"identifier", @"SBTransientOverlayWindow",
219
217
  // To look for 'criticalAlertSetting' elements https://developer.apple.com/documentation/usernotifications/unnotificationsettings/criticalalertsetting
@@ -52,26 +52,28 @@ static const NSTimeInterval FB_MONTORING_INTERVAL = 2.0;
52
52
  NSArray<XCUIApplication *> *activeApps = XCUIApplication.fb_activeApplications;
53
53
  BOOL didDetectAlert = NO;
54
54
  for (XCUIApplication *activeApp in activeApps) {
55
- XCUIElement *alertElement = nil;
56
55
  @try {
57
- alertElement = activeApp.fb_alertElement;
58
- if (nil != alertElement) {
59
- [delegate didDetectAlert:[FBAlert alertWithElement:alertElement]];
56
+ FBAlert *alert = [FBAlert alertWithApplication:activeApp];
57
+ if (alert.isPresent) {
58
+ [delegate didDetectAlert:alert];
59
+ didDetectAlert = YES;
60
60
  }
61
61
  } @catch (NSException *e) {
62
62
  [FBLogger logFmt:@"Got an unexpected exception while monitoring alerts: %@\n%@", e.reason, e.callStackSymbols];
63
63
  }
64
- if (nil != alertElement) {
65
- didDetectAlert = YES;
64
+ if (didDetectAlert) {
66
65
  break;
67
66
  }
68
67
  }
69
68
 
70
69
  if (!didDetectAlert) {
71
70
  @try {
72
- XCUIElement *alertElement = [XCUIApplication fb_limitedAccessPromptAlertElement];
73
- if (nil != alertElement) {
74
- [delegate didDetectAlert:[FBAlert alertWithElement:alertElement]];
71
+ XCUIApplication *promptApp = XCUIApplication.fb_limitedAccessPromptApplication;
72
+ if (nil != promptApp) {
73
+ FBAlert *alert = [FBAlert alertWithApplication:promptApp];
74
+ if (alert.isPresent) {
75
+ [delegate didDetectAlert:alert];
76
+ }
75
77
  }
76
78
  } @catch (NSException *e) {
77
79
  [FBLogger logFmt:@"Got an unexpected exception while monitoring alerts: %@\n%@", e.reason, e.callStackSymbols];
@@ -11,6 +11,7 @@
11
11
  #import <mach/mach_time.h>
12
12
  #import "FBAlert.h"
13
13
  #import "FBErrorBuilder.h"
14
+ #import "FBExceptions.h"
14
15
  #import "FBMacros.h"
15
16
  #import "XCUIApplication+FBHelpers.h"
16
17
  #import "XCUIApplication+FBAlert.h"
@@ -100,10 +101,13 @@
100
101
  break;
101
102
  }
102
103
 
103
- XCUIElement *alertElement = XCUIApplication.fb_systemApplication.fb_alertElement;
104
- if (nil != alertElement) {
105
- FBAlert *alert = [FBAlert alertWithElement:alertElement];
106
- [alert acceptWithError:nil];
104
+ @try {
105
+ [[FBAlert alertWithApplication:XCUIApplication.fb_systemApplication] accept];
106
+ } @catch (NSException *e) {
107
+ if (![e.name isEqualToString:FBAlertNotPresentException]) {
108
+ @throw e;
109
+ }
110
+ // No alert is present on this tick - expected on most iterations of this poll loop
107
111
  }
108
112
  uint64_t timeElapsed = clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) - timeStarted;
109
113
  if (timeElapsed / NSEC_PER_SEC > timeout) {
@@ -48,6 +48,40 @@
48
48
  button.selected = !button.selected;
49
49
  }
50
50
 
51
+ - (IBAction)goToDeepHierarchy:(id)sender
52
+ {
53
+ // Plain UIViews with fixed frames only - no Auto Layout constraints and no
54
+ // specialized subclasses (e.g. UITextView) that carry their own layout/text
55
+ // engines, which can make a deep nested chain pathologically expensive to
56
+ // lay out. This page exists purely as a fixture for exercising element
57
+ // lookups (e.g. class chain locators) against a deep accessibility tree.
58
+ UIViewController *deepHierarchyViewController = [UIViewController new];
59
+ deepHierarchyViewController.view.backgroundColor = UIColor.whiteColor;
60
+ deepHierarchyViewController.view.accessibilityIdentifier = @"DeepHierarchyPage";
61
+
62
+ NSInteger depth = 70;
63
+ // A plain UILabel sibling, not part of the nested chain below, so the
64
+ // fixture stays recognizable to a human glancing at the simulator instead
65
+ // of showing a blank white screen.
66
+ UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 60, CGRectGetWidth(UIScreen.mainScreen.bounds) - 40, 60)];
67
+ titleLabel.text = [NSString stringWithFormat:@"Deep Hierarchy\n%ld nested elements", (long)depth];
68
+ titleLabel.numberOfLines = 2;
69
+ titleLabel.textAlignment = NSTextAlignmentCenter;
70
+ titleLabel.font = [UIFont systemFontOfSize:20];
71
+ [deepHierarchyViewController.view addSubview:titleLabel];
72
+
73
+ UIView *parent = deepHierarchyViewController.view;
74
+ for (NSInteger i = 0; i < depth; i++) {
75
+ UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1, 1)];
76
+ view.accessibilityIdentifier = [NSString stringWithFormat:@"view_%ld", (long)i];
77
+ view.accessibilityLabel = [NSString stringWithFormat:@"View %ld", (long)i];
78
+ [parent addSubview:view];
79
+ parent = view;
80
+ }
81
+
82
+ [self.navigationController pushViewController:deepHierarchyViewController animated:NO];
83
+ }
84
+
51
85
  - (void)viewDidLayoutSubviews
52
86
  {
53
87
  [super viewDidLayoutSubviews];
@@ -74,6 +74,16 @@
74
74
  <segue destination="XaE-eF-eIt" kind="show" id="q3u-B9-6R6"/>
75
75
  </connections>
76
76
  </button>
77
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dHi-01-Btn">
78
+ <rect key="frame" x="150" y="380" width="114" height="30"/>
79
+ <constraints>
80
+ <constraint firstAttribute="height" constant="30" id="dHi-02-Hgt"/>
81
+ </constraints>
82
+ <state key="normal" title="DeepHierarchy"/>
83
+ <connections>
84
+ <action selector="goToDeepHierarchy:" destination="BYZ-38-t0r" eventType="touchUpInside" id="dHi-03-Act"/>
85
+ </connections>
86
+ </button>
77
87
  </subviews>
78
88
  <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
79
89
  <accessibility key="accessibilityConfiguration" label="MainView"/>
@@ -90,6 +100,8 @@
90
100
  <constraint firstItem="uiD-R4-b34" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="nFx-Xr-rGC"/>
91
101
  <constraint firstItem="S56-6U-3gG" firstAttribute="top" secondItem="M2N-Yn-ytb" secondAttribute="bottom" constant="8" id="rMW-LW-ejO"/>
92
102
  <constraint firstItem="M2N-Yn-ytb" firstAttribute="top" secondItem="YgP-SF-TkS" secondAttribute="bottom" constant="8" id="tMN-gl-smg"/>
103
+ <constraint firstItem="dHi-01-Btn" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="dHi-04-Cnt"/>
104
+ <constraint firstItem="dHi-01-Btn" firstAttribute="top" secondItem="1Ht-AF-MGW" secondAttribute="bottom" constant="8" id="dHi-05-Top"/>
93
105
  </constraints>
94
106
  </view>
95
107
  <navigationItem key="navigationItem" id="dmu-Fe-aoT"/>
@@ -69,60 +69,54 @@
69
69
 
70
70
  - (void)testAlertPresence
71
71
  {
72
- FBAlert *alert = [FBAlert alertWithApplication:self.testedApplication];
73
- XCTAssertFalse(alert.isPresent);
72
+ XCTAssertFalse([FBAlert alertWithApplication:self.testedApplication].isPresent);
74
73
  [self showApplicationAlert];
75
- XCTAssertTrue(alert.isPresent);
74
+ XCTAssertTrue([FBAlert alertWithApplication:self.testedApplication].isPresent);
76
75
  }
77
76
 
78
77
  - (void)testAlertText
79
78
  {
80
- FBAlert *alert = [FBAlert alertWithApplication:self.testedApplication];
81
- XCTAssertNil(alert.text);
79
+ XCTAssertNil([FBAlert alertWithApplication:self.testedApplication].text);
82
80
  [self showApplicationAlert];
83
- XCTAssertTrue([alert.text containsString:@"Magic"]);
84
- XCTAssertTrue([alert.text containsString:@"Should read"]);
81
+ NSString *text = [FBAlert alertWithApplication:self.testedApplication].text;
82
+ XCTAssertTrue([text containsString:@"Magic"]);
83
+ XCTAssertTrue([text containsString:@"Should read"]);
85
84
  }
86
85
 
87
86
  - (void)testAlertLabels
88
87
  {
89
- FBAlert* alert = [FBAlert alertWithApplication:self.testedApplication];
90
- XCTAssertNil(alert.buttonLabels);
88
+ XCTAssertNil([FBAlert alertWithApplication:self.testedApplication].buttonLabels);
91
89
  [self showApplicationAlert];
92
- XCTAssertNotNil(alert.buttonLabels);
93
- XCTAssertEqual(1, alert.buttonLabels.count);
94
- XCTAssertEqualObjects(@"Will do", alert.buttonLabels[0]);
90
+ NSArray *labels = [FBAlert alertWithApplication:self.testedApplication].buttonLabels;
91
+ XCTAssertNotNil(labels);
92
+ XCTAssertEqual(1, labels.count);
93
+ XCTAssertEqualObjects(@"Will do", labels[0]);
95
94
  }
96
95
 
97
96
  - (void)testClickAlertButton
98
97
  {
99
- FBAlert* alert = [FBAlert alertWithApplication:self.testedApplication];
100
- XCTAssertFalse([alert clickAlertButton:@"Invalid" error:nil]);
98
+ XCTAssertThrows([[FBAlert alertWithApplication:self.testedApplication] clickAlertButton:@"Invalid"]);
101
99
  [self showApplicationAlert];
102
- XCTAssertFalse([alert clickAlertButton:@"Invalid" error:nil]);
103
- FBAssertWaitTillBecomesTrue(alert.isPresent);
104
- XCTAssertTrue([alert clickAlertButton:@"Will do" error:nil]);
105
- FBAssertWaitTillBecomesTrue(!alert.isPresent);
100
+ XCTAssertThrows([[FBAlert alertWithApplication:self.testedApplication] clickAlertButton:@"Invalid"]);
101
+ FBAssertWaitTillBecomesTrue([FBAlert alertWithApplication:self.testedApplication].isPresent);
102
+ XCTAssertNoThrow([[FBAlert alertWithApplication:self.testedApplication] clickAlertButton:@"Will do"]);
103
+ FBAssertWaitTillBecomesTrue(![FBAlert alertWithApplication:self.testedApplication].isPresent);
106
104
  }
107
105
 
108
106
  - (void)testAcceptingAlert
109
107
  {
110
- NSError *error;
111
108
  [self showApplicationAlert];
112
- XCTAssertTrue([[FBAlert alertWithApplication:self.testedApplication] acceptWithError:&error]);
109
+ XCTAssertNoThrow([[FBAlert alertWithApplication:self.testedApplication] accept]);
113
110
  FBAssertWaitTillBecomesTrue(self.testedApplication.alerts.count == 0);
114
- XCTAssertNil(error);
115
111
  }
116
112
 
117
113
  - (void)testAcceptingAlertWithCustomLocator
118
114
  {
119
- NSError *error;
120
115
  [self showApplicationAlert];
121
116
  [FBConfiguration setAcceptAlertButtonSelector:@"**/XCUIElementTypeButton[-1]"];
122
117
  @try {
123
- XCTAssertTrue([[FBAlert alertWithApplication:self.testedApplication] acceptWithError:&error]);
118
+ XCTAssertNoThrow([[FBAlert alertWithApplication:self.testedApplication] accept]);
124
119
  FBAssertWaitTillBecomesTrue(self.testedApplication.alerts.count == 0);
125
- XCTAssertNil(error);
126
120
  } @finally {
127
121
  [FBConfiguration setAcceptAlertButtonSelector:@""];
128
122
  }
@@ -130,65 +124,52 @@
130
124
 
131
125
  - (void)testDismissingAlert
132
126
  {
133
- NSError *error;
134
127
  [self showApplicationAlert];
135
- XCTAssertTrue([[FBAlert alertWithApplication:self.testedApplication] dismissWithError:&error]);
128
+ XCTAssertNoThrow([[FBAlert alertWithApplication:self.testedApplication] dismiss]);
136
129
  FBAssertWaitTillBecomesTrue(self.testedApplication.alerts.count == 0);
137
- XCTAssertNil(error);
138
130
  }
139
131
 
140
132
  - (void)testDismissingAlertWithCustomLocator
141
133
  {
142
- NSError *error;
143
134
  [self showApplicationAlert];
144
135
  [FBConfiguration setDismissAlertButtonSelector:@"**/XCUIElementTypeButton[-1]"];
145
136
  @try {
146
- XCTAssertTrue([[FBAlert alertWithApplication:self.testedApplication] dismissWithError:&error]);
137
+ XCTAssertNoThrow([[FBAlert alertWithApplication:self.testedApplication] dismiss]);
147
138
  FBAssertWaitTillBecomesTrue(self.testedApplication.alerts.count == 0);
148
- XCTAssertNil(error);
149
139
  } @finally {
150
140
  [FBConfiguration setDismissAlertButtonSelector:@""];
151
141
  }
152
142
  }
153
143
 
154
- - (void)testAlertElement
155
- {
156
- [self showApplicationAlert];
157
- XCUIElement *alertElement = [FBAlert alertWithApplication:self.testedApplication].alertElement;
158
- XCTAssertTrue(alertElement.exists);
159
- XCTAssertTrue(alertElement.elementType == XCUIElementTypeAlert);
160
- }
161
-
162
144
  - (void)testNotificationAlert
163
145
  {
164
- FBAlert *alert = [FBAlert alertWithApplication:self.testedApplication];
165
- XCTAssertNil(alert.text);
146
+ XCTAssertNil([FBAlert alertWithApplication:self.testedApplication].text);
166
147
  [self.testedApplication.buttons[@"Create Notification Alert"] tap];
167
- FBAssertWaitTillBecomesTrue(alert.isPresent);
148
+ FBAssertWaitTillBecomesTrue([FBAlert alertWithApplication:self.testedApplication].isPresent);
168
149
 
169
- XCTAssertTrue([alert.text containsString:@"Would Like to Send You Notifications"]);
170
- XCTAssertTrue([alert.text containsString:@"Notifications may include"]);
150
+ NSString *text = [FBAlert alertWithApplication:self.testedApplication].text;
151
+ XCTAssertTrue([text containsString:@"Would Like to Send You Notifications"]);
152
+ XCTAssertTrue([text containsString:@"Notifications may include"]);
171
153
  }
172
154
 
173
155
  - (void)testCameraRollAlert
174
156
  {
175
- FBAlert *alert = [FBAlert alertWithApplication:self.testedApplication];
176
- XCTAssertNil(alert.text);
157
+ XCTAssertNil([FBAlert alertWithApplication:self.testedApplication].text);
177
158
 
178
159
  [self.testedApplication.buttons[@"Create Camera Roll Alert"] tap];
179
- FBAssertWaitTillBecomesTrue(alert.isPresent);
160
+ FBAssertWaitTillBecomesTrue([FBAlert alertWithApplication:self.testedApplication].isPresent);
180
161
  }
181
162
 
182
163
  - (void)testGPSAccessAlert
183
164
  {
184
- FBAlert *alert = [FBAlert alertWithApplication:self.testedApplication];
185
- XCTAssertNil(alert.text);
165
+ XCTAssertNil([FBAlert alertWithApplication:self.testedApplication].text);
186
166
 
187
167
  [self.testedApplication.buttons[@"Create GPS access Alert"] tap];
188
- FBAssertWaitTillBecomesTrue(alert.isPresent);
168
+ FBAssertWaitTillBecomesTrue([FBAlert alertWithApplication:self.testedApplication].isPresent);
189
169
 
190
- XCTAssertTrue([alert.text containsString:@"location"]);
191
- XCTAssertTrue([alert.text containsString:@"Yo Yo"]);
170
+ NSString *text = [FBAlert alertWithApplication:self.testedApplication].text;
171
+ XCTAssertTrue([text containsString:@"location"]);
172
+ XCTAssertTrue([text containsString:@"Yo Yo"]);
192
173
  }
193
174
 
194
175
  @end
@@ -14,6 +14,13 @@ extern NSString *const FBShowAlertForceTouchButtonName;
14
14
  extern NSString *const FBTouchesCountLabelIdentifier;
15
15
  extern NSString *const FBTapsCountLabelIdentifier;
16
16
 
17
+ /**
18
+ Labels of the buttons on the integration app's main page, in their on-screen
19
+ (top to bottom) order. Update this in one place - WebDriverAgentTests/IntegrationApp/Resources/Base.lproj/Main.storyboard -
20
+ and here, rather than duplicating the list/count across individual tests.
21
+ */
22
+ extern NSArray<NSString *> *const FBMainViewButtonLabels;
23
+
17
24
  /**
18
25
  XCTestCase helper class used for integration tests
19
26
  */
@@ -62,6 +69,15 @@ extern NSString *const FBTapsCountLabelIdentifier;
62
69
  */
63
70
  - (void)goToScrollPageWithCells:(BOOL)showCells;
64
71
 
72
+ /**
73
+ Navigates integration app to a page containing a 70-level-deep chain of
74
+ nested elements (otherElements[@"view_0"]...otherElements[@"view_69"]),
75
+ each nested directly inside the previous one. Intended as a fixture for
76
+ performance testing of element lookups (e.g. class chain locators) against
77
+ a deep accessibility tree.
78
+ */
79
+ - (void)goToDeepHierarchyPage;
80
+
65
81
  /**
66
82
  Verifies no alerts are present on the page.
67
83
  If an alert exists then it is going to be dismissed.
@@ -10,6 +10,7 @@
10
10
 
11
11
  #import "FBAlert.h"
12
12
  #import "FBTestMacros.h"
13
+ #import "FBExceptions.h"
13
14
  #import "FBIntegrationTestCase.h"
14
15
  #import "FBConfiguration.h"
15
16
  #import "FBMacros.h"
@@ -26,6 +27,14 @@ NSString *const FBShowSheetAlertButtonName = @"Create Sheet Alert";
26
27
  NSString *const FBShowAlertForceTouchButtonName = @"Create Alert (Force Touch)";
27
28
  NSString *const FBTouchesCountLabelIdentifier = @"numberOfTouchesLabel";
28
29
  NSString *const FBTapsCountLabelIdentifier = @"numberOfTapsLabel";
30
+ NSArray<NSString *> *const FBMainViewButtonLabels = @[
31
+ @"Alerts",
32
+ @"Deadlock app",
33
+ @"Attributes",
34
+ @"Scrolling",
35
+ @"Touch",
36
+ @"DeepHierarchy",
37
+ ];
29
38
 
30
39
  @interface FBIntegrationTestCase ()
31
40
  @property (nonatomic, strong) XCUIApplication *testedApplication;
@@ -129,10 +138,28 @@ NSString *const FBTapsCountLabelIdentifier = @"numberOfTapsLabel";
129
138
  FBAssertWaitTillBecomesTrue(self.testedApplication.staticTexts[@"3"].fb_isVisible);
130
139
  }
131
140
 
141
+ - (void)goToDeepHierarchyPage
142
+ {
143
+ [self.testedApplication.buttons[@"DeepHierarchy"] tap];
144
+ [self.testedApplication fb_waitUntilStable];
145
+ // Not fb_isVisible: that runs a native visibility computation which is
146
+ // pathologically slow to resolve for a view nested 70 levels deep at a
147
+ // 1x1 frame. Existence in the accessibility tree is all this fixture
148
+ // actually needs to confirm navigation succeeded.
149
+ FBAssertWaitTillBecomesTrue(self.testedApplication.otherElements[@"view_0"].exists);
150
+ }
151
+
132
152
  - (void)clearAlert
133
153
  {
134
154
  [self.testedApplication fb_waitUntilStable];
135
- [[FBAlert alertWithApplication:self.testedApplication] dismissWithError:nil];
155
+ @try {
156
+ [[FBAlert alertWithApplication:self.testedApplication] dismiss];
157
+ } @catch (NSException *e) {
158
+ if (![e.name isEqualToString:FBAlertNotPresentException]) {
159
+ @throw e;
160
+ }
161
+ // No alert is present, nothing to clear
162
+ }
136
163
  [self.testedApplication fb_waitUntilStable];
137
164
  FBAssertWaitTillBecomesTrue(self.testedApplication.alerts.count == 0);
138
165
  }
@@ -67,7 +67,7 @@
67
67
  XCTAssertEqualObjects(buttonLabels.firstObject, @"Close");
68
68
  XCTAssertNotNil([self.safariApp fb_descendantsMatchingXPathQuery:@"//XCUIElementTypeButton[@label='Close']"
69
69
  shouldReturnAfterFirstMatch:YES].firstObject);
70
- XCTAssertTrue([alert acceptWithError:nil]);
70
+ XCTAssertNoThrow([alert accept]);
71
71
  }
72
72
 
73
73
  @end
@@ -130,7 +130,7 @@
130
130
  - (void)testFindMatchesInElement
131
131
  {
132
132
  NSArray<id<FBXCElementSnapshot>> *matchingSnapshots = [FBXPath matchesWithRootElement:self.testedApplication forQuery:@"//XCUIElementTypeButton"];
133
- XCTAssertEqual([matchingSnapshots count], 5);
133
+ XCTAssertEqual([matchingSnapshots count], FBMainViewButtonLabels.count);
134
134
  for (id<FBXCElementSnapshot> element in matchingSnapshots) {
135
135
  XCTAssertTrue([[FBXCElementSnapshotWrapper ensureWrapped:element].wdType isEqualToString:@"XCUIElementTypeButton"]);
136
136
  }
@@ -174,7 +174,7 @@
174
174
  - (void)testFindMatchesInElementWithDotNotation
175
175
  {
176
176
  NSArray<id<FBXCElementSnapshot>> *matchingSnapshots = [FBXPath matchesWithRootElement:self.testedApplication forQuery:@".//XCUIElementTypeButton"];
177
- XCTAssertEqual([matchingSnapshots count], 5);
177
+ XCTAssertEqual([matchingSnapshots count], FBMainViewButtonLabels.count);
178
178
  for (id<FBXCElementSnapshot> element in matchingSnapshots) {
179
179
  XCTAssertTrue([[FBXCElementSnapshotWrapper ensureWrapped:element].wdType isEqualToString:@"XCUIElementTypeButton"]);
180
180
  }
@@ -232,7 +232,7 @@
232
232
  - (void)testFindMultipleMatchesWithMatchesFunction
233
233
  {
234
234
  [self assertXPathQuery:@"//XCUIElementTypeButton[matches(@label, '.*')]"
235
- findsButtonLabels:@[@"Alerts", @"Deadlock app", @"Attributes", @"Scrolling", @"Touch"]];
235
+ findsButtonLabels:FBMainViewButtonLabels];
236
236
  }
237
237
 
238
238
  - (void)testInvalidXPathExtensionFunctionViaElementLookup
@@ -36,13 +36,7 @@
36
36
 
37
37
  - (void)testDescendantsMatchingType
38
38
  {
39
- NSSet<NSString *> *expectedLabels = [NSSet setWithArray:@[
40
- @"Alerts",
41
- @"Attributes",
42
- @"Scrolling",
43
- @"Deadlock app",
44
- @"Touch",
45
- ]];
39
+ NSSet<NSString *> *expectedLabels = [NSSet setWithArray:FBMainViewButtonLabels];
46
40
  NSArray<id<FBXCElementSnapshot>> *matchingSnapshots = [[FBXCElementSnapshotWrapper ensureWrapped:
47
41
  [self.testedView fb_customSnapshot]]
48
42
  fb_descendantsMatchingType:XCUIElementTypeButton];
@@ -41,13 +41,7 @@
41
41
 
42
42
  - (void)testDescendantsWithClassName
43
43
  {
44
- NSSet<NSString *> *expectedLabels = [NSSet setWithArray:@[
45
- @"Alerts",
46
- @"Attributes",
47
- @"Scrolling",
48
- @"Deadlock app",
49
- @"Touch",
50
- ]];
44
+ NSSet<NSString *> *expectedLabels = [NSSet setWithArray:FBMainViewButtonLabels];
51
45
  NSArray<XCUIElement *> *matchingSnapshots = [self.testedView fb_descendantsMatchingClassName:@"XCUIElementTypeButton"
52
46
  shouldReturnAfterFirstMatch:NO];
53
47
  XCTAssertEqual(matchingSnapshots.count, expectedLabels.count);
@@ -273,7 +267,7 @@
273
267
  NSString *queryString =@"XCUIElementTypeWindow/XCUIElementTypeOther/**/XCUIElementTypeButton";
274
268
  matchingSnapshots = [self.testedApplication fb_descendantsMatchingClassChain:queryString
275
269
  shouldReturnAfterFirstMatch:NO];
276
- XCTAssertEqual(matchingSnapshots.count, 5); // /XCUIElementTypeButton
270
+ XCTAssertEqual(matchingSnapshots.count, FBMainViewButtonLabels.count); // /XCUIElementTypeButton
277
271
  for (XCUIElement *matchingSnapshot in matchingSnapshots) {
278
272
  XCTAssertEqual(matchingSnapshot.elementType, XCUIElementTypeButton);
279
273
  }
@@ -292,7 +286,7 @@
292
286
  matchingSnapshots = [self.testedApplication fb_descendantsMatchingClassChain:queryString
293
287
  shouldReturnAfterFirstMatch:NO];
294
288
  }
295
- XCTAssertEqual(matchingSnapshots.count, 5); // /XCUIElementTypeButton
289
+ XCTAssertEqual(matchingSnapshots.count, FBMainViewButtonLabels.count); // /XCUIElementTypeButton
296
290
  for (XCUIElement *matchingSnapshot in matchingSnapshots) {
297
291
  XCTAssertEqual(matchingSnapshot.elementType, XCUIElementTypeButton);
298
292
  }
@@ -376,7 +370,7 @@
376
370
 
377
371
  XCTAssertEqual(matchingSnapshots.count, 1);
378
372
  XCTAssertEqual(matchingSnapshots.lastObject.elementType, XCUIElementTypeButton);
379
- XCTAssertTrue([matchingSnapshots.lastObject.label isEqualToString:@"Touch"]);
373
+ XCTAssertEqualObjects(matchingSnapshots.lastObject.label, FBMainViewButtonLabels.lastObject);
380
374
 
381
375
  matchingSnapshots = [self.testedView fb_descendantsMatchingClassChain:@"XCUIElementTypeButton[-10]"
382
376
  shouldReturnAfterFirstMatch:YES];
@@ -470,3 +464,75 @@
470
464
  }
471
465
 
472
466
  @end
467
+
468
+ @interface XCUIElementFBFindTests_DeepHierarchyPage : FBIntegrationTestCase
469
+ @end
470
+ @implementation XCUIElementFBFindTests_DeepHierarchyPage
471
+
472
+ - (void)setUp
473
+ {
474
+ [super setUp];
475
+ static dispatch_once_t onceToken;
476
+ dispatch_once(&onceToken, ^{
477
+ [self launchApplication];
478
+ [self goToDeepHierarchyPage];
479
+ });
480
+ }
481
+
482
+ // No intermediate segment has a position here (only the final one does), so
483
+ // this exercises the query-based class chain strategy - the fast path for
484
+ // the common case. See fb_hasIntermediatePosition: in XCUIElement+FBClassChain.m.
485
+ - (void)testClassChainWithoutIntermediatePositionOnDeepHierarchy
486
+ {
487
+ NSString *query = @"**/XCUIElementTypeOther[`label BEGINSWITH \"View 19\"`]/XCUIElementTypeOther[1]";
488
+ NSArray<XCUIElement *> *matches = [self.testedApplication fb_descendantsMatchingClassChain:query
489
+ shouldReturnAfterFirstMatch:NO];
490
+ XCTAssertEqual(matches.count, 1);
491
+ XCTAssertEqualObjects(matches.firstObject.label, @"View 20");
492
+ }
493
+
494
+ // The first segment here has an explicit position and is not the last
495
+ // segment in the chain, so this exercises the snapshot-walk class chain
496
+ // strategy - the one that avoids paying an extra accessibility round trip
497
+ // per intermediate indexed segment.
498
+ - (void)testClassChainWithIntermediatePositionOnDeepHierarchy
499
+ {
500
+ NSString *query = @"**/XCUIElementTypeOther[`label == \"View 10\"`][1]/**/XCUIElementTypeOther[`label BEGINSWITH \"View 19\"`]";
501
+ NSArray<XCUIElement *> *matches = [self.testedApplication fb_descendantsMatchingClassChain:query
502
+ shouldReturnAfterFirstMatch:NO];
503
+ XCTAssertEqual(matches.count, 1);
504
+ XCTAssertEqualObjects(matches.firstObject.label, @"View 19");
505
+ }
506
+
507
+ // Exercises the snapshot-walk strategy with several intermediate positions
508
+ // (the shape it exists for), as opposed to testPerformanceOfClassChainLookupOnDeepHierarchy
509
+ // above, which only has a position on the final segment and stays on the
510
+ // query-based strategy. Kept within the default snapshotMaxDepth (50)
511
+ // combined with the app's own chrome depth above this fixture's root -
512
+ // going deeper hit a native kAXErrorIllegalArgument even after raising
513
+ // snapshotMaxDepth, which looks like a platform-level limitation
514
+ // independent of this lookup, not something to route around here.
515
+ - (void)testPerformanceOfMultiCheckpointClassChainLookupOnDeepHierarchy
516
+ {
517
+ NSString *query = @"**/XCUIElementTypeOther[`label == \"View 5\"`][1]/**/XCUIElementTypeOther[`label == \"View 15\"`][1]/**/XCUIElementTypeOther[`label == \"View 25\"`][1]/**/XCUIElementTypeOther[`label BEGINSWITH \"View 30\"`]";
518
+ [self measureBlock:^{
519
+ NSArray<XCUIElement *> *matches = [self.testedApplication fb_descendantsMatchingClassChain:query
520
+ shouldReturnAfterFirstMatch:NO];
521
+ XCTAssertEqual(matches.count, 1);
522
+ }];
523
+ }
524
+
525
+ // Not a strict pass/fail assertion (timings vary across machines/CI) - this
526
+ // records a measurement baseline so future regressions on this lookup show
527
+ // up in Xcode's test reports.
528
+ - (void)testPerformanceOfClassChainLookupOnDeepHierarchy
529
+ {
530
+ NSString *query = @"**/XCUIElementTypeOther[`label BEGINSWITH \"View 19\"`]/XCUIElementTypeOther[1]";
531
+ [self measureBlock:^{
532
+ NSArray<XCUIElement *> *matches = [self.testedApplication fb_descendantsMatchingClassChain:query
533
+ shouldReturnAfterFirstMatch:NO];
534
+ XCTAssertEqual(matches.count, 1);
535
+ }];
536
+ }
537
+
538
+ @end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appium-webdriveragent",
3
- "version": "16.1.1",
3
+ "version": "16.1.3",
4
4
  "description": "Package bundling WebDriverAgent",
5
5
  "keywords": [
6
6
  "Appium",