appium-webdriveragent 16.14.0 → 16.14.1

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,10 @@
1
+ ## [16.14.1](https://github.com/appium/WebDriverAgent/compare/v16.14.0...v16.14.1) (2026-10-07)
2
+
3
+ ### Bug Fixes
4
+
5
+ * report geometry for the selected display ([#1291](https://github.com/appium/WebDriverAgent/issues/1291)) ([43fa6da](https://github.com/appium/WebDriverAgent/commit/43fa6da647fb8adca21fdbd106d8f73d782d6717))
6
+ * skip accessibility idle checks for WebKit services ([#1293](https://github.com/appium/WebDriverAgent/issues/1293)) ([9d06611](https://github.com/appium/WebDriverAgent/commit/9d06611d1f5d342fd16458f707dca3f3e3ec00fc))
7
+
1
8
  ## [16.14.0](https://github.com/appium/WebDriverAgent/compare/v16.13.6...v16.14.0) (2026-10-04)
2
9
 
3
10
  ### Features
@@ -109,6 +109,14 @@ static id swizzledRequestSnapshotForElement(id self, SEL _cmd, id element, id at
109
109
  return original_requestSnapshotForElement(self, _cmd, element, attributes, parameters, error);
110
110
  }
111
111
 
112
+ // Remote WKWebView elements belong to a WebKit service, not the host app.
113
+ // Sending the idle-check AX action to WebContent can crash it (#1290).
114
+ // Read process metadata only: resolving UI attributes here can reenter
115
+ // snapshotting. Leave these services on the original snapshot path.
116
+ if ([application.bundleID hasPrefix:@"com.apple.WebKit."]) {
117
+ return original_requestSnapshotForElement(self, _cmd, element, attributes, parameters, error);
118
+ }
119
+
112
120
  NSNumber *pidKey = @(pid);
113
121
  @synchronized (unresponsiveApplicationPidsLock) {
114
122
  NSDate *markedUnresponsiveAt = unresponsiveApplicationPids[pidKey];
@@ -34,6 +34,7 @@
34
34
  #import "XCUIElement.h"
35
35
  #import "XCUIElement+FBIsVisible.h"
36
36
  #import "XCUIElementQuery.h"
37
+ #import "XCUIScreen.h"
37
38
  #import "FBUnattachedAppLauncher.h"
38
39
 
39
40
  @implementation FBCustomCommands
@@ -162,17 +163,41 @@
162
163
 
163
164
  + (id<FBResponsePayload>)handleGetScreen:(FBRouteRequest *)request
164
165
  {
166
+ NSError *error = nil;
167
+ XCUIScreen *screen = [FBScreen currentScreenWithError:&error];
168
+ if (nil == screen) {
169
+ return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:error.localizedDescription
170
+ traceback:nil]);
171
+ }
165
172
  XCUIApplication *app = XCUIApplication.fb_systemApplication;
166
-
167
- XCUIElement *mainStatusBar = app.statusBars.allElementsBoundByIndex.firstObject;
168
- CGSize statusBarSize = (nil == mainStatusBar) ? CGSizeZero : mainStatusBar.frame.size;
169
-
170
- #if TARGET_OS_TV || TARGET_OS_WATCH
171
- CGSize screenSize = app.frame.size;
172
- #else
173
- CGSize screenSize = FBAdjustDimensionsForApplication(app.wdFrame.size, app.interfaceOrientation);
173
+ CGSize screenSize = CGSizeMake(CGRectGetWidth(screen.bounds) / screen.scale,
174
+ CGRectGetHeight(screen.bounds) / screen.scale);
175
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
176
+ XCUIApplication *activeApp = request.session ? request.session.activeApplication : XCUIApplication.fb_activeApplication;
177
+ screenSize = FBAdjustDimensionsForApplication(screenSize, activeApp.interfaceOrientation);
174
178
  #endif
175
179
 
180
+ // SpringBoard can expose hidden status bars belonging to other displays, and
181
+ // Duo also exposes full-screen containers for its side-mounted status UI.
182
+ // Only a visible horizontal bar at the top of this display can be subtracted
183
+ // from a rectangular viewport.
184
+ CGSize statusBarSize = CGSizeZero;
185
+ for (XCUIElement *statusBar in app.statusBars.allElementsBoundByIndex) {
186
+ // Resolve the element before reading its screen: displayID can be zero on
187
+ // an unresolved query result even when the bar belongs to the main screen.
188
+ CGRect frame = statusBar.frame;
189
+ if (statusBar.screen.displayID != screen.displayID || !statusBar.fb_isVisible) {
190
+ continue;
191
+ }
192
+ if (fabs(CGRectGetMinY(frame)) <= 1 && fabs(CGRectGetMinX(frame)) <= 1
193
+ && fabs(CGRectGetWidth(frame) - screenSize.width) <= 1
194
+ && CGRectGetHeight(frame) > 0 && CGRectGetHeight(frame) < screenSize.height
195
+ && CGRectGetHeight(frame) < CGRectGetWidth(frame)) {
196
+ statusBarSize = frame.size;
197
+ break;
198
+ }
199
+ }
200
+
176
201
  return FBResponseWithObject(
177
202
  @{
178
203
  @"screenSize":@{@"width": @(screenSize.width),
@@ -181,8 +206,8 @@
181
206
  @"statusBarSize": @{@"width": @(statusBarSize.width),
182
207
  @"height": @(statusBarSize.height),
183
208
  },
184
- @"displayId": @([FBScreen displayID]),
185
- @"scale": @([FBScreen scale]),
209
+ @"displayId": @(screen.displayID),
210
+ @"scale": @(screen.scale),
186
211
  });
187
212
  }
188
213
 
@@ -15,11 +15,11 @@
15
15
  <key>CFBundlePackageType</key>
16
16
  <string>FMWK</string>
17
17
  <key>CFBundleShortVersionString</key>
18
- <string>16.14.0</string>
18
+ <string>16.14.1</string>
19
19
  <key>CFBundleSignature</key>
20
20
  <string>????</string>
21
21
  <key>CFBundleVersion</key>
22
- <string>16.14.0</string>
22
+ <string>16.14.1</string>
23
23
  <key>NSPrincipalClass</key>
24
24
  <string/>
25
25
  </dict>
@@ -9,6 +9,10 @@
9
9
  #import "ViewController.h"
10
10
  #import "FBCoordinateProbeViewController.h"
11
11
 
12
+ #if !TARGET_OS_TV
13
+ #import <WebKit/WebKit.h>
14
+ #endif
15
+
12
16
  @interface ViewController ()
13
17
  @property (weak, nonatomic) IBOutlet UILabel *orentationLabel;
14
18
  @property (weak, nonatomic) IBOutlet UIButton *button;
@@ -19,6 +23,17 @@
19
23
  - (void)viewDidLoad
20
24
  {
21
25
  [super viewDidLoad];
26
+
27
+ #if !TARGET_OS_TV
28
+ if ([NSProcessInfo.processInfo.arguments containsObject:@"--webview-fixture"]) {
29
+ WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds];
30
+ webView.autoresizingMask = UIViewAutoresizingFlexibleWidth
31
+ | UIViewAutoresizingFlexibleHeight;
32
+ [self.view addSubview:webView];
33
+ [webView loadHTMLString:@"<!doctype html><html><body>"
34
+ "<p>WebContent snapshot fixture</p></body></html>" baseURL:nil];
35
+ }
36
+ #endif
22
37
 
23
38
  UIAccessibilityCustomAction *action1 =
24
39
  [[UIAccessibilityCustomAction alloc] initWithName:@"Custom Action 1"
@@ -12,6 +12,9 @@
12
12
  #import "FBConfiguration.h"
13
13
  #import "FBRuntimeUtils.h"
14
14
  #import "FBTestMacros.h"
15
+ #import "FBXCAccessibilityElement.h"
16
+ #import "FBXCAXClientProxy.h"
17
+ #import "XCUIApplication.h"
15
18
  #import "XCUIElement.h"
16
19
  #import "XCUIElement+FBIsVisible.h"
17
20
 
@@ -68,4 +71,53 @@
68
71
  }
69
72
  }
70
73
 
74
+ - (void)testAccessibilityDeadlineAllowsResponsiveNativeAppSnapshots
75
+ {
76
+ [self launchApplication];
77
+ NSTimeInterval previousDeadline = FBConfiguration.sharedInstance.accessibilityDeadline;
78
+ @try {
79
+ FBConfiguration.sharedInstance.accessibilityDeadline = 3;
80
+ NSError *error = nil;
81
+ XCTAssertNotNil([self.testedApplication snapshotWithError:&error]);
82
+ XCTAssertNil(error);
83
+ } @finally {
84
+ FBConfiguration.sharedInstance.accessibilityDeadline = previousDeadline;
85
+ [self.testedApplication terminate];
86
+ }
87
+ }
88
+
89
+ - (void)testAccessibilityDeadlineAllowsWebContentSnapshots
90
+ {
91
+ NSTimeInterval previousDeadline = FBConfiguration.sharedInstance.accessibilityDeadline;
92
+ @try {
93
+ // Resolve the remote AX element before enabling the guard so the test
94
+ // exercises a WebContent-owned snapshot, not just the host app's tree.
95
+ FBConfiguration.sharedInstance.accessibilityDeadline = 0;
96
+ self.testedApplication.launchArguments = @[@"--webview-fixture"];
97
+ [self.testedApplication launch];
98
+ XCUIElement *text = self.testedApplication.webViews.staticTexts[
99
+ @"WebContent snapshot fixture"];
100
+ XCTAssertTrue([text waitForExistenceWithTimeout:15]);
101
+ NSError *error = nil;
102
+ id<FBXCElementSnapshot> initial = [text snapshotWithError:&error];
103
+ XCTAssertNotNil(initial);
104
+ XCTAssertNil(error);
105
+ id<FBXCAccessibilityElement> element = initial.accessibilityElement;
106
+ XCTAssertNotEqual(element.processIdentifier, self.testedApplication.processID);
107
+
108
+ FBConfiguration.sharedInstance.accessibilityDeadline = 3;
109
+ for (NSUInteger i = 0; i < 3; i++) {
110
+ error = nil;
111
+ id<FBXCElementSnapshot> snapshot = [FBXCAXClientProxy.sharedClient
112
+ snapshotForElement:element attributes:@[] inDepth:YES error:&error];
113
+ XCTAssertNotNil(snapshot);
114
+ XCTAssertNil(error);
115
+ }
116
+ XCTAssertTrue(text.exists);
117
+ } @finally {
118
+ FBConfiguration.sharedInstance.accessibilityDeadline = previousDeadline;
119
+ [self.testedApplication terminate];
120
+ }
121
+ }
122
+
71
123
  @end
@@ -11,11 +11,23 @@
11
11
  #import "FBIntegrationTestCase.h"
12
12
  #import "FBConfiguration.h"
13
13
  #import "FBScreen.h"
14
+ #import "FBRunLoopSpinner.h"
15
+ #import "XCUIDevice+FBRotation.h"
16
+ #import "XCUIDevice+FBHinge.h"
17
+ #import "XCUIElement.h"
14
18
  #import "FBActiveAppDetectionPoint.h"
15
19
  #import "FBExceptions.h"
16
20
  #import "FBScreenRecordingRequest.h"
17
21
  #import "FBScreenshot.h"
18
22
  #import "XCUIScreen.h"
23
+ #import "FBCustomCommands.h"
24
+ #import "FBRouteRequest.h"
25
+ #import "FBResponsePayload.h"
26
+ #import "RouteResponse.h"
27
+
28
+ @interface FBCustomCommands (FBScreenInfoTesting)
29
+ + (id<FBResponsePayload>)handleGetScreen:(FBRouteRequest *)request;
30
+ @end
19
31
 
20
32
  @interface FBScreen (FBLookupTesting)
21
33
  + (nullable XCUIScreen *)screenWithDisplayID:(long long)displayID device:(id)device error:(NSError **)error;
@@ -299,4 +311,134 @@
299
311
  XCTAssertTrue([FBScreen scale] >= 2);
300
312
  }
301
313
 
314
+ - (NSDictionary *)screenInfoResponse
315
+ {
316
+ FBRouteRequest *request = [FBRouteRequest routeRequestWithURL:[NSURL URLWithString:@"http://localhost:8100/wda/screen"]
317
+ parameters:@{}
318
+ arguments:@{}];
319
+ RouteResponse *response = [RouteResponse new];
320
+ [[FBCustomCommands handleGetScreen:request] dispatchWithResponse:response];
321
+ return [NSJSONSerialization JSONObjectWithData:response.responseData options:0 error:nil][@"value"];
322
+ }
323
+
324
+ - (void)testScreenInfoFollowsDisplaySelection
325
+ {
326
+ for (NSDictionary *screen in [FBScreen screensWithError:nil]) {
327
+ FBConfiguration.sharedInstance.currentDisplayId = screen[@"displayId"];
328
+ NSDictionary *info = [self screenInfoResponse];
329
+ XCTAssertNil(info[@"error"]);
330
+ XCTAssertEqualObjects(info[@"displayId"], screen[@"displayId"]);
331
+ XCTAssertEqualObjects(info[@"scale"], screen[@"scale"]);
332
+ double width = [info[@"screenSize"][@"width"] doubleValue];
333
+ double height = [info[@"screenSize"][@"height"] doubleValue];
334
+ double scale = [screen[@"scale"] doubleValue];
335
+ double expectedWidth = [screen[@"bounds"][@"width"] doubleValue] / scale;
336
+ double expectedHeight = [screen[@"bounds"][@"height"] doubleValue] / scale;
337
+ XCTAssertEqualWithAccuracy(MIN(width, height), MIN(expectedWidth, expectedHeight), 0.01);
338
+ XCTAssertEqualWithAccuracy(MAX(width, height), MAX(expectedWidth, expectedHeight), 0.01);
339
+ double barHeight = [info[@"statusBarSize"][@"height"] doubleValue];
340
+ XCTAssertGreaterThanOrEqual(barHeight, 0);
341
+ XCTAssertLessThan(barHeight, height);
342
+ if (barHeight > 0) {
343
+ XCTAssertEqualWithAccuracy([info[@"statusBarSize"][@"width"] doubleValue], width, 1);
344
+ }
345
+ }
346
+ // tearDown restores the default display even when an assertion fails.
347
+ }
348
+
349
+ - (void)testScreenInfoFollowsDisplayReset
350
+ {
351
+ // Exercise reset independently of the display-selection assertions.
352
+ FBConfiguration.sharedInstance.currentDisplayId = @([self unknownDisplayID]);
353
+ FBConfiguration.sharedInstance.currentDisplayId = nil;
354
+ XCTAssertEqualObjects([self screenInfoResponse][@"displayId"], @([FBScreen displayID]));
355
+ }
356
+
357
+ - (void)testScreenInfoRejectsUnavailableDisplay
358
+ {
359
+ FBConfiguration.sharedInstance.currentDisplayId = @([self unknownDisplayID]);
360
+ NSDictionary *info = [self screenInfoResponse];
361
+ XCTAssertEqualObjects(info[@"error"], @"invalid argument");
362
+ XCTAssertTrue([info[@"message"] containsString:@"Available display ids"]);
363
+ }
364
+
365
+ - (void)testScreenInfoInBothLandscapeOrientations
366
+ {
367
+ XCTSkipIf(XCUIDevice.sharedDevice.fb_canAttemptSimulatedHingeAngleInjection,
368
+ @"Foldable orientations are covered by testScreenInfoAcrossFoldStates");
369
+ XCUIElement *window = self.testedApplication.windows.firstMatch;
370
+ (void)window.frame;
371
+ XCTSkipIf(window.screen.displayID != XCUIScreen.mainScreen.displayID,
372
+ @"Requires the fixture on the main display");
373
+ [self addTeardownBlock:^{
374
+ [[XCUIDevice sharedDevice] fb_setDeviceInterfaceOrientation:UIDeviceOrientationPortrait];
375
+ }];
376
+ BOOL expectsVisibleBar = UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad;
377
+ for (NSNumber *orientation in @[@(UIDeviceOrientationLandscapeLeft), @(UIDeviceOrientationLandscapeRight)]) {
378
+ XCTAssertTrue([[XCUIDevice sharedDevice] fb_setDeviceInterfaceOrientation:orientation.integerValue]);
379
+ __block NSDictionary *info = nil;
380
+ // iPad keeps its top status bar in landscape. Wait for rotation to finish
381
+ // rather than accepting a transient zero crop as a successful result.
382
+ XCTAssertTrue([[[FBRunLoopSpinner new] timeout:5] spinUntilTrue:^BOOL {
383
+ info = [self screenInfoResponse];
384
+ double width = [info[@"screenSize"][@"width"] doubleValue];
385
+ double height = [info[@"screenSize"][@"height"] doubleValue];
386
+ double barWidth = [info[@"statusBarSize"][@"width"] doubleValue];
387
+ double barHeight = [info[@"statusBarSize"][@"height"] doubleValue];
388
+ if (nil != info[@"error"] || width <= height || height <= 0) {
389
+ return NO;
390
+ }
391
+ if (barHeight == 0) {
392
+ return !expectsVisibleBar && barWidth == 0;
393
+ }
394
+ return barHeight > 0 && barHeight < height && fabs(barWidth - width) <= 1;
395
+ }], @"Unexpected landscape screen metadata: %@", info);
396
+ }
397
+ }
398
+
399
+ - (void)testScreenInfoAcrossFoldStates
400
+ {
401
+ XCUIDevice *device = XCUIDevice.sharedDevice;
402
+ XCTSkipIf(!device.fb_canAttemptSimulatedHingeAngleInjection, @"Requires a foldable simulator");
403
+ NSError *error = nil;
404
+ NSNumber *originalAngle = [device fb_getSimulatedHingeAngle:&error];
405
+ XCTAssertNotNil(originalAngle, @"%@", error);
406
+ [self addTeardownBlock:^{
407
+ if (nil != originalAngle) {
408
+ [device fb_setSimulatedHingeAngle:originalAngle.doubleValue error:nil];
409
+ }
410
+ }];
411
+ for (NSNumber *angle in @[@0, @90, @180, @0]) {
412
+ [self.testedApplication terminate];
413
+ XCTAssertTrue([device fb_setSimulatedHingeAngle:angle.doubleValue error:&error], @"%@", error);
414
+ [self launchApplication];
415
+ XCUIElement *window = self.testedApplication.windows.firstMatch;
416
+ CGRect frame = window.frame;
417
+ FBConfiguration.sharedInstance.currentDisplayId = @(window.screen.displayID);
418
+ __block NSDictionary *info = nil;
419
+ XCTAssertTrue([[[FBRunLoopSpinner new] timeout:5] spinUntilTrue:^BOOL {
420
+ info = [self screenInfoResponse];
421
+ double width = [info[@"screenSize"][@"width"] doubleValue];
422
+ double height = [info[@"screenSize"][@"height"] doubleValue];
423
+ double barHeight = [info[@"statusBarSize"][@"height"] doubleValue];
424
+ return nil == info[@"error"] && fabs(width - frame.size.width) <= 1
425
+ && fabs(height - frame.size.height) <= 1
426
+ && barHeight >= 0 && barHeight < height
427
+ && (barHeight == 0 || fabs([info[@"statusBarSize"][@"width"] doubleValue] - width) <= 1)
428
+ && (angle.doubleValue == 0 || barHeight == 0);
429
+ }], @"Unexpected screen metadata at hinge angle %@: %@", angle, info);
430
+ }
431
+ }
432
+
433
+ - (void)testScreenInfoIncludesTopStatusBarOnSingleDisplay
434
+ {
435
+ XCTSkipIf([FBScreen screensWithError:nil].count != 1, @"Requires a single-display device");
436
+ // The integration fixture uses the ordinary visible portrait status bar.
437
+ // An unresolved XCUIElement has displayID == 0; it must not be filtered out.
438
+ NSDictionary *info = [self screenInfoResponse];
439
+ XCTAssertGreaterThan([info[@"statusBarSize"][@"height"] doubleValue], 0);
440
+ XCTAssertEqualWithAccuracy([info[@"statusBarSize"][@"width"] doubleValue],
441
+ [info[@"screenSize"][@"width"] doubleValue], 1);
442
+ }
443
+
302
444
  @end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appium-webdriveragent",
3
- "version": "16.14.0",
3
+ "version": "16.14.1",
4
4
  "description": "Package bundling WebDriverAgent",
5
5
  "keywords": [
6
6
  "Appium",