10

This is a follow-up to What makes a good selenium locator?

Motivation

I've been thinking about detecting the complexity of CSS selectors statically.

The scope I'm working in is both and where choosing a reliable CSS selector is important to be as less dependent on UI/markup/layout changes as possible.

Currently, I'm able to check the "depth" of a CSS selector by parsing it via css-selector-parser and simply counting the number of nodes. If the number of nodes is more than 5 I'm concluding that this is a potentially complex CSS selector. The reasoning behind this is that, the more nodes a selector has in the path, the more dependent on the HTML structure of the page it is - the more fragile it is. For instance, this CSS selector would be considered "fragile" (6 nodes):

.content > table > tbody > tr:nth-child(2) > td.cell > input#email

Eventually, I'm thinking to develop a metric where each selector would get a special "score" value that would a measurement of a selector being more solid/durable. Inspired by famous code complexity metrics, like Cyclomatic Complexity or Maintainability Index.

Question

What parameters would you consider to determine if a CSS selector is resistant to a change? What makes a reliable CSS locator?

Thoughts

  • usage of layout-oriented bootstrap classes inside CSS selectors would be a signal of a bad locator
  • implementation-specific classes and attributes would make a selector less reliable. (e.g. in case of AngularJS - ng-scope or ng-binding classes)
  • using id or name attributes would probably be desirable

2 Answers 2

6
+50

What parameters would you consider to determine if a CSS selector is resistant to a change? What makes a reliable CSS locator? Some factors I would consider:

To start with, the basics are:

  • Locator should not include page layout structure
  • Don't base selector on the actual page text
  • Work within your existing framework(s)
  • Prefer css over xpath for readability
  • Unique and constant css id(s) are best

Then...

  • Whether nested tables are used or not
  • Whether data- attribute identifiers are used
  • Whether a given css id is actually unique or not
  • Whether literal content text is used in the selector
  • The use of repeated elements, e.g. div div div
  • The DOM nesting depth of the first selector element
  • The number of separate html elements in the selector
  • How often any existing selector is changed historically
  • Length of the selector string (number of distinct elements)
  • Length of the selector string (number of distinct characters)
  • How many levels of nesting are in the DOM it goes against
  • Whether partial and wildcard content text is used in the locator
  • Whether tables are used and if so how rows are ordered / reordered
  • Use of index identifiers by, e.g. css :nth-child(number)or xpath[]
  • Use of unique attributes such as id or name with qualifying identifiers
  • The number of times css ids are used against elements within the selector
  • How many instances exist of identifiers such as css class within the selector
  • Whether some id's are all numeric and unique, suggesting database backing
  • Whether some css element id's are dynamic, e.g. unique resource record id's
  • The number of times each individual element of a selector appears in the DOM
  • Whether the selector elements are directly nested or just 'within' other elements
  • Whether you can see a consistent approach when comparing with other selectors

  • Whether within 1 table if more than one table td or tr is used in the selector

  • Use of generic attributes such as span and div without qualifying identifiers on them
  • Whether the DOM elements are directly next to each others or nested within other elements
  • Use of a framework like Ruby on Rails that provides unique identifiers for db related elements

The hard part is knowing what weighting to give each of these. Some will be heavily influenced by the usage of both development and ux frameworks and be more or less relevant in some situations vs. others.

One way to tell the weighting and predictability of the elements would be to reverse-engineer and review past test breakages due to DOM changes to see the relationships. In practice I find this hard to do as devs fix tests before committing code. Larger companies like Google might be able to research this better.

It will be important to also distinguish changing application code vs. automation selectors with this complexity. A ranking or rating of a given selector may provide good feedback for automators to use in their development of selection strings for automated tests but not be suitable for the page content itself as developed by the ux group.

3
  • 1
    Very good summary, thanks! Yeah, the weights are gonna be challenging. I guess, a more informative decision could be made if aside from a CSS selector, there is an HTML of the page given. This would complicate the analysis but may lead to a more realistic result.
    – alecxe
    Commented Jun 26, 2017 at 21:36
  • 1
    Yeah, the actual DOM page you are going against is part of the picture and needed for some of the above analysis Commented Jun 26, 2017 at 21:40
  • I was also thinking to apply some code complexity ideas to understanding a complexity of a CSS selector - for instance, the more operators (selectors like :not, :first-child etc), the more [] conditions - the more complicated a selector is. But, that would not necessarily mean a selector is not good enough. I guess, we'll have to cross a line somewhere since there are no hard rules and avoiding false positives will be impossible.
    – alecxe
    Commented Jun 26, 2017 at 21:45
2

I've been able to reduce the complexity you describe as follows:

Think of code as data, data as code; using collections. This avoids the use of defining complex paths into a web page. With collections and the proper filtering techniques, we can merely rely on the text we see on the page.

Example

var day = 12;
var calendar = calenderElement.Click();  //opens the calendar
//finds the link with the number 12 (in the text) for each link.
var ele= calendar.FindElements.By.TagName("a").Select(i=>i.Text == day).First(); 
ele.Click() //sets the date in the calendar

This code style should be available out of the box for C#, Java and Javascript solutions. All three languages support collection querying and FindEements will work from the current IWebElement (if selector isn't global). Using FindElement from the current element allows for fine tuned drill down to what is needed.

If the code above is refactored to inject the day we want... Then we have a reusable part.

This allows us to think of a web page in a different manner. All we really need are IDs, Links or CSS as our main retrieval points. From there we use collection filtering and or FindEement(s) from those points, to filter what we want. The best part is that it can be based only on Text that we see, not on hidden attributes etc. However, if we need finer grained searches all attributes can also be filtered in same way.

Example 2

We know that Class selectors will return (most often way too many elements) so let's dial-in a filter based on the text we see using only classNames.

var specificTextWanted = "Something we see";
                      //too many elements here               //filtered here
var theSingleElement = wd.FindElements(By.ClassName("alpha").Where(i=>i.text == specificTextWanted).First();

We can now bypass complex selectors using text based filtering based on a returned collection. This works well even for dynamic content but sometimes an stale element reference can be thrown in the Where clause above, it's simple to fix (Try, Catch) and just ignore the element, after all, it's not there any longer!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.