diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index a324a9b7d..89887d258 100644 --- a/.azure-pipelines.yml +++ b/.azure-pipelines.yml @@ -1,19 +1,19 @@ # https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops&tabs=yaml variables: - MIN_VM_IMAGE: macOS-12 - MIN_XCODE_VERSION: 13.1 - MIN_PLATFORM_VERSION: 15.0 - MIN_TV_PLATFORM_VERSION: 15.0 - MIN_TV_DEVICE_NAME: Apple TV 4K (2nd generation) - MIN_IPHONE_DEVICE_NAME: iPhone 11 - MIN_IPAD_DEVICE_NAME: iPad Pro (11-inch) (3rd generation) - MAX_VM_IMAGE: macOS-12 - MAX_XCODE_VERSION: 14.2 - MAX_PLATFORM_VERSION: 16.2 - MAX_PLATFORM_VERSION_TV: 16.1 - MAX_IPHONE_DEVICE_NAME: iPhone 13 - MAX_TV_DEVICE_NAME: Apple TV 4K (2nd generation) - MAX_IPAD_DEVICE_NAME: iPad Pro (11-inch) (3rd generation) + MIN_VM_IMAGE: macOS-14 + MIN_XCODE_VERSION: 14.3.1 + MIN_PLATFORM_VERSION: 16.4 + MIN_TV_PLATFORM_VERSION: 16.4 + MIN_TV_DEVICE_NAME: Apple TV 4K (3rd generation) + MIN_IPHONE_DEVICE_NAME: iPhone 14 Plus + MIN_IPAD_DEVICE_NAME: iPad Pro (11-inch) (4th generation) + MAX_VM_IMAGE: macOS-14 + MAX_XCODE_VERSION: 15.4 + MAX_PLATFORM_VERSION: 17.5 + MAX_PLATFORM_VERSION_TV: 17.5 + MAX_IPHONE_DEVICE_NAME: iPhone 15 Plus + MAX_TV_DEVICE_NAME: Apple TV 4K (3rd generation) + MAX_IPAD_DEVICE_NAME: iPad Air 11-inch (M2) DEFAULT_NODE_VERSION: "18.x" trigger: diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml index e5c1ebad9..4e51234c4 100644 --- a/.github/workflows/functional-test.yml +++ b/.github/workflows/functional-test.yml @@ -2,17 +2,28 @@ name: Functional Tests on: [pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: test: - env: - CI: true - _FORCE_LOGS: 1 - XCODE_VERSION: 13.4 - DEVICE_NAME: iPhone 11 - PLATFORM_VERSION: 15.5 - # https://github.com/actions/runner-images/blob/main/images/macos/macos-12-Readme.md - runs-on: macos-12 + strategy: + fail-fast: false + matrix: + test_targets: + - XCODE_VERSION: '16.0.0' + IOS_VERSION: '18.0' + IOS_MODEL: iPhone 15 Plus + - XCODE_VERSION: '15.3' + IOS_VERSION: '17.4' + IOS_MODEL: iPhone 15 Plus + - XCODE_VERSION: 14.3.1 + IOS_VERSION: '16.4' + IOS_MODEL: iPhone 14 Plus + + # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md + runs-on: macos-14 steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 @@ -20,17 +31,28 @@ jobs: node-version: lts/* - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: "${{ env.XCODE_VERSION }}" + xcode-version: ${{matrix.test_targets.XCODE_VERSION}} - run: | npm install mkdir -p ./Resources/WebDriverAgent.bundle name: Install dev dependencies - - run: | - target_sim_id=$(xcrun simctl list devices available | grep "$DEVICE_NAME (" | cut -d "(" -f2 | cut -d ")" -f1) + - name: Prepare iOS simulator + env: + DEVICE_NAME: ${{matrix.test_targets.IOS_MODEL}} + PLATFORM_VERSION: ${{matrix.test_targets.IOS_VERSION}} + run: | open -Fn "$(xcode-select -p)/Applications/Simulator.app" - xcrun simctl bootstatus $target_sim_id -b - name: Preboot Simulator + udid=$(xcrun simctl list devices available -j | \ + node -p "Object.entries(JSON.parse(fs.readFileSync(0)).devices).filter((x) => x[0].includes('$PLATFORM_VERSION'.replace('.', '-'))).reduce((acc, x) => [...acc, ...x[1]], []).find(({name}) => name === '$DEVICE_NAME').udid") + xcrun simctl bootstatus $udid -b + xcrun simctl shutdown $udid - run: npm run e2e-test name: Run functional tests + env: + CI: true + _FORCE_LOGS: 1 + _LOG_TIMESTAMP: 1 + DEVICE_NAME: ${{matrix.test_targets.IOS_MODEL}} + PLATFORM_VERSION: ${{matrix.test_targets.IOS_VERSION}} diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 9a192aec5..0ff687ca8 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -8,7 +8,7 @@ jobs: name: https://www.conventionalcommits.org runs-on: ubuntu-latest steps: - - uses: beemojs/conventional-pr-action@v2 + - uses: beemojs/conventional-pr-action@v3 with: config-preset: angular env: diff --git a/.github/workflows/publish.js.yml b/.github/workflows/publish.js.yml index 92299e7a8..5295e5802 100644 --- a/.github/workflows/publish.js.yml +++ b/.github/workflows/publish.js.yml @@ -10,10 +10,10 @@ on: jobs: build: - runs-on: macos-13 + runs-on: macos-14 env: - XCODE_VERSION: 14.3.1 + XCODE_VERSION: 15.3 ZIP_PKG_NAME_IOS: "WebDriverAgentRunner-Runner.zip" PKG_PATH_IOS: "appium_wda_ios" ZIP_PKG_NAME_TVOS: "WebDriverAgentRunner_tvOS-Runner.zip" diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index ef98e4ca6..b23ecbee6 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -7,11 +7,11 @@ jobs: prepare_matrix: runs-on: ubuntu-latest outputs: - versions: ${{ steps.generate-matrix.outputs.versions }} + versions: ${{ steps.generate-matrix.outputs.active }} steps: - - name: Select 3 most recent LTS versions of Node.js + - name: Select all active LTS versions of Node.js id: generate-matrix - run: echo "versions=$(curl -s https://endoflife.date/api/nodejs.json | jq -c '[[.[] | select(.lts != false)][:3] | .[].cycle | tonumber]')" >> "$GITHUB_OUTPUT" + uses: msimerson/node-lts-versions@v1 test: needs: diff --git a/.github/workflows/wda-package.yml b/.github/workflows/wda-package.yml index b4b4223f2..8a7d7a526 100644 --- a/.github/workflows/wda-package.yml +++ b/.github/workflows/wda-package.yml @@ -8,10 +8,10 @@ on: - completed env: - HOST: macos-13 - XCODE_VERSION: 14.3.1 - DESTINATION_SIM: platform=iOS Simulator,name=iPhone 14 Pro - DESTINATION_SIM_tvOS: platform=tvOS Simulator,name=Apple TV + HOST: macos-14 + XCODE_VERSION: 15.3 + DESTINATION_SIM: platform=iOS Simulator,name=iPhone 15 Pro + DESTINATION_SIM_tvOS: platform=tvOS Simulator,name=Apple TV 4K (3rd generation) jobs: host_machine: diff --git a/CHANGELOG.md b/CHANGELOG.md index ae9968eeb..619060ee0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,355 @@ +## [8.9.4](https://github.com/appium/WebDriverAgent/compare/v8.9.3...v8.9.4) (2024-10-17) + +### Bug Fixes + +* Consider transient overlay windows when respectSystemAlerts is enabled ([#946](https://github.com/appium/WebDriverAgent/issues/946)) ([f0bdce7](https://github.com/appium/WebDriverAgent/commit/f0bdce7eb8fdb13d2309d28e936950c77f006b20)) + +## [8.9.3](https://github.com/appium/WebDriverAgent/compare/v8.9.2...v8.9.3) (2024-10-07) + +### Miscellaneous Chores + +* remove unused FBBaseActionsParser and cleanup imports in FBConfiguration ([#943](https://github.com/appium/WebDriverAgent/issues/943)) ([a2173d0](https://github.com/appium/WebDriverAgent/commit/a2173d05df8ef831310e805a8e6a8a8d17725201)) + +## [8.9.2](https://github.com/appium/WebDriverAgent/compare/v8.9.1...v8.9.2) (2024-09-13) + +### Miscellaneous Chores + +* **deps-dev:** bump sinon from 18.0.1 to 19.0.1 ([#938](https://github.com/appium/WebDriverAgent/issues/938)) ([3ef0093](https://github.com/appium/WebDriverAgent/commit/3ef009317801dca47efe34bd048d3cab2e644ee2)) + +## [8.9.1](https://github.com/appium/WebDriverAgent/compare/v8.9.0...v8.9.1) (2024-08-09) + +### Bug Fixes + +* Update swizzling of waitForQuiescenceIncludingAnimationsIdle: API for Xcode16-beta5 ([#935](https://github.com/appium/WebDriverAgent/issues/935)) ([2ccc436](https://github.com/appium/WebDriverAgent/commit/2ccc436991ca880a1dfdec688dc8167008fe382d)) + +## [8.9.0](https://github.com/appium/WebDriverAgent/compare/v8.8.0...v8.9.0) (2024-08-07) + +### Features + +* Add idleTimeoutMs param to the openUrl call ([#933](https://github.com/appium/WebDriverAgent/issues/933)) ([5e98841](https://github.com/appium/WebDriverAgent/commit/5e98841f56eda6454d67d813b921bfcf98f1ff78)) + +### Bug Fixes + +* Revert the logic to open the default URL in Safari via deeplink ([#932](https://github.com/appium/WebDriverAgent/issues/932)) ([7c51145](https://github.com/appium/WebDriverAgent/commit/7c5114518509c9a399845283eca7708248fb838f)) + +## [8.8.0](https://github.com/appium/WebDriverAgent/compare/v8.7.12...v8.8.0) (2024-08-06) + +### Features + +* Open the default URL in Safari upon session startup ([#929](https://github.com/appium/WebDriverAgent/issues/929)) ([97cf91d](https://github.com/appium/WebDriverAgent/commit/97cf91de34dc53e5f75f91829dc43224101c1b45)) + +## [8.7.12](https://github.com/appium/WebDriverAgent/compare/v8.7.11...v8.7.12) (2024-08-02) + +### Miscellaneous Chores + +* Replace fancy-log dependency with appium logger ([#928](https://github.com/appium/WebDriverAgent/issues/928)) ([5d2ec24](https://github.com/appium/WebDriverAgent/commit/5d2ec249488655451e2d46384e560fee7e08e840)) + +## [8.7.11](https://github.com/appium/WebDriverAgent/compare/v8.7.10...v8.7.11) (2024-07-29) + +### Bug Fixes + +* Respond to /health with a proper HTML ([#925](https://github.com/appium/WebDriverAgent/issues/925)) ([42c519f](https://github.com/appium/WebDriverAgent/commit/42c519f9df7beec81175fd38af388975d6f6b800)) + +## [8.7.10](https://github.com/appium/WebDriverAgent/compare/v8.7.9...v8.7.10) (2024-07-29) + +### Miscellaneous Chores + +* **deps-dev:** bump @types/node from 20.14.13 to 22.0.0 ([#926](https://github.com/appium/WebDriverAgent/issues/926)) ([1699023](https://github.com/appium/WebDriverAgent/commit/1699023086a243c3d86ddae4da8342c6beda3f48)) + +## [8.7.9](https://github.com/appium/WebDriverAgent/compare/v8.7.8...v8.7.9) (2024-07-21) + +### Miscellaneous Chores + +* keep error handling for the future possible usage ([#921](https://github.com/appium/WebDriverAgent/issues/921)) ([2f90739](https://github.com/appium/WebDriverAgent/commit/2f90739340d70073b48c703b36b9a313d3618972)) + +## [8.7.8](https://github.com/appium/WebDriverAgent/compare/v8.7.7...v8.7.8) (2024-07-18) + +### Bug Fixes + +* do nothing for an empty array in w3c actions ([#919](https://github.com/appium/WebDriverAgent/issues/919)) ([9e70ec1](https://github.com/appium/WebDriverAgent/commit/9e70ec1dbec1d1844278a58297a5b956ebaeb7fc)) + +## [8.7.7](https://github.com/appium/WebDriverAgent/compare/v8.7.6...v8.7.7) (2024-07-18) + +### Bug Fixes + +* Pass-through modifier keys ([#918](https://github.com/appium/WebDriverAgent/issues/918)) ([29d0e5c](https://github.com/appium/WebDriverAgent/commit/29d0e5cb2a19809e1babb06e5adaa49b43c754a5)) + +## [8.7.6](https://github.com/appium/WebDriverAgent/compare/v8.7.5...v8.7.6) (2024-07-02) + +### Miscellaneous Chores + +* Simplify xcodebuild lines monitoring ([#916](https://github.com/appium/WebDriverAgent/issues/916)) ([87678f2](https://github.com/appium/WebDriverAgent/commit/87678f260c98b3a3bc3d37017e9ef39098ccb3c4)) + +## [8.7.5](https://github.com/appium/WebDriverAgent/compare/v8.7.4...v8.7.5) (2024-06-26) + +### Bug Fixes + +* Respect wdaRemotePort capability for real devices ([#915](https://github.com/appium/WebDriverAgent/issues/915)) ([03ea143](https://github.com/appium/WebDriverAgent/commit/03ea1439a9cc5b6495be60707bc474e3ae9bdb06)) + +## [8.7.4](https://github.com/appium/WebDriverAgent/compare/v8.7.3...v8.7.4) (2024-06-20) + +### Miscellaneous Chores + +* Bump chai and chai-as-promised ([#913](https://github.com/appium/WebDriverAgent/issues/913)) ([9086783](https://github.com/appium/WebDriverAgent/commit/90867832ec3077f0036938aa68a168a5702fc90a)) + +## [8.7.3](https://github.com/appium/WebDriverAgent/compare/v8.7.2...v8.7.3) (2024-06-12) + +### Miscellaneous Chores + +* **deps:** bump @appium/support from 4.5.0 to 5.0.3 ([#910](https://github.com/appium/WebDriverAgent/issues/910)) ([936005b](https://github.com/appium/WebDriverAgent/commit/936005b458e7b5b64b60d9bda37d45bb5a90e615)) + +## [8.7.2](https://github.com/appium/WebDriverAgent/compare/v8.7.1...v8.7.2) (2024-06-04) + +### Miscellaneous Chores + +* **deps-dev:** bump sinon from 17.0.2 to 18.0.0 ([#903](https://github.com/appium/WebDriverAgent/issues/903)) ([87e4ba5](https://github.com/appium/WebDriverAgent/commit/87e4ba5ce3868d99ac889795039936be119ef87a)) + +## [8.7.1](https://github.com/appium/WebDriverAgent/compare/v8.7.0...v8.7.1) (2024-06-04) + +### Miscellaneous Chores + +* **deps-dev:** bump semantic-release from 23.1.1 to 24.0.0 and conventional-changelog-conventionalcommits to 8.0.0 ([#908](https://github.com/appium/WebDriverAgent/issues/908)) ([26019ec](https://github.com/appium/WebDriverAgent/commit/26019eca9b7331353e26a1014bc4afcecc0450f3)) + +## [8.7.0](https://github.com/appium/WebDriverAgent/compare/v8.6.0...v8.7.0) (2024-06-01) + + +### Features + +* Add a setting to respect system alerts while detecting active apps ([#907](https://github.com/appium/WebDriverAgent/issues/907)) ([5c82d66](https://github.com/appium/WebDriverAgent/commit/5c82d66890b1a74f9b6f698c87590b2154a6c1bd)) + +## [8.6.0](https://github.com/appium/WebDriverAgent/compare/v8.5.7...v8.6.0) (2024-05-17) + + +### Features + +* support maxTypingFrequency in settings api ([#904](https://github.com/appium/WebDriverAgent/issues/904)) ([fa4776a](https://github.com/appium/WebDriverAgent/commit/fa4776a2bfa15cbec8bba35d8ed11318d9629934)) + +## [8.5.7](https://github.com/appium/WebDriverAgent/compare/v8.5.6...v8.5.7) (2024-05-16) + + +### Miscellaneous Chores + +* Update dev dependencies ([e49dcf2](https://github.com/appium/WebDriverAgent/commit/e49dcf2afb0a10edc7085ac56d297234c00d57b0)) + +## [8.5.6](https://github.com/appium/WebDriverAgent/compare/v8.5.5...v8.5.6) (2024-04-20) + + +### Bug Fixes + +* unit test for linux ([#894](https://github.com/appium/WebDriverAgent/issues/894)) ([3a90158](https://github.com/appium/WebDriverAgent/commit/3a9015898d70b177cb6cbfcaf412dfa3c4ec3865)) + +## [8.5.5](https://github.com/appium/WebDriverAgent/compare/v8.5.4...v8.5.5) (2024-04-20) + + +### Bug Fixes + +* xcode warning about com.facebook.wda.lib ([#892](https://github.com/appium/WebDriverAgent/issues/892)) ([6398079](https://github.com/appium/WebDriverAgent/commit/63980796d8f40bd68ffb5af4b085a2348e544a13)) + +## [8.5.4](https://github.com/appium/WebDriverAgent/compare/v8.5.3...v8.5.4) (2024-04-20) + + +### Miscellaneous Chores + +* remove old iOS/Xcode related test code and errors ([#890](https://github.com/appium/WebDriverAgent/issues/890)) ([2fd0dea](https://github.com/appium/WebDriverAgent/commit/2fd0dead0c86d6be08e040360dec9ea085ba0392)) + +## [8.5.3](https://github.com/appium/WebDriverAgent/compare/v8.5.2...v8.5.3) (2024-04-19) + + +### Miscellaneous Chores + +* update integerationapp for newer OS env ([#891](https://github.com/appium/WebDriverAgent/issues/891)) ([2c78348](https://github.com/appium/WebDriverAgent/commit/2c7834842afeb1aec77e953ce11ac3c43c839431)) + +## [8.5.2](https://github.com/appium/WebDriverAgent/compare/v8.5.1...v8.5.2) (2024-04-09) + + +### Miscellaneous Chores + +* **deps-dev:** bump @typescript-eslint/parser from 6.21.0 to 7.6.0 ([#888](https://github.com/appium/WebDriverAgent/issues/888)) ([ead75eb](https://github.com/appium/WebDriverAgent/commit/ead75eb87a5c8e94088bace8f372ab137dcf57ad)) +* Remove extra imports ([fb25742](https://github.com/appium/WebDriverAgent/commit/fb25742a07a2fbcb0365a48d54117267c7c916df)) + +## [8.5.1](https://github.com/appium/WebDriverAgent/compare/v8.5.0...v8.5.1) (2024-04-08) + + +### Miscellaneous Chores + +* Add more type declarations ([#886](https://github.com/appium/WebDriverAgent/issues/886)) ([9ca7632](https://github.com/appium/WebDriverAgent/commit/9ca7632faf999931e7f5edf47267fcce6d6392b2)) + +## [8.5.0](https://github.com/appium/WebDriverAgent/compare/v8.4.0...v8.5.0) (2024-04-07) + + +### Features + +* Add types for WDA caps and settings ([#885](https://github.com/appium/WebDriverAgent/issues/885)) ([4b3c220](https://github.com/appium/WebDriverAgent/commit/4b3c220c0c609802924b7b6ff9a4dfa7a98eb5f4)) + +## [8.4.0](https://github.com/appium/WebDriverAgent/compare/v8.3.1...v8.4.0) (2024-04-01) + + +### Features + +* add system screen size/width in the system info endpoint ([#881](https://github.com/appium/WebDriverAgent/issues/881)) ([5ebc71c](https://github.com/appium/WebDriverAgent/commit/5ebc71c6ca2b364d44a44716e794885f8d3b6d9c)) + +## [8.3.1](https://github.com/appium/WebDriverAgent/compare/v8.3.0...v8.3.1) (2024-03-31) + + +### Miscellaneous Chores + +* do not cleanup with this.usePrebuiltWDA ([#882](https://github.com/appium/WebDriverAgent/issues/882)) ([0436e95](https://github.com/appium/WebDriverAgent/commit/0436e95752826bee7786577ac1bc0d056af11bc8)) + +## [8.3.0](https://github.com/appium/WebDriverAgent/compare/v8.2.1...v8.3.0) (2024-03-29) + + +### Features + +* Add module version to the /status output ([#878](https://github.com/appium/WebDriverAgent/issues/878)) ([a9603f8](https://github.com/appium/WebDriverAgent/commit/a9603f82acbdacdeb7a55b857512ba35353a4bc3)) + +## [8.2.1](https://github.com/appium/WebDriverAgent/compare/v8.2.0...v8.2.1) (2024-03-28) + + +### Miscellaneous Chores + +* wait for wda start in sim as well for preinstalled wda start ([#876](https://github.com/appium/WebDriverAgent/issues/876)) ([6c8920a](https://github.com/appium/WebDriverAgent/commit/6c8920adddb373b463259c3e6c14cb3c49ecbf2b)) + +## [8.2.0](https://github.com/appium/WebDriverAgent/compare/v8.1.0...v8.2.0) (2024-03-28) + + +### Features + +* Add a capability to customize the default state change timeout on app startup ([#877](https://github.com/appium/WebDriverAgent/issues/877)) ([98351c3](https://github.com/appium/WebDriverAgent/commit/98351c358367e67e63701612fd3702d53437e12e)) + +## [8.1.0](https://github.com/appium/WebDriverAgent/compare/v8.0.2...v8.1.0) (2024-03-26) + + +### Features + +* add updatedWDABundleIdSuffix to handle bundle id for updatedWDABundleId with usePreinstalledWDA ([#871](https://github.com/appium/WebDriverAgent/issues/871)) ([d79b624](https://github.com/appium/WebDriverAgent/commit/d79b6245966baaa57f7a1f785d7f9b4ea5a7f104)) + +## [8.0.2](https://github.com/appium/WebDriverAgent/compare/v8.0.1...v8.0.2) (2024-03-26) + + +### Miscellaneous Chores + +* **deps:** bump appium-ios-simulator from 5.5.3 to 6.0.0 ([#874](https://github.com/appium/WebDriverAgent/issues/874)) ([72f2a97](https://github.com/appium/WebDriverAgent/commit/72f2a97ec31dbb3c66e5f459e0d7fd417c197d5d)) + +## [8.0.1](https://github.com/appium/WebDriverAgent/compare/v8.0.0...v8.0.1) (2024-03-26) + + +### Miscellaneous Chores + +* use bundle id outside opts for this.device.devicectl.launchApp ([#872](https://github.com/appium/WebDriverAgent/issues/872)) ([e2aeda2](https://github.com/appium/WebDriverAgent/commit/e2aeda2f2020f4014cba478b459e47954175f597)) + +## [8.0.0](https://github.com/appium/WebDriverAgent/compare/v7.3.1...v8.0.0) (2024-03-25) + + +### ⚠ BREAKING CHANGES + +* calls launch app process command with devicectl via this.device.devicectl + +### Features + +* launch WDA via devicectl object ([#870](https://github.com/appium/WebDriverAgent/issues/870)) ([090b815](https://github.com/appium/WebDriverAgent/commit/090b815ae47e1ef0e0a9842fac6828346bc38fe6)) + +## [7.3.1](https://github.com/appium/WebDriverAgent/compare/v7.3.0...v7.3.1) (2024-03-24) + + +### Miscellaneous Chores + +* move node-simctl to dev deps ([#869](https://github.com/appium/WebDriverAgent/issues/869)) ([9033759](https://github.com/appium/WebDriverAgent/commit/90337597e6c480c790cf299e160bc53731c0a87d)) + +## [7.3.0](https://github.com/appium/WebDriverAgent/compare/v7.2.0...v7.3.0) (2024-03-23) + + +### Features + +* Support prebuiltWDAPath for iOS 17 ([#868](https://github.com/appium/WebDriverAgent/issues/868)) ([39194d4](https://github.com/appium/WebDriverAgent/commit/39194d4ac6d0072c1214088ff5c15c986969914c)) + +## [7.2.0](https://github.com/appium/WebDriverAgent/compare/v7.1.2...v7.2.0) (2024-03-21) + + +### Features + +* Enable usePreinstalledWDA feature for simulators ([#866](https://github.com/appium/WebDriverAgent/issues/866)) ([7c684e2](https://github.com/appium/WebDriverAgent/commit/7c684e2def9dd968de1cf89e4ec26403a52ba805)) + +## [7.1.2](https://github.com/appium/WebDriverAgent/compare/v7.1.1...v7.1.2) (2024-03-14) + + +### Bug Fixes + +* Always assume en0 is the WiFi interface ([#864](https://github.com/appium/WebDriverAgent/issues/864)) ([6dbfb3f](https://github.com/appium/WebDriverAgent/commit/6dbfb3f2ec8e0bfa5a42c6f8ab882893bfe3f534)) + +## [7.1.1](https://github.com/appium/WebDriverAgent/compare/v7.1.0...v7.1.1) (2024-03-13) + + +### Bug Fixes + +* respect defaultActiveApplication in activeApplication selection ([#862](https://github.com/appium/WebDriverAgent/issues/862)) ([b1ddae2](https://github.com/appium/WebDriverAgent/commit/b1ddae2be3fd3f7c87de79e804d82cf7c13dc56e)) + +## [7.1.0](https://github.com/appium/WebDriverAgent/compare/v7.0.6...v7.1.0) (2024-03-07) + + +### Features + +* Add wrappers for native XCTest video recorder ([#858](https://github.com/appium/WebDriverAgent/issues/858)) ([9728548](https://github.com/appium/WebDriverAgent/commit/9728548676c8de67c30d127ee8b0374f58286e74)) + + +### Miscellaneous Chores + +* bump typescript ([89880f5](https://github.com/appium/WebDriverAgent/commit/89880f509f930f16f6469bcda613569040c337b6)) + +## [7.0.6](https://github.com/appium/WebDriverAgent/compare/v7.0.5...v7.0.6) (2024-03-03) + + +### Miscellaneous Chores + +* Handle app startup errors as session creation exceptions ([#855](https://github.com/appium/WebDriverAgent/issues/855)) ([0ec5398](https://github.com/appium/WebDriverAgent/commit/0ec5398e9cb4b0e5ab133cc0c330b85b3d37766e)) + +## [7.0.5](https://github.com/appium/WebDriverAgent/compare/v7.0.4...v7.0.5) (2024-03-03) + + +### Reverts + +* Revert "chore: tune release packages (#856)" (#857) ([dc72015](https://github.com/appium/WebDriverAgent/commit/dc720157a60925451e6d5935abcd168082d44785)), closes [#856](https://github.com/appium/WebDriverAgent/issues/856) [#857](https://github.com/appium/WebDriverAgent/issues/857) + +## [7.0.4](https://github.com/appium/WebDriverAgent/compare/v7.0.3...v7.0.4) (2024-03-03) + + +### Miscellaneous Chores + +* dummy commit to trigger a release ([0cb66c5](https://github.com/appium/WebDriverAgent/commit/0cb66c5edc91c191d5ec412ba0a479e07cb4214b)) + +## [7.0.3](https://github.com/appium/WebDriverAgent/compare/v7.0.2...v7.0.3) (2024-03-03) + + +### Miscellaneous Chores + +* tune release packages ([#856](https://github.com/appium/WebDriverAgent/issues/856)) ([aa0765e](https://github.com/appium/WebDriverAgent/commit/aa0765e425faba6c035a9933320e91679b167b80)) + +## [7.0.2](https://github.com/appium/WebDriverAgent/compare/v7.0.1...v7.0.2) (2024-02-28) + + +### Miscellaneous Chores + +* Tune alert detection if system app is active ([#854](https://github.com/appium/WebDriverAgent/issues/854)) ([857d3de](https://github.com/appium/WebDriverAgent/commit/857d3decf497935098ba6acb61654be1da173b11)) + +## [7.0.1](https://github.com/appium/WebDriverAgent/compare/v7.0.0...v7.0.1) (2024-02-21) + + +### Miscellaneous Chores + +* Simplify the logic of alert element detection ([#851](https://github.com/appium/WebDriverAgent/issues/851)) ([54f91f1](https://github.com/appium/WebDriverAgent/commit/54f91f198e45535ea9d86b7eee40b21f43f84294)) + +## [7.0.0](https://github.com/appium/WebDriverAgent/compare/v6.1.1...v7.0.0) (2024-02-12) + + +### ⚠ BREAKING CHANGES + +* The following REST endpoints have been removed, use W3C actions instead: +- /wda/touch/perform +- /wda/touch/multi/perform + +### Features + +* Remove obsolete MJSONWP touch actions ([#847](https://github.com/appium/WebDriverAgent/issues/847)) ([d77f640](https://github.com/appium/WebDriverAgent/commit/d77f640867155fddbbbc9575f0a77802602865e7)) + ## [6.1.1](https://github.com/appium/WebDriverAgent/compare/v6.1.0...v6.1.1) (2024-02-11) diff --git a/Configurations/IOSSettings.xcconfig b/Configurations/IOSSettings.xcconfig index 70ae5ec68..b9969a48b 100644 --- a/Configurations/IOSSettings.xcconfig +++ b/Configurations/IOSSettings.xcconfig @@ -28,4 +28,4 @@ RUN_CLANG_STATIC_ANALYZER = YES GCC_PREPROCESSOR_DEFINITIONS = $(inherited) -WARNING_CFLAGS = $(inherited) -Weverything -Wno-objc-missing-property-synthesis -Wno-unused-macros -Wno-disabled-macro-expansion -Wno-gnu-statement-expression -Wno-language-extension-token -Wno-overriding-method-mismatch -Wno-missing-variable-declarations -Rno-module-build -Wno-auto-import -Wno-objc-interface-ivars -Wno-documentation-unknown-command -Wno-reserved-id-macro -Wno-unused-parameter -Wno-gnu-conditional-omitted-operand -Wno-explicit-ownership-type -Wno-date-time -Wno-cast-align -Wno-cstring-format-directive -Wno-double-promotion -Wno-partial-availability +WARNING_CFLAGS = $(inherited) -Weverything -Wno-objc-missing-property-synthesis -Wno-unused-macros -Wno-disabled-macro-expansion -Wno-gnu-statement-expression -Wno-language-extension-token -Wno-overriding-method-mismatch -Wno-missing-variable-declarations -Rno-module-build -Wno-auto-import -Wno-objc-interface-ivars -Wno-documentation-unknown-command -Wno-reserved-id-macro -Wno-unused-parameter -Wno-gnu-conditional-omitted-operand -Wno-explicit-ownership-type -Wno-date-time -Wno-cast-align -Wno-cstring-format-directive -Wno-double-promotion -Wno-partial-availability -Wno-declaration-after-statement -Wno-objc-messaging-id -Wno-direct-ivar-access -Wno-cast-qual -Wno-deprecated-declarations diff --git a/Configurations/TVOSSettings.xcconfig b/Configurations/TVOSSettings.xcconfig index 70ae5ec68..b9969a48b 100644 --- a/Configurations/TVOSSettings.xcconfig +++ b/Configurations/TVOSSettings.xcconfig @@ -28,4 +28,4 @@ RUN_CLANG_STATIC_ANALYZER = YES GCC_PREPROCESSOR_DEFINITIONS = $(inherited) -WARNING_CFLAGS = $(inherited) -Weverything -Wno-objc-missing-property-synthesis -Wno-unused-macros -Wno-disabled-macro-expansion -Wno-gnu-statement-expression -Wno-language-extension-token -Wno-overriding-method-mismatch -Wno-missing-variable-declarations -Rno-module-build -Wno-auto-import -Wno-objc-interface-ivars -Wno-documentation-unknown-command -Wno-reserved-id-macro -Wno-unused-parameter -Wno-gnu-conditional-omitted-operand -Wno-explicit-ownership-type -Wno-date-time -Wno-cast-align -Wno-cstring-format-directive -Wno-double-promotion -Wno-partial-availability +WARNING_CFLAGS = $(inherited) -Weverything -Wno-objc-missing-property-synthesis -Wno-unused-macros -Wno-disabled-macro-expansion -Wno-gnu-statement-expression -Wno-language-extension-token -Wno-overriding-method-mismatch -Wno-missing-variable-declarations -Rno-module-build -Wno-auto-import -Wno-objc-interface-ivars -Wno-documentation-unknown-command -Wno-reserved-id-macro -Wno-unused-parameter -Wno-gnu-conditional-omitted-operand -Wno-explicit-ownership-type -Wno-date-time -Wno-cast-align -Wno-cstring-format-directive -Wno-double-promotion -Wno-partial-availability -Wno-declaration-after-statement -Wno-objc-messaging-id -Wno-direct-ivar-access -Wno-cast-qual -Wno-deprecated-declarations diff --git a/PrivateHeaders/XCTest/XCTRunnerDaemonSession.h b/PrivateHeaders/XCTest/XCTRunnerDaemonSession.h index 7473b2e0a..7ca530371 100644 --- a/PrivateHeaders/XCTest/XCTRunnerDaemonSession.h +++ b/PrivateHeaders/XCTest/XCTRunnerDaemonSession.h @@ -80,6 +80,14 @@ @property(readonly) _Bool supportsLocationSimulation; #endif +// Since Xcode 15.0-beta1 +- (void)stopScreenRecordingWithUUID:(NSUUID *)arg1 + withReply:(void (^)(NSError *))arg2; +- (void)startScreenRecordingWithRequest:(id/* XCTScreenRecordingRequest */)arg1 + withReply:(void (^)(id/* XCTAttachmentFutureMetadata */, NSError *))arg2; +- (_Bool)supportsScreenRecording; +- (_Bool)preferScreenshotsOverScreenRecordings; + // Since Xcode 10.2 - (void)launchApplicationWithPath:(NSString *)arg1 bundleID:(NSString *)arg2 diff --git a/PrivateHeaders/XCTest/XCUIApplicationProcess.h b/PrivateHeaders/XCTest/XCUIApplicationProcess.h index f0b4d358c..64770393e 100644 --- a/PrivateHeaders/XCTest/XCUIApplicationProcess.h +++ b/PrivateHeaders/XCTest/XCUIApplicationProcess.h @@ -65,7 +65,11 @@ - (void)terminate; - (void)waitForViewControllerViewDidDisappearWithTimeout:(double)arg1; - (void)waitForAutomationSession; +// Before Xcode16-beta5 - (void)waitForQuiescenceIncludingAnimationsIdle:(BOOL)arg1; +// Since Xcode16-beta5 +- (void)waitForQuiescenceIncludingAnimationsIdle:(BOOL)arg1 isPreEvent:(BOOL)arg2; + - (id)shortDescription; - (id)_queue_description; diff --git a/Scripts/update-wda-version.js b/Scripts/update-wda-version.js new file mode 100644 index 000000000..142efdce3 --- /dev/null +++ b/Scripts/update-wda-version.js @@ -0,0 +1,41 @@ +const {plist, logger} = require('@appium/support'); +const path = require('node:path'); +const semver = require('semver'); + +const log = logger.getLogger('Versioner'); + +/** + * @param {string} argName + * @returns {string|null} + */ +function parseArgValue (argName) { + const argNamePattern = new RegExp(`^--${argName}\\b`); + for (let i = 1; i < process.argv.length; ++i) { + const arg = process.argv[i]; + if (argNamePattern.test(arg)) { + return arg.includes('=') ? arg.split('=')[1] : process.argv[i + 1]; + } + } + return null; +} + +async function updateWdaVersion() { + const newVersion = parseArgValue('package-version'); + if (!newVersion) { + throw new Error('No package version argument (use `--package-version=xxx`)'); + } + if (!semver.valid(newVersion)) { + throw new Error( + `Invalid version specified '${newVersion}'. Version should be in the form '1.2.3'` + ); + } + + const libManifest = path.resolve('WebDriverAgentLib', 'Info.plist'); + log.info(`Updating the WebDriverAgent manifest at '${libManifest}' to version '${newVersion}'`); + await plist.updatePlistFile(libManifest, { + CFBundleShortVersionString: newVersion, + CFBundleVersion: newVersion, + }, false); +} + +(async () => await updateWdaVersion())(); diff --git a/WebDriverAgent.xcodeproj/project.pbxproj b/WebDriverAgent.xcodeproj/project.pbxproj index 4c098bde8..db4465848 100644 --- a/WebDriverAgent.xcodeproj/project.pbxproj +++ b/WebDriverAgent.xcodeproj/project.pbxproj @@ -49,7 +49,6 @@ 641EE5DA2240C5CA00173FCB /* XCUIApplicationProcessDelay.m in Sources */ = {isa = PBXBuildFile; fileRef = 6385F4A5220A40760095BBDB /* XCUIApplicationProcessDelay.m */; }; 641EE5DB2240C5CA00173FCB /* FBXPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 711084431DA3AA7500F913D6 /* FBXPath.m */; }; 641EE5DC2240C5CA00173FCB /* XCUIApplication+FBAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = 719CD8FB2126C88B00C7D0C2 /* XCUIApplication+FBAlert.m */; }; - 641EE5DD2240C5CA00173FCB /* FBAppiumActionsSynthesizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 71241D771FAE31F100B9559F /* FBAppiumActionsSynthesizer.m */; }; 641EE5DE2240C5CA00173FCB /* XCUIApplication+FBTouchAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 71BD20721F86116100B36EC2 /* XCUIApplication+FBTouchAction.m */; }; 641EE5DF2240C5CA00173FCB /* FBWebServer.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB78D1CAEDF0C008C271F /* FBWebServer.m */; }; 641EE5E02240C5CA00173FCB /* FBTCPSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 715557D2211DBCE700613B26 /* FBTCPSocket.m */; }; @@ -269,7 +268,6 @@ 641EE6D82240C5CA00173FCB /* XCUIElement.h in Headers */ = {isa = PBXBuildFile; fileRef = EE35ACFE1E3B77D600A02D78 /* XCUIElement.h */; settings = {ATTRIBUTES = (Public, ); }; }; 641EE6D92240C5CA00173FCB /* XCKeyboardInputSolver.h in Headers */ = {isa = PBXBuildFile; fileRef = EE35ACBE1E3B77D600A02D78 /* XCKeyboardInputSolver.h */; settings = {ATTRIBUTES = (Public, ); }; }; 641EE6DB2240C5CA00173FCB /* FBPasteboard.h in Headers */ = {isa = PBXBuildFile; fileRef = 71930C4020662E1F00D3AFEC /* FBPasteboard.h */; }; - 641EE6DC2240C5CA00173FCB /* FBAppiumActionsSynthesizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 714097451FAE1B32008FB2C5 /* FBAppiumActionsSynthesizer.h */; }; 641EE6DD2240C5CA00173FCB /* FBDebugLogDelegateDecorator.h in Headers */ = {isa = PBXBuildFile; fileRef = EE7E27181D06C69F001BEC7B /* FBDebugLogDelegateDecorator.h */; settings = {ATTRIBUTES = (Public, ); }; }; 641EE6DE2240C5CA00173FCB /* XCUIDevice+FBHealthCheck.h in Headers */ = {isa = PBXBuildFile; fileRef = EEDFE11F1D9C06F800E6FFE5 /* XCUIDevice+FBHealthCheck.h */; settings = {ATTRIBUTES = (Public, ); }; }; 641EE6DF2240C5CA00173FCB /* FBMjpegServer.h in Headers */ = {isa = PBXBuildFile; fileRef = 7155D701211DCEF400166C20 /* FBMjpegServer.h */; }; @@ -318,7 +316,6 @@ 7119E1EC1E891F8600D0B125 /* FBPickerWheelSelectTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7119E1EB1E891F8600D0B125 /* FBPickerWheelSelectTests.m */; }; 711CD03425ED1106001C01D2 /* XCUIScreenDataSource-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 711CD03325ED1106001C01D2 /* XCUIScreenDataSource-Protocol.h */; }; 711CD03525ED1106001C01D2 /* XCUIScreenDataSource-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 711CD03325ED1106001C01D2 /* XCUIScreenDataSource-Protocol.h */; }; - 71241D781FAE31F100B9559F /* FBAppiumActionsSynthesizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 71241D771FAE31F100B9559F /* FBAppiumActionsSynthesizer.m */; }; 71241D7B1FAE3D2500B9559F /* FBTouchActionCommands.h in Headers */ = {isa = PBXBuildFile; fileRef = 71241D791FAE3D2500B9559F /* FBTouchActionCommands.h */; }; 71241D7C1FAE3D2500B9559F /* FBTouchActionCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = 71241D7A1FAE3D2500B9559F /* FBTouchActionCommands.m */; }; 71241D7E1FAF084E00B9559F /* FBW3CTouchActionsIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 71241D7D1FAF084E00B9559F /* FBW3CTouchActionsIntegrationTests.m */; }; @@ -336,7 +333,6 @@ 713C6DCF1DDC772A00285B92 /* FBElementUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 713C6DCD1DDC772A00285B92 /* FBElementUtils.h */; }; 713C6DD01DDC772A00285B92 /* FBElementUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 713C6DCE1DDC772A00285B92 /* FBElementUtils.m */; }; 714097431FAE1B0B008FB2C5 /* FBBaseActionsSynthesizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 714097411FAE1B0B008FB2C5 /* FBBaseActionsSynthesizer.h */; }; - 714097471FAE1B32008FB2C5 /* FBAppiumActionsSynthesizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 714097451FAE1B32008FB2C5 /* FBAppiumActionsSynthesizer.h */; }; 7140974B1FAE1B51008FB2C5 /* FBW3CActionsSynthesizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 714097491FAE1B51008FB2C5 /* FBW3CActionsSynthesizer.h */; }; 7140974C1FAE1B51008FB2C5 /* FBW3CActionsSynthesizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7140974A1FAE1B51008FB2C5 /* FBW3CActionsSynthesizer.m */; }; 7140974E1FAE20EE008FB2C5 /* FBBaseActionsSynthesizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7140974D1FAE20EE008FB2C5 /* FBBaseActionsSynthesizer.m */; }; @@ -432,7 +428,6 @@ 71930C4220662E1F00D3AFEC /* FBPasteboard.h in Headers */ = {isa = PBXBuildFile; fileRef = 71930C4020662E1F00D3AFEC /* FBPasteboard.h */; }; 71930C4320662E1F00D3AFEC /* FBPasteboard.m in Sources */ = {isa = PBXBuildFile; fileRef = 71930C4120662E1F00D3AFEC /* FBPasteboard.m */; }; 71930C472066434000D3AFEC /* FBPasteboardTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 71930C462066434000D3AFEC /* FBPasteboardTests.m */; }; - 719A97AC1F88E7370063B4BD /* FBAppiumMultiTouchActionsIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 719A97AB1F88E7370063B4BD /* FBAppiumMultiTouchActionsIntegrationTests.m */; }; 719CD8F82126C78F00C7D0C2 /* FBAlertsMonitor.h in Headers */ = {isa = PBXBuildFile; fileRef = 719CD8F62126C78F00C7D0C2 /* FBAlertsMonitor.h */; }; 719CD8F92126C78F00C7D0C2 /* FBAlertsMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = 719CD8F72126C78F00C7D0C2 /* FBAlertsMonitor.m */; }; 719CD8FC2126C88B00C7D0C2 /* XCUIApplication+FBAlert.h in Headers */ = {isa = PBXBuildFile; fileRef = 719CD8FA2126C88B00C7D0C2 /* XCUIApplication+FBAlert.h */; }; @@ -462,9 +457,29 @@ 71B155E123080CA600646AFB /* FBProtocolHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 71B155DE23080CA600646AFB /* FBProtocolHelpers.m */; }; 71B49EC71ED1A58100D51AD6 /* XCUIElement+FBUID.h in Headers */ = {isa = PBXBuildFile; fileRef = 71B49EC51ED1A58100D51AD6 /* XCUIElement+FBUID.h */; }; 71B49EC81ED1A58100D51AD6 /* XCUIElement+FBUID.m in Sources */ = {isa = PBXBuildFile; fileRef = 71B49EC61ED1A58100D51AD6 /* XCUIElement+FBUID.m */; }; + 71BB58DE2B9631B700CB9BFE /* FBVideoRecordingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 71BB58DD2B9631B700CB9BFE /* FBVideoRecordingTests.m */; }; + 71BB58E12B9631F100CB9BFE /* FBScreenRecordingPromise.h in Headers */ = {isa = PBXBuildFile; fileRef = 71BB58DF2B9631F100CB9BFE /* FBScreenRecordingPromise.h */; }; + 71BB58E22B9631F100CB9BFE /* FBScreenRecordingPromise.h in Headers */ = {isa = PBXBuildFile; fileRef = 71BB58DF2B9631F100CB9BFE /* FBScreenRecordingPromise.h */; }; + 71BB58E32B9631F100CB9BFE /* FBScreenRecordingPromise.m in Sources */ = {isa = PBXBuildFile; fileRef = 71BB58E02B9631F100CB9BFE /* FBScreenRecordingPromise.m */; }; + 71BB58E42B9631F100CB9BFE /* FBScreenRecordingPromise.m in Sources */ = {isa = PBXBuildFile; fileRef = 71BB58E02B9631F100CB9BFE /* FBScreenRecordingPromise.m */; }; + 71BB58E52B9631F100CB9BFE /* FBScreenRecordingPromise.m in Sources */ = {isa = PBXBuildFile; fileRef = 71BB58E02B9631F100CB9BFE /* FBScreenRecordingPromise.m */; }; + 71BB58E82B96328700CB9BFE /* FBScreenRecordingRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 71BB58E62B96328700CB9BFE /* FBScreenRecordingRequest.h */; }; + 71BB58E92B96328700CB9BFE /* FBScreenRecordingRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 71BB58E62B96328700CB9BFE /* FBScreenRecordingRequest.h */; }; + 71BB58EA2B96328700CB9BFE /* FBScreenRecordingRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 71BB58E72B96328700CB9BFE /* FBScreenRecordingRequest.m */; }; + 71BB58EB2B96328700CB9BFE /* FBScreenRecordingRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 71BB58E72B96328700CB9BFE /* FBScreenRecordingRequest.m */; }; + 71BB58EC2B96328700CB9BFE /* FBScreenRecordingRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 71BB58E72B96328700CB9BFE /* FBScreenRecordingRequest.m */; }; + 71BB58EF2B96511800CB9BFE /* FBVideoCommands.h in Headers */ = {isa = PBXBuildFile; fileRef = 71BB58ED2B96511800CB9BFE /* FBVideoCommands.h */; }; + 71BB58F02B96511800CB9BFE /* FBVideoCommands.h in Headers */ = {isa = PBXBuildFile; fileRef = 71BB58ED2B96511800CB9BFE /* FBVideoCommands.h */; }; + 71BB58F12B96511800CB9BFE /* FBVideoCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = 71BB58EE2B96511800CB9BFE /* FBVideoCommands.m */; }; + 71BB58F22B96511800CB9BFE /* FBVideoCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = 71BB58EE2B96511800CB9BFE /* FBVideoCommands.m */; }; + 71BB58F32B96511800CB9BFE /* FBVideoCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = 71BB58EE2B96511800CB9BFE /* FBVideoCommands.m */; }; + 71BB58F62B96531900CB9BFE /* FBScreenRecordingContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = 71BB58F42B96531900CB9BFE /* FBScreenRecordingContainer.h */; }; + 71BB58F72B96531900CB9BFE /* FBScreenRecordingContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = 71BB58F42B96531900CB9BFE /* FBScreenRecordingContainer.h */; }; + 71BB58F82B96531900CB9BFE /* FBScreenRecordingContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 71BB58F52B96531900CB9BFE /* FBScreenRecordingContainer.m */; }; + 71BB58F92B96531900CB9BFE /* FBScreenRecordingContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 71BB58F52B96531900CB9BFE /* FBScreenRecordingContainer.m */; }; + 71BB58FA2B96531900CB9BFE /* FBScreenRecordingContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 71BB58F52B96531900CB9BFE /* FBScreenRecordingContainer.m */; }; 71BD20731F86116100B36EC2 /* XCUIApplication+FBTouchAction.h in Headers */ = {isa = PBXBuildFile; fileRef = 71BD20711F86116100B36EC2 /* XCUIApplication+FBTouchAction.h */; }; 71BD20741F86116100B36EC2 /* XCUIApplication+FBTouchAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 71BD20721F86116100B36EC2 /* XCUIApplication+FBTouchAction.m */; }; - 71BD20781F869E0F00B36EC2 /* FBAppiumTouchActionsIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 71BD20771F869E0F00B36EC2 /* FBAppiumTouchActionsIntegrationTests.m */; }; 71C8E55125399A6B008572C1 /* XCUIApplication+FBQuiescence.h in Headers */ = {isa = PBXBuildFile; fileRef = 71C8E54F25399A6B008572C1 /* XCUIApplication+FBQuiescence.h */; }; 71C8E55225399A6B008572C1 /* XCUIApplication+FBQuiescence.h in Headers */ = {isa = PBXBuildFile; fileRef = 71C8E54F25399A6B008572C1 /* XCUIApplication+FBQuiescence.h */; }; 71C8E55325399A6B008572C1 /* XCUIApplication+FBQuiescence.m in Sources */ = {isa = PBXBuildFile; fileRef = 71C8E55025399A6B008572C1 /* XCUIApplication+FBQuiescence.m */; }; @@ -940,7 +955,6 @@ 7119097B2152580600BA3C7E /* XCUIScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCUIScreen.h; sourceTree = ""; }; 7119E1EB1E891F8600D0B125 /* FBPickerWheelSelectTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBPickerWheelSelectTests.m; sourceTree = ""; }; 711CD03325ED1106001C01D2 /* XCUIScreenDataSource-Protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCUIScreenDataSource-Protocol.h"; sourceTree = ""; }; - 71241D771FAE31F100B9559F /* FBAppiumActionsSynthesizer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBAppiumActionsSynthesizer.m; sourceTree = ""; }; 71241D791FAE3D2500B9559F /* FBTouchActionCommands.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBTouchActionCommands.h; sourceTree = ""; }; 71241D7A1FAE3D2500B9559F /* FBTouchActionCommands.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBTouchActionCommands.m; sourceTree = ""; }; 71241D7D1FAF084E00B9559F /* FBW3CTouchActionsIntegrationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBW3CTouchActionsIntegrationTests.m; sourceTree = ""; }; @@ -958,7 +972,6 @@ 713C6DCD1DDC772A00285B92 /* FBElementUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBElementUtils.h; sourceTree = ""; }; 713C6DCE1DDC772A00285B92 /* FBElementUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBElementUtils.m; sourceTree = ""; }; 714097411FAE1B0B008FB2C5 /* FBBaseActionsSynthesizer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBBaseActionsSynthesizer.h; sourceTree = ""; }; - 714097451FAE1B32008FB2C5 /* FBAppiumActionsSynthesizer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBAppiumActionsSynthesizer.h; sourceTree = ""; }; 714097491FAE1B51008FB2C5 /* FBW3CActionsSynthesizer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBW3CActionsSynthesizer.h; sourceTree = ""; }; 7140974A1FAE1B51008FB2C5 /* FBW3CActionsSynthesizer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBW3CActionsSynthesizer.m; sourceTree = ""; }; 7140974D1FAE20EE008FB2C5 /* FBBaseActionsSynthesizer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBBaseActionsSynthesizer.m; sourceTree = ""; }; @@ -1018,7 +1031,6 @@ 71930C4020662E1F00D3AFEC /* FBPasteboard.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBPasteboard.h; sourceTree = ""; }; 71930C4120662E1F00D3AFEC /* FBPasteboard.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBPasteboard.m; sourceTree = ""; }; 71930C462066434000D3AFEC /* FBPasteboardTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBPasteboardTests.m; sourceTree = ""; }; - 719A97AB1F88E7370063B4BD /* FBAppiumMultiTouchActionsIntegrationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBAppiumMultiTouchActionsIntegrationTests.m; sourceTree = ""; }; 719CD8F62126C78F00C7D0C2 /* FBAlertsMonitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBAlertsMonitor.h; sourceTree = ""; }; 719CD8F72126C78F00C7D0C2 /* FBAlertsMonitor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBAlertsMonitor.m; sourceTree = ""; }; 719CD8FA2126C88B00C7D0C2 /* XCUIApplication+FBAlert.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XCUIApplication+FBAlert.h"; sourceTree = ""; }; @@ -1044,9 +1056,17 @@ 71B155DE23080CA600646AFB /* FBProtocolHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBProtocolHelpers.m; sourceTree = ""; }; 71B49EC51ED1A58100D51AD6 /* XCUIElement+FBUID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCUIElement+FBUID.h"; sourceTree = ""; }; 71B49EC61ED1A58100D51AD6 /* XCUIElement+FBUID.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "XCUIElement+FBUID.m"; sourceTree = ""; }; + 71BB58DD2B9631B700CB9BFE /* FBVideoRecordingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBVideoRecordingTests.m; sourceTree = ""; }; + 71BB58DF2B9631F100CB9BFE /* FBScreenRecordingPromise.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBScreenRecordingPromise.h; sourceTree = ""; }; + 71BB58E02B9631F100CB9BFE /* FBScreenRecordingPromise.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBScreenRecordingPromise.m; sourceTree = ""; }; + 71BB58E62B96328700CB9BFE /* FBScreenRecordingRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBScreenRecordingRequest.h; sourceTree = ""; }; + 71BB58E72B96328700CB9BFE /* FBScreenRecordingRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBScreenRecordingRequest.m; sourceTree = ""; }; + 71BB58ED2B96511800CB9BFE /* FBVideoCommands.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBVideoCommands.h; sourceTree = ""; }; + 71BB58EE2B96511800CB9BFE /* FBVideoCommands.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBVideoCommands.m; sourceTree = ""; }; + 71BB58F42B96531900CB9BFE /* FBScreenRecordingContainer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBScreenRecordingContainer.h; sourceTree = ""; }; + 71BB58F52B96531900CB9BFE /* FBScreenRecordingContainer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBScreenRecordingContainer.m; sourceTree = ""; }; 71BD20711F86116100B36EC2 /* XCUIApplication+FBTouchAction.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XCUIApplication+FBTouchAction.h"; sourceTree = ""; }; 71BD20721F86116100B36EC2 /* XCUIApplication+FBTouchAction.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "XCUIApplication+FBTouchAction.m"; sourceTree = ""; }; - 71BD20771F869E0F00B36EC2 /* FBAppiumTouchActionsIntegrationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBAppiumTouchActionsIntegrationTests.m; sourceTree = ""; }; 71C8E54F25399A6B008572C1 /* XCUIApplication+FBQuiescence.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XCUIApplication+FBQuiescence.h"; sourceTree = ""; }; 71C8E55025399A6B008572C1 /* XCUIApplication+FBQuiescence.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "XCUIApplication+FBQuiescence.m"; sourceTree = ""; }; 71C9EAAA25E8415A00470CD8 /* FBScreenshot.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBScreenshot.h; sourceTree = ""; }; @@ -1799,6 +1819,8 @@ EE9AB7631CAEDF0C008C271F /* FBTouchIDCommands.m */, EE9AB7641CAEDF0C008C271F /* FBUnknownCommands.h */, EE9AB7651CAEDF0C008C271F /* FBUnknownCommands.m */, + 71BB58ED2B96511800CB9BFE /* FBVideoCommands.h */, + 71BB58EE2B96511800CB9BFE /* FBVideoCommands.m */, ); name = Commands; path = WebDriverAgentLib/Commands; @@ -1838,6 +1860,12 @@ EE9AB7861CAEDF0C008C271F /* FBRouteRequest-Private.h */, EE9AB7871CAEDF0C008C271F /* FBRouteRequest.h */, EE9AB7881CAEDF0C008C271F /* FBRouteRequest.m */, + 71BB58F42B96531900CB9BFE /* FBScreenRecordingContainer.h */, + 71BB58F52B96531900CB9BFE /* FBScreenRecordingContainer.m */, + 71BB58DF2B9631F100CB9BFE /* FBScreenRecordingPromise.h */, + 71BB58E02B9631F100CB9BFE /* FBScreenRecordingPromise.m */, + 71BB58E62B96328700CB9BFE /* FBScreenRecordingRequest.h */, + 71BB58E72B96328700CB9BFE /* FBScreenRecordingRequest.m */, EE9AB7891CAEDF0C008C271F /* FBSession-Private.h */, EE9AB78A1CAEDF0C008C271F /* FBSession.h */, EE9AB78B1CAEDF0C008C271F /* FBSession.m */, @@ -1866,8 +1894,6 @@ 13815F6E2328D20400CDAB61 /* FBActiveAppDetectionPoint.m */, 719CD8F62126C78F00C7D0C2 /* FBAlertsMonitor.h */, 719CD8F72126C78F00C7D0C2 /* FBAlertsMonitor.m */, - 714097451FAE1B32008FB2C5 /* FBAppiumActionsSynthesizer.h */, - 71241D771FAE31F100B9559F /* FBAppiumActionsSynthesizer.m */, 714097411FAE1B0B008FB2C5 /* FBBaseActionsSynthesizer.h */, 7140974D1FAE20EE008FB2C5 /* FBBaseActionsSynthesizer.m */, 714EAA0B2673FDFE005C5B47 /* FBCapabilities.h */, @@ -1965,8 +1991,6 @@ isa = PBXGroup; children = ( EE9B76991CF799F400275851 /* FBAlertTests.m */, - 71BD20771F869E0F00B36EC2 /* FBAppiumTouchActionsIntegrationTests.m */, - 719A97AB1F88E7370063B4BD /* FBAppiumMultiTouchActionsIntegrationTests.m */, 719CD8FE2126C90200C7D0C2 /* FBAutoAlertsHandlerTests.m */, EE26409C1D0EBA25009BE6B0 /* FBElementAttributeTests.m */, 71F5BE33252E5B2200EE9EBA /* FBElementSwipingTests.m */, @@ -1986,6 +2010,7 @@ EE1E06DC1D1811C4007CF043 /* FBTestMacros.h */, AD76723F1D6B826F00610457 /* FBTypingTest.m */, 714CA3C61DC23186000F12C9 /* FBXPathIntegrationTests.m */, + 71BB58DD2B9631B700CB9BFE /* FBVideoRecordingTests.m */, 71241D7D1FAF084E00B9559F /* FBW3CTouchActionsIntegrationTests.m */, 71241D7F1FAF087500B9559F /* FBW3CMultiTouchActionsIntegrationTests.m */, 7136C0F8243A182400921C76 /* FBW3CTypeActionsTests.m */, @@ -2278,6 +2303,7 @@ 641EE64F2240C5CA00173FCB /* XCTestExpectationWaiter.h in Headers */, 13DE7A5C287CA444003243C6 /* FBXCElementSnapshotWrapper+Helpers.h in Headers */, 641EE6502240C5CA00173FCB /* UIGestureRecognizer-RecordingAdditions.h in Headers */, + 71BB58E92B96328700CB9BFE /* FBScreenRecordingRequest.h in Headers */, 641EE6512240C5CA00173FCB /* XCKeyboardKeyMap.h in Headers */, 641EE6522240C5CA00173FCB /* XCTNSPredicateExpectationObject-Protocol.h in Headers */, 641EE6532240C5CA00173FCB /* WebDriverAgentLib.h in Headers */, @@ -2294,6 +2320,7 @@ 641EE65F2240C5CA00173FCB /* XCSourceCodeTreeNodeEnumerator.h in Headers */, 641EE6602240C5CA00173FCB /* XCUIElement+FBIsVisible.h in Headers */, 641EE6622240C5CA00173FCB /* FBResponsePayload.h in Headers */, + 71BB58E22B9631F100CB9BFE /* FBScreenRecordingPromise.h in Headers */, 641EE6632240C5CA00173FCB /* FBUnknownCommands.h in Headers */, 641EE7062240CDCF00173FCB /* XCUIElement+FBTVFocuse.h in Headers */, 71822738258744B800661B83 /* HTTPConnection.h in Headers */, @@ -2396,6 +2423,7 @@ 7182276E258744C900661B83 /* HTTPErrorResponse.h in Headers */, 641EE6B82240C5CA00173FCB /* FBAlert.h in Headers */, 641EE6B92240C5CA00173FCB /* XCUIElementQuery.h in Headers */, + 71BB58F02B96511800CB9BFE /* FBVideoCommands.h in Headers */, 641EE6BA2240C5CA00173FCB /* XCPointerEvent.h in Headers */, 718F49C923087ACF0045FE8B /* FBProtocolHelpers.h in Headers */, 641EE6BB2240C5CA00173FCB /* XCSourceCodeRecording.h in Headers */, @@ -2433,13 +2461,13 @@ 641EE6D42240C5CA00173FCB /* NSValue-XCTestAdditions.h in Headers */, 641EE6D52240C5CA00173FCB /* _XCTWaiterImpl.h in Headers */, 641EE6D62240C5CA00173FCB /* FBLogger.h in Headers */, + 71BB58F72B96531900CB9BFE /* FBScreenRecordingContainer.h in Headers */, 641EE6D72240C5CA00173FCB /* XCTestObserver.h in Headers */, 641EE6D82240C5CA00173FCB /* XCUIElement.h in Headers */, 641EE6D92240C5CA00173FCB /* XCKeyboardInputSolver.h in Headers */, 718226CB2587443700661B83 /* GCDAsyncUdpSocket.h in Headers */, 641EE6DB2240C5CA00173FCB /* FBPasteboard.h in Headers */, 711CD03525ED1106001C01D2 /* XCUIScreenDataSource-Protocol.h in Headers */, - 641EE6DC2240C5CA00173FCB /* FBAppiumActionsSynthesizer.h in Headers */, 641EE6DD2240C5CA00173FCB /* FBDebugLogDelegateDecorator.h in Headers */, 641EE6DE2240C5CA00173FCB /* XCUIDevice+FBHealthCheck.h in Headers */, 641EE6DF2240C5CA00173FCB /* FBMjpegServer.h in Headers */, @@ -2494,6 +2522,7 @@ EE158ABA1CBD456F00A3E3F0 /* FBCustomCommands.h in Headers */, EE35AD0D1E3B77D600A02D78 /* _XCTestCaseInterruptionException.h in Headers */, EE158AC41CBD456F00A3E3F0 /* FBOrientationCommands.h in Headers */, + 71BB58EF2B96511800CB9BFE /* FBVideoCommands.h in Headers */, 7119097C2152580600BA3C7E /* XCUIScreen.h in Headers */, EE35AD611E3B77D600A02D78 /* XCTRunnerIDESession.h in Headers */, EE158AE01CBD456F00A3E3F0 /* FBRouteRequest-Private.h in Headers */, @@ -2574,6 +2603,7 @@ EE158AD21CBD456F00A3E3F0 /* FBElementCache.h in Headers */, EE35AD5A1E3B77D600A02D78 /* XCTMetric.h in Headers */, EE35AD461E3B77D600A02D78 /* XCTestContextScope.h in Headers */, + 71BB58F62B96531900CB9BFE /* FBScreenRecordingContainer.h in Headers */, 71A7EAF51E20516B001DA4F2 /* XCUIElement+FBClassChain.h in Headers */, EE158ADA1CBD456F00A3E3F0 /* FBResponseJSONPayload.h in Headers */, EE35AD3D1E3B77D600A02D78 /* XCTAutomationTarget-Protocol.h in Headers */, @@ -2636,6 +2666,7 @@ EE35AD331E3B77D600A02D78 /* XCPointerEvent.h in Headers */, EE35AD351E3B77D600A02D78 /* XCSourceCodeRecording.h in Headers */, 71D04DC825356C43008A052C /* XCUIElement+FBCaching.h in Headers */, + 71BB58E12B9631F100CB9BFE /* FBScreenRecordingPromise.h in Headers */, E444DC99249131D40060D7EB /* HTTPLogging.h in Headers */, E444DC9B249131D40060D7EB /* HTTPResponse.h in Headers */, EEE9B4721CD02B88009D2030 /* FBRunLoopSpinner.h in Headers */, @@ -2661,6 +2692,7 @@ EE35AD571E3B77D600A02D78 /* XCTestSuiteRun.h in Headers */, EE35AD701E3B77D600A02D78 /* XCUIElementAsynchronousHandlerWrapper.h in Headers */, EE35AD4C1E3B77D600A02D78 /* XCTestLog.h in Headers */, + 71BB58E82B96328700CB9BFE /* FBScreenRecordingRequest.h in Headers */, EE35AD231E3B77D600A02D78 /* UITapGestureRecognizer-RecordingAdditions.h in Headers */, EE35AD2A1E3B77D600A02D78 /* XCDebugLogDelegate-Protocol.h in Headers */, EE35AD1C1E3B77D600A02D78 /* NSString-XCTAdditions.h in Headers */, @@ -2675,7 +2707,6 @@ EE35AD6F1E3B77D600A02D78 /* XCUIElement.h in Headers */, EE35AD2F1E3B77D600A02D78 /* XCKeyboardInputSolver.h in Headers */, 71930C4220662E1F00D3AFEC /* FBPasteboard.h in Headers */, - 714097471FAE1B32008FB2C5 /* FBAppiumActionsSynthesizer.h in Headers */, EE7E271C1D06C69F001BEC7B /* FBDebugLogDelegateDecorator.h in Headers */, EEDFE1211D9C06F800E6FFE5 /* XCUIDevice+FBHealthCheck.h in Headers */, 7155D703211DCEF400166C20 /* FBMjpegServer.h in Headers */, @@ -3066,8 +3097,8 @@ 641EE5DB2240C5CA00173FCB /* FBXPath.m in Sources */, 71C8E55425399A6B008572C1 /* XCUIApplication+FBQuiescence.m in Sources */, 641EE5DC2240C5CA00173FCB /* XCUIApplication+FBAlert.m in Sources */, - 641EE5DD2240C5CA00173FCB /* FBAppiumActionsSynthesizer.m in Sources */, 641EE70F2240CE4800173FCB /* FBTVNavigationTracker.m in Sources */, + 71BB58EB2B96328700CB9BFE /* FBScreenRecordingRequest.m in Sources */, 714D88CF2733FB970074A925 /* FBXMLGenerationOptions.m in Sources */, 641EE5DE2240C5CA00173FCB /* XCUIApplication+FBTouchAction.m in Sources */, 714E14BB29805CAE00375DD7 /* XCAXClient_iOS+FBSnapshotReqParams.m in Sources */, @@ -3123,6 +3154,7 @@ 641EE6092240C5CA00173FCB /* XCUIElement+FBUtilities.m in Sources */, 641EE60A2240C5CA00173FCB /* FBLogger.m in Sources */, 641EE60B2240C5CA00173FCB /* FBCustomCommands.m in Sources */, + 71BB58E42B9631F100CB9BFE /* FBScreenRecordingPromise.m in Sources */, 641EE60C2240C5CA00173FCB /* XCUIDevice+FBHelpers.m in Sources */, 641EE60D2240C5CA00173FCB /* XCTestPrivateSymbols.m in Sources */, 641EE60E2240C5CA00173FCB /* XCUIElement+FBTyping.m in Sources */, @@ -3134,6 +3166,7 @@ 641EE6132240C5CA00173FCB /* FBDebugLogDelegateDecorator.m in Sources */, 641EE6142240C5CA00173FCB /* FBAlertViewCommands.m in Sources */, 71414EDB2670A1EE003A8C5D /* LRUCacheNode.m in Sources */, + 71BB58F92B96531900CB9BFE /* FBScreenRecordingContainer.m in Sources */, 641EE6152240C5CA00173FCB /* XCUIElement+FBScrolling.m in Sources */, 641EE6162240C5CA00173FCB /* FBSessionCommands.m in Sources */, 641EE6192240C5CA00173FCB /* FBConfiguration.m in Sources */, @@ -3145,6 +3178,7 @@ 716C9DFD27315D21005AD475 /* FBReflectionUtils.m in Sources */, 641EE61D2240C5CA00173FCB /* FBElementCommands.m in Sources */, 641EE61E2240C5CA00173FCB /* FBExceptionHandler.m in Sources */, + 71BB58F22B96511800CB9BFE /* FBVideoCommands.m in Sources */, 641EE61F2240C5CA00173FCB /* FBXCodeCompatibility.m in Sources */, 71E75E70254824230099FC87 /* XCUIElementQuery+FBHelpers.m in Sources */, 641EE6212240C5CA00173FCB /* FBElementTypeTransformer.m in Sources */, @@ -3180,7 +3214,6 @@ 711084451DA3AA7500F913D6 /* FBXPath.m in Sources */, 719CD8FD2126C88B00C7D0C2 /* XCUIApplication+FBAlert.m in Sources */, 13DE7A45287C2A8D003243C6 /* FBXCAccessibilityElement.m in Sources */, - 71241D781FAE31F100B9559F /* FBAppiumActionsSynthesizer.m in Sources */, 641EE70E2240CE4800173FCB /* FBTVNavigationTracker.m in Sources */, 71BD20741F86116100B36EC2 /* XCUIApplication+FBTouchAction.m in Sources */, EE158AE71CBD456F00A3E3F0 /* FBWebServer.m in Sources */, @@ -3193,6 +3226,7 @@ 719DCF172601EAFB000E765F /* FBNotificationsHelper.m in Sources */, E444DCAC24913C220060D7EB /* Route.m in Sources */, 713C6DD01DDC772A00285B92 /* FBElementUtils.m in Sources */, + 71BB58E32B9631F100CB9BFE /* FBScreenRecordingPromise.m in Sources */, 7140974C1FAE1B51008FB2C5 /* FBW3CActionsSynthesizer.m in Sources */, EE6A893B1D0B38640083E92B /* FBFailureProofTestCase.m in Sources */, 713AE576243A53BE0000D657 /* FBW3CActionsHelpers.m in Sources */, @@ -3230,6 +3264,7 @@ 71F5BE51252F14EB00EE9EBA /* FBExceptions.m in Sources */, EE158ABD1CBD456F00A3E3F0 /* FBDebugCommands.m in Sources */, 716E0BCF1E917E810087A825 /* NSString+FBXMLSafeString.m in Sources */, + 71BB58EA2B96328700CB9BFE /* FBScreenRecordingRequest.m in Sources */, EE158ACD1CBD456F00A3E3F0 /* FBUnknownCommands.m in Sources */, EE158AC51CBD456F00A3E3F0 /* FBOrientationCommands.m in Sources */, 716F0DA32A16CA1000CDD977 /* NSDictionary+FBUtf8SafeDictionary.m in Sources */, @@ -3260,8 +3295,10 @@ 71C8E55325399A6B008572C1 /* XCUIApplication+FBQuiescence.m in Sources */, 71414EDA2670A1EE003A8C5D /* LRUCacheNode.m in Sources */, EE158AB91CBD456F00A3E3F0 /* FBAlertViewCommands.m in Sources */, + 71BB58F12B96511800CB9BFE /* FBVideoCommands.m in Sources */, 71F3E7D625417FF400E0C22B /* FBSettings.m in Sources */, 13DE7A57287CA1EC003243C6 /* FBXCElementSnapshotWrapper.m in Sources */, + 71BB58F82B96531900CB9BFE /* FBScreenRecordingContainer.m in Sources */, EE158AB31CBD456F00A3E3F0 /* XCUIElement+FBScrolling.m in Sources */, 718226CE2587443700661B83 /* GCDAsyncSocket.m in Sources */, EE158AC91CBD456F00A3E3F0 /* FBSessionCommands.m in Sources */, @@ -3294,16 +3331,19 @@ buildActionMask = 2147483647; files = ( 71241D801FAF087500B9559F /* FBW3CMultiTouchActionsIntegrationTests.m in Sources */, + 71BB58DE2B9631B700CB9BFE /* FBVideoRecordingTests.m in Sources */, 71241D7E1FAF084E00B9559F /* FBW3CTouchActionsIntegrationTests.m in Sources */, 63FD950221F9D06100A3E356 /* FBImageProcessorTests.m in Sources */, 719CD8FF2126C90200C7D0C2 /* FBAutoAlertsHandlerTests.m in Sources */, EE2202131ECC612200A29571 /* FBIntegrationTestCase.m in Sources */, - 71BD20781F869E0F00B36EC2 /* FBAppiumTouchActionsIntegrationTests.m in Sources */, - 719A97AC1F88E7370063B4BD /* FBAppiumMultiTouchActionsIntegrationTests.m in Sources */, 715AFAC41FFA2AAF0053896D /* FBScreenTests.m in Sources */, + 71BB58EC2B96328700CB9BFE /* FBScreenRecordingRequest.m in Sources */, EE22021E1ECC618900A29571 /* FBTapTest.m in Sources */, 71930C472066434000D3AFEC /* FBPasteboardTests.m in Sources */, + 71BB58E52B9631F100CB9BFE /* FBScreenRecordingPromise.m in Sources */, + 71BB58FA2B96531900CB9BFE /* FBScreenRecordingContainer.m in Sources */, 7150FFF722476B3A00B2EE28 /* FBForceTouchTests.m in Sources */, + 71BB58F32B96511800CB9BFE /* FBVideoCommands.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/WebDriverAgentLib/Categories/XCAXClient_iOS+FBSnapshotReqParams.m b/WebDriverAgentLib/Categories/XCAXClient_iOS+FBSnapshotReqParams.m index 356a1ea05..3dc6754d5 100644 --- a/WebDriverAgentLib/Categories/XCAXClient_iOS+FBSnapshotReqParams.m +++ b/WebDriverAgentLib/Categories/XCAXClient_iOS+FBSnapshotReqParams.m @@ -65,6 +65,7 @@ @implementation XCAXClient_iOS (FBSnapshotReqParams) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-load-method" +#pragma clang diagnostic ignored "-Wcast-function-type-strict" + (void)load { diff --git a/WebDriverAgentLib/Categories/XCTIssue+FBPatcher.m b/WebDriverAgentLib/Categories/XCTIssue+FBPatcher.m index dc27e86a6..a3b099309 100644 --- a/WebDriverAgentLib/Categories/XCTIssue+FBPatcher.m +++ b/WebDriverAgentLib/Categories/XCTIssue+FBPatcher.m @@ -20,6 +20,8 @@ @implementation XCTIssue (AMPatcher) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-load-method" +#pragma clang diagnostic ignored "-Wcast-function-type-strict" + + (void)load { SEL originalShouldInterruptTest = NSSelectorFromString(@"shouldInterruptTest"); @@ -28,6 +30,7 @@ + (void)load if (nil == originalShouldInterruptTestMethod) return; method_setImplementation(originalShouldInterruptTestMethod, (IMP)swizzledShouldInterruptTest); } + #pragma clang diagnostic pop @end diff --git a/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m b/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m index 251fb7ad9..c3c99e6f5 100644 --- a/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m +++ b/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m @@ -533,7 +533,7 @@ - (BOOL)fb_isSameAppAs:(nullable XCUIApplication *)otherApp if (nil == otherApp) { return NO; } - return [self.bundleID isEqualToString:(NSString *)otherApp.bundleID]; + return self == otherApp || [self.bundleID isEqualToString:(NSString *)otherApp.bundleID]; } @end diff --git a/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.h b/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.h index 5ab5e357c..6be298b49 100644 --- a/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.h +++ b/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.h @@ -15,43 +15,6 @@ NS_ASSUME_NONNULL_BEGIN @interface XCUIApplication (FBTouchAction) -/** - Perform complex touch action in scope of the current application. - Touch actions are represented as lists of dictionaries with predefined sets of values and keys. - Each dictionary must contain 'action' key, which is one of the following: - - 'tap' to perform a single tap - - 'longPress' to perform long tap - - 'press' to perform press - - 'release' to release the finger - - 'moveTo' to move the virtual finger - - 'wait' to modify the duration of the preceeding action - - 'cancel' to cancel the preceeding action in the chain - Each dictionary can also contain 'options' key with additional parameters dictionary related to the appropriate action. - - The following options are mandatory for 'tap', 'longPress', 'press' and 'moveTo' actions: - - 'x' the X coordinate of the action - - 'y' the Y coordinate of the action - - 'element' the corresponding element instance, for which the action is going to be performed - If only 'element' is set then hit point coordinates of this element will be used. - If only 'x' and 'y' are set then these will be considered as absolute coordinates. - If both 'element' and 'x'/'y' are set then these will act as relative element coordinates. - - It is also mandatory, that 'release' and 'wait' actions are preceeded with at least one chain item, which contains absolute coordinates, like 'tap', 'press' or 'longPress'. Empty chains are not allowed. - - The following additional options are available for different actions: - - 'tap': 'count' (defines count of taps to be performed in a row; 1 by default) - - 'longPress': 'duration' (number of milliseconds to hold/move the virtual finger; 500.0 ms by default) - - 'wait': 'ms' (number of milliseconds to wait for the preceeding action; 0.0 ms by default) - - List of lists can be passed there is order to perform multi-finger touch action. Each single actions chain is going to be executed by a separate virtual finger in such case. - - @param actions Either array of dictionaries, whose format is described above to peform single-finger touch action or array of array to perform multi-finger touch action. - @param elementCache Cached elements mapping for the currrent application. The method assumes all elements are already represented by their actual instances if nil value is set - @param error If there is an error, upon return contains an NSError object that describes the problem - @return YES If the touch action has been successfully performed without errors - */ -- (BOOL)fb_performAppiumTouchActions:(NSArray *)actions elementCache:(nullable FBElementCache *)elementCache error:(NSError * _Nullable*)error; - /** Perform complex touch action in scope of the current application. diff --git a/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.m b/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.m index 703a0096d..622dac479 100644 --- a/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.m +++ b/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.m @@ -10,7 +10,6 @@ #import "XCUIApplication+FBTouchAction.h" -#import "FBAppiumActionsSynthesizer.h" #import "FBBaseActionsSynthesizer.h" #import "FBConfiguration.h" #import "FBExceptions.h" @@ -54,20 +53,6 @@ - (BOOL)fb_performActionsWithSynthesizerType:(Class)synthesizerType return [self fb_synthesizeEvent:eventRecord error:error]; } -- (BOOL)fb_performAppiumTouchActions:(NSArray *)actions - elementCache:(FBElementCache *)elementCache - error:(NSError **)error -{ - if (![self fb_performActionsWithSynthesizerType:FBAppiumActionsSynthesizer.class - actions:actions - elementCache:elementCache - error:error]) { - return NO; - } - [self fb_waitUntilStableWithTimeout:FBConfiguration.animationCoolOffTimeout]; - return YES; -} - - (BOOL)fb_performW3CActions:(NSArray *)actions elementCache:(FBElementCache *)elementCache error:(NSError **)error diff --git a/WebDriverAgentLib/Categories/XCUIApplicationProcess+FBQuiescence.h b/WebDriverAgentLib/Categories/XCUIApplicationProcess+FBQuiescence.h index 0c78f2403..f7aacb5c5 100644 --- a/WebDriverAgentLib/Categories/XCUIApplicationProcess+FBQuiescence.h +++ b/WebDriverAgentLib/Categories/XCUIApplicationProcess+FBQuiescence.h @@ -18,6 +18,11 @@ NS_ASSUME_NONNULL_BEGIN /*! Defines wtether the process should perform quiescence checks. YES by default */ @property (nonatomic) NSNumber* fb_shouldWaitForQuiescence; +/** + @param waitForAnimations Set it to YES if XCTest should also wait for application animations to complete + */ +- (void)fb_waitForQuiescenceIncludingAnimationsIdle:(bool)waitForAnimations; + @end NS_ASSUME_NONNULL_END diff --git a/WebDriverAgentLib/Categories/XCUIApplicationProcess+FBQuiescence.m b/WebDriverAgentLib/Categories/XCUIApplicationProcess+FBQuiescence.m index cc60e429f..9c1d06eba 100644 --- a/WebDriverAgentLib/Categories/XCUIApplicationProcess+FBQuiescence.m +++ b/WebDriverAgentLib/Categories/XCUIApplicationProcess+FBQuiescence.m @@ -12,10 +12,12 @@ #import #import "FBConfiguration.h" +#import "FBExceptions.h" #import "FBLogger.h" #import "FBSettings.h" static void (*original_waitForQuiescenceIncludingAnimationsIdle)(id, SEL, BOOL); +static void (*original_waitForQuiescenceIncludingAnimationsIdlePreEvent)(id, SEL, BOOL, BOOL); static void swizzledWaitForQuiescenceIncludingAnimationsIdle(id self, SEL _cmd, BOOL includingAnimations) { @@ -38,17 +40,43 @@ static void swizzledWaitForQuiescenceIncludingAnimationsIdle(id self, SEL _cmd, } } +static void swizzledWaitForQuiescenceIncludingAnimationsIdlePreEvent(id self, SEL _cmd, BOOL includingAnimations, BOOL isPreEvent) +{ + NSString *bundleId = [self bundleID]; + if (![[self fb_shouldWaitForQuiescence] boolValue] || FBConfiguration.waitForIdleTimeout < DBL_EPSILON) { + [FBLogger logFmt:@"Quiescence checks are disabled for %@ application. Making it to believe it is idling", + bundleId]; + return; + } + + NSTimeInterval desiredTimeout = FBConfiguration.waitForIdleTimeout; + NSTimeInterval previousTimeout = _XCTApplicationStateTimeout(); + _XCTSetApplicationStateTimeout(desiredTimeout); + [FBLogger logFmt:@"Waiting up to %@s until %@ is in idle state (%@ animations)", + @(desiredTimeout), bundleId, includingAnimations ? @"including" : @"excluding"]; + @try { + original_waitForQuiescenceIncludingAnimationsIdlePreEvent(self, _cmd, includingAnimations, isPreEvent); + } @finally { + _XCTSetApplicationStateTimeout(previousTimeout); + } +} + @implementation XCUIApplicationProcess (FBQuiescence) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-load-method" +#pragma clang diagnostic ignored "-Wcast-function-type-strict" + (void)load { Method waitForQuiescenceIncludingAnimationsIdleMethod = class_getInstanceMethod(self.class, @selector(waitForQuiescenceIncludingAnimationsIdle:)); + Method waitForQuiescenceIncludingAnimationsIdlePreEventMethod = class_getInstanceMethod(self.class, @selector(waitForQuiescenceIncludingAnimationsIdle:isPreEvent:)); if (nil != waitForQuiescenceIncludingAnimationsIdleMethod) { IMP swizzledImp = (IMP)swizzledWaitForQuiescenceIncludingAnimationsIdle; original_waitForQuiescenceIncludingAnimationsIdle = (void (*)(id, SEL, BOOL)) method_setImplementation(waitForQuiescenceIncludingAnimationsIdleMethod, swizzledImp); + } else if (nil != waitForQuiescenceIncludingAnimationsIdlePreEventMethod) { + IMP swizzledImp = (IMP)swizzledWaitForQuiescenceIncludingAnimationsIdlePreEvent; + original_waitForQuiescenceIncludingAnimationsIdlePreEvent = (void (*)(id, SEL, BOOL, BOOL)) method_setImplementation(waitForQuiescenceIncludingAnimationsIdlePreEventMethod, swizzledImp); } else { [FBLogger log:@"Could not find method -[XCUIApplicationProcess waitForQuiescenceIncludingAnimationsIdle:]"]; } @@ -74,4 +102,18 @@ - (void)setFb_shouldWaitForQuiescence:(NSNumber *)value objc_setAssociatedObject(self, &XCUIAPPLICATIONPROCESS_SHOULD_WAIT_FOR_QUIESCENCE, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } +- (void)fb_waitForQuiescenceIncludingAnimationsIdle:(bool)waitForAnimations +{ + if ([self respondsToSelector:@selector(waitForQuiescenceIncludingAnimationsIdle:)]) { + [self waitForQuiescenceIncludingAnimationsIdle:waitForAnimations]; + } else if ([self respondsToSelector:@selector(waitForQuiescenceIncludingAnimationsIdle:isPreEvent:)]) { + [self waitForQuiescenceIncludingAnimationsIdle:waitForAnimations isPreEvent:NO]; + } else { + @throw [NSException exceptionWithName:FBIncompatibleWdaException + reason:@"The current WebDriverAgent build is not compatible to your device OS version" + userInfo:@{}]; + } +} + + @end diff --git a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h index e60efaa48..ca7fc53d8 100644 --- a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h +++ b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h @@ -70,7 +70,7 @@ typedef NS_ENUM(NSUInteger, FBUIInterfaceAppearance) { - (nullable NSData *)fb_screenshotWithError:(NSError*__autoreleasing*)error; /** - Returns device current wifi ip4 address + Returns device's current wifi ip4 address */ - (nullable NSString *)fb_wifiIPAddress; diff --git a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m index 5b95b8c62..ef8664fb6 100644 --- a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m +++ b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m @@ -134,7 +134,7 @@ - (NSString *)fb_wifiIPAddress continue; } NSString *interfaceName = [NSString stringWithUTF8String:temp_addr->ifa_name]; - if(![interfaceName containsString:@"en"]) { + if(![interfaceName isEqualToString:@"en0"]) { temp_addr = temp_addr->ifa_next; continue; } diff --git a/WebDriverAgentLib/Categories/XCUIElement+FBUID.m b/WebDriverAgentLib/Categories/XCUIElement+FBUID.m index 765b410c1..ecac2cd91 100644 --- a/WebDriverAgentLib/Categories/XCUIElement+FBUID.m +++ b/WebDriverAgentLib/Categories/XCUIElement+FBUID.m @@ -42,6 +42,7 @@ static void swizzled_validatePredicateWithExpressionsAllowed(id self, SEL _cmd, #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-load-method" +#pragma clang diagnostic ignored "-Wcast-function-type-strict" + (void)load { Class XCElementSnapshotCls = objc_lookUpClass("XCElementSnapshot"); diff --git a/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.m b/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.m index b5a072c06..5f9fd96f4 100644 --- a/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.m +++ b/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.m @@ -31,6 +31,7 @@ #import "XCTElementSetTransformer-Protocol.h" #import "XCTestPrivateSymbols.h" #import "XCTRunnerDaemonSession.h" +#import "XCUIApplicationProcess+FBQuiescence.h" #import "XCUIElement+FBCaching.h" #import "XCUIElement+FBWebDriverAttributes.h" #import "XCUIElementQuery.h" @@ -190,7 +191,7 @@ - (void)fb_waitUntilStableWithTimeout:(NSTimeInterval)timeout self.application.fb_shouldWaitForQuiescence = YES; } [[[self.application applicationImpl] currentProcess] - waitForQuiescenceIncludingAnimationsIdle:YES]; + fb_waitForQuiescenceIncludingAnimationsIdle:YES]; if (previousQuiescence != self.application.fb_shouldWaitForQuiescence) { self.application.fb_shouldWaitForQuiescence = previousQuiescence; } diff --git a/WebDriverAgentLib/Commands/FBCustomCommands.m b/WebDriverAgentLib/Commands/FBCustomCommands.m index f31c96739..2ab039e6b 100644 --- a/WebDriverAgentLib/Commands/FBCustomCommands.m +++ b/WebDriverAgentLib/Commands/FBCustomCommands.m @@ -15,6 +15,7 @@ #import "FBConfiguration.h" #import "FBKeyboard.h" #import "FBNotificationsHelper.h" +#import "FBMathUtils.h" #import "FBPasteboard.h" #import "FBResponsePayload.h" #import "FBRoute.h" @@ -48,6 +49,7 @@ + (NSArray *)routes [[FBRoute GET:@"/wda/locked"].withoutSession respondWithTarget:self action:@selector(handleIsLocked:)], [[FBRoute GET:@"/wda/locked"] respondWithTarget:self action:@selector(handleIsLocked:)], [[FBRoute GET:@"/wda/screen"] respondWithTarget:self action:@selector(handleGetScreen:)], + [[FBRoute GET:@"/wda/screen"].withoutSession respondWithTarget:self action:@selector(handleGetScreen:)], [[FBRoute GET:@"/wda/activeAppInfo"] respondWithTarget:self action:@selector(handleActiveAppInfo:)], [[FBRoute GET:@"/wda/activeAppInfo"].withoutSession respondWithTarget:self action:@selector(handleActiveAppInfo:)], #if !TARGET_OS_TV // tvOS does not provide relevant APIs @@ -134,10 +136,22 @@ + (NSArray *)routes + (id)handleGetScreen:(FBRouteRequest *)request { - FBSession *session = request.session; - CGSize statusBarSize = [FBScreen statusBarSizeForApplication:session.activeApplication]; + XCUIApplication *app = XCUIApplication.fb_systemApplication; + + XCUIElement *mainStatusBar = app.statusBars.allElementsBoundByIndex.firstObject; + CGSize statusBarSize = (nil == mainStatusBar) ? CGSizeZero : mainStatusBar.frame.size; + +#if TARGET_OS_TV + CGSize screenSize = app.frame.size; +#else + CGSize screenSize = FBAdjustDimensionsForApplication(app.wdFrame.size, app.interfaceOrientation); +#endif + return FBResponseWithObject( @{ + @"screenSize":@{@"width": @(screenSize.width), + @"height": @(screenSize.height) + }, @"statusBarSize": @{@"width": @(statusBarSize.width), @"height": @(statusBarSize.height), }, diff --git a/WebDriverAgentLib/Commands/FBElementCommands.m b/WebDriverAgentLib/Commands/FBElementCommands.m index 0313302f2..762bad327 100644 --- a/WebDriverAgentLib/Commands/FBElementCommands.m +++ b/WebDriverAgentLib/Commands/FBElementCommands.m @@ -352,10 +352,6 @@ + (NSArray *)routes { FBElementCache *elementCache = request.session.elementCache; XCUIElement *element = [self targetFromRequest:request]; - if (![element respondsToSelector:@selector(pressForDuration:thenDragToElement:withVelocity:thenHoldForDuration:)]) { - return FBResponseWithStatus([FBCommandStatus unsupportedOperationErrorWithMessage:@"This method is only supported in Xcode 12 and above" - traceback:nil]); - } [element pressForDuration:[request.arguments[@"pressDuration"] doubleValue] thenDragToElement:[elementCache elementForUUID:(NSString *)request.arguments[@"toElement"]] withVelocity:[request.arguments[@"velocity"] doubleValue] @@ -370,10 +366,6 @@ + (NSArray *)routes (CGFloat)[request.arguments[@"fromY"] doubleValue]); XCUICoordinate *startCoordinate = [self.class gestureCoordinateWithOffset:startOffset element:session.activeApplication]; - if (![startCoordinate respondsToSelector:@selector(pressForDuration:thenDragToCoordinate:withVelocity:thenHoldForDuration:)]) { - return FBResponseWithStatus([FBCommandStatus unsupportedOperationErrorWithMessage:@"This method is only supported in Xcode 12 and above" - traceback:nil]); - } CGVector endOffset = CGVectorMake((CGFloat)[request.arguments[@"toX"] doubleValue], (CGFloat)[request.arguments[@"toY"] doubleValue]); XCUICoordinate *endCoordinate = [self.class gestureCoordinateWithOffset:endOffset diff --git a/WebDriverAgentLib/Commands/FBSessionCommands.m b/WebDriverAgentLib/Commands/FBSessionCommands.m index 9594058a9..c11c5eabb 100644 --- a/WebDriverAgentLib/Commands/FBSessionCommands.m +++ b/WebDriverAgentLib/Commands/FBSessionCommands.m @@ -11,6 +11,7 @@ #import "FBCapabilities.h" #import "FBConfiguration.h" +#import "FBExceptions.h" #import "FBLogger.h" #import "FBProtocolHelpers.h" #import "FBRouteRequest.h" @@ -65,6 +66,7 @@ + (NSArray *)routes return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:@"URL is required" traceback:nil]); } NSString* bundleId = request.arguments[@"bundleId"]; + NSNumber* idleTimeoutMs = request.arguments[@"idleTimeoutMs"]; NSError *error; if (nil == bundleId) { if (![XCUIDevice.sharedDevice fb_openUrl:urlString error:&error]) { @@ -74,6 +76,10 @@ + (NSArray *)routes if (![XCUIDevice.sharedDevice fb_openUrl:urlString withApplication:bundleId error:&error]) { return FBResponseWithUnknownError(error); } + if (idleTimeoutMs.doubleValue > 0) { + XCUIApplication *app = [[XCUIApplication alloc] initWithBundleIdentifier:bundleId]; + [app fb_waitUntilStableWithTimeout:FBMillisToSeconds(idleTimeoutMs.doubleValue)]; + } } return FBResponseWithOK(); } @@ -91,7 +97,7 @@ + (NSArray *)routes traceback:nil]); } if (nil == (capabilities = FBParseCapabilities((NSDictionary *)request.arguments[@"capabilities"], &error))) { - return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:error.description traceback:nil]); + return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:error.localizedDescription traceback:nil]); } [FBConfiguration resetSessionSettings]; @@ -155,16 +161,26 @@ + (NSArray *)routes if (app.running) { [app terminate]; } - NSError *openError; - if (![XCUIDevice.sharedDevice fb_openUrl:initialUrl - withApplication:bundleID - error:&openError]) { - NSString *errorMsg = [NSString stringWithFormat:@"Cannot open the URL %@ wuth the %@ application. Original error: %@", - initialUrl, bundleID, openError.description]; - return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:errorMsg traceback:nil]); + id errorResponse = [self openDeepLink:initialUrl + withApplication:bundleID + timeout:capabilities[FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC]]; + if (nil != errorResponse) { + return errorResponse; } } else { - [app launch]; + NSTimeInterval defaultTimeout = _XCTApplicationStateTimeout(); + if (nil != capabilities[FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC]) { + _XCTSetApplicationStateTimeout([capabilities[FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC] doubleValue]); + } + @try { + [app launch]; + } @catch (NSException *e) { + return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:e.reason traceback:nil]); + } @finally { + if (nil != capabilities[FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC]) { + _XCTSetApplicationStateTimeout(defaultTimeout); + } + } } if (!app.running) { NSString *errorMsg = [NSString stringWithFormat:@"Cannot launch %@ application. Make sure the correct bundle identifier has been provided in capabilities and check the device log for possible crash report occurrences", bundleID]; @@ -173,13 +189,11 @@ + (NSArray *)routes } } else if (appState == XCUIApplicationStateRunningBackground && !forceAppLaunch) { if (nil != initialUrl) { - NSError *openError; - if (![XCUIDevice.sharedDevice fb_openUrl:initialUrl - withApplication:bundleID - error:&openError]) { - NSString *errorMsg = [NSString stringWithFormat:@"Cannot open the URL %@ with the %@ application. Original error: %@", - initialUrl, bundleID, openError.description]; - return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:errorMsg traceback:nil]); + id errorResponse = [self openDeepLink:initialUrl + withApplication:bundleID + timeout:nil]; + if (nil != errorResponse) { + return errorResponse; } } else { [app activate]; @@ -188,11 +202,11 @@ + (NSArray *)routes } if (nil != initialUrl && nil == bundleID) { - NSError *openError; - if (![XCUIDevice.sharedDevice fb_openUrl:initialUrl error:&openError]) { - NSString *errorMsg = [NSString stringWithFormat:@"Cannot open the URL %@. Original error: %@", - initialUrl, openError.description]; - return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:errorMsg traceback:nil]); + id errorResponse = [self openDeepLink:initialUrl + withApplication:nil + timeout:capabilities[FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC]]; + if (nil != errorResponse) { + return errorResponse; } } @@ -270,6 +284,11 @@ + (NSArray *)routes if (nil != upgradeTimestamp && upgradeTimestamp.length > 0) { [buildInfo setObject:upgradeTimestamp forKey:@"upgradedAt"]; } + NSDictionary *infoDict = [[NSBundle bundleForClass:self.class] infoDictionary]; + NSString *version = [infoDict objectForKey:@"CFBundleShortVersionString"]; + if (nil != version) { + [buildInfo setObject:version forKey:@"version"]; + } return FBResponseWithObject( @{ @@ -330,6 +349,8 @@ + (NSArray *)routes FB_SETTING_ACCEPT_ALERT_BUTTON_SELECTOR: FBConfiguration.acceptAlertButtonSelector, FB_SETTING_DISMISS_ALERT_BUTTON_SELECTOR: FBConfiguration.dismissAlertButtonSelector, FB_SETTING_DEFAULT_ALERT_ACTION: request.session.defaultAlertAction ?: @"", + FB_SETTING_MAX_TYPING_FREQUENCY: @([FBConfiguration maxTypingFrequency]), + FB_SETTING_RESPECT_SYSTEM_ALERTS: @([FBConfiguration shouldRespectSystemAlerts]), #if !TARGET_OS_TV FB_SETTING_SCREENSHOT_ORIENTATION: [FBConfiguration humanReadableScreenshotOrientation], #endif @@ -370,6 +391,9 @@ + (NSArray *)routes if (nil != [settings objectForKey:FB_SETTING_KEYBOARD_PREDICTION]) { [FBConfiguration setKeyboardPrediction:[[settings objectForKey:FB_SETTING_KEYBOARD_PREDICTION] boolValue]]; } + if (nil != [settings objectForKey:FB_SETTING_RESPECT_SYSTEM_ALERTS]) { + [FBConfiguration setShouldRespectSystemAlerts:[[settings objectForKey:FB_SETTING_RESPECT_SYSTEM_ALERTS] boolValue]]; + } // SNAPSHOT_TIMEOUT setting is deprecated. Please use CUSTOM_SNAPSHOT_TIMEOUT instead if (nil != [settings objectForKey:FB_SETTING_SNAPSHOT_TIMEOUT]) { [FBConfiguration setCustomSnapshotTimeout:[[settings objectForKey:FB_SETTING_SNAPSHOT_TIMEOUT] doubleValue]]; @@ -396,7 +420,8 @@ + (NSArray *)routes NSError *error; if (![FBActiveAppDetectionPoint.sharedInstance setCoordinatesWithString:(NSString *)[settings objectForKey:FB_SETTING_ACTIVE_APP_DETECTION_POINT] error:&error]) { - return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:error.description traceback:nil]); + return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:error.localizedDescription + traceback:nil]); } } if (nil != [settings objectForKey:FB_SETTING_INCLUDE_NON_MODAL_ELEMENTS]) { @@ -421,13 +446,16 @@ + (NSArray *)routes if ([[settings objectForKey:FB_SETTING_DEFAULT_ALERT_ACTION] isKindOfClass:NSString.class]) { request.session.defaultAlertAction = [settings[FB_SETTING_DEFAULT_ALERT_ACTION] lowercaseString]; } + if (nil != [settings objectForKey:FB_SETTING_MAX_TYPING_FREQUENCY]) { + [FBConfiguration setMaxTypingFrequency:[[settings objectForKey:FB_SETTING_MAX_TYPING_FREQUENCY] unsignedIntegerValue]]; + } #if !TARGET_OS_TV if (nil != [settings objectForKey:FB_SETTING_SCREENSHOT_ORIENTATION]) { NSError *error; if (![FBConfiguration setScreenshotOrientation:(NSString *)[settings objectForKey:FB_SETTING_SCREENSHOT_ORIENTATION] error:&error]) { - return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:error.description + return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:error.localizedDescription traceback:nil]); } } @@ -486,4 +514,33 @@ + (NSDictionary *)currentCapabilities }; } ++(nullable id)openDeepLink:(NSString *)initialUrl + withApplication:(nullable NSString *)bundleID + timeout:(nullable NSNumber *)timeout +{ + NSError *openError; + NSTimeInterval defaultTimeout = _XCTApplicationStateTimeout(); + if (nil != timeout) { + _XCTSetApplicationStateTimeout([timeout doubleValue]); + } + @try { + BOOL result = nil == bundleID + ? [XCUIDevice.sharedDevice fb_openUrl:initialUrl + error:&openError] + : [XCUIDevice.sharedDevice fb_openUrl:initialUrl + withApplication:(id)bundleID + error:&openError]; + if (result) { + return nil; + } + NSString *errorMsg = [NSString stringWithFormat:@"Cannot open the URL %@ with the %@ application. Original error: %@", + initialUrl, bundleID ?: @"default", openError.localizedDescription]; + return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:errorMsg traceback:nil]); + } @finally { + if (nil != timeout) { + _XCTSetApplicationStateTimeout(defaultTimeout); + } + } +} + @end diff --git a/WebDriverAgentLib/Commands/FBTouchActionCommands.m b/WebDriverAgentLib/Commands/FBTouchActionCommands.m index 3e31ddf2e..c14edcb63 100644 --- a/WebDriverAgentLib/Commands/FBTouchActionCommands.m +++ b/WebDriverAgentLib/Commands/FBTouchActionCommands.m @@ -22,25 +22,12 @@ + (NSArray *)routes { return @[ - [[FBRoute POST:@"/wda/touch/perform"] respondWithTarget:self action:@selector(handlePerformAppiumTouchActions:)], - [[FBRoute POST:@"/wda/touch/multi/perform"] respondWithTarget:self action:@selector(handlePerformAppiumTouchActions:)], [[FBRoute POST:@"/actions"] respondWithTarget:self action:@selector(handlePerformW3CTouchActions:)], ]; } #pragma mark - Commands -+ (id)handlePerformAppiumTouchActions:(FBRouteRequest *)request -{ - XCUIApplication *application = request.session.activeApplication; - NSArray *actions = (NSArray *)request.arguments[@"actions"]; - NSError *error; - if (![application fb_performAppiumTouchActions:actions elementCache:request.session.elementCache error:&error]) { - return FBResponseWithUnknownError(error); - } - return FBResponseWithOK(); -} - + (id)handlePerformW3CTouchActions:(FBRouteRequest *)request { XCUIApplication *application = request.session.activeApplication; diff --git a/WebDriverAgentLib/Utilities/FBAppiumActionsSynthesizer.h b/WebDriverAgentLib/Commands/FBVideoCommands.h similarity index 72% rename from WebDriverAgentLib/Utilities/FBAppiumActionsSynthesizer.h rename to WebDriverAgentLib/Commands/FBVideoCommands.h index 090bacdd7..b2e3eb795 100644 --- a/WebDriverAgentLib/Utilities/FBAppiumActionsSynthesizer.h +++ b/WebDriverAgentLib/Commands/FBVideoCommands.h @@ -7,14 +7,14 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import "FBBaseActionsSynthesizer.h" +#import + +#import NS_ASSUME_NONNULL_BEGIN -#if !TARGET_OS_TV -@interface FBAppiumActionsSynthesizer : FBBaseActionsSynthesizer +@interface FBVideoCommands : NSObject @end -#endif NS_ASSUME_NONNULL_END diff --git a/WebDriverAgentLib/Commands/FBVideoCommands.m b/WebDriverAgentLib/Commands/FBVideoCommands.m new file mode 100644 index 000000000..8366c3a79 --- /dev/null +++ b/WebDriverAgentLib/Commands/FBVideoCommands.m @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "FBVideoCommands.h" + +#import "FBRouteRequest.h" +#import "FBScreenRecordingContainer.h" +#import "FBScreenRecordingPromise.h" +#import "FBScreenRecordingRequest.h" +#import "FBSession.h" +#import "FBXCTestDaemonsProxy.h" + +const NSUInteger DEFAULT_FPS = 24; +const NSUInteger DEFAULT_CODEC = 0; + +@implementation FBVideoCommands + ++ (NSArray *)routes +{ + return + @[ + [[FBRoute POST:@"/wda/video/start"] respondWithTarget:self action:@selector(handleStartVideoRecording:)], + [[FBRoute POST:@"/wda/video/stop"] respondWithTarget:self action:@selector(handleStopVideoRecording:)], + [[FBRoute GET:@"/wda/video"] respondWithTarget:self action:@selector(handleGetVideoRecording:)], + + [[FBRoute POST:@"/wda/video/start"].withoutSession respondWithTarget:self action:@selector(handleStartVideoRecording:)], + [[FBRoute POST:@"/wda/video/stop"].withoutSession respondWithTarget:self action:@selector(handleStopVideoRecording:)], + [[FBRoute GET:@"/wda/video"].withoutSession respondWithTarget:self action:@selector(handleGetVideoRecording:)], + ]; +} + ++ (id)handleStartVideoRecording:(FBRouteRequest *)request +{ + FBScreenRecordingPromise *activeScreenRecording = FBScreenRecordingContainer.sharedInstance.screenRecordingPromise; + if (nil != activeScreenRecording) { + return FBResponseWithObject([FBScreenRecordingContainer.sharedInstance toDictionary] ?: [NSNull null]); + } + + NSNumber *fps = (NSNumber *)request.arguments[@"fps"] ?: @(DEFAULT_FPS); + NSNumber *codec = (NSNumber *)request.arguments[@"codec"] ?: @(DEFAULT_CODEC); + FBScreenRecordingRequest *recordingRequest = [[FBScreenRecordingRequest alloc] initWithFps:fps.integerValue + codec:codec.longLongValue]; + NSError *error; + FBScreenRecordingPromise* promise = [FBXCTestDaemonsProxy startScreenRecordingWithRequest:recordingRequest + error:&error]; + if (nil == promise) { + [FBScreenRecordingContainer.sharedInstance reset]; + return FBResponseWithUnknownError(error); + } + [FBScreenRecordingContainer.sharedInstance storeScreenRecordingPromise:promise + fps:fps.integerValue + codec:codec.longLongValue]; + return FBResponseWithObject([FBScreenRecordingContainer.sharedInstance toDictionary]); +} + ++ (id)handleStopVideoRecording:(FBRouteRequest *)request +{ + FBScreenRecordingPromise *activeScreenRecording = FBScreenRecordingContainer.sharedInstance.screenRecordingPromise; + if (nil == activeScreenRecording) { + return FBResponseWithOK(); + } + + NSUUID *recordingId = activeScreenRecording.identifier; + NSDictionary *response = [FBScreenRecordingContainer.sharedInstance toDictionary]; + NSError *error; + if (![FBXCTestDaemonsProxy stopScreenRecordingWithUUID:recordingId error:&error]) { + [FBScreenRecordingContainer.sharedInstance reset]; + return FBResponseWithUnknownError(error); + } + [FBScreenRecordingContainer.sharedInstance reset]; + return FBResponseWithObject(response); +} + ++ (id)handleGetVideoRecording:(FBRouteRequest *)request +{ + return FBResponseWithObject([FBScreenRecordingContainer.sharedInstance toDictionary] ?: [NSNull null]); +} + +@end diff --git a/WebDriverAgentLib/FBAlert.m b/WebDriverAgentLib/FBAlert.m index 0709e5f1f..b06e26788 100644 --- a/WebDriverAgentLib/FBAlert.m +++ b/WebDriverAgentLib/FBAlert.m @@ -261,15 +261,11 @@ - (BOOL)clickAlertButton:(NSString *)label error:(NSError **)error - (XCUIElement *)alertElement { if (nil == self.element) { - self.element = self.application.fb_alertElement; - if (nil == self.element) { - XCUIApplication *systemApp = XCUIApplication.fb_systemApplication; - for (XCUIApplication *activeApp in XCUIApplication.fb_activeApplications) { - if (systemApp.processID == activeApp.processID) { - self.element = activeApp.fb_alertElement; - break; - } - } + XCUIApplication *systemApp = XCUIApplication.fb_systemApplication; + if ([systemApp fb_isSameAppAs:self.application]) { + self.element = systemApp.fb_alertElement; + } else { + self.element = systemApp.fb_alertElement ?: self.application.fb_alertElement; } } return self.element; diff --git a/WebDriverAgentLib/Info.plist b/WebDriverAgentLib/Info.plist index 1b89f4fcd..7749d5e09 100644 --- a/WebDriverAgentLib/Info.plist +++ b/WebDriverAgentLib/Info.plist @@ -1,26 +1,26 @@ - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - com.facebook.wda.lib - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSPrincipalClass - - - + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 8.9.4 + CFBundleSignature + ???? + CFBundleVersion + 8.9.4 + NSPrincipalClass + + + \ No newline at end of file diff --git a/WebDriverAgentLib/Routing/FBExceptionHandler.m b/WebDriverAgentLib/Routing/FBExceptionHandler.m index 7b7a8263f..811a0efb5 100644 --- a/WebDriverAgentLib/Routing/FBExceptionHandler.m +++ b/WebDriverAgentLib/Routing/FBExceptionHandler.m @@ -45,6 +45,9 @@ - (void)handleException:(NSException *)exception forResponse:(RouteResponse *)re } else if ([exception.name isEqualToString:FBTimeoutException]) { commandStatus = [FBCommandStatus timeoutErrorWithMessage:exception.reason traceback:traceback]; + } else if ([exception.name isEqualToString:FBSessionCreationException]) { + commandStatus = [FBCommandStatus sessionNotCreatedError:exception.reason + traceback:traceback]; } else { commandStatus = [FBCommandStatus unknownErrorWithMessage:exception.reason traceback:traceback]; diff --git a/WebDriverAgentLib/Routing/FBExceptions.h b/WebDriverAgentLib/Routing/FBExceptions.h index 1c9507a19..13fda9f4c 100644 --- a/WebDriverAgentLib/Routing/FBExceptions.h +++ b/WebDriverAgentLib/Routing/FBExceptions.h @@ -14,6 +14,9 @@ NS_ASSUME_NONNULL_BEGIN /*! Exception used to notify about missing session */ extern NSString *const FBSessionDoesNotExistException; +/*! Exception used to notify about session creation issues */ +extern NSString *const FBSessionCreationException; + /*! Exception used to notify about application deadlock */ extern NSString *const FBApplicationDeadlockDetectedException; @@ -52,4 +55,7 @@ extern NSString *const FBApplicationCrashedException; /*! Exception used to notify about the application is not installed */ extern NSString *const FBApplicationMissingException; +/*! Exception used to notify about WDA incompatibility with the current platform version */ +extern NSString *const FBIncompatibleWdaException; + NS_ASSUME_NONNULL_END diff --git a/WebDriverAgentLib/Routing/FBExceptions.m b/WebDriverAgentLib/Routing/FBExceptions.m index 571cea4fb..93c1837e7 100644 --- a/WebDriverAgentLib/Routing/FBExceptions.m +++ b/WebDriverAgentLib/Routing/FBExceptions.m @@ -10,6 +10,7 @@ #import "FBExceptions.h" NSString *const FBInvalidArgumentException = @"FBInvalidArgumentException"; +NSString *const FBSessionCreationException = @"FBSessionCreationException"; NSString *const FBSessionDoesNotExistException = @"FBSessionDoesNotExistException"; NSString *const FBApplicationDeadlockDetectedException = @"FBApplicationDeadlockDetectedException"; NSString *const FBElementAttributeUnknownException = @"FBElementAttributeUnknownException"; @@ -21,3 +22,4 @@ NSString *const FBClassChainQueryParseException = @"FBClassChainQueryParseException"; NSString *const FBApplicationCrashedException = @"FBApplicationCrashedException"; NSString *const FBApplicationMissingException = @"FBApplicationMissingException"; +NSString *const FBIncompatibleWdaException = @"FBIncompatibleWdaException"; diff --git a/WebDriverAgentLib/Routing/FBRoute.m b/WebDriverAgentLib/Routing/FBRoute.m index 745ddaf31..5d740de7e 100644 --- a/WebDriverAgentLib/Routing/FBRoute.m +++ b/WebDriverAgentLib/Routing/FBRoute.m @@ -39,7 +39,10 @@ @implementation FBRoute_TargetAction - (void)mountRequest:(FBRouteRequest *)request intoResponse:(RouteResponse *)response { [self decorateRequest:request]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-function-type-strict" id (*requestMsgSend)(id, SEL, FBRouteRequest *) = ((id(*)(id, SEL, FBRouteRequest *))objc_msgSend); +#pragma clang diagnostic pop id payload = requestMsgSend(self.target, self.action, request); [payload dispatchWithResponse:response]; } diff --git a/WebDriverAgentLib/Routing/FBScreenRecordingContainer.h b/WebDriverAgentLib/Routing/FBScreenRecordingContainer.h new file mode 100644 index 000000000..48e5c7481 --- /dev/null +++ b/WebDriverAgentLib/Routing/FBScreenRecordingContainer.h @@ -0,0 +1,57 @@ +/** + * + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class FBScreenRecordingPromise; + +@interface FBScreenRecordingContainer : NSObject + +/** The amount of video FPS */ +@property (readonly, nonatomic) NSUInteger fps; +/** Codec to use, where 0 is h264, 1 - HEVC */ +@property (readonly, nonatomic) long long codec; +/** Keep the currently active screen resording promise. Equals to nil if no active screen recordings are running */ +@property (readonly, nonatomic, nullable) FBScreenRecordingPromise* screenRecordingPromise; +/** The timestamp of the video startup as Unix float seconds */ +@property (readonly, nonatomic, nullable) NSNumber *startedAt; + +/** +@return singleton instance + */ ++ (instancetype)sharedInstance; + +/** + Keeps current screen recording promise + + @param screenRecordingPromise a promise to set + @param fps FPS value + @param codec Codec value + */ +- (void)storeScreenRecordingPromise:(FBScreenRecordingPromise *)screenRecordingPromise + fps:(NSUInteger)fps + codec:(long long)codec; +/** + Resets the current screen recording promise + */ +- (void)reset; + +/** + Transforms the container content to a dictionary. + + @return May return nil if no screen recording is currently running + */ +- (nullable NSDictionary *)toDictionary; + +@end + +NS_ASSUME_NONNULL_END diff --git a/WebDriverAgentLib/Routing/FBScreenRecordingContainer.m b/WebDriverAgentLib/Routing/FBScreenRecordingContainer.m new file mode 100644 index 000000000..b0a744dc0 --- /dev/null +++ b/WebDriverAgentLib/Routing/FBScreenRecordingContainer.m @@ -0,0 +1,73 @@ +/** + * + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "FBScreenRecordingContainer.h" + +#import "FBScreenRecordingPromise.h" + +@interface FBScreenRecordingContainer () + +@property (readwrite) NSUInteger fps; +@property (readwrite) long long codec; +@property (readwrite) FBScreenRecordingPromise* screenRecordingPromise; +@property (readwrite) NSNumber *startedAt; + +@end + +@implementation FBScreenRecordingContainer + ++ (instancetype)sharedInstance +{ + static FBScreenRecordingContainer *instance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[self alloc] init]; + }); + return instance; +} + +- (void)storeScreenRecordingPromise:(FBScreenRecordingPromise *)screenRecordingPromise + fps:(NSUInteger)fps + codec:(long long)codec; +{ + self.fps = fps; + self.codec = codec; + self.screenRecordingPromise = screenRecordingPromise; + self.startedAt = @([NSDate.date timeIntervalSince1970]); +} + +- (void)reset; +{ + self.fps = 0; + self.codec = 0; + if (nil != self.screenRecordingPromise) { + [XCTContext runActivityNamed:@"Video Cleanup" block:^(id activity){ + [activity addAttachment:(XCTAttachment *)self.screenRecordingPromise.nativePromise]; + }]; + self.screenRecordingPromise = nil; + } + self.startedAt = nil; +} + +- (nullable NSDictionary *)toDictionary +{ + if (nil == self.screenRecordingPromise) { + return nil; + } + + return @{ + @"fps": @(self.fps), + @"codec": @(self.codec), + @"uuid": [self.screenRecordingPromise identifier].UUIDString ?: [NSNull null], + @"startedAt": self.startedAt ?: [NSNull null], + }; +} + +@end diff --git a/WebDriverAgentLib/Routing/FBScreenRecordingPromise.h b/WebDriverAgentLib/Routing/FBScreenRecordingPromise.h new file mode 100644 index 000000000..a86918761 --- /dev/null +++ b/WebDriverAgentLib/Routing/FBScreenRecordingPromise.h @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FBScreenRecordingPromise : NSObject + +/** Unique identiifier of the video recording, also used as the default file name */ +@property (nonatomic, readonly) NSUUID *identifier; +/** Native screen recording promise */ +@property (nonatomic, readonly) id nativePromise; + +/** + Creates a wrapper object for a native screen recording promise + + @param promise Native promise object to be wrapped + */ +- (instancetype)initWithNativePromise:(id)promise; + +@end + +NS_ASSUME_NONNULL_END diff --git a/WebDriverAgentLib/Routing/FBScreenRecordingPromise.m b/WebDriverAgentLib/Routing/FBScreenRecordingPromise.m new file mode 100644 index 000000000..9de9dbaf1 --- /dev/null +++ b/WebDriverAgentLib/Routing/FBScreenRecordingPromise.m @@ -0,0 +1,32 @@ +/** + * + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "FBScreenRecordingPromise.h" + +@interface FBScreenRecordingPromise () +@property (readwrite) id nativePromise; +@end + +@implementation FBScreenRecordingPromise + +- (instancetype)initWithNativePromise:(id)promise +{ + if ((self = [super init])) { + self.nativePromise = promise; + } + return self; +} + +- (NSUUID *)identifier +{ + return (NSUUID *)[self.nativePromise valueForKey:@"_UUID"]; +} + +@end diff --git a/WebDriverAgentLib/Routing/FBScreenRecordingRequest.h b/WebDriverAgentLib/Routing/FBScreenRecordingRequest.h new file mode 100644 index 000000000..5e24e5588 --- /dev/null +++ b/WebDriverAgentLib/Routing/FBScreenRecordingRequest.h @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FBScreenRecordingRequest : NSObject + +/** The amount of video FPS */ +@property (readonly, nonatomic) NSUInteger fps; +/** Codec to use, where 0 is h264, 1 - HEVC */ +@property (readonly, nonatomic) long long codec; + +/** + Creates a custom wrapper for a screen recording reqeust + + @param fps FPS value, see baove + @param codec Codex value, see above + */ +- (instancetype)initWithFps:(NSUInteger)fps codec:(long long)codec; + +/** + Transforms the current wrapper instance to a native object, + which is ready to be passed to XCTest APIs + + @param error If there was a failure converting the instance to a native object + @returns Native object instance + */ +- (nullable id)toNativeRequestWithError:(NSError **)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/WebDriverAgentLib/Routing/FBScreenRecordingRequest.m b/WebDriverAgentLib/Routing/FBScreenRecordingRequest.m new file mode 100644 index 000000000..5249b76b0 --- /dev/null +++ b/WebDriverAgentLib/Routing/FBScreenRecordingRequest.m @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "FBScreenRecordingRequest.h" + +#import "FBErrorBuilder.h" +#import "XCUIScreen.h" + +@implementation FBScreenRecordingRequest + +- (instancetype)initWithFps:(NSUInteger)fps codec:(long long)codec +{ + if ((self = [super init])) { + _fps = fps; + _codec = codec; + } + return self; +} + +- (nullable id)createVideoEncodingWithError:(NSError **)error +{ + Class videoEncodingClass = NSClassFromString(@"XCTVideoEncoding"); + if (nil == videoEncodingClass) { + [[[FBErrorBuilder builder] + withDescription:@"Cannot find XCTVideoEncoding class"] + buildError:error]; + return nil; + } + + id videoEncodingAllocated = [videoEncodingClass alloc]; + SEL videoEncodingConstructorSelector = NSSelectorFromString(@"initWithCodec:frameRate:"); + if (![videoEncodingAllocated respondsToSelector:videoEncodingConstructorSelector]) { + [[[FBErrorBuilder builder] + withDescription:@"'initWithCodec:frameRate:' contructor is not found on XCTVideoEncoding class"] + buildError:error]; + return nil; + } + + NSMethodSignature *videoEncodingContructorSignature = [videoEncodingAllocated methodSignatureForSelector:videoEncodingConstructorSelector]; + NSInvocation *videoEncodingInitInvocation = [NSInvocation invocationWithMethodSignature:videoEncodingContructorSignature]; + [videoEncodingInitInvocation setSelector:videoEncodingConstructorSelector]; + long long codec = self.codec; + [videoEncodingInitInvocation setArgument:&codec atIndex:2]; + double frameRate = self.fps; + [videoEncodingInitInvocation setArgument:&frameRate atIndex:3]; + [videoEncodingInitInvocation invokeWithTarget:videoEncodingAllocated]; + id __unsafe_unretained result; + [videoEncodingInitInvocation getReturnValue:&result]; + return result; +} + +- (id)toNativeRequestWithError:(NSError **)error +{ + Class screenRecordingRequestClass = NSClassFromString(@"XCTScreenRecordingRequest"); + if (nil == screenRecordingRequestClass) { + [[[FBErrorBuilder builder] + withDescription:@"Cannot find XCTScreenRecordingRequest class"] + buildError:error]; + return nil; + } + + id screenRecordingRequestAllocated = [screenRecordingRequestClass alloc]; + SEL screenRecordingRequestConstructorSelector = NSSelectorFromString(@"initWithScreenID:rect:preferredEncoding:"); + if (![screenRecordingRequestAllocated respondsToSelector:screenRecordingRequestConstructorSelector]) { + [[[FBErrorBuilder builder] + withDescription:@"'initWithScreenID:rect:preferredEncoding:' contructor is not found on XCTScreenRecordingRequest class"] + buildError:error]; + return nil; + } + id videoEncoding = [self createVideoEncodingWithError:error]; + if (nil == videoEncoding) { + return nil; + } + + NSMethodSignature *screenRecordingRequestContructorSignature = [screenRecordingRequestAllocated methodSignatureForSelector:screenRecordingRequestConstructorSelector]; + NSInvocation *screenRecordingRequestInitInvocation = [NSInvocation invocationWithMethodSignature:screenRecordingRequestContructorSignature]; + [screenRecordingRequestInitInvocation setSelector:screenRecordingRequestConstructorSelector]; + long long mainScreenId = XCUIScreen.mainScreen.displayID; + [screenRecordingRequestInitInvocation setArgument:&mainScreenId atIndex:2]; + CGRect fullScreenRect = CGRectNull; + [screenRecordingRequestInitInvocation setArgument:&fullScreenRect atIndex:3]; + [screenRecordingRequestInitInvocation setArgument:&videoEncoding atIndex:4]; + [screenRecordingRequestInitInvocation invokeWithTarget:screenRecordingRequestAllocated]; + id __unsafe_unretained result; + [screenRecordingRequestInitInvocation getReturnValue:&result]; + return result; +} + +@end diff --git a/WebDriverAgentLib/Routing/FBSession.h b/WebDriverAgentLib/Routing/FBSession.h index 9b73d4d16..b617ae096 100644 --- a/WebDriverAgentLib/Routing/FBSession.h +++ b/WebDriverAgentLib/Routing/FBSession.h @@ -14,6 +14,9 @@ NS_ASSUME_NONNULL_BEGIN +/** Bundle identifier of Mobile Safari browser */ +extern NSString* const FB_SAFARI_BUNDLE_ID; + /** Class that represents testing session */ diff --git a/WebDriverAgentLib/Routing/FBSession.m b/WebDriverAgentLib/Routing/FBSession.m index 888cff8b2..a4460b4a8 100644 --- a/WebDriverAgentLib/Routing/FBSession.m +++ b/WebDriverAgentLib/Routing/FBSession.m @@ -18,7 +18,11 @@ #import "FBElementCache.h" #import "FBExceptions.h" #import "FBMacros.h" +#import "FBScreenRecordingContainer.h" +#import "FBScreenRecordingPromise.h" +#import "FBScreenRecordingRequest.h" #import "FBXCodeCompatibility.h" +#import "FBXCTestDaemonsProxy.h" #import "XCUIApplication+FBQuiescence.h" #import "XCUIElement.h" @@ -29,6 +33,8 @@ */ NSString *const FBDefaultApplicationAuto = @"auto"; +NSString *const FB_SAFARI_BUNDLE_ID = @"com.apple.mobilesafari"; + @interface FBSession () @property (nullable, nonatomic) XCUIApplication *testedApplication; @property (nonatomic) BOOL isTestedApplicationExpectedToRun; @@ -137,6 +143,15 @@ - (void)kill self.alertsMonitor = nil; } + FBScreenRecordingPromise *activeScreenRecording = FBScreenRecordingContainer.sharedInstance.screenRecordingPromise; + if (nil != activeScreenRecording) { + NSError *error; + if (![FBXCTestDaemonsProxy stopScreenRecordingWithUUID:activeScreenRecording.identifier error:&error]) { + [FBLogger logFmt:@"%@", error]; + } + [FBScreenRecordingContainer.sharedInstance reset]; + } + if (nil != self.testedApplication && FBConfiguration.shouldTerminateApp && self.testedApplication.running @@ -153,9 +168,25 @@ - (void)kill - (XCUIApplication *)activeApplication { + BOOL isAuto = [self.defaultActiveApplication isEqualToString:FBDefaultApplicationAuto]; + NSString *defaultBundleId = isAuto ? nil : self.defaultActiveApplication; + + if (nil != defaultBundleId && [self applicationStateWithBundleId:defaultBundleId] >= XCUIApplicationStateRunningForeground) { + return [self makeApplicationWithBundleId:defaultBundleId]; + } + if (nil != self.testedApplication) { XCUIApplicationState testedAppState = self.testedApplication.state; if (testedAppState >= XCUIApplicationStateRunningForeground) { + // We look for `SBTransientOverlayWindow` elements for half modals. See https://github.com/appium/WebDriverAgent/pull/946 + NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:@"%K == %@ OR %K == %@", + @"elementType", @(XCUIElementTypeAlert), + @"identifier", @"SBTransientOverlayWindow"]; + if ([FBConfiguration shouldRespectSystemAlerts] + && [[XCUIApplication.fb_systemApplication descendantsMatchingType:XCUIElementTypeAny] + matchingPredicate:searchPredicate].count > 0) { + return XCUIApplication.fb_systemApplication; + } return (XCUIApplication *)self.testedApplication; } if (self.isTestedApplicationExpectedToRun && testedAppState <= XCUIApplicationStateNotRunning) { @@ -164,9 +195,6 @@ - (XCUIApplication *)activeApplication } } - NSString *defaultBundleId = [self.defaultActiveApplication isEqualToString:FBDefaultApplicationAuto] - ? nil - : self.defaultActiveApplication; return [XCUIApplication fb_activeApplicationWithDefaultBundleId:defaultBundleId]; } diff --git a/WebDriverAgentLib/Routing/FBWebServer.m b/WebDriverAgentLib/Routing/FBWebServer.m index 7e991e0e2..ff9e4c424 100644 --- a/WebDriverAgentLib/Routing/FBWebServer.m +++ b/WebDriverAgentLib/Routing/FBWebServer.m @@ -217,7 +217,7 @@ - (void)handleException:(NSException *)exception forResponse:(RouteResponse *)re - (void)registerServerKeyRouteHandlers { [self.server get:@"/health" withBlock:^(RouteRequest *request, RouteResponse *response) { - [response respondWithString:@"I-AM-ALIVE"]; + [response respondWithString:@"Health Check

I-AM-ALIVE

"]; }]; NSString *calibrationPage = @"" diff --git a/WebDriverAgentLib/Utilities/FBAppiumActionsSynthesizer.m b/WebDriverAgentLib/Utilities/FBAppiumActionsSynthesizer.m deleted file mode 100644 index 235d97e63..000000000 --- a/WebDriverAgentLib/Utilities/FBAppiumActionsSynthesizer.m +++ /dev/null @@ -1,554 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import "FBAppiumActionsSynthesizer.h" - -#import "FBErrorBuilder.h" -#import "FBElementCache.h" -#import "FBLogger.h" -#import "FBMacros.h" -#import "FBMathUtils.h" -#import "FBXCTestDaemonsProxy.h" -#import "FBProtocolHelpers.h" -#import "XCUIElement+FBUtilities.h" -#import "XCUIElement.h" -#import "XCSynthesizedEventRecord.h" -#import "XCPointerEventPath.h" -#import "XCPointerEvent.h" - -static NSString *const FB_ACTION_KEY = @"action"; -static NSString *const FB_ACTION_TAP = @"tap"; -static NSString *const FB_ACTION_PRESS = @"press"; -static NSString *const FB_ACTION_LONG_PRESS = @"longPress"; -static NSString *const FB_ACTION_MOVE_TO = @"moveTo"; -static NSString *const FB_ACTION_RELEASE = @"release"; -static NSString *const FB_ACTION_CANCEL = @"cancel"; -static NSString *const FB_ACTION_WAIT = @"wait"; - -static NSString *const FB_OPTION_DURATION = @"duration"; -static NSString *const FB_OPTION_COUNT = @"count"; -static NSString *const FB_OPTION_MS = @"ms"; -static NSString *const FB_OPTION_PRESSURE = @"pressure"; - -static NSString *const FB_OPTIONS_KEY = @"options"; - -#if !TARGET_OS_TV -// Some useful constants might be found at -// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewConfiguration.java -static const double FB_TAP_DURATION_MS = 100.0; -static const double FB_INTERTAP_MIN_DURATION_MS = 40.0; -static const double FB_LONG_TAP_DURATION_MS = 600.0; - -@interface FBAppiumGestureItem : FBBaseGestureItem - -@end - -@interface FBTapItem : FBAppiumGestureItem - -@end - -@interface FBPressItem : FBAppiumGestureItem -@property (nonatomic, nullable, readonly) NSNumber *pressure; -@end - -@interface FBLongPressItem : FBAppiumGestureItem - -@end - -@interface FBWaitItem : FBAppiumGestureItem - -@end - -@interface FBMoveToItem : FBAppiumGestureItem - -@property (nonatomic, nonnull) NSValue *recentPosition; - -@end - -@interface FBReleaseItem : FBAppiumGestureItem - -@end - - -@implementation FBAppiumGestureItem - -- (nullable instancetype)initWithActionItem:(NSDictionary *)item - application:(XCUIApplication *)application - atPosition:(nullable XCUICoordinate *)atPosition - offset:(double)offset - error:(NSError **)error -{ - self = [super init]; - if (self) { - self.actionItem = item; - self.application = application; - self.offset = offset; - id options = [item objectForKey:FB_OPTIONS_KEY]; - if (nil != atPosition) { - self.atPosition = (id) atPosition; - } else { - XCUICoordinate *result = [self coordinatesWithOptions:options error:error]; - if (nil == result) { - return nil; - } - self.atPosition = result; - } - self.duration = [self durationWithOptions:options]; - if (self.duration < 0) { - NSString *description = [NSString stringWithFormat:@"%@ value cannot be negative for '%@' action", FB_OPTION_DURATION, self.class.actionName]; - if (error) { - *error = [[FBErrorBuilder.builder withDescription:description] build]; - } - return nil; - } - } - return self; -} - -+ (BOOL)hasAbsolutePositioning -{ - @throw [[FBErrorBuilder.builder withDescription:@"Override this method in subclasses"] build]; - return NO; -} - -- (double)durationWithOptions:(nullable NSDictionary *)options -{ - return (options && [options objectForKey:FB_OPTION_DURATION]) ? - ((NSNumber *)[options objectForKey:FB_OPTION_DURATION]).doubleValue : - 0.0; -} - -- (nullable XCUICoordinate *)coordinatesWithOptions:(nullable NSDictionary *)options - error:(NSError **)error -{ - if (![options isKindOfClass:NSDictionary.class]) { - NSString *description = [NSString stringWithFormat:@"'%@' key is mandatory for '%@' action", FB_OPTIONS_KEY, self.class.actionName]; - if (error) { - *error = [[FBErrorBuilder.builder withDescription:description] build]; - } - return nil; - } - XCUIElement *element = FBExtractElement((id) options); - NSNumber *x = [options objectForKey:@"x"]; - NSNumber *y = [options objectForKey:@"y"]; - if ((nil != x && nil == y) || (nil != y && nil == x) || (nil == x && nil == y && nil == element)) { - NSString *description = [NSString stringWithFormat:@"Either element or 'x' and 'y' options should be set for '%@' action", self.class.actionName]; - if (error) { - *error = [[FBErrorBuilder.builder withDescription:description] build]; - } - return nil; - } - NSValue *offset = (nil != x && nil != y) ? [NSValue valueWithCGPoint:CGPointMake(x.floatValue, y.floatValue)] : nil; - return [self hitpointWithElement:element positionOffset:offset error:error]; -} - -@end - -@implementation FBTapItem - -+ (NSString *)actionName -{ - return FB_ACTION_TAP; -} - -+ (BOOL)hasAbsolutePositioning -{ - return YES; -} - -- (NSArray *)addToEventPath:(XCPointerEventPath *)eventPath - allItems:(NSArray *)allItems - currentItemIndex:(NSUInteger)currentItemIndex - error:(NSError **)error -{ - NSTimeInterval currentOffset = FBMillisToSeconds(self.offset); - NSMutableArray *result = [NSMutableArray array]; - XCPointerEventPath *currentPath = [[XCPointerEventPath alloc] - initForTouchAtPoint:self.atPosition.screenPoint - offset:currentOffset]; - [result addObject:currentPath]; - currentOffset += FBMillisToSeconds(FB_TAP_DURATION_MS); - [currentPath liftUpAtOffset:currentOffset]; - - id options = [self.actionItem objectForKey:FB_OPTIONS_KEY]; - if ([options isKindOfClass:NSDictionary.class]) { - NSNumber *tapCount = [options objectForKey:FB_OPTION_COUNT] ?: @1; - for (NSInteger times = 1; times < tapCount.integerValue; ++times) { - currentOffset += FBMillisToSeconds(FB_INTERTAP_MIN_DURATION_MS); - XCPointerEventPath *nextPath = [[XCPointerEventPath alloc] initForTouchAtPoint:self.atPosition.screenPoint - offset:currentOffset]; - [result addObject:nextPath]; - currentOffset += FBMillisToSeconds(FB_TAP_DURATION_MS); - [nextPath liftUpAtOffset:currentOffset]; - } - } - return result.copy; -} - -- (double)durationWithOptions:(nullable NSDictionary *)options -{ - NSNumber *tapCount = @1; - if ([options isKindOfClass:NSDictionary.class]) { - tapCount = [options objectForKey:FB_OPTION_COUNT] ?: tapCount; - } - return FB_TAP_DURATION_MS * tapCount.integerValue + FB_INTERTAP_MIN_DURATION_MS * (tapCount.integerValue - 1); -} - -@end - -@implementation FBPressItem - -- (nullable instancetype)initWithActionItem:(NSDictionary *)item - application:(XCUIApplication *)application - atPosition:(nullable XCUICoordinate *)atPosition - offset:(double)offset - error:(NSError **)error -{ - self = [super initWithActionItem:item - application:application - atPosition:atPosition - offset:offset - error:error]; - if (self) { - _pressure = nil; - id options = [item objectForKey:FB_OPTIONS_KEY]; - if ([options isKindOfClass:NSDictionary.class]) { - _pressure = [options objectForKey:FB_OPTION_PRESSURE]; - } - } - return self; -} - -+ (NSString *)actionName -{ - return FB_ACTION_PRESS; -} - -+ (BOOL)hasAbsolutePositioning -{ - return YES; -} - -- (NSArray *)addToEventPath:(XCPointerEventPath *)eventPath - allItems:(NSArray *)allItems - currentItemIndex:(NSUInteger)currentItemIndex - error:(NSError **)error -{ - XCPointerEventPath *result = [[XCPointerEventPath alloc] - initForTouchAtPoint:self.atPosition.screenPoint - offset:FBMillisToSeconds(self.offset)]; - if (nil != self.pressure && nil != result.pointerEvents.lastObject) { - XCPointerEvent *pointerEvent = (XCPointerEvent *)result.pointerEvents.lastObject; - pointerEvent.pressure = self.pressure.doubleValue; - } - return @[result]; -} - -- (double)durationWithOptions:(nullable NSDictionary *)options -{ - return 0.0; -} - -@end - -@implementation FBLongPressItem - -+ (NSString *)actionName -{ - return FB_ACTION_LONG_PRESS; -} - -+ (BOOL)hasAbsolutePositioning -{ - return YES; -} - -- (NSArray *)addToEventPath:(XCPointerEventPath *)eventPath - allItems:(NSArray *)allItems - currentItemIndex:(NSUInteger)currentItemIndex - error:(NSError **)error -{ - return @[[[XCPointerEventPath alloc] initForTouchAtPoint:self.atPosition.screenPoint - offset:FBMillisToSeconds(self.offset)]]; -} - -- (double)durationWithOptions:(nullable NSDictionary *)options -{ - return (options && [options objectForKey:FB_OPTION_DURATION]) ? - ((NSNumber *)[options objectForKey:FB_OPTION_DURATION]).doubleValue : - FB_LONG_TAP_DURATION_MS; -} - -@end - -@implementation FBWaitItem - -+ (NSString *)actionName -{ - return FB_ACTION_WAIT; -} - -+ (BOOL)hasAbsolutePositioning -{ - return NO; -} - -- (NSArray *)addToEventPath:(XCPointerEventPath *)eventPath - allItems:(NSArray *)allItems - currentItemIndex:(NSUInteger)currentItemIndex - error:(NSError **)error -{ - if (nil != eventPath) { - if (0 == currentItemIndex) { - return @[]; - } - FBBaseGestureItem *preceedingItem = [allItems objectAtIndex:currentItemIndex - 1]; - if (![preceedingItem isKindOfClass:FBReleaseItem.class] && currentItemIndex < allItems.count - 1) { - return @[]; - } - } - NSTimeInterval currentOffset = FBMillisToSeconds(self.offset + self.duration); - XCPointerEventPath *result = [[XCPointerEventPath alloc] initForTouchAtPoint:self.atPosition.screenPoint - offset:currentOffset]; - if (currentItemIndex == allItems.count - 1) { - [result liftUpAtOffset:currentOffset]; - } - return @[result]; -} - -- (double)durationWithOptions:(nullable NSDictionary *)options -{ - return (options && [options objectForKey:FB_OPTION_MS]) ? - ((NSNumber *)[options objectForKey:FB_OPTION_MS]).doubleValue : - 0.0; -} - -@end - -@implementation FBMoveToItem - -+ (NSString *)actionName -{ - return FB_ACTION_MOVE_TO; -} - -+ (BOOL)hasAbsolutePositioning -{ - return YES; -} - -- (NSArray *)addToEventPath:(XCPointerEventPath *)eventPath - allItems:(NSArray *)allItems - currentItemIndex:(NSUInteger)currentItemIndex - error:(NSError **)error -{ - if (nil == eventPath) { - NSString *description = [NSString stringWithFormat:@"Move To must not be the first action in '%@'", self.actionItem]; - if (error) { - *error = [[FBErrorBuilder.builder withDescription:description] build]; - } - return nil; - } - - [eventPath moveToPoint:self.atPosition.screenPoint - atOffset:FBMillisToSeconds(self.offset)]; - return @[]; -} - -@end - -@implementation FBReleaseItem - -+ (NSString *)actionName -{ - return FB_ACTION_RELEASE; -} - -+ (BOOL)hasAbsolutePositioning -{ - return NO; -} - -- (NSArray *)addToEventPath:(XCPointerEventPath *)eventPath - allItems:(NSArray *)allItems - currentItemIndex:(NSUInteger)currentItemIndex - error:(NSError **)error -{ - if (nil == eventPath) { - NSString *description = [NSString stringWithFormat:@"Pointer Up must not be the first action in '%@'", self.actionItem]; - if (error) { - *error = [[FBErrorBuilder.builder withDescription:description] build]; - } - return nil; - } - - [eventPath liftUpAtOffset:FBMillisToSeconds(self.offset)]; - return @[]; -} - -- (double)durationWithOptions:(nullable NSDictionary *)options -{ - return 0.0; -} - -@end - - -@interface FBAppiumGestureItemsChain : FBBaseActionItemsChain - -@end - -@implementation FBAppiumGestureItemsChain - -- (void)addItem:(FBBaseActionItem *)item -{ - self.durationOffset += ((FBAppiumGestureItem *) item).duration; - [self.items addObject:item]; -} - -- (void)reset -{ - [self.items removeAllObjects]; - self.durationOffset = 0.0; -} - -@end - -@implementation FBAppiumActionsSynthesizer - -- (NSArray *> *)preprocessAction:(NSArray *> *)touchActionItems -{ - NSMutableArray *> *result = [NSMutableArray array]; - BOOL shouldSkipNextItem = NO; - for (NSDictionary *touchItem in [touchActionItems reverseObjectEnumerator]) { - id actionItemName = [touchItem objectForKey:FB_ACTION_KEY]; - if ([actionItemName isKindOfClass:NSString.class] && [actionItemName isEqualToString:FB_ACTION_CANCEL]) { - shouldSkipNextItem = YES; - continue; - } - if (shouldSkipNextItem) { - shouldSkipNextItem = NO; - continue; - } - - id options = [touchItem objectForKey:FB_OPTIONS_KEY]; - if (![options isKindOfClass:NSDictionary.class]) { - [result addObject:touchItem]; - continue; - } - id origin = FBExtractElement(options); - XCUIElement *element; - if ([origin isKindOfClass:XCUIElement.class]) { - element = origin; - } else if ([origin isKindOfClass:NSString.class]) { - element = [self.elementCache elementForUUID:(NSString *)origin]; - } else { - [result addObject:touchItem]; - continue; - } - NSMutableDictionary *elementDict = FBCleanupElements(options).mutableCopy; - [elementDict addEntriesFromDictionary:FBToElementDict(element)]; - NSMutableDictionary *processedItem = touchItem.mutableCopy; - processedItem[FB_OPTIONS_KEY] = elementDict.copy; - [result addObject:processedItem.copy]; - } - return [[result reverseObjectEnumerator] allObjects]; -} - -- (nullable NSArray *)eventPathsWithAction:(NSArray *> *)action - error:(NSError **)error -{ - static NSDictionary *gestureItemsMapping; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSMutableDictionary *itemsMapping = [NSMutableDictionary dictionary]; - for (Class cls in @[FBTapItem.class, - FBPressItem.class, - FBLongPressItem.class, - FBMoveToItem.class, - FBWaitItem.class, - FBReleaseItem.class]) { - [itemsMapping setObject:cls forKey:[cls actionName]]; - } - gestureItemsMapping = itemsMapping.copy; - }); - - FBAppiumGestureItemsChain *chain = [[FBAppiumGestureItemsChain alloc] init]; - BOOL isAbsoluteTouchPositionSet = NO; - for (NSDictionary *actionItem in action) { - id actionItemName = [actionItem objectForKey:FB_ACTION_KEY]; - if (![actionItemName isKindOfClass:NSString.class]) { - NSString *description = [NSString stringWithFormat:@"'%@' property is mandatory for gesture chain item %@", FB_ACTION_KEY, actionItem]; - if (error) { - *error = [[FBErrorBuilder.builder withDescription:description] build]; - } - return nil; - } - - Class gestureItemClass = [gestureItemsMapping objectForKey:actionItemName]; - if (nil == gestureItemClass) { - NSString *description = [NSString stringWithFormat:@"%@ value '%@' is unknown", FB_ACTION_KEY, actionItemName]; - if (error) { - *error = [[FBErrorBuilder.builder withDescription:description] build]; - } - return nil; - } - - FBAppiumGestureItem *gestureItem = nil; - if ([gestureItemClass hasAbsolutePositioning]) { - gestureItem = [[gestureItemClass alloc] initWithActionItem:actionItem application:self.application atPosition:nil offset:chain.durationOffset error:error]; - isAbsoluteTouchPositionSet = YES; - } else { - if (!isAbsoluteTouchPositionSet) { - if (error) { - NSString *description = [NSString stringWithFormat:@"'%@' %@ should be preceded by an item with absolute positioning", actionItemName, FB_ACTION_KEY]; - *error = [[FBErrorBuilder.builder withDescription:description] build]; - } - return nil; - } - FBAppiumGestureItem *lastItem = [chain.items lastObject]; - gestureItem = [[gestureItemClass alloc] initWithActionItem:actionItem - application:self.application - atPosition:lastItem.atPosition - offset:chain.durationOffset - error:error]; - } - if (nil == gestureItem) { - return nil; - } - - [chain addItem:gestureItem]; - } - - return [chain asEventPathsWithError:error]; -} - -- (nullable XCSynthesizedEventRecord *)synthesizeWithError:(NSError **)error -{ - XCSynthesizedEventRecord *eventRecord; - BOOL isMultiTouch = [self.actions.firstObject isKindOfClass:NSArray.class]; - eventRecord = [[XCSynthesizedEventRecord alloc] - initWithName:(isMultiTouch ? @"Multi-Finger Touch Action" : @"Single-Finger Touch Action") - interfaceOrientation:self.application.interfaceOrientation]; - for (NSArray *> *action in (isMultiTouch ? self.actions : @[self.actions])) { - NSArray *> *preprocessedAction = [self preprocessAction:action]; - NSArray *eventPaths = [self eventPathsWithAction:preprocessedAction error:error]; - if (nil == eventPaths) { - return nil; - } - for (XCPointerEventPath *eventPath in eventPaths) { - [eventRecord addPointerEventPath:eventPath]; - } - } - return eventRecord; -} - -@end - -#endif diff --git a/WebDriverAgentLib/Utilities/FBBaseActionsParser.m b/WebDriverAgentLib/Utilities/FBBaseActionsParser.m deleted file mode 100644 index 872dc268e..000000000 --- a/WebDriverAgentLib/Utilities/FBBaseActionsParser.m +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import "FBBaseActionsSynthesizer.h" - -#import "FBErrorBuilder.h" - -@implementation FBBaseActionsSynthesizer - -- (instancetype)initWithActions:(NSArray *)actions forApplication:(XCUIApplication *)application -{ - self = [super init]; - if (self) { - _actions = actions; - _application = application; - } - return self; -} - -- (nullable XCSynthesizedEventRecord *)synthesizeWithError:(NSError **)error -{ - @throw [[FBErrorBuilder.builder withDescription:@"Override this method in subclasses"] build]; - return nil; -} - -@end diff --git a/WebDriverAgentLib/Utilities/FBCapabilities.h b/WebDriverAgentLib/Utilities/FBCapabilities.h index 1045a9855..20dad743e 100644 --- a/WebDriverAgentLib/Utilities/FBCapabilities.h +++ b/WebDriverAgentLib/Utilities/FBCapabilities.h @@ -11,7 +11,7 @@ /** Whether to use alternative elements visivility detection method */ extern NSString* const FB_CAP_USE_TEST_MANAGER_FOR_VISIBLITY_DETECTION; -/** Set the maximum amount of charatcers that could be typed within a minute (60 by default) */ +/** Set the maximum amount of characters that could be typed within a minute (60 by default) */ extern NSString* const FB_CAP_MAX_TYPING_FREQUENCY; /** this setting was needed for some legacy stuff */ extern NSString* const FB_CAP_USE_SINGLETON_TEST_MANAGER; @@ -42,3 +42,5 @@ extern NSString* const FB_CAP_ENVIRNOMENT; extern NSString* const FB_CAP_USE_NATIVE_CACHING_STRATEGY; /** Whether to enforce software keyboard presence on simulator */ extern NSString* const FB_CAP_FORCE_SIMULATOR_SOFTWARE_KEYBOARD_PRESENCE; +/** Sets the application state change timeout for the initial app startup */ +extern NSString* const FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC; diff --git a/WebDriverAgentLib/Utilities/FBCapabilities.m b/WebDriverAgentLib/Utilities/FBCapabilities.m index 1798c2195..b6ccc9ce4 100644 --- a/WebDriverAgentLib/Utilities/FBCapabilities.m +++ b/WebDriverAgentLib/Utilities/FBCapabilities.m @@ -23,3 +23,4 @@ NSString* const FB_CAP_ENVIRNOMENT = @"environment"; NSString* const FB_CAP_USE_NATIVE_CACHING_STRATEGY = @"useNativeCachingStrategy"; NSString* const FB_CAP_FORCE_SIMULATOR_SOFTWARE_KEYBOARD_PRESENCE = @"forceSimulatorSoftwareKeyboardPresence"; +NSString* const FB_CAP_APP_LAUNCH_STATE_TIMEOUT_SEC = @"appLaunchStateTimeoutSec"; diff --git a/WebDriverAgentLib/Utilities/FBConfiguration.h b/WebDriverAgentLib/Utilities/FBConfiguration.h index c07c31a1e..75275ccf6 100644 --- a/WebDriverAgentLib/Utilities/FBConfiguration.h +++ b/WebDriverAgentLib/Utilities/FBConfiguration.h @@ -9,10 +9,6 @@ #import -#import "AXSettings.h" -#import "UIKeyboardImpl.h" -#import "TIPreferencesController.h" - NS_ASSUME_NONNULL_BEGIN extern NSString *const FBSnapshotMaxDepthKey; @@ -66,6 +62,10 @@ extern NSString *const FBSnapshotMaxDepthKey; + (void)setShouldUseSingletonTestManager:(BOOL)value; + (BOOL)shouldUseSingletonTestManager; +/* Enforces WDA to verify the presense of system alerts while checking for an active app */ ++ (void)setShouldRespectSystemAlerts:(BOOL)value; ++ (BOOL)shouldRespectSystemAlerts; + /** * Extract switch value from arguments * diff --git a/WebDriverAgentLib/Utilities/FBConfiguration.m b/WebDriverAgentLib/Utilities/FBConfiguration.m index 3bb740a0b..e91717b2d 100644 --- a/WebDriverAgentLib/Utilities/FBConfiguration.m +++ b/WebDriverAgentLib/Utilities/FBConfiguration.m @@ -9,6 +9,10 @@ #import "FBConfiguration.h" +#import "AXSettings.h" +#import "UIKeyboardImpl.h" +#import "TIPreferencesController.h" + #include #import @@ -31,6 +35,7 @@ static BOOL FBShouldUseTestManagerForVisibilityDetection = NO; static BOOL FBShouldUseSingletonTestManager = YES; +static BOOL FBShouldRespectSystemAlerts = NO; static NSUInteger FBMjpegScalingFactor = 100; static BOOL FBMjpegShouldFixOrientation = NO; @@ -373,6 +378,16 @@ + (int)snapshotMaxDepth return [FBGetCustomParameterForElementSnapshot(FBSnapshotMaxDepthKey) intValue]; } ++ (void)setShouldRespectSystemAlerts:(BOOL)value +{ + FBShouldRespectSystemAlerts = value; +} + ++ (BOOL)shouldRespectSystemAlerts +{ + return FBShouldRespectSystemAlerts; +} + + (void)setUseFirstMatch:(BOOL)enabled { FBShouldUseFirstMatch = enabled; diff --git a/WebDriverAgentLib/Utilities/FBScreen.h b/WebDriverAgentLib/Utilities/FBScreen.h index 61dc4aeb4..9c4cca87e 100644 --- a/WebDriverAgentLib/Utilities/FBScreen.h +++ b/WebDriverAgentLib/Utilities/FBScreen.h @@ -18,11 +18,6 @@ NS_ASSUME_NONNULL_BEGIN */ + (double)scale; -/** - The absolute size of application's status bar or CGSizeZero if it's hidden or does not exist - */ -+ (CGSize)statusBarSizeForApplication:(XCUIApplication *)application; - @end NS_ASSUME_NONNULL_END diff --git a/WebDriverAgentLib/Utilities/FBScreen.m b/WebDriverAgentLib/Utilities/FBScreen.m index 8ebe080d5..17b7d8b19 100644 --- a/WebDriverAgentLib/Utilities/FBScreen.m +++ b/WebDriverAgentLib/Utilities/FBScreen.m @@ -19,17 +19,4 @@ + (double)scale return [XCUIScreen.mainScreen scale]; } -+ (CGSize)statusBarSizeForApplication:(XCUIApplication *)application -{ - XCUIApplication *app = XCUIApplication.fb_systemApplication; - // Since iOS 13 the status bar is no longer part of the application, it’s part of the SpringBoard - XCUIElement *mainStatusBar = app.statusBars.allElementsBoundByIndex.firstObject; - if (nil == mainStatusBar) { - return CGSizeZero; - } - CGSize result = mainStatusBar.frame.size; - // Workaround for https://github.com/appium/appium/issues/15961 - return CGSizeMake(MAX(result.width, result.height), MIN(result.width, result.height)); -} - @end diff --git a/WebDriverAgentLib/Utilities/FBSettings.h b/WebDriverAgentLib/Utilities/FBSettings.h index dd82d740c..5f4450110 100644 --- a/WebDriverAgentLib/Utilities/FBSettings.h +++ b/WebDriverAgentLib/Utilities/FBSettings.h @@ -38,6 +38,8 @@ extern NSString* const FB_SETTING_DISMISS_ALERT_BUTTON_SELECTOR; extern NSString* const FB_SETTING_SCREENSHOT_ORIENTATION; extern NSString* const FB_SETTING_WAIT_FOR_IDLE_TIMEOUT; extern NSString* const FB_SETTING_ANIMATION_COOL_OFF_TIMEOUT; +extern NSString* const FB_SETTING_MAX_TYPING_FREQUENCY; +extern NSString* const FB_SETTING_RESPECT_SYSTEM_ALERTS; NS_ASSUME_NONNULL_END diff --git a/WebDriverAgentLib/Utilities/FBSettings.m b/WebDriverAgentLib/Utilities/FBSettings.m index 6b12a4cd4..a45afeb90 100644 --- a/WebDriverAgentLib/Utilities/FBSettings.m +++ b/WebDriverAgentLib/Utilities/FBSettings.m @@ -33,3 +33,5 @@ NSString* const FB_SETTING_SCREENSHOT_ORIENTATION = @"screenshotOrientation"; NSString* const FB_SETTING_WAIT_FOR_IDLE_TIMEOUT = @"waitForIdleTimeout"; NSString* const FB_SETTING_ANIMATION_COOL_OFF_TIMEOUT = @"animationCoolOffTimeout"; +NSString* const FB_SETTING_MAX_TYPING_FREQUENCY = @"maxTypingFrequency"; +NSString* const FB_SETTING_RESPECT_SYSTEM_ALERTS = @"respectSystemAlerts"; diff --git a/WebDriverAgentLib/Utilities/FBW3CActionsHelpers.h b/WebDriverAgentLib/Utilities/FBW3CActionsHelpers.h index afd8249f8..3c194d4cd 100644 --- a/WebDriverAgentLib/Utilities/FBW3CActionsHelpers.h +++ b/WebDriverAgentLib/Utilities/FBW3CActionsHelpers.h @@ -30,19 +30,14 @@ NSString *_Nullable FBRequireValue(NSDictionary *actionItem, NSE */ NSNumber *_Nullable FBOptDuration(NSDictionary *actionItem, NSNumber *_Nullable defaultValue, NSError **error); -/** - * Checks whether the given key action value is a W3C meta modifier - * @param value key action value - * @returns YES if the value is a meta modifier - */ -BOOL FBIsMetaModifier(NSString *value); - /** * Maps W3C meta modifier to XCUITest compatible-one + * See https://w3c.github.io/webdriver/#keyboard-actions * * @param value key action value - * @returns the mapped modifier value or 0 in case of failure + * @returns the mapped modifier value or the same input character + * if no mapped value could be found for it. */ -NSUInteger FBToMetaModifier(NSString *value); +NSString * FBMapIfSpecialCharacter(NSString *value); NS_ASSUME_NONNULL_END diff --git a/WebDriverAgentLib/Utilities/FBW3CActionsHelpers.m b/WebDriverAgentLib/Utilities/FBW3CActionsHelpers.m index bcbf5a783..ae0270dc3 100644 --- a/WebDriverAgentLib/Utilities/FBW3CActionsHelpers.m +++ b/WebDriverAgentLib/Utilities/FBW3CActionsHelpers.m @@ -53,32 +53,68 @@ return durationObj; } -BOOL FBIsMetaModifier(NSString *value) +NSString *FBMapIfSpecialCharacter(NSString *value) { - unichar charCode = [value characterAtIndex:0]; - return charCode >= 0xE000 && charCode <= 0xF8FF; -} - -NSUInteger FBToMetaModifier(NSString *value) -{ - if (!FBIsMetaModifier(value)) { - return 0; + if (0 == [value length]) { + return value; } unichar charCode = [value characterAtIndex:0]; switch (charCode) { case 0xE000: - return XCUIKeyModifierNone; - case 0xE03D: - return XCUIKeyModifierCommand; - case 0xE009: - return XCUIKeyModifierControl; - case 0xE00A: - return XCUIKeyModifierOption; - case 0xE008: - return XCUIKeyModifierShift; + return @""; + case 0xE003: + return [NSString stringWithFormat:@"%C", 0x0008]; + case 0xE004: + return [NSString stringWithFormat:@"%C", 0x0009]; + case 0xE006: + return [NSString stringWithFormat:@"%C", 0x000D]; + case 0xE007: + return [NSString stringWithFormat:@"%C", 0x000A]; + case 0xE00C: + return [NSString stringWithFormat:@"%C", 0x001B]; + case 0xE00D: + case 0xE05D: + return @" "; + case 0xE017: + return [NSString stringWithFormat:@"%C", 0x007F]; + case 0xE018: + return @";"; + case 0xE019: + return @"="; + case 0xE01A: + return @"0"; + case 0xE01B: + return @"1"; + case 0xE01C: + return @"2"; + case 0xE01D: + return @"3"; + case 0xE01E: + return @"4"; + case 0xE01F: + return @"5"; + case 0xE020: + return @"6"; + case 0xE021: + return @"7"; + case 0xE022: + return @"8"; + case 0xE023: + return @"9"; + case 0xE024: + return @"*"; + case 0xE025: + return @"+"; + case 0xE026: + return @","; + case 0xE027: + return @"-"; + case 0xE028: + return @"."; + case 0xE029: + return @"/"; default: - [FBLogger logFmt:@"Skipping the unsupported meta modifier with code %@", @(charCode)]; - return 0; + return charCode >= 0xE000 && charCode <= 0xE05D ? @"" : value; } } diff --git a/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.m b/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.m index dc5060568..c488ca4ac 100644 --- a/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.m +++ b/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.m @@ -410,17 +410,12 @@ - (BOOL)hasDownPairInItems:(NSArray *)allItems currentItemIndex:(NSUInteger)currentItemIndex { NSInteger balance = 1; - BOOL isSelfMetaModifier = FBIsMetaModifier(self.value); for (NSInteger index = currentItemIndex - 1; index >= 0; index--) { FBW3CKeyItem *item = [allItems objectAtIndex:index]; BOOL isKeyDown = [item isKindOfClass:FBKeyDownItem.class]; BOOL isKeyUp = !isKeyDown && [item isKindOfClass:FBKeyUpItem.class]; if (!isKeyUp && !isKeyDown) { - if (isSelfMetaModifier) { - continue; - } else { - break; - } + break; } NSString *value = [item performSelector:@selector(value)]; @@ -434,32 +429,6 @@ - (BOOL)hasDownPairInItems:(NSArray *)allItems return 0 == balance; } -- (NSUInteger)collectModifersWithItems:(NSArray *)allItems - currentItemIndex:(NSUInteger)currentItemIndex -{ - NSUInteger modifiers = 0; - for (NSUInteger index = 0; index < currentItemIndex; index++) { - FBW3CKeyItem *item = [allItems objectAtIndex:index]; - BOOL isKeyDown = [item isKindOfClass:FBKeyDownItem.class]; - BOOL isKeyUp = !isKeyDown && [item isKindOfClass:FBKeyUpItem.class]; - if (!isKeyUp && !isKeyDown) { - continue; - } - - NSString *value = [item performSelector:@selector(value)]; - NSUInteger modifier = FBToMetaModifier(value); - if (modifier > 0) { - if (isKeyDown) { - modifiers |= modifier; - } else if (item.offset < self.offset) { - // only cancel the modifier if it is not in the same group - modifiers &= ~modifier; - } - } - } - return modifiers; -} - - (NSString *)collectTextWithItems:(NSArray *)allItems currentItemIndex:(NSUInteger)currentItemIndex { @@ -473,12 +442,8 @@ - (NSString *)collectTextWithItems:(NSArray *)allItems } NSString *value = [item performSelector:@selector(value)]; - if (FBIsMetaModifier(value)) { - continue; - } - if (isKeyUp) { - [result addObject:value]; + [result addObject:FBMapIfSpecialCharacter(value)]; } } return [result.reverseObjectEnumerator.allObjects componentsJoinedByString:@""]; @@ -497,10 +462,6 @@ - (NSString *)collectTextWithItems:(NSArray *)allItems return nil; } - if (FBIsMetaModifier(self.value)) { - return @[]; - } - BOOL isLastKeyUpInGroup = currentItemIndex == allItems.count - 1 || [[allItems objectAtIndex:currentItemIndex + 1] isKindOfClass:FBKeyPauseItem.class]; if (!isLastKeyUpInGroup) { @@ -510,10 +471,6 @@ - (NSString *)collectTextWithItems:(NSArray *)allItems NSString *text = [self collectTextWithItems:allItems currentItemIndex:currentItemIndex]; NSTimeInterval offset = FBMillisToSeconds(self.offset); XCPointerEventPath *resultPath = [[XCPointerEventPath alloc] initForTextInput]; - // TODO: Figure out how meta modifiers could be applied - // TODO: The current approach throws zero division error on execution - // NSUInteger modifiers = [self collectModifersWithItems:allItems currentItemIndex:currentItemIndex]; - // [resultPath setModifiers:modifiers mergeWithCurrentModifierFlags:NO atOffset:0]; [resultPath typeText:text atOffset:offset typingSpeed:FBConfiguration.maxTypingFrequency @@ -555,17 +512,12 @@ - (BOOL)hasUpPairInItems:(NSArray *)allItems currentItemIndex:(NSUInteger)currentItemIndex { NSInteger balance = 1; - BOOL isSelfMetaModifier = FBIsMetaModifier(self.value); for (NSUInteger index = currentItemIndex + 1; index < allItems.count; index++) { FBW3CKeyItem *item = [allItems objectAtIndex:index]; BOOL isKeyDown = [item isKindOfClass:FBKeyDownItem.class]; BOOL isKeyUp = !isKeyDown && [item isKindOfClass:FBKeyUpItem.class]; if (!isKeyUp && !isKeyDown) { - if (isSelfMetaModifier) { - continue; - } else { - break; - } + break; } NSString *value = [item performSelector:@selector(value)]; @@ -819,7 +771,7 @@ @implementation FBW3CActionsSynthesizer NSArray *> *actionItems = [actionDescription objectForKey:FB_KEY_ACTIONS]; if (nil == actionItems || 0 == actionItems.count) { - NSString *description = [NSString stringWithFormat:@"It is mandatory to have at least one gesture item defined for each action. Action with id '%@' contains none", actionId]; + NSString *description = [NSString stringWithFormat:@"It is mandatory to have at least one gesture item defined for each action. Action with id '%@' contains none", actionId]; if (error) { *error = [[FBErrorBuilder.builder withDescription:description] build]; } @@ -900,7 +852,20 @@ - (nullable XCSynthesizedEventRecord *)synthesizeWithError:(NSError **)error *error = [[FBErrorBuilder.builder withDescription:description] build]; } return nil; + } + NSArray *> *actionItems = [action objectForKey:FB_KEY_ACTIONS]; + if (nil == actionItems) { + NSString *description = [NSString stringWithFormat:@"It is mandatory to have at least one item defined for each action. Action with id '%@' contains none", actionId]; + if (error) { + *error = [[FBErrorBuilder.builder withDescription:description] build]; + } + return nil; } + if (0 == actionItems.count) { + [FBLogger logFmt:@"Action items in the action id '%@' had an empty array. Skipping the action.", actionId]; + continue; + } + [actionIds addObject:actionId]; [actionsMapping setObject:action forKey:actionId]; } diff --git a/WebDriverAgentLib/Utilities/FBWebServerParams.h b/WebDriverAgentLib/Utilities/FBWebServerParams.h new file mode 100644 index 000000000..c92daecb2 --- /dev/null +++ b/WebDriverAgentLib/Utilities/FBWebServerParams.h @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FBWebServerParams : NSObject + +/** The local port number WDA server is running on */ +@property (nonatomic, nullable) NSNumber *port; + ++ (id)sharedInstance; + +@end + +NS_ASSUME_NONNULL_END diff --git a/WebDriverAgentLib/Utilities/FBWebServerParams.m b/WebDriverAgentLib/Utilities/FBWebServerParams.m new file mode 100644 index 000000000..65e85f65a --- /dev/null +++ b/WebDriverAgentLib/Utilities/FBWebServerParams.m @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "FBWebServerParams.h" + +@implementation FBWebServerParams + ++ (instancetype)sharedInstance +{ + static FBWebServerParams *instance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[self alloc] init]; + }); + return instance; +} + +@end diff --git a/WebDriverAgentLib/Utilities/FBXCTestDaemonsProxy.h b/WebDriverAgentLib/Utilities/FBXCTestDaemonsProxy.h index 307edb40d..18761248b 100644 --- a/WebDriverAgentLib/Utilities/FBXCTestDaemonsProxy.h +++ b/WebDriverAgentLib/Utilities/FBXCTestDaemonsProxy.h @@ -18,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN @protocol XCTestManager_ManagerInterface; +@class FBScreenRecordingRequest, FBScreenRecordingPromise; @interface FBXCTestDaemonsProxy : NSObject @@ -29,6 +30,11 @@ NS_ASSUME_NONNULL_BEGIN + (BOOL)openURL:(NSURL *)url usingApplication:(NSString *)bundleId error:(NSError **)error; + (BOOL)openDefaultApplicationForURL:(NSURL *)url error:(NSError **)error; ++ (nullable FBScreenRecordingPromise *)startScreenRecordingWithRequest:(FBScreenRecordingRequest *)request + error:(NSError **)error; ++ (BOOL)stopScreenRecordingWithUUID:(NSUUID *)uuid + error:(NSError **)error; + #if !TARGET_OS_TV + (BOOL)setSimulatedLocation:(CLLocation *)location error:(NSError **)error; + (nullable CLLocation *)getSimulatedLocation:(NSError **)error; diff --git a/WebDriverAgentLib/Utilities/FBXCTestDaemonsProxy.m b/WebDriverAgentLib/Utilities/FBXCTestDaemonsProxy.m index 180689800..59e3a64be 100644 --- a/WebDriverAgentLib/Utilities/FBXCTestDaemonsProxy.m +++ b/WebDriverAgentLib/Utilities/FBXCTestDaemonsProxy.m @@ -16,6 +16,8 @@ #import "FBExceptions.h" #import "FBLogger.h" #import "FBRunLoopSpinner.h" +#import "FBScreenRecordingPromise.h" +#import "FBScreenRecordingRequest.h" #import "XCTestDriver.h" #import "XCTRunnerDaemonSession.h" #import "XCUIApplication.h" @@ -71,9 +73,12 @@ + (void)swizzleLaunchApp { [FBLogger log:@"Could not find method -[XCTRunnerDaemonSession launchApplicationWithPath:]"]; return; } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-function-type-strict" // Workaround for https://github.com/appium/WebDriverAgent/issues/702 originalLaunchAppMethod = (void(*)(id, SEL, NSString*, NSString*, NSArray*, NSDictionary*, void (^)(_Bool, NSError *))) method_getImplementation(original); method_setImplementation(original, (IMP)swizzledLaunchApp); +#pragma clang diagnostic pop } + (id)testRunnerProxy @@ -129,10 +134,9 @@ + (BOOL)openURL:(NSURL *)url usingApplication:(NSString *)bundleId error:(NSErro { XCTRunnerDaemonSession *session = [XCTRunnerDaemonSession sharedSession]; if (![session respondsToSelector:@selector(openURL:usingApplication:completion:)]) { - [[[FBErrorBuilder builder] + return [[[FBErrorBuilder builder] withDescriptionFormat:@"The current Xcode SDK does not support opening of URLs with given application"] buildError:error]; - return NO; } __block NSError *innerError = nil; @@ -157,10 +161,9 @@ + (BOOL)openDefaultApplicationForURL:(NSURL *)url error:(NSError *__autoreleasin { XCTRunnerDaemonSession *session = [XCTRunnerDaemonSession sharedSession]; if (![session respondsToSelector:@selector(openDefaultApplicationForURL:completion:)]) { - [[[FBErrorBuilder builder] + return [[[FBErrorBuilder builder] withDescriptionFormat:@"The current Xcode SDK does not support opening of URLs. Consider upgrading to Xcode 14.3+/iOS 16.4+"] buildError:error]; - return NO; } __block NSError *innerError = nil; @@ -186,16 +189,14 @@ + (BOOL)setSimulatedLocation:(CLLocation *)location error:(NSError *__autoreleas { XCTRunnerDaemonSession *session = [XCTRunnerDaemonSession sharedSession]; if (![session respondsToSelector:@selector(setSimulatedLocation:completion:)]) { - [[[FBErrorBuilder builder] + return [[[FBErrorBuilder builder] withDescriptionFormat:@"The current Xcode SDK does not support location simulation. Consider upgrading to Xcode 14.3+/iOS 16.4+"] buildError:error]; - return NO; } if (![session supportsLocationSimulation]) { - [[[FBErrorBuilder builder] + return [[[FBErrorBuilder builder] withDescriptionFormat:@"Your device does not support location simulation"] buildError:error]; - return NO; } __block NSError *innerError = nil; @@ -254,20 +255,14 @@ + (BOOL)clearSimulatedLocation:(NSError *__autoreleasing*)error { XCTRunnerDaemonSession *session = [XCTRunnerDaemonSession sharedSession]; if (![session respondsToSelector:@selector(clearSimulatedLocationWithReply:)]) { - if (error) { - [[[FBErrorBuilder builder] + return [[[FBErrorBuilder builder] withDescriptionFormat:@"The current Xcode SDK does not support location simulation. Consider upgrading to Xcode 14.3+/iOS 16.4+"] buildError:error]; - } - return NO; } if (![session supportsLocationSimulation]) { - if (error) { - [[[FBErrorBuilder builder] + return [[[FBErrorBuilder builder] withDescriptionFormat:@"Your device does not support location simulation"] buildError:error]; - } - return NO; } __block NSError *innerError = nil; @@ -289,4 +284,77 @@ + (BOOL)clearSimulatedLocation:(NSError *__autoreleasing*)error } #endif ++ (FBScreenRecordingPromise *)startScreenRecordingWithRequest:(FBScreenRecordingRequest *)request + error:(NSError *__autoreleasing*)error +{ + XCTRunnerDaemonSession *session = [XCTRunnerDaemonSession sharedSession]; + if (![session respondsToSelector:@selector(startScreenRecordingWithRequest:withReply:)]) { + [[[FBErrorBuilder builder] + withDescriptionFormat:@"The current Xcode SDK does not support screen recording. Consider upgrading to Xcode 15+/iOS 17+"] + buildError:error]; + return nil; + } + if (![session supportsScreenRecording]) { + [[[FBErrorBuilder builder] + withDescriptionFormat:@"Your device does not support screen recording"] + buildError:error]; + return nil; + } + + id nativeRequest = [request toNativeRequestWithError:error]; + if (nil == nativeRequest) { + return nil; + } + + __block id futureMetadata = nil; + __block NSError *innerError = nil; + [FBRunLoopSpinner spinUntilCompletion:^(void(^completion)(void)){ + [session startScreenRecordingWithRequest:nativeRequest withReply:^(id reply, NSError *invokeError) { + if (nil == invokeError) { + futureMetadata = reply; + } else { + innerError = invokeError; + } + completion(); + }]; + }]; + if (nil != innerError) { + if (error) { + *error = innerError; + } + return nil; + } + return [[FBScreenRecordingPromise alloc] initWithNativePromise:futureMetadata]; +} + ++ (BOOL)stopScreenRecordingWithUUID:(NSUUID *)uuid error:(NSError *__autoreleasing*)error +{ + XCTRunnerDaemonSession *session = [XCTRunnerDaemonSession sharedSession]; + if (![session respondsToSelector:@selector(stopScreenRecordingWithUUID:withReply:)]) { + return [[[FBErrorBuilder builder] + withDescriptionFormat:@"The current Xcode SDK does not support screen recording. Consider upgrading to Xcode 15+/iOS 17+"] + buildError:error]; + + } + if (![session supportsScreenRecording]) { + return [[[FBErrorBuilder builder] + withDescriptionFormat:@"Your device does not support screen recording"] + buildError:error]; + } + + __block NSError *innerError = nil; + [FBRunLoopSpinner spinUntilCompletion:^(void(^completion)(void)){ + [session stopScreenRecordingWithUUID:uuid withReply:^(NSError *invokeError) { + if (nil != invokeError) { + innerError = invokeError; + } + completion(); + }]; + }]; + if (nil != innerError && error) { + *error = innerError; + } + return nil == innerError; +} + @end diff --git a/WebDriverAgentLib/Utilities/XCUIApplicationProcessDelay.m b/WebDriverAgentLib/Utilities/XCUIApplicationProcessDelay.m index c50ca4138..86c1fc45c 100644 --- a/WebDriverAgentLib/Utilities/XCUIApplicationProcessDelay.m +++ b/WebDriverAgentLib/Utilities/XCUIApplicationProcessDelay.m @@ -61,7 +61,10 @@ + (void)swizzleSetEventLoopHasIdled { [FBLogger log:@"Could not find method -[XCUIApplicationProcess setEventLoopHasIdled:]"]; return; } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-function-type-strict" orig_set_event_loop_has_idled = (void(*)(id, SEL, BOOL)) method_getImplementation(original); +#pragma clang diagnostic pop Method replace = class_getClassMethod([XCUIApplicationProcessDelay class], @selector(setEventLoopHasIdled:)); method_setImplementation(original, method_getImplementation(replace)); isSwizzled = YES; diff --git a/WebDriverAgentTests/IntegrationApp/Classes/AppDelegate.h b/WebDriverAgentTests/IntegrationApp/Classes/AppDelegate.h index ae3920f7f..9561057fd 100644 --- a/WebDriverAgentTests/IntegrationApp/Classes/AppDelegate.h +++ b/WebDriverAgentTests/IntegrationApp/Classes/AppDelegate.h @@ -8,8 +8,9 @@ */ #import +#import -@interface AppDelegate : UIResponder +@interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end diff --git a/WebDriverAgentTests/IntegrationApp/Classes/FBAlertViewController.h b/WebDriverAgentTests/IntegrationApp/Classes/FBAlertViewController.h index c657ab618..deb3ddfb4 100644 --- a/WebDriverAgentTests/IntegrationApp/Classes/FBAlertViewController.h +++ b/WebDriverAgentTests/IntegrationApp/Classes/FBAlertViewController.h @@ -8,6 +8,7 @@ */ #import +#import @interface FBAlertViewController : UIViewController diff --git a/WebDriverAgentTests/IntegrationApp/Classes/FBAlertViewController.m b/WebDriverAgentTests/IntegrationApp/Classes/FBAlertViewController.m index c90b58ab4..e431e8684 100644 --- a/WebDriverAgentTests/IntegrationApp/Classes/FBAlertViewController.m +++ b/WebDriverAgentTests/IntegrationApp/Classes/FBAlertViewController.m @@ -37,7 +37,14 @@ - (IBAction)createAppSheet:(UIButton *)sender - (IBAction)createNotificationAlert:(UIButton *)sender { - [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert categories:nil]]; + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center requestAuthorizationWithOptions:(UNAuthorizationOptionSound|UNAuthorizationOptionAlert|UNAuthorizationOptionBadge) + completionHandler:^(BOOL granted, NSError * _Nullable error) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [[UIApplication sharedApplication] registerForRemoteNotifications]; + }); + }]; } - (IBAction)createCameraRollAccessAlert:(UIButton *)sender diff --git a/WebDriverAgentTests/IntegrationApp/Classes/ViewController.m b/WebDriverAgentTests/IntegrationApp/Classes/ViewController.m index 6a0d0441a..7807e59f1 100644 --- a/WebDriverAgentTests/IntegrationApp/Classes/ViewController.m +++ b/WebDriverAgentTests/IntegrationApp/Classes/ViewController.m @@ -37,7 +37,7 @@ - (void)viewDidLayoutSubviews - (void)updateOrentationLabel { NSString *orientation = nil; - switch (self.interfaceOrientation) { + switch (UIDevice.currentDevice.orientation) { case UIInterfaceOrientationPortrait: orientation = @"Portrait"; break; @@ -50,6 +50,12 @@ - (void)updateOrentationLabel case UIInterfaceOrientationLandscapeRight: orientation = @"LandscapeRight"; break; + case UIDeviceOrientationFaceUp: + orientation = @"FaceUp"; + break; + case UIDeviceOrientationFaceDown: + orientation = @"FaceDown"; + break; case UIInterfaceOrientationUnknown: orientation = @"Unknown"; break; diff --git a/WebDriverAgentTests/IntegrationApp/Info.plist b/WebDriverAgentTests/IntegrationApp/Info.plist index 2f4f17529..2dfb754cd 100644 --- a/WebDriverAgentTests/IntegrationApp/Info.plist +++ b/WebDriverAgentTests/IntegrationApp/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.facebook.wda.integrationApp + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -22,12 +22,12 @@ 1 LSRequiresIPhoneOS + NSLocationAlwaysAndWhenInUseUsageDescription + Yo Yo NSLocationAlwaysUsageDescription Yo Yo NSLocationWhenInUseUsageDescription Yo Yo - NSLocationAlwaysAndWhenInUseUsageDescription - Yo Yo NSPhotoLibraryUsageDescription Yo Yo UILaunchStoryboardName diff --git a/WebDriverAgentTests/IntegrationTests/FBAppiumMultiTouchActionsIntegrationTests.m b/WebDriverAgentTests/IntegrationTests/FBAppiumMultiTouchActionsIntegrationTests.m deleted file mode 100644 index 9b767c67a..000000000 --- a/WebDriverAgentTests/IntegrationTests/FBAppiumMultiTouchActionsIntegrationTests.m +++ /dev/null @@ -1,206 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import - -#import "FBIntegrationTestCase.h" - -#import "FBMacros.h" -#import "XCUIElement.h" -#import "XCUIApplication+FBTouchAction.h" -#import "FBTestMacros.h" -#import "XCUIDevice+FBRotation.h" -#import "FBRunLoopSpinner.h" - -@interface FBAppiumMultiTouchActionsIntegrationTestsPart1 : FBIntegrationTestCase -@end - -@interface FBAppiumMultiTouchActionsIntegrationTestsPart2 : FBIntegrationTestCase -@property (nonatomic) XCUIElement *touchesLabel; -@property (nonatomic) XCUIElement *tapsLabel; -@end - -@implementation FBAppiumMultiTouchActionsIntegrationTestsPart1 - -- (void)verifyGesture:(NSArray *> *> *)gesture orientation:(UIDeviceOrientation)orientation -{ - [[XCUIDevice sharedDevice] fb_setDeviceInterfaceOrientation:orientation]; - NSError *error; - XCTAssertTrue([self.testedApplication fb_performAppiumTouchActions:gesture elementCache:nil error:&error]); - FBAssertWaitTillBecomesTrue(self.testedApplication.alerts.count > 0); -} - -- (void)setUp -{ - [super setUp]; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - [self launchApplication]; - [self goToAlertsPage]; - }); - [self clearAlert]; -} - -- (void)tearDown -{ - [self clearAlert]; - [self resetOrientation]; - [super tearDown]; -} - -- (void)testErroneousGestures -{ - NSArray *> *> *invalidGestures = - @[ - // One of the chains is empty - @[ - @[], - @[@{@"action": @"tap", - @"options": @{ - @"ELEMENT": self.testedApplication.buttons[FBShowAlertButtonName], - } - } - ], - ], - - ]; - - for (NSArray *> *> *invalidGesture in invalidGestures) { - NSError *error; - XCTAssertFalse([self.testedApplication fb_performAppiumTouchActions:invalidGesture elementCache:nil error:&error]); - XCTAssertNotNil(error); - } -} - -- (void)testSymmetricTwoFingersTap -{ - XCUIElement *element = self.testedApplication.buttons[FBShowAlertButtonName]; - NSArray *> *> *gesture = - @[ - @[@{ - @"action": @"tap", - @"options": @{ - @"ELEMENT": element - } - } - ], - @[@{ - @"action": @"tap", - @"options": @{ - @"ELEMENT": element - } - } - ], - ]; - - [self verifyGesture:gesture orientation:UIDeviceOrientationPortrait]; -} - -@end - -@implementation FBAppiumMultiTouchActionsIntegrationTestsPart2 - -- (void)verifyGesture:(NSArray *>*>*)gesture orientation:(UIDeviceOrientation)orientation tapsCount:(int)tapsCount touchesCount:(int)touchesCount -{ - [[XCUIDevice sharedDevice] fb_setDeviceInterfaceOrientation:orientation]; - NSError *error; - XCTAssertTrue([self.testedApplication fb_performAppiumTouchActions:gesture elementCache:nil error:&error]); - NSString *taps = [[self tapsLabel] label]; - NSString *touches = [[self touchesLabel] label] ; - BOOL tapsEqual = [[NSString stringWithFormat:@"%d", tapsCount] isEqualToString:taps]; - BOOL touchesEqual = [[NSString stringWithFormat:@"%d", touchesCount] isEqualToString:touches]; - XCTAssertTrue(tapsEqual); - XCTAssertTrue(touchesEqual); -} - -- (void)setUp -{ - [super setUp]; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - [self launchApplication]; - [self goToTouchPage]; - }); - self.touchesLabel = self.testedApplication.staticTexts[FBTouchesCountLabelIdentifier]; - self.tapsLabel = self.testedApplication.staticTexts[FBTapsCountLabelIdentifier]; -} - -- (void)testMultiTouchWithMultiTaps -{ - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"15.0")) { - // Does not work on iOS 15. - // It tapped two times, but one was touch count was one. Not two finguer taps. - return; - } - - XCUIElement *touchableView = self.testedApplication.otherElements[@"touchableView"]; - XCTAssertNotNil(touchableView); - NSArray *>*> *gesture = - @[@[@{ - @"action": @"tap", - @"options": @{ - @"ELEMENT": touchableView - } - }, - @{ - @"action": @"wait", - @"options": @{ - @"ms": @1000 - } - }, - @{ - @"action": @"tap", - @"options": @{ - @"ELEMENT": touchableView - } - }, - @{ - @"action": @"wait", - @"options": @{ - @"ms": @1000 - } - }, - @{ - @"action": @"release" - } - ], - @[@{ - @"action": @"tap", - @"options": @{ - @"ELEMENT": touchableView - } - }, - @{ - @"action": @"wait", - @"options": @{ - @"ms": @1000 - } - }, - @{ - @"action": @"tap", - @"options": @{ - @"ELEMENT": touchableView - } - }, - @{ - @"action": @"wait", - @"options": @{ - @"ms": @1000 - } - }, - @{ - @"action": @"release" - } - ] - - ]; - [self verifyGesture:gesture orientation:UIDeviceOrientationPortrait tapsCount:2 touchesCount:2]; -} - -@end diff --git a/WebDriverAgentTests/IntegrationTests/FBAppiumTouchActionsIntegrationTests.m b/WebDriverAgentTests/IntegrationTests/FBAppiumTouchActionsIntegrationTests.m deleted file mode 100644 index 51525e435..000000000 --- a/WebDriverAgentTests/IntegrationTests/FBAppiumTouchActionsIntegrationTests.m +++ /dev/null @@ -1,404 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import - -#import "FBIntegrationTestCase.h" - -#import "XCUIElement.h" -#import "XCUIApplication+FBTouchAction.h" -#import "FBTestMacros.h" -#import "XCUIDevice+FBRotation.h" -#import "FBRunLoopSpinner.h" -#import "FBXCodeCompatibility.h" - -@interface FBAppiumTouchActionsIntegrationTestsPart1 : FBIntegrationTestCase -@end - -@interface FBAppiumTouchActionsIntegrationTestsPart2 : FBIntegrationTestCase -@property (nonatomic) XCUIElement *pickerWheel; -@end - - -@implementation FBAppiumTouchActionsIntegrationTestsPart1 - -- (void)verifyGesture:(NSArray *> *)gesture orientation:(UIDeviceOrientation)orientation -{ - [[XCUIDevice sharedDevice] fb_setDeviceInterfaceOrientation:orientation]; - NSError *error; - XCTAssertTrue([self.testedApplication fb_performAppiumTouchActions:gesture elementCache:nil error:&error]); - FBAssertWaitTillBecomesTrue(self.testedApplication.alerts.count > 0); -} - -- (void)setUp -{ - [super setUp]; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - [self launchApplication]; - [self goToAlertsPage]; - }); - [self clearAlert]; -} - -- (void)tearDown -{ - [self clearAlert]; - [self resetOrientation]; - [super tearDown]; -} - -- (void)testErroneousGestures -{ - XCUIElement *dstButton = self.testedApplication.buttons[FBShowAlertButtonName]; - - NSArray *> *> *invalidGestures = - @[ - // Empty chain - @[], - - // Chain element without 'action' key - @[@{ - @"options": @{ - @"ms": @100 - } - }, - ], - - // Empty chain because of cancel - @[@{ - @"action": @"moveTo", - @"options": @{ - @"ELEMENT": dstButton, - } - }, - @{ - @"action": @"cancel" - }, - ], - - // Chain with unknown action - @[@{ - @"action": @"tapP", - @"options": @{ - @"ELEMENT": dstButton, - } - }, - ], - - // Wait without preceeding coordinate - @[@{ - @"action": @"wait" - } - ], - - // Wait with negative duration - @[@{ - @"action": @"press", - @"options": @{ - @"x": @1, - @"y": @1 - } - }, - @{ - @"action": @"wait", - @"options": @{ - @"ms": @-1.0 - } - }, - ], - - // Release without preceeding coordinate - @[@{ - @"action": @"release" - }, - @{ - @"action": @"tap", - @"options": @{ - @"x": @1, - @"y": @1 - } - }, - ], - - // Press without coordinates - @[@{ - @"action": @"press" - } - ], - - // longPress with invalid coordinates - @[@{ - @"action": @"longPress", - @"options": @{ - @"x": @1 - } - }, - ], - - // longPress with negative duration - @[@{ - @"action": @"longPress", - @"options": @{ - @"x": @1, - @"y": @1, - @"duration": @-0.01 - } - }, - ], - - ]; - - for (NSArray *> *invalidGesture in invalidGestures) { - NSError *error; - XCTAssertFalse([self.testedApplication fb_performAppiumTouchActions:invalidGesture elementCache:nil error:&error]); - XCTAssertNotNil(error); - } -} - -- (void)testTap -{ - NSArray *> *gesture = - @[@{ - @"action": @"tap", - @"options": @{ - @"ELEMENT": self.testedApplication.buttons[FBShowAlertButtonName] - } - } - ]; - [self verifyGesture:gesture orientation:UIDeviceOrientationPortrait]; -} - -- (void)testTapByCoordinates -{ - CGRect elementRect = self.testedApplication.buttons[FBShowAlertButtonName].frame; - CGFloat x = elementRect.origin.x + elementRect.size.width / 2; - CGFloat y = elementRect.origin.y + elementRect.size.height / 2; - NSArray *> *gesture = - @[@{ - @"action": @"tap", - @"options": @{ - @"x": @(x), - @"y": @(y) - } - } - ]; - [self verifyGesture:gesture orientation:UIDeviceOrientationPortrait]; -} - -- (void)testDoubleTap -{ - NSArray *> *gesture = - @[@{ - @"action": @"tap", - @"options": @{ - @"ELEMENT": self.testedApplication.buttons[FBShowAlertButtonName], - @"count": @2 - } - }, - ]; - [self verifyGesture:gesture orientation:UIDeviceOrientationLandscapeLeft]; -} - -- (void)testPress -{ - NSArray *> *gesture = - @[@{ - @"action": @"press", - @"options": @{ - @"ELEMENT": self.testedApplication.buttons[FBShowAlertButtonName], - @"x": @1, - @"y": @1 - } - }, - @{ - @"action": @"wait", - @"options": @{ - @"ms": @300 - } - }, - @{ - @"action": @"wait", - @"options": @{ - @"ms": @300 - } - }, - @{ - @"action": @"wait", - @"options": @{ - @"ms": @300 - } - }, - @{ - @"action": @"release" - } - ]; - [self verifyGesture:gesture orientation:UIDeviceOrientationLandscapeRight]; -} - -- (void)testLongPress -{ - if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) { - XCTSkip(@"Failed on Azure Pipeline. Local run succeeded."); - } - UIDeviceOrientation orientation = UIDeviceOrientationLandscapeLeft; - [[XCUIDevice sharedDevice] fb_setDeviceInterfaceOrientation:orientation]; - CGRect elementFrame = self.testedApplication.buttons[FBShowAlertButtonName].frame; - NSArray *> *gesture = - @[@{ - @"action": @"longPress", - @"options": @{ - @"x": @(elementFrame.origin.x + 1), - @"y": @(elementFrame.origin.y + 1), - @"duration": @5 - } - }, - @{ - @"action": @"wait", - @"options": @{ - @"ms": @500 - } - }, - @{ - @"action": @"release" - } - ]; - [self verifyGesture:gesture orientation:orientation]; -} - -@end - - -@implementation FBAppiumTouchActionsIntegrationTestsPart2 - -- (void)setUp -{ - [super setUp]; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - [self launchApplication]; - [self goToAttributesPage]; - }); - self.pickerWheel = self.testedApplication.pickerWheels.allElementsBoundByIndex.firstObject; -} - -- (void)tearDown -{ - [self resetOrientation]; - [super tearDown]; -} - -- (void)verifyPickerWheelPositionChangeWithGesture:(NSArray *> *)gesture -{ - NSString *previousValue = self.pickerWheel.value; - NSError *error; - XCTAssertTrue([self.testedApplication fb_performAppiumTouchActions:gesture elementCache:nil error:&error]); - XCTAssertNil(error); - XCTAssertTrue([[[[FBRunLoopSpinner new] - timeout:2.0] - timeoutErrorMessage:@"Picker wheel value has not been changed after 2 seconds timeout"] - spinUntilTrue:^BOOL{ - return ![self.pickerWheel.fb_takeSnapshot.value isEqualToString:previousValue]; - } - error:&error]); - XCTAssertNil(error); -} - -- (void)testSwipePickerWheelWithElementCoordinates -{ - CGRect pickerFrame = self.pickerWheel.frame; - NSArray *> *gesture = - @[@{ - @"action": @"press", - @"options": @{ - @"ELEMENT": self.pickerWheel, - @"x": @(pickerFrame.size.width / 2), - @"y": @(pickerFrame.size.height / 2), - } - }, - @{ - @"action": @"wait", - @"options": @{ - @"ms": @500, - } - }, - @{ - @"action": @"moveTo", - @"options": @{ - @"ELEMENT": self.pickerWheel, - @"x": @(pickerFrame.size.width / 2), - @"y": @(pickerFrame.size.height), - } - }, - @{ - @"action": @"release" - } - ]; - [self verifyPickerWheelPositionChangeWithGesture:gesture]; -} - -- (void)testSwipePickerWheelWithRelativeCoordinates -{ - CGRect pickerFrame = self.pickerWheel.frame; - NSArray *> *gesture = - @[@{ - @"action": @"press", - @"options": @{ - @"ELEMENT": self.pickerWheel, - @"x": @(pickerFrame.size.width / 2), - @"y": @(pickerFrame.size.height / 2), - } - }, - @{ - @"action": @"wait", - @"options": @{ - @"ms": @500, - } - }, - @{ - @"action": @"moveTo", - @"options": @{ - @"x": @(pickerFrame.origin.x / 2), - @"y": @(pickerFrame.origin.y), - } - }, - @{ - @"action": @"release" - } - ]; - [self verifyPickerWheelPositionChangeWithGesture:gesture]; -} - -- (void)testSwipePickerWheelWithAbsoluteCoordinates -{ - CGRect pickerFrame = self.pickerWheel.frame; - NSArray *> *gesture = - @[@{ - @"action": @"longPress", - @"options": @{ - @"x": @(pickerFrame.origin.x + pickerFrame.size.width / 2), - @"y": @(pickerFrame.origin.y + pickerFrame.size.height / 2), - } - }, - @{ - @"action": @"moveTo", - @"options": @{ - @"x": @(pickerFrame.origin.x + pickerFrame.size.width / 2), - @"y": @(pickerFrame.origin.y + pickerFrame.size.height), - } - }, - @{ - @"action": @"release" - } - ]; - [self verifyPickerWheelPositionChangeWithGesture:gesture]; -} - -@end - diff --git a/WebDriverAgentTests/IntegrationTests/FBElementAttributeTests.m b/WebDriverAgentTests/IntegrationTests/FBElementAttributeTests.m index 13cba4e57..2d6f30c5b 100644 --- a/WebDriverAgentTests/IntegrationTests/FBElementAttributeTests.m +++ b/WebDriverAgentTests/IntegrationTests/FBElementAttributeTests.m @@ -48,14 +48,9 @@ - (void)testContainerAccessibilityAttributes XCUIElement *inaccessibleButtonElement = self.testedApplication.buttons[@"not_accessible"]; XCTAssertTrue(inaccessibleButtonElement.exists); XCTAssertFalse(inaccessibleButtonElement.fb_isAccessibilityElement); - if (@available(iOS 13.0, *)) { - // FIXME: Xcode 11 environment returns false even if iOS 12 - // We must fix here to XCTAssertTrue if Xcode version will return the value properly - XCTAssertFalse(inaccessibleButtonElement.isWDAccessibilityContainer); - } else { - // Xcode 10 and the below works fine - XCTAssertTrue(inaccessibleButtonElement.isWDAccessibilityContainer); - } + // FIXME: Xcode 11 environment returns false even if iOS 12 + // We must fix here to XCTAssertTrue if Xcode version will return the value properly + XCTAssertFalse(inaccessibleButtonElement.isWDAccessibilityContainer); } - (void)testIgnoredAccessibilityAttributes diff --git a/WebDriverAgentTests/IntegrationTests/FBElementVisibilityTests.m b/WebDriverAgentTests/IntegrationTests/FBElementVisibilityTests.m index d791e8ab5..1449e1ae8 100644 --- a/WebDriverAgentTests/IntegrationTests/FBElementVisibilityTests.m +++ b/WebDriverAgentTests/IntegrationTests/FBElementVisibilityTests.m @@ -63,11 +63,6 @@ - (void)disabled_testIconsFromSearchDashboard - (void)testTableViewCells { - if (SYSTEM_VERSION_GREATER_THAN(@"12.0")) { - // The test is flacky on iOS 12+ in Travis env - return; - } - [self launchApplication]; [self goToScrollPageWithCells:YES]; for (int i = 0 ; i < 10 ; i++) { diff --git a/WebDriverAgentTests/IntegrationTests/FBSafariAlertTests.m b/WebDriverAgentTests/IntegrationTests/FBSafariAlertTests.m index 73e2de337..5b49b35d9 100644 --- a/WebDriverAgentTests/IntegrationTests/FBSafariAlertTests.m +++ b/WebDriverAgentTests/IntegrationTests/FBSafariAlertTests.m @@ -25,15 +25,13 @@ @interface FBSafariAlertIntegrationTests : FBIntegrationTestCase @end -static NSString *const SAFARI_BUNDLE_ID = @"com.apple.mobilesafari"; - @implementation FBSafariAlertIntegrationTests - (void)setUp { [super setUp]; self.session = [FBSession initWithApplication:XCUIApplication.fb_activeApplication]; - [self.session launchApplicationWithBundleId:SAFARI_BUNDLE_ID + [self.session launchApplicationWithBundleId:FB_SAFARI_BUNDLE_ID shouldWaitForQuiescence:nil arguments:nil environment:nil]; @@ -42,7 +40,7 @@ - (void)setUp - (void)tearDown { - [self.session terminateApplicationWithBundleId:SAFARI_BUNDLE_ID]; + [self.session terminateApplicationWithBundleId:FB_SAFARI_BUNDLE_ID]; } - (void)disabled_testCanHandleSafariInputPrompt diff --git a/WebDriverAgentTests/IntegrationTests/FBScreenTests.m b/WebDriverAgentTests/IntegrationTests/FBScreenTests.m index 4eff7fcd9..0f5bb5bbc 100644 --- a/WebDriverAgentTests/IntegrationTests/FBScreenTests.m +++ b/WebDriverAgentTests/IntegrationTests/FBScreenTests.m @@ -28,12 +28,5 @@ - (void)testScreenScale XCTAssertTrue([FBScreen scale] >= 2); } -- (void)testStatusBarSize -{ - CGSize statusBarSize = [FBScreen statusBarSizeForApplication:self.testedApplication]; - BOOL statusBarSizeIsZero = CGSizeEqualToSize(CGSizeZero, statusBarSize); - XCTAssertFalse(statusBarSizeIsZero); -} - @end diff --git a/WebDriverAgentTests/IntegrationTests/FBVideoRecordingTests.m b/WebDriverAgentTests/IntegrationTests/FBVideoRecordingTests.m new file mode 100644 index 000000000..191af3420 --- /dev/null +++ b/WebDriverAgentTests/IntegrationTests/FBVideoRecordingTests.m @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "FBIntegrationTestCase.h" + +#import "FBConfiguration.h" +#import "FBMacros.h" +#import "FBScreenRecordingPromise.h" +#import "FBScreenRecordingRequest.h" +#import "FBScreenRecordingContainer.h" +#import "FBXCTestDaemonsProxy.h" + +@interface FBVideoRecordingTests : FBIntegrationTestCase +@end + +@implementation FBVideoRecordingTests + +- (void)setUp +{ + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"DisableDiagnosticScreenRecordings"]; + [super setUp]; +} + +- (void)testStartingAndStoppingVideoRecording +{ + XCTSkip(@"Failed on Azure Pipeline. Local run succeeded."); + + // Video recording is only available since iOS 17 + if (SYSTEM_VERSION_LESS_THAN(@"17.0")) { + return; + } + + FBScreenRecordingRequest *recordingRequest = [[FBScreenRecordingRequest alloc] initWithFps:24 + codec:0]; + NSError *error; + FBScreenRecordingPromise *promise = [FBXCTestDaemonsProxy startScreenRecordingWithRequest:recordingRequest + error:&error]; + XCTAssertNotNil(promise); + XCTAssertNotNil(promise.identifier); + XCTAssertNil(error); + + [FBScreenRecordingContainer.sharedInstance storeScreenRecordingPromise:promise + fps:24 + codec:0]; + XCTAssertEqual(FBScreenRecordingContainer.sharedInstance.screenRecordingPromise, promise); + + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.]]; + + BOOL isSuccessfull = [FBXCTestDaemonsProxy stopScreenRecordingWithUUID:promise.identifier error:&error]; + XCTAssertTrue(isSuccessfull); + XCTAssertNil(error); + + [FBScreenRecordingContainer.sharedInstance reset]; + XCTAssertNil(FBScreenRecordingContainer.sharedInstance.screenRecordingPromise); +} + +@end diff --git a/WebDriverAgentTests/IntegrationTests/FBW3CTouchActionsIntegrationTests.m b/WebDriverAgentTests/IntegrationTests/FBW3CTouchActionsIntegrationTests.m index 598cc66c9..ccb996aa3 100644 --- a/WebDriverAgentTests/IntegrationTests/FBW3CTouchActionsIntegrationTests.m +++ b/WebDriverAgentTests/IntegrationTests/FBW3CTouchActionsIntegrationTests.m @@ -70,15 +70,6 @@ - (void)testErroneousGestures }, ], - // Chain element with empty 'actions' - @[@{ - @"type": @"pointer", - @"id": @"finger1", - @"parameters": @{@"pointerType": @"touch"}, - @"actions": @[], - }, - ], - // Chain element without type @[@{ @"id": @"finger1", @@ -276,6 +267,21 @@ - (void)testErroneousGestures } } +- (void)testNothingDoesWithoutError +{ + NSArray *> *gesture = + @[@{ + @"type": @"pointer", + @"id": @"finger1", + @"parameters": @{@"pointerType": @"touch"}, + @"actions": @[], + }, + ]; + NSError *error; + XCTAssertTrue([self.testedApplication fb_performW3CActions:gesture elementCache:nil error:&error]); + XCTAssertNil(error); +} + - (void)testTap { NSArray *> *gesture = diff --git a/WebDriverAgentTests/IntegrationTests/FBW3CTypeActionsTests.m b/WebDriverAgentTests/IntegrationTests/FBW3CTypeActionsTests.m index b202eda95..893271e31 100644 --- a/WebDriverAgentTests/IntegrationTests/FBW3CTypeActionsTests.m +++ b/WebDriverAgentTests/IntegrationTests/FBW3CTypeActionsTests.m @@ -140,18 +140,46 @@ - (void)testTextTyping @{@"type": @"keyUp", @"value": @"N"}, @{@"type": @"keyDown", @"value": @"B"}, @{@"type": @"keyUp", @"value": @"B"}, + @{@"type": @"keyDown", @"value": @"A"}, + @{@"type": @"keyUp", @"value": @"A"}, @{@"type": @"keyDown", @"value": @"a"}, @{@"type": @"keyUp", @"value": @"a"}, + @{@"type": @"keyDown", @"value": [NSString stringWithFormat:@"%C", 0xE003]}, + @{@"type": @"keyUp", @"value": [NSString stringWithFormat:@"%C", 0xE003]}, @{@"type": @"pause", @"duration": @500}, ], }, - ]; + ]; + NSError *error; + XCTAssertTrue([self.testedApplication fb_performW3CActions:typeAction + elementCache:nil + error:&error]); + XCTAssertNil(error); + XCTAssertEqualObjects(textField.wdValue, @"🏀NBA"); +} + +- (void)testTextTypingWithEmptyActions +{ + if (![XCPointerEvent.class fb_areKeyEventsSupported]) { + return; + } + + XCUIElement *textField = self.testedApplication.textFields[@"aIdentifier"]; + [textField tap]; + NSArray *> *typeAction = + @[ + @{ + @"type": @"pointer", + @"id": @"touch", + @"actions": @[], + }, + ]; NSError *error; XCTAssertTrue([self.testedApplication fb_performW3CActions:typeAction elementCache:nil error:&error]); XCTAssertNil(error); - XCTAssertEqualObjects(textField.wdValue, @"🏀NBa"); + XCTAssertEqualObjects(textField.value, @""); } @end diff --git a/WebDriverAgentTests/IntegrationTests/XCUIApplicationHelperTests.m b/WebDriverAgentTests/IntegrationTests/XCUIApplicationHelperTests.m index e86f62021..15b906db7 100644 --- a/WebDriverAgentTests/IntegrationTests/XCUIApplicationHelperTests.m +++ b/WebDriverAgentTests/IntegrationTests/XCUIApplicationHelperTests.m @@ -111,7 +111,25 @@ - (void)testAccessbilityAudit [set addObject:@"XCUIAccessibilityAuditTypeAll"]; NSArray *auditIssues2 = [XCUIApplication.fb_activeApplication fb_performAccessibilityAuditWithAuditTypesSet:set.copy error:&error]; - XCTAssertEqualObjects(auditIssues1, auditIssues2); + // 'elementDescription' is not in this list because it could have + // different object id's debug description in XCTest. + NSArray *checkKeys = @[ + @"auditType", + @"compactDescription", + @"detailedDescription", + @"element", + @"elementAttributes" + ]; + + XCTAssertEqual([auditIssues1 count], [auditIssues2 count]); + for (int i = 1; i < [auditIssues1 count]; i++) { + for (NSString *k in checkKeys) { + XCTAssertEqualObjects( + [auditIssues1[i] objectForKey:k], + [auditIssues2[i] objectForKey:k] + ); + } + } XCTAssertNil(error); } diff --git a/WebDriverAgentTests/IntegrationTests/XCUIElementFBFindTests.m b/WebDriverAgentTests/IntegrationTests/XCUIElementFBFindTests.m index 243ebec5d..713aee760 100644 --- a/WebDriverAgentTests/IntegrationTests/XCUIElementFBFindTests.m +++ b/WebDriverAgentTests/IntegrationTests/XCUIElementFBFindTests.m @@ -71,10 +71,7 @@ - (void)testDescendantsWithIdentifier { NSArray *matchingSnapshots = [self.testedView fb_descendantsMatchingIdentifier:@"Alerts" shouldReturnAfterFirstMatch:NO]; - int snapshotsCount = 1; - if (@available(iOS 13.0, *)) { - snapshotsCount = 2; - } + int snapshotsCount = 2; XCTAssertEqual(matchingSnapshots.count, snapshotsCount); XCTAssertEqual(matchingSnapshots.firstObject.elementType, XCUIElementTypeButton); XCTAssertEqualObjects(matchingSnapshots.lastObject.label, @"Alerts"); @@ -162,10 +159,7 @@ - (void)testDescendantsWithComplexXPathQuery { NSArray *matchingSnapshots = [self.testedView fb_descendantsMatchingXPathQuery:@"//*[@label='Scrolling']/preceding::*[boolean(string(@label))]" shouldReturnAfterFirstMatch:NO]; - int snapshotsCount = 3; - if (@available(iOS 13.0, *)) { - snapshotsCount = 6; - } + int snapshotsCount = 6; XCTAssertEqual(matchingSnapshots.count, snapshotsCount); } @@ -198,10 +192,7 @@ - (void)testDescendantsWithPredicateString NSPredicate *predicate = [NSPredicate predicateWithFormat:@"label = 'Alerts'"]; NSArray *matchingSnapshots = [self.testedView fb_descendantsMatchingPredicate:predicate shouldReturnAfterFirstMatch:NO]; - int snapshotsCount = 1; - if (@available(iOS 13.0, *)) { - snapshotsCount = 2; - } + int snapshotsCount = 2; XCTAssertEqual(matchingSnapshots.count, snapshotsCount); XCTAssertEqual(matchingSnapshots.firstObject.elementType, XCUIElementTypeButton); XCTAssertEqualObjects(matchingSnapshots.lastObject.label, @"Alerts"); @@ -243,10 +234,7 @@ - (void)testDescendantsWithPropertyStrict partialSearch:NO]; XCTAssertEqual(matchingSnapshots.count, 0); matchingSnapshots = [self.testedView fb_descendantsMatchingProperty:@"label" value:@"Alerts" partialSearch:NO]; - int snapshotsCount = 1; - if (@available(iOS 13.0, *)) { - snapshotsCount = 2; - } + int snapshotsCount = 2; XCTAssertEqual(matchingSnapshots.count, snapshotsCount); XCTAssertEqual(matchingSnapshots.firstObject.elementType, XCUIElementTypeButton); XCTAssertEqualObjects(matchingSnapshots.lastObject.label, @"Alerts"); @@ -259,10 +247,7 @@ - (void)testGlobalWithPropertyStrict partialSearch:NO]; XCTAssertEqual(matchingSnapshots.count, 0); matchingSnapshots = [self.testedApplication fb_descendantsMatchingProperty:@"label" value:@"Alerts" partialSearch:NO]; - int snapshotsCount = 1; - if (@available(iOS 13.0, *)) { - snapshotsCount = 2; - } + int snapshotsCount = 2; XCTAssertEqual(matchingSnapshots.count, snapshotsCount); XCTAssertEqual(matchingSnapshots.firstObject.elementType, XCUIElementTypeButton); XCTAssertEqualObjects(matchingSnapshots.lastObject.label, @"Alerts"); @@ -273,10 +258,7 @@ - (void)testDescendantsWithPropertyPartial NSArray *matchingSnapshots = [self.testedView fb_descendantsMatchingProperty:@"label" value:@"Alerts" partialSearch:NO]; - int snapshotsCount = 1; - if (@available(iOS 13.0, *)) { - snapshotsCount = 2; - } + int snapshotsCount = 2; XCTAssertEqual(matchingSnapshots.count, snapshotsCount); XCTAssertEqual(matchingSnapshots.firstObject.elementType, XCUIElementTypeButton); XCTAssertEqualObjects(matchingSnapshots.lastObject.label, @"Alerts"); @@ -297,19 +279,13 @@ - (void)testDescendantsWithClassChain - (void)testDescendantsWithClassChainWithIndex { NSArray *matchingSnapshots; - NSString *queryString = @"XCUIElementTypeWindow/*/*[2]/*/*/XCUIElementTypeButton"; - if (@available(iOS 13.0, *)) { - // iPhone - queryString = @"XCUIElementTypeWindow/*/*/*/*[2]/*/*/XCUIElementTypeButton"; - matchingSnapshots = [self.testedApplication fb_descendantsMatchingClassChain:queryString - shouldReturnAfterFirstMatch:NO]; - if (matchingSnapshots.count == 0) { - // iPad - queryString = @"XCUIElementTypeWindow/*/*/*/*/*[2]/*/*/XCUIElementTypeButton"; - matchingSnapshots = [self.testedApplication fb_descendantsMatchingClassChain:queryString - shouldReturnAfterFirstMatch:NO]; - } - } else { + // iPhone + NSString *queryString = @"XCUIElementTypeWindow/*/*/*/*[2]/*/*/XCUIElementTypeButton"; + matchingSnapshots = [self.testedApplication fb_descendantsMatchingClassChain:queryString + shouldReturnAfterFirstMatch:NO]; + if (matchingSnapshots.count == 0) { + // iPad + queryString = @"XCUIElementTypeWindow/*/*/*/*/*[2]/*/*/XCUIElementTypeButton"; matchingSnapshots = [self.testedApplication fb_descendantsMatchingClassChain:queryString shouldReturnAfterFirstMatch:NO]; } @@ -444,10 +420,7 @@ - (void)setUp - (void)testNestedQueryWithClassChain { - NSString *queryString = @"XCUIElementTypeOther"; - if (@available(iOS 13.0, *)) { - queryString = @"XCUIElementTypePicker"; - } + NSString *queryString = @"XCUIElementTypePicker"; FBAssertWaitTillBecomesTrue(self.testedApplication.buttons[@"Button"].fb_isVisible); XCUIElement *datePicker = [self.testedApplication descendantsMatchingType:XCUIElementTypeDatePicker].allElementsBoundByIndex.firstObject; @@ -455,10 +428,7 @@ - (void)testNestedQueryWithClassChain shouldReturnAfterFirstMatch:NO]; XCTAssertEqual(matches.count, 1); - XCUIElementType expectedType = XCUIElementTypeOther; - if (@available(iOS 13.0, *)) { - expectedType = XCUIElementTypePicker; - } + XCUIElementType expectedType = XCUIElementTypePicker; XCTAssertEqual([matches firstObject].elementType, expectedType); } diff --git a/index.js b/index.js deleted file mode 100644 index 3513d683b..000000000 --- a/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import * as dependencies from './lib/check-dependencies'; -import * as proxies from './lib/no-session-proxy'; -import * as driver from './lib/webdriveragent'; -import * as constants from './lib/constants'; -import * as utils from './lib/utils'; - - -const { checkForDependencies, bundleWDASim } = dependencies; -const { NoSessionProxy } = proxies; -const { WebDriverAgent } = driver; -const { WDA_BASE_URL, WDA_RUNNER_BUNDLE_ID, PROJECT_FILE } = constants; -const { resetTestProcesses, BOOTSTRAP_PATH } = utils; - -export { - WebDriverAgent, - NoSessionProxy, - checkForDependencies, bundleWDASim, - resetTestProcesses, - BOOTSTRAP_PATH, - WDA_RUNNER_BUNDLE_ID, PROJECT_FILE, - WDA_BASE_URL, -}; diff --git a/index.ts b/index.ts new file mode 100644 index 000000000..d8e3996bd --- /dev/null +++ b/index.ts @@ -0,0 +1,7 @@ +export { checkForDependencies, bundleWDASim } from './lib/check-dependencies'; +export { NoSessionProxy } from './lib/no-session-proxy'; +export { WebDriverAgent } from './lib/webdriveragent'; +export { WDA_BASE_URL, WDA_RUNNER_BUNDLE_ID, PROJECT_FILE } from './lib/constants'; +export { resetTestProcesses, BOOTSTRAP_PATH } from './lib/utils'; + +export * from './lib/types'; diff --git a/lib/check-dependencies.js b/lib/check-dependencies.js index ce145f5a1..21c68de60 100644 --- a/lib/check-dependencies.js +++ b/lib/check-dependencies.js @@ -3,6 +3,7 @@ import _ from 'lodash'; import { exec } from 'teen_process'; import path from 'path'; import XcodeBuild from './xcodebuild'; +import xcode from 'appium-xcode'; import { WDA_SCHEME, SDK_SIMULATOR, WDA_RUNNER_APP } from './constants'; @@ -22,17 +23,25 @@ async function buildWDASim () { } // eslint-disable-next-line require-await -async function checkForDependencies () { +export async function checkForDependencies () { log.debug('Dependencies are up to date'); return false; } -async function bundleWDASim (xcodebuild) { +/** + * + * @param {XcodeBuild} xcodebuild + * @returns {Promise} + */ +export async function bundleWDASim (xcodebuild) { if (xcodebuild && !_.isFunction(xcodebuild.retrieveDerivedDataPath)) { - xcodebuild = new XcodeBuild('', {}); + xcodebuild = new XcodeBuild(/** @type {import('appium-xcode').XcodeVersion} */ (await xcode.getVersion(true)), {}); } const derivedDataPath = await xcodebuild.retrieveDerivedDataPath(); + if (!derivedDataPath) { + throw new Error('Cannot retrieve the path to the Xcode derived data folder'); + } const wdaBundlePath = path.join(derivedDataPath, 'Build', 'Products', 'Debug-iphonesimulator', WDA_RUNNER_APP); if (await fs.exists(wdaBundlePath)) { return wdaBundlePath; @@ -40,5 +49,3 @@ async function bundleWDASim (xcodebuild) { await buildWDASim(); return wdaBundlePath; } - -export { checkForDependencies, bundleWDASim }; diff --git a/lib/constants.js b/lib/constants.js index 82f005d1c..fd6ed4803 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -1,7 +1,8 @@ import path from 'path'; +const DEFAULT_TEST_BUNDLE_SUFFIX = '.xctrunner'; const WDA_RUNNER_BUNDLE_ID = 'com.facebook.WebDriverAgentRunner'; -const WDA_RUNNER_BUNDLE_ID_FOR_XCTEST = `${WDA_RUNNER_BUNDLE_ID}.xctrunner`; +const WDA_RUNNER_BUNDLE_ID_FOR_XCTEST = `${WDA_RUNNER_BUNDLE_ID}${DEFAULT_TEST_BUNDLE_SUFFIX}`; const WDA_RUNNER_APP = 'WebDriverAgentRunner-Runner.app'; const WDA_SCHEME = 'WebDriverAgentRunner'; const PROJECT_FILE = 'project.pbxproj'; @@ -19,5 +20,5 @@ export { WDA_RUNNER_BUNDLE_ID, WDA_RUNNER_APP, PROJECT_FILE, WDA_SCHEME, PLATFORM_NAME_TVOS, PLATFORM_NAME_IOS, SDK_SIMULATOR, SDK_DEVICE, WDA_BASE_URL, WDA_UPGRADE_TIMESTAMP_PATH, - WDA_RUNNER_BUNDLE_ID_FOR_XCTEST + WDA_RUNNER_BUNDLE_ID_FOR_XCTEST, DEFAULT_TEST_BUNDLE_SUFFIX }; diff --git a/lib/types.ts b/lib/types.ts new file mode 100644 index 000000000..8b41dfc4e --- /dev/null +++ b/lib/types.ts @@ -0,0 +1,51 @@ +// WebDriverAgentLib/Utilities/FBSettings.h +export interface WDASettings { + elementResponseAttribute?: string; + shouldUseCompactResponses?: boolean; + mjpegServerScreenshotQuality?: number; + mjpegServerFramerate?: number; + screenshotQuality?: number; + elementResponseAttributes?: string; + mjpegScalingFactor?: number; + mjpegFixOrientation?: boolean; + keyboardAutocorrection?: boolean; + keyboardPrediction?: boolean; + customSnapshotTimeout?: number; + snapshotMaxDepth?: number; + useFirstMatch?: boolean; + boundElementsByIndex?: boolean; + reduceMotion?: boolean; + defaultActiveApplication?: string; + activeAppDetectionPoint?: string; + includeNonModalElements?: boolean; + defaultAlertAction?: 'accept' | 'dismiss'; + acceptAlertButtonSelector?: string; + dismissAlertButtonSelector?: string; + screenshotOrientation?: 'auto' | 'portrait' | 'portraitUpsideDown' | 'landscapeRight' | 'landscapeLeft' + waitForIdleTimeout?: number; + animationCoolOffTimeout?: number; + maxTypingFrequency?: number; +} + +// WebDriverAgentLib/Utilities/FBCapabilities.h +export interface WDACapabilities { + bundleId?: string; + initialUrl?: string; + arguments?: string[]; + environment?: Record; + eventloopIdleDelaySec?: number; + shouldWaitForQuiescence?: boolean; + shouldUseTestManagerForVisibilityDetection?: boolean; + maxTypingFrequency?: number; + shouldUseSingletonTestManager?: boolean; + waitForIdleTimeout?: number; + shouldUseCompactResponses?: number; + elementResponseFields?: unknown; + disableAutomaticScreenshots?: boolean; + shouldTerminateApp?: boolean; + forceAppLaunch?: boolean; + useNativeCachingStrategy?: boolean; + forceSimulatorSoftwareKeyboardPresence?: boolean; + defaultAlertAction?: 'accept' | 'dismiss'; + appLaunchStateTimeoutSec?: number; +} diff --git a/lib/webdriveragent.js b/lib/webdriveragent.js index fdae2bff6..f5cda7ee3 100644 --- a/lib/webdriveragent.js +++ b/lib/webdriveragent.js @@ -1,3 +1,4 @@ +import { waitForCondition } from 'asyncbox'; import _ from 'lodash'; import path from 'path'; import url from 'url'; @@ -14,8 +15,8 @@ import AsyncLock from 'async-lock'; import { exec } from 'teen_process'; import { bundleWDASim } from './check-dependencies'; import { - WDA_RUNNER_BUNDLE_ID, WDA_RUNNER_BUNDLE_ID_FOR_XCTEST, WDA_RUNNER_APP, - WDA_BASE_URL, WDA_UPGRADE_TIMESTAMP_PATH + WDA_RUNNER_BUNDLE_ID, WDA_RUNNER_APP, + WDA_BASE_URL, WDA_UPGRADE_TIMESTAMP_PATH, DEFAULT_TEST_BUNDLE_SUFFIX } from './constants'; import {Xctest} from 'appium-ios-device'; import {strongbox} from '@appium/strongbox'; @@ -26,7 +27,19 @@ const WDA_CF_BUNDLE_NAME = 'WebDriverAgentRunner-Runner'; const SHARED_RESOURCES_GUARD = new AsyncLock(); const RECENT_MODULE_VERSION_ITEM_NAME = 'recentWdaModuleVersion'; -class WebDriverAgent { +export class WebDriverAgent { + /** @type {string} */ + bootstrapPath; + + /** @type {string} */ + agentPath; + + /** + * @param {import('appium-xcode').XcodeVersion} xcodeVersion + * // TODO: make args typed + * @param {import('@appium/types').StringRecord} [args={}] + * @param {import('@appium/types').AppiumLogger?} [log=null] + */ constructor (xcodeVersion, args = {}, log = null) { this.xcodeVersion = xcodeVersion; @@ -45,7 +58,8 @@ class WebDriverAgent { this.setWDAPaths(args.bootstrapPath, args.agentPath); this.wdaLocalPort = args.wdaLocalPort; - this.wdaRemotePort = args.wdaLocalPort || WDA_AGENT_PORT; + this.wdaRemotePort = ((this.isRealDevice ? args.wdaRemotePort : null) ?? args.wdaLocalPort) + || WDA_AGENT_PORT; this.wdaBaseUrl = args.wdaBaseUrl || WDA_BASE_URL; this.prebuildWDA = args.prebuildWDA; @@ -66,8 +80,10 @@ class WebDriverAgent { this.updatedWDABundleId = args.updatedWDABundleId; + this.wdaLaunchTimeout = args.wdaLaunchTimeout || WDA_LAUNCH_TIMEOUT; this.usePreinstalledWDA = args.usePreinstalledWDA; this.xctestApiClient = null; + this.updatedWDABundleIdSuffix = args.updatedWDABundleIdSuffix ?? DEFAULT_TEST_BUNDLE_SUFFIX; this.xcodebuild = this.canSkipXcodebuild ? null @@ -87,7 +103,7 @@ class WebDriverAgent { useSimpleBuildTest: args.useSimpleBuildTest, usePrebuiltWDA: args.usePrebuiltWDA, updatedWDABundleId: this.updatedWDABundleId, - launchTimeout: args.wdaLaunchTimeout || WDA_LAUNCH_TIMEOUT, + launchTimeout: this.wdaLaunchTimeout, wdaRemotePort: this.wdaRemotePort, useXctestrunFile: this.useXctestrunFile, derivedDataPath: args.derivedDataPath, @@ -109,13 +125,21 @@ class WebDriverAgent { } /** + * Return bundle id for WebDriverAgent to launch the WDA. + * The primary usage is with 'this.usePreinstalledWDA'. + * It adds `.xctrunner` as suffix by default but 'this.updatedWDABundleIdSuffix' + * lets skip it. * * @returns {string} Bundle ID for Xctest. */ get bundleIdForXctest () { - return this.updatedWDABundleId ? `${this.updatedWDABundleId}.xctrunner` : WDA_RUNNER_BUNDLE_ID_FOR_XCTEST; + return `${this.updatedWDABundleId ? this.updatedWDABundleId : WDA_RUNNER_BUNDLE_ID}${this.updatedWDABundleIdSuffix}`; } + /** + * @param {string} [bootstrapPath] + * @param {string} [agentPath] + */ setWDAPaths (bootstrapPath, agentPath) { // allow the user to specify a place for WDA. This is undocumented and // only here for the purposes of testing development of WDA @@ -127,8 +151,11 @@ class WebDriverAgent { this.log.info(`Using WDA agent: '${this.agentPath}'`); } + /** + * @returns {Promise} + */ async cleanupObsoleteProcesses () { - const obsoletePids = await getPIDsListeningOnPort(this.url.port, + const obsoletePids = await getPIDsListeningOnPort(/** @type {string} */ (this.url.port), (cmdLine) => cmdLine.includes('/WebDriverAgentRunner') && !cmdLine.toLowerCase().includes(this.device.udid.toLowerCase())); @@ -157,6 +184,9 @@ class WebDriverAgent { return !!(await this.getStatus()); } + /** + * @returns {string} + */ get basePath () { if (this.url.path === '/') { return ''; @@ -183,28 +213,61 @@ class WebDriverAgent { * } * } * - * @return {Promise} State Object - * @throws {Error} If there was invalid response code or body + * @param {number} [timeoutMs=0] If the given timeoutMs is zero or negative number, + * this function will return the response of `/status` immediately. If the given timeoutMs, + * this function will try to get the response of `/status` up to the timeoutMs. + * @return {Promise} State Object + * @throws {Error} If there was an error within timeoutMs timeout. + * No error is raised if zero or negative number for the timeoutMs. */ - async getStatus () { + async getStatus (timeoutMs = 0) { const noSessionProxy = new NoSessionProxy({ server: this.url.hostname, port: this.url.port, base: this.basePath, timeout: 3000, }); + + const sendGetStatus = async () => await /** @type import('@appium/types').StringRecord */ (noSessionProxy.command('/status', 'GET')); + + if (_.isNil(timeoutMs) || timeoutMs <= 0) { + try { + return await sendGetStatus(); + } catch (err) { + this.log.debug(`WDA is not listening at '${this.url.href}'. Original error:: ${err.message}`); + return null; + } + } + + let lastError = null; + let status = null; try { - return await noSessionProxy.command('/status', 'GET'); + await waitForCondition(async () => { + try { + status = await sendGetStatus(); + return true; + } catch (err) { + lastError = err; + } + return false; + }, { + waitMs: timeoutMs, + intervalMs: 300, + }); } catch (err) { - this.log.debug(`WDA is not listening at '${this.url.href}'`); - return null; + this.log.debug(`Failed to get the status endpoint in ${timeoutMs} ms. ` + + `The last error while accessing ${this.url.href}: ${lastError}. Original error:: ${err.message}.`); + throw new Error(`WDA was not ready in ${timeoutMs} ms.`); } + return status; } /** * Uninstall WDAs from the test device. * Over Xcode 11, multiple WDA can be in the device since Xcode 11 generates different WDA. * Appium does not expect multiple WDAs are running on a device. + * + * @returns {Promise} */ async uninstall () { try { @@ -287,10 +350,38 @@ class WebDriverAgent { } } + + /** + * @typedef {Object} LaunchWdaViaDeviceCtlOptions + * @property {Record} [env] environment variables for the launching WDA process + */ + + /** + * Launch WDA with preinstalled package with 'xcrun devicectl device process launch'. + * The WDA package must be prepared properly like published via + * https://github.com/appium/WebDriverAgent/releases + * with proper sign for this case. + * + * When we implement launching XCTest service via appium-ios-device, + * this implementation can be replaced with it. + * + * @param {LaunchWdaViaDeviceCtlOptions} [opts={}] launching WDA with devicectl command options. + * @return {Promise} + */ + async _launchViaDevicectl(opts = {}) { + const {env} = opts; + + await this.device.devicectl.launchApp( + this.bundleIdForXctest, { env, terminateExisting: true } + ); + } + /** * Launch WDA with preinstalled package without xcodebuild. * @param {string} sessionId Launch WDA and establish the session with this sessionId - * @return {Promise} State Object + * @return {Promise} State Object + * @throws {Error} If there was an error within timeoutMs timeout. + * No error is raised if zero or negative number for the timeoutMs. */ async launchWithPreinstalledWDA(sessionId) { const xctestEnv = { @@ -301,12 +392,38 @@ class WebDriverAgent { xctestEnv.MJPEG_SERVER_PORT = this.mjpegServerPort; } this.log.info('Launching WebDriverAgent on the device without xcodebuild'); - this.xctestApiClient = new Xctest(this.device.udid, this.bundleIdForXctest, null, {env: xctestEnv}); - - await this.xctestApiClient.start(); + if (this.isRealDevice) { + // Current method to launch WDA process can be done via 'xcrun devicectl', + // but it has limitation about the WDA preinstalled package. + // https://github.com/appium/appium/issues/19206#issuecomment-2014182674 + if (util.compareVersions(this.platformVersion, '>=', '17.0')) { + await this._launchViaDevicectl({env: xctestEnv}); + } else { + this.xctestApiClient = new Xctest(this.device.udid, this.bundleIdForXctest, null, {env: xctestEnv}); + await this.xctestApiClient.start(); + } + } else { + await this.device.simctl.exec('launch', { + args: [ + '--terminate-running-process', + this.device.udid, + this.bundleIdForXctest, + ], + env: xctestEnv, + }); + } this.setupProxies(sessionId); - const status = await this.getStatus(); + let status; + try { + status = await this.getStatus(this.wdaLaunchTimeout); + } catch (err) { + throw new Error( + `Failed to start the preinstalled WebDriverAgent in ${this.wdaLaunchTimeout} ms. ` + + `The WebDriverAgent might not be properly built or the device might be locked. ` + + `The 'appium:wdaLaunchTimeout' capability modifies the timeout.` + ); + } this.started = true; return status; } @@ -343,10 +460,7 @@ class WebDriverAgent { } if (this.usePreinstalledWDA) { - if (this.isRealDevice) { - return await this.launchWithPreinstalledWDA(sessionId); - } - throw new Error('usePreinstalledWDA is available only for a real device.'); + return await this.launchWithPreinstalledWDA(sessionId); } this.log.info('Launching WebDriverAgent on the device'); @@ -360,7 +474,7 @@ class WebDriverAgent { // useXctestrunFile and usePrebuiltWDA use existing dependencies // It depends on user side - if (this.idb || this.useXctestrunFile || (this.derivedDataPath && this.usePrebuiltWDA)) { + if (this.idb || this.useXctestrunFile || this.usePrebuiltWDA) { this.log.info('Skipped WDA project cleanup according to the provided capabilities'); } else { const synchronizationKey = path.normalize(this.bootstrapPath); @@ -387,12 +501,15 @@ class WebDriverAgent { return await this.xcodebuild.start(); } + /** + * @returns {Promise} + */ async startWithIDB () { this.log.info('Will launch WDA with idb instead of xcodebuild since the corresponding flag is enabled'); const {wdaBundleId, testBundleId} = await this.prepareWDA(); const env = { USE_PORT: this.wdaRemotePort, - WDA_PRODUCT_BUNDLE_IDENTIFIER: this.updatedWDABundleId, + WDA_PRODUCT_BUNDLE_IDENTIFIER: this.bundleIdForXctest, }; if (this.mjpegServerPort) { env.MJPEG_SERVER_PORT = this.mjpegServerPort; @@ -401,6 +518,11 @@ class WebDriverAgent { return await this.idb.runXCUITest(wdaBundleId, wdaBundleId, testBundleId, {env}); } + /** + * + * @param {string} wdaBundlePath + * @returns {Promise} + */ async parseBundleId (wdaBundlePath) { const infoPlistPath = path.join(wdaBundlePath, 'Info.plist'); const infoPlist = await plist.parsePlist(await fs.readFile(infoPlistPath)); @@ -410,6 +532,9 @@ class WebDriverAgent { return infoPlist.CFBundleIdentifier; } + /** + * @returns {Promise<{wdaBundleId: string, testBundleId: string, wdaBundlePath: string}>} + */ async prepareWDA () { const wdaBundlePath = this.wdaBundlePath || await this.fetchWDABundle(); const wdaBundleId = await this.parseBundleId(wdaBundlePath); @@ -420,9 +545,12 @@ class WebDriverAgent { return {wdaBundleId, testBundleId, wdaBundlePath}; } + /** + * @returns {Promise} + */ async fetchWDABundle () { if (!this.derivedDataPath) { - return await bundleWDASim(this.xcodebuild); + return await bundleWDASim(/** @type {XcodeBuild} */ (this.xcodebuild)); } const wdaBundlePaths = await fs.glob(`${this.derivedDataPath}/**/*${WDA_RUNNER_APP}/`, { absolute: true, @@ -433,14 +561,21 @@ class WebDriverAgent { return wdaBundlePaths[0]; } + /** + * @returns {Promise} + */ async isSourceFresh () { const existsPromises = [ 'Resources', `Resources${path.sep}WebDriverAgent.bundle`, - ].map((subPath) => fs.exists(path.resolve(this.bootstrapPath, subPath))); + ].map((subPath) => fs.exists(path.resolve(/** @type {String} */ (this.bootstrapPath), subPath))); return (await B.all(existsPromises)).some((v) => v === false); } + /** + * @param {string} sessionId + * @returns {void} + */ setupProxies (sessionId) { const proxyOpts = { log: this.log, @@ -458,12 +593,21 @@ class WebDriverAgent { this.noSessionProxy = new NoSessionProxy(proxyOpts); } + /** + * @returns {Promise} + */ async quit () { if (this.usePreinstalledWDA) { + this.log.info('Stopping the XCTest session'); if (this.xctestApiClient) { - this.log.info('Stopping the XCTest session'); this.xctestApiClient.stop(); this.xctestApiClient = null; + } else { + try { + await this.device.simctl.terminateApp(this.bundleIdForXctest); + } catch (e) { + this.log.warn(e.message); + } } } else if (!this.args.webDriverAgentUrl) { this.log.info('Shutting down sub-processes'); @@ -487,6 +631,9 @@ class WebDriverAgent { } } + /** + * @returns {import('url').UrlWithStringQuery} + */ get url () { if (!this._url) { if (this.webDriverAgentUrl) { @@ -500,30 +647,44 @@ class WebDriverAgent { return this._url; } + /** + * @param {string} _url + * @returns {void} + */ set url (_url) { this._url = url.parse(_url); } + /** + * @returns {boolean} + */ get fullyStarted () { return this.started; } + /** + * @param {boolean} started + * @returns {void}s + */ set fullyStarted (started) { this.started = started ?? false; } + /** + * @returns {Promise} + */ async retrieveDerivedDataPath () { if (this.canSkipXcodebuild) { return; } - // @ts-ignore xcodebuild should be set - return await this.xcodebuild.retrieveDerivedDataPath(); + return await /** @type {XcodeBuild} */ (this.xcodebuild).retrieveDerivedDataPath(); } /** * Reuse running WDA if it has the same bundle id with updatedWDABundleId. * Or reuse it if it has the default id without updatedWDABundleId. * Uninstall it if the method faces an exception for the above situation. + * @returns {Promise} */ async setupCaching () { const status = await this.getStatus(); @@ -565,6 +726,7 @@ class WebDriverAgent { /** * Quit and uninstall running WDA. + * @returns {Promise} */ async quitAndUninstall () { await this.quit(); @@ -573,4 +735,3 @@ class WebDriverAgent { } export default WebDriverAgent; -export { WebDriverAgent }; diff --git a/lib/xcodebuild.js b/lib/xcodebuild.js index bdd484db2..54add615e 100644 --- a/lib/xcodebuild.js +++ b/lib/xcodebuild.js @@ -10,7 +10,6 @@ import { } from './utils'; import _ from 'lodash'; import path from 'path'; -import { EOL } from 'os'; import { WDA_RUNNER_BUNDLE_ID } from './constants'; @@ -26,6 +25,13 @@ const IGNORED_ERRORS = [ ERROR_COPYING_ATTACHMENT, 'Failed to remove screenshot at path', ]; +const IGNORED_ERRORS_PATTERN = new RegExp( + '(' + + IGNORED_ERRORS + .map((errStr) => _.escapeRegExp(errStr)) + .join('|') + + ')' +); const RUNNER_SCHEME_TV = 'WebDriverAgentRunner_tvOS'; const LIB_SCHEME_TV = 'WebDriverAgentLib_tvOS'; @@ -33,15 +39,16 @@ const LIB_SCHEME_TV = 'WebDriverAgentLib_tvOS'; const xcodeLog = logger.getLogger('Xcode'); -class XcodeBuild { +export class XcodeBuild { /** @type {SubProcess} */ xcodebuild; /** - * @param {string} xcodeVersion + * @param {import('appium-xcode').XcodeVersion} xcodeVersion * @param {any} device - * @param {any} args - * @param {import('@appium/types').AppiumLogger?} log + * // TODO: make args typed + * @param {import('@appium/types').StringRecord} [args={}] + * @param {import('@appium/types').AppiumLogger?} [log=null] */ constructor (xcodeVersion, device, args = {}, log = null) { this.xcodeVersion = xcodeVersion; @@ -92,6 +99,11 @@ class XcodeBuild { this._didProcessExit = false; } + /** + * + * @param {any} noSessionProxy + * @returns {Promise} + */ async init (noSessionProxy) { this.noSessionProxy = noSessionProxy; @@ -120,6 +132,9 @@ class XcodeBuild { } } + /** + * @returns {Promise} + */ async retrieveDerivedDataPath () { if (this.derivedDataPath) { return this.derivedDataPath; @@ -154,6 +169,9 @@ class XcodeBuild { return await this._derivedDataPathPromise; } + /** + * @returns {Promise} + */ async reset () { // if necessary, reset the bundleId to original value if (this.realDevice && this.updatedWDABundleId) { @@ -161,6 +179,9 @@ class XcodeBuild { } } + /** + * @returns {Promise} + */ async prebuild () { // first do a build phase this.log.debug('Pre-building WDA before launching test'); @@ -173,6 +194,9 @@ class XcodeBuild { } } + /** + * @returns {Promise} + */ async cleanProject () { const libScheme = isTvOS(this.platformName) ? LIB_SCHEME_TV : LIB_SCHEME_IOS; const runnerScheme = isTvOS(this.platformName) ? RUNNER_SCHEME_TV : RUNNER_SCHEME_IOS; @@ -187,18 +211,24 @@ class XcodeBuild { } } + /** + * + * @param {boolean} [buildOnly=false] + * @returns {{cmd: string, args: string[]}} + */ getCommand (buildOnly = false) { - let cmd = 'xcodebuild'; - let args; + const cmd = 'xcodebuild'; + /** @type {string[]} */ + const args = []; // figure out the targets for xcodebuild const [buildCmd, testCmd] = this.useSimpleBuildTest ? ['build', 'test'] : ['build-for-testing', 'test-without-building']; if (buildOnly) { - args = [buildCmd]; + args.push(buildCmd); } else if (this.usePrebuiltWDA || this.useXctestrunFile) { - args = [testCmd]; + args.push(testCmd); } else { - args = [buildCmd, testCmd]; + args.push(buildCmd, testCmd); } if (this.allowProvisioningDeviceRegistration) { @@ -260,6 +290,10 @@ class XcodeBuild { return {cmd, args}; } + /** + * @param {boolean} [buildOnly=false] + * @returns {Promise} + */ async createSubProcess (buildOnly = false) { if (!this.useXctestrunFile && this.realDevice) { if (this.keychainPath && this.keychainPassword) { @@ -296,31 +330,35 @@ class XcodeBuild { ? `Output from xcodebuild ${this.showXcodeLog ? 'will' : 'will not'} be logged` : 'Output from xcodebuild will only be logged if any errors are present there'; this.log.debug(`${logMsg}. To change this, use 'showXcodeLog' desired capability`); - xcodebuild.on('output', (stdout, stderr) => { - let out = stdout || stderr; + const onStreamLine = (/** @type {string} */ line) => { + if (this.showXcodeLog === false || IGNORED_ERRORS_PATTERN.test(line)) { + return; + } // if we have an error we want to output the logs // otherwise the failure is inscrutible // but do not log permission errors from trying to write to attachments folder - const ignoreError = IGNORED_ERRORS.some((x) => out.includes(x)); - if (this.showXcodeLog !== false && out.includes('Error Domain=') && !ignoreError) { + if (line.includes('Error Domain=')) { logXcodeOutput = true; - // handle case where xcode returns 0 but is failing this._didBuildFail = true; } - - // do not log permission errors from trying to write to attachments folder - if (logXcodeOutput && !ignoreError) { - for (const line of out.split(EOL)) { - xcodeLog.error(line); - } + if (logXcodeOutput) { + xcodeLog.info(line); } - }); + }; + for (const streamName of ['stderr', 'stdout']) { + xcodebuild.on(`line-${streamName}`, onStreamLine); + } return xcodebuild; } + + /** + * @param {boolean} [buildOnly=false] + * @returns {Promise} + */ async start (buildOnly = false) { this.xcodebuild = await this.createSubProcess(buildOnly); @@ -356,8 +394,7 @@ class XcodeBuild { const timer = new timing.Timer().start(); await this.xcodebuild.start(true); if (!buildOnly) { - let status = await this.waitForStart(timer); - resolve(status); + resolve(/** @type {import('@appium/types').StringRecord} */ (await this.waitForStart(timer))); } } catch (err) { let msg = `Unable to start WebDriverAgent: ${err}`; @@ -368,6 +405,11 @@ class XcodeBuild { }); } + /** + * + * @param {any} timer + * @returns {Promise} + */ async waitForStart (timer) { // try to connect once every 0.5 seconds, until `launchTimeout` is up this.log.debug(`Waiting up to ${this.launchTimeout}ms for WebDriverAgent to start`); @@ -412,10 +454,12 @@ class XcodeBuild { return currentStatus; } + /** + * @returns {Promise} + */ async quit () { await killProcess('xcodebuild', this.xcodebuild); } } -export { XcodeBuild }; export default XcodeBuild; diff --git a/package.json b/package.json index c4f3cda47..922e3a73e 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { "name": "appium-webdriveragent", - "version": "6.1.1", + "version": "8.9.4", "description": "Package bundling WebDriverAgent", "main": "./build/index.js", + "types": "./build/index.d.ts", "scripts": { "build": "tsc -b", "dev": "npm run build -- --watch", @@ -11,12 +12,14 @@ "format": "prettier -w ./lib", "lint:fix": "npm run lint -- --fix", "prepare": "npm run build", + "version": "npm run sync-wda-version", "test": "mocha --exit --timeout 1m \"./test/unit/**/*-specs.js\"", "e2e-test": "mocha --exit --timeout 10m \"./test/functional/**/*-specs.js\"", "bundle": "npm run bundle:ios && npm run bundle:tv", "bundle:ios": "TARGET=runner SDK=sim node ./Scripts/build-webdriveragent.js", "bundle:tv": "TARGET=tv_runner SDK=tv_sim node ./Scripts/build-webdriveragent.js", - "fetch-prebuilt-wda": "node ./Scripts/fetch-prebuilt-wda.js" + "fetch-prebuilt-wda": "node ./Scripts/fetch-prebuilt-wda.js", + "sync-wda-version": "node ./scripts/update-wda-version.js --package-version=${npm_package_version} && git add WebDriverAgentLib/Info.plist" }, "engines": { "node": ">=14", @@ -45,7 +48,6 @@ }, "homepage": "https://github.com/appium/WebDriverAgent#readme", "devDependencies": { - "@appium/eslint-config-appium": "^8.0.4", "@appium/eslint-config-appium-ts": "^0.x", "@appium/test-support": "^3.0.0", "@appium/tsconfig": "^0.x", @@ -53,52 +55,41 @@ "@semantic-release/changelog": "^6.0.1", "@semantic-release/git": "^10.0.1", "@types/bluebird": "^3.5.38", - "@types/chai": "^4.3.5", - "@types/chai-as-promised": "^7.1.5", "@types/lodash": "^4.14.196", "@types/mocha": "^10.0.1", - "@types/node": "^20.4.7", - "@types/sinon": "^17.0.0", - "@types/sinon-chai": "^3.2.9", + "@types/node": "^22.0.0", "@types/teen_process": "^2.0.1", - "@typescript-eslint/eslint-plugin": "^6.9.0", - "@typescript-eslint/parser": "^6.9.0", "appium-xcode": "^5.0.0", - "chai": "^4.2.0", - "chai-as-promised": "^7.1.1", - "conventional-changelog-conventionalcommits": "^7.0.1", - "eslint": "^8.46.0", - "eslint-config-prettier": "^9.0.0", - "eslint-import-resolver-typescript": "^3.5.5", - "eslint-plugin-import": "^2.28.0", - "eslint-plugin-mocha": "^10.1.0", - "eslint-plugin-promise": "^6.1.1", + "chai": "^5.1.1", + "chai-as-promised": "^8.0.0", + "conventional-changelog-conventionalcommits": "^8.0.0", + "node-simctl": "^7.0.1", "mocha": "^10.0.0", "prettier": "^3.0.0", - "semantic-release": "^23.0.0", - "sinon": "^17.0.0", + "semantic-release": "^24.0.0", + "semver": "^7.3.7", + "sinon": "^19.0.1", "ts-node": "^10.9.1", - "typescript": "~5.2" + "typescript": "^5.4.2" }, "dependencies": { "@appium/base-driver": "^9.0.0", "@appium/strongbox": "^0.x", - "@appium/support": "^4.0.0", + "@appium/support": "^5.0.3", "appium-ios-device": "^2.5.0", - "appium-ios-simulator": "^5.0.1", + "appium-ios-simulator": "^6.0.0", "async-lock": "^1.0.0", "asyncbox": "^3.0.0", "axios": "^1.4.0", "bluebird": "^3.5.5", "lodash": "^4.17.11", - "node-simctl": "^7.0.1", "source-map-support": "^0.x", - "teen_process": "^2.0.0" + "teen_process": "^2.2.0" }, "files": [ - "index.js", + "index.ts", "lib", - "build/index.js", + "build/index.*", "build/lib", "Scripts/build.sh", "Scripts/fetch-prebuilt-wda.js", diff --git a/test/functional/webdriveragent-e2e-specs.js b/test/functional/webdriveragent-e2e-specs.js index e715fb6d4..3cf2862f1 100644 --- a/test/functional/webdriveragent-e2e-specs.js +++ b/test/functional/webdriveragent-e2e-specs.js @@ -1,5 +1,3 @@ -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; import Simctl from 'node-simctl'; import { getVersion } from 'appium-xcode'; import { getSimulator } from 'appium-ios-simulator'; @@ -10,15 +8,11 @@ import { retryInterval } from 'asyncbox'; import { WebDriverAgent } from '../../lib/webdriveragent'; import axios from 'axios'; -const MOCHA_TIMEOUT_MS = 60 * 1000 * 4; +const MOCHA_TIMEOUT_MS = 60 * 1000 * 5; const SIM_DEVICE_NAME = 'webDriverAgentTest'; const SIM_STARTUP_TIMEOUT_MS = MOCHA_TIMEOUT_MS; - -chai.should(); -chai.use(chaiAsPromised); - let testUrl = 'http://localhost:8100/tree'; function getStartOpts (device) { @@ -36,9 +30,16 @@ function getStartOpts (device) { describe('WebDriverAgent', function () { this.timeout(MOCHA_TIMEOUT_MS); - + let chai; let xcodeVersion; + before(async function () { + chai = await import('chai'); + const chaiAsPromised = await import('chai-as-promised'); + + chai.should(); + chai.use(chaiAsPromised.default); + // Don't do these tests on Sauce Labs if (process.env.CLOUD) { this.skip(); @@ -58,6 +59,15 @@ describe('WebDriverAgent', function () { PLATFORM_VERSION ); device = await getSimulator(simctl.udid); + + // Prebuild WDA + const wda = new WebDriverAgent(xcodeVersion, { + iosSdkVersion: PLATFORM_VERSION, + platformVersion: PLATFORM_VERSION, + showXcodeLog: true, + device, + }); + await wda.xcodebuild.start(true); }); after(async function () { diff --git a/test/unit/utils-specs.js b/test/unit/utils-specs.js index 1d61b4854..c8002ea43 100644 --- a/test/unit/utils-specs.js +++ b/test/unit/utils-specs.js @@ -1,16 +1,26 @@ import { getXctestrunFilePath, getAdditionalRunContent, getXctestrunFileName } from '../../lib/utils'; import { PLATFORM_NAME_IOS, PLATFORM_NAME_TVOS } from '../../lib/constants'; -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; import { withMocks } from '@appium/test-support'; import { fs } from '@appium/support'; import path from 'path'; import { fail } from 'assert'; +import { arch } from 'os'; -chai.should(); -chai.use(chaiAsPromised); +function get_arch() { + return arch() === 'arm64' ? 'arm64' : 'x86_64'; +} describe('utils', function () { + let chai; + + before(async function() { + chai = await import('chai'); + const chaiAsPromised = await import('chai-as-promised'); + + chai.should(); + chai.use(chaiAsPromised.default); + }); + describe('#getXctestrunFilePath', withMocks({fs}, function (mocks) { const platformVersion = '12.0'; const sdkVersion = '12.2'; @@ -56,7 +66,7 @@ describe('utils', function () { .withExactArgs(path.resolve(`${bootstrapPath}/${udid}_${sdkVersion}.xctestrun`)) .returns(false); mocks.fs.expects('exists') - .withExactArgs(path.resolve(`${bootstrapPath}/WebDriverAgentRunner_iphonesimulator${sdkVersion}-x86_64.xctestrun`)) + .withExactArgs(path.resolve(`${bootstrapPath}/WebDriverAgentRunner_iphonesimulator${sdkVersion}-${get_arch()}.xctestrun`)) .returns(false); mocks.fs.expects('exists') .withExactArgs(path.resolve(`${bootstrapPath}/${udid}_${platformVersion}.xctestrun`)) @@ -73,17 +83,17 @@ describe('utils', function () { .withExactArgs(path.resolve(`${bootstrapPath}/${udid}_${sdkVersion}.xctestrun`)) .returns(false); mocks.fs.expects('exists') - .withExactArgs(path.resolve(`${bootstrapPath}/WebDriverAgentRunner_iphonesimulator${sdkVersion}-x86_64.xctestrun`)) + .withExactArgs(path.resolve(`${bootstrapPath}/WebDriverAgentRunner_iphonesimulator${sdkVersion}-${get_arch()}.xctestrun`)) .returns(false); mocks.fs.expects('exists') .withExactArgs(path.resolve(`${bootstrapPath}/${udid}_${platformVersion}.xctestrun`)) .returns(false); mocks.fs.expects('exists') - .withExactArgs(path.resolve(`${bootstrapPath}/WebDriverAgentRunner_iphonesimulator${platformVersion}-x86_64.xctestrun`)) + .withExactArgs(path.resolve(`${bootstrapPath}/WebDriverAgentRunner_iphonesimulator${platformVersion}-${get_arch()}.xctestrun`)) .returns(true); mocks.fs.expects('copyFile') .withExactArgs( - path.resolve(`${bootstrapPath}/WebDriverAgentRunner_iphonesimulator${platformVersion}-x86_64.xctestrun`), + path.resolve(`${bootstrapPath}/WebDriverAgentRunner_iphonesimulator${platformVersion}-${get_arch()}.xctestrun`), path.resolve(`${bootstrapPath}/${udid}_${platformVersion}.xctestrun`) ) .returns(true); @@ -94,7 +104,7 @@ describe('utils', function () { }); it('should raise an exception because of no files', async function () { - const expected = path.resolve(`${bootstrapPath}/WebDriverAgentRunner_iphonesimulator${sdkVersion}-x86_64.xctestrun`); + const expected = path.resolve(`${bootstrapPath}/WebDriverAgentRunner_iphonesimulator${sdkVersion}-${get_arch()}.xctestrun`); mocks.fs.expects('exists').exactly(4).returns(false); const deviceInfo = {isRealDevice: false, udid, platformVersion}; @@ -140,7 +150,7 @@ describe('utils', function () { const deviceInfo = {isRealDevice: false, udid, platformVersion, platformName}; getXctestrunFileName(deviceInfo, '10.2.0').should.equal( - 'WebDriverAgentRunner_iphonesimulator10.2.0-x86_64.xctestrun'); + `WebDriverAgentRunner_iphonesimulator10.2.0-${get_arch()}.xctestrun`); }); it('should return tvos format, real device', function () { @@ -156,7 +166,7 @@ describe('utils', function () { const deviceInfo = {isRealDevice: false, udid, platformVersion, platformName}; getXctestrunFileName(deviceInfo, '10.2.0').should.equal( - 'WebDriverAgentRunner_tvOS_appletvsimulator10.2.0-x86_64.xctestrun'); + `WebDriverAgentRunner_tvOS_appletvsimulator10.2.0-${get_arch()}.xctestrun`); }); }); }); diff --git a/test/unit/webdriveragent-specs.js b/test/unit/webdriveragent-specs.js index 6eac94c5b..1b844e08a 100644 --- a/test/unit/webdriveragent-specs.js +++ b/test/unit/webdriveragent-specs.js @@ -1,16 +1,10 @@ import { BOOTSTRAP_PATH } from '../../lib/utils'; import { WebDriverAgent } from '../../lib/webdriveragent'; import * as utils from '../../lib/utils'; -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; import path from 'path'; import _ from 'lodash'; import sinon from 'sinon'; - -chai.should(); -chai.use(chaiAsPromised); - const fakeConstructorArgs = { device: 'some sim', platformVersion: '9', @@ -25,6 +19,16 @@ const customAgentPath = '/path/to/some/agent/WebDriverAgent.xcodeproj'; const customDerivedDataPath = '/path/to/some/agent/DerivedData/'; describe('Constructor', function () { + let chai; + + before(async function() { + chai = await import('chai'); + const chaiAsPromised = await import('chai-as-promised'); + + chai.should(); + chai.use(chaiAsPromised.default); + }); + it('should have a default wda agent if not specified', function () { let agent = new WebDriverAgent({}, fakeConstructorArgs); agent.bootstrapPath.should.eql(BOOTSTRAP_PATH); @@ -340,3 +344,45 @@ describe('setupCaching()', function () { }); }); }); + + +describe('usePreinstalledWDA related functions', function () { + describe('bundleIdForXctest', function () { + it('should have xctrunner automatically', function () { + const args = Object.assign({}, fakeConstructorArgs); + args.updatedWDABundleId = 'io.appium.wda'; + const agent = new WebDriverAgent({}, args); + agent.bundleIdForXctest.should.equal('io.appium.wda.xctrunner'); + }); + + it('should have xctrunner automatically with default bundle id', function () { + const args = Object.assign({}, fakeConstructorArgs); + const agent = new WebDriverAgent({}, args); + agent.bundleIdForXctest.should.equal('com.facebook.WebDriverAgentRunner.xctrunner'); + }); + + it('should allow an empty string as xctrunner suffix', function () { + const args = Object.assign({}, fakeConstructorArgs); + args.updatedWDABundleId = 'io.appium.wda'; + args.updatedWDABundleIdSuffix = ''; + const agent = new WebDriverAgent({}, args); + agent.bundleIdForXctest.should.equal('io.appium.wda'); + }); + + it('should allow an empty string as xctrunner suffix with default bundle id', function () { + const args = Object.assign({}, fakeConstructorArgs); + args.updatedWDABundleIdSuffix = ''; + const agent = new WebDriverAgent({}, args); + agent.bundleIdForXctest.should.equal('com.facebook.WebDriverAgentRunner'); + }); + + it('should have an arbitrary xctrunner suffix', function () { + const args = Object.assign({}, fakeConstructorArgs); + args.updatedWDABundleId = 'io.appium.wda'; + args.updatedWDABundleIdSuffix = '.customsuffix'; + const agent = new WebDriverAgent({}, args); + agent.bundleIdForXctest.should.equal('io.appium.wda.customsuffix'); + }); + + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 33d83ef45..e7becfe3b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,12 +3,13 @@ "extends": "@appium/tsconfig/tsconfig.json", "compilerOptions": { "strict": false, // TODO: make this flag true + "esModuleInterop": true, "outDir": "build", "types": ["node"], "checkJs": true }, "include": [ - "index.js", + "index.ts", "lib" ] }