@devicelab/maestro-runner-darwin-arm64 1.1.28 → 1.1.30

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 (118) hide show
  1. package/README.md +30 -3
  2. package/bin/maestro-runner +0 -0
  3. package/drivers/android/devicelab-android-driver-test.apk +0 -0
  4. package/drivers/android/devicelab-android-driver.apk +0 -0
  5. package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/ATTRIBUTION.md +16 -0
  6. package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests+Lifecycle.swift +33 -0
  7. package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests+Snapshot.swift +195 -88
  8. package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests.swift +3 -0
  9. package/drivers/ios/WebDriverAgent/CHANGELOG.md +202 -0
  10. package/drivers/ios/WebDriverAgent/README.md +18 -9
  11. package/drivers/ios/WebDriverAgent/Scripts/ci/build-real.sh +6 -10
  12. package/drivers/ios/WebDriverAgent/Scripts/fetch-prebuilt-wda.mjs +3 -2
  13. package/drivers/ios/WebDriverAgent/WebDriverAgent.xcodeproj/project.pbxproj +119 -0
  14. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCAXClient_iOS+FBSnapshotReqParams.m +8 -0
  15. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.h +3 -0
  16. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m +30 -1
  17. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.h +53 -0
  18. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.m +124 -0
  19. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h +6 -0
  20. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m +100 -23
  21. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBHinge.h +41 -0
  22. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBHinge.m +215 -0
  23. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBRotation.m +8 -3
  24. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBFind.m +20 -7
  25. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBScrolling.m +110 -50
  26. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBTyping.h +7 -0
  27. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBTyping.m +20 -0
  28. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.m +14 -11
  29. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBCustomCommands.m +86 -10
  30. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBElementCommands.m +57 -7
  31. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBSessionCommands.m +3 -2
  32. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Info.plist +2 -2
  33. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBHTTPServer.m +39 -16
  34. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBScreenRecordingRequest.m +7 -2
  35. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBSession.h +22 -0
  36. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBSession.m +78 -12
  37. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBTCPSocket.h +5 -1
  38. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBTCPSocket.m +10 -1
  39. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBWebServer.m +4 -1
  40. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBActiveAppDetectionPoint.h +2 -2
  41. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBActiveAppDetectionPoint.m +20 -0
  42. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBConfiguration.h +8 -0
  43. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBConfiguration.m +2 -0
  44. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBHIDOperations.h +41 -0
  45. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBHIDOperations.m +244 -0
  46. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBImageProcessor.m +3 -1
  47. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBMathUtils.h +11 -0
  48. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBMathUtils.m +38 -0
  49. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBMjpegServer.m +5 -3
  50. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBScreen.h +17 -0
  51. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBScreen.m +76 -1
  52. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBScreenshot.h +2 -2
  53. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBScreenshot.m +6 -2
  54. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBSettings.h +2 -0
  55. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBSettings.m +2 -0
  56. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBSettingsHandler.m +48 -0
  57. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.m +121 -13
  58. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBXPath.m +181 -128
  59. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/LRUCache/LRUCache.m +8 -5
  60. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/LRUCache/LRUCacheNode.h +1 -1
  61. package/drivers/ios/WebDriverAgent/WebDriverAgentLib/WebDriverAgentLib.h +1 -0
  62. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/COORDINATE_PROBE.md +3 -1
  63. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/FBCoordinateProbeViewController.m +18 -0
  64. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/SceneDelegate.m +33 -0
  65. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/ViewController.m +26 -7
  66. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBConfigurationTests.m +52 -0
  67. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBDisplayGestureTests.m +541 -0
  68. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBElementSwipingTests.m +95 -0
  69. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBImageProcessorTests.m +34 -0
  70. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBScreenTests.m +392 -0
  71. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBSessionIntegrationTests.m +97 -0
  72. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBTypingTest.m +33 -0
  73. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBW3CTouchActionsIntegrationTests.m +126 -0
  74. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIApplicationHelperTests.m +111 -0
  75. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m +97 -0
  76. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIDeviceRotationTests.m +53 -0
  77. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIElementFBFindTests.m +113 -0
  78. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests_tvOS/FBTVFocusIntegrationTests.m +21 -0
  79. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests_watchOS/WDAAppLifecycleIntegrationTests.swift +12 -0
  80. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBCustomCommandsTests.m +61 -0
  81. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBElementUtilitiesTests.m +40 -0
  82. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBHTTPServerSessionTests.m +22 -4
  83. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBHTTPServerTests.m +199 -5
  84. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBLRUCacheTests.m +34 -0
  85. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBMathUtilsTests.m +45 -0
  86. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBSessionTests.m +201 -0
  87. package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBXPathTests.m +101 -0
  88. package/drivers/ios/WebDriverAgent/docs/hinge-angle.md +54 -0
  89. package/drivers/ios/WebDriverAgent/docs/multiple-displays.md +168 -0
  90. package/drivers/ios/WebDriverAgent/lib/webdriveragent.ts +1 -2
  91. package/drivers/ios/WebDriverAgent/lib/xcodebuild.ts +58 -10
  92. package/drivers/ios/WebDriverAgent/package.json +8 -15
  93. package/drivers/ios/WebDriverAgent/test/functional/helpers/display-settings.ts +49 -0
  94. package/drivers/ios/WebDriverAgent/test/functional/helpers/simulator.ts +7 -7
  95. package/drivers/ios/WebDriverAgent/test/functional/webdriveragent-e2e.spec.ts +4 -0
  96. package/drivers/ios/WebDriverAgent/test/unit/xcodebuild.spec.ts +230 -0
  97. package/drivers/ios/WebDriverAgent/tsconfig.json +2 -1
  98. package/drivers/ios/devicelab-ios-agent/README.md +12 -0
  99. package/drivers/ios/devicelab-ios-agent/device/DevicelabIOSAgent.app/DevicelabIOSAgent +0 -0
  100. package/drivers/ios/devicelab-ios-agent/device/DevicelabIOSAgentUITests-Runner.app/PlugIns/DevicelabIOSAgentUITests.xctest/DevicelabIOSAgentUITests +0 -0
  101. package/drivers/ios/devicelab-ios-agent/device/manifest.json +3 -3
  102. package/drivers/ios/devicelab-ios-agent/simulator/DevicelabIOSAgent.app/DevicelabIOSAgent +0 -0
  103. package/drivers/ios/devicelab-ios-agent/simulator/DevicelabIOSAgentUITests-Runner.app/PlugIns/DevicelabIOSAgentUITests.xctest/DevicelabIOSAgentUITests +0 -0
  104. package/drivers/ios/devicelab-ios-agent/simulator/manifest.json +3 -3
  105. package/package.json +1 -1
  106. package/drivers/ios/WebDriverAgent/.editorconfig +0 -11
  107. package/drivers/ios/WebDriverAgent/.github/ISSUE_TEMPLATE.md +0 -4
  108. package/drivers/ios/WebDriverAgent/.github/actions/xcode-setup/action.yml +0 -32
  109. package/drivers/ios/WebDriverAgent/.github/dependabot.yml +0 -22
  110. package/drivers/ios/WebDriverAgent/.github/workflows/functional-test.yml +0 -103
  111. package/drivers/ios/WebDriverAgent/.github/workflows/pr-title.yml +0 -10
  112. package/drivers/ios/WebDriverAgent/.github/workflows/publish.js.yml +0 -112
  113. package/drivers/ios/WebDriverAgent/.github/workflows/unit-test.yml +0 -32
  114. package/drivers/ios/WebDriverAgent/.github/workflows/wda-package.yml +0 -114
  115. package/drivers/ios/WebDriverAgent/.github/workflows/wda-tests.yml +0 -246
  116. package/drivers/ios/WebDriverAgent/oxfmt.config.mjs +0 -6
  117. package/drivers/ios/WebDriverAgent/oxlint.config.mjs +0 -6
  118. package/drivers/ios/WebDriverAgent/release.config.mjs +0 -14
@@ -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
@@ -13,6 +13,10 @@
13
13
  #import "XCUIElement+FBWebDriverAttributes.h"
14
14
  #import "FBXCodeCompatibility.h"
15
15
  #import "XCUIElement+FBSwiping.h"
16
+ #import "FBExceptions.h"
17
+ #import "XCUIApplication+FBTouchAction.h"
18
+ #import "XCSynthesizedEventRecord.h"
19
+ #import "XCUICoordinate.h"
16
20
 
17
21
  @interface FBElementSwipingTests : FBIntegrationTestCase
18
22
  @property (nonatomic, strong) XCUIElement *scrollView;
@@ -125,3 +129,94 @@
125
129
  }
126
130
 
127
131
  @end
132
+
133
+ /**
134
+ Stands in for a coordinate whose application did not deliver a usable snapshot (as happens for a
135
+ busy application right after an app switch), which XCTest resolves to an infinite screen point.
136
+ */
137
+ @interface FBUnresolvableCoordinate : XCUICoordinate
138
+ @end
139
+
140
+ @implementation FBUnresolvableCoordinate
141
+
142
+ - (CGPoint)screenPoint
143
+ {
144
+ return CGPointMake(INFINITY, INFINITY);
145
+ }
146
+
147
+ @end
148
+
149
+ @interface FBPressAndDragTests : FBIntegrationTestCase
150
+ @end
151
+
152
+ @implementation FBPressAndDragTests
153
+
154
+ - (void)setUp
155
+ {
156
+ [super setUp];
157
+ [self resetOrientation];
158
+ [self launchApplication];
159
+ [self goToScrollPageWithCells:YES];
160
+ }
161
+
162
+ - (void)testPressAndDragBetweenApplicationCoordinates
163
+ {
164
+ XCUICoordinate *start = [self.testedApplication coordinateWithNormalizedOffset:CGVectorMake(0.5, 0.7)];
165
+ XCUICoordinate *end = [self.testedApplication coordinateWithNormalizedOffset:CGVectorMake(0.5, 0.3)];
166
+ NSError *error;
167
+ XCTAssertTrue([self.testedApplication fb_pressAtCoordinate:start
168
+ forDuration:0.3
169
+ thenDragToCoordinate:end
170
+ withVelocity:XCUIGestureVelocityDefault
171
+ thenHoldForDuration:0
172
+ error:&error]);
173
+ XCTAssertNil(error);
174
+ FBAssertInvisibleCell(@"0");
175
+ }
176
+
177
+ // https://github.com/appium/WebDriverAgent/issues/1056
178
+ - (void)testPressAndDragFailsCleanlyForUnresolvableCoordinates
179
+ {
180
+ XCUICoordinate *start = [[FBUnresolvableCoordinate alloc] initWithElement:self.testedApplication
181
+ normalizedOffset:CGVectorMake(0.5, 0.7)];
182
+ XCUICoordinate *end = [self.testedApplication coordinateWithNormalizedOffset:CGVectorMake(0.5, 0.3)];
183
+ NSError *error;
184
+ XCTAssertFalse([self.testedApplication fb_pressAtCoordinate:start
185
+ forDuration:0.3
186
+ thenDragToCoordinate:end
187
+ withVelocity:XCUIGestureVelocityDefault
188
+ thenHoldForDuration:0
189
+ error:&error]);
190
+ XCTAssertTrue([error.localizedDescription containsString:@"Cannot resolve the on-screen position"]);
191
+ FBAssertVisibleCell(@"0");
192
+ }
193
+
194
+ - (void)testPressDragEventKeepsDisplayIDAndTiming
195
+ {
196
+ XCSynthesizedEventRecord *event = [XCUIApplication fb_pressDragEventFromPoint:CGPointMake(100, 500)
197
+ forDuration:0.3
198
+ toPoint:CGPointMake(100, 100)
199
+ pointsPerSecond:500
200
+ thenHoldForDuration:0.1
201
+ displayID:42
202
+ interfaceOrientation:UIInterfaceOrientationPortrait];
203
+ XCTAssertEqual(event.displayID, 42ULL);
204
+ XCTAssertEqual(event.eventPaths.count, 1U);
205
+ // 0.3 s press + 400 pt / 500 pt per s drag + 0.1 s hold
206
+ XCTAssertEqualWithAccuracy(event.maximumOffset, 1.2, 0.0001);
207
+ }
208
+
209
+ - (void)testPressAndDragRejectsInvalidVelocity
210
+ {
211
+ XCUICoordinate *start = [self.testedApplication coordinateWithNormalizedOffset:CGVectorMake(0.5, 0.7)];
212
+ XCUICoordinate *end = [self.testedApplication coordinateWithNormalizedOffset:CGVectorMake(0.5, 0.3)];
213
+ XCTAssertThrowsSpecificNamed([self.testedApplication fb_pressAtCoordinate:start
214
+ forDuration:0
215
+ thenDragToCoordinate:end
216
+ withVelocity:0
217
+ thenHoldForDuration:0
218
+ error:nil],
219
+ NSException, FBInvalidArgumentException);
220
+ }
221
+
222
+ @end
@@ -9,6 +9,7 @@
9
9
  #import <XCTest/XCTest.h>
10
10
 
11
11
  #import "FBImageProcessor.h"
12
+ @import UniformTypeIdentifiers;
12
13
  #import "FBIntegrationTestCase.h"
13
14
 
14
15
 
@@ -80,3 +81,36 @@
80
81
 
81
82
  @end
82
83
 
84
+
85
+
86
+ // Synthetic images keep these renderer regressions independent of the app's screen.
87
+ @interface FBImageRendererScalingTests : XCTestCase
88
+ @end
89
+
90
+ @implementation FBImageRendererScalingTests
91
+
92
+ - (void)testOrientationFixDoesNotApplyScalingTwice
93
+ {
94
+ for (NSNumber *sourceScale in @[@1, @2, @3]) {
95
+ UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat new];
96
+ format.scale = sourceScale.doubleValue;
97
+ UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc]
98
+ initWithSize:CGSizeMake(120, 80) format:format];
99
+ NSData *data = [renderer PNGDataWithActions:^(UIGraphicsImageRendererContext *context) {
100
+ [UIColor.redColor setFill];
101
+ [context fillRect:CGRectMake(0, 0, 120, 80)];
102
+ }];
103
+ FBImageProcessor *processor = [FBImageProcessor new];
104
+ NSData *scaled = [processor scaledImageWithData:data
105
+ uti:UTTypePNG
106
+ scalingFactor:0.5
107
+ compressionQuality:1.0
108
+ error:nil];
109
+ UIImage *image = [UIImage imageWithData:scaled];
110
+ XCTAssertNotNil(image);
111
+ XCTAssertEqual(CGImageGetWidth(image.CGImage), (size_t)(60 * sourceScale.integerValue));
112
+ XCTAssertEqual(CGImageGetHeight(image.CGImage), (size_t)(40 * sourceScale.integerValue));
113
+ }
114
+ }
115
+
116
+ @end