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

linpengcheng · June 18, 2018 · Last by meiyo replied at May 11, 2023 · 7217 hits

分享一个基于 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  ·  June 24, 2018

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  ·  June 19, 2018

还有个 python 第三方库 psutil

附言 3  ·  June 18, 2018

需要依赖的 python 第三方库 tinydbuiautomator2seleniumjinja2

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

开源大赞

来学习下

不错不错

学习~学习~😀

先 star 了

学习~学习~

和 appium 结合 stf 的思想差不多

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

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

9Floor has deleted

在 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 代码有问题吧

Author only
renfenghui 回复

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

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

枫叶 回复

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

学习了!!

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'
大神,运行报这个错要怎么解决的,是哪里的路径需要改吗

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 回复

?检查检查代码看

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

andyzhouh2017 回复

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

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

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

andyzhouh2017 回复

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

运行脚本的时候 $ 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)
.

andyzhouh2017 回复

Auto_ota.py 试啥?

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

andyzhouh2017 回复

传参 加个循环???

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

Author only
andyzhouh2017 回复

直接手机号找人就好


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

边万成 回复

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

linpengcheng 回复

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

linpengcheng 回复

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

49Floor has deleted
边万成 回复

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

linpengcheng 回复


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

边万成 回复

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

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

bjxiehong 回复

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

Bach · #56 · May 22, 2019
Author only
Bach 回复

windows 的?

Bach · #58 · May 22, 2019
Author only
Bach 回复

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

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

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 语法知识 中提及了此贴 22 Oct 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 回复

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

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

qugo9955 回复

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

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

qugo9955 回复

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

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 的版本号是多少呢

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

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'
需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up