@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.
Files changed (118) hide show
  1. package/README.md +15 -4
  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,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
+ #import <UIKit/UIKit.h>
11
+
12
+ /**
13
+ HID paths for Home, lock/unlock, and rotation on hinged iOS devices.
14
+
15
+ Remove each workaround once its XCTest-only path passes the corresponding regression
16
+ test on supported runtimes and both panels: testHomeButtonBackgroundsApplication,
17
+ testRepeatedLockUnlockAcrossHingePoses, and testRotationOnSelectedDuoDisplay.
18
+ Physical-device Home, lock/unlock, and rotation behavior require verification.
19
+
20
+ Once all paths are obsolete, remove the FBHIDOperations branches and imports from
21
+ XCUIDevice+FBHelpers.m and XCUIDevice+FBRotation.m, then delete this module and its
22
+ Xcode project entries. Hinge control in XCUIDevice+FBHinge is independent.
23
+
24
+ Tracking: https://github.com/appium/appium/issues/22857
25
+ Rotation: https://github.com/appium/appium-ios/pull/81
26
+ */
27
+ @interface FBHIDOperations : NSObject
28
+
29
+ // All operations apply to hinged iOS devices, including physical devices.
30
+ + (BOOL)appliesToDevice:(XCUIDevice *)device;
31
+
32
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
33
+ // Call only after the corresponding applicability check succeeds.
34
+ + (BOOL)pressHomeWithError:(NSError **)error;
35
+ + (BOOL)pressLockWithError:(NSError **)error;
36
+ // interfaceOrientation is the UIKit equivalent of the requested device orientation.
37
+ + (BOOL)rotateToOrientation:(UIDeviceOrientation)orientation
38
+ interfaceOrientation:(UIInterfaceOrientation)interfaceOrientation;
39
+ #endif
40
+
41
+ @end
@@ -0,0 +1,244 @@
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 "FBHIDOperations.h"
10
+
11
+ #import <dlfcn.h>
12
+ #import <mach/mach_time.h>
13
+
14
+ #import "FBConfiguration.h"
15
+ #import "FBErrorBuilder.h"
16
+ #import "FBLogger.h"
17
+ #import "FBRunLoopSpinner.h"
18
+ #import "FBScreen.h"
19
+ #import "FBSession.h"
20
+ #import "XCUIScreen.h"
21
+ #import "XCUIDevice+FBHinge.h"
22
+ #import "XCUIApplication.h"
23
+ #import "XCUIApplication+FBHelpers.h"
24
+ #import "XCUIElement+FBUtilities.h"
25
+
26
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
27
+
28
+ static const uint32_t FBHIDConsumerUsagePage = 0x0C;
29
+ static const uint32_t FBHIDHomeUsage = 0x40;
30
+ static const uint32_t FBHIDPowerUsage = 0x30;
31
+ static const uint32_t FBHIDOrientationUsagePage = 0xff61;
32
+ static const uint32_t FBHIDOrientationUsage = 0x5b;
33
+ static const CFOptionFlags FBHIDBinarySerialization = 1;
34
+ static const NSTimeInterval FBHIDButtonPressDuration = 0.03;
35
+ static const NSTimeInterval FBHIDRotationTimeout = 5.0;
36
+
37
+ typedef struct {
38
+ CFTypeRef (*createKeyboardEvent)(CFAllocatorRef, uint64_t, uint32_t, uint32_t, Boolean, uint32_t);
39
+ CFTypeRef (*createVendorEvent)(CFAllocatorRef, uint64_t, uint32_t, uint32_t,
40
+ uint32_t, uint8_t *, CFIndex, uint32_t);
41
+ CFTypeRef (*createClient)(CFAllocatorRef);
42
+ void (*dispatchEvent)(CFTypeRef, CFTypeRef);
43
+ void (*setTimestamp)(CFTypeRef, uint64_t);
44
+ CFDataRef (*serialize)(CFTypeRef, CFOptionFlags);
45
+ } FBHIDAPI;
46
+
47
+ static const FBHIDAPI *FBGetHIDAPI(void)
48
+ {
49
+ static FBHIDAPI api;
50
+ static dispatch_once_t onceToken;
51
+ dispatch_once(&onceToken, ^{
52
+ // Keep the framework loaded for the lifetime of the cached function pointers.
53
+ void *handle = dlopen("/System/Library/Frameworks/IOKit.framework/IOKit", RTLD_LAZY);
54
+ if (NULL == handle) {
55
+ return;
56
+ }
57
+ api.createKeyboardEvent = (CFTypeRef (*)(CFAllocatorRef, uint64_t, uint32_t, uint32_t, Boolean, uint32_t))
58
+ dlsym(handle, "IOHIDEventCreateKeyboardEvent");
59
+ api.createVendorEvent = (CFTypeRef (*)(CFAllocatorRef, uint64_t, uint32_t, uint32_t,
60
+ uint32_t, uint8_t *, CFIndex, uint32_t))
61
+ dlsym(handle, "IOHIDEventCreateVendorDefinedEvent");
62
+ api.createClient = (CFTypeRef (*)(CFAllocatorRef))dlsym(handle, "IOHIDEventSystemClientCreate");
63
+ api.dispatchEvent = (void (*)(CFTypeRef, CFTypeRef))dlsym(handle, "IOHIDEventSystemClientDispatchEvent");
64
+ api.setTimestamp = (void (*)(CFTypeRef, uint64_t))dlsym(handle, "IOHIDEventSetTimeStamp");
65
+ api.serialize = (CFDataRef (*)(CFTypeRef, CFOptionFlags))dlsym(handle, "IOCFSerialize");
66
+ });
67
+ // Each operation checks only the symbols it needs.
68
+ return &api;
69
+ }
70
+
71
+ static void FBLogUnverifiedPhysicalHIDOperation(NSString *operation)
72
+ {
73
+ #if !TARGET_OS_SIMULATOR
74
+ [FBLogger logFmt:@"HID %@ on physical hinged devices: not verified yet.", operation];
75
+ #endif
76
+ }
77
+
78
+ static BOOL FBWaitForStableInterfaceOrientation(XCUIApplication *application,
79
+ UIInterfaceOrientation orientation)
80
+ {
81
+ [application fb_waitUntilStableWithTimeout:FBConfiguration.sharedInstance.animationCoolOffTimeout];
82
+ return application.interfaceOrientation == orientation;
83
+ }
84
+
85
+ // Workaround tracked in https://github.com/appium/appium/issues/22857.
86
+ // Reproduced on the iOS 27.1 Duo simulator with Xcode 27.1: XCTest's Home
87
+ // request returns without backgrounding the app. This direct HID path is also
88
+ // enabled on physical hinged devices, but Home behavior there is unverified.
89
+ // The affected Xcode/runtime version range has not been established. Remove
90
+ // the Home workaround when XCTest-only presses pass testHomeButtonBackgroundsApplication
91
+ // on supported Duo environments, including physical devices and both panels.
92
+ static BOOL FBPressHIDButton(uint32_t usage, NSError **error)
93
+ {
94
+ const FBHIDAPI *api = FBGetHIDAPI();
95
+ if (NULL == api->createKeyboardEvent || NULL == api->createClient
96
+ || NULL == api->dispatchEvent || NULL == api->setTimestamp) {
97
+ return [[FBErrorBuilder.builder withDescription:@"The button HID APIs are unavailable"] buildError:error];
98
+ }
99
+ CFTypeRef client = api->createClient(kCFAllocatorDefault);
100
+ CFTypeRef down = api->createKeyboardEvent(kCFAllocatorDefault, mach_absolute_time(),
101
+ FBHIDConsumerUsagePage, usage, YES, 0);
102
+ CFTypeRef up = api->createKeyboardEvent(kCFAllocatorDefault, mach_absolute_time(),
103
+ FBHIDConsumerUsagePage, usage, NO, 0);
104
+ BOOL ready = NULL != client && NULL != down && NULL != up;
105
+ if (ready) {
106
+ // Dispatch a short paired press. Holding Power for 0.5s invokes Siri on Duo.
107
+ api->dispatchEvent(client, down);
108
+ [NSThread sleepForTimeInterval:FBHIDButtonPressDuration];
109
+ api->setTimestamp(up, mach_absolute_time());
110
+ api->dispatchEvent(client, up);
111
+ }
112
+ if (NULL != down) {
113
+ CFRelease(down);
114
+ }
115
+ if (NULL != up) {
116
+ CFRelease(up);
117
+ }
118
+ if (NULL != client) {
119
+ CFRelease(client);
120
+ }
121
+ return ready || [[FBErrorBuilder.builder
122
+ withDescription:@"Cannot create the button HID event or client"] buildError:error];
123
+ }
124
+
125
+ // Work around XCTest updating Duo device orientation without rotating the app.
126
+ // Reproduction details and checks required before removing this workaround:
127
+ // https://github.com/appium/appium-ios/pull/81
128
+ static BOOL FBDispatchHIDOrientation(UIDeviceOrientation orientation)
129
+ {
130
+ XCUIScreen *screen = [FBScreen currentScreenWithError:nil];
131
+ if (nil == screen) {
132
+ return NO;
133
+ }
134
+ // The inner panel's native orientation is a quarter turn from the cover.
135
+ // Values match DeviceKit's orientation-picker-control (Xcode 27.1).
136
+ NSString *value;
137
+ switch (orientation) {
138
+ case UIDeviceOrientationPortrait:
139
+ value = screen.isMainScreen ? @"portrait" : @"landscape-left";
140
+ break;
141
+ case UIDeviceOrientationLandscapeLeft:
142
+ value = screen.isMainScreen ? @"landscape-left" : @"pud";
143
+ break;
144
+ case UIDeviceOrientationLandscapeRight:
145
+ value = screen.isMainScreen ? @"landscape-right" : @"portrait";
146
+ break;
147
+ case UIDeviceOrientationPortraitUpsideDown:
148
+ value = screen.isMainScreen ? @"pud" : @"landscape-right";
149
+ break;
150
+ case UIDeviceOrientationUnknown:
151
+ case UIDeviceOrientationFaceUp:
152
+ case UIDeviceOrientationFaceDown:
153
+ default:
154
+ return NO;
155
+ }
156
+
157
+ const FBHIDAPI *api = FBGetHIDAPI();
158
+ if (NULL == api->serialize || NULL == api->createVendorEvent
159
+ || NULL == api->createClient || NULL == api->dispatchEvent) {
160
+ return NO;
161
+ }
162
+ NSDictionary *payload = @{
163
+ @"provider": @"com.apple.Virtualization.VirtualMachines",
164
+ @"source": @"orientation-picker-control",
165
+ @"type": @"enum",
166
+ @"value": value,
167
+ };
168
+ // The receiver expects IOCF binary serialization, not a binary plist.
169
+ CFDataRef data = api->serialize((__bridge CFTypeRef)payload, FBHIDBinarySerialization);
170
+ if (NULL == data) {
171
+ return NO;
172
+ }
173
+ CFTypeRef event = api->createVendorEvent(kCFAllocatorDefault, mach_absolute_time(),
174
+ FBHIDOrientationUsagePage, FBHIDOrientationUsage, 0,
175
+ (uint8_t *)CFDataGetBytePtr(data), CFDataGetLength(data), 0);
176
+ CFRelease(data);
177
+ CFTypeRef client = api->createClient(kCFAllocatorDefault);
178
+ BOOL canDispatch = NULL != event && NULL != client;
179
+ if (canDispatch) {
180
+ api->dispatchEvent(client, event);
181
+ }
182
+ if (NULL != event) {
183
+ CFRelease(event);
184
+ }
185
+ if (NULL != client) {
186
+ CFRelease(client);
187
+ }
188
+ return canDispatch;
189
+ }
190
+
191
+ #endif
192
+
193
+ @implementation FBHIDOperations
194
+
195
+ + (BOOL)appliesToDevice:(XCUIDevice *)device
196
+ {
197
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
198
+ return device.fb_supportsHingeAngleReading;
199
+ #else
200
+ return NO;
201
+ #endif
202
+ }
203
+
204
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
205
+ + (BOOL)pressHomeWithError:(NSError **)error
206
+ {
207
+ FBLogUnverifiedPhysicalHIDOperation(@"Home");
208
+ return FBPressHIDButton(FBHIDHomeUsage, error);
209
+ }
210
+
211
+ + (BOOL)pressLockWithError:(NSError **)error
212
+ {
213
+ FBLogUnverifiedPhysicalHIDOperation(@"lock");
214
+ // XCTest locking times out on Duo / iOS 27.1 (Xcode 27.1). Remove this
215
+ // workaround once XCTest alone passes testRepeatedLockUnlockAcrossHingePoses.
216
+ return FBPressHIDButton(FBHIDPowerUsage, error);
217
+ }
218
+
219
+ + (BOOL)rotateToOrientation:(UIDeviceOrientation)orientation
220
+ interfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
221
+ {
222
+ FBLogUnverifiedPhysicalHIDOperation(@"rotation");
223
+ XCUIApplication *application = XCUIApplication.fb_activeApplication;
224
+ // Point-based detection can select the cover SpringBoard while the app
225
+ // is on the inner panel. Prefer the session's active application.
226
+ application = FBSession.activeSession.activeApplication ?: application;
227
+ // Keep XCTest's reported device orientation in sync on the simulator too.
228
+ [XCUIDevice sharedDevice].orientation = orientation;
229
+ // On Duo simulators XCTest can update its value without rotating the app.
230
+ // Use the same direct HID path on simulators and physical hinged devices.
231
+ if (!FBDispatchHIDOrientation(orientation)) {
232
+ return NO;
233
+ }
234
+ // Event dispatch is asynchronous. Check the app, not just XCTest's value.
235
+ if (![[[FBRunLoopSpinner new] timeout:FBHIDRotationTimeout] spinUntilTrue:^BOOL{
236
+ return application.interfaceOrientation == interfaceOrientation;
237
+ }]) {
238
+ return NO;
239
+ }
240
+ return FBWaitForStableInterfaceOrientation(application, interfaceOrientation);
241
+ }
242
+ #endif
243
+
244
+ @end
@@ -144,7 +144,9 @@ const CGFloat FBMaxCompressionQuality = 1.0f;
144
144
  }
145
145
 
146
146
  UIGraphicsImageRendererFormat *format = [[UIGraphicsImageRendererFormat alloc] init];
147
- format.scale = scalingFactor;
147
+ // scaledSize already applies the requested factor in points. Preserve the
148
+ // image's pixels-per-point scale instead of applying that factor twice.
149
+ format.scale = image.scale;
148
150
  UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:scaledSize
149
151
  format:format];
150
152
  UIImageOrientation desiredOrientation = orientation == nil
@@ -35,8 +35,19 @@ BOOL FBSizeFuzzyEqualToSize(CGSize size1, CGSize size2, CGFloat threshold);
35
35
  BOOL FBRectFuzzyEqualToRect(CGRect rect1, CGRect rect2, CGFloat threshold);
36
36
 
37
37
  #if !TARGET_OS_TV && !TARGET_OS_WATCH
38
+ /*! Converts a portrait-screen rect into the application's orientation, preserving its clipping. */
39
+ CGRect FBRectFromPortraitCoordinates(CGRect frame, CGSize screenSize, UIInterfaceOrientation orientation);
40
+
38
41
  /*! Inverts size if necessary to match current screen orientation */
39
42
  CGSize FBAdjustDimensionsForApplication(CGSize actualSize, UIInterfaceOrientation orientation);
43
+
44
+ /*!
45
+ Offsets XCTest screenPoint coordinates for an explicitly selected display.
46
+ XCTest rotates these coordinates using the main screen's dimensions even when
47
+ the synthesized event record targets another display. Sizes are in points.
48
+ */
49
+ CGVector FBDisplayCoordinateOffset(CGSize mainSize, CGSize displaySize,
50
+ UIInterfaceOrientation orientation);
40
51
  #endif
41
52
 
42
53
  #if !TARGET_OS_TV
@@ -50,6 +50,27 @@ BOOL FBRectFuzzyEqualToRect(CGRect rect1, CGRect rect2, CGFloat threshold)
50
50
 
51
51
  #if !TARGET_OS_TV && !TARGET_OS_WATCH
52
52
 
53
+ CGRect FBRectFromPortraitCoordinates(CGRect frame, CGSize screenSize, UIInterfaceOrientation orientation)
54
+ {
55
+ if (CGRectIsEmpty(frame)) {
56
+ return frame;
57
+ }
58
+ CGFloat width = MIN(screenSize.width, screenSize.height);
59
+ CGFloat height = MAX(screenSize.width, screenSize.height);
60
+ switch (orientation) {
61
+ case UIInterfaceOrientationLandscapeLeft:
62
+ return CGRectMake(height - CGRectGetMaxY(frame), CGRectGetMinX(frame), frame.size.height, frame.size.width);
63
+ case UIInterfaceOrientationLandscapeRight:
64
+ return CGRectMake(CGRectGetMinY(frame), width - CGRectGetMaxX(frame), frame.size.height, frame.size.width);
65
+ case UIInterfaceOrientationPortraitUpsideDown:
66
+ return CGRectMake(width - CGRectGetMaxX(frame), height - CGRectGetMaxY(frame), frame.size.width, frame.size.height);
67
+ case UIInterfaceOrientationUnknown:
68
+ case UIInterfaceOrientationPortrait:
69
+ default:
70
+ return frame;
71
+ }
72
+ }
73
+
53
74
  CGSize FBAdjustDimensionsForApplication(CGSize actualSize, UIInterfaceOrientation orientation)
54
75
  {
55
76
  if (orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight) {
@@ -107,3 +128,20 @@ BOOL FBScrollGestureOffsets(CGRect scrollingFrame,
107
128
  (endPoint.y - anchorFrame.origin.y) / anchorFrame.size.height);
108
129
  return YES;
109
130
  }
131
+
132
+ #if !TARGET_OS_TV && !TARGET_OS_WATCH
133
+ CGVector FBDisplayCoordinateOffset(CGSize mainSize, CGSize displaySize,
134
+ UIInterfaceOrientation orientation)
135
+ {
136
+ CGVector offset = CGVectorMake(0, 0);
137
+ if (orientation == UIInterfaceOrientationLandscapeRight
138
+ || orientation == UIInterfaceOrientationPortraitUpsideDown) {
139
+ offset.dx = displaySize.width - mainSize.width;
140
+ }
141
+ if (orientation == UIInterfaceOrientationLandscapeLeft
142
+ || orientation == UIInterfaceOrientationPortraitUpsideDown) {
143
+ offset.dy = displaySize.height - mainSize.height;
144
+ }
145
+ return offset;
146
+ }
147
+ #endif
@@ -16,6 +16,7 @@
16
16
  #import "FBConfiguration.h"
17
17
  #import "FBLogger.h"
18
18
  #import "FBScreenshot.h"
19
+ #import "FBScreen.h"
19
20
  #import "FBImageProcessor.h"
20
21
  #import "FBImageUtils.h"
21
22
  #import "XCUIScreen.h"
@@ -42,7 +43,6 @@ static NSUInteger FBNormalizedMjpegFramerate(NSUInteger framerate)
42
43
  @property (nonatomic, readonly) dispatch_queue_t backgroundQueue;
43
44
  @property (nonatomic, readonly) NSMutableArray<nw_connection_t> *listeningClients;
44
45
  @property (nonatomic, readonly) FBImageProcessor *imageProcessor;
45
- @property (nonatomic, readonly) long long mainScreenID;
46
46
  @property (nonatomic, assign) NSUInteger consecutiveScreenshotFailures;
47
47
  @property (atomic, assign) BOOL isStreaming;
48
48
  @property (nonatomic, assign) NSUInteger sentFramesCount;
@@ -67,7 +67,6 @@ static NSUInteger FBNormalizedMjpegFramerate(NSUInteger framerate)
67
67
  _pendingFrameCounts = [NSMapTable mapTableWithKeyOptions:(NSPointerFunctionsOptions)(NSMapTableObjectPointerPersonality | NSMapTableStrongMemory)
68
68
  valueOptions:(NSPointerFunctionsOptions)NSMapTableStrongMemory];
69
69
  _imageProcessor = [[FBImageProcessor alloc] init];
70
- _mainScreenID = [XCUIScreen.mainScreen displayID];
71
70
  dispatch_queue_attr_t queueAttributes = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0);
72
71
  _backgroundQueue = dispatch_queue_create(QUEUE_NAME, queueAttributes);
73
72
  __weak typeof(self) weakSelf = self;
@@ -116,7 +115,8 @@ static NSUInteger FBNormalizedMjpegFramerate(NSUInteger framerate)
116
115
  NSError *error;
117
116
  CGFloat compressionQuality = MAX(FBMinCompressionQuality,
118
117
  MIN(FBMaxCompressionQuality, (double)FBConfiguration.sharedInstance.mjpegServerScreenshotQuality / 100.0));
119
- NSData *screenshotData = [FBScreenshot takeInOriginalResolutionWithScreenID:self.mainScreenID
118
+ XCUIScreen *screen = [FBScreen currentScreenWithError:&error];
119
+ NSData *screenshotData = nil == screen ? nil : [FBScreenshot takeInOriginalResolutionWithScreenID:screen.displayID
120
120
  compressionQuality:compressionQuality
121
121
  uti:UTTypeJPEG
122
122
  timeout:FRAME_TIMEOUT
@@ -221,6 +221,8 @@ static NSUInteger FBNormalizedMjpegFramerate(NSUInteger framerate)
221
221
  [self.listeningClients removeObject:client];
222
222
  [self.pendingFrameCounts removeObjectForKey:client];
223
223
  }
224
+ // Nothing is owed to a client that stopped listening, so its socket can go right away.
225
+ nw_connection_cancel(client);
224
226
  [FBLogger log:@"Disconnected a client from screenshots broadcast"];
225
227
  }
226
228
 
@@ -17,6 +17,23 @@ NS_ASSUME_NONNULL_BEGIN
17
17
  */
18
18
  + (nullable NSArray<NSDictionary<NSString *, id> *> *)screensWithError:(NSError **)error;
19
19
 
20
+ /**
21
+ The display with the given identifier
22
+
23
+ @param displayID The display identifier, as returned by screensWithError:
24
+ @param error Set if the display is not available
25
+ @return The matching display or nil
26
+ */
27
+ + (nullable XCUIScreen *)screenWithDisplayID:(long long)displayID error:(NSError **)error;
28
+
29
+ /**
30
+ The display selected by the currentDisplayId setting, or the main display if the setting is not set
31
+
32
+ @param error Set if the selected display is no longer available
33
+ @return The selected display or nil
34
+ */
35
+ + (nullable XCUIScreen *)currentScreenWithError:(NSError **)error;
36
+
20
37
  /**
21
38
  The identifier of the main device's display
22
39
  */
@@ -7,16 +7,32 @@
7
7
  */
8
8
 
9
9
  #import "FBScreen.h"
10
+ #import "FBConfiguration.h"
11
+ #import "FBErrorBuilder.h"
10
12
  #import "XCUIElement+FBIsVisible.h"
11
13
  #import "FBXCodeCompatibility.h"
12
14
  #import "XCUIDevice.h"
13
15
  #import "XCUIScreen.h"
16
+ #import <objc/message.h>
17
+
18
+ static NSArray<XCUIScreen *> *FBScreensForDevice(id device, NSError **error)
19
+ {
20
+ SEL selector = NSSelectorFromString(@"screensOrError:");
21
+ if ([device respondsToSelector:selector]) {
22
+ return ((NSArray<XCUIScreen *> *(*)(id, SEL, NSError **))objc_msgSend)(device, selector, error);
23
+ }
24
+ // Older XCTest versions expose screens on XCUIScreen instead of XCUIDevice.
25
+ if ([XCUIScreen respondsToSelector:NSSelectorFromString(@"screens")]) {
26
+ return XCUIScreen.screens;
27
+ }
28
+ return @[XCUIScreen.mainScreen];
29
+ }
14
30
 
15
31
  @implementation FBScreen
16
32
 
17
33
  + (nullable NSArray<NSDictionary<NSString *, id> *> *)screensWithError:(NSError **)error
18
34
  {
19
- NSArray<XCUIScreen *> *screens = [XCUIDevice.sharedDevice screensOrError:error];
35
+ NSArray<XCUIScreen *> *screens = FBScreensForDevice(XCUIDevice.sharedDevice, error);
20
36
  if (nil == screens) {
21
37
  return nil;
22
38
  }
@@ -40,6 +56,65 @@
40
56
  return result.copy;
41
57
  }
42
58
 
59
+ // Kept separate from sharedDevice so legacy lookup behavior can be tested.
60
+ + (nullable XCUIScreen *)screenWithDisplayID:(long long)displayID
61
+ device:(XCUIDevice *)device
62
+ error:(NSError **)error
63
+ {
64
+ SEL lookupSelector = NSSelectorFromString(@"screenWithDisplayID:orError:");
65
+ BOOL hasNativeLookup = [device respondsToSelector:lookupSelector];
66
+ BOOL needsEnumeratedLookup = !hasNativeLookup;
67
+ NSError *lookupError = nil;
68
+ if (hasNativeLookup) {
69
+ XCUIScreen *screen = ((XCUIScreen *(*)(id, SEL, long long, NSError **))objc_msgSend)(
70
+ device, lookupSelector, displayID, &lookupError);
71
+ if ([screen respondsToSelector:NSSelectorFromString(@"bounds")] && !CGRectIsEmpty(screen.bounds)) {
72
+ return screen;
73
+ }
74
+ // XCTest may return a zero-sized placeholder for an inactive wireless
75
+ // display which screensOrError: omits. Confirm its availability by listing.
76
+ needsEnumeratedLookup = nil != screen;
77
+ }
78
+ // Enumerate only for legacy/placeholder lookups or to enrich a lookup error.
79
+ NSError *enumerationError = nil;
80
+ NSArray<XCUIScreen *> *screens = FBScreensForDevice(device, &enumerationError);
81
+ if (nil == screens) {
82
+ if (NULL != error) {
83
+ *error = lookupError ?: enumerationError;
84
+ }
85
+ return nil;
86
+ }
87
+ NSMutableArray<NSNumber *> *availableIDs = [NSMutableArray arrayWithCapacity:screens.count];
88
+ for (XCUIScreen *screen in screens) {
89
+ if (needsEnumeratedLookup && screen.displayID == displayID) {
90
+ return screen;
91
+ }
92
+ [availableIDs addObject:@(screen.displayID)];
93
+ }
94
+ FBErrorBuilder *builder = [FBErrorBuilder.builder withDescriptionFormat:
95
+ @"No display with id %lld is available. Available display ids: [%@]",
96
+ displayID, [availableIDs componentsJoinedByString:@", "]];
97
+ if (nil != lookupError) {
98
+ [builder withInnerError:lookupError];
99
+ }
100
+ [builder buildError:error];
101
+ return nil;
102
+ }
103
+
104
+ + (nullable XCUIScreen *)screenWithDisplayID:(long long)displayID error:(NSError **)error
105
+ {
106
+ return [self screenWithDisplayID:displayID device:XCUIDevice.sharedDevice error:error];
107
+ }
108
+
109
+ + (nullable XCUIScreen *)currentScreenWithError:(NSError **)error
110
+ {
111
+ NSNumber *displayID = FBConfiguration.sharedInstance.currentDisplayId;
112
+ if (nil == displayID) {
113
+ return XCUIScreen.mainScreen;
114
+ }
115
+ return [self screenWithDisplayID:displayID.longLongValue error:error];
116
+ }
117
+
43
118
  + (long long)displayID
44
119
  {
45
120
  return XCUIScreen.mainScreen.displayID;
@@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
14
14
  @interface FBScreenshot : NSObject
15
15
 
16
16
  /**
17
- Retrieves non-scaled screenshot of the whole screen
17
+ Retrieves non-scaled screenshot of the display selected by the currentDisplayId setting
18
18
 
19
19
  @param quality The number in range 0-3, where 0 is PNG (lossless), 3 is HEIC (lossless), 1- low quality JPEG and 2 - high quality JPEG
20
20
  @param error If there is an error, upon return contains an NSError object that describes the problem.
@@ -24,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN
24
24
  error:(NSError **)error;
25
25
 
26
26
  /**
27
- Retrieves non-scaled screenshot of the whole screen
27
+ Retrieves non-scaled screenshot of the display selected by the currentDisplayId setting
28
28
 
29
29
  @param screenID The screen identifier to take the screenshot from
30
30
  @param compressionQuality Normalized screenshot quality value in range 0..1, where 1 is the best quality
@@ -15,6 +15,7 @@
15
15
  #import "FBImageProcessor.h"
16
16
  #import "FBLogger.h"
17
17
  #import "FBMacros.h"
18
+ #import "FBScreen.h"
18
19
  #import "FBXCodeCompatibility.h"
19
20
  #import "FBXCTestDaemonsProxy.h"
20
21
  #import "XCTMessagingChannel_RunnerToDaemon-Protocol.h"
@@ -60,8 +61,11 @@ NSString *formatTimeInterval(NSTimeInterval interval) {
60
61
  + (NSData *)takeInOriginalResolutionWithQuality:(NSUInteger)quality
61
62
  error:(NSError **)error
62
63
  {
63
- XCUIScreen *mainScreen = XCUIScreen.mainScreen;
64
- return [self.class takeWithScreenID:mainScreen.displayID
64
+ XCUIScreen *screen = [FBScreen currentScreenWithError:error];
65
+ if (nil == screen) {
66
+ return nil;
67
+ }
68
+ return [self.class takeWithScreenID:screen.displayID
65
69
  scale:SCREENSHOT_SCALE
66
70
  compressionQuality:[self.class compressionQualityWithQuality:quality]
67
71
  sourceUTI:[self.class imageUtiWithQuality:quality]
@@ -36,6 +36,7 @@ extern NSString* const FB_SETTING_SCREENSHOT_ORIENTATION;
36
36
  extern NSString* const FB_SETTING_WAIT_FOR_IDLE_TIMEOUT;
37
37
  extern NSString* const FB_SETTING_ANIMATION_COOL_OFF_TIMEOUT;
38
38
  extern NSString* const FB_SETTING_MAX_TYPING_FREQUENCY;
39
+ extern NSString* const FB_SETTING_KEYBOARD_WAIT_TIMEOUT;
39
40
  extern NSString* const FB_SETTING_RESPECT_SYSTEM_ALERTS;
40
41
  extern NSString* const FB_SETTING_USE_CLEAR_TEXT_SHORTCUT;
41
42
  extern NSString* const FB_SETTING_LIMIT_XPATH_CONTEXT_SCOPE;
@@ -46,5 +47,6 @@ extern NSString *const FB_SETTING_INCLUDE_NATIVE_ACCESSIBILITY_ELEMENT_IN_PAGE_S
46
47
  extern NSString *const FB_SETTING_INCLUDE_MIN_MAX_VALUE_IN_PAGE_SOURCE;
47
48
  extern NSString *const FB_SETTING_INCLUDE_CUSTOM_ACTIONS_IN_PAGE_SOURCE;
48
49
  extern NSString *const FB_SETTING_ENFORCE_CUSTOM_SNAPSHOTS;
50
+ extern NSString *const FB_SETTING_CURRENT_DISPLAY_ID;
49
51
 
50
52
  NS_ASSUME_NONNULL_END
@@ -32,6 +32,7 @@ NSString* const FB_SETTING_SCREENSHOT_ORIENTATION = @"screenshotOrientation";
32
32
  NSString* const FB_SETTING_WAIT_FOR_IDLE_TIMEOUT = @"waitForIdleTimeout";
33
33
  NSString* const FB_SETTING_ANIMATION_COOL_OFF_TIMEOUT = @"animationCoolOffTimeout";
34
34
  NSString* const FB_SETTING_MAX_TYPING_FREQUENCY = @"maxTypingFrequency";
35
+ NSString* const FB_SETTING_KEYBOARD_WAIT_TIMEOUT = @"keyboardWaitTimeout";
35
36
  NSString* const FB_SETTING_RESPECT_SYSTEM_ALERTS = @"respectSystemAlerts";
36
37
  NSString* const FB_SETTING_USE_CLEAR_TEXT_SHORTCUT = @"useClearTextShortcut";
37
38
  NSString* const FB_SETTING_LIMIT_XPATH_CONTEXT_SCOPE = @"limitXPathContextScope";
@@ -42,3 +43,4 @@ NSString* const FB_SETTING_INCLUDE_NATIVE_ACCESSIBILITY_ELEMENT_IN_PAGE_SOURCE =
42
43
  NSString* const FB_SETTING_INCLUDE_MIN_MAX_VALUE_IN_PAGE_SOURCE = @"includeMinMaxValueInPageSource";
43
44
  NSString* const FB_SETTING_INCLUDE_CUSTOM_ACTIONS_IN_PAGE_SOURCE = @"includeCustomActionsInPageSource";
44
45
  NSString* const FB_SETTING_ENFORCE_CUSTOM_SNAPSHOTS = @"enforceCustomSnapshots";
46
+ NSString* const FB_SETTING_CURRENT_DISPLAY_ID = @"currentDisplayId";