Appium PageObject+Python+Appium

测试小书童 · 2017年06月07日 · 最后由 程文平 回复于 2018年01月11日 · 3193 次阅读

之前发过一篇帖子开源框架 appium+python

此次主要大改了一次,代码和框架,去掉了监控闪退和性能测试监控

简介

  • 基于 appium+python3 封装的自动化测试框架

功能

  • python3
  • unittest 参数化
  • pageobject
  • 数据维护用的 YMAL
  • excel 的测试报告
  • 支持多设备 andoird 并行
  • 支持 webview

常用目录

  • Base 封装常用方法
  • Log 记录不同设备的操作用例的日志,操作失败的截图
  • PageObject 放 page
  • test 目录写测试用例
  • runner 运行入口

配置

配置 run.yaml

app: Jianshu.apk

配置 devices.yaml

- devices:  emulator-5554
  port: 4724
  config: appium --session-override  -p 4724 -bp 4734 -U  emulator-5554
  platformName: android
- devices: DU2TAN15AJ049163
  port: 4725
  config: appium --session-override  -p 4725 -bp 4735 -U  DU2TAN15AJ049163
  platformName: android

实例 - 第一次启动 app

配置用例 yaml

testinfo:
    - id: test001
      title: 第一次打开
testcase:
    - operate_type: swipeLeft
      time: 4
      element_info: android.widget.ImageView
      find_type: class_name
    - element_info: com.jianshu.haruki:id/tv_enter
      find_type: id
      operate_type: click
check:
    - element_info: com.jianshu.haruki:id/btn_login
      find_type: id

PageObject

class FirstOpen:
    '''
    kwargs: WebDriver driver, String path(yaml配置参数)
    isOperate: 操作失败,检查点就失败
    testInfo:
    testCase:
    '''

    def __init__(self, **kwargs):
        self.driver = kwargs["driver"]
        self.path = kwargs["path"]
        self.operateElement = OperateElement(self.driver)
        self.isOperate = True
        self.testInfo = getYam(self.path)["testinfo"]
        self.testCase = getYam(self.path)["testcase"]


    '''
    操作步骤
    logTest 日记记录器
    '''

    def operate(self, logTest):
        for item in self.testCase:
            result = self.operateElement.operate(item, self.testInfo, logTest)

            if not result:
                print("操作失败")
                self.isOperate = False
                break

    '''
    检查点
    caseName:函数名
    logTest 记录日志:一个手机记录单独记录一个日志
    '''

    def checkPoint(self, caseName, logTest, devices):
        result = False
        if not self.isOperate:
            print("操作失败,检查点失败")
            # return self.isOperate
        else:
            check = getYam(self.path)["check"]
            result = self.operateElement.findElement(check)  # 检查点

        countSum(result)
        countInfo(result=result, testInfo=self.testInfo, caseName=caseName, driver=self.driver, logTest=logTest, devices=devices)
        return result

test

PATH = lambda p: os.path.abspath(
    os.path.join(os.path.dirname(__file__), p)
)


class FirstOpenTest(ParametrizedTestCase):
    def testFirst(self):
        firsOpen = FirstOpen(driver=self.driver, path=PATH("../yaml/firstOpen.yaml"))
        firsOpen.operate(logTest=self.logTest)
        firsOpen.checkPoint(caseName=self.__class__.__name__, logTest=self.logTest, devices=self.devices["deviceName"])


    def setUp(self):
        super(FirstOpenTest, self).setUp()

实例 - 登录

配置 yaml

testinfo:
    - id: test0002
      title: 登录
testcase:
    - element_info: com.jianshu.haruki:id/btn_login
      find_type: id
      operate_type: click
    - element_info: com.jianshu.haruki:id/et_tel
      find_type: id
      operate_type: set_value
      text: username
    - element_info: com.jianshu.haruki:id/et_password
      find_type: id
      operate_type: set_value
      text: pwd
    - element_info: com.jianshu.haruki:id/btn_register_1
      find_type: id
      operate_type: click
    - element_info: //android.widget.ImageView[@index='0']
      find_type: xpath
      operate_type: click
check:
    - element_info: com.jianshu.haruki:id/add_subscribe
      find_type: id
    - element_info: com.jianshu.haruki:id/tab_more
      find_type: id

PageObject

class Login:
    '''
    kwargs: WebDriver driver, String path(yaml配置参数)
    isOperate: 操作失败,检查点就失败
    testInfo:
    testCase:
    '''

    def __init__(self, **kwargs):
        self.driver = kwargs["driver"]
        self.path = kwargs["path"]
        self.operateElement = OperateElement(self.driver)
        self.isOperate = True
        self.testInfo = getYam(self.path)["testinfo"]
        self.testCase = getYam(self.path)["testcase"]

    '''
    操作步骤
     logTest 日记记录器
    '''

    def operate(self, logTest):
        for item in self.testCase:
            result = self.operateElement.operate(item, self.testInfo, logTest)

            if not result:
                print("操作失败")
                self.isOperate = False
                break
    '''
    检查点
    caseName:测试用例函数名 用作统计
    logTest: 日志记录
    devices 设备名
    '''

    def checkPoint(self, caseName, logTest, devices):
        result = False
        if not self.isOperate:
            print("操作失败,检查点失败")
        else:
            check = getYam(self.path)["check"]
            result = self.operateElement.findElement(check)  # 检查点

        countSum(result)
        countInfo(result=result, testInfo=self.testInfo, caseName=caseName, driver=self.driver, logTest=logTest, devices=devices)
        return result

test

PATH = lambda p: os.path.abspath(
    os.path.join(os.path.dirname(__file__), p)
)


class LoginTest(ParametrizedTestCase):

    def testLogin(self):
        login = Login(driver=self.driver, path=PATH("../yaml/login.yaml"))
        login.operate(logTest=self.logTest)
        login.checkPoint(caseName=self.__class__.__name__, logTest=self.logTest, devices=self.devices["deviceName"])

    # def testWrongPwd(self):
    #     pass

    def setUp(self):
        super(LoginTest, self).setUp()

代码入口实例

def runnerCaseApp(devices):
    starttime = datetime.now()
    suite = unittest.TestSuite()
    suite.addTest(ParametrizedTestCase.parametrize(FirstOpenTest, param=devices)) # 引用不同的测试类
    suite.addTest(ParametrizedTestCase.parametrize(LoginTest, param=devices)) # 引用不同的测试类
    unittest.TextTestRunner(verbosity=2).run(suite)
    endtime = datetime.now()
    countDate(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), str((endtime - starttime).seconds) + "秒"

    ...


if __name__ == '__main__':
    if AndroidDebugBridge().attached_devices():
        getDevices = init()
        appium_server = AppiumServer(getDevices)
        appium_server.start_server()
        while not appium_server.is_runnnig():
            time.sleep(2)
        runnerPool(getDevices)
        appium_server.stop_server()
        writeExcel()
    else:
        print(u"设备不存在")

结果展示

日志目录

文件夹:samsung_GT-I9500_android_4.4,包含截图

2017-06-07 19:39:35,972  - INFO - ----  test001_第一次打开_android.widget.ImageView   START     ----
2017-06-07 19:39:44,433  - INFO - [CheckPoint_1]: FirstOpenTest: NG
2017-06-07 19:40:02,013  - INFO - ----  test0002_登录_com.jianshu.haruki:id/btn_login   START     ----
2017-06-07 19:40:03,075  - INFO - ----  test0002_登录_com.jianshu.haruki:id/et_tel   START     ----
2017-06-07 19:40:07,460  - INFO - ----  test0002_登录_com.jianshu.haruki:id/et_password   START     ----
2017-06-07 19:40:08,480  - INFO - ----  test0002_登录_com.jianshu.haruki:id/btn_register_1   START     ----
2017-06-07 19:40:13,640  - INFO - ----  test0002_登录_//android.widget.ImageView[@index='0']   START     ----

测试报告

开源地址点击查看

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

多谢分享

—— 来自 TesterHome 官方 安卓客户端

感谢分享,我在执行过程中 BaseApk.py 里的 output 得到是一个空值,是路径的问题吗?请问应该怎么修改呢?

矜玪 回复

我这里试过了虚拟机和其他华为手机,都是没有问题的。你可以先手动指定包名和启动类,在 runner.py 下:

_initApp["appPackage"] = getDevices[i]["appPackage"]
_initApp["appActivity"] = getDevices[i]["appActivity"]
矜玪 回复

把 err 用 gbk 解码打印出来看看你就知道原因了

谢谢,解决了,因为我 aapt 没有配环境变量😂 ,不过 appium 输出日志提示一直找不到 apk 启动的第一个 activity ,好像是因为打开以后时间太短了,我暂时把 BaseRunner.py 下

desired_caps['appActivity'] = devices["appActivity"] 

注释掉了,目前还没找到解决方法

学习了!谢谢楼主的分享!

@lose 这次改动代码结构是清晰了很多,也增加了日志记录 ,但是在用例组织这块,设计思路感觉倒退了,之前只需写 yaml 用例,无需写代码就可以执行测试,现在每条用例都得在 PageObject 写一个操作方法 py,在 test 里面写一个执行 py,这反而增加了维护难度。

lose 大神,我运行你这个框架会报错,这个错误是什么意思,改怎么解决啊,appActivity 错了吗???

独行数息 回复
  • 这样代码结果要清晰,对应相对应的测试用例,现在最新代码基本上已经和测试用例一致,从测试介绍,前置条件,操作步骤,执行结果,一一对应。后续我是想实现和测试用例无缝对接
小小测试 回复

请确定下启动类应该是:com.baiji.jianshu.account.SplashScreenActivity


lose 大神,问一下,执行你代码的时候报这个错误是什么原因?

你自己调试下,应该是创建文件有问题:

of = OperateFile(PATH("../Log/info.pickle"))
of.mkdir_file()

上面问题解决了。。。但是启动后,日志截图同时打开了 appium 就停住了(开启后)。也没有什么报错! appium 截图

  • 我是在线安装的 appium,用 AppiumServer 里面的代码启动的方式,根据配置项的config: appium --session-override -p 4724 -bp 4734 -U emulator-5554,进行启动。
  • 如果你不是在线安装,可以尝试这样配置config: node D:\app\Appium\node_modules\appium\bin\appium.js -p 4723 -bp 4733 -U JTJ4C16331013562

大神,请教下这个问题:
appium+python 切入 webview 时报错 Chromedriver: Error: An unknown server-side error occurred while processing the command. 换成原生控件登录时使用 clear 然后 send_keys 实现账号密码输入时又会先在文本框点击 2 次,在第 3 次点击时才会执行输入,导致循环输入时第二次输入参数错误
问题详情在:https://testerhome.com/topics/9830#reply1

代码入口那里写的功能是支持多设备运行吗

大神,请教一下,down 下你的代码运行,发现每次执行完一条 case 后,就会重新启动 driver。没有执行调用的 setUpClass 和 tearDownClass,这个时什么原因导致的呢

测试小书童 关闭了讨论 01月19日 21:50
测试小书童 Appium 开源分享优化版 中提及了此贴 03月14日 17:06
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册