完整代码见:
https://github.com/zx490336534/selenium-po/blob/master/selenium_po/elementoperator.py
在初始化的时候,完成Yaml文件的读取与解析
file_name
对应的是Yaml文件中的pageName
class ElementOperator:
def __init__(self, path=None, file_name=None):
"""
:param path: 元素定位yaml文件
:param file_name: 页面名
"""
self.path = path
self.file_name = file_name or os.path.splitext(os.path.split(path)[-1])[0]
self.data_dict = self._parse_yaml() # 读取Yaml文件内容
self._locator_map = self.read_yaml() # 页面元素定位解析
使用os
模块切割文件夹路径,也就是如果没有传入file_name的时候默认pageName
就是这个文件名
>>> path = r"/Users/zhongxin/gitproject/wytest/report/demo/home.yaml"
>>> os.path.split(path)
('/Users/zhongxin/gitproject/wytest/report/demo', 'home.yaml')
>>> os.path.split(path)[-1]
'home.yaml'
>>> os.path.splitext(os.path.split(path)[-1])
('home', '.yaml')
>>> os.path.splitext(os.path.split(path)[-1])[0]
'home'
读取yaml文件,如果出现问题那就返回一个空的字典
因为该类主要功能不是操作yaml,所以在函数前面增加了一个下划线_
。表示它名义上是个私有的函数
def _parse_yaml(self):
"""
读取Yaml文件内容
:return:
"""
data_dict = {}
try:
with open(self.path, 'r+', encoding='utf-8') as f:
data_dict = yaml.load(f, Loader=yaml.FullLoader) or {}
except Exception as e:
raise Exception(e)
finally:
return data_dict
使用硬编码的方式去读取指定格式的yaml文件中的元素定位语句,并使用Locator
将他们实例化后存入locator_map
这个字典中
def read_yaml(self):
"""
页面元素定位解析
:return:
"""
pages_list = self.data_dict["pages"]
locator_map = dict()
for page in pages_list:
page_name = page["page"]["pageName"]
page_desc = page["page"]["desc"]
locator_map[page_name] = dict()
locators_list = page["page"]["locators"]
for locator in locators_list:
by_type = locator["type"]
element = locator["value"]
wait_sec = int(locator.get("timeout", 3))
locator_name = locator["name"]
desc = f"{page_desc}_{locator['desc']}"
tmp = Locator(element, wait_sec, by_type, locator_name, desc)
locator_map[page_name][locator_name] = tmp
return locator_map
从yaml解析并转换后的字典中拿到指定的元素定位Locator对象
def get_locator(self, locator_name):
locator = self._locator_map.get(self.file_name)
if locator:
locator = locator.get(locator_name)
return locator
为了通过「实例名称.属性名」的方式来拿到元素定位信息,需要实现__getatter__
魔术方法
__getatter__
** 函数:如果在实例以及对应的类中**查找属性失败, 那么会调用到类的__getatter__
函数
为了防止重复调用出现「Fatal Python error: Cannot recover from stack overflow. Python runtime state: initialized」问题,在首行判断一下_locator_map
和file_name
有没有存在dir(self)
中
def __getattr__(self, item):
if "_locator_map" in dir(self) and "file_name" in dir(self):
if item in self._locator_map[self.file_name]:
locator = self.get_locator(item)
if locator:
return locator
else:
return self[item]
def find_element(self, locator):
self.wait_for(locator.wait_sec)
web_ele = self._get_element(locator)
return web_ele
implicitly_wait:隐式等待 当使用了隐式等待执行测试的时候,如果 WebDriver没有在 DOM中找到元素,将继续等待,超出设定时间后则抛出找不到元素的异常。也就是说当查找元素或元素并没有立即出现的时候,隐式等待将等待一段时间再查找 DOM,默认的时间是0。一旦设置了隐式等待,则它存在整个 WebDriver 对象实例的声明周期中,隐式的等到会让一个正常响应的应用的测试变慢,它将会在寻找每个元素的时候都进行等待,这样会增加整个测试执行的时间。
def wait_for(self, wait_sec):
self.driver.implicitly_wait(wait_sec)
由于隐式等待部分版本的driver可能会不支持,容易出现不太稳定的问题,所以采用显式等待的方式,并加下亿点点细节
raise
抛出异常height_light
对元素进行高亮标记def _get_element(self, locator):
start_time = time.time()
while True:
try:
value_text = locator.desc
except:
value_text = ""
try:
locator_t = self._get_locator_tuple(locator)
web_element = self.driver.find_element(*locator_t)
try:
self.height_light(web_element)
except Exception:
pass
return web_element
except NoSuchElementException as n:
time.sleep(0.5)
if time.time() - start_time >= self.time_out:
raise NoSuchElementException(f"{self.time_out}秒后仍没有找到元素「{value_text}」:{n}")
except WebDriverException as w:
time.sleep(0.5)
if time.time() - start_time >= self.time_out:
raise WebDriverException(f"{self.time_out}秒后浏览器仍异常「{value_text}」:{w}")
except Exception as e:
raise Exception(f"查找元素异常:{e}")
效果如下
def height_light(self, element):
"""
元素高亮
:param element:
:return:
"""
self.driver.execute_script("arguments[0].setAttribute('style',arguments[1]);",
element, "border:2px solid red;")
https://docs.qameta.io/allure/#_steps_5
在Allure的官方文档中可以看到,使用pytest+allure可以在函数的头部获取入参的内容
import allure
@allure.step('Step with placeholders in the title, positional: "{0}", keyword: "{key}"')
def step_with_title_placeholders(arg1, key=None):
pass
根据这个语法,我们将元素的操作步骤写到函数的头部
比如我现在要判断一个元素是否存在
@allure.step("查看「{locator}」是否存在")
def has_element(self, locator):
ret = False
try:
self.find_element(locator)
ret = True
except Exception:
pass
return ret
具体效果:
with allure.step("查看登录名是否存在"):
assert login.has_element(login.user_info_name), '登录名不存在'
同理,封装一下send_keys
、 click
等常用元素操作方法
@allure.step("往「{locator}」输入「{msg}」")
def input(self, locator, msg, clear=True):
ele = self.find_element(locator)
if clear:
ele.clear()
try:
ele.send_keys(msg)
except Exception as e:
logger.error(f"往「{locator}」输入「{msg}」失败:{e}"
其他封装及完整代码见
https://github.com/zx490336534/selenium-po/blob/master/selenium_po/elementoperator.py
到此,UI框架的元素操作部分已经完成了
不过为了让它从demo阶段变成一个更好用的框架,还有亿点点细节需要补充