Types of widgets and their methods.
##Table of Contents
A widget is the fundamental Capybara-UI element. A widget abstracts a DOM element, allowing you to call methods on that element, like checking for text or submitting a form.
A basic widget can be declared with the widget
macro, or as a class.
Macro widget declarations take a CSS or XPath selector as the first argument.
widget :todo_item, '.todo-item'
widget :todo_item, [:xpath, '//some/node']
Class widget declarations define the widget with selector via the root
method.
class TodoItem < Capybara::UI::Widget
root '.todo-item'
end
class TodoItem < Capybara::UI::Widget
root :xpath, '//some/node'
end
Widgets can take a Proc in addition to a CSS selector or XPath expression, allowing us to more precisely define the widget at call time.
class TodoManager < Capybara::UI::Role
widget :todo_item, -> (text) { ['.todo-item', text: text] }
def select_item(description)
click :todo_item, description
end
end
It is enough to call a widget to know if it is on the page. But we can also return a boolean with #visible?
and not_visible?
.
widget(:todo_item, 'Buy Milk') # => returns widget object
visible?(:todo_item, 'Buy Milk') # => true
not_visible?(:todo_item, 'Buy Milk') # => false
widget(:todo_item, 'Write a Novel') # => raises Capybara::UI::MissingWidget error
visible?(:todo_item, 'Write a Novel') # => false
not_visible?(:todo_item, 'Write a Novel') # => true
The widget
method blocks, meaning it pauses Ruby code execution, and checks the page for that element until found or it reaches Capybara's timeout limit. It does not pause JavaScript code execution. This is handy for dynamic UI tests.
roles.myrole.create_todo_item("Buy Milk")
# the item form is submitted to server and on response
# from the server, a new item is appended to the list.
# 'widget' waits until the item appears on the list,
# or Capybara's timeout limit is reached
expect(roles.myrole).to see :todo_item, "Buy Milk"
The root of a widget is the Capybara element itself that Capybara-UI abstracts. When a widget is declared with the widget macro, the root declaration is implicit and equal to the element with the css class in the definition.
class TodoManager < Capybara::UI::Role
widget :todo_item, '.todo-item' do
def delete
root.find('a.delete').click
end
end
end
We can simulate a user interacting with a widget. Note not all methods will be supported by all drivers.
# dsl methods
hover :todo_item
click :todo_item
double_click :todo_item
right_click :todo_item
# widget methods
widget(:todo_item).click
widget(:todo_item).hover
widget(:todo_item).double_click
widget(:todo_item).right_click
# nested widget methods
widget(:todo_item).click :delete_button
widget(:todo_item).hover :delete_button
widget(:todo_item).double_click :delete_button
widget(:todo_item).right_click :delete_button
You can access the id and classes of the widget as well with Capybara-UI methods. Other attributes can be accessed from the Capybara element, via the root
method.
# <a href="/items/1" id="todo_item" class="todo-item right-aligned">
widget(:todo_item).id #=> "todo_form"
widget(:todo_item).classes #=> ["todo-item", "right-aligned"]
widget(:todo_item).class?("todo-item") #=> true
widget(:todo_item).root['href'] #=> "/items/1"
You can also access the HTML of the element
# <a href="/items/1" id="todo_item" class="todo-item right-aligned"></a>
widget(:html).html # => "<a href=\"/items/1\" id=\"todo_item\" class=\"todo-item right-aligned\"></a>\n"
Note: Some drivers like Poltergeist currently do not provide a way to view the HTML.
You can get a list of all the elements that match your selector(s) on the page with the widgets
method.
# note: `widgets` does not wait like `widget` does
widgets(:todo_item)
Widgets can be nested inside other widgets. Definining attributes of the inner widget, such as classes, will be scoped to within the outer widget.
Note: Nested widgets and instance methods like #click will not wait for the element to appear.
widget :todo_item, '.todo-item' do
widget :delete_button, 'a.delete'
end
And can be called from that widget.
widget(:todo_item).click :delete_button
You can define custom methods on a widget object.
class TodoManager < Capybara::UI::Role
# see the Forms section for more information about form widgets
form :new_email, '#new_email' do
text_field :email, ["[id ^= 'email_addresses_']"]
def body=(body)
page.execute_script <<-JS
jQuery('#email_body').data('wysihtml5').setValue(#{body.inspect});
setTimeout(function() { $('#email_body').trigger('change') }, 500);
JS
end
end
def send_email(body)
submit :new_email, body: "My test email body content."
end
end
#Forms Forms inherit all the properties of widgets, and have some of their own.
# with explicit class
widget :todo_form, '.todo-form', Capybara::UI::Form
# with the form macro
form :todo_form, '.todo-form'
Forms can have form-field widgets defined, as well as any regular sub-widgets. Form field widgets by default try to match their second argument with the text of a label, an input name or an input id.
form :form_with_everything, '.form-with-everything' do
# text field
text_field :request, 'request'
# select field
select :state, 'state'
# checkbox field
check_box :receive_email, 'receive_email'
# radio button field group
radio_button :favorite_color, '.favorite-color-parent'
# regular sub-widget
widget :hidden_field, '.hidden-field'
end
Note: Radio buttons share the same name, and so must be treated differently. Define a group of radio buttons via a parent element, using standard css selectors rather than field-specific selectors.
If you'd rather use a CSS selector, you can do that by passing the second argument as an array, with the selector as the array's first element.
text_field :request, ['.request-field-class'],
You can get the current value of form elements by calling their methods, automagically defined by Capybara-UI.
widget(:form_with_everything).state #=> 'CO'
widget(:form_with_everything).request #=> 'Please send me more info'
widget(:form_with_everything).receive_email #=> true
For text fields, check for content by calling the method + question mark.
# <input type="text" value="Please send me more info">
widget(:form_with_everything).request? # => true
# <input type="text" value="">
widget(:form_with_everything).request? # => false
For select fields, the default method returns the selected option's text. Call the <name>_value
method to return the value of the selected option.
# <option value="38" selected>CO</option>
widget(:form_with_everything).state # => CO
widget(:form_with_everything).state_value # => "38"
For radio button field groups, the default method returns the checked button's label text. Call the <name>_value
method to return the value of the checked button.
# <label for="b">Blue</label>
# <input type="radio" id="b" value="#0033CC" checked>
widget(:form_with_everything).favorite_color # => Blue
widget(:form_with_everything).favorite_color_value # => "#0033CC"
Capybara-UI will easily submit a form for you, via the UI, with either of these methods.
params = { request: 'Call me', state: 'CO', receive_email: true, favorite_color: 'blue' }
# the submit macro
submit :form_with_everything, params
# the submit widget method
widget(:form_with_everything).submit_with(params)
You can also submit a form by setting the fields individually.
widget(:form_with_everything).tap do |f|
f.request = 'Call me'
f.state = 'CO'
f.receive_email = true
f.submit
end
If you'd prefer to just set the fields without submitting the form, Capybara-UI can handle that, too, with the set
macro, or the set
method on the form widget.
set :form_with_everything, params
widget(:form_with_everything).set params
#Field Groups A field group is like a form without a submit button. It has all the same functionality as a Form object, minus the submitting function.
widget :todo_form, '.todo-form', Capybara::UI::FieldGroup
A list is a collection of Capybara-UI ListItem objects, and is a good way to map a simple list format with a Capybara-UI element. (For more complicated lists, consider the table element.)
# with explicit class
class TodoList < Capybara::UI::List
root '.todo-list'
end
# with widget macro and explicit class
widget :todo_list, '.todo-list', Capybara::UI::List
# with the list macro
list :todo_list, '.todo-list'
A List has access to its items, defined with a CSS selector.
list :todo_list, '.todo-list' do
item 'li'
end
widget(:todo_list).items
List items are just a form of widget, and can have methods defined on them.
list :todo_list, '.todo-list' do
item 'li' do
def downcased
text.downcase
end
end
end
Inside the list, you can access all of its items.
list :todo_list, '.todo-list' do
item 'li'
def view_items_as_rows
# list has Enumerable!
items.map { |item| item.to_row }
end
end
If you only care about the list items themselves, it might be better to define the items as widgets.
# now when you go to find your item,
# Capybara::UI will wait for that item to appear
widget :todo_list_item, '.todo-list-item'
Table widgets map tables, or table-like DOM structures that have a header and data rows.
Default Table widgets define the root as a table
element, headers as thead > tr
, and data rows as tbody > tr
. Both headers and rows have a default column of td
.
Custom tables allow you to map a Capybara-UI Table element over a table-like structure.
# with explicit class
class TodoTable < Capybara::UI::Table
end
# with widget macro and explicit class
widget :todo_table, Capybara::UI::Table
# with explicit class
class TodoTable < Capybara::UI::Table
root '.todo-table'
end
# with widget macro and explicit class
widget :todo_table, '.todo-table', Capybara::UI::Table
Tables allow you to define the header row and data rows by class.
Given this HTML...
<ul class="todo-table">
<li class="header-row">
<span>Header Col 1</span>
<span>Header Col 2</span>
<span>Header Col 3</span>
</li>
<li class="data-row">
<span>Val 1.1</span>
<span>Val 1.2</span>
<span>Val 1.3</span>
</li>
<li class="data-row">
<span>Val 2.1</span>
<span>Val 2.2</span>
<span>Val 2.3</span>
</li>
</ul>
... You can define the headers, data_rows and their columns with CSS selectors.
widget :todo_table, '.todo-table', Capybara::UI::Table do
header_row '.header-row' do
column 'span'
end
data_row '.data-row' do
column 'span'
end
end
In both default and custom tables, you can get the values in a row, or in a column.
widget(:list_table).rows[0] #=> ['Val 1.1', 'Val 1.2']
widget(:list_table).columns[2] #=> ['Val 1.3', 'Val 2.3']
widget(:list_table).columns['Header Col 2'] #=> ['Val 1.2', 'Val 2.2']