appium-webdriveragent 16.13.5 → 16.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +8 -0
- package/WebDriverAgent.xcodeproj/project.pbxproj +16 -0
- package/WebDriverAgentLib/Categories/XCUIDevice+FBHinge.h +41 -0
- package/WebDriverAgentLib/Categories/XCUIDevice+FBHinge.m +215 -0
- package/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.m +14 -11
- package/WebDriverAgentLib/Commands/FBCustomCommands.m +36 -0
- package/WebDriverAgentLib/Info.plist +2 -2
- package/WebDriverAgentLib/Routing/FBScreenRecordingRequest.m +7 -2
- package/WebDriverAgentLib/Utilities/FBActiveAppDetectionPoint.h +2 -2
- package/WebDriverAgentLib/Utilities/FBActiveAppDetectionPoint.m +20 -0
- package/WebDriverAgentLib/Utilities/FBConfiguration.h +1 -1
- package/WebDriverAgentLib/Utilities/FBMjpegServer.m +3 -3
- package/WebDriverAgentLib/Utilities/FBScreen.m +53 -10
- package/WebDriverAgentLib/WebDriverAgentLib.h +1 -0
- package/WebDriverAgentTests/IntegrationTests/FBScreenTests.m +180 -3
- package/WebDriverAgentTests/UnitTests/FBCustomCommandsTests.m +61 -0
- package/WebDriverAgentTests/UnitTests/FBElementUtilitiesTests.m +40 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [16.14.0](https://github.com/appium/WebDriverAgent/compare/v16.13.6...v16.14.0) (2026-10-04)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* support Duo hinge control and secondary display capture ([#1287](https://github.com/appium/WebDriverAgent/issues/1287)) ([76d6aa0](https://github.com/appium/WebDriverAgent/commit/76d6aa03e2423290bb05c436afc153bd32878ce0))
|
|
6
|
+
|
|
7
|
+
## [16.13.6](https://github.com/appium/WebDriverAgent/compare/v16.13.5...v16.13.6) (2026-09-30)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* restore stability wait settings when quiescence throws ([#1281](https://github.com/appium/WebDriverAgent/issues/1281)) ([bcc434c](https://github.com/appium/WebDriverAgent/commit/bcc434c08127c370c0cb05ce1dc9f4cea9f98559))
|
|
12
|
+
|
|
1
13
|
## [16.13.5](https://github.com/appium/WebDriverAgent/compare/v16.13.4...v16.13.5) (2026-09-30)
|
|
2
14
|
|
|
3
15
|
### Performance Improvements
|
package/README.md
CHANGED
|
@@ -26,6 +26,14 @@ and start sending [requests](https://github.com/facebook/WebDriverAgent/wiki/Que
|
|
|
26
26
|
|
|
27
27
|
More about how to start WebDriverAgent [here](https://github.com/facebook/WebDriverAgent/wiki/Starting-WebDriverAgent).
|
|
28
28
|
|
|
29
|
+
## Multiple displays
|
|
30
|
+
|
|
31
|
+
See [Multiple displays](docs/multiple-displays.md) for display selection, capture,
|
|
32
|
+
and active-app detection.
|
|
33
|
+
|
|
34
|
+
See [Hinge angle](docs/hinge-angle.md) for reading angles, sending simulated hinge
|
|
35
|
+
events, runtime requirements, and physical-device testing feedback.
|
|
36
|
+
|
|
29
37
|
## Known Issues
|
|
30
38
|
If you are having some issues please checkout [wiki](https://github.com/facebook/WebDriverAgent/wiki/Common-Issues) first.
|
|
31
39
|
|
|
@@ -245,6 +245,7 @@
|
|
|
245
245
|
57C72DA18039FED310967F8F /* XCTSourceCodeLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = 81BB54ECBFB5B7B680BB5D4F /* XCTSourceCodeLocation.h */; };
|
|
246
246
|
57EAF450031647694B6491F8 /* FBConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9B76A21CF7A43900275851 /* FBConfiguration.m */; };
|
|
247
247
|
583AD004FF71CAA305E6DEAF /* XCUIDevice+FBHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = AD6C26971CF2481700F8B5FF /* XCUIDevice+FBHelpers.m */; };
|
|
248
|
+
0460879AD6034130AE0A3DEC /* XCUIDevice+FBHinge.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C8E6FD103A84C908F5746EA /* XCUIDevice+FBHinge.m */; };
|
|
248
249
|
587AA46196BC2C46555C8E44 /* XCUIButtonConsole.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E09842154A44874C4E9CA01 /* XCUIButtonConsole.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
|
249
250
|
588F51093FD2A5597366C282 /* XCUIElementEventTarget-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 0DC62BF635704E9C72AF533E /* XCUIElementEventTarget-Protocol.h */; };
|
|
250
251
|
5967912A40E807CDBE87E4B2 /* XCTReportingSessionIssueReporter-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 525D62A58488A52AA1BFE94A /* XCTReportingSessionIssueReporter-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
|
@@ -327,6 +328,7 @@
|
|
|
327
328
|
641EE60A2240C5CA00173FCB /* FBLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9B76A41CF7A43900275851 /* FBLogger.m */; };
|
|
328
329
|
641EE60B2240C5CA00173FCB /* FBCustomCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB7531CAEDF0C008C271F /* FBCustomCommands.m */; };
|
|
329
330
|
641EE60C2240C5CA00173FCB /* XCUIDevice+FBHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = AD6C26971CF2481700F8B5FF /* XCUIDevice+FBHelpers.m */; };
|
|
331
|
+
181F0DBB6D28418680D52128 /* XCUIDevice+FBHinge.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C8E6FD103A84C908F5746EA /* XCUIDevice+FBHinge.m */; };
|
|
330
332
|
641EE60D2240C5CA00173FCB /* XCTestPrivateSymbols.m in Sources */ = {isa = PBXBuildFile; fileRef = EE6B64FC1D0F86EF00E85F5D /* XCTestPrivateSymbols.m */; };
|
|
331
333
|
641EE60E2240C5CA00173FCB /* XCUIElement+FBTyping.m in Sources */ = {isa = PBXBuildFile; fileRef = AD76723C1D6B7CC000610457 /* XCUIElement+FBTyping.m */; };
|
|
332
334
|
641EE60F2240C5CA00173FCB /* XCUIElement+FBAccessibility.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB7461CAEDF0C008C271F /* XCUIElement+FBAccessibility.m */; };
|
|
@@ -433,6 +435,7 @@
|
|
|
433
435
|
641EE6BF2240C5CA00173FCB /* FBKeyboard.h in Headers */ = {isa = PBXBuildFile; fileRef = EE3A18641CDE734B00DE4205 /* FBKeyboard.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
|
434
436
|
641EE6C02240C5CA00173FCB /* XCUIApplication+FBHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = AD6C269A1CF2494200F8B5FF /* XCUIApplication+FBHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
|
435
437
|
641EE6C22240C5CA00173FCB /* XCUIDevice+FBHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = AD6C26961CF2481700F8B5FF /* XCUIDevice+FBHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
|
438
|
+
95EF6989BCBE4C548C26B140 /* XCUIDevice+FBHinge.h in Headers */ = {isa = PBXBuildFile; fileRef = E7536267DE094076A4EEE1D7 /* XCUIDevice+FBHinge.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
|
436
439
|
641EE6C32240C5CA00173FCB /* FBClassChainQueryParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 71A7EAF71E224648001DA4F2 /* FBClassChainQueryParser.h */; };
|
|
437
440
|
641EE6C42240C5CA00173FCB /* FBMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = EE9B76A51CF7A43900275851 /* FBMacros.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
|
438
441
|
641EE6C52240C5CA00173FCB /* XCTestExpectationDelegate-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = EE35ACD91E3B77D600A02D78 /* XCTestExpectationDelegate-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
|
@@ -887,7 +890,9 @@
|
|
|
887
890
|
AD6C26941CF2379700F8B5FF /* FBAlert.h in Headers */ = {isa = PBXBuildFile; fileRef = AD6C26921CF2379700F8B5FF /* FBAlert.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
|
888
891
|
AD6C26951CF2379700F8B5FF /* FBAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = AD6C26931CF2379700F8B5FF /* FBAlert.m */; };
|
|
889
892
|
AD6C26981CF2481700F8B5FF /* XCUIDevice+FBHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = AD6C26961CF2481700F8B5FF /* XCUIDevice+FBHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
|
893
|
+
210AB789D14D40308B764DD0 /* XCUIDevice+FBHinge.h in Headers */ = {isa = PBXBuildFile; fileRef = E7536267DE094076A4EEE1D7 /* XCUIDevice+FBHinge.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
|
890
894
|
AD6C26991CF2481700F8B5FF /* XCUIDevice+FBHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = AD6C26971CF2481700F8B5FF /* XCUIDevice+FBHelpers.m */; };
|
|
895
|
+
25C9BDFC65564D78A70231B4 /* XCUIDevice+FBHinge.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C8E6FD103A84C908F5746EA /* XCUIDevice+FBHinge.m */; };
|
|
891
896
|
AD6C269C1CF2494200F8B5FF /* XCUIApplication+FBHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = AD6C269A1CF2494200F8B5FF /* XCUIApplication+FBHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
|
892
897
|
AD6C269D1CF2494200F8B5FF /* XCUIApplication+FBHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = AD6C269B1CF2494200F8B5FF /* XCUIApplication+FBHelpers.m */; };
|
|
893
898
|
AD764F10E2F00090AEB802F2 /* XCUIElement+FBIsVisible.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB7481CAEDF0C008C271F /* XCUIElement+FBIsVisible.m */; };
|
|
@@ -987,6 +992,7 @@
|
|
|
987
992
|
D1172A7F53F89B78D8324A13 /* RouteRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 374BB11AE4E4243BCA444CF8 /* RouteRequest.m */; };
|
|
988
993
|
D1212325FDE77754EE503755 /* FBRouteRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB7881CAEDF0C008C271F /* FBRouteRequest.m */; };
|
|
989
994
|
D142183578036F5FD5B9880B /* XCUIDevice+FBHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = AD6C26961CF2481700F8B5FF /* XCUIDevice+FBHelpers.h */; };
|
|
995
|
+
2E71304F39B64EDC98EE3974 /* XCUIDevice+FBHinge.h in Headers */ = {isa = PBXBuildFile; fileRef = E7536267DE094076A4EEE1D7 /* XCUIDevice+FBHinge.h */; };
|
|
990
996
|
D14718959F7D46949859EEE6 /* FBDebugLogDelegateDecorator.m in Sources */ = {isa = PBXBuildFile; fileRef = EE7E27191D06C69F001BEC7B /* FBDebugLogDelegateDecorator.m */; };
|
|
991
997
|
D1DE06DF54156F744E364B5F /* FBDebugLogDelegateDecorator.h in Headers */ = {isa = PBXBuildFile; fileRef = EE7E27181D06C69F001BEC7B /* FBDebugLogDelegateDecorator.h */; };
|
|
992
998
|
D203F622C7C0D968F3BD2AAF /* XCUIPlatformApplicationServicesProviding-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 3BA71BCF1FDA482419CA8596 /* XCUIPlatformApplicationServicesProviding-Protocol.h */; };
|
|
@@ -1760,7 +1766,9 @@
|
|
|
1760
1766
|
AD6C26921CF2379700F8B5FF /* FBAlert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FBAlert.h; path = WebDriverAgentLib/FBAlert.h; sourceTree = SOURCE_ROOT; };
|
|
1761
1767
|
AD6C26931CF2379700F8B5FF /* FBAlert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = FBAlert.m; path = WebDriverAgentLib/FBAlert.m; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
|
|
1762
1768
|
AD6C26961CF2481700F8B5FF /* XCUIDevice+FBHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCUIDevice+FBHelpers.h"; sourceTree = "<group>"; };
|
|
1769
|
+
E7536267DE094076A4EEE1D7 /* XCUIDevice+FBHinge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCUIDevice+FBHinge.h"; sourceTree = "<group>"; };
|
|
1763
1770
|
AD6C26971CF2481700F8B5FF /* XCUIDevice+FBHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "XCUIDevice+FBHelpers.m"; sourceTree = "<group>"; };
|
|
1771
|
+
8C8E6FD103A84C908F5746EA /* XCUIDevice+FBHinge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "XCUIDevice+FBHinge.m"; sourceTree = "<group>"; };
|
|
1764
1772
|
AD6C269A1CF2494200F8B5FF /* XCUIApplication+FBHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCUIApplication+FBHelpers.h"; sourceTree = "<group>"; };
|
|
1765
1773
|
AD6C269B1CF2494200F8B5FF /* XCUIApplication+FBHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "XCUIApplication+FBHelpers.m"; sourceTree = "<group>"; };
|
|
1766
1774
|
AD76723B1D6B7CC000610457 /* XCUIElement+FBTyping.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCUIElement+FBTyping.h"; sourceTree = "<group>"; };
|
|
@@ -2415,7 +2423,9 @@
|
|
|
2415
2423
|
EEDFE11F1D9C06F800E6FFE5 /* XCUIDevice+FBHealthCheck.h */,
|
|
2416
2424
|
EEDFE1201D9C06F800E6FFE5 /* XCUIDevice+FBHealthCheck.m */,
|
|
2417
2425
|
AD6C26961CF2481700F8B5FF /* XCUIDevice+FBHelpers.h */,
|
|
2426
|
+
E7536267DE094076A4EEE1D7 /* XCUIDevice+FBHinge.h */,
|
|
2418
2427
|
AD6C26971CF2481700F8B5FF /* XCUIDevice+FBHelpers.m */,
|
|
2428
|
+
8C8E6FD103A84C908F5746EA /* XCUIDevice+FBHinge.m */,
|
|
2419
2429
|
A1B2C3D41F001A00A1B0001 /* XCUIDevice+FBVoiceOver.h */,
|
|
2420
2430
|
A1B2C3D41F001A00A1B0002 /* XCUIDevice+FBVoiceOver.m */,
|
|
2421
2431
|
EEE3763D1D59F81400ED88DD /* XCUIDevice+FBRotation.h */,
|
|
@@ -3178,6 +3188,7 @@
|
|
|
3178
3188
|
641EE6C02240C5CA00173FCB /* XCUIApplication+FBHelpers.h in Headers */,
|
|
3179
3189
|
714EAA0E2673FDFE005C5B47 /* FBCapabilities.h in Headers */,
|
|
3180
3190
|
641EE6C22240C5CA00173FCB /* XCUIDevice+FBHelpers.h in Headers */,
|
|
3191
|
+
95EF6989BCBE4C548C26B140 /* XCUIDevice+FBHinge.h in Headers */,
|
|
3181
3192
|
71D3B3D6267FC7260076473D /* XCUIElement+FBResolve.h in Headers */,
|
|
3182
3193
|
641EE6C32240C5CA00173FCB /* FBClassChainQueryParser.h in Headers */,
|
|
3183
3194
|
641EE6C42240C5CA00173FCB /* FBMacros.h in Headers */,
|
|
@@ -3489,6 +3500,7 @@
|
|
|
3489
3500
|
81231F08969740B0C8C67A30 /* XCUIApplication+FBHelpers.h in Headers */,
|
|
3490
3501
|
9AA0E22033281B4EAA78BC51 /* FBCapabilities.h in Headers */,
|
|
3491
3502
|
D142183578036F5FD5B9880B /* XCUIDevice+FBHelpers.h in Headers */,
|
|
3503
|
+
2E71304F39B64EDC98EE3974 /* XCUIDevice+FBHinge.h in Headers */,
|
|
3492
3504
|
09D97BD46764FD8149747723 /* XCUIElement+FBResolve.h in Headers */,
|
|
3493
3505
|
6BF3F57247F59B81B8BCEE7B /* FBClassChainQueryParser.h in Headers */,
|
|
3494
3506
|
8AAA33A5B1943B667B0DB05E /* FBMacros.h in Headers */,
|
|
@@ -3756,6 +3768,7 @@
|
|
|
3756
3768
|
AD6C269C1CF2494200F8B5FF /* XCUIApplication+FBHelpers.h in Headers */,
|
|
3757
3769
|
714D88CC2733FB970074A925 /* FBXMLGenerationOptions.h in Headers */,
|
|
3758
3770
|
AD6C26981CF2481700F8B5FF /* XCUIDevice+FBHelpers.h in Headers */,
|
|
3771
|
+
210AB789D14D40308B764DD0 /* XCUIDevice+FBHinge.h in Headers */,
|
|
3759
3772
|
71A7EAF91E224648001DA4F2 /* FBClassChainQueryParser.h in Headers */,
|
|
3760
3773
|
EE9B76AA1CF7A43900275851 /* FBMacros.h in Headers */,
|
|
3761
3774
|
C8FB547922D4C1FC00B69954 /* FBUnattachedAppLauncher.h in Headers */,
|
|
@@ -4513,6 +4526,7 @@
|
|
|
4513
4526
|
641EE60B2240C5CA00173FCB /* FBCustomCommands.m in Sources */,
|
|
4514
4527
|
71BB58E42B9631F100CB9BFE /* FBScreenRecordingPromise.m in Sources */,
|
|
4515
4528
|
641EE60C2240C5CA00173FCB /* XCUIDevice+FBHelpers.m in Sources */,
|
|
4529
|
+
181F0DBB6D28418680D52128 /* XCUIDevice+FBHinge.m in Sources */,
|
|
4516
4530
|
641EE60D2240C5CA00173FCB /* XCTestPrivateSymbols.m in Sources */,
|
|
4517
4531
|
641EE60E2240C5CA00173FCB /* XCUIElement+FBTyping.m in Sources */,
|
|
4518
4532
|
641EE60F2240C5CA00173FCB /* XCUIElement+FBAccessibility.m in Sources */,
|
|
@@ -4668,6 +4682,7 @@
|
|
|
4668
4682
|
EDC993E0858F89F73D8E9DF8 /* FBCustomCommands.m in Sources */,
|
|
4669
4683
|
45033CF6BD35C16E34BF76CC /* FBScreenRecordingPromise.m in Sources */,
|
|
4670
4684
|
583AD004FF71CAA305E6DEAF /* XCUIDevice+FBHelpers.m in Sources */,
|
|
4685
|
+
0460879AD6034130AE0A3DEC /* XCUIDevice+FBHinge.m in Sources */,
|
|
4671
4686
|
192CACF49EC3825BA6D2EF4F /* XCTestPrivateSymbols.m in Sources */,
|
|
4672
4687
|
53E0F0F38E7847065810FF41 /* XCUIElement+FBTyping.m in Sources */,
|
|
4673
4688
|
A5353D849D394BC630839E59 /* XCUIElement+FBAccessibility.m in Sources */,
|
|
@@ -4787,6 +4802,7 @@
|
|
|
4787
4802
|
EE9B76A91CF7A43900275851 /* FBLogger.m in Sources */,
|
|
4788
4803
|
EE158ABB1CBD456F00A3E3F0 /* FBCustomCommands.m in Sources */,
|
|
4789
4804
|
AD6C26991CF2481700F8B5FF /* XCUIDevice+FBHelpers.m in Sources */,
|
|
4805
|
+
25C9BDFC65564D78A70231B4 /* XCUIDevice+FBHinge.m in Sources */,
|
|
4790
4806
|
716C9E0227315EFF005AD475 /* XCUIApplication+FBUIInterruptions.m in Sources */,
|
|
4791
4807
|
EE6B64FE1D0F86EF00E85F5D /* XCTestPrivateSymbols.m in Sources */,
|
|
4792
4808
|
AD76723E1D6B7CC000610457 /* XCUIElement+FBTyping.m in Sources */,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2015-present, Facebook, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the BSD-style license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#import <XCTest/XCTest.h>
|
|
10
|
+
|
|
11
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
12
|
+
|
|
13
|
+
@interface XCUIDevice (FBHinge)
|
|
14
|
+
|
|
15
|
+
/** Whether a hinge and the CoreMotion angle-reading APIs are available. */
|
|
16
|
+
- (BOOL)fb_supportsHingeAngleReading;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
Whether a hinge and the IOKit APIs needed to attempt injection are available.
|
|
20
|
+
This does not guarantee event acceptance, particularly on physical devices.
|
|
21
|
+
*/
|
|
22
|
+
- (BOOL)fb_canAttemptSimulatedHingeAngleInjection;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
Reads the current hinge angle in degrees, including changes made outside WDA.
|
|
26
|
+
Waits up to 5 seconds for a valid reading. Does not change the hinge angle.
|
|
27
|
+
@return The angle, or nil with an error if unavailable or reading times out.
|
|
28
|
+
*/
|
|
29
|
+
- (nullable NSNumber *)fb_getSimulatedHingeAngle:(NSError **)error;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
Sends a simulated hinge angle in degrees (0 = closed, 180 = fully open).
|
|
33
|
+
This dispatches the event; the display transition completes asynchronously.
|
|
34
|
+
Verified on the iPhone Duo simulator; physical-device behavior is unverified.
|
|
35
|
+
Does not change currentDisplayId.
|
|
36
|
+
*/
|
|
37
|
+
- (BOOL)fb_setSimulatedHingeAngle:(double)angle error:(NSError **)error;
|
|
38
|
+
|
|
39
|
+
@end
|
|
40
|
+
|
|
41
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2015-present, Facebook, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the BSD-style license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#import "XCUIDevice+FBHinge.h"
|
|
10
|
+
|
|
11
|
+
#import <dlfcn.h>
|
|
12
|
+
#import <math.h>
|
|
13
|
+
#import <mach/mach_time.h>
|
|
14
|
+
|
|
15
|
+
#import "FBErrorBuilder.h"
|
|
16
|
+
|
|
17
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
18
|
+
static const NSTimeInterval FBHingeAngleReadingTimeout = 5.0;
|
|
19
|
+
|
|
20
|
+
typedef CFDataRef (*FBIOCFSerialize)(CFTypeRef object, CFOptionFlags options);
|
|
21
|
+
typedef CFTypeRef (*FBIOHIDEventCreateVendorDefinedEvent)(CFAllocatorRef allocator,
|
|
22
|
+
uint64_t timestamp,
|
|
23
|
+
uint32_t usagePage,
|
|
24
|
+
uint32_t usage,
|
|
25
|
+
uint32_t version,
|
|
26
|
+
uint8_t *data,
|
|
27
|
+
CFIndex length,
|
|
28
|
+
uint32_t options);
|
|
29
|
+
typedef CFTypeRef (*FBIOHIDEventSystemClientCreate)(CFAllocatorRef allocator);
|
|
30
|
+
typedef void (*FBIOHIDEventSystemClientDispatchEvent)(CFTypeRef client, CFTypeRef event);
|
|
31
|
+
|
|
32
|
+
@protocol FBAngle <NSObject>
|
|
33
|
+
- (BOOL)isAngleValid;
|
|
34
|
+
- (float)angleDegrees;
|
|
35
|
+
@end
|
|
36
|
+
|
|
37
|
+
@protocol FBAngleManager <NSObject>
|
|
38
|
+
+ (instancetype)new;
|
|
39
|
+
+ (BOOL)isAvailable;
|
|
40
|
+
+ (BOOL)instancesRespondToSelector:(SEL)selector;
|
|
41
|
+
- (void)startAngleUpdatesToQueue:(NSOperationQueue *)queue handler:(void (^)(id<FBAngle>))handler;
|
|
42
|
+
- (void)stopAngleUpdates;
|
|
43
|
+
@end
|
|
44
|
+
|
|
45
|
+
static Class<FBAngleManager> FBAngleManagerClass(void)
|
|
46
|
+
{
|
|
47
|
+
static Class<FBAngleManager> angleManagerClass;
|
|
48
|
+
static dispatch_once_t onceToken;
|
|
49
|
+
dispatch_once(&onceToken, ^{
|
|
50
|
+
// This path also loads on physical devices (verified on an iPad mini).
|
|
51
|
+
// Framework/class presence does not imply that a hinge sensor is available.
|
|
52
|
+
if (NULL != dlopen("/System/Library/Frameworks/CoreMotion.framework/CoreMotion", RTLD_LAZY)) {
|
|
53
|
+
angleManagerClass = NSClassFromString(@"CMAngleManager");
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
return angleManagerClass;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static BOOL FBHasAvailableHinge(void)
|
|
60
|
+
{
|
|
61
|
+
Class<FBAngleManager> managerClass = FBAngleManagerClass();
|
|
62
|
+
return [managerClass respondsToSelector:@selector(isAvailable)] && [managerClass isAvailable];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
typedef struct {
|
|
66
|
+
FBIOCFSerialize serialize;
|
|
67
|
+
FBIOHIDEventCreateVendorDefinedEvent createEvent;
|
|
68
|
+
FBIOHIDEventSystemClientCreate createClient;
|
|
69
|
+
FBIOHIDEventSystemClientDispatchEvent dispatchEvent;
|
|
70
|
+
} FBHingeInjectionAPI;
|
|
71
|
+
|
|
72
|
+
// Loads and caches the injection APIs; NULL means at least one is unavailable.
|
|
73
|
+
// Resolving these functions does not dispatch an event or confirm its acceptance.
|
|
74
|
+
static const FBHingeInjectionAPI *FBHingeInjectionFunctions(void)
|
|
75
|
+
{
|
|
76
|
+
static FBHingeInjectionAPI api;
|
|
77
|
+
static dispatch_once_t onceToken;
|
|
78
|
+
dispatch_once(&onceToken, ^{
|
|
79
|
+
void *handle = dlopen("/System/Library/Frameworks/IOKit.framework/IOKit", RTLD_LAZY);
|
|
80
|
+
if (NULL != handle) {
|
|
81
|
+
api.serialize = (FBIOCFSerialize)dlsym(handle, "IOCFSerialize");
|
|
82
|
+
api.createEvent = (FBIOHIDEventCreateVendorDefinedEvent)dlsym(handle, "IOHIDEventCreateVendorDefinedEvent");
|
|
83
|
+
api.createClient = (FBIOHIDEventSystemClientCreate)dlsym(handle, "IOHIDEventSystemClientCreate");
|
|
84
|
+
api.dispatchEvent = (FBIOHIDEventSystemClientDispatchEvent)dlsym(handle, "IOHIDEventSystemClientDispatchEvent");
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
return NULL != api.serialize && NULL != api.createEvent
|
|
88
|
+
&& NULL != api.createClient && NULL != api.dispatchEvent ? &api : NULL;
|
|
89
|
+
}
|
|
90
|
+
#endif
|
|
91
|
+
|
|
92
|
+
@implementation XCUIDevice (FBHinge)
|
|
93
|
+
|
|
94
|
+
- (BOOL)fb_supportsHingeAngleReading
|
|
95
|
+
{
|
|
96
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
97
|
+
Class<FBAngleManager> managerClass = FBAngleManagerClass();
|
|
98
|
+
return FBHasAvailableHinge()
|
|
99
|
+
&& [managerClass instancesRespondToSelector:@selector(startAngleUpdatesToQueue:handler:)]
|
|
100
|
+
&& [managerClass instancesRespondToSelector:@selector(stopAngleUpdates)];
|
|
101
|
+
#else
|
|
102
|
+
return NO;
|
|
103
|
+
#endif
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
- (BOOL)fb_canAttemptSimulatedHingeAngleInjection
|
|
107
|
+
{
|
|
108
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
109
|
+
// Sensor presence and local APIs permit an attempt, not proof that the
|
|
110
|
+
// device's receiver accepts the vendor event. Physical devices are unverified.
|
|
111
|
+
return FBHasAvailableHinge() && NULL != FBHingeInjectionFunctions();
|
|
112
|
+
#else
|
|
113
|
+
return NO;
|
|
114
|
+
#endif
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
- (nullable NSNumber *)fb_getSimulatedHingeAngle:(NSError **)error
|
|
118
|
+
{
|
|
119
|
+
if (!self.fb_supportsHingeAngleReading) {
|
|
120
|
+
[[FBErrorBuilder.builder withDescription:@"Hinge angle reading is unavailable on this device"] buildError:error];
|
|
121
|
+
return nil;
|
|
122
|
+
}
|
|
123
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
124
|
+
id<FBAngleManager> manager = [FBAngleManagerClass() new];
|
|
125
|
+
NSOperationQueue *queue = [NSOperationQueue new];
|
|
126
|
+
queue.maxConcurrentOperationCount = 1;
|
|
127
|
+
NSCondition *condition = [NSCondition new];
|
|
128
|
+
__block NSNumber *reading = nil;
|
|
129
|
+
NSNumber *result;
|
|
130
|
+
@try {
|
|
131
|
+
[manager startAngleUpdatesToQueue:queue handler:^(id<FBAngle> angle) {
|
|
132
|
+
if (![angle respondsToSelector:@selector(isAngleValid)]
|
|
133
|
+
|| ![angle respondsToSelector:@selector(angleDegrees)] || !angle.isAngleValid) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
float degrees = angle.angleDegrees;
|
|
137
|
+
if (!isfinite(degrees)) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
[condition lock];
|
|
141
|
+
if (nil == reading) {
|
|
142
|
+
reading = @(degrees);
|
|
143
|
+
[condition signal];
|
|
144
|
+
}
|
|
145
|
+
[condition unlock];
|
|
146
|
+
}];
|
|
147
|
+
NSDate *deadline = [NSDate dateWithTimeIntervalSinceNow:FBHingeAngleReadingTimeout];
|
|
148
|
+
[condition lock];
|
|
149
|
+
while (nil == reading) {
|
|
150
|
+
if (![condition waitUntilDate:deadline]) {
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
result = reading;
|
|
155
|
+
[condition unlock];
|
|
156
|
+
} @finally {
|
|
157
|
+
[manager stopAngleUpdates];
|
|
158
|
+
}
|
|
159
|
+
if (nil == result) {
|
|
160
|
+
[[FBErrorBuilder.builder withDescriptionFormat:@"Timed out after %g seconds waiting for a valid hinge angle",
|
|
161
|
+
FBHingeAngleReadingTimeout] buildError:error];
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
#else
|
|
165
|
+
return nil;
|
|
166
|
+
#endif
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
- (BOOL)fb_setSimulatedHingeAngle:(double)angle error:(NSError **)error
|
|
170
|
+
{
|
|
171
|
+
if (!isfinite(angle) || angle < 0 || angle > 180) {
|
|
172
|
+
return [[FBErrorBuilder.builder withDescription:@"Hinge angle must be a finite number between 0 and 180 degrees"] buildError:error];
|
|
173
|
+
}
|
|
174
|
+
if (!self.fb_canAttemptSimulatedHingeAngleInjection) {
|
|
175
|
+
return [[FBErrorBuilder.builder withDescription:@"Hinge angle injection requires an available hinge and the IOKit HID APIs"] buildError:error];
|
|
176
|
+
}
|
|
177
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
178
|
+
// WDA's fb_performIOHIDEventWithPage:usage:duration:error: uses XCDeviceEvent
|
|
179
|
+
// with page, usage, and duration only. The XCDeviceEvent interfaces checked
|
|
180
|
+
// in Xcode 27.1 provide no way to attach the vendor payload containing the
|
|
181
|
+
// hinge angle, so this command creates and dispatches an IOKit event directly.
|
|
182
|
+
const FBHingeInjectionAPI *api = FBHingeInjectionFunctions();
|
|
183
|
+
// Matches Device Hub's hinge-slider-control payload (Xcode 27.1). It is an
|
|
184
|
+
// IOCF binary serialization, not an NSPropertyListSerialization binary plist.
|
|
185
|
+
NSDictionary *payload = @{
|
|
186
|
+
@"provider": @"com.apple.Virtualization.VirtualMachines",
|
|
187
|
+
@"source": @"hinge-slider-control",
|
|
188
|
+
@"type": @"range",
|
|
189
|
+
@"value": @(angle),
|
|
190
|
+
};
|
|
191
|
+
CFDataRef data = api->serialize((__bridge CFTypeRef)payload, 1);
|
|
192
|
+
if (NULL == data) {
|
|
193
|
+
return [[FBErrorBuilder.builder withDescription:@"Cannot serialize the simulated hinge event"] buildError:error];
|
|
194
|
+
}
|
|
195
|
+
CFTypeRef event = api->createEvent(kCFAllocatorDefault, mach_absolute_time(), 0xff61, 0x5b, 0,
|
|
196
|
+
(uint8_t *)CFDataGetBytePtr(data), CFDataGetLength(data), 0);
|
|
197
|
+
CFRelease(data);
|
|
198
|
+
CFTypeRef client = api->createClient(kCFAllocatorDefault);
|
|
199
|
+
BOOL canDispatch = NULL != event && NULL != client;
|
|
200
|
+
if (canDispatch) {
|
|
201
|
+
api->dispatchEvent(client, event);
|
|
202
|
+
}
|
|
203
|
+
if (NULL != event) {
|
|
204
|
+
CFRelease(event);
|
|
205
|
+
}
|
|
206
|
+
if (NULL != client) {
|
|
207
|
+
CFRelease(client);
|
|
208
|
+
}
|
|
209
|
+
return canDispatch || [[FBErrorBuilder.builder withDescription:@"Cannot create the simulated hinge HID event or client"] buildError:error];
|
|
210
|
+
#else
|
|
211
|
+
return NO;
|
|
212
|
+
#endif
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@end
|
|
@@ -141,18 +141,21 @@
|
|
|
141
141
|
return;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
FBConfiguration.sharedInstance
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
144
|
+
XCUIApplication *application = self.application;
|
|
145
|
+
// Recursive synchronization also covers nested stability checks on this thread.
|
|
146
|
+
@synchronized (FBConfiguration.sharedInstance) {
|
|
147
|
+
NSTimeInterval previousTimeout = FBConfiguration.sharedInstance.waitForIdleTimeout;
|
|
148
|
+
BOOL previousQuiescence = application.fb_shouldWaitForQuiescence;
|
|
149
|
+
@try {
|
|
150
|
+
FBConfiguration.sharedInstance.waitForIdleTimeout = timeout;
|
|
151
|
+
application.fb_shouldWaitForQuiescence = YES;
|
|
152
|
+
[[[application applicationImpl] currentProcess]
|
|
153
|
+
fb_waitForQuiescenceIncludingAnimationsIdle:YES];
|
|
154
|
+
} @finally {
|
|
155
|
+
application.fb_shouldWaitForQuiescence = previousQuiescence;
|
|
156
|
+
FBConfiguration.sharedInstance.waitForIdleTimeout = previousTimeout;
|
|
157
|
+
}
|
|
154
158
|
}
|
|
155
|
-
FBConfiguration.sharedInstance.waitForIdleTimeout = previousTimeout;
|
|
156
159
|
}
|
|
157
160
|
|
|
158
161
|
- (void)fb_raiseStaleElementExceptionWithError:(NSError *)error __attribute__((noreturn))
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
#import "XCUIApplication.h"
|
|
30
30
|
#import "XCUIApplication+FBHelpers.h"
|
|
31
31
|
#import "XCUIDevice+FBHelpers.h"
|
|
32
|
+
#import "XCUIDevice+FBHinge.h"
|
|
32
33
|
#import "XCUIDevice+FBVoiceOver.h"
|
|
33
34
|
#import "XCUIElement.h"
|
|
34
35
|
#import "XCUIElement+FBIsVisible.h"
|
|
@@ -74,6 +75,10 @@
|
|
|
74
75
|
[[FBRoute POST:@"/wda/expectNotification"] respondWithTarget:self action:@selector(handleExpectNotification:)],
|
|
75
76
|
[[FBRoute POST:@"/wda/siri/activate"] respondWithTarget:self action:@selector(handleActivateSiri:)],
|
|
76
77
|
[[FBRoute POST:@"/wda/apps/launchUnattached"].withoutSession respondWithTarget:self action:@selector(handleLaunchUnattachedApp:)],
|
|
78
|
+
[[FBRoute GET:@"/wda/device/hingeAngle"] respondWithTarget:self action:@selector(handleGetSimulatedHingeAngle:)],
|
|
79
|
+
[[FBRoute GET:@"/wda/device/hingeAngle"].withoutSession respondWithTarget:self action:@selector(handleGetSimulatedHingeAngle:)],
|
|
80
|
+
[[FBRoute POST:@"/wda/device/hingeAngle"] respondWithTarget:self action:@selector(handleSetSimulatedHingeAngle:)],
|
|
81
|
+
[[FBRoute POST:@"/wda/device/hingeAngle"].withoutSession respondWithTarget:self action:@selector(handleSetSimulatedHingeAngle:)],
|
|
77
82
|
[[FBRoute GET:@"/wda/device/info"] respondWithTarget:self action:@selector(handleGetDeviceInfo:)],
|
|
78
83
|
[[FBRoute POST:@"/wda/resetAppAuth"] respondWithTarget:self action:@selector(handleResetAppAuth:)],
|
|
79
84
|
[[FBRoute GET:@"/wda/device/info"].withoutSession respondWithTarget:self action:@selector(handleGetDeviceInfo:)],
|
|
@@ -777,4 +782,35 @@
|
|
|
777
782
|
return FBResponseWithObject(result);
|
|
778
783
|
}
|
|
779
784
|
|
|
785
|
+
+ (id<FBResponsePayload>)handleGetSimulatedHingeAngle:(FBRouteRequest *)request
|
|
786
|
+
{
|
|
787
|
+
if (!XCUIDevice.sharedDevice.fb_supportsHingeAngleReading) {
|
|
788
|
+
return FBResponseWithStatus([FBCommandStatus unsupportedOperationErrorWithMessage:
|
|
789
|
+
@"Hinge angle reading is unavailable on this device" traceback:nil]);
|
|
790
|
+
}
|
|
791
|
+
NSError *error;
|
|
792
|
+
NSNumber *angle = [XCUIDevice.sharedDevice fb_getSimulatedHingeAngle:&error];
|
|
793
|
+
return nil == angle ? FBResponseWithUnknownError(error) : FBResponseWithObject(angle);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
+ (id<FBResponsePayload>)handleSetSimulatedHingeAngle:(FBRouteRequest *)request
|
|
797
|
+
{
|
|
798
|
+
id angle = request.arguments[@"angle"];
|
|
799
|
+
if (![angle isKindOfClass:NSNumber.class]
|
|
800
|
+
|| CFGetTypeID((__bridge CFTypeRef)angle) == CFBooleanGetTypeID()
|
|
801
|
+
|| !isfinite([angle doubleValue]) || [angle doubleValue] < 0 || [angle doubleValue] > 180) {
|
|
802
|
+
return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:
|
|
803
|
+
@"'angle' must be a finite number between 0 and 180 degrees" traceback:nil]);
|
|
804
|
+
}
|
|
805
|
+
if (!XCUIDevice.sharedDevice.fb_canAttemptSimulatedHingeAngleInjection) {
|
|
806
|
+
return FBResponseWithStatus([FBCommandStatus unsupportedOperationErrorWithMessage:
|
|
807
|
+
@"Setting a simulated hinge angle requires a compatible foldable device" traceback:nil]);
|
|
808
|
+
}
|
|
809
|
+
NSError *error;
|
|
810
|
+
if (![XCUIDevice.sharedDevice fb_setSimulatedHingeAngle:[angle doubleValue] error:&error]) {
|
|
811
|
+
return FBResponseWithUnknownError(error);
|
|
812
|
+
}
|
|
813
|
+
return FBResponseWithOK();
|
|
814
|
+
}
|
|
815
|
+
|
|
780
816
|
@end
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
<key>CFBundlePackageType</key>
|
|
16
16
|
<string>FMWK</string>
|
|
17
17
|
<key>CFBundleShortVersionString</key>
|
|
18
|
-
<string>16.
|
|
18
|
+
<string>16.14.0</string>
|
|
19
19
|
<key>CFBundleSignature</key>
|
|
20
20
|
<string>????</string>
|
|
21
21
|
<key>CFBundleVersion</key>
|
|
22
|
-
<string>16.
|
|
22
|
+
<string>16.14.0</string>
|
|
23
23
|
<key>NSPrincipalClass</key>
|
|
24
24
|
<string/>
|
|
25
25
|
</dict>
|
|
@@ -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
|
|
84
|
-
[screenRecordingRequestInitInvocation setArgument:&
|
|
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];
|
|
@@ -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);
|
|
@@ -369,7 +369,7 @@ typedef NS_ENUM(NSInteger, FBConfigurationKeyboardPreference) {
|
|
|
369
369
|
@property (atomic, assign) BOOL enforceCustomSnapshots;
|
|
370
370
|
|
|
371
371
|
/**
|
|
372
|
-
* The identifier of the display targeted by screenshots.
|
|
372
|
+
* The identifier of the display targeted by screenshots, MJPEG, and new screen recordings.
|
|
373
373
|
* nil means the main display. Reset to nil for each new session.
|
|
374
374
|
*/
|
|
375
375
|
@property (atomic, copy, nullable) NSNumber *currentDisplayId;
|
|
@@ -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
|
-
|
|
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
|
|
@@ -13,12 +13,26 @@
|
|
|
13
13
|
#import "FBXCodeCompatibility.h"
|
|
14
14
|
#import "XCUIDevice.h"
|
|
15
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
|
+
}
|
|
16
30
|
|
|
17
31
|
@implementation FBScreen
|
|
18
32
|
|
|
19
33
|
+ (nullable NSArray<NSDictionary<NSString *, id> *> *)screensWithError:(NSError **)error
|
|
20
34
|
{
|
|
21
|
-
NSArray<XCUIScreen *> *screens =
|
|
35
|
+
NSArray<XCUIScreen *> *screens = FBScreensForDevice(XCUIDevice.sharedDevice, error);
|
|
22
36
|
if (nil == screens) {
|
|
23
37
|
return nil;
|
|
24
38
|
}
|
|
@@ -42,27 +56,56 @@
|
|
|
42
56
|
return result.copy;
|
|
43
57
|
}
|
|
44
58
|
|
|
45
|
-
|
|
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
|
|
46
63
|
{
|
|
47
|
-
|
|
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);
|
|
48
81
|
if (nil == screens) {
|
|
82
|
+
if (NULL != error) {
|
|
83
|
+
*error = lookupError ?: enumerationError;
|
|
84
|
+
}
|
|
49
85
|
return nil;
|
|
50
86
|
}
|
|
87
|
+
NSMutableArray<NSNumber *> *availableIDs = [NSMutableArray arrayWithCapacity:screens.count];
|
|
51
88
|
for (XCUIScreen *screen in screens) {
|
|
52
|
-
if (screen.displayID == displayID) {
|
|
89
|
+
if (needsEnumeratedLookup && screen.displayID == displayID) {
|
|
53
90
|
return screen;
|
|
54
91
|
}
|
|
55
|
-
}
|
|
56
|
-
NSMutableArray<NSNumber *> *availableIDs = [NSMutableArray arrayWithCapacity:screens.count];
|
|
57
|
-
for (XCUIScreen *screen in screens) {
|
|
58
92
|
[availableIDs addObject:@(screen.displayID)];
|
|
59
93
|
}
|
|
60
|
-
[
|
|
61
|
-
|
|
62
|
-
|
|
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];
|
|
63
101
|
return nil;
|
|
64
102
|
}
|
|
65
103
|
|
|
104
|
+
+ (nullable XCUIScreen *)screenWithDisplayID:(long long)displayID error:(NSError **)error
|
|
105
|
+
{
|
|
106
|
+
return [self screenWithDisplayID:displayID device:XCUIDevice.sharedDevice error:error];
|
|
107
|
+
}
|
|
108
|
+
|
|
66
109
|
+ (nullable XCUIScreen *)currentScreenWithError:(NSError **)error
|
|
67
110
|
{
|
|
68
111
|
NSNumber *displayID = FBConfiguration.sharedInstance.currentDisplayId;
|
|
@@ -47,6 +47,7 @@ FOUNDATION_EXPORT const unsigned char WebDriverAgentLib_VersionString[];
|
|
|
47
47
|
#import <WebDriverAgentLib/XCTIssue+FBPatcher.h>
|
|
48
48
|
#import <WebDriverAgentLib/XCUIApplication+FBHelpers.h>
|
|
49
49
|
#import <WebDriverAgentLib/XCUIDevice+FBHelpers.h>
|
|
50
|
+
#import <WebDriverAgentLib/XCUIDevice+FBHinge.h>
|
|
50
51
|
#import <WebDriverAgentLib/XCUIDevice+FBRotation.h>
|
|
51
52
|
#import <WebDriverAgentLib/XCUIDevice+FBVoiceOver.h>
|
|
52
53
|
#import <WebDriverAgentLib/XCUIElement.h>
|
|
@@ -11,9 +11,58 @@
|
|
|
11
11
|
#import "FBIntegrationTestCase.h"
|
|
12
12
|
#import "FBConfiguration.h"
|
|
13
13
|
#import "FBScreen.h"
|
|
14
|
+
#import "FBActiveAppDetectionPoint.h"
|
|
15
|
+
#import "FBExceptions.h"
|
|
16
|
+
#import "FBScreenRecordingRequest.h"
|
|
14
17
|
#import "FBScreenshot.h"
|
|
15
18
|
#import "XCUIScreen.h"
|
|
16
19
|
|
|
20
|
+
@interface FBScreen (FBLookupTesting)
|
|
21
|
+
+ (nullable XCUIScreen *)screenWithDisplayID:(long long)displayID device:(id)device error:(NSError **)error;
|
|
22
|
+
@end
|
|
23
|
+
|
|
24
|
+
@interface FBEmptyScreenDouble : NSObject
|
|
25
|
+
@property (nonatomic) CGRect bounds;
|
|
26
|
+
@end
|
|
27
|
+
@implementation FBEmptyScreenDouble
|
|
28
|
+
@end
|
|
29
|
+
|
|
30
|
+
@interface FBScreenDeviceDouble : NSObject
|
|
31
|
+
@property (nonatomic) BOOL nativeLookupAvailable;
|
|
32
|
+
@property (nonatomic) BOOL enumerationUnavailable;
|
|
33
|
+
@property (nonatomic) NSUInteger enumerationCount;
|
|
34
|
+
@property (nonatomic, strong) XCUIScreen *nativeScreen;
|
|
35
|
+
@property (nonatomic, strong) NSArray<XCUIScreen *> *screens;
|
|
36
|
+
@property (nonatomic, strong) NSError *lookupError;
|
|
37
|
+
@property (nonatomic, strong) NSError *enumerationError;
|
|
38
|
+
@end
|
|
39
|
+
|
|
40
|
+
@implementation FBScreenDeviceDouble
|
|
41
|
+
- (BOOL)respondsToSelector:(SEL)selector
|
|
42
|
+
{
|
|
43
|
+
if (selector == NSSelectorFromString(@"screensOrError:") && self.enumerationUnavailable) {
|
|
44
|
+
return NO;
|
|
45
|
+
}
|
|
46
|
+
return selector == @selector(screenWithDisplayID:orError:)
|
|
47
|
+
? self.nativeLookupAvailable : [super respondsToSelector:selector];
|
|
48
|
+
}
|
|
49
|
+
- (XCUIScreen *)screenWithDisplayID:(long long)displayID orError:(NSError **)error
|
|
50
|
+
{
|
|
51
|
+
if (NULL != error) {
|
|
52
|
+
*error = self.lookupError;
|
|
53
|
+
}
|
|
54
|
+
return self.nativeScreen;
|
|
55
|
+
}
|
|
56
|
+
- (NSArray<XCUIScreen *> *)screensOrError:(NSError **)error
|
|
57
|
+
{
|
|
58
|
+
self.enumerationCount++;
|
|
59
|
+
if (NULL != error) {
|
|
60
|
+
*error = self.enumerationError;
|
|
61
|
+
}
|
|
62
|
+
return self.screens;
|
|
63
|
+
}
|
|
64
|
+
@end
|
|
65
|
+
|
|
17
66
|
@interface FBScreenTests : FBIntegrationTestCase
|
|
18
67
|
@end
|
|
19
68
|
|
|
@@ -62,9 +111,13 @@
|
|
|
62
111
|
- (void)testScreenWithDisplayID
|
|
63
112
|
{
|
|
64
113
|
NSError *error = nil;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
114
|
+
for (NSDictionary<NSString *, id> *entry in [FBScreen screensWithError:&error]) {
|
|
115
|
+
long long displayID = [entry[@"displayId"] longLongValue];
|
|
116
|
+
XCUIScreen *screen = [FBScreen screenWithDisplayID:displayID error:&error];
|
|
117
|
+
XCTAssertNotNil(screen);
|
|
118
|
+
XCTAssertNil(error);
|
|
119
|
+
XCTAssertEqual(screen.displayID, displayID);
|
|
120
|
+
}
|
|
68
121
|
|
|
69
122
|
XCTAssertNil([FBScreen screenWithDisplayID:[self unknownDisplayID] error:&error]);
|
|
70
123
|
XCTAssertNotNil(error);
|
|
@@ -77,6 +130,84 @@
|
|
|
77
130
|
XCTAssertTrue([error.localizedDescription containsString:availableDisplays]);
|
|
78
131
|
}
|
|
79
132
|
|
|
133
|
+
- (void)testNativeScreenLookupDoesNotEnumerateOnSuccess
|
|
134
|
+
{
|
|
135
|
+
FBScreenDeviceDouble *device = [FBScreenDeviceDouble new];
|
|
136
|
+
device.nativeLookupAvailable = YES;
|
|
137
|
+
device.nativeScreen = XCUIScreen.mainScreen;
|
|
138
|
+
NSError *error = nil;
|
|
139
|
+
XCTAssertEqualObjects([FBScreen screenWithDisplayID:device.nativeScreen.displayID device:device error:&error],
|
|
140
|
+
device.nativeScreen);
|
|
141
|
+
XCTAssertNil(error);
|
|
142
|
+
XCTAssertEqual(device.enumerationCount, 0UL);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
- (void)testNativeScreenPlaceholderMustBeAvailable
|
|
146
|
+
{
|
|
147
|
+
FBScreenDeviceDouble *device = [FBScreenDeviceDouble new];
|
|
148
|
+
device.nativeLookupAvailable = YES;
|
|
149
|
+
device.nativeScreen = (id)[FBEmptyScreenDouble new];
|
|
150
|
+
device.screens = @[XCUIScreen.mainScreen];
|
|
151
|
+
NSError *error = nil;
|
|
152
|
+
XCTAssertNil([FBScreen screenWithDisplayID:-1 device:device error:&error]);
|
|
153
|
+
XCTAssertNotNil(error);
|
|
154
|
+
XCTAssertEqual(device.enumerationCount, 1UL);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
- (void)testNativeScreenLookupFailureIncludesAvailableIDs
|
|
158
|
+
{
|
|
159
|
+
FBScreenDeviceDouble *device = [FBScreenDeviceDouble new];
|
|
160
|
+
device.nativeLookupAvailable = YES;
|
|
161
|
+
device.screens = @[XCUIScreen.mainScreen];
|
|
162
|
+
device.lookupError = [NSError errorWithDomain:@"NativeLookup" code:1 userInfo:nil];
|
|
163
|
+
NSError *error = nil;
|
|
164
|
+
XCTAssertNil([FBScreen screenWithDisplayID:-1 device:device error:&error]);
|
|
165
|
+
XCTAssertEqual(device.enumerationCount, 1UL);
|
|
166
|
+
XCTAssertTrue([error.localizedDescription containsString:@"Available display ids"]);
|
|
167
|
+
XCTAssertEqualObjects(error.userInfo[NSUnderlyingErrorKey], device.lookupError);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
- (void)testNativeScreenLookupErrorSurvivesEnumerationFailure
|
|
171
|
+
{
|
|
172
|
+
FBScreenDeviceDouble *device = [FBScreenDeviceDouble new];
|
|
173
|
+
device.nativeLookupAvailable = YES;
|
|
174
|
+
device.lookupError = [NSError errorWithDomain:@"NativeLookup" code:1 userInfo:nil];
|
|
175
|
+
device.enumerationError = [NSError errorWithDomain:@"Enumeration" code:2 userInfo:nil];
|
|
176
|
+
NSError *error = nil;
|
|
177
|
+
XCTAssertNil([FBScreen screenWithDisplayID:-1 device:device error:&error]);
|
|
178
|
+
XCTAssertEqualObjects(error, device.lookupError);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
- (void)testScreenLookupWithoutNativeSelector
|
|
182
|
+
{
|
|
183
|
+
FBScreenDeviceDouble *device = [FBScreenDeviceDouble new];
|
|
184
|
+
device.screens = @[XCUIScreen.mainScreen];
|
|
185
|
+
NSError *error = nil;
|
|
186
|
+
XCTAssertEqualObjects([FBScreen screenWithDisplayID:XCUIScreen.mainScreen.displayID device:device error:&error],
|
|
187
|
+
XCUIScreen.mainScreen);
|
|
188
|
+
XCTAssertNil(error);
|
|
189
|
+
XCTAssertEqual(device.enumerationCount, 1UL);
|
|
190
|
+
XCTAssertNil([FBScreen screenWithDisplayID:-1 device:device error:&error]);
|
|
191
|
+
XCTAssertTrue([error.localizedDescription containsString:@"Available display ids"]);
|
|
192
|
+
device.screens = nil;
|
|
193
|
+
device.enumerationError = [NSError errorWithDomain:@"Enumeration" code:2 userInfo:nil];
|
|
194
|
+
XCTAssertNil([FBScreen screenWithDisplayID:-1 device:device error:&error]);
|
|
195
|
+
XCTAssertEqualObjects(error, device.enumerationError);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
- (void)testScreenLookupWithoutDeviceScreenSelectors
|
|
199
|
+
{
|
|
200
|
+
FBScreenDeviceDouble *device = [FBScreenDeviceDouble new];
|
|
201
|
+
device.enumerationUnavailable = YES;
|
|
202
|
+
NSError *error = nil;
|
|
203
|
+
XCTAssertEqualObjects([FBScreen screenWithDisplayID:XCUIScreen.mainScreen.displayID device:device error:&error],
|
|
204
|
+
XCUIScreen.mainScreen);
|
|
205
|
+
XCTAssertNil(error);
|
|
206
|
+
XCTAssertNil([FBScreen screenWithDisplayID:-1 device:device error:&error]);
|
|
207
|
+
XCTAssertNotNil(error);
|
|
208
|
+
XCTAssertEqual(device.enumerationCount, 0UL);
|
|
209
|
+
}
|
|
210
|
+
|
|
80
211
|
- (void)testCurrentScreenDefaultsToMainScreen
|
|
81
212
|
{
|
|
82
213
|
NSError *error = nil;
|
|
@@ -108,6 +239,52 @@
|
|
|
108
239
|
XCTAssertNil(FBConfiguration.sharedInstance.currentDisplayId);
|
|
109
240
|
}
|
|
110
241
|
|
|
242
|
+
- (void)testSecondaryDisplayDoesNotHitTestMainDisplay
|
|
243
|
+
{
|
|
244
|
+
NSArray<NSDictionary<NSString *, id> *> *screens = [FBScreen screensWithError:nil];
|
|
245
|
+
BOOL testedSecondaryDisplay = NO;
|
|
246
|
+
for (NSDictionary<NSString *, id> *screen in screens) {
|
|
247
|
+
if ([screen[@"isMain"] boolValue]) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
testedSecondaryDisplay = YES;
|
|
251
|
+
FBConfiguration.sharedInstance.currentDisplayId = screen[@"displayId"];
|
|
252
|
+
XCTAssertNil(FBActiveAppDetectionPoint.sharedInstance.axElement);
|
|
253
|
+
}
|
|
254
|
+
XCTSkipIf(!testedSecondaryDisplay, @"Requires a secondary display");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
- (void)testActiveAppDetectionRejectsUnavailableDisplay
|
|
258
|
+
{
|
|
259
|
+
FBConfiguration.sharedInstance.currentDisplayId = @([self unknownDisplayID]);
|
|
260
|
+
XCTAssertThrowsSpecificNamed(FBActiveAppDetectionPoint.sharedInstance.axElement,
|
|
261
|
+
NSException, FBInvalidArgumentException);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
- (void)testRecordingUsesSelectedDisplay
|
|
265
|
+
{
|
|
266
|
+
XCTSkipIf(nil == NSClassFromString(@"XCTScreenRecordingRequest"), @"Screen recording is unavailable");
|
|
267
|
+
FBScreenRecordingRequest *request = [[FBScreenRecordingRequest alloc] initWithFps:10 codec:0];
|
|
268
|
+
NSError *error = nil;
|
|
269
|
+
id defaultRequest = [request toNativeRequestWithError:&error];
|
|
270
|
+
XCTAssertNotNil(defaultRequest);
|
|
271
|
+
XCTAssertNil(error);
|
|
272
|
+
XCTAssertEqualObjects([defaultRequest valueForKey:@"screenID"], @([FBScreen displayID]));
|
|
273
|
+
|
|
274
|
+
for (NSDictionary<NSString *, id> *screen in [FBScreen screensWithError:nil]) {
|
|
275
|
+
FBConfiguration.sharedInstance.currentDisplayId = screen[@"displayId"];
|
|
276
|
+
id nativeRequest = [request toNativeRequestWithError:&error];
|
|
277
|
+
XCTAssertNotNil(nativeRequest);
|
|
278
|
+
XCTAssertNil(error);
|
|
279
|
+
XCTAssertEqualObjects([nativeRequest valueForKey:@"screenID"], screen[@"displayId"]);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
FBConfiguration.sharedInstance.currentDisplayId = @([self unknownDisplayID]);
|
|
283
|
+
XCTAssertNil([request toNativeRequestWithError:&error]);
|
|
284
|
+
XCTAssertNotNil(error);
|
|
285
|
+
XCTAssertTrue([error.localizedDescription containsString:@"Available display ids"]);
|
|
286
|
+
}
|
|
287
|
+
|
|
111
288
|
- (long long)unknownDisplayID
|
|
112
289
|
{
|
|
113
290
|
long long maxID = 0;
|
|
@@ -12,12 +12,17 @@
|
|
|
12
12
|
#import "FBElementCache.h"
|
|
13
13
|
#import "FBRouteRequest-Private.h"
|
|
14
14
|
#import "FBSession.h"
|
|
15
|
+
#import "FBResponsePayload.h"
|
|
16
|
+
#import "RouteResponse.h"
|
|
17
|
+
#import "XCUIDevice+FBHinge.h"
|
|
15
18
|
#import "Doubles/XCUIElementDouble.h"
|
|
16
19
|
|
|
17
20
|
#if !TARGET_OS_TV && __clang_major__ >= 15
|
|
18
21
|
|
|
19
22
|
@interface FBCustomCommands (FBWDATestable)
|
|
20
23
|
+ (id<FBResponsePayload>)handleKeyboardInput:(FBRouteRequest *)request;
|
|
24
|
+
+ (id<FBResponsePayload>)handleGetSimulatedHingeAngle:(FBRouteRequest *)request;
|
|
25
|
+
+ (id<FBResponsePayload>)handleSetSimulatedHingeAngle:(FBRouteRequest *)request;
|
|
21
26
|
@end
|
|
22
27
|
|
|
23
28
|
@interface FBCustomCommandsTests : XCTestCase
|
|
@@ -78,6 +83,62 @@
|
|
|
78
83
|
XCTAssertEqual(element.lastTypedModifierFlags, 2);
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
- (NSDictionary *)hingeResponseWithArguments:(NSDictionary *)arguments
|
|
87
|
+
{
|
|
88
|
+
FBRouteRequest *request = [FBRouteRequest routeRequestWithURL:[NSURL URLWithString:@"http://localhost:8100/"]
|
|
89
|
+
parameters:@{}
|
|
90
|
+
arguments:arguments];
|
|
91
|
+
RouteResponse *response = [RouteResponse new];
|
|
92
|
+
[[FBCustomCommands handleSetSimulatedHingeAngle:request] dispatchWithResponse:response];
|
|
93
|
+
return [NSJSONSerialization JSONObjectWithData:response.responseData options:0 error:nil];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
- (void)testHingeAngleReading
|
|
97
|
+
{
|
|
98
|
+
NSError *error = nil;
|
|
99
|
+
NSNumber *angle = [XCUIDevice.sharedDevice fb_getSimulatedHingeAngle:&error];
|
|
100
|
+
BOOL available = XCUIDevice.sharedDevice.fb_supportsHingeAngleReading;
|
|
101
|
+
if (available) {
|
|
102
|
+
XCTAssertNotNil(angle);
|
|
103
|
+
XCTAssertNil(error);
|
|
104
|
+
XCTAssertTrue(isfinite(angle.doubleValue));
|
|
105
|
+
} else {
|
|
106
|
+
XCTAssertNil(angle);
|
|
107
|
+
XCTAssertNotNil(error);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
FBRouteRequest *request = [FBRouteRequest routeRequestWithURL:[NSURL URLWithString:@"http://localhost:8100/"]
|
|
111
|
+
parameters:@{}
|
|
112
|
+
arguments:@{}];
|
|
113
|
+
RouteResponse *response = [RouteResponse new];
|
|
114
|
+
[[FBCustomCommands handleGetSimulatedHingeAngle:request] dispatchWithResponse:response];
|
|
115
|
+
NSDictionary *body = [NSJSONSerialization JSONObjectWithData:response.responseData options:0 error:nil];
|
|
116
|
+
if (available) {
|
|
117
|
+
XCTAssertTrue([body[@"value"] isKindOfClass:NSNumber.class]);
|
|
118
|
+
XCTAssertTrue(isfinite([body[@"value"] doubleValue]));
|
|
119
|
+
} else {
|
|
120
|
+
XCTAssertEqualObjects(body[@"value"][@"error"], @"unsupported operation");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
- (void)testHingeAngleRejectsInvalidArguments
|
|
125
|
+
{
|
|
126
|
+
for (id value in @[@(-1), @181, @YES, @"90", NSNull.null, @(NAN), @(INFINITY)]) {
|
|
127
|
+
NSDictionary *response = [self hingeResponseWithArguments:@{@"angle": value}];
|
|
128
|
+
XCTAssertEqualObjects(response[@"value"][@"error"], @"invalid argument");
|
|
129
|
+
}
|
|
130
|
+
XCTAssertEqualObjects([self hingeResponseWithArguments:@{}][@"value"][@"error"], @"invalid argument");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
- (void)testHingeAngleRejectsUnsupportedDevices
|
|
134
|
+
{
|
|
135
|
+
XCTSkipIf(XCUIDevice.sharedDevice.fb_canAttemptSimulatedHingeAngleInjection, @"Requires a device without an available hinge");
|
|
136
|
+
for (NSNumber *angle in @[@0, @90, @180]) {
|
|
137
|
+
XCTAssertEqualObjects([self hingeResponseWithArguments:@{@"angle": angle}][@"value"][@"error"],
|
|
138
|
+
@"unsupported operation");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
81
142
|
@end
|
|
82
143
|
|
|
83
144
|
#endif
|
|
@@ -12,12 +12,52 @@
|
|
|
12
12
|
#import "FBElement.h"
|
|
13
13
|
#import "XCUIElementDouble.h"
|
|
14
14
|
#import "FBElementUtils.h"
|
|
15
|
+
#import "FBConfiguration.h"
|
|
16
|
+
#import "XCUIElement+FBUtilities.h"
|
|
17
|
+
#import <objc/runtime.h>
|
|
18
|
+
|
|
19
|
+
@interface FBQuiescenceApplicationDouble : NSObject
|
|
20
|
+
@property (nonatomic) BOOL fb_shouldWaitForQuiescence;
|
|
21
|
+
@property (nonatomic) NSTimeInterval observedTimeout;
|
|
22
|
+
@end
|
|
23
|
+
@implementation FBQuiescenceApplicationDouble
|
|
24
|
+
- (id)applicationImpl { return self; }
|
|
25
|
+
- (id)currentProcess { return self; }
|
|
26
|
+
- (void)fb_waitForQuiescenceIncludingAnimationsIdle:(BOOL)includingAnimations
|
|
27
|
+
{
|
|
28
|
+
self.observedTimeout = FBConfiguration.sharedInstance.waitForIdleTimeout;
|
|
29
|
+
@throw [NSException exceptionWithName:@"QuiescenceFailure" reason:@"test" userInfo:nil];
|
|
30
|
+
}
|
|
31
|
+
@end
|
|
15
32
|
|
|
16
33
|
@interface FBElementUtilitiesTests : XCTestCase
|
|
17
34
|
@end
|
|
18
35
|
|
|
19
36
|
@implementation FBElementUtilitiesTests
|
|
20
37
|
|
|
38
|
+
- (void)testStabilityWaitRestoresSettingsAfterAnException
|
|
39
|
+
{
|
|
40
|
+
NSTimeInterval originalTimeout = FBConfiguration.sharedInstance.waitForIdleTimeout;
|
|
41
|
+
FBQuiescenceApplicationDouble *application = [FBQuiescenceApplicationDouble new];
|
|
42
|
+
XCUIElementDouble *element = [XCUIElementDouble new];
|
|
43
|
+
element.application = (id)application;
|
|
44
|
+
SEL selector = @selector(fb_waitUntilStableWithTimeout:);
|
|
45
|
+
#pragma clang diagnostic push
|
|
46
|
+
#pragma clang diagnostic ignored "-Wcast-function-type-strict"
|
|
47
|
+
void (*wait)(id, SEL, NSTimeInterval) = (void (*)(id, SEL, NSTimeInterval))
|
|
48
|
+
class_getMethodImplementation(XCUIElement.class, selector);
|
|
49
|
+
#pragma clang diagnostic pop
|
|
50
|
+
@try {
|
|
51
|
+
FBConfiguration.sharedInstance.waitForIdleTimeout = 17;
|
|
52
|
+
XCTAssertThrowsSpecificNamed(wait(element, selector, 2), NSException, @"QuiescenceFailure");
|
|
53
|
+
XCTAssertEqual(application.observedTimeout, 2);
|
|
54
|
+
XCTAssertFalse(application.fb_shouldWaitForQuiescence);
|
|
55
|
+
XCTAssertEqual(FBConfiguration.sharedInstance.waitForIdleTimeout, 17);
|
|
56
|
+
} @finally {
|
|
57
|
+
FBConfiguration.sharedInstance.waitForIdleTimeout = originalTimeout;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
21
61
|
- (void)testTypesFiltering {
|
|
22
62
|
NSMutableArray *elements = [NSMutableArray new];
|
|
23
63
|
XCUIElementDouble *el1 = [XCUIElementDouble new];
|