A framework for implementing the page object pattern and automating web page interactions
gamera lets you control and interact with web pages directly from your Ruby code. Essentially, you can wrap any web page with a Ruby API.
The brilliant Martin Fowler describes the PageObject pattern in detail. Here's is a list of the essential features of the pattern.
create_user_account
instead of fill in this
field, click on this button
gamera requires Ruby 1.9.3 or later. To install, add this line to your
Gemfile
and run bundle install
:
gem 'gamera'
If you're not using Bundler, you can install with
gem install 'gamera'
gamera has two primary classes:
Builder
subclasses are also used to create or alter data in the
system.Given a registration page that looks like
<html>
<body>
<h2>Register!</h2>
<form action="#">
<label for='first_name'>First Name</label> <input type='text' id='first_name'>
<label for='last_name'>Last Name</label> <input type='text' id='last_name'>
<label for='email'>Email</label> <input type='text' id='email'>
<label for='password'>Email</label> <input type='text' id='password'>
<input type='button' id='save_button' name='Save' value='Save'>
</form>
</detail>
</body>
</html>
create a corresponding page object class
require 'gamera'
class RegistrationPage < Gamera::Page
def initialize
@url = 'http://example.com/registration'
@url_matcher = %r{/registration}
end
def register_user(first_name:, last_name:, email_address:, password:)
first_name_field.set(first_name)
last_name_field.set(last_name)
email_field.set(email)
password_field.set(password)
save
end
private
def first_name_field
find_field('First Name') # Capybara finder
end
def last_name_field
find_field('Last Name') # Capybara finder
end
def email_address_field
find_field('Email') # Capybara finder
end
def password_field
find_field('Password') # Capybara finder
end
def save
find_button('Save').click # Capybara finder
end
end
You could also simplify this by using Gamera::PageSection::Form
require 'gamera'
class RegistrationPage < Gamera::Page
def initialize
@url = 'http://example.com/registration/new'
@url_matcher = %r{/registration/new}
form_fields = {
first_name: 'First Name',
last_name: 'Last Name',
email: 'Email',
password: 'Password'
}
@registration_form = Gamera::PageSections::Form.new(form_fields)
def_delegators :registration_form, *registration_form.field_method_names
end
def register_user(first_name:, last_name:, email_address:, password:)
first_name_field.set(first_name)
last_name_field.set(last_name)
email_field.set(email)
password_field.set(password)
save
end
private
def save
find_button('Save').click # Capybara finder
end
end
In either case, you can then call
rp = RegistrationPage.new
rp.visit
rp.register_user(first_name: 'Laurence',
last_name: 'Peltier',
email_address: 'lpeltier@example.com',
password: 'so_secret')
in your code to register a new user through your web app's registration page.
For a given web app, you may find that you want to capture other common elements
in your page objects, such as, for example, flash messages in a Rails app or a
navigational node that's common to the entire site. One approach to this is to
subclass Page
, add the common elements and then use the new subclass as the
parent for the actual page object classes.
For a Rails app, a new RailsPage
class might look something like
class RailsPage < Gamera::Page
def flash_error
flash_error_div.text
end
def flash_message
flash_notice_div.text
end
def has_flash_message?(message)
has_css?(flash_notice_css, text: message)
end
def has_flash_error?(error)
has_css?(flash_error_css, text: error)
end
def has_no_flash_error?
has_no_css?(flash_error_css)
end
def has_no_flash_message?
has_no_css?(flash_notice_css)
end
def has_submission_problems?
has_flash_error?('There were problems with your submission')
end
def fail_if_submission_problems
fail(SubmissionProblemsError, flash_error.text) if has_submission_problems?
end
private
def flash_error_css
'div.flash.error'
end
def flash_notice_css
'div.flash.notice'
end
def flash_error_div
find(flash_error_css)
end
def flash_notice_div
find(flash_notice_css)
end
end
This could then be used as the parent class for the RegistrationPage in the previous example, adding the ability to check the flash message when the user is registered.
process
For this example, let's assume we're automating a task management site that
lets a manager assign task to members of her team and that we've already created
page objects for some of the pages: NewTaskPage
,
UserLoginPage
, AssignTaskPage
. Then we might create a AssignedTaskBuilder
like so,
require 'gamera'
require 'page_objects'
class AssignedTaskBuilder < Gamera::Builder.with_options(
:admin_email, :task_name, :task_due_date, :assignee_email
)
def build
user_login_page = UserLoginPage.new
new_task_page = NewTaskPage.new
assign_task_page = AssignTaskPage.new
user_login_page.visit
user_login_page.login_as(admin_email)
new_task_page.visit
new_task_page.create_task(task_name, task_due_date)
assign_task_page.visit
assign_task_page.assign(task_name: task_name, to: assignee)
end
# Give back a builder with default values set (say for easy test data setup)
def assigned_task_builder
AssignedTaskBuilder.new(
admin_email: 'ann_admin@example.com',
task_name: 'That thing you do'
task_due_date: Time.now + 24.hours
assignee: 'tessa_lation@example.com')
end
end
Notice that an instance of the class won't actually do anything until the
build
method is called. This lets us to defer the build until the data or
process neeeds to happen. The builder as data factory model allows us to reuse
the builder, change the defaults or create a new builder instance with
different defaults.
require 'assigned_task_builder`
include AssignedTaskBuilder
assigned_task_builder # => builder with the default options
assigned_task_builder.build # => actually assign the default task
another_task_builder = assigned_task_builder.refine_with(task_name: 'That other
thing you do') # => a new builder with a different task name
another_task_builder.build # => assign the new task
We've created some toy web apps in Sinatra and some simple page objects on top of them to test gamera. You can play with some of the spec pages and apps in pry, using the following
cd ~/workspace/talos/lib
pry -r ./pry_setup.rb
This will add convenience methods that can be used in pry
Start the single page SimpleForm web app from pry with pry> simple_form
. Use
this:
pry> simple_form_page.visit
pry> simple_form_page.fill_in_form(:text => 'Entered Text', :selection => 'C')
pry> simple_form_page.submit
to fill in the form on the app and submit it.
To see page object examples which handles page redirection or return page content, start the SimpleSite web app with
pry> simple_site
pry> redirect_page.visit # => should redirect to home page
pry> redirect_page.displayed? # => false
pry> home_page.displayed? # => true
pry> hit_counter_page.visit
pry> hit_counter_page.text =~ /You have visited this page 1 times/ # => match!
See this great guide to contributing to Open Source projects from GitHub