Skip to content

二、常见脚本逻辑与代码示例

1. 代码示例

1)Airtest脚本

Airtest脚本的示例代码,可以在 这里 找到。其中的 test_blackjack.air 为一个正常的.air脚本,大家可以下载之后在AirtestIDE中打开它查看。同样地,在 pure_python_example 中存放的是普通的 .py 脚本(内容与 .air 脚本中的同名 .py 文件相同),为了说明一个Airtest脚本其实也是一个普通的 .py 文件,只是在其中引入了 Airtest 这一python第三方库。

2)Poco脚本

Poco的示例demo与代码,请在 这里 下载,安装到手机后可以尝试查看UI树以及熟悉API用法。

我们在Github上也有一个 Example 仓库,里面放置了一些在本教程中出现过的代码示例,大家也可以参考。

2. 连接各种设备

1)命令行添加设备参数

根据 运行脚本 的方式不同,连接手机的方式也略有区别,例如当使用 airtest run test.air 这种方式运行脚本时,只需要根据文档内容的范例,在命令行里加上 --device 设备连接字符串 即可。关于设备字符串如何填写,请查阅 文档 ,此处是简单示例:

>airtest run untitled.air --device Android:///手机设备号 --log log/
>python -m airtest run untitled.air --device Android:///手机设备号 --log log/

2)脚本连接设备

如果是在 .py 脚本文件中 import airtest/poco ,希望以普通python脚本的方式来运行的话,可以在脚本代码中使用 connect_device 接口:

from airtest.core.api import *
connect_device("Android:///")  # 同样是设备字符串

更多 连接设备的接口不同平台设备字符串的编写 详情,我们可以参考 Airtest连接设备的脚本介绍 小结的内容。(在脚本中添加连接设备的语句同样适用于.air脚本)

3)请勿重复进行连接设备的工作

上述两种连接方式,命令行添加设备参数/脚本添加设备连接语句,我们只需要 选择其中一种 即可,如果都使用了,很可能造成重复连接等其他问题。

如果设备字符串 Android:/// 不太会编写的话,可以直接先用AirtestIDE连上一次设备后,复制IDE里运行脚本时打印出来的 --device Android:/// 的字符串内容使用,这样可以简单方便地连上设备。

3. 输入文字

1)Airtest中的文字输入接口text

在脚本中,如果想要实现文字输入,一般需要这样的流程:

  • 点一下你需要输入的位置,激活输入光标

  • 调用airtest的 text() 接口来输入内容

image

如图,在这个脚本里 先点击了需要输入的位置 ,然后调用了 text 接口进行输入,在运行时手机将会自动被安装一个名为 yosemite.apk 的应用,然后启用 yosemite输入法来进行输入。

而且需要注意的是,在使用过 text 接口后,手机输入法会被切换为yosemite输入法,因此看不到正常键盘了(无需惊慌)。如果需要手工输入,可以在系统的输入法设置中,把输入法切换回系统输入法即可恢复。同时我们也提供了 安卓手机助手功能 ,在手机助手中可以简单地点击鼠标来切换输入法。

2)输入失败的处理

如果 text() 接口输入失败,请查看是否手机阻止了该apk的安装及运行,或是部分手机的兼容性问题导致的,具体可以查阅 Android连接FAQ ,以及部分型号的手机不允许输入密码时调用第三方输入法,请查阅FAQ文档对手机设置进行修改。

同时可以尝试将 yosemite输入法 设置为手机默认输入法,然后再进行 text() 接口的调用。

部分型号手机在使用 text() 接口 输入密码 时,会输入失败,这是因为手机设置中的 语言与输入法-安全输入 没有打开,打开该选项后就可以使用非系统自带的输入法来输入密码了。

有部分特殊型号的手机,可能在使用Yosemite输入法时容易失败,无法输入文字(OPPO与Vivo品牌更容易出现),假如没有输入中文的需求,可以尝试使用 adb shell input 指令来进行文字输入:

shell("input text 'hello world'")

同时,上面这种 adb shell input 可以直接设置为默认的输入方式,替换原先的Yosemite输入,例如这样在python代码中这样初始化手机:

from airtest.core.api import *
# 相当于命令行中使用 --device Android:///?ime_method=ADBIME 连接手机
init_device("Android", ime_method="ADBIME")
text("hello")

部分模拟器(例如夜神模拟器)在输入时可能无法成功,可以尝试确认设置中是否打开了 硬件-物理键盘,尝试关闭这个选项,并设置默认输入法为yosemite输入法后再次重试即可。具体设置方式请参考 部分模拟器相关问题

3)输入完毕后的回车与搜索键

text 接口有一个默认参数 enter=True,会在输入完毕后自动按一下回车键(相当于 keyevent("ENTER")),假如不需要,请传入 enter=False

text("test", enter=False)

部分输入框,需要在输入内容后,点击输入键盘上的 搜索 按钮才能够激活搜索操作,可以传入search=True 参数:

text("test", search=True)

在这里,回车键的 keyevent("ENTER") 与输入法中的搜索、换行等操作是不同的。输入法中显示的额外按键是 EDITOR CODE,刚才这个代码中的 search=True 实际上是传入了 editor code 3 。因为搜索键是最常用的按键,因此我们将它封装进了 text 接口中,如果有点击除了 搜索 以外其他按钮的需求,需要查阅文档 Editor Action Code 来获取代码(如果该网页访问不了的话,可以自行搜索关键词 IME_ACTION_SEARCH ),然后手动传入代码进行点击:

dev = device()
# 按一下输入法的Go按钮 IME_ACTION_GO,对应键值为2,同理,输入法的搜索键实际上对应值为3
dev.yosemite_ime.code("2")
# 上面代码等价于下面这个shell调用
# shell("am broadcast -a ADB_EDITOR_CODE --ei code 2")

更多代码,可以查询 Editor Action Code 来获取,如果该网页访问不了的话,可以自行搜索关键词 IME_ACTION_SEARCH

4)Poco的set_text

除了Airtest的 text 接口之外,我们在poco中也提供了一个 set_text 接口,这个接口无需调用输入法,可以直接设置文字。但是指定的控件必须是一个可输入的控件,例如在Android中,是一个 EditText 类型的widget:

poco("com.android.mms:id/recipients_editor").set_text("test")

我们建议各位如果项目已经接入了poco,可以多尝试使用poco的set_text来设置文字内容,如果无法输入(部分机型、部分输入框有可能不支持 set_text 接口),再尝试用Airtest的 text 接口。

4. 删除文字

1)Android平台的删除操作

# 利用按键码进行逐字删除
for i in range(10):
    keyevent("67") # 等同于keyevent("KEYCODE_DEL")

# 利用Poco的set_text置空进行删除
poco("com.netease.cloudmusic:id/search_src_text").set_text(" ")

2)iOS平台的删除操作

# 逐字删除
for i in range(10):
    text("\b",enter=False)

3)Windows平台的删除操作

# 逐字删除
for i in range(10):
    keyevent("{BACK}")

5. 录屏

录屏

录屏功能仅适用于安卓平台,并且部分安卓模拟器可能也不支持录屏操作。

1)命令行添加录屏参数

在命令行使用 airtest run 运行脚本时,我们可以通过添加 --recording 参数来录制脚本运行的视频。

airtest run "D:\test\Airtest_example.air"  --device android:/// --log "D:/test\41f68fdf265d8c13998d0a1a7b992889" --recording

并且airtest1.1.6支持 --recording 参数后面加上一个文件名来命名录屏文件 ,例如 --recording test.mp4

airtest run "D:\test\untitled.air"  --device android:/// --log "D:/test\6fe87b11ca1fc75ebe670439f20fabfc" --recording 123.mp4

2)脚本添加录屏语句

除了在命令行使用 --recording 参数来录制脚本运行的视频之外,我们还可以直接在脚本中调用开始录屏和结束录屏的方法来帮助我们录制脚本运行过程的视频。使用的方法分别是 start_recording()stop_recording()

# -*- encoding=utf8 -*-
__author__ = "AirtestProject"

from airtest.core.api import *
from airtest.core.android.recorder import *
from airtest.core.android.adb import *

auto_setup(__file__,devices=["android://127.0.0.1:5037/emulator-5554"])

adb = ADB(serialno="emulator-5554")
recorder = Recorder(adb)

# 开启录屏
recorder.start_recording(max_time=10)

touch(Template(r"tpl1603091574169.png", record_pos=(0.113, -0.302), resolution=(900, 1600)))
sleep(3.0)

# 结束录屏
recorder.stop_recording(output="test.mp4")

6. keyevent按键码

1)Android设备的按键码

在脚本中,有时需要输入一些指定的按键,例如点一下HOME键、BACK键等,如果设备是Android设备,可以参考谷歌的 Android按键码 (国内用户如果打不开此链接,可直接以关键词:Android keyevent 搜索)。

keyevent("HOME")
keyevent("BACK")

keyevent("KEYCODE_DEL") # 等同于keyevent("67")

注意参数为字符串!!!

2)Windows平台的按键码

在Windows系统中,请参考 pywinauto 这个库提供的 Windows按键码

keyevent("{DEL}")
keyevent("{BACKSPACE}")

3)iOS设备的按键码

iOS设备现在暂时 只支持 HOME 按键的 keyevent

keyevent("HOME")

7. Windows平台下输入空格

# 中文输入
text("薛之谦",enter=False)
keyevent("{VK_SPACE}") # 空格
text("天外来物")
sleep(1.0)

# 纯英文输入
text("Love{VK_SPACE}Story")

8. 各种常用的pip命令

① 安装Airtest库: pip install airtest

② 安装poco库: pip install pocoui

③ 更新Airtest: pip install -U airtest

④ 更新Poco: pip install -U pocoui

⑤ 卸载Airtest库: pip uninstall airtest

特别注意:Poco依赖库是 pocoui 而不是 poco,如果你发现你的环境里面同时存在 pocopocoui ,请务必把 poco 卸载了,留下 pocoui即可。

另外,如果你的电脑同时安装了 python3 和 python2 ,在不同python环境里面使用pip命令时可以使用如下方法:

# Python2
pip2 install XXX
python2 -m pip install XXX
# Python3
pip3 install XXX
python3 -m pip install XXX

⑥ 查看python环境安装的库:pip list

⑦ 安装指定版本的库:pip install -U airtest==1.2.3

9. 自定义截图压缩精度

当Airtest版本≥1.1.2时,我们可以自定义截图精度:

1)命令行添加compress参数

# quality取值[1,99],airtest默认取10,希望获得更高精度可以取值75
airtest run xxx --compress quality

2)脚本中定义quality

自定义全局的截图压缩精度:

import airtest.core.api import *
ST.SNAPSHOT_QUALITY = xxx  

自定义单张截图的压缩精度:

snapshot(quality=my_quality)

10. 怎么输入随机数字

首先我们要利用python的 random 函数创建出符合要求的随机数;比如想要随机输入20-100的1个整数型随机数:

import random
r = random.randint(20,100)

然后使用 text 接口完成随机数的输入,但要注意的是,text 接口传入的是一个字符串类型,所以把随机数传入 text 接口之前,要把刚才创建的随机数转化为字符串类型:

text(str(random.randint(20,100)))

这样我们就可以完成输入随机数字的需求啦。

11. 如何取消脚本执行过程刷新的大量log信息

在脚本运行的时候, Airtest 默认会刷新很多log信息,如下图所示:

image

如果你不想这些log信息干扰你提取有效的报错信息时,你可以在脚本代码开头加上 log 级别的设定:

# -*- encoding=utf8 -*-
__author__ = "user"
import logging
logger = logging.getLogger("airtest")
logger.setLevel(logging.ERROR)
from airtest.core.api import *
auto_setup(__file__)

这样运行时只会在初始化手机时会有少量 log 输出,初始化完毕后就能够对 logger 进行过滤了。

12. 如何模拟鼠标右键

pywinauto.mouse 这个模块给我们提供了各种模拟鼠标操作的方法,例如大家常问的右键操作,就可以使用这个模块的 right_click() 方法:

首先我们需要获取当前连接的窗口,然后再调用 right_click() 方法实施鼠标右键点击操作:

from airtest.core.api import *
auto_setup(__file__)
# 获取当前连接的窗口
dev = device()
# 拿到鼠标,并模拟鼠标的右键点击操作
dev.mouse.right_click(coords=(1920,100))

更多模拟鼠标或者键盘的操作,可以参考我们往期的推文: “如何用python模拟鼠标和键盘的操作”

13. 局部截图/区域截图

局部截图或者说按坐标截图是大家经常会问到的问题,Airtest提供了 crop_image(img, rect) 方法可以帮助我们实现局部截图:

image

举个例子,我们想要截取手机屏幕中被红框圈中位置的截图:

image

我们可以这么实现:

# -*- encoding=utf8 -*-
__author__ = "AirtestProject"

from airtest.core.api import *
# crop_image()方法在airtest.aircv中,需要引入
from airtest.aircv import *

auto_setup(__file__)
screen = G.DEVICE.snapshot()

# 局部截图
screen = aircv.crop_image(screen,(0,160,1067,551))
# 保存局部截图到log文件夹中
try_log_screen(screen)

结果如图:

image

14. 局部找图

screen = G.DEVICE.snapshot()
# 局部截图
local_screen = aircv.crop_image(screen,(0,949,1067,1500))

# 将我们的目标截图设置为一个Template对象
tempalte = Template(r"png_code/设置.png")
# 在局部截图里面查找指定的图片对象
pos = tempalte.match_in(local_screen)

详细教程和实例可以查看官方公众号的教程推文:使用Airtest最常遇到的几个需求,都帮你们实现好了

15. 批量运行/多任务运行/多机运行

16. 判断元素/截图存在

  • airtest的,判断截图目标是否存在于当前画面上:exists(截图)
  • poco的,判断元素是否存在:poco("xxx").exists()

17. 等待元素/截图出现

  • airtest的,等待某个截图目标出现在当前画面上:wait(截图)
  • poco的,等待某个控件元素出现:poco("xxx").wait_for_appearance()

18. 断言元素/图片存在

  • 断言某个截图目标是否存在于当前画面上:assert_exists(截图)
  • 断言某个poco元素是否存在:assert_equal(poco(xxx).exists(),True,"xxx控件存在")

19. 如何引用别的.air脚本中封装好的方法

如果想要在一个 .air 脚本中,调用另外一个 .air 脚本里封装的公用函数,可以这样做:

from airtest.core.api import using
# 相对路径或绝对路径,确保代码能够找得到即可
using("common.air")

from common import common_function
common_function()

如果需要引用的子脚本路径统一都放在某个目录下,可以通过设定一个默认项目根目录 PROJECT_ROOT ,让使用 using 接口时能够在当前根目录下寻找别的子脚本,无需填写完整路径,让脚本之间相互调用使用更加方便。

例如,我们建立一个名为 test1.air 的脚本,实际路径为 /User/test/project/test1.air :

from airtest.core.api import *

def test():
    touch("tmp.png")

在同一目录下另外一个 main.air 脚本可以这样引用到它里面的 test :

from airtest.core.api import *

ST.PROJECT_ROOT = "/User/test/project"
using("test1.air")
from test1 import test

如使用 using 调用之后,发现程序仍是找不到脚本,建议检查 using 接口里面的脚本路径是否有误,另外还可以尝试在脚本开头把待引入的脚本路径添加到 sys.path 中:

#将test1.air的路径添加到sys.path里面
sys.path.append(r"D:\test\user\project\test1.air")

20. Airtest的多图查找

Airtest没有提供专门的API给我们进行多图查找,但是我们可以用简单的语法来实现:

picList = [pic1,pic2,pic3]  # 截图的图片对象列表
for pic in picList:
     pos = exists(pic)
     if pos:
         touch(pos)
         break  # 只要找到图片列表中的任何一张图片,就执行touch

21. poco的多元素查找

poco有专门的API实现了多元素等待:

# 等待多元素其中的任意一个元素出现
bomb = poco("bomb")
yellow = poco("yellow")
blue = poco("blue")

while True:
    fish = poco.wait_for_any([bomb,yellow,blue])
    print(fish.get_name())

# 等待多元素的所有元素都出现
poco.wait_for_all([yellow,blue,black])

22. 绝对坐标与相对坐标互相转换的代码示例

用代码实现绝对坐标和相对坐标之间的切换,我们需要先 获取当前设备的屏幕分辨率 :

# 获取设备屏幕分辨率(竖屏)
height = G.DEVICE.display_info['height']
width = G.DEVICE.display_info['width']

# 已知绝对坐标[311,1065],转换成相对坐标
x1 = 311/width
y1 = 1065/height
poco.click([x1,y1])

# 已知相对坐标[0.3,0.55],转换成绝对坐标
x2 = 0.3*width
y2 = 0.55*height
touch([x2,y2])

# 如果是横屏设备的话,则分辨率如下
height = G.DEVICE.display_info['width']
width = G.DEVICE.display_info['height']

我们还可以用下述方式,判断当前屏幕为横屏还是竖屏,并获取当前屏幕的分辨率:

if G.DEVICE.display_info['orientation'] in [1,3]:
    height = G.DEVICE.display_info['width']
    width = G.DEVICE.display_info['height']
else:
    height = G.DEVICE.display_info['height']
    width = G.DEVICE.display_info['width']

23. 如何安排脚本初始化、poco初始化和启动游戏应用的顺序

很多新手在使用poco的时候,都容易把连接设备、初始化poco和打开游戏应用的脚本顺序搞乱,导致出现一大堆奇妙的报错。

比如大家最常见的:“远程主机强迫关闭了一个现有的连接”“socket connection broken” 等等。

最正确的顺序是:先连接设备(一般在 auto_setup 接口里面连接)--> 再打开游戏应用(一般用 start_app 接口)--> 等应用开启完毕,最后才初始化 poco

# -*- encoding=utf8 -*-
__author__ = "Airtest"
from airtest.core.api import *

# 连接设备
auto_setup(__file__,logdir=True,devices=["Android://127.0.0.1:5037/emulator-5554?cap_method=JAVACAP"])

#启动应用
start_app("com.NetEase")
sleep(6.0)

# 初始化poco
from poco.drivers.unity3d import UnityPoco
poco = UnityPoco()

poco("btn_start").click()

所以同学们在初始化poco的时候,千万记住要检查下这几条代码的顺序,避免产生不必要的报错。

这里简单解释下,为什么我们要先打开游戏应用,再来初始化对应的poco呢?这是因为游戏应用里,事先嵌入了Poco-SDK,当游戏完全启动时,我们的Poco服务才会跟着启动起来,所以我们必须等游戏完全启动(start_app 后面带充足的 sleep )之后,才能开始初始化游戏的poco。

而Android和iOS原生应用则没有这一点限制,只要我们先连接上Android/iOS设备之后,就可以开始初始化Android poco或者iOS poco啦。

24. 同一个脚本初始化多个poco

在同一个脚本内,支持初始化多个不一样的poco,比如 Android poco 和 unity poco,就是可以共存的:

image

from airtest.core.api import *

auto_setup(__file__,devices=["android://127.0.0.1:5037/emulator-5554?cap_method=MINICAP_STREAM&&ori_method=MINICAPORI&&touch_method=MINITOUCH"])

from poco.drivers.android.uiautomation import AndroidUiautomationPoco
A_poco = AndroidUiautomationPoco(use_airtest_input=True, screenshot_each_action=False)

sleep(1.0)

A_poco("poco").click()
sleep(2.0)

from poco.drivers.unity3d import UnityPoco
U_poco = UnityPoco()

U_poco("btn_start").click()

但是我们不能在同一个脚本内,反复初始化相同的poco,比如多次初始化Android poco,就有可能导致奇奇怪怪的错误出现。

25. 自定义滑动

1)使用swipe_along滑动解锁

有些时候在 安卓 手机上面,我们需要实现连续滑动多个点的操作(例如屏幕滑动图案解锁功能),我们提供了一个 swipe_along 操作,示例代码如下:

from airtest.core.api import *
dev = device()  # 获取当前手机设备
# 手指按照顺序依次滑过3个坐标
dev.swipe_along([(100, 100), (200, 200), (300, 300)])

关于手机屏幕坐标的获取,可以在IDE的选项-设置中 打开 Show Real-time Cursor Coordinate 选项后即可实时在手机画面上看到坐标信息,右键点击鼠标还能将坐标复制到剪贴板中,这样可以很方便地实现一些在坐标间滑动的需求,如图:

image

请注意这个接口目前只有在使用了默认的 minitouch 模式(Android10使用 maxtouch )时才能使用,同时我们还提供了一种自定义点击或滑动操作的方案,可以供大家实现更加符合需求的操作:

from airtest.core.api import *
from airtest.core.android.touch_methods.base_touch import *

connect_device("Android:///")

# 实现两个手指同时点击的操作
multitouch_event = [
    DownEvent((100, 100), 0),  # 手指1按下(100, 100)
    DownEvent((200, 200), 1),  # 手指2按下(200, 200)
    SleepEvent(1),
    UpEvent(0), UpEvent(1)]  # 2个手指分别抬起

device().touch_method.perform(multitouch_event)

2)使用swipe_along滑个圈

# -*- encoding=utf8 -*-
__author__ = "AirtestProject"

from airtest.core.api import *

auto_setup(__file__)

# 获取当前手机设备
dev = device()
# 手指按照顺序依次滑过多个坐标
dev.swipe_along([[959, 418],[1157, 564],[1044, 824],[751, 638],[945, 415]])

图片

3)长按删除

我们先来分解下长按删除应用的整个操作,首先是长按某个应用不松手,然后再把应用滑动到垃圾桶的位置,最后点击弹窗的确认按钮即可完成删除应用的操作。

如果使用封装好的接口来实现,先使用 long_click ,再使用 swipe ,是完成不了这个任务的。因为 long_click 实现的是 点下-停顿-抬起 的动作,而我们在把应用拖到删除应用的垃圾桶之前,是不能有抬起操作的。

所以对于长按删除应用的操作,我们可以使用 basetouch 里面的4个 "event" 来拼接实现:

图片

from airtest.core.android.touch_methods.base_touch import *
dev = device()

# 案例一
# 长按删除应用
longtouch_event = [
    DownEvent([908, 892]),# 待删除应用的坐标
    SleepEvent(2),
    MoveEvent([165,285]),# 删除应用的垃圾桶坐标
    UpEvent(0)]

dev.touch_proxy.perform(longtouch_event)

# 取消卸载
poco("android:id/button2").click()

26. 多指滑动

三根手指同时在设备屏幕上滑动一定距离。

图片

# 案例三
# 三指滑动
swipe_event2 = [DownEvent((100, 300), 0), DownEvent((100, 500), 1), DownEvent((100, 700), 2), SleepEvent(0.1)]

for i in range(5):
    swipe_event2.append(MoveEvent((100 + 100*i, 300), 0))# 第一根手指
    swipe_event2.append(MoveEvent((100 + 100*i, 500), 1))# 第二根手指
    swipe_event2.append(MoveEvent((100 + 100*i, 700), 2))# 第三根手指
    swipe_event2.append(SleepEvent(0.2))

swipe_event2.append(UpEvent(0))
swipe_event2.append(UpEvent(1))
swipe_event2.append(UpEvent(2))

dev.touch_proxy.perform(swipe_event2)

27. 双指捏合

打开手机相册,随意选取一张图片,然后我们用这张图片来示范 双指捏合操作,实现放大缩小图片的效果:

图片

# 获取当前手机设备
dev = device()

# 向内捏合
dev.pinch(in_or_out='in', center=None, percent=0.5)
sleep(1.0)

# 向外捏合
dev.pinch(in_or_out='out', center=None, percent=0.2)
sleep(1.0)

dev.pinch(in_or_out='out', center=None, percent=0.2)
sleep(1.0)

pinch() 接口的参数详情如下(链接:https://airtest.readthedocs.io/zh_CN/latest/all_module/airtest.core.api.html?highlight=滑动#airtest.core.api.pinch ):

图片

28. airtest脚本中适当添加等待时间sleep

在脚本编写过程中,如果有连续点击操作,屏幕内容可能会不断变化,有时候会导致脚本明明运行到了点击操作却发现没有生效的情况。这是因为屏幕内容切换速度过快,界面还未稳定的同时airtest就进行了元素识别和操作,导致没有成功点击到对应元素。

因此我们通常建议,在一些操作步骤结束后,适当等待一个合适的时间再进行下一步操作,例如:

from airtest.core.api import *
start_app("test_package")
sleep(5)
touch([500, 500])
sleep(1)
touch([600, 0])

如果希望在每一步操作后都增加一个固定的统一等待时间,我们提供了一个名为 OPDELAY 的全局变量,设置它可以修改airtest操作之间的间隔,可以参考 Airtest脚本相关配置 中的 OPDELAY相关介绍。