Guest Post: Designing a Ruby-based Sauce Test Automation Framework Pt. 2

March 12th, 2014 by Bill McGee

Designing a Ruby-based Sauce Test Automation Framework – Implementing it on Ruby using Page Object Pattern 

In the first part of this blog series, we defined Page Object Pattern and also familiarized ourselves with the various building blocks of the Ruby-based test automation framework.

Now that, we have realized the benefits of using such a framework, let us try implementing a Simple Test Framework on Ruby using Page Object Pattern.

Implementing a Simple Test Framework on Ruby using Page Object Pattern

In the following section, we will implement a simple Selenium test framework using Page object pattern. Our framework contains Base classes for Test, Action, Page and Page element objects.

Next, we will write a few very simple test cases for:

  1. Verifying the page title on a web page
  2. b.     Test a form by filling the data elements in the form and testing a successful submit.

 

Test Base Class

First, let’s do a walkthrough of the Test Base class.

  1. Include the Selenium-webdriver Rubygem which is used to connect the testing framework with Selenium web driver.
  2. The start_browser() function invokes the Firefox browser with required options such as OS type, browser version, etc.

Please Note: For testing purposes we have set the default browser as Firefox on Linux, but we can make this dynamic by passing the corresponding parameters.

  1. This class also has other utility functions such as
    1. get_data() – to make Database connections and retrieve data
    2. launch_url() – to launch a new URL on the browser
    3. switch_to_frame() – to switch between active frames / windows.
require 'rubygems'
require 'selenium-webdriver'

class SeleniumTestBase

  def initialize (webDriver)
    @driver = webDriver
  end

  #this function starts the selenium webDriver on Firefox browser type
  #we can make it dynamic by using the params

  def start_browser(browser_type,platform,version)

    caps = Selenium::WebDriver::Remote::Capabilities.firefox
    caps.version = "5"
    caps.platform = :LINUX
    caps[:name] = "Testing Selenium 2 with Ruby on SauceLabs Environment"

    @driver = Selenium::WebDriver.for(
      :remote,
      :url => "<Use SauceLabs URL with appropriate key>",
      :desired_capabilities => caps
    )
  end

  def switch_to_window(handle)
    @driver.switch_to.window handle
  end

  def switch_to_frame (handle)
    @driver.switch.frame handle
  end

  def launch_url ( url)
    @driver.navigate.to url
  end

  def get_data(conn_string,sql1 )
    #some sql statements
  end

  def refresh
    @driver.get @driver.url
  end

  def teardown
    @driver.quit
  end
end

Please note one of the advantages of writing a separate Test Base class is that it uses the underlying driver implementation from the Test cases. Therefore, if we want to experiment with a new Driver version or even a completely new Driver, then the TestBase is the only class that will change leaving other parts of the framework untouched.

In a later section, we will show how one can create a project specific TestBase class which extends the main TestBase class. This will allow individual project teams to make project specific changes to the base class.

Action Base Class

Actions Base classes are responsible for the following:

  1. Executing steps required to run the test
  2. Inserting / Updating data elements and changing the state of the application while executing the test
  3. Reverting the changes made during the tests (for eg., rolling back the changes made on the database)
  4. Page operations such as navigating to a page, refreshing, switching across different frames and windows, etc.
class SeleniumActionBase

  def initialize (webDriver)
    @driver =  webDriver
  end

  def get_data(conn_string,sql1 )
    #connect to db
    #execute sql to retrieve data
  end

  def start_browser(browser_type)
    #override
  end

  def switch_to_window(handle)
    #override
  end

  def switch_to_frame (handle)\
    #override
  end

  def launch_url (url)
    #override
  end

  def refresh
    #override
  end
end

Page Base Class

The Page Class refers to actual web pages on the application that we want to test and essentially consists of a group of Page Elements. This class also defines a variable called BrowserIndex. Different browsers implement the locator start indexes differently and this variable can be used to handle the different implementations based on the browser type.

class SeleniumPageBase

  attr_reader :browserIndex

  def initialize ( webDriver)
    @driver = webDriver
    @browserIndex  = 1
  end

end

Page Element Base Class

The Page Element class is responsible for the following:

  1. Handle the actual interactions between the tests and the web pages.
  2. Wait for a particular page element to load (especially in case of Ajax)
  3. Provide specific error messages to describe why a particular page interaction failed.

 

class SeleniumPageElement

  def initialize(webDriver)
    @driver = webDriver
  end

  def waiting_get(seconds = 5, errorMessage = "")
    wait = Selenium::WebDriver::Wait.new(:timeout => seconds)
    wait.until { @myDriver.find_element(:id, @myBy) }
  end

  def click(seconds = 5, errorMessage = "")
    waiting_get(seconds, errorMessage).click();
  end

  def load_element(locator,val,errorMessage ='')
    @myBy = locator
    @element = find_element_id
    @element.send_keys val
  end

  def submit
    @element.submit
  end

  def get_response
    @driver.find_element(:id, 'your_comments')
  end

  protected

  def find_element_id
    begin
      return  @driver.find_element(:id, @myBy)
    rescue  Exception=>e
      raise e
    end
  end
end

 

(In our next blog in this series, we would see how we can build a test project using this framework)

References:

http://sauceio.com/index.php/2011/11/selenium-testing-framework-pt-2-base-classes/

This guest blog post was written by Vinodh Balaji Sridharan and Ganesh Kaliannan, Neev Technologies

Remote file uploads with Selenium & Capybara

December 15th, 2013 by Dylan

Capybara & Selenium(This post is a little code journey. If you just want to know how to enable remote uploads in Capybara, skip to the end)

Usually a file upload is for a file on the same computer as the browser you’re uploading with:

User on System 1: Upload C:/files/selfie.jpg to Facebook
Browser on System 1: Opening C:/files/selfie.jpg, Uploading…. Done! Nice haircut!

When you’re using a remote browser (say, when you’re using one of our 157+ platforms), it still looks for files as though they were on the same system. However, the file you’re trying to upload only exists on YOUR system, so it can’t find it:

User on System 1: Upload C:/files/dignity.jpg to Facebook
Browser on System 2: Opening C:/fi…… Ack, dignity.jpg doesn’t exist, WAT DO 0_0

Selenium 2 uses FileDetectors to fix this problem for us. When you’ve got a FileDetector set, any file path you pass to a file input element (with the “send_keys” method) is sent to the FileDetector. If it decides that file path is a (local) file, Selenium will upload the (local) file to the (remote) browser’s server. It will then set the (remote) file path as the value of the file input field. Bang! Automagic remote file uploads.

So how do the Ruby bindings work? Let’s go check out the Selenium gem source!

# The detector is an object that responds to #call, and when called
# will determine if the given string represents a file. If it does,
# the path to the file on the local file system should be returned,
# otherwise nil or false.

So, as long as the object we set as a file_detector can verify that a passed in string is a file path, and then return that path, we can add uploads to Capybara. Sweet! Kinda. See, there’s a catch – Capybara doesn’t provide direct access to the Selenium driver object, which is kinda the point of Capybara. We’ll have to get access to it:

selenium_driver = page.object.browser

Let’s create a file_detector object and pass it to selenium_driver. It needs to respond to ‘call’. You know what responds to ‘call’ and doesn’t require us lazy, lazy Ruby programmers to create (and then instantiate) a whole new class? Lambdas.

selenium_driver.file_detector = lambda do |args|
# Check that the first arg is really a file, for realz

Hmm, actually, we might be getting ahead of ourselves. It would suuuuuck to have to change all our tests to find file inputs and call send_keys and all that junk, so let’s check out the Capybara DSL method “attach_file” to see what changes we have to make to it:

### lib/capybara/node/actions.rb
def attach_file(locator, path, options={})

    Array(path).each do |p|
        raise Capybara::FileNotFound, "cannot attach file, #{p} does not exist" unless File.exist?(p.to_s)
    end
    find(:file_field, locator, options).set(path)
end

### lib/capybara/selenium/node.rb
  def set(value)
  # SNIP #
  elsif tag_name == 'input' and type == 'file'
      path_names = value.to_s.empty? ? [] : value
      native.send_keys(*path_names)

Oh, awesome! Check out the highlighted lines — Capybara will already check that the file path we’re providing exists, before it’s even passed to the file_detector. That means we don’t even need to *do* anything in our file_detector lambda! The entirety of what we need to do to give us remote file uploading in Capybara is below:

The Solution

## Allows remote uploads.  Totally awesomesauce (labs).
selenium_driver = page.object.browser

selenium_driver.file_detector = lambda {|args| args.first.to_s}

Isn’t Ruby great?

Setting up iOS Automation on Sauce Labs with Cucumber and Appium

October 7th, 2013 by Shashikant Jagtap

Automated mobile testing with Cucumber & AppiumAbstract

Sauce Labs has recently announced Appium support which makes it easier to test mobile apps in the cloud. Appium is a mobile test automation framework for hybrid and native mobile apps. Cucumber is a behaviour driven development a.k.a BDD tool used with different programming languages. The combination of Cucumber and Appium can be used for automating iOS apps in the cloud using Sauce Labs. This is a repost of the original post. In this post, we will see how to set up test automation of our iOS app in the cloud using Sauce Labs.

Getting Started

In order to get started, we need to have the initial setup handy. This includes the following:

  • Mac OSX 10.7.4 or higher with Xcode installed with command line tools.
  • Your app source code or a prebuilt .app bundle for your app. Browse wide range of open-source iOS apps
  • Saucelabs Username and API key. Signup for Saucelabs free account.
  • Web development environment on Mac OSX for Ruby including Xcode, RVM, HomeBrew, Git and Ruby. Follow Moncef’s blog
  • Appium-Ruby-console  with Node and npm (Node must be >= v0.8)
  • Ruby 1.9.3

Get Appium and Your iOS App

Before we go ahead, let’s get the Appium server up and running. There are two ways to do it-

  •  You can download Mac OSX Appium.dmg package and launch Appium.
  • You can run it from source. Follow instructions. You need to make sure, You have authorized use of the iOS Simulator. If you are running Appium from NPM, you’ll do this by running
$ sudo authorize_ios

There are a wide range of open source iOS mobile apps available here, we are going to use PlainNote iOS app for this tutorial.

Compile and Upload iOS App on SauceLabs

Now we need to compile PlainNote App with Sauce. [ Note Additional parameter TARGET_DEVICE_FAMILY]

$ git clone https://github.com/vkoser/PlainNote
$ cd PlainNote
$ xcodebuild -sdk iphonesimulator6.0 TARGETED_DEVICE_FAMILY=1

In Sauce, there are optional parameters like TARGETED_DEVICE_FAMILY parameter . To build an app for iPhone, we use  TARGETED_DEVICE_FAMILY= 1, for iPad  TARGETED_DEVICE_FAMILY=2 .

Now, once the build is successful, it will create “PlaneNote.app” at ‘/build/Release-iphonesimulator‘.

$ cd /build/Release-iphonesimulator/
$ ls
PlainNote.app PlainNote.app.dSYM
  •  Zip the PlainNote App & Upload to SauceLabs 

Now that we have ‘PlainNote.app‘, we need to zip it by navigating to that directory

$ cd /build/Release-iphonesimulator/
$ zip -r PlainNote.zip PlainNote.app

Now you will see ‘PlainNote.zip‘ file in that directory

$ ls
PlainNote.app      PlainNote.app.dSYM PlainNote.zip

Now, we need to upload this file to Sauce Labs temporary storage using the Sauce REST API. I am using my Username and API key here.

$ curl -u Shashikant86:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxbfc3 -X POST "http://saucelabs.com/rest/v1/storage/Shashikant86/PlainNote.zip?overwrite=true" -H "Content-Type: application/octet-stream" --data-binary @/path/to/PlainNote/build/Release-iphonesimulator/PlainNote.zip

It will show you response something like this,

{"username": "Shashikant86", "size": 42190, "md5": "6ef42125b024188976af9d6b8a104105", "filename": "PlainNote.zip"}

It will be now uploaded to “sauce-storage:PlainNote.zip“. Now we are all set for writing tests for the application with Cucumber.

Setup Cucumber project

Now that we have already uploaded our app on SauceLabs temporary storage, we can setup Cucumber project to talk to the mobile app in the cloud. I assume that you are familiar with the BDD Code and code structure for the Cucumber project. Please refer my old post if you are not aware of BDD code.

  • Create Gemfile

We need a Gemfile in order to specify all our dependencies

$ mkdir sauce-cucumber-appium
$ cd sauce-cucumber-appium
$ rvm use 1.9.3
$ vim Gemfile

Now insert the following dependencies into the Gemfile

source "https://www.rubygems.org"
gem "rest-client"
gem "rspec"
gem "selenium-webdriver"
gem "cucumber"
gem "rspec-expectations"

Now we need to install the bundle to download all the dependencies.

$ bundle install

This will create a ‘Gemfile.lock’ file.

  •  Create Feature File 

Now, we will write a feature file using Given When Then format. The feature file is written using the Gherkin Domain Specific Language.

Let’s create ‘features/plain_note_sauce.feature‘ file.

$ vim features/plain_note_sauce.feature

The PlainNote app feature will look something like this

Feature: Notes 
  As iOS automation specialist 
  I want to setup iOS app automation in the cloud using Saucelabs, Appium and cucumber 

Scenario: Add new Note using PlainNote App 

  Given I have App running with appium on Sauce 
  When click + button using sauce driver
  And I enter text "Data" and saved it on sauce
  Then I should see "Data" note added on home page in the sauce cloud

This feature is self explanatory, we are going to add a new note and make sure it displayed on the Home page.

  • Setup Cucumber Environment  

Let’s create ‘features/support/env.rb‘ where we can put our support code. We need to add sauce_capabilities mentioned in the Sauce Labs Appium tutorial.

$ vim features/support/env.rb

Insert the following code in the file.

require 'rspec/expectations'
require 'selenium-webdriver'

def sauce_capabilities
  {
    'app' => 'sauce-storage:PlainNote.zip',
    'device' => 'iPhone Simulator',
    'username' => 'Shashikant86',
    'access-key' => 'a0e37e25-e2f3-4cba-95d3-936007d8bfc3',
    'platform' => 'OS X 10.8',
    'version' => '6.0',
    'name' => 'Running PlainNote wit Cucumber and Appium', 
    'passed' => 'true'
  }
end

def sauce_url
  "http://Shashikant86:a0e37e25-e2f3-4cba-95d3-936007d8bfc3@ondemand.saucelabs.com:80/wd/hub"
end

def sauce
  @sauce ||= Selenium::WebDriver.for(:remote, :desired_capabilities => sauce_capabilities, :url => sauce_url)
end

After { @sauce.quit }

Now that we have  a created ‘sauce’ driver with all required desired capabilities, ee will using the ‘sauce’ object in our step_definitions

  •  Write Step definitions using Selenium-Webdriver  JSON Wire Protocol

At this point if you run the ‘bundle exec cucumber’ command it will tell you steps that are not implemented yet. We need to implement these step definitions using Selenium-Webdriver JSON Wire Protocol for Appium. Now we will create a step definition file and implement it

$ vim features/step_definitions/plain_note.rb

Now add these step definitions to the file.

Given(/^I have App running with appium on Sauce$/) do
end

When(/^click \+ button using sauce driver$/) do
sauce.find_element(:name, "Add").click
end

When(/^I enter text "(.*?)" and saved it on sauce$/) do |data|
sauce.find_element(:xpath, "//window[1]/scrollview[1]/textview[1]").send_keys data
sauce.find_element(:name, "Done").click
sauce.find_element(:name, "Save").click
end

Then(/^I should see "(.*?)" note added on home page in the sauce cloud$/) do |text|
note = sauce.find_element(:xpath,  "//window[1]/tableview[1]/cell[1]/text[1]")
note.attribute("value").should match text 
end

Appium Inspector 

Appium Inspector is a feature of the Appium OSX app which allows you to inspect elements on your mobile app. You can also record tests in the different languages. Writing the Ruby code is easy if you have used Appium Inspector locally to record tests. Watch this video to know ‘How to use Appium Inspector‘.

Now, we are all set to run cucumber to execute tests on the SauceLabs

$ bundle exec cucumber features/plain_note_sauce.feature

Now you will see the tests running on Sauce Labs and in your terminal you will see something like this

Feature: Notes
  As iOS automation specialist
  I want to setup iOS app automation in the cloud using Saucelabs, Appium and cucumber

  Scenario: Add new Note using PlainNote App                            # features/plain_note_sauce.feature:5
    Given I have App running with appium on Sauce                       # features/step_definitions/plain_note_sauce.rb:1
    When click + button using sauce driver                              # features/step_definitions/plain_note_sauce.rb:4
    And I enter text "Data" and saved it on sauce                       # features/step_definitions/plain_note_sauce.rb:8
    Then I should see "Data" note added on home page in the sauce cloud # features/step_definitions/plain_note_sauce.rb:14

1 scenario (1 passed)
4 steps (4 passed)
0m35.213s

You can watch video and screenshots of the job.

Cucumber-Appium-SauceLabs

You can find source code on GitHub:

GitHub : cucumber-appium

Watch this video get a clear idea of the cucumber-appium setup on Sauce Labs. If any questions, feel free to contact me.

Join Us for a Ruby Hackathon

May 3rd, 2013 by Bill McGee

rubyMore events? You bet! It’s spring, and we’re in the mood to party, so we’re throwing our first Ruby hackathon! Stop by our office next Thursday, May 9th, starting at 3PM, to eat, drink, and hack with us. Come get started with running your existing Selenium integration tests on Sauce using the Sauce Gem, enhance the gem, or just hang out with the Saucers and Ruby community. Cross-browser testing on Ruby with Sauce is a cinch, and we want to make it even better! Our beloved Ruby dude Dylan will be in town all the way from Australia; if you ask him nicely enough he may agree to do a jig.

We’re providing pizza and beer, so bring yourself and your laptop down to our office to hang, eat, drink, and maybe get some work done! Register here.

We’re located at 500 3rd St. (cross street Bryant), Ste 240, San Francisco.

See you there!