diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..17b79984 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,26 @@ +[run] +branch = True + +[report] +; Regexes for lines to exclude from consideration +exclude_also = + ; Don't complain about missing debug-only code: + def __repr__ + if self\.debug + + ; Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + + ; Don't complain if non-runnable code isn't run: + if 0: + if __name__ == .__main__.: + + ; Don't complain about abstract methods, they aren't run: + @(abc\.)?abstractmethod + +ignore_errors = True + +omit = + "tests/*" + "docs/*" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..418370d2 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,55 @@ +name: Python application + +on: [push] + +jobs: + build-and-publish: + name: ${{ matrix.os }} / ${{ matrix.python-version }} + runs-on: ${{ matrix.image }} + strategy: + matrix: + os: [Ubuntu, macOS, Windows] + python-version: ["3.8", "3.11"] + include: + - os: Ubuntu + image: ubuntu-latest + - os: Windows + image: windows-2022 + - os: macOS + image: macos-12 + fail-fast: false + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Get full Python version + id: full-python-version + run: echo version=$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") >> $GITHUB_OUTPUT + + - name: Update PATH + if: ${{ matrix.os != 'Windows' }} + run: echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Update Path for Windows + if: ${{ matrix.os == 'Windows' }} + run: echo "$APPDATA\Python\Scripts" >> $GITHUB_PATH + + - name: Enable long paths for git on Windows + if: ${{ matrix.os == 'Windows' }} + # Enable handling long path names (+260 char) on the Windows platform + # https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation + run: git config --system core.longpaths true + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install poetry + poetry install + + - name: Run tests with coverage + run: | + make cov diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml deleted file mode 100644 index f0e5a5b0..00000000 --- a/.github/workflows/pythonapp.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Python application - -on: [push] - -jobs: - build-and-publish: - name: Build and publish Python 🐍 distributions 📦 to PyPI - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - with: - fetch-depth: 10 - - name: Set up Python 3.7 - uses: actions/setup-python@v1 - with: - python-version: 3.7 - - name: Install dependencies - run: | - ./uiautomator2/assets/sync.sh - python -m pip install --upgrade pip - pip install -r requirements.txt - - name: Test with pytest - run: | - pip install pytest - pip install -e ".[image]" - pytest tests/test_utils.py - - name: Install pypa/build - run: >- - python3 -m pip install wheel - - name: Build targz and wheel - run: >- - python3 setup.py sdist bdist_wheel - - name: Publish distribution 📦 to PyPI - if: startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@master - with: - skip_existing: true - password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..ed86995a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,33 @@ +name: Release + +on: + push: + tags: + - *.*.* + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install poetry + + - name: Build + run: | + make build + + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + skip-existing: true + password: ${{ secrets.PYPI_API_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index c718bad7..df54b8cc 100644 --- a/.gitignore +++ b/.gitignore @@ -114,3 +114,4 @@ docs/*.rst .DS_Store apk_version.txt +*.lock diff --git a/DEVELOP.md b/DEVELOP.md index 043f876b..e75ff18c 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -2,27 +2,17 @@ ``` git clone https://github.com/openatx/uiautomator2 -pip3 install -e uiautomator2 -``` - -`-e`这个选项可以将该目录以软连接的形式添加到Python `site-packages` - -## 生成CHANGELOG -See changelog from git history +cd uiautomator2 -``` -git log --graph --date-order -C -M --pretty=format:"<%h> %ad [%an] %Cgreen%d%Creset %s" --all --date=short +pip install poetry +poetry install ``` -## 使用Sphinx生成文档 -```bash -pip3 install -e . -cd docs -make publish -``` +项目使用poetry做包管理和打包发布功能 + ## ViewConfiguration -一些默认的配置,从 `/android/view/ViewConfiguration.java`中可以查到 +一些默认的配置,从 [/android/view/ViewConfiguration.java](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewConfiguration.java)中可以查到 > 单位: 毫秒 diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 828a79d8..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,10 +0,0 @@ -# Format -# https://packaging.python.org/guides/using-manifest-in/ - -graft uiautomator2 - -prune tests/ -prune docs/ -prune examples/ -prune .github/ -global-exclude *.py[cod] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..258fe049 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +.PHONY: build + +format: + poetry run isort . -m HANGING_INDENT -l 120 + +test: + poetry run pytest -v tests + +cov: + poetry run pytest -v tests/unittests --cov=. --cov-report xml --cov-report term + +build: + rm -fr dist + poetry build + +init: + if [ ! -f "ApiDemos-debug.apk" ]; then \ + wget https://github.com/appium/appium/raw/master/packages/appium/sample-code/apps/ApiDemos-debug.apk; \ + fi + poetry run python -m adbutils -i ./ApiDemos-debug.apk diff --git a/README.md b/README.md index a0c91681..c266b608 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,7 @@ QQ交流群: 815453846 ## Requirements - Android版本 4.4+ -- Python 3.6+ (社区反馈3.8.0不支持, 但是3.8.2支持) - ->如果用python2的pip安装,会安装本库的老版本0.2.3;如果用python3.5的pip安装,会安装本库的老版本0.3.3;两者均已经不会再维护;PYPI上的最近版本是这个:https://pypi.org/project/uiautomator2/ +- Python 3.8+ ## QUICK START 先准备一台(不要两台)开启了`开发者选项`的安卓手机,连接上电脑,确保执行`adb devices`可以看到连接上的设备。 @@ -1468,8 +1466,8 @@ for elem in d.xpath("//android.widget.TextView").all(): 点击查看[其他XPath常见用法](XPATH.md) -### Screenrecord -视频录制 +### Screenrecord (Deprecated) +视频录制(废弃) 这里没有使用手机中自带的screenrecord命令,是通过获取手机图片合成视频的方法,所以需要安装一些其他的依赖,如imageio, imageio-ffmpeg, numpy等 因为有些依赖比较大,推荐使用镜像安装。直接运行下面的命令即可。 @@ -1491,7 +1489,7 @@ d.screenrecord.stop() # 停止录制后,output.mp4文件才能打开 录制的时候也可以指定fps(当前是20),这个值是率低于minicap输出图片的速度,感觉已经很好了,不建议你修改。 -### Image match +### Image match (3.x开始移除该功能) 图像匹配,在使用这个功能之前你需要先把依赖安装上 ```bash @@ -1564,6 +1562,12 @@ https://www.cnblogs.com/insist8089/p/6898181.html ## [CHANGELOG (generated by pbr)](CHANGELOG) 重大更新 +- 3.x + + 最低Python从3.5改为3.8 + 使用poetry代替pbr管理包依赖 + 移除图片匹配和视频录制功能 + - 1.0.0 移除 `d.watchers.watched` (会拖慢自动化的执行速度并且还会降低稳定性) @@ -1592,7 +1596,7 @@ https://www.cnblogs.com/insist8089/p/6898181.html Other [contributors](../../graphs/contributors) -## 其他优秀的项目 +## 其他优秀的项目 (好久没更新了,去谷歌吧) - [google/mobly](https://github.com/google/mobly) 谷歌内部的测试框架,虽然我不太懂,但是感觉很好用 - https://www.appetizer.io/ 包含一个很好用的IDE,快速编写脚本,也可以插桩采集性能。 - https://github.com/atinfo/awesome-test-automation 所有优秀测试框架的集合,包罗万象 diff --git a/docs/conf.py b/docs/conf.py index 86db64cd..8ca23a33 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,7 +16,6 @@ import uiautomator2 - # -- Project information ----------------------------------------------------- master_doc = 'index' diff --git a/examples/apk_install.py b/examples/apk_install.py index 5f5c0732..104e7924 100644 --- a/examples/apk_install.py +++ b/examples/apk_install.py @@ -5,6 +5,7 @@ # OPPO need password import time + import uiautomator2 as u2 diff --git a/examples/batteryweb/main.py b/examples/batteryweb/main.py index 03c355e8..24353d67 100644 --- a/examples/batteryweb/main.py +++ b/examples/batteryweb/main.py @@ -4,7 +4,6 @@ import flask import requests - app = flask.Flask(__name__) diff --git a/examples/com.codeskyblue.remotecamera/main_test.py b/examples/com.codeskyblue.remotecamera/main_test.py index 4eac762b..f595220c 100644 --- a/examples/com.codeskyblue.remotecamera/main_test.py +++ b/examples/com.codeskyblue.remotecamera/main_test.py @@ -2,7 +2,6 @@ import uiautomator2 as u2 - pkg_name = 'com.codeskyblue.remotecamera' d = u2.connect() diff --git a/examples/com.netease.cloudmusic/xpath_test.py b/examples/com.netease.cloudmusic/xpath_test.py index 902685b0..1d2bfb88 100644 --- a/examples/com.netease.cloudmusic/xpath_test.py +++ b/examples/com.netease.cloudmusic/xpath_test.py @@ -2,6 +2,7 @@ # import unittest + from logzero import logger import uiautomator2 as u2 diff --git a/examples/minitouch.py b/examples/minitouch.py index 5d850ac7..2ae03e53 100644 --- a/examples/minitouch.py +++ b/examples/minitouch.py @@ -2,9 +2,11 @@ # # 半成品 import json -from . import Device + from websocket import create_connection +from . import Device + class Minitouch: # TODO: need test diff --git a/examples/multi-thread-example.py b/examples/multi-thread-example.py index a1a7271a..5d4599ec 100644 --- a/examples/multi-thread-example.py +++ b/examples/multi-thread-example.py @@ -4,11 +4,13 @@ # But is seems fine, because these operation have so many socket IO # So it seems no need to use multiprocess # -import uiautomator2 as u2 -import adbutils import threading + +import adbutils from logzero import logger +import uiautomator2 as u2 + def worker(d: u2.Device): d.app_start("io.appium.android.apis", stop=True) diff --git a/examples/runyaml/run.py b/examples/runyaml/run.py index 4f28046f..9eba868b 100755 --- a/examples/runyaml/run.py +++ b/examples/runyaml/run.py @@ -2,16 +2,16 @@ # coding: utf-8 # -import re +import argparse import os +import re import time -import argparse -import yaml import bunch -import uiautomator2 as u2 +import yaml from logzero import logger +import uiautomator2 as u2 CLICK = "click" # swipe diff --git a/poetry.toml b/poetry.toml new file mode 100644 index 00000000..53b35d37 --- /dev/null +++ b/poetry.toml @@ -0,0 +1,3 @@ +[virtualenvs] +create = true +in-project = true diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..b42b872b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,42 @@ +[tool.poetry] +name = "uiautomator2" +version = "0.0.0" +description = "automator for anroid device" +homepage = "https://github.com/openatx/uiautomator2" +authors = ["codeskyblue "] +license = "MIT" +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.8" +requests = "*" +lxml = "*" +adbutils = ">=2.2" +retry = ">=0,<1" +packaging = ">=20.3" + +# TODO: remove later +Deprecated = "*" +logzero = "*" +filelock = ">=3,<4" +progress = "^1.6" +pillow = "*" + +[tool.poetry.group.dev.dependencies] +pytest = "^8.1.1" +isort = "^5.13.2" +pytest-cov = "^4.1.0" + +[tool.poetry.scripts] +uiautomator2 = "uiautomator2.__main__:main" + +[tool.poetry-dynamic-versioning] # 根据tag来动态配置版本号 +enable = true +pattern = "^((?P\\d+)!)?(?P\\d+(\\.\\d+)*)" + +[tool.poetry-dynamic-versioning.substitution] +files = ["uiautomator2/version.py"] + +[build-system] +requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] +build-backend = "poetry_dynamic_versioning.backend" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 5c0741ac..00000000 --- a/requirements.txt +++ /dev/null @@ -1,19 +0,0 @@ -six -requests>=2 -whichcraft - -logzero -progress~=1.3 -retry~=0.9 -adbutils>=1.2.3 -Deprecated~=1.2.6 - -# Not supported in QPython -Pillow -lxml>=4.3 -cached-property>=1.5.1,<2.0 -packaging>=20.3 - -#websocket-client - -filelock>=3.0.12,<4.0 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index cf1f5cb9..00000000 --- a/setup.cfg +++ /dev/null @@ -1,36 +0,0 @@ -[metadata] -name = uiautomator2 -author = codeskyblue -author_email = codeskyblue@gmail.com -summary = Python Wrapper for Google Android UiAutomator2 test tool -license = MIT -description_file = ABOUT.rst -home_page = https://github.com/openatx/uiautomator2 -# all classifier can be found in https://pypi.python.org/pypi?%3Aaction=list_classifiers -classifier = - Development Status :: 4 - Beta - Environment :: Console - Intended Audience :: Developers - Operating System :: POSIX - Programming Language :: Python :: 3 - Topic :: Software Development :: Libraries :: Python Modules - Topic :: Software Development :: Testing - -[files] -packages = - uiautomator2 - -# support: pip3 install uiautomator2[image] -[extras] -image = - scikit-image - imutils - findit - imageio>=2.8.0,<3.0 - imageio-ffmpeg>=0.4.2,<1.0 - websocket_client>=0.57.0,<1.0 - -[entry_points] -# https://docs.openstack.org/pbr/3.1.1/#entry-points -console_scripts = - uiautomator2 = uiautomator2.__main__:main diff --git a/setup.py b/setup.py deleted file mode 100644 index 7977a0e0..00000000 --- a/setup.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 -# -# Licensed under MIT -# - -import setuptools -setuptools.setup(setup_requires=['pbr'], python_requires='>=3.6', pbr=True) diff --git a/tests/conftest.py b/tests/real_device/conftest.py similarity index 95% rename from tests/conftest.py rename to tests/real_device/conftest.py index 2ac22450..e16caba2 100644 --- a/tests/conftest.py +++ b/tests/real_device/conftest.py @@ -1,14 +1,14 @@ # coding: utf-8 import adbutils -import uiautomator2 as u2 import pytest +import uiautomator2 as u2 + @pytest.fixture(scope="module") def d(device): _d = device - #_d = u2.connect() _d.settings['operation_delay'] = (0.2, 0.2) _d.settings['operation_delay_methods'] = ['click', 'swipe'] return _d @@ -20,7 +20,7 @@ def package_name(): @pytest.fixture(scope="function") -def sess(d, package_name) -> u2.Device: +def sess(d, package_name) -> u2.Device: # type: ignore d.watcher.reset() d.app_start(package_name, stop=True) diff --git a/tests/runtest.sh b/tests/real_device/runtest.sh similarity index 100% rename from tests/runtest.sh rename to tests/real_device/runtest.sh diff --git a/tests/skip_test_image.py b/tests/real_device/skip_test_image.py similarity index 100% rename from tests/skip_test_image.py rename to tests/real_device/skip_test_image.py diff --git a/tests/test_push_pull.py b/tests/real_device/test_push_pull.py similarity index 99% rename from tests/test_push_pull.py rename to tests/real_device/test_push_pull.py index 7c355e7f..7fea051a 100644 --- a/tests/test_push_pull.py +++ b/tests/real_device/test_push_pull.py @@ -1,8 +1,9 @@ # coding: utf-8 # -import os import io +import os + import uiautomator2 as u2 diff --git a/tests/test_screenrecord.py b/tests/real_device/test_screenrecord.py similarity index 91% rename from tests/test_screenrecord.py rename to tests/real_device/test_screenrecord.py index 8f68ba29..7c9a2465 100644 --- a/tests/test_screenrecord.py +++ b/tests/real_device/test_screenrecord.py @@ -2,13 +2,15 @@ # import time -import uiautomator2 as u2 + import pytest -import imageio + +import uiautomator2 as u2 -# @pytest.mark.skip("Too long") +@pytest.mark.skip("deprecated") def test_screenrecord(d: u2.Device): + import imageio with pytest.raises(RuntimeError): d.screenrecord.stop() diff --git a/tests/test_session.py b/tests/real_device/test_session.py similarity index 87% rename from tests/test_session.py rename to tests/real_device/test_session.py index f839849c..68b00f43 100644 --- a/tests/test_session.py +++ b/tests/real_device/test_session.py @@ -1,20 +1,15 @@ # coding: utf-8 # -from collections import namedtuple - def test_session(sess): sess.wlan_ip - # sess.widget sess.watcher - sess.image sess.jsonrpc sess.open_identify sess.shell sess.set_new_command_timeout sess.settings - sess.taobao sess.xpath diff --git a/tests/test_settings.py b/tests/real_device/test_settings.py similarity index 99% rename from tests/test_settings.py rename to tests/real_device/test_settings.py index 09417e7c..4fd9385a 100644 --- a/tests/test_settings.py +++ b/tests/real_device/test_settings.py @@ -1,11 +1,13 @@ # coding: utf-8 # -import uiautomator2 as u2 -import pytest import logging import time +import pytest + +import uiautomator2 as u2 + def test_set_xpath_debug(sess): with pytest.raises(TypeError): diff --git a/tests/test_simple.py b/tests/real_device/test_simple.py similarity index 96% rename from tests/test_simple.py rename to tests/real_device/test_simple.py index a3e15e96..04f9b7fe 100644 --- a/tests/test_simple.py +++ b/tests/real_device/test_simple.py @@ -87,7 +87,7 @@ def test_get_text(sess): d = sess text = d(resourceId="android:id/list").child( className="android.widget.TextView", instance=2).get_text() - assert text == "App" + assert text == "Animation" def test_xpath(sess): @@ -96,7 +96,7 @@ def test_xpath(sess): assert len(d.xpath("//*[@text='Media']").all()) == 1 assert len(d.xpath("//*[@text='MediaNotExists']").all()) == 0 d.xpath("//*[@text='Media']").click() - assert d.xpath('//*[contains(@text, "Audio")]').wait(5) + assert d.xpath('//*[contains(@text, "VideoView")]').wait(5) @pytest.mark.skip("Need fix") @@ -141,6 +141,7 @@ def test_send_keys(sess): d.xpath("App").click() d.xpath("Search").click() d.xpath('//*[@text="Invoke Search"]').click() + d.xpath('@io.appium.android.apis:id/txt_query_prefill').click() d.send_keys("hello", clear=True) assert d.xpath('io.appium.android.apis:id/txt_query_prefill').info['text'] == 'hello' diff --git a/tests/test_swipe.py b/tests/real_device/test_swipe.py similarity index 93% rename from tests/test_swipe.py rename to tests/real_device/test_swipe.py index 74e5ac4c..bee1afff 100644 --- a/tests/test_swipe.py +++ b/tests/real_device/test_swipe.py @@ -2,13 +2,13 @@ # import time + import uiautomator2 as u2 def test_swipe_duration(d: u2.Device): w, h = d.window_size() start = time.time() - d.debug = True d.swipe(w//2, h//2, w-1, h//2, 2.0) duration = time.time() - start assert duration >= 1.5 # actually duration is about 7s in my TT diff --git a/tests/test_utils.py b/tests/real_device/test_utils.py similarity index 99% rename from tests/test_utils.py rename to tests/real_device/test_utils.py index 845da9ca..e6a5e60d 100644 --- a/tests/test_utils.py +++ b/tests/real_device/test_utils.py @@ -1,11 +1,14 @@ # coding: utf-8 # -import time import threading +import time + import pytest + from uiautomator2 import utils + def test_list2cmdline(): testdata = [ [("echo", "hello"), "echo hello"], diff --git a/tests/test_watcher.py b/tests/real_device/test_watcher.py similarity index 100% rename from tests/test_watcher.py rename to tests/real_device/test_watcher.py diff --git a/tests/test_xpath.py b/tests/real_device/test_xpath.py similarity index 91% rename from tests/test_xpath.py rename to tests/real_device/test_xpath.py index b8ac5e7d..7f5a4bf1 100644 --- a/tests/test_xpath.py +++ b/tests/real_device/test_xpath.py @@ -5,9 +5,10 @@ from functools import partial from pprint import pprint -import uiautomator2 as u2 import pytest +import uiautomator2 as u2 + def test_get_text(sess: u2.Session): assert sess.xpath("App").get_text() == "App" @@ -39,11 +40,6 @@ def test_element_all(sess: u2.Session): assert len(app.all()) == 1 assert app.exists - elements = sess.xpath('//*[@resource-id="android:id/list"]/android.widget.TextView').all() - assert len(elements) == 11 - el = elements[0] - assert el.text == 'Accessibility' - def test_watcher(sess: u2.Session, request): sess.xpath.when("App").click() diff --git a/tests/testdata/AE86.jpg b/tests/real_device/testdata/AE86.jpg similarity index 100% rename from tests/testdata/AE86.jpg rename to tests/real_device/testdata/AE86.jpg diff --git a/tests/unittests/test_import.py b/tests/unittests/test_import.py new file mode 100644 index 00000000..4a46d44d --- /dev/null +++ b/tests/unittests/test_import.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +"""Created on Wed Mar 20 2024 14:51:03 by codeskyblue +""" + +import uiautomator2 as u2 + + +def test_import(): + u2.Device + u2.connect + u2.connect_usb + u2.connect_wifi + u2.connect_adb_wifi + u2.Device.app_install + u2.Device.app_uninstall + u2.Device.app_current + u2.Device.app_list + u2.Device.shell + u2.Device.send_keys + u2.Device.click + u2.Device.swipe + u2.Device.dump_hierarchy + u2.Device.freeze_rotation + u2.Device.open_notification + u2.Device.info + u2.Device.xpath + u2.Device.clipboard + u2.Device.orientation \ No newline at end of file diff --git a/uiautomator2/__init__.py b/uiautomator2/__init__.py index cf6672dc..db975d49 100644 --- a/uiautomator2/__init__.py +++ b/uiautomator2/__init__.py @@ -26,13 +26,13 @@ import re import shutil import subprocess -import sys -import threading import time +import urllib.parse as urlparse import warnings import xml.dom.minidom -from collections import defaultdict, namedtuple +from collections import namedtuple from datetime import datetime +from functools import cached_property from pathlib import Path from typing import List, Optional, Tuple, Union @@ -40,13 +40,10 @@ import adbutils import filelock import logzero -from packaging import version as packaging_version import requests -import six -import six.moves.urllib.parse as urlparse -from cached_property import cached_property from deprecated import deprecated from logzero import setup_logger +from packaging import version as packaging_version from PIL import Image from retry import retry from urllib3.util.retry import Retry @@ -54,11 +51,9 @@ from . import xpath from ._proto import HTTP_TIMEOUT, SCROLL_STEPS, Direction from ._selector import Selector, UiObject -from .exceptions import (BaseError, ConnectError, GatewayError, JSONRPCError, - NullObjectExceptionError, NullPointerExceptionError, - RetryError, ServerError, SessionBrokenError, - StaleObjectExceptionError, - UiAutomationNotConnectedError, UiObjectNotFoundError) +from .exceptions import BaseError, ConnectError, GatewayError, JSONRPCError, NullObjectExceptionError, \ + NullPointerExceptionError, RetryError, ServerError, SessionBrokenError, StaleObjectExceptionError, \ + UiAutomationNotConnectedError, UiObjectNotFoundError from .init import Initer # from .session import Session # noqa: F401 from .settings import Settings @@ -67,9 +62,6 @@ from .version import __apk_version__, __atx_agent_version__ from .watcher import WatchContext, Watcher -if six.PY2: - FileNotFoundError = OSError - DEBUG = False WAIT_FOR_DEVICE_TIMEOUT = int(os.getenv("WAIT_FOR_DEVICE_TIMEOUT", 20)) @@ -534,9 +526,7 @@ def _jsonrpc_call(self, method, params=[], http_timeout=60): def is_exception(err, exception_name): return err.exception_name == exception_name or exception_name in err.message - if isinstance( - err.data, - six.string_types) and 'UiAutomation not connected' in err.data: + if isinstance(err.data, str) and 'UiAutomation not connected' in err.data: err.__class__ = UiAutomationNotConnectedError elif err.message: if is_exception(err, 'uiautomator.UiObjectNotFoundException'): @@ -734,7 +724,7 @@ def _is_apk_outdated(self): def _package_exists(self, package_name: str) -> bool: return self.shell(['pm', 'path', package_name]).exit_code == 0 - def _package_version(self, package_name: str) -> Optional[packaging.version.Version]: + def _package_version(self, package_name: str) -> Optional[packaging_version.Version]: dump_output = self.shell(['dumpsys', 'package', package_name]).output m = re.compile(r'versionName=(?P[\d.]+)').search(dump_output) if m is None: @@ -819,7 +809,7 @@ def push(self, src, dst: str, mode=0o644, show_progress=False): modestr = oct(mode).replace('o', '') pathname = '/upload/' + dst.lstrip('/') filesize = 0 - if isinstance(src, six.string_types): + if isinstance(src, str): if re.match(r"^https?://", src): r = requests.get(src, stream=True) if r.status_code != 200: @@ -1751,7 +1741,7 @@ def send_action(self, code): "done": 6, "previous": 7, } - if isinstance(code, six.string_types): + if isinstance(code, str): code = __alias.get(code, code) self.shell([ 'am', 'broadcast', '-a', 'ADB_EDITOR_CODE', '--ei', 'code', @@ -1952,7 +1942,7 @@ def connect_adb_wifi(addr) -> Device: Raises: ConnectError """ - assert isinstance(addr, six.string_types) + assert isinstance(addr, str) subprocess.call([adbutils.adb_path(), "connect", addr]) try: diff --git a/uiautomator2/__main__.py b/uiautomator2/__main__.py index ea6a22bb..31c171d1 100644 --- a/uiautomator2/__main__.py +++ b/uiautomator2/__main__.py @@ -10,12 +10,12 @@ import os import re +import adbutils import progress.bar import requests from logzero import logger from retry import retry -import adbutils import uiautomator2 as u2 from .init import Initer diff --git a/uiautomator2/_selector.py b/uiautomator2/_selector.py index cc82559d..dd23f912 100644 --- a/uiautomator2/_selector.py +++ b/uiautomator2/_selector.py @@ -3,13 +3,12 @@ import warnings import requests -import six from PIL import Image from retry import retry +from ._proto import SCROLL_STEPS from .exceptions import UiObjectNotFoundError from .utils import Exists, intersect -from ._proto import SCROLL_STEPS class Selector(dict): @@ -413,7 +412,7 @@ def __getitem__(self, instance: int): Raises: IndexError """ - if isinstance(self.selector, six.string_types): + if isinstance(self.selector, str): raise IndexError( "Index is not supported when UiObject returned by child_by_xxx" ) diff --git a/uiautomator2/exceptions.py b/uiautomator2/exceptions.py index 1a1207ff..951b777f 100644 --- a/uiautomator2/exceptions.py +++ b/uiautomator2/exceptions.py @@ -1,9 +1,6 @@ # coding: utf-8 # -# class ATXError(Exception): -# pass - import json diff --git a/uiautomator2/ext-archived/aircv/__init__.py b/uiautomator2/ext-archived/aircv/__init__.py index ff9836f4..11644676 100644 --- a/uiautomator2/ext-archived/aircv/__init__.py +++ b/uiautomator2/ext-archived/aircv/__init__.py @@ -1,13 +1,13 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import cv2 -import numpy as np +import threading import time +import cv2 +import numpy as np import requests import websocket -import threading __version__ = "0.0.1" diff --git a/uiautomator2/ext-archived/ocr/__init__.py b/uiautomator2/ext-archived/ocr/__init__.py index a4951406..95989e65 100644 --- a/uiautomator2/ext-archived/ocr/__init__.py +++ b/uiautomator2/ext-archived/ocr/__init__.py @@ -10,9 +10,10 @@ d.ext_ocr("对战模式").click() """ -import requests import time +import requests + API = "" @@ -95,8 +96,8 @@ def click(self, timeout=10): if __name__ == '__main__': - import uiautomator2.ext.ocr as ocr import uiautomator2 as u2 + import uiautomator2.ext.ocr as ocr d = u2.connect() print(ocr.OCR(d)("王者峡谷").click()) \ No newline at end of file diff --git a/uiautomator2/ext-archived/ocr/baiduOCR.py b/uiautomator2/ext-archived/ocr/baiduOCR.py index 6cba9f34..1d512667 100644 --- a/uiautomator2/ext-archived/ocr/baiduOCR.py +++ b/uiautomator2/ext-archived/ocr/baiduOCR.py @@ -6,9 +6,10 @@ @description: 使用百度OCR实现截屏选取元素 """ +from aip import AipOcr + from uiautomator2.ext.ocr import OCR as u2OCR from uiautomator2.ext.ocr import OCRSelector as u2OCRSelector -from aip import AipOcr class OCR(u2OCR): diff --git a/uiautomator2/ext/htmlreport/__init__.py b/uiautomator2/ext/htmlreport/__init__.py index 7d18e2c2..8bf7d7ee 100644 --- a/uiautomator2/ext/htmlreport/__init__.py +++ b/uiautomator2/ext/htmlreport/__init__.py @@ -11,9 +11,11 @@ import sys import time import types -import uiautomator2 + from PIL import ImageDraw +import uiautomator2 + def mark_point(im, x, y): """ diff --git a/uiautomator2/ext/htmlreport/assets/simplehttpserver.py b/uiautomator2/ext/htmlreport/assets/simplehttpserver.py index 7df15dcb..54471d92 100644 --- a/uiautomator2/ext/htmlreport/assets/simplehttpserver.py +++ b/uiautomator2/ext/htmlreport/assets/simplehttpserver.py @@ -1,17 +1,11 @@ #!/usr/bin/env python # coding: utf-8 -import six +import http.server as SimpleHTTPServer import socket -from contextlib import closing +import socketserver as SocketServer import webbrowser - -if six.PY2: - import SimpleHTTPServer - import SocketServer -else: - import http.server as SimpleHTTPServer - import socketserver as SocketServer +from contextlib import closing def is_port_avaiable(port): diff --git a/uiautomator2/ext/info/__init__.py b/uiautomator2/ext/info/__init__.py index 3b0a0216..e7ac3726 100644 --- a/uiautomator2/ext/info/__init__.py +++ b/uiautomator2/ext/info/__init__.py @@ -1,7 +1,7 @@ +import atexit +import datetime import json import os -import datetime -import atexit from uiautomator2 import UIAutomatorServer from uiautomator2.ext.info import conf diff --git a/uiautomator2/ext/perf/__init__.py b/uiautomator2/ext/perf/__init__.py index 5ffec2ec..d3482db0 100644 --- a/uiautomator2/ext/perf/__init__.py +++ b/uiautomator2/ext/perf/__init__.py @@ -3,14 +3,14 @@ from __future__ import absolute_import, print_function -import threading -import re -import time -import datetime +import atexit import csv -import sys +import datetime import os -import atexit +import re +import sys +import threading +import time from collections import namedtuple _MEM_PATTERN = re.compile(r'TOTAL[:\s]+(\d+)') @@ -291,11 +291,13 @@ def csv2images(self, src=None, target_dir='.'): src: csv file, default to perf record csv path target_dir: images store dir """ - import pandas as pd - import matplotlib.pyplot as plt - import matplotlib.ticker as ticker import datetime import os + + import matplotlib.pyplot as plt + import matplotlib.ticker as ticker + import pandas as pd + from uiautomator2.utils import natualsize src = src or self.csv_output diff --git a/uiautomator2/ext/xpath/__init__.py b/uiautomator2/ext/xpath/__init__.py index de586ff7..e412d77f 100644 --- a/uiautomator2/ext/xpath/__init__.py +++ b/uiautomator2/ext/xpath/__init__.py @@ -2,6 +2,7 @@ # import warnings + from uiautomator2.xpath import * warnings.simplefilter("always", DeprecationWarning) diff --git a/uiautomator2/init.py b/uiautomator2/init.py index 5f8ecec9..0284c5f8 100644 --- a/uiautomator2/init.py +++ b/uiautomator2/init.py @@ -14,9 +14,8 @@ from logzero import logger, setup_logger from retry import retry -from uiautomator2.version import (__apk_version__, __atx_agent_version__, - __jar_version__, __version__) from uiautomator2.utils import natualsize +from uiautomator2.version import __apk_version__, __atx_agent_version__, __jar_version__, __version__ appdir = os.path.join(os.path.expanduser("~"), '.uiautomator2') diff --git a/uiautomator2/screenrecord.py b/uiautomator2/screenrecord.py index 5535eb93..7840dde5 100644 --- a/uiautomator2/screenrecord.py +++ b/uiautomator2/screenrecord.py @@ -2,8 +2,8 @@ # import re -import time import threading +import time import cv2 import imageio diff --git a/uiautomator2/settings.py b/uiautomator2/settings.py index d8623277..df9058cc 100644 --- a/uiautomator2/settings.py +++ b/uiautomator2/settings.py @@ -6,7 +6,6 @@ import pprint from typing import Any - logger = logging.getLogger("uiautomator2") class Settings(object): diff --git a/uiautomator2/swipe.py b/uiautomator2/swipe.py index 00e71c8a..0c131911 100644 --- a/uiautomator2/swipe.py +++ b/uiautomator2/swipe.py @@ -1,6 +1,7 @@ # coding: utf-8 from typing import Union + from ._proto import Direction diff --git a/uiautomator2/utils.py b/uiautomator2/utils.py index 820a90f2..08c1a17b 100644 --- a/uiautomator2/utils.py +++ b/uiautomator2/utils.py @@ -10,24 +10,11 @@ from typing import Union import filelock -import six from ._proto import Direction from .exceptions import SessionBrokenError, UiObjectNotFoundError -def U(x): - if six.PY3: - return x - return x.decode('utf-8') if type(x) is str else x - - -def E(x): - if six.PY3: - return x - return x.encode('utf-8') if type(x) is unicode else x # noqa: F821 - - def check_alive(fn): @functools.wraps(fn) def inner(self, *args, **kwargs): @@ -74,7 +61,7 @@ def inner(self, *args, **kwargs): if not self.wait(timeout=timeout): raise UiObjectNotFoundError({ 'code': -32002, - 'message': E(self.selector.__str__()) + 'message': self.selector.__str__() }) return fn(self, *args, **kwargs) diff --git a/uiautomator2/version.py b/uiautomator2/version.py index 486411ef..8d534586 100644 --- a/uiautomator2/version.py +++ b/uiautomator2/version.py @@ -1,11 +1,8 @@ # coding: utf-8 # -import pkg_resources -try: - __version__ = pkg_resources.get_distribution("uiautomator2").version -except pkg_resources.DistributionNotFound: - __version__ = "unknown" +# version managed by poetry +__version__ = "0.0.0" # See ChangeLog for details diff --git a/uiautomator2/webview.py b/uiautomator2/webview.py index 87e1d291..d277b217 100644 --- a/uiautomator2/webview.py +++ b/uiautomator2/webview.py @@ -11,7 +11,6 @@ import adbutils import pychrome import requests - from logzero import logger @@ -134,9 +133,11 @@ def screenshot(self): raise NotImplementedError() -from selenium import webdriver from contextlib import contextmanager +from selenium import webdriver + + @contextmanager def driver(package_name): serial = adbutils.adb.device().serial diff --git a/uiautomator2/xpath.py b/uiautomator2/xpath.py index 23268329..4e5773db 100644 --- a/uiautomator2/xpath.py +++ b/uiautomator2/xpath.py @@ -25,7 +25,7 @@ from ._proto import Direction from .abcd import BasicUIMeta from .exceptions import XPathElementNotFoundError -from .utils import U, inject_call, swipe_in_bounds +from .utils import inject_call, swipe_in_bounds try: from lxml import etree