一、什么是 Page Object 模式

Page Object 模式是 selenium/appium 自动化测试项目开发最佳测试设计模式,它将每个页面设计成一个类 class,类中包含了页面中需要使用的元素(按钮、输入框、标题等),测试用例可以通过调用类里面的方法和属性来获取到页面需要操作的元素。当页面元素位置发生变化时,Page Object 模式可以通过更改类的属性,不需要修改测试用例(参考百度)。以下将 Page Object 模式简称为 PO 模式。

二、PO 模式有哪些优点

基本的实现逻辑:基于某个页面,操作某个元素,实现某个特定的功能

三、python+appium 如何实现 PO 模式

四、例子

下面使用 PO 模式实现微博的账号密码登录,使用环境如下:

PO 模式

框架:

1、init.py 文件 — app 参数配置,实现 app 的启动和退出

# 该类主要用于开启和退出app
from appium import webdriver
from page.login_page import LoginPage

class AppStart:
    # 声明driver对象
    driver: webdriver = None

    # 使用classmethod方法,可以直接调用类中的属性和函数
    @classmethod
    def start(cls):
        caps = {
        "platformName": "Android",
                "deviceName": "U4AIUKFAL7W4MJLR",
                "platforVersion": "9",
                "appPackage": "com.sina.weibo",
                "appActivity": "com.sina.weibo.SplashActivity",
                "autoGrantPermissions": "true",
                "automationName": "UiAutomator2"
                }

        cls.driver = webdriver.Remote("http://localhost:4723/wd/hub", caps)
        cls.driver.implicitly_wait(20)
        return LoginPage(cls.driver)

    # 退出app
    @classmethod
    def quit(cls):
        cls.driver.quit()

注意:上述代码中设置的具体参数可以查找之前文章:https://testerhome.com/topics/27803

2、base_page.py 文件 — 基础类,用于封装元素定位操作

# 基础类,封装元素定位操作
from selenium.webdriver.remote.webdriver import WebDriver

class BasePage:
        # 初始化,定义driver的类型为WebDriver
    def __init__(self, driver: WebDriver):
        self.driver = driver

    # 根据id定位
    def find_element(self, locator):
        try:
        # 单个元素定位,获取到的是单个元素的位置
            return self.driver.find_element(*locator)
        except:
        # 多个相同id元素定位,获取到的是一个列表,具体某个元素的位置可以使用列表查询
            return self.driver.find_elements(*locator)

    # 根据xpath定位
    def find_xpath(self, locator):
        try:
        # 单个元素定位,获取到的是单个元素的位置
            return self.driver.find_element_by_xpath(locator)
        except:
        # 多个相同xpath元素定位,获取到的是列表,具体某个元素的位置可以使用列表查询(xpath为唯一定位,一般不需要使用到该方法)
            return self.driver.find_elements(locator)

    # 根据classname定位
    def find_classname(self, *locator):
     # classname定位使用较少,一般用于无法使用id定位和xpath定位时使用,获取到的是列表,具体某个元素的位置可以使用列表查询
        return self.driver.find_elements_by_class_name(*locator)

注意:

*locator:表示传入的参数数量是不固定的,可以传一个或多个参数
locator:表示传入的参数数量固定为一个
通过 ID 定位,传入的元素最少为两个,通过 xpath 和 classname 定位,传入的元素为一个

3、loge_page.py 文件 — 欢迎页面操作

# 欢迎页相关操作
from appium import webdriver
from selenium.webdriver.common.by import By
from page.account_login_page import AccountLoginPage
from page.base_page import BasePage

# 继承BasePage类
class LoginPage(BasePage):
    # 声明driver变量
    driver: webdriver = None
    # 隐私政策的同意按钮(根据xpath查找元素)
    _btn_agree = "/hierarchy/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.LinearLayout[2]/android.widget.LinearLayout/android.widget.TextView"
    # 账号密码登录按钮(根据id查找元素)
    _btn_account_pwd = (By.ID, "com.sina.weibo:id/tv_for_psw_login")

    # 进入账号密码登录页面
    def enter_account_login(self):
    # 调用xpath元素定位方法,点击隐私政策同意按钮
        self.find_xpath(self._btn_agree).click()
    # 调用id元素定位方法,进入账号密码登录页面
        self.find_element(self._btn_account_pwd).click()
        return AccountLoginPage(self.driver)

4、account_login_page.py 文件 — 账号密码登录页面操作,主要内容为设置账号密码输入内容

# 账号密码登录页面相关操作
import time
from selenium.webdriver.common.by import By
from page.base_page import BasePage

# 继承BasePage类
class AccountLoginPage(BasePage):
    # 账号输入框
    _et_account = (By.ID, "com.sina.weibo:id/et_pws_username")
    # 密码输入框
    _et_pwd = (By.ID, "com.sina.weibo:id/et_pwd")
    # 登录按钮
    _btn_login = (By.ID, "com.sina.weibo:id/bn_pws_Login")
    # 手机号格式错误弹框的内容(底标为0),取消按钮(底标为2),弹框立即注册按钮或国际手机号登录按钮(底标为4)
    _tv_bounced_context = "android.widget.TextView"

    # 输入账号函数
    def input_account(self, account):
    # 清空账号输入框内容
        self.find_element(self._et_account).clear()
    # 账号输入框输入内容
        self.find_element(self._et_account).send_keys(account)

    # 输入密码函数
    def input_pwd(self, pwd):
    # 清空密码输入框内容
        self.find_element(self._et_pwd).clear()
    # 密码输入框输入内容
        self.find_element(self._et_pwd).send_keys(pwd)

    # 输入账号和密码函数
    def input_account_pwd(self, account, pwd):
    # 调用输入账号函数
        self.input_account(account)
    # 调用输入密码函数
        self.input_pwd(pwd)
    # 点击登录密码
        self.find_element(self._btn_login).click()

    # 输入错误格式的手机号码获取弹框内容函数
    def get_bounced_context(self):
    # 根据classname定位,将相同的classname元素存放在列表中
        bounced = self.find_classname(self._tv_bounced_context)
    # 手机号码格式错误弹框内容再列表的第一位,列表的下表从0开始,并将弹框的内容定义为text属性
        bounced_context = bounced[0].text
        time.sleep(1)
    # 输入账号密码后点击注册按钮,该按钮的位置在列表的第三位,故下标为2
        bounced[2].click()
    # 将获取到的弹框内容返回,以便后续调用函数时能获取到弹框内容
        return bounced_context

5、account_login_testcase.py 文件 — 账号密码登录测试用例

# 手机账号密码登录测试用例
from common.init import AppStart

class TestAccountLogin:
    # 初始化
    def setup(self):
        self.accountloginpage = AppStart.start().enter_account_login()

    # 输入格式错误的账号
    def test_error_format_account(self):
    # 设置输入的账号和密码
        account = "123123231321313"
        pwd = "asdfgh"
    # 调用输入账号密码函数
        self.accountloginpage.input_account_pwd(account, pwd)
    # 使用断言判断实际结果与预期结果是否一致,左边为实际结果,右边为预期结果
        assert self.accountloginpage.get_bounced_context() == "手机格式有问题,若非中国大陆手机号码请点击国际手机登录"

    # 退出app
    def tearwodn(self):
        AppStart.quit()

以上内容有错误的地方,大家多多指正,谢谢!


↙↙↙阅读原文可查看相关链接,并与作者交流