@devicelab/maestro-runner-darwin-arm64 1.1.29 → 1.1.31

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
@@ -9,9 +9,67 @@
9
9
  #import <XCTest/XCTest.h>
10
10
 
11
11
  #import "FBSession.h"
12
+ #import "FBSession-Private.h"
13
+ #import "FBExceptions.h"
12
14
  #import "FBConfiguration.h"
13
15
  #import "XCUIApplicationDouble.h"
14
16
 
17
+ @interface FBSession (SystemApplicationTests)
18
+ - (BOOL)fb_isApplicationSameAsSystemApp:(XCUIApplication *)application timeout:(NSTimeInterval)timeout;
19
+ @end
20
+
21
+ static id (^FBSystemApplicationLookup)(void);
22
+
23
+ // Run teardown at the exact point where launch has started but the AUT has
24
+ // not yet been published. No wall-clock sleeps or simulator launch timing.
25
+ @interface FBAUTLaunchApplicationDouble : XCUIApplicationDouble
26
+ @property (nonatomic, copy) void (^duringLaunch)(void);
27
+ @property (nonatomic, copy) NSArray *launchArguments;
28
+ @property (nonatomic, copy) NSDictionary *launchEnvironment;
29
+ @property (nonatomic) BOOL didLaunch;
30
+ @end
31
+
32
+ @implementation FBAUTLaunchApplicationDouble
33
+ + (id)fb_systemApplication
34
+ {
35
+ return FBSystemApplicationLookup ? FBSystemApplicationLookup() : nil;
36
+ }
37
+ - (void)launch
38
+ {
39
+ self.didLaunch = YES;
40
+ if (self.duringLaunch) {
41
+ self.duringLaunch();
42
+ }
43
+ }
44
+ - (BOOL)fb_isSameAppAs:(id)application
45
+ {
46
+ return self == application;
47
+ }
48
+ @end
49
+
50
+ @interface FBAUTTerminationFailureApplicationDouble : FBAUTLaunchApplicationDouble
51
+ @property (nonatomic) BOOL didAttemptTermination;
52
+ @end
53
+
54
+ @implementation FBAUTTerminationFailureApplicationDouble
55
+ - (void)terminate
56
+ {
57
+ self.didAttemptTermination = YES;
58
+ @throw [NSException exceptionWithName:@"TerminationFailure" reason:nil userInfo:nil];
59
+ }
60
+ @end
61
+
62
+ @interface FBAUTLaunchSession : FBSession
63
+ @property (nonatomic, strong) FBAUTLaunchApplicationDouble *launchApplication;
64
+ @end
65
+
66
+ @implementation FBAUTLaunchSession
67
+ - (XCUIApplication *)makeApplicationWithBundleId:(NSString *)bundleIdentifier
68
+ {
69
+ return (id)self.launchApplication;
70
+ }
71
+ @end
72
+
15
73
  @interface FBSessionTests : XCTestCase
16
74
  @property (nonatomic, strong) FBSession *session;
17
75
  @property (nonatomic, strong) XCUIApplication *testedApplication;
@@ -31,6 +89,10 @@
31
89
 
32
90
  - (void)tearDown
33
91
  {
92
+ if ([self.session isKindOfClass:FBAUTLaunchSession.class]) {
93
+ ((FBAUTLaunchSession *)self.session).launchApplication.duringLaunch = nil;
94
+ }
95
+ FBSystemApplicationLookup = nil;
34
96
  [self.session kill];
35
97
  FBConfiguration.sharedInstance.shouldTerminateApp = self.shouldTerminateAppValue;
36
98
  [super tearDown];
@@ -64,4 +126,143 @@
64
126
  XCTAssertNil([FBSession activeSession]);
65
127
  }
66
128
 
129
+ - (FBAUTLaunchApplicationDouble *)prepareAUTLaunch
130
+ {
131
+ FBAUTLaunchSession *session = [FBAUTLaunchSession new];
132
+ session.identifier = NSUUID.UUID.UUIDString;
133
+ session.launchApplication = [FBAUTLaunchApplicationDouble new];
134
+ [FBSession markSessionActive:session];
135
+ self.session = session;
136
+ return session.launchApplication;
137
+ }
138
+
139
+ - (void)launchAsAUT
140
+ {
141
+ [self.session launchApplicationWithBundleId:@"com.facebook.awesome"
142
+ shouldWaitForQuiescence:nil
143
+ arguments:nil
144
+ environment:nil
145
+ asApplicationUnderTest:YES];
146
+ }
147
+
148
+ - (void)testCompletedLaunchRegistersAUT
149
+ {
150
+ FBAUTLaunchApplicationDouble *app = [self prepareAUTLaunch];
151
+ XCTAssertNoThrow([self launchAsAUT]);
152
+ XCTAssertEqual([self.session valueForKey:@"testedApplication"], app);
153
+ XCTAssertFalse(app.didTerminate);
154
+ }
155
+
156
+ - (void)testDeletedSessionCleansUpInFlightAUTLaunch
157
+ {
158
+ FBAUTLaunchApplicationDouble *app = [self prepareAUTLaunch];
159
+ FBConfiguration.sharedInstance.shouldTerminateApp = YES;
160
+ FBSession *session = self.session;
161
+ app.duringLaunch = ^{ [session kill]; };
162
+ XCTAssertThrowsSpecificNamed([self launchAsAUT], NSException,
163
+ FBSessionDoesNotExistException);
164
+ XCTAssertTrue(app.didTerminate);
165
+ XCTAssertNil([self.session valueForKey:@"testedApplication"]);
166
+ XCTAssertNil(FBSession.activeSession);
167
+ }
168
+
169
+ - (void)testTerminationFailurePreservesDeletedSessionErrorAndReleasesClaim
170
+ {
171
+ [self prepareAUTLaunch];
172
+ FBAUTTerminationFailureApplicationDouble *app = [FBAUTTerminationFailureApplicationDouble new];
173
+ ((FBAUTLaunchSession *)self.session).launchApplication = app;
174
+ FBConfiguration.sharedInstance.shouldTerminateApp = YES;
175
+ FBSession *session = self.session;
176
+ app.duringLaunch = ^{ [session kill]; };
177
+
178
+ XCTAssertThrowsSpecificNamed([self launchAsAUT], NSException, FBSessionDoesNotExistException);
179
+ XCTAssertTrue(app.didAttemptTermination);
180
+ XCTAssertNil([self.session valueForKey:@"testedApplication"]);
181
+ XCTAssertNil(FBSession.activeSession);
182
+
183
+ // Creating the next session must succeed even when cleanup termination throws.
184
+ // A leaked generation claim would make this fail with a session creation error.
185
+ FBSession *replacement = nil;
186
+ XCTAssertNoThrow(replacement = [FBSession initWithApplication:nil]);
187
+ XCTAssertNotNil(replacement);
188
+ XCTAssertEqual(FBSession.activeSession, replacement);
189
+ [replacement kill];
190
+ }
191
+
192
+ - (void)testDeletedSessionPreservesAppWhenTerminationIsDisabled
193
+ {
194
+ FBAUTLaunchApplicationDouble *app = [self prepareAUTLaunch];
195
+ FBSession *session = self.session;
196
+ app.duringLaunch = ^{ [session kill]; };
197
+ XCTAssertThrowsSpecificNamed([self launchAsAUT], NSException,
198
+ FBSessionDoesNotExistException);
199
+ XCTAssertFalse(app.didTerminate);
200
+ }
201
+
202
+ - (void)testAbandonedAUTLaunchDoesNotTerminateReplacementSessionApp
203
+ {
204
+ FBAUTLaunchApplicationDouble *app = [self prepareAUTLaunch];
205
+ FBConfiguration.sharedInstance.shouldTerminateApp = YES;
206
+ FBSession *session = self.session;
207
+ __block FBSession *replacement;
208
+ app.duringLaunch = ^{
209
+ [session kill];
210
+ replacement = [FBSession initWithApplication:(id)app];
211
+ };
212
+ XCTAssertThrowsSpecificNamed([self launchAsAUT], NSException,
213
+ FBSessionDoesNotExistException);
214
+ XCTAssertFalse(app.didTerminate);
215
+ XCTAssertEqual(FBSession.activeSession, replacement);
216
+ [replacement kill];
217
+ }
218
+
219
+ - (void)testAUTLaunchRejectsAnAlreadyDeletedSession
220
+ {
221
+ FBAUTLaunchApplicationDouble *app = [self prepareAUTLaunch];
222
+ [self.session kill];
223
+ XCTAssertThrowsSpecificNamed([self launchAsAUT], NSException,
224
+ FBSessionDoesNotExistException);
225
+ XCTAssertFalse(app.didLaunch);
226
+ }
227
+
228
+ - (void)testSystemApplicationCheckUsesSuppliedApplication
229
+ {
230
+ FBAUTLaunchApplicationDouble *app = [self prepareAUTLaunch];
231
+ FBSystemApplicationLookup = ^id { return app; };
232
+ XCTAssertTrue([self.session fb_isApplicationSameAsSystemApp:(id)app timeout:1.]);
233
+ FBSystemApplicationLookup = ^id { return nil; };
234
+ XCTAssertFalse([self.session fb_isApplicationSameAsSystemApp:(id)app timeout:1.]);
235
+ }
236
+
237
+ - (void)testSystemApplicationLookupExceptionSkipsTermination
238
+ {
239
+ FBAUTLaunchApplicationDouble *app = [self prepareAUTLaunch];
240
+ FBConfiguration.sharedInstance.shouldTerminateApp = YES;
241
+ FBSession *session = self.session;
242
+ app.duringLaunch = ^{ [session kill]; };
243
+ FBSystemApplicationLookup = ^id {
244
+ @throw [NSException exceptionWithName:@"LookupFailure" reason:nil userInfo:nil];
245
+ };
246
+ XCTAssertThrowsSpecificNamed([self launchAsAUT], NSException, FBSessionDoesNotExistException);
247
+ XCTAssertFalse(app.didTerminate);
248
+ }
249
+
250
+ - (void)testSystemApplicationLookupTimeoutSkipsTermination
251
+ {
252
+ FBAUTLaunchApplicationDouble *app = [self prepareAUTLaunch];
253
+ dispatch_semaphore_t releaseLookup = dispatch_semaphore_create(0);
254
+ XCTestExpectation *finished = [self expectationWithDescription:@"System application lookup finished"];
255
+ FBSystemApplicationLookup = ^id {
256
+ dispatch_semaphore_wait(releaseLookup, DISPATCH_TIME_FOREVER);
257
+ [finished fulfill];
258
+ return nil;
259
+ };
260
+ @try {
261
+ XCTAssertTrue([self.session fb_isApplicationSameAsSystemApp:(id)app timeout:0.01]);
262
+ } @finally {
263
+ dispatch_semaphore_signal(releaseLookup);
264
+ [self waitForExpectationsWithTimeout:5. handler:nil];
265
+ }
266
+ }
267
+
67
268
  @end
@@ -16,11 +16,112 @@
16
16
  #import "FBXCElementSnapshotWrapper+Helpers.h"
17
17
  #import "XCTestPrivateSymbols.h"
18
18
 
19
+ // libxml's node callbacks are thread-local; restore any existing callbacks after each probe.
20
+ static __thread NSUInteger FBReviewCreatedDocs;
21
+ static __thread NSUInteger FBReviewFreedDocs;
22
+ static __thread xmlRegisterNodeFunc FBReviewPreviousRegister;
23
+ static __thread xmlDeregisterNodeFunc FBReviewPreviousDeregister;
24
+
25
+ static void FBReviewRegisterNode(xmlNodePtr node)
26
+ {
27
+ if (node->type == XML_DOCUMENT_NODE) { FBReviewCreatedDocs++; }
28
+ if (NULL != FBReviewPreviousRegister) { FBReviewPreviousRegister(node); }
29
+ }
30
+
31
+ static void FBReviewDeregisterNode(xmlNodePtr node)
32
+ {
33
+ if (node->type == XML_DOCUMENT_NODE) { FBReviewFreedDocs++; }
34
+ if (NULL != FBReviewPreviousDeregister) { FBReviewPreviousDeregister(node); }
35
+ }
36
+
37
+ @interface FBThrowingLabelSnapshot : XCElementSnapshotDouble
38
+ @end
39
+ @implementation FBThrowingLabelSnapshot
40
+ - (NSString *)label
41
+ {
42
+ @throw [NSException exceptionWithName:@"SnapshotAttributeFailure" reason:@"test" userInfo:nil];
43
+ }
44
+ @end
45
+
46
+ @interface FBTraversalSnapshot : XCElementSnapshotDouble
47
+ @property (nonatomic, copy) NSArray *childSnapshots;
48
+ @property (nonatomic, weak) FBTraversalSnapshot *parentSnapshot;
49
+ @property (nonatomic) NSUInteger childrenReads;
50
+ @property (nonatomic) NSUInteger frameReads;
51
+ @end
52
+ @implementation FBTraversalSnapshot
53
+ - (NSArray *)children { self.childrenReads++; return self.childSnapshots ?: @[]; }
54
+ - (id)parent { return self.parentSnapshot; }
55
+ - (CGRect)frame { self.frameReads++; return CGRectMake(1, 2, 3, 4); }
56
+ @end
57
+
19
58
  @interface FBXPathTests : XCTestCase
20
59
  @end
21
60
 
22
61
  @implementation FBXPathTests
23
62
 
63
+ - (void)testSerializationVisitsSiblingListAndFrameOnce
64
+ {
65
+ FBTraversalSnapshot *root = [FBTraversalSnapshot new];
66
+ NSMutableArray *children = [NSMutableArray array];
67
+ for (NSUInteger index = 0; index < 64; index++) {
68
+ FBTraversalSnapshot *child = [FBTraversalSnapshot new];
69
+ child.parentSnapshot = root;
70
+ [children addObject:child];
71
+ }
72
+ root.childSnapshots = children;
73
+ NSString *query = @"//*[@index >= 0 and @x=1 and @y=2 and @width=3 and @height=4]";
74
+ xmlDocPtr doc = [self documentForSnapshot:root query:query];
75
+ @try {
76
+ XCTAssertEqual(root.childrenReads, 1u);
77
+ XCTAssertEqual(root.frameReads, 1u);
78
+ for (FBTraversalSnapshot *child in children) {
79
+ XCTAssertEqual(child.frameReads, 1u);
80
+ }
81
+ XCTAssertEqualObjects([self xpathStringResultForQuery:@"count(//*[@index=63])" document:doc], @"1");
82
+ } @finally {
83
+ xmlFreeDoc(doc);
84
+ }
85
+ }
86
+
87
+ - (void)testDirectXmlWriterPreservesEscapingAndFormatting
88
+ {
89
+ XCElementSnapshotDouble *snapshot = [XCElementSnapshotDouble new];
90
+ snapshot.label = @"line one\nline two\t& < > \" йоло";
91
+ snapshot.value = @"value";
92
+ NSString *expected = [[self xmlStringWithElement:(id)snapshot xpathQuery:nil excludingAttributes:nil]
93
+ stringByReplacingOccurrencesOfString:@" private_indexPath=\"top\"" withString:@""];
94
+ NSString *actual = [FBXPath xmlStringWithRootElement:(id)snapshot options:nil];
95
+ XCTAssertEqualObjects(actual, expected);
96
+ }
97
+
98
+ - (void)testXmlDocumentsAreFreedWhenSnapshotAttributesThrow
99
+ {
100
+ for (NSNumber *isLookup in @[@NO, @YES]) {
101
+ FBReviewCreatedDocs = 0;
102
+ FBReviewFreedDocs = 0;
103
+ FBReviewPreviousRegister = xmlRegisterNodeDefault(FBReviewRegisterNode);
104
+ FBReviewPreviousDeregister = xmlDeregisterNodeDefault(FBReviewDeregisterNode);
105
+ @try {
106
+ id<FBElement> snapshot = (id)[FBThrowingLabelSnapshot new];
107
+ if (isLookup.boolValue) {
108
+ XCTAssertThrowsSpecificNamed([FBXPath matchesWithRootElement:snapshot forQuery:@"//*[@label]"],
109
+ NSException, @"SnapshotAttributeFailure");
110
+ } else {
111
+ XCTAssertThrowsSpecificNamed([FBXPath xmlStringWithRootElement:snapshot options:nil],
112
+ NSException, @"SnapshotAttributeFailure");
113
+ }
114
+ if (isLookup.boolValue) {
115
+ XCTAssertGreaterThan(FBReviewCreatedDocs, 0u);
116
+ }
117
+ XCTAssertEqual(FBReviewCreatedDocs, FBReviewFreedDocs);
118
+ } @finally {
119
+ xmlRegisterNodeDefault(FBReviewPreviousRegister);
120
+ xmlDeregisterNodeDefault(FBReviewPreviousDeregister);
121
+ }
122
+ }
123
+ }
124
+
24
125
  - (NSString *)xmlStringWithElement:(id<FBXCElementSnapshot>)snapshot
25
126
  xpathQuery:(nullable NSString *)query
26
127
  excludingAttributes:(nullable NSArray<NSString *> *)excludedAttributes
@@ -0,0 +1,54 @@
1
+ # Hinge angle
2
+
3
+ These endpoints use runtime capability checks on both simulators and physical
4
+ devices, rather than restricting access to a particular device model or to
5
+ Simulator builds. Reading an angle and injecting a simulated angle have separate
6
+ requirements.
7
+
8
+ `POST /session/:sessionId/wda/device/hingeAngle` with `{"angle": 90}` sends an
9
+ event requesting a simulated hinge angle of 90 degrees. The angle
10
+ must be a finite number from `0` (closed) to `180` (fully open); fractional values
11
+ are supported. POST checks for a hinge and the required IOKit serialization,
12
+ event creation, and dispatch APIs. This permits an injection attempt; it does not
13
+ establish that the device accepts this vendor payload. Physical devices may attempt
14
+ injection when those prerequisites exist, but event acceptance and the resulting
15
+ angle/layout changes on a physical Duo remain unverified. Devices without these
16
+ prerequisites, tvOS, and watchOS return `unsupported operation`.
17
+
18
+ The response confirms event dispatch, not that the device applied the angle.
19
+ Wait for the expected app layout before continuing, since folding completes
20
+ asynchronously. The command does not rotate the device or update
21
+ `currentDisplayId`; select the desired display separately.
22
+
23
+ `GET /session/:sessionId/wda/device/hingeAngle` reads the current angle in degrees
24
+ and returns a numeric value, for example `{"value": 90.5}`. It reads CoreMotion
25
+ rather than remembering the last angle sent by WDA, so changes made outside WDA
26
+ are reflected too. The request waits up to five seconds for a valid reading and
27
+ returns an error if none arrives. GET separately checks for a hinge and the
28
+ CoreMotion subscription APIs; it does not depend on the IOKit injection APIs.
29
+ Devices without these reading prerequisites return `unsupported operation`.
30
+ A reading during a fold may reflect an intermediate angle; it does not wait for
31
+ the transition to finish.
32
+
33
+ Both hinge angle endpoints are also available without a session:
34
+ `GET /wda/device/hingeAngle` and `POST /wda/device/hingeAngle`. They operate on
35
+ the device and do not require creating an Appium session first.
36
+
37
+ ## Validation and feedback
38
+
39
+ Hinge operations have been tested on the iPhone Duo simulator with Xcode 27.1.
40
+ We have not had access to a physical Duo for testing,
41
+ so hinge readings, acceptance of the private HID events, and the resulting
42
+ display/layout behavior on that hardware remain unverified. Loading CoreMotion
43
+ and finding `CMAngleManager` were verified on a physical iPad mini, which reported
44
+ no available hinge. On the same iPad mini running iPadOS 27.0.1, IOKit loading,
45
+ resolution of all four injection functions, payload serialization, vendor-event
46
+ creation, and HID client creation also succeeded. Event dispatch was not called.
47
+ These checks do not validate hinge readings or event acceptance on physical
48
+ hardware with a hinge.
49
+
50
+ If you have access to a physical device with a hinge, we would appreciate your
51
+ help testing these endpoints and [reporting the observed behavior](https://github.com/appium/WebDriverAgent/issues).
52
+ Please include the device model, iOS and Xcode versions, WDA version, requests and
53
+ responses, and any observed angle or display/layout changes. Reports of unsupported
54
+ operations or successful responses with no observable change are useful too.
@@ -0,0 +1,168 @@
1
+ # Multiple displays
2
+
3
+ On devices such as iPhone Duo, `GET /wda/screens` lists the available displays.
4
+ Set `currentDisplayId` through `POST /session/:sessionId/appium/settings` to select
5
+ one of these IDs. Do not assume that the main display is the currently visible
6
+ one after folding or unfolding the device.
7
+
8
+ The setting selects the display for screenshots and MJPEG frames, and passes the
9
+ selected display ID to newly started XCTest screen recordings (see the
10
+ [simulator recording limitation](#xctest-recording-on-the-duo-simulator) below). MJPEG follows setting changes on the existing
11
+ connection; an in-progress recording keeps the display selected when it started.
12
+ An unavailable display causes screenshots and recording requests to fail; MJPEG
13
+ pauses frame delivery until a valid display is selected. Set `currentDisplayId`
14
+ to `null` to restore the main display. WDA does not automatically switch this
15
+ setting when the device changes pose.
16
+
17
+ `GET /wda/screen` also follows `currentDisplayId`: `displayId` and `scale`
18
+ belong to the selected screen, and `screenSize` is its size in logical pixels,
19
+ adjusted to the active application's orientation. Unavailable selections return
20
+ an error. The status bar must belong to that display and be visible at its top.
21
+ Hidden containers and side-mounted status UI do not define a top-edge crop;
22
+ `statusBarSize` is zero when no such bar is present. Clients should refresh this
23
+ information after display, orientation, or status-bar changes.
24
+
25
+ The XCTest point lookup used by WDA has no display argument, including in the
26
+ Xcode 27.1 interfaces checked for this change. When a secondary display is
27
+ selected, WDA uses its active-app fallback instead of that main-display lookup.
28
+ This is not display-specific app filtering: set `defaultActiveApplication` to the
29
+ app's bundle ID when multiple foreground apps make the result ambiguous. An app
30
+ specified when creating the session retains its existing priority.
31
+
32
+ W3C touch actions target the selected display. Viewport, element, and pointer
33
+ origins can be mixed within one action sequence; element offsets remain relative
34
+ to the element's center. Element origins require `currentDisplayId` to match the
35
+ element's display; they do not override the selected display. Key-only and
36
+ pause-only sequences do not require the selected display to be available.
37
+ For an application-element origin on a secondary display, compute offsets from
38
+ its returned element rect, just as for any other element. XCTest may report that
39
+ rect with dimensions transposed relative to the application's window; WDA uses
40
+ the returned rect's center plus the requested offset as a viewport point and
41
+ applies the selected-display correction. Do not substitute the window's center
42
+ when calculating an offset for an application-element origin. This is separate
43
+ from the older-SDK compatibility issue below. Main-display application origins
44
+ retain their existing behavior.
45
+ Element scrolling uses the visible area in the same
46
+ coordinate space as the element frame, including on rotated main and secondary displays.
47
+ WDA scrolling rejects an unavailable selected display instead of using an
48
+ uncorrected frame. Each normalized scroll keeps its converted frame and vector
49
+ in the same coordinate space across its drags. Keep the device orientation and
50
+ fold state stable until the command completes; change the display setting between
51
+ commands. Scroll-to-visible takes a fresh parent snapshot for each scroll step.
52
+
53
+ Native application-root coordinates on a secondary display require a
54
+ non-empty application window on that display as a coordinate anchor. Edge and
55
+ outside coordinates are preserved, as on the main display; XCTest determines
56
+ how the gesture is delivered. No hit testing or clamping is performed here.
57
+ Requests fail if no such window exists on the selected display.
58
+
59
+ This gesture support builds on [#1269](https://github.com/appium/WebDriverAgent/pull/1269).
60
+
61
+ ## Legacy SDK compatibility windows
62
+
63
+ “Legacy SDK” here means the SDK used to build the app: the tested older app was
64
+ built with **iOS SDK 26.5 (Xcode 26.6)** and ran in a compatibility window on
65
+ iPhone Duo. It does not refer to the device's iOS version or the SDK used to build
66
+ WDA. See Apple's [SDK-dependent layout guidance](https://developer.apple.com/videos/play/tech-talks/111461/).
67
+
68
+ On the **iPhone Duo simulator, iOS 27.1 (24A94401), with an Xcode 27.2 beta 2
69
+ UI-test runner**, direct XCTest element and coordinate taps failed to deliver
70
+ input to that app in fully open and some intermediate poses. The same app source
71
+ built with **SDK 27.2** received the taps. The failure reproduced without WDA;
72
+ it is a known limitation observed in the Apple compatibility-layout/XCTest path,
73
+ although the failing internal component has not been identified. Physical devices
74
+ and other SDK/runtime combinations remain unverified.
75
+
76
+ Select the app's `currentDisplayId` explicitly, but be aware that selecting the
77
+ correct display does not resolve this issue. WDA does not apply a compatibility
78
+ coordinate correction. Rebuilding with SDK 27.2 avoided the failure in the tested
79
+ app; for diagnosis, compare direct XCTest taps with app-recorded input.
80
+
81
+ ## XCTest recording on the Duo simulator
82
+
83
+ On **iOS 27.1 (24A94401), with Xcode 27.2 beta 2**, the native XCTest
84
+ recording path produced black video for the closed Duo's outer display.
85
+ This reproduced after starting WDA afresh while the device was closed, as well
86
+ as after switching from the inner display back to the outer display.
87
+
88
+ The recorded native `XCTScreenRecordingRequest.screenID` was `1` for the outer
89
+ screen and `3` for the inner screen, matching the selected IDs returned by
90
+ `GET /wda/screens`. Simultaneous outer-display screenshots were correct at
91
+ 1398 x 2034 pixels. Nevertheless, each four-second outer recording in the fresh
92
+ runner experiment contained only one black frame at 2006 x 2852 pixels; the
93
+ inner-display recording contained visible content. A successful start/stop
94
+ response is therefore insufficient to verify captured video.
95
+
96
+ The failure occurs downstream of the selected ID being supplied to XCTest;
97
+ this experiment does not identify the failing internal Apple component or
98
+ establish behavior on other runtimes or physical devices. WDA does not remap
99
+ display IDs to work around it. Use the MJPEG/ffmpeg recording path when affected;
100
+ that path was verified across closed, book, and fully open poses with both MJPEG
101
+ and H.264 output. Set `mjpegFixOrientation` to `true` if upright encoded MJPEG
102
+ pixels are required: the default preserves the existing EXIF-oriented output.
103
+
104
+ ## Waiting for rotated layouts
105
+
106
+ An application's reported interface orientation can update before its element
107
+ frames settle. On an iPad simulator running iOS 26.5, the gesture fixture's Alerts
108
+ button briefly retained transposed dimensions immediately after rotation;
109
+ resolving an element-origin gesture during that interval missed the button.
110
+ The same failure reproduced on the unmodified WDA baseline. Waiting for the
111
+ fixture's expected landscape geometry resolved it; direct XCTest gesture
112
+ controls also passed. Tests should wait for the application's expected layout
113
+ before resolving coordinates, in addition to keeping its pose stable during a
114
+ command. This observation does not imply that every missed gesture is an
115
+ XCTest defect.
116
+
117
+ ## Home button on Duo devices
118
+
119
+ On the Duo simulator running iOS 27.1, XCTest's Home button and its IOHID
120
+ wrapper can return success without leaving the foreground application. WDA
121
+ uses a directly dispatched IOKit Home key-down/key-up pair for Home requests
122
+ on iOS devices with an available hinge sensor, including simulators and physical
123
+ devices. Devices without a hinge retain the XCTest path. The direct path sends button events;
124
+ it does not substitute application activation or change the selected display.
125
+
126
+ The Home key event has been verified on the Duo simulator; physical Duo Home
127
+ behavior remains unverified and feedback is welcome. Availability of IOKit APIs
128
+ and successful event dispatch do not by themselves confirm that the app left
129
+ the foreground.
130
+
131
+ This workaround does not address rotation or input immediately after an
132
+ application is reactivated. See [Appium #22857](https://github.com/appium/appium/issues/22857).
133
+
134
+ ## Validation and feedback
135
+
136
+ Duo display behavior has been tested on the iPhone Duo simulator with Xcode 27.1
137
+ and Xcode 27.2 beta 2.
138
+ We have not had access to a physical Duo for testing. If you have access to one,
139
+ we would appreciate [reports of its display and capture behavior](https://github.com/appium/WebDriverAgent/issues),
140
+ including the device model, iOS/Xcode and WDA versions, selected display IDs,
141
+ requests and responses, and observed results.
142
+
143
+ ### Rotation on Duo devices
144
+
145
+ On Duo Simulator 27.1, assigning `XCUIDevice.orientation` can update XCTest's
146
+ reported orientation without rotating the application. On Duo simulators with
147
+ an available hinge sensor, WDA directly dispatches the vendor-defined
148
+ `orientation-picker-control` event used by Xcode 27.1's Duo controls, while also
149
+ keeping XCTest's reported device orientation in sync.
150
+
151
+ On physical devices with an available hinge sensor, WDA first uses XCTest and
152
+ checks the application's interface orientation after waiting for stability.
153
+ Only if that check fails does WDA log the transition to the vendor HID fallback
154
+ and attempt it. Devices without a hinge retain the XCTest path.
155
+
156
+ The vendor event has only been verified on the Duo simulator. Its
157
+ `com.apple.Virtualization.VirtualMachines` receiver may not exist on physical
158
+ hardware. Physical-device feedback is welcome; availability of the IOKit APIs
159
+ or successful event submission does not establish that rotation works.
160
+
161
+ Select the active panel with `currentDisplayId` before rotating. The inner
162
+ panel's native orientation is a quarter turn from the cover panel, so WDA
163
+ translates the requested orientation for the selected display. Both orientation
164
+ and numeric rotation commands use the same path. WDA waits up to five seconds
165
+ for the application's interface orientation to match, then waits for stability;
166
+ event submission alone is not success. An app or panel that does not support
167
+ the requested orientation still returns an error. In particular, the tested
168
+ cover panel did not adopt upside-down portrait.
@@ -4,8 +4,7 @@ import {WebDriverProxy} from '@appium/base-driver';
4
4
  import {strongbox} from '@appium/strongbox';
5
5
  import {fs, util} from '@appium/support';
6
6
  import type {AppiumLogger, StringRecord} from '@appium/types';
7
- import AsyncLock from 'async-lock';
8
- import {waitForCondition} from 'asyncbox';
7
+ import {AsyncLock, waitForCondition} from 'asyncbox';
9
8
 
10
9
  import {WDA_RUNNER_BUNDLE_ID, WDA_BASE_URL, DEFAULT_TEST_BUNDLE_SUFFIX} from './constants.js';
11
10
  import {log as defaultLogger} from './logger.js';