最近在重读 Web 测试囧事的自动化这章。在读到书中总结的 UI 自动化的经验的时候,脑袋里面闪现出了自己项目中的一些 Web UI 自动化代码,突然觉得这些代码的可读性和可维护性都可以再提升一下。
目前项目中有一些痛点,例如页面显示的是一个商品 List,那么添加、删除一些测试数据的时候,有可能会改变现有数据的位置。这就要求在维护测试数据时,需要着重考虑是否会影响已有的自动化测试结果。再例如有些元素为动态生成,或者随机数作为 ID 属性,这样的元素定位通常会比较困难。在项目的日常工作中,会花费大量的时间在维护这些测试脚本上面。
先尝试从最简单和代价最小的方式入手进行改造,以提升测试稳定性和客户维护性。想了一下上面列举的两大痛点,应该可以通过改造元素定位方式来解决。
我们常用的定位器通常有:
针对这些定位器的使用优先级,书中推荐的优先级是
ID>Name>CSS>XPath
原因是 ID 和 Name 最直观和最易维护的,而且不受 HTML 文档结构影响,即无论元素变化到哪个位置都可以被定位到。而 ID 和 Name 相比的话,具有不可重复性这样的特点,所以优先级更高 (有些网站会出现重复的 ID 甚至 XPath,这样的 Web 是不符合可及性及 Web 开发规范的。这样的网站存在,但是不合理)。
这两者之间的比较自古就存在,围观群众对孰优孰劣的问题各持己见。我的看法是:
从工作的机制来看,XPath 使用路径标记在 XML 文档层次结构中进行导航,简单说就是遍历文档路径。Selector则是一种匹配模式,并且针对 XML、HTML 做了优化,速度上优于 XPath。除此之外,网站上的不同元素 CSS,也是通过 selector 去定位和绑定的。
从书写形式上看,CSS 选择器提供简单的语法,稍加学习即可掌握。举个例子,假设我们有这么一段 HTML,
<div class="field-item odd">
<div>...<div>
</div>
<div class="field-item even">
<div>...<div>
</div>
分别用 XPath 和 CSS 选择器定位class="field-item odd"
的 div,效果如下:
//XPath
@FindBy( xpath = "//*[contains(concat(\' \', normalize-space(@class), \' \'), \'field-item odd\')]")
//XPath
@FindBy( xpath = "//*[@class='field-item odd']")
//CSS selector
@FindBy( css = ".field-item.odd")
Tips:
- 以上定位代码均可以在https://www.whitehouse.gov/中试用
- 用 Firefox 或者 Chrome inspector 查看元素,可以直接使用 XPath 或者 CSS 选择器搜索页面元素
所以我觉得 CSS 选择器在书写的方便和阅读的直观程度上,也略占优势。
但是 XPath 也有它独特的优势,比如 “查找包含某个子元素的父元素” 这样的操作,CSS 选择器就暂时无法做到 (不过实战中也极少用到)。
最重要的一点,在我采访了多位专注前端开发数十年的研发后发现,前端开发们纷纷表示对 CSS 选择器熟悉,并且平时的工作中也会经常使用;而对 XPath 则没那么多的了解 (因为几乎用不到)。考虑到今后自动化的脚本开发们也可以帮忙维护,再加上新入坑的同学可以由开发来做一些指导,所以 CSS 选择器再得一分。
综上,我决定对项目中的定位方式改造和维护时,以 CSS 选择器为主,实在不行再使用 XPath 补充。
在项目老代码里,传统定位方式是从 Chrome 中直接复制下目标元素的 XPath 进行定位。所以经常会在代码里面看到很长很长的一串的定位字符串,例如
//定位白宫官网FEATURED CONTENT的第一个图片
image1 = "//*[@id="page"]/div[2]/div/div/div[2]/div/div/div/div/div[1]/div/div[1]/a/div/div/div/img"
这样的定位代码既难度,又容易失效。
举个例子,假设现在有一个自动化的用例是:打开 JD 商城,搜索 “web 测试囧事”,然后找到 “旷氏文豪图书专营店” 的这条记录,点进去最后验证书店名称和价格。
那么按照传统做法,复制下这条记录的 XPath,以定位这个商品,再做后续操作:
//*[@id="J_goodsList"]/ul/li[4]/div/div[1]/a
我们来解读下这个 XPath:解释器会先找到 id 为J_goodsList
这个元素,然后找到这个元素下的第一个ul
标签,再去找到ui
下面的第 4 个li
,以此类推,最后找到目标a
标签。这样根据 XML(html) 文档结构的绝对位置产生的定位方式,我喜欢叫它绝对路径定位。
那么问题来了,假设产品给了个新需求,要求商品默认按照价格降序排序;或者是测试场景变更为:先删除第三个商家的产品,然后再定位到 “旷氏..” 这条记录。这时候页面的文档结构必然发生改变的时候 (商品位置变更),之前定位的绝对位置指向的元素就发生了改变,那不得又得重新维护一次脚本了?
所以是时候重新考虑一下定位方式了。观察后发现,数据变化后仅会引起商品绝对位置的改变,对商品内部的 icon,文字描述等则没有影响。那么,使用相对模糊的定位方式,应该可以解决这个问题。根据之前定位器优先级的调研结果,优先考虑使用 CSS 选择器重写。
还是以上面的商品为例子,在商品列表页面用 CSS 选择器定位到这个带herf
的<a>
标签,以做后续跳转。
想要定位到某个元素的话,首先应该考虑这个元素有没有唯一属性,例如 id。
如果没有唯一属性的话,再考虑从这个元素的父元素、祖宗元素、隔壁元素中找到唯一属性,然后再定位到这个元素 (参考)。
//通过有唯一属性的父元素,向下定位到目标元素。注意"空格"和">"的区别
@FindBy( css = "li[data-sku='13435315793'] .p-img>a")
这样一来,不管这个数据的位置如何改变,只要改商品的标志,即13435315793
不变,测试用例都不需要额外的维护了。
有些网站中,会存在一些动态生成的视图。它们通常随机生成标志 (ID),或者是在 ID/CLASS 后面增加无序随机字符串作为标识;有时候甚至会在同一个页面中生成多个相同的元素 ID。这就导致元素定位工作变得更加麻烦。例如下面这个网站,在一个页面上生成多个带data-view
属性的 div,其 value 后面的三位数字为随机生成。
要定位到页面上 “第二大类问题的第二个小问题” 里面的这个 input 框,使用工具复制下该元素的 XPath 或者 CSS 选择器的时候,发现复制下来的定位器查找到的元素不止一个。原因就是视图动态生成,生成了很多 ID 重复的元素,而通过工具复制下来的定位器会优先选择 ID 属性。
动态视图大多都通过模板生成,所以这类视图的内部结构通常都是一毛一样的。所以只要能定位到这个视图,后续再在这个视图中根据文档结构找到这个元素就可以了。
上面例子中的视图 div 的唯一标志 data-veiw 的值是随机的,每次打开页面都会更新,但是每个视图在页面上的顺序不会发生改变。所以这时候我选择从更上级的元素开始定位,通过nth-child
来确定查找第几个视图中的元素。
@FindBy( css = ".multiRegistration__category-list>div:nth-child(2) .form-inline>div:nth-child(2) input")
先定位到更上级包含了所有问题的 div,其 class 为.multiRegistration__category-list
,然后向下一级查找到第二个 div,即 “第二个大类的问题”;再查找这个元素的所有下级元素中查找包含.form-inline
class 的元素 (class 全写为select-race__registrationCategory form-inline
),然后向下一级查找到第二个 div,即 “第二个小问题”;最后再在其所有子元素下面找到 input 标签。需要注意的是每一个步骤查找出来的元素都应该是唯一的。
这样的定位方式我觉得算是绝对路径定位方式的升级版,绝对路径定位和模糊定位相结合。相比直接按照文档接口定位的方式来看,少了很多层级关系和减轻了书写的难度;从维护角度考虑,尽量减少>
符号这种查找直属下级的定位方可以有效提高容错率,模板结构不做巨大变更的话,无需维护脚本。
学习完了 CSS 选择器后,发现这些定位元素的方法也可以拿到浏览器中去直接执行。再结合一些简单的 JavaScript 脚本,甚至可以实现页面半自动测试。对需要大量重复手动作业、设置代码运行环境困难、觉得各种浏览器 driver 和 selenium 版本不好对应、懒癌晚期的同学来说简直是天降福音。
这种方法可以在各种浏览器自带的开发工具 (windows 下通常可以通过 F12 调出) 里的 console 里面直接执行。随便找个浏览器打开京东首页,然后在 console 中粘贴下面这段代码,敲下回车试试看会发生什么:
javascript:(function(){$("#key").focus().val("web测试");$(".form button").click()})()
对于没有使用 jQuery 的网站,也可以使用 JavaScript 原始方法定位,例如document.querySelector('#id')
。
除此之外,还也可以把代码放到书签里面,通过点击直接执行。效果如下:
在面对使用不同技术栈、不同风格、不同编码水平的前端页面的时候,对我们定位元素会有一些挑战。灵活的运用 CSS 选择器以及其他定位器可以让我们事半功倍。定位元素可遵循如下思路:
>
, 空格,以及+
等进行辅助定位。