APP自动化实战之原生app测试

本文介绍了如何对混合型APP进行原生自动化测试,重点讲解了使用Uiautomator2和Appium进行定位,PO设计的区别,以及处理滑动操作以找到隐藏元素的方法。涉及登录失败场景、菜单导航和题库选择的自动化测试步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

介绍

这次我们要测试的app其实严格来说是一个混合型的APP,因为它里面嵌套了webview。但这里我们先不去测webview的场景,而是测全原生的测试场景,所以先把它当做是一款原生型的app。
对于原生型app,定位时可以全程使用原生app开发者工具:uiautomator2级appium inspector进行定位。

框架设计

在框架设计上,app自动化测试与web自动化测试并无差别,仍然可以沿用web自动化测试的框架,其根本原因在于appium本来就是继承自selenium的。

PO设计

在PO设计上,app自动化测试与web测试相比就有些差别了。
我们知道,web自动化测试中的PO是以域名(不包括后面的参数)来划分页面的。比如说登录页是一个页面,登录成功后跳转到个人中心页是另一个页面。但app自动化测试就不一样了,它是根据功能来划分页面的。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
举个例子,像上面的三个页面,第一个是未登录的个人中心页,第二个是登录页,第三个是已登录的个人中心页,它们的功能都是一致的,都是用户管理的相关功能,且都在我的柠檬这一栏中。所以这三个看似不同的页面实则可以归到同一个PO中。

代码实战

通用页

在app中,我们可以发现下方有三个菜单,分别是:首页、题库、我的柠檬,所有的页面都是以这三个菜单为起点展开的,进入任一个页面,都需要点击这三个菜单中的其中一个。所以,这里我们可以把这三个菜单的切换封装到一个PO中。
在这里插入图片描述

  • navpage.py
from common.basepage import BasePage
from middleware.page.userpage import Userpage
from middleware.page.homepage import Homepage
from middleware.page.exampage import Exampage
from appium.webdriver.common.mobileby import MobileBy

class Navpage(BasePage):
    #元素定位
    home_locator = (MobileBy.ID,"com.lemon.lemonban:id/navigation_home")
    examination_locator = (MobileBy.ID,"com.lemon.lemonban:id/navigation_tiku")
    my_lemon_locator = (MobileBy.ID, "com.lemon.lemonban:id/navigation_my")

    #点击'主页'按钮
    def click_home_btn(self):
        self.click(self.home_locator)
        return Homepage(self.driver)

    #点击'题库'按钮
    def click_examation_btn(self):
        self.click(self.examination_locator)
        return Exampage(self.driver)

    #点击'我的柠檬'按钮
    def click_mylemon_btn(self):
        self.click(self.my_lemon_locator)
        return Userpage(self.driver)

登录失败用例

场景分析

如下,首先进入app后,我们是在首页页面的。接着切换至‘我的柠檬’页:
在这里插入图片描述
在‘我的柠檬’页之后,点击‘点击头像登录’按钮:
在这里插入图片描述
接着跳转至登录页,输入空的用户名、密码,点击登录,弹出toast,验证toast中的文案是否符合预期:
在这里插入图片描述

测试步骤

1.在首页中,点击’我的柠檬‘按钮
2.在我的柠檬页中,点击‘点击头像登录’按钮
3.在’登录页’,输入用户名、密码,点击登录按钮
4.定位toast弹框中的字符

前置条件

在登录失败的场景中,业务上来说是没有前置条件的,但代码层面来说,前置条件就是初始化driver。

  • conftest.py
import pytest
from appium import webdriver

# 定义测试夹具
@pytest.fixture()
def driver():
    """前置条件"""
    #准备desired_capabilities
    caps = {
        "platformName": "Android",
        "deviceName": "emulator-5554",
        "appPackage": "com.lemon.lemonban",
        "appActivity": ".activity.MainActivity",
        "automationName": "UiAutomator2",
    }
    #初始化客户端
    driver = webdriver.Remote(
        command_executor='http://127.0.0.1:4723/wd/hub',
        desired_capabilities=caps,
    )
    #隐性等待
    driver.implicitly_wait(10)
    yield driver
    """后置条件"""
    driver.quit()

代码

  • userpage.py
from appium.webdriver.common.mobileby import MobileBy
from common.basepage import BasePage
class Userpage(BasePage):
    #元素定位
    head_to_login_btn_locator = (MobileBy.ID,"com.lemon.lemonban:id/fragment_my_lemon_avatar_layout")
    username_locator = (MobileBy.ID,"com.lemon.lemonban:id/et_mobile")
    password_locator = (MobileBy.ID,"com.lemon.lemonban:id/et_password")
    login_btn_locator = (MobileBy.ID,"com.lemon.lemonban:id/btn_login")
    toast_locator = (MobileBy.XPATH,"//android.widget.Toast")

    #点击'点击头像登录'按钮
    def click_head_to_login_btn(self):
        self.click(self.head_to_login_btn_locator)
        return self

    #点击'登录'按钮
    def click_login_btn(self,username,password):
        self.write(self.username_locator,username)
        self.write(self.password_locator,password)
        self.click(self.login_btn_locator)
        return self

    #获取登录失败的toast文案
    def get_login_error_meg(self):
       toast = self.wait_element_presence(self.toast_locator)
       return toast.text

  • test_login.py
import conftest
from middleware.page.navpage import Navpage
from data import login_data
import pytest

login_success_data = login_data.login_success
login_failed_data = login_data.login_failed

class TestLogin():
    #登录成功用例
    @pytest.mark.parametrize("test_data",login_success_data)
    def test_success(self,driver,test_data):
        """
        测试步骤:
        1.在首页中,点击‘我的柠檬’按钮
        2.在‘我的柠檬’页,点击‘点击头像登录’按钮
        3.输入用户名、密码,点击登录按钮
        """
        actual_result = Navpage(driver).\
            click_mylemon_btn().\
            click_login_btn(test_data["username"],test_data["password"])

    #登录失败用例
    @pytest.mark.parametrize("test_data", login_failed_data)
    def test_failed(self,test_data,driver):
        """
        测试步骤:
        1.在首页中,点击’我的柠檬‘按钮
        2.在我的柠檬页中,点击‘点击头像登录’按钮
        3.在'登录页',输入用户名、密码,点击登录按钮
        4.定位toast弹框中的字符
        """

        actual_result = Navpage(driver).\
            click_mylemon_btn().\
            click_head_to_login_btn().\
            click_login_btn(username=test_data["username"],password=test_data["password"]).\
            get_login_error_meg()

        assert actual_result == test_data["expected"]

进入题库用例

场景分析

进入题库,首先得先登录:
在这里插入图片描述
接着点击题库,选择“逻辑思维题”
在这里插入图片描述
进入题库详情页后,验证title是否符合预期:
在这里插入图片描述

测试步骤

1.登录
2.首页中,点击‘题库’按钮
3.进入题库列表页,点击其中一个题库
4.进入题库详情页,验证title

前置条件

进入题库前,有一个前置条件,就是登录。这里登录的相关操作应该写到conftest.py中,这是pytest中专门负责测试夹具的一个模块。
在登录函数中,我们直接调用userpage中的登录相关方法进行登录,最终要返回一个driver对象,因为在要调用登录前置条件的测试用例中,调用PO需要传入driver对象。

#登录
@pytest.fixture()
def login(driver):
    user_data = Handler.yaml["user"]
    name = user_data["mobile_phone"]
    pwd = user_data["pwd"]
    Navpage(driver).\
        click_mylemon_btn().\
        click_head_to_login_btn().\
        click_login_btn(name,pwd)
    yield driver

问题:要找的元素在页面下方需要滑动怎么办?

在选择逻辑思维题库的时候,遇到了个问题。
一开始进入题库的时候,是无法找到逻辑思维题库的,需要滑动至下方才能找到:
在这里插入图片描述

在这里插入图片描述
这里就有个问题了,怎么通过滑动屏幕来寻找元素?
可以想到的是,我们可以通过driver的swipe方法进行滑动,然后再定位元素。接下来又有两个问题:

  • 往那边划?
  • 划多少次才能找到元素?
    对于第一个问题很好回答,元素是在下方,所以应该往上滑动屏幕。
    对于第二个问题,如果我滑动一次就能找到元素,那当然是最理想的。但真实情况往往是页面很长,需要滑动多次才能找到元素。那么该如何设置滑动的次数,以及找到元素后停止滑动呢?可以写一个循环,循环中写上滑动及寻找元素的相关代码,当满足某个条件后,停止滑动。那么这里的终止滑动的条件应该是什么呢?这里提供两个方案:一个是按时间来滑动,当达到某个时间就退出循环;另一个是按次数来滑动,达到某个次数就退出循环,这两个方案都是可以的。这里说下按时间来滑动的方案。
    按时间来滑动的话,我们可以设置一个超时时间,达到超时时间后就退出循环,不再滑动,然后设置滑动一次所需要的时间,每次滑动后都加上这个时间。当总的滑动时间小于超时时间时,则继续滑动;大于超时时间时,则找不到元素,抛出Timeout异常:
     #选择其中一个题库
     def click_exam(self,name,timeout=10,poll=0.2):
         """
         设置一个循环,如果在超时时间内没有找到元素,则会一直上滑并寻找含有该题库名的元素;
         如果超过了超时时间,则抛Timeout异常
         """
         used_time = 0
         while used_time < timeout:
             try:
                 #这里重新设置隐性等待是因为如果该元素如果没找到,则需要隐性等待10秒,这10秒是没必要等的,所以重新设一个较短的等待时间
                 self.driver.implicitly_wait(0.2)
                 element = self.find_element((MobileBy.XPATH,f"//*[contains(@text, '{name}')]"))
                 #在找到了元素之后,再把隐性等待重置回原来的值
                 self.driver.implicitly_wait(10)
                 element.click()
                 #由于点击题库后跳转的下个页面还是在本页面中,所以返回self
                 return self
             #如果find_element()没找到,就会抛出timeout异常,所以这里也要捕获timeout异常,当没找到元素时,执行except下的代码
             except TimeoutError as e:
                #没有找到元素就进行滑屏,因为说不定要定位的元素就在屏幕下方
                self.swipe_up()
                #滑动之后等待一下
                time.sleep(poll)
                used_time += poll
         #如果超过超时时间还是找不到元素,则抛出Timeout,异常
         raise TimeoutError("找不到题库")

代码

  • exampage.py
from common.basepage import BasePage
from appium.webdriver.common.mobileby import MobileBy
import time

class Exampage(BasePage):

     #定位元素
     title_name_locator = (MobileBy.ID,"com.lemon.lemonban:id/category_title")

     #选择其中一个题库
     def click_exam(self,name,timeout=10,poll=0.2):
         """
         设置一个循环,如果在超时时间内没有找到元素,则会一直上滑并寻找含有该题库名的元素;
         如果超过了超时时间,则抛Timeout异常
         """
         used_time = 0
         while used_time < timeout:
             try:
                 #这里重新设置隐性等待是因为如果该元素如果没找到,则需要隐性等待10秒,这10秒是没必要等的,所以重新设一个较短的等待时间
                 self.driver.implicitly_wait(0.2)
                 element = self.find_element((MobileBy.XPATH,f"//*[contains(@text, '{name}')]"))
                 #在找到了元素之后,再把隐性等待重置回原来的值
                 self.driver.implicitly_wait(10)
                 element.click()
                 #由于点击题库后跳转的下个页面还是在本页面中,所以返回self
                 return self
             #如果find_element()没找到,就会抛出timeout异常,所以这里也要捕获timeout异常,当没找到元素时,执行except下的代码
             except TimeoutError as e:
                #没有找到元素就进行滑屏,因为说不定要定位的元素就在屏幕下方
                self.swipe_up()
                #滑动之后等待一下
                time.sleep(poll)
                used_time += poll
         #如果超过超时时间还是找不到元素,则抛出Timeout,异常
         raise TimeoutError("找不到题库")

     #获取title
     def get_title(self):
         return self.find_element(self.title_name_locator).text

  • test_exam.py
import conftest
from middleware.page.navpage import Navpage
from data.exam_data import logic_exam
import pytest

class TestExam():
    @pytest.mark.parametrize("test_data",logic_exam)
    def test_exam(self,login,test_data):
        """
        测试步骤:
        1.登录
        2.首页中,点击‘题库’按钮
        3.进入题库列表页,点击其中一个题库
        4.进入题库详情页,验证title
        """
        driver = login
        actual_result = Navpage(driver).\
            click_examation_btn().\
            click_exam(test_data["exam_type"]).\
            get_title()

        assert actual_result == test_data["except"]

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值