@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
@@ -9,7 +9,8 @@
9
9
  #import "FBElementCommands.h"
10
10
 
11
11
  #import "FBConfiguration.h"
12
- #import "FBKeyboard.h"
12
+ #import "FBScreen.h"
13
+ #import "XCUIScreen.h"
13
14
  #import "FBRoute.h"
14
15
  #import "FBRouteRequest.h"
15
16
  #import "FBRunLoopSpinner.h"
@@ -22,6 +23,7 @@
22
23
  #import "FBRuntimeUtils.h"
23
24
  #import "NSPredicate+FBFormat.h"
24
25
  #import "XCTestPrivateSymbols.h"
26
+ #import "XCUIApplication+FBTouchAction.h"
25
27
  #import "XCUICoordinate.h"
26
28
  #import "XCUIDevice.h"
27
29
  #import "XCUIElement+FBIsVisible.h"
@@ -370,10 +372,14 @@
370
372
  if (nil == endCoordinate) {
371
373
  return FBResponseWithStatus([FBCommandStatus invalidElementStateErrorWithMessage:error.description traceback:nil]);
372
374
  }
373
- [startCoordinate pressForDuration:[request.arguments[@"pressDuration"] doubleValue]
374
- thenDragToCoordinate:endCoordinate
375
- withVelocity:[request.arguments[@"velocity"] doubleValue]
376
- thenHoldForDuration:[request.arguments[@"holdDuration"] doubleValue]];
375
+ if (![application fb_pressAtCoordinate:startCoordinate
376
+ forDuration:[request.arguments[@"pressDuration"] doubleValue]
377
+ thenDragToCoordinate:endCoordinate
378
+ withVelocity:[request.arguments[@"velocity"] doubleValue]
379
+ thenHoldForDuration:[request.arguments[@"holdDuration"] doubleValue]
380
+ error:&error]) {
381
+ return FBResponseWithStatus([FBCommandStatus invalidElementStateErrorWithMessage:error.description traceback:nil]);
382
+ }
377
383
  return FBResponseWithOK();
378
384
  }
379
385
 
@@ -456,7 +462,17 @@
456
462
  return FBResponseWithStatus([FBCommandStatus invalidElementStateErrorWithMessage:error.description traceback:nil]);
457
463
  }
458
464
  NSTimeInterval duration = [request.arguments[@"duration"] doubleValue];
459
- [startCoordinate pressForDuration:duration thenDragToCoordinate:endCoordinate];
465
+ XCUIApplication *application = [target isKindOfClass:XCUIApplication.class]
466
+ ? (XCUIApplication *)target
467
+ : target.application;
468
+ if (![application fb_pressAtCoordinate:startCoordinate
469
+ forDuration:duration
470
+ thenDragToCoordinate:endCoordinate
471
+ withVelocity:XCUIGestureVelocityDefault
472
+ thenHoldForDuration:0
473
+ error:&error]) {
474
+ return FBResponseWithStatus([FBCommandStatus invalidElementStateErrorWithMessage:error.description traceback:nil]);
475
+ }
460
476
  return FBResponseWithOK();
461
477
  }
462
478
 
@@ -539,6 +555,13 @@
539
555
  NSString *textToType = [request.arguments[@"value"] componentsJoinedByString:@""];
540
556
  NSUInteger frequency = [request.arguments[@"frequency"] unsignedIntegerValue] ?: FBConfiguration.sharedInstance.maxTypingFrequency;
541
557
  NSError *error;
558
+ #if TARGET_OS_IOS
559
+ NSTimeInterval keyboardWait = FBConfiguration.sharedInstance.keyboardWaitTimeout;
560
+ if (keyboardWait > 0) {
561
+ XCUIApplication *application = request.session.activeApplication ?: XCUIApplication.fb_activeApplication;
562
+ FBWaitForHittableKey(application, keyboardWait);
563
+ }
564
+ #endif
542
565
  if (!FBTypeText(textToType, frequency, &error)) {
543
566
  return FBResponseWithStatus([FBCommandStatus invalidElementStateErrorWithMessage:error.description
544
567
  traceback:nil]);
@@ -683,7 +706,34 @@ static const NSInteger DEFAULT_MAX_PICKER_ATTEMPTS = 25;
683
706
  element:(XCUIElement *)element
684
707
  error:(NSError **)error
685
708
  {
686
- return FBCoordinateWithAnchorOffset(element, CGVectorMake(0, 0), offset, error);
709
+ if (element.elementType != XCUIElementTypeApplication) {
710
+ return FBCoordinateWithAnchorOffset(element, CGVectorMake(0, 0), offset, error);
711
+ }
712
+ XCUIScreen *screen = [FBScreen currentScreenWithError:error];
713
+ if (nil == screen) {
714
+ return nil;
715
+ }
716
+ if (screen.displayID == XCUIScreen.mainScreen.displayID) {
717
+ return FBCoordinateWithAnchorOffset(element, CGVectorMake(0, 0), offset, error);
718
+ }
719
+ // Application-root coordinates are rotated using the main display on Duo.
720
+ // A window coordinate keeps XCTest's display affinity and rotation intact.
721
+ // It is only a coordinate anchor, not a hit-test boundary. Preserve edge and
722
+ // outside offsets just as on the main display; XCTest handles their delivery.
723
+ CGPoint point = CGPointMake(offset.dx, offset.dy);
724
+ for (XCUIElement *window in [element childrenMatchingType:XCUIElementTypeWindow].allElementsBoundByIndex) {
725
+ // Resolve the window before reading its display affinity: an unresolved
726
+ // element may report displayID == 0.
727
+ (void)window.frame;
728
+ CGRect frame = window.wdFrame;
729
+ if (window.screen.displayID == screen.displayID && !CGRectIsEmpty(frame)) {
730
+ return FBCoordinateWithAnchorOffset(window, CGVectorMake(0, 0),
731
+ CGVectorMake(point.x - frame.origin.x, point.y - frame.origin.y), error);
732
+ }
733
+ }
734
+ [[[FBErrorBuilder builder] withDescriptionFormat:@"No non-empty application window on display %lld is available as a coordinate anchor", screen.displayID]
735
+ buildError:error];
736
+ return nil;
687
737
  }
688
738
 
689
739
  /**
@@ -131,7 +131,8 @@
131
131
  [request.session launchApplicationWithBundleId:(id)request.arguments[@"bundleId"]
132
132
  shouldWaitForQuiescence:request.arguments[@"shouldWaitForQuiescence"]
133
133
  arguments:request.arguments[@"arguments"]
134
- environment:request.arguments[@"environment"]];
134
+ environment:request.arguments[@"environment"]
135
+ asApplicationUnderTest:[request.arguments[@"asAppUnderTest"] boolValue]];
135
136
  return FBResponseWithOK();
136
137
  }
137
138
 
@@ -410,7 +411,7 @@
410
411
  if (nil != initialUrl) {
411
412
  return [self openDeepLink:initialUrl withApplication:bundleID timeout:nil];
412
413
  }
413
- [app activate];
414
+ [app fb_activate];
414
415
  return nil;
415
416
  }
416
417
 
@@ -15,11 +15,11 @@
15
15
  <key>CFBundlePackageType</key>
16
16
  <string>FMWK</string>
17
17
  <key>CFBundleShortVersionString</key>
18
- <string>16.12.8</string>
18
+ <string>16.17.5</string>
19
19
  <key>CFBundleSignature</key>
20
20
  <string>????</string>
21
21
  <key>CFBundleVersion</key>
22
- <string>16.12.8</string>
22
+ <string>16.17.5</string>
23
23
  <key>NSPrincipalClass</key>
24
24
  <string/>
25
25
  </dict>
@@ -13,6 +13,7 @@
13
13
  #import "FBLogger.h"
14
14
  #import "FBResponsePayload.h"
15
15
  #import "FBTCPSocket.h"
16
+ #import "FBSession.h"
16
17
 
17
18
  static NSData *FBCRLFCRLFData(void)
18
19
  {
@@ -131,10 +132,6 @@ static BOOL FBParseContentLength(NSString *value, NSUInteger *outLength)
131
132
  // standalone or not (except DELETE /session itself - see -dispatchMethod:). See
132
133
  // -abandonPendingRequestsForSessionID:. Guarded by @synchronized(self.pendingSessionRequests).
133
134
  @property (nonatomic, strong) NSMutableDictionary<NSString *, NSMutableSet<FBPendingRequest *> *> *pendingSessionRequests;
134
- // Already-abandoned sessions mapped to the response they were abandoned with, so a request parsed
135
- // after that point is answered at once instead of queueing for a session that is gone. Kept for
136
- // the server's lifetime; ids are UUIDs. Guarded by @synchronized(self.pendingSessionRequests).
137
- @property (nonatomic, strong) NSMutableDictionary<NSString *, RouteResponse *> *abandonedSessionResponses;
138
135
  // When each connection started waiting for its current request. The reaper closes connections
139
136
  // whose entry outlives FBIncompleteRequestTimeout; idle keep-alive connections have no entry and
140
137
  // are exempt. Guarded by @synchronized(self.connectionBuffers).
@@ -163,7 +160,6 @@ static const int64_t FBStaleConnectionSweepIntervalSec = 10;
163
160
  _connectionsAwaitingResponse = [NSMutableSet set];
164
161
  _standaloneWaiters = [NSMutableDictionary dictionary];
165
162
  _pendingSessionRequests = [NSMutableDictionary dictionary];
166
- _abandonedSessionResponses = [NSMutableDictionary dictionary];
167
163
  _incompleteRequestStarts = [NSMapTable mapTableWithKeyOptions:(NSPointerFunctionsOptions)(NSMapTableObjectPointerPersonality | NSMapTableStrongMemory)
168
164
  valueOptions:(NSPointerFunctionsOptions)NSMapTableStrongMemory];
169
165
  }
@@ -340,12 +336,28 @@ static const int64_t FBStaleConnectionSweepIntervalSec = 10;
340
336
 
341
337
  - (void)didClientDisconnect:(nw_connection_t)client
342
338
  {
343
- @synchronized (self.connectionBuffers) {
344
- [self.connectionBuffers removeObjectForKey:client];
345
- [self.pendingRequestHeaders removeObjectForKey:client];
346
- [self.connectionsAwaitingResponse removeObject:client];
347
- [self.incompleteRequestStarts removeObjectForKey:client];
348
- }
339
+ // EOF must follow every receive already queued for this client. Otherwise it can
340
+ // remove the buffer before those bytes have even been appended and parsed.
341
+ __weak typeof(self) weakSelf = self;
342
+ dispatch_async(self.bufferProcessingQueue, ^{
343
+ __strong typeof(weakSelf) strongSelf = weakSelf;
344
+ if (nil == strongSelf) {
345
+ nw_connection_cancel(client);
346
+ return;
347
+ }
348
+ BOOL isAwaitingResponse;
349
+ @synchronized (strongSelf.connectionBuffers) {
350
+ isAwaitingResponse = [strongSelf.connectionsAwaitingResponse containsObject:client];
351
+ [strongSelf.connectionBuffers removeObjectForKey:client];
352
+ [strongSelf.pendingRequestHeaders removeObjectForKey:client];
353
+ [strongSelf.incompleteRequestStarts removeObjectForKey:client];
354
+ }
355
+ // A client that only stopped sending may still read the response to its in-flight request, so
356
+ // that response's send completion closes the connection instead (it finds no buffer left).
357
+ if (!isAwaitingResponse) {
358
+ nw_connection_cancel(client);
359
+ }
360
+ });
349
361
  }
350
362
 
351
363
  - (void)client:(nw_connection_t)client didReceiveData:(NSData *)data
@@ -723,9 +735,15 @@ static const int64_t FBStaleConnectionSweepIntervalSec = 10;
723
735
  - (nullable RouteResponse *)trackPendingRequest:(FBPendingRequest *)pendingRequest forSessionID:(NSString *)sessionID
724
736
  {
725
737
  @synchronized (self.pendingSessionRequests) {
726
- RouteResponse *abandonedResponse = self.abandonedSessionResponses[sessionID];
727
- if (nil != abandonedResponse) {
728
- return abandonedResponse;
738
+ // Checking the current session under this lock closes the admission/teardown
739
+ // race without retaining a tombstone and a full response for every past UUID.
740
+ // -kill clears the active session before posting its abandonment notification.
741
+ if (nil == [FBSession sessionWithIdentifier:sessionID]) {
742
+ RouteResponse *response = [RouteResponse new];
743
+ [FBResponseWithStatus([FBCommandStatus noSuchDriverErrorWithMessage:@"Session does not exist"
744
+ traceback:nil]) dispatchWithResponse:response];
745
+ [self applyDefaultHeadersToResponse:response];
746
+ return response;
729
747
  }
730
748
  NSMutableSet<FBPendingRequest *> *pendingRequests = self.pendingSessionRequests[sessionID];
731
749
  if (nil == pendingRequests) {
@@ -760,8 +778,6 @@ static const int64_t FBStaleConnectionSweepIntervalSec = 10;
760
778
  @synchronized (self.pendingSessionRequests) {
761
779
  pendingRequests = [self.pendingSessionRequests[sessionID] copy];
762
780
  [self.pendingSessionRequests removeObjectForKey:sessionID];
763
- // Recorded before the lock is dropped, so requests admitted from here on are rejected.
764
- self.abandonedSessionResponses[sessionID] = response;
765
781
  }
766
782
  for (FBPendingRequest *pendingRequest in pendingRequests) {
767
783
  [self writeResponse:response toClient:pendingRequest.client];
@@ -881,14 +897,21 @@ static const int64_t FBStaleConnectionSweepIntervalSec = 10;
881
897
  if (nil == queuedSelf) {
882
898
  return;
883
899
  }
900
+ BOOL isDisconnected;
884
901
  @synchronized (queuedSelf.connectionBuffers) {
885
902
  [queuedSelf.connectionsAwaitingResponse removeObject:client];
903
+ isDisconnected = nil == [queuedSelf.connectionBuffers objectForKey:client];
886
904
  // Mid-request connections get their window from when parsing could resume, not from the
887
905
  // previous request. Absent entries stay absent, so idle keep-alives remain exempt.
888
906
  if (nil != [queuedSelf.incompleteRequestStarts objectForKey:client]) {
889
907
  [queuedSelf.incompleteRequestStarts setObject:[NSDate date] forKey:client];
890
908
  }
891
909
  }
910
+ if (isDisconnected) {
911
+ // The peer stopped sending while this request was in flight - see -didClientDisconnect:.
912
+ [queuedSelf closeClient:client];
913
+ return;
914
+ }
892
915
  [queuedSelf processBufferForClient:client];
893
916
  });
894
917
  }];
@@ -9,6 +9,7 @@
9
9
  #import "FBScreenRecordingRequest.h"
10
10
 
11
11
  #import "FBErrorBuilder.h"
12
+ #import "FBScreen.h"
12
13
  #import "XCUIScreen.h"
13
14
 
14
15
  @implementation FBScreenRecordingRequest
@@ -56,6 +57,10 @@
56
57
 
57
58
  - (id)toNativeRequestWithError:(NSError **)error
58
59
  {
60
+ XCUIScreen *screen = [FBScreen currentScreenWithError:error];
61
+ if (nil == screen) {
62
+ return nil;
63
+ }
59
64
  Class screenRecordingRequestClass = NSClassFromString(@"XCTScreenRecordingRequest");
60
65
  if (nil == screenRecordingRequestClass) {
61
66
  [[[FBErrorBuilder builder]
@@ -80,8 +85,8 @@
80
85
  NSMethodSignature *screenRecordingRequestContructorSignature = [screenRecordingRequestAllocated methodSignatureForSelector:screenRecordingRequestConstructorSelector];
81
86
  NSInvocation *screenRecordingRequestInitInvocation = [NSInvocation invocationWithMethodSignature:screenRecordingRequestContructorSignature];
82
87
  [screenRecordingRequestInitInvocation setSelector:screenRecordingRequestConstructorSelector];
83
- long long mainScreenId = XCUIScreen.mainScreen.displayID;
84
- [screenRecordingRequestInitInvocation setArgument:&mainScreenId atIndex:2];
88
+ long long screenId = screen.displayID;
89
+ [screenRecordingRequestInitInvocation setArgument:&screenId atIndex:2];
85
90
  CGRect fullScreenRect = CGRectNull;
86
91
  [screenRecordingRequestInitInvocation setArgument:&fullScreenRect atIndex:3];
87
92
  [screenRecordingRequestInitInvocation setArgument:&videoEncoding atIndex:4];
@@ -107,6 +107,28 @@ extern NSString* const FBSessionWasKilledNotification;
107
107
  arguments:(nullable NSArray<NSString *> *)arguments
108
108
  environment:(nullable NSDictionary <NSString *, NSString *> *)environment;
109
109
 
110
+ /**
111
+ Launch an application with given bundle identifier in scope of current session
112
+ and optionally make it the application under test of this session.
113
+
114
+ @param bundleIdentifier Valid bundle identifier of the application to be launched
115
+ @param shouldWaitForQuiescence whether to wait for quiescence on application startup.
116
+ If not provided then the setting of the current application under test is inherited.
117
+ If there is no application under test yet then quiescence is only awaited if asApplicationUnderTest is YES
118
+ @param arguments The optional array of application command line arguments. The arguments are going to be applied if the application was not running before.
119
+ @param environment The optional dictionary of environment variables for the application, which is going to be executed. The environment variables are going to be applied if the application was not running before.
120
+ @param asApplicationUnderTest If YES then the launched application replaces the current application under test
121
+ of the session (if any), the same way as if it was launched on session startup: other applications launched later
122
+ inherit its quiescence setting, system alerts over it are respected, its unexpected termination is reported as a crash
123
+ and it gets terminated on session deletion if shouldTerminateApp is enabled
124
+ @return The application instance
125
+ */
126
+ - (XCUIApplication *)launchApplicationWithBundleId:(NSString *)bundleIdentifier
127
+ shouldWaitForQuiescence:(nullable NSNumber *)shouldWaitForQuiescence
128
+ arguments:(nullable NSArray<NSString *> *)arguments
129
+ environment:(nullable NSDictionary <NSString *, NSString *> *)environment
130
+ asApplicationUnderTest:(BOOL)asApplicationUnderTest;
131
+
110
132
  /**
111
133
  Activate an application with given bundle identifier in scope of current session.
112
134
  !This method is only available since Xcode9 SDK
@@ -24,6 +24,7 @@
24
24
  #import "FBXCTestDaemonsProxy.h"
25
25
  #import "XCUIApplication+FBQuiescence.h"
26
26
  #import "XCUIElement.h"
27
+ #import "XCUIElement+FBUtilities.h"
27
28
 
28
29
  /*!
29
30
  The intial value for the default application property.
@@ -43,13 +44,14 @@ static const NSTimeInterval FB_KILL_WAIT_TIMEOUT_SEC = 35.;
43
44
  NSString *const FBSessionWasKilledNotification = @"FBSessionWasKilledNotification";
44
45
 
45
46
  @interface FBSession ()
46
- @property (nullable, nonatomic) XCUIApplication *testedApplication;
47
+ // Atomic, since /wda/apps/launch may replace it while a concurrent DELETE /session reads it in -kill
48
+ @property (nullable, atomic) XCUIApplication *testedApplication;
47
49
  @property (nonatomic) BOOL isTestedApplicationExpectedToRun;
48
50
  @property (nonatomic) BOOL shouldAppsWaitForQuiescence;
49
51
  @property (nonatomic, nullable) FBAlertsMonitor *alertsMonitor;
50
52
  @property (nonatomic, readwrite) NSMutableDictionary<NSNumber *, NSMutableDictionary<NSString *, NSNumber *> *> *elementsVisibilityCache;
51
53
 
52
- - (BOOL)fb_isTestedApplicationSameAsSystemAppWithTimeout:(NSTimeInterval)timeout;
54
+ - (BOOL)fb_isApplicationSameAsSystemApp:(XCUIApplication *)application timeout:(NSTimeInterval)timeout;
53
55
  - (void)fb_terminateTestedApplicationWithTimeout:(NSTimeInterval)timeout generation:(NSUInteger)generation;
54
56
  @end
55
57
 
@@ -332,7 +334,7 @@ static NSUInteger _committedTerminationCount = 0;
332
334
  if (nil != self.testedApplication
333
335
  && FBConfiguration.sharedInstance.shouldTerminateApp
334
336
  && self.testedApplication.running
335
- && ![self fb_isTestedApplicationSameAsSystemAppWithTimeout:FB_IS_SYSTEM_APP_CHECK_TIMEOUT_SEC]) {
337
+ && ![self fb_isApplicationSameAsSystemApp:self.testedApplication timeout:FB_IS_SYSTEM_APP_CHECK_TIMEOUT_SEC]) {
336
338
  // Blocks until the app is either actually terminated or durably given up on (never left
337
339
  // pending) - see -fb_terminateTestedApplicationWithTimeout:generation: - so it's safe to
338
340
  // report this teardown as finished as soon as this returns.
@@ -387,10 +389,42 @@ static NSUInteger _committedTerminationCount = 0;
387
389
  arguments:(nullable NSArray<NSString *> *)arguments
388
390
  environment:(nullable NSDictionary <NSString *, NSString *> *)environment
389
391
  {
392
+ return [self launchApplicationWithBundleId:bundleIdentifier
393
+ shouldWaitForQuiescence:shouldWaitForQuiescence
394
+ arguments:arguments
395
+ environment:environment
396
+ asApplicationUnderTest:NO];
397
+ }
398
+
399
+ - (XCUIApplication *)launchApplicationWithBundleId:(NSString *)bundleIdentifier
400
+ shouldWaitForQuiescence:(nullable NSNumber *)shouldWaitForQuiescence
401
+ arguments:(nullable NSArray<NSString *> *)arguments
402
+ environment:(nullable NSDictionary <NSString *, NSString *> *)environment
403
+ asApplicationUnderTest:(BOOL)asApplicationUnderTest
404
+ {
405
+ NSCondition *condition = self.class.teardownCondition;
406
+ NSUInteger generation = 0;
407
+ BOOL shouldTerminateApp = NO;
408
+ if (asApplicationUnderTest) {
409
+ [condition lock];
410
+ BOOL isActive = self == _activeSession;
411
+ generation = _sessionGeneration;
412
+ shouldTerminateApp = FBConfiguration.sharedInstance.shouldTerminateApp;
413
+ [condition unlock];
414
+ if (!isActive) {
415
+ @throw [NSException exceptionWithName:FBSessionDoesNotExistException
416
+ reason:@"Session does not exist"
417
+ userInfo:nil];
418
+ }
419
+ }
420
+
390
421
  XCUIApplication *app = [self makeApplicationWithBundleId:bundleIdentifier];
391
422
  if (nil == shouldWaitForQuiescence) {
392
- // Iherit the quiescence check setting from the main app under test by default
393
- app.fb_shouldWaitForQuiescence = nil != self.testedApplication && self.shouldAppsWaitForQuiescence;
423
+ // Iherit the quiescence check setting from the main app under test by default.
424
+ // The first app under test of the session waits for quiescence, as it would on session startup
425
+ app.fb_shouldWaitForQuiescence = nil == self.testedApplication
426
+ ? asApplicationUnderTest
427
+ : self.shouldAppsWaitForQuiescence;
394
428
  } else {
395
429
  app.fb_shouldWaitForQuiescence = [shouldWaitForQuiescence boolValue];
396
430
  }
@@ -399,7 +433,39 @@ static NSUInteger _committedTerminationCount = 0;
399
433
  app.launchEnvironment = environment ?: @{};
400
434
  [app launch];
401
435
  } else {
402
- [app activate];
436
+ [app fb_activate];
437
+ }
438
+ if (asApplicationUnderTest) {
439
+ BOOL waitForQuiescence = app.fb_shouldWaitForQuiescence;
440
+ // A DELETE can finish while launch/activate is still waiting for XCTest.
441
+ // Publish the AUT under the same lock that -kill uses to clear the session.
442
+ [condition lock];
443
+ BOOL isActive = self == _activeSession && generation == _sessionGeneration;
444
+ if (isActive) {
445
+ self.testedApplication = app;
446
+ self.shouldAppsWaitForQuiescence = waitForQuiescence;
447
+ }
448
+ [condition unlock];
449
+ if (!isActive) {
450
+ // The HTTP request may already have been abandoned, but its handler keeps
451
+ // running. Clean up only while this launch still owns the generation;
452
+ // a replacement session may already be using the same bundle identifier.
453
+ if (shouldTerminateApp
454
+ && ![self fb_isApplicationSameAsSystemApp:app timeout:FB_IS_SYSTEM_APP_CHECK_TIMEOUT_SEC]
455
+ && [self.class beginTerminationForGeneration:generation]) {
456
+ @try {
457
+ [app terminate];
458
+ } @catch (NSException *e) {
459
+ [FBLogger logFmt:@"%@", e.description];
460
+ } @finally {
461
+ [self.class endTermination];
462
+ }
463
+ }
464
+ @throw [NSException exceptionWithName:FBSessionDoesNotExistException
465
+ reason:@"Session was deleted during application launch"
466
+ userInfo:nil];
467
+ }
468
+ [FBLogger logFmt:@"The application '%@' is set as the application under test", bundleIdentifier];
403
469
  }
404
470
  if ([app fb_isSameAppAs:self.testedApplication]) {
405
471
  self.isTestedApplicationExpectedToRun = YES;
@@ -410,7 +476,7 @@ static NSUInteger _committedTerminationCount = 0;
410
476
  - (XCUIApplication *)activateApplicationWithBundleId:(NSString *)bundleIdentifier
411
477
  {
412
478
  XCUIApplication *app = [self makeApplicationWithBundleId:bundleIdentifier];
413
- [app activate];
479
+ [app fb_activate];
414
480
  return app;
415
481
  }
416
482
 
@@ -440,9 +506,9 @@ static NSUInteger _committedTerminationCount = 0;
440
506
  }
441
507
 
442
508
  // Has no async variant and can block on the shared accessibility channel. Run off-thread and give
443
- // up after `timeout`, assuming the tested app IS the system app - safer, since it means skipping
509
+ // up after `timeout`, assuming the supplied app IS the system app - safer, since it means skipping
444
510
  // termination rather than risking terminating springboard.
445
- - (BOOL)fb_isTestedApplicationSameAsSystemAppWithTimeout:(NSTimeInterval)timeout
511
+ - (BOOL)fb_isApplicationSameAsSystemApp:(XCUIApplication *)application timeout:(NSTimeInterval)timeout
446
512
  {
447
513
  __block XCUIApplication *systemApp = nil;
448
514
  __block NSException *caughtException = nil;
@@ -451,7 +517,7 @@ static NSUInteger _committedTerminationCount = 0;
451
517
  // Undocumented private API; guard in case it hard-asserts off-main like -terminate does on
452
518
  // some Xcode/iOS version - uncaught, that would crash the whole process.
453
519
  @try {
454
- systemApp = XCUIApplication.fb_systemApplication;
520
+ systemApp = [[application class] fb_systemApplication];
455
521
  } @catch (NSException *e) {
456
522
  caughtException = e;
457
523
  }
@@ -459,10 +525,10 @@ static NSUInteger _committedTerminationCount = 0;
459
525
  });
460
526
  int64_t timeoutNs = (int64_t)(timeout * NSEC_PER_SEC);
461
527
  if (0 != dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, timeoutNs)) || nil != caughtException) {
462
- [FBLogger logFmt:@"Could not determine the system application within %@ seconds%@; assuming '%@' might be it and skipping its termination", @(timeout), nil == caughtException ? @"" : [NSString stringWithFormat:@" (%@)", caughtException.description], self.testedApplication.bundleID];
528
+ [FBLogger logFmt:@"Could not determine the system application within %@ seconds%@; assuming '%@' might be it and skipping its termination", @(timeout), nil == caughtException ? @"" : [NSString stringWithFormat:@" (%@)", caughtException.description], application.bundleID];
463
529
  return YES;
464
530
  }
465
- return [self.testedApplication fb_isSameAppAs:systemApp];
531
+ return [application fb_isSameAppAs:systemApp];
466
532
  }
467
533
 
468
534
  // -terminate hard-asserts off-main, but -kill can now run on a background queue. Dispatching to
@@ -36,7 +36,11 @@ NS_ASSUME_NONNULL_BEGIN
36
36
  - (void)client:(nw_connection_t)client didReceiveData:(NSData *)data;
37
37
 
38
38
  /**
39
- The callback which is fired when TCP client disconnects
39
+ The callback which is fired when TCP client disconnects or finishes sending
40
+
41
+ A connection that failed is cancelled by the socket. One whose peer only finished sending stays
42
+ writable (the peer may be awaiting a response), and the delegate must cancel it once it has
43
+ nothing left to send - an uncancelled connection keeps its socket open.
40
44
 
41
45
  @param client The actual disconnected client
42
46
  */
@@ -137,6 +137,8 @@
137
137
  } else if (nw_connection_state_failed == state || nw_connection_state_cancelled == state) {
138
138
  __strong typeof(weakSelf) strongSelf = weakSelf;
139
139
  [strongSelf handleDisconnectForConnection:connection];
140
+ // A failed connection keeps its socket until cancelled (a no-op once cancelled).
141
+ nw_connection_cancel(connection);
140
142
  }
141
143
  });
142
144
  nw_connection_start(connection);
@@ -167,7 +169,14 @@
167
169
  }
168
170
  }
169
171
  }
170
- if (nil != receiveError || (isComplete && nil == content)) {
172
+ if (nil != receiveError) {
173
+ [strongSelf handleDisconnectForConnection:connection];
174
+ nw_connection_cancel(connection);
175
+ return;
176
+ }
177
+ if (isComplete && nil == content) {
178
+ // The peer is done sending but may still be reading - a half-closed client awaits its
179
+ // response - so the delegate cancels the connection once it has nothing left to send.
171
180
  [strongSelf handleDisconnectForConnection:connection];
172
181
  return;
173
182
  }
@@ -133,6 +133,9 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
133
133
  self.mjpegServer = [[FBMjpegServer alloc] init];
134
134
  self.screenshotsBroadcaster = [[FBTCPSocket alloc]
135
135
  initWithPort:(uint16_t)FBConfiguration.sharedInstance.mjpegServerPort];
136
+ // Bind to the same address as the HTTP server when USE_IP is set.
137
+ // nil (the default) keeps listening on all interfaces.
138
+ self.screenshotsBroadcaster.interface = FBConfiguration.sharedInstance.bindingIPAddress;
136
139
  self.mjpegServer.socket = self.screenshotsBroadcaster;
137
140
  self.screenshotsBroadcaster.delegate = self.mjpegServer;
138
141
  NSError *error;
@@ -265,7 +268,7 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
265
268
 
266
269
  - (void)registerServerKeyRouteHandlers
267
270
  {
268
- [self.server get:@"/health" withBlock:^(RouteRequest *request, RouteResponse *response) {
271
+ [self.server handleMethod:@"GET" withPath:@"/health" standalone:YES block:^(RouteRequest *request, RouteResponse *response) {
269
272
  [response respondWithString:@"<!DOCTYPE html><html><title>Health Check</title><body><p>I-AM-ALIVE</p></body></html>"];
270
273
  }];
271
274
 
@@ -24,14 +24,14 @@ NS_ASSUME_NONNULL_BEGIN
24
24
  * Calculates the accessbility element which is located at the given screen coordinates
25
25
  *
26
26
  * @param point The screen coordinates
27
- * @returns The retrieved accessbility element or nil if it cannot be detected
27
+ * @returns The retrieved accessbility element or nil if it cannot be detected or a secondary display is selected
28
28
  */
29
29
  + (nullable id<FBXCAccessibilityElement>)axElementWithPoint:(CGPoint)point;
30
30
 
31
31
  /**
32
32
  * Retrieves the accessbility element for the current screen point
33
33
  *
34
- * @returns The retrieved accessbility element or nil if it cannot be detected
34
+ * @returns The retrieved accessbility element or nil if it cannot be detected or a secondary display is selected
35
35
  */
36
36
  - (nullable id<FBXCAccessibilityElement>)axElement;
37
37
 
@@ -13,6 +13,10 @@
13
13
  #endif
14
14
 
15
15
  #import "FBErrorBuilder.h"
16
+ #import "FBScreen.h"
17
+ #import "FBConfiguration.h"
18
+ #import "FBExceptions.h"
19
+ #import "XCUIScreen.h"
16
20
  #import "FBLogger.h"
17
21
  #import "FBXCTestDaemonsProxy.h"
18
22
  #import "XCTMessagingChannel_RunnerToDaemon-Protocol.h"
@@ -45,6 +49,22 @@
45
49
 
46
50
  + (id<FBXCAccessibilityElement>)axElementWithPoint:(CGPoint)point
47
51
  {
52
+ if (nil != FBConfiguration.sharedInstance.currentDisplayId) {
53
+ NSError *error;
54
+ XCUIScreen *screen = [FBScreen currentScreenWithError:&error];
55
+ if (nil == screen) {
56
+ @throw [NSException exceptionWithName:FBInvalidArgumentException
57
+ reason:error.localizedDescription
58
+ userInfo:nil];
59
+ }
60
+ // XCTest's point lookup has no display parameter and always hits the main
61
+ // display. On Duo this can select the outer SpringBoard while the app is
62
+ // foreground on the inner display. Let callers use their active-app fallback
63
+ // instead of treating an element from a different display as authoritative.
64
+ if (screen.displayID != XCUIScreen.mainScreen.displayID) {
65
+ return nil;
66
+ }
67
+ }
48
68
  __block id<FBXCAccessibilityElement> onScreenElement = nil;
49
69
  id<XCTMessagingChannel_RunnerToDaemon> proxy = [FBXCTestDaemonsProxy testRunnerProxy];
50
70
  dispatch_semaphore_t sem = dispatch_semaphore_create(0);
@@ -60,6 +60,8 @@ typedef NS_ENUM(NSInteger, FBConfigurationKeyboardPreference) {
60
60
 
61
61
  /*! The maximum typing frequency for all typing activities */
62
62
  @property (atomic, assign) NSUInteger maxTypingFrequency;
63
+ /*! Seconds to wait for a hittable key before typing on iOS. 0 disables the wait. */
64
+ @property (atomic, assign) NSTimeInterval keyboardWaitTimeout;
63
65
  @property (atomic, readonly) NSUInteger defaultTypingFrequency;
64
66
 
65
67
  /*! Use singleton test manager proxy */
@@ -368,6 +370,12 @@ typedef NS_ENUM(NSInteger, FBConfigurationKeyboardPreference) {
368
370
  */
369
371
  @property (atomic, assign) BOOL enforceCustomSnapshots;
370
372
 
373
+ /**
374
+ * The identifier of the display targeted by screenshots, MJPEG, and new screen recordings.
375
+ * nil means the main display. Reset to nil for each new session.
376
+ */
377
+ @property (atomic, copy, nullable) NSNumber *currentDisplayId;
378
+
371
379
  @end
372
380
 
373
381
  NS_ASSUME_NONNULL_END
@@ -343,6 +343,7 @@ static NSString *const axSettingsClassName = @"AXSettings";
343
343
  self.shouldUseCompactResponses = YES;
344
344
  self.elementResponseAttributes = @"type,label";
345
345
  self.maxTypingFrequencyOverride = @(self.defaultTypingFrequency);
346
+ self.keyboardWaitTimeout = 0;
346
347
  self.screenshotQuality = 3;
347
348
  self.useFirstMatch = NO;
348
349
  self.boundElementsByIndex = NO;
@@ -357,6 +358,7 @@ static NSString *const axSettingsClassName = @"AXSettings";
357
358
  FBSetCustomParameterForElementSnapshot(FBSnapshotMaxChildrenKey, @INT_MAX);
358
359
  self.useClearTextShortcut = YES;
359
360
  self.limitXpathContextScope = YES;
361
+ self.currentDisplayId = nil;
360
362
  #if !TARGET_OS_TV && !TARGET_OS_WATCH
361
363
  self.screenshotOrientationStorage = UIInterfaceOrientationUnknown;
362
364
  #endif