Zoltán Tamási
Zoltán Tamási

Reputation: 12809

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

Given a Selenium WebDriver element instance, I would like to check if that element is matched by a given CSS selector. The functionality is similar jQuery's is() function.

I'm using the .NET bindings.

Example (suppose the method would be called 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
}

Is there any kind of built-in stuff to achieve this, or if not, can anybody suggest a possible implementation? I'm new to Selenium, and have no idea yet.

UPDATE

I see that there is no built-in way to do this. My final goal is to implement methods like jQuery's parents(selector) and closest(selector), so any suggestions will be appreciated about this more special case.

Upvotes: 2

Views: 878

Answers (3)

Zoltán Tamási
Zoltán Tamási

Reputation: 12809

My final solution was these three methods: Parent, Parents(selector), Closest(selector). After this it could be easy to implement several other jQuery-like helper methods. Hope it's gonna help out someone in the future.

    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;
    }

Upvotes: 0

Louis
Louis

Reputation: 151551

There is no Selenium method that would do the equivalent of jQuery's $(...).is(...). However, the DOM offers matches. It is not a complete replacement for $(...).is(...) since it does not support jQuery's extensions to the CSS selector syntax but, then again, Selenium does not support these extensions either.

You'd use it by passing the element you want to test to a JavaScript script that you pass to ExecuteScript. This element will appear as the first element of arguments inside the script. You just call matches on it and return the value. I don't do C# but based on the documentation, I believe it would look like this in C#:

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

isIt contains the result of the test. element is the element you want to test, and driver is a driver you've already created. See caniuse for compatibility. In my applications, I use a polyfill to provide matches on all platforms I care about.


This being said I do not recommend looping over the elements and testing them one by one. The problem is that each ExecuteScript or GetAttribute call is a round-trip between your script and the browser. When you run full test suites, this adds up, especially if the browser is run on a server farm away from your script. Then it really ads up. I would structure your test so that it queries a list of all a elements and a list of all a.myclass[myattr='myvalue'] elements. This boils down to two round-trips. From the two lists you have all the information you need to do your if() test.

Upvotes: 3

alecxe
alecxe

Reputation: 474281

In general, in order to compare elements in selenium you can compare their outerHTML or innerHTML representation. It is not bullet-proof, but should work in practice:

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

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

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

Note that in your case, it looks like you can just check the values of class and myattr attributes using GetAttribute():

string linkClass = link.GetAttribute("class");
string linkMyAttr = link.GetAttribute("myattr");

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

Upvotes: 1

Related Questions