Difference between revisions of "Lapis Lazuli:Selecting an element"

From Test Automation Wiki
Jump to: navigation, search
 
(6 intermediate revisions by the same user not shown)
Line 1: Line 1:
=This page is under construction=
+
Lapis Lazuli is an addition to Watir. This page explains how to select an element using Lapis Lazuli. Then, once you have the element selected, you can start interacting with the element. See [[Interacting with an element]]
  
Lapis Lazuli is an addition to Watir. This page explains how to select an element using Lapis Lazuli. Then, once you have the element selected, you can start interacting with the element. See [[Interacting with an element]]
+
You can directly try out this code by starting IRB in the console:
 +
 
 +
<syntaxhighlight>
 +
cd path/to/your/project
 +
bundle exec irb
 +
 
 +
require 'lapis_lazuli'
 +
include LapisLazuli
 +
# start writing code here
 +
</syntaxhighlight>
  
 
== Finding the element ==
 
== Finding the element ==
Line 19: Line 28:
  
 
=== Find and wait ===
 
=== Find and wait ===
 +
 
<syntaxhighlight lang="ruby">
 
<syntaxhighlight lang="ruby">
 
browser.goto 'http://training-page.testautomation.info/'
 
browser.goto 'http://training-page.testautomation.info/'
Line 36: Line 46:
 
# Setting the timeout to 3 seconds, so it would throw an error after 3 seconds.
 
# Setting the timeout to 3 seconds, so it would throw an error after 3 seconds.
 
elm5 = browser.wait(:input => {:id => 'login-username'}, :timeout => 3)
 
elm5 = browser.wait(:input => {:id => 'login-username'}, :timeout => 3)
 +
 +
# Selecting an element by providing 2 attributes
 +
elm6 = browser.find(:input => {:id => 'login-username', :class => /form-control/})
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Line 70: Line 83:
 
elm5 = browser.wait(:like => [:input, :id, 'login-username'])
 
elm5 = browser.wait(:like => [:input, :id, 'login-username'])
 
</syntaxhighlight>
 
</syntaxhighlight>
 
  
 
=== Find all ===
 
=== Find all ===
Line 135: Line 147:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
In addition, a helper function is exposed that allows you to pick an element from a collection (array) of elements.
+
== Selection options ==
 +
 
 +
* <b>Selector options</b>, these options are part of the selector.
 +
** <code>:throw</code> - <code>true</code> or <code>false</code> weather an error should be thrown if the element can't be found.
 +
** <code>:context</code> - give a found element to search within.
 +
** <code>:filter_by</code> - the expected value is a symbol that the element responds to, e.g. <code>:present?</code>. If provided, only present elements (in the example) will be matched.
 +
* <b>General options</b>, these options are for the whole find function
 +
** <code>:message</code> - when failing, raise a custom message.
 +
** <code>:timout</code> - Browser.wait option only, time to wait to find your selector.
 +
** <code>:condition</code> - Browser.wait option only, weather to wait <code>:until</code> or <code>:while</code> your selector found.
 +
** <code>:selectors</code> - a list of selectors by which to find elements. Implicit when no other options are provided (see above).
 +
** <code>:pick</code> - one of the possible first parameter values to the <code>pick_one</code> function. Note that for <code>find</code> and <code>:multi_find</code>, the default is to pick the first element found.
 +
** <code>:mode</code> - one of <code>match_one</code> or <code>match_all</code> - determines whether the multi find functions return after finding a single element matching any _one_ of the provided selectors, or only when _all_ selectors given match at least one element.
 +
For the default values of all the options, see [[Lapis Lazuli:Default values for find / multi find / find all / multi find all and wait options]]
 +
 
 +
=== Selector specific v.s. general options ===
 +
 
 +
In most cases, you will use both type of options in a list without separating them. But it's important to understand the difference, in case you're going to use multi_find. In multi_find you use multiple selectors, and so the notation is different.
 +
 
 +
Example of an <b>invalid</b> selection
 +
<syntaxhighlight lang="ruby">
 +
browser.multi_find(
 +
  :selectors => [
 +
    {:like => :div},
 +
    {:like => :input}
 +
  ],
 +
  :filter_by => :exists? # <-- This is the wrong place to put this, because now it's not part of the selector.
 +
)
 +
</syntaxhighlight>
 +
 
 +
A correct example, where 1 selector is filtered on <code>:exists?</code> and another searched within a <code>:context</code>.
 +
<syntaxhighlight lang="ruby">
 +
browser.multi_find(
 +
  :selectors => [
 +
    {:like => :div, :filter_by => :exists?},
 +
    {:like => :input, :context => form}
 +
  ]
 +
)
 +
</syntaxhighlight>
 +
 
 +
=== :throw ===
 +
 
 +
<code>:throw</code> decides weather an error should be thrown when it fails to find your specifications.
 +
 
 +
<syntaxhighlight lang="ruby">
 +
browser.goto 'http://training-page.testautomation.info/'
 +
 
 +
# An error will be thrown (default is :throw => true)
 +
browser.find(:like => [:div, :class, 'doesnotexist'])
 +
 
 +
# No error will be thrown and elm1 will result in being nil
 +
elm1 = browser.find(:like => [:div, :class, 'doesnotexist'], :throw => false)
 +
if elm1 == nil
 +
  # Do something else if the element did not exist
 +
end
 +
</syntaxhighlight>
 +
 
 +
=== :context ===
 +
 
 +
<code>:context</code> is an element you've already found, to look inside for something else.
 +
<syntaxhighlight lang="ruby">
 +
browser.goto 'http://training-page.testautomation.info/'
 +
 
 +
# First wait for the login form container
 +
form = browser.wait(:like => [:form, :id, 'form-login'])
 +
# Then select the input fields inside the form
 +
inputs = browser.find_all(
 +
  :like => :input,
 +
  :context => form,
 +
  :filter_by => :exists?
 +
)
 +
inputs.length
 +
=> 2
 +
# To prove the difference without using a context
 +
inputs = browser.find_all(
 +
  :like => :input,
 +
  :filter_by => :exists?
 +
)
 +
inputs.length
 +
=> 9
 +
 
 +
</syntaxhighlight>
 +
<blockquote>
 +
Note that when a find/wait function become too long (2 or more options), we're putting it on multiple lines. This is to keep the code look clean and easy to read. Also a best practise to do yourself.
 +
</blockquote>
 +
 
 +
=== :filter_by ===
 +
 
 +
Possibility between <code>:present?</code> and <code>:exists?</code>
 +
 
 +
<syntaxhighlight lang="ruby">
 +
browser.goto 'http://training-page.testautomation.info/'
 +
 
 +
# Then select the input fields inside the form
 +
inputs = browser.find_all(
 +
  :like => :input,
 +
  :context => form,
 +
  :filter_by => :exists?
 +
)
 +
inputs.length
 +
=> 2
 +
# To prove the difference without using a context
 +
inputs = browser.find_all(
 +
  :like => :input,
 +
  :filter_by => :exists?
 +
)
 +
inputs.length
 +
=> 9
 +
 
 +
</syntaxhighlight>
 +
 
 +
=== :message ===
 +
 
 +
Lapis Lazuli will give a default message when an error is thrown. This default message can be overwritten with your own custom message.
 +
 
 +
<syntaxhighlight lang="ruby">
 +
browser.goto 'http://training-page.testautomation.info/'
 +
 
 +
# Default error
 +
browser.find(:like => [:input, :id, 'doesnotexist'])
 +
=> RuntimeError: Error in find - Cannot find elements with selectors: [{:like=>{:element=>:input, :attribute=>:id, :include=>"doesnotexist"}}]
 +
 
 +
# Custom error
 +
browser.find(
 +
  :like => [:input, :id, 'doesnotexist'],
 +
  :message => 'O dear, something went wrong!'
 +
)
 +
=> RuntimeError: O dear, something went wrong! - Cannot find elements with selectors: [{:like=>{:element=>:input, :attribute=>:id, :include=>"doesnotexist"}}]
 +
 
 +
# Default wait error
 +
browser.wait(:like => [:input, :id, 'doesnotexist'], :timeout => 1)
 +
=> Watir::Wait::TimeoutError: Error in wait - timed out after 1 seconds with selectors: [{:like=>[:input, :id, "doesnotexist"], :filter_by=>:present?}]
 +
 
 +
# Custom wait error
 +
browser.wait(
 +
  :like => [:input, :id, 'doesnotexist'],
 +
  :timeout => 1,
 +
  :message => 'Oh no! Nothing found while waiting for 1 second!'
 +
)
 +
=> Watir::Wait::TimeoutError: Oh no! Nothing found while waiting for 1 second! - timed out after 1 seconds with selectors: [{:like=>[:input, :id, "doesnotexist"], :filter_by=>:present?}]
 +
</syntaxhighlight>
 +
 
 +
=== :timeout ===
 +
 
 +
The <code>:timeout</code> option is only used in wait functions. The default timeout is 10 seconds.
 +
 
 +
 
 +
<syntaxhighlight lang="ruby">
 +
browser.goto 'http://training-page.testautomation.info/'
 +
 
 +
# Click the register button
 +
browser.find(:button => {:id => 'button-register'}).click
  
== Picking an Element ==
+
# Then wait for the register form to appear within 2 seconds
 +
reg_form = browser.wait(
 +
  :like => [:form, :id, 'form-register'],
 +
  :timeout => 2
 +
)
  
The <code>[[Lapis_Lazuli:Browser.pick_one()|pick_one]]</code> helper function is easily explained in isolation, but it should be noted that it also underlies the <code>find</code> and <code>multi_find</code> functions:
+
# And start filling in the fields
 +
browser.find(
 +
  :like => [:input, :id, 'register-username'],
 +
  :context => reg_form
 +
).set 'Hello'
  
<syntaxhighlight >
+
</syntaxhighlight>
browser.pick_one(:first, collection)  # returns the first item from the collection
 
browser.pick_one(:last, collection)  # returns the last item from the collection
 
browser.pick_one(:random, collection) # returns a random item from the collection
 
browser.pick_one(12, collection)      # returns the 12th item from the collection
 
</syntaxhighlight >
 
  
You should not usually need to use this function directly, but instead use one of the find functions.
+
=== :condition===
  
== Find Function Syntax ==
+
The <code>:condition</code> option is only used in wait functions. The default is <code>:until</code>. Sometimes you want to wait <code>:while</code> however, for example if you want to make sure a pop-up message disappeared.
  
All find functions accept much the same parameters; the general syntax is this:
 
  
<syntaxhighlight >
+
<syntaxhighlight lang="ruby">
browser.find(selector1)
+
browser.goto 'http://training-page.testautomation.info/'
browser.find(:some_option => value, :selectors => [selector1])
 
  
# browser.find_all same as browser.find
+
# Click the register button
 +
browser.find(:button => {:id => 'button-register'}).click
  
browser.multi_find(selector1, selector2, ...)
+
# Then wait for the register form to appear within 2 seconds
browser.multi_find(:some_option => value, :selectors => [selector1, selector2, ...])
+
browser.wait(
 +
  :like => [:form, :id, 'form-register'],
 +
  :condition => :until # default
 +
)
  
# browser.multi_find_all same as browser.multi_find
+
# Then click the close button
</syntaxhighlight >
+
browser.find(:like => [:button, :id, 'modal-close-bottom']).click
  
This syntax affords the flexibility of providing options to the functions, without forcing you to provide any.
+
# And wait for the form the have disappeared
 +
browser.wait(
 +
  :like => [:form, :id, 'form-register'],
 +
  :condition => :while
 +
)
  
== Find Options ==
+
</syntaxhighlight>
  
The following options are interpreted by all find functions:
 
  
* <code>[[Lapis_Lazuli:find()/selectors/|:selectors]]</code> - a list of selectors by which to find elements. Implicit when no other options are provided (see above).
+
=== :selectors ===
* <code>[[Lapis_Lazuli:find()/pick/|:pick]]</code> - one of the possible first parameter values to the <code>pick_one</code> function. Note that for <code>find</code> and <code>:multi_find</code>, the default is to pick the first element found.
 
* <code>[[Lapis_Lazuli:find()/mode/|:mode]]</code> - one of <code>match_one</code> or <code>match_all</code> - determines whether the multi find functions return after finding a single element matching any _one_ of the provided selectors, or only when _all_ selectors given match at least one element.
 
* <code>[[Lapis_Lazuli:find()/filter_by/|:filter_by]]</code> - the expected value is a symbol that the element responds to, e.g. <code>:present?</code>. If provided, only present elements (in the example) will be matched.
 
* <code>[[Lapis_Lazuli:find()/context/|:context]]</code> - give a found element to search within.
 
* <code>[[Lapis_Lazuli:find()/message/|:message]]</code> - when failing, raise a custom message.
 
  
== Selectors ==
+
When using only 1 selector, this option doesn't have to be used, like in most previous examples. But in the case of Multi find, you're using multiple selectors to find elements. In that case, the <code>:selectors</code> option is needed to sum up these different selectors.
  
Selectors can be the kind of selectors that would be passed to the Watir browser's functions, but LapisLazuli affors far greater flexibility here.
+
<syntaxhighlight lang="ruby">
 +
browser.goto 'http://training-page.testautomation.info/'
  
# If a selector is a string, e.g. <code>"a"</code>, then elements with the symbol name are found, using Watir's built-in functions.
+
# Find the register button
# If a selector is a symbol, e.g. <code>:a</code>, then elements with the symbol name are found, but using XPath instead.
+
elm1 = browser.find(
# If a selector is a hash, it is considered to be a regular Watir selector, e.g. <code>:id => /some-id/</code>.
+
  :button => {:id => 'button-register'}
## If this hash contains a <code>:like</code> key, the value of this key is further interpreted before passing the entire hash on to Watir (see below).
+
)
## If this hash contains a <code>:context</code> key, the value of this key is expected to be a Watir element, and a search will be performed relative to this element. If the context is not provided, the search starts at the document root.
 
  
== Like Selectors ==
+
# This is the exact same thing
 +
elm1 = browser.find(
 +
  :selectors => [
 +
    :button => {:id => 'button-register'}
 +
  ]
 +
)
  
Like selectors are shorthands for more complex XPath-based selection. A like selector is either of:
+
# This will fail, because you need to seperate the selectors with { and }
 +
elm1 = browser.find(
 +
  :selectors => [
 +
    :button => {:id => 'button-register'}, # WRONG!
 +
    :button => {:id => 'button-login'}
 +
  ]
 +
)
 +
=> warning: key :button is duplicated and overwritten on line ..
 +
 
 +
# This is the proper way, but it will find no results, because browser.find will only look for the first selector
 +
elm1 = browser.find(
 +
  :selectors => [
 +
    {:button => {:id => 'button-not_found'}},
 +
    {:button => {:id => 'button-register'}}
 +
  ]
 +
)
 +
=> RuntimeError: Error in find - Cannot find elements with selectors (...)
 +
 
 +
# Now multi_find with try the second selector if the first one fails or a third, fourth etc.
 +
elm1 = browser.multi_find(
 +
  :selectors => [
 +
    {:button => {:id => 'button-not_found'}},
 +
    {:button => {:id => 'button-register'}}
 +
  ]
 +
)
 +
 
 +
# multi_find_all will go trough all the selectors and return everything it can find with your different selections.
 +
elms = browser.multi_find_all(
 +
  :selectors => [
 +
    {:button => {:id => 'button-not_found'}},
 +
    {:button => {:id => 'button-register'}},
 +
    {:like => :input, :filter_by => :exists?},
 +
    {:like => :a, :filter_by => :exists?}
 +
  ]
 +
)
 +
elms.length
 +
=> 18
 +
</syntaxhighlight>
 +
 
 +
=== :pick ===
 +
 
 +
With <code>:pick</code> you tell the finder which element to pick from the list. This only applies to non '_all' functions.
 +
* <code>:pick => :first</code> is the default, it returns the first found element.
 +
* <code>:pick => :last</code> returns the last found element.
 +
* <code>:pick => :random</code> returns a random found element.
 +
* <code>:pick => 6</code> returns the 6th found element. Can be any integrature.
 +
 
 +
<syntaxhighlight lang="ruby">
 +
browser.goto 'http://training-page.testautomation.info/'
 +
 
 +
# Get the ID of the first input field
 +
browser.find(
 +
  :like => :input,
 +
  :pick => :first #default
 +
).id
 +
=> "login-username"
 +
 
 +
# And of the last one
 +
browser.find(
 +
  :like => :input,
 +
  :pick => :last
 +
).id
 +
=> "login-password"
 +
 
 +
# Will randomly return "login-password" or "login-username"
 +
browser.find(
 +
  :like => :input,
 +
  :pick => :random
 +
).id
 +
 
 +
# Will randomly return "login-password" or "login-username"
 +
browser.find(
 +
  :like => :input,
 +
  :pick => 1
 +
).id
 +
=> "login-username"
 +
 
 +
</syntaxhighlight>
 +
 
 +
=== :mode ===
 +
 
 +
The <code>:mode</code> option is only used for multi find.
 +
* For <code>browser.multi_find</code> the default is <code>:mode => :match_one</code>, meaning that in a list of selectors, it will stop looking once it has found one of the multiple selectors.
 +
* For <code>browser.multi_find_all</code> the default is <code>:mode => :match_any</code>, meaning that in a list of selectors, it will accept any successful selection.
 +
* Finally there is <code>:match_all</code>, which means that it expects all the selectors to be present on the page, when there is any of the selectors missing, it will throw an error.
 +
 
 +
<syntaxhighlight lang="ruby">
 +
browser.goto 'http://training-page.testautomation.info/'
 +
 
 +
# Adding :match_one to multi_find_all will actually do the exact same thing as multi_find (without all), but then return it as an array
 +
elms = browser.multi_find_all(
 +
  :selectors => [
 +
    {:button => {:id => 'button-not_found'}},
 +
    {:button => {:id => 'button-register'}},
 +
    {:like => :input, :filter_by => :exists?},
 +
    {:like => :a, :filter_by => :exists?}
 +
  ],
 +
  :mode => :match_one
 +
)
 +
=> [#<Watir::Button: located: true; {:id=>"button-register", :tag_name=>"button", :index=>0}>]
 +
 
 +
# Adding :match_any is the default for multi_find_all
 +
elms = browser.multi_find_all(
 +
  :selectors => [
 +
    {:button => {:id => 'button-not_found'}},
 +
    {:button => {:id => 'button-register'}},
 +
    {:like => :input, :filter_by => :exists?},
 +
    {:like => :a, :filter_by => :exists?}
 +
  ],
 +
  :mode => :match_any # Default for multi_find_all
 +
)
 +
elms.length
 +
=> 18
  
# A symbol, e.g. <code>:a</code>, in which case an XPath query is constructed for finding <code>a</code> elements.
+
# Adding :match_all will fail here, because 'button-not_found' doesn't exist
# An array of three elements, in which case an XPath query is constructed with the following components:
+
elms = browser.multi_find_all(
## The first is interpreted as the element name (as above),
+
  :selectors => [
## The second is interpreted as an attribute to match, and
+
    {:button => {:id => 'button-register'}},
## The third is interpreted as an attribute value to match.
+
    {:like => :input, :filter_by => :exists?},
 +
    {:like => :a, :filter_by => :exists?}
 +
  ],
 +
  :mode => :match_all
 +
)
 +
=> [<array>, <with>, <many>, <elements>]
 +
</syntaxhighlight>
  
Note that like selectors **add** to a regular Watir selector. It's perfectly legitimate to use regular selectors and like selectors in combination, but the result may be unexpected.
+
In addition, a helper function is exposed that allows you to pick an element from a collection (array) of elements.
  
== Examples ==
+
== Other Examples ==
  
 
<syntaxhighlight lang="ruby">
 
<syntaxhighlight lang="ruby">
# Find any link. same as browser.a
+
# Find any link. same as browser.a in Watir
browser.find(:a)                    
+
browser.find(:a) # Beta, doesn't work in all cases
 
browser.find({:like => :a}) # xpath
 
browser.find({:like => :a}) # xpath
  
  
# same as browser.a(:href => /test/)
+
# same as browser.a(:href => /test/) in Watir
 
browser.find(:a => {:href => /test/})
 
browser.find(:a => {:href => /test/})
  
# More complicated version:
+
# Looking for an <a> element with class "loginButton" AND href "[anything]login[anything]" AND name "login"
 
browser.find(
 
browser.find(
   {:a => {:class => "loginButton"}},  
+
   :a => {
  {:a => {:href => /login/}},
+
    :class => "loginButton",
  {:button => {:name => "login"}}
+
    :href => /login/,
 +
    :name => "login"
 +
  }
 
)
 
)
  
Line 242: Line 533:
  
 
# Finding based on name, id or text
 
# Finding based on name, id or text
browser.find("login")
+
browser.find("login") # Beta, might not always work as expected
  
 
# Finding an element within another element
 
# Finding an element within another element

Latest revision as of 09:06, 7 June 2017

Lapis Lazuli is an addition to Watir. This page explains how to select an element using Lapis Lazuli. Then, once you have the element selected, you can start interacting with the element. See Interacting with an element

You can directly try out this code by starting IRB in the console:

cd path/to/your/project
bundle exec irb

require 'lapis_lazuli'
include LapisLazuli
# start writing code here

Finding the element

There are 8 ways to find an element:

  1. browser.find finds a single element matching the specifications passed to it.
  2. browser.wait does the same as find, but then it waits until it's successful to find the matching specifications.
  3. browser.find_all finds all elements matching the specifications passed to it.
  4. browser.wait_all waits until it finds at least one matching element, but will return all of them if more were found.
  5. browser.multi_find finds a single element matching one of multiple specifications passed to it. Specifications are handled in order and only the first match is returned.
  6. browser.multi_wait does the same as multi_find, but then waits until it's successful to find an element.
  7. browser.multi_find_all finds all elements matching one of multiple specifications passed to it. Specifications are handled in order and only elements belonging to the first match are returned.
  8. browser.multi_wait_all does the same as multi_find_all, but again, it waits for at least 1 result to match the specifications.

Element selection examples

Find and wait

browser.goto 'http://training-page.testautomation.info/'

# Selecting the username input field using :objects
elm1 = browser.find(:input => {:id => 'login-username'})

# Does the same thing, also 'strings' are allowed
elm2 = browser.find('input' => {'id' => 'login-username'})

# Selecting it using a /regular expression/ (see http://rubular.com/ )
elm3 = browser.find(:input => {:id => /gin-usern/})

# Doing the same thing, but now we wait until it's present. So if it takes 5 seconds for it to show, elm1, 2 and 3 would give an error, but this one will succeed.
elm4 = browser.wait(:input => {:id => 'login-username'})

# Setting the timeout to 3 seconds, so it would throw an error after 3 seconds.
elm5 = browser.wait(:input => {:id => 'login-username'}, :timeout => 3)

# Selecting an element by providing 2 attributes
elm6 = browser.find(:input => {:id => 'login-username', :class => /form-control/})

Like selector

Like selectors are shorthands for more complex XPath-based selection. A like selector is either of:

  1. A symbol, e.g. :a, in which case an XPath query is constructed for finding a elements.
  2. An array of three elements, in which case an XPath query is constructed with the following components:
    1. The first is interpreted as the element name (as above),
    2. The second is interpreted as an attribute to match, and
    3. The third is interpreted as an attribute value to match.
browser.goto 'http://training-page.testautomation.info/'

# Full notation
elm1 = browser.find(:like => [
  :element => :input, 
  :attribute => :id, 
  :include => 'login-username'
])

# Short notation
elm2 = browser.find(:like => [:input, :id, 'login-username'])

# The 3rd value is "include", so you don't need to use the full ID, just a part of the text will work too.
elm3 = browser.find(:like => [:input, :id, 'gin-usern'])

# Will select the first :input field it finds present on the page
elm4 = browser.find(:like => :input)

# Also works fine with wait, or any other find alternative.
elm5 = browser.wait(:like => [:input, :id, 'login-username'])

Find all

browser.goto 'http://training-page.testautomation.info/'

# Find all input elements that are present on this page
elm1 = browser.find_all(:like => :input)
# Check how many elements were found
elm1.length
=> 2
# Select the first element [0] (zero is the first in arrays), and show the html
elm1[0].html
=> <input id="login-username" class="form-control ng-pristine ng-untouched ng-valid" ng-model="Users.login_data.name" size="60" placeholder="Username" type="text">

# Find all input elements that exist on the page (so also the ones in the background)
elm2 = browser.find_all(:like => :input, :filter_by => :exists?)
elm2.length
=> 9

Multi find

Multi find supports the use of multiple selectors and tries to find a match with any of these.

browser.goto 'http://training-page.testautomation.info/'

# Find both the username and password input field
elms = browser.multi_find_all(
  :selectors => [
    {:like => [:input, :id, 'login-username']},
    {:like => [:input, :id, 'login-password']}
  ]
)
elms.length
=> 2
elms[0].html
=> <input id="login-username" class="form-control ng-pristine ng-untouched ng-valid" ng-model="Users.login_data.name" size="60" placeholder="Username" type="text">
elms[1].html
=> <input ng-model="Users.login_data.password" class="form-control ng-pristine ng-untouched ng-valid" id="login-password" placeholder="Password" type="password">

# Find all input elements AND <a> elements that exist on the page
elms = browser.multi_find_all(
  :selectors => [
    {
      :like => :input,
      :filter_by => :exists?
    },
    {
      :like => :a,
      :filter_by => :exists?
    }
  ]
)
elms.length
=> 17
# This will print out the ID of all the elements you found.
elms.each do |e| 
  puts e.id 
end

Selection options

  • Selector options, these options are part of the selector.
    • :throw - true or false weather an error should be thrown if the element can't be found.
    • :context - give a found element to search within.
    • :filter_by - the expected value is a symbol that the element responds to, e.g. :present?. If provided, only present elements (in the example) will be matched.
  • General options, these options are for the whole find function
    • :message - when failing, raise a custom message.
    • :timout - Browser.wait option only, time to wait to find your selector.
    • :condition - Browser.wait option only, weather to wait :until or :while your selector found.
    • :selectors - a list of selectors by which to find elements. Implicit when no other options are provided (see above).
    • :pick - one of the possible first parameter values to the pick_one function. Note that for find and :multi_find, the default is to pick the first element found.
    • :mode - one of match_one or match_all - determines whether the multi find functions return after finding a single element matching any _one_ of the provided selectors, or only when _all_ selectors given match at least one element.

For the default values of all the options, see Lapis Lazuli:Default values for find / multi find / find all / multi find all and wait options

Selector specific v.s. general options

In most cases, you will use both type of options in a list without separating them. But it's important to understand the difference, in case you're going to use multi_find. In multi_find you use multiple selectors, and so the notation is different.

Example of an invalid selection

browser.multi_find(
  :selectors => [
    {:like => :div},
    {:like => :input}
  ],
  :filter_by => :exists? # <-- This is the wrong place to put this, because now it's not part of the selector.
)

A correct example, where 1 selector is filtered on :exists? and another searched within a :context.

browser.multi_find(
  :selectors => [
    {:like => :div, :filter_by => :exists?},
    {:like => :input, :context => form}
  ]
)

:throw

:throw decides weather an error should be thrown when it fails to find your specifications.

browser.goto 'http://training-page.testautomation.info/'

# An error will be thrown (default is :throw => true)
browser.find(:like => [:div, :class, 'doesnotexist'])

# No error will be thrown and elm1 will result in being nil
elm1 = browser.find(:like => [:div, :class, 'doesnotexist'], :throw => false)
if elm1 == nil
  # Do something else if the element did not exist
end

:context

:context is an element you've already found, to look inside for something else.

browser.goto 'http://training-page.testautomation.info/'

# First wait for the login form container
form = browser.wait(:like => [:form, :id, 'form-login'])
# Then select the input fields inside the form
inputs = browser.find_all(
  :like => :input,
  :context => form,
  :filter_by => :exists?
)
inputs.length
=> 2
# To prove the difference without using a context
inputs = browser.find_all(
  :like => :input,
  :filter_by => :exists?
)
inputs.length
=> 9

Note that when a find/wait function become too long (2 or more options), we're putting it on multiple lines. This is to keep the code look clean and easy to read. Also a best practise to do yourself.

:filter_by

Possibility between :present? and :exists?

browser.goto 'http://training-page.testautomation.info/'

# Then select the input fields inside the form
inputs = browser.find_all(
  :like => :input,
  :context => form,
  :filter_by => :exists?
)
inputs.length
=> 2
# To prove the difference without using a context
inputs = browser.find_all(
  :like => :input,
  :filter_by => :exists?
)
inputs.length
=> 9

:message

Lapis Lazuli will give a default message when an error is thrown. This default message can be overwritten with your own custom message.

browser.goto 'http://training-page.testautomation.info/'

# Default error
browser.find(:like => [:input, :id, 'doesnotexist'])
=> RuntimeError: Error in find - Cannot find elements with selectors: [{:like=>{:element=>:input, :attribute=>:id, :include=>"doesnotexist"}}]

# Custom error
browser.find(
  :like => [:input, :id, 'doesnotexist'],
  :message => 'O dear, something went wrong!'
)
=> RuntimeError: O dear, something went wrong! - Cannot find elements with selectors: [{:like=>{:element=>:input, :attribute=>:id, :include=>"doesnotexist"}}]

# Default wait error
browser.wait(:like => [:input, :id, 'doesnotexist'], :timeout => 1)
=> Watir::Wait::TimeoutError: Error in wait - timed out after 1 seconds with selectors: [{:like=>[:input, :id, "doesnotexist"], :filter_by=>:present?}]

# Custom wait error
browser.wait(
  :like => [:input, :id, 'doesnotexist'], 
  :timeout => 1,
  :message => 'Oh no! Nothing found while waiting for 1 second!'
)
=> Watir::Wait::TimeoutError: Oh no! Nothing found while waiting for 1 second! - timed out after 1 seconds with selectors: [{:like=>[:input, :id, "doesnotexist"], :filter_by=>:present?}]

:timeout

The :timeout option is only used in wait functions. The default timeout is 10 seconds.


browser.goto 'http://training-page.testautomation.info/'

# Click the register button
browser.find(:button => {:id => 'button-register'}).click

# Then wait for the register form to appear within 2 seconds
reg_form = browser.wait(
  :like => [:form, :id, 'form-register'],
  :timeout => 2
)

# And start filling in the fields
browser.find(
  :like => [:input, :id, 'register-username'],
  :context => reg_form
).set 'Hello'

:condition

The :condition option is only used in wait functions. The default is :until. Sometimes you want to wait :while however, for example if you want to make sure a pop-up message disappeared.


browser.goto 'http://training-page.testautomation.info/'

# Click the register button
browser.find(:button => {:id => 'button-register'}).click

# Then wait for the register form to appear within 2 seconds
browser.wait(
  :like => [:form, :id, 'form-register'],
  :condition => :until # default
)

# Then click the close button
browser.find(:like => [:button, :id, 'modal-close-bottom']).click

# And wait for the form the have disappeared
browser.wait(
  :like => [:form, :id, 'form-register'],
  :condition => :while
)


:selectors

When using only 1 selector, this option doesn't have to be used, like in most previous examples. But in the case of Multi find, you're using multiple selectors to find elements. In that case, the :selectors option is needed to sum up these different selectors.

browser.goto 'http://training-page.testautomation.info/'

# Find the register button
elm1 = browser.find(
  :button => {:id => 'button-register'}
)

# This is the exact same thing
elm1 = browser.find(
  :selectors => [
    :button => {:id => 'button-register'}
  ]
)

# This will fail, because you need to seperate the selectors with { and }
elm1 = browser.find(
  :selectors => [
    :button => {:id => 'button-register'}, # WRONG!
    :button => {:id => 'button-login'}
  ]
)
=> warning: key :button is duplicated and overwritten on line ..

# This is the proper way, but it will find no results, because browser.find will only look for the first selector
elm1 = browser.find(
  :selectors => [
    {:button => {:id => 'button-not_found'}},
    {:button => {:id => 'button-register'}}
  ]
)
=> RuntimeError: Error in find - Cannot find elements with selectors (...)

# Now multi_find with try the second selector if the first one fails or a third, fourth etc.
elm1 = browser.multi_find(
  :selectors => [
    {:button => {:id => 'button-not_found'}},
    {:button => {:id => 'button-register'}}
  ]
)

# multi_find_all will go trough all the selectors and return everything it can find with your different selections.
elms = browser.multi_find_all(
  :selectors => [
    {:button => {:id => 'button-not_found'}},
    {:button => {:id => 'button-register'}},
    {:like => :input, :filter_by => :exists?},
    {:like => :a, :filter_by => :exists?}
  ]
)
elms.length
=> 18

:pick

With :pick you tell the finder which element to pick from the list. This only applies to non '_all' functions.

  • :pick => :first is the default, it returns the first found element.
  • :pick => :last returns the last found element.
  • :pick => :random returns a random found element.
  • :pick => 6 returns the 6th found element. Can be any integrature.
browser.goto 'http://training-page.testautomation.info/'

# Get the ID of the first input field
browser.find(
  :like => :input,
  :pick => :first #default
).id
=> "login-username"

# And of the last one
browser.find(
  :like => :input,
  :pick => :last
).id
=> "login-password"

# Will randomly return "login-password" or "login-username"
browser.find(
  :like => :input,
  :pick => :random
).id

# Will randomly return "login-password" or "login-username"
browser.find(
  :like => :input,
  :pick => 1
).id
=> "login-username"

:mode

The :mode option is only used for multi find.

  • For browser.multi_find the default is :mode => :match_one, meaning that in a list of selectors, it will stop looking once it has found one of the multiple selectors.
  • For browser.multi_find_all the default is :mode => :match_any, meaning that in a list of selectors, it will accept any successful selection.
  • Finally there is :match_all, which means that it expects all the selectors to be present on the page, when there is any of the selectors missing, it will throw an error.
browser.goto 'http://training-page.testautomation.info/'

# Adding :match_one to multi_find_all will actually do the exact same thing as multi_find (without all), but then return it as an array
elms = browser.multi_find_all(
  :selectors => [
    {:button => {:id => 'button-not_found'}},
    {:button => {:id => 'button-register'}},
    {:like => :input, :filter_by => :exists?},
    {:like => :a, :filter_by => :exists?}
  ],
  :mode => :match_one
)
=> [#<Watir::Button: located: true; {:id=>"button-register", :tag_name=>"button", :index=>0}>]

# Adding :match_any is the default for multi_find_all
elms = browser.multi_find_all(
  :selectors => [
    {:button => {:id => 'button-not_found'}},
    {:button => {:id => 'button-register'}},
    {:like => :input, :filter_by => :exists?},
    {:like => :a, :filter_by => :exists?}
  ],
  :mode => :match_any # Default for multi_find_all
)
elms.length
=> 18

# Adding :match_all will fail here, because 'button-not_found' doesn't exist
elms = browser.multi_find_all(
  :selectors => [
    {:button => {:id => 'button-register'}},
    {:like => :input, :filter_by => :exists?},
    {:like => :a, :filter_by => :exists?}
  ],
  :mode => :match_all
)
=> [<array>, <with>, <many>, <elements>]

In addition, a helper function is exposed that allows you to pick an element from a collection (array) of elements.

Other Examples

# Find any link. same as browser.a in Watir
browser.find(:a) # Beta, doesn't work in all cases
browser.find({:like => :a}) # xpath


# same as browser.a(:href => /test/) in Watir
browser.find(:a => {:href => /test/})

# Looking for an <a> element with class "loginButton" AND href "[anything]login[anything]" AND name "login"
browser.find(
  :a => {
    :class => "loginButton",
    :href => /login/,
    :name => "login"
  }
)

# Find element where an attribute contains something. 
# Uses XPath instead of the slower regexes:
# browser.a(:href => /account\/login/)
browser.find(:like => {
    :element => :a, 
    :attribute => :href, 
    :include => "account/login"
})

# A shorthand
browser.find(:like => [:a, :href, "account/login"])

# Finding a single class is also possible if you add spaces around it
browser.find(:like => [:a, :class, " login "])

# And also support for XPath text
browser.find(:like => {
    :element => :a, 
    :attribute => :text, 
    :include => "Login"
})

# Finding based on name, id or text
browser.find("login") # Beta, might not always work as expected

# Finding an element within another element
form = browser.find("register_form")
firstname_field = browser.find(
    :input => {:name => 'firstname'},
    :context => form
)