Selenium [Selenium CSS locator] 记一次元素定位优化行动

xuxtc · 发布于 2017年09月25日 · 最后由 tester6636865 回复于 2017年10月19日 · 724 次阅读
本帖已被设为精华帖!

最近在重读Web测试囧事的自动化这章。在读到书中总结的UI自动化的经验的时候,脑袋里面闪现出了自己项目中的一些Web UI自动化代码,突然觉得这些代码的可读性和可维护性都可以再提升一下。

目前项目中有一些痛点,例如页面显示的是一个商品List,那么添加、删除一些测试数据的时候,有可能会改变现有数据的位置。这就要求在维护测试数据时,需要着重考虑是否会影响已有的自动化测试结果。再例如有些元素为动态生成,或者随机数作为ID属性,这样的元素定位通常会比较困难。在项目的日常工作中,会花费大量的时间在维护这些测试脚本上面。

先尝试从最简单和代价最小的方式入手进行改造,以提升测试稳定性和客户维护性。想了一下上面列举的两大痛点,应该可以通过改造元素定位方式来解决。

定位器使用优先级

我们常用的定位器通常有:

  • XPath
  • CSS selector
  • ID
  • Name
  • 其他

针对这些定位器的使用优先级,书中推荐的优先级是

ID>Name>CSS>XPath

原因是ID和Name最直观和最易维护的,而且不受HTML文档结构影响,即无论元素变化到哪个位置都可以被定位到。而ID和Name相比的话,具有不可重复性这样的特点,所以优先级更高(有些网站会出现重复的ID甚至XPath,这样的Web是不符合可及性及Web开发规范的。这样的网站存在,但是不合理)。

XPath和CSS选择器比较

这两者之间的比较自古就存在,围观群众对孰优孰劣的问题各持己见。我的看法是:

从工作的机制来看,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:

  1. 以上定位代码均可以在https://www.whitehouse.gov/中试用
  2. 用Firefox或者Chrome inspector查看元素,可以直接使用XPath或者CSS选择器搜索页面元素

所以我觉得CSS选择器在书写的方便和阅读的直观程度上,也略占优势。

但是XPath也有它独特的优势,比如“查找包含某个子元素的父元素”这样的操作,CSS选择器就暂时无法做到(不过实战中也极少用到)。

最重要的一点,在我采访了多位专注前端开发数十年的研发后发现,前端开发们纷纷表示对CSS选择器熟悉,并且平时的工作中也会经常使用;而对XPath则没那么多的了解(因为几乎用不到)。考虑到今后自动化的脚本开发们也可以帮忙维护,再加上新入坑的同学可以由开发来做一些指导,所以CSS选择器再得一分。

综上,我决定对项目中的定位方式改造和维护时,以CSS选择器为主,实在不行再使用XPath补充。

解决问题

变化的ListItem位置

在项目老代码里,传统定位方式是从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>标签,以做后续跳转。

html文档结构

想要定位到某个元素的话,首先应该考虑这个元素有没有唯一属性,例如id。
如果没有唯一属性的话,再考虑从这个元素的父元素、祖宗元素、隔壁元素中找到唯一属性,然后再定位到这个元素(参考)。

//通过有唯一属性的父元素,向下定位到目标元素。注意"空格"和">"的区别
@FindBy( css = "li[data-sku='13435315793'] .p-img>a")

这样一来,不管这个数据的位置如何改变,只要改商品的标志,即13435315793不变,测试用例都不需要额外的维护了。

动态生成的视图

有些网站中,会存在一些动态生成的视图。它们通常随机生成标志(ID),或者是在ID/CLASS后面增加无序随机字符串作为标识;有时候甚至会在同一个页面中生成多个相同的元素ID。这就导致元素定位工作变得更加麻烦。例如下面这个网站,在一个页面上生成多个带data-view属性的div,其value后面的三位数字为随机生成。

某运动类网站

要定位到页面上“第二大类问题的第二个小问题”里面的这个input框,使用工具复制下该元素的XPath或者CSS选择器的时候,发现复制下来的定位器查找到的元素不止一个。原因就是视图动态生成,生成了很多ID重复的元素,而通过工具复制下来的定位器会优先选择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-inlineclass的元素(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选择器以及其他定位器可以让我们事半功倍。定位元素可遵循如下思路:

  1. 找到待定位元素的唯一属性。
  2. 如果该元素没有唯一属性,则先找到能被唯一定位到的父元素/子元素/相邻元素,再使用>, 空格,以及+等进行辅助定位。
  3. 不要使用随机唯一属性定位。
  4. 最重要的是多跟研发沟通,尽量把关键元素加上ID或者name,并减少不合理的页面元素,例如重复ID这样的事情最好不要发生。
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 8 条回复
104 seveniruby 将本帖设为了精华贴 09月25日 19:51
110

css 建议注入 sizzle

6504

一直习惯了用xpath

9210
xuxtc · #4 · 2017年09月26日 作者
6504lose 回复

大家在开始玩儿UI自动化的时候,几乎都是从xpath开始的, 我也不知道为啥 ┑( ̄Д  ̄)┍
然后在某次跑一大堆脚本但是时候发现了比较严重的性能问题,分析问题原因然后看到了这个,才开始动起了换CSS的脑筋。然后发现CSS selector 越用越顺手..

6504
9210xuxtc 回复

谢谢分享,最近的确体会到xpath有点慢,但是在原生app下元素并不唯一,appium下,不用xpath能有什么好用的?😨

9210
xuxtc · #6 · 2017年09月26日 作者

┑( ̄Д  ̄)┍
无奈的耸耸肩

10083

额,个人觉得用CSS和XPATH其实没有太大的区别,如果你要说XPATH效率慢的话,那自动化本身就不是一个讲效率的东西,也没必要为那一两秒的时间去计较。
在Webdriver的源代码里面,即使是通过By.id去定位某个元素,在源代码里面也会有一个补充的地方,如果通过By.id没有get到元素,那么也会用一次XPATH再查一次。
至于JD商城的那个case,其实用CSS和XPATH本质都是一样的,都是通过元素属性去定位的,CSS不太懂,但我更多时候用xpath是为了顺便检查一下页面有没有变形之类的,通过这种方法来间接使脚本也做了一次UI的检查。

但是用CSS和XPATH,这两个东西并没有错,看个人喜好就行。

96
10083iamcodylee 回复

很对

14381

自己优化一下Xpath,不要直接元素右键,这样会快很多。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册