该网站上更新文章有延迟,如需查看关于python+appium文章,请前往testhome关注我哦。https://testerhome.com/topics/27802
一、什么是Page Object模式
Page Object 模式是selenium/appium自动化测试项目开发最佳测试设计模式,它将每个页面设计成一个类class,类中包含了页面中需要使用的元素(按钮、输入框、标题等),测试用例可以通过调用类里面的方法和属性来获取到页面需要操作的元素。当页面元素位置发生变化时,Page Object模式可以通过更改类的属性,不需要修改测试用例(参考百度)。以下将Page Object模式简称为PO模式。
二、PO模式有哪些优点
减少代码的编码量和维护量
清晰而明确的业务测试流程
使页面元素、元素操作和页面业务的分离
较少重复find click 样板代码,页面元素修改不影响测试用例
易读性提高
基本的实现逻辑:基于某个页面,操作某个元素,实现某个特定的功能
三、python+appium如何实现PO模式
Webdrive.Remote(url):链接server端,配置app的参数,实现启动和退出app
find_element_by_xxx:对元素定位方法封装
click/send_keys:对页面封装,操作元素、特定数据实现特定功能
testcase:测试用例封装,组合不同功能,实现某个特定的业务
四、例子
下面使用PO模式实现微博的账号密码登录,使用环境如下:
Java version: "1.8.0_241"
Python: 3.7.3
Node: v12.19.0
Appium: v1.18.3
PyCharm: Professional 2020.2
weibo: v10.11.3
测试框架:pytest
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://www.jianshu.com/p/4f11290abe0d
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()