【问题标题】:XPath trouble with SeleniumSelenium 的 XPath 问题
【发布时间】:2025-12-29 00:00:04
【问题描述】:

我想这是那些永恒的问题之一,但我需要一些关于 XPath 表达式的帮助。使用 Selenium 搜索的 HTML 如下所示:

<div class="container">
  <div class"row">
    <div class="col-md-6 col-md-offset-3 jumbotron">
      <div class="text-center">
        <h1>Start a new To-Do list</h1>
        <form method="POST" action="/lists/new">
          <input name="item_text" id="id_new_item"
            class="form-control input-lg"
            placeholder="Enter a to-do item" />
          <input type="hidden" name="csrfmiddlewaretoken" value="***********">
          <div class="form-group has-error">
            <span class="help-block">You can&#39;t have an empty list item</span>
          </div>    
        </form>
      </div>
    </div>
  </div>
</div>

Python 中的搜索表达式如下所示:

self.wait_for(lambda: self.assertEqual(
    self.browser.find_element_by_xpath(
        "//span[contains(text(), 'You can&#39;t have an empty list item')]"
        )
    )
)

这是在测试中运行的,即使文本明显存在,它也无法找到文本。测试的回溯是:

ERROR: test_cannot_add_empty_list_items (functional_tests.test_list_item_validation.ItemValidationTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/eric/Git/TDD/functional_tests/test_list_item_validation.py", line 15, in test_cannot_add_empty_list_items
    self.wait_for(lambda: self.assertEqual(
  File "/home/eric/Git/TDD/functional_tests/base.py", line 40, in wait_for
    raise e
  File "/home/eric/Git/TDD/functional_tests/base.py", line 37, in wait_for
    return fn()
  File "/home/eric/Git/TDD/functional_tests/test_list_item_validation.py", line 17, in <lambda>
    "//span[contains(text(), 'You can&#39;t have an empty list item')]"
  File "/home/eric/Git/TDD/venv/lib/python3.6/site-packages/selenium/webdriver/remote/webdriver.py", line 394, in find_element_by_xpath
    return self.find_element(by=By.XPATH, value=xpath)
  File "/home/eric/Git/TDD/venv/lib/python3.6/site-packages/selenium/webdriver/remote/webdriver.py", line 978, in find_element
    'value': value})['value']
  File "/home/eric/Git/TDD/venv/lib/python3.6/site-packages/selenium/webdriver/remote/webdriver.py", line 321, in execute
    self.error_handler.check_response(response)
  File "/home/eric/Git/TDD/venv/lib/python3.6/site-packages/selenium/webdriver/remote/errorhandler.py", line 242, in check_response
    raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: //span[contains(text(), 'You can&#39;t have an empty list item')]


----------------------------------------------------------------------
Ran 4 tests in 34.851s

FAILED (errors=1)

编辑:断言应该是 assertTrue 而不是 assertEqual,因为我没有将结果与任何东西进行比较。

【问题讨论】:

  • 作为调试的第一步,简化您的 xpath 表达式以查找带有 class='help-block' 或更简单的任何 span 元素的 span 元素。

标签: python selenium selenium-webdriver xpath xpath-1.0


【解决方案1】:

您的 HTML 文档中没有 &amp;#39;。有一个'

&amp;#39; 仅通知 HTML 解析器在此位置将单引号插入到文档树中,它实际上并没有最终成为您可以搜索的内容。

你可以这样做:

self.wait_for(lambda: self.assertEqual(
    self.browser.find_element_by_xpath(
        '//span[contains(text(), "You can\'t have an empty list item")]'
        )
    )
)

但这只有在引号完全这样时才有效。当您的搜索文本包含双引号时,上述内容会中断,您必须以相反的方式转义。只要搜索文本是预定义的,这是可行的。

只要生成的 XPath 有效,就可以开始了。在这种情况下,上面的结果是这个完全有效的 XPath 表达式:

//span[contains(text(), "You can't have an empty list item")]

但如果搜索文本是可变的(例如用户定义的),那么事情就会变得棘手。 Python 知道字符串转义序列,您始终可以使用\"\' 将引号放入字符串中。 XPath 不知道这样的事情。

假设搜索文本为You can't have an "empty" list item。这很容易用 Python 生成,但它不起作用:

//span[contains(text(), "You can't have an "empty" list item")]
-------------------------------------------^ breaks here

这个 XPath 也不起作用:

//span[contains(text(), 'You can't have an "empty" list item')]
--------------------------------^ breaks here

这个也不会,因为 XPath 没有转义序列:

//span[contains(text(), 'You can\'t have an "empty" list item')]
---------------------------------^ breaks here

您可以在 XPath 中解决此问题的方法是连接不同引用的字符串。这个:

//span[contains(text(), concat('You can', "'" ,'t have an "empty" list item'))]

完全有效,将搜索文本You can't have an "empty" list item

你可以在 Python 中做的就是创建这个结构:

  1. '处拆分搜索字符串
  2. 加入', "'", '的部分
  3. 添加concat(',添加')
  4. 插入 XPath 表达式

以下将允许字符串搜索永远不会因为 XPath 格式错误而引发运行时错误:

search_text = 'You can\'t have an "empty" list item'

concat_expr = "', \"'\", '".join(search_text.split("'"))
concat_expr = "concat('" + concat_expr + "')"

xpath = "//span[contains(text(), %s)]" % concat_expr

xpath,作为 Python 字符串文字(将其打印到控制台时会看到):

'//span[contains(text(), concat(\'You can\', "\'", \'t have an "empty" list item\'))]'

XPath 引擎查看它的方式(即内存中的实际字符串):

//span[contains(text(), concat('You can', "'", 't have an "empty" list item'))]

lxml 库允许 XPath variables,这比这要优雅得多,但我怀疑 Selenium 的 find_elements_by_xpath 是否支持它们。

【讨论】:

    【解决方案2】:

    @Tomalak 的回答让我们对 xpathtext() 有了很好的了解。但是,当您使用 find_element_by_xpath() 时,您会轻松地使用 class 属性,并且可以使用以下基于 xpath 的解决方案:

    self.wait_for(lambda: self.assertEqual(
        self.browser.find_element_by_xpath(
        "//span[@class='help-block' and contains(., 'have an empty list item')]"
        )
      )
    )
    

    【讨论】: