Appium Appium 开源分享优化版

测试小书童 · 2018年01月19日 · 最后由 kevin2017 回复于 2018年09月25日 · 7142 次阅读
本帖已被设为精华帖!

之前分享过PageObject+Python+Appium

本版本是对上次版本较大改版,主要解决了:

  • 失败重连一次(默认一次)可配置多次
  • 基于appium1.7.1 uiautomator2
  • 解决uiautomator2启动超时
  • 新增检查点关键字,contrary_getval,exceptsl,contrary暂时没有写到yml配置文件中,直接在写在了page层
    • 检查点关键字contrary:相反检查点,传1表示如果检查元素存在就说明失败,如删除后,此元素依然存在
    • 检查点关键字toast: 表示提示框检查点
    • 检查点关键字contrary_getval: 相反值检查点,如果对比成功,说明失败
    • 检查点关键字check: 自定义检查结果
    • 检查点关键字excepts: 如果为1,表示操作出现异常情况检查点为成功。如:删除成功了,此数据不存在则为真
  • 测试报告新增对多台测试机结果统计(安卓)
  • 适配mac,windows平台
  • 适配ios测试(简单调试通过,后续有机会实际去使用和优化)

代码简要分析

yml测试用例

testinfo:
- id: test004
title: 每日新闻卡片浏览记录
info: 打开知识
testcase:

- element_info: com.huawei.works.knowledge:id/title
find_type: ids
index: 0
operate_type: get_value
info: 获取每日新闻下对第一条数据
- element_info: com.huawei.works.knowledge:id/title
find_type: ids
index: 0
operate_type: click
is_time: 3
info: 点击每日新闻下对第一条数据
- element_info: h5-scroll
find_type: id
is_webview: 1 # 切换到webview
info: 查找并获取详情页标题
- element_info: com.huawei.works.knowledge:id/vtb_img_left
find_type: id
is_webview: 2 # 切换到native
operate_type: click
info: 点击返回按钮
- element_info: com.huawei.works.knowledge:id/vtb_img_right2
find_type: id
operate_type: click
info: 点击首页历史记录按钮
check:
- element_info: com.huawei.works.knowledge:id/browser_knowledge_history_text
find_type: ids
index: 0
operate_type: get_value
info: 查找是否存在历史记录

某个用例的page层

from PageObject import Pages


class DayNewHistoryPage:
'''
每日新闻浏览历史
'''

def __init__(self, kwargs):
_init = {"driver": kwargs["driver"], "path": kwargs["path"], "device": kwargs["device"],
"logTest": kwargs["logTest"], "caseName": kwargs["caseName"]}
self.page = Pages.PagesObjects(_init)

def operate(self): # 操作步骤
self.page.operate()

def checkPoint(self): # 检查点
self.page.checkPoint()
  • pages再次封装了一层,主要可以看下重连机制的实现
    • 其实主要用的是launch_app+setupclass,另外一个好处是避免用例依赖,并不会重新启动一个session
if result is not True and be.RE_CONNECT:
self.msg = "用例失败重连过一次,失败原因:" + self.testInfo[0]["msg"]
self.logTest.buildStartLine(self.caseName + "_失败重连") # 记录日志
self.operateElement.switchToNative()
self.driver.launch_app()
self.isOperate = True
self.get_value = []
self.is_get = False
self.operate() # 执行步骤
result = self.check(kwargs) # 坚持点
self.testInfo[0]["msg"] = self.msg
self.operateElement.switchToNative()

testcase层调用page层

class HomeTest(ParametrizedTestCase):
# 首页下拉
def testAHomeSwipeDown(self):
app = {}
app["logTest"] = self.logTest
app["driver"] = self.driver
app["path"] = PATH("../yaml/home/HomeSwipeDown.yaml")
app["device"] = self.devicesName
app["caseName"] = sys._getframe().f_code.co_name

page = HomeSwipeDownPage(app)
page.operate()
page.checkPoint()

# banner浏览历史记录
def testB0annerHistory(self):
app = {}
app["logTest"] = self.logTest
app["driver"] = self.driver
app["path"] = PATH("../yaml/home/BannerHistory.yaml")
app["device"] = self.devicesName
app["caseName"] = sys._getframe().f_code.co_name
page = BannerHistoryPage(app)
page.operate()
page.checkPoint()

@classmethod
def setUpClass(cls):
super(HomeTest, cls).setUpClass()

@classmethod
def tearDownClass(cls):
super(HomeTest, cls).tearDownClass()

代码入口

def runnerCaseApp(devices):
starttime = datetime.now()
suite = unittest.TestSuite()
suite.addTest(ParametrizedTestCase.parametrize(HomeTest, param=devices))
suite.addTest(ParametrizedTestCase.parametrize(TestWeiQunTest, param=devices))
unittest.TextTestRunner(verbosity=2).run(suite)

比较麻烦case处理

  • 当遇到有些用例比较麻烦,必须单独写page 层,比如长按交换空间位置
  • 自定义page层
.....
def operate(self):
for item in self.testCase:
m_s_g = self.msg + "\n" if self.msg != "" else ""

result = self.operateElement.operate(item, self.testInfo, self.logTest, self.device)
if not result["result"]:
msg = "执行过程中失败,请检查元素是否存在" + item["element_info"]
print(msg)
self.msg = m_s_g + msg
self.testInfo[0]["msg"] = msg
self.isOperate = False
return False

if item.get("operate_type", "0") == "location":
app = {}
web_element = self.driver.find_elements_by_id(item["element_info"])[item["index"]]
start = web_element.location
# 获取控件开始位置的坐标轴
app["startX"] = start["x"]
app["startY"] = start["y"]
# 获取控件坐标轴差
size1 = web_element.size

width = size1["width"]
height = size1["height"]
# 计算出控件结束坐标
endX = width + app["startX"]
endY = height + app["startY"]

app["endX"] = endX - 20
app["endY"] = endY - 60

self.location.append(app)
# self.driver.swipe(endX, endY, starty, endY)
if item.get("operate_type", "0") == be.GET_VALUE:
self.get_value.append(result["text"])

if item.get("is_swpie", "0") != "0":
print(self.location)
self.driver.swipe(self.location[0]["endX"], self.location[0]["endY"], self.location[1]["endX"], self.location[1]["endY"]+10)
  • yaml用例 可以自定义一些关键字给page用
testinfo:
- id: test019
title: 拖动排序知识卡片
info: 打开知识
testcase:
- element_info: com.huawei.works.knowledge:id/vtb_img_right
find_type: id
operate_type: click
info: 点击排序卡片按钮
- element_info: com.huawei.works.knowledge:id/my_card_item_name_text
find_type: ids
index: 1
operate_type: get_value
info: 得到第二个卡片的值
- element_info: com.huawei.works.knowledge:id/drag_item_image
find_type: ids
index: 0
operate_type: location
info: 得到第一卡片的坐标
- element_info: com.huawei.works.knowledge:id/drag_item_image
find_type: ids
index: 1
operate_type: location
is_swpie: 1 # 特殊关键字,滑动指令
info: 得到第二个卡片的坐标并拖动
- element_info: com.huawei.works.knowledge:id/vtb_img_left
find_type: id
operate_type: click
info: 点击返回按钮
check:
- element_info: com.huawei.works.knowledge:id/title_txt
find_type: id
operate_type: get_value

其他

  • 顺便说下遇到浮动层无法点击
    • 浮动层造成可以识别到元素,触发了点击却失效的处理方法是,得到元素坐标,然后用adb shell方式去触发,感兴趣的可以看下adb_tap关键字的封装
  • 其他更多优化可以看我的CHANGELOG
  • 开源地址
附言 1  ·  2018年05月08日
  • 删除了多余代码
  • 本次测试用的喜马拉雅app和雷电模拟器
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 52 条回复 时间 点赞
思寒_seveniruby 将本帖设为了精华贴 01月20日 02:48

mark一下

楼主好,我有个疑惑,之前试用过你的老版本框架,发现每个用例执行完毕后都会回到系统桌面,然后再次吊起APP主界面,继续执行下一个用例,后来看代码似乎是由于每条用例执行都会调用下面的setup和tearDown。使用没问题,只是感觉正常的应该是进入APP,所有用例完成后退出。不知道新改版后的代码,是否还有这个现象,对python不是很了解,还请解惑哈。
@classmethod
def setUpClass(cls):
super(HomeTest, cls).setUpClass()

@classmethod
def tearDownClass(cls):
super(HomeTest, cls).tearDownClass()

wu 回复

l新版本帖子中也说了用的是setupclass和launch_app的方式,避免了每个用例的依赖,同时也没有像setup那样每次都安装和设置appium setting

小马 python appium UI 自动化测试框架讨论 中提及了此贴 02月01日 10:48

创建文件/home/cmd/workspace/git/appium/Log/info.pickle成功
创建文件/home/cmd/workspace/git/appium/Log/sum.pickle成功
创建文件/home/cmd/workspace/git/appium/Log/devices.pickle成功

创建这几个文件 和 *.pickle 是做什么用的 能介绍下吗?

仅楼主可见
小马 回复

测试报告使用,sum是统计所有用例,info是详情页的报告,devices是对应所有设备用例的执行统计

wholegale39 回复

代码我写死了启动类,你可以改下,下次更新我改成自动获取apk

仅楼主可见
郭丽丽 线下班第二期 Bash 教程 中提及了此贴 02月03日 21:06

大佬,菜鸟问个问题,这个错误:
AttributeError: module 'yaml' has no attribute 'load'
是什么原因?
试过安装yaml了,也不行

问下楼主,有没有遇到使用UIAutomator2,调用 pressKeyCode(66) 方法无效的情况?server无报错,但是app上面也没起作用。

Original error: com.huawei.works/huawei.w3.ui.welcome.W3SplashScreenActivity never started. Current: com.huawei.android.launcher/.unihome.UniHomeLauncher 报这个错是啥原因

.chao 回复

检查是否创建了与yaml 同名的文件目录

KD 回复

遇到过,有些页面的确无法触发回车键,包括我设置成了第三方输入法和appium的,都没有解决

薛蕾 回复

启动类问题,等我下个版本,剥离出现在的逻辑,自动安装app,自动获取启动包和类

请问一下楼主这个测的是哪个app?是不是预先安装好就可以跑起来了

薛蕾 回复

是公司内部的一个app,没有发出来,后面有时间会用一个简单的app做实际例子

楼主,找你找得好辛苦,我是来问接口自动化测试贴子里的,接口测试参数生成器的使用方法和具体的实验成功路径,现在有报错,初步判断是接口已关闭导致的,方便的话能不能加一下我的微信tsb5885632感激啦!!!

Garrosh 回复

那个很久之前的代码了,自己早忘了,觉得不好用,好像是删除了,生成器就是自动生成一个xml ,然后用这个帖子的代码解析xml进行请求,你感兴趣可以看这个帖子:https://testerhome.com/topics/5606

楼主有新进展了, “屏蔽for p in”那一段的话可以正常运行但是生成的report里面都是没拿到返回值

感谢您的回复,现在的状态是这句代码报错。。。。然后屏蔽的话可以正常输出report里面都是没拿到返回值 麻烦您再解答一下啦,辛苦啦!!!!

大佬您有空的时候帮我看一下哇,这个框架就剩这最后一步了!!!!!感激不尽!!!!!么么哒

那这种问题怎么处理呢?我现在卡在这里了......当把启动参数设置成automationName=Appium,pressKeyCode就正常了。就不能使用uiautomator2 处理 toast了。

K · #27 · 2018年03月23日
仅楼主可见
28楼 已删除
29楼 已删除

请教问题,com.huawei.works.knowledge 这个是什么APP?
路径是C:\Users\ADMINI~1\AppData\Local\Temp\?

31楼 已删除

appium 1.7.2+xcode9.3 启动报错为什么
[MJSONWP] Encountered internal error running command: Error: Unable to launch WebDriverAgent because of xcodebuild failure: "xcodebuild failed with code 65". Make sure you follow the tutorial at https://github.com/appium/appium-xcuitest-driver/blob/master/docs/real-device-config.md. Try to remove the WebDriverAgentRunner application from the device if it is installed and reboot the device.

请教一个问题,比如有个定位提示框、升级提示框之类的弹出框,我想实现如果有提示框就操作,没有就跳过,我想问下这个判断是在哪个函数里实现的?思路是怎样的,请大神帮忙回答一下,谢谢了

看完点个赞!!!

lose,您好。请问如果我要校验检查点是否一致的问题,比如新增一个体重值,然后要比对在不同界面的值是否一致。用您的框架需要用哪个检查点关键字呢,我实践的结果是,用contrary_getval可以运行成功,但这样跟您上面的解释和代码的实现有矛盾,对检查点关键字的使用,看的不是很明白。麻烦大神赐教!

kekocool 回复

已经在看这块了,BaseOperate中的operate_by中threading._start_new_thread(self.click_windows(device),())注释打开试试,我还没有进行详细的测试

shenyuexiu 回复

现在检查点已经封装到了yml文件的check下,可以重新看下

楼主,最近调试你的框架,然后一直提示 :-------start_win_server-------,如何解决,万分感谢

ruby 回复

重新试试?有时候启动是慢点

楼主,那个compare检查点历史数据和实际数据都是从哪儿来的啊?我看了半天百思不得其解,望楼主百忙中能抽空回复一下

请问那个如果自定义 不是yaml的测试,生成测试报告怎么生成啊,添加截图。我自己修改了一些代码运行iOS的 自定义的,可以运行测试用例,但是那个报告不对。谢谢。

ruby 回复
testinfo:
- id: test003
title: 热门话题
info: 打开testerhome
testcase:
- element_info: dropdown-avatar
find_type: class_name
operate_type: click
info: 点击图像
- element_info: //ul[@class='dropdown-menu']/li/a
find_type: xpath
operate_type: click
info: 点击用户名
- element_info: //ul[@class="list-group"]/li[1]/div/a[2]
find_type: xpath
operate_type: get_text
info: 获取热门话题下的第一条标题
- element_info: ul.list-group > li:nth-child(1) > div.title > a:nth-child(2)
find_type: css
operate_type: click
info: 点击热门话题下的第一条标题

check:
- element_info: /html/head/title
find_type: xpath
operate_type: get_text
check: compare
info: 详情页的标题和历史标题相等

testcase里面的一个 operate_type: get_text取第一个值,check下的 operate_type: get_text取第二个值,两个值进行对比

Kai Zhang 回复

ios我只是简单调试过,后面一直没有去怎么适配和测试

看看能不能大家一起维护一下,请问还有好的appium 测试框架推荐吗?支持能够初步写代码运行测试用例,生成带截图的测试报告,邮件发送的功能。感觉您这个框架基本思路都是有的,比如配置驱动测试用例,生成测试报告,邮件发送等。

你好,楼主,最近在学习您的框架,有一个点弄了2,3天没有搞定,如果有时间,希望您百忙之中抽时间帮忙看下:
问题:如果我们需要运行某用例,目前需要在主入口Runner中通过Import TestCase然后主入口调用这个模块名。一条case还好,要是case非常多,就不太方便。

疑问:能否做成yaml中填入用例名,直接调用

错误的过程:我试了下yaml或者txt填入多个用例名,然后通过unittest.defaultTestLoader.discover()来载入用例,但是出现如下问题:
在BaseRunner模块下的parametrize静态方法函数中会弹出错误 NoneType

通过调试发现错误原因是:
在discover加载TestCase 中用例.py 时,做了第2次载入,载入的用例为空。
而第2次载入的原因是:
框架中的测试用例是继承于父类ParametrizedTestCase,而ParametrizedTestCase 继承于Unittest.TestCase.
当执行 TestCase 中用例.py 时,unittest.defaultTestLoader.discover() 会错误的再次载入ParametrizedTestCase 的Case (包括setup/teardown/testcase) .导致的结果是:
unittest.defaultTestLoader.discover() 产生的suite会有2个case ,1个是正确的可执行的,1个是空。
而空的case执行的时候出现 NoneType Error.

这个问题把我困住了。
暂时没有找到解决的方法。
不知道楼主有没有计划 把 用例导入做成yaml参数化,如果有,请您务必帮忙看下。

非常感谢楼主,看后获益匪浅,现在又面临一些别的问题:1)添加安装app功能以及对安装过程中和启动时对权限等提示框的处理,2)是在安装uiautomator2的提示框(android7.0),看了些说是在appium源码上修改,不知道楼主有没有好办法?

袁和壮 回复

权限提示,论坛里你可以好好搜索下有很多解决方案,我这边尝试过解决8.0系统的问题,所以一直用的5的版本,有点问题,后续解决了会更新

FFMS 回复

我这里的case是放在class里面的,你只要在class引用不同的case就可以了,如果需要关联其他用例,你可以在case里调用相应的page就可以了,至于你想支持其他方式,你可以自己改造,我这里够用了……

楼主你好 。我碰到了一个问题,当页面局部刷新(即点击A按钮后,会出现B编辑按钮)时,新增的元素在使用uiaotumator2框架的时候,会识别失败。 同样的方式,单独写的demo应用时识别成功。 我有发帖求助了…… 几天了一直没找到问题原因……https://testerhome.com/topics/14306

lei___ 回复

是真机吗,我现在没有真机,用的是雷电模拟器?如果是真机,你可以尝试把baserunner.py里面的代码打开,在测试下

desired_caps['appPackage'] = devices["appPackage"]
desired_caps['appActivity'] = devices["appActivity"]
# desired_caps['app'] = devices["app"] 注释这个

真机和模拟器都是酱紫的… 真机的话,desired_caps['automationName'] = 'Appium' 运行后,第一次改成desired_caps['automationName'] = 'uiautomator2' 能识别元素成功,第二次就又失败了…… 我猜测是局部页面刷新的时候,缓存没有更新…… 但是不知道该如何解决

楼主你好,我碰到一个问题:向一个文本框输入中文后再清除,UiObject总是清除不成功

想问下楼主,自己公司做ui自动化也是采用这种通过yml的方式去驱动用例执行吗?

仅楼主可见
仅楼主可见

一名刚入行测试小白,表示看不懂。。。。

purplerain 回复

用例函授必须用test开头

建议增加封装“滑屏查找 element”,比如說進入APP某個頁面,一下向下滑動,直接找到某個element,然后點擊之類的。目前看到只有swipe,但是不同屏幕尺寸下滑動屏幕后的位置不確定,導致用例的執行結果難以得到保證...

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