Project

parfait

0.0
No commit activity in last 3 years
No release in over 3 years
Parfait uses layers to simplify the creation and maintenance of your web browser test automation.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Development

 Project Readme

Parfait¶ ↑

What is Parfait?¶ ↑

Parfait uses layers to simplify the creation and maintenance of your web browser test automation.

A typical automated test suite can be broken down into layers:

  • A workflow layer on top, which dictates the user’s path through the application. Think in terms of verbs and user stories: “As a user, when I login, I expect to be taken to my home page.” The workflow layer cares about user actions and results, not about the mechanics of entering data into the browser.

  • A control layer beneath, which defines the specific interactions between the user and the web page. This control layer would be implemented in your favorite browser automation tool, e.g. Selenium, Watir, etc. The control layer cares only about getting data into and out of specific fields on specific pages.

Parfait allows for easy separation of the workflow and control layers by sitting between them. At the control layer, it provides a simple infrastructure for creation and maintenance of web controls. At the workflow layer, it provides a set of basic test directives, allowing for easier manipulation of data without having to worry about the details of the underlying web page.

How does Parfait work?¶ ↑

At the control layer, Parfait allows you to define four basic objects: Applications, Pages, Regions, and Controls.

Applications¶ ↑

An Application is the top layer of the Parfait object hierarchy. It is simply a collection of Pages.

Pages¶ ↑

A Page is a collection of Controls and Regions that are presented on the same screen. This could be an actual page, or a panel on the screen that is used throughout the application.

Regions¶ ↑

A Region is a specific section on the screen. Parfait does not require Regions to be defined, but they can be helpful when there are multiple similar controls on the screen, like in a list.

A Region can contain Controls or other Regions.

Controls¶ ↑

A Control is a single interaction point on a web page. There can be data controls like links, text fields, and pulldown menus, as well as non-data controls like links and buttons. All controls have a set of directives associated with them to enable easier implementation of your test automation.

Data Controls¶ ↑

Each data control is built around the two primary directives get and set:

  • get - reads and returns the current value from the Control

  • set - uses the Control to set a new value.

Once these primary directives are defined, Parfait puts them together to build the following directives for free:

  • retrieve - Reads and returns the current value from the Control. Does not log anything. (This is the user-callable version of get.)

  • update - Uses the Control to set a new value. Logs the value of the Control both before and after the change for auditing purposes.

  • confirm - Tests the value of the Control, returning true if the input matches the value of the Control and false otherwise. Does not log anything.

  • verify = Test the value of the Control, raising an exception if the input does not match the value of the Control. Returns true otherwise. Logs successes for auditing purposes.

Non-Data Controls¶ ↑

Each non-data control is built upon a single goto directive:

  • goto - selects or clicks the control to navigate elsewhere in the application

From this directive, a secondary navigate directive is built:

  • navigate - Uses the Control to navigate. Logs its action. (This is the user-callable version of goto.)

Additional Directives¶ ↑

Additionally, Application, Page, Region, and Control objects can each have a check directive defined:

  • check - returns true if the Parfait artifact is present on the current screen and false otherwise

In the spirit of Parfait, this builds a secondary present directive:

  • present - returns true if the Parfait artifact is present on the current screen and false otherwise.

While manually invokable, the present directive for a control will also be called under the covers each time that control is used to confirm that the artifact you are acting on is still there.

How do I install Parfait?¶ ↑

Install the Parfait gem from the command line as follows:

$ gem install parfait

How do I get started with Parfait?¶ ↑

The code examples used below are all taken from the Parfait repository in the examples/example1 directory. In this directory, we will be automating control of the index.html page.

Remember, Parfait breaks the code into two layers, so we’ll define those layers in different files. The workflow layer will be defined in demo.rb, but let’s start by using Parfait to define the control layer in page.rb.

Define a Page¶ ↑

Our first step is to define a page. We’ll name it “Sample Page” to match the name of the page we’re controlling:

sample_page = Parfait::Page.new(:name => "Sample Page")

Next we should add the page to our application:

sample_page.add_to_application("Sample App")

Since our application has not yet been defined, this call will actually create the Parfait::Application object under the covers. This may seem a bit backward, but it allows a whole bunch of Parfait Page definitions to be pre-loaded in our test suite without having to worry about passing around an Application object. We’ll be able to get an instance of this object later on in the workflow layer.

We can also add a check to confirm that we are on our new page:

sample_page.add_check {
  Parfait::browser.h1(:text => "Parfait Example Page").present?
}

Every time we reference the page object, this condition will be tested to ensure that we are where we think we are before continuing.

Add some Controls¶ ↑

Our sample web page starts with a set of radio buttons. A data control like this can be defined in three steps.

First, we create a Control object on our Page:

party = Parfait::Control.new(
  :name => "Party", 
  :logtext => "political party"
  :parent => sample_page)

Then we define get and set directives for the control. For the get, no parameters are necessary and the code passed in should return the value of the control:

party.add_get {
  retval = nil
  retval = Parfait::browser.div(:id => "radio-example").radio(:value => "Federalist").set? ? "Federalist" : retval
  retval = Parfait::browser.div(:id => "radio-example").radio(:value => "Republicrat").set? ? "Republicrat" : retval
  retval = Parfait::browser.div(:id => "radio-example").radio(:value => "Know-Nothing").set? ? "Know-Nothing" : retval
  retval
}

For the set directive, a single value is taken as input, followed by an optional hash. In most cases, we can ignore the hash.

party.add_set { |input|
  Parfait::browser.div(:id => "radio-example").radio(:value => input).set
}

Now that the get and set directives are defined for this control, Parfait will use them to build update, retrieve, confirm, and verify directives for you. We’ll use those in the workflow layer in a bit.

We also have a “Set My Party” button to add as a non-data Control. Let’s add that to our Page definition as well. The difference with the non-data Control is that the only directive we set here is the goto method:

set_my_party = Parfait::Control.new(
  :name => "Set My Party", 
  :logtext => "Set My Party button"
  :parent => sample_page)

set_my_party.add_goto {
  Parfait::browser.button(:value => "Set My Party").click
}

With a goto directive defined, Parfait will build the navigate directive from that code, allowing navigation with better logging. Again, we’ll see that when we code the workflow layer.

Working with Regions¶ ↑

When automating web pages, we often have to deal with repeated elements that are similar, or even identical. Parfait allows the definition of Regions to focus on a specific entry in that list and then locate the common control from there. Consider the list of presidents presented in index.html/.

We will define a Region to isolate information for a single president. This wil allow us to easily get at the fields for that president with subsequent control definitions for that region. First we can define the Region and add it to the Page.

president_region = Parfait::Region.new(
  :name => "President", 
  :parent => sample_page)

The key piece in defining the region is the filter, which reduces the scope Watir is looking at by starting with Parfait::browser, applying the filter and then calling Parfait::filter_browser to store the reduced scope.

To filter based on an input, the add_filter method should take a single parameter - if more are necessary, then a subfilter should be used instead. To filter without an input, the add_filter method can be written with no parameters.

# Define a filter so that this region will allow focus on a single entry
president_region.add_filter { |president_name|
  table_rows = Parfait::browser.div(:id => "presidents").trs
  table_rows.each do |tr|
    if tr.text =~ /#{president_name}/
      Parfait::filter_browser tr
      break
    end
  end
}

With this filter defined, we can access the controls under each president much more easily. Let’s start with the nickname control:

nickname = Parfait::Control.new(
  :name => "Nickname", 
  :logtext => "president nickname", 
  :parent => president_region)

nickname.add_get {
  Parfait::browser.text_field.value
}

nickname.add_set { |input|
  Parfait::browser.text_field.set input
}

Note that the get and the set rely on inheriting the filtered browser from the “President” region. That means the Watir code can just assume that it’s looking at a single table row. Since this is the only text field within that row, no further specification is necessary than Parfait::browser.text_field to access that control.

Similarly, the “Update Nickname” control can be defined:

update_nickname = Parfait::Control.new(
  :name => "Set My Party", 
  :logtext => "Set My Party button", 
  :parent => president_region)

update_nickname.add_goto {
  Parfait::browser.button(:value => "Update").click
}

Finally, we can define the remaining two controls as well:

biography = Parfait::Control.new(
  :name => "Biography", 
  :logtext => "Biography link", 
  :parent => president_region)

biography.add_goto {
  Parfait::browser.link(:text => "Biography").click
}

cabinet = Parfait::Control.new(
  :name => "Cabinet", 
  :logtext => "Cabinet link", 
  :parent => president_region)

cabinet.add_goto {
  Parfait::browser.link(:text => "Cabinet").click
}

And that completes the Page definition. Now on to the workflow layer.

Invoke the Workflow¶ ↑

To define the workflow layer, let’s edit demo.rb. First we need to require Watir and Parfait:

require 'watir-webdriver'
require 'parfait'

Next we want to include our page definition.

require './page'

Then we can set up the browser, go to the sample page, and tell Parfait about it. You’ll want to adjust the directory here as necessary.

browser = Watir::Browser.new
browser.goto "file://#{ENV['HOME']}/git/parfait/examples/example1/index.html"

Parfait::set_browser(browser)

Before we start using our directives, we also need to pass a logging routine to Parfait. This routine just tells Parfait how to log a given string. For our demonstration, we’ll just print to stdout:

Parfait::set_logroutine { |logstring|
  puts logstring
}

And finally, let’s get the Application we defined in page.rb saved into an object:

app = Parfait::Application.find("Sample App")

Now we can invoke directives! With all the setup behind us, it’s now pretty straightforward to manipulate the controls on this page. Let’s start by setting our party:

app.page("Sample Page").control("Party").update "Republicrat"

Note that the logging for that call indicates both what the control contained previously as well as what it has been set to.

Also, remember that under the covers, for any call taking action on +page(“Sample Page”)+, Parfait is using the present directive to confirm that the page is still visible before continuing.

Any of the directives will work on this control. If we want to verify that it is set as we requested, we can do that as well:

app.page("Sample Page").control("Party").verify "Republicrat"

We can submit our change using the “Set My Party” button:

app.page("Sample Page").control("Set My Party").navigate

Using the Region we defined, we can also easily manipulate controls within the table. To change the nickname for John Adams, we can just target his table entry like this:

app.page("Sample Page").region("President" => "John Adams").control("Nickname").update "Sammy's Bro"

Note that if the filter for this region were written without need for a parameter, the +region()+ call would take only the name of the Region instead of a Hash.

Similarly, we can verify his nickname:

app.page("Sample Page").region("President" => "John Adams").control("Nickname").verify "Sammy's Bro"

We can also use our Region to navigate to the biography for Thomas Jefferson:

app.page("Sample Page").region("President" => "Thomas Jefferson").control("Biography").navigate

This is a simple example, but hopefully it demonstrates the power and flexibility Parfait gives you in managing controls and using them in the workflow layer of your test automation.