TA Basics: Element selection with the lapis_lazuli gem

In case you've never head of the phrase "All Roads Lead to Rome", it refers to the fact that many routes can lead to a given result. The same counts for finding elements on your website under test. There are many attributes which can be used to find the element you need. So let's have a look at a bunch of functions/options with the `lapis_lazuli` gem to limit the amount of roads a bit but still keep the test reliable.

Side note

Let's start with something I mentioned in a previous post, but it's good to keep in mind. The `lapis_lazuli` gem is an extension build on top of the `watir` gem. Even though I focus on LapisLazuli functions, you can also use the Watir way instead to locate the elements or even combine the two to a certain extend.

Documentation

All LapisLazuli documentation for locating elements can be found on the testautomation.info website. And also on github, but this is less complete. Since most of the element selection information is very well documented on the testautomation.info website, I'll focus on how I use it, instead of covering everything that is already documented. The LL gem can use 8 ways to find an element, which should be enough to find any element you need to find. I mainly use find/wait/find_all/wait_all but if this is not enough, you know there is more.

Tools and general rules

A tool you need to get familiar with is the element inspector of the browser. On a Windows machine, press the `F12` button while the browser is open, to open the development tools and then use the element inspector to figure out all the details of the elements.
The key to writing a good and stable test suite, is to find the elements by a unique as possible value. The most unique is the element `id`, but if there is no `id`, try to find it by something that is as unique as possible. The `class` or `l10n` attributes are something I use very often. Try to avoid the text of an element as much as possible. Why? Try to thing about what will happen with the text when you need to test your website in multiple languages.

Find versus Wait

The only difference between `find` and `wait` is the time before it throws an exception. The `find` function will try to find the element and if it does not, it will throw an exception directly. The `wait` function tries to find the element, but if it cannot find the element, it will loop for 10 seconds (unless specified otherwise) to try to find the element before it will throws an exception.
# Find element and throw exception when not found browser.find(like: [:div, :class, 'title']) # Find element and throw exception when not found within 5 seconds browser.wait(like: [:div, :class, 'title'], timeout: 5)

There is no golden rule, but I tend to use `find` for elements I want to click/interact and `wait` for elements like notifications, spinners, loaders, or loading results (i.c.w. the condition selector) as much as possible. If your website does not have any indicators that it's loading you're kind of forced to use the `wait` function to find the element to make sure the loading is done.
# Some examples of the condition selector i.c.w. the wait function and custom message def gloader_until browser.wait( like: [:div, :class, 'g-loader'], condition: :until, message: 'gloader not found within 10 seconds' ) end def gloader_while browser.wait( like: [:div, :class, 'g-loader'], condition: :while, message: 'gloader did not disappear within 10 seconds' ) end

I do not recommend to ONLY use the `wait` function because it can seriously delay a test run when some tests are failing. For example, when you know the element should be there instantly, but you use the wait function and the element does not appear, it will still take 10 seconds before the exception is thrown. And for just one element that is fine, but if 6 tests fail that way, you basically already lost one minute where it could have taken 1 second so keep that in mind. On the other end you want to keep your test suite as reliable as possible which also means that it should not fail if the loading time takes one second longer than usual. Finding the balance is important.

Find all

When you just start with test automation, you'll most likely will notice that you cannot find/interact with the element you want, even though you're sure the selector you used is unique. If an element contains an `id`, it should NEVER appear twice on the website according to the W3C standards. But in case you try to find the element by anything else, it's very likely there could be more elements on the page that are similar. A quick way to be 100% sure you only find one element is to use the `find_all` function. If elm.length returns you more than one, you know you need to be more specific to be sure you interact with the correct one.
all_elms = browser.find_all(like: [:li, :class, 'mp-tabpage-control'])

Context

My favorite way to make sure I interact with the right element is by using the context selector. In a nutshell this is how you use it. A simple example is when you have two of the same elements (e.g. a div) with the same class, same text, everything is the same. The only difference is the location on the website. So what I do in this case is I first locate the unique block (e.g. the active li element), and then I use that element as a context for finding the element I need.
active_li = browser.find(like: [:li, :class, 'mp-tabpage-control active']) elm = browser.find(like: [:div, 'data-mp-l10n', 'Attachments'], context: active_li)




Pick

As a last resort I use the pick selector. Let's say I have two elements on my website, and I'm not able to identify them in a way that is unique. This should rarely happen but it happens. With the regular `find` and `wait` functions it will by default return the first element it encounters. Luckely there is the pick selector, which let's you choose which one to take. The pick selector can also be handy when you have a list of options/checkboxes/etc and you just want to select a random element from that list.
def actionbar_last browser.find( like: [:ul, 'data-ng-controller', 'MProof.Caas.Ui.Controls.ActionBar'], context: Material.listrow, pick: :last # 2 existing and I need the last one ) end

Last but not least

For whoever noticed that the examples on the testautomation.info website are using this format ':like =>', ':filter_by =>', ':condition =>', etc. But I'm using 'like:', 'filter_by:', 'condition:', etc. What is the difference? There is no difference in functionality, both should work. The examples are the old Ruby style of writing and what I use is the newer Ruby style of writing. To be honest I like the old style more, it looks a bit more readable to me, but I also want to keep things as up-to-date as possible.

Comments

Popular posts from this blog

PowerShell - How to overcome Azure VM's fixed resolution limitation

TA Basics: Website Test Automation on mobile devices via Appium server

TA: Who doesn't like proxies? Me!