install adb
After installing adb, use the command adb devices
to display the following figure;
Install python dependencies (uiautomator2,weditor)
pip install uiautomator2==2.16.23 weditor==0.6.8 -i https://pypi.doubanio.com/simple
# 在手机上安装 atx-agent 应用
# 安装apk服务到手机上
python -m uiautomator2 init
Solve the error UnicodeDecodeError: 'gbk' codec can't decode byte 0xad in position 825 when installing weditor
The script is as follows (can be copied and run directly)
import os
# https://pypi.douban.com/simple # 豆瓣镜像
# https://pypi.tuna.tsinghua.edu.cn/simple # 清华镜像
mirror = " -i https://pypi.douban.com/simple"
os.system("python -m pip install --upgrade pip" + mirror) # 更新 pip
os.system("pip install --pre -U uiautomator2" + mirror) # 安装 uiautomator2
os.system("pip install --pre weditor" + mirror) # 安装 weditor
os.system("python -m uiautomator2 init") #安装 atx-agent 至手机
run demo
import uiautomator2 as ui
import os
from loguru import logger
ui.DEBUG = False
class Connet(object):
'''连接设备'''
def __init__(self):
self.devices_name_list: list = [] # 所有设备名称
self.device: list = [] # 所有设备连接对象(每一个元素都是连接对象)
os.environ['PATH'] += ';' + os.path.join(os.getcwd(), 'ADB') # 设置adb为环境变量
def _get_devices_name(self) -> None:
'''提取设备名称'''
devices_all = []
for i in os.popen('adb devices').read().split('\n'):
if i not in ('List of devices attached', ''):
devices_all.append(i.split()[0])
if len(devices_all) < 1:
logger.error('检测不到设备')
self.devices_name_list = devices_all
def _connect(self):
'''连接设备并提取设备对象至变量'''
for device_name in self.devices_name_list:
logger.warning(f'设备名称:【{device_name}】')
self.device.append(ui.connect(device_name))
logger.success(f'【{device_name}】...连接成功')
def _start_vx(self):
for device in self.device:
try:
device(scrollable=True).scroll.toEnd() # 滑动至屏幕最底部
except ui.exceptions.UiObjectNotFoundError as e:
...
device(text='微信').click() # 点击微信
# qujianma_assembly = device(resourceId="com.landicorp.jd.delivery:id/etPickUpCode")
# if not qujianma_assembly.exists:
# logger.error(f'找不到取件码组件,请切换至取件码页面')
# exit()
def start(self, weishu):
self._get_devices_name()
self._connect()
self._start_vx()
c = Connet()
c.start()
Detailed explanation of API methods
1.1 Device connection Device
connection is mainly divided into wired connection and wireless connection, as follows:
# 有线连接
d = u2.connect_usb(id) # id 为 adb devices 命令中得到的设备 id
# 无线连接
d = u2.connect(ip) # ip 为 手机 ip
Description: The returned d is the connection handle, and the operation on the mobile phone can be realized through d.
method name | analyze |
---|---|
d.app_install(‘http://domain.com/xxx.apk’) | Install the application 【Note】It can only be installed from the URL |
d.app_start(“app_package_name”, stop=False) | Opening the application (here is the package name) can also be used d(text='支付宝').click() ; the stop parameter is whether to cold start, the default is False |
d.app_stop(“app_package_name”) | close application |
d.app_stop_all() | close all apps |
d.implicitly_wait(20) | Implicitly wait for 20s to ensure that the control is loaded (can be 全局 set) |
d.app_uninstall(‘package_name’) | uninstall |
d.app_current() | Get the package name of the currently running app |
d.app_info() | Get app information |
d.app_list_running() | List the running apps, this can also get the package name |
d.app_clear(‘paceage_name’) | clear app data |
d.info() | Get basic device information |
d.device_info() | Get device details |
d.window_size() | Get device size |
d.screenshot(‘d:/hello.png’) | Get the screenshot of the device and transfer it to the computer storage path |
d.push(‘d:/hello.png’,‘/data/’) | Push file (upload file) The first parameter PC needs to upload the file path The second parameter is the storage path of the mobile terminal |
d.pull(‘/data/hello.png’,‘d:/desktop/’) | Pulling files is the opposite of pushing file parameters |
d.click_post_delay = 1.5 | 全局 Set a delay of 1.5 seconds between clicking again after each UI click默认无延迟 |
---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
d(resourceId=element ID).click() | ID positioning |
d(text="Official Account: AirPython").click() | Text text positioning |
d(description=“AirPython”).click() | Control Description Positioning |
d(className=“android.widget.TextView”).click() | The class to which the control belongs |
d.xpath(“//*[@content-desc=‘AirPython’]”) | Xpath positioning |
d(className=“android.widget.ListView”, resourceId=元素ID) | Combined positioning |
d(text=‘hello’, className=‘android.widget.TextView’) | Select the element whose text is 'hello' and className is 'android.widget.TextView' |
d(text=“WiFi”).right(className=“android.widget.Switch”).click() | 同d(text="WiFi").right().click() Select "switch" left, right, top, bottom on the right side of "WiFi" |
d(text=“hello”, instance=0) | Get the element object with "hello" in the first text同device(text="hello")[0] |
------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
d.wait_timeout = 10 | Set a global timeout of 10s只要设置了全局的超时时间,则其他的操作也会内置智能等待,不需要再进行任何操作 |
d.app_start(‘packagename’,wait=True) | Turn on smart waiting, that is, the following code will be executed after the app is fully opened |
d.wait_activity() | wait for the page to load |
d().wait() | wait for element to appear |
d().wait_gone() | wait for element to disappear |
d().exists() | Wait for element to exist |
--------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
d.click() | click control支持xy坐标参数 |
d.double_click() | Double click on the control支持xy坐标参数 |
d.long_click(duration=0.5) | long press control支持xy坐标参数 |
d.set_text(text) | enter text |
d.send_keys(text) | enter text广播式输入 |
d.set_fastinput_ime(True) | open close input method为True打开 否则关闭 |
d.clear_text() | clear textd[index].clear_text() 定位的控件有多个,通过 index 指定某一个 |
d.get_text() | get text定位的控件只有一个,等价于 element[0].get_text() |
--------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
device.screen_on() | bright screen |
device.screen_off() | screen off |
device.press(“home”) | front page |
device.press(“back”) | return key |
device.press(“left”) | move left |
device.press(“right”) | move right |
device.press(“up”) | Swipe up |
device.press(“down”) | underline |
device.press(“center”) | back to middle page |
device.press(“menu”) | menu |
device.press(“search”) | search bar |
device.press(“enter”) | Enter key |
device.press(“delete”) | delete key |
device.press(“recent”) | open recent page |
device.press(“camera”) | open camera |
device.press(“power”) | Power button |
device.press(“volume_up”) | Turn up the volume |
device.press(“volume_down”) | turn down the volume |
device.press(“volume_mute”) | Mute |
d.orientation | Get the screen orientation, the value is {"natural", "left", "right", "upsidedown"} |
d.freeze_rotation() | lock screen orientation |
d.freeze_rotation(True) | Unlock screen orientation |
d.set_orientation(“left”) | turn left to landscaped.set_orientation("right") # 向右转为横屏 |
device.screenshot(“screenshot.png”)) | Screen capture, support custom path |
d.screenrecord.start(“screenrecord.mp4”) | Screen recording needs to be from cv2d.screenrecord.stop() 停止录屏 |
device.open_notification() | open notification bar |
slide operation
device(scrollable=True).fling(steps=5) 飞滑5次,默认为1 手势向上,页面向下(正常翻页)
device(scrollable=True).fling.horiz.toBeginning() 横滑 手势向右,页面向左
device(scrollable=True).fling.toEnd() 飞滑到页面最底部
device(scrollable=True).scroll(steps=100) 滑动滚动条 距离为100
device(scrollable=True).scroll.horiz.forward(steps=100) 手势向左 页面向右
device(scrollable=True).scroll.horiz.toBeginning(steps=100, max_swipes=1000) 手势向右 页面向左
device(scrollable=True).scroll.toEnd() 滑动到末尾
device(scrollable=True).scroll.to(text="Security") scroll 向前垂直,直到出现指定ui object
device.swipe(500,500,100,500)
"""
swipe 平滑
第一种方式:需要传入四个参数
startX:起始X坐标
startY:起始Y坐标
endX:结束X坐标
endY:结束Y坐标
"""
"""
第二种方式:需要传入两个参数
direction(滑动的方向) : up、down、left、right
scale(滑动的百分比)
"""
device.swipe_ext('left',scale=0.9)
"""
第三种方式:先进行元素定位 再滑动
direction(滑动的方向) : up、down、left、right
steps(滑动的速度) ms
d.swipe(sx, sy, ex, ey, duration=0.5)
"""
e = device(text='appname')
e.swipe('left',steps=100)
Drag and drop
d.drag(sx, sy, ex, ey, duration=0.5)
appendix
#设置每次点击UI后再次单击之间延迟1.5秒
d.click_post_delay = 1.5
#设置默认元素等待超时(秒)
d.wait_timeout = 20
d.info
#得出设备链接信息
print(d.window_size())
#获取屏幕大小
print(d.current_app())
#获取当前应用的信息
print(d.serial)
#获取设备序列号
print(d.wlan_ip)
#获取WIFI IP
print(d.device_info)
#获取详细的设备信息
#打开/关闭屏幕
d.screen_on()
#开启屏幕
d.screen_off()
#关闭屏幕
d.info.get("screen")
#获取屏幕开/关状态
#android>=4.4
d.press("home")
#按下home键
d.press("back")
#按下back键
d.press(0*07,0*02)
#按下编码
'''支持按键模式'''
home
#主页按钮
back
#返回
left
#左
right
#右
up
#上
down
#下
center
#回车
menu
#菜单
search
#搜索
enter
#输入
delete ( or del)
#删除
recent (recent apps)
#打开最近项目
volume_up
#音量+
volume_down
#音量—
volume_mute
#静音
camera
#相机
power
#电源键
d.unlock()
#解锁屏幕
d.click(X,Y)
#点击屏幕坐标
d.long_click(x,y)
#长按屏幕
d.long_click(x,y,1)
#长按屏幕1s,默认是0.5,可自行配置
d.swipe(sx, sy, ex, ey)
#根据坐标滑动
d.swipe(sx, sy, ex, ey,1)
#根据坐标滑动,1代表滑动速度,默认0.5
d.drag(sx, sy, ex, ey)
#根据坐标拖动,适用于结算和滑块处理
d.drag(sx, sy, ex, ey, 1)
#根据坐标拖动,拖动时长1s,默认0.5
#截图
d.screenshot('1.jpg')
#截图保存在本地,文件名为1.jpg
#想获取其他格式的需要安装 pillow、numpy和cv2等库,具体不累述
d.open_notification()
#打开通知
d.open_quick_settings()
#打开快速设置
d.freeze_rotation()
# 冻结旋转
d.freeze_rotation(False)
# 开启旋转
'''检查特定的UI对象是否存在'''
d(text="Settings").exists
# 返回布尔值,如果存在则为True,否则为False
d.exists(text="Settings")
# 另一种写法
# 高级用法
d(text="Settings").exists(timeout=3)
# 等待'Settings'在3秒钟出现
d(text="Settings").info
# 获取特定UI对象的信息
'''获取/设置/清除可编辑字段的文本(例如EditText小部件)'''
d(text="Settings").get_text()
#得到文本小部件
d(text="Settings").set_text("My text...")
#设置文本
d(text="Settings").clear_text()
#清除文本
d(text="Settings").center()
# 获取Widget中心点
#d(text="Settings").center(offset=(0, 0)) # 基准位置左前
d.push('1.txt','sdcard/downloacd')
#推送到文件下
d.push('1.txt','sdcard/downloacd/2.txt')
#推送并重命名到文件夹下
with open("foo.txt", 'rb') as f:
d.push(f, "/sdcard/")
#push fileobj
d.push("1.sh", "/data/local/tmp/", mode=0o755)
#推送并修改文件模式,在Python中表示八进制的友好方法默认0o755,文件权限设置
d.pull("/sdcard/1.txt", "1.txt")
#从设备侧拉取文件
'''定位方法'''
#text定位单击
d(text="Settings").click()
d(text="Settings", className="android.widget.TextView").click()
#resourceId定位单击
d(resourceId="com.ruguoapp.jike:id/tv_title", className="android.widget.TextView").click()
#description定位单击
d(description="确定").click()
d(description="确定", className="android.widget.TextView").click()
#className定位单击
d(className="android.widget.TextView").click()
#xpath定位单击
d.xpath("//android.widget.FrameLayout[@index='0']/android.widget.LinearLayout[@index='0']").click()
#坐标单击
d.click(182, 1264)
'''常用方法'''
# 等待10s
d.xpath("//android.widget.TextView").wait(10.0)
# 找到并单击
d.xpath("//*[@content-desc='分享']").click()
# 检查是否存在
if d.xpath("//android.widget.TextView[contains(@text, 'Se')]").exists:
print("exists")
# 获取所有文本视图文本、属性和中心点
for elem in d.xpath("//android.widget.TextView").all():
print("Text:", elem.text)
#获取视图文本
for elem in d.xpath("//android.widget.TextView").all():
print("Attrib:", elem.attrib)
#获取属性和中心点
#返回: (100, 200)
for elem in d.xpath("//android.widget.TextView").all():
print("Position:", elem.center())
'''xpath常见用法'''
# 所有元素
//*
# resource-id包含login字符
//*[contains(@resource-id, 'login')]
# 按钮包含账号或帐号
//android.widget.Button[contains(@text, '账号') or contains(@text, '帐号')]
# 所有ImageView中的第二个
(//android.widget.ImageView)[2]
# 所有ImageView中的最后一个
(//android.widget.ImageView)[last()]
# className包含ImageView
//*[contains(name(), "ImageView")]
# 等待10s
d.xpath("//android.widget.TextView").wait(10.0)
# 找到并单击
d.xpath("//*[@content-desc='分享']").click()
# 检查是否存在
if d.xpath("//android.widget.TextView[contains(@text, 'Se')]").exists:
print("exists")
# 获取所有文本视图文本、属性和中心点
for elem in d.xpath("//android.widget.TextView").all():
print("Text:", elem.text)
#获取视图文本
for elem in d.xpath("//android.widget.TextView").all():
print("Attrib:", elem.attrib)
#获取属性和中心点
#返回: (100, 200)
for elem in d.xpath("//android.widget.TextView").all():
print("Position:", elem.center())