Toggle a checkbox


Checking a checkbox is as simple as:


Clearing a checkbox is just as easy:


What if you want to toggle the state of the checkbox – ie check it if its unchecked or uncheck it if it is checked?


The set? method returns the current state of the checkbox, therefore the negation is the state of the checkbox once toggled:

#=> true
#=> false

We could write an if statement based on the set? method. However, we can find a simpler solution by passing a parameter to the set:

  • true – sets the checkbox
  • false – clears the checkbox

Putting it all together, we can toggle the checkbox by:

checkbox = browser.checkbox
Posted in Watir, Watir-Classic, Watir-Webdriver | Tagged | 16 Comments

Unhiding the overflow:hidden in DevExtreme select lists


When Dev Silver asked me how to automate a DevExtreme select list, it seemed like a simple task. It looked like any other set of elements styled as a select list.

# Go to the page
browser =
iframe = browser.iframe(id: 'demo-frame')

# Find the dropdown
dropdown = iframe.div(id: 'products-simple')

# Open the list of dropdown options
dropdown.div(class: 'dx-dropdowneditor-button').click
list_container = iframe.div(class: 'dx-dropdownlist-popup-wrapper').div(class: 'dx-scrollable-container')

# Click a list item
list_container.div(text: 'SuperLED 50').click

It worked! Well… at least for some options. Clicking some of the other options resulted in an exception:

list_container.div(text: 'Projector Plus').click
#=> Selenium::WebDriver::Error::ElementNotVisibleError

What was going on?


After investigating the DevExtreme HTML, I was able to narrow the problematic HTML to the following:

<div style="overflow:hidden; height:59px; width:200px; border:1px solid black;" class="dx-scrollable-container">
  <div style="line-height:20px" class="dx-list-item">HD Video Player</div>
  <div style="line-height:20px" class="dx-list-item">SuperPlasma 50</div>
  <div style="line-height:20px" class="dx-list-item">SuperLED 50</div>
  <div style="line-height:20px" class="dx-list-item">Projector Plus</div>
  <div style="line-height:20px" class="dx-list-item">ExcelRemote IR</div>

Which gets rendered as:

HD Video Player
SuperPlasma 50
SuperLED 50
Projector Plus
ExcelRemote IR


Notice that the outer div element has a style of overflow:hidden. This means that any content that does not fit within the element’s dimensions will be hidden to the user. There is no scrollbar for the user to see the hidden content. In other words, a user can see the first 3 list items, but not the last 2. WebDriver mimics the user – it can only see and click the first 3 items. The last 2 items are not visible and therefore cannot be interacted with.

browser.divs(class: 'dx-list-item').map(&:visible?)
#=> [true, true, true, false, false]

To interact with the overflowed/hidden items, they must first be made visible. The actual DevExtreme control does provide another element that mimics the scrollbar behaviour. However, with the control relying on mouseovers, it seemed a nuisance to automate. Instead, I chose to mimic the scrolling via a JQuery script written by James on StackOverflow:

list_item = list_container.div(text: 'Projector Plus')
script = '$(arguments[0]).scrollTop($(arguments[1]).offset().top - $(arguments[0]).offset().top + $(arguments[0]).scrollTop());'
iframe.execute_script(script, list_container, list_item)
Posted in Watir, Watir-Webdriver | Tagged , | 2 Comments

Translating a headers attribute to text


I was struggling to determine a cell’s headers associated via the headers attribute. The table was large with auto-generated ids, which created headers attributes like:


Finding each of the header cells became tedious, so I turned to Watir for a solution.

As a simplified example, let us consider the following table from the WCAG examples:

    <th rowspan="2" id="h">Homework</th>
    <th colspan="3" id="e">Exams</th>
    <th colspan="3" id="p">Projects</th>
    <th id="e1" headers="e">1</th>
    <th id="e2" headers="e">2</th>
    <th id="ef" headers="e">Final</th>
    <th id="p1" headers="p">1</th>
    <th id="p2" headers="p">2</th>
    <th id="pf" headers="p">Final</th>
    <td headers="h">15%</td>
    <td headers="e e1">15%</td>
    <td headers="e e2">15%</td>
    <td headers="e ef">20%</td>
    <td headers="p p1">10%</td>
    <td headers="p p2">10%</td>
    <td headers="p pf">15%</td>

From inspection, we can see that the headers attribute “e e2” will be read as “Exams 2”. How can we programmatically determine this?

Solution – Watir-Classic

The headers attribute is a space-deliminated list of element ids. We can split the attribute value into a list of ids and then locate each of the referenced elements:

require 'watir-classic'
browser = Watir::Browser.attach(:url, //)

headers = 'e e2'

headers.split(' ')
  .map { |header| browser.element(id: header, tag_name: ['th', 'td']).text }
  .join(' ')
#=> "Exams 2"

Note that this will find the cells anywhere on the page. If you are concerned about the header cell possibly being in another table, you can restrict the search scope to a specific table.

Solution – Watir-Webdriver

As the purpose of this script was to assist manual testing, it was written in Watir-Classic to leverage attaching to browsers. If you want to get the headers in Watir-Webdriver, the code is a little different. This is due to the different values supported by the :tag_name locator. Watir-Classic does not support regular expressions, which is a known bug, while Watir-Webdriver does not support arrays.

When searching multiple tag names, you can use a regular expression:

headers = 'e e2'

headers.split(' ')
  .map { |header| browser.element(id: header, tag_name: /^(th|td)$/).text }
  .join(' ')
#=> "Exams 2"
Posted in Watir, Watir-Classic, Watir-Webdriver | Tagged | Leave a comment

Returning Watir element from execute_script


I noticed the following Stack Overflow answer by Jeff Price yesterday:

One thing we have done at times when a query becomes oppressive is to get out of the selenium/watir loop altogether and ask the browser to execute some javascript to grab the elements we want. Obviously this has some limitations especially if you need to use watir on the object you get back (you can’t). But if you are looking for something in particular, you can’t get much faster. I am assuming you are using jQuery, but in reality the JavaScript can be arbitrary and whatever you need.

The answer seemed to suggest that it is not possible to get a Watir::Element from the DOM element returned by execute_script. This is not how I remember it, but it has been a long time since I tried doing this.

What is the current behaviour?

Answer – Watir-Webdriver

From a code review of the Watir::Browser, Watir will take the value returned from Selenium-WebDriver’s execute_script method and, if a Selenium::WebDriver::Element, convert it to a Watir object:

def execute_script(script, *args)! { |e| e.kind_of?(Watir::Element) ? e.wd : e }
  returned = @driver.execute_script(script, *args)


def wrap_elements_in(obj)
  case obj
  when Selenium::WebDriver::Element
  when Array { |e| wrap_elements_in(e) }
  when Hash
    obj.each { |k,v| obj[k] = wrap_elements_in(v) }


def wrap_element(element)
  Watir.element_class_for(element.tag_name.downcase).new(self, element: element)

This suggests that Watir’s execute_script can return a Watir::Element. Let us see if this is true.

Assume that the page being worked on is:

    <script src=""></script>
      <span id="the_span">span text</span>
      <input type="text" id="the_text_field">

When using JavaScript to return the DOM element for the span, it can be seen that Watir creates the corresponding Watir::Element – ie a Watir::Span:

e = browser.execute_script('return document.getElementById("the_span");')
p e.class
#=> Watir::Span
p e.text
#=> "span text"

When the same is performed for the text field input, the results are not as great. A Watir::Input object is returned, which means that methods such as set will not be available. The code appears to be selecting the object type based on just the tag name (presumably a bug). At any rate, the desired Watir::TextField object can be retrieved by calling the to_subtype method.

e = browser.execute_script('return document.getElementById("the_text_field");')
p e.class
#=> Watir::Input

# Switch to the subtype to get the type specific methods
e = e.to_subtype
p e.class
#=> Watir::TextField

Watir elements are also created when using jQuery to locate the element.

# The value returned by jQuery() will be an array of Watir elements
e = browser.execute_script('return jQuery("#the_span");')
p e.class
#=> Array
p e.first.class
#=> Watir::Span
p e.first.text
#=> "span text"

Answer – Watir-Classic

From the code in the page-container, it does not look like there is anything to return a Watir object.

def execute_script(source)
  result = nil
    source = with_json2_if_needed source
    result = document.parentWindow.eval(source)
  rescue WIN32OLERuntimeError, NoMethodError #if eval fails we need to use execScript(source.to_s) which does not return a value, hence the workaround
    escaped_src = source.gsub(/\r?\n/, "\\n").gsub("'", "\\\\'")
    wrapper = "_watir_helper_div_#{SecureRandom.uuid}"
    cmd = "var e = document.createElement('DIV');'none';'#{wrapper}'; e.innerHTML = eval('#{escaped_src}'); document.body.appendChild(e);"
    result = document.getElementById(wrapper).innerHTML

  MultiJson.load(result)["value"] rescue nil

In fact, trying to return the DOM element results in an exception:

e = browser.execute_script('return document.getElementById("the_span");')
#=> WIN32OLERuntimeError: (in OLE method `execScript': )
#=>     OLE error code:80020101 in <Unknown>
#=>       Could not complete the operation due to error 80020101.
#=>     HRESULT error code:0x80020009
#=>       Exception occurred.
#=>         from C:/Ruby193/lib/ruby/gems/1.9.1/gems/watir-classic-4.1.0/lib/watir-classic/page-container.rb:31:in `method_missing'
#=>         from C:/Ruby193/lib/ruby/gems/1.9.1/gems/watir-classic-4.1.0/lib/watir-classic/page-container.rb:31:in `rescue in execute_script'
#=>         from C:/Ruby193/lib/ruby/gems/1.9.1/gems/watir-classic-4.1.0/lib/watir-classic/page-container.rb:24:in `execute_script'
#=>         from (irb):3
#=>         from C:/Ruby193/bin/irb:12:in `<main>'

However, with a little bit more work, you can directly interact with the OLE objects to get the desired results:

ole_object = browser.document.getElementById("the_span")
e = browser.span(ole_object: ole_object)
p e.text
#=> "span text"

Or when using jQuery:

ole_object = browser.document.parentWindow.eval('jQuery("#the_span")[0];')
e = browser.span(ole_object: ole_object)
p e.text
#=> "span text"


It is possible to take the DOM element returned from a script execution and turn it into a Watir object.

Posted in Watir, Watir-Classic, Watir-Webdriver | Tagged | Leave a comment

Are :xpath locators slower?


In the past, the Watir community has been resistive to using XPath locators. One of the reasons being the poor performance when locating elements. Occasionally I still see warnings of poor performance, but there seems to be more uncertainty.

Some comments I have seen over time include:

Is the concern about poor XPath performance still valid?


IE11 (for Watir-Classic) and Chrome (for Watir-Webdriver) were used during the benchmark tests. The test page was a simple table:

    <table id="report" class="results_grid" data-rows="1">
        <th>Heading 1</th>
        <th>Heading 2</th>
        <td>Data 1</td>
        <td>Data 2</td>


As the performance concern started a long time ago, it makes sense to investigate Watir-Classic first.

The following benchmark located the table by its id (100 times):

n = 100 do |x|"watir:") { n.times { browser.table(id: 'report').exists? } }"xpath:") { n.times { browser.table(xpath: '//table[@id="report"]').exists? } }"css:") { n.times { browser.table(css: 'table#report').exists? } }

The results show that XPath (and CSS-Selectors) are a bit slower:

            user     system      total        real
watir:  0.031000   0.000000   0.031000 (  0.124800)
xpath:  0.406000   0.156000   0.562000 (  0.748801)
css:    0.296000   0.031000   0.327000 (  0.546001)

When locating nested elements (elements within an element object as opposed to within the browser object):

n = 100 do |x|"watir:") { n.times { browser.table(id: 'report').tr.exists? } }"xpath:") { n.times { '//table[@id="report"]//tr').exists? } }"css:") { n.times { 'table#report tr').exists? } }

The poor XPath performance is more evident:

            user     system      total        real
watir:  0.093000   0.015000   0.108000 (  0.202801)
xpath:  0.968000   0.344000   1.312000 (  2.277604)
css:    0.842000   0.171000   1.013000 (  2.043603)


When it comes to locating elements, Watir-Webdriver takes a very different approach compared to Watir-Classic. There are various code paths depending on what the locator is. For simplicity, we will ignore the special handling for Regexp values.

If there is a single locator that is supported by Selenium-WebDriver, there is no processing by Watir-Webdriver. The locator is simply passed on as written. Benchmarking the most common locator – :id:

n = 100 do |x|"watir:") { n.times { browser.table(id: 'report').exists? } }"xpath:") { n.times { browser.table(xpath: '//table[@id="report"]').exists? } }"css:") { n.times { browser.table(css: 'table#report').exists? } }

We see very little difference between using :id and :xpath:

            user     system      total        real
watir:  0.531000   0.156000   0.687000 (  6.984375)
xpath:  0.641000   0.063000   0.704000 (  7.093750)
css:    0.609000   0.078000   0.687000 (  6.843750)

When the locator is not supported by Selenium-WebDriver, Watir-WebDriver will build an XPath (or CSS-selector). For example, this occurs when locating an element by a data-* attribute:

n = 100 do |x|"watir:") { n.times { browser.table(data_rows: '1').exists? } }"xpath:") { n.times { browser.table(xpath: '//table[@data-rows="1"]').exists? } }"css:") { n.times { browser.table(css: 'table[data-rows="1"]').exists? } }

As the locator from Selenium-WebDriver’s perspective is still just XPath, it is no surprise that the performance differences are insignificant:

            user     system      total        real
watir:  0.203000   0.093000   0.296000 (  6.781250)
xpath:  0.484000   0.235000   0.719000 (  7.093750)
css:    0.344000   0.125000   0.469000 (  6.656250)

Selenium-WebDriver only supports a single locator, so Watir-WebDriver will also build an XPath if there are multiple locators:

n = 100 do |x|"watir:") { n.times { browser.table(data_rows: '1', class: 'results_grid').exists? } }"xpath:") { n.times { browser.table(xpath: '//table[@data-rows="1" and @class="results_grid"]').exists? } }"css:") { n.times { browser.table(css: 'table[data-rows="1"][class="results_grid"]').exists? } }

Again, there is no performance impact for writing your own XPath locator:

            user     system      total        real
watir:  0.562000   0.125000   0.687000 (  7.125000)
xpath:  0.625000   0.235000   0.860000 (  7.203125)
css:    0.625000   0.171000   0.796000 (  7.187500)

We can find a performance difference if we return to our nested element scenario:

n = 100 do |x|"watir:") { n.times { browser.table(id: 'report').tr.exists? } }"xpath:") { n.times { '//table[@id="report"]//tr').exists? } }"css:") { n.times { 'table#report tr').exists? } }

However, using XPath actually gives us better performance!

            user     system      total        real
watir:  1.203000   0.625000   1.828000 ( 14.015625)
xpath:  0.329000   0.078000   0.407000 (  6.593750)
css:    0.375000   0.079000   0.454000 (  6.796875)

Adding a third level of nesting:

n = 100 do |x|"watir:") { n.times { browser.table(id: 'report') } }"xpath:") { n.times { '//table[@id="report"]//tr//th').exists? } }"css:") { n.times { 'table#report tr th').exists? } }

We see that each additional element method call has a significant impact, while the XPath performance is not impacted:

            user     system      total        real
watir:  1.828000   0.547000   2.375000 ( 25.328125)
xpath:  0.312000   0.110000   0.422000 (  7.296875)
css:    0.406000   0.125000   0.531000 (  6.703125)


The impact of XPath locators on performance depends on the Watir gem:

  • When using Watir-Classic, using XPath will always be slower.
  • When using Watir-Webdriver, using XPath does not have a negative performance impact. In some cases, XPath actually improves performance.

If you are looking to get the best performance:

  • When using Watir-Classic, avoid using XPath.
  • When using Watir-Webdriver, replace multiple nested element method calls with a single XPath or CSS-selector.
Posted in Watir, Watir-Classic, Watir-Webdriver | Tagged , , | Leave a comment

Find elements based on the presence or absence of an attribute

Watir-Webdriver Issue #345 is a feature request to locate elements based on the presence of an attribute.

For example, in the following HTML, the first div element does not have the data-test attribute. The other three divs do have the attribute – without a value, an empty value and a value respectively.

    <div id="no_attribute">text</div>
    <div id="no_value" data-test>text</div>
    <div id="empty_value" data-test="">text</div>
    <div id="with_value" data-test="something">text</div>

The request is to allow a Boolean, ie true/false, for the locator value. The elements with the attribute would be found using:

p browser.divs(data-test: true).map(&:id)
#=> ["no_value", "empty_value", "with_value"]

It seems like a good idea. As there is no scheduled release for this feature, what options do we have today (in version 0.7.0)?

Find element with attribute

As mentioned in the issue, you could write a CSS-selector to find elements with the attribute:

locator = {css: 'div[data-test]'}
p browser.divs(locator).map(&:id)
#=> ["no_value", "empty_value", "with_value"]

Of course, you could also write the same thing using XPath:

locator = {xpath: '//div[@data-test]'}
p browser.divs(locator).map(&:id)
#=> ["no_value", "empty_value", "with_value"]

Knowing that the Element#attribute_value method will return nil for absent attributes, an alternative would be:

p browser.divs.reject { |div| div.attribute_value('data-test').nil? }.map(&:id)
#=> ["no_value", "empty_value", "with_value"]

When a locator uses a regular expression, Watir will match the regular expression against the element’s attribute value. An empty regular expression will match any String, but not nil, which means we have shortcut of:

locator = {data_test: //}
p browser.divs(locator).map(&:id)
#=> ["no_value", "empty_value", "with_value"]

Find elements without attribute

Finding elements without the attribute simply requires a negation in the CSS-selector:

locator = {css: 'div:not([data-test])'}
p browser.divs(locator).map(&:id)
#=> ["no_attribute"]

The same would be true for the XPath locator:

locator = {xpath: '//div[not(@data-test)]'}
p browser.divs(locator).map(&:id)
#=> ["no_attribute"]

For the inspecting the attribute value approach, one would want to take the elements where the value is nil:

p { |div| div.attribute_value('data-test').nil? }.map(&:id)
#=> ["no_attribute"]
Posted in Watir | Tagged , , | Leave a comment

Check if an element is within another element


Consider the following HTML where the span is the child element of interest:

    <div id="ancestor">
      <div id="parent">
        <span id="child">the text</span>
    <div id="not_related">

Given we have created a reference to each of the elements:

ancestor = browser.div(:id => 'ancestor')
parent = browser.div(:id => 'parent')
child = browser.span(:id => 'child')
not_related = browser.div(:id => 'not_related')

How do you check which include the child element?


The Element#== method allows you to check if two elements are referencing the same node in the DOM.

To check if an element is the immediate parent, compare it to the element returned by the child’s parent method:

child.parent == parent
#=> true
child.parent == ancestor
#=> false
child.parent == not_related
#=> false

To check if an element is an ancestor, check if any of its elements match the child of interest:

ancestor.elements.any? { |e| e == child }
#=> true
not_related.elements.any? { |e| e == child }
#=> false

Aside – Do Not Use :element Locator

My original thought was to use the :element locator. Unfortunately that failed as it returned true regardless of the element being checked:

parent.element(:element => child.wd).exists?
#=> true
not_related.element(:element => child.wd).exists?
#=> true

When using the :element locator, the underlying element is directly set. There is no validation of the parent (or any validation on the element):

# Ensure that the element exists, making sure that it is not stale and located if necessary
def assert_exists
  @element ||= @selector[:element]

  if @element
    ensure_not_stale # ensure not stale
    @element = locate


Arguably a bug, but not one that is really worth the time to fix.

Posted in Watir, Watir-Classic, Watir-Webdriver | Tagged , | Leave a comment