RSpec, Capybara for Web Automation

Why Automate?

Becuase manual testing is boring! Hmm, well in some case it is but we have to accept the fact that a lot of errors do happen when we have to test the same scenarios multiple times with different set of data and on different platforms running different browsers. Woah! that’s something to think about. But remember, automation is very useful only for repetitive tasks that rarely change and for those applications which need to be verified with a variety of datasets. No automation can replace manual testers in the case of exploratory testing and it is evident that any product without manual testing is difficult to trust upon and it is the humans who decide on what to automate and what not to!

automation-300x300

Let’s learn how to automate web applications. Today we have a lot of frameworks for web automation, to name a few famous ones: Selenium-webdriver, Watir-webdriver. And selenium forms the backbone for most of the frameworks.

We will consider Capybara which runs on selenium, RSpec for BDD and the website under test, the-internet, which has all the features necessary for an interactive web application.

Installation

Install Ruby and from the command line run

gem install capybara

Note: By default Capybara will only locate visible elements. This is because a real user would not be able to interact with non-visible elements.

Note: All searches in Capybara are case sensitive. This is because Capybara heavily uses XPath, which doesn’t support case insensitivity.

If you need to know about different frameworks and the methods to learn basics of Capybara then hit the site GitHub. But before we jump in automating the website, it is better to visit the site and explore manually once. Then go ahead, I will be waiting for you…

The Beginning

I believe you got an insight on the website and ready to automate. Wait! do we have any plan for how we are automating, I mean, do we need to follow any rules or just make some script which just automates the task at hand?. Well, that’s a valid question and an important thing to consider before automating any software.

Here, we will use ‘Page Object Model’, a way of grouping together the set of elements so as to keep the code clean and maintainable. If you wish to learn more about POM then I suggest you to Google it, as one can find tons of resource on the internet.

I have written a simple POM for the-internet website as given below. Here, I am imitating the human interaction with the website using Capybara and Ruby and verifying them using RSpec. I believe the below  code is self explanatory.

Create a Class file, 'the_heroku_app.rb':

class The_Heroku_App

  def start_browser
    # This will create a Capybara session and uses the default browser of firefox.
    @session = Capybara::Session.new (:selenium)
  end

  def tear_down
    @session.execute_script('window.close();')
  end

  def go_to(site)
    @session.visit site
  end

  def click_on_link(name)
    @session.click_link name
  end

  def checkbox_clicked?
    @session.find(:css, 'input:nth-child(1)[type="checkbox"]').checked?
  end

  def click_checkbox
    @session.find(:css, 'input:nth-child(1)[type="checkbox"]').click
  end

  def clear_checkbox
    @session.find(:css, 'input:nth-child(1)[type="checkbox"]').clear
  end

  # Save the file which was downloaded from GitHub, the below step will 
  # simulate the drag and drop functionality.
  def drag_and_drop_script
    dnd_javascript = File.read('C:\...\***_Test_Automation' + '\dnd.js')
    dnd_javascript+"$('#column-a').simulateDragDrop({ dropTarget: '#column-b'});"
  end

  def by_id(name)
    @session.find_by_id(name)
  end

  def find_all_drop_down
    @session.find('#dropdown').all('option').collect(&:text)
  end

  def select_dropdown(x)
    @session.select(x, from: 'dropdown')
  end

end

I have used RSpec as the testing framework and all the test scripts are run via RSpec. The scripts can be run as a complete suite or can be run individually using the ‘tags’. I will show how to use both.

Note:

  1. The ‘Basic Auth’ for invalid credentials scenario needs you to click, ‘Cancel’ when prompted for user/pass details. I want you guys to figure out on how to overcome this. (It’s easy tough!!)
  2. To simulate the ‘Drag and Drop’ functionality, I have used the javascript written by rcorreia.
Save the below code in a separate file called, 'the_heroku_spec.rb'

require 'rspec'
require 'capybara'
require 'C:\...\***_Test_Automation\the_heroku_app.rb'

describe 'Verifying the components of the_heroku_app' do

  # The before and after is run everytime for each test cases.
  before(:each) do
    @cls_instance = The_Heroku_App.new
    @browser = @cls_instance.start_browser
    @cls_instance.go_to 'https://the-internet.herokuapp.com'
  end

  after(:each) do
    @cls_instance.tear_down
  end

  context 'Title and Body' do

    it 'Check for title of the page', page_title: true  do
      expect(@browser.title).to eq('The Internet')
    end

    it 'Check for the body of page', page_body: true  do
      expect(@browser).to have_content('Welcome to the Internet')
    end

  end

  context 'Checkbox' do

    it 'Verify Checkbox by clicking', chk_box: true  do
      expect(@browser).to have_content('Welcome')
      @cls_instance.click_on_link 'Checkboxes'
      checkbox_1 = @cls_instance.checkbox_clicked?

      if !checkbox_1
        @cls_instance.click_checkbox
        expect(@cls_instance.checkbox_clicked?).to eq TRUE
      else
        @cls_instance.clear_checkbox
        expect(@cls_instance.checkbox_clicked?).to eq FALSE
      end
    end

  end

  context 'Credentials' do

    it 'Verify Basic Auth with VALID credentials', valid: true do
      @cls_instance.click_on_link 'Basic Auth'
      @browser.visit 'http://admin:admin@the-internet.herokuapp.com/basic_auth'
      expect(@browser).to have_content 'Congratulations! You must have the proper credentials.'
    end

    it 'Verify Basic Auth with INVALID credentials', invalid: true  do
      @cls_instance.click_on_link 'Basic Auth'
      @browser.visit 'http://admin:123@the-internet.herokuapp.com/basic_auth'
      expect(@browser).to have_content 'Not authorized'
    end

  end

  context 'Drag and Drop' do

    it 'Should drag and drop Column A to Column B', dnd: true  do
      @cls_instance.click_on_link 'Drag and Drop'
      @browser.execute_script(@cls_instance.drag_and_drop_script)
      expect(@cls_instance.by_id('column-a').text).to eq 'B'
      expect(@cls_instance.by_id('column-b').text).to eq 'A'
    end

  end

  context 'Dropdown box/Select box' do

    it 'Should select all options', drop_down: true  do
      @cls_instance.click_on_link 'Dropdown'
      values = @cls_instance.find_all_drop_down
      nw_vals = values[1..values.length]
      results = []
      nw_vals.each {|x| results << @cls_instance.select_dropdown(x) == 'ok' ? true: false}
      results.each{|x| expect(x).to eq 'ok'}
    end

  end


end

Run the complete Suite of test cases:

complete_suite

Run particular test case from the suite (use 'tag'):

individual_test_case


If you want to exclude few of the test cases from the suite then add the below code in your RSpec,
above the 'describe':

RSpec.configure do |c|
  c.filter_run_excluding valid: true
  c.filter_run_excluding invalid: true
end
describe 'Verifying the components of the_heroku_app' do

  before(:each) do
    @cls_instance = The_Heroku_App.new
    @browser = @cls_instance.start_browser
    @cls_instance.go_to 'https://the-internet.herokuapp.com'
  end
.
.
.
.

When you run the RSpec it will skip those test cases which you have specified with the tag name.

rspec_exclude

Test Result:

Test_result

Note: Post will be updated...

API Testing

What is an API?

Well, an API stands for Application Programming Interface. It is basically the functions exposed by the software developers which makes it easy for other developers/business entities to use them in their products. Got it?

Ok, lets break it down. I believe many of you have used the travelling sites like Expedia, MakeMyTrip etc. Have you ever wondered how these sites know about the flight details, its schedules and seat availabilities? Well, the companies that operates flights expose their apis and travelling sites makes use to integrate those into their sites, thus making availablility of the data. Ok I think this gives you a hint of what an API is? Lets leave it as a further reading for you and jump straight into using scripts.

Python is an elegant language with many supporting libraries and here we will be using ‘requests’ module, which basically handles most of the heavy underlying work and gives the users a simple syntax like requests.get, requests.post.

Ok lets dive into the code, we will use Google’s map api here:

First lets try to use the below link directly into your browser:

http://maps.googleapis.com/maps/api/geocode/json?sensor=false&address=San%20Jose

and you will get a json reply as below:

{
   "results" : [
      {
         "address_components" : [
            {
               "long_name" : "San Jose",
               "short_name" : "San Jose",
               "types" : [ "locality", "political" ]
            },
            {
               "long_name" : "Santa Clara County",
               "short_name" : "Santa Clara County",
               "types" : [ "administrative_area_level_2", "political" ]
            },
            {
               "long_name" : "California",
               "short_name" : "CA",
               "types" : [ "administrative_area_level_1", "political" ]
            },
truncated....

Hope you got an idea of what we will be doing, lets start the code. But first install the requests module using:

pip install requests

Now lets code:

import requests, pprint

google_api = 'https://maps.googleapis.com/maps/api/geocode/json?'

address = input('Enter the location to search for: ')

extra = {'sensor':'false', 'address':address}

r = requests.get(google_api, params=extra)

pprint.pprint(r.json())

In the above code, pprint is for pretty printing, it will print the json response in a indent format. The below will be the response:

google_requests

Yay!  you have sent your first API request from Python. Awesome! Now lets try to test whether we got the results which we are looking for. In the above example we searched for ‘India’, and the first thing we have to make sure is the status code, whether we have successfully received the query, if it is then 200 should be returned.

In below code we will search for ‘India’ first and check for few fields of json and redo the search with a different name, say ‘San Jose’:

import requests, pprint

google_api = 'https://maps.googleapis.com/maps/api/geocode/json?'

address = input('Enter the location to search for: ')

extra = {'sensor':'false', 'address':address}

r = requests.get(google_api, params=extra)

pprint.pprint(r.json())

#print(r.json()['results'][0]['types'][0])
#print(r.json()['results'][0]['address_components'][0]['long_name'])

if r.status_code == 200:
 print("Status Code == 200")
else:
 print("Failed")
 
if r.json()['results'][0]['types'][0] == 'country':
 print("Search for 'India' results in type == country")
else:
 print("Search is not a Country")
 
if r.json()['results'][0]['address_components'][0]['long_name'] == 'India':
 print("Long name field has name of India")
else:
 print("Long name field does not have name == India")

India_google

Now search for 'San Jose' will result in:

san_jose_google

Hope you enjoy!

Continuing on rspec…

As we have seen in the previous post on a simple rspec, it consists of keywords like, ‘describe’, ‘context’, ‘it’, ‘expect’ etc. Let’s try to understand each of them:

describe: It is a keyword which describes the class under test. For example, if we have a class by the name ‘Zoom’ then we can write:

describe Zoom do        
  ...                  
end                     

[OR]

describe "test suites for class zoom" do
  ...
end

context: This is used to have a group of test cases in the same view. Like one can group a set of valid test cases by categorizing them into groups of ‘valid’, ‘invalid’ etc.

context "Set of valid test data" do
  ...
end

it: This is where we write our test scripts for verifying the class under test.

it "is expected to return 'true' after execution" do
  ...
end

expect: The keyword ‘expect’ takes an argument called ‘actual’ value and compares it with the expected value.

expect('<return_value'>).to eq('<expected_value>')

Now let us try to write a new ruby class called ‘Zombie’ in a new file called ‘zombie.rb’that consists of below details:

class Zombie
  attr_accessor :name, :age, :str, :num

  def initialize
    puts "\n New instance of Zombie..."
    @name = "zom"
    @age = 2
  end

  def wish_me(tes)
    "Hello #{tes}"
  end

  def am_i_vowel?(str)
    !!(str =~ /[aeiou]+/i)
  end

  def am_i_greater?(num)
    num > 10 ? true:false
  end
end

Let us write an rspec to test this class and its methods. Create a new file called 'zombie_spec.rb' and fill in the below details:

require 'rspec'
require 'zombie'

describe Zombie do

  before(:each) do
    @class_instance = Zombie.new
  end

  context 'check name and wish_me' do
    #it 'is named zom', :slow => true do
    it 'is named zom', slow: true do
      #zombie = Zombie.new
      #expect(zombie.name).to eq('zom')
      #expect(zombie.age).to eql 2
      #sleep 20
      expect(@class_instance.name).to eq 'zom'
      expect(@class_instance.age).to eq 2
    end

    it 'Tell Zombie to wish_me' do
      #zombie = Zombie.new
      #msg = zombie.wish_me('Ron')
      #expect(msg).to eq('Hello Ron')
      msg = @class_instance.wish_me('Ron')
      expect(msg).to eq 'Hello Ron'
    end
  end

  context 'check if string has vowels' do
    it 'should have vowels' do
      #g = Zombie.new
      #expect(g.am_i_vowel?('Testing')).to be true
      expect(@class_instance.am_i_vowel?('Testing')).to be true
    end

    it 'Should not have VOWELS' do
      #g = Zombie.new
      #expect(g.am_i_vowel?('tyrp')).to be false
      expect(@class_instance.am_i_vowel?('typ')).to be false
    end
  end

  context 'Check if a number is greater than 10' do
    it 'The number should be grater than 10' do
      #number = Zombie.new
      #expect(number.am_i_greater?(5)).to be false
      expect(@class_instance.am_i_greater?(5)).to be false
    end
  end
end

I am using 'RubyMine' IDE to run the code. Below is the output after running the zombie_spec.rb:

      Testing started at 7:57 PM ...

        New instance of Zombie...

        New instance of Zombie...
        New instance of Zombie...

        New instance of Zombie...

        New instance of Zombie...

      5 examples, 0 failures, 5 passed

      Finished in 0.046516 seconds

      Process finished with exit code 0

Learning rspec!

In this blog, we will learn about rspec, a BDD (Behavioural Driven Development) for the Ruby Programming language. It is easy to learn and any person with some programming background can follow it.

Make sure you have Ruby installed.

Follow the below steps to install the rspec gem.

  • Open ‘git bash’.
  • Enter ‘gem install rspec’, it will automatically install all the dependencies.
  • Enter ‘rspec -v’ to see the version.
  • Make a folder, say ‘/Ruby_Testing’ and inside this folder create another folder called ‘spec’.
  • Create a small Hello-world class in Ruby as shown below.
Create a Ruby class called, 'hello_world.rb' with below content in it:

class Hello_World
  def say_hello
    "Hello World"
  end
end

Create another Ruby file called, 'hello_spec.rb' and fill in with:

require 'rspec'
require 'C:\..\..\Desktop\Temporary\spec\hello_spec'

describe Hello_World do
  it "when say_hello function is called, it should say Hello World" do
    wish = Hello_World.new
    expect(wish.say_hello).to eq "Hello W0rld"
  end
end

Now let's run the above code from gitbash command line:

rspec --format doc <test_spec.rb>

The above command will give the detailed output of the test result.

Failing_rspec

Now let's correct it and re-run the program:
require 'rspec'
require 'C:\..\..\Desktop\Temporary\spec\hello_spec'

describe Hello_World do
  it "when say_hello function is called, it should say Hello World" do
    wish = Hello_World.new
    expect(wish.say_hello).to eq "Hello World"
  end
end

Now we get the correct result:

Correct_rspec.png