前言:
我们知道,UI 自动化测试最基本的功能就是如何定位元素并进行接下来的点击、输入、验证等操作;从另一角度来看,UI 自动化测试用例是否稳定,关键也要看你的元素定位是否合理,健壮、可靠的元素定位策略可以保障测试成功率的提高,经过一段时间的基于Cypress的UI自动化实践工作,发现用例失败的90%的原因应该就是元素定位失败导致的,而且目前业务大部分使用vue. react等框架,遇到开发升级框架组件等,一定会大修一次元素定位,因此为减少一部分维护成本,总结下一些元素稳定定位方式。而以下方式基本是基于Cypress该自动化框架,但思想应该差不多通用。
一、元素定位方式总结
1.id定位
- 概念:通过元素的id属性来定位元素
- 前置:所要定位的元素必须要有id属性
- Cypress通过id定位: cypress.get(‘#id’)
该方式无需多说,因为id具有唯一性,定位准确快捷,无须遍历dom
2.class定位
- 概念:通过元素的class样式定位元素
- 前置: 标签必须有class属性
- 特点: class属性可以有多个值 & 重复
- 提示:如果标签有多个class值,只能任意选择其中一个
现在大部分业务都是使用vue react等框架,因此很多样式都是框架自动生成和命名的,遇到
框架升级,有可能所有元素的均定位失败,因此尽可能避免使用class定位
3.tag定位
- 概念:根据的标签名进⾏定位,例如:input form table
- 提示:如果⻚⾯存在多个相同标签,默认返回第⼀个
- cypress中通过tag定位: cypress.get(‘input’) 、 cypress.get(‘table’)
在经典Selenium中,一般不建议使用tag定位, 因为标签可能有太多重复,例如div标签,一个页面可能有几十个。但在cypress中可以链式调用,所以我们可以结合需要定位元素的父元素/子元素/相邻元素进行辅助查找。例如:
在如下表格中我们需要定位第二条数据的添加按钮,此时该按钮无id,并且每条数据的按钮都是框架生成的样式,因此无法使用id或样式定位。但这个页面可能只存在一个table, 因此我们通过标签可以很容易定位到table:
cypress.get(‘table’),通过table可以定位到第二行,通过第二行又可以定位到最后一列,在最后一列中我们直接cypress.get(‘button’),就能获取到第二条数据的添加按钮,代码如下:
cypress.get('table').get('tr').eq(1) //查找table的第二行,index从0开始计算
.get('td').last() // 查找table的第二行的最后一列
.within(() => {
cy.get('button')
// 查找table的第二行的最后一列中的button按钮。
})
web端的系统,大多都是表格、表单等,这种通过tag定位元素的方式不受样式等影响,是比较稳定的定位方式之一
4.xpath定位
当我刚开始搞cypress自动化时,听一位同事说可以使用xpath定位,由于当时不怎么会学习, 没有仔细学习xpath定位方式,直接在浏览器进行copy xpath / copy full xpath,然后当遇到一次业务UI改版,发现xpath定位的基本全失败,这让我一度让我在自动化的MR里无知的和其他同事说: 在咱们自动化项目里绝对不要出现xpath定位,但仔细看了下xpath定位方式后,发现他跟上述tag定位有异曲同工之妙,先上正确用法:
表达式 | 描述 |
---|---|
nodename | 选取次节点的所有子节点 |
/ | 从当前页面的跟路径开始遍历查找 |
// | 从匹配选择的当前节点选择页面中的节点,不考虑位置 |
. | 选取当前节点 |
… | 选取当前节点的父节点 |
@ | 选取属性 |
首先,介绍一下xpath的路径节点表达式,如:
表达式描述nodename选取次节点的所有子节点/从当前页面的跟路径开始遍历查找//从匹配选择的当前节点选择页面中的节点,不考虑位置.选取当前节点…选取当前节点的父节点@选取属性
- xpath绝对定位:严格按照元素顺序,过于依赖当前页面元素结构,不稳定,最开始就使用的copy full xpath,让我一度误会了xpath的方法 ,cy.xpath(‘/html/body/div[1]/div[1]/div[5]/div/div/form/span[1]/input’),缺点就是当整个页面有一个元素变化了或新增元素,就又可能造成大部分元素定位失败
- xpath相对定位:灵活、用法多、建议用,根据元素本身特征查找、不用按特定顺序.包含以下这些方法:
- 单属性定位:
//标签名[@属性名=属性值]
cy.xpath('//div[@role='dialog']')
- 多属性定位: 使用and or关键字
//标签名[@属性名=属性值 and @属性名=属性值]
cy.xpath('//div[@role='dialog' and @id='country'] ') // 表单弹窗中找到country输入框
- 文本定位:
//标签名[text()=“文本”]
cy.xpath("//button[text()='提交']")
4. 文本与属性值组合:
//标签名[text()=“已签到” and @class=“tip”]
cy.xpath("//button[text()='提交' and @disabled='true']")
- 模糊查找-contains
//标签名[contains(text(),“文本”)]
cy.xpath("//*[contanins(text(),'提交' ) ]
-
完全模糊查找
//[XXXX] //[text()=“提交”] //span[@=“提交”] //[@*=“XXX”] -
层级定位,也就是通过3种链式条用的方式
7.1. 通过父或者祖先找子元素 (经常用到),通过自己本身的特征无法唯一定位自己的时用层级定位。格式:
语法 |
---|
//父标签[@id=“u1”]//子标签[@name=“tj_login”] |
7.2 轴定位:通过子元素找祖先元素
语法 |
---|
已知元素//…//…/轴名称::标签名[@属性名=“属性值” and @属性名=“属性值”]//… |
轴名称:
名称 | 描述 | 语法 |
---|---|---|
parent: | 父元素 – 通过子找父 (找到子元素再父) | //子标签名[@属性名=“属性值”]/parent::父标签[@属性名=“属性值”] |
ancestor:: | 通过子找祖先元素 | //子标签名[@属性名=“属性值”]//子元素标签/ancestor::父标签[@属性名=“属性值”] |
preceding-sibling:: | 通过当前元素 找姐姐/哥哥元素(同一父元素,排在当前元素前面的) | //标签名[@属性名=“属性值”]//标签/preceding-sibling::标签[@属性名=“属性值”] |
following-sibling:: | 通过哥哥姐姐找弟弟 | //标签名[@属性名=“属性值”]//标签/following-sibling::标签[@属性名=“属性值”] |
- 属性定位
- 概念:通过元素的特定属性定位
- 前置:通过属性查找也应该和id一致,使用唯一数据定位
- Cypress通过属性定位:
cy.get('[title="XXXX" ]'
例如:下面这种日期选择,就可以通过属性定位,其中title属性对应到某一天,这个属性就是唯一的,不会重复
conststartDate = '2022-12-16'
constendDate = '2022-12-17'
cy.get('[title="'+ startDate + '"]').click({ force: true});
cy.get('[title="'+ endDate + '"]').click({ force: true});
6.键盘辅助
在写用例时,发现有一种情况的元素很难定位,即下拉框中的内容
上图中:由于是框架生成的,每个选择框的元素的样式都是一样的,并且下拉框中每个选项样式又是一样的,也没有其他特定属性,我们很难定位到下拉框&下拉框中的选项
这时候我们可以使用键盘辅助,例如我每次均选择第二个:
cy.get('contentTag').click().type('{downarrow}{enter}') // 点击输入框唤起下拉框,然后键盘向下选择,enter选中
当然,很难定位的元素比较少,遇到以上情况可以尝试下键盘辅助操作。
二、思想总结
看了一篇文章,觉得思想上写的和我实践过程中遇到的感想非常相似,也用自己的话解释下这个大佬的思想:
1.大道至简
大道至简指的是有明显特征的元素一定要用明显的特征去定位。此处特别强调一下,不一定是只有id是明显特征,有时候当前页面只有一个table,那table也就是明显特征,可以直接get。
2.分而治之 & 由大到小
一般页面层级是非常清晰的,当没有明显特征的时候,一定要善用层级定位,也就是把页面可以划分为几个区域,例如在表单中查找,在table中查找,甚至在小一点,我在某个表格的某一行的某一列内进行查找
3.酌情而定
定位最怕在一棵树上吊死。当你一种方式不行的时候就要视情况换一种方式。下面是我一个刚开始做自动化的同事的反馈,当我们遇到很难定位的时候可以换一种思路,日常多积累些稳定定位的方法,遇到比较难定位的可以逐一尝试
4.动静结合
有些元素是有特殊属性的,但是这些属性是动态变化的。这时候我们就要想办法去获得动态属性,例如上边讲到过的定位日期,这里的title就是动态的,我们可以通过写一些逻辑,获得动态属性,再去进行查找
conststartDate = '2022-12-16'
constendDate = '2022-12-17'
cy.get('[title="'+ startDate + '"]').click({ force: true});
cy.get('[title="'+ endDate + '"]').click({ force: true});
5.殊途同归
所有方便、逼格较高的定位方式,都是殊途同归,只要能够优雅的定位(不要用机器自动生成的xpath)到元素,一切方法和手段都是等价的,不管黑猫白猫,能稳定抓到元素的就是好猫!