Python PageFactory-使用配置文件动态生成页面PageObject
需求
在Python Selenium 的 PageObject模式中,一般每个页面需要写一个类, 一种PageObject的写法如下:
class BaiduPageObject(object):
def __init__(self, driver): # 一般通过继成BasePage类实现该方法
self.driver = driver
# 页面元素 -----
search_ipt_loc = ('id', 'kw')
search_btn_loc = ('id', 'su')
# 页面操作 -----
def input_search_ipt(self, keyword):
search_ipt = self.driver.find_element(*search_ipt_loc)
search_ipt.clear()
search_ipt.send_keys(keyword):
def click_search_btn(self):
self.driver.find_element(*search_btn_loc).click()
def search(self, keyword):
self.input_search_ipt(keyword)
self.click_search_btn()
对于一个成型的UI项目来说, 在编写用例前, 动辄要先编写数十上百个这种PageObject类. 鉴于每个PageObject类的格式相似, 可以采用
Page Factory(页面工厂)+配置文件(yaml) -> PageObject
的方式来动态生成每一个页面对象
PageFactory本是Selenium Java SDK中的一种基于PageObject的设计模式, 使用
@FindBy(id = "kw")语法糖来标记页面元素, 并支持使用@CacheLookup来缓存页面元素(这样元素只需要查找一次)
完整介绍: https://github.com/SeleniumHQ/selenium/wiki/PageFactory
鉴于 Python的动态特性, 这里的Page Factory的实现方式与Selenium Java SDK中的完全不同
设计与实现
首先设计及约定页面配置文件的格式如下:
# filename: baidu.yaml
url: https://www.baidu.com # 可选字段, 用于打开网页
title: 百度一下,你就知道 # 可选字段, 用于判断是否在当前网页
elements: # 页面元素
search_ipt: [id, kw] # 支持id, name, class name, tag name, link text
search_btn: [id, su] # partial link text, xpath, css selector
actions: # 可选字段, 页面操作
search:
input: [search_ipt, 默认值] # input: 固定字段, 第一参数为上面的元素, 第二个为输入的文本
click: search_btn # click: 固定字段, 参数为上面配置的元素
page_factory.py
# page_factory.py
from types import MethodType # 用于为实例动态绑定方法
from collections import OrderedDict # 有序字典, 用于按顺序遍历actions中的操作步骤
import yaml
from selenium import webdriver
class Page(object):
def __init__(self, driver, from_yaml=None, **kwargs):
self.driver = driver
self.url = kwargs.get('url', None)
self.title = kwargs.get('title', None)
self.elements = kwargs.get('elements', {})
self.actions = kwargs.get('url', OrderedDict())
if from_yaml is not None:
self.init_page(from_yaml)
def init_page(self, from_yaml):
with open(from_yaml, encoding='utf-8') as f:
data = yaml.load(f)
self.url = data.get('url', None)
self.title = data.get('title', None)
self.elements = data.get('elements', {})
self.actions = OrderedDict(data.get('actions', {}))
def open(self): # 打开页面url
if self.url is not None:
self.driver.get(self.url)
def on_page(self): # 通过title判断是否在当前页面上
if self.title is not None:
return self.title in self.driver.title
def input(self, element, text): # 输入操作, 指定配置中的元素名及文本
element = self.__getattr__(element) #
element.clear()
element.send_keys(text)
def click(self, element): # 点击操作, 指定元素名
element = self.__getattr__(element)
element.click()
def _handle_element(self, item): # 处理要查找的页面元素
element_loc = self.elements.get(item, None)
if element_loc is not None:
element = self.driver.find_element(*element_loc) # todo try
setattr(Page, item, element) # 缓存元素作为页面属性
return element
def _handle_action(self, item): # 处理要查找的操作方法
action = self.actions.get(item, None)
if action is not None:
def pack_action(self, text=None): # 解析约定的action格式, 组装操作方法
for step, params in action.items():
if step == 'input':
if text is not None and isinstance(params, list) and len(params)>1:
params[1] = text # 使用调用时的text参数替换input: [search_ipt, $keyword]的第2个参数
self.input(*params)
elif step == 'click':
self.click(params)
return MethodType(pack_action, self) # 为实例绑定操作方法并返回
def __getattr__(self, item): # 查找页面属性和操作方法
if not hasattr(Page, item): # 如无该属性/方法则动态为页面添加元素属性和操作方法
return self._handle_element(item) or self._handle_action(item)
if __name__ == '__main__': # 使用方法
driver = webdriver.Chrome()
baidu = Page(driver, 'baidu.yaml')
baidu.open()
baidu.search('临渊')
TODO
- 异常捕获
- 单url框架页面的加载, 每个action的返回页面, 页面之间的流转
- 缓存页面对象, 比如不同用例中多次调用baidu = PageFactory(driver, ...), 使用同一个退休
- 增加更多的action支持, 如select, hover, switch等等
- 独立模板解析parser方法, 支持更多的变量, 如
$1, $2/$keyword, $func(a)等 - 为Page增加hooks方法让使用更灵活
PS:
使用该Page Factory(页面工厂)方法暂时还没有减轻, 数十上百的页面编写工作, 只是从原来的编写Python类改为编写yaml配置文件, 然而,静态的配置文件相对与Python类来说, 有以下好处:
- 实现了数据(页面元素定位器)与代码的分离
- PageFactory支持传入字典格式的配置参数来生成页面, 数据可以来源于yaml文件,也可以来源于页面表单/数据库,甚至爬虫爬取, 便于集成前端web页面, 或爬虫自动生成Pages
Python PageFactory-使用配置文件动态生成页面PageObject的更多相关文章
- 动态生成页面(一)——ASP.NET中Literal使用
在页面中加入内容时,假设是静态内容.无需使用容器,能够直接将标记作为HTML直接加入到页面中:可是,假设是动态内容,则必须借助容器将内容加入到页面中.典型的容器有:Label控件.Literal控件. ...
- python解析git log后生成页面显示git更新日志信息
使用git log可以查到git上项目的更新日志. 如下两个git项目,我想把git的日志信息解析成一个便于在浏览器上查看的页面. https://github.com/gityf/lua https ...
- sitemap.xml 静态和动态生成页面 shopnc二次开发 动态生成sitemap.xml
Sitemap 可方便网站管理员通知搜索引擎他们网站上有哪些可供抓取的网页.最简单的 Sitemap 形式,就是XML 文件,在其中列出网站中的网址以及关于每个网址的其他元数据(上次更新的时间.更改的 ...
- 通过js动态生成页面表格
var redlineTemplateP = $(".redlineDataList"); for (var index in detailArraryLists.rows){ v ...
- Javascript动态生成的页面信息爬取和openpyxl包FAQ小记
最近,笔者在使用Requests模拟浏览器发送Post请求时,发现程序返回的html与浏览器F12观察到的略有不同,经过观察返回的response.text,cookies确认有效,因为我们可以看到返 ...
- CGI实现页面的动态生成
传统的Web应用开发局限于有限的静态页面(HTML静态页面),不利于系统的扩展,不能提供及时信息,而且修改维护麻烦,所以建立一个动态Web应用程序尤为重要.一方面根据访问者的不同请求返回不同的访问信息 ...
- Python调用ansible API系列(四)动态生成hosts文件
方法一:通过最原始的操作文件的方式 #!/usr/bin/env python # -*- coding: utf-8 -*- """ 通过操作文件形式动态生成ansib ...
- Python+Selenium爬取动态加载页面(1)
注: 最近有一小任务,需要收集水质和水雨信息,找了两个网站:国家地表水水质自动监测实时数据发布系统和全国水雨情网.由于这两个网站的数据都是动态加载出来的,所以我用了Selenium来完成我的数据获取. ...
- SSI注入--嵌入HTML页面中的指令,类似jsp、asp对现有HTML页面增加动态生成内容,见后面例子
SSI注入漏洞总结 from:https://www.mi1k7ea.com/2019/09/28/SSI%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E6%80%BB%E ...
随机推荐
- Oracle练习(一)
Oracle 1.检索部门编号.部门名称.部门所在地及其每个部门的员工总数. select d.deptno,d.dname,d.loc,count(*) from emp e,dept d wher ...
- UVA Tress in a Wood
https://vjudge.net/problem/UVA-10214 题意:给定一个坐标系.|x|<=a, |y|<=b 求坐标系中有多少点是可以从原点直接看到(即从原点和一个点连线, ...
- windows服务与log4net应用
有时候我们需要用到window服务来执行定时任务,然后配合log4net记录程序运行情况,这里简单记录下配置的整个过程以及注意要点: 一.添加windows服务 1.设计页面,右键添加安装程序
- [NOIP10.5模拟赛]1.a题解--离散化+异或线段树
题目链接: 咕咕咕 https://www.luogu.org/problemnew/show/CF817F 闲扯 在Yali经历几天折磨后信心摧残,T1数据结构裸题考场上连暴力都TM没打满 分析 观 ...
- [转载]机器学习优化方法总结:SGD,Momentum,AdaGrad,RMSProp,Adam
[转载]机器学习优化方法总结:SGD,Momentum,AdaGrad,RMSProp,Adam https://blog.csdn.net/u010089444/article/details/76 ...
- plsql developer字符集和oracle字符集不一致的解决方法(转)
如果oracle安装选择的字符集与PLSQL Developer的字符集不同,则打开 PLSQL Developer时会提示字符不一致可能造成问题: 定位到注册表的以下位置: HKEY_LOCAL_M ...
- android程序复制数据库
一般项目中我们把db文件放到assert或者raw目录下面,在程序第一次启动的时候复制到私有目录下面 在使用过程中,老是发现复制不成功,私有目录下的db文件总是3072 后来发现应该是使用Conten ...
- go语言入门(6)复合类型
1,分类 2,指针 指针是一个代表着某个内存地址的值.这个内存地址往往是在内存中存储的另一个变量的值的起始位置. 1)基本操作 Go语言虽然保留了指针,但与其它编程语言不同的是: 默认值 nil,没有 ...
- 【loj#6220】sum
题目传送门:https://loj.ac/problem/6220 题意:对于一个序列$a$,找出它的一个子序列$b$,使$\sum_{a_i \in b}a_i \equiv 0 \pmod n$ ...
- RabbitMQ 功能
学习完了rabbitmq总一下 RabbitMQ依赖的语言 erlang 第一它可以实现不同程序之间的程序信息储存交互,在易用性.扩展性.高可用性的方面不俗. rabbitmq相当于一个中间人,我们同 ...