Data Transformation between different Codebases

I came across an interesting situation. My application under test requires to send out emails/ICS and I had to write automation tests to ensure the emails/ICS reach the recipient.

Abiding by the "Every test is Independent" rule, data is seeded as part of every test (cucumber scenarios) and destroyed after the test is run. And I also require my framework to re-validate the response from the Inbox back into the application.

To solve this situation, I made use of the Hooks in cucumber and the almightly "JSON".

How?

Lets take an example data of a Order Summary for an ecommerce website. Considering that as our AUT (application under test), we will then know that an order summary will have details like

  • Product Details
    • Product Name
    • Product Quanitity
    • Product Cost
  • Sender Details
  • Recipient Details

There can be multiple products under Product Details. The email is sent with these details to the buyer needs to be asserted. As a best practice,

Always create a class to store your more frequent datasets. For an ecommerce site, Order is the most frequent dataset and when automating, this data set can be used to pass along data across context.

So, a class Order is created as

class Order
    attr_accessor(:order_id, :purchased_date,:recipient,:order_status,:delivery_address,:products)

    def initialize
     self.products = [] # on object creation initialize this to an array
    end

    def to_hash
        hash = {}
        instance_variables.each {|var| hash[var.to_s.delete("@")] = instance_variable_get(var) }
        hash
    end
end

class Product
    attr_accessor(:product_id, :product_name, :quantity, :cost, :seller)
end

Do note that I have created a method to_hash in Order class. This method will convert all instance variables into a hash with Keys as the attribute names.

Cucumber Hooks

The Before and After Hooks in cucumber gets called right before and after every scenario. Add a hook say @hold_on to those scenarios where you do not want to clear the data after scenario completion.

The After hook will have a code to move this order from deletion list (the teardown) to a save list

At the end of all scenarios (at_exit), move all these orders to a list.

def at_exit
    @saved_orders << current_order
    orders_json = @saved_orders.each{|order| order.to_hash}.to_json
    f=File.new("orders.json","w+")
    f.write(order_json).close
end

The orders are now hashed and converted into a JSON and saved.

Now, you can have a different code base, read this JSON and convert it to an Order object at the other end (best practice to manipulate data)

[Capybara] Infinite Scroll Automation

Most modern websites have gone the way of having cleaner, uncluttered views. One of the concepts that goes into buidling a clutter free views is the term "unpagination".

To Quote,

 Infinite scrolling, also known as "endless scrolling," "unpagination," and others, is a technique where additional content for a web page is appended dynamically to the bottom of the page as the user approaches the end of the content. 

In terms of automation, what this means is when you are to assert a set of data, only part of it is available in the view for you to assert. The rest of them only loads when you scroll.

To achieve this, we have make use of some Javascript. for example, consider the following html

    <html>
        ...
        <body>
            <div class='.item'> Item 1 </div>
            <div class='.item'> Item 2 </div>
            <div class='.item'> Item 3 </div>
            <div class='.item'> Item 4 </div>
            <div class='.footer'> Footer message here </div>
        </body>
    </html>

In this case, only the first few items are loaded at Screen1. On reaching the end of the page, the next set loads and so on. How do u say, reach Item 50

The idea is to use scrollIntoView() method. This method can be applied to any JQuery Object. Most pages have a footer (Our example has one). Scroll into view on Footer will trigger the call to pull the next set of items. Now, we would not want to keep scrolling till heaven meets earth, right? So, we bring in an end point. A point at which our code stops scrolling. The code snippet below will scroll till we reach any selector with a specific keyword.

def scroll_until_element_exist(selector,keyword)

    script=%{
        function checkID(){ var allSections = $('#{selector}');
        var result = false;
        allSections.each(function(i, sec) {
            if (!result) {
                result = sec.innerHTML.contains('#{keyword}');
            }
        });
    return result;}
    return checkID();
    }

    meeting_present=false
    count=0
    while(!meeting_present&&count<=100)
        page.execute_script("$('div.footer')[0].scrollIntoView(true)")
        meeting_present=page.execute_script(script)
        count+=1
    end
end

To call the method

 scroll_until_element_exist('.item',"Item50")

will scroll the page till we reach Item 50

Protip Use this method in your base page (if you follow Page Object model) and that you won't have to repeat this code anywhere else.

Moving from Tumblr to Github IO

The Need

I have been blogging quite infreqently these days, infrequent partly due to lack of time and partly due to wrong choice of blogger. Tumblr looked quite easy to manage to begin with. It looked and felt quite compact as well. My needs of blogging required less time to publish and more options to format.

However, I figured tumblr has its own limitations in terms of support for multiple formats. More specifically, I found the need to add more code snippets as and when required and putting them up on Tumblr was rather laborious. Hence decided to move.

Github IO

Github has been the de-facto place to store code and it was only a matter of time, I moved here. Considering my urge to stay hands-on, Setting up a blog on Github IO turned out to be more Terminal-based. This worked well for me.

This also throws up a lot of other possibities. With Github hosting the blogs, I can later do * Link my repos to this * Added code snippets and Gists and place references here * Buy a domain and host few other content there

Given the plethora of things that can be done, Github IO seems to be the right way to go. So, moving all the older posts from Tumblr to Github..

Cucumber - Single column tables

Many a times, we come across a situation, where we need to send out a list of values to a step definition.

The most common way of doing that is using a comma-seperated list. Example:

Given I visit a page 
When I pass value1, value2, value 3 
Then the action must be successful

A more cleaner way of passing arguments will be to use Single column tables. Re-writing the earlier step

    Given I visit a page
    When I pass the following values 
        | value 1 | 
        | value 2 | 
        | value 3 | 

Then the action must be successful

This in turn can be handled from the code as

When /^I pass the following values$/ do |table|
    table.raw.flatten
end

Accessing DOM elements in a HTML 5, CSS 3 setup

In the recent times, to bring about a Fluid UI, CSS3 and Html 5 are being used prominently these days. Some of the practices is having a colorful html element which will not have an action, but will be bound to a regular html element like a checkbox. The user will only see this fancy element and checkbox stays hidden. Now coming to automating this: Capybara for all the right purposes, does not allow actions on hidden elements. And not always will the hidden element be accessible. So what is the next best thing? Using Javascript.

So, Capybara will use an implementation of a JS friendly back-end (selenium web-driver, phantomjs, etc). Use the execute script functionality to do achieve this. Example.

Given our element looks something like this.

<div class="toggle-switch"><input id="all_notifications_enabled" type="checkbox" checked="checked"><label for="all_notifications_enabled"></label></div>

The label element is the UI part and the input element contains the action, and yes, the input element is hidden.

page.execute_script("document.querySelector('[id^=#{id}]').checked=true")

where executescript is an extension of selenium-webdriver’s driver.executescript method.