Watir needs to remove the Watir#always_locate
feature toggle.
Most people don’t use it and most of those who do will be unlikely
to notice any differences at this point. This post is an overview
of how Selenium and Watir view elements differently, how that has
affected Watir’s code, and what Watir needs to do to address it.
To follow along with the code at home:
Watir vs Selenium
These projects have traditionally had a fundamental difference in the concept of what an element is.
- In Selenium an element is a specific object on a specific page that hasn’t changed.
- In Watir an element is whichever object is found at the location of the provided selector.
Watir stores the location or “address” of an element in a selector variable:
Alternately, Selenium WebDriver does not store the locator for an element, but maintains a reference to the object in the DOM (underlying page code) as provided by the driver:
When the DOM changes (e.g. the page is refreshed), these object references go “stale.” Attempting to interact with a stale element in Selenium throws an error:
Attempting to interact with a stale element in Watir results in different
behaviors based on how the the always_locate
toggle is set.
History of #always_locate
In the original version of Watir (watir-classic), there was no notion of a stale element. The desired element was always located before every action based on the selector that is provided.
When Watir was first implemented with WebDriver, it adopted the Selenium idea of what an element is. For various (good) reasons, Watir does a lot of work when it locates an element. As such, there is a performance benefit to only looking it up once, storing the reference id from the driver, and then using that for all future interactions.
As people began converting their test suites from using
watir-classic to watir-webdriver, they began to see these
Watir::Exception::UnknownObjectException
errors as a result of stale elements,
because their tests were written assuming that the DOM could change
without needing to redefine their elements. Rather than restoring
backward compatibility completely, the Watir#always_locate
toggle was created. The default value (true) was backward compatible with
the original Watir, but it could be set to false to allow users to
consider elements the same way Selenium does (and reap the performance boost).
Fortunately, caching elements and relocating them do not have to be
all-or-nothing.
The default always_locate
behavior was updated last year to cache the
element and then make a single call to verify that it is not stale before
using it, rather than the multiple calls that go into locating it from
scratch every time. Essentially the default “always locate” is currently
“locate when necessary.” This provided a significant performance improvement
for the default behavior.
Inconsistencies
Considering Watir elements in the Selenium fashion results in some unfortunate inconsistencies:
A stale element will return false for #exists?
, but only the first
time it is called:
Or in the case of taking an action on an element:
Oddly, this also means that there is no functional distinction between
wait_while
and wait_until
:
The Solution
Because the performance difference between the options is now
negligible, there is no significant benefit to maintaining two
separate notions of what an element is. Watir should only implement its
original approach. That means that #exists?
will always be true if an
element with the provided selector is on the page.
To assist those relying on the Selenium approach to elements, Watir needs
to implement a public #stale?
method. This method could only be
called on previously located elements and would return true if the driver
shows that the associated reference id is no longer attached to the DOM.
This means #stale?
would return true regardless of how many times it is called:
In code that uses explicit waits to check for DOM changes (as in the
examples in the previous section), rather than using #wait_while_present
or #wait_until_present
, it would need to be changed to #wait_until_stale
:
It might also be worth noting that there is a major anti-pattern that can be used with the Selenium approach that would also need to be addressed. If the test code takes an action on an element that might be stale and rescues the exception to use for flow control (yes, I’ve seen this in actual test code), then it will need to be changed as well:
This should be handled the same way as in the previous example, by explicitly waiting for the condition that needs to be met:
Conclusion
Watir needs to standardize on its original definition of what an element is.
This will significantly reduce both complexity in the code and testing time.
It shouldn’t impact many people, and for those whom it does, it should
be relatively painless to swap out “stale” for “exist” or “present” in
various places of their test suites.
No comments
Comments are closed