UI test with SpecFlow and Selenium - 定位元素

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. 不要包含过多路径,尤其是绝对路径,除非是必要的
  2. 可变因素尽量少,能用1个className定位的元素就不要用多个

验证xpath和cssSelector的有效性,Chrome Dev Tools也提供了2种方法。一种是使用Elements的search功能,在Elements中 Ctrl+F 打开搜索,输入xpath或者cssSelector,就可以查看elements的数量和位置。


图4.png

另一种方法是在Console中使用$x("some_xpath")或者$$("css-selectors")

图3.png

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 查找当前节点前面所有的兄弟节点。
image.png

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

  1. 当element有id属性时,尽量使用id
  2. xpath很强大,但是定位性能不是很好,可以考虑cssSelector
  3. 当有超链接元素时,可以考虑partialText和linkText
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容

  • 在使用selenium webdriver进行元素定位时,通常使用findElement或findElements...
    不勤奋阅读 1,571评论 1 3
  • 这篇文章在介绍官网的同时使用了比较多的脚本示例,示例里遇到的问题有部分在本篇文章进行了解释,还有一篇文章专门记录了...
    顾顾314阅读 12,913评论 3 32
  • 前言: Appium Python API全集,不知道哪个大神整理的,这里贴出来分享给大家。 1.contex...
    Roshan_阅读 1,964评论 0 5
  • 炎炎夏日(好热啊天天三十多度)怎能少得了西瓜,我不仅吃瓜还画瓜哈哈 吧唧吧唧快来吃瓜 先用铅笔画好你要画的西瓜的形...
    与屿羽阅读 2,038评论 0 29
  • 清晨,送女儿去幼儿园,给她梳头时闻到口里有异味就说“你胃里有火了”。女儿反问到,“爸爸,那我的胃怎么没有烧起来呢?...
    但看人生福祸阅读 197评论 0 0