مجموعة التواصل 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>
، 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" )
إخوة