Airtest
Airtest 简介:
AirtestIDE 是一个跨平台的UI自动化测试编辑器,适用于游戏和App。
自动化脚本录制、一键回放、报告查看,轻而易举实现自动化测试流程;
支持基于图像识别的 Airtest 框架,适用于所有Android和Windows游戏;
支持基于UI控件搜索的 Poco 框架,适用于Unity3d,Cocos2d与Android App;
能够运行在Windows和MacOS上;
网易内部已成功应用在数十个项目上,利用 手机集群 进行大规模自动化测试。
类似工具:uiautomator2 appium airtest
Airtest 相关链接
项目地址:http://airtest.netease.com/
快速上手教程:http://airtest.netease.com/tutorial/Tutorial.html
IDE快速上手教程:http://airtest.netease.com/docs/docs_AirtestIDE-zh_CN/1_quick_start.html
Popc 插件教程:https://www.jianshu.com/p/6bf26d1192b4
爬虫实战
环境
airtest 版本:1.2.6
python 的 airtest 库:1.1.7 (已支持 python3.8 和 python3.9)
pocoui:1.0.80
使用步骤
1、下载并安装模拟器(以逍遥模拟器举例)
逍遥模拟器官网:https://www.xyaz.cn/
2、模拟器开启开发者模式
小技巧,把模拟器的分辨率设置的宽度大一些,一页可以显示更多条目,对于下拉爬取能减少很多下拉次数哦!
比如 长1000 宽2000;
设置 -> 关于手机(关于平板电脑) -> 连续点击7次版本号 -> 打开开发者模式
设置 -> 开发者选项 -> 开启 -> USB 调试开启
3、下载并安装 Airtest
Airtest 官网: http://airtest.netease.com/
下载完解压即可
4、使用
打开 AirtestIDE.exe ,右侧 点击 远程设备连接,把端口号改为 21503(逍遥模拟器对应的是 21503),点击连接,然后在右上方看到 移动设备的状态,如果是 device,则直接点击connect,此时 模拟器的屏幕会投到 Airtest 界面右侧;
touch:点击左上侧区域的 touch,然后在右侧的手机屏幕里拖动选中一个APP,代码区域会显示 touch(“app的图片”);
点击 Airtest 上方的 运行(三角)可以看到,被选中的 APP 会自动打开
5、在 airtest 中 使用 本地 python环境
打开 AirtestIDE.exe,点击工具栏的选项 -> 设置 ,在自定义 Python.exe 路径的地方选择本地 Python 路径,选择后,需要在本地安装 airtest 和 pocoui 两个库。
pip install -i http://pypi.douban.com/simple --trusted-host pypi.douban.com airtest
pip install -i http://pypi.douban.com/simple --trusted-host pypi.douban.com pocoui
pip install -U numpy==1.19.3
6、利用 poco 编程
poco 辅助窗 在 Airtest 的左下侧,先将stop模式改成安卓模式,可以看到一个层级的树,这个树表示了当前屏幕的布局信息,同时在python代码编辑区也会插入一段代码:
# -*- encoding=utf8 -*-
__author__ = "Ezrealer"
from airtest.core.api import *
auto_setup(__file__)
# 自动插入的代码
from poco.drivers.android.uiautomation import AndroidUiautomationPoco
poco = AndroidUiautomationPoco(use_airtest_input=True, screenshot_each_action=False)
点击 poco 辅助窗的锁头,然后点击 右侧 手机屏幕区域的一个APP,此时APP不会打开,但是 poco辅助窗 显示了 这个 APP 所在的屏幕层级,同时中间下方的 Log 查看窗 可以看到这个app节点的信息:
Path from root node: [0, 0, 0, 0, 1, 0, 0, 0, 5]
Payload details:
type : android.widget.TextView
name : android.widget.TextView
text : 得物(毒)
enabled : True
visible : True
checkable : False
pos : [0.1388888888888889, 0.509375]
scrollable : False
boundsInParent : [0.18055555555555555, 0.19296875]
selected : False
anchorPoint : [0.5, 0.5]
size : [0.18055555555555555, 0.19296875]
zOrders : {'global': 0, 'local': 6}
editalbe : False
checked : False
focused : False
touchable : True
package : b'com.microvirt.launcher2'
scale : [1, 1]
dismissable : False
longClickable : True
focusable : False
可以看到 text 是 得物(毒) ,于是我们可以这样编程来实现打开这个app的目的:
# -*- encoding=utf8 -*-
__author__ = "Ezrealer"
from airtest.core.api import *
auto_setup(__file__)
from poco.drivers.android.uiautomation import AndroidUiautomationPoco
poco = AndroidUiautomationPoco(use_airtest_input=True, screenshot_each_action=False)
poco(text="得物(毒)").click()
poco(text=“得物(毒)”) 也可以点击左下的 poco 辅助窗的对应元素来自动生成。
7、一个例子
# -*- encoding=utf8 -*-
__author__ = "Ezrealer"
from airtest.core.api import *
from airtest.cli.parser import cli_setup
import emoji
import time
import pymysql
import re
import json
from urllib import parse
# if not cli_setup():
# auto_setup(__file__, logdir=False, devices=[
# "android://127.0.0.1:5037/127.0.0.1:21503?cap_method=JAVACAP&&ori_method=ADBORI&&touch_method=MINITOUCH",
# #"android://127.0.0.1:5037/1f1293ea0407?cap_method=JAVACAP&&ori_method=ADBORI&&touch_method=MINITOUCH",
# ])
# script content
print("start...")
# generate html report
# from airtest.report.report import simple_report
# simple_report(__file__, logpath=True)
from poco.drivers.android.uiautomation import AndroidUiautomationPoco
def get_mysql_conn():
while 1:
try:
conn = pymysql.connect(host='120.133.xx.xxx', port=3306, user='root', passwd='123',
db='ezrealer', charset='utf8mb4')
cur = conn.cursor()
break
except Exception as e:
print(e)
time.sleep(3)
return cur, conn
def execute_sql(cur, conn, table, item):
keys = ', '.join(item.keys())
values = ', '.join(['%s'] * len(item))
sql = 'INSERT INTO {table}({keys}) VALUES ({values})'.format(table=table, keys=keys, values=values)
while 1:
try:
cur.execute(sql, tuple(item.values()))
conn.commit()
print("插入成功...")
break
except Exception as e:
print(e)
cur, conn = get_mysql_conn()
time.sleep(3)
cur,conn = get_mysql_conn()
exits_titles = []
sql = "select title from du_tags"
cur.execute(sql)
results = cur.fetchall()
for i in results:
exits_titles.append(i[0])
print("已存在:",len(exits_titles))
# keyevent("BACK")
# time.sleep(1)
# keyevent("BACK")
# time.sleep(1)
# keyevent("BACK")
# time.sleep(1)
# keyevent("BACK")
poco = AndroidUiautomationPoco(use_airtest_input=True, screenshot_each_action=False)
# time.sleep(3)
# poco(text="得物(毒)").click()
# print("连接成功...")
# time.sleep(10)
# poco("com.shizhuang.duapp:id/rbtn_trend").click()
# time.sleep(2)
#while 1:
authors = []
page_cards_module = poco("android.widget.LinearLayout").offspring("com.shizhuang.duapp:id/trendContainer").offspring("com.shizhuang.duapp:id/viewPager").offspring("android.widget.LinearLayout").children()
suc_num = 0
for page_card_module in page_cards_module:
page_card = page_card_module.get_text()
print(page_card)
if page_card != "全部":
page_card_module.click()
time.sleep(1)
while 1:
# articles = poco("android.widget.LinearLayout").offspring("com.shizhuang.duapp:id/trendContainer").offspring("com.shizhuang.duapp:id/recyclerView").children()
articles = poco("com.shizhuang.duapp:id/clGridRoot")
print(len(articles))
titles = []
for article in articles:
if not article.child("com.shizhuang.duapp:id/tvTitle").exists():
continue
article_title = article.child("com.shizhuang.duapp:id/tvTitle")
author = article.child("com.shizhuang.duapp:id/tvUsername").get_text()
title = article_title.get_text()
print("标题:",title)
if title in titles or title in exits_titles:
print("已存在...")
continue
# print("下拉加载更多airtile...")
# poco.swipe([0.5, 0.8], [0.5, 0.3])
# time.sleep(2)
# continue
titles.append(title)
article_title.click()
time.sleep(1)
item = {}
if poco("com.shizhuang.duapp:id/tags_container").child("android.widget.LinearLayout").offspring("android.view.ViewGroup").exists():
tags = []
tags_modules = poco("com.shizhuang.duapp:id/tags_container").child("android.widget.LinearLayout").offspring("android.view.ViewGroup")
for tags_module in tags_modules:
tag = []
# tag_modules = tags_module.child("android.widget.LinearLayout").offspring("android.view.ViewGroup").children()
tag_modules = tags_module.children()
for tag_module in tag_modules:
print(tag_module.attr("type"))
if str(tag_module.attr("type")).strip() == "android.widget.TextView":
tag_cnt = tag_module.get_text()
tag_cnt = tag_cnt.encode("utf8","ignore").decode('unicode_escape')
#tag_cnt = tag_cnt.replace("\\x", "%")
tag_cnt = parse.unquote(tag_cnt)
tag.append(tag_cnt)
tag_str = "|".join(tag)
tags.append(tag_str)
print("标签:",tags)
item['tags'] = json.dumps(tags)
poco.swipe([0.5, 0.8], [0.5, 0.3])
time.sleep(1)
item['title'] = title
item['author'] = author
item['page_card'] = page_card
if poco("com.shizhuang.duapp:id/tv_circle").exists():
circle = poco("com.shizhuang.duapp:id/tv_circle").get_text()
print("圈子:",circle)
joined_circle = poco("com.shizhuang.duapp:id/tv_circleno").get_text()
item['circle'] = circle
item['joined_circle'] = joined_circle.replace("人已加入","")
if poco("com.shizhuang.duapp:id/tv_label").exists():
topic_module = poco("com.shizhuang.duapp:id/tv_label")
topic = topic_module.get_text()
time.sleep(1)
topic_module.click()
time.sleep(2)
if poco("com.shizhuang.duapp:id/numContent").exists():
topic_cnt_num = poco("com.shizhuang.duapp:id/numContent").get_text()
item['topic_cnt_num'] = topic_cnt_num.replace("条内容","")
print("话题:",topic)
item['topic'] = topic
keyevent("BACK")
time.sleep(1)
suc_num += 1
print(suc_num,item)
table = "du_tags"
execute_sql(cur,conn,table,item)
keyevent("BACK")
print("回到主页面...")
time.sleep(2)
print("下拉加载更多airtile...")
poco.swipe([0.5, 0.8], [0.5, 0.3])
time.sleep(2)
在 pycharm 中使用
https://mp.weixin.qq.com/s/-gGplycWKAsJ6Os3XQFARA
常见问题
Airtest 中 device 是 offline 状态
win + R
powershell
# 查看占用5037的pid
netstat -aon|findstr "5037"
# 查看占用该pid的任务
tasklist|findstr "4452"
# 终止任务
taskkill /f /t /im vmware-hostd.exe
报错
卸载 手机上的 PocoService 和 Yosemite,启动 airtest的 程序后会重新安装。