Skip to content
Go to file

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time


Build Status

WatirPump is a Page Object pattern implementation for Watir. Hacker friendly and enterprise ready. Heavily inspired by SitePrism and Watirsome.

To learn WatirPump by example please refer to THIS TUTORIAL

Table of contents

To learn WatirPump by example please refer to THIS TUTORIAL

Key features

DSL to describe pages

class SeachPage < WatirPump::Page
  text_field :query_input, id: 'query'
  button :search_button, id: 'btnG'

Class macro methods (here: text_field, button) act as a proxy to watir element locator methods with same names.

DSL to interact with pages do
  query_input.set 'Watir'

Nestable components

class SubComponent < WatirPump::Component
  # some elements

class LoginBox < WatirPump::Component
  component :sub, SubComponent, -> { root.div(class: 'resetPassword') }
  text_field :username, id: 'user'
  text_field :password, id: 'pass'
  button :login, id: 'login'

class HomePage < WatirPump::Page
  component login_box, LoginBox, -> { root.div(id: 'login_box') }

  def do_login(user, pass)
    login_box.username.set user
    login_box.password.set pass

Element action macros

class LoginPage < WatirPump::Page
  text_field_writer :username, id: 'user'
  text_field_writer :password, id: 'pass'
  button_clicker :login, id: 'login'
end do
  username = 'bob'     # same as element.set 'bob'
  password = '$3crEt'  # same as element.set '$3crEt'
  login                # same as

Helpers for forms

class NewProductPage < WatirPump::Page
  text_field_writer :name, id: 'name'
  text_field_writer :quantity, id: 'qty'
  button_clicker :submit, id: 'add'

class ShowProductPage < WatirPump::Page
  span_reader :name, id: 'name'
  span_reader :quantity, id: 'qty'

RSpec.describe 'product creation' do
  let(:data) { { name: 'Hammer XT-431', quantity: 500 } }

  it 'saves product' do do
    ShowProductPage.use do
      expect(form_data).to eq data

Support for parametrized URLs

class SearchResults < WatirPump::Page
  url '/search/{phrase}'
  divs :results, class: 'result-item'
end 'watir') do
  expect(results.count).to be > 0


Imagine a page that contains three ToDo lists. Or maybe instead of imagining just clone this repo and open sinatra_app/public/todos.html in your browser. This page will serve as an example of how one can model and test pages using WatirPump.

The HTML code representing a single ToDo list can look like this:

<div id="todos_home" role="todo_list">
  <div role="title">Home</div>
  <input role="new_item" /><button role="add">Add</button>
    <li><span role="name">Dishes</span><a role="rm">[rm]</a></li>
    <li><span role="name">Laundry</span><a role="rm">[rm]</a></li>
    <li><span role="name">Vacuum</span><a role="rm">[rm]</a></li>

Step 1: Just Watir elements

For the sake of simplicity let's focus on just one ToDo list for the start.

class ToDosPage < WatirPump::Page
  uri '/todos.html'
  # Watir equivalent: browser.div(role: 'title')
  div :title, role: 'title'
  # similarly:
  text_field :new_item, role: 'new_item'
  button :add, role: 'add'
  lis :items, role: 'name'

RSpec.describe ToDosPage do
  let(:browser) { }
  let(:page) { }
  before(:all) { WatirPump.config.base_url = 'http://localhost:4567' }

  it 'adds an item to the "Home" ToDo list' do
    page.new_item.set 'Ironing'
    new_items = { |li| li.span(role: 'name').text }
    expect(new_items).to include('Ironing')

Step 2: Make it a component

The previous example works fine for a page containing just one ToDo list. Let's encapsulate the elements into a Component, so that it could be reused on multiple pages, or even on one page.

Components can be nested, and grouped into ComponentCollections.

Additionally in this iteration element action macros are introduced. Instead of generating methods that return Watir elements they perform certain actions at once.

class ToDoList < WatirPump::Component
  div_reader :title, role: 'title'
  text_field_writer :new_item, role: 'new_item'
  button_clicker :btn_add, role: 'add'
  components :items, ToDoListItem, :lis

class ToDoListItem < WatirPump::Component
  link_clicker :rm, role: 'rm'
  span_reader :name, role: 'name'

class ToDosPage < WatirPump::Page
  uri '/todos.html'
  # page contains several ToDo lists (an Array)
  components :todo_lists, ToDoList, :divs, role: 'todo_list'

RSpec.describe ToDosPage do
  before(:each) { |example| WatirPump.config.current_example = example }
  before :all do
    WatirPump.configure do |c|
      c.base_url = 'http://localhost:4567'
      c.browser =

  it 'adds an item to the "Home" ToDo list' do
    # another way of opening and accessing page do
      home_todo_list = todo_lists.find { |l| l.title == 'Home' }
      home_todo_list.new_item = 'Ironing'
      new_items =
      expect(new_items).to include('Ironing')

Step 3: Make it more elegant and ready for Ajax

The new concept introduced here is the query class macro.

And now the improved example:

# ToDoListItem stays same as before

class ToDoList < WatirPump::Component
  div_reader :title, role: 'title'
  text_field_writer :new_item, role: 'new_item'
  button_clicker :btn_add, role: 'add'
  # use array of Watir elements internally
  components :item_elements, ToDoListItem, :lis
  # expose shorter method name to return just array of strings
  query :items, -> { }

  def items_alternative
    # another way to return items, class macro query is just nicer

  def add(item)
    cnt_before = item_elements.count
    # mind the self. without it a local variable will be crated
    self.new_item = text
    # assume that the addition is performed over an Ajax call
    Watir::Wait.until { item_elements.count == cnt_before + 1 }

class ToDoListCollection < WatirPump::ComponentCollection
  def [](title)
    find { |l| l.title == title }

class ToDosPage < WatirPump::Page
  uri '/todos.html'
  # Page will declare itself loaded once todo_lists are present
  query :loaded?, -> { todo_lists.present? }
  components :todo_lists, ToDoList, :divs, role: 'todo_list'
  decorate :todo_lists, ToDoListCollection

RSpec.describe ToDosPage do
  # setup omitted for brevity

  it 'adds an item to the "Home" ToDo list' do do
      # possible thanks to decoration of todo_lists in ToDosPage
      home_todo_list = todo_lists['Home']
      expect(home_todo_list.items).to include('Ironing')



Just like with any other gem:


gem install watir_pump

or via Gemfile + bundle install

gem 'watir_pump', '~>0.2'


WatirPump includes ActiveSupport::Configurable - a popular concept known from Rails.

The following settings are required to start:

WatirPump.configure do |c|
  # Self explanatory: Watir::Browser instance
  c.browser =

  # Self explanatory: root URL for the application under test
  c.base_url = 'http://localhost:4567'

  # Flag defining execution context of blocks passed to Page.use and
  # See 'Interacting with pages'
  #   true  - block is evaluated with yield and accepts |page, browser| arguments
  #   false - block is evaluated with instance_exec on Page (default)
  c.call_page_blocks_with_yield = false

To make rspec work with page DSL the following key has to be set:

before(:each) { |example| WatirPump.config.current_example = example }


Page class definition consists of a list of class macros invocations. Most of them are inherited from Component class. Few exceptions are:

  • uri - the URL part that is relative to WatirPump.config.base_url
  • loaded? - predicate returning true if page is ready to be interacted with. Default implementation checks if current browser URL matches the uri

For information about how to declare elements and component for the Page please go to Component section. Internally Page itself is a Component, that holds other components and Watir elements (components are nestable).

URI & loaded?

Let's consider the following configuration for the examples below:

WatirPump.config.base_url = 'https://myapp.local:8080'

URI without parameters

class ContactPage
  uri "/contact"
 # => https://myapp.local:8080/contact

URI with a single parameter

class UserPage
  uri "/users{/username}"
end 'boromir')
 # => https://myapp.local:8080/users/boromir

URI with a query string

class UserPage
  uri "/search{?query*}"
end { phrase: 'watir', offset: 50, limit: 100 })
 # => https://myapp.local:8080/search?phrase=watir&offset=50&limit=100

Customized loaded? condition

class HeavyReactPage
  uri "/spa"
  query :loaded?, -> { root.div(class: 'ajax-fetched-content').visible? }
end do
  # 'This will execute once JS renders the element referenced in loaded? method'
 # => https://myapp.local:8080/spa

See addressable gem for more information about the URL template format.

Interacting with pages

Let's consider the following pages (simplified declaration):

class SearchFormPage < WatirPump::Page
  uri '/search'
  text_field :phrase, id: 'q'
  button :search, id: 'btnG'

  def do_search(query)
    phrase.set query

class SearchResultsPage < WatirPump::Page
  uri '/results'
  divs :results, class: 'result-item'

There are three ways that page objects can be interacted with.

1. DSL like style

Block is evaluated in scope of the Page object. Looks nice (no need to type 'page.') but methods visible in the spec are not visible in the block. The only exception are the RSpec methods.

WatirPump.config.call_page_blocks_with_yield = false # this is default

# this is required to make rspec expectations work inside the block
before(:each) { |example| WatirPump.config.current_example = example } do
  phrase.set 'watir'
SearchResultsPage.use do
  expect(results.cnt).to be > 0

IMPORTANT NOTICE: This won't work:

def search_term
end do
  phrase.set search_term
  # Error: Method search_term is undefined in this scope.

Use rspec's let instead:

let(:search_term) { 'watir' } do
  phrase.set search_term
  # now it works

2. A regular yield

A regular block. page and browser references are passed as parameters to the block

WatirPump.config.call_page_blocks_with_yield = true do |page, _browser|
  page.phrase.set 'watir'
SearchResultsPage.use do |page, browser|
  expect(page.results.cnt).to be > 0
  expect(browser.title) to include 'Results'

So how it works internally?

Internally methods uses one of:

Page.open_yield Page.use_yield
Page.open_dsl   Page.use_dsl

depending on the value of config field call_page_blocks_with_yield. These methods can be called directly if there is a need to mix the approaches.

use vs open { block }
# browser navigates to page's uri before executing the block

MyPage.use { block }
# block is executed once page is loaded. No browser.goto called internally
# use has an alias method called act

3. No magic, the regular Page Object pattern way

page =
page.phrase.set 'watir'
page =
expect(page.results.cnt).to be > 0

# or more elegantly:
search_page =
results_page = search_page.do_search('watir')
expect(results_page.results.cnt).to be > 0


Component is the core concept of WatirPump page object model definition. It provides a set of class macros and regular instance methods that make creation of such model easy.

Instance methods

  • browser - reference to Watir::Browser instance
  • root (alias: node) - reference to Watir::Element: component's 'mounting point' inside the DOM tree. (WARNING: for Pages it refers to browser)
  • parent - reference to parent component (nil for Pages)

Declaring elements and subcomponents with class macros


Declaration of simple HTML/Watir elements is easy. Every instance method of Watir::Container module is exposed to WatirPump::Component as a class macro method.


class MyPage < WatirPump::Page
  link :index, href: /index/
  # equivalent of:
  def index href: /index/
    # more WatirPump like notation would be to use root instead of browser:
    # href: /index/
  # usage:

  button :ok, value: 'OK'
  # equivalent of:
  def ok
    root.button value: 'OK'
  # usage:

  button :action, ->(val) { root.button(value: val) }
  # equivalent of:
  def action(val)
    root.button(value: val)
  # usage: page.action('Confirm').click

Fore more examples see Watir guides.


There are two class macros: component and components that are used to declare a single subcomponent, or a collection.


component :name, ComponentClass, <locator_for_single_node>
components :name, ComponentClass, <locator_for_multiple_nodes>


class LoginBox < WatirPump::Components
  button :login, id: 'btn_login'

class MyPage < WatirPump::Page
  component :login_box, LoginBox, :div, id: 'login_box'
  # usage:

  components :results, SearchResultItem, :divs, class: 'login_box'
  # usage: page.results.count

For other ways of locating elements (using lambdas and parametrized lambdas) see below.


Other macros, like query, region and component actions are documented in the following paragraphs.

Locating elements and subcomponents

There are two ways of defining location of subcomponents within the current component (or page). Both are relative to current component's root. Location used in declaration of a subcomponent (invocation of componenet class macro) will be the root of that subcomponent.

The parent component reference is accessible through parent method.

The Watir way

For complete list of elements supported this way please see Watir::Container.


component <name>, <component_class>, <watir_method_name>, <watir_method_params_optionally>


# component class LoginBox, instance name login_box, located under root.div(id: 'login_box')
component :login_box, LoginBox, :div, id: 'login_box'
# example usage: page.login_box.wait_until_present

# component class ArticleParagraph, instance name paragraph, located under root.p
component :paragraph, ArticleParagraph, :p
# example usage: page.paragraph.visible?


# component class LoginBox, instance name login_box, located under root.div(id: 'login_box')
component :login_box, LoginBox, -> { root.div(id: 'login_box') }

# component class ArticleParagraph, instance name paragraph, located under root.p(id: <passed as an argument>)
component :paragraph, ArticleParagraph, ->(cls) { root.p(id: cls) }
# example usage: page.paragraph('abstract').text
root vs browser

For top level components (pages) both root.div(class: 'asd') and browser.div(class: 'asd') would work the same. This is because root of every Page is browser. For subcomponents however root points to node which is the mounting point of the component in the DOM tree.

Using root as a base for locating elements is recommended as a more robust convention.

Use browser to interact with the browser itself (cookies, navigation, javascript, title, etc.). NOT to navigate DOM.


Let's consider the following Page structure:

class MyPage < WatirPump::Page
  component :login_box, LoginBox, :div, id: 'login_box'

class LoginBox < WatirPump::Component
  component :reset_password, ResetPassword, -> { root.div(class: 'reset-password') }

class ResetPassword < WatirPump::Component
  button :send_link, class: 'send-link'

This is how certain elements/components are located:

page =
 # => browser.body

 # => browser.div(id: 'login_box')

 # => browser.div(id: 'login_box').div(class: 'reset-password')

 # => page.login_box

 # => browser.div(id: 'login_box').div(class: 'reset-password').button(class: 'send-link')

query class macro

It is a shorthand to generate simple methods, usually to query DOM tree with Watir. Examples:

class SamplePage < WatirPump::Page
  spans :items, class: 'search-result'

  # regular methods
  def items_text

  def items_cnt

  def items_with_substring(phrase) { |item| item.include? phrase }

  # query class macro equivalent
  query :items_text, -> { }
  query :items_cnt, -> { items.count }
  query :items_with_substring ->(phrase) { { |item| item.include? phrase } }

  # more examples: watir methods can be chained
  query :nested_watir_element ->  { root.form(id: 'new_item').button(class: 'reset_count') }

As one can see query macro is not specific to Watir, it's just a general purpose shorthand to define methods.

query has two decorated variants:

  • element - raises error if value returned from query is not a Watir::Element
  • elements - raises error if value returned from query is not a Watir::ElementCollection One can use them to declare page objects in watir-drops style.

Element action macros

There are cases where certain page element is used only to perform one action: either click, write into, or read value. In such case it would be more convenient to have a page object method that would perform that action at once, instead of returning the Watir element.

Element actions macros are design to do just that.

Declaration in page class Element action example
span :name, id: 'abc' n =
span_reader :name, id: 'abc' n =
link :goto_contacts, id: 'abc'
link_clicker :goto_contacts, id: 'abc' page.goto_contacts
text_field :email, id: 'abc' ''
text_field_writer :email, id: 'abc' = ''

How it internally works?

Macro span_reader :article_title, id: 'title' creates two public methods:

  • article_title_reader_element which returns Watir element :span, id: 'title'
  • article_title which returns article_title_reader_element.text

WARNING: radios, checkboxes and select lists (dropdowns) are handled slightly differently. See below.

Macros *_clicker and *_writer follow the same convention: additional _(clicker|writer)_element method is created next to the action method.

Full list of tags supported by certain action macros can be found in WatirPump::Constants.

Keep in mind that writers cannot rely on element location using parametrized lambda. field('Employee')="John" just won't work.

In order to create both reader and writer for the same element one can use _accessor macro.

radio_group, checkbox_group, flag, dropdown_list

Radios, checkboxes and selects require special handling because they don't represent a single HTML element, but several of them. For example:

  <label>Yes<input type="radio" name="predicate" value="yes" /></label>
  <label>No<input type="radio" name="predicate" value="no" /></label>
<!-- There are two radio buttons that describe values for one form field `predicate`. -->

There's a handful of macros to describe such fields in our page objects:

class UserFormPage < WatirPump::Page
  # input(name: 'gender') matches a collection of radio elements
  radio_reader :gender, name: 'gender'
  radio_writer :gender, name: 'gender'
  radio_accessor :gender, name: 'gender' # alias: radio_group, combined radio_reader and radio_writer
  # page.gender = 'Female' will click the radio button with a corresponding label (NOT value)
  # page.gender will return 'Female'

  # input(name: 'hobbies[]') matches a collection of checkbox elements
  checkbox_reader :hobbies, name: 'hobbies[]'
  checkbox_writer :hobbies, name: 'hobbies[]'
  checkbox_accessor :hobbies, name: 'hobbies[]' # alias: checkbox_group, combined checkbox_reader and checkbox_writer
  # page.hobbies = 'Yoga' will tick the checkbox with the corresponding label (NOT value)
  # page.hobbies = ['Yoga', 'Music'] sets multiple values
  # page.hobbies will return an array of ticked values

  # input(name: 'confirmed') matches a single checkbox element
  flag_writer :confirmed, name: 'confirmed'
  flag_reader :confirmed, name: 'confirmed'
  flag_accessor :confirmed, name: 'confirmed' # alias: flag, combined flag_writer and flag_reader
  # page.confirmed = true will tick the checkbox
  # page.confirmed will return a boolean with the `checked` status of the element
  # page.confirmed? - same as above

  # select(name: 'ingredients[]') matches a select element
  select_reader :ingredients, name: 'ingredients[]'
  select_writer :ingredients, name: 'ingredients[]'
  select_accessor :ingredients, name: 'ingredients[]' # alias: dropdown_list, combined select_reader and select_writer
  # page.ingredients = 'Salt' will select option with a respective label (NOT value)
  # page.ingredients = ['Salt', 'Oregano'] will select multiple options with respective labels, if select is declared as multiple
  # page.ingredients will return a selected option (single or multiple - depending on 'multiple' attribute of the select element)

Custom readers and writers

Whenever reading or writing value for given form field is more sophisticated than just simple interaction with one HTML element custom_reader and custom_writer come handy. Let's consider that a value for certain field should be an array, and the HTML code that represents it looks like this:

<ul id="hobbies">

There are two ways custom_reader for this field could be created:

# 1. for one-liners passing a lambda to the class macro invocation will suffice
custom_reader :hobbies, -> { root.ul(id: 'hobbies')&.lis&.map(&:text) || [] }

# 2. for more sophisticated cases use class macro to declare that certain instance method should be treated as a reader
custom_reader :hobbies

def hobbies
  # lots of other code if necessary
  root.ul(id: 'hobbies')&.lis&.map(&:text) || [] }

# page.hobbies == ['Gardening', 'Dancing', 'Golf']

Same principles apply for custom_writer. Let's rewrite the default text_field_writer using custom_writer as an example.

# 1. for one-liner use lambda
custom_writer :first_name, ->(val) { root.text_field(name: 'first_name').set(val) }

# 2. for more complex writer logic use a separate method. NOTE the '=' in method name!
custom_writer :first_name

def first_name=(val)
  # do some fancy logic here if necessary
  root.text_field(name: 'first_name').set(val)

Form helpers

fill_form(data) - invokes writer method for every key of the data hash (or struct), with associated value as a parameter. Example:

fill_form(name: 'Bob', surname: 'Williams', age: 34)
# is equivalent of = 'Bob'
self.surname = 'Williams'
self.age = 34

fill_form!(data) - invokes fill_form(data) and additionally submit method if it exists (otherwise it raises an exception).

form_data - returns a hash of values of all elements that have a _reader declared. Example:

class UserFormPage < WatirPump::Page
  span_reader :name, id: 'name'
  span_reader :surname, id: 'surname'
  span_reader :age, id: 'age'
end do
  expect(form_data).to contain_exactly(name: 'Bob', surname: 'Williams', age: 34)

Forwarding to root

There's a few methods that components forward directly to its root:

  • visible?
  • present?
  • stale?
  • wait_until_present
  • wait_while_present
  • wait_until
  • wait_while
  • flash

Thanks to this one can write just comp.present? instead of comp.root.present?.

Customized inspect method

This features is especially useful when using WatirPump with testing frameworks like rspec.

Consider the following example:

class MyComponent < WatirPump::Component
  span_reader :name, id: 'name'
  span_reader :surname, id: 'surname'
  span_reader :age, id: 'age'
  inspect_properties [:name, :surname, :age]

class HomePage < WatirPump::Page
  component :details, MyComponent
end do
  expect(details).to have_attributes(name: 'John', surname: 'Smith')

In case of expectation (here: have_attributes) not matched rspec will display the expected and got values. Without the usage of inspect_properties the default implementation of inspect would be used, and it's not very useful for test debugging since it's too verbose. Use inspect_properties to have your rspec logs easy to work with.

Region aka anonymous component

If certain HTML section appears only on one page (thus there's no point in creating another Component class) it can be declared in-place, as a region (anonymous component), which will just act as a name space in the Page object.

class HomePage < WatirPump::Page
  region :login_box, :div, id: 'login_box' do
    text_field :username, id: 'user'
    text_field :password, id: 'pass'
    button :login, id: 'login'

  def do_login(user, pass)
    login_box.username.set user
    login_box.password.set pass

region class macro accepts the following parameters:

  • name of region
  • root node locator
  • block with group of elements/subcomponents


ComponentCollection is a wrapper for collection of components. For example: a list of search results. See Subcomponents for an example.

Basically it's an array, with few extra methods that return true if any of the collection items return true.

The example methods are:


The complete list lives in WatirPump::Constants::METHODS_FORWARDED_TO_ROOT.


under construction

How it works:

decorate :method_to_decorate, DecoratorClass, AnotherDecoratorClasses

New method_to_decorate is created this way (simplified):

def method_to_decorate

See this example: class ToDoListCollection and invocation of decorate macro.

# decorator class for component/element collections should extend WatirPump::ComponentCollection
decorate :todo_lists, ToDoListCollection, DummyDecoratedCollection

# decorator class for elements should extend WatirPump::DecoratedElement
decorate :btn_add, DummyDecoratedElement


Page Object pattern for Watir. Hacker friendly and enterprise ready.



No packages published
You can’t perform that action at this time.