基础层承担了整个UI自动化框架的基础任务,后续框架开发的底层代码。
设计思路:本层主要对基本的方法如定位方法,断言基本类,混淆数据处理,驱动选择等方法提到的一个概念层,分一个主目录存放 目录设计:BasePage.py 基本类 封装基本的元素定位,元素操作类和方法 BaseAsert.py 断言类,异常处理类封装,驱动类封装等等。
1、框架中设计基础层实际是为了应对后续繁杂的业务和逻辑代码,试想如果没有基础层的处理,框架后续交互使用时大量测试人员进入到脚本开发,每个人人都在代码里面写重复的代码或者说他们内部之间协定互相引用公共类,这样对于框架的健壮性可扩展性是不强的。如果最开始框架设计时就给与一个基础层和说明,后续交互后测试人员时都引用基础层的代码即可。
2、基于pageobject的代码设计思路,需要基础层的出现,分离公共代码和其他层代码的耦合
当前的目录设计下我是写了这么几个py文件,如下
当前只是完成了这么几个基类,后续随着框架的完善,会新增部分代码
UI自动化实际大家接触的时候最开始学习到的就是元素定位,比如形如find_element_by_id()的几大定位方法,那么本文件中对这些操作都做了一次性的封装,
web端的页面对象处理需要使用到PageObject类,本类基本上处理了浏览器初始化时遇到的公共函数,以下对各个类进行代码演示
class PageObject(object):
"""
Page Object pattern.
"""
def __init__(self, driver, url=None):
"""
:param driver: `selenium.webdriver.WebDriver` Selenium webdriver instance
:param url: `str`
Root URI to base any calls to the ``PageObject.get`` method. If not defined
in the constructor it will try and look it from the webdriver object.
"""
self.driver = driver
self.root_uri = url if url else getattr(self.driver, 'url', None)
def get(self, uri):
"""
:param uri: URI to GET, based off of the root_uri attribute.
"""
root_uri = self.root_uri or ''
self.driver.get(root_uri + uri)
self.driver.implicitly_wait(4)
def add_cookies(self, cookies_dict):
self.driver.add_cookie(cookies_dict)
def get_all_cookies(self):
return self.driver.get_cookies()
def delete_all_cookie(self):
self.driver.delete_all_cookies()
def get_one_cookies(self, name):
return self.driver.get_cookie(name)
def delete_one_cookies(self, name):
self.driver.delete_cookie(name)
def get_page_source(self):
return self.driver.page_source
单独拿出get()函数来分析,此函数使用到的host+path格式的url拼接跳转,
def get(self, uri):
"""
:param uri: URI to GET, based off of the root_uri attribute.
"""
root_uri = self.root_uri or ''
self.driver.get(root_uri + uri)
self.driver.implicitly_wait(4)
接下来看一下另外一个主类Element,对元素定位进行了公共的封装,以前我再书籍中写到的定位元素封装使通过对yaml文件中形如这样的数据xpath->//button[contains(text(),‘确认支付’)],进行分割识别定位方式和定位的元素信息,而在这里换了另外一种更为简洁的方法来完成,依赖键值对的特性来完成。这里我截取了部分核心代码供参考
class Element(object):
"""
元素对象
"""
def __init__(self, timeout=5, describe="undefined", index=0, **kwargs):
self.timeout = timeout
self.index = index
self.desc = describe
if not kwargs:
raise ValueError("Please specify a locator")
if len(kwargs) > 1:
raise ValueError("Please specify only one locator")
self.kwargs = kwargs
self.k, self.v = next(iter(kwargs.items()))
if self.k not in LOCATOR_LIST.keys():
raise FindElementTypesError("Element positioning of type '{}' is not supported.".format(self.k))
def __get__(self, instance, owner):
if instance is None:
return None
Browser.driver = instance.driver
return self
def __set__(self, instance, value):
self.__get__(instance, instance.__class__)
self.send_keys(value)
@func_set_timeout(0.5)
def __elements(self, key, vlaue):
elems = Browser.driver.find_elements(by=key, value=vlaue)
return elems
def __find_element(self, elem):
"""
Find if the element exists.
"""
for i in range(self.timeout):
try:
elems = self.__elements(elem[0], elem[1])
except FunctionTimedOut:
elems = []
if len(elems) == 1:
# app.logger.info("✅ Find element: {by}={value} ".format(by=elem[0], value=elem[1]))
logger.info("✅ Find element: {by}={value} ".format(by=elem[0], value=elem[1]))
break
elif len(elems) > 1:
logger.info("❓ Find {n} elements through: {by}={value}".format(
n=len(elems), by=elem[0], value=elem[1]))
break
else:
sleep(1)
else:
error_msg = "❌ Find 0 elements through: {by}={value}".format(by=elem[0], value=elem[1])
logger.error(error_msg)
print(error_msg)
raise NoSuchElementException(error_msg)
def __get_element(self, by, value):
"""
根据传入的定位方式获取到页面对象供后续调用
"""
# selenium
if by == "id_":
self.__find_element((By.ID, value))
elem = Browser.driver.find_elements_by_id(value)[self.index]
elif by == "name":
self.__find_element((By.NAME, value))
elem = Browser.driver.find_elements_by_name(value)[self.index]
elif by == "class_name":
self.__find_element((By.CLASS_NAME, value))
elem = Browser.driver.find_elements_by_class_name(value)[self.index]
elif by == "tag":
self.__find_element((By.TAG_NAME, value))
elem = Browser.driver.find_elements_by_tag_name(value)[self.index]
elif by == "link_text":
self.__find_element((By.LINK_TEXT, value))
elem = Browser.driver.find_elements_by_link_text(value)[self.index]
elif by == "partial_link_text":
self.__find_element((By.PARTIAL_LINK_TEXT, value))
elem = Browser.driver.find_elements_by_partial_link_text(value)[self.index]
elif by == "xpath":
self.__find_element((By.XPATH, value))
elem = Browser.driver.find_elements_by_xpath(value)[self.index]
elif by == "css":
self.__find_element((By.CSS_SELECTOR, value))
elem = Browser.driver.find_elements_by_css_selector(value)[self.index]
大概对文中的实现思路则是这样的,通过获取入参数据来识别定位方式和定位元素入xpath=//button[contains(text(),‘确认支付’)],则定位方式为xpath,元素信息是//button[contains(text(),‘确认支付’)],之所以说对比我之前的思路相对更优,是在于使用到了这两个方法__get__、set。便于了获取对象后直接使用方法如这个用法
user_input = Element(id_="j_username")
page.user_input="yuanbaojun"
实际就是单个元素和一组元素定位的区别,不做概述了
这里面其他类的开发实际根据后面的实际项目开发需要来开发,比如当前能很容易想到的元素隐藏的查找类,元素等待类等等
UI自动化框架的开发最开始的切入方向就是从web端自动化开始实现的,在web端自动化时浏览器相关的操作会被经常使用到的,所以基于web自动化的特性需要对浏览器相关的操作做一次封装,BrowserPage.py文件目前是主要的封装类文件了
在这里我把Page类继承自PageObject类,让page类拥有PageObject类的属性,page类的内容有fram的切换,浏览器标签页的切换,截图等功能。这里我罗列出了部分的代码供参考
class Page(PageObject):
"""
Implement the APIs with javascript,
and selenium/appium extension APIs。
"""
def window_scroll(self, width=None, height=None):
"""
JavaScript API, Only support css positioning
Setting width and height of window scroll bar.
"""
if width is None:
width = "0"
if height is None:
height = "0"
js = "window.scrollTo({w},{h});".format(w=str(width), h=height)
self.driver.execute_script(js)
@property
def title(self):
"""
JavaScript API
Get page title.
"""
js = 'return document.title;'
return self.driver.execute_script(js)
@property
def url(self):
"""
JavaScript API
Get page URL.
"""
js = "return document.URL;"
return self.driver.execute_script(js)
def switch_to_frame(self, frame_reference):
"""
selenium API
Switches focus to the specified frame, by id, name, or webelement.
"""
self.driver.switch_to.frame(frame_reference)
def switch_to_parent_frame(self