Repository: SonicCloudOrg/sonic-driver-core Branch: main Commit: d8d310a20ccd Files: 60 Total size: 251.5 KB Directory structure: gitextract_j_5ezhz9/ ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ ├── maven.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── README_CN.md ├── pom.xml └── src/ ├── main/ │ └── java/ │ └── org/ │ └── cloud/ │ └── sonic/ │ └── driver/ │ ├── android/ │ │ ├── AndroidDriver.java │ │ ├── enmus/ │ │ │ └── AndroidSelector.java │ │ └── service/ │ │ ├── AndroidElement.java │ │ ├── UiaClient.java │ │ └── impl/ │ │ ├── AndroidElementImpl.java │ │ └── UiaClientImpl.java │ ├── common/ │ │ ├── enums/ │ │ │ └── PasteboardType.java │ │ ├── models/ │ │ │ ├── BaseElement.java │ │ │ ├── BaseResp.java │ │ │ ├── Capabilities.java │ │ │ ├── ElementRect.java │ │ │ ├── ErrorMsg.java │ │ │ ├── SessionInfo.java │ │ │ └── WindowSize.java │ │ └── tool/ │ │ ├── Logger.java │ │ ├── RespHandler.java │ │ └── SonicRespException.java │ ├── ios/ │ │ ├── IOSDriver.java │ │ ├── enums/ │ │ │ ├── ActionType.java │ │ │ ├── AuthResource.java │ │ │ ├── IOSSelector.java │ │ │ ├── Orientation.java │ │ │ ├── SystemButton.java │ │ │ ├── TextKey.java │ │ │ └── XCUIElementType.java │ │ ├── models/ │ │ │ └── TouchActions.java │ │ └── service/ │ │ ├── IOSElement.java │ │ ├── WdaClient.java │ │ └── impl/ │ │ ├── IOSElementImpl.java │ │ └── WdaClientImpl.java │ └── poco/ │ ├── PocoDriver.java │ ├── enums/ │ │ ├── PocoEngine.java │ │ └── PocoSelector.java │ ├── models/ │ │ ├── PocoElement.java │ │ └── RootElement.java │ ├── service/ │ │ ├── PocoClient.java │ │ ├── PocoConnection.java │ │ └── impl/ │ │ ├── PocoClientImpl.java │ │ ├── SocketClientImpl.java │ │ └── WebSocketClientImpl.java │ └── util/ │ ├── PocoJsonToXml.java │ ├── PocoTool.java │ └── PocoXYTransformer.java └── test/ └── java/ └── org/ └── cloud/ └── sonic/ └── driver/ ├── android/ │ ├── AndroidDriverTest.java │ └── service/ │ └── UiaClientTest.java ├── common/ │ └── tool/ │ ├── RespHandlerTest.java │ └── SonicRespExceptionTest.java ├── ios/ │ ├── IOSDriverTest.java │ └── service/ │ └── WdaClientTest.java └── poco/ ├── PocoDriverTest.java ├── PocoJsonToXmlTest.java └── PocoXYTransformerTest.java ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "maven" directory: "/" schedule: interval: "weekly" ================================================ FILE: .github/workflows/maven.yml ================================================ name: maven compile test on: [pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up JDK 17 uses: actions/setup-java@v2 with: java-version: '17' distribution: 'temurin' cache: maven - name: Validate and Compile with Maven run: mvn validate compile ================================================ FILE: .github/workflows/release.yml ================================================ # name: release to github # on: # push: # tags: # - "*.*.*" # jobs: # doc: # runs-on: ubuntu-latest # steps: # - uses: actions/checkout@v2 # with: # fetch-depth: 0 # - name: 'Get Previous tag' # id: previoustag # uses: "WyriHaximus/github-action-get-previous-tag@v1" # - name: 'Get previous release tag' # id: tag # uses: "sammcoe/get-previous-release-action@v1" # - name: replace version # run: sed -i "s/${{steps.tag.outputs.tag}}/${{steps.previoustag.outputs.tag}}/g" README*.md # - name: Create Pull Request # id: cpr # uses: peter-evans/create-pull-request@v4 # with: # commit-message: Update README file. # author: GitHub # signoff: false # branch: doc/${{steps.previoustag.outputs.tag}} # labels: document # base: main # delete-branch: true # title: 'doc: update README files version to ${{steps.previoustag.outputs.tag}}' # body: | # **在提出此拉取请求时,我确认了以下几点(保存后请点击复选框):** # - [x] 标题为fix、feat或doc开头 # - [x] 我已检查没有与此请求重复的拉取请求。 # - [x] 我已经考虑过,并确认这份呈件对其他人很有价值。 # - [x] 我接受此提交可能不会被使用,并根据维护人员的意愿关闭拉取请求。 # **填写PR内容:** # - Update README files version to latest tag by bot 🚀. # draft: false # - name: Auto approve # if: steps.cpr.outputs.pull-request-operation == 'created' # uses: juliangruber/approve-pull-request-action@v1 # with: # github-token: ${{ secrets.PAT }} # number: ${{ steps.cpr.outputs.pull-request-number }} # - id: automerge # name: automerge # uses: "pascalgn/automerge-action@v0.15.3" # env: # MERGE_LABELS: "document" # MERGE_DELETE_BRANCH: true # GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" # PULL_REQUEST: ${{ steps.cpr.outputs.pull-request-number }} # MERGE_RETRIES: 18 # MERGE_RETRY_SLEEP: 10000 # release: # needs: doc # runs-on: ubuntu-latest # steps: # - uses: actions/checkout@v2 # with: # fetch-depth: 0 # - name: 'Get Previous tag' # id: previoustag # uses: "WyriHaximus/github-action-get-previous-tag@v1" # - name: replace version # run: sed -i "s/SONIC_VERSION/${{ steps.previoustag.outputs.tag }}/g" pom.xml # - name: Set up Maven Central Repo # uses: actions/setup-java@v1 # with: # java-version: 1.8 # server-id: sonatype-nexus-staging # server-username: ${{ secrets.OSSRH_USER }} # server-password: ${{ secrets.OSSRH_PASSWORD }} # gpg-passphrase: ${{ secrets.GPG_PASSWORD }} # - name: Release Maven package # uses: WasiqB/maven-publish-action@v1 # with: # maven_args: -Dmaven.test.skip=true # gpg_private_key: ${{ secrets.GPG_SECRET }} # gpg_passphrase: ${{ secrets.GPG_PASSWORD }} # nexus_username: ${{ secrets.OSSRH_USER }} # nexus_password: ${{ secrets.OSSRH_PASSWORD }} # - uses: softprops/action-gh-release@v1 # with: # draft: false # generate_release_notes: true # # mvn clean deploy -Pdeploy -Dmaven.test.skip=true ================================================ FILE: .gitignore ================================================ /.idea/ /target/ /logs/ *.iml */.DS_Store .DS_Store ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright © [SonicCloudOrg] Sonic Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================

🎉The Sonic UIAutomation Driver Core

English | 简体中文

## What is sonic-driver-core? sonic-driver-core can be separated from appium and interact directly with webdriveragent or uiautomator2, which reduces the communication layer of appium and makes the test faster and more stable. ## Use in Java code ### Add dependency #### Maven Central ```xml io.github.soniccloudorg sonic-driver-core 1.1.30 ``` #### Gradle ``` implementation 'io.github.soniccloudorg:sonic-driver-core:1.1.30' ``` ### Code ```java package org.cloud.sonic.driver.ios; import org.cloud.sonic.driver.ios.enums.IOSSelector; import org.cloud.sonic.driver.common.tool.SonicRespException; public class MyTest { public void test() throws SonicRespException { IOSDriver iosDriver = new IOSDriver("http://localhost:8100"); iosDriver.showLog(); //touch iosDriver.swipe(100, 256, 50, 256); iosDriver.tap(150, 81); iosDriver.longPress(150, 281, 1500); iosDriver.performTouchAction(new TouchActions().press(50, 256).wait(50).move(100, 256).wait(10).release()); //element iosDriver.findElement(IOSSelector.XPATH, "//XCUIElementTypeTextField").click(); //more... } } ``` ## More Example See [Here](https://github.com/SonicCloudOrg/sonic-uiautomation-example/tree/main/java-example). ## Document See [Here](https://sonic-cloud.cn/sdc/re-sdc.html). ## Sponsors Thank you to all our sponsors! [霍格沃兹测试开发学社](https://qrcode.testing-studio.com/f?from=sonic&url=https://ceshiren.com) > [霍格沃兹测试开发学社](https://qrcode.testing-studio.com/f?from=sonic&url=https://ceshiren.com)是业界领先的测试开发技术高端教育品牌,隶属于[测吧(北京)科技有限公司](http://qrcode.testing-studio.com/f?from=sonic&url=https://www.testing-studio.com) 。学院课程由一线大厂测试经理与资深测试开发专家参与研发,实战驱动。课程涵盖 web/app 自动化测试、接口测试、性能测试、安全测试、持续集成/持续交付/DevOps,测试左移&右移、精准测试、测试平台开发、测试管理等内容,帮助测试工程师实现测试开发技术转型。通过优秀的学社制度(奖学金、内推返学费、行业竞赛等多种方式)来实现学员、学社及用人企业的三方共赢。[进入测试开发技术能力测评!](https://qrcode.testing-studio.com/f?from=sonic&url=https://ceshiren.com/t/topic/14940) ## LICENSE [License](LICENSE) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FSonicCloudOrg%2Fsonic-driver-core.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FSonicCloudOrg%2Fsonic-driver-core?ref=badge_large) ================================================ FILE: README_CN.md ================================================

🎉Sonic UI自动化Driver核心

English | 简体中文

## sonic-driver-core是什么? sonic-driver-core可以脱离Appium,直接与WebDriverAgent或UIautomator2交互,减少了Appium的通信层,让测试更快更稳定。 ## 在你的Java代码中使用 ### 引用库 #### Maven ```xml io.github.soniccloudorg sonic-driver-core 1.1.30 ``` #### Gradle ``` implementation 'io.github.soniccloudorg:sonic-driver-core:1.1.30' ``` ### 代码 ```java package org.cloud.sonic.driver.ios; import org.cloud.sonic.driver.common.tool.SonicRespException; public class MyTest { public void test() throws SonicRespException { IOSDriver iosDriver = new IOSDriver("http://localhost:8100"); iosDriver.showLog(); //touch iosDriver.swipe(100, 256, 50, 256); iosDriver.tap(150, 81); iosDriver.longPress(150, 281, 1500); iosDriver.performTouchAction(new TouchActions().press(50, 256).wait(50).move(100, 256).wait(10).release()); //element iosDriver.findElement(IOSSelector.XPATH, "//XCUIElementTypeTextField").click(); //更多... } } ``` ## 更多例子 查看 [这里](https://github.com/SonicCloudOrg/sonic-uiautomation-example/tree/main/java-example). ## 文档 查看 [这里](https://sonic-cloud.cn/sdc/re-sdc.html). ## 赞助商 感谢所有赞助商! [霍格沃兹测试开发学社](https://qrcode.testing-studio.com/f?from=sonic&url=https://ceshiren.com) > [霍格沃兹测试开发学社](https://qrcode.testing-studio.com/f?from=sonic&url=https://ceshiren.com)是业界领先的测试开发技术高端教育品牌,隶属于[测吧(北京)科技有限公司](http://qrcode.testing-studio.com/f?from=sonic&url=https://www.testing-studio.com) 。学院课程由一线大厂测试经理与资深测试开发专家参与研发,实战驱动。课程涵盖 web/app 自动化测试、接口测试、性能测试、安全测试、持续集成/持续交付/DevOps,测试左移&右移、精准测试、测试平台开发、测试管理等内容,帮助测试工程师实现测试开发技术转型。通过优秀的学社制度(奖学金、内推返学费、行业竞赛等多种方式)来实现学员、学社及用人企业的三方共赢。[进入测试开发技术能力测评!](https://qrcode.testing-studio.com/f?from=sonic&url=https://ceshiren.com/t/topic/14940) ## 开源许可协议 [License](LICENSE) ================================================ FILE: pom.xml ================================================ 4.0.0 io.github.soniccloudorg sonic-driver-core 1.1.33 sonic-driver-core The Sonic Project UIAutomation Driver Core for Android, iOS, Windows, Mac and so on. https://github.com/SonicCloudOrg/sonic-driver-core 8 8 UTF-8 UTF-8 main https://github.com/SonicCloudOrg/sonic-driver-core https://github.com/SonicCloudOrg/sonic-driver-core https://github.com/SonicCloudOrg/sonic-driver-core APACHE2 https://github.com/SonicCloudOrg/sonic-driver-core/blob/main/LICENSE Github Issue https://github.com/SonicCloudOrg/sonic-driver-core/issues ossrh https://s01.oss.sonatype.org/content/repositories/snapshots ossrh https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ SonicCloudOrg soniccloudorg@163.com +8 Developer org.projectlombok lombok 1.18.36 compile cn.hutool hutool-http 5.8.5 org.apache.commons commons-lang3 3.17.0 com.alibaba fastjson 2.0.56 org.java-websocket Java-WebSocket 1.5.4 junit junit 4.13.2 test org.mockito mockito-core 4.6.1 test org.jsoup jsoup 1.15.4 deploy org.apache.maven.plugins maven-source-plugin 2.4 attach-sources jar-no-fork org.apache.maven.plugins maven-javadoc-plugin 2.10.4 -Xdoclint:none attach-javadocs jar org.apache.maven.plugins maven-gpg-plugin 1.6 sign-artifacts verify sign --pinentry-mode loopback org.codehaus.mojo cobertura-maven-plugin 2.7 html xml org.sonatype.plugins nexus-staging-maven-plugin 1.6.8 true ossrh https://s01.oss.sonatype.org/ true ================================================ FILE: src/main/java/org/cloud/sonic/driver/android/AndroidDriver.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.android; import com.alibaba.fastjson.JSONObject; import org.cloud.sonic.driver.android.enmus.AndroidSelector; import org.cloud.sonic.driver.android.service.AndroidElement; import org.cloud.sonic.driver.android.service.UiaClient; import org.cloud.sonic.driver.android.service.impl.AndroidElementImpl; import org.cloud.sonic.driver.android.service.impl.UiaClientImpl; import org.cloud.sonic.driver.common.enums.PasteboardType; import org.cloud.sonic.driver.common.models.WindowSize; import org.cloud.sonic.driver.common.tool.RespHandler; import org.cloud.sonic.driver.common.tool.SonicRespException; import java.util.List; /** * @author Eason * android driver */ public class AndroidDriver { private UiaClient uiaClient; /** * Init android driver. * * @param url * @throws SonicRespException */ public AndroidDriver(String url) throws SonicRespException { this(url, RespHandler.DEFAULT_REQUEST_TIMEOUT); } /** * Init android driver. * * @param url * @param timeOut * @throws SonicRespException */ public AndroidDriver(String url, int timeOut) throws SonicRespException { this(url, timeOut, new JSONObject()); } /** * Init android driver. * * @param url * @param cap * @throws SonicRespException */ public AndroidDriver(String url, JSONObject cap) throws SonicRespException { this(url, RespHandler.DEFAULT_REQUEST_TIMEOUT, cap); } /** * Init android driver. * * @param url * @param timeOut * @param cap * @throws SonicRespException */ public AndroidDriver(String url, int timeOut, JSONObject cap) throws SonicRespException { uiaClient = new UiaClientImpl(); uiaClient.setRemoteUrl(url); uiaClient.setGlobalTimeOut(timeOut); uiaClient.newSession(cap); } /** * Get uia2 client. * * @return */ public UiaClient getUiaClient() { return uiaClient; } /** * get uia2 sessionId. * * @return */ public String getSessionId() { return uiaClient.getSessionId(); } /** * destroy sessionId. * * @throws SonicRespException */ public void closeDriver() throws SonicRespException { uiaClient.closeSession(); } /** * show log. */ public void showLog() { uiaClient.showLog(); } /** * disable log. */ public void disableLog() { uiaClient.disableLog(); } /** * get device window size. * * @return * @throws SonicRespException */ public WindowSize getWindowSize() throws SonicRespException { return uiaClient.getWindowSize(); } /** * send key without element. * * @param text * @throws SonicRespException */ public void sendKeys(String text) throws SonicRespException { sendKeys(text, false); } /** * send key without element. * * @param text * @param isCover * @throws SonicRespException */ public void sendKeys(String text, boolean isCover) throws SonicRespException { uiaClient.sendKeys(text, isCover); } /** * set pasteboard. * * @param contentType * @param content * @throws SonicRespException */ public void setPasteboard(String contentType, String content) throws SonicRespException { uiaClient.setPasteboard(contentType, content); } /** * set pasteboard. * * @param pasteboardType * @param content * @throws SonicRespException */ public void setPasteboard(PasteboardType pasteboardType, String content) throws SonicRespException { setPasteboard(pasteboardType.getType(), content); } /** * get pasteboard. * * @param contentType * @return * @throws SonicRespException */ public byte[] getPasteboard(String contentType) throws SonicRespException { return uiaClient.getPasteboard(contentType); } /** * get pasteboard. * * @param pasteboardType * @return * @throws SonicRespException */ public byte[] getPasteboard(PasteboardType pasteboardType) throws SonicRespException { return getPasteboard(pasteboardType.getType()); } /** * get page source. * * @return * @throws SonicRespException */ public String getPageSource() throws SonicRespException { return uiaClient.pageSource(); } /** * set default FindElement retry time and interval. * * @param retry * @param interval */ public void setDefaultFindElementInterval(Integer retry, Integer interval) { uiaClient.setDefaultFindElementInterval(retry, interval); } /** * find element in device. * * @param androidSelector * @param value * @return * @throws SonicRespException */ public AndroidElement findElement(AndroidSelector androidSelector, String value) throws SonicRespException { return findElement(androidSelector, value, null); } /** * find element in device. * * @param uiaElementID This ID is the id returned by uia after finding the control * @return * @throws SonicRespException */ public AndroidElement findElement(String uiaElementID) throws SonicRespException { return new AndroidElementImpl(uiaElementID, uiaClient); } /** * find element in device. * * @param selector * @param value * @return * @throws SonicRespException */ public AndroidElement findElement(String selector, String value) throws SonicRespException { return findElement(selector, value, null); } /** * find element in device. * * @param androidSelector * @param value * @param retry * @return * @throws SonicRespException */ public AndroidElement findElement(AndroidSelector androidSelector, String value, Integer retry) throws SonicRespException { return findElement(androidSelector, value, retry, null); } /** * find element in device. * * @param selector * @param value * @param retry * @return * @throws SonicRespException */ public AndroidElement findElement(String selector, String value, Integer retry) throws SonicRespException { return findElement(selector, value, retry, null); } /** * find element in device. * * @param androidSelector * @param value * @param retry * @param interval * @return * @throws SonicRespException */ public AndroidElement findElement(AndroidSelector androidSelector, String value, Integer retry, Integer interval) throws SonicRespException { return findElement(androidSelector.getSelector(), value, retry, interval); } /** * find element in device. * * @param selector * @param value * @param retry * @param interval * @return * @throws SonicRespException */ public AndroidElement findElement(String selector, String value, Integer retry, Integer interval) throws SonicRespException { return uiaClient.findElement(selector, value, retry, interval); } /** * find element list in device. * * @param androidSelector * @param value * @return * @throws SonicRespException */ public List findElementList(AndroidSelector androidSelector, String value) throws SonicRespException { return findElementList(androidSelector, value, null); } /** * find element list in device. * * @param selector * @param value * @return * @throws SonicRespException */ public List findElementList(String selector, String value) throws SonicRespException { return findElementList(selector, value, null); } /** * find element list in device. * * @param androidSelector * @param value * @param retry * @return * @throws SonicRespException */ public List findElementList(AndroidSelector androidSelector, String value, Integer retry) throws SonicRespException { return findElementList(androidSelector, value, retry, null); } /** * find element list in device. * * @param selector * @param value * @param retry * @return * @throws SonicRespException */ public List findElementList(String selector, String value, Integer retry) throws SonicRespException { return findElementList(selector, value, retry, null); } /** * find element list in device. * * @param androidSelector * @param value * @param retry * @param interval * @return * @throws SonicRespException */ public List findElementList(AndroidSelector androidSelector, String value, Integer retry, Integer interval) throws SonicRespException { return findElementList(androidSelector.getSelector(), value, retry, interval); } /** * find element list in device. * * @param selector * @param value * @param retry * @param interval * @return * @throws SonicRespException */ public List findElementList(String selector, String value, Integer retry, Integer interval) throws SonicRespException { return uiaClient.findElementList(selector, value, retry, interval); } /** * get screenshot. * * @return * @throws SonicRespException */ public byte[] screenshot() throws SonicRespException { return uiaClient.screenshot(); } /** * set appium settings. * * @param settings * @throws SonicRespException */ public void setAppiumSettings(JSONObject settings) throws SonicRespException { uiaClient.setAppiumSettings(settings); } /** * tap position on screen. * * @param x * @param y * @throws SonicRespException */ public void tap(int x, int y) throws SonicRespException { uiaClient.tap(x, y); } /** * long press position on screen. * * @param x * @param y * @param ms * @throws SonicRespException */ public void longPress(double x, double y, double ms) throws SonicRespException { uiaClient.longPress(x, y, ms); } /** * swipe position on screen. * * @param fromX * @param fromY * @param toX * @param toY * @throws SonicRespException */ public void swipe(int fromX, int fromY, int toX, int toY) throws SonicRespException { this.swipe(fromX, fromY, toX, toY, null); } /** * swipe position on screen with target time * * @param fromX * @param fromY * @param toX * @param toY * @param duration * @throws SonicRespException */ public void swipe(int fromX, int fromY, int toX, int toY, Integer duration) throws SonicRespException { uiaClient.swipe(fromX, fromY, toX, toY, duration); } /** * Performs a long press followed by an immediate drag to a location and releases. * fromX(Y) are required if elementId is not provided, so do toX(Y) if destElId is not provided. * * @param fromX Starting X coordinate * @param fromY Starting Y coordinate * @param toX Ending X coordinate * @param toY Ending Y coordinate * @param duration Duration of the action in milliseconds * @param elementId ID of the original element (optional), for specific interaction scenarios * @param destElId ID of the target element (optional), for specific interaction scenarios * @throws SonicRespException Throws when the operation fails */ public void drag(int fromX, int fromY, int toX, int toY, Integer duration, String elementId, String destElId) throws SonicRespException { uiaClient.drag(fromX, fromY, toX, toY, duration, elementId, destElId); } /** * Performs a touch action. * This method delegates to the UIA client's touchAction method to simulate * a touch event at a specified position on the screen with a given action type. * * @param methodType The type of touch action, enumerate in (down, up, move). * Specific supported types depend on the UIA client's implementation. * @param x The X coordinate of the touch point. * @param y The Y coordinate of the touch point. * @throws SonicRespException If an error occurs while performing the touch action, * this exception is thrown. */ public void touchAction(String methodType, int x, int y) throws SonicRespException { uiaClient.touchAction(methodType, x, y); } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/android/enmus/AndroidSelector.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.android.enmus; public enum AndroidSelector { CLASS_NAME("class name"), Id("id"), ACCESSIBILITY_ID("accessibility id"), XPATH("xpath"), UIAUTOMATOR("-android uiautomator"); private final String selector; AndroidSelector(String selector) { this.selector = selector; } public String getSelector() { return selector; } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/android/service/AndroidElement.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.android.service; import org.cloud.sonic.driver.common.models.BaseElement; import org.cloud.sonic.driver.common.tool.SonicRespException; /** * @author Eason * web element interface */ public interface AndroidElement extends BaseElement { void click() throws SonicRespException; void sendKeys(String text) throws SonicRespException; void sendKeys(String text, boolean isCover) throws SonicRespException; void clear() throws SonicRespException; String getText() throws SonicRespException; byte[] screenshot() throws SonicRespException; } ================================================ FILE: src/main/java/org/cloud/sonic/driver/android/service/UiaClient.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.android.service; import com.alibaba.fastjson.JSONObject; import org.cloud.sonic.driver.common.models.WindowSize; import org.cloud.sonic.driver.common.tool.Logger; import org.cloud.sonic.driver.common.tool.RespHandler; import org.cloud.sonic.driver.common.tool.SonicRespException; import java.util.List; /** * @author Eason * uia client interface */ public interface UiaClient { //Client Setting void setGlobalTimeOut(int timeOut); RespHandler getRespHandler(); void setRespHandler(RespHandler respHandler); Logger getLogger(); void showLog(); void disableLog(); //Session handler. String getRemoteUrl(); void setRemoteUrl(String remoteUrl); String getSessionId(); void setSessionId(String sessionId); void newSession(JSONObject capabilities) throws SonicRespException; void closeSession() throws SonicRespException; void checkSessionId() throws SonicRespException; //window handler. WindowSize getWindowSize() throws SonicRespException; //keyboard handler. void sendKeys(String text, boolean isCover) throws SonicRespException; void setPasteboard(String contentType, String content) throws SonicRespException; byte[] getPasteboard(String contentType) throws SonicRespException; //source handler. String pageSource() throws SonicRespException; //element handler. void setDefaultFindElementInterval(Integer retry, Integer interval); AndroidElement findElement(String selector, String value, Integer retry, Integer interval) throws SonicRespException; List findElementList(String selector, String value, Integer retry, Integer interval) throws SonicRespException; //screen handler. byte[] screenshot() throws SonicRespException; //appium setting handler. void setAppiumSettings(JSONObject settings) throws SonicRespException; void tap(int x, int y) throws SonicRespException; void longPress(double x, double y, double ms) throws SonicRespException; void swipe(int fromX, int fromY, int toX, int toY, Integer duration) throws SonicRespException; void drag(int fromX, int fromY, int toX, int toY, Integer duration, String elementId, String destElId) throws SonicRespException; // touch handler. void touchAction(String methodType, int x, int y) throws SonicRespException; } ================================================ FILE: src/main/java/org/cloud/sonic/driver/android/service/impl/AndroidElementImpl.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.android.service.impl; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson2.JSON; import org.cloud.sonic.driver.android.service.AndroidElement; import org.cloud.sonic.driver.android.service.UiaClient; import org.cloud.sonic.driver.common.models.BaseResp; import org.cloud.sonic.driver.common.models.ElementRect; import org.cloud.sonic.driver.common.tool.Logger; import org.cloud.sonic.driver.common.tool.SonicRespException; import java.util.Base64; public class AndroidElementImpl implements AndroidElement { private String id; private UiaClient uiaClient; private Logger logger; public AndroidElementImpl(String id, UiaClient uiaClient) { this.id = id; this.uiaClient = uiaClient; logger = uiaClient.getLogger(); } @Override public void click() throws SonicRespException { uiaClient.checkSessionId(); BaseResp b = uiaClient.getRespHandler().getResp( HttpUtil.createPost(uiaClient.getRemoteUrl() + "/session/" + uiaClient.getSessionId() + "/element/" + id + "/click")); if (b.getErr() == null) { logger.info("click element %s.", id); } else { logger.error("click element %s failed.", id); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void sendKeys(String text) throws SonicRespException { sendKeys(text, false); } @Override public void sendKeys(String text, boolean isCover) throws SonicRespException { uiaClient.checkSessionId(); JSONObject data = new JSONObject(); data.put("text", text); data.put("replace", isCover); BaseResp b = uiaClient.getRespHandler().getResp( HttpUtil.createPost(uiaClient.getRemoteUrl() + "/session/" + uiaClient.getSessionId() + "/element/" + id + "/value") .body(data.toJSONString()), 60000); if (b.getErr() == null) { logger.info("send key to %s.", id); } else { logger.error("send key to %s failed.", id); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void clear() throws SonicRespException { uiaClient.checkSessionId(); BaseResp b = uiaClient.getRespHandler().getResp( HttpUtil.createPost(uiaClient.getRemoteUrl() + "/session/" + uiaClient.getSessionId() + "/element/" + id + "/clear"), 60000); if (b.getErr() == null) { logger.info("clear %s.", id); } else { logger.error("clear %s failed.", id); throw new SonicRespException(b.getErr().getMessage()); } } @Override public String getText() throws SonicRespException { uiaClient.checkSessionId(); BaseResp b = uiaClient.getRespHandler().getResp( HttpUtil.createGet(uiaClient.getRemoteUrl() + "/session/" + uiaClient.getSessionId() + "/element/" + id + "/text")); if (b.getErr() == null) { logger.info("get %s text %s.", id, b.getValue().toString()); return b.getValue().toString(); } else { logger.error("get %s text failed.", id); throw new SonicRespException(b.getErr().getMessage()); } } @Override public String getAttribute(String name) throws SonicRespException { uiaClient.checkSessionId(); BaseResp b = uiaClient.getRespHandler().getResp( HttpUtil.createGet(uiaClient.getRemoteUrl() + "/session/" + uiaClient.getSessionId() + "/element/" + id + "/attribute/" + name)); if (b.getErr() == null) { logger.info("get %s attribute %s result %s.", id, name, b.getValue().toString()); return b.getValue().toString(); } else { logger.error("get %s attribute failed.", id); throw new SonicRespException(b.getErr().getMessage()); } } @Override public String getUniquelyIdentifies() throws SonicRespException { return id; } @Override public ElementRect getRect() throws SonicRespException { uiaClient.checkSessionId(); BaseResp b = uiaClient.getRespHandler().getResp( HttpUtil.createGet(uiaClient.getRemoteUrl() + "/session/" + uiaClient.getSessionId() + "/element/" + id + "/rect")); if (b.getErr() == null) { ElementRect elementRect = JSON.parseObject(b.getValue().toString(), ElementRect.class); logger.info("get %s rect %s.", id, elementRect.toString()); return elementRect; } else { logger.error("get %s rect failed.", id); throw new SonicRespException(b.getErr().getMessage()); } } @Override public byte[] screenshot() throws SonicRespException { uiaClient.checkSessionId(); BaseResp b = uiaClient.getRespHandler().getResp( HttpUtil.createGet(uiaClient.getRemoteUrl() + "/session/" + uiaClient.getSessionId() + "/element/" + id + "/screenshot"), 60000); if (b.getErr() == null) { logger.info("get element %s screenshot.", id); return Base64.getMimeDecoder().decode(b.getValue().toString()); } else { logger.error("get element %s screenshot failed.", id); throw new SonicRespException(b.getErr().getMessage()); } } @Override public boolean isDisplayed() throws SonicRespException { String result = getAttribute("displayed"); return Boolean.parseBoolean(result); } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/android/service/impl/UiaClientImpl.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.android.service.impl; import cn.hutool.http.HttpUtil; import cn.hutool.http.Method; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.cloud.sonic.driver.android.service.AndroidElement; import org.cloud.sonic.driver.android.service.UiaClient; import org.cloud.sonic.driver.common.models.BaseResp; import org.cloud.sonic.driver.common.models.SessionInfo; import org.cloud.sonic.driver.common.models.WindowSize; import org.cloud.sonic.driver.common.tool.Logger; import org.cloud.sonic.driver.common.tool.RespHandler; import org.cloud.sonic.driver.common.tool.SonicRespException; import java.nio.charset.StandardCharsets; import java.util.*; public class UiaClientImpl implements UiaClient { private String remoteUrl; private String sessionId; private RespHandler respHandler; private Logger logger; private final String LEGACY_WEB_ELEMENT_IDENTIFIER = "ELEMENT"; private final String WEB_ELEMENT_IDENTIFIER = "element-6066-11e4-a52e-4f735466cecf"; private int FIND_ELEMENT_INTERVAL = 3000; private int FIND_ELEMENT_RETRY = 5; private WindowSize size; public UiaClientImpl() { respHandler = new RespHandler(); logger = new Logger(); } private void checkBundleId(String bundleId) throws SonicRespException { if (bundleId == null || bundleId.length() == 0) { logger.error("bundleId not found."); throw new SonicRespException("bundleId not found."); } } private String parseElementId(Object o) { JSONObject jsonObject = (JSONObject) o; List identifier = Arrays.asList(LEGACY_WEB_ELEMENT_IDENTIFIER, WEB_ELEMENT_IDENTIFIER); for (String i : identifier) { String result = jsonObject.getString(i); if (result != null && result.length() > 0) { return result; } } return ""; } @Override public void setGlobalTimeOut(int timeOut) { respHandler.setRequestTimeOut(timeOut); } @Override public RespHandler getRespHandler() { return respHandler; } @Override public void setRespHandler(RespHandler respHandler) { this.respHandler = respHandler; } @Override public Logger getLogger() { return logger; } @Override public void showLog() { logger.showLog(); } @Override public void disableLog() { logger.disableLog(); } @Override public String getRemoteUrl() { return remoteUrl; } @Override public void setRemoteUrl(String remoteUrl) { this.remoteUrl = remoteUrl; } @Override public String getSessionId() { return sessionId; } @Override public void setSessionId(String sessionId) { this.sessionId = sessionId; } @Override public void newSession(JSONObject capabilities) throws SonicRespException { JSONObject data = new JSONObject(); data.put("capabilities", capabilities); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session").body(data.toJSONString())); if (b.getErr() == null) { SessionInfo sessionInfo = JSON.parseObject(b.getValue().toString(), SessionInfo.class); setSessionId(sessionInfo.getSessionId()); logger.info("start session successful!"); logger.info("session : %s", sessionInfo.getSessionId()); } else { logger.error("start session failed."); logger.error("cause: %s", b.getErr().toString()); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void closeSession() throws SonicRespException { checkSessionId(); respHandler.getResp(HttpUtil.createRequest(Method.DELETE, remoteUrl + "/session/" + sessionId)); logger.info("close session successful!"); } @Override public void checkSessionId() throws SonicRespException { if (sessionId == null || sessionId.length() == 0) { logger.error("sessionId not found."); throw new SonicRespException("sessionId not found."); } } @Override public WindowSize getWindowSize() throws SonicRespException { if (size == null) { checkSessionId(); BaseResp b = respHandler.getResp(HttpUtil.createGet(remoteUrl + "/session/" + sessionId + "/window/:windowHandle/size")); if (b.getErr() == null) { size = JSON.parseObject(b.getValue().toString(), WindowSize.class); logger.info("get window size %s.", size.toString()); } else { logger.error("get window size failed."); throw new SonicRespException(b.getErr().getMessage()); } } return size; } @Override public void sendKeys(String text, boolean isCover) throws SonicRespException { checkSessionId(); JSONObject data = new JSONObject(); data.put("text", text); data.put("replace", isCover); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/keys") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("send key %s.", text); } else { logger.error("send key failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void setPasteboard(String contentType, String content) throws SonicRespException { checkSessionId(); JSONObject data = new JSONObject(); data.put("contentType", contentType.toUpperCase(Locale.ROOT)); data.put("content", Base64.getEncoder().encodeToString(content.getBytes(StandardCharsets.UTF_8))); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/appium/device/set_clipboard") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("set pasteboard %s.", content); } else { logger.error("set pasteboard failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public byte[] getPasteboard(String contentType) throws SonicRespException { checkSessionId(); JSONObject data = new JSONObject(); data.put("contentType", contentType.toUpperCase(Locale.ROOT)); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/appium/device/get_clipboard") .body(data.toJSONString())); if (b.getErr() == null) { byte[] result = Base64.getMimeDecoder().decode(b.getValue().toString()); logger.info("get pasteboard length: %d.", result.length); return result; } else { logger.error("get pasteboard failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public String pageSource() throws SonicRespException { checkSessionId(); BaseResp b = respHandler.getResp(HttpUtil.createGet(remoteUrl + "/session/" + sessionId + "/source"), 60000); if (b.getErr() == null) { logger.info("get page source."); return b.getValue().toString(); } else { logger.error("get page source failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void setDefaultFindElementInterval(Integer retry, Integer interval) { if (retry != null) { FIND_ELEMENT_RETRY = retry; } if (interval != null) { FIND_ELEMENT_INTERVAL = interval; } } @Override public AndroidElement findElement(String selector, String value, Integer retry, Integer interval) throws SonicRespException { AndroidElement androidElement = null; int wait = 0; int intervalInit = (interval == null ? FIND_ELEMENT_INTERVAL : interval); int retryInit = (retry == null ? FIND_ELEMENT_RETRY : retry); String errMsg = ""; while (wait < retryInit) { wait++; checkSessionId(); JSONObject data = new JSONObject(); data.put("strategy", selector); data.put("selector", value); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/element") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("find element successful."); String id = parseElementId(b.getValue()); if (id.length() > 0) { androidElement = new AndroidElementImpl(id, this); break; } else { logger.error("parse element id %s failed. retried %d times, retry in %d ms.", b.getValue().toString(), wait, intervalInit); } } else { logger.error("element not found. retried %d times, retry in %d ms.", wait, intervalInit); errMsg = b.getErr().getMessage(); } if (wait < retryInit) { try { Thread.sleep(intervalInit); } catch (InterruptedException e) { e.printStackTrace(); } } } if (androidElement == null) { throw new SonicRespException(errMsg); } return androidElement; } @Override public List findElementList(String selector, String value, Integer retry, Integer interval) throws SonicRespException { List androidElementList = new ArrayList<>(); int wait = 0; int intervalInit = (interval == null ? FIND_ELEMENT_INTERVAL : interval); int retryInit = (retry == null ? FIND_ELEMENT_RETRY : retry); String errMsg = ""; while (wait < retryInit) { wait++; checkSessionId(); JSONObject data = new JSONObject(); data.put("strategy", selector); data.put("selector", value); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/elements") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("find elements successful."); List ids = JSON.parseObject(b.getValue().toString(), ArrayList.class); for (JSONObject ele : ids) { String id = parseElementId(ele); if (id.length() > 0) { androidElementList.add(new AndroidElementImpl(id, this)); } else { logger.error("parse element id %s failed.", ele); } } if (androidElementList.size() > 0) { break; } } else { logger.error("elements not found. retried %d times, retry in %d ms.", wait, intervalInit); errMsg = b.getErr().getMessage(); } if (wait < retryInit) { try { Thread.sleep(intervalInit); } catch (InterruptedException e) { e.printStackTrace(); } } } if (androidElementList.size() == 0) { throw new SonicRespException(errMsg); } return androidElementList; } @Override public byte[] screenshot() throws SonicRespException { checkSessionId(); BaseResp b = respHandler.getResp( HttpUtil.createGet(remoteUrl + "/session/" + sessionId + "/screenshot"), 60000); if (b.getErr() == null) { logger.info("get screenshot."); return Base64.getMimeDecoder().decode(b.getValue().toString()); } else { logger.error("get screenshot failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void setAppiumSettings(JSONObject settings) throws SonicRespException { checkSessionId(); JSONObject data = new JSONObject(); data.put("settings", settings); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/appium/settings") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("set appium settings %s.", settings.toJSONString()); } else { logger.error("set appium settings failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void tap(int x, int y) throws SonicRespException { checkSessionId(); JSONObject data = new JSONObject(); data.put("x", x); data.put("y", y); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/appium/tap") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("perform tap action %s.", data.toString()); } else { logger.error("perform tap action failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void longPress(double x, double y, double ms) throws SonicRespException { checkSessionId(); JSONObject data = new JSONObject(); JSONObject touchEventParams = new JSONObject(); touchEventParams.put("x", x); touchEventParams.put("y", y); touchEventParams.put("duration", ms); data.put("params", touchEventParams); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/touch/longclick") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("perform longPress action %s.", data.toString()); } else { logger.error("perform longPress action failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void swipe(int fromX, int fromY, int toX, int toY, Integer duration) throws SonicRespException { checkSessionId(); JSONObject data = new JSONObject(); data.put("startX", fromX); data.put("startY", fromY); data.put("endX", toX); data.put("endY", toY); // steps 参数为uiautomator层定义的单位 // example:So for a 100 steps, the swipe will take about 1/2 second to complete. if (duration == null) { data.put("steps", 100); } else { data.put("steps", duration / 5); } BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/touch/perform") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("perform swipe action %s.", data.toString()); } else { logger.error("perform swipe action failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void drag(int fromX, int fromY, int toX, int toY, Integer duration, String elementId, String destElId) throws SonicRespException { checkSessionId(); JSONObject data = new JSONObject(); data.put("startX", fromX); data.put("startY", fromY); data.put("endX", toX); data.put("endY", toY); data.put("steps", duration != null && duration > 0 ? duration / 5 : 100); data.put("elementId", elementId); data.put("destElId", destElId); BaseResp move = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/touch/drag").body(data.toJSONString())); if (move.getErr() == null) { logger.info("perform drag action %s.", data.toString()); } else { logger.error("perform drag action failed."); throw new SonicRespException(move.getErr().getMessage()); } } @Override public void touchAction(String methodType, int x, int y) throws SonicRespException { checkSessionId(); JSONObject data = new JSONObject(); JSONObject touchEventParams = new JSONObject(); touchEventParams.put("x", x); touchEventParams.put("y", y); data.put("params", touchEventParams); String path = "/touch/down"; switch (methodType) { case "down": break; case "up": path = "/touch/up"; break; case "move": path = "/touch/move"; break; default: throw new RuntimeException("methodType error."); } BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + path) .body(data.toJSONString())); if (b.getErr() == null) { logger.info(String.format("perform touch %s action %s.", methodType, data)); } else { logger.error("perform touch %s action failed.", methodType); throw new SonicRespException(b.getErr().getMessage()); } } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/common/enums/PasteboardType.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.common.enums; public enum PasteboardType { PLAIN_TEXT("plaintext"), IMAGE("image"), URL("url"); private final String type; PasteboardType(String pasteboardType) { this.type = pasteboardType; } public String getType() { return type; } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/common/models/BaseElement.java ================================================ package org.cloud.sonic.driver.common.models; import org.cloud.sonic.driver.common.tool.SonicRespException; public interface BaseElement { ElementRect getRect() throws SonicRespException; String getAttribute(String name) throws SonicRespException; // the xpath or id or csspath... String getUniquelyIdentifies() throws SonicRespException; // List getChildren() throws SonicRespException; /** * Is this element displayed or not? * This method avoids the problem of having to parse an element's "style" attribute. * * @return whether the element is displayed */ boolean isDisplayed() throws SonicRespException; } ================================================ FILE: src/main/java/org/cloud/sonic/driver/common/models/BaseResp.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.common.models; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @NoArgsConstructor public class BaseResp { private String sessionId; private ErrorMsg err; private T value; public void setErr(ErrorMsg err) { this.err = err; } public void setValue(T value) { this.value = value; } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/common/models/Capabilities.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.common.models; import lombok.AllArgsConstructor; import lombok.ToString; @ToString @AllArgsConstructor public class Capabilities { private String device; private String browserName; private String sdkVersion; private String CFBundleIdentifier; } ================================================ FILE: src/main/java/org/cloud/sonic/driver/common/models/ElementRect.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.common.models; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; @Getter @ToString @AllArgsConstructor public class ElementRect { private int x; private int y; private int width; private int height; @Getter @ToString @AllArgsConstructor public class IOSRectCenter { private int x; private int y; } public IOSRectCenter getCenter() { return new IOSRectCenter(x / 2, y / 2); } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/common/models/ErrorMsg.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.common.models; import lombok.AllArgsConstructor; import lombok.ToString; @ToString @AllArgsConstructor public class ErrorMsg { private String error; private String message; private String traceback; public String getMessage() { return message; } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/common/models/SessionInfo.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.common.models; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; @Getter @ToString @AllArgsConstructor public class SessionInfo { private String sessionId; private Capabilities capabilities; } ================================================ FILE: src/main/java/org/cloud/sonic/driver/common/models/WindowSize.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.common.models; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; @Getter @ToString @AllArgsConstructor public class WindowSize { private int width; private int height; } ================================================ FILE: src/main/java/org/cloud/sonic/driver/common/tool/Logger.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.common.tool; import java.text.SimpleDateFormat; import java.util.Date; public class Logger { private SimpleDateFormat formatter; private boolean isShowLog; public Logger() { formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); isShowLog = true; } public void showLog() { isShowLog = true; } public void disableLog() { isShowLog = false; } private void print(String level, String msg, Object... args) { if (isShowLog) { System.out.println(String.format("[sonic-driver-core] %s [%s] %s", formatter.format(new Date()), level, String.format(msg, args))); } } public void info(String msg, Object... args) { print("INFO", msg, args); } public void error(String msg, Object... args) { print("ERROR", msg, args); } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/common/tool/RespHandler.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.common.tool; import cn.hutool.core.io.IORuntimeException; import cn.hutool.http.HttpException; import cn.hutool.http.HttpRequest; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.cloud.sonic.driver.common.models.BaseResp; import org.cloud.sonic.driver.common.models.ErrorMsg; import java.util.HashMap; import java.util.Map; public class RespHandler { public static final int DEFAULT_REQUEST_TIMEOUT = 15000; private int requestTimeout = 15000; public void setRequestTimeOut(int timeOut) { requestTimeout = timeOut; } public BaseResp getResp(HttpRequest httpRequest) throws SonicRespException { return getResp(httpRequest, requestTimeout); } public BaseResp getResp(HttpRequest httpRequest, int timeout) throws SonicRespException { synchronized (this) { try { return initResp(httpRequest.addHeaders(initHeader()).timeout(timeout).execute().body()); } catch (HttpException | IORuntimeException e) { e.printStackTrace(); throw new SonicRespException(e.getMessage()); } } } public BaseResp initResp(String response) { if (response.contains("traceback") || response.contains("stacktrace")) { return initErrorMsg(response.replace("stacktrace", "traceback")); } else { return JSON.parseObject(response, BaseResp.class); } } public Map initHeader() { Map headers = new HashMap<>(); headers.put("Content-Type", "application/json; charset=utf-8"); return headers; } public BaseResp initErrorMsg(String resp) { BaseResp err = JSON.parseObject(resp, BaseResp.class); ErrorMsg errorMsg = JSONObject.parseObject(err.getValue().toString(), ErrorMsg.class); err.setErr(errorMsg); err.setValue(null); return err; } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/common/tool/SonicRespException.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.common.tool; public class SonicRespException extends Exception { public SonicRespException(String message) { super(message); } public SonicRespException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/ios/IOSDriver.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.ios; import com.alibaba.fastjson.JSONObject; import org.cloud.sonic.driver.common.enums.PasteboardType; import org.cloud.sonic.driver.common.models.WindowSize; import org.cloud.sonic.driver.common.tool.RespHandler; import org.cloud.sonic.driver.common.tool.SonicRespException; import org.cloud.sonic.driver.ios.enums.*; import org.cloud.sonic.driver.ios.models.TouchActions; import org.cloud.sonic.driver.ios.service.IOSElement; import org.cloud.sonic.driver.ios.service.WdaClient; import org.cloud.sonic.driver.ios.service.impl.IOSElementImpl; import org.cloud.sonic.driver.ios.service.impl.WdaClientImpl; import java.util.List; /** * @author Eason * ios driver */ public class IOSDriver { private WdaClient wdaClient; /** * Init ios driver. * * @param url * @throws SonicRespException */ public IOSDriver(String url) throws SonicRespException { this(url, RespHandler.DEFAULT_REQUEST_TIMEOUT); } /** * Init ios driver. * * @param url * @param timeOut * @throws SonicRespException */ public IOSDriver(String url, int timeOut) throws SonicRespException { this(url, timeOut, new JSONObject()); } /** * Init ios driver. * * @param url * @param cap * @throws SonicRespException */ public IOSDriver(String url, JSONObject cap) throws SonicRespException { this(url, RespHandler.DEFAULT_REQUEST_TIMEOUT, cap); } /** * Init ios driver. * * @param url * @param timeOut * @param cap * @throws SonicRespException */ public IOSDriver(String url, int timeOut, JSONObject cap) throws SonicRespException { wdaClient = new WdaClientImpl(); wdaClient.setRemoteUrl(url); wdaClient.setGlobalTimeOut(timeOut); wdaClient.newSession(cap); } /** * Get wda client. * * @return */ public WdaClient getWdaClient() { return wdaClient; } /** * get wda sessionId. * * @return */ public String getSessionId() { return wdaClient.getSessionId(); } /** * destroy sessionId. * * @throws SonicRespException */ public void closeDriver() throws SonicRespException { wdaClient.closeSession(); } /** * show log. */ public void showLog() { wdaClient.showLog(); } /** * disable log. */ public void disableLog() { wdaClient.disableLog(); } /** * get device window size. * * @return * @throws SonicRespException */ public WindowSize getWindowSize() throws SonicRespException { return wdaClient.getWindowSize(); } /** * get device lock status. * * @return * @throws SonicRespException */ public boolean isLocked() throws SonicRespException { return wdaClient.isLocked(); } /** * lock device. * * @throws SonicRespException */ public void lock() throws SonicRespException { wdaClient.lock(); } /** * unlock device. * * @throws SonicRespException */ public void unlock() throws SonicRespException { wdaClient.unlock(); } /** * tap position on screen. * * @param x * @param y * @throws SonicRespException */ public void tap(int x, int y) throws SonicRespException { performTouchAction(new TouchActions.FingerTouchAction().press(x, y).release()); } /** * double tap position on screen * @param x * @param y * @throws SonicRespException */ public void doubleTap(int x, int y) throws SonicRespException { wdaClient.doubleTap(x,y); } /** * long press position on screen. * * @param x * @param y * @param ms * @throws SonicRespException */ public void longPress(int x, int y, int ms) throws SonicRespException { performTouchAction(new TouchActions.FingerTouchAction().press(x, y).wait(ms).release()); } /** * swipe position on screen. * * @param fromX * @param fromY * @param toX * @param toY * @throws SonicRespException */ public void swipe(int fromX, int fromY, int toX, int toY) throws SonicRespException { performTouchAction(new TouchActions.FingerTouchAction().press(fromX, fromY).wait(300).move(toX, toY).wait(10).release()); } /** * swipe position on screen with target time * * @param fromX * @param fromY * @param toX * @param toY * @param duration 滑动完成的时间,单位为毫秒 * @throws SonicRespException */ public void swipe(double fromX, double fromY, double toX, double toY, double duration) throws SonicRespException { wdaClient.swipe(fromX, fromY, toX, toY, duration); } /** * perform touch action. * * @param touchActions * @throws SonicRespException */ public void performTouchAction(TouchActions touchActions) throws SonicRespException { wdaClient.performTouchAction(touchActions); } public void performTouchAction(TouchActions.FingerTouchAction fingerTouchActions) throws SonicRespException { performTouchAction(new TouchActions(fingerTouchActions)); } /** * press system button. * * @param systemButton * @throws SonicRespException */ public void pressButton(String systemButton) throws SonicRespException { wdaClient.pressButton(systemButton); } /** * press system button. * * @param systemButton * @throws SonicRespException */ public void pressButton(SystemButton systemButton) throws SonicRespException { pressButton(systemButton.getButton()); } /** * send key without element. * * @param text * @throws SonicRespException */ public void sendKeys(String text) throws SonicRespException { sendKeys(text, 3); } /** * send key without element. * * @param text * @param frequency * @throws SonicRespException */ public void sendKeys(String text, int frequency) throws SonicRespException { wdaClient.sendKeys(text, frequency); } /** * send key without element. * * @param text * @throws SonicRespException */ public void sendKeys(TextKey text) throws SonicRespException { sendKeys(text, 3); } /** * send key without element. * * @param text * @param frequency * @throws SonicRespException */ public void sendKeys(TextKey text, int frequency) throws SonicRespException { sendKeys(text.getKey(), frequency); } /** * set pasteboard. * * @param contentType * @param content * @throws SonicRespException */ public void setPasteboard(String contentType, String content) throws SonicRespException { wdaClient.setPasteboard(contentType, content); } /** * set pasteboard. * * @param pasteboardType * @param content * @throws SonicRespException */ public void setPasteboard(PasteboardType pasteboardType, String content) throws SonicRespException { setPasteboard(pasteboardType.getType(), content); } /** * get pasteboard. * * @param contentType * @return * @throws SonicRespException */ public byte[] getPasteboard(String contentType) throws SonicRespException { return wdaClient.getPasteboard(contentType); } /** * get pasteboard. * * @param pasteboardType * @return * @throws SonicRespException */ public byte[] getPasteboard(PasteboardType pasteboardType) throws SonicRespException { return getPasteboard(pasteboardType.getType()); } /** * get page source. * * @return * @throws SonicRespException */ public String getPageSource() throws SonicRespException { return wdaClient.pageSource(); } /** * send siri command. * * @param command * @throws SonicRespException */ public void sendSiriCommand(String command) throws SonicRespException { wdaClient.sendSiriCommand(command); } /** * activate app. * * @param bundleId * @throws SonicRespException */ public void appActivate(String bundleId) throws SonicRespException { wdaClient.appActivate(bundleId); } /** * terminate app and get status. * * @param bundleId * @return * @throws SonicRespException */ public boolean appTerminate(String bundleId) throws SonicRespException { return wdaClient.appTerminate(bundleId); } /** * run app background in seconds. * * @param duration * @throws SonicRespException */ public void appRunBackground(int duration) throws SonicRespException { wdaClient.appRunBackground(duration); } /** * reset app auth source. * * @param resource * @throws SonicRespException */ public void appAuthReset(int resource) throws SonicRespException { wdaClient.appAuthReset(resource); } /** * reset app auth source. * * @param authResource * @throws SonicRespException */ public void appAuthReset(AuthResource authResource) throws SonicRespException { appAuthReset(authResource.getResource()); } /** * set default FindElement retry time and interval. * * @param retry * @param interval */ public void setDefaultFindElementInterval(Integer retry, Integer interval) { wdaClient.setDefaultFindElementInterval(retry, interval); } /** * find element in device. * * @param iosSelector * @param value * @return * @throws SonicRespException */ public IOSElement findElement(IOSSelector iosSelector, String value) throws SonicRespException { return findElement(iosSelector, value, null); } /** * find element in device. * * @param wdaElementID This id is the id returned by the wda lookup control * @return * @throws SonicRespException */ public IOSElement findElement(String wdaElementID) throws SonicRespException { return new IOSElementImpl(wdaElementID, wdaClient); } /** * find element in device. * * @param xcuiElementType * @return * @throws SonicRespException */ public IOSElement findElement(XCUIElementType xcuiElementType) throws SonicRespException { return findElement(xcuiElementType, null); } /** * find element in device. * * @param selector * @param value * @return * @throws SonicRespException */ public IOSElement findElement(String selector, String value) throws SonicRespException { return findElement(selector, value, null); } /** * find element in device. * * @param iosSelector * @param value * @param retry * @return * @throws SonicRespException */ public IOSElement findElement(IOSSelector iosSelector, String value, Integer retry) throws SonicRespException { return findElement(iosSelector, value, retry, null); } /** * find element in device. * * @param xcuiElementType * @param retry * @return * @throws SonicRespException */ public IOSElement findElement(XCUIElementType xcuiElementType, Integer retry) throws SonicRespException { return findElement(xcuiElementType, retry, null); } /** * find element in device. * * @param selector * @param value * @param retry * @return * @throws SonicRespException */ public IOSElement findElement(String selector, String value, Integer retry) throws SonicRespException { return findElement(selector, value, retry, null); } /** * find element in device. * * @param iosSelector * @param value * @param retry * @param interval * @return * @throws SonicRespException */ public IOSElement findElement(IOSSelector iosSelector, String value, Integer retry, Integer interval) throws SonicRespException { return findElement(iosSelector.getSelector(), value, retry, interval); } /** * find element in device. * * @param xcuiElementType * @param retry * @param interval * @return * @throws SonicRespException */ public IOSElement findElement(XCUIElementType xcuiElementType, Integer retry, Integer interval) throws SonicRespException { return findElement(IOSSelector.CLASS_NAME.getSelector(), xcuiElementType.getType(), retry, interval); } /** * find element in device. * * @param selector * @param value * @param retry * @param interval * @return * @throws SonicRespException */ public IOSElement findElement(String selector, String value, Integer retry, Integer interval) throws SonicRespException { return wdaClient.findElement(selector, value, retry, interval); } /** * find element list in device. * * @param iosSelector * @param value * @return * @throws SonicRespException */ public List findElementList(IOSSelector iosSelector, String value) throws SonicRespException { return findElementList(iosSelector, value, null); } /** * find element list in device. * * @param xcuiElementType * @return * @throws SonicRespException */ public List findElementList(XCUIElementType xcuiElementType) throws SonicRespException { return findElementList(xcuiElementType, null); } /** * find element list in device. * * @param selector * @param value * @return * @throws SonicRespException */ public List findElementList(String selector, String value) throws SonicRespException { return findElementList(selector, value, null); } /** * find element list in device. * * @param iosSelector * @param value * @param retry * @return * @throws SonicRespException */ public List findElementList(IOSSelector iosSelector, String value, Integer retry) throws SonicRespException { return findElementList(iosSelector, value, retry, null); } /** * find element list in device. * * @param xcuiElementType * @param retry * @return * @throws SonicRespException */ public List findElementList(XCUIElementType xcuiElementType, Integer retry) throws SonicRespException { return findElementList(xcuiElementType, retry, null); } /** * find element list in device. * * @param selector * @param value * @param retry * @return * @throws SonicRespException */ public List findElementList(String selector, String value, Integer retry) throws SonicRespException { return findElementList(selector, value, retry, null); } /** * find element list in device. * * @param iosSelector * @param value * @param retry * @param interval * @return * @throws SonicRespException */ public List findElementList(IOSSelector iosSelector, String value, Integer retry, Integer interval) throws SonicRespException { return findElementList(iosSelector.getSelector(), value, retry, interval); } /** * find element list in device. * * @param xcuiElementType * @param retry * @param interval * @return * @throws SonicRespException */ public List findElementList(XCUIElementType xcuiElementType, Integer retry, Integer interval) throws SonicRespException { return findElementList(IOSSelector.CLASS_NAME.getSelector(), xcuiElementType.getType(), retry, interval); } /** * find element list in device. * * @param selector * @param value * @param retry * @param interval * @return * @throws SonicRespException */ public List findElementList(String selector, String value, Integer retry, Integer interval) throws SonicRespException { return wdaClient.findElementList(selector, value, retry, interval); } /** * get current active element * @return * @throws SonicRespException */ public IOSElement activeElement() throws SonicRespException{ return wdaClient.activeElement(); } /** * get screenshot. * * @return * @throws SonicRespException */ public byte[] screenshot() throws SonicRespException { return wdaClient.screenshot(); } /** * set appium settings. * * @param settings * @throws SonicRespException */ public void setAppiumSettings(JSONObject settings) throws SonicRespException { wdaClient.setAppiumSettings(settings); } /** * rotate screen orientation * @throws SonicRespException */ public void rotate(Orientation orientation) throws SonicRespException { wdaClient.rotate(orientation); } public Orientation getRotate() throws SonicRespException { return wdaClient.getRotate(); } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/ios/enums/ActionType.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.ios.enums; public enum ActionType { PRESS("pointerDown"), WAIT("pause"), MOVE("pointerMove"), RELEASE("pointerUp"); private final String type; ActionType(String type) { this.type = type; } public String getType() { return type; } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/ios/enums/AuthResource.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.ios.enums; public enum AuthResource { CONTACTS(1), CALENDAR(2), REMINDERS(3), PHOTOS(4), MICROPHONE(5), CAMERA(6), MEDIA_LIBRARY(7), HOME_KIT(8), SYSTEM_ROOT_DIRECTORY(0x40000000), USER_DESKTOP_DIRECTORY(0x40000001), USER_DOWNLOADS_DIRECTORY(0x40000002), USER_DOCUMENTS_DIRECTORY(0x40000003), BLUETOOTH(-0x40000000), KEYBOARD_NETWORK(-0x40000001), LOCATION(-0x40000002), HEALTH(-0x40000003); private final int resource; AuthResource(int resource) { this.resource = resource; } public int getResource() { return resource; } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/ios/enums/IOSSelector.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.ios.enums; public enum IOSSelector { CLASS_NAME("class name"), NAME("name"), Id("id"), ACCESSIBILITY_ID("accessibility id"), XPATH("xpath"), PREDICATE("predicate string"), CLASS_CHAIN("class chain"), LINK_TEXT("link text"), PARTIAL_LINK_TEXT("partial link text"); private final String selector; IOSSelector(String selector) { this.selector = selector; } public String getSelector() { return selector; } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/ios/enums/Orientation.java ================================================ package org.cloud.sonic.driver.ios.enums; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import java.util.HashMap; public enum Orientation { UNKNOWN(0), PORTRAIT(1), PORTRAITUPSIDEDOWN(2), LANDSCAPELEFT(3), LANDSCAPERIGHT(4); private final int orientation; Orientation(int orientation) { this.orientation = orientation; } public JSONObject getValue() { JSONObject value = new JSONObject(); value.put("x",0); value.put("y",0); switch (this.orientation) { case 1: value.put("z",0); break; case 2: value.put("z",180); break; case 3: value.put("z",90); break; case 4: value.put("z",270); break; } return value; } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/ios/enums/SystemButton.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.ios.enums; public enum SystemButton { HOME("home"), VOLUME_UP("volumeUp"), VOLUME_DOWN("volumeDown"); private final String button; SystemButton(String button) { this.button = button; } public String getButton() { return button; } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/ios/enums/TextKey.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.ios.enums; public enum TextKey { BACK_SPACE("\u0008"), DELETE("\u007F"); private final String key; TextKey(String key) { this.key = key; } public String getKey() { return key; } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/ios/enums/XCUIElementType.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License")), * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.ios.enums; public enum XCUIElementType { ANY("XCUIElementTypeAny"), OTHER("XCUIElementTypeOther"), APPLICATION("XCUIElementTypeApplication"), GROUP("XCUIElementTypeGroup"), WINDOW("XCUIElementTypeWindow"), SHEET("XCUIElementTypeSheet"), DRAWER("XCUIElementTypeDrawer"), ALERT("XCUIElementTypeAlert"), DIALOG("XCUIElementTypeDialog"), BUTTON("XCUIElementTypeButton"), DISCLOSURE_TRIANGLE("XCUIElementTypeDisclosureTriangle"), POP_UP_BUTTON("XCUIElementTypePopUpButton"), COMBO_BOX("XCUIElementTypeComboBox"), MENU_BUTTON("XCUIElementTypeMenuButton"), TOOLBAR_BUTTON("XCUIElementTypeToolbarButton"), POPOVER("XCUIElementTypePopover"), KEYBOARD("XCUIElementTypeKeyboard"), NAVIGATION_BAR("XCUIElementTypeNavigationBar"), KEY("XCUIElementTypeKey"), STATUS_BAR("XCUIElementTypeStatusBar"), SWITCH("XCUIElementTypeSwitch"), TOGGLE("XCUIElementTypeToggle"), LINK("XCUIElementTypeLink"), IMAGE("XCUIElementTypeImage"), ICON("XCUIElementTypeIcon"), SEARCH_FIELD("XCUIElementTypeSearchField"), STATIC_TEXT("XCUIElementTypeStaticText"), CHECK_BOX("XCUIElementTypeCheckBox"), TEXT_VIEW("XCUIElementTypeTextView"), SEGMENTED_CONTROL("XCUIElementTypeSegmentedControl"), BROWSER("XCUIElementTypeBrowser"), COLLECTION_VIEW("XCUIElementTypeCollectionView"), SLIDER("XCUIElementTypeSlider"), MAP("XCUIElementTypeMap"), WEB_VIEW("XCUIElementTypeWebView"), TIME_LINE("XCUIElementTypeTimeline"), COLOR_WELL("XCUIElementTypeColorWell"), HELP_TAG("XCUIElementTypeHelpTag"), MATTE("XCUIElementTypeMatte"), DOCK_ITEM("XCUIElementTypeDockItem"), GRID("XCUIElementTypeGrid"), CELL("XCUIElementTypeCell"), HANDLE("XCUIElementTypeHandle"), STEPPER("XCUIElementTypeStepper"), TAB("XCUIElementTypeTab"), TOUCH_BAR("XCUIElementTypeTouchBar"), STATUS_ITEM("XCUIElementTypeStatusItem"), LAYOUT_AREA("XCUIElementTypeLayoutArea"), LAYOUT_ITEM("XCUIElementTypeLayoutItem"), RULER("XCUIElementTypeRuler"), RULER_MARKER("XCUIElementTypeRulerMarker"), RADIO_BUTTON("XCUIElementTypeRadioButton"), RADIO_GROUP("XCUIElementTypeRadioGroup"), TAB_BAR("XCUIElementTypeTabBar"), TAB_GROUP("XCUIElementTypeTabGroup"), TABLE("XCUIElementTypeTable"), TABLE_ROW("XCUIElementTypeTableRow"), TABLE_COLUMN("XCUIElementTypeTableColumn"), OUTLINE("XCUIElementTypeOutline"), OUTLINE_ROW("XCUIElementTypeOutlineRow"), PAGE_INDICATOR("XCUIElementTypePageIndicator"), PROGRESS_INDICATOR("XCUIElementTypeProgressIndicator"), ACTIVITY_INDICATOR("XCUIElementTypeActivityIndicator"), RATING_INDICATOR("XCUIElementTypeRatingIndicator"), VALUE_INDICATOR("XCUIElementTypeValueIndicator"), RELEVANCE_INDICATOR("XCUIElementTypeRelevanceIndicator"), LEVEL_INDICATOR("XCUIElementTypeLevelIndicator"), SPLIT_GROUP("XCUIElementTypeSplitGroup"), SPLITTER("XCUIElementTypeSplitter"), PICKER("XCUIElementTypePicker"), PICKER_WHEEL("XCUIElementTypePickerWheel"), DATE_PICKER("XCUIElementTypeDatePicker"), SCROLL_VIEW("XCUIElementTypeScrollView"), SCROLL_BAR("XCUIElementTypeScrollBar"), TEXT_FIELD("XCUIElementTypeTextField"), SECURE_TEXT_FIELD("XCUIElementTypeSecureTextField"), MENU("XCUIElementTypeMenu"), MENU_ITEM("XCUIElementTypeMenuItem"), MENU_BAR("XCUIElementTypeMenuBar"), MENU_BAR_ITEM("XCUIElementTypeMenuBarItem"), INCREMENT_ARROW("XCUIElementTypeIncrementArrow"), DECREMENT_ARROW("XCUIElementTypeDecrementArrow"), ; private final String type; XCUIElementType(String type) { this.type = type; } public String getType() { return type; } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/ios/models/TouchActions.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.ios.models; import cn.hutool.core.map.MapUtil; import lombok.Getter; import lombok.ToString; import org.cloud.sonic.driver.ios.enums.ActionType; import java.util.ArrayList; import java.util.List; import java.util.Map; @Getter @ToString public class TouchActions { private List actions; @Getter @ToString public static class FingerTouchAction { private final String id; private final String type = "pointer"; private final Map parameters = MapUtil.of("pointerType", "touch"); private final List actions; public FingerTouchAction(String fingerName) { this.id = "finger-" + fingerName; actions = new ArrayList<>(); } public FingerTouchAction() { this("0"); } public FingerTouchAction press(int x, int y) { move(x, y); TouchAction touchAction = new TouchAction(ActionType.PRESS); actions.add(touchAction); return this; } public FingerTouchAction wait(int ms) { PauseAction touchAction = new PauseAction(); touchAction.duration = ms; actions.add(touchAction); return this; } public FingerTouchAction move(int x, int y) { MoveAction touchAction = new MoveAction(); touchAction.x = x; touchAction.y = y; actions.add(touchAction); return this; } public FingerTouchAction release() { TouchAction touchAction = new TouchAction(ActionType.RELEASE); actions.add(touchAction); return this; } } @Getter @ToString public static class TouchAction { private final String type; public TouchAction(ActionType actionType) { this.type = actionType.getType(); } } @Getter @ToString(callSuper = true) public static class MoveAction extends TouchAction { private int x; private int y; public MoveAction() { super(ActionType.MOVE); } } @Getter @ToString(callSuper = true) public static class PauseAction extends TouchAction { private int duration; public PauseAction() { super(ActionType.WAIT); } } public TouchActions() { actions = new ArrayList<>(); } public TouchActions(FingerTouchAction finger) { this(); this.actions.add(finger); } public FingerTouchAction finger(String name) { FingerTouchAction finger = new FingerTouchAction(name); actions.add(finger); return finger; } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/ios/service/IOSElement.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.ios.service; import org.cloud.sonic.driver.common.models.BaseElement; import org.cloud.sonic.driver.common.tool.SonicRespException; /** * @author Eason * web element interface */ public interface IOSElement extends BaseElement { void click() throws SonicRespException; void sendKeys(String text) throws SonicRespException; void sendKeys(String text, int frequency) throws SonicRespException; void clear() throws SonicRespException; String getText() throws SonicRespException; byte[] screenshot() throws SonicRespException; } ================================================ FILE: src/main/java/org/cloud/sonic/driver/ios/service/WdaClient.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.ios.service; import com.alibaba.fastjson.JSONObject; import org.cloud.sonic.driver.common.models.WindowSize; import org.cloud.sonic.driver.common.tool.Logger; import org.cloud.sonic.driver.common.tool.RespHandler; import org.cloud.sonic.driver.common.tool.SonicRespException; import org.cloud.sonic.driver.ios.enums.Orientation; import org.cloud.sonic.driver.ios.models.TouchActions; import java.util.List; /** * @author Eason * wda client interface */ public interface WdaClient { //Client Setting void setGlobalTimeOut(int timeOut); RespHandler getRespHandler(); void setRespHandler(RespHandler respHandler); Logger getLogger(); void showLog(); void disableLog(); //Session handler. String getRemoteUrl(); void setRemoteUrl(String remoteUrl); String getSessionId(); void setSessionId(String sessionId); void newSession(JSONObject capabilities) throws SonicRespException; void closeSession() throws SonicRespException; void checkSessionId() throws SonicRespException; //window handler. WindowSize getWindowSize() throws SonicRespException; //lock handler. boolean isLocked() throws SonicRespException; void lock() throws SonicRespException; void unlock() throws SonicRespException; //perform handler. void performTouchAction(TouchActions touchActions) throws SonicRespException; //button handler. void pressButton(String buttonName) throws SonicRespException; void doubleTap(int x, int y) throws SonicRespException; //keyboard handler. void sendKeys(String text, Integer frequency) throws SonicRespException; void setPasteboard(String contentType, String content) throws SonicRespException; byte[] getPasteboard(String contentType) throws SonicRespException; //source handler. String pageSource() throws SonicRespException; //siri handler. void sendSiriCommand(String command) throws SonicRespException; //app handler. void appActivate(String bundleId) throws SonicRespException; boolean appTerminate(String bundleId) throws SonicRespException; void appRunBackground(int duration) throws SonicRespException; void appAuthReset(int resource) throws SonicRespException; //element handler. void setDefaultFindElementInterval(Integer retry, Integer interval); IOSElement findElement(String selector, String value, Integer retry, Integer interval) throws SonicRespException; List findElementList(String selector, String value, Integer retry, Integer interval) throws SonicRespException; // get current active element IOSElement activeElement() throws SonicRespException; //screen handler. byte[] screenshot() throws SonicRespException; //appium setting handler. void setAppiumSettings(JSONObject settings) throws SonicRespException; void swipe(double fromX, double fromY, double toX, double toY, double duration) throws SonicRespException; void rotate(Orientation orientation) throws SonicRespException; Orientation getRotate() throws SonicRespException; } ================================================ FILE: src/main/java/org/cloud/sonic/driver/ios/service/impl/IOSElementImpl.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.ios.service.impl; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson2.JSON; import org.cloud.sonic.driver.common.models.BaseResp; import org.cloud.sonic.driver.common.models.ElementRect; import org.cloud.sonic.driver.common.tool.Logger; import org.cloud.sonic.driver.common.tool.SonicRespException; import org.cloud.sonic.driver.ios.service.IOSElement; import org.cloud.sonic.driver.ios.service.WdaClient; import java.util.Base64; public class IOSElementImpl implements IOSElement { private String id; private WdaClient wdaClient; private Logger logger; public IOSElementImpl(String id, WdaClient wdaClient) { this.id = id; this.wdaClient = wdaClient; logger = wdaClient.getLogger(); } @Override public void click() throws SonicRespException { wdaClient.checkSessionId(); BaseResp b = wdaClient.getRespHandler().getResp( HttpUtil.createPost(wdaClient.getRemoteUrl() + "/session/" + wdaClient.getSessionId() + "/element/" + id + "/click")); if (b.getErr() == null) { logger.info("click element %s.", id); } else { logger.error("click element %s failed.", id); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void sendKeys(String text) throws SonicRespException { sendKeys(text, 3); } @Override public void sendKeys(String text, int frequency) throws SonicRespException { wdaClient.checkSessionId(); JSONObject data = new JSONObject(); data.put("value", text.split("")); data.put("frequency", frequency); BaseResp b = wdaClient.getRespHandler().getResp( HttpUtil.createPost(wdaClient.getRemoteUrl() + "/session/" + wdaClient.getSessionId() + "/element/" + id + "/value") .body(data.toJSONString()), 60000); if (b.getErr() == null) { logger.info("send key to %s.", id); } else { logger.error("send key to %s failed.", id); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void clear() throws SonicRespException { wdaClient.checkSessionId(); BaseResp b = wdaClient.getRespHandler().getResp( HttpUtil.createPost(wdaClient.getRemoteUrl() + "/session/" + wdaClient.getSessionId() + "/element/" + id + "/clear"), 60000); if (b.getErr() == null) { logger.info("clear %s.", id); } else { logger.error("clear %s failed.", id); throw new SonicRespException(b.getErr().getMessage()); } } @Override public String getText() throws SonicRespException { wdaClient.checkSessionId(); BaseResp b = wdaClient.getRespHandler().getResp( HttpUtil.createGet(wdaClient.getRemoteUrl() + "/session/" + wdaClient.getSessionId() + "/element/" + id + "/text")); if (b.getErr() == null) { logger.info("get %s text %s.", id, b.getValue().toString()); return b.getValue().toString(); } else { logger.error("get %s text failed.", id); throw new SonicRespException(b.getErr().getMessage()); } } @Override public ElementRect getRect() throws SonicRespException { wdaClient.checkSessionId(); BaseResp b = wdaClient.getRespHandler().getResp( HttpUtil.createGet(wdaClient.getRemoteUrl() + "/session/" + wdaClient.getSessionId() + "/element/" + id + "/rect")); if (b.getErr() == null) { ElementRect elementRect = JSON.parseObject(b.getValue().toString(), ElementRect.class); logger.info("get %s rect %s.", id, elementRect.toString()); return elementRect; } else { logger.error("get %s rect failed.", id); throw new SonicRespException(b.getErr().getMessage()); } } @Override public String getAttribute(String name) throws SonicRespException { wdaClient.checkSessionId(); BaseResp b = wdaClient.getRespHandler().getResp( HttpUtil.createGet(wdaClient.getRemoteUrl() + "/session/" + wdaClient.getSessionId() + "/element/" + id + "/attribute/" + name), 60000); if (b.getErr() == null) { logger.info("get %s attribute %s.", id, name); return b.getValue().toString(); } else { logger.info("get %s attribute %s.", id, name); throw new SonicRespException(b.getErr().getMessage()); } } @Override public String getUniquelyIdentifies() throws SonicRespException { return id; } @Override public byte[] screenshot() throws SonicRespException { wdaClient.checkSessionId(); BaseResp b = wdaClient.getRespHandler().getResp( HttpUtil.createGet(wdaClient.getRemoteUrl() + "/session/" + wdaClient.getSessionId() + "/element/" + id + "/screenshot"), 60000); if (b.getErr() == null) { logger.info("get element %s screenshot.", id); return Base64.getMimeDecoder().decode(b.getValue().toString()); } else { logger.error("get element %s screenshot failed.", id); throw new SonicRespException(b.getErr().getMessage()); } } @Override public boolean isDisplayed() throws SonicRespException { wdaClient.checkSessionId(); BaseResp b = wdaClient.getRespHandler().getResp( HttpUtil.createGet(wdaClient.getRemoteUrl() + "/session/" + wdaClient.getSessionId() + "/element/" + id + "/displayed")); if (b.getErr() == null) { logger.info("get %s displayed,result is %s.", id, b.getValue().toString()); return Boolean.parseBoolean(b.getValue().toString()); } else { logger.error("get %s displayed failed.", id); throw new SonicRespException(b.getErr().getMessage()); } } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/ios/service/impl/WdaClientImpl.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.ios.service.impl; import cn.hutool.http.HttpUtil; import cn.hutool.http.Method; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.cloud.sonic.driver.common.models.BaseResp; import org.cloud.sonic.driver.common.models.SessionInfo; import org.cloud.sonic.driver.common.models.WindowSize; import org.cloud.sonic.driver.common.tool.Logger; import org.cloud.sonic.driver.common.tool.RespHandler; import org.cloud.sonic.driver.common.tool.SonicRespException; import org.cloud.sonic.driver.ios.enums.Orientation; import org.cloud.sonic.driver.ios.models.TouchActions; import org.cloud.sonic.driver.ios.service.IOSElement; import org.cloud.sonic.driver.ios.service.WdaClient; import org.jsoup.select.CombiningEvaluator; import java.nio.charset.StandardCharsets; import java.util.*; public class WdaClientImpl implements WdaClient { private String remoteUrl; private String sessionId; private RespHandler respHandler; private Logger logger; private final String LEGACY_WEB_ELEMENT_IDENTIFIER = "ELEMENT"; private final String WEB_ELEMENT_IDENTIFIER = "element-6066-11e4-a52e-4f735466cecf"; private int FIND_ELEMENT_INTERVAL = 3000; private int FIND_ELEMENT_RETRY = 5; public WdaClientImpl() { respHandler = new RespHandler(); logger = new Logger(); } private void checkBundleId(String bundleId) throws SonicRespException { if (bundleId == null || bundleId.length() == 0) { logger.error("bundleId not found."); throw new SonicRespException("bundleId not found."); } } private String parseElementId(Object o) { JSONObject jsonObject = (JSONObject) o; List identifier = Arrays.asList(LEGACY_WEB_ELEMENT_IDENTIFIER, WEB_ELEMENT_IDENTIFIER); for (String i : identifier) { String result = jsonObject.getString(i); if (result != null && result.length() > 0) { return result; } } return ""; } @Override public void setGlobalTimeOut(int timeOut) { respHandler.setRequestTimeOut(timeOut); } @Override public RespHandler getRespHandler() { return respHandler; } @Override public void setRespHandler(RespHandler respHandler) { this.respHandler = respHandler; } @Override public Logger getLogger() { return logger; } @Override public void showLog() { logger.showLog(); } @Override public void disableLog() { logger.disableLog(); } @Override public String getRemoteUrl() { return remoteUrl; } @Override public void setRemoteUrl(String remoteUrl) { this.remoteUrl = remoteUrl; } @Override public String getSessionId() { return sessionId; } @Override public void setSessionId(String sessionId) { this.sessionId = sessionId; } @Override public void newSession(JSONObject capabilities) throws SonicRespException { JSONObject data = new JSONObject(); data.put("capabilities", capabilities); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session").body(data.toJSONString())); if (b.getErr() == null) { SessionInfo sessionInfo = JSON.parseObject(b.getValue().toString(), SessionInfo.class); setSessionId(sessionInfo.getSessionId()); logger.info("start session successful!"); logger.info("session : %s", sessionInfo.getSessionId()); logger.info("session capabilities : %s", sessionInfo.getCapabilities().toString()); } else { logger.error("start session failed."); logger.error("cause: %s", b.getErr().toString()); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void closeSession() throws SonicRespException { checkSessionId(); respHandler.getResp(HttpUtil.createRequest(Method.DELETE, remoteUrl + "/session/" + sessionId)); logger.info("close session successful!"); } @Override public void checkSessionId() throws SonicRespException { if (sessionId == null || sessionId.length() == 0) { logger.error("sessionId not found."); throw new SonicRespException("sessionId not found."); } } @Override public WindowSize getWindowSize() throws SonicRespException { checkSessionId(); BaseResp b = respHandler.getResp(HttpUtil.createGet(remoteUrl + "/session/" + sessionId + "/window/size")); if (b.getErr() == null) { logger.info("get window size %s.", b.getValue().toString()); return JSON.parseObject(b.getValue().toString(), WindowSize.class); } else { logger.error("get window size failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public boolean isLocked() throws SonicRespException { checkSessionId(); BaseResp b = respHandler.getResp(HttpUtil.createGet(remoteUrl + "/session/" + sessionId + "/wda/locked")); if (b.getErr() == null) { logger.info("device lock status: %b.", b.getValue()); return (boolean) b.getValue(); } else { logger.error("get device lock status failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void lock() throws SonicRespException { checkSessionId(); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/wda/lock")); if (b.getErr() == null) { logger.info("lock device."); } else { logger.error("lock device failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void unlock() throws SonicRespException { checkSessionId(); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/wda/unlock")); if (b.getErr() == null) { logger.info("unlock device."); } else { logger.error("unlock device failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void performTouchAction(TouchActions touchActions) throws SonicRespException { checkSessionId(); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/actions") .body(JSON.toJSONString(touchActions))); if (b.getErr() == null) { logger.info("perform action %s.", touchActions.toString()); } else { logger.error("perform failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void pressButton(String buttonName) throws SonicRespException { checkSessionId(); JSONObject data = new JSONObject(); data.put("name", buttonName); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/wda/pressButton") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("press button %s.", buttonName); } else { logger.error("press button failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void doubleTap(int x, int y) throws SonicRespException { checkSessionId(); JSONObject data = new JSONObject(); data.put("x",x); data.put("y",y); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/wda/doubleTap") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("double tap success. %s", data.toJSONString()); } else { logger.error("double tap failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void sendKeys(String text, Integer frequency) throws SonicRespException { checkSessionId(); JSONObject data = new JSONObject(); data.put("value", text.split("")); data.put("frequency", frequency); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/wda/keys") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("send key %s.", text); } else { logger.error("send key failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void setPasteboard(String contentType, String content) throws SonicRespException { checkSessionId(); JSONObject data = new JSONObject(); data.put("contentType", contentType); data.put("content", Base64.getEncoder().encodeToString(content.getBytes(StandardCharsets.UTF_8))); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/wda/setPasteboard") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("set pasteboard %s.", content); } else { logger.error("set pasteboard failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public byte[] getPasteboard(String contentType) throws SonicRespException { checkSessionId(); JSONObject data = new JSONObject(); data.put("contentType", contentType); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/wda/getPasteboard") .body(data.toJSONString())); if (b.getErr() == null) { byte[] result = Base64.getMimeDecoder().decode(b.getValue().toString()); logger.info("get pasteboard length: %d.", result.length); return result; } else { logger.error("get pasteboard failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public String pageSource() throws SonicRespException { checkSessionId(); BaseResp b = respHandler.getResp(HttpUtil.createGet(remoteUrl + "/session/" + sessionId + "/source"), 5 * 60 * 1000); if (b.getErr() == null) { logger.info("get page source."); return b.getValue().toString(); } else { logger.error("get page source failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void sendSiriCommand(String command) throws SonicRespException { if (command != null && command.length() != 0) { checkSessionId(); JSONObject data = new JSONObject(); data.put("text", command); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/wda/siri/activate") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("send siri command: %s", command); } else { logger.error("send siri command [%s] failed.", command); throw new SonicRespException(b.getErr().getMessage()); } } else { logger.error("siri command is null!"); throw new SonicRespException("siri command is null!"); } } @Override public void appActivate(String bundleId) throws SonicRespException { checkSessionId(); checkBundleId(bundleId); JSONObject data = new JSONObject(); data.put("bundleId", bundleId); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/wda/apps/activate") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("activate app %s.", bundleId); } else { logger.error("activate app %s failed.", bundleId); throw new SonicRespException(b.getErr().getMessage()); } } @Override public boolean appTerminate(String bundleId) throws SonicRespException { checkSessionId(); checkBundleId(bundleId); JSONObject data = new JSONObject(); data.put("bundleId", bundleId); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/wda/apps/terminate") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("terminate app %s status: %b.", bundleId, b.getValue()); return (boolean) b.getValue(); } else { logger.error("terminate app failed.", bundleId); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void appRunBackground(int duration) throws SonicRespException { checkSessionId(); JSONObject data = new JSONObject(); data.put("duration", duration); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/wda/deactivateApp") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("run app background in %d seconds.", duration); } else { logger.error("run app background failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void appAuthReset(int resource) throws SonicRespException { checkSessionId(); JSONObject data = new JSONObject(); data.put("resource", resource); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/wda/resetAppAuth") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("reset app auth %s.", resource); } else { logger.error("reset app auth failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void setDefaultFindElementInterval(Integer retry, Integer interval) { if (retry != null) { FIND_ELEMENT_RETRY = retry; } if (interval != null) { FIND_ELEMENT_INTERVAL = interval; } } @Override public IOSElement findElement(String selector, String value, Integer retry, Integer interval) throws SonicRespException { IOSElement iosElement = null; int wait = 0; int intervalInit = (interval == null ? FIND_ELEMENT_INTERVAL : interval); int retryInit = (retry == null ? FIND_ELEMENT_RETRY : retry); String errMsg = ""; while (wait < retryInit) { wait++; checkSessionId(); JSONObject data = new JSONObject(); data.put("using", selector); data.put("value", value); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/element") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("find element successful."); String id = parseElementId(b.getValue()); if (id.length() > 0) { iosElement = new IOSElementImpl(id, this); break; } else { logger.error("parse element id %s failed. retried %d times, retry in %d ms.", b.getValue().toString(), wait, intervalInit); } } else { logger.error("element not found. retried %d times, retry in %d ms.", wait, intervalInit); errMsg = b.getErr().getMessage(); } if (wait < retryInit) { try { Thread.sleep(intervalInit); } catch (InterruptedException e) { e.printStackTrace(); } } } if (iosElement == null) { throw new SonicRespException(errMsg); } return iosElement; } @Override public List findElementList(String selector, String value, Integer retry, Integer interval) throws SonicRespException { List iosElementList = new ArrayList<>(); int wait = 0; int intervalInit = (interval == null ? FIND_ELEMENT_INTERVAL : interval); int retryInit = (retry == null ? FIND_ELEMENT_RETRY : retry); String errMsg = ""; while (wait < retryInit) { wait++; checkSessionId(); JSONObject data = new JSONObject(); data.put("using", selector); data.put("value", value); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/elements") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("find elements successful."); List ids = JSON.parseObject(b.getValue().toString(), ArrayList.class); for (JSONObject ele : ids) { String id = parseElementId(ele); if (id.length() > 0) { iosElementList.add(new IOSElementImpl(id, this)); } else { logger.error("parse element id %s failed.", ele); } } if (iosElementList.size() > 0) { break; } } else { logger.error("elements not found. retried %d times, retry in %d ms.", wait, intervalInit); errMsg = b.getErr().getMessage(); } if (wait < retryInit) { try { Thread.sleep(intervalInit); } catch (InterruptedException e) { e.printStackTrace(); } } } if (iosElementList.size() == 0) { throw new SonicRespException(errMsg); } return iosElementList; } @Override public IOSElement activeElement() throws SonicRespException { checkSessionId(); BaseResp b = respHandler.getResp( HttpUtil.createGet(remoteUrl + "/session/" + sessionId + "/element/active")); if (b.getErr() == null) { logger.info("find active elements successful."); JSONObject value = JSON.parseObject(b.getValue().toString(), JSONObject.class); List identifier = Arrays.asList("ELEMENT", "element-6066-11e4-a52e-4f735466cecf"); Iterator var4 = identifier.iterator(); String eleId; do { if (!var4.hasNext()) { return null; } String i = (String)var4.next(); eleId = value.getString(i); } while(eleId == null || eleId.length() <= 0); return new IOSElementImpl(eleId,this); } else { logger.error("active element not found."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public byte[] screenshot() throws SonicRespException { checkSessionId(); BaseResp b = respHandler.getResp( HttpUtil.createGet(remoteUrl + "/session/" + sessionId + "/screenshot"), 60000); if (b.getErr() == null) { logger.info("get screenshot."); return Base64.getMimeDecoder().decode(b.getValue().toString()); } else { logger.error("get screenshot failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void setAppiumSettings(JSONObject settings) throws SonicRespException { checkSessionId(); JSONObject data = new JSONObject(); data.put("settings", settings); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/appium/settings") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("set appium settings %s.", settings.toJSONString()); } else { logger.error("set appium settings failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void swipe(double fromX, double fromY, double toX, double toY, double duration) throws SonicRespException { checkSessionId(); JSONObject data = new JSONObject(); double maxSwipeDistance = Math.max(Math.abs(toX - fromX), Math.abs(toY - fromY)); double velocity = maxSwipeDistance / duration * 1000; // velocity 单位为像素/秒,这里做个限制,如果超过1000则滑动的过快,很容易触发fling行为 velocity = Math.min(velocity, 1000); data.put("fromX", fromX); data.put("fromY", fromY); data.put("toX", toX); data.put("toY", toY); data.put("velocity", velocity); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/session/" + sessionId + "/wda/pressAndDragWithVelocity") .body(data.toJSONString())); if (b.getErr() == null) { logger.info("perform swipe %s successful.", data.toString()); } else { logger.error("perform swipe %s failed.", data.toString()); throw new SonicRespException(b.getErr().getMessage()); } } @Override public void rotate(Orientation orientation) throws SonicRespException { JSONObject xyz = orientation.getValue(); BaseResp b = respHandler.getResp(HttpUtil.createPost(remoteUrl + "/rotation").body(String.valueOf(xyz.toJSONString()))); if (b.getErr() == null) { logger.info("set orientation success. %s", xyz.toJSONString()); } else { logger.error("set orientation failed."); throw new SonicRespException(b.getErr().getMessage()); } } @Override public Orientation getRotate() throws SonicRespException { BaseResp b = respHandler.getResp(HttpUtil.createGet(remoteUrl + "/rotation")); if (b.getErr() == null) { logger.info("get orientation %s.",b.getValue()); JSONObject value = JSON.parseObject(b.getValue().toString(), JSONObject.class); Integer zValue = Integer.valueOf(value.getString("z")); switch (zValue) { case 0: return Orientation.PORTRAIT; case 180: return Orientation.PORTRAITUPSIDEDOWN; case 90: return Orientation.LANDSCAPELEFT; case 270: return Orientation.LANDSCAPERIGHT; default: return Orientation.UNKNOWN; } } else { logger.error("get orientation failed."); throw new SonicRespException(b.getErr().getMessage()); } } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/poco/PocoDriver.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.poco; import org.cloud.sonic.driver.common.models.WindowSize; import org.cloud.sonic.driver.common.tool.SonicRespException; import org.cloud.sonic.driver.poco.enums.PocoEngine; import org.cloud.sonic.driver.poco.enums.PocoSelector; import org.cloud.sonic.driver.poco.models.PocoElement; import org.cloud.sonic.driver.poco.service.PocoClient; import org.cloud.sonic.driver.poco.service.impl.PocoClientImpl; import org.jsoup.nodes.Element; import java.util.List; /** * @author Eason * poco driver */ public class PocoDriver { private PocoClient pocoClient; /** * init poco driver * * @param pocoEngine */ public PocoDriver(PocoEngine pocoEngine) { this(pocoEngine, pocoEngine.getDefaultPort()); } /** * init poco driver * * @param pocoEngine * @param port */ public PocoDriver(PocoEngine pocoEngine, int port) { pocoClient = new PocoClientImpl(); pocoClient.newClient(pocoEngine, port); } /** * close driver */ public void closeDriver() { pocoClient.closeClient(); } /** * show log. */ public void showLog() { pocoClient.showLog(); } /** * disable log. */ public void disableLog() { pocoClient.disableLog(); } /** * get Poco element * * @return * @throws SonicRespException */ public PocoElement getPageSource() throws SonicRespException { return pocoClient.pageSource(); } /** * get Poco element for Json * * @return * @throws SonicRespException */ public String getPageSourceForJsonString() throws SonicRespException { return pocoClient.pageSourceForJsonString(); } /** * get Poco element for jsoup.Element * * @return * @throws SonicRespException */ public Element getPageSourceForXmlElement() throws SonicRespException { return pocoClient.pageSourceForXmlElement(); } /** * find poco elements * * @param expression * @return */ public List findElements(String selector, String expression) throws SonicRespException { return pocoClient.findElements(selector, expression); } /** * find poco elements * * @param expression * @return */ public List findElements(PocoSelector selector, String expression) throws SonicRespException { return findElements(selector.getSelector(), expression); } /** * find poco element * * @param expression * @return */ public PocoElement findElement(String selector, String expression) throws SonicRespException { return pocoClient.findElement(selector, expression); } /** * find poco element * * @param expression * @return */ public PocoElement findElement(PocoSelector selector, String expression) throws SonicRespException { return findElement(selector.getSelector(), expression); } /** * Freeze page source */ public void freezeSource() { pocoClient.freezeSource(); } /** * Thaw page source */ public void thawSource() { pocoClient.thawSource(); } /** * get windows size * * @return * @throws SonicRespException */ public WindowSize getScreenSize() throws SonicRespException { return pocoClient.getScreenSize(); } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/poco/enums/PocoEngine.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.poco.enums; public enum PocoEngine { UNITY_3D("Unity3d", 5001), UE4("UE4", 5001), COCOS_2DX_JS("Cocos2dx-js", 5003), COCOS_2DX_LUA("Cocos2dx-lua", 15004), COCOS_2DX_C_PLUS_1("Cocos2dx-c++", 18888), COCOS_CREATOR("cocos-creator", 5003), EGRET("Egret", 5003); private final String name; private final int defaultPort; PocoEngine(String name, int defaultPort) { this.name = name; this.defaultPort = defaultPort; } public int getDefaultPort() { return defaultPort; } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/poco/enums/PocoSelector.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.poco.enums; public enum PocoSelector { POCO("poco"), XPATH("xpath"), CSS_SELECTOR("cssSelector"); private final String selector; PocoSelector(String selector) { this.selector = selector; } public String getSelector() { return selector; } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/poco/models/PocoElement.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.poco.models; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; import org.cloud.sonic.driver.common.models.BaseElement; import org.cloud.sonic.driver.common.models.ElementRect; import org.cloud.sonic.driver.common.tool.SonicRespException; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @Getter @ToString @AllArgsConstructor public class PocoElement implements BaseElement { public String currentNodeSelector = "Root"; private Payload payload; private List children; RootElement rootElement; private Element currentNodeXmlElement; private long version; @Override // the poco given pos is a point where the origin of the coordinates will change with // the rotation of the screen. The function and others are not uniform, so the implementation // is not considered, you can get the pos point through the payload, and then convert it at // the application layer public ElementRect getRect() throws SonicRespException { throw new SonicRespException("poco element unrealized"); } @Override public String getAttribute(String name) throws SonicRespException { return currentNodeXmlElement.attr(name); } @Override public String getUniquelyIdentifies() throws SonicRespException { return currentNodeSelector; } @Override public boolean isDisplayed() throws SonicRespException { throw new SonicRespException("poco element unrealized"); } @Getter @ToString @AllArgsConstructor public class Payload { private String layer; private String name; private String tag; private String text; private String texture; private Integer _instanceId; private Integer _ilayer; private String type; private Boolean visible; private ZOrders zOrders; private List components; private List anchorPoint; private List scale; private List size; private List pos; private Boolean clickable; @Getter @ToString @AllArgsConstructor public class ZOrders { private Float global; private Float local; public ZOrders() { } } public Payload() { zOrders = new ZOrders(); } } public PocoElement(RootElement root) { this.rootElement = root; this.currentNodeSelector = root.getXmlElement().cssSelector(); payload = new Payload(); children = new ArrayList<>(); } public PocoElement(RootElement root, Element currentNodeXmlElement) { this(root); this.currentNodeXmlElement = currentNodeXmlElement; this.currentNodeSelector = currentNodeXmlElement.cssSelector(); parseXmlNode(currentNodeXmlElement); } public Payload getPayload() { if (rootElement.getVersion() != getVersion()) { Element xmlPocoNode = rootElement.getXmlElement().select(currentNodeSelector).first(); if (xmlPocoNode == null) { return null; } parseXmlNode(xmlPocoNode); version = rootElement.getVersion(); } return payload; } public List getChildren() { if (rootElement.getVersion() != getVersion()) { Element xmlPocoNode = rootElement.getXmlElement().select(currentNodeSelector).first(); if (xmlPocoNode == null) { return null; } children.clear(); currentNodeXmlElement = xmlPocoNode; Elements childList = xmlPocoNode.children(); for (Element child : childList) { children.add(new PocoElement(rootElement, child)); } version = rootElement.getVersion(); } return children; } public void parseXmlNode(Element xmlNode) { payload.layer = xmlNode.attr("layer"); payload.name = xmlNode.attr("name"); payload.tag = xmlNode.attr("tag"); payload.text = xmlNode.attr("text"); payload.texture = xmlNode.attr("texture"); String _instanceId = xmlNode.attr("_instanceId"); payload._instanceId = _instanceId.isEmpty() ? 0 : Integer.parseInt(_instanceId); String _ilayer = xmlNode.attr("_ilayer"); payload._ilayer = _ilayer.isEmpty() ? 0 : Integer.parseInt(_ilayer); payload.type = xmlNode.attr("type"); String visible = xmlNode.attr("visible"); payload.visible = Boolean.parseBoolean(visible); String global = xmlNode.attr("global"); payload.zOrders.global = global.isEmpty() ? 0 : Float.parseFloat(global); String local = xmlNode.attr("local"); payload.zOrders.local = local.isEmpty() ? 0 : Float.parseFloat(local); String components = xmlNode.attr("components"); if (components.length() > 2) { components = components.substring(1, components.length() - 1); payload.components = Arrays.asList(components.split(",")); } payload.anchorPoint = parseFloatAttrList(xmlNode.attr("anchorPoint")); payload.scale = parseFloatAttrList(xmlNode.attr("scale")); payload.size = parseFloatAttrList(xmlNode.attr("size")); payload.pos = parseFloatAttrList(xmlNode.attr("pos")); payload.clickable = Boolean.parseBoolean(xmlNode.attr("clickable")); } private List parseFloatAttrList(String floatAttrStr) { if (floatAttrStr.length() <= 2) return null; List result = new ArrayList(); floatAttrStr = floatAttrStr.substring(1, floatAttrStr.length() - 1); for (String numStr : floatAttrStr.split(",")) { result.add(Float.parseFloat(numStr)); } return result; } public Boolean currentTheNodeExists() { return rootElement.getXmlElement().select(currentNodeSelector).first() != null; } public PocoElement getParentNode() { Element xmlPocoNode = rootElement.getXmlElement().select(currentNodeSelector).first(); if (xmlPocoNode == null) { return null; } Element parentNode = xmlPocoNode.parent(); if (parentNode == null) { return null; } return new PocoElement(rootElement, parentNode); } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/poco/models/RootElement.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.poco.models; import org.jsoup.nodes.Element; public class RootElement { private Element XmlElement; private long version; public RootElement() { } public RootElement(Element XmlElement) { this.XmlElement = XmlElement; } public RootElement(Element XmlElement, long version) { this(XmlElement); this.version = version; } public Element getXmlElement() { return XmlElement; } public void setXmlElement(Element xmlElement) { this.XmlElement = xmlElement; } public long getVersion() { return version; } public void setVersion(long version) { this.version = version; } public synchronized void updateVersion(Element rootXmlElement) { this.XmlElement = rootXmlElement; this.version += 1; } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/poco/service/PocoClient.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.poco.service; import org.cloud.sonic.driver.common.models.WindowSize; import org.cloud.sonic.driver.common.tool.Logger; import org.cloud.sonic.driver.common.tool.SonicRespException; import org.cloud.sonic.driver.poco.enums.PocoEngine; import org.cloud.sonic.driver.poco.models.PocoElement; import org.jsoup.nodes.Element; import java.util.List; /** * @author Eason * poco client interface */ public interface PocoClient { //Client Setting Logger getLogger(); void showLog(); void disableLog(); //Client handler. void newClient(PocoEngine engine, int port); void closeClient(); //source handler. PocoElement pageSource() throws SonicRespException; String pageSourceForJsonString() throws SonicRespException; Element pageSourceForXmlElement() throws SonicRespException; PocoElement findElement(String selector, String expression) throws SonicRespException; List findElements(String selector, String expression) throws SonicRespException; void freezeSource(); void thawSource(); //other WindowSize getScreenSize() throws SonicRespException; } ================================================ FILE: src/main/java/org/cloud/sonic/driver/poco/service/PocoConnection.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.poco.service; import com.alibaba.fastjson.JSONObject; import org.cloud.sonic.driver.common.tool.SonicRespException; public interface PocoConnection { void connect(); void disconnect(); String sendAndReceive(JSONObject jsonObject) throws SonicRespException; } ================================================ FILE: src/main/java/org/cloud/sonic/driver/poco/service/impl/PocoClientImpl.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.poco.service.impl; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import org.cloud.sonic.driver.common.models.WindowSize; import org.cloud.sonic.driver.common.tool.Logger; import org.cloud.sonic.driver.common.tool.SonicRespException; import org.cloud.sonic.driver.poco.enums.PocoEngine; import org.cloud.sonic.driver.poco.models.PocoElement; import org.cloud.sonic.driver.poco.models.RootElement; import org.cloud.sonic.driver.poco.service.PocoClient; import org.cloud.sonic.driver.poco.service.PocoConnection; import org.cloud.sonic.driver.poco.util.PocoJsonToXml; import org.jsoup.Jsoup; import org.jsoup.nodes.Element; import org.jsoup.parser.Parser; import org.jsoup.select.Elements; import java.util.*; import static org.cloud.sonic.driver.poco.enums.PocoEngine.*; public class PocoClientImpl implements PocoClient { private Logger logger; private PocoConnection pocoConnection; private PocoEngine engine; private String source; public RootElement rootNode; private boolean isFrozen = false; public PocoClientImpl() { logger = new Logger(); rootNode = new RootElement(); } @Override public Logger getLogger() { return logger; } @Override public void showLog() { logger.showLog(); } @Override public void disableLog() { logger.disableLog(); } @Override public void newClient(PocoEngine engine, int port) { this.engine = engine; if (engine.equals(COCOS_2DX_JS) || engine.equals(COCOS_CREATOR) || engine.equals(EGRET)) { pocoConnection = new WebSocketClientImpl(port, logger); } else { pocoConnection = new SocketClientImpl(port, logger); } pocoConnection.connect(); } @Override public void closeClient() { pocoConnection.disconnect(); } @Override public PocoElement pageSource() throws SonicRespException { pageSourceForXmlElement(); return new PocoElement(rootNode); } @Override public String pageSourceForJsonString() throws SonicRespException { if (isFrozen) { return source; } JSONObject jsonObject = new JSONObject(); jsonObject.put("jsonrpc", "2.0"); jsonObject.put("params", Arrays.asList(true)); jsonObject.put("id", UUID.randomUUID().toString()); jsonObject.put("method", "Dump"); if (engine.equals(COCOS_CREATOR) || engine.equals(COCOS_2DX_JS)) { jsonObject.put("method", "dump"); } source = pocoConnection.sendAndReceive(jsonObject); return source; } @Override public Element pageSourceForXmlElement() throws SonicRespException { pageSourceForJsonString(); // String pocoJson = "{\"Root\"" + source.substring("{\"result\"".length()); Element rootXmlElement = Jsoup.parse(PocoJsonToXml.jsonObjToXml(JSON.parseObject(source).getJSONObject("result")), "", Parser.xmlParser()); rootNode.updateVersion(rootXmlElement); return rootNode.getXmlElement(); } @Override public PocoElement findElement(String selector, String expression) throws SonicRespException { List pocoElements = findElements(selector, expression); return pocoElements.get(0); } @Override public List findElements(String selector, String expression) throws SonicRespException { if (rootNode.getXmlElement() == null) { pageSourceForXmlElement(); } Elements xmlNodes = null; switch (selector) { case "poco": String newExpress = ""; String[] steps = expression.split("\\."); for (String step : steps) { if (step.startsWith("poco")) { newExpress += ("//*" + parseAttr(step)); } else if (step.startsWith("child")) { newExpress += ("/*" + parseAttr(step)); } if (step.endsWith("]") && step.contains("[")) { int index = Integer.parseInt(step.substring(step.indexOf("[") + 1, step.indexOf("]"))); newExpress = String.format("(%s)[%d]", newExpress, (index + 1)); } } xmlNodes = rootNode.getXmlElement().selectXpath(newExpress); break; case "xpath": xmlNodes = rootNode.getXmlElement().selectXpath(expression); break; case "cssSelector": xmlNodes = rootNode.getXmlElement().select(expression); break; } if (xmlNodes == null || xmlNodes.isEmpty()) { throw new SonicRespException(String.format("poco element not found for selector:%s, value:%s", selector, expression)); } List result = new ArrayList<>(); for (Element node : xmlNodes) { PocoElement pocoElement = new PocoElement(rootNode, node); result.add(pocoElement); } return result; } private String parseAttr(String express) { String result = "["; String attrExpression = express.substring(express.indexOf("(") + 1, express.lastIndexOf(")")); if (attrExpression.startsWith("\"") && attrExpression.endsWith("\"")) { attrExpression = "name=" + attrExpression.replace("\"", ""); } String[] attrs = attrExpression.split(","); for (String attr : attrs) { String field = attr.substring(0, attr.indexOf("=")); String value = attr.substring(attr.indexOf("=") + 1).replace("\"", ""); if ("visible".equals(value) || "clickable".equals(value)) { value = value.toLowerCase(Locale.ROOT); } result += ("@" + field + "=\"" + value + "\" and "); } result += "]"; return result.replace(" and ]", "]"); } @Override public void freezeSource() { isFrozen = true; } @Override public void thawSource() { isFrozen = false; } @Override public WindowSize getScreenSize() throws SonicRespException { if (engine.equals(UNITY_3D) || engine.equals(COCOS_2DX_LUA)) { JSONObject jsonObject = new JSONObject(); jsonObject.put("jsonrpc", "2.0"); jsonObject.put("params", Arrays.asList(true)); jsonObject.put("id", UUID.randomUUID().toString()); jsonObject.put("method", "GetScreenSize"); List result = ((JSONArray) JSONObject.parseObject( pocoConnection.sendAndReceive(jsonObject)) .get("result")) .toJavaList(Integer.class); return new WindowSize(result.get(0), result.get(1)); } return null; } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/poco/service/impl/SocketClientImpl.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.poco.service.impl; import com.alibaba.fastjson.JSONObject; import org.cloud.sonic.driver.common.tool.Logger; import org.cloud.sonic.driver.common.tool.SonicRespException; import org.cloud.sonic.driver.poco.service.PocoConnection; import org.cloud.sonic.driver.poco.util.PocoTool; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; public class SocketClientImpl implements PocoConnection { private Socket poco = null; private InputStream inputStream = null; private OutputStream outputStream = null; private int port; private Logger logger; public SocketClientImpl(int port, Logger logger) { this.port = port; this.logger = logger; } @Override public String sendAndReceive(JSONObject jsonObject) throws SonicRespException { byte[] data = jsonObject.toJSONString().getBytes(StandardCharsets.UTF_8); byte[] header = intToByteArray(data.length); synchronized (SocketClientImpl.class) { try { outputStream.write(header); outputStream.write(data); outputStream.flush(); byte[] head = new byte[4]; byte[] buffer = new byte[8192]; inputStream.read(head); int headLen = toInt(head); ByteBuffer rData = ByteBuffer.allocate(headLen); while (poco.isConnected() && !Thread.interrupted()) { int realLen; realLen = inputStream.read(buffer); if (realLen > 0) { rData.put(buffer, 0, realLen); } if (rData.position() == headLen) { rData.flip(); String pocoResult = new String(rData.array(), StandardCharsets.UTF_8); int subStartIndex = pocoResult.indexOf("\"result\""); // when cocos is integrated there will be no id // String pocoPrefix = pocoResult.substring(0, subStartIndex) + "}"; // if (PocoTool.checkPocoRpcResultID(pocoPrefix, jsonObject.getString("id"))) { return "{" + pocoResult.substring(subStartIndex); // } else { // throw new SonicRespException("id not found!"); // } } } } catch (Exception e) { throw new SonicRespException(e.getMessage()); } } return null; } @Override public void connect() { int waitConnect = 0; while (poco == null || !poco.isConnected()) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } waitConnect++; if (waitConnect >= 20) { break; } try { poco = new Socket("localhost", port); inputStream = poco.getInputStream(); outputStream = poco.getOutputStream(); } catch (Exception e) { logger.info(e.getMessage()); } } if (poco != null) { logger.info("poco socket connected."); } else { logger.info("poco socket disconnected."); } } @Override public void disconnect() { if (poco != null && poco.isConnected()) { try { poco.close(); logger.info("poco socket closed."); } catch (IOException e) { e.printStackTrace(); } } if (inputStream != null) { try { inputStream.close(); logger.info("poco input stream closed."); } catch (IOException e) { e.printStackTrace(); } } if (outputStream != null) { try { outputStream.close(); logger.info("poco output stream closed."); } catch (IOException e) { e.printStackTrace(); } } } private byte[] intToByteArray(int i) { byte[] result = new byte[4]; result[0] = (byte) (i & 0xff); result[1] = (byte) (i >> 8 & 0xff); result[2] = (byte) (i >> 16 & 0xff); result[3] = (byte) (i >> 24 & 0xff); return result; } private int toInt(byte[] b) { int res = 0; for (int i = 0; i < b.length; i++) { res += (b[i] & 0xff) << (i * 8); } return res; } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/poco/service/impl/WebSocketClientImpl.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.poco.service.impl; import com.alibaba.fastjson.JSONObject; import org.cloud.sonic.driver.common.tool.Logger; import org.cloud.sonic.driver.common.tool.SonicRespException; import org.cloud.sonic.driver.poco.service.PocoConnection; import org.cloud.sonic.driver.poco.util.PocoTool; import org.java_websocket.handshake.ServerHandshake; import java.net.URI; import java.net.URISyntaxException; public class WebSocketClientImpl implements PocoConnection { private int port; private Logger logger; private org.java_websocket.client.WebSocketClient webSocketClient; private String result = null; public WebSocketClientImpl(int port, Logger logger) { this.port = port; this.logger = logger; } @Override public String sendAndReceive(JSONObject jsonObject) throws SonicRespException { synchronized (WebSocketClientImpl.class) { webSocketClient.send(jsonObject.toString()); int wait = 0; while (result == null) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } wait++; if (wait >= 20) { break; } } if (result != null) { int subStartIndex = result.indexOf("\"result\""); String pocoPrefix = result.substring(0, subStartIndex) + "}"; if (PocoTool.checkPocoRpcResultID(pocoPrefix, jsonObject.getString("id"))) { String re = "{" + result.substring(subStartIndex); result = null; return re; } else { throw new SonicRespException("id not found!"); } } } return null; } @Override public void connect() { URI ws = null; try { ws = new URI("ws://localhost:" + port); } catch (URISyntaxException e) { e.printStackTrace(); } webSocketClient = new org.java_websocket.client.WebSocketClient(ws) { @Override public void onOpen(ServerHandshake serverHandshake) { logger.info("poco ws connected."); } @Override public void onMessage(String s) { logger.info(s); result = s; } @Override public void onClose(int i, String s, boolean b) { logger.info("poco ws close."); } @Override public void onError(Exception e) { } }; webSocketClient.connect(); int waitConnect = 0; while (!webSocketClient.isOpen()) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } waitConnect++; if (waitConnect >= 20) { break; } } } @Override public void disconnect() { webSocketClient.close(); } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/poco/util/PocoJsonToXml.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.poco.util; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import org.cloud.sonic.driver.common.tool.SonicRespException; import java.util.Map; public class PocoJsonToXml { /** * convert json to xml * * @param jo JSONObject * @return xml */ public static String jsonObjToXml(JSONObject jo) throws SonicRespException { String xml = "\n" + jsonToXml(jo, ""); return xml; } /** * json object to xml * * @param jo JSONObject * @param gt "\n" shifter * @return XML */ @SuppressWarnings("rawtypes") private static String jsonToXml(JSONObject jo, String gt) throws SonicRespException { StringBuffer xmlStr = new StringBuffer(); try { JSONObject payload = jo.getJSONObject("payload"); JSONArray children = jo.getJSONArray("children"); xmlStr.append(gt); xmlStr.append("<"); String name = String.join("__", payload.get("name").toString().split(" ")); if (name.equals("")) name = "Root"; if (!checkName(name)) { name = "invalidName"; } xmlStr.append(name); xmlStr.append(getAttrStr(payload)); xmlStr.append(">\n"); if (children != null) { for (int i = 0; i < children.size(); i++) { JSONObject child = children.getJSONObject(i); xmlStr.append(jsonToXml(child, gt + "\t")); } } addTag(xmlStr, gt, name, true); } catch (Exception e) { throw new SonicRespException(e.getMessage()); } return xmlStr.toString(); } public static void addTag(StringBuffer xmlStr, String gt, String tagName, boolean isItTheEnd) { xmlStr.append(gt); if (isItTheEnd) { xmlStr.append("\n"); } public static StringBuilder getAttrStr(JSONObject payload) { StringBuilder sb = new StringBuilder(); sb.append(" "); for (Map.Entry stringObjectEntry : payload.entrySet()) { Map.Entry entry = (Map.Entry) stringObjectEntry; String key = entry.getKey().toString(); String val = entry.getValue().toString(); if (key.equals("zOrders")) { JSONObject zOrders = JSONObject.parseObject(val); sb.append(String.format("global=\"%s\" ", zOrders.get("global"))); sb.append(String.format("local=\"%s\" ", zOrders.get("local"))); } else if (key.equals("components")) { val = val.replace("\"", ""); sb.append(String.format("%s=\"%s\" ", key, val)); } else { val = escapingSpecialCharacters(val); sb.append(String.format("%s=\"%s\" ", key, val)); } } return sb; } private static boolean checkName(String name) { String re = "^[:A-Z_a-z\\u00C0\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02ff\\u0370-\\u037d" + "\\u037f-\\u1fff\\u200c\\u200d\\u2070-\\u218f\\u2c00-\\u2fef\\u3001-\\ud7ff" + "\\uf900-\\ufdcf\\ufdf0-\\ufffd\\x10000-\\xEFFFF]" + "[:A-Z_a-z\\u00C0\\u00D6\\u00D8-\\u00F6" + "\\u00F8-\\u02ff\\u0370-\\u037d\\u037f-\\u1fff\\u200c\\u200d\\u2070-\\u218f" + "\\u2c00-\\u2fef\\u3001-\\udfff\\uf900-\\ufdcf\\ufdf0-\\ufffd\\-\\.0-9" + "\\u00b7\\u0300-\\u036f\\u203f-\\u2040]*\\Z"; return name.matches(re); } private static String escapingSpecialCharacters(String originStr) { if (originStr == null) return null; originStr = originStr.replace("&", "&"); originStr = originStr.replace("<", "<"); originStr = originStr.replace(">", ">"); originStr = originStr.replace("\"", """); originStr = originStr.replace("'", "'"); return originStr; } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/poco/util/PocoTool.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.poco.util; import com.alibaba.fastjson.JSONObject; public class PocoTool { public static boolean checkPocoRpcResultID(String pocoRpcResult, String sendID) { JSONObject object = JSONObject.parseObject(pocoRpcResult); return sendID.equals(object.getString("id")); } } ================================================ FILE: src/main/java/org/cloud/sonic/driver/poco/util/PocoXYTransformer.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.poco.util; /*** * poco coordinate system conversion and vertical coordinate system converter */ public class PocoXYTransformer { /** * Convert adb vertical coordinates to poco coordinate system coordinates according to different directions * * @param x x poco coordinate point x * @param y y poco coordinate point y * @param w w ScreenWidth * @param h h ScreenHeight * @param orientation The device orientation, based on the vertical direction of the mobile device, * rotate 90° counterclockwise, orientation=90, 180° counterclockwise, orientation=180, * and so on * @return {@link int[]} */ public static double[] PocoTransformerVertical(double x, double y, double w, double h, Integer orientation) { if (orientation == 90) { double temp = x; x = w - y; y = temp; } else if (orientation == 180) { x = w - x; y = h - y; } else if (orientation == 270) { double temp = x; x = y; y = h - temp; } return new double[]{x, y}; } /** * Convert the coordinate system of poco to the coordinates in the adb vertical coordinate system * * @param x x vertical coordinate x * @param y y vertical coordinate y * @param w w ScreenWidth * @param h h ScreenHeight * @param orientation The device orientation, based on the vertical direction of the mobile device, * rotate 90° counterclockwise, orientation=90, 180° counterclockwise, orientation=180, * and so on * @return {@link int[]} */ public static double[] VerticalTransformerPoco(double x, double y, double w, double h, Integer orientation) { if (orientation == 90) { double temp = x; x = y; y = w - temp; } else if (orientation == 180) { x = w - x; y = h - y; } else if (orientation == 270) { double temp = x; x = h - y; y = temp; } return new double[]{x, y}; } } ================================================ FILE: src/test/java/org/cloud/sonic/driver/android/AndroidDriverTest.java ================================================ package org.cloud.sonic.driver.android; import com.alibaba.fastjson.JSONObject; import org.cloud.sonic.driver.android.enmus.AndroidSelector; import org.cloud.sonic.driver.android.service.AndroidElement; import org.cloud.sonic.driver.common.enums.PasteboardType; import org.cloud.sonic.driver.common.models.ElementRect; import org.cloud.sonic.driver.common.models.WindowSize; import org.cloud.sonic.driver.common.tool.SonicRespException; import org.junit.*; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import javax.imageio.stream.FileImageOutputStream; import java.io.File; import java.io.IOException; import java.util.UUID; @RunWith(Parameterized.class) public class AndroidDriverTest { static AndroidDriver androidDriver; static String url = "http://localhost:6790"; @Parameterized.Parameters public static Object[][] data() { return new Object[1][0]; } @Before public void before() throws InterruptedException { Thread.sleep(2000); } @BeforeClass public static void beforeClass() throws SonicRespException { Boolean hasThrow = false; try { new AndroidDriver(url, null); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals("io.appium.uiautomator2.common.exceptions.InvalidArgumentException: 'capabilities' are mandatory for session creation", e.getMessage()); } Assert.assertTrue(hasThrow); androidDriver = new AndroidDriver(url, new JSONObject()); androidDriver.disableLog(); Assert.assertEquals(url, androidDriver.getUiaClient().getRemoteUrl()); Assert.assertTrue(androidDriver.getSessionId().length() > 0); androidDriver.showLog(); } @AfterClass public static void afterClass() throws SonicRespException { androidDriver.closeDriver(); } @Test public void testSession() { String sessionId = androidDriver.getSessionId(); androidDriver.getUiaClient().setSessionId(null); Boolean hasThrow = false; try { androidDriver.getUiaClient().pageSource(); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals("sessionId not found.", e.getMessage()); } Assert.assertTrue(hasThrow); androidDriver.getUiaClient().setSessionId(""); hasThrow = false; try { androidDriver.getUiaClient().pageSource(); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals("sessionId not found.", e.getMessage()); } Assert.assertTrue(hasThrow); androidDriver.getUiaClient().setSessionId(sessionId); } @Test public void testSource() throws SonicRespException { Assert.assertTrue(androidDriver.getPageSource().contains("android.widget.FrameLayout")); } @Test public void testGetWindowSize() throws SonicRespException { WindowSize size = androidDriver.getWindowSize(); Assert.assertNotNull(size); Assert.assertTrue(size.getHeight() > 0); Assert.assertTrue(size.getWidth() > 0); } @Test public void testClipboard() throws SonicRespException { androidDriver.setPasteboard(PasteboardType.PLAIN_TEXT, "abc"); androidDriver.getPasteboard(PasteboardType.PLAIN_TEXT); } @Test public void testFindElement() throws SonicRespException, InterruptedException, IOException { Boolean hasThrow = false; try { androidDriver.findElement("id", "android:id/content1").click(); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertTrue(e.getMessage().contains("An element could not be located on the page using the given search parameters")); } Assert.assertTrue(hasThrow); Thread.sleep(2000); androidDriver.findElement(AndroidSelector.Id, "android:id/content").click(); Thread.sleep(2000); AndroidElement w = androidDriver.findElement(AndroidSelector.XPATH, "//*[@text='标题']"); w.click(); String text = UUID.randomUUID().toString().substring(0, 6) + "中文"; w.sendKeys(text); w.sendKeys(text, true); Assert.assertEquals(text, w.getText()); w.clear(); androidDriver.setDefaultFindElementInterval(null, 3000); androidDriver.setDefaultFindElementInterval(5, null); androidDriver.setDefaultFindElementInterval(null, null); ElementRect elementRect = w.getRect(); Assert.assertTrue(elementRect.getX() > 0); Assert.assertTrue(elementRect.getY() > 0); Assert.assertTrue(elementRect.getWidth() > 0); Assert.assertTrue(elementRect.getHeight() > 0); Assert.assertTrue(elementRect.getCenter().getX() > 0); Assert.assertTrue(elementRect.getCenter().getY() > 0); byte[] bt = w.screenshot(); File output = new File("./" + UUID.randomUUID() + ".png"); FileImageOutputStream imageOutput = new FileImageOutputStream(output); imageOutput.write(bt, 0, bt.length); imageOutput.close(); output.delete(); Assert.assertEquals("android.widget.EditText", w.getAttribute("class")); } @Test public void testFindElementList() throws SonicRespException { int eleSize = androidDriver.findElementList("id", "android:id/content").size(); Assert.assertEquals(eleSize, androidDriver.findElementList(AndroidSelector.Id, "android:id/content").size()); } @Test public void testScreenshot() throws IOException, SonicRespException { byte[] bt = androidDriver.screenshot(); File output = new File("./" + UUID.randomUUID() + ".png"); FileImageOutputStream imageOutput = new FileImageOutputStream(output); imageOutput.write(bt, 0, bt.length); imageOutput.close(); output.delete(); } @Test public void testSetAppiumSettings() throws SonicRespException { androidDriver.setAppiumSettings(new JSONObject()); } @Test public void testSwipeAction() throws SonicRespException { // 默认滑动操作的完成时间,500毫秒 androidDriver.swipe(540, 1710, 540, 200); // 指定滑动操作在1000毫秒内完成 androidDriver.swipe(540, 1710, 540, 200, 1000); } @Test public void testTapAction() throws SonicRespException { // 替换为任意待测试的元素 AndroidElement androidElement = androidDriver.findElement(AndroidSelector.Id, "com.xueqiu.android:id/my_groups_new_title_bar_ding"); ElementRect elementRect = androidElement.getRect(); androidDriver.tap(elementRect.getX() + elementRect.getWidth() / 2, elementRect.getY() + elementRect.getHeight() / 2); } @Test public void testLongPressAction() throws SonicRespException { // 替换为任意待测试的元素 AndroidElement androidElement = androidDriver.findElement(AndroidSelector.Id, "com.xueqiu.android:id/my_groups_list_item_stock_name_label"); ElementRect elementRect = androidElement.getRect(); androidDriver.longPress((double) elementRect.getX() + (double) elementRect.getWidth() / 2, (double) elementRect.getY() + (double) elementRect.getHeight() / 2, 100); } @Test public void testDragAction() throws SonicRespException { // 替换为任意待测试的元素 androidDriver.drag(182, 629, 565, 2259, 100, null, null); } @Test public void testTouchActionDown() throws SonicRespException { // 替换为任意待测试的元素 androidDriver.touchAction("down", 182, 629); } @Test public void testTouchActionMove() throws SonicRespException { // 替换为任意待测试的元素 androidDriver.touchAction("move", 565, 2259); } @Test public void testTouchActionUp() throws SonicRespException { // 替换为任意待测试的元素 androidDriver.touchAction("up", 565, 2259); } } ================================================ FILE: src/test/java/org/cloud/sonic/driver/android/service/UiaClientTest.java ================================================ package org.cloud.sonic.driver.android.service; import com.alibaba.fastjson.JSONObject; import org.cloud.sonic.driver.android.service.impl.UiaClientImpl; import org.cloud.sonic.driver.common.enums.PasteboardType; import org.cloud.sonic.driver.common.models.BaseResp; import org.cloud.sonic.driver.common.models.ErrorMsg; import org.cloud.sonic.driver.common.tool.RespHandler; import org.cloud.sonic.driver.common.tool.SonicRespException; import org.cloud.sonic.driver.ios.enums.IOSSelector; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.mockito.Mockito; import java.lang.reflect.Field; public class UiaClientTest { static UiaClient uiaClient; private static final String ERROR_MSG = "err message"; static String url = "http://localhost:6790"; @BeforeClass public static void before() throws Exception { uiaClient = new UiaClientImpl(); uiaClient.setSessionId("test"); uiaClient.setRemoteUrl(url); RespHandler respHandler = Mockito.mock(RespHandler.class); BaseResp b = new BaseResp(); b.setErr(new ErrorMsg("testErr", ERROR_MSG, "traceback")); Assert.assertNull(b.getSessionId()); Mockito.when(respHandler.getResp(Mockito.any())).thenReturn(b); Mockito.when(respHandler.getResp(Mockito.any(), Mockito.anyInt())).thenReturn(b); Field respField = uiaClient.getClass().getDeclaredField("respHandler"); respField.setAccessible(true); respField.set(uiaClient, respHandler); } @Test public void testGetWindowSize() { Boolean hasThrow = false; try { uiaClient.getWindowSize(); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testSendKeys() { Boolean hasThrow = false; try { uiaClient.sendKeys("test", false); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testSetPasteboard() { Boolean hasThrow = false; try { uiaClient.setPasteboard(PasteboardType.PLAIN_TEXT.getType(), "text"); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testGetPasteboard() { Boolean hasThrow = false; try { uiaClient.getPasteboard(PasteboardType.PLAIN_TEXT.getType()); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testPageSource() { Boolean hasThrow = false; try { uiaClient.pageSource(); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testFindElement() { Boolean hasThrow = false; try { uiaClient.findElement(IOSSelector.ACCESSIBILITY_ID.getSelector(), "abc", 10, 10); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testFindElements() { Boolean hasThrow = false; try { uiaClient.findElementList(IOSSelector.ACCESSIBILITY_ID.getSelector(), "abc", 10, 10); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testSetAppiumSettings() { Boolean hasThrow = false; try { uiaClient.setAppiumSettings(new JSONObject()); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testScreenShot() { Boolean hasThrow = false; try { uiaClient.screenshot(); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } } ================================================ FILE: src/test/java/org/cloud/sonic/driver/common/tool/RespHandlerTest.java ================================================ package org.cloud.sonic.driver.common.tool; import cn.hutool.http.HttpUtil; import org.junit.Assert; import org.junit.Test; public class RespHandlerTest { @Test public void testTimeOut() { RespHandler respHandler = new RespHandler(); respHandler.setRequestTimeOut(0); Boolean hasThrow = false; try { respHandler.getResp(HttpUtil.createGet("http://localhost:1234")); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertTrue(e.getMessage().length() > 0); } Assert.assertTrue(hasThrow); } } ================================================ FILE: src/test/java/org/cloud/sonic/driver/common/tool/SonicRespExceptionTest.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.common.tool; import org.junit.Assert; import org.junit.Test; public class SonicRespExceptionTest { @Test public void testSonicRespExceptionTest() { SonicRespException e = new SonicRespException("test"); Assert.assertEquals("test", e.getMessage()); } @Test public void testSonicRespExceptionTestWithMsg() { SonicRespException e = new SonicRespException("test", new Exception("hello")); Assert.assertEquals("test", e.getMessage()); Assert.assertEquals("hello", e.getCause().getMessage()); } } ================================================ FILE: src/test/java/org/cloud/sonic/driver/ios/IOSDriverTest.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.ios; import com.alibaba.fastjson.JSONObject; import org.cloud.sonic.driver.common.enums.PasteboardType; import org.cloud.sonic.driver.common.models.ElementRect; import org.cloud.sonic.driver.common.models.WindowSize; import org.cloud.sonic.driver.common.tool.SonicRespException; import org.cloud.sonic.driver.ios.enums.*; import org.cloud.sonic.driver.ios.models.TouchActions; import org.cloud.sonic.driver.ios.service.IOSElement; import org.junit.*; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import javax.imageio.stream.FileImageOutputStream; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.UUID; @RunWith(Parameterized.class) public class IOSDriverTest { static IOSDriver iosDriver; static String url = "http://localhost:8100"; @Parameterized.Parameters public static Object[][] data() { return new Object[1][0]; } @Before public void before() throws InterruptedException { Thread.sleep(2000); } @BeforeClass public static void beforeClass() throws SonicRespException { Boolean hasThrow = false; try { new IOSDriver(url, null); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals("'capabilities' is mandatory to create a new session", e.getMessage()); } Assert.assertTrue(hasThrow); iosDriver = new IOSDriver(url, new JSONObject()); iosDriver.disableLog(); iosDriver.showLog(); Assert.assertEquals(url, iosDriver.getWdaClient().getRemoteUrl()); Assert.assertTrue(iosDriver.getSessionId().length() > 0); iosDriver.closeDriver(); iosDriver = new IOSDriver(url); Assert.assertEquals(url, iosDriver.getWdaClient().getRemoteUrl()); Assert.assertTrue(iosDriver.getSessionId().length() > 0); } @Test public void testApp() throws SonicRespException { iosDriver.appActivate("developer.apple.wwdc-Release"); Boolean hasThrow = false; try { iosDriver.appActivate(""); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals("bundleId not found.", e.getMessage()); } Assert.assertTrue(hasThrow); hasThrow = false; try { iosDriver.appActivate(null); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals("bundleId not found.", e.getMessage()); } Assert.assertTrue(hasThrow); iosDriver.appRunBackground(5); Assert.assertTrue(iosDriver.appTerminate("developer.apple.wwdc-Release")); Assert.assertFalse(iosDriver.appTerminate("developer.apple.wwdc-Release")); iosDriver.appAuthReset(AuthResource.CAMERA); } @Test public void testSiriAndSendKeys() throws SonicRespException, InterruptedException { iosDriver.sendSiriCommand("打开提醒事项"); Boolean hasThrow = false; try { iosDriver.sendSiriCommand(""); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals("siri command is null!", e.getMessage()); } Assert.assertTrue(hasThrow); hasThrow = false; try { iosDriver.sendSiriCommand(null); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals("siri command is null!", e.getMessage()); } Assert.assertTrue(hasThrow); Thread.sleep(4000); iosDriver.tap(150, 181); iosDriver.sendKeys("中文123"); iosDriver.sendKeys(TextKey.DELETE); iosDriver.sendKeys(TextKey.BACK_SPACE); iosDriver.pressButton(SystemButton.HOME); } @Test public void testPasteboard() throws SonicRespException, InterruptedException { iosDriver.pressButton(SystemButton.HOME); String text = UUID.randomUUID() + "中文"; iosDriver.appActivate("com.apple.springboard"); iosDriver.findElement(IOSSelector.ACCESSIBILITY_ID, "WebDriverAgentRunner-Runner").click(); iosDriver.setPasteboard(PasteboardType.PLAIN_TEXT, text); Thread.sleep(1000); Assert.assertEquals(text, new String(iosDriver.getPasteboard(PasteboardType.PLAIN_TEXT), StandardCharsets.UTF_8)); iosDriver.pressButton(SystemButton.HOME); } @Test public void testSwipe() throws SonicRespException, InterruptedException { iosDriver.swipe(100, 256, 50, 256); Thread.sleep(500); iosDriver.swipe(100, 600, 100, 200); } @Test public void testSwipeWithTime() throws SonicRespException, InterruptedException { iosDriver.swipe(100, 600, 100, 200, 2000); } @Test public void testTap() throws SonicRespException, InterruptedException { iosDriver.tap(150, 81); Thread.sleep(500); iosDriver.pressButton(SystemButton.HOME); } @Test public void testLongPress() throws SonicRespException { iosDriver.longPress(150, 281, 1500); iosDriver.pressButton(SystemButton.HOME); } @Test public void testPerformTouchAction() throws SonicRespException, InterruptedException { iosDriver.performTouchAction(new TouchActions.FingerTouchAction().press(100, 256).wait(50).move(50, 256).wait(10).release()); Thread.sleep(1500); iosDriver.performTouchAction(new TouchActions.FingerTouchAction().press(50, 256).wait(50).move(100, 256).wait(10).release()); } @Test public void testMultiFingerTouchAction() throws SonicRespException, InterruptedException { iosDriver.appActivate("com.apple.camera"); Thread.sleep(1000); TouchActions zoomIn = new TouchActions(); zoomIn.finger("0").press(100, 256).wait(50).move(150, 256).wait(10).release(); zoomIn.finger("1").press(100, 256).wait(50).move(50, 256).wait(10).release(); iosDriver.performTouchAction(zoomIn); Thread.sleep(500); TouchActions zoomOut = new TouchActions(); zoomOut.finger("0").press(50, 256).wait(50).move(100, 256).wait(10).release(); zoomOut.finger("1").press(150, 256).wait(50).move(100, 256).wait(10).release(); iosDriver.performTouchAction(zoomOut); Thread.sleep(500); iosDriver.pressButton(SystemButton.HOME); } @Test public void testPressButton() throws SonicRespException, InterruptedException { iosDriver.pressButton(SystemButton.HOME); Thread.sleep(1000); iosDriver.pressButton(SystemButton.VOLUME_DOWN); Thread.sleep(1000); iosDriver.pressButton(SystemButton.VOLUME_UP); Thread.sleep(1000); iosDriver.pressButton("home"); } @Test public void testGetPageSource() throws SonicRespException { Assert.assertTrue(iosDriver.getPageSource().contains("XCUIElementTypeApplication")); } @Test public void testLock() throws SonicRespException { iosDriver.lock(); Assert.assertTrue(iosDriver.isLocked()); iosDriver.unlock(); Assert.assertFalse(iosDriver.isLocked()); } @Test public void testSession() { String sessionId = iosDriver.getSessionId(); iosDriver.getWdaClient().setSessionId(null); Boolean hasThrow = false; try { iosDriver.getWdaClient().lock(); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals("sessionId not found.", e.getMessage()); } Assert.assertTrue(hasThrow); iosDriver.getWdaClient().setSessionId(""); hasThrow = false; try { iosDriver.getWdaClient().lock(); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals("sessionId not found.", e.getMessage()); } Assert.assertTrue(hasThrow); iosDriver.getWdaClient().setSessionId(sessionId); } @Test public void testFindElement() throws SonicRespException, InterruptedException, IOException { Boolean hasThrow = false; try { iosDriver.findElement("accessibility id", "地图1").click(); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertTrue(e.getMessage().contains("unable to find an element")); } Assert.assertTrue(hasThrow); iosDriver.pressButton(SystemButton.HOME); Thread.sleep(2000); iosDriver.findElement("accessibility id", "地图").click(); iosDriver.findElement(XCUIElementType.ANY); Thread.sleep(2000); iosDriver.pressButton(SystemButton.HOME); Thread.sleep(2000); iosDriver.findElement(IOSSelector.ACCESSIBILITY_ID, "地图").click(); Thread.sleep(2000); iosDriver.findElement(IOSSelector.ACCESSIBILITY_ID, "搜索地点或地址").click(); IOSElement w = iosDriver.findElement(IOSSelector.ACCESSIBILITY_ID, "搜索地点或地址"); String text = UUID.randomUUID().toString().substring(0, 6) + "中文"; w.sendKeys(text); Assert.assertEquals(text, w.getText()); w.clear(); Assert.assertEquals("搜索地点或地址", w.getAttribute("name")); Assert.assertEquals("搜索地点或地址", w.getText()); ElementRect elementRect = w.getRect(); Assert.assertTrue(elementRect.getX() > 0); Assert.assertTrue(elementRect.getY() > 0); Assert.assertTrue(elementRect.getWidth() > 0); Assert.assertTrue(elementRect.getHeight() > 0); Assert.assertTrue(elementRect.getCenter().getX() > 0); Assert.assertTrue(elementRect.getCenter().getY() > 0); byte[] bt = w.screenshot(); File output = new File("./" + UUID.randomUUID() + ".png"); FileImageOutputStream imageOutput = new FileImageOutputStream(output); imageOutput.write(bt, 0, bt.length); imageOutput.close(); output.delete(); iosDriver.findElement(IOSSelector.ACCESSIBILITY_ID, "取消").click(); iosDriver.setDefaultFindElementInterval(null, 3000); iosDriver.setDefaultFindElementInterval(5, null); iosDriver.setDefaultFindElementInterval(null, null); iosDriver.pressButton(SystemButton.HOME); } @Test public void testFindElementList() throws SonicRespException { int eleSize = iosDriver.findElementList(XCUIElementType.WINDOW).size(); Assert.assertEquals(eleSize, iosDriver.findElementList("class name", "XCUIElementTypeWindow").size()); Assert.assertEquals(eleSize, iosDriver.findElementList(IOSSelector.CLASS_NAME, "XCUIElementTypeWindow").size()); } @Test public void testScreenshot() throws IOException, SonicRespException { byte[] bt = iosDriver.screenshot(); File output = new File("./" + UUID.randomUUID() + ".png"); FileImageOutputStream imageOutput = new FileImageOutputStream(output); imageOutput.write(bt, 0, bt.length); imageOutput.close(); output.delete(); } @Test public void testGetWindowSize() throws SonicRespException { WindowSize size = iosDriver.getWindowSize(); Assert.assertNotNull(size); Assert.assertTrue(size.getHeight() > 0); Assert.assertTrue(size.getWidth() > 0); } @Test public void testSetAppiumSettings() throws SonicRespException { iosDriver.setAppiumSettings(new JSONObject()); } @Test public void testIsDisplayed() throws SonicRespException { String value = "name CONTAINS 'QDII' AND label CONTAINS 'QDII' AND enabled == true AND visible == true"; IOSElement element1 = iosDriver.findElement(IOSSelector.PREDICATE, value); System.out.println(element1.getUniquelyIdentifies() + ",isDisplayed=" + element1.isDisplayed()); System.out.println(element1.getUniquelyIdentifies() + ",rect=" + element1.getRect()); IOSElement element2 = iosDriver.findElement(IOSSelector.ACCESSIBILITY_ID, "QDII"); System.out.println(element2.getUniquelyIdentifies() + ",isDisplayed=" + element2.isDisplayed()); System.out.println(element2.getUniquelyIdentifies() + ",rect=" + element2.getRect()); } @Test public void testDoubleTap() throws SonicRespException { iosDriver.doubleTap(100, 100); iosDriver.pressButton("HOME"); } @Test public void testActiveElement() throws SonicRespException { IOSElement element = iosDriver.activeElement(); Assert.assertNotNull(element); element.sendKeys("find active element"); } @Test public void testOrientation() throws SonicRespException, InterruptedException { Orientation orientation = iosDriver.getRotate(); System.out.print("current orientation: " + orientation); iosDriver.rotate(Orientation.LANDSCAPELEFT); Thread.sleep(2000); iosDriver.rotate(Orientation.LANDSCAPERIGHT); Thread.sleep(2000); iosDriver.rotate(Orientation.PORTRAIT); } @AfterClass public static void after() throws SonicRespException { iosDriver.closeDriver(); } } ================================================ FILE: src/test/java/org/cloud/sonic/driver/ios/service/WdaClientTest.java ================================================ /* * Copyright (C) [SonicCloudOrg] Sonic Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.cloud.sonic.driver.ios.service; import com.alibaba.fastjson.JSONObject; import org.cloud.sonic.driver.common.enums.PasteboardType; import org.cloud.sonic.driver.common.models.BaseResp; import org.cloud.sonic.driver.common.models.ErrorMsg; import org.cloud.sonic.driver.common.tool.RespHandler; import org.cloud.sonic.driver.common.tool.SonicRespException; import org.cloud.sonic.driver.ios.enums.IOSSelector; import org.cloud.sonic.driver.ios.models.TouchActions; import org.cloud.sonic.driver.ios.service.impl.WdaClientImpl; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.mockito.Mockito; import java.lang.reflect.Field; public class WdaClientTest { static WdaClient wdaClient; private static final String ERROR_MSG = "err message"; static String url = "http://localhost:8100"; @BeforeClass public static void before() throws Exception { wdaClient = new WdaClientImpl(); wdaClient.setSessionId("test"); wdaClient.setRemoteUrl(url); RespHandler respHandler = Mockito.mock(RespHandler.class); BaseResp b = new BaseResp(); b.setErr(new ErrorMsg("testErr", ERROR_MSG, "traceback")); Assert.assertNull(b.getSessionId()); Mockito.when(respHandler.getResp(Mockito.any())).thenReturn(b); Mockito.when(respHandler.getResp(Mockito.any(), Mockito.anyInt())).thenReturn(b); Field respField = wdaClient.getClass().getDeclaredField("respHandler"); respField.setAccessible(true); respField.set(wdaClient, respHandler); } @Test public void testGetWindowSize() { Boolean hasThrow = false; try { wdaClient.getWindowSize(); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testLocked() { Boolean hasThrow = false; try { wdaClient.isLocked(); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testLock() { Boolean hasThrow = false; try { wdaClient.lock(); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testUnLock() { Boolean hasThrow = false; try { wdaClient.unlock(); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testPerformTouchAction() { Boolean hasThrow = false; try { wdaClient.performTouchAction(new TouchActions()); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testSendKeys() { Boolean hasThrow = false; try { wdaClient.sendKeys("test", 1); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testSetPasteboard() { Boolean hasThrow = false; try { wdaClient.setPasteboard(PasteboardType.PLAIN_TEXT.getType(), "text"); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testGetPasteboard() { Boolean hasThrow = false; try { wdaClient.getPasteboard(PasteboardType.PLAIN_TEXT.getType()); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testPageSource() { Boolean hasThrow = false; try { wdaClient.pageSource(); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testPressButton() { Boolean hasThrow = false; try { wdaClient.pressButton("home"); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testSiriCommand() { Boolean hasThrow = false; try { wdaClient.sendSiriCommand("home"); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testAppActivate() { Boolean hasThrow = false; try { wdaClient.appActivate("developer.apple.wwdc-Release"); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testAppTerminate() { Boolean hasThrow = false; try { wdaClient.appTerminate("developer.apple.wwdc-Release"); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testAppRunBackground() { Boolean hasThrow = false; try { wdaClient.appRunBackground(10); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testAppAuthReset() { Boolean hasThrow = false; try { wdaClient.appAuthReset(6); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testFindElement() { Boolean hasThrow = false; try { wdaClient.findElement(IOSSelector.ACCESSIBILITY_ID.getSelector(), "abc", 10, 10); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testFindElements() { Boolean hasThrow = false; try { wdaClient.findElementList(IOSSelector.ACCESSIBILITY_ID.getSelector(), "abc", 10, 10); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testSetAppiumSettings() { Boolean hasThrow = false; try { wdaClient.setAppiumSettings(new JSONObject()); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } @Test public void testScreenShot() { Boolean hasThrow = false; try { wdaClient.screenshot(); } catch (Throwable e) { hasThrow = true; Assert.assertEquals(SonicRespException.class, e.getClass()); Assert.assertEquals(e.getMessage(), ERROR_MSG); } Assert.assertTrue(hasThrow); } } ================================================ FILE: src/test/java/org/cloud/sonic/driver/poco/PocoDriverTest.java ================================================ package org.cloud.sonic.driver.poco; import org.cloud.sonic.driver.common.models.WindowSize; import org.cloud.sonic.driver.common.tool.SonicRespException; import org.cloud.sonic.driver.poco.enums.PocoEngine; import org.cloud.sonic.driver.poco.enums.PocoSelector; import org.cloud.sonic.driver.poco.models.PocoElement; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import java.util.List; public class PocoDriverTest { static PocoDriver pocoDriver; @BeforeClass public static void beforeClass() { pocoDriver = new PocoDriver(PocoEngine.UNITY_3D); pocoDriver.disableLog(); pocoDriver.showLog(); } @Test public void testPageSource() throws SonicRespException { // Assert.assertEquals("Root", pocoDriver.getPageSource().getPayload().getType()); Assert.assertTrue(pocoDriver.getPageSourceForJsonString().length() > 0); Assert.assertNotNull(pocoDriver.getPageSourceForXmlElement().toString()); System.out.println(pocoDriver.getPageSourceForXmlElement().toString()); } @Test public void testFindElement() throws SonicRespException { String expression = "poco(\"star\")[3]"; List pocoElements = pocoDriver.findElements(PocoSelector.POCO, expression); System.out.println(pocoElements.size()); for (PocoElement pocoElement : pocoElements) { System.out.println(pocoElement.getPayload().getPos()); Assert.assertNotNull(pocoElement.getPayload().getName()); System.out.println(pocoElement.getPayload().getName()); Assert.assertNotNull(pocoElement.currentNodeSelector); System.out.println(pocoElement.currentNodeSelector); } } @Test public void testFreeze() throws SonicRespException { String r = pocoDriver.getPageSourceForJsonString(); pocoDriver.freezeSource(); try { // change page tree operation Thread.sleep(5 * 1000); } catch (InterruptedException e) { throw new RuntimeException(e); } Assert.assertEquals(r, pocoDriver.getPageSourceForJsonString()); pocoDriver.thawSource(); } @Test public void testWindowsSize() throws SonicRespException { WindowSize windowSize = pocoDriver.getScreenSize(); Assert.assertTrue(windowSize.getHeight() > 0); Assert.assertTrue(windowSize.getWidth() > 0); } @Test public void testNodeExist() throws SonicRespException { String expression = "Root > MEHolo > AnchorManager"; PocoElement pocoElement = pocoDriver.findElement(PocoSelector.CSS_SELECTOR, expression); Assert.assertTrue(pocoElement.currentTheNodeExists()); // mock node does not exist pocoElement.currentNodeSelector = "Root > children > MEHolo > children > AnchorManager222"; Assert.assertTrue(pocoElement.currentTheNodeExists()); } @Test public void testGetParent() throws SonicRespException { String expression = "Root > MEHolo > AnchorManager"; PocoElement pocoElement = pocoDriver.findElement(PocoSelector.CSS_SELECTOR, expression); PocoElement parentPocoElement = pocoElement.getParentNode(); Assert.assertNotNull(parentPocoElement.getPayload().getName()); System.out.println(parentPocoElement.getPayload().getName()); Assert.assertNotNull(parentPocoElement.currentNodeSelector); System.out.println(parentPocoElement.currentNodeSelector); } @Test public void testElementGetChild() throws SonicRespException { String expression = "Root > Canvas"; PocoElement pocoElement = pocoDriver.findElement(PocoSelector.CSS_SELECTOR, expression); Assert.assertTrue(!pocoElement.getChildren().isEmpty()); System.out.println(pocoElement.getChildren().size()); } @Test public void testUpdateRootCase() throws SonicRespException { String expression = "Root > MEHolo > AnchorManager"; PocoElement pocoElement = pocoDriver.findElement(PocoSelector.CSS_SELECTOR, expression); String lastRootXml = pocoElement.getRootElement().getXmlElement().toString(); try { System.out.println("into blocking,please perform a page tree change operation"); // change page tree operation Thread.sleep(5 * 1000); } catch (InterruptedException e) { throw new RuntimeException(e); } pocoDriver.getPageSource(); Assert.assertEquals(lastRootXml, pocoElement.getRootElement().getXmlElement().toString()); } @Test public void testGetAttribute() throws SonicRespException { String expression = "poco(\"star\")[3]"; PocoElement pocoElement = pocoDriver.findElement(PocoSelector.POCO, expression); System.out.println(pocoElement.getAttribute("_instanceId")); assert pocoElement.getAttribute("_instanceId") != null; } @AfterClass public static void afterClass() { pocoDriver.closeDriver(); } } ================================================ FILE: src/test/java/org/cloud/sonic/driver/poco/PocoJsonToXmlTest.java ================================================ package org.cloud.sonic.driver.poco; import com.alibaba.fastjson.JSON; import org.cloud.sonic.driver.common.tool.SonicRespException; import org.cloud.sonic.driver.poco.models.PocoElement; import org.cloud.sonic.driver.poco.models.RootElement; import org.cloud.sonic.driver.poco.service.impl.PocoClientImpl; import org.cloud.sonic.driver.poco.util.PocoJsonToXml; import org.jsoup.Jsoup; import org.jsoup.nodes.Element; import org.jsoup.parser.Parser; import org.junit.Test; import java.util.List; public class PocoJsonToXmlTest { @Test public void testToXml() throws SonicRespException { System.out.println(PocoJsonToXml.jsonObjToXml(JSON.parseObject(dump).getJSONObject("Root"))); } @Test public void mockPocoResultTest() throws SonicRespException { String expression = "poco(\"\").child(\"Main&&Camera\")"; // String expression = "poco(\"\")"; Element rootXmlElement = Jsoup.parse(PocoJsonToXml.jsonObjToXml(JSON.parseObject(dump).getJSONObject("result")), "", Parser.xmlParser()); PocoClientImpl pocoClient = new PocoClientImpl(); RootElement rootElement = new RootElement(rootXmlElement); pocoClient.rootNode = rootElement; System.out.println(rootXmlElement); List result = pocoClient.findElements("poco", expression); System.out.println(result.size()); // pocoClient.findElements("cssSelector", expression); } String dump = "{\"result\":{\"name\":\"\",\"payload\":{\"name\":\"\",\"type\":\"Root\",\"visible\":true,\"pos\":[0.0,0.0],\"size\":[0.0,0.0],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"local\":0,\"global\":0}},\"children\":[{\"name\":\"Main Camera\",\"payload\":{\"name\":\"Main&&Camera\",\"type\":\"Camera\",\"visible\":true,\"pos\":[0.0,0.0],\"size\":[0.0,0.0],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":0.0},\"clickable\":false,\"components\":[\"Transform\",\"Camera\",\"GUILayer\",\"FlareLayer\",\"AudioListener\"],\"tag\":\"MainCamera\",\"_instanceId\":10098}},{\"name\":\"Canvas\",\"payload\":{\"name\":\"Canvas\",\"type\":\"Node\",\"visible\":true,\"pos\":[0.5,0.5],\"size\":[1.0,1.0],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"components\":[\"RectTransform\",\"Canvas\",\"CanvasScaler\",\"GraphicRaycaster\",\"PocoManager\",\"HunterInterface\"],\"_instanceId\":10158},\"children\":[{\"name\":\"plays\",\"payload\":{\"name\":\"plays\",\"type\":\"Node\",\"visible\":true,\"pos\":[0.5,0.5],\"size\":[1.0,1.0],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"components\":[\"RectTransform\"],\"_instanceId\":10074},\"children\":[{\"name\":\"playBasic\",\"payload\":{\"name\":\"playBasic\",\"type\":\"Image\",\"visible\":true,\"pos\":[0.501041651,0.495370358],\"size\":[1.94782829,1.90740752],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Image\",\"CanvasGroup\"],\"texture\":\"Background\",\"_instanceId\":10188},\"children\":[{\"name\":\"title\",\"payload\":{\"name\":\"title\",\"type\":\"Text\",\"visible\":true,\"pos\":[0.226041645,0.140740708],\"size\":[0.322395861,0.06111111],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"text\":\"Basic test\",\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Text\"],\"_instanceId\":10226}},{\"name\":\"star_single\",\"payload\":{\"name\":\"star_single\",\"type\":\"Image\",\"visible\":true,\"pos\":[0.501041651,0.3],\"size\":[0.130208343,0.231481493],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Image\",\"StrongFeedback\"],\"texture\":\"star\",\"_instanceId\":10078}},{\"name\":\"pos_input\",\"payload\":{\"name\":\"pos_input\",\"type\":\"InputField\",\"visible\":true,\"pos\":[0.501041651,0.5833333],\"size\":[0.3463542,0.06018519],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Image\",\"InputField\"],\"texture\":\"InputFieldBackground\",\"_instanceId\":10184},\"children\":[{\"name\":\"pos_input Input Caret\",\"payload\":{\"name\":\"pos_input Input Caret\",\"type\":\"Node\",\"visible\":true,\"pos\":[0.501041651,0.5837963],\"size\":[0.3359375,0.04814815],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"components\":[\"RectTransform\",\"CanvasRenderer\",\"LayoutElement\"],\"_instanceId\":-10060}},{\"name\":\"Placeholder\",\"payload\":{\"name\":\"Placeholder\",\"type\":\"Text\",\"visible\":true,\"pos\":[0.501041651,0.5837963],\"size\":[0.3359375,0.04814815],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"text\":\"Enter text...\",\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Text\"],\"_instanceId\":10152}},{\"name\":\"Text\",\"payload\":{\"name\":\"Text\",\"type\":\"Text\",\"visible\":true,\"pos\":[0.501041651,0.5837963],\"size\":[0.3359375,0.04814815],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"text\":\"xx&cc_ggg\",\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Text\"],\"_instanceId\":10148}}]}]}]},{\"name\":\"globalControl\",\"payload\":{\"name\":\"globalControl\",\"type\":\"Image\",\"visible\":true,\"pos\":[0.0770833045,0.862037063],\"size\":[0.08816848,0.0881684646],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Image\",\"CanvasGroup\"],\"texture\":\"Background\",\"_instanceId\":10118},\"children\":[{\"name\":\"btn_back\",\"payload\":{\"name\":\"btn_back\",\"type\":\"Button\",\"visible\":true,\"pos\":[0.07740478,0.8608941],\"size\":[0.0551053,0.06530997],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":true,\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Image\",\"Button\",\"StrongFeedback\"],\"texture\":\"UISprite\",\"_instanceId\":10192},\"children\":[{\"name\":\"Text\",\"payload\":{\"name\":\"Text\",\"type\":\"Text\",\"visible\":true,\"pos\":[0.07740478,0.8608941],\"size\":[0.0551053,0.06530997],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"text\":\"Back\",\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Text\"],\"_instanceId\":10132}}]}]}]},{\"name\":\"EventSystem\",\"payload\":{\"name\":\"EventSystem\",\"type\":\"GameObject\",\"visible\":true,\"pos\":[0.0,0.0],\"size\":[0.0,0.0],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"components\":[\"Transform\",\"EventSystem\",\"StandaloneInputModule\",\"EventRegistration\"],\"_instanceId\":10160}}]}}"; // String dump = "{\"Root\":{\"name\":\"\",\"payload\":{\"name\":\"\",\"type\":\"Root\",\"visible\":true,\"pos\":[0.0,0.0],\"size\":[0.0,0.0],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"local\":0,\"global\":0}},\"children\":[{\"name\":\"Main&&Camera\",\"payload\":{\"name\":\"Main&&Camera\",\"type\":\"Camera\",\"visible\":true,\"pos\":[0.0,0.0],\"size\":[0.0,0.0],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":0.0},\"clickable\":false,\"components\":[\"Transform\",\"Camera\",\"GUILayer\",\"FlareLayer\",\"AudioListener\",\"PocoManager\"],\"tag\":\"MainCamera\",\"_ilayer\":0,\"layer\":\"Default\",\"_instanceId\":838}},{\"name\":\"Canvas\",\"payload\":{\"name\":\"Canvas\",\"type\":\"Node\",\"visible\":true,\"pos\":[0.5,0.5],\"size\":[1.0,1.0],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"components\":[\"RectTransform\",\"Canvas\",\"CanvasScaler\",\"GraphicRaycaster\",\"PocoManager\"],\"_ilayer\":5,\"layer\":\"UI\",\"_instanceId\":898},\"children\":[{\"name\":\"plays\",\"payload\":{\"name\":\"plays\",\"type\":\"Node\",\"visible\":true,\"pos\":[0.5,0.5],\"size\":[1.0,1.0],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"components\":[\"RectTransform\"],\"_ilayer\":5,\"layer\":\"UI\",\"_instanceId\":814},\"children\":[{\"name\":\"playDragAndDrop\",\"payload\":{\"name\":\"playDragAndDrop\",\"type\":\"Image\",\"visible\":true,\"pos\":[0.498958319,0.502777755],\"size\":[1.94782817,1.9074074],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Image\",\"CanvasGroup\"],\"texture\":\"Background\",\"_ilayer\":5,\"layer\":\"UI\",\"_instanceId\":934},\"children\":[{\"name\":\"collectionArea\",\"payload\":{\"name\":\"collectionArea\",\"type\":\"Node\",\"visible\":true,\"pos\":[0.516666651,0.220370367],\"size\":[0.0,0.0],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"components\":[\"RectTransform\"],\"_ilayer\":5,\"layer\":\"UI\",\"_instanceId\":860},\"children\":[{\"name\":\"shell\",\"payload\":{\"name\":\"shell\",\"type\":\"Image\",\"visible\":true,\"pos\":[0.504166663,0.705555558],\"size\":[0.224570453,0.284520835],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Image\",\"BoxCollider2D\"],\"texture\":\"clamshell_open\",\"_ilayer\":5,\"layer\":\"UI\",\"_instanceId\":922}}]},{\"name\":\"star\",\"payload\":{\"name\":\"star\",\"type\":\"Image\",\"visible\":true,\"pos\":[0.156958327,0.317777783],\"size\":[0.130208328,0.231481478],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Image\",\"TestDragAndDrop\"],\"texture\":\"star\",\"tag\":\"star\",\"_ilayer\":5,\"layer\":\"UI\",\"_instanceId\":926}},{\"name\":\"star\",\"payload\":{\"name\":\"star\",\"type\":\"Image\",\"visible\":true,\"pos\":[0.335958362,0.317777783],\"size\":[0.130208328,0.231481478],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Image\",\"TestDragAndDrop\"],\"texture\":\"star\",\"tag\":\"star\",\"_ilayer\":5,\"layer\":\"UI\",\"_instanceId\":950}},{\"name\":\"star\",\"payload\":{\"name\":\"star\",\"type\":\"Image\",\"visible\":true,\"pos\":[0.5149583,0.317777783],\"size\":[0.130208328,0.231481478],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Image\",\"TestDragAndDrop\"],\"texture\":\"star\",\"tag\":\"star\",\"_ilayer\":5,\"layer\":\"UI\",\"_instanceId\":868}},{\"name\":\"star\",\"payload\":{\"name\":\"star\",\"type\":\"Image\",\"visible\":true,\"pos\":[0.693958342,0.317777783],\"size\":[0.130208328,0.231481478],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Image\",\"TestDragAndDrop\"],\"texture\":\"star\",\"tag\":\"star\",\"_ilayer\":5,\"layer\":\"UI\",\"_instanceId\":852}},{\"name\":\"star\",\"payload\":{\"name\":\"star\",\"type\":\"Image\",\"visible\":true,\"pos\":[0.872958362,0.317777783],\"size\":[0.130208328,0.231481478],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Image\",\"TestDragAndDrop\"],\"texture\":\"star\",\"tag\":\"star\",\"_ilayer\":5,\"layer\":\"UI\",\"_instanceId\":802}},{\"name\":\"Text\",\"payload\":{\"name\":\"Text\",\"type\":\"Text\",\"visible\":true,\"pos\":[0.15,0.143518522],\"size\":[0.11776042,0.153611109],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"text\":\"score\",\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Text\"],\"_ilayer\":5,\"layer\":\"UI\",\"_instanceId\":844}},{\"name\":\"scoreVal\",\"payload\":{\"name\":\"scoreVal\",\"type\":\"Text\",\"visible\":true,\"pos\":[0.266145825,0.1435184],\"size\":[0.11776042,0.153611109],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"text\":\"0\",\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Text\"],\"_ilayer\":5,\"layer\":\"UI\",\"_instanceId\":836}}]}]},{\"name\":\"globalControl\",\"payload\":{\"name\":\"globalControl\",\"type\":\"Image\",\"visible\":true,\"pos\":[0.0770833343,0.862037063],\"size\":[0.08816848,0.0881684646],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Image\",\"CanvasGroup\"],\"texture\":\"Background\",\"_ilayer\":5,\"layer\":\"UI\",\"_instanceId\":858},\"children\":[{\"name\":\"btn_back\",\"payload\":{\"name\":\"btn_back\",\"type\":\"Button\",\"visible\":true,\"pos\":[0.07740478,0.8608941],\"size\":[0.0551053025,0.06530998],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":true,\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Image\",\"Button\",\"StrongFeedback\"],\"texture\":\"UISprite\",\"_ilayer\":5,\"layer\":\"UI\",\"_instanceId\":932},\"children\":[{\"name\":\"Text\",\"payload\":{\"name\":\"Text\",\"type\":\"Text\",\"visible\":true,\"pos\":[0.07740478,0.8608941],\"size\":[0.0551053025,0.06530998],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"text\":\"Back\",\"components\":[\"RectTransform\",\"CanvasRenderer\",\"Text\"],\"_ilayer\":5,\"layer\":\"UI\",\"_instanceId\":872}}]}]}]},{\"name\":\"EventSystem\",\"payload\":{\"name\":\"EventSystem\",\"type\":\"GameObject\",\"visible\":true,\"pos\":[0.0,0.0],\"size\":[0.0,0.0],\"scale\":[1.0,1.0],\"anchorPoint\":[0.5,0.5],\"zOrders\":{\"global\":0.0,\"local\":-10.0},\"clickable\":false,\"components\":[\"Transform\",\"EventSystem\",\"StandaloneInputModule\",\"EventRegistration\",\"BaseInput\"],\"_ilayer\":0,\"layer\":\"Default\",\"_instanceId\":900}}]}}"; } ================================================ FILE: src/test/java/org/cloud/sonic/driver/poco/PocoXYTransformerTest.java ================================================ package org.cloud.sonic.driver.poco; import org.cloud.sonic.driver.poco.util.PocoXYTransformer; import org.junit.Assert; import org.junit.Test; public class PocoXYTransformerTest { double width = 100, height = 1000; @Test public void testOriToUP() { double pocoX = 100, pocoY = 50; double[] result = PocoXYTransformer.PocoTransformerVertical(pocoX, pocoY, width, height, 270); Assert.assertEquals(result[0], pocoY, 0); Assert.assertEquals(result[1], height - pocoX, 0); System.out.printf("x:%s,y:%s", result[0], result[1]); System.out.println(); result = PocoXYTransformer.PocoTransformerVertical(pocoX, pocoY, width, height, 90); Assert.assertEquals(result[0], pocoY, 0); Assert.assertEquals(result[1], pocoX, 0); System.out.printf("x:%s,y:%s", result[0], result[1]); System.out.println(); } @Test public void testUPToOri() { double upx = 50, upy = 100; double[] result = PocoXYTransformer.VerticalTransformerPoco(upx, upy, width, height, 270); Assert.assertEquals(result[0], height - upy, 0); Assert.assertEquals(result[1], upx, 0); System.out.printf("x:%s,y:%s", result[0], result[1]); System.out.println(); result = PocoXYTransformer.VerticalTransformerPoco(upx, upy, width, height, 90); Assert.assertEquals(result[0], upy, 0); Assert.assertEquals(result[1], upx, 0); System.out.printf("x:%s,y:%s", result[0], result[1]); System.out.println(); } }