How to inject JavaScript using Robot Framework and Selenium - codecentric AG Blog

:

My team has chosen – amongst other TDD approaches – ATDD as the way to go. We are delighted every day by the Robot Framework and the related Selenium Library. We write our tests in a natural language like syntax. For complex web applications, you will sooner or later need to perform assertions for which no standard keywords are available. In this article, I’ll show you how to inject JavaScript into the HTML GUI under test, which enables you to perform complex or unusual assertions

In the case I’m using for this example, we have developed a website on which different insurance products are compared after a couple of calculation steps have been performed. The website is quite smart, thus it is also able to sort the products based on different criteria. Another feature is a filter that allows the customer to – well – filter the products 🙂 If the customer selects one of the filters, certain products will become invisible.

For the tests, this implies that we need to have automated tests for both the sorting and the filter mechanism. I’ll use the filter as an example here. The test case looks roughly like this (I’ve introduced a little abstraction of course):

*** Keyword ***
Behaviour
    [Arguments]  ${Filter}  ${o1}  ${o2}
 
    Given that there is a product page for XYZ 
 
    If the filter is set to ${Filter}
 
    Then product A is optically ${o1}
    And product B is optically ${o2}
 
| *Test Case* | | *Filter*    | *o1*      | *o2*      |
| 1 | Behaviour | no filter   | visible   | invisible |
| 2 | Behaviour | F1          | invisible | visible   |
| 3 | Behaviour | F2          | visible   | invisible |

*** Keyword *** Behaviour [Arguments] ${Filter} ${o1} ${o2} Given that there is a product page for XYZ If the filter is set to ${Filter} Then product A is optically ${o1} And product B is optically ${o2} | *Test Case* | | *Filter* | *o1* | *o2* | | 1 | Behaviour | no filter | visible | invisible | | 2 | Behaviour | F1 | invisible | visible | | 3 | Behaviour | F2 | visible | invisible |

There is no predefined keyword for visibility checks in the Selenium Library. In the Selenium IDE you can use “assertVisible”, but we prefer to use JavaScript. Our templates use Prototype, which provides quite a couple of very comfortable methods. I had the following idea how I might check the visibility of an element:

  1. Add a hidden input to the DOM tree
  2. Set the hidden input’s value to true or false, which indicates visibility of the targeted element
  3. Finally, check that input’s value using “Text Field Value Should Be”.

Okay, so I wrote the following JavaScript function that performs the hidden input magic:

function prepareHiddenField() {
  if(!$('HIDDENROBOT')) {
    Element.insert(
      $('products'),
      new Element(
        'input',
        {type:'hidden', id:'HIDDENROBOT'}
      )
    );
  }
 
  $('HIDDENROBOT').value = $('product_A').visible();
}

function prepareHiddenField() { if(!$('HIDDENROBOT')) { Element.insert( $('products'), new Element( 'input', {type:'hidden', id:'HIDDENROBOT'} ) ); }$('HIDDENROBOT').value = $('product_A').visible(); }

In order to inject this function into the page being tested, it has to be transformed into an anonymous function that immediately calls itself. Also, the entire JS code must be put on a single line in order to avoid line break foobar in the BDD file. Kudos to those who can read this – here is the result, spiced up with a variable “pid” as in “product ID”:

(function(){!$('HIDDENROBOT') && Element.insert($('products'), new Element('input', {type:'hidden', id:'HIDDENROBOT'})); $('HIDDENROBOT').value=$('product_${pid}').visible();})()

(function(){!$('HIDDENROBOT') && Element.insert($('products'), new Element('input', {type:'hidden', id:'HIDDENROBOT'})); $('HIDDENROBOT').value=$('product_${pid}').visible();})()

So – now we can write the required Robot keywords:

Prepare hidden visibility field for ${product}
	${pid} =  productId for  ${product}
	Call Selenium Api  runScript  theJavaScriptAsShownAbove
 
product ${product} is optically visible
	Prepare hidden visibility field for ${product}
	Text Field Value Should Be  HIDDENROBOT  true
 
product ${product} is optically invisible
	Prepare hidden visibility field for ${product}
	Text Field Value Should Be  HIDDENROBOT  false

Prepare hidden visibility field for ${product} ${pid} = productId for ${product} Call Selenium Api runScript theJavaScriptAsShownAbove product ${product} is optically visible Prepare hidden visibility field for ${product} Text Field Value Should Be HIDDENROBOT trueproduct ${product} is optically invisible Prepare hidden visibility field for ${product} Text Field Value Should Be HIDDENROBOT false

This example may seem to lack elegance, but I hope you see the point I’m trying to make. In many cases, CSS or XPath selectors won’t help you much, for example if you intend to check style attributes set by visual effects. To do such things, JavaScript injection is a very handy tool.

I mentioned earlier that we test the re-sorting of the compared products as well, so I’ll give you another example which is a little more complex. Upon sorting, the order of elements inside the DOM tree is changed. Thus, we need to assert that those DOM tree elements have the correct order after they have been sorted.

To do this, we require n hidden inputs, matching n products being compared. Here’s an excerpt from the test case, using hard-coded values:

Then product B is in position ${posB}
And product A is in position ${posA}

Then product B is in position ${posB} And product A is in position ${posA}

So, let’s create a hidden input for each product:

(function(){
  Element.insert(
    $('products'),
    new Element(
      'input',
      {type:'hidden', id:'HIDDENROBOT${index}', value:$$('div.product')[${index}-1].id}
    )
  );
})()

(function(){ Element.insert( $('products'), new Element( 'input', {type:'hidden', id:'HIDDENROBOT${index}', value:$$('div.product')[${index}-1].id} ) ); })()

The required position is passed as the variable “index”, 1-based, so that we need to reduce it by one in order to get a correct index for the element array collected by $$(‘div.product’).

Here goes the JavaScript in one line again:

(function(){ Element.insert($('products'), new Element('input', {type:'hidden', id:'HIDDENROBOT${index}', value:$$('div.product')[${index}-1].id})); })()

(function(){ Element.insert($('products'), new Element('input', {type:'hidden', id:'HIDDENROBOT${index}', value:$$('div.product')[${index}-1].id})); })()

And the entire keyword for the position check:

product ${product} is in position ${index}
	${pid} =  productId for  ${product}
 
	Call Selenium API  runScript  theJavaScriptAsShownAbove
	Text Field Value Should Be  HIDDENROBOT${index}  product_${pid}

product ${product} is in position ${index} ${pid} = productId for ${product} Call Selenium API runScript theJavaScriptAsShownAbove Text Field Value Should Be HIDDENROBOT${index} product_${pid}

In this case, we did really not have any chance to use the default Selenium or Robot toolbox. As you can see, injecting JavaScript is a great tool in order to build very complex tests.

Kindly let me advise you that we are offering an ATDD training with Elisabeth Hendrickson on June 21/22 2010. There are still seats available – come join us! 😉