Table of Contents
1. Introduction
1.1 Watir gems
Watir consists of three gems (or projects):
- Watir - This gem is just a loader. Based on the browser that you want to run and your operating system, the Watir gem will load either Watir-Classic or Watir-Webdriver.
- Watir-Classic - This is the original Watir gem that drives Internet Explorer.
- Watir-Webdriver - This gem allows the driving of additional browsers - eg Chrome and Firefox. It is an API wrapper around the Selenium-Webdriver gem (ie translates the Watir commands to Selenium-Webdriver commands).
For most users, the structure of the framework will have no significance or impact. Scripts only need to require Watir; making the use of Watir-Classic versus Watir-Webdriver transparent.
However, there are occasions where understanding the gem relationship is crucial:
- API differences - The Watir-Classic and Watir-Webdriver projects strive to be API compatible though a common specification called Watirspec. Unfortunately, due to historical reasons, implementation details, etc., there are still some API differences. The code found in this book can be assumed to work in both projects unless otherwise specified.
- Monkey patching - When adding functionality to Watir, the code may need to consider which browser/gem is being used.
- Getting help - Specifying the gem being used helps ensure that solutions are applicable.
1.2 Building a script
Say we want to explain to someone how to do a Google search for Watir. We might tell them to:
- Open a browser
- Enter the url to go the Google search page
- Input ‘watir’ into the search field
- Click the search button
- Wait for the search results to load
- Click the link for the main Watir page
If you look carefully, you will notice that each step consists of two parts:
- Target - Something to interact with (eg browser, element)
- Action - Something to do with the target (eg click, type, inspect)
We can make these two parts more clear in a table:
Step | Target | Action |
---|---|---|
1 | browser | open |
2 | browser | goto url |
3 | search field | type ‘watir’ |
4 | search button | click |
5 | result link | wait for it to load |
6 | result link | click |
Watir is designed to mimic a user’s actions, which means that these same directions can be used when creating an automated script. The only complication is the language barrier. Watir understands Ruby, not English. In essence, building a Watir script is translation exercise.
A translation of the previous table into Watir code would be:
Step | Target | Action |
---|---|---|
1 | browser | Watir::Browser.new |
2 | browser | goto(‘http://www.google.com’) |
3 | text_field(:name ⇒ ‘q’) | set(‘watir’) |
4 | button(:name ⇒ ‘btnG’) | click |
5 | link(:text ⇒ /Watir.com/) | wait_until_present |
6 | link(:text ⇒ /Watir.com/) | click |
To make an executable script, the target and action need to be combined back into sentences (or lines of code):
browser
=
Watir
::
Browser
.
new
browser
.
goto
(
'http://www.google.com'
)
browser
.
text_field
(
:name
=>
'q'
)
.
set
(
'watir'
)
browser
.
button
(
:name
=>
'btnG'
)
.
click
browser
.
link
(
:text
=>
/Watir\.com/
)
.
wait_until_present
browser
.
link
(
:text
=>
/Watir\.com/
)
.
click
The following chapters will discuss how to translate the targets and actions.
2. Locating an Element - Basics
A locator describes how to find an element amongst all of the other elements on the page. Think of it as giving directions to your house. The uniqueness of the house, who you are talking to, the complexity of your area, etc. affect how detailed the directions need to be.
Say our city has 2 streets and 4 houses with families.
It is unambiguous to say that you want to go the “Wilson Family” house. There is exactly one house that matches that criteria.
What if you say you want to go to the “Smith Family” house? Now it is ambiguous as there are 2 houses that meet that criteria. We can remove the ambiguity by doing one of the following:
- Providing additional details about the house. For example, the house number is 1.
- Providing details about where to look for the house. For example, the house is on Main Street.
- Providing details about what is in the house. For example, the house where Ronald and Alice live.
- Providing details about what is around the house. For example, the house is beside the Wilson’s house.
The same strategies can be used to help Watir find the exact element you want to interact with.
For example, the city’s information could be presented as a web page.
<div
class=
"city"
id=
"Kitchener"
>
<div
class=
"street"
id=
"MainStreet"
>
<div
class=
"house"
data-name=
"SmithFamily"
data-number=
"1"
>
<span
class=
"resident"
>
Ronald</span>
<span
class=
"resident"
>
Alice</span>
</div>
<div
class=
"house"
data-name=
"WilsonFamily"
data-number=
"2"
>
<span
class=
"resident"
>
Henry</span>
<span
class=
"resident"
>
Mary</span>
</div>
</div>
<div
class=
"street"
id=
"CedarStreet"
>
<div
class=
"house"
data-name=
"ChanFamily"
data-number=
"1"
>
<span
class=
"resident"
>
Blake</span>
<span
class=
"resident"
>
Terra</span>
</div>
<div
class=
"house"
data-name=
"SmithFamily"
data-number=
"2"
>
<span
class=
"resident"
>
George</span>
<span
class=
"resident"
>
Coraline</span>
</div>
</div>
</div>
Again, assume that we want to find the “Smith Family” house. To tell Watir to find the div element with the data-name attribute value of “SmithFamily” is ambiguous (ie there are multiple). We can use the same four approaches to remove the ambiguity.
- Providing additional details about the house. For example, the desired div element has a data-number attribute value of 1.
- Providing details about where to look for the house. For example, the desired div element is within the div element that has the id of MainStreet.
- Providing details about what is in the house. For example, the desired div element that includes the spans with text Ronald and Alice.
- Providing details about what is around the house. For example, the desired div element is beside the div element with data-name attribute of “WilsonFamily”.
These four strategies can be conceptually generalized based on the relationship of the element we want to find and the structure of elements. The strategies are to use the properties of:
- The Element - This is the specific element that you want to find.
- Ancestors - These are the elements that contain the desired element.
- Decedents - These are the elements contained within the desired element.
- Siblings - These are elements that share the same parent as the desired element.
The following image illustrates the relationship of the elements.
2.1 Concepts
An element’s properties, such as attributes, text, label, etc., can be used to be specific about which element to find.
Single vs multiple properties
An element can be located using 0 properties, which returns the first element.
browser
.
div
To locate an element with specific properties, create a hash of the properties. A hash looks like:
:property1
=>
'value1'
,
:property2
=>
'value2'
Where:
- The keys of the hash are the properties to test or the how to match the element.
- The values of the hash are the expected property values or the what to match.
The hash can be used to match a single property. For example, the following says to find a div element that has the id “my_id”.
browser
.
div
(
:id
=>
'my_id'
)
The hash can be expanded to include any number of properties. The following locates a div element that has the class “enabled”, the text “Navigate” and name attribute containing the word “navigate”.
browser
.
div
(
:class
=>
'enabled'
,
:text
=>
'Navigate'
,
:name
=>
/navigate/
)
Exact vs partial match
When looking for matching elements, Watir can check if the property is an exact match or a partial match. This is controlled by the value in the locator.
- A String object will perform an exact match.
- A Regular Expression (Regexp) object will perform a partial match.
For example, the following locator is using a String (denoted by the quotation marks) for the value of the :id property. This means that it will only match elements whose id attribute is exactly “my_id”. It will not match elements whose id is “before_my_id” or “my_id_after”.
browser
.
div
(
:id
=>
'my_id'
)
If you want to match elements whose id attribute contains “my_id” anywhere (ie a partial match), you can use a Regexp (denoted by the forward slashes). The following locator will match elements that have the id “my_id”, as well as “before_my_id” or “my_id_after”.
browser
.
div
(
:id
=>
/my_id/
)
2.2 Element type
When locating an element, Watir needs to be told:
- What type of element to find and
- How many matches to return.
This is achieved by calling the corresponding element type method.
Element vs collection
Watir can find and return an:
- Element - The first (single) matching element or
- Element Collection - All matching elements.
There are two element type methods, for each supported element, that correlate to the return type:
- Singular - This version returns the element.
- Plual - This version returns the element collection.
For example, the div method, which is singular, will tell Watir to find the first div element on the page.
browser
.
div
The pluralization of div is divs, which will return all div elements on the page.
browser
.
divs
Note that the pluralized version is generally the singular version with an ‘s’ added to the end. However, following English conventions, there will be some element types that have ‘es’ added to the end or have the ending ‘y’ replaced by ‘ies’.
Scenario | HTML Element | Singular | Plural |
---|---|---|---|
Add ‘s’ | <span> | span | spans |
Add ‘es’ | <progress> | progress | progresses |
Add ‘ies’ | <summary> | summary | summaries |
Standard html element methods
In general, the singular element type method is the same as the html element tag.
For example, the html ul element:
<ul
id=
"my_id"
>
<li>
Item 1</li>
<li>
Item 2</li>
</ul>
Would be retrieved by Watir’s ul method.
browser
.
ul
The following table lists the methods to locate some common element types.
Element Method | Collection Method | HTML Elements Matched |
---|---|---|
a | as | <a> |
div | divs | <div> |
h1 | h1s | <h1> |
li | lis | <li> |
span | spans | <span> |
table | tables | <table> |
Convenience methods
Watir defines additional element type methods that:
- Are more specific. For example, <input type=”checkbox”> can be located using browser.checkbox instead of using browser.input (which would include non-checkbox inputs).
- Makes visually similar elements equivalent. For example, html has a variety of buttons - <button>, <input type=”button”>, etc. They are all equivalent in Watir as each is located with the same browser.button method.
- Provide more readable names through aliases. For example, <a> elements can be located via browser.a or browser.link.
The following table lists the convenience element type methods.
Element Method | Collection Method | HTML Elements Matched |
---|---|---|
button | buttons | <button> |
<input type=”button”> | ||
<input type=”image”> | ||
<input type=”reset”> | ||
<input type=”submit”> | ||
checkbox | checkboxes | <input type=”checkbox”> |
element | elements | All |
file_field | file_fields | <input type=”file”> |
frame | frames | <frame> |
<iframe> | ||
hidden | hiddens | <input type=”hidden”> |
image | images | <img> |
link | links | <a> |
radio | radios | <input type=”radio”> |
select_list | select_lists | <select> |
text_field | text_fields | <input type=”password”> |
<input type=”text”> | ||
<textarea> |
Custom elements
Some applications use element types that are not in the specifications.
<li
class=
"lastMove"
>
<div
id=
"81ae2"
class=
"folder"
>
<small
onclick=
"someFunction1()"
>
</small>
</div>
</li>
The small element type is not defined in the html specifications. As a result, Watir does not define a small method – ie you cannot do browser.small.click.
To locate these elements, you can use the genaric element method with a tag_name locator.
browser
.
element
(
:tag_name
=>
'small'
)
2.3 Attributes
Attributes provide additional infomation (meaning and context) about the element. They appear in the opening tag of an element in the form name=”value”.
In the following element, there are 3 attributes - id, name and class. They have the values “divs_id”, “divs_name” and “divs_class” respectively.
<
div
id
=
"divs_id"
name
=
"divs_name"
class
=
"divs_class"
>
text
<
/div>
Watir can locate an element by its attributes by setting attribute name as the key and the attribute value as the value.
browser
.
div
(
:attribute_name
=>
"attribute_value"
)
Standard attributes
The html specifications only allow certain attributes to be included on an element. In watir-webdriver, any of these attributes can be used to locate the element. In watir-classic, only the more common attributes are supported (the other attributes must be treated like non-standard attributes).
As an example, consider the id attribute used in the following html. The uniqueness of this attribute makes it an ideal unambiguous locator.
<div
id=
"phone_number"
>
<span
id=
"area_code"
>
123</span>
<span
id=
"exchange"
>
456</span>
<span
id=
"subscriber"
>
7890</span>
</div>
An element can be located by an exact id match:
browser
.
span
(
:id
=>
"exchange"
)
.
text
#=> "456"
Or by a partial id match:
browser
.
span
(
:id
=>
/area/
)
.
text
#=> "123"
Class
If something on the page looks different (ex the text size, the background colour, etc), it means that a style has been applied to the element. Often, to reduce maintenance of the page, developers will extract these styles into a style sheet that defines groups of styles. These groups of styles are called classes. An element can use one or more classes by defining the class attribute.
While the class is a standard html attribute, there is special handling by Watir. Consider the following html:
<span
class=
"number"
>
123</span>
<span
class=
"bold number"
>
456</span>
<span
class=
"bold number"
>
7890</span>
The first span element has a single class called “number”. The other two spans have two classes - “bold” and “number”. Note that in the class attribute value, multiple classes are separated by a space.
When locating an element by its class, watir will try matching each individual class rather than the entire attribute value. For example, locating an element with class “number”, will return elements with a class attribute value “number” as well as “multiple number classes”.
Locating an element based on an exact class match would look like:
browser
.
span
(
:class
=>
'number'
)
.
text
#=> "123"
A regular expression can be used to partially match a class name:
browser
.
span
(
:class
=>
/bo/
)
.
text
#=> "456"
Data attributes
In some cases, the standard html attributes do not provide enough information for an application. Custom data attributes allow additional information to be added to the element, while still being valid html markup.
For example, a div element that represents a person might use custom data attributes to store the person’s height and weight.
<div
data-height=
"30cm"
data-weight=
"9kg"
>
Owen</div>
<div
data-height=
"20cm"
data-weight=
"7kg"
>
Izzy</div>
The custom data attributes will have the form “data-customname”, where “customname” can be any text.
These custom attributes can be used the same as the standard locators. However, for the attribute name, the dash (-) must be replaced by an underscore (_).
Locate an element based on an exact match of the attribute value:
browser
.
div
(
:data_height
=>
'20cm'
)
.
text
#=> "Izzy"
Locate an element based on a partial match of the attribute value:
browser
.
div
(
:data_weight
=>
/9/
)
.
text
#=> "Owen"
Custom attributes
Some applications will still be using attributes that are not in the html specifications (likely due to the application being developed before data attributes were introduced). These attributes are not directly supported by watir as locators. In the case of watir-classic, this also includes some of the defined but less common attributes.
For these attributes, you can use a css or xpath selector.
<div>
<span
customattribute=
"custom1"
>
Custom Attribute 1</span>
<span
customattribute=
"custom2"
>
Custom Attribute 2</span>
<span
customattribute=
"custom3"
>
Custom Attribute 3</span>
</div>
In css-locators, the square brackets are used for matching attributes.
To locate a span that has the customattribute attribute, of any value:
browser
.
span
(
:css
,
'span[customattribute]'
)
.
text
#=> "Custom Attribute 1"
To locate the span with a specific customattribute value:
browser
.
span
(
:css
,
'span[customattribute="custom2"]'
)
.
text
#=> "Custom Attribute 2"
Xpath is similar to css-locators, however the attribute name must be prefixed with an at symbol (@):
browser
.
span
(
:xpath
,
'//span[@customattribute="custom2"]'
)
.
text
#=> "Custom Attribute 2"
2.4 Text
The :text locator allows elements to be located by their text.
<div>
This is the text of the element.</div>
The element can located by an exact text match:
browser
.
div
(
:text
=>
'This is the text of the element.'
)
As well as by a partial text match:
browser
.
div
(
:text
=>
/text of the element/
)
Note that an element’s text is considered all text nodes of the element as well as its descendents.
<span
id=
"container"
>
This line has <span
id=
"inner"
>
inner</span>
text</span>
For this element, the span’s text is “This line has inner text”. It is not just the element’s direct child text nodes - “This line has text”.
browser
.
span
(
:text
=>
'This line has inner text'
)
.
exists?
#=> true
This is important to remember when locating an element by its text using a regular expression. If locating a span that contains the text “inner”, the outer span with id “container” will be returned rather than the inner span.
browser
.
span
(
:text
=>
/inner/
)
.
id
#=> "container"
2.5 Label
Elements can also be located by their associated label element’s text. Note that this only works in Watir-Webdriver.
In the following html, the input field has an associated label with the text “First Name:”.
<label
for=
"first_name"
>
First Name:</label>
<input
type=
"text"
id=
"first_name"
name=
"first_name"
/>
The input field can be located by the exact label text:
browser
.
text_field
(
:label
=>
'First Name:'
)
Or by part of the label text:
browser
.
text_field
(
:label
=>
/Name/
)
2.6 Index
When locating a single element, Watir will, by default, return the first element matching the criteria.
For example, given the html:
<div>
<a
href=
"http://www.watir.com"
>
Watir</a>
<a
href=
"http://watirwebdriver.com"
>
Watir-Webdriver</a>
</div>
The first matching element is returned when no :index is specified or when the :index value is 0.
browser
.
link
.
text
#=> "Watir"
browser
.
link
(
:index
=>
0
)
.
text
#=> "Watir"
The second matching element is found using an :index with value 1. Notice that Watir is using a 0-based index - ie 0 is the first element, 1 is the second element, etc.
browser
.
link
(
:index
=>
1
)
.
text
#=> "Watir-Webdriver"
A couple of items note:
- The value can be an integer or a string.
- The :index locator only applies to locating a single element. An exception will occur when using it to locate an element collection.
browser
.
divs
(
:text
=>
/Watir/
,
:index
=>
1
)
.
length
#=> Watir::Exception::MissinWayOfFindingObjectException
2.7 CSS
The :css locator allows a css-selector to be used to find an element.
browser
.
div
(
:css
=>
'div#my_id'
)
For more details on creating a css-selector, see the css-selector specifications.
2.8 Xpath
The :xpath locator allows an element to be located based on its path.
browser
.
div
(
:xpath
=>
'//div[@id="my_id"]'
)
For more details on determining an element’s xpath, see the xpath specifications.
3. Locating an Element - Relationships
3.1 Based on its ancestors
Any element that contains the target element is considered an “ancestor”. Ancestor elements can be used to reduce the scope of where Watir searches for an element. The element type method will only look for the element within the html of the calling object.
- When the calling object is the browser, the entire page will be searched.
- When the calling object is an element, only the html of that element will be searched.
As an example, say a page lists a project’s team members by their role.
<span>
Project Managers:</span>
<ul
id=
"project_managers"
>
<li>
Alicia Mcbride</li>
<li>
Jake Woods</li>
</ul>
<span>
Developers:</span>
<ul
id=
"developers"
>
<li>
Jack Greer</li>
<li>
Cora Williamson</li>
<li>
Albert Gross</li>
</ul>
<span>
Testers:</span>
<ul
id=
"testers"
>
<li>
Owen Francis</li>
<li>
Luis Leonard</li>
</ul>
Consider a test that needs to retrieve the name of one of the testers. It is not possible to locate the tester li elements directly as there are no properties to differentiate them from the other roles. However, the ul element does have an identifying attribute - the id specifies the role being listed. In other words, the test needs to get the first li element within the ul element with id of “testers”. This is done by chaining element type methods that identify the unique ancestors.
browser
.
ul
(
:id
=>
'testers'
)
.
li
.
text
#=> "Owen Francis"
In this code, Watir will look for an ul element with id “tester” anywhere within the browser. Then, only within the ul element found, Watir will look for the first li element.
3.2 Based on its decendants
There are several approaches that can be used to locate an element based on its decendants, which are the elements within the target element.
For example, consider locating the li elements in the following html. The li elements do not have any distinguishing attributes. However, they do have child link elements with unique attributes - the data-componentcode.
<ul>
<li
id=
"ctl00_EnabledComponentListItem"
>
<a
id=
"ctl00_Component"
data-componentcode=
"ItemA"
href=
"#"
>
Item A</a>
<a
href=
"#"
>
X</a>
</li>
<li
id=
"ctl01_EnabledComponentListItem"
>
<a
id=
"ctl01_Component"
data-componentcode=
"ItemB"
href=
"X"
>
Item B</a>
<a
href=
"#"
>
X</a>
</li>
<li
id=
"ctl02_EnabledComponentListItem"
>
<a
id=
"ctl02_Component"
data-componentcode=
"ItemC"
href=
"#"
>
Item C</a>
<a
href=
"#"
>
X</a>
</li>
</ul>
Using find
One approach is to iterate through the li elements until one is found with specified link.
browser
.
lis
(
:id
,
/EnabledComponentListItem/
)
.
find
do
|
li
|
li
.
a
(
:data_componentcode
,
'ItemC'
)
.
exists?
end
Navigate from descendant to parent
If the html structure is stable, you can locate the descendant link (using standard locators) and then traverse the DOM up to the parent (ie li element).
browser
.
a
(
:data_componentcode
,
'ItemC'
)
.
parent
Using xpath
Using xpath, it is possible to get the element directly.
browser
.
li
(
:xpath
,
"//li[.//a[@data-componentcode='ItemC'']]"
)
3.3 Based on its siblings
Locating an element by its siblings is often seen when working with tables. For example, in the below table, you might need to find the colour of a specific creature. A user knows which colour belongs to the creature because they are in the same row. In terms of the html, the colour td element is a sibling of the creature td element since they share the same parent element (tr element).
Table:
Creature | Colour |
---|---|
Elf | Blue |
Dragon | Green |
Imp | Black |
HTML:
<table>
<tbody>
<tr>
<th>
Creature</th>
<th>
Colour</th>
</tr>
<tr>
<td>
Elf</td>
<td>
Blue</td>
</tr>
<tr>
<td>
Dragon</td>
<td>
Green</td>
</tr>
<tr>
<td>
Imp</td>
<td>
Black</td>
</tr>
</tbody>
</table>
Sibling to parent to element
To find an element based on its sibling, the general strategy is:
- Locate the unique sibling element.
- Get the parent element using the parent method.
- Locate the required element within the scope of the parent.
Applying this strategy to the table, the colour of the dragon can be obtained by:
#Get the unique element
unique_element
=
browser
.
table
.
td
(
:text
=>
'Dragon'
)
#Get the parent element
parent_element
=
unique_element
.
parent
#Get the actual element
parent_element
.
td
(
:index
=>
1
)
.
text
#=> "Green"
Parent by descendent to element
When the unique element is a cousin (ie a descendent of the sibling element), it is easier to locate the parent based on its descendents. The reason being that it is less fragile - ie ensuring that the correct number of “parent” methods are called becomes more difficult.
parent_row
=
browser
.
table
.
trs
.
find
do
|
tr
|
tr
.
td
(
:text
=>
'Dragon'
)
.
present?
end
parent_row
.
td
(
:index
=>
1
)
.
text
#=> "Green"
3.4 DOM Traversal
Most often elements are located by traversing down the DOM. However, there are also methods for when travelling up or horizontally is required.
Parent
The DOM can be traversed upwards to an element’s parent.
In the html:
<div
class=
"parent"
>
<span
class=
"child"
>
Target</span>
</div>
The parent element of the span tag can be retrieved by using the parent method:
child
=
browser
.
span
(
:class
=>
'child'
)
parent
=
child
.
parent
parent
.
tag_name
#=> "div"
Previous/next sibling
The DOM can also be traversed horizontally between sibling elements. For example, the following page has 4 sibling elements in the body - 2 h1 and 2 ul elements.
<html>
<body>
<h1>
Heading 1</h1>
<ul>
<li>
Item 1a</li>
</ul>
<h1>
Heading 2</h1>
<ul>
<li>
Item 2a</li>
<li>
Item 2b</li>
</ul>
</body>
</html>
Watir-Webdriver
Xpath has a following-sibling axis that can be used to find the next sibling of a given Watir element. This example shows that the next sibling of “Heading 2” is the unordered list with 2 items.
h1
=
browser
.
h1
(
:text
=>
'Heading 2'
)
ul
=
h1
.
element
(
:xpath
=>
'./following-sibling::*'
)
puts
ul
.
lis
.
length
#=> 2
The preceding-sibling axis can find previous sibling of the Watir element. The [1] is added to get the adjacent sibling. Otherwise, you would get the first sibling in the tree, which is the “Heading 1” h1 element.
h1
=
browser
.
h1
(
:text
=>
'Heading 2'
)
ul
=
h1
.
element
(
:xpath
=>
'./preceding-sibling::*[1]'
)
puts
ul
.
lis
.
length
#=> 1
Note that the element type returned must match what you are looking for. In the above examples, we looked for any element type. You can replace the * and use a specific watir element to look for a specific type. For example, to get the preceding h1:
start_h1
=
browser
.
h1
(
:text
=>
'Heading 2'
)
end_h1
=
start_h1
.
h1
(
:xpath
=>
'./preceding-sibling::h1[1]'
)
puts
end_h1
.
text
#=> "Heading 1"
Watir-Classic
For some reason, the same xpath solution produces a UnknownObjectException in Watir-Classic. As an alternative, you can use the nextSibling property of the OLE object.
h1
=
browser
.
h1
(
:text
=>
'Heading 2'
)
ul
=
browser
.
ul
(
:ole_object
=>
h1
.
document
.
nextSibling
)
puts
ul
.
lis
.
length
#=> 2
Similarly, you can use the previousSibling property to get the preceding element.
h1
=
browser
.
h1
(
:text
=>
'Heading 2'
)
ul
=
browser
.
ul
(
:ole_object
=>
h1
.
document
.
previousSibling
)
puts
ul
.
lis
.
length
#=> 1
4. Inspecting an Element
4.1 Text
The text method returns the visible text of an element.
Given the html:
<div>
This is the text of the element.</div>
The text of the div element is:
browser
.
div
.
text
#=> "This is the text of the element."
Note that the text of an element includes:
- The text nodes of the element and
- The text nodes of the element’s descendants.
The following span has its own text node, “ is a Ruby gem.”, as well as a descendant text node, “Watir”.
<span><a
href=
"watir.com"
>
Watir</a>
is a Ruby gem.</span>
The text of the span is the concatenation of the text nodes:
browser
.
span
.
text
#=> "Watir is a Ruby gem."
Also note that Watir will only include the text that is visible to the user. In the following div, the first span is visible to the user, while the second span is not.
<div>
<span>
visible text</span>
<span
style=
"display:none;"
>
hidden text</span>
</div>
Therefore, text of the div will only be the first span:
browser
.
div
.
text
#=> "visible text"
4.2 Attribute value
Attributes provide additional information or behaviour for an element. They can be found within the element’s start tag.
Watir provides 2 ways to get attribute values:
- Using the method that returns the specific attribute’s value.
- Using the attribute_value method.
Standard attributes
For attributes that are defined in the HTML specification, there is a corresponding method that returns the attribute value. In most cases, the attribute name and the method name are exactly the same. However, Ruby naming conventions are applied in some situations.
Scenario | Naming Rule | Example Attribute | Corresponding Method |
---|---|---|---|
Single word attribute | Match attribute name | id | id |
Multi-word attribute | Words separated by underscore | maxlength | max_length |
Boolean attribute | End with question mark | disabled | disabled? |
Data attribute | Dashes replaced by underscores | data-field | data_field |
Aria attribute | Dashes replaced by underscores | aria-describedby | aria_describedby |
Class attribute | Due to Ruby objects already having a class method, which returns the instance’s class, the “class” attribute is a special case. | class | class_name |
The following HTML has several attributes:
<div
data-field=
"first_name"
>
<label
id=
"tp1-label"
for=
"first"
>
First Name:</label>
<input
type=
"text"
id=
"first"
maxlength=
"50"
required=
"required"
aria-labelledby=
"tp1-label"
aria-describedby=
"tp1"
>
<div
id=
"tp1"
class=
"tooltip"
role=
"tooltip"
hidden=
"hidden"
aria-hidden=
"true"
>
Your first name is a required</div>
</div>
The attribute methods can be used to get the values:
# Retrieve a single word attribute
browser
.
text_field
.
id
#=> "first"
# Retrieve a multi-word attribute
browser
.
text_field
.
max_length
#=> 50
# Retrieve a boolean attribute
browser
.
text_field
.
required?
#=> true
browser
.
text_field
.
disabled?
#=> false
# Retrieve a data attribute
browser
.
div
.
data_field
#=> "first_name"
# Retrieve an aria attribute
browser
.
input
.
aria_labelledby
#=> "tp1-label"
# Retrieve a class attribute
browser
.
div
(
id
:
'tp1'
)
.
class_name
#=> "tooltip"
Note that the object type returned by the method will depend on the attribute:
- Boolean attributes will return a TrueClass or FalseClass object.
- Numeric attributes will return a Fixnum object.
- The rest of the attributes will return a String object.
Custom attributes
There will be some attributes that do not have an associated method:
- Attributes that are not defined in the HTML specification.
- Standard attribute methods that are not yet implemented in Watir-Classic.
In these cases, the attribute_value method is required.
Given an element with a custom attribute:
<div
myCustomAttribute=
"custom"
id=
"div_id"
>
text</div>
The value of the custom attribute can be obtained by passing the name of the attribute to the attribute_value method:
browser
.
div
.
attribute_value
(
'myCustomAttribute'
)
#=> "custom"
The method can also be useful when dynamically retrieving attribute values. For example, the following collects the attribute values specified in an array.
attrs
=
[
'id'
,
'myCustomAttribute'
]
values
=
attrs
.
map
{
|
attr
|
browser
.
div
.
attribute_value
(
attr
)
}
#=> ["div_id", "custom"]
4.3 Computed style
These days elements are rarely styled with just inline styles. For example, a, inline style can be applied to an error message to make the text red:
<div
style=
"color:red;"
>
Error</div>
Styles are now often moved into classes so that they can be consistently applied across different elements and pages. For example, using a class, the element might become:
<div
class=
"error_message"
>
Error</div>
To check that the error message text is red, it is not sufficient to just check the style attribute. Instead, you need to check the computed style - ie the actual style applied by the browser.
For Watir-Webdriver, this can be checked by using the Element#style method and specifying a style property:
browser
.
div
.
style
(
'color'
)
#=> "rgba(255, 0, 0, 1)"
Note that shorthand CSS properties (e.g. background, font, etc.) are not supported. The longhand properties should be used instead - eg background-color.
In Watir-Classic, the Element#style method will only return the value of the inline styles. The computed style can be retrieved by:
browser
.
div
.
document
.
currentStyle
.
color
#=> "red"
Note that depending on the browser, the formatting of the property may vary.
4.4 Existence and visibility
Watir has three methods to check whether an elements exists on the page:
- exists? – Returns true if the element exists (in the DOM).
- visible? - Returns true if the element is visible to the user.
- present? – Returns true if the element exists and is visible to the user.
These methods are called similar to:
browser
.
div
.
exists?
browser
.
div
.
visible?
browser
.
div
.
present?
Consider the following page, which has one div tag that is visible (ie displayed to the user) and one that is not.
<body>
<div
id=
"1"
style=
"block:display;"
>
This text is visible</div>
<div
id=
"2"
style=
"block:none;"
>
This text is not visible</div>
</body>
When each method (exists?, present?, visible?) is run for the different elements (displayed, not displayed, non-existent), the following results are seen:
Element | .exists? | .visible? | .present? |
---|---|---|---|
Displayed (browser.div(:id ⇒ ‘1’)) | true | true | true |
Not Displayed (browser.div(:id ⇒ ‘2’)) | true | false | false |
Non-Existent (browser.div(:id ⇒ ‘3’)) | false | exception | false |
Element#exists? checks if the element is in the DOM (or HTML). Element#visible? checks if the element can be seen by the user, but throws an exception if the element is not in the DOM. Element#present? is the same as Element#visible? except that it returns false, instead of an exception, when the element is not in the DOM.
While the three methods are definitely different, I think, at least from my experience, that you should typically be using Element#present?.
One point to keep in mind, especially for those working with legacy watir libraries/tests, is that .exists? can lead to false positives. Imagine you have a form. When the form is submitted, an error message is displayed for fields that fail validation (ex missing a required field). In some applications this will be implemented by simply changing the style of the element containing the error message from display:none to display:block. Now consider writing a test to check that the validation message is displayed. If you use .exists?, the test will have false positives (ie it will always pass since the element is always in the DOM). In this situation, .visible? or present? should have been used.
5. Forms
5.1 Text Field
Input
To simulate a user typing into a text field, use the set method:
browser
.
text_field
.
set
(
"first"
)
p
browser
.
text_field
.
value
#=> "first"
Note that the set method will overwrite the previous text:
browser
.
text_field
.
set
(
"first"
)
browser
.
text_field
.
set
(
"second"
)
p
browser
.
text_field
.
value
#=> "second"
If you want to add additional text, use the append method instead:
browser
.
text_field
.
set
(
"first"
)
browser
.
text_field
.
append
(
"second"
)
p
browser
.
text_field
.
value
#=> "firstsecond"
To make the text field blank, use the clear method:
browser
.
text_field
.
clear
p
browser
.
text_field
.
value
#=> ""
Inspect
The text that a user sees in a text field is stored in the input’s value attribute. Therefore, to get the text of the text field, use the standard attribute methods:
browser
.
text_field
.
set
(
'some text'
)
p
browser
.
text_field
.
value
#=> "some text"
Textarea
While textareas can be located differently than text fields, they are set and inspected using the same methods. What makes textareas special on web pages is that they can have multi-line text. A real user would create multiple lines by pressing the Enter key on the keyboard. With Watir, you can do the exact same keystrokes:
browser
.
textarea
.
set
(
'first line'
,
:return
,
'second line'
)
puts
browser
.
textarea
.
value
#=> first line
#=> second line
However, it is often more convenient to send a double-quoted String with the line break character (“\n”):
browser
.
textarea
.
set
(
"first line
\n
second line"
)
puts
browser
.
textarea
.
value
#=> first line
#=> second line
5.2 Checkbox
Input
A checkbox is checked by using the set method:
browser
.
checkbox
.
set
It can be unchecked by using the clear method:
browser
.
checkbox
.
clear
The set method can take a boolean parameter, where true will check the box and false will uncheck the box:
# Check
browser
.
checkbox
.
set
(
true
)
# Uncheck
browser
.
checkbox
.
set
(
false
)
This is useful when the checkbox needs to be set based on a condition. For example, an if-else statement:
if
condition_met?
browser
.
checkbox
.
set
else
browser
.
checkbox
.
clear
end
Can be re-written as:
browser
.
checkbox
.
set
(
condition_met?
)
Inspect
The state of the checkbox, whether it is checked or unchecked, can be determined by the set? method:
browser
.
checkbox
.
set
p
browser
.
checkbox
.
set?
#=> true
browser
.
checkbox
.
clear
p
browser
.
checkbox
.
set?
#=> false
5.3 Radio
Input
A radio button is selected by using the set method:
browser
.
radio
.
set
Inspect
The set? method is used to determine if a radio button is turned on:
p
browser
.
radio
.
set?
#=> false
browser
.
radio
.
set
p
browser
.
radio
.
set?
#=> true
6. Element Collections
6.1 Counting
Given an element collection, you can check how many elements were found by using the size method.
<html>
<body>
<div
class=
"title"
>
Title</div>
<div
class=
"content"
>
Content A</div>
<div
class=
"content"
>
Content B</div>
</body>
</html>
There are 3 div elements on the page.
browser
.
divs
.
size
#=> 3
With 2 of the div elements having the class “content”.
browser
.
divs
(
:class
=>
'content'
)
.
size
#=> 2
Depending on your preferences, you can also use the method length instead of size.
browser
.
divs
.
length
#=> 3
7. Troubleshooting
7.1 UnknownObjectException - Unable to Locate Element
Have you tried to interact with an element, only to get an exception like:
Watir
::
Exception
::
UnknownObjectException
:
Unable
to
locate
element
,
using
{
:tag_name
=>[
"div"
]
}
from
.
.
/
lib
/
watir
-
classic
/
element
.
rb
:
66
:in
`assert_exists'
from ../lib/watir-classic/element.rb:125:in `
text
'
from (irb):5
from C:/Ruby193/bin/irb:12:in `<main>'
Watir is saying that it cannot find the element. Yet when you manually go and look at the page, you can see the element! Why can’t Watir locate it?
There are a variety of possible reasons, which have been summarized in the table below.
Problem | Solution |
---|---|
Element is in a frame | You must explicitly tell Watir that the |
element is within a frame. | |
browser.frame.div.text |
|
Element has not finished | Add an explicit wait. |
loading | |
browser.div.when_present.text |
|
Element locator uses | Use a regex to only match the stable |
dynamic attribute values | part of the attribute. |
The following locator includes random | |
values (bad): | |
browser.div(:id, 'static_rand5').text |
|
Change the locator to a regex and just | |
match the static part: | |
browser.div(:id, /static/).text |
|
Element is in a popup | You must explicitly tell Watir to use |
window | the popup window. |
b.window(:title => /window title/) do |
|
b.div(:id => 'div_in_popup').text |
|
end |
|
Element type is incorrect | Verify that you are trying to find the |
right element type. | |
For example, what looks like a button | |
might actually be a link. | |
Results in an UnknownObjectException: | |
browser.button.click |
|
This will work: | |
browser.link.click |