Selenium 自动化测试(四):高级元素定位技巧

本篇深入讲解动态元素定位、自定义 XPath/CSS Selector 以及复杂 DOM 结构的处理方法。

一、动态元素定位

1.1 什么是动态元素?

动态元素是指每次页面加载时属性会变化的元素,常见模式包括:

  • 动态 IDid="btn_20260407_12345"
  • 动态 Classclass="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 子代和兄弟选择器

下一篇:等待机制与脚本优化,让你的测试脚本更稳定、更健壮。

posted @ 2026-04-07 15:59  小小阿狸。  阅读(1)  评论(0)    收藏  举报