Selenium-WebDriver API for locating UI elements
Selenium Webdriver提供了很多种获取UI Element的方法,By id, className, tagName, name, linkText, partialLinkText, cssSelector, xpath, Using JavaScript。参考Selenium官网 Locating UI Elements (WebElements)。
我们这里只介绍3种经常被用到的方法:id, cssSelector, xpath。
-
id: 最理想的定位。但是一个设计良好的页面,是不可能给每一个element都加上id的。
IWebElement element = driver.findElement(By.id("coolestWidgetEvah"));
-
xpath: XML Path的简称。 HTML文档本身也是一个标准的XML页面,所以我们可以使用xpath来定义页面的element。
- 非常强大的定位方法,所有的element都可以用xpath获取
- 如果xpath包含很多绝对路径的话,一旦页面结构发生变化,测试就会失败。
<input type="text" name="example" /> <INPUT type="text" name="other" /> List<IWebElement> inputs = driver.findElements(By.xpath("//input"));
-
cssSelector: 跟xpath比较类似,但是速度更快。
- 使用xpath时采用的是遍历页面的方法,而cssSelector使用css来定位element,因此在性能上cssSelector开销更少。
- cssSelector会比xpath稳定,原因是页面结构常变化,但是用于定义element样式的css不会经常改变,除非有大的UI变动。
- 需要注意的是,在不同的浏览器上,同一个元素的cssSelector可能是不一样的。
<div id="food"><span class="dairy">milk</span><span class="dairy aged">cheese</span></div> IWebElement cheese = driver.findElement(By.cssSelector("#food span.dairy.aged"));
Evaluate and validate XPath/CSS selectors in Chrome Developer Tools
获取xpath和cssSelector,可以借助Chrome Developer Tooles:
图1.png
也可以借助Chrome的小工具Copy Css Selector,在Chrome Extensions搜索安装即可。
图2.png
借助工具拷贝得到的结果,往往不能直接拿来使用:
#xpath
IWebElement button = driver.findElement(By.xpath("//*[@id='login']/div/div[1]/div[1]/button"));
#cssSelector
IWebElement button = driver.findElement(By.cssSelector("#login > div:nth-child(4) > div > div > button"));
这是两个是用Developer Tooles拷贝的结果,它们都是从一个id为login的element开始遍历,一旦element结构发生变化,测试就会失败。
来看一个比较好的例子:
xpath
WebElement button = driver.findElemen(By.xpath("body[data-controller='flight'][data-action='index']"));
cssSelector
WebElement button = driver.findElement(By.cssSelector("[id$=search_from_date]"));
对比上面两个例子,我们来总结一下,好的locating有哪些特点:
- 不要包含过多路径,尤其是绝对路径,除非是必要的
- 可变因素尽量少,能用1个className定位的元素就不要用多个
验证xpath和cssSelector的有效性,Chrome Dev Tools也提供了2种方法。一种是使用Elements的search功能,在Elements中 Ctrl+F 打开搜索,输入xpath或者cssSelector,就可以查看elements的数量和位置。
图4.png
另一种方法是在Console中使用$x("some_xpath")
或者$$("css-selectors")
。
Usage of xpath
- 当xpath的路径以“/”开头时,表示绝对路径,xpath解析引擎会从文档的根节点开始解析。当/出现在xpath的路径中时,表示寻找父节点的直接子节点。
- 当xpath的路径以“//”开头时,表示相对路径,xapth解析引擎会从文档的任意符合的节点开始解析。当//出现在xpath路径中时,表示寻找父节点下任意符合条件的子节点,不管嵌套了多少层级。
xpath基于准确元素属性的定位:
表达式 | 描述 |
---|---|
/bookstore |
查找根元素bookstore |
/bookstore/book[1] |
查找根元素bookstore子元素的第一个book元素 |
/bookstore/input[last()-1] |
查找根元素bookstore子元素的倒数第二个book元素 |
//form[1] |
查找页面上第一个form元素 |
//form[1]/input |
查找页面上第一个form元素内直接子input元素(即from的下一级是input的元素) |
//form[1]//input |
查找页面上第一个form元素内所有的input元素(不管嵌套了多少层) |
//form[@id='loginForm'] |
查找id为loginForm的form元素 |
//input[@name='username'] |
查找name位username的input元素 |
//*[@id='login'] |
查找id为login的元素(不确定类型) |
//form[@id='loginForm']/input[1] |
查找id为loginForm的Form元素下的第一个input元素 |
//input[@name='continue'][@type='button'] |
查找name为continue,type为button的input元素 |
xpath还支持模糊匹配查询:
-
用contains关键字
<a rel="logout" href="https://www.logout.com"> driver.findElement(By.xpath("//a[contains(@href, 'logout')]"));
查找页面上所有href包含logout的a元素。
-
用star-with
driver.findElement(By.xpath("//a[start-with(@rel, 'log')]"));
-
用text关键字
driver.findElement(By.xpath("//*[text()='logout']"));
查找页面中所有的logout,适合纯文本的查询。
driver.findElement(By.xpath("//a[contains(text(), 'logout')]"));
一般用于超文本上显示的部分或全部文本信息。
-
用*查找未知元素
表达式 描述 /bookstore/* 选取 bookstore 元素的所有子元素 //* 选取文档中的所有元素 //title[@*] 选取所有带有属性的 title 元素
XPath轴(Axes)可定义相对于当前元素的元素集。
轴名称 | 表达式 | 描述 |
---|---|---|
parent | //*[@id="q"]/parent::div |
查找当前节点父节点,等价于//*[@id="q"]/..
|
child | //*[@id="fkbx"]/child::input |
查找当前节点的子节点,等价于//*[@id="fkbx"]/input ,通常可省略。 |
ancestor | //*[@id="q"]/ancestor::div |
查找当前节点的所有上层节点,只能找上层节点,不能找上层节点的兄弟节点。 |
descendant | ///*[@id="f"]/descendant::div |
查找当前节点的所有下层节点,不管嵌套多少层。 |
following | //*[@id="hf"]/following::input[@id="q"] |
查找当前节点之后显示的所有节点,包括:子节点、兄弟节点、兄弟节点的子节点。 |
following-sibling | //*[@id="hf"]/following-sibling::div |
查找当前节点之后显示的所有兄弟节点。 |
preceding | //*[@id="spchx"]/preceding::input[@id="q"] |
查找当前节点前面显示的所有节点,包括兄弟节点、兄弟节点的子节点、父节点、父节点的兄弟节点等。 |
preceding-sibling | //*[@id="fkbx"]/preceding-sibling::div |
查找当前节点前面所有的兄弟节点。 |
Usage of CSS Selector
-
定位href属性为
/about.html
的a元素:a[href="about.html"]
-
定位id为loginForm中的name为username, type为text的子input元素:
#loginForm > input[name="username"][type="text"]
IWebElement password = driver.findElement(By.cssSelector("#login_form > dl > dt > input.credential));
注意:跟xpath相比,[]内的不需要用@指明属性。这里的路径 > 不可省略。
-
定位使用了复合样式表的元素
<button id="J_sidebar_login" class="btn btn_big btn_submit" type="submit">登录</button> driver.findElement(By.cssSelector("button.btn.btn_big.btn_submit"));
-
CSS Selector还有一些高级用法: “^”匹配一个前缀字符,“$”匹配一个后置字符, “*”匹配任意字符
定位一个有id属性,并且id属性是以”id_”开头的a元素:
a[id^='id_']
定位一个有id属性,并且id属性是以”_about”结尾的a元素:a[id$='_about']
定位一个有id属性,并且id属性中包含”pattern”字符的a元素:a[id*='id_pattern']
-
CSS Selector同样支持定位子节点、兄弟节点的选择:CSS选择器
-
当不想包含某个class的时候, 比如:
By.CssSelector("div.active:not(.hide)")
Summary
- 当element有id属性时,尽量使用id
- xpath很强大,但是定位性能不是很好,可以考虑cssSelector
- 当有超链接元素时,可以考虑partialText和linkText
网友评论