PO原则:
方法意义:
- 用公共方法代表UI所提供的服务;
- 方法应该返回其他的PageObject或者返回用于断言的数据;
- 同样的行为不同的结果可以建模不同的方法;
- 不要在方法内加断言;
字段意义:
- 不要暴露页面内部元素给外部;
- 不需要建模UI内的所有元素。
主要组成元素
- Page对象:完成对页面的封装;
- Driver对象:完成对web、android、ios、接口的驱动;
- 测试用例:调用Page对象实现业务并断言;
- 数据封装:配置文件和数据驱动;
- Utils:其他功能封装,改变原生框架不足。
常用命令
adb shell "uiautomator dump && cat /sdcard/window_dump.xml" #获取当前页面布局结构
针对循环导入的方式,采用局部导入
PO改造
- 搭建PO框架,对不同页面进行封装;
- 填充代码,后续只需要更改PO中的代码,业务逻辑不需要更改;
- 对公共方法进行封装;初始化封装,复用driver
举例
企业微信添加联系人的PO封装:
- 创建数据存储文件夹datas,页面Pages,测试用例testcases;
- Page对各个页面进行封装,对于企业微信添加联系人来说,主要封装页面包括:app相关(启动、关闭、重启等),主页,联系人页面,联系人添加页面,添加页面,基本功能(driver find等)。
- datas中可以创建yaml文件存放caps中devicesName、platformName、IP等内容;
各个Page页面实现代码:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from appium.webdriver.common.mobileby import MobileBy
from app.page.addresslist_page import AddressListPage
from app.page.base_page import BasePage
class MainPage(BasePage):
"""
BasePage中封装,所以去除
"""
# def __init__(self,driver):
# self.driver = driver
## 通讯录全局使用,所以定义为全局变量
address_element = (MobileBy.XPATH, "//*[@text='通讯录']")
"""由于每次都要使用self.driver,所以需要对其进行init"""
def goto_message(self):
"""
进入到消息页
:return:
"""
pass
def goto_address(self)->AddressListPage:
"""
进入联系人页
:return:
"""
self.find_and_click(*self.address_element)
return AddressListPage(self.driver)
def goto_workspace(self):
"""
进入工作台
:return:
"""
pass
def goto_me(self):
"""
进入个人信息
:return:
"""
pass
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 通讯录界面
from appium.webdriver.common.mobileby import MobileBy
from app.page.base_page import BasePage
from app.page.member_invite_menu_page import MemberInviteMenuPage
class AddressListPage(BasePage):
# def __init__(self,driver):
# self.driver = driver
def click_addmember(self)->MemberInviteMenuPage:
"""
点击添加联系人页面:滚动查找【添加成员】
:return:
"""
self.driver.find_element(MobileBy.ANDROID_UIAUTOMATOR,
'new UiScrollable(new UiSelector()\
.scrollable(true).instance(0))\
.scrollIntoView(new UiSelector()\
.text("添加成员").instance(0));').click()
return MemberInviteMenuPage(self.driver)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
app.py 模块,存放app相关的一些操作。
比如 启动应用,重启应用,停止应用,进入到首页
"""
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import yaml
from appium import webdriver
from app.page.base_page import BasePage
from app.page.main_page import MainPage
"""使用yaml方式传入caps所需要的相关值"""
with open('../datas/caps.yml') as f:
myconfig = yaml.safe_load(f)
caps = myconfig['desirecaps']
ip = myconfig['server']['ip']
port = myconfig['server']['port']
class AppPage:
def start(self):
"""为了避免第二次安装服务影响时间,对driver进行判断,如果已经存在,不再重复启动"""
if self.driver == None:
# 启动app
# 定义了一个字典
# caps = {}
# caps["platformName"] = "Android"
# caps["deviceName"] = "hogwarts"
# caps["appPackage"] = "com.tencent.wework"
# caps["appActivity"] = ".launch.LaunchSplashActivity"
# # noReset 保留缓存, 比如登录状态
# caps["noReset"] = "True"
# # 不停止应用,直接运行测试用例
# # caps["dontStopAppOnReset"] = "true"
# caps['skipDeviceInitialization'] = 'true'
# caps['skipServerInstallation'] = 'true'
# # caps["settings[waitForIdleTimeout]"] = 0
# # 关键 localhost:4723 本机ip:server端口
self.driver = webdriver.Remote(f"http://{ip}:{port}/wd/hub", caps)
self.driver.implicitly_wait(5)
else:
self.driver.launch_app()
# self.driver.start_activity(package,activity)
return self
def restart(self):
# 重启 app
self.driver.close_app()
self.driver.launch_app()
pass
def stop(self):
# 停止 app
self.driver.quit()
def goto_main(self) -> MainPage:
# 进入到首页
return MainPage(self.driver)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
base_page.py 基类模块:主要用于初始化driver, 定义find, 常用的最基本的方法
"""
import logging
from appium.webdriver.common.mobileby import MobileBy
from appium.webdriver.webdriver import WebDriver
class BasePage:
"""
log的打印,level=logging.INFO是可变的
"""
root_logger = logging.getLogger()
print(f"root_logger.handlers:{logging.getLogger().handlers}")
for h in root_logger.handlers[:]:
root_logger.removeHandler(h)
logging.basicConfig(level=logging.INFO)
def __init__(self, driver: WebDriver = None):
self.driver = driver
def find(self, by, locator):
"""
对find_element方法进行封装
"""
# logging.info("aaaaa")
logging.info(by)
logging.info(locator)
return self.driver.find_element(by, locator)
def find_and_click(self, by, locator):
"""
在find_element方法上对click方法进行封装
"""
logging.info('click')
self.find(by, locator).click()
def find_by_scroll(self, text):
"""
对滑动寻找方式进行封装
"""
logging.info('find_by_scroll')
logging.info(text)
return self.driver.find_element(MobileBy.ANDROID_UIAUTOMATOR,
f'new UiScrollable(new UiSelector()\
.scrollable(true).instance(0))\
.scrollIntoView(new UiSelector()\
.text("{text}").instance(0));')
def get_toast_text(self):
"""
对获取toast页面的text值进行封装
"""
logging.info('get_toast_text')
result = self.find(MobileBy.XPATH, "//*[@class='android.widget.Toast']").text
logging.info(result)
return result
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
编辑联系人页面
"""
from appium.webdriver.common.mobileby import MobileBy
from selenium.webdriver.support.wait import WebDriverWait
# from app.page.member_invite_menu_page import MemberInviteMenuPage
from app.page.base_page import BasePage
class ContactAddPage(BasePage):
# def __init__(self,driver):
# self.driver = driver
"""需要将参数抽离出来"""
def add_contact(self, name, gender, phonenum):
# 设置 【用户名】【性别】【手机号】
self.find(MobileBy.XPATH,
"//*[contains(@text, '姓名')]/../*[@text='必填']").send_keys(name)
self.find(MobileBy.XPATH, "//*[contains(@text, '性别')]/..//*[@text='男']").click()
if gender == "男":
WebDriverWait(self.driver, 10).until(lambda x: x.find_element(MobileBy.XPATH, "//*[@text='女']"))
self.find(MobileBy.XPATH, "//*[@text='男']").click()
else:
self.find(MobileBy.XPATH, "//*[@text='女']").click()
self.find(MobileBy.XPATH,
'//*[contains(@text, "手机") and contains(@class, "TextView")]/..//android.widget.EditText').send_keys(
phonenum)
# 点击【保存】
self.find(MobileBy.XPATH, "//*[@text='保存']").click()
from app.page.member_invite_menu_page import MemberInviteMenuPage
return MemberInviteMenuPage(self.driver)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
邀请页面
"""
from appium.webdriver.common.mobileby import MobileBy
# from app.page.contactadd_page import ContactAddPage
from app.page.base_page import BasePage
class MemberInviteMenuPage(BasePage):
# def __init__(self,driver):
# self.driver = driver
def add_member_menual(self):
# 点击【手动输入添加】
self.find(MobileBy.XPATH, "//*[@text='手动输入添加']").click()
"""由于ContactAddPage与MemberInviteMenuPage互相调用,所以会产生循环调用的问题,此时我们采用局部调用"""
from app.page.contactadd_page import ContactAddPage
return ContactAddPage(self.driver)
用例:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from app.page.app import AppPage
class TestContact:
def setup(self):
self.app = AppPage()
self.main = self.app.start().goto_main()
def teardown(self):
self.app.stop()
def test_addcontact(self):
name = "hogwarts__004"
gender = "男"
phonenum = "13500000003"
result = self.main.goto_address().\
click_addmember().\
add_member_menual().\
add_contact(name, gender, phonenum).\
get_toast()
assert '添加成功' == result