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 '))]"}
Advertisements
This entry was posted in Watir and tagged , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s