View abstraction for integration testing
View abstraction for integration testing
To create a basic Domino class, inherit from Domino and
define a selector and attributes:
module Dom
class Post < Domino
selector '#posts .post'
attribute :title # selector defaults to .title
attribute :author_name # selector defaults to .author-name
attribute :body, '.post-body' # example of selector override
# pass a block if you want to convert the value
attribute :comments do |text|
text.to_i
end
attribute :posted_at do |text|
Date.parse(text)
end
# Combination selector is a boolean that combines with the root node. Boolean.
# You can find a post that also has the .active class.
attribute :active, "&.active"
# Combination selector for a data attribute. Finds the value of the data attribute
# on the root node. You can convert it with a callback like any other attribute.
attribute :rating, "&[data-rating]", &:to_i
# Combination selector for a data attribute with no value set. Must convert to
# boolean in the callback.
attribute :blah, "&[data-blah]" do |a|
!a.nil?
end
end
end
Now in your integration test you can use some of Domino’s methods:
assert_equal 4, Dom::Post.count
refute_nil Dom::Post.find_by_title('First Post')
# Find by attribute yields the node when using a block.
assert_equal Dom::Post.find_by_title { |node| node.text == "First Post" && node.tag_name == 'p' }
# Multiple attributes, returns first match if any
refute_nil Dom::Post.find_by(title: 'First Post', author: 'Jane Doe')
# Multiple attributes with exception if no match is found
refute_nil Dom::Post.find_by!(title: 'First Post', author: 'Jane Doe')
# Multiple attributes, returns all matches if any
assert_equal ["12/06/2014", "12/01/2014"], Dom::Post.where(author: 'Jane Doe').map(&:posted_on)
What makes it really powerful is defining scoped actions:
module Dom
class Post < Domino
def delete
within(id) { click_button 'Delete' }
end
end
end
refute_nil Dom::Post.find_by_title('First Post')
Dom::Post.find_by_title('First Post').delete
assert_nil Dom::Post.find_by_title('First Post')
Domino makes it easy to model your forms for testing with Domino::Form
.
To create a basic form, simply inherit from Domino::Form
and define a
selector, a key (optional), and a set of fields.
module Dom
class PersonForm < Domino::Form
selector 'form.person'
# For forms with names like `person[age]`, no need to define the
# locator on each field. Define a key to automatically generate
# locators based on the field name.
key 'person'
# Define a custom selector to click to submit the form
submit_with "input[type='submit']" # this is the default
# locate field by label
field :first_name, 'First Name'
# locate field by automatically generated name (uses key, person[last_name])
field :last_name
# locate field by fully qualified name
field :biography, 'person[bio]'
# locate select field by label, acts as select
# callback mapper operates on selected option nodes
field :favorite_color, 'Favorite Color', as: :select, &:text
# automatically handles select[multiple]
# callback mapper operates on selected option nodes: &:value by default
field :allergies, as: :select
# locate by id, convert value via callback
field :age, 'person_age', &:to_i
# use a custom field type for unusual or composite fields
field :vehicles, '.input.vehicles', as: CheckBoxesField
# locate a field with a name that doesn't use the key
field :is_human, 'is_human', as: :boolean
# still supports attributes for non-input nodes
attribute :action, "&[action]"
attribute :submit_method, "&[method]"
end
end
In the above example, you can define a field to get a reader and writer
method for the field. A form will also provide a mass-assignment writer
and a save method to submit the form.
person = Dom::PersonForm.find!
person.age #=> 25
person.vehicles #=> ["Car", "Bike"]
person.is_human #=> true
person.favorite_color #=> Blue
person.age = 35
person.age #=> 35
person.set(vehicles: ["Car", "Van"], first_name: "Jessica", last_name: "Jones")
person.attributes #=> { first_name: "Jessica", last_name: "Jones", biography: "", favorite_color: "Blue", age: 35, vehicles: ["Car", "Van"], is_human: true }
Domino::Form
provides basic field types for text inputs and textareas,
single-selects, and boolean fields. You can create custom field types
for more complex form inputs by subclassing Domino::Form::Field
and
overriding the read
and write
methods. For example, if you have a
collection of check boxes, this might suit your needs:
class CheckBoxesField < Domino::Form::Field
def read(node)
node.find(locator).all('input[type=checkbox]').select(&:checked?).map(&:value)
end
def write(node, value)
value = Array(value)
node.find(locator).all('input[type=checkbox]').each do |box|
box.set(value.include?(box.value))
end
end
end
Provide your custom class using the :as
option when defining your field,
as shown in the example above.
The named accessor method for any field or attribute will yield the Capybara
node of the attribute if you pass a block. You can use this to check
certain properties of the node without having to break out of your Dominos.
Example: Checking available options in a select field
person = Dom::PersonForm.find!
expected_options = ["- Select a Color -", "Red", "Blue", "Green"]
assert_equal expected_options, person.favorite_color { |n| n.all('option').map(&:text) }
Example: Checking the tag of the node containing the attribute value
person = Dom::Person.find_by!(uuid: "e94bb2d3-71d2-4efb-abd4-ebc0cb58d19f")
assert_equal "h2", person.name { |n| n.tag_name }
Domino uses capybara internally to search html for nodes and
attributes. If you need to do something special, you can have direct
access to the capybara node.
module Dom
class Account < Domino
selector "#accounts li"
# Returns this node text
def text
node.text
end
end
end
For more information about using Capybara nodes, check Capybara Documentation.
When working with Capybara drivers that support JavaScript, it may be
necessary to wait for elements to appear. Note that the following code
simply collects all Account
dominos currently on the page and
returns the first:
Dom::Account.first # returns nil if account is displayed asynchronously
When you are waiting for a unique domino to appear, you can instead
use the find!
method:
Dom::Account.find! # waits for matching element to appear
If no matching element appears, Capybara will raise an error telling
you about the expected selector. Depending on the
Capybara.match
option,
this will also raise an error if the selector matches multiple nodes.
Add a features/support/dominos.rb file, in which you define your dominos.
Use them in your steps.
Include “domino” in your Gemfile if using bundler, or simply
require 'domino'
If you’re not using Bundler.
Now, define your Dominos anywhere you want. The easiest place to start is
in your test_helper.rb (doesn’t have to be inside a Rails test class).
Check out Domino Example for an
example of using Test::Unit and Cucumber with Domino.
Copyright © 2011 Nick Gauthier, released under the MIT license