ATX 基于 ATX-Server 的 UI 自动化测试框架

linpengcheng · 2018年06月18日 · 最后由 meiyo 回复于 2023年05月11日 · 7144 次阅读

分享一个基于 ATX-Server 的 UI 自动化测试框架,可以实现多设备的并行测试,并生成统一的测试报告 GitHub 地址:https://github.com/pengchenglin/ATX-Test

前置条件

Android 设备需要通过 uiautomator2 init 初始化完成,确认可以正常连接 ,或者 init 接入 atx-server
相关的基础链接如下
小白入门篇:python uiautomator2 的代码示例
浅谈自动化测试工具 python-uiautomator2
atx 安卓集群管理 安装运行及自动化的实践
ATX-uiautomator2 实现 webview 的操作

先行声明:

1.下面展示的内容多源于 TesterHome 各位前辈的经验总结,我只是按照个人想法进行了简单拼接(基于 ATX-uiautomator2 的 Android 自动化测试)
2.主要参考了@hualin (王华林) 老师的https://testerhome.com/topics/7550uiautomator2 实现,并在此基础上结合
3.所用语言为 Python,测试报告模板借用了https://github.com/Gelomen/HTMLTestReportCN-ScreenShot,并进行了简单的修改以方便截图
4.使用了 macaca 的 bootstrap app 作为 demo 演示

工程介绍

工程目录如下

主体结构和@hualin (王华林) 老师的https://testerhome.com/topics/7550 的一致,主要修改了 Pubilc 下一些东西,并增加了一些东西

Public:

  • ATX-Server.py 获取 atx-server 上特定设备、或 config.ini 下 devices 列表的在线设备
  • Devices.py 获取 atx-server 上特定设备(ATX_Server(object))、或 config.ini 下 devices IP 列表的在线设备(get_devices())、有线连接电脑的设备自动连接 u2(connect_devices())
  • BasePage.py 用于设备的初始化 u2.connect 以及一些公共模块的封装
  • chromedriver.py 和 Ports.py 结合使用,启动 chromedriver 以便实现 u2 的 webview 操作(目前还没做到根据设备的 chromeversion 启动指定版本的 chromedriver)
  • Casestrategy.py 获取指定路径下的 testcases
  • Decorator.py 有@testcase@teststep这样的装饰器用例执行日志打印、错误后的处理(截图)
  • Report.py 对生成的报告的一些操作,备份 Testreport 的报告到 TestReport_backup 下、多设备统一报告的生成、报告的文件夹压缩
  • Test_data.py 在执行测试前的测试数据的生成,会在 Plubic 下生成 data.json,测试执行的时候各个设设备根据自己的 serial 获取对应的测试数据
  • Drivers.py 设备的获取,初始化准备,测试执行都是在这里完成的
  • RunCases.py 存放测试报告/日志/截图的路径的生成,以及最终通过 HTMLTestRunner 来执行用例
  • config.ini 一些需要用到的数据,tatx-server 地址、测试设备的 ip、测试数据等

下面介绍一下流程:
1、通过 run_cases .py 或者 run_all_cases.py 开始执行测试

if __name__ == '__main__':
    # back up old report dir 备份旧的测试报告文件夹到TestReport_backup下
    backup_report()

    cs = CaseStrategy()
    cases = cs.collect_cases(suite=False)
    Drivers().run(cases)

    # Generate zip_report file  压缩测试报告文件
    # zip_report()

​ 1.首先会将 Testreport 目录剪切到 TestReport_backup 目录下,备份旧的测试报告
​ 2.通过 CaseStrategy 获取到需要执的测试用例
​ 3.Drivers().run(cases)开始执行测试
​ 4.执行完成之后打包压缩,没啥用 注释掉了

2、run(cases)执行测试

def run(self, cases):
    # 根据method 获取android设备
    method = ReadConfig().get_method().strip()
    if method == 'SERVER':
        # get ATX-Server Online devices
        devices = ATX_Server(ReadConfig().get_server_url()).online_devices()
        print('\nThere has %s online devices in ATX-Server' % len(devices))
    elif method == 'IP':
        # get  devices from config devices list
        devices = get_devices()
        print('\nThere has %s  devices alive in config IP list' % len(devices))
    elif method == 'USB':
        # get  devices connected PC with USB
        devices = connect_devices()
        print('\nThere has %s  USB devices alive ' % len(devices))

    else:
        raise Exception('Config.ini method illegal:method =%s' % method)

    if not devices:
        print('There is no device found')
        return

    # generate test data data.json 准备测试数据
    generate_test_data(devices)

    print('Starting Run test >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
    runs = []
    for i in range(len(devices)):
        runs.append(RunCases(devices[i]))

    # run on every device 开始执行测试
    pool = Pool(processes=len(runs))
    for run in runs:
        pool.apply_async(self._run_cases,
                         args=(run, cases,))
    print('Waiting for all runs done........ ')
    pool.close()
    pool.join()
    print('All runs done........ ')
    ChromeDriver.kill()

    #  Generate statistics report  生成统计测试报告 将所有设备的报告在一个HTML中展示
    create_statistics_report(runs)

​ 1.首先根据 config.ini 中method的值来判断从 atx-serve 获取 online 的设备 还是从 config.ini 中的 ip 来获取在线的设备,或者直接获取连接电脑的安卓设备
​ 2.在获取到设备之后,根据设备生产 data.json 测试数据
​ 3.并行多设备执行测试
​ 4.测试完之后,杀掉执行过程中打开的所有的 chromedriver 进程
​ 5.最后在 TestReport 下生成统计测试报告(自动化测试报告.html)

结果展示

生成的测试报告路径结构如下

每个设备的测试结果及报告或存放在单独的文件夹下
在 Testreport 目录下会有一个统计测试报告(自动化测试报告.html) 会将所有设备的报告统一在一个页面展示
报告展示:

附言 1  ·  2018年06月24日

method 设置为 USB 之后,手机有线连接到电脑的,就算 offline 的设备也能自动重启 u2 跑脚本了。
前提是你的 u2 要升级到 Version: 0.1.3.dev5 及以后,guthub 上更新最新的 ATX-Test 代码
因为 uiautomator2 更新了 connect_usb 方法
def connect_usb(serial=None):
"""
Args:
serial (str): android device serial
"""
adb = adbutils.Adb(serial)
lport = adb.forward_port(7912)
device = connect_wifi('127.0.0.1:' + str(lport))
if not device.alive:
warnings.warn("atx-agent is not alive, start again ...", RuntimeWarning)
adb.execute("shell", "/data/local/tmp/atx-agent", "-d")
device.healthcheck()
return device

附言 2  ·  2018年06月19日

还有个 python 第三方库 psutil

附言 3  ·  2018年06月18日

需要依赖的 python 第三方库 tinydbuiautomator2seleniumjinja2

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 98 条回复 时间 点赞

uiatuomator2 实现了 toast 的获取 请更新 u2 到最新的 0.1.3.dev2
https://github.com/openatx/uiautomator2#toast

然而
d.watchers.watched = True有问题 可能在 0.1.3.dev3 中解决

Waiting for all runs done........
2018-10-31 15:34:11,248 - huawei tag-tl00 - INFO - udid: 00b84da5--huawei_tag-tl00
All runs done........
All chromedriver pid killed
Traceback (most recent call last):
File "C:/Users/diaost/Desktop/ATX-Test-master/run_all_cases.py", line 17, in
Drivers().run(cases)
File "C:\Users\diaost\Desktop\ATX-Test-master\Public\Drivers.py", line 97, in run
create_statistics_report(runs)
File "C:\Users\diaost\Desktop\ATX-Test-master\Public\Report.py", line 66, in create_statistics_report
tmp_dic.update(_get_report_info(run))
File "C:\Users\diaost\Desktop\ATX-Test-master\Public\Report.py", line 38, in _get_report_info
with open(report, 'r', encoding='utf-8') as f:
FileNotFoundError: [Errno 2] No such file or directory: './TestReport/2018-10-31_15_34_10-huawei tag-tl00/TestReport.html'
大神,运行报这个错要怎么解决的,是哪里的路径需要改吗

不错不错

学习~学习~😀

学习~学习~

和 appium 结合 stf 的思想差不多

在 CaseStrategy 里的 suite_path 和 case_path ,case_pattern 路径应该要怎样配置?配置全路径吗?
我运行时报了以下错

Traceback (most recent call last):
  File "/my_work/my_work/ATX-Test-master/run_all_cases.py", line 16, in <module>
    cases = cs.collect_cases(suite=True)
  File "/my_work/my_work/ATX-Test-master/Public/CaseStrategy.py", line 40, in collect_cases
    self._collect_cases(cases, top_dir=test_suite)
  File "/my_work/my_work/ATX-Test-master/Public/CaseStrategy.py", line 16, in _collect_cases
    pattern=self.case_pattern, top_level_dir=top_dir)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/loader.py", line 304, in discover
    os.path.dirname((the_module.__file__)))
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/posixpath.py", line 156, in dirname
    p = os.fspath(p)
TypeError: expected str, bytes or os.PathLike object, not NoneType
huan 回复

self.suite_path = 'TestSuite_'
self.case_path = 'TestCase'
self.case_pattern = 'test*.py'

文件夹和文件以上面的命名开头就行了啊

linpengcheng 回复

就是跑你的 demo😅

huan 回复

TestSuite_demo 下的 run_cases.py 运行乐视报这个错?

linpengcheng 回复

这个没报,是 run all cases 报错

huan 回复

看看和最新 github 上的是不是一样的 你本地的代码是什么样的

2018-06-26 10:20:40,943 - Redmi Note 4X - INFO - udid: c9c6d32a0804-38:e6:0a:7c:1b:20-Redmi_Note_4X
Error TestCase..pytest_cache (unittest.loader._FailedTest)
大神,这个错误是哪里的问题

renfenghui 回复

unittest.loader._FailedTest 用例的 case 代码有问题吧

仅楼主可见
renfenghui 回复

错误日志贴全一点 不知道到底哪里出的错 你自己分析分析应该也能找到出错的原因的

测试报告不错,是不是我在 appium 框架中也能用

枫叶 回复

可以啊 吧截图的那个改一下就可以了 我也是拿过来稍微改了下

学习了!!

yideng-dst 回复

你的用例没执行成功吧 TestReport.html 文件都没生成
可以直接将 ATX-Test-master\Public\Drivers.py", line 97 的 create_statistics_report(runs) 这句话注释掉好了 其实这个只是在运行完生成一个统计的报告页面罢了

@linpengcheng 单个 case 如何调试, 不能用 TestSuite() 来调试么

梵心一点 回复

可以啊 这样就好了吧

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
sys.path.append('..')
from Public.Drivers import Drivers
from Public.Report import *
from TestSuite_demo.TestCase import test_01_install


if __name__ == '__main__':
    # back up old report dir 备份旧的测试报告文件夹到TestReport_backup下
    backup_report()

    cases =  unittest.TestSuite()
    cases.addTest(test_01_install.app_install('test_01_install_apk'))

    Drivers().run(cases)
linpengcheng 回复

多谢,早上来公司已经可以了

hi,
我把 APP 的包名改成我们公司的 app,为什么启动的是 debug 的 Leaks?

521anran 回复

?检查检查代码看

andyzhouh2017 回复

oppo 的安装是要输入密码才能安装的吧 直接 adb install 应该会有问题

我在 win10 上运行的,但是无法安装 apk,报 Exception, -32002 Client error: <> data: , method: None 错误,请帮忙看下?

目前采用 Uiautomator2 工具做 UI 自动化测试,发现经常有些异常信息出现导致程序不能正常的执行下去,所以针对这些异常,我想采用监控 app 软件是否在最上层,如果不在最上层就 home 键盘把后台进程都 kill 掉 用 python 实现?或者有没有更好的方法来处理?

另外,可以 python 实现 Uiautomator2 监听网络状态,连接指定 Wi-Fi 吗?

andyzhouh2017 回复

d.current_app()可以查看当前页面启动的包名和 activity 名称
d.app_stop_all()可以关闭所有的第三方安装启动着的 app

andyzhouh2017 回复

Auto_ota.py 试啥?

运行脚本的时候 $ python3 Auto_ota.py 为什么总是报如下警告:
C:\Python36\lib\subprocess.py:761: ResourceWarning: subprocess 14572 is still running
ResourceWarning, source=self)
Auto_ota.py:38: ResourceWarning: unclosed file <io.BufferedReader name=3>
check_ip_ping()
C:\Python36\lib\site-packages\uiautomator2__init
_.py:375: RuntimeWarning: uiautomator2 is not reponding, restart uiautomator2 automatically
stacklevel=1)
.

你好,请问在你的框架基础上如何单个循环执行 cases 100 次?

andyzhouh2017 回复

传参 加个循环???

是的,比如:run_all_case 用例 循环次数,以这种方式跑,可以做吗?

仅楼主可见
andyzhouh2017 回复

直接手机号找人就好

仅楼主可见


博主你好这里是匹配的什么呢?图片的路径吗?还有啊 image = self.REPORT_TEST_OUTPUT_IMAGE % dict(
screenshot=saxutils.escape(uo + ue) 这一部分不太懂

边万成 回复

截图的操作会输出 htmltesterrunner 读取到输出的内容 带 IMAGE:字段的 解析出来 吧真实的图片地址添加到报告里

边万成 回复

直接 logger.info()输出内容就好了吧

linpengcheng 回复

谢谢楼主,已经摸索出来了

linpengcheng 回复

如果我想在控制台打印出测试案例里面的日志改怎么设置呢?比如
控制台打印出 print 语句

linpengcheng 回复


我用这种方式,不会打印测试案例里面的 print 或者 Logging 的东西,只打印了成功或者失败的结果。请问是怎么配置的?

边万成 回复

testsuite 下的 csaes 的 py 文件写法和 pageobject 目录下的写法一样 log.i(xxxxxxxx)就好了

楼主您好,在执行 case 的时候,如果多个手机里面的 webview 对应的 chromdriver 版本不一样,请问这样的该怎么处理呢?

bjxiehong 回复

获取到手机上 chrome 的版本 然后启动 PC 端指定版本的 chromedriver
可以参考这个
https://testerhome.com/topics/15915

Bach 回复

看了下貌似是 requests 请求报错了

仅楼主可见
Bach 回复

windows 的?

楼主,你好,请教个问题,为什么每次执行都会先退到桌面,相关逻辑代码可以帮忙指出吗?
还有就是每次自动化测试执行完后都会弹出下面的图片所示的界面,是否可以取消,不让其显示?

LinXunFeng 回复

1、退回桌面应该是 setup terndown 里写脚本 cls.d.app_stop("com.github.android_app_bootstrap")
2、Public 下的 Drivers.py 94 行的 base_page.identify() 注释掉就好了

linpengcheng 回复

感谢楼主😀

  • 第二点已经解决了
  • 第一点还是跟之前一样,执行的时候还是会退到桌面,如果是在桌面则回到桌面首页,类似执行了 "home" 操作

报告里多出一条这样的记录 "W/ActivityManager( 2931): Invalid packageName: com.github.android_app_bootstrap"
请问 返回桌面(首页) 这个问题还有其它的解决办法吗?

LinXunFeng 回复

什么时候回到桌面?

linpengcheng 回复

每次执行 python run_cases.py 都会回到桌面,下面是具体代码,不知道是不是我写的有问题

  • 具体的自动化操作相关代码

    class apk_install(unittest.TestCase, BasePage):
    @classmethod
    @setupclass
    def setUpClass(cls):
        print("setupClass")
        # cls.d.app_stop_all()
        cls.d.app_stop("com.github.android_app_bootstrap")
        pass
    
    @classmethod
    @teardownclass
    def tearDownClass(cls):
        print("tearDownClass")
        cls.d.app_stop("com.github.android_app_bootstrap")
        pass
    
    @testcase
    def test_install_apk(self):
        self.d.app_uninstall(pkg_name)
        # self.d.app_info(apk_url)
        self.local_install(apk_path)
        self.d.app_start(pkg_name)
    
  • 执行操作的相关代码

cases.addTest(test_install_apk.apk_install('test_install_apk'))
Drivers().run(cases)
LinXunFeng 回复

setup 和 teardown 都杀掉 app 当然会回到桌面了

linpengcheng 回复

那我弄错你之前回复的意思了,不好意思。
我遇到的问题是:本来代码里就没有加这个 cls.d.app_stop 代码的,但是在执行 python run_cases.py 也是会退到桌面,不是杀死指定程序,而是执行了home的操作 (推测),有什么办法可以让它不做出这样的操作呢?

我所说的执行了home 操作的现象:

  • 如果当前设备显示 app,则退回桌面(不一定是首页)
  • 如果当前设备显示桌面,则滚回到桌面首页
LinXunFeng 回复

执行 cases 前会 check_alive 里面有一个 d.healthcheck() 的方法。

def healthcheck(self):
    """
    Reset device into health state

    Raises:
        RuntimeError
    """
    sh = self.ash
    if not sh.is_screen_on():
        print(time.strftime("[%Y-%m-%d %H:%M:%S]"), "wakeup screen")
        sh.keyevent("WAKEUP")
        sh.keyevent("HOME")
        sh.swipe(0.1, 0.9, 0.9, 0.1)  # swipe to unlock

    sh.keyevent("HOME")
    sh.keyevent("BACK")
    self.reset_uiautomator()
LinXunFeng 回复

你可以注释掉 drivers.py 下 check_alive 方法下的 d.healthcheck() 的方法,但是 你运行的时候 要保证手机是不再锁屏状态的

linpengcheng 回复

好的楼主,十分感谢👍 👍 👍

请问这个是多设备并行 run 还是串行 run 的测试用例?

xiaoxiao 回复

多进程的并行

楼主刚开始看,麻烦问下 token 怎么获取的?

GoTesting 回复


页面右上角点一下进去就能看到

花开 ATX UI 自动化学习-偏 python 语法知识 中提及了此贴 10月22日 00:51

请问如果是 usb 链接的是不是需要把 config 里面的 method = USB 开启,其他的都注释了?

请问 ATX_Server.py 和 atxserver2.py 的区别在哪里,是分场景使用?

小肉包 回复

一个是 server1 的 一个是 server2 的

小肉包 回复

是的

不知道怎么回事,我有时候用 exists 做校验,好像不准,有时候又准,还有什么其他的方法可以做校验吗?

BitGuo 回复

❓

qugo9955 回复

我在设备初始化 的时候将输入法切换掉了 主要是为了防止跑脚本的时候 弹出的默认输入法太长遮挡 UI
https://github.com/pengchenglin/ATX-Test/blob/master/Public/Drivers.py

linpengcheng 回复

嗯嗯 已了解处理了,谢谢~~

qugo9955 回复

支持的吧 都是从 atxserver2 上初始化设备 不过没怎么写相关的实现


请教下:脚本模拟单击输入框,没有弹出键盘
手动单击会弹出键盘,是 什么情况呢?

请教下:push 到 data/local/tmp 路径里面的.apk 安装包,在安装包里面看不到,有什么解决方法吗?

qugo9955 回复

那你 push 到别的地方好了 例如/sdcard/

请教下 ATX-Test 支持 IOS 手机吗?

linpengcheng 回复

如果支持的话 用例脚本 IOS 和 andriod 是不是分开的? 不能跨平台吧?

qugo9955 回复

是要分开的

请教下:如果公司有多个 APP 项目,是都在这个工程下面,通过测试套区分还是说每个项目独立一个工程呢?如果每个项目独立一个工程,那维护公共代码就有点不好维护了。大家是怎么做的呢?

@linpengcheng 请教下 我们这套框架在 weditor 能获取到元素,但是执行脚本的时候,经常出现找不到元素的情况。是不是因为对 RN 的兼容问题? 对原生的 app 页面 是不是更友好些?

qugo9955 回复

我目前将这个项目的结构做了修改 多个 app 可以在一个项目下进行
在 weditor 能获取到元素,但是执行脚本的时候,经常出现找不到元素的情况。 是不是你用的 xpath 定位 而且是直接复制的 weditor 内的代码?

linpengcheng 回复

在 weditor 能获取到元素,但是执行脚本的时候,经常出现找不到元素的情况:用 xpath 和 text 或者 description 都试过,都有这种情况。xpath 基本就是直接复制的 weditor 里面的。 text 和 description 相对简单,会手动输入。
直接复制会有什么已知的问题吗?或者说和我们 APP 不是原生的有关?

qugo9955 回复

xpath 每次既然怒页面 对应的相对位置可能不太一样 估计是你写的元素定位不太正确

有 text 和 description 或者 resouceid 的 建议优先使用 这几个参数来定位,或者好好了解下 xpath 定位,吧定位写的通用一点,一般就不会报 元素找不到的错误了。
weditor 定位元素 自动生成代码只是为了方便,具体的脚本还是要调试的

目前正在使用大佬的框架,非常好用👍

请问下,取 atx2 的设备,然后这个报错是什么情况
``py
File "D:/Projects/Python/ATX-Test-master/Demo/TestSuite_demo/run_cases.py", line 29, in
devices = check_devives(dm_config.method, dm_config.udid)
File "D:\Projects\Python\ATX-Test-master\Public\devices_new.py", line 133, in check_devives
devices = atxserver2_online_devices(atxserver2().present_android_devices())
File "D:\Projects\Python\ATX-Test-master\Public\atxserver2.py", line 139, in present_android_devices
self.refresh()
File "D:\Projects\Python\ATX-Test-master\Public\atxserver2.py", line 91, in refresh
self.purge()
File "D:\Projects\Python\ATX-Test-master\Public\atxserver2.py", line 106, in purge
self.db.purge_tables()
File "C:\Users\xxxxxxx\AppData\Local\Programs\Python\Python37\lib\site-packages\tinydb\database.py", line 255, in __getattr
_
return getattr(self.table(self.default_table_name), name)
AttributeError: 'Table' object has no attribute 'purge_tables'

tinydb 啥版本

我这边还是 3.15.2 的

linpengcheng 回复

数据库装老版本可以了,但是后面报这个

  File "D:\Projects\Python\ATX-Test-master\Public\devices_new.py", line 30, in atxserver2_online_devices
    if i.get():
  File "C:\Users\XXXXXXX\AppData\Local\Programs\Python\Python37\lib\multiprocessing\pool.py", line 657, in get
    raise self._value
AttributeError: 'Device' object has no attribute '_filelock'

装个老版本试试看

请问下 uiautomator2 的版本号是多少呢

先 star 了

9楼 已删除
49楼 已删除

开源大赞

你好,执行的时候打印日志报错,请问如何解决?

Traceback (most recent call last):
  File "/Users/tester/PycharmProjects/ATX_UI_Test/Public/decorator.py", line 92, in wrapper
    log.d('--> %s' % func.__qualname__)
  File "/Users/tester/PycharmProjects/ATX_UI_Test/Public/log.py", line 30, in d
    self.logger.debug(msg, *args, **kwargs)
AttributeError: 'Log' object has no attribute 'logger'

来学习下

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册