UiAutomator2+Pytest+Allure+PO模型实现Android自动化测试_atx-agent po模式-程序员宅基地

技术标签: python  android  android studio  自动化  测试工程师  

介绍

uiautomator2 是一个可以使用Python对Android设备进行UI自动化的库。其底层基于Google uiautomator,Google提供的uiautomator库可以获取屏幕上任意一个APP的任意一个控件属性,并对其进行任意操作

环境搭建
  1. 安装JDK,请参考此文章

  2. 安装Android SDK,构建工具版本需大于24,下载并安装工具包时请注意版本,SDK配置请参考此文章

  3. 安装uiautomator2

    pip install uiautomator2	# 安装uiautomator2
    uiautomator2 version		# 查看版本
    uiautomator2 --help			# 查看帮助
    
  4. 安装查看器,用于元素定位辅助

    pip install weditor	# 安装weditor
    weditor -v			# 查看版本
    weditor --help		# 查看帮助
    

    启动查看器,在命令行输入weditorpython -m weditor,又或者创建weditor桌面快捷键weditor --shortcut,通过桌面图标运行程序

连接设备

首次连接设备会安装【ATX】和【com.github.uiautomator.test】两个软件

  1. 手机开启【开发者模式】,打开【USB调试模式】数据线连接手机,选择【传输文件】

  2. 通过cmd进入命令行页面,通过adb devices查看设备是否连接成功,adb相关命令请查看此文章

  3. 设备连接成功后打开查看器,在查看器页面点击【Connect】,出现绿色树叶表示连接成功,左侧会出现手机投屏(手机亮屏状态)
    在这里插入图片描述

  4. 通过Python脚本连接手机:

    import uiautomator2 as u2	# 导入uiautomator2库并重命名为u2
    driver = u2.connect()		# 连接手机,若电脑只连接了一部手机,则不需要设备信息
    print(driver.info)			# 打印设备信息
    

    输入如下系统信息,说明连接手机成功,就可以开始使用uiautomator2库啦

    {
          'currentPackageName': 'com.oppo.launcher', 'displayHeight': 2297, 'displayRotation': 0, 'displaySizeDpX': 360, 'displaySizeDpY': 800, 'displayWidth': 1080, 'productName': 'OnePlus9R_CH', 'screenOn': True, 'sdkInt': 30, 'naturalOrientation': True}
    
    driver = u2.connect("48fd6742")	# 若电脑连接了多部手机,则需要添加设备序列号
    
常用操作
driver = u2.connect("48fd6742")	# 连接设备
driver.screen_on()  # 点亮屏幕
driver.screen_off() # 熄屏
driver.unlock() 	# 解锁,真机测试发现密码输入页面无法定位,部分APP也有无法同步登录页面导致不能定位的情况
print(driver.app_info("com.sankuai.meituan"))   # 获取指定APP的信息
print(driver.app_list_running())# 列出所有正在运行的APP
print(driver.app_current()) 	# 获取当前打开的APP信息
print(driver.window_size()) 	# 获取屏幕大小
print(driver.device_info)   	# 获取详细的设备信息
print(driver.serial)	    	# 获取设备序列号
print(driver.wlan_ip)   		# 获取设备IP地址
driver.press("back")			# 点击返回键
driver.open_notification()  	# 打开通知栏消息页,关闭通知栏用滑屏操作
driver.open_quick_settings()    # 打开通知栏中的快速设置
driver.info.get("screen_on")	# 获取当前屏幕是否为亮起状态
driver.swipe(552,2066,552,700)  # 按绝对坐标滑动屏幕,默认滑动时长0.5s
driver.swipe_ext("up",scale=0.5)# 按方向滑动屏幕,设置滑动距离为屏幕宽度的50%,默认是90%
driver.open_url("https://www.baidu.com") # 直接调用默认浏览器并访问指定网站
driver(description="信息").click()  		# 单击短信APP
driver.double_click(0.375, 0.496)		 # 双击指定的相对坐标
driver(description="信息").long_click(1) 	# 长按短信APP,默认长按0.5s
driver(description="短信").send_keys("12")# 定位元素并输入文本
driver(description="短信").set_text("A12")# 也是定位元素并输入文本
driver(description="短信").clear_text()	# 清空输入的信息
driver(resourceId="com.sankuai.meituan:id/passport_mobile_phone").get_text()	# 获取文本内容
driver(text="美团").drag_to(0.375, 0.375,duration=1) # 将美团APP拖动到指定地点,持续时间1s,默认0.5s
driver.screenshot(r"D:\Download\test.png")			# 保存截屏到指定位置
driver.push(r"C:\Download\test.png","/sdcard/")		# 将截图推送到手机中
driver.pull("/sdcard/rider.txt","rider.txt")		# 将手机中的文件传送到电脑
driver.app_icon("com.sankuai.meituan").save(r"D:\Download\icon.png") # 获取APP图标并保存到指定位置
driver.app_start("com.sankuai.meituan",stop=True)	# 指定包名启动APP,启动前先结束应用运行状态
driver.implicitly_wait(10)  # 原生操作,隐式等待,全局有效
driver(description="天天领红包").exists()		   # 判断元素是否出现,真返回True,否返回False
driver(description="电影/演出").wait(timeout=5)		# 等待元素出现,超时时间为5s
driver(description="跑腿").wait_gone(timeout=5)	 # 等待元素消失,超时时间为5s
driver.app_wait("com.sankuai.meituan",timeout=30,front=True)		# 等待程序开始前台运行,默认超时时间20s
driver.wait_activity("com.meituan.mmp.lib.mp.MPActivity0",timeout=5) # 等待活动页加载完成,默认超时时间10s
driver.app_install(r"D:\Download\meituan.apk")	# 使用本地安装包安装
driver.app_install("http://www.meituan.com/mobile/download/meituan/android/meituan?from=new") # 在线下载安装
driver.app_uninstall("com.sankuai.meituan")		# 卸载APP
driver.app_stop("com.sankuai.meituan")			# 关闭APP
元素定位

所选元素的属性都可以用来定位,目的是通过这些属性获取唯一的定位元素,属性可以组合定位,常用的定位如下

driver(text="运动健康")			# 通过文本定位
driver(description="我的")	 # 通过描述定位
driver.xpath('//*[@text="今日特价"]')				 # 通过Xpath定位
driver(className="android.widget.ImageView")		# 通过className定位
driver(resourceId="com.sankuai.meituan:id/button")	# 通过ID定位
driver(resourceId="com.android.systemui:id/tile_label", text="省电模式")	# 通过组合定位

还可以根据层次结构中的节点进行定位,如下

# 在android.widget.GridLayout的兄弟节点中找className=android.view.View的元素
driver(className="android.widget.GridLayout").sibling(className="android.view.View")
# 在android.widget.GridLayout的子节点中找第4个className=android.view.View的元素
driver(className="android.widget.GridLayout").child_by_instance(3,className="android.view.View")
# 在android.widget.LinearLayout的子节点中找className=android.widget.TextView且文本为“骑车”的元素,allow_scroll_search=True表示允许滑动屏幕进行查找
driver(className="android.widget.LinearLayout")\
    .child_by_text("骑车",allow_scroll_search=True,className="android.widget.TextView")
# 在android.widget.FrameLayout的子节点中找className=android.view.ViewGroup且描述为“评论插图”的元素
driver(className="android.widget.FrameLayout")\
    .child_by_description("评论插图",allow_scroll_search=True,className="android.view.ViewGroup")
# 通过方向(up/down/left/right)定位元素,如下:定位并点击网易云音乐APP右边的APP
driver(text="网易云音乐").right(className="android.widget.TextView").click()
断言

通常通过判断元素是否存在,或文本信息返回是否正确来确认响应结果,如下图所示的两种情况
请添加图片描述

使用assert判断实际结果是否与预期结果一致

# 获取登录失败的提示信息
text = driver(resourceId="com.sankuai.meituan:id/passport_account_tips").get_text()
# 判断提示是否正确
assert text == "账号或密码错误,请重新输入"
# 因登录成功后才会出现成长值,所以通过查看成长值的元素是否存在来判断是否登录成功
assert driver(resourceId="com.sankuai.meituan:id/grouth_tv").exists

对于没有焦点的,显示时间有限的提示框,使用toast进行断言

tips = driver.toast.get_message()	 # 获取错误提示信息
assert "用户名或密码错误" in tips		# 判断获取的信息中是否有"用户名或密码错误"
案例演示

示例一:设置图形验证码并解锁,真机未能实现,无法进入解锁界面,可能是权限问题,用模拟器执行成功

import time
import uiautomator2 as u2

driver = u2.connect()  # 连接设备
driver(text="设置").click()  # 点击设置APP
# ↓使用节点方式定位元素
driver(className="android.widget.LinearLayout").child_by_text("安全", allow_scroll_search=True,className="android.widget.TextView").click()
driver(resourceId="android:id/title", text="屏幕锁定").click()  # 使用ID方式定位元素
# ↓使用xpath方式定位元素
driver.xpath('//*[@resource-id="com.android.settings:id/list"]/android.widget.LinearLayout[3]/android.widget.RelativeLayout[1]').click()
driver.swipe_points([(0.223, 0.658), (0.5, 0.832), (0.5, 0.652), (0.78, 0.487)], 0.2)  # 多点滑动,设置图形密码
driver(resourceId="com.android.settings:id/footerRightButton").click()  # 点击【继续】
driver.swipe_points([(0.223, 0.658), (0.5, 0.832), (0.5, 0.652), (0.78, 0.487)], 0.2)
driver(resourceId="com.android.settings:id/footerRightButton").click()  # 点击【确定】
driver(resourceId="com.android.settings:id/redaction_done_button").click()  # 点击【完成】
driver.press("power")  # 点击电源键
time.sleep(3)  # 操作太快只会熄灭点亮屏幕,所以等待3秒,让设备上锁
driver.unlock()  # 解锁操作
driver.swipe_points([(0.273, 0.728), (0.5, 0.867), (0.5, 0.728), (0.719, 0.591)], 0.2)  # 绘制图形密码
assert driver(text="安全").exists # 判断是否返回到安全页面

在这里插入图片描述

示例二:以美团APP为例,测试登录、查看订单、搜索商品相关操作,因存在大量定位元素操作,不使用框架就不便于维护,所以使用Pytest+Allure及PO模型实现此操作,结构如下图所示:
在这里插入图片描述

以登录为例,首先创建基类basepage.py文件,封装一些公共方法,比如:定位、点击、输入、清除、获取文本信息、断言等

import re

class BasePage():  # 构造函数
    def __init__(self, driver):
        self.driver = driver

    def click(self, element):  # 点击
        if str(element).startswith("com"):  # 若开头是com则使用ID定位
            self.driver(resourceId=element).click()  # 点击定位元素
        elif re.findall("//", str(element)):  # 若//开头则使用正则表达式匹配后用xpath定位
            self.driver.xpath(element).click()  # 点击定位元素
        else:  # 若以上两种情况都不是,则使用描述定位
            self.driver(description=element).click()  # 点击定位元素

    def click_text(self, element):  # 点击,根据文本定位
        self.driver(text=element).click()  # 点击定位元素

    def clear(self, element):	# 清空输入框中的内容
        if str(element).startswith("com"):  # 若开头是com则使用ID定位
            self.driver(resourceId=element).clear_text()  # 清除文本
        elif re.findall("//", str(element)):  # 若//开头则使用正则表达式匹配后用xpath定位
            self.driver.xpath(element).clear_text()  # 清除文本
        else:  # 若以上两种情况都不是,则使用描述定位
            self.driver(description=element).clear_text()  # 清除文本

    def find_elements(self, element, timeout=5):  # 找元素
        is_exited = False
        try:
            while timeout > 0:
                xml = self.driver.dump_hierarchy()  # 获取网页层次结构
                if re.findall(element, xml):
                    is_exited = True
                    break
                else:
                    timeout -= 1
        except:
            print("元素未找到!")
        finally:
            return is_exited

    def assert_exited(self, element):  # 断言元素是否存在
        assert self.find_elements(element) == True, "断言失败,{}元素不存在!".format(element)

然后创建封装相应页面操作的方法,比如登录操作,创建login_page.py文件,此层主要是封装操作流程

from base.basepage import BasePage	# 导入基类中封装的方法
import allure	# 导入allure

class LoginPage(BasePage):
    # 元素定位,元素位置信息
    agreement = "com.sankuai.meituan:id/permission_agree_btn"
    get_loc_info = "com.android.permissioncontroller:id/permission_allow_foreground_only_button"
    get_pho_perm = "com.android.permissioncontroller:id/permission_deny_button"
    notice = "暂不"
    login_in_now = "com.sankuai.meituan:id/button"
    pwd_login = "com.sankuai.meituan:id/user_password_login"
    username = "com.sankuai.meituan:id/passport_mobile_phone"
    password = "com.sankuai.meituan:id/edit_password"
    tick = "com.sankuai.meituan:id/passport_account_checkbox"
    click_login = "com.sankuai.meituan:id/login_button"
    mine = "我的"
    growth = "com.sankuai.meituan:id/grouth_tv"

    # 行为,页面操作
    def login(self,user,pwd):			# 登录流程,以首次运行APP为例
        self.click(self.agreement)		# 同意使用APP协议
        self.click(self.get_loc_info)	# 获取位置信息,选择【使用时允许】
        self.click(self.get_pho_perm)	# 获取电话权限,选择【拒绝】
        self.click_text(self.notice)	# 是否开启消息通知,选择【暂不】
        self.click(self.login_in_now)	# 点击首页的【马上登录】
        self.click(self.pwd_login)		# 登录方式选择【密码登录】
        self.input(self.username,user)	# 输入用户名
        self.input(self.password,pwd)	# 输入密码
        self.click(self.tick)			# 勾选同意用户协议
        self.click(self.click_login)	# 点击【登录】
        self.click(self.mine)			# 点击【我的】
        self.assert_exited(self.growth) # 断言,因登录成功才显示成长值,故以此元素是否出现判断是否登录成功
        self.click(self.homepage)		# 切到首页

最后传参并执行用例操作,因用Pytest框架,所以此文件注意格式,文件名需以test开头,创建test_login.py文件

import uiautomator2 as u2
import pytest
from pageobject.login_page import LoginPage	# 导入上一步操作流程中的类

@allure.story('测试美团APP')
@allure.title("APP登录")
# 连接手机并启动APP进行登录
class TestLogin:	# 类以Test开头
    def test_login(self):   # 方法以test_或_test开头
        driver = u2.connect("48fd6742")
        driver.app_start("com.sankuai.meituan", stop=True)
        login_page = LoginPage(driver=driver)
        login_page.login("16666666666","123456abc")

至此登录操作就算完成,执行测试就可以啦!

搜索操作也是相同的操作,把公共方法直接写入到基类文件中,操作流程单独创建search_page.py文件

from base.basepage import BasePage	# 导入基类,直接调用公共方法
import allure

class SearchPage(BasePage):
    # 元素定位信息
    inputfield = "com.sankuai.meituan:id/search_edit_flipper_container"
    clicksearch = "com.sankuai.meituan:id/search"
    inputvalue = "com.sankuai.meituan:id/search_edit"
    back = "com.sankuai.meituan:id/back"

    def search(self,value):					# 搜索操作流程
        self.click(self.inputfield)			# 点击搜索框
        self.clear(self.inputvalue)			# 每次搜索前线清空搜索框
        self.input(self.inputvalue,value)	# 定位输入框并输入关键字
        self.click(self.clicksearch)		# 点击【搜索】按钮
        self.click(self.back)				# 返回到上一级
        self.click(self.back)				# 返回到首页

创建传参及执行搜索操作的文件test_search.py

import uiautomator2 as u2
import pytest
from pageobject.search_page import SearchPage	# 导入上一步操作流程中的类

class TestSearch:
    keyword = ["奶茶","桌球","景点"]
    @pytest.mark.parametrize("value",keyword)	# 使用pytest参数化,搜索三个关键字
    @allure.title("搜索")
    def test_search(self,value):	
        driver = u2.connect("48fd6742")
        driver.app_start("com.sankuai.meituan")
        search_page = SearchPage(driver=driver)
        search_page.search(value)

使用allure执行测试并生成报告

pytest --alluredir=./result testcases # 执行testcases文件下所有测试用例,并将中间结果生成到result目录中
allure serve ./result	# 生成最终报告

关于Pytest、Allure的使用请查看此文章

关于PO模型请查看此文章

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Q0717168/article/details/122272374

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签