3、元素定位(下):XPath定位与CSS Selector定位

好,咱们接着聊元素定位。上一章我们把基础定位方法过了一遍,这一章要深入两个真正有杀伤力的定位方式——XPath和CSS Selector。说实话,这两个才是自动化测试里的重头戏。我个人习惯是,能用CSS Selector就用CSS,搞不定的再上XPath。为什么?后面我会细说。

一、XPath定位:绝对路径与相对路径

XPath这东西,说白了就是给HTML文档画一张“地图”。你可以用绝对路径,也可以用相对路径来找元素。

1. 绝对路径

绝对路径从根节点/html开始,一层层往下写。比如:

/html/body/div[2]/div[1]/form/input[3]

嗯,这里要注意——这种写法极其脆弱。我在项目中遇到过,前端同事只是加了一个div,整个定位就全崩了。你想想看,页面结构稍微一变,你的脚本就得重写。所以,绝对路径能不用就别用

避坑指南: 我曾经在一个大型电商项目中,因为用了绝对路径,每次迭代都要花半天时间修定位。后来全部改成相对路径,维护成本直接降了80%。

2. 相对路径

相对路径用双斜杠//开头,表示从任意位置开始查找。这才是我们日常用的主力:

//input[@id='username']
//div[@class='login-box']//button

相对路径灵活多了。你只要找到元素的一个特征,就能定位到它。我个人习惯是,优先用id或name属性,没有的话再用class或其他属性组合。

二、XPath轴:让你在DOM树里“横着走”

XPath轴是个好东西,但很多人不太会用。它让你可以在DOM树里上下左右自由穿梭。比如你想找一个元素,但它本身没什么特征,你可以通过它的兄弟节点或父节点来定位。

常用的轴有这些:

轴名称 含义 示例
ancestor 所有祖先节点 //span/ancestor::div
following-sibling 后面的兄弟节点 //h2/following-sibling::p
preceding-sibling 前面的兄弟节点 //p/preceding-sibling::h2
child 直接子节点 //div/child::input
parent 父节点 //input/parent::form

举个例子。假设页面上有个表格,你想定位第三行第二列的元素,但那个元素没有id也没有class。怎么办?你可以用轴:

//table[@id='data-table']/tbody/tr[3]/td[2]

或者更灵活一点,先定位到某个已知元素,再用轴找它的邻居:

//span[text()='用户名']/following-sibling::input

为什么会这样?因为很多时候,页面上有文字标签,但输入框本身没有特征。用轴就能轻松搞定。

三、XPath常用函数:让定位更精准

XPath自带一些函数,能帮你做更精细的筛选。我常用的有这几个:

  • text():匹配文本内容。比如//button[text()='提交']
  • contains():包含匹配。比如//input[contains(@class, 'search')]
  • starts-with():开头匹配。比如//div[starts-with(@id, 'result')]
  • not():取反。比如//input[not(@disabled)]
  • normalize-space():去除前后空格。比如//span[normalize-space()='确定']
小技巧: 我个人最常用的是contains()。因为很多前端框架会动态生成class,比如"btn btn-primary btn-lg",用contains(@class, 'btn-primary')比用精确匹配靠谱得多。

来看个综合例子。假设页面上有个搜索框,它的id是动态生成的,但placeholder是固定的:

//input[contains(@placeholder, '搜索')]

这样不管id怎么变,定位都不会失效。我在项目中遇到过类似场景,当时用contains()一次搞定,同事还在那纠结怎么处理动态id呢。

四、CSS Selector定位:更快更简洁

CSS Selector是Selenium官方推荐的定位方式。为什么?因为它比XPath快,而且写法更简洁。说白了,XPath能做的,CSS Selector大部分也能做,但CSS Selector写起来更清爽。

1. ID选择器

用#号表示:

#username  // 等价于 //*[@id='username']

2. 类选择器

用点号表示:

.login-btn  // 等价于 //*[@class='login-btn']

3. 属性选择器

用方括号表示:

input[name='email']          // 属性精确匹配
input[placeholder*='邮箱']    // 包含匹配(类似contains)
input[type^='sub']           // 开头匹配(类似starts-with)
input[class$='btn']          // 结尾匹配

4. 层级选择器

CSS Selector的层级表达比XPath更直观:

div.login-box > input         // 直接子元素(>号)
div.login-box input           // 后代元素(空格)
label + input                 // 相邻兄弟元素(+号)
h2 ~ p                        // 通用兄弟元素(~号)

举个例子。你想定位登录框里的提交按钮,用CSS Selector可以这样写:

div.login-form > button[type='submit']

用XPath写就是:

//div[@class='login-form']/button[@type='submit']

你对比一下,哪个更清爽?我个人习惯是,能用CSS Selector绝不用XPath。除非遇到文本匹配、轴操作这些CSS搞不定的场景,我才会切回XPath。

核心原则: CSS Selector优先,XPath兜底。CSS搞不定的(比如按文本内容定位、按轴定位),再用XPath。

五、实战对比:CSS vs XPath

咱们来看几个实际场景,对比一下两种定位方式的写法:

场景 CSS Selector XPath
按ID定位 #username //*[@id='username']
按class定位 .btn-primary //*[@class='btn-primary']
按属性定位 input[type='text'] //input[@type='text']
按文本定位 不支持 //button[text()='提交']
按部分属性匹配 input[class*='btn'] //input[contains(@class, 'btn')]
找父元素 不支持 //input/parent::div

看到没?CSS Selector在大部分场景下更简洁,但遇到文本匹配和轴操作时,就得靠XPath了。嗯,这里要注意——不要死磕一种方式,两种都掌握,根据场景灵活切换才是正道。

我的建议: 写定位时,先想想这个元素有什么特征。有id?用CSS的#。有class?用CSS的点。有属性?用CSS的方括号。如果这些都没有,或者需要按文本找,再上XPath。这样写出来的脚本,既快又稳。

好了,这一章的内容就到这。下一章我们会讲等待机制——说实话,这是自动化测试里最容易踩坑的地方。到时候我会分享一些我踩过的坑,保证让你少走弯路。