Locate by classes in a specific order

Problem

In HTML, the class attribute is a space-separated list of classes for the element. The order of classes does not matter when applying style sheets. For example, in the following page, all 3 div elements would look the same even though their class value is different.

<html>
  <body>
    <div class="a b">1</div> 
    <div class="b a">2</div>
    <div class="a b">3</div>
  </body>
</html>

For many years, Watir’s :class locator has been order dependent when using multiple classes:

# Only matches the exact order "a" and then "b"
browser.divs(class: 'a b').map(&:text)
#=> ["1", "3"]

# Only matches the exact order "b" and then "a"
browser.divs(class: 'b a').map(&:text)
#=> ["2"]

Watir 6.5 introduced the ability to be order independent. This is done by providing an Array of classes:

browser.divs(class: ['a', 'b']).map(&:text)
#=> ["1", "2", "3"]

browser.divs(class: ['b', 'a']).map(&:text)
#=> ["1", "2", "3"]

For backwards compatibility, the decision was to leave the existing order dependent behaviour for String values as-is. However, a deprecation warning will be generated:

browser.divs(class: 'a b').map(&:text)
#=> 2017-08-16 21:50:17 WARN Watir [DEPRECATION] 
#=>   Using the :class locator to locate multiple classes with 
#=>   a String value is deprecated. Use Array instead.

Hopefully no one has to deal with a class order requirement, but if you do, what can you do to prevent the deprecation warning?

Solution

The new handling for Array values still builds the XPath the same as when a String value is used. The only difference is that the Array value will “and” the items together. With only a single item in the Array, the behaviour is the same as using a String. As well, the Array is not checked for items with spaces – ie is not subject to the deprecation warning.

This means that the order dependent classes can simply be passed as one of the Array elements:

browser.divs(class: ['a b']).map(&:text)
#=> ["1", "3"]
	
browser.divs(class: ['b a']).map(&:text)
#=> ["2"]

The XPath generated for the deprecated approach and this workaround only differ by an unimportant set of brackets:

# Using browser.divs(class: 'a b')
{"using":"xpath","value":".//div[contains(concat(' ', @class, ' '), ' a b ')]"}

# Using browser.divs(class: ['a b'])
{"using":"xpath","value":".//div[(contains(concat(' ', @class, ' '), ' a b '))]"}
This entry was posted in Watir and tagged , . Bookmark the permalink.

Leave a comment