需要转储selenium s中具有元素id的整个DOM树

2024-05-02 16:44:20 发布

您现在位置:Python中文网/ 问答频道 /正文

我一直在使用python selenium进行web自动化测试。自动化的关键部分是为HTML页面中用户可见的对象找到正确的元素。下面的API大部分时间都可以工作,但不是所有时间都可以。

find_element_by_xxx,  xxx can be id, name, xpath, tag_name etc. 

当HTML页面太复杂时,我想搜索dom树。不知道是否可以要求selenium服务器序列化整个DOM(使用可以通过webdriver服务器执行操作的元素id)。客户端(python脚本)可以使用自己的搜索算法来查找正确的元素。

注意,python selenium可以通过

drv.page_source

但是,从selenium服务器的角度来看,解析它并没有给出内部元素id,因此没有用处。

编辑1: 解释一下以使其更清楚(感谢@alecxe):这里需要的是selenium服务器中所有DOM元素的序列化表示(保留它们的DOM结构),这个序列化表示可以发送到客户端(python selenium测试应用程序),客户端可以进行自己的搜索。


Tags: 用户name服务器webid元素客户端序列化
3条回答

问题

好的,所以可能有一些情况需要在客户端(Python)而不是服务器(browser)端对页面执行一些实质性的处理。例如,如果您已经用Python编写了某种机器学习系统,并且在对它们执行操作之前需要分析整个页面,那么尽管可以通过一堆find_element调用来完成,但这会非常昂贵,因为每个调用都是客户机和服务器之间的往返调用。而重写它以便在浏览器中工作可能代价太高。

为什么硒的识别物不会“这么做”

但是,我看不到一种有效的方法来获得DOM与Selenium自己的标识符的序列化。Selenium根据需要在调用find_element或DOM节点从execute_script调用返回(或传递给execute_async_script给脚本的回调)时创建这些标识符。但是如果您调用find_element来获取每个元素的标识符,那么您就回到了原点。我可以想象在浏览器中用所需的信息装饰DOM,但是没有公共API来请求某种预分配WebElementid。事实上,这些标识符的设计是不透明的,所以即使解决方案设法获得所需的信息,我也会担心跨浏览器的可行性和持续的支持。

解决方案

然而,有一种方法可以得到一个在双方都能工作的寻址系统:XPath。其思想是将DOM序列化解析为客户端的树,然后获取您感兴趣的节点的XPath,并使用它获取相应的WebElement。因此,如果您需要执行数十次客户机-服务器往返以确定需要执行单击的单个元素,那么您就可以将其减少为对页源的初始查询,再加上对所需XPath的单个find_element调用。

这是一个非常简单的概念证明。它获取Google首页的主输入字段。

from StringIO import StringIO

from selenium import webdriver
import lxml.etree

#
# Make sure that your chromedriver is in your PATH, and use the following line...
#
driver = webdriver.Chrome()
#
# ... or, you can put the path inside the call like this:
# driver = webdriver.Chrome("/path/to/chromedriver")
#

parser = lxml.etree.HTMLParser()

driver.get("http://google.com")

# We get this element only for the sake of illustration, for the tests later.
input_from_find = driver.find_element_by_id("gbqfq")
input_from_find.send_keys("foo")

html = driver.execute_script("return document.documentElement.outerHTML")
tree = lxml.etree.parse(StringIO(html), parser)

# Find our element in the tree.
field = tree.find("//*[@id='gbqfq']")
# Get the XPath that will uniquely select it.
path = tree.getpath(field)

# Use the XPath to get the element from the browser.
input_from_xpath = driver.find_element_by_xpath(path)

print "Equal?", input_from_xpath == input_from_find
# In JavaScript we would not call ``getAttribute`` but Selenium treats
# a query on the ``value`` attribute as special, so this works.
print "Value:", input_from_xpath.get_attribute("value")

driver.quit()

注:

  1. 上面的代码没有使用driver.page_source,因为Selenium的文档声明不能保证它返回的内容的新鲜度。它可以是当前DOM的状态,也可以是首次加载页面时的DOM状态。

  2. 这个解决方案与find_element在动态内容方面遇到的问题完全相同。如果在进行分析时DOM发生了更改,那么您正在处理DOM的陈旧表示。

  3. 如果在执行分析时必须生成JavaScript事件,并且这些事件会更改DOM,则需要再次获取DOM。(这与前一点类似,但使用find_element调用的解决方案可以通过仔细排序调用序列来避免我在这一点中讨论的问题。)

  4. lxml的树可能在结构上不同于DOM树,这样从lxml获得的XPath就不会寻址DOM中的相应元素。什么lxml进程是浏览器传递给它的HTML的已清理序列化视图。因此,只要代码是为了防止我在第2点和第3点中提到的问题而编写的,我不认为这是可能的情况,但这并非不可能。

有关获取Selenium标识符的任何尝试的问题,请参见myother answer

同样,问题是减少一堆find_element调用,从而避免与它们相关联的往返。

与我的另一个答案不同的方法是使用execute_script在浏览器上执行搜索,然后返回所需的所有元素。例如,此代码需要三次往返,但可以减少为一次往返:

el, parent, text = driver.execute_script("""
var el = document.querySelector(arguments[0]);
return [el, el.parentNode, el.textContent];
""", selector)

这将根据我希望传递的CSS选择器返回元素、元素的父元素和元素的文本内容。在页面加载了jQuery的情况下,我可以使用jQuery执行搜索。而且逻辑可以根据需要变得复杂。

这种方法处理了绝大多数需要减少往返次数的情况,但它并没有考虑到我在另一个答案中所给出的那种情况。

尝试:

find_elements_by_xpath("//*")

它应该与文档中的所有元素匹配。

更新(以匹配问题优化):

使用javascript并将DOM作为字符串返回:

execute_script("return document.documentElement.outerHTML")

相关问题 更多 >