元素定位介绍
在Webdriver中,定位元素由WebDriver实例对象完成,通过find_element(s)_by_locator
方法根据不同的定位器locator进行定位,返回另一个基本对象类型WebElement,WebElement对象也可以选择元素。
-
WebDriver
代表浏览器 -
WebElement
表示特定的 DOM 节点(控件,例如链接或输入栏等)
find_element两种表示(以id定位为例):
driver.find_element_by_id('kw')
driver.find_element(By.ID, 'kw')
element与elements:
-
find_element
只返回第一个符合条件的元素,没有符合元素则抛出异常 -
find_elements
返回所有符合条件的元素,没有符合元素则返回空列表
元素选择策略
在 WebDriver 中有 8 种不同的内置元素定位策略:
定位器 Locator | 描述 |
---|---|
class name | 定位class属性与搜索值匹配的元素(不允许使用复合类名) |
css selector | 定位 CSS 选择器匹配的元素 |
id | 定位 id 属性与搜索值匹配的元素 |
name | 定位 name 属性与搜索值匹配的元素 |
link text | 定位link text可视文本与搜索值完全匹配的锚元素 |
partial link text | 定位link text可视文本部分与搜索值部分匹配的锚点元素。如果匹配多个元素,则只选择第一个元素。 |
tag name | 定位标签名称与搜索值匹配的元素 |
xpath | 定位与 XPath 表达式匹配的元素 |
1. 定位器class_name、id、name和tag_name
根据class属性、id属性、name属性、标签名称定位元素。
以百度为例,定位搜索框,HTML页面代码(搜索框为input标签):
<span id="s_kw_wrap" class="bg s_ipt_wr new-pmd quickdelete-wrap">
<span class="soutu-btn"></span>
<input type="text" class="s_ipt" name="wd" id="kw" maxlength="100" autocomplete="off">
<span class="soutu-hover-tip" style="display: none;">按图片搜索</span></span>
定位方式:
driver.find_element_by_class_name('s_ipt')
driver.find_element_by_id('kw')
driver.find_element_by_name('wd')
driver.find_element_by_tag_name('input')
注意实际百度页面input标签不唯一且第一个input标签不是输入框,以上tag name方式无法定位。
2. 定位器link text和partial link text
根据链接文本和部分链接文本定位元素。
依然以百度为例:
<a href="http://xueshu.baidu.com" target="_blank" class="mnav c-font-normal c-color-t">学术</a>
定位相应超链接文本并点击:
driver.find_element_by_link_text('学术').click()
driver.find_element_by_partial_link_text('术').click()
3. CSS选择器
方法:find_element(s)_by_css_selector(CSS Selector参数)
根据tag name、id、class选择元素:
# 1. tag_name
elements = driver.find_elements_by_css_selector('div')
# 等价于
elements = driver.find_elements_by_tag_name('div')
# 2. id(需要在id前加上'#') #id值
elements = driver.find_elements_by_css_selector('#kw')
# 等价于
elements = driver.find_elements_by_id('kw')
# 3. class(需要在class值前加上'.') .class值
elements = driver.find_elements_by_css_selector('.s_ipt')
# 等价于
elements = driver.find_elements_by_class_name('s_ipt')
根据属性选择元素:
element = driver.find_element_by_css_selector('[href="http://news.baidu.com"]')
element = driver.find_element_by_css_selector('[href]')
# 可以组合
element = driver.find_element_by_css_selector('div[target="_blank"]')
选择子元素和后代元素(可组合使用):
-
限定元素1的子元素 元素1 > 元素2 如:#s_kw_wrap > .s_ipt
elements = driver.find_elements_by_css_selector('#s_kw_wrap > .s_ipt')
支持多层级 元素1 > 元素2 > 元素3 > 元素4
-
限定元素1的后代元素 元素1 元素2 如:# form .s_ipt
支持多层级 元素1 元素2 元素3 元素4
组选择:
用逗号分隔,如:.s_ipt,#kw
表示class属性为s_ipt
或id为kw
根据次序选择子节点:
p:nth-child(2)
:选择属于其父元素的第二个子元素的每个p元素。p元素为其父节点的第二个元素。
p:nth-last-child(2)
:选择属于其父元素的倒数第2个子元素的p元素
p:nth-of-type(2)
:选择属于其父元素第二个p元素的每个p元素。
p:nth-last-of-type(2)
:选择属于其父元素倒数第二个p元素的每个p元素。
p:nth-child(even)
:偶数节点
p:nth-child(odd)
:奇数节点
选择兄弟节点:
div > p
:选择父元素是div元素的所有p元素
div + p
:选择紧跟div元素的首个p元素
div ~ p
:选择前面有div元素的每个p元素
CSS选择器完整列表,取自w3school:
选择器 | 例子 | 例子描述 |
---|---|---|
.class | .intro | 选择 class="intro" 的所有元素。 |
.class1.class2 | .name1.name2 | 选择 class 属性中同时有 name1 和 name2 的所有元素。 |
.class1 .class2 | .name1 .name2 | 选择作为类名 name1 元素后代的所有类名 name2 元素。 |
#id | #firstname | 选择 id="firstname" 的元素。 |
* | * | 选择所有元素。 |
element | p | 选择所有 p 元素。 |
element.class | p.intro | 选择 class="intro" 的所有 p 元素。 |
element,element | div, p | 选择所有 div 元素和所有 p 元素。 |
element element | div p | 选择 div 元素内的所有 p 元素。 |
element>element | div > p | 选择父元素是 div 的所有 p 元素。 |
element+element | div + p | 选择紧跟 div 元素的首个 p 元素。 |
element1~element2 | p ~ ul | 选择前面有 p 元素的每个 ul 元素。 |
[attribute] | [target] | 选择带有 target 属性的所有元素。 |
[attribute=value] | [target=_blank] | 选择带有 target="_blank" 属性的所有元素。 |
[attribute~=value] | [title~=flower] | 选择 title 属性包含单词 "flower" 的所有元素。 |
[attribute|=value] | [lang|=en] | 选择 lang 属性值以 "en" 开头的所有元素。 |
[attribute^=value] | a[href^="https"] | 选择其 src 属性值以 "https" 开头的每个 a 元素。 |
[attribute$=value] | a[href$=".pdf"] | 选择其 src 属性以 ".pdf" 结尾的所有 a 元素。 |
[attribute**=value*] | a[href*="w3schools"] | 选择其 href 属性值中包含 "abc" 子串的每个 a 元素。 |
:active | a:active | 选择活动链接。 |
::after | p::after | 在每个 p 的内容之后插入内容。 |
::before | p::before | 在每个 p 的内容之前插入内容。 |
:checked | input:checked | 选择每个被选中的 input 元素。 |
:default | input:default | 选择默认的 input 元素。 |
:disabled | input:disabled | 选择每个被禁用的 input 元素。 |
:empty | p:empty | 选择没有子元素的每个 p 元素(包括文本节点)。 |
:enabled | input:enabled | 选择每个启用的 input 元素。 |
:first-child | p:first-child | 选择属于父元素的第一个子元素的每个 p 元素。 |
::first-letter | p::first-letter | 选择每个 p 元素的首字母。 |
::first-line | p::first-line | 选择每个 p 元素的首行。 |
:first-of-type | p:first-of-type | 选择属于其父元素的首个 p 元素的每个 p 元素。 |
:focus | input:focus | 选择获得焦点的 input 元素。 |
:fullscreen | :fullscreen | 选择处于全屏模式的元素。 |
:hover | a:hover | 选择鼠标指针位于其上的链接。 |
:in-range | input:in-range | 选择其值在指定范围内的 input 元素。 |
:indeterminate | input:indeterminate | 选择处于不确定状态的 input 元素。 |
:invalid | input:invalid | 选择具有无效值的所有 input 元素。 |
:lang(language) | p:lang(it) | 选择 lang 属性等于 "it"(意大利)的每个 p 元素。 |
:last-child | p:last-child | 选择属于其父元素最后一个子元素每个 p 元素。 |
:last-of-type | p:last-of-type | 选择属于其父元素的最后 p 元素的每个 p 元素。 |
:link | a:link | 选择所有未访问过的链接。 |
:not(selector) | :not(p) | 选择非 p 元素的每个元素。 |
:nth-child(n) | p:nth-child(2) | 选择属于其父元素的第二个子元素的每个 p 元素。 |
:nth-last-child(n) | p:nth-last-child(2) | 同上,从最后一个子元素开始计数。 |
:nth-of-type(n) | p:nth-of-type(2) | 选择属于其父元素第二个 p 元素的每个 p 元素。 |
:nth-last-of-type(n) | p:nth-last-of-type(2) | 同上,但是从最后一个子元素开始计数。 |
:only-of-type | p:only-of-type | 选择属于其父元素唯一的 p 元素的每个 p 元素。 |
:only-child | p:only-child | 选择属于其父元素的唯一子元素的每个 p 元素。 |
:optional | input:optional | 选择不带 "required" 属性的 input 元素。 |
:out-of-range | input:out-of-range | 选择值超出指定范围的 input 元素。 |
::placeholder | input::placeholder | 选择已规定 "placeholder" 属性的 input 元素。 |
:read-only | input:read-only | 选择已规定 "readonly" 属性的 input 元素。 |
:read-write | input:read-write | 选择未规定 "readonly" 属性的 input 元素。 |
:required | input:required | 选择已规定 "required" 属性的 input 元素。 |
:root | :root | 选择文档的根元素。 |
::selection | ::selection | 选择用户已选取的元素部分。 |
:target | #news:target | 选择当前活动的 #news 元素。 |
:valid | input:valid | 选择带有有效值的所有 input 元素。 |
:visited | a:visited | 选择所有已访问的链接。 |
4. XPath表达式
以百度搜索输入栏为例
-
绝对路径:
/
表示/html/body/div[1]/div[2]/div[5]/div[1]/div/form/span[1]/input
-
相对路径:
//
表示//input
,当前存在20个input标签,不唯一,需要其他信息-
相对路径 + 索引定位
找到上级中唯一的标签,
//form/span[1]/input
-
相对路径 + 属性定位
找到唯一属性值,
//input[@autocomplete="off"]
-
相对路径 + 通配符定位(Chrome复制xpath方式)
任意标签下的唯一属性值,
//*[@autocomplete="off"]
,//*[@*="off"]
复制xpath经常会出错,不是万能的
-
相对路径 + 部分属性值定位
以of开头:
//input[starts-with(@autocomplete,"of")]
以ff结尾:
//*[substring(@autocomplete,2)='ff']
从第2个字符开始的值为ff包含:
//*[contains(@autocomplete,'of')]
-
相对路径 + 文本定位
非超链接文本:
//span[text()='按图片搜索']
-
XPath语法部分,选自w3school。
选取节点:
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点。 |
/ | 从根节点选取,绝对路径。 |
// | 从匹配选择的当前节点选择文档中的节点,相对路径。 |
. | 选取当前节点。 |
.. | 选取当前节点的父节点。 |
@ | 选取属性。 |
以XML文档为例:
<?xml version="1.0" encoding="ISO-8859-1"?>
<bookstore>
<book>
<title lang="eng">Harry Potter</title>
<price>29.99</price>
</book>
<book>
<title lang="eng">Learning XML</title>
<price>39.95</price>
</book>
</bookstore>
路径表达式 | 结果 |
---|---|
bookstore | 选取 bookstore 元素的所有子节点。 |
/bookstore | 选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径! |
bookstore/book | 选取属于 bookstore 的子元素的所有 book 元素。 |
//book | 选取所有 book 子元素,而不管它们在文档中的位置。 |
bookstore//book | 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。 |
//@lang | 选取名为 lang 的所有属性 |
选取指定节点
路径表达式 | 结果 |
---|---|
/bookstore/book[1] | 选取属于 bookstore 子元素的第一个 book 元素。 |
/bookstore/book[last()] | 选取属于 bookstore 子元素的最后一个 book 元素。 |
/bookstore/book[last()-1] | 选取属于 bookstore 子元素的倒数第二个 book 元素。 |
/bookstore/book[position()<=2] | 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。 |
//title[@lang] | 选取所有拥有名为 lang 的属性的 title 元素。 |
//title[@lang='eng'] | 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。 |
/bookstore/book[price>35.00] | 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。 |
/bookstore/book[price>35.00]/title | 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。 |
选取未知节点
通配符 | 描述 |
---|---|
* | 匹配任何元素节点。 |
@* | 匹配任何属性节点。 |
node() | 匹配任何类型的节点。 |
路径表达式 | 结果 |
---|---|
/bookstore/* | 选取 bookstore 元素的所有子元素。 |
//* | 选取文档中的所有元素。 |
//title[@*] | 选取所有带有属性的 title 元素。 |
组选择,用"|"运算符表示:
路径表达式 | 结果 |
---|---|
//book/title | //book/price | 选取 book 元素的所有 title 和 price 元素。 |
//title | //price | 选取文档中的所有 title 和 price 元素。 |
/bookstore/book/title | //price | 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。 |
XPath轴
轴名称 | 结果 |
---|---|
ancestor | 选取当前节点的所有先辈(父、祖父等)。 |
ancestor-or-self | 选取当前节点的所有先辈(父、祖父等)以及当前节点本身。 |
attribute | 选取当前节点的所有属性。 |
child | 选取当前节点的所有子元素。 |
descendant | 选取当前节点的所有后代元素(子、孙等)。 |
descendant-or-self | 选取当前节点的所有后代元素(子、孙等)以及当前节点本身。 |
following | 选取文档中当前节点的结束标签之后的所有节点。 |
namespace | 选取当前节点的所有命名空间节点。 |
parent | 选取当前节点的父节点。 |
preceding | 选取文档中当前节点的开始标签之前的所有节点。 |
preceding-sibling | 选取当前节点之前的所有同级节点。 |
self | 选取当前节点。 |
步的实例:
轴名称::节点测试[谓语]
例子 | 结果 |
---|---|
child::book | 选取所有属于当前节点的子元素的 book 节点。 |
attribute::lang | 选取当前节点的 lang 属性。 |
child::* |
选取当前节点的所有子元素。 |
attribute::* |
选取当前节点的所有属性。 |
child::text() | 选取当前节点的所有文本子节点。 |
child::node() | 选取当前节点的所有子节点。 |
descendant::book | 选取当前节点的所有 book 后代。 |
ancestor::book | 选择当前节点的所有 book 先辈。 |
ancestor-or-self::book | 选取当前节点的所有 book 先辈以及当前节点(如果此节点是 book 节点) |
child::* /child::price |
选取当前节点的所有 price 孙节点。 |
(以上:*
在Markdown格式显示成了emoji,所以用代码表示了)
选择定位器
如果HTML的 id 唯一、可用、可预测,优先选择id定位。
如果没有唯一的id,优先使用CSS选择器查找元素。
XPath表达式类似CSS选择器,功能强大、非常灵活,但语法复杂、很难调试,通常未经过浏览器厂商的性能测试,并且运行速度很慢。在定位元素时,建议优先使用CSS选择器,CSS不好解决再考虑XPath。
链接文本和部分链接文本的选择策略只能作用于链接元素,且它们在WebDriver内部调用XPath选择器。
由于页面上经常出现同一标签的多个元素,标签名定位元素比较危险,但是在调用find_elements方法返回元素集合的时候非常有用。
建议尽可能保持定位器的紧凑性和可读性。使用 WebDriver 遍历 DOM 结构是一项性能花销很大的操作,搜索范围越小越好。