我从事前端开发的时间足够长,多年来看到了一种趋势:年轻的开发人员在不了解其历史背景的情况下使用新的编程范式。 当然,不知道某事是完全可以理解的。网络是一个非常大的地方,拥有各种各样的技能和专业,我们并不总是知道我们不知道什么。这个领域的学习是一个持续的旅程,而不是一次发生就结束的事情。 举个例子:我的团队中有人询问是否可以判断用户是否离开了 UI 中的特定选项卡。我指出了 JavaScript 的 beforeunload 事件。但那些以前解决过这个问题的人知道这是可能的,因为他们收到过有关其他网站上未保存数据的警报,其中 beforeunload 是一个典型的用例。我还向我的同事指出了 pageHide 和visibilityChange 事件,以便更好地衡量。 我怎么知道的?因为它是在另一个项目中出现的,而不是因为我最初学习 JavaScript 时研究过它。 事实是,现代前端框架正站在之前技术巨头的肩膀上。他们抽象开发实践,通常是为了获得更好的开发人员体验,从而减少甚至消除了解或接触传统上每个人可能都应该了解的基本前端概念的需要。 考虑 CSS 对象模型 (CSSOM)。您可能认为任何从事 CSS 和 JavaScript 工作的人都拥有丰富的 CSSOM 实践经验,但情况并非总是如此。 我工作过的电子商务网站有一个 React 项目,我们需要为当前选择的支付提供商加载样式表。问题在于,当仅在特定页面上真正需要样式表时,样式表会在每个页面上加载。负责实现这一目标的开发人员从未动态加载过样式表。同样,当 React 抽象出您可能已经达到的传统方法时,这是完全可以理解的。 CSSOM 可能不是您日常工作中需要的东西。但您可能需要在某个时刻与其进行交互,即使是在一次性的情况下。 这些经历启发了我写这篇文章。有许多现有的 Web 功能和技术是您在日常工作中可能永远不会直接接触的。也许您对 Web 开发相当陌生,只是不知道它们,因为您沉浸在特定框架的抽象中,不需要您深入了解,甚至根本不了解。 我特意谈论 XML,我们很多人都知道 XML 是一种古老的语言,与 HTML 并没有完全不同。 我提出这个问题是因为最近 WHATWG 的讨论建议应该从浏览器中删除 XML 堆栈的很大一部分(称为 XSLT 编程)。这正是我们多年来拥有的那种较旧的现有技术,可以用于像我的团队所处的 CSSOM 情况这样实用的事情。 您以前使用过 XSLT 吗?让我们看看我们是否严重依赖这种旧技术并在 XML 上下文之外利用其功能来解决当今的现实问题。 XPath:中央 API 最重要的 XML 技术(也许除了直接的 XML 视角之外也是最有用的)是 XPath,它是一种查询语言,允许您通过一个根元素查找标记树中的任何节点或属性。我对XSLT有个人感情,但这也依赖于XPath,在重要性排名中必须抛开个人感情。 删除 XSLT 的论点没有提及 XPath,所以我认为它仍然是允许的。这很好,因为 XPath 是这套技术中最核心、最重要的 API,尤其是在尝试查找正常 XML 使用之外的内容时。这很重要,因为虽然 CSS 选择器可用于查找页面中的大多数元素,但它们无法找到全部元素。此外,CSS 选择器不能用于根据元素在 DOM 中的当前位置查找元素。 XPath 可以。 现在,阅读本文的有些人可能了解 XPath,有些人可能不了解。 XPath 是一个相当大的技术领域,我无法在像这样的一篇文章中真正教授所有基础知识并向您展示如何使用它来完成很酷的事情。我实际上尝试过写那篇文章,但 Smashing 杂志的出版物平均字数不会超过 5,000 字。我已经超过了2000字,基础知识只完成了一半。 因此,我将开始使用 XPath 做一些很酷的事情,并为您提供一些链接,如果您发现这些内容有趣,您可以使用它们来了解基础知识。 结合 XPath 和 CSS 在查询元素时,XPath 可以做很多 CSS 选择器不能做的事情。但是 CSS 选择器还可以做一些 XPath 不能做的事情,即按类名查询元素。
CSS X路径 .myClass /*[包含(@class, "myClass")]
在此示例中,CSS 查询包含 .myClass 类名的元素。同时,XPath 示例查询包含字符串“myClass”的属性类的元素。换句话说,它选择任何属性中带有 myClass 的元素,包括类名为 .myClass 的元素,以及字符串中带有“myClass”的元素,例如 .myClass2。从这个意义上来说,XPath 更广泛。 所以,不。我并不是建议我们应该抛弃 CSS 并开始通过 XPath 选择所有元素。这不是重点。 关键是 XPath 可以做 CSS 不能做的事情,而且仍然非常有用,尽管它是浏览器堆栈中的一项较旧的技术,并且乍一看似乎并不明显。 让我们一起使用这两种技术,不仅因为我们可以,而且因为我们将在此过程中了解一些有关 XPath 的知识,使其成为您堆栈中的另一个工具 - 您可能不知道它一直存在! 问题在于 JavaScript 的 document.evaluate 方法和我们与 JavaScript 的 CSS API 一起使用的各种查询选择器方法不兼容。 我已经制作了一个兼容的查询 API 来帮助我们开始,但不可否认的是,我没有对此进行太多思考,因为它与我们在这里所做的事情背道而驰。这是一个可重用查询构造函数的相当简单的工作示例: 请参阅 Bryan Rasmussen 的 Pen queryXPath [forked]。 我在文档对象上添加了两个方法:queryCSSSelectors(本质上是 querySelectorAll)和 queryXPaths。这两个都返回一个 queryResults 对象:
{ 查询类型:节点|字符串|数量 |布尔值, results: any[] // html 元素、xml 元素、字符串、数字、布尔值、 queryCSSSelectors:(查询:字符串,修改:布尔值)=> queryResults, queryXpaths:(查询:字符串,修改:布尔值)=> queryResults }
当然,只要结果数组是节点类型,queryCSSSelectors 和 queryXpaths 函数就会对结果数组中的元素运行您提供的查询。否则,它将返回一个带有空数组和一种节点类型的 queryResult。如果 amend 属性设置为 true,函数将更改自己的 queryResults。 在任何情况下都不应在生产环境中使用它。我这样做纯粹是为了演示同时使用两个查询 API 的各种效果。 查询示例 我想展示一些不同 XPath 查询的示例,这些示例展示了它们可以执行的一些强大功能以及如何使用它们来代替其他方法。 第一个示例是 //li/text()。这会查询所有 li 元素并返回它们的文本节点。因此,如果我们要查询以下 HTML:
- 一个
- 两个
- 三个
...这是返回的内容:
{“queryType”:“xpathEvaluate”,“结果”:[“一”,“二”,“三”],“resultType”:“字符串”}
换句话说,我们得到以下数组:[“一”,“二”,“三”]。 通常,您会查询 li 元素来获取该内容,将该查询的结果转换为数组,映射该数组,然后返回每个元素的文本节点。但我们可以使用 XPath 更简洁地做到这一点: document.queryXPaths("//li/text()").results.
请注意,获取文本节点的方法是使用 text(),它看起来像一个函数签名 - 确实如此。它返回元素的文本节点。在我们的示例中,标记中有三个 li 元素,每个元素都包含文本(“一”、“二”和“三”)。
让我们再看一个 text() 查询的示例。假设这是我们的标记:
让我们编写一个返回 href 属性值的查询: document.queryXPaths("//a[text() = '登录']/@href").results.
这是对当前文档的 XPath 查询,就像上一个示例一样,但这次我们返回包含文本“Sign In”的链接(元素)的 href 属性。实际返回的结果是[“/login.html”]。 XPath 函数概述 有许多 XPath 函数,您可能不熟悉它们。我认为有几个值得了解的内容,包括以下内容:
starts-with如果文本以特定的其他文本示例开头,则如果 href 属性以 http: 开头,starts-with(@href, 'http:') 将返回 true。 contains如果文本包含特定的其他文本示例,并且文本节点中的任意位置包含单词“Smashing Magazine”,则 contains(text(), "Smashing Magazine") 返回 true。 count 返回查询有多少匹配项的计数。例如,count(//*[starts-with(@href, 'http:']) 返回上下文节点中有多少个链接的计数,其中包含带有 href 属性的元素,该属性包含以 http: 开头的文本。 substring 与 JavaScript 子字符串类似,只不过您将字符串作为参数传递。例如,substring("my text", 2, 4) 返回“y t”。 substring-before 返回一个字符串在另一个字符串之前的部分。例如,substing-before("my text", " ") 返回“my”。类似地, substring-before("hi","bye") 返回一个空字符串。 substring-after 返回字符串中位于另一个字符串之后的部分。例如,substing-after("my text", " ") 返回“text”。类似地,substring-after("hi","bye") 返回一个空字符串。 Normalize-space 返回带有标准化空白的参数字符串,方法是去除前导和尾随空白并将空白字符序列替换为单个空格。 如果参数为 false,则 not 返回布尔值 true,否则返回 false。 true 返回布尔值 true。 false返回布尔值 false。 concat与 JavaScript concat 相同,只是您不将其作为字符串上的方法运行。相反,您输入要连接的所有字符串。 string-length这与 JavaScript string-length 不同,而是返回作为参数给出的字符串的长度。 translateThis 接受一个字符串并将第二个参数更改为第三个参数。例如,translate("abcdef", "abc", "XYZ") 输出 XYZdef。
除了这些特定的 XPath 函数之外,还有许多其他函数的工作方式与 JavaScript 对应函数(或基本上任何编程语言中的对应函数)相同,您可能也会发现这些函数很有用,例如下限、上限、圆、总和等。 以下演示说明了每个功能: 请参阅 Bryan Rasmussen 的 Pen XPath 数值函数 [forked]。 请注意,与大多数字符串操作函数一样,许多数字函数采用单个输入。当然,这是因为它们应该用于查询,如最后一个 XPath 示例所示: //li[地板(text()) > 250]/@val
如果像大多数示例一样使用它们,您最终将在与路径匹配的第一个节点上运行它。 还有一些类型转换函数你可能应该避免,因为 JavaScript 已经有自己的类型转换问题。但有时您可能希望将字符串转换为数字,以便将其与其他数字进行检查。 设置某种类型的函数有布尔值、数字、字符串和节点。这些是重要的 XPath 数据类型。 正如您可能想象的那样,这些函数中的大多数都可以用于非 DOM 节点的数据类型。例如,substring-after 接受我们已经介绍过的字符串,但它也可以是来自 href 属性的字符串。它也可以只是一个字符串:
const testSubstringAfter = document.queryXPaths("substring-after('hello world',' ')");
显然,这个例子将返回结果数组[“world”]。为了实际演示这一点,我使用函数针对非 DOM 节点创建了一个演示页面: 请参阅 Bryan Rasmussen 的 Pen queryXPath [forked]。 您应该注意到翻译函数的令人惊讶的方面,即如果第二个参数(即要翻译的字符列表)中有一个字符并且没有要翻译的匹配字符,则该字符将从输出中删除。 因此,这个:
翻译('你好,我的名字是伊尼戈蒙托亚,你杀了我的父亲,准备去死','abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,','*')
...字符串结果,包括空格: [“ * * ** ”]
这意味着字母“a”将被翻译为星号 (*),但在给定目标字符串的情况下,所有其他没有翻译的字符将被完全删除。空白是我们剩下的全部位于翻译后的“a”字符之间。 再说一次,这个查询:
翻译('你好,我的名字是伊尼戈蒙托亚,你杀了我的父亲,准备去死','abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,','********************************************************')”)
...没有问题并输出如下所示的结果:
“********************************************************************************”
您可能会发现 JavaScript 中没有简单的方法可以完全完成 XPath 翻译函数的功能,尽管对于许多用例,使用正则表达式的 ReplaceAll 可以处理它。 您可以使用我演示的相同方法,但如果您只想翻译字符串,那么这不是最佳选择。以下演示包装了 XPath 的翻译函数以提供 JavaScript 版本: 请参阅 Bryan Rasmussen 的笔翻译函数 [forked]。 你可能会在哪里使用这样的东西?考虑具有三位偏移量的凯撒密码加密(例如,公元前 48 年的顶级加密):
translate("凯撒正计划渡过卢比孔河!", “ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz”, “XYZABCDEFGHIJKLMNOPQRSTUVWxyzabcdefghijklmnopqrstuvw”)
输入文本“凯撒正计划渡过卢比孔河!”结果是“Zxbpxo fp mixkkfkd ql zolpp qeb Oryfzlk!” 为了给出不同可能性的另一个简单示例,我创建了一个金属函数,它接受字符串输入并使用翻译函数返回文本,包括所有带有变音符号的字符。 请参阅 Bryan Rasmussen 的 Pen 金属函数 [forked]。
常量金属 = (str) => { returntranslate(str, "AOUaou","äÖÜäöü"); }
并且,如果给出文本“Motley Cruerules, rock on dudes!”,则返回“Mötley Crüe rüles, röck ön düdes!” 显然,人们可能会对该函数进行各种模仿。如果您就是这样,那么这篇 TVTropes 文章应该能为您提供充足的灵感。 将 CSS 与 XPath 结合使用 请记住我们将 CSS 选择器与 XPath 一起使用的主要原因:CSS 非常了解类是什么,而使用 XPath 可以做的最好的事情就是类属性的字符串比较。这在大多数情况下都有效。 但是,如果您遇到这样的情况,例如,有人创建了名为 .primaryLinks 和 .primaryLinks2 的类,而您使用 XPath 来获取 .primaryLinks 类,那么您可能会遇到问题。只要不存在这样愚蠢的事情,您就可能会使用 XPath。但我很遗憾地告诉大家,我曾在人们做这类愚蠢事情的地方工作过。 这是另一个结合使用 CSS 和 XPath 的演示。它显示了当我们使用代码在不是文档节点的上下文节点上运行 XPath 时会发生什么。 请参阅 Bryan Rasmussen 的 Pen css 和 xpath [forked]。 CSS 查询是 .latedarticles a,它获取分配了 .latedarticles 类的 div 中的两个 a 元素。 之后是三个“坏”查询,也就是说,在使用这些元素作为上下文节点运行时,查询没有执行我们希望它们执行的操作。 我可以解释为什么他们的行为与你想象的不同。有问题的三个错误查询是:
//text():返回文档中的所有文本。 //a/text():返回文档中链接内的所有文本。 ./a/text():不返回结果。
产生这些结果的原因是,虽然您的上下文是从 CSS 查询返回的元素,但 // 违背了整个文档。这就是 XPath 的优势; CSS 不能从一个节点向上到达祖先,然后到达该祖先的兄弟节点,再向下走到该兄弟节点的后代。但 XPath 可以。 同时,./查询当前节点的子节点,其中点(.)代表当前节点,正斜杠(/)代表前往某个子节点——它是属性、元素还是文本由路径的下一部分决定。但是 CSS 查询没有选择子 a 元素,因此该查询也不会返回任何内容。 最后一个演示中有三个很好的查询:
.//文本(), ./文本(), 标准化空间(./text())。
规范化空间查询演示了 XPath 函数的用法,但也修复了其他查询中包含的问题。 HTML 的结构如下:
使用 Selenium WebDriver 自动化您的功能测试
查询返回文本节点开头和结尾的换行符,并且标准化空间删除了这个。 使用任何返回布尔值以外的值且输入 XPath 的 XPath 函数适用于其他函数。以下演示显示了许多示例: 请参阅 Bryan Rasmussen 的 Pen xpath 函数示例 [forked]。 第一个示例显示了您应该注意的问题。具体来说,代码如下:
document.queryXPaths("substring-after(//a/@href,'https://')");
...返回一个字符串:
“www.smashingmagazine.com/2018/04/feature-testing-selenium-webdriver/”
这是有道理的,对吧?这些函数不返回数组,而是返回单个字符串或单个数字。在具有多个结果的任何地方运行该函数仅返回第一个结果。 第二个结果显示了我们真正想要的:
document.queryCSSSelectors("a").queryXPaths("substring-after(./@href,'https://')");
它返回两个字符串的数组:
[“www.smashingmagazine.com/2018/04/feature-testing-selenium-webdriver/”,“www.smashingmagazine.com/2022/11/automated-test-results-improve-accessibility/”]
XPath 函数可以像 JavaScript 中的函数一样嵌套。因此,如果我们知道 Smashing Magazine URL 结构,我们可以执行以下操作(建议使用模板文字): `翻译( 子串( substring-after(./@href, ‘www.smashingmagazine.com/’) ,9), '/','')`
这变得有点太复杂了,需要注释来描述它的作用:从 www.smashingmagazine.com/ 之后的 href 属性中获取所有 URL,删除前九个字符,然后将正斜杠 (/) 字符转换为空,以便摆脱结尾的正斜杠。 结果数组:
[“功能测试selenium-webdriver”,“自动测试结果改进可访问性”]
更多 XPath 用例 XPath 在测试中确实可以大放异彩。原因不难看出,因为 XPath 可以用于从 DOM 中的任何位置获取 DOM 中的每个元素,而 CSS 则不能。 您不能指望 CSS 类在许多现代构建系统中保持一致,但是使用 XPath,我们能够对元素的文本内容进行更稳健的匹配,而不管 DOM 结构如何变化。 人们已经对允许您进行弹性 XPath 测试的技术进行了研究。没有什么比仅仅因为 CSS 选择器因某些内容被重命名或删除而不再工作而导致测试失败并失败更糟糕的了。 XPath 在多定位器提取方面也非常出色。使用 XPath 查询来匹配元素的方法有不止一种。 CSS 也是如此。但是 XPath 查询可以以更有针对性的方式深入了解内容,从而限制返回的内容,从而使您能够在可能存在多个可能匹配的情况下找到特定的匹配。 例如,我们可以使用 XPath 返回一个特定的 h2 元素,该元素包含在紧跟在同级 div 后面的 div 中,而该同级 div 又包含一个带有 data-testID="leader" 属性的子图像元素:
不明白这个标题
也不要得到这个标题
领导者图像的标题
这是查询: 文档.queryXPaths(` //div[ 以下兄弟姐妹::div[1] /img[@data-testID='领导者'] ] /h2/ 文本() `);
让我们看一下演示,看看这一切是如何结合在一起的: 请参阅 Bryan Rasmussen 的 Pen Complex H2 查询 [forked]。 所以,是的。使用 XPath 的测试中的任何元素都有许多可能的路径。 XSLT 1.0 弃用 我之前提到 Chrome 团队计划从浏览器中删除 XSLT 1.0 支持。这很重要,因为 XSLT 1.0 使用以 XML 为中心的编程进行文档转换,而文档转换又依赖于 XPath 1.0(大多数浏览器都采用 XPath 1.0)。 当这种情况发生时,我们将失去 XPath 的一个关键组件。但鉴于 XPath 确实非常适合编写测试,我发现 XPath 整体上不太可能很快消失。 也就是说,我注意到人们会对某个功能被删除时感兴趣。在 XSLT 1.0 被弃用的情况下确实如此。黑客新闻上正在进行一场完整的讨论,充满了反对弃用的论点。这篇文章本身就是使用 XSLT 创建博客框架的一个很好的例子。你您可以自己阅读讨论,但它涉及如何使用 JavaScript 作为 XLST 的填充程序来处理此类情况。 我还看到建议浏览器应该使用 SaxonJS,它是 JavaScript 的 Saxon XSLT、XQUERY 和 XPath 引擎的端口。这是一个有趣的想法,特别是 Saxon-JS 实现了这些规范的当前版本,而没有浏览器实现任何高于 1.0 的 XPath 或 XSLT 版本,也没有浏览器实现 XQuery。 我联系了 Saxonica 的 Norm Tovey-Walsh,该公司是 SaxonJS 和其他版本 Saxon 引擎背后的公司。他说: “如果任何浏览器供应商有兴趣将 SaxonJS 作为将现代 XML 技术集成到浏览器中的起点,我们将很高兴与他们讨论。”— Norm Tovey-Walsh
但还补充道: “如果有人认为采用当前形式的 SaxonJS 并将其原封不动地放入浏览器构建中将是理想的方法,我会感到非常惊讶。浏览器供应商,本质上是他们构建浏览器的事实,可以比我们‘从外部’更深层次地实现集成。”—— Norm Tovey-Walsh
值得注意的是,Tovey-Walsh 的评论是在 XSLT 弃用声明发布前一周左右发表的。 结论 我还可以继续说下去。但我希望这已经展示了 XPath 的强大功能,并为您提供了大量示例来演示如何使用它来实现伟大的目标。它是浏览器堆栈中旧技术的完美示例,即使您从未知道它的存在或从未考虑过使用它,但如今仍然具有大量实用性。 进一步阅读
“利用自然语言增强自动化 Web 测试的弹性”(ACM 数字图书馆)作者:Maroun Ayli、Youssef Bakouny、Nader Jalloul 和 Rima Kilany 本文提供了许多用于编写弹性测试的 XPath 示例。 XPath (MDN) 如果您想要详细了解 XPath 如何工作的技术解释,这是一个很好的起点。 XPath 教程 (ZVON) 我发现该教程对我自己的学习最有帮助,这要归功于大量的示例和清晰的解释。 XPather 这个交互式工具可让您直接使用代码。