MQC功能测试大揭秘(3)- Appium基础篇

上文回顾

上一篇为大家介绍了如何通过appium桌面客户端的方式来快速搭建appium环境,桌面客户端的appium版本目前为1.6.4,更新稍慢于appium项目,但目前已经支持在线更新,大家不用再有客户端版本过低的顾虑。

接下来将介绍如何使用python来开发appium功能测试脚本,包括启动、控件定位、操作、函数封装、组织用例五个部分。

启动

Appium启动时需要指定一些通用配置,统称为Desired Capabilities,具体的一些参数可以参考Appium服务器初始化参数。这里介绍一些通用的参数与一些常见的问题。

automationName

自动化测试的引擎,Appium (默认)、Selendroid、Uiautomator2。Appium使用的是UI Automator v1,相比之下UI Automator v2修复了一些v1的bug,在结构上也有一些优化。对于Android7.0以上的系统,UI Automator v1可能在查找控件时出现超时导致appium服务端报错,这时候可以考虑改用Uiautomator2。

platformName

手机操作系统。

platformVersion

手机操作系统版本。

deviceName

手机类型

app

待测app的路径

newCommandTimeout

两条appium命令间的最长时间间隔,若超过这个时间,appium会自动结束并退出app

noReset, fullReset

noReset 不要在会话前重置应用状态。默认值false。 fullReset (Android) 通过卸载而不是清空数据来重置应用状态。在Android上, 这也会在会话结束后自动清除被测应用。默认值false。

unicodeKeyboard, resetKeyboard

在输入的时候,可能出现键盘挡住控件的情况,这时候需要使用 appium 提供的输入法(支持输入多语言,没有键盘 ui ),unicodeKeyboard 为 true 表示使用 appium-ime 输入法。 resetKeyboard 表示在测试结束后切回系统输入法。

appActivity, appPackage

appActivity与appPackage指用于启动待测app的activityName与packageName,appium(1.6.4)已经支持activityName与packageName的自动检测,这两个参数已经可以省略了

appWaitActivity, appWaitPackage

appium需要等待的activityName与packageName,与appActivity不同的是,对于有启动动画的app来说,appWaitActivity应该是启动activity消失后出现的activity。这两个参数可以指定多个。

有了以上介绍的这些参数,我们可以启动appium并开始测试app了。将desired capibilities进行封装,python脚本如下:

from appium import webdriver

def get_desired_capabilities():
    desired_caps = {
        'platformName': 'Android',
        'platformVersion': '18',
        'deviceName': 'mqcDevice',
        'udid': 'a05aacaf7d53',
        'app': "D:\\appium\\alicrowdtest.apk",
        'newCommandTimeout': 60,
        'automationName': 'appium',
        'unicodeKeyboard': True,
        'resetKeyboard': True,
        'appWaitActivity': 'com.yunos.mqc.view.activity.MainActivity',
    }

    return desired_caps

def setUp():
    # 获取我们设定的capabilities,通知Appium Server创建相应的会话。
    desired_caps = get_desired_capabilities()
    # 获取server的地址。
    uri = "http://localhost:4723/wd/hub"
    # 创建会话,得到driver对象,driver对象封装了所有的设备操作。下面会具体讲。
    driver = webdriver.Remote(uri, desired_caps)
    return driver

if name == '__main__':
    driver = setUp()
    // 打印当前activity
    print driver.current_activity

控件定位

android sdk的tools目录下自带一个元素查看工具-uiautomatorviewer,通过这个工具可以获取到app的各个元素属性,辅助我们编写相关的脚本,uiautomatorviewer的界面如下:

如图为了定位 尚未登录 这个控件,我们推荐以下几种方法:

  • xpath: xpath定位效率低,但胜在定位准确,在控件没有明显的唯一特征时,xpath的优势就体现出来了。使用xpath时可以选择以控件树的最近公共祖先的节点开始生成查询路径。
driver.find_element_by_xpath("//android.widget.LinearLayout[1]/android.widget.FrameLayout[1]/android.widget.ScrollView[1]/android.widget.LinearLayout[1]/android.widget.RelativeLayout[1]/android.widget.LinearLayout[1]/1android.widget.TextView[1]")

  • id: 为了定位方便,开发一般会为部分控件添加resource-id属性,一般来说可以在一个页面中唯一确定一个控件,对于存在多个相同resource-id控件的页面,可以通过index来定位这些控件中的某一个
driver.find_element_by_id("com.yunos.mqc:id/user_nickname")
或者
driver.find_elements_by_id("com.yunos.mqc:id/user_nickname")[index]

  • text: 定位控件较为简洁清晰地一种方式,就是通过text来查找控件。
#1.5以上的版本已弃用
driver.find_element_by_name("尚未登录")
#新版appium使用xpath来实现这个功能
driver.find_element_by_xpath("//*[@text='%s']" % (text))  

操作

  • swipe: 很多App都会有引导页需要左滑,这里提供一个左滑的例子,注意,滑动速度不应太快,否则容易导致引导页滑动不成功。
window_size = driver.get_window_size()
driver.swipe(start_x=window_size["width"] * 0.9,
             start_y=window_size["height"] * 0.5,
             end_x=window_size["width"] * 0.1,
             end_y=window_size["height"] * 0.5, 500)  

  • TouchAction: touchaction可以用来实现点击坐标,滑动等操作,需要注意的是在android系统上,TouchAction的move_to函数是相对坐标。
#点击操作
TouchAction(driver).press(None, x, y).release().perform()
#滑动操作
TouchAction(driver).press(None, x, y).wait(20).move_to(None, dx, dy).release().perform()  

  • Scroll: 对于ListView这样的控件,若要滑动到某个控件位置,按照坐标滑动的方式很难适配不同分辨率的手机,这时候需要考虑使用scroll的方式进行滑动。
#从控件 el1 滑动到 el2
driver.scroll(el1, el2)  

#HOME 键
driver.keyevent(3)  

  • long_press: 利用TouchAction,可以实现长按操作:
#长按 el 控件 20s
action = TouchAction(driver)
action.long_press(el, 20000).perform()
sleep(20)
action.release().perform()  

  • hidekeyboard: 若 desiredcapbility 没有指定使用 unicodeKeyboard,在输入的时候需要注意键盘 ui 可能会挡住控件的情况,这时候使用 hidekeyboard 函数隐藏键盘
# python
driver.hide_keyboard()   

函数封装

在写脚本的时候,把一些常用的功能合理封装起来,能够大大提高脚本执行的成功率。

  • 滑动函数: 通过一组坐标点来进行滑动,从而实现一些曲线形的滑动。我们可以实现一个函数用来支持曲线滑动,输入为形如[[x1, y1],[x2, y2]]的一组坐标点。
def swipe(points):
    last_x = 0
    last_y = 0
    swipe_action = TouchAction(driver)
    for i in range(0, len(points)):
        x=points[i][0]
        y=points[i][1]
        if i == 0:
            swipe_action = swipe_action.press(None, x, y).wait(20)
        elif i == (len(points) - 1):
            swipe_action = swipe_action.move_to(None, x - last_x, y - last_y).release()
            swipe_action.perform()
        else:
            swipe_action = swipe_action.move_to(None, x - last_x, y - last_y).wait(20)
        last_x = x
        last_y = y  

  • 查找控件: 对于在调试的时候一切顺利的脚本,真正到了云测平台海量真机上测试的时候,却经常出现控件找不到导致脚本执行失败的问题。实际真机运行的时候,可能会有很多和本地并不相同的环境情况,比如网络延迟导致控件较迟刷新出来,我们应当封装一个较为稳定的控件查找函数。

appium本身有提供waitUntil的api,现在要找图中的 个人中心 控件,使用显示等待的方法如下:

from appium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait # available since 2.4.0
from selenium.webdriver.support import expected_conditions as EC 

#通过xpath的方式搜索
element1 = WebDriverWait(driver, 3).until(EC.presence_of_element_located((By.XPATH,
"//android.widget.FrameLayout[1]/android.widget.LinearLayout[1]/android.widget.FrameLayout[1]/android.widget.LinearLayout[1]/android.widget.FrameLayout[1]/android.widget.LinearLayout[1]/android.widget.TabHost[1]/android.widget.LinearLayout[1]/android.widget.TabWidget[1]/android.view.View[4]")))

#通过resource-id的方式搜索,这里底部导航的resource-id是相同,需要通过下标来区分,搜出多个elements后,需要指定需要的下标
element2 = WebDriverWait(driver, 3).until(EC.presence_of_all_elements_located((By.ID, "com.yunos.mqc:id/id_indicator_discovery")))
element2[3].click()  

我们也可以封装一个多方式定位控件的函数,这里需要自己把握超时时间

import time
from time import sleep
def wait_for_element(xpath=None, id=None, text=None, index=None, timeout=3):
    startTime = time.time()
    nowTime = time.time()
    while nowTime - startTime < timeout:
        # 通过 xpath 查找控件
        try:
            if xpath is not None:
                el = driver.find_element_by_xpath(xpath)
                return el
        except:
            pass

        # 通过 id 查找控件
        try:
            if id is not None:
                if index is not None:
                    return driver.find_elements_by_id(self.id(id))[index]
                else:
                    return driver.find_element_by_id(self.id(id))
        except:
            pass

        # 通过 text 查找控件
        try:
            if text is not None:
                return driver.find_element_by_name(text)
        except:
            pass

        sleep(1)
        nowTime = time.time()
    raise Exception("Element id[%s] text[%s]" % (id, text))  

组织用例

unittest是python的一个单元测试框架,它可以帮助我们有效组织用例,把用例的不同部分区分开来。结合已经封装好的函数,我们写一个登录的测试脚本:

# -*- coding: UTF-8 -*-
import unittest
import time
import sys

from appium import webdriver
from time import sleep
from unittest import TestCase
from appium.webdriver.common.touch_action import TouchAction
from selenium.webdriver.common.touch_actions import TouchActions

class MqcAppium(TestCase):
    #设备宽高
    global width
    global height

    def get_desired_capabilities(self):
        desired_caps = {
            'platformName': 'Android',
            'platformVersion': '18',
            'deviceName': 'mqcDevice',
            'udid': 'a05aacaf7d53',
            'app': "D:\\appium\\test.apk",
            'newCommandTimeout': 600,
            'automationName': 'appium',
            'unicodeKeyboard': True,
            'resetKeyboard': True,
            'appWaitActivity': 'com.yunos.mqc.view.activity.MainActivity',
        }

        return desired_caps

    #unittest 启动
    def setUp(self):
        desired_caps = self.get_desired_capabilities()
        uri = "http://localhost:4723/wd/hub"
        retry = 0
        while retry < 2:
            try:
                self.driver = webdriver.Remote(uri, desired_caps)
                break
            except Exception, e:
                retry += 1
                if retry == 2:
                    raise e
        sleep(10)
        # 获取当前设备分辨率
        self.window_size  = self.driver.get_window_size()
        self.width = self.window_size["width"]
        self.height = self.window_size["height"]

    # unittest 用例,用 test_**** 命名
    def test_login(self):
        #大部分app启动后会有动画,启动延迟等,视情况预留启动延迟
        sleep(5)

        #通过 resource-id 与 index 查找 个人中心 控件
        navPerson = self.wait_for_element(id="com.yunos.mqc:id/id_indicator_discovery", index=3);
        navPerson.click()

        #通过 text 查找 尚未登录
        noLogin = self.wait_for_element(xpath=("//*[@text='%s']" % ("尚未登录")));
        noLogin.click()

        #通过 xpath、resource-id 多种方式定位登录控件,避免有些手机上 xpath 失效或者不一致的情况
        inputUsername = self.wait_for_element(xpath="//android.widget.FrameLayout[1]/android.widget.LinearLayout[1]/android.widget.FrameLayout[1]/android.widget.LinearLayout[1]\
        /android.widget.FrameLayout[1]/android.widget.LinearLayout[1]/android.widget.LinearLayout[1]/android.widget.EditText[1]", id="com.yunos.mqc:id/custom_account")
        inputUsername.click()
        inputUsername.send_keys("mqc_test")

        inputPassword = self.wait_for_element(xpath="//android.widget.FrameLayout[1]/android.widget.LinearLayout[1]/android.widget.FrameLayout[1]/android.widget.LinearLayout[1]/\
        android.widget.FrameLayout[1]/android.widget.LinearLayout[1]/android.widget.LinearLayout[1]/android.widget.EditText[2]", id="com.yunos.mqc:id/custom_passwd")
        inputPassword.click()
        inputPassword.send_keys("123456")

        login = self.wait_for_element(id="com.yunos.mqc:id/custom_loginBtn")
        login.click()

        #返回 广场 并且向下滑动
        navGround = self.wait_for_element(id="com.yunos.mqc:id/id_indicator_discovery", index=0);
        navGround.click()

        #与前文 swipe 函数不同的是,为了兼容不同分辨率的手机,滑动操作应当使用 比例 而非 绝对坐标,当然,若是需要精确地滑动操作,建议使用scrollTo
        self.swipe([[0.5, 0.7], [0.5, 0.6], [0.5, 0.5], [0.5, 0.4], [0.5, 0.3]])

    def tearDown(self):
        try:
            self.driver.quit()
        except:
            pass

    def swipe(self, points):
        last_x = 0
        last_y = 0
        swipe_action = TouchAction(self.driver)
        for i in range(0, len(points)):
            x=float(points[i][0]) * self.width
            y=float(points[i][1]) * self.height
            if i == 0:
                swipe_action = swipe_action.press(None, x, y).wait(20)
            elif i == (len(points) - 1):
                swipe_action = swipe_action.move_to(None, x - last_x, y - last_y).release()
                swipe_action.perform()
            else:
                swipe_action = swipe_action.move_to(None, x - last_x, y - last_y).wait(20)
            last_x = x
            last_y = y

    def wait_for_element(self, xpath=None, id=None, index=None, timeout=3):
        startTime = time.time()
        nowTime = time.time()
        while nowTime - startTime < timeout:
            # 通过 xpath 查找控件
            try:
                if xpath is not None:
                    el = self.driver.find_element_by_xpath(xpath)
                    return el
            except:
                pass

            # 通过 id 查找控件
            try:
                if id is not None:
                    if index is not None:
                        return self.driver.find_elements_by_id(id)[index]
                    else:
                        return self.driver.find_element_by_id(id)
            except:
                pass

            sleep(1)
            nowTime = time.time()
        raise Exception("Element xpath[%s] id[%s] index[%s] not found" % (xpath, id, index))

if __name__ == '__main__':
    try: unittest.main()
    except SystemExit: pass
时间: 2024-05-21 15:49:27

MQC功能测试大揭秘(3)- Appium基础篇的相关文章

MQC功能测试大揭秘

一. 从Android自动化测试谈起 In software testing, test automation is the use of special software (separate from the software being tested) to control the execution of tests and the comparison of actual outcomes with predicted outcomes. Test automation can auto

MQC功能测试大揭秘(2)- Appium环境搭建

这章将会介绍如何搭建与安装 Appium 的开发环境,主要介绍 Windows 平台的环境搭建,mac 或 linux 需要的相关环境与 Windows 是一样的,环境搭建本身并不困难,遇到问题大家可以多做尝试. 相关依赖 Appium 是一款移动端的自动化测试开源工具,Appium 遵循以下4条设计哲学: You shouldn't have to recompile your app or modify it in any way in order to automate it. You s

MQC功能测试大揭秘(1) - 从Android自动化测试谈起

In software testing, test automation is the use of special software (separate from the software being tested) to control the execution of tests and the comparison of actual outcomes with predicted outcomes. Test automation can automate some repetitiv

【算法与数据结构】在n个数中取第k大的数(基础篇)

(转载请注明出处:http://blog.csdn.net/buptgshengod) 题目介绍           在n个数中取第k大的数(基础篇),之所以叫基础篇是因为还有很多更高级的算法,这些以后再讨论.本文用两种最基本的方法来解决这个问题.使用java语言描述.例子是十个数中取第三大的. 算法一              用冒泡法将n个数从大到小排序,再取第k大. public class test { public static void main(String []args) { i

360黑匣子之谜,奇虎360“癌”性基因大揭秘

核心提示: 360到底怎么了?这是一家什么样的企业?带着这样的疑问,<每日经济新闻>记者经过数月调查,并在微博名人"独立调查员"等一批程序"猿"的帮助下,揭开了360的层层内幕. 360黑匣子之谜:奇虎360"癌"性基因大揭秘 每经记者 秦俑 昨日(2月25日),正是奇虎360所有APP产品被苹果全面下架一个月的日子. 就在此前,360的CFO亲赴美国"负荆请罪",但360相关产品并未重新上架. 知情人士向 <

360黑匣子之谜:奇虎360“癌”性基因大揭秘

来源:每日经济新闻 核心提示: 360到底怎么了?这是一家什么样的企业?带着这样的疑问,<每日经济新闻>记者经过数月调查,并在微博名人"独立调查员"等一批程序"猿"的帮助下,揭开了360的层层内幕. 360黑匣子之谜:奇虎360"癌"性基因大揭秘 每经记者 秦俑 昨日(2月25日),正是奇虎360所有APP产品被苹果全面下架一个月的日子. 就在此前,360的CFO亲赴美国"负荆请罪",但360相关产品并未重新上架.

吴锋:打造人气博客大揭秘(上)

中介交易 SEO诊断 淘宝客 云主机 技术大厅 引导语:关于打造人气博客的文章随意搜索下,搜索结果不下千万篇(百度搜索结果为10,200,000篇),同样是打造人气博客主题,因为讲述的角度不同,读者应该都有不同的收获吧.今天咱们分享主题侧重于个人独立博客,而且局限网络推广领域,要说网络推广领域的人气博客,应数卢松松和牟长青吧,卢松松博客凭着博客的评论互动获得极高人气,是把简单网络推广做到极致的典范,也再次验证了网络推广最重要是执行力;牟长青博客更多凭原创内容分享取胜,从而获得草根们的青睐. 一直

吴锋:打造人气博客大揭秘(下)

中介交易 SEO诊断 淘宝客 云主机 技术大厅 引导语:前段花了2整天时间写了一篇<打造人气博客大揭秘(上)>,在A5.Chinaz.推一把等地方发布,甚受欢迎,很荣幸被A5推荐到正头条,在这里感谢A5,看过该篇文章的同学都知道是关于博客优化以及增强博客互动等基础建设方面内容,所以自己感觉还得写一篇运营推广篇.最近走亲戚.朋友聚会忙得不也乐乎,今天是年初六有点空闲,没事得动动笔了. 一个人气博客能取得成功它的因素是综合的,但"内容为王",有优质的内容,就"酒香不怕

JAVA程序员必读:基础篇(3)

程序|程序员 JAVA程序员必读:基础篇时间:2001/09/13 13:31作者:ZSC 太平洋网络学院 2.2什么是消息 软件对象之间进行交互作用和通讯是利用消息的. 单一的一个对象通常不是很有用的.相反,一个对象通常是一个包含了许多其它对象的更大的程序或者应用程序.通过这些对象的交互作用,程序员可以获得高阶的功能以及更为复杂的行为.你的自行车如果不使用它的时候,它就是一堆铝合金和橡胶,它没有任何的活动.而只有当有其它的对象来和它交互的时候才是有用的. 软件对象与其它对象进行交互与通讯是利用