AppUI自动化实战封装

发布时间:2026/9/1 6:30:23
AppUI自动化实战封装 AppUI自动化实战封装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-idcom.xueqiu.android:id/login_account]).send_keys(13262506790) #在登录页面输入密码 driver.find_element(By.XPATH, //*[resource-idcom.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-idcom.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-idcom.xueqiu.android:id/login_account]).send_keys(13262506790) #在登录页面输入密码 driver.find_element(By.XPATH, //*[resource-idcom.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-idcom.xueqiu.android:id/post_status]).click() #点击【发讨论】按钮进入发布讨论页面 driver.find_element(By.XPATH, //*[text发讨论]).click() #在发布讨论页面输入框中输入讨论内容 title_text 随着气候变暖今年会不会有一个寒冷的冬天 driver.find_element(By.XPATH, //*[resource-idcom.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-idcom.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进行初始化并返回创建的driverimport 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-idcom.xueqiu.android:id/login_account]).send_keys( 13262506790) self.driver.find_element(By.XPATH, //*[resource-idcom.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-idcom.xueqiu.android:id/name]) name_text name_ele.get_attribute(text) return name_text5、在修改原来测试用例采用调用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-idcom.xueqiu.android:id/name]) name_text name_ele.get_attribute(text) return name_textfrom 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-idcom.xueqiu.android:id/login_account]).send_keys( 13262506790) self.driver.find_element(By.XPATH, //*[resource-idcom.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,driverNone): 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,driverNone): 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-idcom.xueqiu.android:id/name]) name_text name_ele.get_attribute(text) return name_textfrom py_page.app import App from appium.webdriver.common.mobileby import MobileBy as By class LoginPage(App): def __init__(self,driverNone): if driverNone: self.driver self.start() else: self.driverdriver 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-idcom.xueqiu.android:id/login_account]).send_keys( 13262506790) self.driver.find_element(By.XPATH, //*[resource-idcom.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,driverNone): self.driver driver def start(self): # 初始化app if self.driverNone: 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.driverfrom 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-idcom.xueqiu.android:id/name]) name_text name_ele.get_attribute(text) return name_textfrom 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-idcom.xueqiu.android:id/login_account]).send_keys( 13262506790) self.driver.find_element(By.XPATH, //*[resource-idcom.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中的构造方法初始化一个driverNone但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,driverNone): self.driver driver def start(self): # 初始化app if self.driverNone: 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 思源_smile7、继续优化py_page使元素定位及交互从页面类中分离减少页面类中的代码量方便维护与管理1在py_page中新建base_page.py封装页面基类实现对页面元素的定位及交互页面元素的查找常用有两种形式查找单个元素查找多个元素集即封装find finds方法每个查找元素的形式包含id, xpath, class ,ACCESSIBILITY_ID四种方式故作如下封装由于BasePage中findfinds方法需要用到driver故BasePage初始化一个driver来实现调用self.driverfrom 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, encodingUTF-8) as f: datas yaml.safe_load(f) return datas修改app.py使App继承BasePage, 因此调用这个App()时会调用BasePage中的构造方法初始化一个self.driverNonefrom 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.driverNone: 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-idcom.xueqiu.android:id/name]) name_text name_ele.get_attribute(text) return name_textfrom 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-idcom.xueqiu.android:id/login_account],13262506790) self.find_and_send(By.XPATH, //*[resource-idcom.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_pageyaml_page文件夹下对应每一个页面类创建对应的yamlPagelogin_page.yamlaccount_login: login_account: [XPATH, //*[resource-idcom.xueqiu.android:id/login_account]] login_password: [XPATH, //*[resource-idcom.xueqiu.android:id/login_password]] login_button: [XPATH, //*[text登录]]main_page.yamlgoto_myPage: my: [XPATH, //*[text我的]]my_page.yamlgoto_login: account_login: [XPATH, //*[text帐号密码登录]] is_cancel: cancel: [XPATH, //*[text取消]] get_login_name: login_name: [XPATH, //*[resource-idcom.xueqiu.android:id/name]]修改对应py_page中页面类中的代码mian_page.pyfrom 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.pyfrom 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)