UI 自动化的页面对象管理工具之实现思路
原文由alex发表于TesterHome社区网站,点击原文链接可与作者直接交流
本文将介绍下 UI 自动化的页面对象管理工具PO-Manager之实现思路:
更多PO-Manager的介绍,可查看上篇《UI 自动化的页面对象管理神器 PO-Manager》
基本架构
如图所示,该工具作为vscode 插件,因为要跟webpage的dom 进行双向沟通,我们将借用webdriver 里的js 执行器来进行通讯。
加载插件
我们需要启动一个chromedriver session, 同时加载一个chrome插件, 这插件其是基于selenium IDE recorder的一个改造版本,
//driver = new Builder().forBrowser('chrome').build();const builder = new Builder().withCapabilities({browserName: 'chrome','goog:chromeOptions': {// Don't set it to headless as extensions dont work in general// when used in headless modeargs: [`load-extension=${path.join(__dirname + '../../build')}`],},})driver = await builder.build()
然后在每个dom 里会有下面几个方法:
//在dom中我们将通过给window 对象绑定新函数的方式注入我们需要调用的方法
window.__side.selectElement = async callback => {await window.__side.postMessage(window, {action: 'select',}).then(callback)
}window.__side.generateElement = async (callback, options) => {await window.__side.postMessage(window, {action: 'generateElement',builderOptions: options}).then(callback)
}window.__side.generateElements = async (callback, options) => {await window.__side.postMessage(window, {action: 'generateElements',builderOptions: options}).then(callback)
}window.__side.generateAllElements = async (callback, options) => {await window.__side.postMessage(window, {action: 'generateAllElements',builderOptions: options}).then(callback)
}
vscode 调用
在PO-Manager 中我们将通过jsExecutor 去向对应的web页面中执行js脚本,你可能会好奇这里为啥要用executeAsyncScript 而不是executeScript, 并且还有个callback,这个其实是因为我们选择页面元素是一个异步过程,所以需要callback 来保证正确的返回。
executeAsyncScript的用法可以参考这里:
async selectElement(): Promise<string> {await this.showBrowser()return this.driver.executeAsyncScript(`var callback = arguments[arguments.length - 1]; window.__side.selectElement(callback);`).then(value => {return value})}async generateElement(): Promise<string> {await this.showBrowser()let options = {pascalCase: this.pascalCase, preserveConsecutiveUppercase: this.preserveConsecutiveUppercase}return this.driver.executeAsyncScript(`var callback = arguments[arguments.length - 1]; window.__side.generateElement(callback, arguments[0]);`, options).then(value => {return value})}async generateElements(): Promise<string> {await this.showBrowser()let options = {pascalCase: this.pascalCase, preserveConsecutiveUppercase: this.preserveConsecutiveUppercase}return this.driver.executeAsyncScript(`var callback = arguments[arguments.length - 1]; window.__side.generateElements(callback, arguments[0]);`, options).then(value => {return value})}async generateAllElements(): Promise<string> {let options = {pascalCase: this.pascalCase, preserveConsecutiveUppercase: this.preserveConsecutiveUppercase}return this.driver.executeAsyncScript(`var callback = arguments[arguments.length - 1]; window.__side.generateAllElements(callback, arguments[0]);`, options).then(value => {return value})}
选中元素
从上图中我们可以看到,当我们想添加元素对象的时候,我们要去选择一个页面元素,我们得要想页面注入mousemove, click, mouseout的事件监听,并且要有highlight的效果显示我们focus 元素:
注入监听
//TargetSelectorconstructor(callback, cleanupCallback) {this.callback = callbackthis.cleanupCallback = cleanupCallback// Instead, we simply assign global content window to this.winthis.win = windowconst doc = this.win.documentconst div = doc.createElement('div')div.setAttribute('style', 'display: none;')//doc.body.insertBefore(div, doc.body.firstChild)doc.body.appendChild(div)this.div = divthis.e = nullthis.r = nullif (window === window.top) {this.showBanner(doc)doc.body.insertBefore(this.banner, div)}doc.addEventListener('mousemove', this, true)doc.addEventListener('click', this, true)doc.addEventListener('mouseout', this, true)}
处理mouseover和click事件
handleEvent(evt) {switch (evt.type) {case 'mousemove':this.highlight(evt.target.ownerDocument, evt.clientX, evt.clientY)breakcase 'click':if (evt.button == 0 && this.e && this.callback) {this.callback(this.e, this.win)} //Right click would cancel the selectevt.preventDefault()evt.stopPropagation()this.cleanup()breakcase 'mouseout':this.removeHighlight()this.e = nullbreak}}
上面部分的js都是以content-script的形式注入的,但我们调用js executor的时候只能去执行在dom 底下的 function,这时候在里dom 就只能通过postMessage 的方式了来调用content-script的方法了。
content-script介绍具体可以参考chrome插件开发
如何生成一个元素对象
"新闻Link": {"type": "xpath","locator": "(//a[contains(text(),'新闻')])[1]"}
当我们选中一个元素后,要生成上面一个对象,我们需要处理2个部分
- 定位器
下面的代码是按照以找到id 为唯一标识,然后生成相对路径的xpath, 这是最通用的策略,当然对于link或者button 我们也可以以文字显示为标识。
更多的定位器生成方法,可以查看这里的 源码:
LocatorBuilders.add('xpath:idRelative', function xpathIdRelative(e) {let path = ''let current = ewhile (current != null) {if (current.parentNode != null) {path = this.relativeXPathFromParent(current) + pathif (1 == current.parentNode.nodeType && // ELEMENT_NODEcurrent.parentNode.getAttribute('id')) {return this.preciseXPath('//' +this.xpathHtmlElement(current.parentNode.nodeName.toLowerCase()) +'[@id=' +this.attributeValue(current.parentNode.getAttribute('id')) +']' +path,e)}else if(current.parentNode.nodeName.toLowerCase() == 'body'){return this.preciseXPath('//body' + path,e)}} else {return null}current = current.parentNode}return null
})
- 生成元素名
基本上选择了所见即所得的策略,优先选择显示文字,然后id, class次之, 这个当然在设定中可以配置
buildName(element){if(element.tagName == "svg") {element = element.parentElement}let attrList = ["id", "text", "class"]let originalText = this.nameCandidate(element, attrList)let name = ""let nameArr = (typeof originalText == "string" && originalText.match(/[a-zA-Z0-9\u4e00-\u9fa5]+/g)) || ["DefaultElement"]for(const n of nameArr){if(name.length >= 30) breakname += n + ' '}name = camelcase(name, this.options)name = this.append(element, name)return name}
元素筛选
上面是针对单个元素,如果我们要选择批量添加元素或者整个页面添加的时候,我们会遇到一个问题,dom 元素太多,那我们该如何筛选呢?
通过对dom 的研究发现,我们大部分时候,只需要包含文字的叶节点,而且table select 之类的复杂元素,我们也只需要加入这个父节点就行。
所以针对这个,我用xpath 做了如下的过滤规则
xpath=.//*[not(ancestor::table)][normalize-space(translate(text(),' ',' '))][not(ancestor::select)][not(self::sup)][not(self::iframe)][not(self::frame)][not(self::script)]|.//input[not(ancestor::table)][@type!='hidden']|(.//img|.//select|.//i|.//a|.//h1|.//h2|.//h3|.//h4)[not(ancestor::table)]
然后再对元素进行可见性筛选,基本就能满足大部分的需求了。
最后对每个元素生成定位器和元素名,然后返回到PO-Manager 插件并转化成json 插入到编辑器就行了
function generateElements(options){return new Promise(res => {new TargetSelector(function(element, win) {if (element && win) {//TODO: generateElementslet elements = {}let xpathFilter = `xpath=.//*[not(ancestor::table)][normalize-space(translate(text(),' ',' '))][not(ancestor::select)][not(self::sup)][not(self::iframe)][not(self::frame)][not(self::script)]|.//input[not(ancestor::table)][@type!='hidden']|(.//img|.//select|.//i|.//a|.//h1|.//h2|.//h3|.//h4)[not(ancestor::table)]`let elementList = locatorBuilders.findElements(xpathFilter, element)for(const ele of elementList){if(!isDisplayed(ele)) continueconst target = locatorBuilders.build(ele)nameBuilder.setBuilderOptions(options)const elementName = nameBuilder.buildName(ele)elements[elementName] = {type: target.slice(0,target.indexOf('=')),locator: target.slice(target.indexOf('=') + 1)}}if (elements) {res(JSON.stringify(elements))}}})})
}
下面是一个添加选中区域内元素的例子:
总结一下:
- selenium 启动browser的时候加载了一个插件,存放一些常驻的js 方法(比如元素选择,生成定位器,元素名称等等)
- 当用户选择元素时,通过driver.executeAsyncScript这个方法调用后台js
- new TargetSelector 这个方法会网页注入js mouseover 和click的监听, 当用户click的时候,选择的元素然后通过locatorBuilders.build(element) 生成对应的定位器
- 生成定位器的方式主要是基于当前元素和一些固定属性之间的反推,判定某些属性可以唯一定位到该元素即可
- 然后给元素生成合适的名称,最后组成json 返回给vscode 插件,生成json 插入到编辑器中
最后欢迎大家star 和 fork,一起来改进。
https://github.com/zzhengjian/PO-Manager
原文由alex发表于TesterHome社区网站,点击原文链接可与作者直接交流
想了解更多关开源工具,与更多开源项目作者直接交流?
欢迎关注第十届中国互联网测试开发大会(MTSC 2002 上海)· 开源专场 >>>
UI 自动化的页面对象管理工具之实现思路相关推荐
- KingbaseES数据库对象管理工具
目录 1. 简介 2. 启动数据库对象管理工具 3. 管理和配置数据库服务器 3.1. 实例管理 3.2. 数据库管理 3.3. 模式管理 4. 对象管理 5. 安全管理 5.1. 用户管理 5.2. ...
- 数据库人大金仓KingbaseES 数据库对象管理工具连接错误(实例创建失败)问题解决办法
目录 特别注意!需要跟下图一致! 解决方案一 解决方案二 解决方案三 可能遇见的问题 错误原因 最终效果 特别注意!需要跟下图一致! 然后检查!如果无报错再点击确定,否则连接不上数据库. 经常性的报错 ...
- 在使用数据库对象管理工具新建数据库时,默认自带的模式有哪些? A. public B. sys_catalog C. exam D. sysaudit...
在使用数据库对象管理工具新建数据库时,默认自带的模式有A. public和B. sys_catalog.
- 自动化集成:Jenkins管理工具详解
前言:该系列文章,围绕持续集成:Jenkins+Docker+K8S相关组件,实现自动化管理源码编译.打包.镜像构建.部署等操作:本篇文章主要描述Jenkins基础用法. 一.Jenkins安装 1. ...
- ETL自动化运维调度管理工具 TASKCTL 流程文件系统
模块信息组织思路 模块作为流程核心信息的基本组织单位,同时也是用户开发设计流程的主要对象.为了更好地设计流程,首先需要深入理解模块信息的组织思路. TASKCTL 流程以模块为单位的流程核心信息组织思 ...
- 网易 UI 自动化工具 Airtest 浅用记录
一 使用目的 该工具主要是面向游戏UI测试基于图像识别,如游戏框架unity,Cocos-js以及网易内部的游戏框架 同时也支持原生Android App 的基于元素识别的UI自动化测试. 本文主要使 ...
- python ui自动化配置文件,python UI自动化实战记录八:添加配置
添加配置文件写入测试地址等,当环境切换时只需修改配置文件即可. 1 在项目目录下添加文件 config.ini 写入: [Domain] domain = http://test.domain.cn ...
- pythonWeb UI自动化最流行的工具 解放双手 双手打字以示清白!
Selenium WebDriver是Web UI自动化最流行的工具之一.没有比Python更好的补充它来自动化广泛的Web应用程序.因此,我们提供了这个Selenium Webdriver Pyth ...
- Web UI自动化测试之Selenium工具篇
本文大纲截图: 一.自动化测试介绍 1.基本介绍 1.1 自动化 概念: 由机器设备代替人工自动完成指定目标的过程 优点: 1)减少人工劳动力 2)提高工作效率 3)产品规格统一标准 4)规模化(批量 ...
最新文章
- 南加大提出NeROIC:还有什么不能渲染的?重建效果太强悍了
- 《认知突围》做复杂时代的明白人,读书分享
- rtmp/rtsp/hls公网测试地址
- 程序员最喜欢用的在线IDE代码编译器,什么?你竟然不知道!
- [团队项目3.0]Scrum团队成立
- python dataframe遍历_对Python中DataFrame按照行遍历的方法
- CComPtr对象作为参数进行 1.值传递 2.引用传递 3.做为返回值的注意事项
- linux下使用tc工具模拟网络延迟和丢包
- linux如何杀死进程最快,如何在Linux系统中杀掉内存消耗最大的进程?
- 服务器LCD显示面板,DELL服务器2950的错误代码表(前LCD面板)
- gis怎么提取水系_利用ArcGIS水文分析工具提取河网
- Apple和Ruby近况:Rails的iPhone配置实用工具和Ruby的SproutCore工具
- 计算机主板自动重启,电脑自动重启频繁是主板的问题吗
- 解决Android手机人民币符号--¥显示问题
- 从强化学习的角度看alphago与MCTS
- 跟我学c++中级篇——decay
- Swing绝对布局之setBounds
- 外卖订单语音通知功能如何实现?(附外卖订单语音通知模板)
- 目前常用 心电数据库ECG:MITBIH,AHA,CSE,ST-T,PTB,PAF 详细介绍+下载
- Java求两点的中点坐标_计算两点坐标距离与中点坐标
热门文章
- 电脑计算机界面打开后无法缩小,技术编辑演示win10系统iE网页界面大小无法缩放的办法...
- python去掉两边空格,Python去除字符串两端空格的方法
- 百度平台上的网络舆情信息怎么搜查的方法
- c++(最大公约数)
- python的flask框架实现的小型二手商城
- transformers的beam_search
- 中科院最新通告:弃用影响因子!将替以「期刊超越指数」发布期刊分区表......
- 【Hive】快速入门~
- Linux之父:我们都老了,但Linux维护后继无人
- 数据分析案例-基于PCA主成分分析法对葡萄酒数据进行分析