😏作者简介:博主是一位测试管理者,同时也是一名对外企业兼职讲师。
📡主页地址:【Austin_zhai】
🙆目的与景愿:旨在于能帮助更多的测试行业人员提升软硬技能,分享行业相关最新信息。
💎声明:博主日常工作较为繁忙,文章会不定期更新,各类行业或职场问题欢迎大家私信,有空必回。
阅读目录
- 1. 目的
- 2. 定位简介
- 2.1 CSS Selector定位
- 2.1.1 选择器语法
- 2.1.2 组合定位
- 2.2 XPath定位
- 2.2.1 相对路径定位
- 2.2.2 相对路径的运算符运用
- 2.2.3 轴
- 2.2.4 函数
- 3. 注意点
- 3.1 CSS Selector的使用注意点
- 3.2 XPath的使用注意点
1. 目的
web自动化测试作为软件自动化测试领域中绕不过去的一个“香饽饽”,通常都会作为广大测试从业者的首选学习对象,相较于C/S架构的自动化来说,B/S有着其无法忽视的诸多优势,从行业发展趋、研发模式特点、测试工具支持,其整体的完整生态已经远远超过了C/S架构方面的测试价值。
我们上一次介绍了基于配置文件方式的元素管理的方法,接下去博主会就一些元素的高级定位方法来做一个较为全面的讲解。这些高级定位技巧虽然不会在日常的工作中高频出现,但在一些基础定位方法无法见效的场景中却往往会达到意想不到的效果。
2. 定位简介
上几期中介绍到的常用元素定位方式这里就不在展开篇幅进行赘述了,其实在我们日常的web自动化测试的脚本设计过程中,常常会碰到因为代码规范或开发同学个人的习惯,导致web页面中的元素某些属性值重复或缺失的现象出现,此时再用ID、CLASSNAME等常用的元素定位方法往往就会定位不到你想要的元素。那么我们这期就着重介绍一下CSS Selector与XPath这两种定位方法。
2.1 CSS Selector定位
CSS Selector虽然也是较为常用的定位方式,但其出镜率远远不如ID、Class Name等定位。作为元素定位的基准来说,我们的选取准则仍然是唯一性、可读性、维护性这三兄弟,所以当普通定位方式失效之后,我们会优先选择CSS Selector,之后才是XPath。
CSS Selector是通过HTML 元素的 class、id、标签名、属性等来定位元素,同时也正因此特性,该定位方式就具有很高的灵活性与可读性,精准度也较高。另外也正是由于其可读性精准度高,在执行测试脚本时其执行速度也是较为快速。
2.1.1 选择器语法
相较于其他的单一定位方式,CSS Selector本身涵盖有以下这些基本语法,这个也是我们后期进行组合定位的重要基础与依据。
标签选择器 —— 选择特定标签类型的元素。EX:div
类选择器 —— 选择具有特定类名的元素。EX:.class (.表示选择类)
ID选择器 —— 选择具有特定ID的元素。EX:#id (#表示选择ID)
属性选择器 —— 选择具有特定属性的元素。EX:[attribute=value]
子选择器 —— 选择某个元素的直接子元素。EX:parent > child
后代选择器 —— 选择某个元素的后代元素。EX:ancestor descendant
2.1.2 组合定位
当我们掌握了以上这些选择器的全部用法之后,那么在我们设计脚本的过程中运用灵活的组合方式来进行高进度的元素定位。
【标签+类】
比如我们可以组合标签名与类名两个属性,这里我们的HTML代码为:
<div class="SignFlow-tab" role="button" tabindex="0">登录</div>
我们使用【标签+类】的组合方式来进行元素定位
driver.find_element(By.CSS_SELECTOR, "div.SignFlow-tab")
【标签+属性】
举例如下HTML代码:
<link data-rh="true" rel="apple-touch-icon" href="https://static.xxx.com/icon-eac.png" sizes="60x60">
我们使用【标签+属性】的组合方式来进行元素定位
driver.find_element(By.CSS_SELECTOR, "link[href]")
【类+属性】
举例如下HTML代码:
<input name="digits" type="number" class="Input i7cW1UcwT6ThdhTakqFm" placeholder="输入六位短信验证码" value="">
我们使用【类+属性】的组合方式来进行元素定位
这里需要注意的是,这个input元素拥有多个属性,这边是查找了三个属性以做到尽量的定位精准为目的,如果只抽取任意两个或多个组合都是可以的。
driver.find_element(By.CSS_SELECTOR, "input.Input.i7cW1UcwT6ThdhTakqFm[name=digits]")
【后代+类】
示例代码如下:
<div class="SignFlow-tabs">
<span class="SignFlow-tab" role="button" tabindex="0">密码登录</span>
<span class="SignFlow-tab" role="button" tabindex="-1">短信登录</span>
</div>
我们使用【后代+类】的组合方式来进行元素定位
这里需要说明一下的是,在html中一个元素被另一个元素所包含,就类似于上面的这段html代码,最外层的div元素是父元素,span元素就是子元素,这个相信应该很好理解。而后代选择器是可以指定父元素中的任意子元素的,也就是说这个选择器可以选择div元素下所有具有SignFlow-tab类属性的span子元素。另外就是无论这些span子元素是否直接作为子元素,或者嵌套在更深层次的子元素中,都是可以被后代选择器指定的。
driver.find_element(By.CSS_SELECTOR, "div.SignFlow-tabs span.SignFlow-tab")
【子+类】
示例代码如下:
<div class="SignFlow-tabs">
<span class="SignFlow-tab" role="button" tabindex="0">密码登录</span>
<span role="button" tabindex="-1">
<span class="SignFlow-tab">短信登录</span>
</span>
</div>
我们使用【子+类】的组合方式来进行元素定位
对于前面介绍的后代选择器来说,子选择器和其的区别在于:子选择器只会选择直接子元素,而后代选择器则可以选择所有子孙元素。写法也需要区别开,子选择器使用“>”,而后代选择器使用空格。
driver.find_element(By.CSS_SELECTOR, "div.SignFlow-tabs > span.SignFlow-tab")
2.2 XPath定位
XPath定位可以说是广大测开同学的最后一根救命稻草,所有搞不定的元素定位都可以用该定位方式来达成,当然其中也存在着很多风险在其中,最粗暴也最不可取的方式就是直接复制XPath的绝对路径来进行定位,取值晦涩难懂不说,维护性也几乎不存在。那么我该如何用好XPath定位这个最终手段呢?让我们接着往下看。
2.2.1 相对路径定位
既然不我们不提倡使用绝对路径来进行XPath方式定位,那么相对路径自然就是其另一面的良好解决方案。在相对路径的定位方法中,我们则需要指定一些关键字和符号来构建路径,以实现准确定位。
以下是日常中我们经常会用到的一些路径定位关键字:
. 表示当前节点,即定位的起点
… 表示当前节点的父节点
// 表示从根节点开始查找元素,不考虑当前节点位置
@ 表示元素的属性
光说可能有点抽象,那我们就来看一个对应的例子:
某个HTML的源代码如下:
<html>
<head>
<title>UI自动化测试平台首页</title>
</head>
<body>
<div id="ui_automation_t">
<h1>这个是标题1</h1>
<p>这个是测试信息</p>
</div>
</body>
</html>
如果我们要定位对应的<p>
标签元素,那我们的XPath相对路径就可以这么写:
//div[@id='ui_automation_t']/p
这段相对路径该怎么理解呢?其实很简单,我们结合着上面相对路径的关键字来解读一下。首先 // 是从根节点开始查找,div则是查找下面的所有div元素,然后在所有的div元素中搜索id属性值为ui_automation_t的元素,最后在匹配到的结果中定位下面的所有p元素。相对路径定位的好处就是完全不用考虑结构变化会带来的元素路径变动影响,除非是元素本身发生了变化或是被取消了。
2.2.2 相对路径的运算符运用
在XPath的相对路径定位中我们也可以使用运算符来进行对应属性的定位。很多的条件判断都可以用运算符来进行达成。
【等于】
很好理解,等于运算符用于匹配元素的属性值是否等于指定的值。
driver.find_element(By.XPATH, "//input[@name='discount']")
【包含】
contains() 函数用于匹配元素的属性值是否包含指定的字符串。
driver.find_element(By.XPATH, "//input[contains(@class, 'icon-title')]")
【与或】
and与or都属于逻辑运算符,可以用于连接多个表达式。
driver.find_element(By.XPATH, "//input[@name='discount' and @type='content']")
2.2.3 轴
既然说到了XPath,就绕不过“轴”这个概念,说直白点它就是用来表示当前页面中节点一个大合集,掌握了轴的用法可以帮助我们快速的定位页面中的节点,在复杂的页面中,经常会有多层嵌套的的结构,我们可以跳过一些不相关的节点来直接定位到所需的节点(避免遍历),从而提升脚本的执行效率。
同样的,我们来看看轴相关的一些基础定义:
ancestor 轴 —— 选择当前节点的所有祖先节点
descendant 轴 —— 选择当前节点的所有子孙节点
parent 轴 —— 选择当前节点的父节点
child 轴 —— 选择当前节点的所有子节点
preceding-sibling 轴 —— 选择当前节点之前的所有兄弟节点
following-sibling 轴 —— 选择当前节点之后的所有兄弟节点
self 轴 —— 选择当前节点本身
【ancestor 轴】
例如有下面这样一个HTML代码:
<div class="k_interface">
<div class="c_app">
<span class="g_center"></span>
</div>
</div>
如果我们想要定位span元素的祖先元素(parent)时,就可以使用ancestor 轴实现:
driver.find_element(By.XPATH, "//span[@class='g_center']/ancestor::div[@class='k_interface']")
上面的这个相对路径表达式用到了ancestor轴,ancestor表示在//span[@class=‘g_center’]的定位结果后使用该轴搜索其节点上的祖先元素,而该祖先的元素指定为div[@class=‘k_interface’]。但这里有一个地方需要注意的是,轴定位的结果都是一个节点的合集,所以我们定位的时候需要调用find_element方法,而千万不要用find_elements,如果要选择该节点中的所有的节点,我们可以使用ancestor-or-self轴来实现,用法同上,依然调用find_element且用ancestor-or-self关键字来替换掉ancestor即可。
【descendant 轴】
示例代码如下:
<div class="k_interface">
<div class="c_app">
<span>test_page</span>
</div>
<div class="c_app">
<input id="text" placeholder="Enter your case">
</div>
<div class="g_center">
<button>Submit</button>
</div>
</div>
需要定位k_interface元素的后代元素时,就可以使用descendant轴实现:
driver.find_element(By.XPATH, "//div[@class='k_interface']//descendant::input[@id='text']")
如果需要查询与其相关的所有后代元素,使用通配符*代替:
driver.find_element(By.XPATH, "//div[@class='k_interface']//descendant::*")
上面的descendant轴中的后代元素与之前所的子元素区别也是类似的,可以获取到其父节点下所有间接与直接的任意后代元素。
【parent 轴】
示例代码如下:
<div class="k_interface">
<p class="c_app">test_index</p>
</div>
查找某个元素的父元素,我们就可以使用parent轴来实现:
driver.find_element(By.XPATH, "//p[@class='c_app']/parent::div")
这里我们通过p元素的class属性c_app来获取其父元素div。同理需要进行复数获取的时候使用*代表即可。
【child 轴】
有如下一段代码:
<div class="k_interface">
<div class="c_app">
<span class="g_center">test_group_1</span>
</div>
<div class="c_app">
<span class="g_center">test_group_2</span>
</div>
</div>
如果我们要获取父元素下的第二级div元素下的test_group_2元素,就可以使用child轴。
driver.find_element(By.XPATH, "//div[@class='k_interface']/*[2]/*")
这里使用child轴来获取后,/*[2]/*
代表的就是第二个div元素下的后代元素test_group_2。
【preceding-sibling 轴】
示例代码如下:
<ul>
<li>Beijing</li>
<li>Shanghai</li>
<li class="selected">Guangzhou</li>
<li>Nanjing</li>
<li>Jinan</li>
</ul>
如果我们想要定位Beijing和Shanghai两个元素,就可以使用以下的路径表达式。
driver.find_element(By.XPATH, "//li[@class='selected']/preceding-sibling::li[position()<=2]")
代码中的//li[@class=‘selected’]会选择到Guangzhou这个元素,因为这里指定了class的属性,然后我们使用preceding-sibling轴选择该元素之前的所有同级元素,加之使用了li[position()<=2]的特定条件来筛选出前两个元素。
【following-sibling 轴】
还是同样的一套代码:
<ul>
<li>Beijing</li>
<li>Shanghai</li>
<li class="selected">Guangzhou</li>
<li>Nanjing</li>
<li>Jinan</li>
</ul>
假如我们需要选中Nanjing与Jinan这两个元素,就可以使用以下的路径表达式。
driver.find_element(By.XPATH, "//li[@class='selected']/following-sibling::li")
同样的,//li[@class=‘selected’]不多解释,这里使用following-sibling轴来选择该元素之后的所有同级元素,因为这里选取了之后的所有元素,所以就不需要进行特定的位置条件筛选了,直接指定标签名即可。
【self 轴】
实例代码如下:
<div id="a_word">
<p class="selected">The word is a.</p>
</div>
这里我们如果要选中p元素,直接使用self轴即可。
driver.find_element(By.XPATH, "//p[@class='selected']/self::node()")
上面的代码使用了self::node()表示当前选择的节点,但它和其他的轴特性不同,一般情况下我们直接使用.
就可以了,没有必要大费周章。
2.2.4 函数
上面的相对路径定位方法中我们可以看到一些关于函数的用法,比如position()<=2
等,其实XPath定位方式是支持多种内置函数的,用好这些内置函数也可以帮助我们更加精准高效的定位到自己需要的元素。下面我们就来介绍一下,一些常用的内置函数。
starts-with()
这个函数用来匹配元素的属性值是否以指定的字符串开头。
driver.find_element(By.XPATH, "//input[starts-with(@id, 'ke')]")
如上代码,这里我们使用starts-with来匹配元素的id属性是否以ke开头。
contains()
这个函数用来检查元素中的文本内容是否包含指定的字符串。
driver.find_element(By.XPATH, "//div[contains(text(), 'fill')]")
这里我们使用contains来检查div元素的文本内容是否包含fill。
substring()
这个函数用于截取字符串中的指定部分内容。
driver.find_element(By.XPATH, "//span[substring(text(), 1, 3) = 'key']")
以上代码是将span元素进行截取,截取内容为前三个字符,这里的= 'key'
是用来做结果比较的,查看截取的结果是否等于指定的字符内容。需要注意的是,匹配的起始数是1,而不像是下标中的从0开始计算。如果比较的结果不匹配,那么这个元素的查找结果仍然会抛出一个NoSuchElementException的异常。
count()
count函数用于获取指定元素的数量。
if driver.find_elements(By.XPATH, "count(//div[@class='advance']) > 1"):
print("找到了符合条件的元素!")
else:
print("没有找到符合条件的元素。")
这里我们直接使用count函数来对元素进行个数判断并返回对应的打印结果。
elements = driver.find_elements(By.XPATH, "count(//div[@class='my-class']) > 1")
if elements:
print("元素个数大于 1")
else:
print("元素个数小于等于 1")
判断方法随意,不过在使用count函数时,需要使用find_elements
方法而不是find_element
。
3. 注意点
以上就是CSS Selector与XPath的一些进阶元素定位技巧,那么在我们的日常工作中,有哪些需要注意的点呢?
3.1 CSS Selector的使用注意点
-
使用CSS Selector定位元素的时候尽量避免单独使用某个属性来定位,比如div标签这样的,页面中肯定存在多个,单独使用会导致定位到多个元素而无法特定下来导致报错;
-
如果没有十足的把握,尽量少用
*
通配符进行定位,往往匹配了某个节点的全部元素或某个指定特征的全部元素,这样的结果特别是在后期脚本运行或维护阶段特别的要命; -
组合定位与选择器的语法一定要熟悉,什么时候用空格什么时候用特定符号要熟练,代码中的字符绝大部分都是英文,一旦用错了这些,排查起来这些字符与空格也会变得较为困难;
-
页面中也会存在一些动态生产的元素属性,这个很多做测开的同学都碰到过,我们一般都会用
attribute
来协助进行定位; -
有些同学喜欢使用嵌套的形式在使用选择器,不是不行,但这样的编程形式会无形降低代码本身的可读性与提升后期维护成本,得不偿失;
3.2 XPath的使用注意点
-
不要使用绝对路径,不要使用绝对路径,不要使用绝对路径,重要的事情说三遍;
-
相对于元素较多或较为复杂结构的页面,使用相对路径并多结合轴、运算符、内置函数来提升表达式的精简与精准程度,提高代码的可读性;
-
一段较为繁琐的表达式,可以尝试将其拆解开进行表达,这个也是提高测试执行效率的技巧之一;
-
浏览器内开发者工具中的copy xpath可以看看,但做做参看就行,实用性讲真不高。