Ask a Selenium Expert: Strategies for Cross-Browser Testing

April 23rd, 2014 by Amber Kaplan

selenium testing & sauceThis is part 4 of 8 in a mini series of follow-up Q&A’s from Selenium expert Dave Haeffner. You can read up on the firstsecond, and third.

Dave discussed  how to build out a well factored, maintainable, resilient, and parallelized suite of tests that run locally, on a Continuous Integration system, and in the cloud in our recent webinar, “Selenium Bootcamp“.

Following the webinar, we had several follow-up questions. Dave’s agreed to respond to 8. Below you’ll find the third Q&A. Stay tuned next Wednesday for the next question.

3. ­I would like to see strategies for getting tests to work in multiple browers. For example, if my test works in Chrome but not Firefox, what do I do?

There are two things you’re likely to run into ­when running your tests across multiple browsers: speed of execution, and locator limitations.

Speed of execution issues occur when things execute too quickly (which indicates that you need to add explicit waits to your test code) or timeout (which indicates that the explicit waits you have are too tight and need to be loosened). The best approach to take is an iterative one. Run your tests and find the failures. Take each failed test, adjust your code as needed, and run it against the browsers you care about. Repeat until everything’s green.

In older browsers (e.g., Internet Explorer 8) you’ll be limited by the locators you can use (e.g., CSS3 nth locators like nth-child, nth-of-type, etc) and likely run into issues with some dynamic functionality (e.g., hovers). In cases like this, it’s simple enough to find an alternative set of locators to use that are specific to this browser and update your test to use them only when this browser is being used.

-Dave Haeffner, April 9, 2014

Can’t wait to see the rest of the Q&A? Read the whole post here.  Get more info on Selenium with Dave’s book, The Selenium Guidebook, or follow him on Twitter orGithub.

Have an idea for a blog post, webinar, or more? We want to hear from you! Submit topic ideas (or questions!)  here.

Ask a Selenium Expert: Selenium Test Report Example

April 16th, 2014 by Amber Kaplan

selenium testing & sauce

This is part 2 of 8 in a mini series of follow-up Q&A’s from Selenium expert Dave Haeffner. Read the first Q&A here.

Dave joined us and led our recent webinar, “Selenium Bootcamp“, wherein he discussed  how to build out a well factored, maintainable, resilient, and parallelized suite of tests that will run locally, on a Continuous Integration system, and in the cloud.

He’s also agreed to respond to 8 of the many follow-up questions we received post-webinar. Below you’ll find the second Q&A. Stay tuned next Wednesday for the next question.

2. ­Can you please show some examples of a Selenium test report?

Here are two examples:

-Dave Haeffner, April 9, 2014

Can’t wait to see the rest of the Q&A? Read the whole post here.  Get more info on Selenium with Dave’s book, The Selenium Guidebook, or follow him on Twitter orGithub.

Have an idea for a blog post, webinar, or more? We want to hear from you! Submit topic ideas (or questions!)  here.

Ask a Selenium Expert: Convert a Selenium IDE Script to Use Webdriver

April 9th, 2014 by Amber Kaplan

selenium testing & sauceThis is part 1 of  8 in a mini series of follow-up Q&A’s from Selenium expert Dave Haeffner.

Dave was kind enough to join us for a recent webinar, “Selenium Bootcamp“.   Topics included how to build out a well factored, maintainable, resilient, and parallelized suite of tests that will run locally, on a Continuous Integration system, and in the cloud.

He’s also agreed to respond to 8 of the many follow-up questions we received post-webinar. Below you’ll find the first Q&A. Stay tuned next Wednesday for the next question.

1. Is converting my Selenium IDE script to a programming language to start using webdriver sufficient?

You may get some value out of exporting your existing IDE tests, but they will likely require a good amount of clean-up. You’re likely to get more value out of identifying a few pieces of core functionality in the application you’re testing, and writing new tests for this functionality in a programming language from scratch.

-Dave Haeffner, April 9, 2014

Can’t wait to see the rest of the Q&A? Read the whole post here.  Get more info on Selenium with Dave’s book, The Selenium Guidebook, or follow him on Twitter or Github.

Have an idea for a blog post, webinar, or more? We want to hear from you! Submit topic ideas (or questions!)  here.

Guest Post: Cross-Browser Selenium Testing with Robot Framework and Sauce Labs

April 3rd, 2014 by Amber Kaplan

Robot FrameworkEver wondered how to keep your Selenium tests up-to-date with your ever-changing user interface?

Sauce Labs customer Asko Soukka set out to answer just that in his post, “Cross-Browser Selenium Testing with Robot Framework and Sauce Labs”.

See a snippet below:

Do you try to fix your existing tests, or do you just re-record them over and over again?

In the Plone Community, we have chosen the former approach (Plone is a popular open source CMS written in Python). We use a tool called Robot Framework to write our Selenium acceptance tests as maintainable BDD-style stories. Robot Framework’s extensible test language allows us to describe Plone’s features in a natural language sentences, which can then be expanded into either our domain specific or Selenium WebDriver API based testing language.

-Asko Soukka, datakurre.pandala.org, March 20, 2014

Asko walks you through the process of installing Robot Framework, writing and running a Selenium test suite in Robot, to refactoring that suite to run cross-browser on Sauce Labs. Be sure to check out the rest of his excellent post and tutorial here.

Do you have a topic you’d like to share with our community? We’d love to hear from you! Submit topics here, feel free to leave a comment, or tweet at us any time.

Getting Started With Selenium – Chapter 8: Tying It All Together

March 26th, 2014 by Bill McGee

selenium-logo-160x144This post is the eighth and final in a series of “Getting Started with Selenium Testing” posts from Dave Haeffner, a noted expert on Selenium and automated testing, and a frequent contributor to the Sauce blog and Selenium community. This series is for those who are brand new to test automation with Selenium.

Can’t get enough of Dave or his Selenium boot camp? Sign up for our webinar featuring Dave on  March 27, 2014. Register today!

Tying It All Together

You’ll probably get a lot of mileage out of your test suite in its current form if you just run things from your computer, look at the results, and inform people on your team when there are issues. But that only helps you solve part of the problem.

The real goal in all of this is to find issues reliably, quickly, and continuously — and ideally in sync with the development workflow you are a part of. In order to do that, we want to use a Continuous Integration (CI) server.

A Continuous Integration Primer

Continuous Integration (a.k.a. CI) is the practice of merging code that is actively being worked on into a shared mainline (e.g., trunk or master) as often as possible (e.g., several times a day). This is with the hopes of finding issues early and avoiding merging and integration issues that are not only considered a special kind of hell, but can dramatically slow the time it takes to release software.

The use of a CI server (a.k.a. build server) enables this practice to be automated, and to have tests run as part of the work flow. The lion’s share of tests that are typically run on a CI Server are unit (and potentially integration) tests. But we can very easily add in our automated acceptance tests.

There are numerous CI Servers available for use today, most notably:

Tagging & Workflow

In order to get the most out of our test runs in a CI environment, we want to break up our test suite into small, relevant chunks and have separate jobs for each. This helps keep test runs fast (so people on your team will care about them) and informative. Gojko Adzic refers to these as ‘Test Packs’.

The workflow is pretty straightforward. The CI Server pulls in the latest code, merges it, runs unit tests, and deploys to a test server. We then have the CI Server kick off a new job to run a subset of our acceptance tests — our critical ones (e.g., smoke or sanity tests). Assuming those pass, we can have another job run to promote the code to another environment (e.g., for manual testing) or fire up the remaining (longer running) tests. Adam Goucher refers to this strategy as a ‘shallow’ and ‘deep’ tagging model.

Machine Readable vs. Human Readable

In order to make the test output useful for a CI Server we need to generate it in a standard way. One format that works across most CI Servers is JUnit XML. Your test runner should be able to accommodate this either out of the box, or through the addition of a third-party library or plugin (e.g., like rspecjunitformatter for RSpec.

If you’re using Sauce Labs, then you will get all of the human readable test output you need to diagnose a test failure at a glance. You will just need to find a way to tie the Sauce Labs job to the failed test. In RSpec, this can be accomplished through creating custom failure messages that will output the Sauce Labs job URL (an example which is available in my book). Alternatively, you can use a CI server plugin to help accomplish the same thing. Sauce Labs has a write-up on how to do this with Jenkins.

CI Server Job Configuration

Here are the basic steps that go into setting up a simple Jenkins job that will be trigger dynamically.

1. Create a Job
2. Pull In Your Test Code
3. Set up Build Triggers
4. Configure Build steps
5. Configure Test Reports
6. Run Tests & View The Results
7. Set Up Notifications (e.g., e-mail, chat, etc.)

For a walk through of how to set this up, check out The Selenium Guidebook.

Outro

Your Selenium journey may prove challenging at times. But by adhering to the principals in this course, you are better equipped to start off on the right foot and avoid common Selenium pitfalls. You are now ready to continue your Selenium journey on your own. For a full list of additional Selenium resources — check out this write-up.

Happy Testing!

Previous Chapters: 1. Getting Started | 2. Writing a Good Acceptance Test | 3. Writing Your First Selenium Test | 4. How To Reuse Your Test Code | 5. Writing Resilient Test Code | 6. Packaging for Use | 7. How To Scale Your Tests

Dave is the author of Elemental Selenium (a free, once weekly Selenium tip newsletter that is read by hundreds of testing professionals) as well as a new book, The Selenium Guidebook. He is also the creator and maintainer of ChemistryKit (an open-source Selenium framework). He has helped numerous companies successfully implement automated acceptance testing; including The Motley Fool, ManTech International, Sittercity, and Animoto. He is a founder and co-organizer of the Selenium Hangout and has spoken at numerous conferences and meetups about acceptance testing.

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

March 19th, 2014 by lauren nguyen

Designing a Ruby-based Sauce Test Automation Framework – Running a Test project on Sauce Labs

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.

We then familiarized ourselves on implementing a Simple Test Framework on Ruby using Page Object Pattern.

Let’s now look at building a test project in Ruby using this framework and try executing it on Sauce Labs.

Building a Test Project

Now that we have finished implementing the framework base classes in Ruby, we are going to use these framework classes in a simple Test project.

When using this framework in an actual project, it would be a good idea to separate the framework base classes and make them into a gem. This allows multiple project teams to reuse the framework classes and at the same time allows centralized version control of the framework base classes.

In our test project, Project level Test Base classes will extend the Framework Base classes. This will allow project teams to override and specify project specific actions and configurations in Project Base Classes.

project_test_base.rb

  • This class also extends the Selenium Test Base class and handles project level common setup steps, teardown steps and action initialization steps for tests.
  • In addition, this class also calls and initializes the Actions and Pages required to carry out the tests. As we discussed, in the Page Object pattern, each Test class will create Pages through individual instances and the Actions that are needed to execute the test case. Accordingly, this Project Test Base will create the most commonly used Action classes in the project through individual instances. In this case, we are creating the Simple Form Actions from within the Project Test Base itself.
# project_action_base.rb
# (Note: require_relative has been used assuming that all files would be stored in the same folder)

require_relative 'selenium_test_base'
require_relative 'simple_form_actions'
require_relative 'simple_form_page'

class ProjectTestBase   < SeleniumTestBase
  def initialize(webDriver)
    super(webDriver)
  end

  def create_actions(webDriver)
    @form_actions = SimpleFormActions.new(webDriver)
  end

  def create_pages(webDriver)
    #code to create the respective page instance
  end

  def  setup_test_fixture()
  end

  def setup
    webDriver = start_browser("firefox")
    create_actions(webDriver)
    launch_url("http://saucelabs.com/test/guinea-pig")
  end
end

This class extends the Selenium Action Base class. The Project Action Base class also creates Page objects required to execute the test.

# simple_form_actions.rb
require_relative 'selenium_action_base'

class ProjectActionBase < SeleniumActionBase
  def initialize(webdriver)
    super(webdriver)
    @form_page = SimpleFormPage.new(webdriver)
  end

end

This Action class extends the Project Action Base and performs actions on the Simple Form page for executing the tests. As we will see later, this Action class will be later called by the Simple Form Test class to perform these actions on the Simple Form Page.

# simple_form_page.rb
require_relative 'project_action_base'

class SimpleFormActions < ProjectActionBase
  def initialize(webDriver)
    @driver = webDriver
    @form = SimpleFormPage.new(@driver)
  end

  def attempt_submit( email,comments,isValid = nil)
    @form.comment_field(comments)
    @form.email_field(email)
    @form.submit()
  end
end

Defines the page elements on the SimpleFormPage and manages the interactions within such specific page elements.

# simple_form_tests.rb
require_relative 'selenium_page_base'
require_relative 'selenium_page_element'

class SimpleFormPage < SeleniumPageBase
  def initialize(webdriver)
    super(webdriver)
    @page_element = SeleniumPageElement.new(webdriver)
  end

  def email_field(value)
    @page_element.load_element('fbemail',value)
  end

  def comment_field(value)
    @page_element.load_element('comments',value)
  end

  def submit
    @page_element.submit
  end

  def read_response
    #removing element ID explicitly using slice
    @page_element.get_response().text.slice "Ruby Selenium framework runs on sauce labs"
  end
end

SimpleFormTests is the first test case class that we are creating. Since we are using RSpec to perform the assertions, this test class limits itself with just executing the test actions.

# project_test_base_spec.rb
require_relative 'project_test_base'

class SimpleFormTests < ProjectTestBase
  def initialize(webDriver)
    super(webDriver)
  end

  def get_page_title
    @driver.title
  end

  def successful_form_submit
    @form_actions.attempt_submit("email@welcome.com", "Ruby Selenium framework runs on sauce labs", true)
  end
end

This is where the real test assertions happen. The RSpec creates Test objects and performs the Test Assertions.

require_relative "project_test_base"
require_relative "simple_form_tests"

describe SimpleFormTests, "#testing simple form submit on selenium saucelabs environment" do
  before(:all) do
    @simple_form = SimpleFormTests.new('webDriver')
    @simple_form.setup
  end

  it "returns page title of the website" do
    @simple_form.get_page_title.should eql("I am a page title - Sauce Labs")
  end

  it "should render comment on sucessful form submit" do
    @simple_form.successful_form_submit.should eql("Ruby Selenium framework runs on sauce labs")
  end

  after(:all) do
    @simple_form.teardown
  end
end

The framework provides the advantages of Ruby. The framework is re-usable and easily maintainable in the long run. This framework would help QA professionals save time and effort. It could also result in cost savings for organizations.

References:

  • http://sauceio.com/index.php/2011/12/selenium-testing-framework-part-3-putting-it-all-together/

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

Getting Started with Selenium – Chapter 7: How To Scale Your Tests

March 18th, 2014 by Bill McGee

selenium-logo-160x144This post is the seventh in a series of “Getting Started with Selenium Testing” posts from Dave Haeffner, a noted expert on Selenium and automated testing, and a frequent contributor to the Sauce blog and Selenium community. This series is for those who are brand new to test automation with Selenium and a new chapter will be posted every Tuesday (eight chapters in all).

How To Scale Your Tests

If you’ve ever needed to test features in an older browser like Internet Explorer 8 then odds are you ran a virtual machine (VM) on your computer with a “legit” version of Windows XP.

Handy, but what happens when you need to check things on multiple versions of IE? Now you’re looking at multiple VMs. And what about when you need to scale and cover other browser and Operating System (OS) combinations? Now you’re looking at provisioning, running, and maintaining your own farm of machines and standing up something like Selenium Grid to coordinate tests across them.

And all you wanted to do was run your tests on the browsers you cared about…

Rather than take on the overhead of a test infrastructure you can easily outsource things to a third-party cloud provider – like Sauce Labs

A Selenium Remote, Selenium Grid, And Sauce Labs Primer

At the heart of Selenium at scale is the use of Selenium Grid and Selenium Remote. Selenium Grid lets you distribute test execution across several machines and you connect to it with Selenium Remote – specifying the browser type and version through the use of Selenium Remote’s Capabilities.

This is fundamentally how Sauce Labs works. Behind the curtain they are ultimately running Selenium Grid, and they receive and execute your tests through Selenium Remote – knowing which browser and operating system to use because of the Capabilities its users specify.

Let’s dig in with an example.

An Example

Part 1: Initial Setup

NOTE: You’ll need an account to use Sauce Labs. Their free one offers enough to get you started.

With Sauce Labs we need to provide specifics about what we want in our test environment, our credentials, and configure Selenium a little bit differently than we have been. Building on the last write-up, let’s start by creating a new config file for cloud execution (‘config_cloud.rb’).

filename: config_cloud.rb

ENV['base_url']         ||= 'http://the-internet.herokuapp.com'
ENV['host']             = 'saucelabs'
ENV['operating_system'] ||= 'Windows XP'
ENV['browser']          ||= 'internet_explorer'
ENV['browser_version']  ||= '8'
ENV['SAUCE_USERNAME']   ||= 'your-sauce-username'
ENV['SAUCE_ACCESS_KEY'] ||= 'your-sauce-access-key'

Notice the use of a host environment variable. This is what we’ll use in our spec_helper file to determine whether to run things locally or in the cloud — and we’ll use the other environment variables to populate the Capabilities.

filename: spec/spec_helper.rb

require 'selenium-webdriver'

RSpec.configure do |config|

  config.before(:each) do
    case ENV['host']
    when 'saucelabs'
      caps = Selenium::WebDriver::Remote::Capabilities.send(ENV['browser'])
      caps.version = ENV['browser_version']
      caps.platform = ENV['operating_system']
      caps[:name] = example.metadata[:full_description]

      @driver = Selenium::WebDriver.for(
        :remote,
        url: "http://#{ENV['SAUCE_USERNAME']}:#{ENV['SAUCE_ACCESS_KEY']}@ondemand.saucelabs.com:80/wd/hub",
        desired_capabilities: caps)
    else
      @driver = Selenium::WebDriver.for :firefox
    end
  end

  config.after(:each) do
    @driver.quit
  end

end

Notice that we’ve added a conditional to check on the host environment variable. If the host is set to ‘saucelabs’, then we configure the capabilities for Selenium Remote, passing in the requisite information that we will need for our Sauce Labs session. Otherwise, it will run our tests locally using Firefox.

Now if we run our test suite (rspec -r ./config_cloud.rb) and navigate to our Sauce Labs Account page then we should see each of the tests running in their own job, with proper names, against Internet Explorer 8.

Part 2: Test Status

The only thing missing now is the pass/fail status of the job. In our local terminal window everything should be coming up green. But in the list of our Sauce jobs, the ‘Results’ panel for everything will just say ‘Finished’. This will make our results less useful in the long run, so let’s fix it.

Thanks to Sauce Labs’ sauce whisk gem, it’s really simple to do.

After we install it we will need to require it somewhere, and our ‘config_cloud.rb’ file seems like a logical place, since we will only need it when running our tests in Sauce. So let’s add it to the top of the file.

filename: config_cloud.rb

require 'sauce_whisk' ...

All that’s left is to add an action to our after(:each) block in our ‘spec_helper’ file.

Before we issue @driver.quit we will want to grab the job ID from our @driver object and set the job status based on the test result. Also, we’ll want to make sure that it only executes when running tests against Sauce Labs — so we’ll want to wrap it in a conditional check against the host environment variable.

filename: spec/spec_helper.rb

require 'selenium-webdriver'

RSpec.configure do |config|
  ...

  config.after(:each) do
    if ENV['host'] == 'saucelabs'
      if example.exception.nil?
        SauceWhisk::Jobs.pass_job @driver.session_id
      else
        SauceWhisk::Jobs.fail_job @driver.session_id
      end
    end

    @driver.quit
  end

end

Now when we run our tests (rspec -r ./config_cloud.rb) and navigate to our Sauce Labs Account page, we should see our tests running like before – but now when they finish there should be a proper test status (e.g., ‘Pass’ or ‘Fail’).

Speeding Up Your Tests

Now we can easily run our tests in Sauce Labs, but it’s a real bummer that all of our tests are executing in series. As our suite grows, things will quickly start to add up and really hamper our ability to get feedback quickly.

With parallelization we can quickly remedy this — and there are a few ways to go about accomplishing it.

  • In code
  • Through a test runner
  • Through your Continuous Integration (CI) Server

Outro

For a deeper dive on how to scale your tests (e.g., running tests in parallel, testing applications behind a firewall, outputting Sauce Labs job information into your test results, etc.) then be sure to check out The Selenium Guidebook.

Up next, in my final write-up, I’ll help you tie everything together through the use of Continuous Integration.

Previous Chapters: 1. Getting Started | 2. Writing a Good Acceptance Test | 3. Writing Your First Selenium Test | 4. How To Reuse Your Test Code | 5. Writing Resilient Test Code | 6. Packaging for Use

Dave is the author of Elemental Selenium (a free, once weekly Selenium tip newsletter that is read by hundreds of testing professionals) as well as a new book, The Selenium Guidebook. He is also the creator and maintainer of ChemistryKit (an open-source Selenium framework). He has helped numerous companies successfully implement automated acceptance testing; including The Motley Fool, ManTech International, Sittercity, and Animoto. He is a founder and co-organizer of the Selenium Hangout and has spoken at numerous conferences and meetups about acceptance testing.

How to Integrate Sauce Labs Reports with MSTest and Jenkins

March 13th, 2014 by Amber Kaplan

Ever wondered how to integrate Sauce Selenium Testing with Jenkins? Yup, there’s a plugin for that.

But as many have discovered, the Sauce OnDemand plugin for Jenkins requires a bit of finessing in order to work with MSTest. One customer, Victor, was kind enough to share this issue and his solution with the web:

“The challenge that I’ve found with that is that the plugin is only for JUnit like tests, so, as we’re a .NET shop and we use MSTest to run automated builds, the plugin does not work completely out-of-the-box. Specifically the reporting part, as it takes an “.xml” (JUnit like) test results file and parses the console output of the tests looking for an specific SauceLabs ID to link tests back to SauceLabs jobs (More details: Integrating tests with the Sauce OnDemand plugin for Jenkins).

- Victor Pascual, Testing Island, February 17, 2014

Victor walks you through all the details with instructions and screenshots in his full post, here. Other useful tips, curated by Victor:

Definitely worth a read!

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

March 12th, 2014 by lauren nguyen

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

Getting Started With Selenium – Chapter 6: Packaging for Use

March 11th, 2014 by Bill McGee

selenium-logo-160x144This post is the sixth in a series of “Getting Started with Selenium Testing” posts from Dave Haeffner, a noted expert on Selenium and automated testing, and a frequent contributor to the Sauce blog and Selenium community. This series is for those who are brand new to test automation with Selenium and a new chapter will be posted every Tuesday (eight chapters in all).

Packaging For Use

In order to get the most out of our tests and page objects, we’ll need to package them into a more useful structure.

Let’s do that using the examples from the previous write-ups.

Global Setup And Teardown

First we’ll need to pull the test setup and teardown actions out of our tests and into a central place. In RSpec this is straight-forward through the use of a ‘spec_helper’ file.

# filename: spec_helper.rb

require 'selenium-webdriver'

RSpec.configure do |config|

  config.before(:each) do
    @driver = Selenium::WebDriver.for :firefox
  end

  config.after(:each) do
    @driver.quit
  end

end

We need to include the Selenium library here, and by doing so, can remove it from our tests. And by having our test configuration here, we can remove it from the `before(:each)` and `after(:each)` in our tests as well — replacing them with a simple require statement at the top of the file (`require_relative ‘spec_helper’`).

Base URL

Rather than hard-coding a URL in our tests and page objects, We’ll want to put it someplace central, and we’ll want it to be configurable. So let’s create a ‘config.rb’ file in the parent directory and place it there.

# filename: config.rb

ENV['base_url'] ||= 'http://the-internet.herokuapp.com'

By using a conditional when setting the environment variable (`||=`) we are making it so we can override this value when launching our test suite (e.g., `base_url=’http://localhost:4567′`). It essentially means if the environment variable already exists and contains a value, use it. Otherwise, set it to ‘http://the-internet.herokuapp.com’. We can then reference this variable in our page objects where necessary (if we haven’t already).

For instance:

# filename: login.rb

class Login
...

def initialize(driver)
@driver = driver
@driver.get ENV['base_url'] + '/login'
end

...

Folder Organization

It’s about time we create some folders for our specs and page objects. To err on the side of simplicity, let’s call the folders ‘spec’ (for our tests) and ‘pages’ (for our page objects). We are using ‘spec’ since it is a default folder that RSpec will look for.

Here’s everything we should have after creating folders and moving files around:

.
|-- config.rb
|-- Gemfile
|-- pages
| |-- dynamic_loading.rb
| `-- login.rb
`-- spec
|-- dynamic_loading_spec.rb
|-- login_spec.rb
`-- spec_helper.rb

Updating Require Statements

As a result of doing this, we will need to update the require statements in our tests.

# filename: spec/login_spec.rb

require_relative 'spec_helper'
require_relative '../pages/login'

describe 'Login' do
...

# filename: spec/dynamic_loading_spec.rb

require_relative 'spec_helper'
require_relative '../pages/dynamic_loading'

describe 'Dynamic Loading' do
...

Note the use of double-dots (`..`) in the page object require statement. This is how we tell Ruby to traverse up a directory (from our spec directory) before trying to access the page objects folder. The `spec_helper` require remains unchanged since this file lives in the same directory as our tests.

Running Everything

Now that things are cleaned up, we can run everything. To do that we’ll want to make sure to include our new config file. We can do that by specifying it at run time with `rspec –require ./config.rb`, or, `rspec -r ./config.rb` (for short).

Note the `./` before `config.rb`. This tells RSpec that the config file is in the current directory.

Give it a shot. All of the tests should run and pass.

For more examples like this (along with complete working code) — grab your copy of  The Selenium Guidebook.

Previous Chapters: 1. Getting Started | 2. Writing a Good Acceptance Test | 3. Writing Your First Selenium Test | 4. How To Reuse Your Test Code | 5. Writing Resilient Test Code

Dave is the author of Elemental Selenium (a free, once weekly Selenium tip newsletter that is read by hundreds of testing professionals) as well as a new book, The Selenium Guidebook. He is also the creator and maintainer of ChemistryKit (an open-source Selenium framework). He has helped numerous companies successfully implement automated acceptance testing; including The Motley Fool, ManTech International, Sittercity, and Animoto. He is a founder and co-organizer of the Selenium Hangout and has spoken at numerous conferences and meetups about acceptance testing.