Skip to content
This repository has been archived by the owner on Mar 21, 2023. It is now read-only.

gamera-team/gamera

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

68 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gamera

Build Status Dependency Status Ebert

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.

Note: We're a very young project and currently subject to frequent changes as we sort everything out. Any help is welcome!

Table of Contents

Features

  • a lightweight way of implementing the PageObject pattern on top of Capybara
  • a framework for abstracting web pages into Ruby classes
  • rainbows, puppies and kittens for every user (not included)

What is the Page Object Pattern?

The brilliant Martin Fowler describes the PageObject pattern in detail. Here's is a list of the essential features of the pattern.

  • A PageObject creates an application-level API for a web page or section of a web page. For example, create_user_account instead of fill in this field, click on this button
  • A PageObject allows a software client to do or see anything that a human can.
  • A PageObject provides an easily programmable interface that hides details of the specific HTML controls and elements.
  • Changes to the underlying web page shouldn't alter the PageObject interface (as long as the web page still supports the same business processes)
  • The result of navigating from a PageObject (e.g. clicking on a link or submitting a form), should be another PageObject.

Requirements

Gamera requires Ruby 2.1 or later.

Setup

Gamera is cryptographically signed. To be sure the gem you install hasn’t been tampered with:

Add our public key (if you haven’t already) as a trusted certificate

gem cert --add <(curl -Ls https://raw.github.com/gamera-team/gamera/master/certs/glena-b.pem)

To install with gem, run

gem install gamera -P HighSecurity

The HighSecurity trust profile will verify signed gems and dependencies. Gamera's dependencies should also be signed,

To install with Bundler, add this line to your Gemfile:

gem 'gamera'

and then run

bundle [install] --trust-policy [HighSecurity|MediumSecurity]

You may need to use MediumSecurity if other gems in your Gemfile have unsigned dependencies.

Using gamera

gamera has two primary classes:

  1. Page is a base class which you can subclass to create specific page objects or you can create subclasses that capture additional common behavior (for example, flash messages in a Rails app, common header or footer menus and so forth) and extend those to create specific page objects.
  2. Builder is a base class for capturing business process methods that require multiple page objects (for example, adding a new user to a new group, which might require creating a new user, creating a group and adding the user with each step occurring on different pages in the web app). In practice, Builder subclasses are also used to create or alter data in the system.

Example: Use gamera's Page class to create a user registration page object

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.

Example: Extend gamera's Page class to create a RailsPage class

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.

Using Gamera's modules directly instead of the Page class

If you are not able to or do not want to inherit directly from Page, you can include Gamera's Visitable module directly where needed. Visitable includes methods to include on pages that are visitable, i.e. have a URL to visit and are then displayed. You may also want to add in the PathJoiner class in a manner similar to how it is used in Gamera::Page.

Example: Creating a Builder subclass to capture a multipage business 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

Using gamera with Pry

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 gamera//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

Example: Page Redirection via Pry

pry> redirect_page.visit(fail_on_redirect: false) # => should redirect to home page; "false" tells it not to validate that the page landed on is the page requested
pry> redirect_page.displayed? # => false
pry> home_page.displayed? # => true

Example: Page Content from a PageObject via Pry

pry> hit_counter_page.visit
pry> hit_counter_page.text =~ /You have visited this page 1 times/ # => match!

Contributing

See this great guide to contributing to Open Source projects from GitHub