如何判断一个元素是否被CSS选择器匹配?

How to determine if an element is matched by CSS selector?

给定一个 Selenium WebDriver 元素实例,我想检查该元素是否与给定的 CSS 选择器匹配。功能类似于 jQuery 的 is() 功能。

我正在使用 .NET 绑定。

示例(假设该方法将被调用 Is

var links = _driver.FindElements(By.CssSelector("a"));
foreach (var link in links) 
{
  if (link.Is(".myclass[myattr='myvalue']"))
    // ... do something
  else 
    // ... do some other thing
}

是否有任何内置的东西可以实现这一点,或者如果没有,有人可以建议一个可能的实现吗?我是 Selenium 的新手,还不知道。

更新

我发现没有内置方法可以执行此操作。我的最终目标是实现 jQuery 的 parents(selector)closest(selector) 等方法,因此对于这种更特殊的情况,我们将不胜感激。

一般来说,为了比较 selenium 中的元素,您可以比较它们的 outerHTMLinnerHTML 表示。它不是防弹的,但在实践中应该有效:

IWebElement elm = _driver.FindElement(By.CssSelector(".myclass[myattr='myvalue']"));

string linkHtml = link.GetAttribute("outerHTML");
string elmHtml = elm.GetAttribute("outerHTML");

if (linkHtml == elmHtml) {
    ...
}

请注意,在您的情况下,您似乎可以使用 GetAttribute():

检查 classmyattr 属性的值
string linkClass = link.GetAttribute("class");
string linkMyAttr = link.GetAttribute("myattr");

if (linkClass.Contains("myclass") && linkMyAttr == "myvalue") {
    ...
}

没有任何 Selenium 方法可以达到 jQuery 的 $(...).is(...) 的效果。但是,DOM 报价 matches。它不是 $(...).is(...) 的完全替代品,因为它不支持 jQuery 对 CSS 选择器语法的扩展,但是,Selenium 也不支持这些扩展。

您可以通过将要测试的元素传递给 JavaScript 脚本来使用它,然后再传递给 ExecuteScript。该元素将作为脚本中 arguments 的第一个元素出现。您只需调用 matches 和 return 值。我不使用 C#,但根据文档,我相信它在 C# 中看起来像这样:

bool isIt = (bool)(driver as IJavaScriptExecutor).ExecuteScript(
    "return arguments[0].matches(\".myclass[myattr='myvalue']\")", element);

isIt 包含测试结果。 element 是您要测试的元素,driver 是您已经创建的驱动程序。有关兼容性,请参阅 caniuse。在我的应用程序中,我使用 polyfill 在我关心的所有平台上提供 matches


话虽这么说我不建议遍历元素并逐个测试它们。问题是每个 ExecuteScriptGetAttribute 调用都是脚本和浏览器之间的往返。当您 运行 完整的测试套件时,这会加起来,尤其是当浏览器 运行 在远离您的脚本的服务器场上时。然后它真的 登广告了。我会构建您的测试,以便它查询所有 a 元素的列表和所有 a.myclass[myattr='myvalue'] 元素的列表。这归结为两次往返。从这两个列表中,您可以获得进行 if() 测试所需的所有信息。

我最终的解决方案是这三种方法:Parent, Parents(selector), Closest(selector)。在此之后,可以很容易地实现其他几个类似 jQuery 的辅助方法。希望以后能帮到别人。

    public static IWebElement Parent(this IWebElement elem)
    {
        var script = new[]
        {
            "return",
            "  (function(elem) {",
            "     return elem.parentNode;",
            "   })(arguments[0]);"
        };
        var remoteWebElement = elem as RemoteWebElement;
        if (remoteWebElement == null)
            throw new NotSupportedException("This method is only supported on RemoteWebElement instances. Got: {0}".FormatWith(elem.GetType().Name));

        var scriptTxt = script.Implode(separator: " ");
        var scriptExecutor = remoteWebElement.WrappedDriver as IJavaScriptExecutor;
        if (scriptExecutor == null)
            throw new NotSupportedException("This method is only supported on drivers implementing IJavaScriptExecutor interface. Got: {0}".FormatWith(elem.GetType().Name));

        return scriptExecutor.ExecuteScript(scriptTxt, elem) as IWebElement;
    }

    public static ReadOnlyCollection<IWebElement> Parents(this IWebElement elem, string selector = null)
    {
        var script = new[]
        {
            "return",
            "  (function(elem) {",
            //"     console.log(elem);",
            "     var result = [];",
            "     var p = elem.parentNode;",
            "     while (p && p != document) {",
            //"       console.log(p);",
            (string.IsNullOrWhiteSpace(selector) ? null :
                "     if (p.matches && p.matches('" + selector + "'))"),
            "          result.push(p);",
            "       p = p.parentNode;",
            "     }",
            "     return result;",
            "   })(arguments[0]);"
        };
        var remoteWebElement = elem as RemoteWebElement;
        if (remoteWebElement == null)
            throw new NotSupportedException("This method is only supported on RemoteWebElement instances. Got: {0}".FormatWith(elem.GetType().Name));

        var scriptTxt = script.Implode(separator: " ");
        var scriptExecutor = remoteWebElement.WrappedDriver as IJavaScriptExecutor;
        if (scriptExecutor == null)
            throw new NotSupportedException("This method is only supported on drivers implementing IJavaScriptExecutor interface. Got: {0}".FormatWith(elem.GetType().Name));

        var resultObj = scriptExecutor.ExecuteScript(scriptTxt, elem) as ReadOnlyCollection<IWebElement>;
        if (resultObj == null)
            return new ReadOnlyCollection<IWebElement>(new List<IWebElement>());
        return resultObj;
    }

    public static IWebElement Closest(this IWebElement elem, string selector)
    {
        var script = new[]
        {
            "return",
            "  (function(elem) {",
            "     var p = elem;",
            "     while (p && p != document) {",
            "       if (p.matches && p.matches('" + selector + "'))",
            "          return p;",
            "       p = p.parentNode;",
            "     }",
            "     return null;",
            "   })(arguments[0]);"
        };
        var remoteWebElement = elem as RemoteWebElement;
        if (remoteWebElement == null)
            throw new NotSupportedException("This method is only supported on RemoteWebElement instances. Got: {0}".FormatWith(elem.GetType().Name));

        var scriptTxt = script.Implode(separator: " ");
        var scriptExecutor = remoteWebElement.WrappedDriver as IJavaScriptExecutor;
        if (scriptExecutor == null)
            throw new NotSupportedException("This method is only supported on drivers implementing IJavaScriptExecutor interface. Got: {0}".FormatWith(elem.GetType().Name));

        return scriptExecutor.ExecuteScript(scriptTxt, elem) as IWebElement;
    }