appium-webdriveragent 16.14.0 → 16.14.2

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.
@@ -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,140 @@
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)testScreenInfoAcrossPortraitAndLandscapeOrientations
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 @[@(UIDeviceOrientationPortrait),
378
+ @(UIDeviceOrientationLandscapeLeft),
379
+ @(UIDeviceOrientationLandscapeRight),
380
+ @(UIDeviceOrientationPortrait)]) {
381
+ XCTAssertTrue([[XCUIDevice sharedDevice] fb_setDeviceInterfaceOrientation:orientation.integerValue]);
382
+ __block NSDictionary *info = nil;
383
+ BOOL isPortrait = UIDeviceOrientationIsPortrait(orientation.integerValue);
384
+ // Include portrait before and after rotation: landscape-native iPad panels
385
+ // must swap their screen bounds back and retain the visible top status bar.
386
+ // Wait for rotation rather than accepting a transient zero crop.
387
+ XCTAssertTrue([[[FBRunLoopSpinner new] timeout:5] spinUntilTrue:^BOOL {
388
+ info = [self screenInfoResponse];
389
+ double width = [info[@"screenSize"][@"width"] doubleValue];
390
+ double height = [info[@"screenSize"][@"height"] doubleValue];
391
+ double barWidth = [info[@"statusBarSize"][@"width"] doubleValue];
392
+ double barHeight = [info[@"statusBarSize"][@"height"] doubleValue];
393
+ if (nil != info[@"error"] || width <= 0 || height <= 0
394
+ || (isPortrait ? width >= height : width <= height)) {
395
+ return NO;
396
+ }
397
+ if (barHeight == 0) {
398
+ return !expectsVisibleBar && !isPortrait && barWidth == 0;
399
+ }
400
+ return barHeight > 0 && barHeight < height && fabs(barWidth - width) <= 1;
401
+ }], @"Unexpected screen metadata at orientation %@: %@", orientation, info);
402
+ }
403
+ }
404
+
405
+ - (void)testScreenInfoAcrossFoldStates
406
+ {
407
+ XCUIDevice *device = XCUIDevice.sharedDevice;
408
+ XCTSkipIf(!device.fb_canAttemptSimulatedHingeAngleInjection, @"Requires a foldable simulator");
409
+ NSError *error = nil;
410
+ NSNumber *originalAngle = [device fb_getSimulatedHingeAngle:&error];
411
+ XCTAssertNotNil(originalAngle, @"%@", error);
412
+ [self addTeardownBlock:^{
413
+ if (nil != originalAngle) {
414
+ [device fb_setSimulatedHingeAngle:originalAngle.doubleValue error:nil];
415
+ }
416
+ }];
417
+ for (NSNumber *angle in @[@0, @90, @180, @0]) {
418
+ [self.testedApplication terminate];
419
+ XCTAssertTrue([device fb_setSimulatedHingeAngle:angle.doubleValue error:&error], @"%@", error);
420
+ [self launchApplication];
421
+ XCUIElement *window = self.testedApplication.windows.firstMatch;
422
+ CGRect frame = window.frame;
423
+ FBConfiguration.sharedInstance.currentDisplayId = @(window.screen.displayID);
424
+ __block NSDictionary *info = nil;
425
+ XCTAssertTrue([[[FBRunLoopSpinner new] timeout:5] spinUntilTrue:^BOOL {
426
+ info = [self screenInfoResponse];
427
+ double width = [info[@"screenSize"][@"width"] doubleValue];
428
+ double height = [info[@"screenSize"][@"height"] doubleValue];
429
+ double barHeight = [info[@"statusBarSize"][@"height"] doubleValue];
430
+ return nil == info[@"error"] && fabs(width - frame.size.width) <= 1
431
+ && fabs(height - frame.size.height) <= 1
432
+ && barHeight >= 0 && barHeight < height
433
+ && (barHeight == 0 || fabs([info[@"statusBarSize"][@"width"] doubleValue] - width) <= 1)
434
+ && (angle.doubleValue == 0 || barHeight == 0);
435
+ }], @"Unexpected screen metadata at hinge angle %@: %@", angle, info);
436
+ }
437
+ }
438
+
439
+ - (void)testScreenInfoIncludesTopStatusBarOnSingleDisplay
440
+ {
441
+ XCTSkipIf([FBScreen screensWithError:nil].count != 1, @"Requires a single-display device");
442
+ // The integration fixture uses the ordinary visible portrait status bar.
443
+ // An unresolved XCUIElement has displayID == 0; it must not be filtered out.
444
+ NSDictionary *info = [self screenInfoResponse];
445
+ XCTAssertGreaterThan([info[@"statusBarSize"][@"height"] doubleValue], 0);
446
+ XCTAssertEqualWithAccuracy([info[@"statusBarSize"][@"width"] doubleValue],
447
+ [info[@"screenSize"][@"width"] doubleValue], 1);
448
+ }
449
+
302
450
  @end
@@ -9,6 +9,9 @@
9
9
  #import <XCTest/XCTest.h>
10
10
 
11
11
  #import "FBIntegrationTestCase.h"
12
+ #import "FBConfiguration.h"
13
+ #import "FBMathUtils.h"
14
+ #import "FBScreen.h"
12
15
 
13
16
  #import "XCUIElement.h"
14
17
  #import "XCUIDevice.h"
@@ -21,6 +24,7 @@
21
24
  #import "XCSynthesizedEventRecord.h"
22
25
  #import "XCPointerEventPath.h"
23
26
  #import "XCPointerEvent.h"
27
+ #import "XCUIScreen.h"
24
28
 
25
29
  @interface FBW3CTouchActionsIntegrationTestsPart1 : FBIntegrationTestCase
26
30
  @end
@@ -32,9 +36,36 @@
32
36
 
33
37
  @implementation FBW3CTouchActionsIntegrationTestsPart1
34
38
 
39
+ - (void)testDisplayIndependentActionsAfterSelectedDisplayDisappears
40
+ {
41
+ // setUp already opens the Alerts fixture; relaunching would reset that page
42
+ // and break the subsequent touch tests that share it.
43
+ NSNumber *previous = FBConfiguration.sharedInstance.currentDisplayId;
44
+ [self addTeardownBlock:^{ FBConfiguration.sharedInstance.currentDisplayId = previous; }];
45
+ NSArray *sequences = @[
46
+ @{@"id": @"keyboard", @"type": @"key", @"actions": @[@{@"type": @"keyDown", @"value": @"a"}, @{@"type": @"keyUp", @"value": @"a"}]},
47
+ @{@"id": @"finger", @"type": @"pointer", @"parameters": @{@"pointerType": @"touch"}, @"actions": @[@{@"type": @"pause", @"duration": @10}]}
48
+ ];
49
+ for (NSDictionary *sequence in sequences) {
50
+ for (NSNumber *display in @[@1, @987654]) {
51
+ FBConfiguration.sharedInstance.currentDisplayId = display;
52
+ NSError *error = nil;
53
+ FBW3CActionsSynthesizer *synthesizer = [[FBW3CActionsSynthesizer alloc] initWithActions:@[sequence] forApplication:self.testedApplication elementCache:nil error:&error];
54
+ XCSynthesizedEventRecord *record = [synthesizer synthesizeWithError:&error];
55
+ XCTAssertNotNil(record, @"Display-independent %@ should succeed: %@", sequence[@"type"], error);
56
+ }
57
+ }
58
+ }
59
+
35
60
  - (void)verifyGesture:(NSArray<NSDictionary<NSString *, id> *> *)gesture orientation:(UIDeviceOrientation)orientation
36
61
  {
37
62
  [[XCUIDevice sharedDevice] fb_setDeviceInterfaceOrientation:orientation];
63
+ // XCTest may report the new interface orientation while the Alerts button
64
+ // still has transposed geometry from the rotation transition. The fixture's
65
+ // button is wider than it is tall in every settled orientation. Wait for that
66
+ // layout before resolving the W3C action's element-origin coordinates.
67
+ XCUIElement *button = self.testedApplication.buttons[FBShowAlertButtonName];
68
+ FBAssertWaitTillBecomesTrue(CGRectGetWidth(button.frame) > CGRectGetHeight(button.frame));
38
69
  NSError *error;
39
70
  XCTAssertTrue([self.testedApplication fb_performW3CActions:gesture elementCache:nil error:&error]);
40
71
  FBAssertWaitTillBecomesTrue(self.testedApplication.alerts.count > 0);
@@ -53,11 +84,106 @@
53
84
 
54
85
  - (void)tearDown
55
86
  {
87
+ FBConfiguration.sharedInstance.currentDisplayId = nil;
56
88
  [self clearAlert];
57
89
  [self resetOrientation];
58
90
  [super tearDown];
59
91
  }
60
92
 
93
+ - (nullable XCSynthesizedEventRecord *)tapRecordWithError:(NSError **)error
94
+ {
95
+ NSArray *gesture = @[@{
96
+ @"type": @"pointer", @"id": @"finger", @"parameters": @{@"pointerType": @"touch"},
97
+ @"actions": @[
98
+ @{@"type": @"pointerMove", @"x": @100, @"y": @100, @"duration": @0},
99
+ @{@"type": @"pointerDown", @"button": @0},
100
+ @{@"type": @"pointerUp", @"button": @0},
101
+ ],
102
+ }];
103
+ FBW3CActionsSynthesizer *synthesizer = [[FBW3CActionsSynthesizer alloc] initWithActions:gesture
104
+ forApplication:self.testedApplication
105
+ elementCache:nil
106
+ error:error];
107
+ return [synthesizer synthesizeWithError:error];
108
+ }
109
+
110
+ - (NSArray<XCPointerEvent *> *)pointerEventsOfRecord:(XCSynthesizedEventRecord *)record
111
+ {
112
+ XCPointerEventPath *eventPath = record.eventPaths.firstObject;
113
+ return eventPath.pointerEvents;
114
+ }
115
+
116
+ - (void)testMainDisplaySettingKeepsDefaultCoordinates
117
+ {
118
+ for (NSNumber *orientation in @[@(UIDeviceOrientationPortrait),
119
+ @(UIDeviceOrientationLandscapeLeft),
120
+ @(UIDeviceOrientationLandscapeRight)]) {
121
+ [[XCUIDevice sharedDevice] fb_setDeviceInterfaceOrientation:orientation.integerValue];
122
+ NSError *error = nil;
123
+ FBConfiguration.sharedInstance.currentDisplayId = nil;
124
+ XCSynthesizedEventRecord *baseline = [self tapRecordWithError:&error];
125
+ FBConfiguration.sharedInstance.currentDisplayId = @([FBScreen displayID]);
126
+ XCSynthesizedEventRecord *selected = [self tapRecordWithError:&error];
127
+ XCTAssertNotNil(baseline);
128
+ XCTAssertNotNil(selected);
129
+ XCTAssertNil(error);
130
+ NSArray<XCPointerEvent *> *baselineEvents = [self pointerEventsOfRecord:baseline];
131
+ NSArray<XCPointerEvent *> *selectedEvents = [self pointerEventsOfRecord:selected];
132
+ XCTAssertEqual(baselineEvents.count, selectedEvents.count);
133
+ for (NSUInteger i = 0; i < baselineEvents.count; i++) {
134
+ XCTAssertTrue(CGPointEqualToPoint(baselineEvents[i].coordinate, selectedEvents[i].coordinate));
135
+ }
136
+ }
137
+ }
138
+
139
+ - (void)testUnavailableDisplaySettingIsRejected
140
+ {
141
+ long long maxID = 0;
142
+ for (NSDictionary<NSString *, id> *screen in [FBScreen screensWithError:nil]) {
143
+ maxID = MAX(maxID, [screen[@"displayId"] longLongValue]);
144
+ }
145
+ FBConfiguration.sharedInstance.currentDisplayId = @(maxID + 1);
146
+ NSError *error = nil;
147
+ XCTAssertNil([self tapRecordWithError:&error]);
148
+ XCTAssertNotNil(error);
149
+ }
150
+
151
+ - (void)testSecondaryDisplaySetting
152
+ {
153
+ XCUIScreen *secondary = nil;
154
+ NSError *error = nil;
155
+ NSArray<NSDictionary<NSString *, id> *> *screens = [FBScreen screensWithError:&error];
156
+ XCTAssertNotNil(screens);
157
+ XCTAssertNil(error);
158
+ for (NSDictionary<NSString *, id> *screen in screens) {
159
+ long long displayID = [screen[@"displayId"] longLongValue];
160
+ if (displayID != [FBScreen displayID]) {
161
+ secondary = [FBScreen screenWithDisplayID:displayID error:&error];
162
+ XCTAssertNotNil(secondary);
163
+ XCTAssertNil(error);
164
+ break;
165
+ }
166
+ }
167
+ if (nil == secondary) {
168
+ XCTSkip(@"The device has no secondary display");
169
+ }
170
+ XCSynthesizedEventRecord *baseline = [self tapRecordWithError:&error];
171
+ FBConfiguration.sharedInstance.currentDisplayId = @(secondary.displayID);
172
+ XCSynthesizedEventRecord *selected = [self tapRecordWithError:&error];
173
+ XCTAssertNotNil(selected);
174
+ XCTAssertNil(error);
175
+ XCTAssertEqual(selected.displayID, (unsigned long long)secondary.displayID);
176
+ XCUIScreen *main = XCUIScreen.mainScreen;
177
+ CGVector offset = FBDisplayCoordinateOffset(
178
+ CGSizeMake(main.bounds.size.width / main.scale, main.bounds.size.height / main.scale),
179
+ CGSizeMake(secondary.bounds.size.width / secondary.scale, secondary.bounds.size.height / secondary.scale),
180
+ selected.interfaceOrientation);
181
+ CGPoint expected = [self pointerEventsOfRecord:baseline].firstObject.coordinate;
182
+ CGPoint actual = [self pointerEventsOfRecord:selected].firstObject.coordinate;
183
+ XCTAssertEqual(actual.x, expected.x + offset.dx);
184
+ XCTAssertEqual(actual.y, expected.y + offset.dy);
185
+ }
186
+
61
187
  - (void)testErroneousGestures
62
188
  {
63
189
  NSArray<NSArray<NSDictionary<NSString *, id> *> *> *invalidGestures =
@@ -148,4 +148,49 @@
148
148
  XCTAssertTrue(endOffset.dx == -1 && endOffset.dy == -1);
149
149
  }
150
150
 
151
+ - (void)testDisplayCoordinateOffsets
152
+ {
153
+ CGSize mainSize = CGSizeMake(402, 874);
154
+ CGSize innerSize = CGSizeMake(669, 951);
155
+ NSArray<NSNumber *> *orientations = @[
156
+ @(UIInterfaceOrientationPortrait),
157
+ @(UIInterfaceOrientationLandscapeLeft),
158
+ @(UIInterfaceOrientationLandscapeRight),
159
+ @(UIInterfaceOrientationPortraitUpsideDown),
160
+ ];
161
+ NSArray<NSValue *> *expected = @[
162
+ [NSValue valueWithCGPoint:CGPointZero],
163
+ [NSValue valueWithCGPoint:CGPointMake(0, 77)],
164
+ [NSValue valueWithCGPoint:CGPointMake(267, 0)],
165
+ [NSValue valueWithCGPoint:CGPointMake(267, 77)],
166
+ ];
167
+ for (NSUInteger i = 0; i < orientations.count; i++) {
168
+ UIInterfaceOrientation orientation = orientations[i].integerValue;
169
+ CGVector offset = FBDisplayCoordinateOffset(mainSize, innerSize, orientation);
170
+ XCTAssertEqual(offset.dx, expected[i].CGPointValue.x);
171
+ XCTAssertEqual(offset.dy, expected[i].CGPointValue.y);
172
+ CGVector sameDisplay = FBDisplayCoordinateOffset(mainSize, mainSize, orientation);
173
+ XCTAssertEqual(sameDisplay.dx, 0);
174
+ XCTAssertEqual(sameDisplay.dy, 0);
175
+ CGVector reverse = FBDisplayCoordinateOffset(innerSize, mainSize, orientation);
176
+ XCTAssertEqual(reverse.dx, -expected[i].CGPointValue.x);
177
+ XCTAssertEqual(reverse.dy, -expected[i].CGPointValue.y);
178
+ }
179
+ }
180
+
181
+ - (void)testPortraitVisibleFrameRotation
182
+ {
183
+ CGSize size = CGSizeMake(669, 951);
184
+ CGRect clipped = CGRectMake(200, 100, 300, 400);
185
+ XCTAssertTrue(CGRectEqualToRect(FBRectFromPortraitCoordinates(clipped, size, UIInterfaceOrientationPortrait), clipped));
186
+ XCTAssertTrue(CGRectEqualToRect(FBRectFromPortraitCoordinates(clipped, size, UIInterfaceOrientationLandscapeLeft), CGRectMake(451, 200, 400, 300)));
187
+ XCTAssertTrue(CGRectEqualToRect(FBRectFromPortraitCoordinates(clipped, size, UIInterfaceOrientationLandscapeRight), CGRectMake(100, 169, 400, 300)));
188
+ XCTAssertTrue(CGRectEqualToRect(FBRectFromPortraitCoordinates(clipped, size, UIInterfaceOrientationPortraitUpsideDown), CGRectMake(169, 451, 300, 400)));
189
+ // Observed on Duo's inner landscape display: keep the clipping in the same
190
+ // coordinate space as the element's reported frame.
191
+ XCTAssertTrue(CGRectEqualToRect(FBRectFromPortraitCoordinates(CGRectMake(236, 104, 379, 827), size, UIInterfaceOrientationLandscapeLeft), CGRectMake(20, 236, 827, 379)));
192
+ XCTAssertTrue(CGRectIsEmpty(FBRectFromPortraitCoordinates(CGRectZero, size, UIInterfaceOrientationLandscapeLeft)));
193
+ XCTAssertTrue(CGRectIsNull(FBRectFromPortraitCoordinates(CGRectNull, size, UIInterfaceOrientationLandscapeLeft)));
194
+ }
195
+
151
196
  @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.2",
4
4
  "description": "Package bundling WebDriverAgent",
5
5
  "keywords": [
6
6
  "Appium",