Selenium 自动化测试(四):高级元素定位技巧
本篇深入讲解动态元素定位、自定义 XPath/CSS Selector 以及复杂 DOM 结构的处理方法。
一、动态元素定位
1.1 什么是动态元素?
动态元素是指每次页面加载时属性会变化的元素,常见模式包括:
- 动态 ID:
id="btn_20260407_12345" - 动态 Class:
class="btn btn-abc123" - 动态生成的内容:通过 AJAX 异步加载的元素
1.2 应对策略
策略一:使用稳定的部分属性
# 动态 ID:使用 starts-with
driver.find_element(By.XPATH, "//input[starts-with(@id, 'username_')]")
# 动态 ID:使用 contains
driver.find_element(By.XPATH, "//div[contains(@id, 'user-info')]")
# 动态 Class:使用 contains
driver.find_element(By.XPATH, "//button[contains(@class, 'submit')]")
策略二:使用 CSS Selector 通配符
# ID 以 "btn_" 开头
driver.find_element(By.CSS_SELECTOR, "[id^='btn_']")
# ID 以 "_submit" 结尾
driver.find_element(By.CSS_SELECTOR, "[id$='_submit']")
# ID 包含 "user"
driver.find_element(By.CSS_SELECTOR, "[id*='user']")
# Class 包含 "btn"
driver.find_element(By.CSS_SELECTOR, "[class*='btn']")
策略三:通过父元素或兄弟元素间接定位
# 通过父元素定位
driver.find_element(By.XPATH, "//div[@id='form-container']//input")
# 通过 label 文本定位关联的 input
driver.find_element(By.XPATH, "//label[text()='用户名']/following-sibling::input")
# 通过兄弟元素定位
driver.find_element(By.XPATH, "//td[text()='张三']/preceding-sibling::td/input[@type='checkbox']")
策略四:使用多个属性组合定位
# 多属性组合(XPath)
driver.find_element(By.XPATH, "//input[@type='text' and @placeholder='请输入用户名']")
# 多属性组合(CSS Selector)
driver.find_element(By.CSS_SELECTOR, "input[type='text'][placeholder='请输入用户名']")
二、自定义 XPath 高级技巧
2.1 XPath 轴(Axes)
XPath 轴允许在 DOM 树中沿不同方向导航:
# following-sibling:后续兄弟节点
driver.find_element(By.XPATH, "//label[@for='email']/following-sibling::input")
# preceding-sibling:前面的兄弟节点
driver.find_element(By.XPATH, "//input[@id='password']/preceding-sibling::label")
# parent:父节点
driver.find_element(By.XPATH, "//input[@id='username']/parent::div")
# ancestor:所有祖先节点
driver.find_element(By.XPATH, "//input[@id='username']/ancestor::form")
# descendant:所有后代节点
driver.find_element(By.XPATH, "//form[@id='login-form']/descendant::button")
# child:直接子节点
driver.find_element(By.XPATH, "//div[@class='list']/child::li[1]")
2.2 XPath 内置函数
# text() - 文本内容
driver.find_element(By.XPATH, "//button[text()='登录']")
# contains() - 包含
driver.find_element(By.XPATH, "//a[contains(text(), '更多')]")
# normalize-space() - 去除空白
driver.find_element(By.XPATH, "//button[normalize-space(text())='提 交']")
# position() - 位置
driver.find_element(By.XPATH, "//ul/li[position()=3]")
driver.find_element(By.XPATH, "//ul/li[last()]")
# string-length() - 文本长度
driver.find_element(By.XPATH, "//a[string-length(text()) > 5]")
# translate() - 大小写转换
driver.find_element(By.XPATH, "//button[translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')='submit']")
# not() - 取反
driver.find_element(By.XPATH, "//input[not(@disabled)]")
2.3 实战案例:表格数据定位
<table id="user-table">
<tr>
<th>姓名</th><th>年龄</th><th>操作</th>
</tr>
<tr>
<td>张三</td><td>28</td><td><button class="edit-btn">编辑</button></td>
</tr>
<tr>
<td>李四</td><td>32</td><td><button class="edit-btn">编辑</button></td>
</tr>
</table>
# 找到"张三"所在行的编辑按钮
driver.find_element(
By.XPATH,
"//table[@id='user-table']//td[text()='张三']/following-sibling::td/button"
).click()
# 找到第二行数据
driver.find_element(By.XPATH, "//table[@id='user-table']//tr[2]/td[1]").text # "张三"
# 获取表格所有行
rows = driver.find_elements(By.XPATH, "//table[@id='user-table']//tr[position()>1]")
for row in rows:
cells = row.find_elements(By.TAG_NAME, "td")
print(f"姓名: {cells[0].text}, 年龄: {cells[1].text}")
三、自定义 CSS Selector 高级技巧
3.1 CSS Selector 伪类
# :first-child / :last-child
driver.find_element(By.CSS_SELECTOR, "ul li:first-child")
driver.find_element(By.CSS_SELECTOR, "ul li:last-child")
# :nth-child(n)
driver.find_element(By.CSS_SELECTOR, "ul li:nth-child(2)")
# :not() 排除
driver.find_element(By.CSS_SELECTOR, "input:not([disabled])")
driver.find_element(By.CSS_SELECTOR, "div:not(.hidden)")
# :has() 包含(CSS4,部分浏览器支持)
driver.find_element(By.CSS_SELECTOR, "div:has(> .error-msg)")
3.2 CSS Selector 属性选择器
# 精确匹配
driver.find_element(By.CSS_SELECTOR, "input[name='username']")
# 以某值开头
driver.find_element(By.CSS_SELECTOR, "input[name^='user']")
# 以某值结尾
driver.find_element(By.CSS_SELECTOR, "input[name$='name']")
# 包含某值
driver.find_element(By.CSS_SELECTOR, "input[name*='name']")
# 多值匹配(class 包含某个词)
driver.find_element(By.CSS_SELECTOR, "button[class~='primary']")
# 以某个词开头(class 以 btn 开头)
driver.find_element(By.CSS_SELECTOR, "button[class|='btn']")
3.3 CSS Selector 层级关系
# 后代选择器(所有层级)
driver.find_element(By.CSS_SELECTOR, "form input")
# 子代选择器(直接子元素)
driver.find_element(By.CSS_SELECTOR, "form > input")
# 相邻兄弟选择器
driver.find_element(By.CSS_SELECTOR, "label + input")
# 通用兄弟选择器
driver.find_element(By.CSS_SELECTOR, "label ~ input")
四、处理 Shadow DOM
4.1 什么是 Shadow DOM?
Shadow DOM 是 Web Components 的一部分,它将 DOM 树封装在隐藏的边界中。Selenium 默认无法直接访问 Shadow DOM 内的元素。
4.2 Shadow DOM 定位方法
# 访问 Shadow DOM
shadow_host = driver.find_element(By.CSS_SELECTOR, "custom-element")
# 获取 shadow root
shadow_root = shadow_host.shadow_root
# 在 shadow root 内定位元素
element = shadow_root.find_element(By.CSS_SELECTOR, "#inner-button")
element.click()
4.3 嵌套 Shadow DOM
# 外层 Shadow DOM
outer_shadow = driver.find_element(By.CSS_SELECTOR, "outer-component").shadow_root
# 内层 Shadow DOM
inner_shadow = outer_shadow.find_element(By.CSS_SELECTOR, "inner-component").shadow_root
# 内层元素
button = inner_shadow.find_element(By.CSS_SELECTOR, "button")
button.click()
4.4 使用 JavaScript 穿透 Shadow DOM
# 使用 JS 获取 Shadow DOM 内的元素
script = """
return document.querySelector('custom-element')
.shadowRoot.querySelector('#inner-button');
"""
element = driver.execute_script(script)
element.click()
五、定位策略选择决策树
元素有唯一 ID?
├── 是 → By.ID
└── 否 → 元素有唯一 Name?
├── 是 → By.NAME
└── 否 → 需要复杂层级导航?
├── 是 → By.XPATH(轴定位)
└── 否 → CSS Selector 能满足?
├── 是 → By.CSS_SELECTOR
└── 否 → By.XPATH(函数定位)
六、总结
| 场景 | 推荐方法 |
|---|---|
| 动态 ID/Class | contains() / starts-with() / 属性通配符 |
| 表格数据 | XPath 轴定位 + text() 函数 |
| Shadow DOM | shadow_root / JavaScript 穿透 |
| 排除条件 | XPath not() / CSS :not() |
| 复杂层级 | XPath 轴 / CSS 子代和兄弟选择器 |
下一篇:等待机制与脚本优化,让你的测试脚本更稳定、更健壮。

浙公网安备 33010602011771号