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.
- package/CHANGELOG.md +13 -0
- package/README.md +9 -8
- package/WebDriverAgent.xcodeproj/project.pbxproj +4 -0
- package/WebDriverAgentLib/Categories/XCAXClient_iOS+FBSnapshotReqParams.m +8 -0
- package/WebDriverAgentLib/Categories/XCUIElement+FBScrolling.m +110 -50
- package/WebDriverAgentLib/Commands/FBCustomCommands.m +42 -10
- package/WebDriverAgentLib/Commands/FBElementCommands.m +30 -1
- package/WebDriverAgentLib/Info.plist +2 -2
- package/WebDriverAgentLib/Utilities/FBMathUtils.h +11 -0
- package/WebDriverAgentLib/Utilities/FBMathUtils.m +38 -0
- package/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.m +121 -13
- package/WebDriverAgentTests/IntegrationApp/COORDINATE_PROBE.md +3 -1
- package/WebDriverAgentTests/IntegrationApp/Classes/FBCoordinateProbeViewController.m +18 -0
- package/WebDriverAgentTests/IntegrationApp/Classes/ViewController.m +15 -0
- package/WebDriverAgentTests/IntegrationTests/FBConfigurationTests.m +52 -0
- package/WebDriverAgentTests/IntegrationTests/FBDisplayGestureTests.m +541 -0
- package/WebDriverAgentTests/IntegrationTests/FBScreenTests.m +148 -0
- package/WebDriverAgentTests/IntegrationTests/FBW3CTouchActionsIntegrationTests.m +126 -0
- package/WebDriverAgentTests/UnitTests/FBMathUtilsTests.m +45 -0
- package/package.json +1 -1
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2015-present, Facebook, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the BSD-style license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#import <XCTest/XCTest.h>
|
|
10
|
+
|
|
11
|
+
#import "FBIntegrationTestCase.h"
|
|
12
|
+
#import "FBConfiguration.h"
|
|
13
|
+
#import "FBScreen.h"
|
|
14
|
+
#import "FBRunLoopSpinner.h"
|
|
15
|
+
#import "FBExceptions.h"
|
|
16
|
+
#import "FBElementCommands.h"
|
|
17
|
+
#import "FBElementCache.h"
|
|
18
|
+
#import "XCUIElement+FBWebDriverAttributes.h"
|
|
19
|
+
|
|
20
|
+
@interface FBElementCommands (CoordinateTests)
|
|
21
|
+
+ (nullable XCUICoordinate *)gestureCoordinateWithOffset:(CGVector)offset element:(XCUIElement *)element error:(NSError **)error;
|
|
22
|
+
@end
|
|
23
|
+
|
|
24
|
+
#import "XCUIApplication+FBTouchAction.h"
|
|
25
|
+
#import "XCUIElement+FBScrolling.h"
|
|
26
|
+
#import "XCUIDevice+FBRotation.h"
|
|
27
|
+
#import "XCUIDevice+FBHinge.h"
|
|
28
|
+
|
|
29
|
+
@interface FBDisplayGestureTests : FBIntegrationTestCase
|
|
30
|
+
@property (nonatomic) NSNumber *previousDisplayId;
|
|
31
|
+
@property (nonatomic) XCUIElement *canvas;
|
|
32
|
+
@property (nonatomic) FBElementCache *actionElementCache;
|
|
33
|
+
@end
|
|
34
|
+
|
|
35
|
+
@implementation FBDisplayGestureTests
|
|
36
|
+
|
|
37
|
+
- (void)setUp
|
|
38
|
+
{
|
|
39
|
+
[super setUp];
|
|
40
|
+
self.previousDisplayId = FBConfiguration.sharedInstance.currentDisplayId;
|
|
41
|
+
self.actionElementCache = nil;
|
|
42
|
+
if (!XCUIDevice.sharedDevice.fb_canAttemptSimulatedHingeAngleInjection) {
|
|
43
|
+
[self resetOrientation];
|
|
44
|
+
}
|
|
45
|
+
[self launchApplication];
|
|
46
|
+
[self.testedApplication.buttons[@"coordinate-probe"] tap];
|
|
47
|
+
self.canvas = self.testedApplication.otherElements[@"coordinate-canvas"];
|
|
48
|
+
|
|
49
|
+
[self selectFixtureDisplay];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
- (void)selectFixtureDisplay
|
|
53
|
+
{
|
|
54
|
+
// The fixture reports the screen it actually occupies. Do not assume the main
|
|
55
|
+
// screen is active: Duo changes displays when its hinge changes position.
|
|
56
|
+
NSArray<NSNumber *> *size = [self measurement][@"screenSize"];
|
|
57
|
+
NSNumber *displayId = nil;
|
|
58
|
+
for (NSDictionary *screen in [FBScreen screensWithError:nil]) {
|
|
59
|
+
CGFloat width = [screen[@"bounds"][@"width"] doubleValue] / [screen[@"scale"] doubleValue];
|
|
60
|
+
CGFloat height = [screen[@"bounds"][@"height"] doubleValue] / [screen[@"scale"] doubleValue];
|
|
61
|
+
if (fabs(MAX(width, height) - MAX(size[0].doubleValue, size[1].doubleValue)) < 1
|
|
62
|
+
&& fabs(MIN(width, height) - MIN(size[0].doubleValue, size[1].doubleValue)) < 1) {
|
|
63
|
+
displayId = screen[@"displayId"];
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
XCTAssertNotNil(displayId, @"Cannot identify the fixture's display");
|
|
68
|
+
FBConfiguration.sharedInstance.currentDisplayId = displayId;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
- (void)tearDown
|
|
72
|
+
{
|
|
73
|
+
FBConfiguration.sharedInstance.currentDisplayId = self.previousDisplayId;
|
|
74
|
+
[super tearDown];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
- (NSDictionary *)measurement
|
|
78
|
+
{
|
|
79
|
+
NSString *value = self.testedApplication.staticTexts[@"probe-status"].label;
|
|
80
|
+
NSError *error = nil;
|
|
81
|
+
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:[value dataUsingEncoding:NSUTF8StringEncoding]
|
|
82
|
+
options:0 error:&error];
|
|
83
|
+
XCTAssertNil(error);
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
- (void)performMoves:(NSArray<NSDictionary *> *)moves expectedEnd:(CGPoint)expected
|
|
88
|
+
{
|
|
89
|
+
NSUInteger count = [[self measurement][@"count"] unsignedIntegerValue];
|
|
90
|
+
NSMutableArray *items = [NSMutableArray arrayWithArray:@[
|
|
91
|
+
moves.firstObject,
|
|
92
|
+
@{@"type": @"pointerDown", @"button": @0},
|
|
93
|
+
@{@"type": @"pause", @"duration": @100},
|
|
94
|
+
]];
|
|
95
|
+
if (moves.count > 1) {
|
|
96
|
+
[items addObjectsFromArray:[moves subarrayWithRange:NSMakeRange(1, moves.count - 1)]];
|
|
97
|
+
}
|
|
98
|
+
[items addObject:@{@"type": @"pointerUp", @"button": @0}];
|
|
99
|
+
NSError *error = nil;
|
|
100
|
+
NSArray *actions = @[@{
|
|
101
|
+
@"type": @"pointer", @"id": @"finger", @"parameters": @{@"pointerType": @"touch"}, @"actions": items,
|
|
102
|
+
}];
|
|
103
|
+
XCTAssertTrue([self.testedApplication fb_performW3CActions:actions elementCache:self.actionElementCache error:&error], @"%@", error);
|
|
104
|
+
NSDictionary *result = [self measurement];
|
|
105
|
+
NSLog(@"W3C_DELIVERED expected=%@ actual=%@ count=%@ phase=%@", NSStringFromCGPoint(expected), result[@"last"], result[@"count"], result[@"phase"]);
|
|
106
|
+
XCTAssertEqual([result[@"count"] unsignedIntegerValue], count + 1);
|
|
107
|
+
XCTAssertEqualObjects(result[@"phase"], @"ended");
|
|
108
|
+
NSArray<NSNumber *> *actual = result[@"last"];
|
|
109
|
+
XCTAssertEqualWithAccuracy(actual[0].doubleValue, expected.x, 1);
|
|
110
|
+
XCTAssertEqualWithAccuracy(actual[1].doubleValue, expected.y, 1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
- (NSDictionary *)moveFrom:(id)origin x:(CGFloat)x y:(CGFloat)y duration:(NSUInteger)duration
|
|
114
|
+
{
|
|
115
|
+
if (nil != self.actionElementCache && [origin isKindOfClass:XCUIElement.class]) {
|
|
116
|
+
NSString *uuid = [self.actionElementCache storeElement:origin];
|
|
117
|
+
XCTAssertNotNil(uuid);
|
|
118
|
+
origin = @{@"element-6066-11e4-a52e-4f735466cecf": uuid};
|
|
119
|
+
}
|
|
120
|
+
return @{@"type": @"pointerMove", @"origin": origin, @"x": @(x), @"y": @(y), @"duration": @(duration)};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
- (void)verifyNativeApplicationCoordinates
|
|
124
|
+
{
|
|
125
|
+
NSDictionary *geometry = [self measurement];
|
|
126
|
+
NSArray<NSNumber *> *bounds = geometry[@"canvasBounds"];
|
|
127
|
+
NSArray<NSNumber *> *frame = geometry[@"canvasWindowRect"];
|
|
128
|
+
for (NSNumber *x in @[@40, @(bounds[0].doubleValue - 40)]) {
|
|
129
|
+
for (NSNumber *y in @[@40, @(bounds[1].doubleValue - 40)]) {
|
|
130
|
+
NSUInteger count = [[self measurement][@"count"] unsignedIntegerValue];
|
|
131
|
+
NSError *error = nil;
|
|
132
|
+
XCUICoordinate *coordinate = [FBElementCommands gestureCoordinateWithOffset:
|
|
133
|
+
CGVectorMake(frame[0].doubleValue + x.doubleValue, frame[1].doubleValue + y.doubleValue)
|
|
134
|
+
element:self.testedApplication error:&error];
|
|
135
|
+
XCTAssertNotNil(coordinate);
|
|
136
|
+
XCTAssertNil(error);
|
|
137
|
+
[coordinate tap];
|
|
138
|
+
NSDictionary *result = [self measurement];
|
|
139
|
+
XCTAssertEqual([result[@"count"] unsignedIntegerValue], count + 1);
|
|
140
|
+
XCTAssertEqualWithAccuracy([result[@"last"][0] doubleValue], x.doubleValue, 1);
|
|
141
|
+
XCTAssertEqualWithAccuracy([result[@"last"][1] doubleValue], y.doubleValue, 1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
- (void)verifyNativeEdgeCoordinates
|
|
147
|
+
{
|
|
148
|
+
NSArray<NSNumber *> *size = [self measurement][@"windowSize"];
|
|
149
|
+
CGFloat width = size[0].doubleValue;
|
|
150
|
+
CGFloat height = size[1].doubleValue;
|
|
151
|
+
NSError *error = nil;
|
|
152
|
+
XCUICoordinate *center = [FBElementCommands gestureCoordinateWithOffset:CGVectorMake(width / 2, height / 2)
|
|
153
|
+
element:self.testedApplication error:&error];
|
|
154
|
+
XCTAssertNotNil(center);
|
|
155
|
+
XCTAssertNil(error);
|
|
156
|
+
for (NSValue *value in @[
|
|
157
|
+
[NSValue valueWithCGPoint:CGPointMake(width, height / 2)],
|
|
158
|
+
[NSValue valueWithCGPoint:CGPointMake(width / 2, height)],
|
|
159
|
+
[NSValue valueWithCGPoint:CGPointMake(width + 30, height / 2)],
|
|
160
|
+
[NSValue valueWithCGPoint:CGPointMake(width / 2, height + 30)],
|
|
161
|
+
[NSValue valueWithCGPoint:CGPointMake(-30, height / 2)],
|
|
162
|
+
[NSValue valueWithCGPoint:CGPointMake(width / 2, -30)],
|
|
163
|
+
]) {
|
|
164
|
+
CGPoint point = value.CGPointValue;
|
|
165
|
+
error = nil;
|
|
166
|
+
XCUICoordinate *coordinate = [FBElementCommands gestureCoordinateWithOffset:CGVectorMake(point.x, point.y)
|
|
167
|
+
element:self.testedApplication error:&error];
|
|
168
|
+
XCTAssertNotNil(coordinate, @"%@", error);
|
|
169
|
+
XCTAssertNil(error);
|
|
170
|
+
if (nil == coordinate) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
// Check extrapolation, not just acceptance: boundary points must not clamp.
|
|
174
|
+
CGPoint expected = [center coordinateWithOffset:CGVectorMake(point.x - width / 2, point.y - height / 2)].screenPoint;
|
|
175
|
+
XCTAssertEqualWithAccuracy(coordinate.screenPoint.x, expected.x, 1);
|
|
176
|
+
XCTAssertEqualWithAccuracy(coordinate.screenPoint.y, expected.y, 1);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
- (void)testNativeEdgeCoordinates
|
|
181
|
+
{
|
|
182
|
+
[self verifyNativeEdgeCoordinates];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
- (void)testNativeApplicationCoordinates
|
|
186
|
+
{
|
|
187
|
+
[self verifyNativeApplicationCoordinates];
|
|
188
|
+
[self verifyNativeEdgeCoordinates];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
- (void)testNativeMainDisplayDefaultCoordinates
|
|
192
|
+
{
|
|
193
|
+
XCTSkipIf(FBConfiguration.sharedInstance.currentDisplayId.longLongValue != [FBScreen displayID],
|
|
194
|
+
@"Requires the fixture on the main display");
|
|
195
|
+
FBConfiguration.sharedInstance.currentDisplayId = nil;
|
|
196
|
+
[self verifyNativeApplicationCoordinates];
|
|
197
|
+
FBConfiguration.sharedInstance.currentDisplayId = @([FBScreen displayID]);
|
|
198
|
+
[self verifyNativeApplicationCoordinates];
|
|
199
|
+
[self verifyNativeEdgeCoordinates];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
- (void)verifyNativeCoordinatesInOrientation:(UIDeviceOrientation)orientation
|
|
203
|
+
{
|
|
204
|
+
XCTSkipIf(XCUIDevice.sharedDevice.fb_canAttemptSimulatedHingeAngleInjection,
|
|
205
|
+
@"Foldable orientations are covered by testNativeCoordinatesAcrossFoldStates");
|
|
206
|
+
UIDeviceOrientation previousOrientation = XCUIDevice.sharedDevice.orientation;
|
|
207
|
+
[self addTeardownBlock:^{
|
|
208
|
+
[XCUIDevice.sharedDevice fb_setDeviceInterfaceOrientation:previousOrientation];
|
|
209
|
+
}];
|
|
210
|
+
XCTAssertTrue([XCUIDevice.sharedDevice fb_setDeviceInterfaceOrientation:orientation]);
|
|
211
|
+
XCTAssertTrue([[[FBRunLoopSpinner new] timeout:5] spinUntilTrue:^BOOL {
|
|
212
|
+
NSDictionary *geometry = [self measurement];
|
|
213
|
+
NSArray<NSNumber *> *size = geometry[@"windowSize"];
|
|
214
|
+
return size[0].doubleValue > size[1].doubleValue
|
|
215
|
+
&& ![geometry[@"rotationInProgress"] boolValue];
|
|
216
|
+
}]);
|
|
217
|
+
[self verifyNativeApplicationCoordinates];
|
|
218
|
+
[self verifyNativeEdgeCoordinates];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
- (void)testNativeCoordinatesInLandscapeLeft
|
|
222
|
+
{
|
|
223
|
+
[self verifyNativeCoordinatesInOrientation:UIDeviceOrientationLandscapeLeft];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
- (void)testNativeCoordinatesInLandscapeRight
|
|
227
|
+
{
|
|
228
|
+
[self verifyNativeCoordinatesInOrientation:UIDeviceOrientationLandscapeRight];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
- (void)testNativeCoordinatesRejectMismatchedWindowDisplay
|
|
232
|
+
{
|
|
233
|
+
NSNumber *fixtureDisplay = FBConfiguration.sharedInstance.currentDisplayId;
|
|
234
|
+
NSArray<NSNumber *> *frame = [self measurement][@"canvasWindowRect"];
|
|
235
|
+
BOOL checkedSecondaryDisplay = NO;
|
|
236
|
+
for (NSDictionary *screen in [FBScreen screensWithError:nil]) {
|
|
237
|
+
if ([screen[@"isMain"] boolValue] || [screen[@"displayId"] isEqual:fixtureDisplay]) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
checkedSecondaryDisplay = YES;
|
|
241
|
+
FBConfiguration.sharedInstance.currentDisplayId = screen[@"displayId"];
|
|
242
|
+
NSError *error = nil;
|
|
243
|
+
XCUICoordinate *coordinate = [FBElementCommands gestureCoordinateWithOffset:
|
|
244
|
+
CGVectorMake(frame[0].doubleValue + frame[2].doubleValue / 2,
|
|
245
|
+
frame[1].doubleValue + frame[3].doubleValue / 2)
|
|
246
|
+
element:self.testedApplication error:&error];
|
|
247
|
+
XCTAssertNil(coordinate, @"A window on display %@ must not satisfy a request for display %@",
|
|
248
|
+
fixtureDisplay, screen[@"displayId"]);
|
|
249
|
+
XCTAssertNotNil(error);
|
|
250
|
+
}
|
|
251
|
+
XCTSkipIf(!checkedSecondaryDisplay, @"Requires an available secondary display without the fixture");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
- (void)testNativeCoordinatesAcrossFoldStates
|
|
255
|
+
{
|
|
256
|
+
XCUIDevice *device = XCUIDevice.sharedDevice;
|
|
257
|
+
XCTSkipIf(!device.fb_canAttemptSimulatedHingeAngleInjection, @"Requires a foldable simulator");
|
|
258
|
+
NSError *error = nil;
|
|
259
|
+
NSNumber *originalAngle = [device fb_getSimulatedHingeAngle:&error];
|
|
260
|
+
XCTAssertNotNil(originalAngle, @"%@", error);
|
|
261
|
+
[self addTeardownBlock:^{
|
|
262
|
+
if (nil != originalAngle) {
|
|
263
|
+
[device fb_setSimulatedHingeAngle:originalAngle.doubleValue error:nil];
|
|
264
|
+
}
|
|
265
|
+
}];
|
|
266
|
+
for (NSNumber *angle in @[@0, @90, @180, @0]) {
|
|
267
|
+
[self.testedApplication terminate];
|
|
268
|
+
XCTAssertTrue([device fb_setSimulatedHingeAngle:angle.doubleValue error:&error], @"%@", error);
|
|
269
|
+
[self launchApplication];
|
|
270
|
+
[self.testedApplication.buttons[@"coordinate-probe"] tap];
|
|
271
|
+
[self selectFixtureDisplay];
|
|
272
|
+
[self verifyNativeApplicationCoordinates];
|
|
273
|
+
[self verifyNativeEdgeCoordinates];
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
- (void)verifyW3CApplicationOrigin
|
|
278
|
+
{
|
|
279
|
+
// Resolve serialized element IDs through the same cache as the HTTP API.
|
|
280
|
+
// Passing raw XCTest objects can hide application-root coordinate errors.
|
|
281
|
+
self.actionElementCache = [FBElementCache new];
|
|
282
|
+
NSDictionary *geometry = [self measurement];
|
|
283
|
+
NSArray<NSNumber *> *bounds = geometry[@"canvasBounds"];
|
|
284
|
+
NSArray<NSNumber *> *frame = geometry[@"canvasWindowRect"];
|
|
285
|
+
CGPoint local = CGPointMake(bounds[0].doubleValue / 2, bounds[1].doubleValue / 2 - 20);
|
|
286
|
+
CGPoint target = CGPointMake(frame[0].doubleValue + local.x, frame[1].doubleValue + local.y);
|
|
287
|
+
// Use the same rect a client receives, not the window's differently oriented
|
|
288
|
+
// frame. On Duo the app rect may still have transposed dimensions.
|
|
289
|
+
CGRect appRect = self.testedApplication.wdFrame;
|
|
290
|
+
NSDictionary *appMove = [self moveFrom:self.testedApplication
|
|
291
|
+
x:target.x - CGRectGetMidX(appRect)
|
|
292
|
+
y:target.y - CGRectGetMidY(appRect) duration:0];
|
|
293
|
+
[self performMoves:@[[self moveFrom:@"viewport" x:target.x y:target.y duration:0]] expectedEnd:local];
|
|
294
|
+
[self performMoves:@[[self moveFrom:self.canvas x:local.x - bounds[0].doubleValue / 2
|
|
295
|
+
y:local.y - bounds[1].doubleValue / 2 duration:0]] expectedEnd:local];
|
|
296
|
+
NSLog(@"W3C_APPLICATION_ORIGIN display=%@ app=%@ window=%@ target=%@", FBConfiguration.sharedInstance.currentDisplayId,
|
|
297
|
+
NSStringFromCGRect(appRect), NSStringFromCGRect(self.testedApplication.windows.firstMatch.frame), NSStringFromCGPoint(target));
|
|
298
|
+
[self performMoves:@[appMove] expectedEnd:local];
|
|
299
|
+
[self performMoves:@[appMove, [self moveFrom:@"pointer" x:0 y:-20 duration:200]]
|
|
300
|
+
expectedEnd:CGPointMake(local.x, local.y - 20)];
|
|
301
|
+
NSMutableDictionary *appDrag = appMove.mutableCopy;
|
|
302
|
+
appDrag[@"duration"] = @200;
|
|
303
|
+
[self performMoves:@[[self moveFrom:self.canvas x:0 y:0 duration:0], appDrag,
|
|
304
|
+
[self moveFrom:@"pointer" x:0 y:-20 duration:200]]
|
|
305
|
+
expectedEnd:CGPointMake(local.x, local.y - 20)];
|
|
306
|
+
[self performMoves:@[appMove, [self moveFrom:self.canvas x:0 y:-40 duration:200],
|
|
307
|
+
[self moveFrom:@"pointer" x:0 y:-20 duration:200]]
|
|
308
|
+
expectedEnd:CGPointMake(bounds[0].doubleValue / 2, bounds[1].doubleValue / 2 - 60)];
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
- (void)testW3CApplicationOrigin
|
|
312
|
+
{
|
|
313
|
+
[self verifyW3CApplicationOrigin];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
- (void)testW3CApplicationOriginAcrossOrientations
|
|
317
|
+
{
|
|
318
|
+
XCTSkipIf(XCUIDevice.sharedDevice.fb_canAttemptSimulatedHingeAngleInjection, @"Covered by fold states");
|
|
319
|
+
[self addTeardownBlock:^{ [self resetOrientation]; }];
|
|
320
|
+
for (NSNumber *orientation in @[@(UIDeviceOrientationLandscapeLeft), @(UIDeviceOrientationLandscapeRight)]) {
|
|
321
|
+
[self resetOrientation];
|
|
322
|
+
XCTAssertTrue([[XCUIDevice sharedDevice] fb_setDeviceInterfaceOrientation:orientation.integerValue]);
|
|
323
|
+
[self verifyW3CApplicationOrigin];
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
- (void)testW3CApplicationOriginAcrossFoldStates
|
|
328
|
+
{
|
|
329
|
+
XCUIDevice *device = XCUIDevice.sharedDevice;
|
|
330
|
+
XCTSkipIf(!device.fb_canAttemptSimulatedHingeAngleInjection, @"Requires a foldable simulator");
|
|
331
|
+
NSError *error = nil;
|
|
332
|
+
NSNumber *originalAngle = [device fb_getSimulatedHingeAngle:&error];
|
|
333
|
+
XCTAssertNotNil(originalAngle);
|
|
334
|
+
[self addTeardownBlock:^{ if (nil != originalAngle) { [device fb_setSimulatedHingeAngle:originalAngle.doubleValue error:nil]; } }];
|
|
335
|
+
for (NSNumber *angle in @[@0, @180, @90, @0]) {
|
|
336
|
+
[self.testedApplication terminate];
|
|
337
|
+
XCTAssertTrue([device fb_setSimulatedHingeAngle:angle.doubleValue error:&error], @"%@", error);
|
|
338
|
+
XCTAssertTrue([[[FBRunLoopSpinner new] timeout:5] spinUntilTrue:^BOOL {
|
|
339
|
+
NSNumber *reading = [device fb_getSimulatedHingeAngle:nil];
|
|
340
|
+
return nil != reading && fabs(reading.doubleValue - angle.doubleValue) < 1;
|
|
341
|
+
}]);
|
|
342
|
+
[self launchApplication];
|
|
343
|
+
[self.testedApplication.buttons[@"coordinate-probe"] tap];
|
|
344
|
+
self.canvas = self.testedApplication.otherElements[@"coordinate-canvas"];
|
|
345
|
+
[self selectFixtureDisplay];
|
|
346
|
+
[self verifyW3CApplicationOrigin];
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
- (void)testViewportAndElementCorners
|
|
351
|
+
{
|
|
352
|
+
NSDictionary *geometry = [self measurement];
|
|
353
|
+
NSArray<NSNumber *> *bounds = geometry[@"canvasBounds"];
|
|
354
|
+
NSArray<NSNumber *> *frame = geometry[@"canvasWindowRect"];
|
|
355
|
+
CGFloat width = bounds[0].doubleValue, height = bounds[1].doubleValue;
|
|
356
|
+
for (NSNumber *x in @[@40, @(width - 40)]) {
|
|
357
|
+
for (NSNumber *y in @[@40, @(height - 40)]) {
|
|
358
|
+
CGPoint point = CGPointMake(x.doubleValue, y.doubleValue);
|
|
359
|
+
[self performMoves:@[[self moveFrom:@"viewport"
|
|
360
|
+
x:frame[0].doubleValue + point.x y:frame[1].doubleValue + point.y duration:0]]
|
|
361
|
+
expectedEnd:point];
|
|
362
|
+
[self performMoves:@[[self moveFrom:self.canvas x:point.x - width / 2 y:point.y - height / 2 duration:0]]
|
|
363
|
+
expectedEnd:point];
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
- (void)testElementHitpoint
|
|
369
|
+
{
|
|
370
|
+
NSArray<NSNumber *> *bounds = [self measurement][@"canvasBounds"];
|
|
371
|
+
[self performMoves:@[@{@"type": @"pointerMove", @"origin": self.canvas, @"duration": @0}]
|
|
372
|
+
expectedEnd:CGPointMake(bounds[0].doubleValue / 2, bounds[1].doubleValue / 2)];
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
- (void)testPointerRelativeMovesPreserveOriginSpace
|
|
376
|
+
{
|
|
377
|
+
NSDictionary *geometry = [self measurement];
|
|
378
|
+
NSArray<NSNumber *> *bounds = geometry[@"canvasBounds"];
|
|
379
|
+
NSArray<NSNumber *> *frame = geometry[@"canvasWindowRect"];
|
|
380
|
+
CGPoint center = CGPointMake(bounds[0].doubleValue / 2, bounds[1].doubleValue / 2);
|
|
381
|
+
for (NSDictionary *start in @[
|
|
382
|
+
[self moveFrom:self.canvas x:0 y:0 duration:0],
|
|
383
|
+
[self moveFrom:@"viewport" x:frame[0].doubleValue + center.x y:frame[1].doubleValue + center.y duration:0],
|
|
384
|
+
]) {
|
|
385
|
+
[self performMoves:@[start,
|
|
386
|
+
[self moveFrom:@"pointer" x:0 y:-40 duration:200],
|
|
387
|
+
[self moveFrom:@"pointer" x:0 y:-40 duration:200]]
|
|
388
|
+
expectedEnd:CGPointMake(center.x, center.y - 80)];
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
- (void)testMixedOriginsWithinOneTouch
|
|
393
|
+
{
|
|
394
|
+
NSDictionary *geometry = [self measurement];
|
|
395
|
+
NSArray<NSNumber *> *bounds = geometry[@"canvasBounds"];
|
|
396
|
+
NSArray<NSNumber *> *frame = geometry[@"canvasWindowRect"];
|
|
397
|
+
CGPoint center = CGPointMake(bounds[0].doubleValue / 2, bounds[1].doubleValue / 2);
|
|
398
|
+
for (NSNumber *startAtElement in @[@YES, @NO]) {
|
|
399
|
+
NSDictionary *start = startAtElement.boolValue
|
|
400
|
+
? [self moveFrom:self.canvas x:0 y:0 duration:0]
|
|
401
|
+
: [self moveFrom:@"viewport" x:frame[0].doubleValue + center.x y:frame[1].doubleValue + center.y duration:0];
|
|
402
|
+
NSDictionary *next = startAtElement.boolValue
|
|
403
|
+
? [self moveFrom:@"viewport" x:frame[0].doubleValue + center.x y:frame[1].doubleValue + center.y - 40 duration:200]
|
|
404
|
+
: [self moveFrom:self.canvas x:0 y:-40 duration:200];
|
|
405
|
+
[self performMoves:@[start, next, [self moveFrom:@"pointer" x:0 y:-40 duration:200]]
|
|
406
|
+
expectedEnd:CGPointMake(center.x, center.y - 80)];
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
- (void)testScrollRemainsInForegroundAndMovesContent
|
|
411
|
+
{
|
|
412
|
+
[self.testedApplication.buttons[@"Scroll"] tap];
|
|
413
|
+
XCUIElement *table = self.testedApplication.tables[@"probe-table"];
|
|
414
|
+
CGFloat before = [[self measurement][@"scrollY"] doubleValue];
|
|
415
|
+
// Exceeds the per-drag limit, exercising multiple drags in one coordinate space.
|
|
416
|
+
[table fb_scrollDownByNormalizedDistance:1.0];
|
|
417
|
+
XCTAssertEqual(self.testedApplication.state, XCUIApplicationStateRunningForeground);
|
|
418
|
+
CGFloat after = [[self measurement][@"scrollY"] doubleValue];
|
|
419
|
+
XCTAssertGreaterThan(after, before + 10);
|
|
420
|
+
[table fb_scrollUpByNormalizedDistance:0.5];
|
|
421
|
+
XCTAssertLessThan([[self measurement][@"scrollY"] doubleValue], after - 10);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
- (void)testDirectionalScrollRejectsUnavailableDisplay
|
|
425
|
+
{
|
|
426
|
+
[self.testedApplication.buttons[@"Scroll"] tap];
|
|
427
|
+
XCUIElement *table = self.testedApplication.tables[@"probe-table"];
|
|
428
|
+
CGFloat before = [[self measurement][@"scrollY"] doubleValue];
|
|
429
|
+
NSNumber *displayId = FBConfiguration.sharedInstance.currentDisplayId;
|
|
430
|
+
@try {
|
|
431
|
+
FBConfiguration.sharedInstance.currentDisplayId = @(-1);
|
|
432
|
+
XCTAssertThrowsSpecificNamed([table fb_scrollDownByNormalizedDistance:0.5], NSException, FBInvalidArgumentException);
|
|
433
|
+
XCTAssertThrowsSpecificNamed([table fb_scrollUpByNormalizedDistance:0.5], NSException, FBInvalidArgumentException);
|
|
434
|
+
XCTAssertThrowsSpecificNamed([table fb_scrollLeftByNormalizedDistance:0.5], NSException, FBInvalidArgumentException);
|
|
435
|
+
XCTAssertThrowsSpecificNamed([table fb_scrollRightByNormalizedDistance:0.5], NSException, FBInvalidArgumentException);
|
|
436
|
+
} @finally {
|
|
437
|
+
FBConfiguration.sharedInstance.currentDisplayId = displayId;
|
|
438
|
+
}
|
|
439
|
+
XCTAssertEqualWithAccuracy([[self measurement][@"scrollY"] doubleValue], before, 1);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
- (void)testScrollToElementRejectsUnavailableDisplay
|
|
443
|
+
{
|
|
444
|
+
[self.testedApplication.buttons[@"Scroll"] tap];
|
|
445
|
+
XCUIElement *cell = self.testedApplication.cells[@"probe-row-13"];
|
|
446
|
+
NSNumber *displayId = FBConfiguration.sharedInstance.currentDisplayId;
|
|
447
|
+
@try {
|
|
448
|
+
FBConfiguration.sharedInstance.currentDisplayId = @(-1);
|
|
449
|
+
NSError *error = nil;
|
|
450
|
+
XCTAssertFalse([cell fb_scrollToVisibleWithError:&error]);
|
|
451
|
+
XCTAssertNotNil(error);
|
|
452
|
+
} @finally {
|
|
453
|
+
FBConfiguration.sharedInstance.currentDisplayId = displayId;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
- (void)testScrollToElementKeepsTheWholeCellVisible
|
|
458
|
+
{
|
|
459
|
+
[self.testedApplication.buttons[@"Scroll"] tap];
|
|
460
|
+
XCUIElement *cell = self.testedApplication.cells[@"probe-row-13"];
|
|
461
|
+
NSError *error = nil;
|
|
462
|
+
XCTAssertTrue([cell fb_scrollToVisibleWithError:&error], @"%@", error);
|
|
463
|
+
XCTAssertEqual(self.testedApplication.state, XCUIApplicationStateRunningForeground);
|
|
464
|
+
NSArray<NSNumber *> *frame = [self measurement][@"canvasWindowRect"];
|
|
465
|
+
XCTAssertTrue(cell.hittable);
|
|
466
|
+
XCTAssertGreaterThanOrEqual(CGRectGetMinY(cell.frame), frame[1].doubleValue - 1);
|
|
467
|
+
XCTAssertLessThanOrEqual(CGRectGetMaxY(cell.frame), frame[1].doubleValue + frame[3].doubleValue + 1);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
- (void)verifyLandscapeScrolling:(UIDeviceOrientation)orientation
|
|
471
|
+
{
|
|
472
|
+
XCTSkipIf(XCUIDevice.sharedDevice.fb_canAttemptSimulatedHingeAngleInjection,
|
|
473
|
+
@"Foldable simulator orientations are covered by testGesturesAcrossFoldStates");
|
|
474
|
+
UIDeviceOrientation previousOrientation = XCUIDevice.sharedDevice.orientation;
|
|
475
|
+
[self addTeardownBlock:^{
|
|
476
|
+
[XCUIDevice.sharedDevice fb_setDeviceInterfaceOrientation:previousOrientation];
|
|
477
|
+
}];
|
|
478
|
+
XCTAssertTrue([[XCUIDevice sharedDevice] fb_setDeviceInterfaceOrientation:orientation]);
|
|
479
|
+
[self.testedApplication.buttons[@"Scroll"] tap];
|
|
480
|
+
XCUIElement *table = self.testedApplication.tables[@"probe-table"];
|
|
481
|
+
// Choose a nearby offscreen row: the phone's landscape table is intentionally
|
|
482
|
+
// short, and this test checks coordinates rather than the 25-scroll limit.
|
|
483
|
+
NSUInteger row = (NSUInteger)ceil(table.frame.size.height / 44) + 2;
|
|
484
|
+
XCUIElement *cell = self.testedApplication.cells[[NSString stringWithFormat:@"probe-row-%lu", (unsigned long)row]];
|
|
485
|
+
NSError *error = nil;
|
|
486
|
+
XCTAssertTrue([cell fb_scrollToVisibleWithError:&error], @"%@", error);
|
|
487
|
+
XCTAssertEqual(self.testedApplication.state, XCUIApplicationStateRunningForeground);
|
|
488
|
+
XCTAssertGreaterThan([[self measurement][@"scrollY"] doubleValue], 10);
|
|
489
|
+
XCTAssertTrue([[self measurement][@"fullyVisibleRows"] containsObject:@(row)]);
|
|
490
|
+
XCTAssertTrue(cell.hittable);
|
|
491
|
+
XCTAssertGreaterThanOrEqual(CGRectGetMinY(cell.frame), CGRectGetMinY(table.frame) - 1);
|
|
492
|
+
XCTAssertLessThanOrEqual(CGRectGetMaxY(cell.frame), CGRectGetMaxY(table.frame) + 1);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
- (void)testScrollToElementInLandscapeLeft
|
|
496
|
+
{
|
|
497
|
+
[self verifyLandscapeScrolling:UIDeviceOrientationLandscapeLeft];
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
- (void)testScrollToElementInLandscapeRight
|
|
501
|
+
{
|
|
502
|
+
[self verifyLandscapeScrolling:UIDeviceOrientationLandscapeRight];
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
- (void)testGesturesAcrossFoldStates
|
|
506
|
+
{
|
|
507
|
+
XCUIDevice *device = XCUIDevice.sharedDevice;
|
|
508
|
+
XCTSkipIf(!device.fb_canAttemptSimulatedHingeAngleInjection, @"Requires a foldable simulator");
|
|
509
|
+
NSError *error = nil;
|
|
510
|
+
NSNumber *originalAngle = [device fb_getSimulatedHingeAngle:&error];
|
|
511
|
+
XCTAssertNotNil(originalAngle, @"%@", error);
|
|
512
|
+
[self addTeardownBlock:^{
|
|
513
|
+
if (nil != originalAngle) {
|
|
514
|
+
[device fb_setSimulatedHingeAngle:originalAngle.doubleValue error:nil];
|
|
515
|
+
}
|
|
516
|
+
}];
|
|
517
|
+
for (NSNumber *angle in @[@0, @90, @180, @0]) {
|
|
518
|
+
[self.testedApplication terminate];
|
|
519
|
+
XCTAssertTrue([device fb_setSimulatedHingeAngle:angle.doubleValue error:&error], @"%@", error);
|
|
520
|
+
[self launchApplication];
|
|
521
|
+
[self.testedApplication.buttons[@"coordinate-probe"] tap];
|
|
522
|
+
self.canvas = self.testedApplication.otherElements[@"coordinate-canvas"];
|
|
523
|
+
[self selectFixtureDisplay];
|
|
524
|
+
NSDictionary *geometry = [self measurement];
|
|
525
|
+
NSArray<NSNumber *> *bounds = geometry[@"canvasBounds"];
|
|
526
|
+
NSArray<NSNumber *> *frame = geometry[@"canvasWindowRect"];
|
|
527
|
+
CGPoint center = CGPointMake(bounds[0].doubleValue / 2, bounds[1].doubleValue / 2);
|
|
528
|
+
[self performMoves:@[[self moveFrom:@"viewport" x:frame[0].doubleValue + center.x
|
|
529
|
+
y:frame[1].doubleValue + center.y duration:0],
|
|
530
|
+
[self moveFrom:self.canvas x:0 y:-20 duration:200],
|
|
531
|
+
[self moveFrom:@"pointer" x:0 y:-20 duration:200]]
|
|
532
|
+
expectedEnd:CGPointMake(center.x, center.y - 40)];
|
|
533
|
+
[self.testedApplication.buttons[@"Scroll"] tap];
|
|
534
|
+
XCUIElement *table = self.testedApplication.tables[@"probe-table"];
|
|
535
|
+
[table fb_scrollDownByNormalizedDistance:1.0];
|
|
536
|
+
XCTAssertGreaterThan([[self measurement][@"scrollY"] doubleValue], 10);
|
|
537
|
+
XCTAssertEqual(self.testedApplication.state, XCUIApplicationStateRunningForeground);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
@end
|