Appium Appium 开源分享优化版

测试小书童 · 2018年01月19日 · 最后由 Beddywang 回复于 2019年06月29日 · 8815 次阅读
本帖已被设为精华帖!

之前分享过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 和雷电模拟器
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 53 条回复 时间 点赞
思寒_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

仅楼主可见
郭丽丽 [该话题已被删除] 中提及了此贴 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日
仅楼主可见
60楼 已删除
29楼 已删除

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

61楼 已删除

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,但是不同屏幕尺寸下滑動屏幕后的位置不確定,導致用例的執行結果難以得到保證...

simple 专栏文章:[精华帖] 社区历年精华帖分类归总 中提及了此贴 12月13日 14:44
simple [精彩盘点] TesterHome 社区 2018 年 度精华帖 中提及了此贴 01月07日 12:08

楼主请教一下,一个 yaml 里只能有一个测试用例吗,如果是多个 testid 的时候,是不是要对应改代码呢?

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