QQ 커뮤니케이션 그룹: 815453846 디스코드: https://discord.gg/PbJhnZJKDd
한동안(어쩌면 2년 정도) 이 프로젝트를 유지하지 못했는데, 최근에 안드로이드 네이티브 자동화에 대한 재조사가 필요합니다. 물론 Appium도 조사해 본 결과 uiautomator2 프로젝트의 실행 속도를 발견했습니다. 정말 좋습니다. 요소 감지부터 클릭까지 모두 밀리초 안에 완료되며 코드도 이해하기 쉽습니다. 이전에 이렇게 마법 같은 프로젝트를 작성했다고는 전혀 예상하지 못했습니다. 어떻게 이렇게 좋은 프로젝트에 먼지가 쌓일 수 있었을까요? 재구성이 필요하고 일부 정크 코드를 정리해야 할까요? 그래서 프로젝트 버전이 2.xx에서 3.xx로 업그레이드되었습니다.
아직 버전 2.xx를 사용하고 있는 사용자는 3.xx로 업그레이드할지 여부를 결정하기 전에 2to3을 살펴보세요. (개인적으로 업그레이드를 적극 권장합니다.)
결국 버전 2~3은 메이저 버전 업그레이드로 많은 기능이 삭제됐다. 가장 먼저 삭제해야 할 것은 atx-agent이고, 두 번째로 atx-agent 관련 함수들이 잔뜩 있습니다. init와 같은 더 이상 사용되지 않는 기능입니다.
다양한 종속 라이브러리의 버전 번호
UiAutomator는 접근성 서비스를 기반으로 Android 자동화 테스트를 위해 Google에서 제공하는 Java 라이브러리입니다. 이는 매우 강력하며 타사 앱을 테스트하고, 화면에 있는 모든 앱의 제어 속성을 얻고, 모든 작업을 수행할 수 있지만 두 가지 단점이 있습니다. 1. 테스트 스크립트는 Java 언어만 사용할 수 있습니다. 2. 테스트 스크립트 jar 또는 apk 패키지로 패키징하여 장치에 업로드해야 실행됩니다.
테스트 로직이 Python으로 작성되어 컴퓨터에서 실행되는 동안 휴대폰을 제어할 수 있기를 바랍니다. 이 아이디어를 구현해준 Xiaocong He(@xiaocong)에게 진심으로 감사를 전하고 싶습니다(xiaocong/uiautomator 참조). 원칙은 휴대폰에서 http rpc 서비스를 실행하고 uiautomator에서 기능을 연 다음 이러한 http를 사용하는 것입니다. 인터페이스는 Python 라이브러리로 캡슐화됩니다. xiaocong/uiautomator
라이브러리가 오랫동안 업데이트되지 않았기 때문입니다. 그래서 버전을 직접 포크했습니다. 쉽게 구별하기 위해 마지막에 2개의 openatx/uiautomator2를 추가했습니다. 또한 해당 Android 패키지 소스 코드인 openatx/android-uiautomator-server도 포크했습니다.
원본 라이브러리의 버그 수정 외에도 많은 새로운 기능도 추가되었습니다. 그것은 주로 다음과 같은 부분을 포함합니다:
많은 사람들이 openatx/uiautomator2가 iOS 테스트를 지원하지 않는다고 자주 묻기 때문에 여기서 먼저 설명하겠습니다. iOS 자동화 테스트가 필요한 경우 openatx/facebook-wda 라이브러리로 이동할 수 있습니다.
PS: 이 라이브러리
https://github.com/NeteaseGame/ATX더 이상 유지보수 작업이 진행되지 않으므로 최대한 빨리 교체하시기 바랍니다.
이미 시작한 분들에게 적합한 빠른 참조 빠른 참조 가이드입니다. 의견을 환영합니다.
먼저开发者选项
켜진 안드로이드 폰을 한 대(두 개가 아닌) 준비하고, 컴퓨터에 연결한 후, adb devices
실행하여 연결된 기기가 보이는지 확인합니다.
pip3 install -U uiautomator2
실행하여 uiautomator2를 설치합니다.
명령줄에서 python
실행하여 Python 대화형 창을 엽니다. 그런 다음 창에 다음 명령을 입력하십시오.
import uiautomator2 as u2
d = u2 . connect () # connect to device
print ( d . info )
아래와 유사한 출력이 표시되면 공식적으로 라이브러리 사용을 시작할 수 있습니다. 이 라이브러리에는 기능이 너무 많고 그 뒤에 숨은 내용도 많기 때문에 천천히 읽어야 합니다....
{'currentPackageName': 'net.oneplus.launcher', 'displayHeight': 1920, 'displayRotation': 0, 'displaySizeDpX': 411, 'displaySizeDpY': 731, 'displayWidth': 1080, 'productName': 'OnePlus5', '
screenOn': True, 'sdkInt': 27, 'naturalOrientation': True}
또한, 안정성을 유지하기 위해서는小黄车
의 플로팅 창 권한을 활성화해야 합니다. 참고 기사 py-uiautomator2는 부동 창을 통해 오랫동안 서비스를 제공합니다.
일반적으로 성공하지만 놀라운 일이 있을 수 있습니다. QQ 그룹에 가입하여 문제를 보고할 수 있습니다(그룹 번호는 상단에 있습니다). 그룹에는 문제 해결에 도움을 줄 수 있는 많은 전문가들이 있습니다.
후원자 여러분 감사합니다!
비어 있는
우수한 기사 추천(피드백을 위해 QQ 그룹에 오신 것을 환영합니다)
成都-测试只会一点点
설치
장치에 연결
명령줄
전역 설정
앱 관리
UI 자동화
기여자
특허
uiautomator2 설치
pip install -U uiautomator2
설치 성공 여부 테스트 uiautomator2 --help
UI 검사기
pip install uiautodev
# 启动
uiauto.dev
현재 장치의 인터페이스 구조를 보려면 브라우저에서 https://uiauto.dev를 엽니다.
uiauto.dev
uiauto.dev는 uiautomator2와 독립적인 프로젝트로, 레이어 구조를 보는 데 사용됩니다. 이는 이전 프로젝트 편집기를 재구성한 버전이며, 현재 프로젝트의 지속적인 유지 관리를 지원하기 위해 나중에 요금이 청구될 수 있습니다(가격은 확실히 그만한 가치가 있습니다). 관심이 있으시면 그룹에 가입하여 토론(요청 포함)을 하실 수 있습니다. QQ 그룹 536481989
serialno를 사용하여 장치를 연결합니다(예: 123456f
)( adb devices
에서 볼 수 있음)
import uiautomator2 as u2
d = u2 . connect ( '123456f' ) # alias for u2.connect_usb('123456f')
print ( d . info )
직렬은 env-var ANDROID_SERIAL
통해 전달될 수 있습니다.
# export ANDROID_SERIAL=123456f
d = u2 . connect ()
$device_ip
장치의 IP 주소를 나타냅니다.
장치를 지정해야 하는 경우 --serial
(예: python3 -m uiautomator2 --serial bff1234
)을 전달해야 합니다. SubCommand는 하위 명령(스크린샷, 현재 등)입니다.
1.0.3 추가됨:
python3 -m uiautomator2
uiautomator2
와 같습니다.
스크린샷: 스크린샷
$ uiautomator2 screenshot screenshot.jpg
current: 현재 패키지 이름과 활동을 가져옵니다.
$ uiautomator2 current
{
" package " : " com.android.browser " ,
" activity " : " com.uc.browser.InnerUCMobile " ,
" pid " : 28478
}
제거: 앱 제거
$ uiautomator2 uninstall < package-name > # 卸载一个包
$ uiautomator2 uninstall < package-name- 1> < package-name- 2> # 卸载多个包
$ uiautomator2 uninstall --all # 全部卸载
중지: 앱 중지
$ uiautomator2 stop com.example.app # 停止一个app
$ uiautomator2 stop --all # 停止所有的app
의사:
$ uiautomator2 doctor
[I 2024-04-25 19:53:36,288 __main__:101 pid:15596] uiautomator2 is OK
Python이 종료되면 UiAutomation 서비스도 종료됩니다.
코드 뒤의 HTTP 요청 정보를 인쇄합니다.
> >> d . debug = True
> >> d . info
12 : 32 : 47.182 $ curl - X POST - d '{"jsonrpc": "2.0", "id": "b80d3a488580be1f3e9cb3e926175310", "method": "deviceInfo", "params": {}}' 'http://127.0.0.1:54179/jsonrpc/0'
12 : 32 : 47.225 Response >> >
{ "jsonrpc" : "2.0" , "id" : "b80d3a488580be1f3e9cb3e926175310" , "result" :{ "currentPackageName" : "com.android.mms" , "displayHeight" : 1920 , "displayRotation" : 0 , "displaySizeDpX" : 360 , "displaySizeDpY" : 640 , "displayWidth" : 1080 , "productName"
: "odin" , "screenOn" : true , "sdkInt" : 25 , "naturalOrientation" : true }}
< << END
요소 검색 대기시간 설정(기본값 20초)
d . implicitly_wait ( 10.0 ) # 也可以通过d.settings['wait_timeout'] = 10.0 修改
d ( text = "Settings" ). click () # if Settings button not show in 10s, UiObjectNotFoundError will raised
print ( "wait timeout" , d . implicitly_wait ()) # get default implicit wait
이 함수는 click
, long_click
, drag_to
, get_text
, set_text
, clear_text
등에 영향을 미칩니다.
이 부분에서는 앱 관리를 수행하는 방법을 보여줍니다.
URL을 통한 APK 설치만 지원됩니다.
d . app_install ( 'http://some-domain.com/some.apk' )
# 默认的这种方法是先通过atx-agent解析apk包的mainActivity,然后调用am start -n $package/$activity启动
d . app_start ( "com.example.hello_world" )
# 使用 monkey -p com.example.hello_world -c android.intent.category.LAUNCHER 1 启动
# 这种方法有个副作用,它自动会将手机的旋转锁定给关掉
d . app_start ( "com.example.hello_world" , use_monkey = True ) # start with package name
# 通过指定main activity的方式启动应用,等价于调用am start -n com.example.hello_world/.MainActivity
d . app_start ( "com.example.hello_world" , ".MainActivity" )
# equivalent to `am force-stop`, thus you could lose data
d . app_stop ( "com.example.hello_world" )
# equivalent to `pm clear`
d . app_clear ( 'com.example.hello_world' )
# stop all
d . app_stop_all ()
# stop all app except for com.examples.demo
d . app_stop_all ( excludes = [ 'com.examples.demo' ])
d . app_info ( "com.examples.demo" )
# expect output
#{
# "mainActivity": "com.github.uiautomator.MainActivity",
# "label": "ATX",
# "versionName": "1.1.7",
# "versionCode": 1001007,
# "size":1760809
#}
# save app icon
img = d . app_icon ( "com.examples.demo" )
img . save ( "icon.png" )
d . app_list_running ()
# expect output
# ["com.xxxx.xxxx", "com.github.uiautomator", "xxxx"]
pid = d . app_wait ( "com.example.android" ) # 等待应用运行, return pid(int)
if not pid :
print ( "com.example.android is not running" )
else :
print ( "com.example.android pid is %d" % pid )
d . app_wait ( "com.example.android" , front = True ) # 等待应用前台运行
d . app_wait ( "com.example.android" , timeout = 20.0 ) # 最长等待时间20s(默认)
버전 1.2.0에 추가됨
장치에 파일 푸시
# push to a folder
d . push ( "foo.txt" , "/sdcard/" )
# push and rename
d . push ( "foo.txt" , "/sdcard/bar.txt" )
# push fileobj
with open ( "foo.txt" , 'rb' ) as f :
d . push ( f , "/sdcard/" )
# push and change file access mode
d . push ( "foo.sh" , "/data/local/tmp/" , mode = 0o755 )
장치에서 파일을 가져옵니다
d . pull ( "/sdcard/tmp.txt" , "tmp.txt" )
# FileNotFoundError will raise if the file is not found on the device
d . pull ( "/sdcard/some-file-not-exists.txt" , "tmp.txt" )
# grant all the permissions
d . app_auto_grant_permissions ( "io.appium.android.apis" )
# open scheme
d . open_url ( "appname://appnamehost" )
# same as
# adb shell am start -a android.intent.action.VIEW -d "appname://appnamehost"
이 부분에서는 일반적인 장치 작업을 수행하는 방법을 보여줍니다.
시간 초과 보호 기능을 사용하여 단기 쉘 명령을 실행합니다(기본 시간 초과 60초).
참고: 시간 초과를 지원하려면 atx-agent >=0.3.3
이 필요합니다.
adb_shell
함수는 더 이상 사용되지 않습니다. 대신 shell
사용하세요.
간단한 사용법
output , exit_code = d . shell ( "pwd" , timeout = 60 ) # timeout 60s (Default)
# output: "/n", exit_code: 0
# Similar to command: adb shell pwd
# Since `shell` function return type is `namedtuple("ShellResponse", ("output", "exit_code"))`
# so we can do some tricks
output = d . shell ( "pwd" ). output
exit_code = d . shell ( "pwd" ). exit_code
예를 들어 첫 번째 인수는 목록일 수 있습니다.
output , exit_code = d . shell ([ "ls" , "-l" ])
# output: "/....", exit_code: 0
이는 stderr과 병합된 stdout에 대한 문자열을 반환합니다. 명령이 차단 명령인 경우 명령이 완료되거나 시간 초과가 시작될 때까지 shell
도 차단됩니다. 명령 실행 중에는 부분 출력이 수신되지 않습니다. 장기 실행 명령에 적합합니다. 주어진 셸 명령은 adb
또는 shell
(앱 권한보다 높은) Linux 권한 수준을 갖는 adb shell
과 유사한 환경에서 실행됩니다.
장기 실행 셸 명령 실행(제거됨)
세션은 앱 수명 주기를 나타내며 앱을 시작하고 앱 충돌을 감지하는 데 사용할 수 있습니다.
앱 실행 및 닫기
sess = d . session ( "com.netease.cloudmusic" ) # start 网易云音乐
sess . close () # 停止网易云音乐
sess . restart () # 冷启动网易云音乐
Python을 사용 with
앱을 시작하고 닫습니다.
with d . session ( "com.netease.cloudmusic" ) as sess :
sess ( text = "Play" ). click ()
실행 중인 앱에 연결
# launch app if not running, skip launch if already running
sess = d . session ( "com.netease.cloudmusic" , attach = True )
앱 충돌 감지
# When app is still running
sess ( text = "Music" ). click () # operation goes normal
# If app crash or quit
sess ( text = "Music" ). click () # raise SessionBrokenError
# other function calls under session will raise SessionBrokenError too
# check if session is ok.
# Warning: function name may change in the future
sess . running () # True or False
기본 정보 얻기
d . info
다음은 가능한 출력입니다.
{'currentPackageName': 'com.android.systemui',
'displayHeight': 1560,
'displayRotation': 0,
'displaySizeDpX': 360,
'displaySizeDpY': 780,
'displayWidth': 720,
'naturalOrientation': True,
'productName': 'ELE-AL00',
'screenOn': True,
'sdkInt': 29}
창 크기 가져오기
print ( d . window_size ())
# device upright output example: (1080, 1920)
# device horizontal output example: (1920, 1080)
현재 앱 정보를 가져옵니다. 일부 Android 장치의 경우 출력이 비어 있을 수 있습니다( 출력 예제 3 참조).
print ( d . app_current ())
# Output example 1: {'activity': '.Client', 'package': 'com.netease.example', 'pid': 23710}
# Output example 2: {'activity': '.Client', 'package': 'com.netease.example'}
# Output example 3: {'activity': None, 'package': None}
대기 활동
d . wait_activity ( ".ApiDemos" , timeout = 10 ) # default timeout 10.0 seconds
# Output: true of false
장치 일련번호 받기
print ( d . serial )
# output example: 74aAEDR428Z9
WLAN IP 얻기
print ( d . wlan_ip )
# output example: 10.0.0.1 or None
자세한 장치 정보 얻기 d.device_info
장치_정보
print ( d . device_info )
다음은 가능한 출력입니다.
{'arch': 'arm64-v8a',
'brand': 'google',
'model': 'sdk_gphone64_arm64',
'sdk': 34,
'serial': 'EMULATOR34X1X19X0',
'version': 14}
설정된 클립보드 콘텐츠 가져오기
임시 보드 콘텐츠 설정 또는 콘텐츠 가져오기
클립보드/set_clipboard
d . clipboard = 'hello-world'
# or
d . set_clipboard ( 'hello-world' , 'label' )
클립보드 콘텐츠 가져오기
클립보드를 얻으려면 IME(com.github.uiautomator/.AdbKeyboard)를 사용하기 전에
d.set_input_ime()
호출해야 합니다.
```python
# get clipboard content
print(d.clipboard)
```
화면 켜기/끄기
d . screen_on () # turn on the screen
d . screen_off () # turn off the screen
현재 화면 상태 가져오기
d . info . get ( 'screenOn' ) # require Android >= 4.4
하드/소프트 키 누르기
d . press ( "home" ) # press the home key, with key name
d . press ( "back" ) # press the back key, with key name
d . press ( 0x07 , 0x02 ) # press keycode 0x07('0') with META ALT(0x02)
현재 지원되는 키 이름은 다음과 같습니다.
Android KeyEvnet에서 모든 키 코드 정의를 찾을 수 있습니다.
화면 잠금해제
d . unlock ()
# This is equivalent to
# 1. press("power")
# 2. swipe from left-bottom to right-top
화면을 클릭하세요
d . click ( x , y )
더블클릭
d . double_click ( x , y )
d . double_click ( x , y , 0.1 ) # default duration between two click is 0.1s
화면을 길게 클릭하세요.
d . long_click ( x , y )
d . long_click ( x , y , 0.5 ) # long click 0.5s (default)
강타
d . swipe ( sx , sy , ex , ey )
d . swipe ( sx , sy , ex , ey , 0.5 ) # swipe for 0.5s(default)
SwipeExt 확장 기능
d . swipe_ext ( "right" ) # 手指右滑,4选1 "left", "right", "up", "down"
d . swipe_ext ( "right" , scale = 0.9 ) # 默认0.9, 滑动距离为屏幕宽度的90%
d . swipe_ext ( "right" , box = ( 0 , 0 , 100 , 100 )) # 在 (0,0) -> (100, 100) 这个区域做滑动
# 实践发现上滑或下滑的时候,从中点开始滑动成功率会高一些
d . swipe_ext ( "up" , scale = 0.8 ) # 代码会vkk
# 还可以使用Direction作为参数
from uiautomator2 import Direction
d . swipe_ext ( Direction . FORWARD ) # 页面下翻, 等价于 d.swipe_ext("up"), 只是更好理解
d . swipe_ext ( Direction . BACKWARD ) # 页面上翻
d . swipe_ext ( Direction . HORIZ_FORWARD ) # 页面水平右翻
d . swipe_ext ( Direction . HORIZ_BACKWARD ) # 页面水平左翻
견인
d . drag ( sx , sy , ex , ey )
d . drag ( sx , sy , ex , ey , 0.5 ) # swipe for 0.5s(default)
스와이프 포인트
# swipe from point(x0, y0) to point(x1, y1) then to point(x2, y2)
# time will speed 0.2s bwtween two points
d . swipe_points ([( x0 , y0 ), ( x1 , y1 ), ( x2 , y2 )], 0.2 ))
주로 Jiugong 패턴을 잠금 해제하는 데 사용됩니다. 각 지점의 상대 좌표를 미리 얻을 수 있습니다. (퍼센트는 여기에서 지원됩니다.) 자세한 사용법은 u2를 사용하여 Jiugong 패턴을 잠금 해제하세요.
터치 앤 드래그(베타)
이 인터페이스는 상대적으로 낮은 수준의 원시 인터페이스로 완벽하지는 않지만 사용할 수 있습니다. 참고: 이 장소는 백분율을 지원하지 않습니다.
d . touch . down ( 10 , 10 ) # 模拟按下
time . sleep ( .01 ) # down 和 move 之间的延迟,自己控制
d . touch . move ( 15 , 15 ) # 模拟移动
d . touch . up ( 10 , 10 ) # 模拟抬起
참고: 클릭, 스와이프, 드래그 작업은 백분율 위치 값을 지원합니다.
d.long_click(0.5, 0.5)
화면 중앙을 길게 클릭함을 의미합니다.
장치 방향 검색/설정
가능한 방향:
natural
또는 n
left
아니면 l
right
또는 r
upsidedown
또는 u
(설정할 수 없음) # retrieve orientation. the output could be "natural" or "left" or "right" or "upsidedown"
orientation = d . orientation
# WARNING: not pass testing in my TT-M1
# set orientation and freeze rotation.
# notes: setting "upsidedown" requires Android>=4.3.
d . set_orientation ( 'l' ) # or "left"
d . set_orientation ( "l" ) # or "left"
d . set_orientation ( "r" ) # or "right"
d . set_orientation ( "n" ) # or "natural"
고정/고정 해제 회전
# freeze rotation
d . freeze_rotation ()
# un-freeze rotation
d . freeze_rotation ( False )
스크린샷 찍기
# take screenshot and save to a file on the computer, require Android>=4.2.
d . screenshot ( "home.jpg" )
# get PIL.Image formatted images. Naturally, you need pillow installed first
image = d . screenshot () # default format="pillow"
image . save ( "home.jpg" ) # or home.png. Currently, only png and jpg are supported
# get opencv formatted images. Naturally, you need numpy and cv2 installed first
import cv2
image = d . screenshot ( format = 'opencv' )
cv2 . imwrite ( 'home.jpg' , image )
# get raw jpeg data
imagebin = d . screenshot ( format = 'raw' )
open ( "some.jpg" , "wb" ). write ( imagebin )
덤프 UI 계층 구조
# get the UI hierarchy dump content
xml = d . dump_hierarchy ()
# compressed=True: include not import nodes
# pretty: format xml
# max_depth: limit xml depth, default 50
xml = d . dump_hierarchy ( compressed = False , pretty = False , max_depth = 50 )
알림 또는 빠른 설정 열기
d . open_notification ()
d . open_quick_settings ()
선택기는 현재 창에서 특정 UI 개체를 식별하는 편리한 메커니즘입니다.
# Select the object with text 'Clock' and its className is 'android.widget.TextView'
d ( text = 'Clock' , className = 'android.widget.TextView' )
Selector는 아래 매개변수를 지원합니다. 자세한 내용은 UiSelector Java 문서를 참조하세요.
text
, textContains
, textMatches
, textStartsWith
className
, classNameMatches
description
, descriptionContains
, descriptionMatches
, descriptionStartsWith
checkable
, checked
, clickable
, longClickable
scrollable
, enabled
, focusable
, focused
, selected
packageName
, packageNameMatches
resourceId
, resourceIdMatches
index
, instance
어린이들
# get the children or grandchildren
d ( className = "android.widget.ListView" ). child ( text = "Bluetooth" )
형제
# get siblings
d ( text = "Google" ). sibling ( className = "android.widget.ImageView" )
텍스트나 설명 또는 사례를 통한 어린이
# get the child matching the condition className="android.widget.LinearLayout"
# and also its children or grandchildren with text "Bluetooth"
d ( className = "android.widget.ListView" , resourceId = "android:id/list" )
. child_by_text ( "Bluetooth" , className = "android.widget.LinearLayout" )
# get children by allowing scroll search
d ( className = "android.widget.ListView" , resourceId = "android:id/list" )
. child_by_text (
"Bluetooth" ,
allow_scroll_search = True ,
className = "android.widget.LinearLayout"
)
child_by_description
은 지정된 설명을 가진 손자를 가진 하위 항목을 찾는 것입니다. 다른 매개변수는 child_by_text
와 유사합니다.
child_by_instance
는 지정된 인스턴스에 있는 하위 계층 내 어디든 하위 UI 요소가 있는 하위 항목을 찾는 것입니다. 이는 스크롤 없이 표시되는 뷰에서 수행됩니다.
자세한 내용은 아래 링크를 참조하세요.
getChildByDescription
, getChildByText
, getChildByInstance
getChildByDescription
, getChildByText
, getChildByInstance
위의 메소드는 체인 호출을 지원합니다(예: 아래 계층 구조).
< node index = " 0 " text = " " resource-id = " android:id/list " class = " android.widget.ListView " ...>
< node index = " 0 " text = " WIRELESS & NETWORKS " resource-id = " " class = " android.widget.TextView " .../>
< node index = " 1 " text = " " resource-id = " " class = " android.widget.LinearLayout " ...>
< node index = " 1 " text = " " resource-id = " " class = " android.widget.RelativeLayout " ...>
< node index = " 0 " text = " Wi‑Fi " resource-id = " android:id/title " class = " android.widget.TextView " .../>
node >
< node index = " 2 " text = " ON " resource-id = " com.android.settings:id/switchWidget " class = " android.widget.Switch " .../>
node >
...
node >
TextView 'Wi-Fi' 바로 옆에 있는 스위치 위젯을 클릭하려면 먼저 스위치 위젯을 선택해야 합니다. 그러나 UI 계층 구조에 따라 둘 이상의 스위치 위젯이 존재하며 className으로 선택하면 거의 동일한 속성을 갖습니다. 또는 아래 선택 전략이 도움이 될 것입니다.
d ( className = "android.widget.ListView" , resourceId = "android:id/list" )
. child_by_text ( "Wi‑Fi" , className = "android.widget.LinearLayout" )
. child ( className = "android.widget.Switch" )
. click ()
상대 위치 지정
또한 상대 위치 지정 방법을 사용하여 left
, right
, top
, bottom
뷰를 얻을 수 있습니다.
d(A).left(B)
, A의 왼쪽에서 B를 선택합니다.d(A).right(B)
, A의 오른쪽에 있는 B를 선택합니다.d(A).up(B)
, A 위에서 B를 선택합니다.d(A).down(B)
, A에서 B를 선택합니다.따라서 위의 경우 다음을 사용하여 선택할 수도 있습니다.
## select "switch" on the right side of "Wi‑Fi"
d ( text = "Wi‑Fi" ). right ( className = "android.widget.Switch" ). click ()
다중 인스턴스
때로는 화면에 동일한 속성(예: 텍스트)을 가진 여러 보기가 포함될 수 있습니다. 그런 다음 선택기에서 "인스턴스" 속성을 사용하여 아래와 같이 적합한 인스턴스 중 하나를 선택해야 합니다.
d ( text = "Add new" , instance = 0 ) # which means the first instance with text "Add new"
또한 uiautomator2는 목록과 유사한 API(jQuery와 유사)를 제공합니다.
# get the count of views with text "Add new" on current screen
d ( text = "Add new" ). count
# same as count property
len ( d ( text = "Add new" ))
# get the instance via index
d ( text = "Add new" )[ 0 ]
d ( text = "Add new" )[ 1 ]
...
# iterator
for view in d ( text = "Add new" ):
view . info # ...
참고 : 결과 목록을 살펴보는 코드 블록에서 선택기를 사용할 때 화면의 UI 요소가 변경되지 않은 상태로 유지되도록 해야 합니다. 그렇지 않으면 목록을 반복할 때 요소를 찾을 수 없음 오류가 발생할 수 있습니다.
특정 UI 객체가 존재하는지 확인
d ( text = "Settings" ). exists # True if exists, else False
d . exists ( text = "Settings" ) # alias of above property.
# advanced usage
d ( text = "Settings" ). exists ( timeout = 3 ) # wait Settings appear in 3s, same as .wait(3)
특정 UI 객체의 정보를 검색합니다.
d ( text = "Settings" ). info
다음은 가능한 출력입니다.
{ u'contentDescription': u'',
u'checked': False,
u'scrollable': False,
u'text': u'Settings',
u'packageName': u'com.android.launcher',
u'selected': False,
u'enabled': True,
u'bounds': {u'top': 385,
u'right': 360,
u'bottom': 585,
u'left': 200},
u'className': u'android.widget.TextView',
u'focused': False,
u'focusable': True,
u'clickable': True,
u'chileCount': 0,
u'longClickable': True,
u'visibleBounds': {u'top': 385,
u'right': 360,
u'bottom': 585,
u'left': 200},
u'checkable': False
}
편집 가능한 필드의 텍스트 가져오기/설정/지우기(예: EditText 위젯)
d ( text = "Settings" ). get_text () # get widget text
d ( text = "Settings" ). set_text ( "My text..." ) # set the text
d ( text = "Settings" ). clear_text () # clear the text
위젯 중심점 가져오기
x , y = d ( text = "Settings" ). center ()
# x, y = d(text="Settings").center(offset=(0, 0)) # left-top x, y
위젯 스크린샷 찍기
im = d ( text = "Settings" ). screenshot ()
im . save ( "settings.jpg" )
특정 개체를 클릭하십시오.
# click on the center of the specific ui object
d ( text = "Settings" ). click ()
# wait element to appear for at most 10 seconds and then click
d ( text = "Settings" ). click ( timeout = 10 )
# click with offset(x_offset, y_offset)
# click_x = x_offset * width + x_left_top
# click_y = y_offset * height + y_left_top
d ( text = "Settings" ). click ( offset = ( 0.5 , 0.5 )) # Default center
d ( text = "Settings" ). click ( offset = ( 0 , 0 )) # click left-top
d ( text = "Settings" ). click ( offset = ( 1 , 1 )) # click right-bottom
# click when exists in 10s, default timeout 0s
clicked = d ( text = 'Skip' ). click_exists ( timeout = 10.0 )
# click until element gone, return bool
is_gone = d ( text = "Skip" ). click_gone ( maxretry = 10 , interval = 1.0 ) # maxretry default 10, interval default 1.0
특정 UI 객체를 길게 클릭하세요.
# long click on the center of the specific UI object
d ( text = "Settings" ). long_click ()
UI 개체를 다른 지점이나 다른 UI 개체 쪽으로 드래그합니다.
# notes : drag can not be used for Android<4.3.
# drag the UI object to a screen point (x, y), in 0.5 second
d ( text = "Settings" ). drag_to ( x , y , duration = 0.5 )
# drag the UI object to (the center position of) another UI object, in 0.25 second
d ( text = "Settings" ). drag_to ( text = "Clock" , duration = 0.25 )
UI 개체의 중앙에서 가장자리로 스와이프합니다.
스와이프는 4가지 방향을 지원합니다:
d ( text = "Settings" ). swipe ( "right" )
d ( text = "Settings" ). swipe ( "left" , steps = 10 )
d ( text = "Settings" ). swipe ( "up" , steps = 20 ) # 1 steps is about 5ms, so 20 steps is about 0.1s
d ( text = "Settings" ). swipe ( "down" , steps = 20 )
한 지점에서 다른 지점으로의 2점 제스처
d ( text = "Settings" ). gesture (( sx1 , sy1 ), ( sx2 , sy2 ), ( ex1 , ey1 ), ( ex2 , ey2 ))
특정 UI 객체에 대한 2점 제스처
두 가지 제스처를 지원합니다:
In
가장자리에서 중앙까지Out
, 중앙에서 가장자리까지 # notes : pinch can not be set until Android 4.3.
# from edge to center. here is "In" not "in"
d ( text = "Settings" ). pinch_in ( percent = 100 , steps = 10 )
# from center to edge
d ( text = "Settings" ). pinch_out ()
특정 UI가 나타나거나 사라질 때까지 기다리세요
# wait until the ui object appears
d ( text = "Settings" ). wait ( timeout = 3.0 ) # return bool
# wait until the ui object gone
d ( text = "Settings" ). wait_gone ( timeout = 1.0 )
기본 시간 제한은 20초입니다. 자세한 내용은 전역 설정을 참조하세요.
특정 UI 객체에 플링 수행(스크롤 가능)
가능한 속성:
horiz
또는 vert
forward
또는 backward
또는 toBeginning
또는 toEnd
# fling forward(default) vertically(default)
d ( scrollable = True ). fling ()
# fling forward horizontally
d ( scrollable = True ). fling . horiz . forward ()
# fling backward vertically
d ( scrollable = True ). fling . vert . backward ()
# fling to beginning horizontally
d ( scrollable = True ). fling . horiz . toBeginning ( max_swipes = 1000 )
# fling to end vertically
d ( scrollable = True ). fling . toEnd ()
특정 UI 객체에 대해 스크롤 수행(스크롤 가능)
가능한 속성:
horiz
또는 vert
forward
또는 backward
또는 toBeginning
또는 toEnd
또는 to
# scroll forward(default) vertically(default)
d ( scrollable = True ). scroll ( steps = 10 )
# scroll forward horizontally
d ( scrollable = True ). scroll . horiz . forward ( steps = 100 )
# scroll backward vertically
d ( scrollable = True ). scroll . vert . backward ()
# scroll to beginning horizontally
d ( scrollable = True ). scroll . horiz . toBeginning ( steps = 100 , max_swipes = 1000 )
# scroll to end vertically
d ( scrollable = True ). scroll . toEnd ()
# scroll forward vertically until specific ui object appears
d ( scrollable = True ). scroll . to ( text = "Security" )
현재 watch_context는 스레딩을 사용하여 시작되며 2초마다 확인됩니다. 현재 트리거 작업은 클릭뿐입니다.
with d . watch_context () as ctx :
# 当同时出现 (立即下载 或 立即更新)和 取消 按钮的时候,点击取消
ctx . when ( "^立即(下载|更新)" ). when ( "取消" ). click ()
ctx . when ( "同意" ). click ()
ctx . when ( "确定" ). click ()
# 上面三行代码是立即执行完的,不会有什么等待
ctx . wait_stable () # 开启弹窗监控,并等待界面稳定(两个弹窗检查周期内没有弹窗代表稳定)
# 使用call函数来触发函数回调
# call 支持两个参数,d和el,不区分参数位置,可以不传参,如果传参变量名不能写错
# eg: 当有元素匹配仲夏之夜,点击返回按钮
ctx . when ( "仲夏之夜" ). call ( lambda d : d . press ( "back" ))
ctx . when ( "确定" ). call ( lambda el : el . click ())
# 其他操作
# 为了方便也可以使用代码中默认的弹窗监控逻辑
# 下面是目前内置的默认逻辑,可以加群at群主,增加新的逻辑,或者直接提pr
# when("继续使用").click()
# when("移入管控").when("取消").click()
# when("^立即(下载|更新)").when("取消").click()
# when("同意").click()
# when("^(好的|确定)").click()
with d . watch_context ( builtin = True ) as ctx :
# 在已有的基础上增加
ctx . when ( "@tb:id/jview_view" ). when ( '//*[@content-desc="图片"]' ). click ()
# 其他脚本逻辑
글쓰기의 또 다른 방법
ctx = d . watch_context ()
ctx . when ( "设置" ). click ()
ctx . wait_stable () # 等待界面不在有弹窗了
ctx . close ()
좀 더 간결하게 작성하려면 WatchContext를 사용하는 것이 좋습니다 .
선택기가 일치하는 항목을 찾지 못한 경우 일부 작업을 수행하도록 감시자를 등록할 수 있습니다.
2.0.0 이전에는 uiautomator-jar 라이브러리에서 제공하는 [Watcher]((http://developer.android.com/tools/help/uiautomator/UiWatcher.html) 메소드를 사용했지만 실제로는 한번 uiautomator 연결이 실패했습니다. 다시 시작한 후 모든 감시자 구성이 손실되며 이는 절대 용납할 수 없는 일입니다.
따라서 현재의 방법은 백그라운드에서 스레드를 실행(스레딩 라이브러리에 의존)한 다음 가끔씩 계층 구조를 덤프하고 요소를 일치시킨 후 해당 작업을 수행하는 것입니다.
사용 예
등록 모니터링
# 常用写法,注册匿名监控
d . watcher . when ( "安装" ). click ()
# 注册名为ANR的监控,当出现ANR和Force Close时,点击Force Close
d . watcher ( "ANR" ). when ( xpath = "ANR" ). when ( "Force Close" ). click ()
# 其他回调例子
d . watcher . when ( "抢红包" ). press ( "back" )
d . watcher . when ( "//*[@text = 'Out of memory']" ). call ( lambda d : d . shell ( 'am force-stop com.im.qq' ))
# 回调说明
def click_callback ( d : u2 . Device ):
d . xpath ( "确定" ). click () # 在回调中调用不会再次触发watcher
d . xpath ( "继续" ). click () # 使用d.xpath检查元素的时候,会触发watcher(目前最多触发5次)
# 开始后台监控
d . watcher . start ()
모니터 작업
# 移除ANR的监控
d . watcher . remove ( "ANR" )
# 移除所有的监控
d . watcher . remove ()
# 开始后台监控
d . watcher . start ()
d . watcher . start ( 2.0 ) # 默认监控间隔2.0s
# 强制运行所有监控
d . watcher . run ()
# 停止监控
d . watcher . stop ()
# 停止并移除所有的监控,常用于初始化
d . watcher . reset ()
그 외에도 아직 작성되지 않은 문서가 많이 있으니 소스코드 watcher.py로 직접 가보시는 걸 추천드립니다.
u2 . HTTP_TIMEOUT = 60 # 默认值60s, http默认请求超时时间
다른 구성의 대부분은 현재 d.settings
에 집중되어 있으며 나중에 필요에 따라 구성이 늘어나거나 줄어들 수 있습니다.
print ( d . settings )
{ 'operation_delay' : ( 0 , 0 ),
'operation_delay_methods' : [ 'click' , 'swipe' ],
'wait_timeout' : 20.0 }
# 配置点击前延时0.5s,点击后延时1s
d . settings [ 'operation_delay' ] = ( .5 , 1 )
# 修改延迟生效的方法
# 其中 double_click, long_click 都对应click
d . settings [ 'operation_delay_methods' ] = [ 'click' , 'swipe' , 'drag' , 'press' ]
d . settings [ 'wait_timeout' ] = 20.0 # 默认控件等待时间(原生操作,xpath插件的等待时间)
d . settings [ 'max_depth' ] = 50 # 默认50,限制dump_hierarchy返回的元素层级
버전 업그레이드의 경우 만료된 구성을 설정하면 더 이상 사용되지 않음이라는 메시지가 표시되지만 예외는 발생하지 않습니다.
>>> d.settings[ ' click_before_delay ' ] = 1
[W 200514 14:55:59 settings:72] d.settings[click_before_delay] deprecated: Use operation_delay instead
uiautomator 복구 모드 설정
주의 깊게 살펴보면 휴대전화에 실제로 두 개의 APK가 설치되어 있다는 것을 알 수 있으며 그 중 하나가 전경(작은 노란색 자동차)에 표시됩니다. com.github.uiautomator.test
라는 패키지는 백그라운드에 표시되지 않습니다. 이 두 APK는 동일한 인증서로 서명됩니다. 보이지 않는 애플리케이션은 실제로 모든 테스트 코드가 포함된 테스트 패키지이며, 이를 통해 핵심 테스트 서비스도 시작됩니다. 그러나 실행 중일 때 시스템은 작은 노란색 자동차가 항상 실행되어야 합니다(백그라운드에서 실행될 수도 있음). 작은 노란색 자동차 애플리케이션이 종료되면 백그라운드에서 실행 중인 테스트 서비스도 빠르게 종료됩니다. 아무것도 하지 않더라도 애플리케이션이 백그라운드에 있으면 시스템에서 빠르게 재활용됩니다. (가짜 앱에 의존하지 않는 방법에 대해 전문가들이 나에게 몇 가지 지침을 제공할 수 있기를 바랍니다. 이론적으로는 가능하다고 생각하지만 아직 어떻게 해야 할지 모르겠습니다.)
작은 노란색 자동차를 백그라운드에서 실행하는 방법에는 두 가지가 있습니다. 하나는 애플리케이션을 시작하고 백그라운드(기본값)에 두는 것입니다. 또한 am startservice
통해 백그라운드 서비스를 시작할 수도 있습니다.
이 동작은 d.settings["uiautomator_runtest_app_background"] = True
통해 조정할 수 있습니다. True는 애플리케이션 시작을 의미하고, False는 서비스 시작을 의미합니다.
UiAutomator의 시간 초과 설정(숨겨진 메서드)
>> d . jsonrpc . getConfigurator ()
{ 'actionAcknowledgmentTimeout' : 500 ,
'keyInjectionDelay' : 0 ,
'scrollAcknowledgmentTimeout' : 200 ,
'waitForIdleTimeout' : 0 ,
'waitForSelectorTimeout' : 0 }
>> d . jsonrpc . setConfigurator ({ "waitForIdleTimeout" : 100 })
{ 'actionAcknowledgmentTimeout' : 500 ,
'keyInjectionDelay' : 0 ,
'scrollAcknowledgmentTimeout' : 200 ,
'waitForIdleTimeout' : 100 ,
'waitForSelectorTimeout' : 0 }
클라이언트 프로그램이 시간 초과에 응답하는 것을 방지하기 위해 waitForIdleTimeout
및 waitForSelectorTimeout
이제 0
으로 변경되었습니다.
참조: Google uiautomator 구성자
이 방법은 일반적으로 제어가 알려지지 않은 입력에 사용됩니다.
# 目前采用从剪贴板粘贴的方式输入
d . send_keys ( "你好123abcEFG" )
d . send_keys ( "你好123abcEFG" , clear = True )
d . clear_text () # 清除输入框所有内容
d . send_action () # 根据输入框的需求,自动执行回车、搜索等指令, Added in version 3.1
# 也可以指定发送的输入法action, eg: d.send_action("search") 支持 go, search, send, next, done, previous
print ( d . current_ime ()) # 获取当前输入法ID
추가 참조: IME_ACTION_CODE
print ( d . last_toast ) # get last toast, if not toast return None
d . clear_toast ()
버전 3.2.0에서 수정됨
Java uiautoamtor는 기본적으로 xpath를 지원하지 않으므로 이는 확장된 기능입니다. 속도는 그리 빠르지 않습니다.
예: 노드 중 하나의 내용
">< android .widget.TextView index = " 2 " text = " 05:19 " resource-id = " com.netease.cloudmusic:id/qf " package = " com.netease.cloudmusic " content-desc = " " checkable = " false " checked = " false " clickable = " false " enabled = " true " focusable = " false " focused = " false " scrollable = " false " long-clickable = " false " password = " false " selected = " false " visible-to-user = " true " bounds = " [957,1602][1020,1636] " />
xpath 위치 지정 및 사용법
일부 속성의 이름이 수정되었으므로 기록해두어야 합니다.
description -> content-desc
resourceId -> resource-id
일반적인 사용법
# wait exists 10s
d . xpath ( "//android.widget.TextView" ). wait ( 10.0 )
# find and click
d . xpath ( "//*[@content-desc='分享']" ). click ()
# check exists
if d . xpath ( "//android.widget.TextView[contains(@text, 'Se')]" ). exists :
print ( "exists" )
# get all text-view text, attrib and center point
for elem in d . xpath ( "//android.widget.TextView" ). all ():
print ( "Text:" , elem . text )
# Dictionary eg:
# {'index': '1', 'text': '999+', 'resource-id': 'com.netease.cloudmusic:id/qb', 'package': 'com.netease.cloudmusic', 'content-desc': '', 'checkable': 'false', 'checked': 'false', 'clickable': 'false', 'enabled': 'true', 'focusable': 'false', 'focused': 'false','scrollable': 'false', 'long-clickable': 'false', 'password': 'false', 'selected': 'false', 'visible-to-user': 'true', 'bounds': '[661,1444][718,1478]'}
print ( "Attrib:" , elem . attrib )
# Coordinate eg: (100, 200)
print ( "Position:" , elem . center ())
XPath의 다른 일반적인 용도를 보려면 클릭하세요.
비디오 녹화(구식), 대신 scrcpy를 사용하세요
여기서는 휴대폰과 함께 제공되는 screenrecord 명령을 사용하지 않습니다. 휴대폰 사진을 얻어서 영상을 합성하는 방식이기 때문에 imageio, imageio-ffmpeg, numpy 등과 같은 다른 종속성을 설치해야 합니다. 종속성이 상대적으로 크기 때문에 미러 설치를 사용하는 것이 좋습니다. 다음 명령을 실행하면 됩니다.
pip3 install -U " uiautomator2[image] " -i https://pypi.doubanio.com/simple
사용방법
d.screenrecord('output.mp4')
time.sleep(10)
# or do something else
d.screenrecord.stop() # 停止录制后,output.mp4文件才能打开
녹화 시 fps(현재 20)를 지정할 수도 있습니다. 이 값은 미니캡 출력 사진의 속도보다 낮으므로 수정하지 않는 것이 좋습니다.
from uiautomator2 import enable_pretty_logging
enable_pretty_logging ()
또는
logger = logging.getLogger("uiautomator2")
# setup logger
Python 프로그램이 종료되면 UiAutomation이 종료됩니다. 그러나 인터페이스 메소드를 통해 서비스를 중지할 수도 있습니다.
d . stop_uiautomator ()
https://www.cnblogs.com/insist8089/p/6898181.html
기타 기여자
순위가 정해져 있습니다. 추가해 주세요.
MIT