1、新建一个项目
2、在项目中写两条用例:
启动雪球-进入我的页面进入登录-登录成功后获取登录名验证是否成功登录
import time
from appium import webdriver
from appium.webdriver.common.mobileby import MobileBy as By
from appium.webdriver.common.touch_action import TouchAction
# 初始化app
desired_caps = {}
desired_caps['platformName'] = 'Android' # 'Android' or ’Ios'
desired_caps['deviceName'] = 'emulator-5554' # 设备名称,可以自定义
# desired_caps['appPackage'] = 'com.iodkols.onekeylockscreen'
# desired_caps['appActivity'] = '.OneLock'
desired_caps['appPackage'] = 'com.xueqiu.android'
desired_caps['appActivity'] = 'view.WelcomeActivityAlias'
#desired_caps['noReset'] = 'True'
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) # 类似于driver = webdriver.Chrome()
driver.implicitly_wait(10)
time.sleep(8)
#在首页进入【我的】页面
driver.find_element(By.XPATH, "//*[@text='我的']").click()
#在【我的】页面点击【账号密码登录】进入登录页面
driver.find_element(By.XPATH, "//*[@text='帐号密码登录']").click()
#在登录页面输入账号
driver.find_element(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/login_account']").send_keys('13262506790')
#在登录页面输入密码
driver.find_element(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/login_password']").send_keys('1111111a')
#在登录页面点击【登录】
driver.find_element(By.XPATH, "//*[@text='登录']").click()
# 对一键导入弹框进行判断处理
try:
cancel_ele = driver.find_element(By.XPATH, "//*[@text='取消']").click()
except:
pass
#登录后在我的页面获取登录昵称
name_ele = driver.find_element(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/name']")
name_text = name_ele.get_attribute("text")
print(name_text)
assert '思源_smile' == name_text
driver.quit()
启动雪球-进行登录-在首页进入讨论帖页面-编辑并发布帖子-进入我的页面校验帖子是否成功生成
import time
from appium import webdriver
from appium.webdriver.common.mobileby import MobileBy as By
from appium.webdriver.common.touch_action import TouchAction
# 初始化app
desired_caps = {}
desired_caps['platformName'] = 'Android' # 'Android' or ’Ios'
desired_caps['deviceName'] = 'emulator-5554'
desired_caps['appPackage'] = 'com.xueqiu.android'
desired_caps['appActivity'] = 'view.WelcomeActivityAlias'
#desired_caps['noReset'] = 'True'
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) # 类似于driver = webdriver.Chrome()
driver.implicitly_wait(10)
time.sleep(8)
#在首页进入【我的】页面
driver.find_element(By.XPATH, "//*[@text='我的']").click()
#在【我的】页面点击【账号密码登录】进入登录页面
driver.find_element(By.XPATH, "//*[@text='帐号密码登录']").click()
#在登录页面输入账号
driver.find_element(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/login_account']").send_keys('13262506790')
#在登录页面输入密码
driver.find_element(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/login_password']").send_keys('1111111a')
#在登录页面点击【登录】
driver.find_element(By.XPATH, "//*[@text='登录']").click()
#登录后进入雪球首页
driver.find_element(By.XPATH, "//*[@text='雪球']").click()
#在首页点击【编辑】按钮
driver.find_element(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/post_status']").click()
#点击【发讨论】按钮进入发布讨论页面
driver.find_element(By.XPATH, "//*[@text='发讨论']").click()
#在发布讨论页面输入框中输入讨论内容
title_text = "随着气候变暖,今年会不会有一个寒冷的冬天?"
driver.find_element(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/status_edit']").send_keys(title_text)
#点击【发布】返回首页
driver.find_element(By.XPATH, "//*[@text='发布']").click()
#进入【我的】页面
driver.find_element(By.XPATH, "//*[@text='我的']").click()
#在【我的】页面点击【讨论】进入讨论页面
driver.find_element(By.XPATH, "//*[@text='讨论']").click()
#在讨论页面获取讨论标题元素
eles = driver.find_elements(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/statusText']")
#对标题元素获取text属性存入列表
title_list = [ele.get_attribute("text") for ele in eles]
# 写个函数对结果进行断言
def assert_title():
for title in title_list:
if title == title_text:
return True
else:
return False
assert assert_title()
driver.quit()
处理用例中公共的部分app初始化,对公共部分单独封装实现模块化及分层复用
3、对初始化app代码单独封装
新建py_page包-包下新建app模块,定义一个App类,类下定义一个start()实例方法对driver进行初始化并返回创建的driver
import time
from appium import webdriver
from appium.webdriver.common.mobileby import MobileBy as By
from appium.webdriver.common.touch_action import TouchAction
class App:
def start(self):
# 初始化app
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['deviceName'] = 'emulator-5554'
desired_caps['appPackage'] = 'com.xueqiu.android'
desired_caps['appActivity'] = 'view.WelcomeActivityAlias'
self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) # 类似于driver = webdriver.Chrome()
self.driver.implicitly_wait(10)
return self.driver
在测试用例中app初始化通过from py_page.app import App及driver = App().start()来代替,即实现所有用例共用app中的初始化代码;
4、比较多个用例存在公共页面元素交互部分,把这部分元素的定位与交互单独封装,以页面为模块归类,类中方法为在该页面执行的元素交互;
from py_page.app import App
from appium.webdriver.common.mobileby import MobileBy as By
class LoginPage(App):
def __init__(self):
self.driver = self.start()
def account_login(self):
# 在登录页面进行账号密码登录
self.driver.find_element(By.XPATH, "//*[@text='我的']").click()
self.driver.find_element(By.XPATH, "//*[@text='帐号密码登录']").click()
self.driver.find_element(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/login_account']").send_keys(
'13262506790')
self.driver.find_element(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/login_password']").send_keys('1111111a')
self.driver.find_element(By.XPATH, "//*[@text='登录']").click()
from appium.webdriver.common.mobileby import MobileBy as By
class MainPage(App):
def __init__(self):
self.driver = self.start()
def goto_myPage(self):
# 在首页进入【我的】页面
self.driver.find_element(By.XPATH, "//*[@text='我的']").click()
from py_page.app import App
from appium.webdriver.common.mobileby import MobileBy as By
class MyPage(App):
def __init__(self):
self.driver = self.start()
def goto_login(self):
# 在我的页面进入【登录】页面
self.driver.find_element(By.XPATH, "//*[@text='我的']").click()
def is_cancel(self):
# 对一键导入弹框进行判断处理
try:
self.driver.find_element(By.XPATH, "//*[@text='取消']").click()
except:
pass
def get_login_name(self):
# 登录后在我的页面获取登录昵称
name_ele = self.driver.find_element(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/name']")
name_text = name_ele.get_attribute("text")
return name_text
5、在修改原来测试用例,采用调用py_page中各方法来实现测试业务的流转执行
from py_page.main_page import MainPage
class TestLogin:
def test_login_pos(self):
name_text = MainPage().goto_myPage().goto_login().account_login().is_cancel().get_login_name()
print(name_text)
assert name_text == '思源_smile'
似乎存在什么问题?
优化上述py_page保证链式调用
from appium.webdriver.common.mobileby import MobileBy as By
class MainPage(App):
def __init__(self):
self.driver = self.start()
def goto_myPage(self):
# 在首页进入【我的】页面
self.driver.find_element(By.XPATH, "//*[@text='我的']").click()
from py_page.my_page import MyPage
return MyPage()
from py_page.app import App
from appium.webdriver.common.mobileby import MobileBy as By
class MyPage(App):
def __init__(self):
self.driver = self.start()
def goto_login(self):
# 在我的页面进入【登录】页面
self.driver.find_element(By.XPATH, "//*[@text='我的']").click()
return LoginPage()
def is_cancel(self):
# 对一键导入弹框进行判断处理
try:
self.driver.find_element(By.XPATH, "//*[@text='取消']").click()
except:
pass
return self
def get_login_name(self):
# 登录后在我的页面获取登录昵称
name_ele = self.driver.find_element(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/name']")
name_text = name_ele.get_attribute("text")
return name_text
from py_page.app import App
from appium.webdriver.common.mobileby import MobileBy as By
class LoginPage(App):
def __init__(self):
self.driver = self.start()
def account_login(self):
# 在登录页面进行账号密码登录
self.driver.find_element(By.XPATH, "//*[@text='我的']").click()
self.driver.find_element(By.XPATH, "//*[@text='帐号密码登录']").click()
self.driver.find_element(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/login_account']").send_keys(
'13262506790')
self.driver.find_element(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/login_password']").send_keys('1111111a')
self.driver.find_element(By.XPATH, "//*[@text='登录']").click()
from py_page.my_page import MyPage
return MyPage()
上述代码似乎还存在问题,在测试用例的链式调用中,涉及页面跳转的每次调用页面类都会调用app初始化一个driver,而不是传递原来的driver;
初步解决方案,每个页面类在构造方法中对driver进行判断,并在涉及页面跳转的方法中return 页面类(self.driver),即把当前页面的driver作为参数传入要跳转的页面,需要引入单例设计
from py_page.app import App
from appium.webdriver.common.mobileby import MobileBy as By
class MainPage(App):
def __init__(self,driver=None):
if driver == None:
self.driver = self.start()
else:
self.driver = driver
def goto_myPage(self):
# 在首页进入【我的】页面
self.driver.find_element(By.XPATH, "//*[@text='我的']").click()
from py_page.my_page import MyPage
return MyPage(self.driver)
from py_page.app import App
from appium.webdriver.common.mobileby import MobileBy as By
class MyPage(App):
def __init__(self,driver=None):
if driver == None:
self.driver = self.start()
else:
self.driver = driver
def goto_login(self):
# 在我的页面进入【登录】页面
self.driver.find_element(By.XPATH, "//*[@text='我的']").click()
from py_page.login_page import LoginPage
return LoginPage(self.driver)
def is_cancel(self):
# 对一键导入弹框进行判断处理
try:
self.driver.find_element(By.XPATH, "//*[@text='取消']").click()
except:
pass
return self
def get_login_name(self):
# 登录后在我的页面获取登录昵称
name_ele = self.driver.find_element(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/name']")
name_text = name_ele.get_attribute("text")
return name_text
from py_page.app import App
from appium.webdriver.common.mobileby import MobileBy as By
class LoginPage(App):
def __init__(self,driver=None):
if driver==None:
self.driver = self.start()
else:
self.driver=driver
def account_login(self):
# 在登录页面进行账号密码登录
self.driver.find_element(By.XPATH, "//*[@text='我的']").click()
self.driver.find_element(By.XPATH, "//*[@text='帐号密码登录']").click()
self.driver.find_element(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/login_account']").send_keys(
'13262506790')
self.driver.find_element(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/login_password']").send_keys('1111111a')
self.driver.find_element(By.XPATH, "//*[@text='登录']").click()
from py_page.my_page import MyPage
return MyPage(self.driver)
再次执行测试用例,即可实现完整的元素定位交互以及页面跳转
6、继续优化py_page中的代码,把页面类中的构造方法封装到app中,减少代码冗余
app模块的初步实现
from appium import webdriver
from appium.webdriver.common.mobileby import MobileBy as By
from appium.webdriver.common.touch_action import TouchAction
class App:
def __init__(self,driver=None):
self.driver = driver
def start(self):
# 初始化app
if self.driver==None:
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['deviceName'] = 'emulator-5554'
desired_caps['appPackage'] = 'com.xueqiu.android'
desired_caps['appActivity'] = 'view.WelcomeActivityAlias'
#desired_caps['noReset'] = 'True'
self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) # 类似于driver = webdriver.Chrome()
self.driver.implicitly_wait(10)
return self.driver
from py_page.app import App
from appium.webdriver.common.mobileby import MobileBy as By
class MainPage(App):
def goto_myPage(self):
# 在首页进入【我的】页面
self.driver.find_element(By.XPATH, "//*[@text='我的']").click()
from py_page.my_page import MyPage
return MyPage(self.driver)
from py_page.app import App
from appium.webdriver.common.mobileby import MobileBy as By
class MyPage(App):
def goto_login(self):
# 在我的页面进入【登录】页面
self.driver.find_element(By.XPATH, "//*[@text='账号密码登录']").click()
from py_page.login_page import LoginPage
return LoginPage(self.driver)
def is_cancel(self):
# 对一键导入弹框进行判断处理
try:
self.driver.find_element(By.XPATH, "//*[@text='取消']").click()
except:
pass
return self
def get_login_name(self):
# 登录后在我的页面获取登录昵称
name_ele = self.driver.find_element(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/name']")
name_text = name_ele.get_attribute("text")
return name_text
from py_page.app import App
from appium.webdriver.common.mobileby import MobileBy as By
class LoginPage(App):
def account_login(self):
# 在登录页面进行账号密码登录
self.driver.find_element(By.XPATH, "//*[@text='我的']").click()
self.driver.find_element(By.XPATH, "//*[@text='帐号密码登录']").click()
self.driver.find_element(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/login_account']").send_keys(
'13262506790')
self.driver.find_element(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/login_password']").send_keys('1111111a')
self.driver.find_element(By.XPATH, "//*[@text='登录']").click()
from py_page.my_page import MyPage
return MyPage(self.driver)
from py_page.main_page import MainPage
class TestLogin:
def test_login_pos(self):
name_text = MainPage().goto_myPage().goto_login().account_login().is_cancel().get_login_name()
print(name_text)
assert name_text == '思源_smile'
基于上面代码修改app后原测试用例会报错,想一想这是为什么?
因为测试用例中调用MainPage()时由于继承了app,会调用app中的构造方法初始化一个driver=None,但goto_myPage()中有self.driver.find_element(By.XPATH, “//*[@text=’我的’]”).click(),然而这个driver是一个None自然没有find_element这个方法
修改方案如下
from appium import webdriver
from appium.webdriver.common.mobileby import MobileBy as By
from appium.webdriver.common.touch_action import TouchAction
class App:
def __init__(self,driver=None):
self.driver = driver
def start(self):
# 初始化app
if self.driver==None:
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['deviceName'] = 'emulator-5554'
desired_caps['appPackage'] = 'com.xueqiu.android'
desired_caps['appActivity'] = 'view.WelcomeActivityAlias'
#desired_caps['noReset'] = 'True'
self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) # 类似于driver = webdriver.Chrome()
self.driver.implicitly_wait(10)
return self
def main(self):
from py_page.main_page import MainPage
return MainPage(self.driver)
测试用例修改如下:
from py_page.main_page import MainPage
from py_page.app import App
class TestLogin:
def test_login_pos(self):
name_text = App().start().main().goto_myPage().goto_login().account_login().is_cancel().get_login_name()
print(name_text)
assert name_text == '思源_smile'
7、继续优化py_page,使元素定位及交互从页面类中分离,减少页面类中的代码量,方便维护与管理
(1)在py_page中新建base_page.py封装页面基类,实现对页面元素的定位及交互,页面元素的查找常用有两种形式:查找单个元素&&查找多个元素集,即封装find && finds方法,每个查找元素的形式包含id, xpath, class ,ACCESSIBILITY_ID四种方式故作如下封装,由于BasePage中find&&finds方法需要用到driver,故BasePage初始化一个driver来实现调用self.driver
from appium.webdriver.common.mobileby import MobileBy as By
class BasePage:
def __init__(self, driver: webdriver = None):
self.driver = driver
def find(self, by, locator):
by_locator = None
if by == 'id' or by == 'ID':
by_locator = (By.ID, locator)
elif by == 'xpath' or by == 'XPATH':
by_locator = (By.XPATH, locator)
elif by == 'class' or by == 'CLASS':
by_locator = (By.CLASS_NAME, locator)
elif by == 'ACCESSIBILITY_ID' or by == 'accessibility_id':
by_locator = (By.ACCESSIBILITY_ID, locator)
ele = self.driver.find_element(*(by_locator))
return ele
def finds(self, by, locator):
by_locator = None
if by == 'id' or by == 'ID':
by_locator = (By.ID, locator)
elif by == 'xpath' or by == 'XPATH':
by_locator = (By.XPATH, locator)
elif by == 'class' or by == 'CLASS':
by_locator = (By.CLASS_NAME, locator)
elif by == 'ACCESSIBILITY_ID' or by == 'accessibility_id':
by_locator = (By.ACCESSIBILITY_ID, locator)
eles = self.driver.find_elements(*(by_locator))
return eles
def find_and_click(self, by, locator):
ele = self.find(by, locator)
ele.click()
def finds_and_click(self, by, locator, index):
eles = self.finds(by, locator)
eles[index].click()
def finds_and_send(self, by, locator, index, text):
eles = self.finds(by, locator)
eles[index].send_keys(text)
def find_and_send(self, by, locator, text):
self.find(by, locator).send_keys(text)
def find_and_clear(self, by, locator):
self.find(by, locator).clear()
@staticmethod
def get_yaml_eles(yaml_path):
with open(yaml_path, "r", encoding="UTF-8") as f:
datas = yaml.safe_load(f)
return datas
修改app.py,使App继承BasePage, 因此调用这个App()时会调用BasePage中的构造方法,初始化一个self.driver==None
from appium import webdriver
from appium.webdriver.common.mobileby import MobileBy as By
from appium.webdriver.common.touch_action import TouchAction
from py_page.base_page import BasePage
class App(BasePage):
def start(self):
# 初始化app
if self.driver==None:
desired_caps = {}
desired_caps['platformName'] = 'Android' # 'Android' or ’Ios'
desired_caps['deviceName'] = 'emulator-5554'
desired_caps['appPackage'] = 'com.xueqiu.android'
desired_caps['appActivity'] = 'view.WelcomeActivityAlias'
#desired_caps['noReset'] = 'True'
self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) # 类似于driver = webdriver.Chrome()
self.driver.implicitly_wait(10)
return self
def main(self):
from py_page.main_page import MainPage
return MainPage(self.driver)
def stop(self):
self.driver.quit()
�py_page中其他页面类继承页面基类BasePage,使得页面类能够调用页面基类中元素定位及交互方法,后续在测试中需要用到页面元素交互的方法都可以封装在BasePage中,供页面类直接调用;
基于BasePage的封装,优化py_page中页面类的代码实现:
from appium.webdriver.common.mobileby import MobileBy as By
from py_page.base_page import BasePage
class MainPage(BasePage):
def goto_myPage(self):
# 在首页进入【我的】页面
self.find_and_click(By.XPATH, "//*[@text='我的']")
from py_page.my_page import MyPage
return MyPage(self.driver)
from appium.webdriver.common.mobileby import MobileBy as By
from py_page.base_page import BasePage
class MyPage(BasePage):
def goto_login(self):
# 在我的页面进入【登录】页面
self.find_and_click(By.XPATH, "//*[@text='帐号密码登录']")
from py_page.login_page import LoginPage
return LoginPage(self.driver)
def is_cancel(self):
# 对一键导入弹框进行判断处理
try:
self.find_and_click(By.XPATH, "//*[@text='取消']")
except:
pass
return self
def get_login_name(self):
# 登录后在我的页面获取登录昵称
name_ele = self.find(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/name']")
name_text = name_ele.get_attribute("text")
return name_text
from appium.webdriver.common.mobileby import MobileBy as By
from py_page.base_page import BasePage
class LoginPage(BasePage):
def account_login(self):
# 在登录页面进行账号密码登录
self.find_and_send(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/login_account']",'13262506790')
self.find_and_send(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/login_password']",'1111111a')
self.find_and_click(By.XPATH, "//*[@text='登录']")
from py_page.my_page import MyPage
return MyPage(self.driver)
(2)把py_page页面类中的页面元素集中管理,从代码中分离,当前端页面元素及交互变更时,我们减少对页面类中代码的修改,从数据载体中集中统一维护;
新建yaml_page,yaml_page文件夹下对应每一个页面类创建对应的yamlPage
login_page.yaml
account_login:
login_account: [XPATH, "//*[@resource-id='com.xueqiu.android:id/login_account']"]
login_password: [XPATH, "//*[@resource-id='com.xueqiu.android:id/login_password']"]
login_button: [XPATH, "//*[@text='登录']"]
main_page.yaml
goto_myPage:
my: [XPATH, "//*[@text='我的']"]
my_page.yaml
goto_login:
account_login: [XPATH, "//*[@text='帐号密码登录']"]
is_cancel:
cancel: [XPATH, "//*[@text='取消']"]
get_login_name:
login_name: [XPATH, "//*[@resource-id='com.xueqiu.android:id/name']"]
修改对应py_page中页面类中的代码
mian_page.py
from py_page.base_page import BasePage
class MainPage(BasePage):
yaml_path = './yaml_page/main_page.yaml'
yaml_datas = BasePage.get_yaml_eles(yaml_path)
def goto_myPage(self):
# 在首页进入【我的】页面
datas = self.yaml_datas['goto_myPage']
self.find_and_click(*datas['my'])
from py_page.my_page import MyPage
return MyPage(self.driver)
(3)继续优化py_page中页面类的代码,下面login_page.py中的account_login方法包含多个步骤,可用行为驱动的方式优化
login_page.py
from py_page.base_page import BasePage
class LoginPage(BasePage):
yaml_path = './yaml_page/login_page.yaml'
yaml_datas = BasePage.get_yaml_eles(yaml_path)
def account_login(self):
# 在登录页面进行账号密码登录
datas = self.yaml_datas['account_login']
self.find_and_send(*datas['login_account'],'13262506790')
self.find_and_send(*datas['login_password'],'1111111a')
self.find_and_click(*datas['login_button'])
from py_page.my_page import MyPage
return MyPage(self.driver)