@devicelab/maestro-runner-darwin-arm64 1.1.29 → 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.
- package/README.md +15 -4
- package/bin/maestro-runner +0 -0
- package/drivers/android/devicelab-android-driver-test.apk +0 -0
- package/drivers/android/devicelab-android-driver.apk +0 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/ATTRIBUTION.md +16 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests+Lifecycle.swift +33 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests+Snapshot.swift +195 -88
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests.swift +3 -0
- package/drivers/ios/WebDriverAgent/CHANGELOG.md +202 -0
- package/drivers/ios/WebDriverAgent/README.md +18 -9
- package/drivers/ios/WebDriverAgent/Scripts/ci/build-real.sh +6 -10
- package/drivers/ios/WebDriverAgent/Scripts/fetch-prebuilt-wda.mjs +3 -2
- package/drivers/ios/WebDriverAgent/WebDriverAgent.xcodeproj/project.pbxproj +119 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCAXClient_iOS+FBSnapshotReqParams.m +8 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.h +3 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m +30 -1
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.h +53 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.m +124 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h +6 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m +100 -23
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBHinge.h +41 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBHinge.m +215 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBRotation.m +8 -3
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBFind.m +20 -7
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBScrolling.m +110 -50
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBTyping.h +7 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBTyping.m +20 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.m +14 -11
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBCustomCommands.m +86 -10
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBElementCommands.m +57 -7
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBSessionCommands.m +3 -2
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Info.plist +2 -2
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBHTTPServer.m +39 -16
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBScreenRecordingRequest.m +7 -2
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBSession.h +22 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBSession.m +78 -12
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBTCPSocket.h +5 -1
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBTCPSocket.m +10 -1
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBWebServer.m +4 -1
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBActiveAppDetectionPoint.h +2 -2
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBActiveAppDetectionPoint.m +20 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBConfiguration.h +8 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBConfiguration.m +2 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBHIDOperations.h +41 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBHIDOperations.m +244 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBImageProcessor.m +3 -1
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBMathUtils.h +11 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBMathUtils.m +38 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBMjpegServer.m +5 -3
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBScreen.h +17 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBScreen.m +76 -1
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBScreenshot.h +2 -2
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBScreenshot.m +6 -2
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBSettings.h +2 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBSettings.m +2 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBSettingsHandler.m +48 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.m +121 -13
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBXPath.m +181 -128
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/LRUCache/LRUCache.m +8 -5
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/LRUCache/LRUCacheNode.h +1 -1
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/WebDriverAgentLib.h +1 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/COORDINATE_PROBE.md +3 -1
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/FBCoordinateProbeViewController.m +18 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/SceneDelegate.m +33 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/ViewController.m +26 -7
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBConfigurationTests.m +52 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBDisplayGestureTests.m +541 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBElementSwipingTests.m +95 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBImageProcessorTests.m +34 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBScreenTests.m +392 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBSessionIntegrationTests.m +97 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBTypingTest.m +33 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBW3CTouchActionsIntegrationTests.m +126 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIApplicationHelperTests.m +111 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m +97 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIDeviceRotationTests.m +53 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIElementFBFindTests.m +113 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests_tvOS/FBTVFocusIntegrationTests.m +21 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests_watchOS/WDAAppLifecycleIntegrationTests.swift +12 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBCustomCommandsTests.m +61 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBElementUtilitiesTests.m +40 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBHTTPServerSessionTests.m +22 -4
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBHTTPServerTests.m +199 -5
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBLRUCacheTests.m +34 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBMathUtilsTests.m +45 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBSessionTests.m +201 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBXPathTests.m +101 -0
- package/drivers/ios/WebDriverAgent/docs/hinge-angle.md +54 -0
- package/drivers/ios/WebDriverAgent/docs/multiple-displays.md +168 -0
- package/drivers/ios/WebDriverAgent/lib/webdriveragent.ts +1 -2
- package/drivers/ios/WebDriverAgent/lib/xcodebuild.ts +58 -10
- package/drivers/ios/WebDriverAgent/package.json +8 -15
- package/drivers/ios/WebDriverAgent/test/functional/helpers/display-settings.ts +49 -0
- package/drivers/ios/WebDriverAgent/test/functional/helpers/simulator.ts +7 -7
- package/drivers/ios/WebDriverAgent/test/functional/webdriveragent-e2e.spec.ts +4 -0
- package/drivers/ios/WebDriverAgent/test/unit/xcodebuild.spec.ts +230 -0
- package/drivers/ios/WebDriverAgent/tsconfig.json +2 -1
- package/drivers/ios/devicelab-ios-agent/README.md +12 -0
- package/drivers/ios/devicelab-ios-agent/device/DevicelabIOSAgent.app/DevicelabIOSAgent +0 -0
- package/drivers/ios/devicelab-ios-agent/device/DevicelabIOSAgentUITests-Runner.app/PlugIns/DevicelabIOSAgentUITests.xctest/DevicelabIOSAgentUITests +0 -0
- package/drivers/ios/devicelab-ios-agent/device/manifest.json +3 -3
- package/drivers/ios/devicelab-ios-agent/simulator/DevicelabIOSAgent.app/DevicelabIOSAgent +0 -0
- package/drivers/ios/devicelab-ios-agent/simulator/DevicelabIOSAgentUITests-Runner.app/PlugIns/DevicelabIOSAgentUITests.xctest/DevicelabIOSAgentUITests +0 -0
- package/drivers/ios/devicelab-ios-agent/simulator/manifest.json +3 -3
- package/package.json +1 -1
- package/drivers/ios/WebDriverAgent/.editorconfig +0 -11
- package/drivers/ios/WebDriverAgent/.github/ISSUE_TEMPLATE.md +0 -4
- package/drivers/ios/WebDriverAgent/.github/actions/xcode-setup/action.yml +0 -32
- package/drivers/ios/WebDriverAgent/.github/dependabot.yml +0 -22
- package/drivers/ios/WebDriverAgent/.github/workflows/functional-test.yml +0 -103
- package/drivers/ios/WebDriverAgent/.github/workflows/pr-title.yml +0 -10
- package/drivers/ios/WebDriverAgent/.github/workflows/publish.js.yml +0 -112
- package/drivers/ios/WebDriverAgent/.github/workflows/unit-test.yml +0 -32
- package/drivers/ios/WebDriverAgent/.github/workflows/wda-package.yml +0 -114
- package/drivers/ios/WebDriverAgent/.github/workflows/wda-tests.yml +0 -246
- package/drivers/ios/WebDriverAgent/oxfmt.config.mjs +0 -6
- package/drivers/ios/WebDriverAgent/oxlint.config.mjs +0 -6
- package/drivers/ios/WebDriverAgent/release.config.mjs +0 -14
package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.m
CHANGED
|
@@ -9,17 +9,67 @@
|
|
|
9
9
|
|
|
10
10
|
#import "XCUIApplication+FBTouchAction.h"
|
|
11
11
|
|
|
12
|
+
#import <dlfcn.h>
|
|
13
|
+
|
|
12
14
|
#import "FBBaseActionsSynthesizer.h"
|
|
13
15
|
#import "FBConfiguration.h"
|
|
16
|
+
#import "FBErrorBuilder.h"
|
|
14
17
|
#import "FBExceptions.h"
|
|
15
18
|
#import "FBLogger.h"
|
|
19
|
+
#import "FBMathUtils.h"
|
|
16
20
|
#import "FBRunLoopSpinner.h"
|
|
17
21
|
#import "FBW3CActionsSynthesizer.h"
|
|
18
22
|
#import "FBXCTestDaemonsProxy.h"
|
|
23
|
+
#import "XCPointerEventPath.h"
|
|
24
|
+
#import "XCSynthesizedEventRecord.h"
|
|
25
|
+
#import "XCUIElement.h"
|
|
19
26
|
#import "XCUIElement+FBUtilities.h"
|
|
20
27
|
|
|
21
28
|
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
22
29
|
|
|
30
|
+
/**
|
|
31
|
+
Maps a drag XCUIGestureVelocity to the speed XCTest drags at. XCTest calls the result "pixels per
|
|
32
|
+
second", but divides the distance between the two screen points (which are in points) by it.
|
|
33
|
+
Prefers XCTest's own (exported, but undeclared) mapping function, so any future change to its
|
|
34
|
+
presets is picked up; falls back to the values it returns now.
|
|
35
|
+
|
|
36
|
+
@throws FBInvalidArgumentException if the velocity is neither one of the XCUIGestureVelocity
|
|
37
|
+
presets nor a positive finite number
|
|
38
|
+
*/
|
|
39
|
+
static CGFloat FBPointsPerSecondForDragVelocity(XCUIGestureVelocity velocity)
|
|
40
|
+
{
|
|
41
|
+
BOOL isDefault = FBFloatFuzzyEqualToFloat(velocity, XCUIGestureVelocityDefault, 0);
|
|
42
|
+
BOOL isSlow = FBFloatFuzzyEqualToFloat(velocity, XCUIGestureVelocitySlow, 0);
|
|
43
|
+
BOOL isFast = FBFloatFuzzyEqualToFloat(velocity, XCUIGestureVelocityFast, 0);
|
|
44
|
+
if (!isDefault && !isSlow && !isFast && !(isfinite(velocity) && velocity > 0)) {
|
|
45
|
+
NSString *reason = [NSString stringWithFormat:@"%@ is an invalid drag velocity. It must be greater than 0", @(velocity)];
|
|
46
|
+
@throw [NSException exceptionWithName:FBInvalidArgumentException reason:reason userInfo:nil];
|
|
47
|
+
}
|
|
48
|
+
static double (*xctestMapping)(double) = NULL;
|
|
49
|
+
static dispatch_once_t onceToken;
|
|
50
|
+
dispatch_once(&onceToken, ^{
|
|
51
|
+
xctestMapping = (double (*)(double))dlsym(RTLD_DEFAULT, "XCUIPixelsPerSecondForDragGestureVelocity");
|
|
52
|
+
if (NULL == xctestMapping) {
|
|
53
|
+
[FBLogger log:@"Could not find XCUIPixelsPerSecondForDragGestureVelocity. Using built-in drag velocity presets instead"];
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
if (NULL != xctestMapping) {
|
|
57
|
+
return (CGFloat)xctestMapping(velocity);
|
|
58
|
+
}
|
|
59
|
+
if (isSlow) {
|
|
60
|
+
return 250;
|
|
61
|
+
}
|
|
62
|
+
if (isFast) {
|
|
63
|
+
return 750;
|
|
64
|
+
}
|
|
65
|
+
return isDefault ? 500 : velocity;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static BOOL FBIsFinitePoint(CGPoint point)
|
|
69
|
+
{
|
|
70
|
+
return isfinite(point.x) && isfinite(point.y);
|
|
71
|
+
}
|
|
72
|
+
|
|
23
73
|
@implementation XCUIApplication (FBTouchAction)
|
|
24
74
|
|
|
25
75
|
+ (BOOL)handleEventSynthesWithError:(NSError *)error
|
|
@@ -65,6 +115,80 @@
|
|
|
65
115
|
return YES;
|
|
66
116
|
}
|
|
67
117
|
|
|
118
|
+
- (BOOL)fb_pressAtCoordinate:(XCUICoordinate *)startCoordinate
|
|
119
|
+
forDuration:(NSTimeInterval)pressDuration
|
|
120
|
+
thenDragToCoordinate:(XCUICoordinate *)endCoordinate
|
|
121
|
+
withVelocity:(XCUIGestureVelocity)velocity
|
|
122
|
+
thenHoldForDuration:(NSTimeInterval)holdDuration
|
|
123
|
+
error:(NSError **)error
|
|
124
|
+
{
|
|
125
|
+
CGFloat pointsPerSecond = FBPointsPerSecondForDragVelocity(velocity);
|
|
126
|
+
if (!isfinite(pressDuration) || pressDuration < 0 || !isfinite(holdDuration) || holdDuration < 0) {
|
|
127
|
+
NSString *reason = [NSString stringWithFormat:@"Drag press and hold durations must be non-negative numbers of seconds. Got %@ and %@", @(pressDuration), @(holdDuration)];
|
|
128
|
+
@throw [NSException exceptionWithName:FBInvalidArgumentException reason:reason userInfo:nil];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Resolved once, here, so the points validated below are exactly the ones sent. XCTest's own
|
|
132
|
+
// drag re-resolves them inside its event dispatch instead, after re-snapshotting the application.
|
|
133
|
+
CGPoint startPoint = startCoordinate.screenPoint;
|
|
134
|
+
CGPoint endPoint = endCoordinate.screenPoint;
|
|
135
|
+
if (!FBIsFinitePoint(startPoint) || !FBIsFinitePoint(endPoint)) {
|
|
136
|
+
return [[[FBErrorBuilder builder]
|
|
137
|
+
withDescriptionFormat:@"Cannot resolve the on-screen position of the drag gesture (from %@ to %@). "
|
|
138
|
+
"This usually means '%@' did not deliver a valid accessibility snapshot, e.g. because it is busy "
|
|
139
|
+
"or still transitioning after an application switch. Make sure it is idle and retry",
|
|
140
|
+
NSStringFromCGPoint(startPoint), NSStringFromCGPoint(endPoint), self.bundleID]
|
|
141
|
+
buildError:error];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Like XCTest, target the display of the element the start coordinate belongs to. displayID
|
|
145
|
+
// only reads the element's last snapshot, which resolving screenPoint above has just refreshed.
|
|
146
|
+
XCUIElement *referencedElement = startCoordinate.referencedElement;
|
|
147
|
+
long long displayID = [referencedElement respondsToSelector:@selector(displayID)]
|
|
148
|
+
? referencedElement.displayID
|
|
149
|
+
: 0;
|
|
150
|
+
XCSynthesizedEventRecord *event = [self.class fb_pressDragEventFromPoint:startPoint
|
|
151
|
+
forDuration:pressDuration
|
|
152
|
+
toPoint:endPoint
|
|
153
|
+
pointsPerSecond:pointsPerSecond
|
|
154
|
+
thenHoldForDuration:holdDuration
|
|
155
|
+
displayID:displayID
|
|
156
|
+
interfaceOrientation:self.interfaceOrientation];
|
|
157
|
+
if (![self fb_synthesizeEvent:event error:error]) {
|
|
158
|
+
return NO;
|
|
159
|
+
}
|
|
160
|
+
[self fb_waitUntilStableWithTimeout:FBConfiguration.sharedInstance.animationCoolOffTimeout];
|
|
161
|
+
return YES;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
+ (XCSynthesizedEventRecord *)fb_pressDragEventFromPoint:(CGPoint)startPoint
|
|
165
|
+
forDuration:(NSTimeInterval)pressDuration
|
|
166
|
+
toPoint:(CGPoint)endPoint
|
|
167
|
+
pointsPerSecond:(CGFloat)pointsPerSecond
|
|
168
|
+
thenHoldForDuration:(NSTimeInterval)holdDuration
|
|
169
|
+
displayID:(long long)displayID
|
|
170
|
+
interfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
|
|
171
|
+
{
|
|
172
|
+
// Mirrors XCTest's own press-hold-drag event builder
|
|
173
|
+
XCPointerEventPath *path = [[XCPointerEventPath alloc] initForTouchAtPoint:startPoint offset:0];
|
|
174
|
+
if (pressDuration > 0) {
|
|
175
|
+
[path moveToPoint:startPoint atOffset:pressDuration];
|
|
176
|
+
}
|
|
177
|
+
CGFloat distance = (CGFloat)hypot(endPoint.x - startPoint.x, endPoint.y - startPoint.y);
|
|
178
|
+
NSTimeInterval dragEndOffset = pressDuration + distance / pointsPerSecond;
|
|
179
|
+
[path moveToPoint:endPoint atOffset:dragEndOffset];
|
|
180
|
+
[path liftUpAtOffset:dragEndOffset + holdDuration];
|
|
181
|
+
|
|
182
|
+
NSString *name = @"Press, drag and hold";
|
|
183
|
+
XCSynthesizedEventRecord *event = [XCSynthesizedEventRecord instancesRespondToSelector:@selector(initWithName:displayID:interfaceOrientation:)]
|
|
184
|
+
? [[XCSynthesizedEventRecord alloc] initWithName:name
|
|
185
|
+
displayID:(unsigned long long)displayID
|
|
186
|
+
interfaceOrientation:interfaceOrientation]
|
|
187
|
+
: [[XCSynthesizedEventRecord alloc] initWithName:name interfaceOrientation:interfaceOrientation];
|
|
188
|
+
[event addPointerEventPath:path];
|
|
189
|
+
return event;
|
|
190
|
+
}
|
|
191
|
+
|
|
68
192
|
- (BOOL)fb_synthesizeEvent:(XCSynthesizedEventRecord *)event error:(NSError *__autoreleasing*)error
|
|
69
193
|
{
|
|
70
194
|
return [FBXCTestDaemonsProxy synthesizeEventWithRecord:event error:error];
|
|
@@ -233,4 +233,10 @@ typedef NS_ENUM(NSUInteger, FBUIInterfaceAppearance) {
|
|
|
233
233
|
|
|
234
234
|
@end
|
|
235
235
|
|
|
236
|
+
/**
|
|
237
|
+
Returns the map of lowercase button names to their XCTest enum values
|
|
238
|
+
supported on the current platform (cached after the first call).
|
|
239
|
+
*/
|
|
240
|
+
extern NSDictionary<NSString *, NSNumber *> *FBAvailableButtonNames(void);
|
|
241
|
+
|
|
236
242
|
NS_ASSUME_NONNULL_END
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
#include <notify.h>
|
|
14
14
|
#import <objc/runtime.h>
|
|
15
15
|
|
|
16
|
+
#import "FBHIDOperations.h"
|
|
16
17
|
#import "FBErrorBuilder.h"
|
|
17
18
|
#import "FBImageUtils.h"
|
|
18
19
|
#import "FBMacros.h"
|
|
@@ -22,12 +23,17 @@
|
|
|
22
23
|
#import "FBXCodeCompatibility.h"
|
|
23
24
|
#import "FBXCTestDaemonsProxy.h"
|
|
24
25
|
#import "XCUIDevice.h"
|
|
26
|
+
#import "XCUIApplication.h"
|
|
25
27
|
|
|
26
28
|
static const NSTimeInterval FBHomeButtonCoolOffTime = 1.;
|
|
27
29
|
static const NSTimeInterval FBScreenLockTimeout = 5.;
|
|
28
30
|
|
|
31
|
+
#if TARGET_OS_WATCH
|
|
32
|
+
static NSString *const FBClockFaceBundleId = @"com.apple.clockface";
|
|
33
|
+
#endif
|
|
34
|
+
|
|
29
35
|
#if TARGET_OS_TV
|
|
30
|
-
NSDictionary<NSString *, NSNumber *> *
|
|
36
|
+
NSDictionary<NSString *, NSNumber *> *FBAvailableButtonNames(void) {
|
|
31
37
|
static dispatch_once_t onceToken;
|
|
32
38
|
static NSDictionary *result;
|
|
33
39
|
dispatch_once(&onceToken, ^{
|
|
@@ -58,7 +64,7 @@ NSDictionary<NSString *, NSNumber *> *fb_availableButtonNames(void) {
|
|
|
58
64
|
return result;
|
|
59
65
|
}
|
|
60
66
|
#else
|
|
61
|
-
NSDictionary<NSString *, NSNumber *> *
|
|
67
|
+
NSDictionary<NSString *, NSNumber *> *FBAvailableButtonNames(void) {
|
|
62
68
|
static dispatch_once_t onceToken;
|
|
63
69
|
static NSDictionary *result;
|
|
64
70
|
dispatch_once(&onceToken, ^{
|
|
@@ -136,29 +142,90 @@ static bool fb_isLocked;
|
|
|
136
142
|
|
|
137
143
|
- (BOOL)fb_goToHomescreenWithError:(NSError **)error
|
|
138
144
|
{
|
|
145
|
+
#if TARGET_OS_TV
|
|
146
|
+
return [self fb_goToHomescreenOnTVWithError:error];
|
|
147
|
+
#elif TARGET_OS_WATCH
|
|
148
|
+
return [self fb_goToHomescreenOnWatchWithError:error];
|
|
149
|
+
#else
|
|
139
150
|
return [XCUIApplication fb_switchToSystemApplicationWithError:error];
|
|
151
|
+
#endif
|
|
140
152
|
}
|
|
141
153
|
|
|
154
|
+
#if TARGET_OS_WATCH
|
|
155
|
+
- (BOOL)fb_goToHomescreenOnWatchWithError:(NSError **)error
|
|
156
|
+
{
|
|
157
|
+
// Carousel rejects launch/activate requests on watchOS 27.0.
|
|
158
|
+
XCUIApplication *foregroundApp = XCUIApplication.fb_activeApplication;
|
|
159
|
+
// The watch face is a separate foreground application from Carousel.
|
|
160
|
+
if ([foregroundApp.bundleID isEqualToString:FBClockFaceBundleId]
|
|
161
|
+
|| [foregroundApp fb_isSameAppAs:XCUIApplication.fb_systemApplication]) {
|
|
162
|
+
return YES;
|
|
163
|
+
}
|
|
164
|
+
@try {
|
|
165
|
+
[self pressButton:XCUIDeviceButtonHome];
|
|
166
|
+
} @catch (NSException *exception) {
|
|
167
|
+
return [[[FBErrorBuilder alloc] withDescription:exception.reason] buildError:error];
|
|
168
|
+
}
|
|
169
|
+
return [[[[FBRunLoopSpinner new] timeout:5]
|
|
170
|
+
timeoutErrorMessage:@"Timed out while waiting for the home screen"]
|
|
171
|
+
spinUntilTrue:^BOOL{
|
|
172
|
+
return foregroundApp.state != XCUIApplicationStateRunningForeground;
|
|
173
|
+
} error:error];
|
|
174
|
+
}
|
|
175
|
+
#endif
|
|
176
|
+
|
|
177
|
+
#if TARGET_OS_TV
|
|
178
|
+
- (BOOL)fb_goToHomescreenOnTVWithError:(NSError **)error
|
|
179
|
+
{
|
|
180
|
+
// PineBoard rejects launch/activate requests on tvOS 26.5 and 27.0.
|
|
181
|
+
XCUIApplication *foregroundApp = XCUIApplication.fb_activeApplication;
|
|
182
|
+
if ([foregroundApp fb_isSameAppAs:XCUIApplication.fb_systemApplication]) {
|
|
183
|
+
return YES;
|
|
184
|
+
}
|
|
185
|
+
@try {
|
|
186
|
+
[XCUIRemote.sharedRemote pressButton:XCUIRemoteButtonHome];
|
|
187
|
+
} @catch (NSException *exception) {
|
|
188
|
+
return [[[FBErrorBuilder alloc] withDescription:exception.reason] buildError:error];
|
|
189
|
+
}
|
|
190
|
+
return [[[[FBRunLoopSpinner new] timeout:5]
|
|
191
|
+
timeoutErrorMessage:@"Timed out while waiting for the home screen"]
|
|
192
|
+
spinUntilTrue:^BOOL{
|
|
193
|
+
return foregroundApp.state != XCUIApplicationStateRunningForeground;
|
|
194
|
+
} error:error];
|
|
195
|
+
}
|
|
196
|
+
#endif
|
|
197
|
+
|
|
142
198
|
- (BOOL)fb_lockScreen:(NSError **)error
|
|
143
199
|
{
|
|
144
200
|
if (fb_isLocked) {
|
|
145
201
|
return YES;
|
|
146
202
|
}
|
|
147
|
-
#if
|
|
148
|
-
[self
|
|
149
|
-
|
|
150
|
-
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"27.0")) {
|
|
151
|
-
// iOS 27: pressLockButton no longer locks; use a direct IOHID Power press (0x0C/0x30, ~0.5s hold).
|
|
152
|
-
if (![self fb_performIOHIDEventWithPage:0x0C
|
|
153
|
-
usage:0x30
|
|
154
|
-
duration:0.5
|
|
155
|
-
error:error]) {
|
|
203
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
204
|
+
if ([FBHIDOperations appliesToDevice:self]) {
|
|
205
|
+
if (![FBHIDOperations pressLockWithError:error]) {
|
|
156
206
|
return NO;
|
|
157
207
|
}
|
|
158
|
-
} else
|
|
208
|
+
} else
|
|
209
|
+
#endif
|
|
210
|
+
{
|
|
211
|
+
#if TARGET_OS_SIMULATOR
|
|
159
212
|
[self pressLockButton];
|
|
160
|
-
|
|
213
|
+
#else
|
|
214
|
+
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"27.0")) {
|
|
215
|
+
// iOS 27: pressLockButton no longer locks; use a short IOHID Power press.
|
|
216
|
+
// A 0.5s hold can invoke Siri instead of locking the screen.
|
|
217
|
+
const NSTimeInterval duration = 0.2;
|
|
218
|
+
if (![self fb_performIOHIDEventWithPage:0x0C
|
|
219
|
+
usage:0x30
|
|
220
|
+
duration:duration
|
|
221
|
+
error:error]) {
|
|
222
|
+
return NO;
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
[self pressLockButton];
|
|
226
|
+
}
|
|
161
227
|
#endif
|
|
228
|
+
}
|
|
162
229
|
return [[[[FBRunLoopSpinner new]
|
|
163
230
|
timeout:FBScreenLockTimeout]
|
|
164
231
|
timeoutErrorMessage:@"Timed out while waiting until the screen gets locked"]
|
|
@@ -177,14 +244,19 @@ static bool fb_isLocked;
|
|
|
177
244
|
if (!fb_isLocked) {
|
|
178
245
|
return YES;
|
|
179
246
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
247
|
+
for (NSUInteger attempt = 0; attempt < 2; attempt++) {
|
|
248
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
249
|
+
if ([FBHIDOperations appliesToDevice:self]) {
|
|
250
|
+
if (![FBHIDOperations pressHomeWithError:error]) {
|
|
251
|
+
return NO;
|
|
252
|
+
}
|
|
253
|
+
} else
|
|
186
254
|
#endif
|
|
187
|
-
|
|
255
|
+
{
|
|
256
|
+
[self pressButton:XCUIDeviceButtonHome];
|
|
257
|
+
}
|
|
258
|
+
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:FBHomeButtonCoolOffTime]];
|
|
259
|
+
}
|
|
188
260
|
return [[[[FBRunLoopSpinner new]
|
|
189
261
|
timeout:FBScreenLockTimeout]
|
|
190
262
|
timeoutErrorMessage:@"Timed out while waiting until the screen gets unlocked"]
|
|
@@ -307,7 +379,7 @@ static bool fb_isLocked;
|
|
|
307
379
|
|
|
308
380
|
- (BOOL)fb_hasButton:(NSString *)buttonName
|
|
309
381
|
{
|
|
310
|
-
return
|
|
382
|
+
return FBAvailableButtonNames()[buttonName.lowercaseString] != nil;
|
|
311
383
|
}
|
|
312
384
|
|
|
313
385
|
- (BOOL)fb_pressButton:(NSString *)buttonName
|
|
@@ -318,7 +390,7 @@ static bool fb_isLocked;
|
|
|
318
390
|
return [self fb_pressButton:buttonName error:error];
|
|
319
391
|
#else
|
|
320
392
|
|
|
321
|
-
NSDictionary<NSString *, NSNumber *> *availableButtons =
|
|
393
|
+
NSDictionary<NSString *, NSNumber *> *availableButtons = FBAvailableButtonNames();
|
|
322
394
|
NSNumber *buttonValue = availableButtons[buttonName.lowercaseString];
|
|
323
395
|
|
|
324
396
|
if (!buttonValue) {
|
|
@@ -342,7 +414,7 @@ static bool fb_isLocked;
|
|
|
342
414
|
- (BOOL)fb_pressButton:(NSString *)buttonName
|
|
343
415
|
error:(NSError **)error
|
|
344
416
|
{
|
|
345
|
-
NSDictionary<NSString *, NSNumber *> *availableButtons =
|
|
417
|
+
NSDictionary<NSString *, NSNumber *> *availableButtons = FBAvailableButtonNames();
|
|
346
418
|
NSNumber *buttonValue = availableButtons[buttonName.lowercaseString];
|
|
347
419
|
|
|
348
420
|
if (!buttonValue) {
|
|
@@ -351,6 +423,11 @@ static bool fb_isLocked;
|
|
|
351
423
|
withDescriptionFormat:@"The button '%@' is not supported. The device under test only supports the following buttons: %@", buttonName, sortedKeys]
|
|
352
424
|
buildError:error];
|
|
353
425
|
}
|
|
426
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
427
|
+
if (buttonValue.unsignedIntegerValue == XCUIDeviceButtonHome && [FBHIDOperations appliesToDevice:self]) {
|
|
428
|
+
return [FBHIDOperations pressHomeWithError:error];
|
|
429
|
+
}
|
|
430
|
+
#endif
|
|
354
431
|
[self pressButton:(XCUIDeviceButton)[buttonValue unsignedIntegerValue]];
|
|
355
432
|
return YES;
|
|
356
433
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
12
|
+
|
|
13
|
+
@interface XCUIDevice (FBHinge)
|
|
14
|
+
|
|
15
|
+
/** Whether a hinge and the CoreMotion angle-reading APIs are available. */
|
|
16
|
+
- (BOOL)fb_supportsHingeAngleReading;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
Whether a hinge and the IOKit APIs needed to attempt injection are available.
|
|
20
|
+
This does not guarantee event acceptance, particularly on physical devices.
|
|
21
|
+
*/
|
|
22
|
+
- (BOOL)fb_canAttemptSimulatedHingeAngleInjection;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
Reads the current hinge angle in degrees, including changes made outside WDA.
|
|
26
|
+
Waits up to 5 seconds for a valid reading. Does not change the hinge angle.
|
|
27
|
+
@return The angle, or nil with an error if unavailable or reading times out.
|
|
28
|
+
*/
|
|
29
|
+
- (nullable NSNumber *)fb_getSimulatedHingeAngle:(NSError **)error;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
Sends a simulated hinge angle in degrees (0 = closed, 180 = fully open).
|
|
33
|
+
This dispatches the event; the display transition completes asynchronously.
|
|
34
|
+
Verified on the iPhone Duo simulator; physical-device behavior is unverified.
|
|
35
|
+
Does not change currentDisplayId.
|
|
36
|
+
*/
|
|
37
|
+
- (BOOL)fb_setSimulatedHingeAngle:(double)angle error:(NSError **)error;
|
|
38
|
+
|
|
39
|
+
@end
|
|
40
|
+
|
|
41
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,215 @@
|
|
|
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 "XCUIDevice+FBHinge.h"
|
|
10
|
+
|
|
11
|
+
#import <dlfcn.h>
|
|
12
|
+
#import <math.h>
|
|
13
|
+
#import <mach/mach_time.h>
|
|
14
|
+
|
|
15
|
+
#import "FBErrorBuilder.h"
|
|
16
|
+
|
|
17
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
18
|
+
static const NSTimeInterval FBHingeAngleReadingTimeout = 5.0;
|
|
19
|
+
|
|
20
|
+
typedef CFDataRef (*FBIOCFSerialize)(CFTypeRef object, CFOptionFlags options);
|
|
21
|
+
typedef CFTypeRef (*FBIOHIDEventCreateVendorDefinedEvent)(CFAllocatorRef allocator,
|
|
22
|
+
uint64_t timestamp,
|
|
23
|
+
uint32_t usagePage,
|
|
24
|
+
uint32_t usage,
|
|
25
|
+
uint32_t version,
|
|
26
|
+
uint8_t *data,
|
|
27
|
+
CFIndex length,
|
|
28
|
+
uint32_t options);
|
|
29
|
+
typedef CFTypeRef (*FBIOHIDEventSystemClientCreate)(CFAllocatorRef allocator);
|
|
30
|
+
typedef void (*FBIOHIDEventSystemClientDispatchEvent)(CFTypeRef client, CFTypeRef event);
|
|
31
|
+
|
|
32
|
+
@protocol FBAngle <NSObject>
|
|
33
|
+
- (BOOL)isAngleValid;
|
|
34
|
+
- (float)angleDegrees;
|
|
35
|
+
@end
|
|
36
|
+
|
|
37
|
+
@protocol FBAngleManager <NSObject>
|
|
38
|
+
+ (instancetype)new;
|
|
39
|
+
+ (BOOL)isAvailable;
|
|
40
|
+
+ (BOOL)instancesRespondToSelector:(SEL)selector;
|
|
41
|
+
- (void)startAngleUpdatesToQueue:(NSOperationQueue *)queue handler:(void (^)(id<FBAngle>))handler;
|
|
42
|
+
- (void)stopAngleUpdates;
|
|
43
|
+
@end
|
|
44
|
+
|
|
45
|
+
static Class<FBAngleManager> FBAngleManagerClass(void)
|
|
46
|
+
{
|
|
47
|
+
static Class<FBAngleManager> angleManagerClass;
|
|
48
|
+
static dispatch_once_t onceToken;
|
|
49
|
+
dispatch_once(&onceToken, ^{
|
|
50
|
+
// This path also loads on physical devices (verified on an iPad mini).
|
|
51
|
+
// Framework/class presence does not imply that a hinge sensor is available.
|
|
52
|
+
if (NULL != dlopen("/System/Library/Frameworks/CoreMotion.framework/CoreMotion", RTLD_LAZY)) {
|
|
53
|
+
angleManagerClass = NSClassFromString(@"CMAngleManager");
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
return angleManagerClass;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static BOOL FBHasAvailableHinge(void)
|
|
60
|
+
{
|
|
61
|
+
Class<FBAngleManager> managerClass = FBAngleManagerClass();
|
|
62
|
+
return [managerClass respondsToSelector:@selector(isAvailable)] && [managerClass isAvailable];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
typedef struct {
|
|
66
|
+
FBIOCFSerialize serialize;
|
|
67
|
+
FBIOHIDEventCreateVendorDefinedEvent createEvent;
|
|
68
|
+
FBIOHIDEventSystemClientCreate createClient;
|
|
69
|
+
FBIOHIDEventSystemClientDispatchEvent dispatchEvent;
|
|
70
|
+
} FBHingeInjectionAPI;
|
|
71
|
+
|
|
72
|
+
// Loads and caches the injection APIs; NULL means at least one is unavailable.
|
|
73
|
+
// Resolving these functions does not dispatch an event or confirm its acceptance.
|
|
74
|
+
static const FBHingeInjectionAPI *FBHingeInjectionFunctions(void)
|
|
75
|
+
{
|
|
76
|
+
static FBHingeInjectionAPI api;
|
|
77
|
+
static dispatch_once_t onceToken;
|
|
78
|
+
dispatch_once(&onceToken, ^{
|
|
79
|
+
void *handle = dlopen("/System/Library/Frameworks/IOKit.framework/IOKit", RTLD_LAZY);
|
|
80
|
+
if (NULL != handle) {
|
|
81
|
+
api.serialize = (FBIOCFSerialize)dlsym(handle, "IOCFSerialize");
|
|
82
|
+
api.createEvent = (FBIOHIDEventCreateVendorDefinedEvent)dlsym(handle, "IOHIDEventCreateVendorDefinedEvent");
|
|
83
|
+
api.createClient = (FBIOHIDEventSystemClientCreate)dlsym(handle, "IOHIDEventSystemClientCreate");
|
|
84
|
+
api.dispatchEvent = (FBIOHIDEventSystemClientDispatchEvent)dlsym(handle, "IOHIDEventSystemClientDispatchEvent");
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
return NULL != api.serialize && NULL != api.createEvent
|
|
88
|
+
&& NULL != api.createClient && NULL != api.dispatchEvent ? &api : NULL;
|
|
89
|
+
}
|
|
90
|
+
#endif
|
|
91
|
+
|
|
92
|
+
@implementation XCUIDevice (FBHinge)
|
|
93
|
+
|
|
94
|
+
- (BOOL)fb_supportsHingeAngleReading
|
|
95
|
+
{
|
|
96
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
97
|
+
Class<FBAngleManager> managerClass = FBAngleManagerClass();
|
|
98
|
+
return FBHasAvailableHinge()
|
|
99
|
+
&& [managerClass instancesRespondToSelector:@selector(startAngleUpdatesToQueue:handler:)]
|
|
100
|
+
&& [managerClass instancesRespondToSelector:@selector(stopAngleUpdates)];
|
|
101
|
+
#else
|
|
102
|
+
return NO;
|
|
103
|
+
#endif
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
- (BOOL)fb_canAttemptSimulatedHingeAngleInjection
|
|
107
|
+
{
|
|
108
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
109
|
+
// Sensor presence and local APIs permit an attempt, not proof that the
|
|
110
|
+
// device's receiver accepts the vendor event. Physical devices are unverified.
|
|
111
|
+
return FBHasAvailableHinge() && NULL != FBHingeInjectionFunctions();
|
|
112
|
+
#else
|
|
113
|
+
return NO;
|
|
114
|
+
#endif
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
- (nullable NSNumber *)fb_getSimulatedHingeAngle:(NSError **)error
|
|
118
|
+
{
|
|
119
|
+
if (!self.fb_supportsHingeAngleReading) {
|
|
120
|
+
[[FBErrorBuilder.builder withDescription:@"Hinge angle reading is unavailable on this device"] buildError:error];
|
|
121
|
+
return nil;
|
|
122
|
+
}
|
|
123
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
124
|
+
id<FBAngleManager> manager = [FBAngleManagerClass() new];
|
|
125
|
+
NSOperationQueue *queue = [NSOperationQueue new];
|
|
126
|
+
queue.maxConcurrentOperationCount = 1;
|
|
127
|
+
NSCondition *condition = [NSCondition new];
|
|
128
|
+
__block NSNumber *reading = nil;
|
|
129
|
+
NSNumber *result;
|
|
130
|
+
@try {
|
|
131
|
+
[manager startAngleUpdatesToQueue:queue handler:^(id<FBAngle> angle) {
|
|
132
|
+
if (![angle respondsToSelector:@selector(isAngleValid)]
|
|
133
|
+
|| ![angle respondsToSelector:@selector(angleDegrees)] || !angle.isAngleValid) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
float degrees = angle.angleDegrees;
|
|
137
|
+
if (!isfinite(degrees)) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
[condition lock];
|
|
141
|
+
if (nil == reading) {
|
|
142
|
+
reading = @(degrees);
|
|
143
|
+
[condition signal];
|
|
144
|
+
}
|
|
145
|
+
[condition unlock];
|
|
146
|
+
}];
|
|
147
|
+
NSDate *deadline = [NSDate dateWithTimeIntervalSinceNow:FBHingeAngleReadingTimeout];
|
|
148
|
+
[condition lock];
|
|
149
|
+
while (nil == reading) {
|
|
150
|
+
if (![condition waitUntilDate:deadline]) {
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
result = reading;
|
|
155
|
+
[condition unlock];
|
|
156
|
+
} @finally {
|
|
157
|
+
[manager stopAngleUpdates];
|
|
158
|
+
}
|
|
159
|
+
if (nil == result) {
|
|
160
|
+
[[FBErrorBuilder.builder withDescriptionFormat:@"Timed out after %g seconds waiting for a valid hinge angle",
|
|
161
|
+
FBHingeAngleReadingTimeout] buildError:error];
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
#else
|
|
165
|
+
return nil;
|
|
166
|
+
#endif
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
- (BOOL)fb_setSimulatedHingeAngle:(double)angle error:(NSError **)error
|
|
170
|
+
{
|
|
171
|
+
if (!isfinite(angle) || angle < 0 || angle > 180) {
|
|
172
|
+
return [[FBErrorBuilder.builder withDescription:@"Hinge angle must be a finite number between 0 and 180 degrees"] buildError:error];
|
|
173
|
+
}
|
|
174
|
+
if (!self.fb_canAttemptSimulatedHingeAngleInjection) {
|
|
175
|
+
return [[FBErrorBuilder.builder withDescription:@"Hinge angle injection requires an available hinge and the IOKit HID APIs"] buildError:error];
|
|
176
|
+
}
|
|
177
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
178
|
+
// WDA's fb_performIOHIDEventWithPage:usage:duration:error: uses XCDeviceEvent
|
|
179
|
+
// with page, usage, and duration only. The XCDeviceEvent interfaces checked
|
|
180
|
+
// in Xcode 27.1 provide no way to attach the vendor payload containing the
|
|
181
|
+
// hinge angle, so this command creates and dispatches an IOKit event directly.
|
|
182
|
+
const FBHingeInjectionAPI *api = FBHingeInjectionFunctions();
|
|
183
|
+
// Matches Device Hub's hinge-slider-control payload (Xcode 27.1). It is an
|
|
184
|
+
// IOCF binary serialization, not an NSPropertyListSerialization binary plist.
|
|
185
|
+
NSDictionary *payload = @{
|
|
186
|
+
@"provider": @"com.apple.Virtualization.VirtualMachines",
|
|
187
|
+
@"source": @"hinge-slider-control",
|
|
188
|
+
@"type": @"range",
|
|
189
|
+
@"value": @(angle),
|
|
190
|
+
};
|
|
191
|
+
CFDataRef data = api->serialize((__bridge CFTypeRef)payload, 1);
|
|
192
|
+
if (NULL == data) {
|
|
193
|
+
return [[FBErrorBuilder.builder withDescription:@"Cannot serialize the simulated hinge event"] buildError:error];
|
|
194
|
+
}
|
|
195
|
+
CFTypeRef event = api->createEvent(kCFAllocatorDefault, mach_absolute_time(), 0xff61, 0x5b, 0,
|
|
196
|
+
(uint8_t *)CFDataGetBytePtr(data), CFDataGetLength(data), 0);
|
|
197
|
+
CFRelease(data);
|
|
198
|
+
CFTypeRef client = api->createClient(kCFAllocatorDefault);
|
|
199
|
+
BOOL canDispatch = NULL != event && NULL != client;
|
|
200
|
+
if (canDispatch) {
|
|
201
|
+
api->dispatchEvent(client, event);
|
|
202
|
+
}
|
|
203
|
+
if (NULL != event) {
|
|
204
|
+
CFRelease(event);
|
|
205
|
+
}
|
|
206
|
+
if (NULL != client) {
|
|
207
|
+
CFRelease(client);
|
|
208
|
+
}
|
|
209
|
+
return canDispatch || [[FBErrorBuilder.builder withDescription:@"Cannot create the simulated hinge HID event or client"] buildError:error];
|
|
210
|
+
#else
|
|
211
|
+
return NO;
|
|
212
|
+
#endif
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@end
|
|
@@ -9,16 +9,23 @@
|
|
|
9
9
|
#import "XCUIDevice+FBRotation.h"
|
|
10
10
|
|
|
11
11
|
#import "FBConfiguration.h"
|
|
12
|
+
#import "FBHIDOperations.h"
|
|
12
13
|
#import "XCUIApplication.h"
|
|
13
14
|
#import "XCUIApplication+FBHelpers.h"
|
|
14
15
|
#import "XCUIElement+FBUtilities.h"
|
|
15
16
|
|
|
16
17
|
# if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
17
18
|
|
|
19
|
+
static UIInterfaceOrientation FBInterfaceOrientationFromDeviceOrientation(UIDeviceOrientation orientation);
|
|
20
|
+
|
|
18
21
|
@implementation XCUIDevice (FBRotation)
|
|
19
22
|
|
|
20
23
|
- (BOOL)fb_setDeviceInterfaceOrientation:(UIDeviceOrientation)orientation
|
|
21
24
|
{
|
|
25
|
+
if ([FBHIDOperations appliesToDevice:self]) {
|
|
26
|
+
return [FBHIDOperations rotateToOrientation:orientation
|
|
27
|
+
interfaceOrientation:FBInterfaceOrientationFromDeviceOrientation(orientation)];
|
|
28
|
+
}
|
|
22
29
|
XCUIApplication *application = XCUIApplication.fb_activeApplication;
|
|
23
30
|
[XCUIDevice sharedDevice].orientation = orientation;
|
|
24
31
|
return [self waitUntilInterfaceIsAtOrientation:orientation application:application];
|
|
@@ -31,9 +38,7 @@
|
|
|
31
38
|
return NO;
|
|
32
39
|
}
|
|
33
40
|
UIDeviceOrientation orientation = (UIDeviceOrientation)keysForRotationObj.firstObject.integerValue;
|
|
34
|
-
|
|
35
|
-
[XCUIDevice sharedDevice].orientation = orientation;
|
|
36
|
-
return [self waitUntilInterfaceIsAtOrientation:orientation application:application];
|
|
41
|
+
return [self fb_setDeviceInterfaceOrientation:orientation];
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
static UIInterfaceOrientation FBInterfaceOrientationFromDeviceOrientation(UIDeviceOrientation orientation)
|