مجموعة التواصل QQ: 815453846 الديسكورد: https://discord.gg/PbJhnZJKDd
لم أحافظ على هذا المشروع لفترة من الوقت (ربما عامين)، لكنني بحاجة مؤخرًا إلى إعادة البحث في الأتمتة الأصلية لنظام Android. بالطبع، قمت أيضًا بالتحقق من Appium، بعد مقارنته، وجدت أن سرعة تشغيل مشروع uiautomator2 إنه جيد حقًا، فهو سريع، بدءًا من اكتشاف العناصر وحتى النقر، ويتم كل ذلك بالمللي ثانية، كما أن الكود أسهل في الفهم. لم أكن أتوقع حقًا أنني كتبت مثل هذا المشروع السحري من قبل. كيف يمكن لمثل هذا المشروع الجيد أن يترك الغبار يتراكم؟ يجب إعادة تنظيمه وتنظيف بعض الأكواد غير المرغوب فيها. لذلك تمت ترقية إصدار المشروع من 2.xx إلى 3.xx
يمكن للمستخدمين الذين ما زالوا يستخدمون الإصدار 2.xx إلقاء نظرة على 2to3 قبل أن يقرروا ما إذا كانوا يريدون الترقية إلى 3.xx (أنا شخصيًا أوصي بشدة بالترقية)
بعد كل شيء، الإصدار 2 إلى 3 هو ترقية إصدار رئيسية، وقد تم حذف العديد من الوظائف. أول شيء يجب حذفه هو atx-agent، وثانيًا هناك مجموعة من الوظائف المرتبطة بـ atx-agent. الوظائف المهملة مثل init.
أرقام إصدارات المكتبات التابعة المختلفة
UiAutomator هي مكتبة Java مقدمة من Google لاختبار التشغيل الآلي لنظام Android، استنادًا إلى خدمة إمكانية الوصول. إنه قوي للغاية ويمكنه اختبار تطبيقات الطرف الثالث، والحصول على أي سمة تحكم لأي تطبيق على الشاشة، وإجراء أي عملية عليه، ولكن له عيبان: 1. يمكن لنص الاختبار استخدام لغة Java فقط 2. الاختبار البرنامج النصي يجب أن يتم تعبئته في حزمة jar أو apk وتحميله على الجهاز لتشغيله.
نأمل أن تتم كتابة منطق الاختبار بلغة Python ويمكن التحكم في الهاتف المحمول أثناء تشغيله على الكمبيوتر. أود أن أشكر Xiaocong He (@xiaocong) كثيرًا على تحقيق هذه الفكرة (انظر xiaocong/uiautomator). المبدأ هو تشغيل خدمة http rpc على الهاتف المحمول، وفتح الوظائف في uiautomator، ثم استخدام http. يتم تغليف الواجهة في مكتبة بايثون. لأن مكتبة xiaocong/uiautomator
لم يتم تحديثها لفترة طويلة. لذلك قمنا بتقسيم الإصدار مباشرةً لتسهيل التمييز، أضفنا 2 openatx/uiautomator2 في النهاية، وقمنا أيضًا بتشعب نسخة من الكود المصدري لحزمة Android المقابلة، openatx/android-uiautomator-server.
بالإضافة إلى إصلاح الأخطاء في المكتبة الأصلية، تمت أيضًا إضافة العديد من الميزات الجديدة. ويشمل بشكل رئيسي الأجزاء التالية:
اسمحوا لي أن أشرح هنا أولاً، لأن الكثير من الأشخاص يسألون غالبًا أن openatx/uiautomator2 لا يدعم اختبار iOS. إذا كنت بحاجة إلى اختبار iOS الآلي، فيمكنك الانتقال إلى هذه المكتبة openatx/facebook-wda.
ملاحظة: هذه المكتبة
https://github.com/NeteaseGame/ATXلم يعد تحت الصيانة، يرجى استبداله في أقرب وقت ممكن.
فيما يلي دليل مرجعي سريع مناسب لأولئك الذين بدأوا بالفعل، ونرحب بالتعليقات.
قم أولاً بإعداد هاتف Android واحد (وليس اثنين) مع تشغيل开发者选项
، وتوصيله بالكمبيوتر، وتأكد من أنه يمكنك رؤية الجهاز المتصل عن طريق تنفيذ 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 للحصول على تعليقات)
成都-测试只会一点点
تثبيت
الاتصال بجهاز
سطر الأوامر
الإعدادات العالمية
إدارة التطبيق
أتمتة واجهة المستخدم
المساهمين
رخصة
قم بتثبيت uiautomator2
pip install -U uiautomator2
اختبر ما إذا كان التثبيت ناجحًا uiautomator2 --help
مفتش واجهة المستخدم
pip install uiautodev
# 启动
uiauto.dev
افتح https://uiauto.dev في المتصفح لعرض بنية واجهة الجهاز الحالي.
uiauto.dev
uiauto.dev هو مشروع مستقل عن uiautomator2، يستخدم لعرض بنية الطبقة. إنها نسخة أعيد بناؤها من محرر المشروع القديم، وقد يتم تحصيلها لاحقًا (السعر بالتأكيد يستحق المال) لدعم الصيانة المستمرة للمشروع الحالي. إذا كنت مهتمًا، يمكنك الانضمام إلى المجموعة للمناقشة (بما في ذلك تقديم الطلبات) مجموعة QQ 536481989
استخدم الرقم التسلسلي لتوصيل الجهاز على سبيل المثال 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
الحالي: احصل على اسم الحزمة الحالية والنشاط
$ 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
عند إنهاء بايثون، يتم إنهاء خدمة 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
، وما إلى ذلك.
يعرض هذا الجزء كيفية تنفيذ إدارة التطبيق
نحن ندعم فقط تثبيت ملف APK من عنوان URL
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"
يعرض هذا الجزء كيفية تنفيذ عمليات الجهاز الشائعة:
قم بتشغيل أمر Shell قصير الأمد مع حماية المهلة (المهلة الافتراضية 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
يؤدي هذا إلى إرجاع سلسلة لـ stdout مدمجة مع stderr. إذا كان الأمر عبارة عن أمر حظر، فسيتم حظر shell
أيضًا حتى اكتمال الأمر أو بدء المهلة. لن يتم تلقي أي إخراج جزئي أثناء تنفيذ الأمر مناسب للأوامر طويلة الأمد. يعمل أمر shell المعطى في بيئة مماثلة لـ adb shell
، والتي لديها مستوى إذن Linux من adb
أو shell
(أعلى من إذن التطبيق).
تشغيل أمر 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، ويمكن الحصول على الإحداثيات النسبية لكل نقطة مسبقًا (النسبة المئوية مدعومة هنا) لمزيد من الاستخدام التفصيلي، يرجى الرجوع إلى هذا المنشور.
اللمس والسحب (تجريبي)
هذه الواجهة عبارة عن واجهة بدائية منخفضة المستوى نسبيًا، ولا تبدو مثالية، ولكنها قابلة للاستخدام. ملحوظة: هذا المكان لا يدعم النسب المئوية
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 )
تفريغ التسلسل الهرمي لواجهة المستخدم
# 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 ()
يعد المحدد آلية سهلة الاستخدام لتحديد كائن واجهة مستخدم معين في النافذة الحالية.
# Select the object with text 'Clock' and its className is 'android.widget.TextView'
d ( text = 'Clock' , className = 'android.widget.TextView' )
يدعم المحدد المعلمات أدناه، راجع مستند 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
هو العثور على العناصر الفرعية التي تحتوي على عنصر واجهة مستخدم فرعي في أي مكان ضمن التسلسل الهرمي الفرعي الموجود في المثيل المحدد، ويتم تنفيذه على طرق العرض المرئية دون التمرير .
انظر الروابط أدناه للحصول على معلومات مفصلة:
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"، نحتاج إلى تحديد أدوات التبديل أولاً. ومع ذلك، وفقًا للتسلسل الهرمي لواجهة المستخدم، لن يكون هناك أكثر من أداة تبديل واحدة ولها نفس الخصائص تقريبًا وبدلاً من ذلك، فإن استراتيجية الاختيار أدناه ستساعد:
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)
، يحدد B على الجانب الأيسر من A.d(A).right(B)
، يحدد B على الجانب الأيمن من A.d(A).up(B)
، يحدد B فوق A.d(A).down(B)
، يحدد B ضمن A.لذلك بالنسبة للحالات المذكورة أعلاه، يمكننا بدلاً من ذلك تحديدها باستخدام:
## 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 واجهة برمجة تطبيقات تشبه القائمة (مماثلة لـ 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 # ...
ملاحظات : عند استخدام المحددات في مقطع تعليمات برمجية يمر عبر قائمة النتائج، يجب عليك التأكد من أن عناصر واجهة المستخدم الموجودة على الشاشة تظل دون تغيير، وإلا فإنه قد يحدث خطأ لم يتم العثور على العنصر عند التكرار عبر القائمة.
تحقق من وجود كائن واجهة المستخدم المحدد
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)
استرجع معلومات كائن واجهة المستخدم المحدد
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
قم بإجراء نقرة طويلة على كائن واجهة المستخدم المحدد
# long click on the center of the specific UI object
d ( text = "Settings" ). long_click ()
اسحب كائن واجهة المستخدم نحو نقطة أخرى أو كائن واجهة مستخدم آخر
# 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 )
اسحب من وسط كائن واجهة المستخدم إلى حافته
انتقاد يدعم 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 )
لفتة من نقطتين من نقطة إلى أخرى
d ( text = "Settings" ). gesture (( sx1 , sy1 ), ( sx2 , sy2 ), ( ex1 , ey1 ), ( ex2 , ey2 ))
إيماءة من نقطتين على كائن واجهة المستخدم المحدد
يدعم اثنين من الإيماءات:
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 ()
انتظر حتى تظهر واجهة المستخدم المحددة أو تختفي
# 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 ثانية، راجع الإعدادات العامة لمزيد من التفاصيل
إجراء عملية قذف على كائن واجهة المستخدم المحدد (قابل للتمرير)
الخصائص المحتملة:
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 ()
قم بإجراء التمرير على كائن واجهة المستخدم المحدد (قابل للتمرير)
الخصائص المحتملة:
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 الحالي باستخدام الترابط ويتم فحصه كل ثانيتين. حاليًا، توجد عملية تشغيل واحدة فقط: النقر.
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، تم استخدام طريقة [Watcher]((http://developer.android.com/tools/help/uiautomator/UiWatcher.html) المتوفرة في مكتبة uiautomator-jar، ولكن من الناحية العملية وجد أنه بمجرد فشل اتصال 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
غير مرئية في الخلفية. تم توقيع هذين التطبيقين بنفس الشهادة. التطبيق غير المرئي هو في الواقع حزمة اختبار تحتوي على جميع رموز الاختبار، ويتم تشغيل خدمة الاختبار الأساسية من خلالها أيضًا. ولكن عند التشغيل، يحتاج النظام إلى تشغيل السيارة الصفراء الصغيرة طوال الوقت (يمكن أيضًا تشغيلها في الخلفية). بمجرد إيقاف تطبيق السيارة الصفراء الصغيرة، سيتم أيضًا إيقاف خدمة الاختبار التي تعمل في الخلفية بسرعة. حتى إذا لم تفعل شيئًا، فسيتم إعادة تدوير التطبيق بسرعة بواسطة النظام إذا كان في الخلفية. (آمل أن يقدم لي الخبراء بعض الإرشادات هنا حول كيفية تجنب الاعتماد على التطبيقات المزيفة. أعتقد أن ذلك ممكن من الناحية النظرية، لكنني لا أعرف كيفية القيام بذلك بعد).
هناك طريقتان للسماح للسيارة الصفراء الصغيرة بالعمل في الخلفية، إحداهما هي تشغيل التطبيق ووضعه في الخلفية (افتراضي). بالإضافة إلى ذلك، يمكنك أيضًا بدء خدمة الخلفية من خلال am startservice
.
يمكن تعديل هذا السلوك عبر d.settings["uiautomator_runtest_app_background"] = True
. صحيح يعني بدء التطبيق، وخطأ يعني بدء الخدمة.
إعدادات المهلة في 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 افتراضيًا، لذا فهذه وظيفة موسعة. السرعة ليست بهذه السرعة.
على سبيل المثال: محتويات إحدى العقد
" style=";text-align:right;direction:rtl">< 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文件才能打开
يمكنك أيضًا تحديد إطار في الثانية (حاليًا 20) عند التسجيل، وهذه القيمة أقل من سرعة إخراج الصور المصغرة، ولا أنصحك بتعديلها.
from uiautomator2 import enable_pretty_logging
enable_pretty_logging ()
أو
logger = logging.getLogger("uiautomator2")
# setup logger
عند خروج برنامج بايثون، يخرج UiAutomation. ومع ذلك، يمكنك أيضًا إيقاف الخدمة من خلال طريقة الواجهة.
d . stop_uiautomator ()
https://www.cnblogs.com/insist8089/p/6898181.html
المساهمين الآخرين
الترتيب في محله، مرحبا بكم في الإضافة
معهد ماساتشوستس للتكنولوجيا