Pupeteer是Node的浏览器自动化库:它允许您使用简单而现代的JavaScript API控制浏览器。
当然,最突出的浏览器任务是浏览网页。自动执行此任务实质上等同于自动执行与网页的交互。
在Puppeteer中,这是通过使用基于字符串的选择器查询DOM元素并执行诸如在元素上单击或键入文本等操作来实现的。例如,打开developer.google.com、找到搜索框并搜索puppetaria的脚本可能如下所示:
(async()=>;{const Browser=await puppeteer.unch({Headless:False});const page=aWait Browser.newPage();await page.goto(';https://developers.google.com/';,{waitUntil:';Load';});//使用合适的css选择器查找搜索框。Const search=等待页面。$(';devsite-search>;form>;div.devsite-search-tainer';);//单击以展开搜索框并将其聚焦。等待搜索。点击();//输入搜索字符串并按Enter键。等待搜索。type(';Pppetaria);等待搜索。按(#39;Enter;);})();
因此,如何使用查询选择器识别元素是Puppeteer体验的重要组成部分。到目前为止,Puppeteer中的选择器仅限于CSS和XPath选择器,尽管这两种选择器在表达上非常强大,但在脚本中持久保存浏览器交互方面可能存在缺陷。
CSS选择器本质上是语法的;它们与DOM树的文本表示的内部工作紧密绑定,因为它们引用了DOM中的ID和类名。因此,它们为Web开发人员提供了一个完整的工具,用于修改或添加样式到页面中的元素,但在这种情况下,开发人员可以完全控制页面及其DOM树。
另一方面,Puppeteer脚本是页面的外部观察者,因此当在此上下文中使用CSS选择器时,它引入了Puppeteer脚本无法控制的有关页面如何实现的隐藏假设。
其结果是,这样的脚本很脆弱,很容易受到源代码更改的影响。例如,假设您使用Puppeteer脚本对一个Web应用程序进行自动化测试,该Web应用程序包含节点<;Button>;作为Body元素的第三个子节点。测试用例中的一段代码可能如下所示:
在这里,我们使用选择器Body:nth-Child(3)&39;来查找提交按钮,但它与这个版本的网页紧密绑定。如果稍后在按钮上方添加了元素,则此选择器将不再起作用!
这对测试编写者来说并不是什么新鲜事:木偶师用户已经在尝试挑选对这种变化很健壮的选择器。有了Puppetaria,我们为用户提供了一种新的工具。
Pupeteer现在提供了一个基于查询可访问性树的替代查询处理程序,而不是依赖于CSS选择器。这里的基本原理是,如果我们要选择的具体元素没有更改,那么相应的可访问性节点也不应该更改。
我们将这样的选择器命名为“ARIA选择器”,并支持查询计算出的可访问性树的可访问名称和角色。与CSS选择器相比,这些属性本质上是语义的。它们与DOM的语法属性无关,而是描述如何通过屏幕阅读器等辅助技术观察页面。
在上面的测试脚本示例中,我们可以使用选择器aria/Submit[Role=";Button";]来选择所需的按钮,其中Submit指的是元素的可访问名称:
现在,如果我们稍后决定将按钮的文本内容从Submit更改为Done,测试将再次失败,但在这种情况下,这是可取的;通过更改按钮的名称,我们更改了页面的内容,而不是它的可视表示形式或它恰好在DOM中的结构。我们的测试应该对这些变化发出警告,以确保这些变化是有意的。
返回到搜索栏的较大示例,我们可以利用新的aria处理程序替换。
更广泛地说,我们认为使用这样的ARIA选择器可以为Puppeteer用户提供以下好处:
本文的其余部分将深入讨论我们如何实现Puppetaria项目的细节。
如上所述,我们希望能够通过元素的可访问名称和角色来查询元素。这些都是可访问性树的属性,这是通常的DOM树的双重属性,屏幕阅读器等设备使用它来显示网页。
从计算可访问名称的规范来看,很明显,计算元素的名称不是一项简单的任务,所以我们从一开始就决定重用Chromium的现有基础设施来实现这一点。
即使仅限于使用Chromium的可访问性树,也有很多方法可以在Puppeteer中实现ARIA查询。要了解原因,首先让我们看看Puppeteer是如何控制浏览器的。
浏览器通过称为Chrome DevTools协议(CDP)的协议公开调试界面。这将公开诸如重新加载页面或在页面中执行这段JavaScript并通过与语言无关的界面返回结果等功能。
DevTools前端和Puppeteer都使用CDP与浏览器通信。要实现CDP命令,Chrome的所有组件中都有DevTools基础设施:在浏览器、渲染器等中。CDP负责将命令路由到正确的位置。
操纵者的操作(如查询、单击和计算表达式)是通过利用CDP命令(例如直接在页面上下文中计算JavaScript并返回结果的Runme.valuate)来执行的。其他木偶操作(如模拟色觉缺陷、截屏或捕捉痕迹)使用CDP直接与Blink呈现进程通信。
用JavaScript编写我们的查询逻辑,并使用Runtime.valuate将其注入到页面中,或者
使用可以在闪烁过程中直接访问和查询辅助功能树的CDP端点。
木偶人AXTree遍历-基于使用现有CDP访问可访问性树。
CDP DOM遍历-使用专门为查询可访问性树而构建的新CDP端点。
此原型对DOM进行完全遍历,并使用由ComputedAccessibilityInfo启动标志控制的element.cultedName和element.cultedRole在遍历过程中检索每个元素的名称和角色。
这里,我们通过CDP检索完整的可访问性树,并在Puppeteer中遍历它。然后将得到的可访问性节点映射到DOM节点。
对于这个原型,我们实现了一个新的CDP端点,专门用于查询可访问性树。这样,查询可以通过C++实现在后端进行,而不是通过JavaScript在页面上下文中进行。
下图比较了3个原型查询4个元素1000次的总运行时间。该基准测试在3种不同的配置中执行,不同的配置改变了页面大小以及是否启用了辅助功能元素的缓存。
很明显,CDP支持的查询机制与仅在Puppeteer中实现的其他两种查询机制之间存在着相当大的性能差距,并且相对差异似乎随着页面大小的增加而显著增大。有趣的是,JS DOM遍历原型对启用可访问性缓存做出了如此好的响应。在禁用缓存的情况下,可访问性树按需计算,如果域被禁用,则在每次交互后丢弃该树。启用该域将使Chromium缓存计算树。
对于JS DOM遍历,我们在遍历过程中要求每个元素的可访问名称和角色,因此如果禁用缓存,Chromium将计算并丢弃我们访问的每个元素的可访问性树。另一方面,对于基于CDP的方法,仅在对CDP的每个调用之间(即,对于每个查询)丢弃树。这些方法还受益于启用缓存,因为可访问性树随后在CDP调用之间保持不变,但性能提升相对较小。
尽管启用缓存在这里看起来是可取的,但它确实会带来额外的内存使用成本。对于记录跟踪文件的Puppeteer脚本来说,这可能是有问题的。因此,我们决定默认情况下不启用可访问性树缓存。用户可以通过启用CDP可访问域自己打开缓存。
之前的基准测试表明,在CDP层实现我们的查询机制可以在临床单元测试场景中提升性能。
为了查看差异是否足够明显,以使其在运行完整测试套件的更现实场景中变得明显,我们为DevTools端到端测试套件打了补丁,以利用基于JavaScript和CDP的原型,并比较了运行时。在这个基准测试中,我们总共更改了43个选择器,从[aria-Label=…]。发送到自定义查询处理程序aria/…。,然后我们使用每个原型实现它。
有些选择器在测试脚本中被多次使用,因此每次运行套件时,aria查询处理程序的实际执行次数是113次。查询选择的总数是2253,所以只有一小部分查询选择是通过原型实现的。
如上图所示,总运行时间有明显的差异。这些数据太过嘈杂,无法得出任何具体的结论,但很明显,在这种情况下,两种原型之间的性能差距也是显而易见的。
根据上述基准,并且由于基于启动标志的方法通常是不可取的,我们决定继续实现用于查询可访问性树的新的CDP命令。现在,我们必须弄清楚这个新端点的接口。
对于我们在Puppeteer中的用例,我们需要端点将所谓的RemoteObjectIds作为参数,并且为了使我们能够在之后找到相应的DOM元素,它应该返回包含DOM元素的backendNodeIds的对象列表。
如下图所示,我们尝试了相当多的方法来满足此界面。由此,我们发现返回对象的大小,即我们是否返回完整的可访问性节点或仅返回backendNodeIds没有明显的差别。另一方面,我们发现使用现有的NextInPreOrderIncludingIgnored在这里实现遍历逻辑是一个糟糕的选择,因为这会导致明显的速度减慢。
现在,CDP端点就位后,我们在Puppeteer端实现了查询处理程序。这里的繁重工作是重组查询处理代码,使查询能够直接通过CDP解析,而不是通过在页面上下文中计算的JavaScript进行查询。
Puppeteer V5.4.0附带的新的aria处理程序是内置的查询处理程序。我们期待着看到用户如何将其应用到他们的测试脚本中,我们迫不及待地想听听您关于如何让它变得更有用的想法!
谢谢你的反馈。如果您对如何改进此页面有具体的想法,请创建一个问题。
谢谢你的反馈。如果您对如何改进此页面有具体的想法,请创建一个问题。
谢谢你的反馈。如果您对如何改进此页面有具体的想法,请创建一个问题。
谢谢你的反馈。如果您对如何改进此页面有具体的想法,请创建一个问题。
谢谢你的反馈。如果您对如何改进此页面有具体的想法,请创建一个问题。
谢谢你的反馈。如果您对如何改进此页面有具体的想法,请创建一个问题。
谢谢你的反馈。如果您对如何改进此页面有具体的想法,请创建一个问题。
谢谢你的反馈。如果您对如何改进此页面有具体的想法,请创建一个问题。
谢谢你的反馈。如果您对如何改进此页面有具体的想法,请创建一个问题。
谢谢你的反馈。如果您对如何改进此页面有具体的想法,请创建一个问题。
要讨论这篇文章中的新功能和变化,或者与DevTools相关的任何其他内容:
以下是Chrome DevTools工程博客系列中涉及的所有内容。
订阅我们的RSS或Atom提要,在您最喜爱的提要阅读器中获取最新更新!