Overview

This tutorial demonstrates how one can deal with data held in HTML tables using the sample ATM application that can be accessed at http://www.inflectra.com/SampleATM.

For this example, let’s assume that the test engineer has to implement an automated test for the ‘ACCOUNT BALANCE’ page in the application:

This page has a table with several columns. Our goal is to validate it in the following way:

  1. Check that number of rows in it is more than 10
  2. Check that account #190001 belongs to Jess Smith
  3. Verify that Anette Dellatolas has maximum amount of Savings among all accounts

As we can see from these scenarios we will allow the table to be dynamic (i.e. the data in it may change). We don’t require the whole table to stay the same between different test executions. All we want is to make sure that certain table-wide properties are checked. For example, it is seen from the screenshot the number of data rows in the table is 12. But our goal is to know that it is more than 10, so once a couple of data rows are removed or added the test will stay valid.

Another important note here is that there are no assumptions about row order in a given table. All we need to know that certain rows are present (Jess Smith and Anette Dellatolas). But we are intentionally avoiding any assumptions about their order.

Static vs Dynamic Test

Now, Rapise allows you to easily check that certain cell in the table has certain value. One may simply use the ‘Verify’ feature and check a value of a specific table cell. This will allow us to easily build a simple and working test. However, this test will no longer work when something is added to the table. This kind of test is considered ‘Static’ since it assumes that the same cell always stays at the same position in the table. We do not recommend static tests when dealing with dynamic data stored in HTML tables as it makes your tests very “brittle” and not accommodating of change in the application.

Our goal therefore is to unlink the data value from its position in the screen. Anything may appear before or after ‘Jess Smith’ and the test should not start failing every time this happens. I.e. the test should allow dynamic data change and be focused on actual data rather than on its position. We call such data position change-friendly tests “Dynamic”. We recommend using Dynamic tests wherever possible and it makes your testing process more fluid and agile.

Preparation

The first thing that we need to do is to record the steps necessary to reach the ‘ACCOUNT BALANCE’ page. The sample application requires users to sign on before reaching this page. So we need to create a new test, and record the login procedure:

You need to login with the sample user – user@mycompany.com password = rapise.

Next, click on ‘BALANCE’ button:

And that will take us to the screen that displays the balance table:

Finally we do one special thing. We learn (using Ctrl+2) one of the cells (with ‘177656’ ACCT#). We will need it as a reference to the whole table. So after finishing the recording session we have the following script:

Let’s see the learned table cell in more details:


As a beginning we are going to make several tweaks to this object:

  1. Change name to ‘BalanceTable’ and set ‘Ignore Object Name’ to ‘True’.
  2. Change ID to ‘BalanceTable’
  3. Tweak XPath. Currently it points to a certain table cell:

It is learned as:

/html/body/table[2]/tbody/tr/td[6]/table/tbody/tr[7]/td[2]

Where td[2] means ‘2nd cell in the row’ and tr[7] means 7th row in the table. So we just cut trailing part to climb on the table level. Using the DOM inspector tool we may see how this trailing part matches actual page contents:


File:WorkingWithHTMLTables08.png

So we are cutting the last part of the XPath to point at the whole table body.

Finally object and its properties look as follows:

To check that everything is correct right-click on the ‘BalanceTable’ object and choose ‘Flash’.

The whole table is then flashed in the web browser:

So now we have an object representing the whole table. Next we want to extract some data from it so that we can achieve our automated testing goals.

Goal #1: Check number of rows

At this point we have a table. To find out something about rows we need to access them using our BalanceTable object. Each HTML object has special method designed to get query data:

DoDOMQueryXPath(xpath)

You may see it if in auto-completion window:

 

File:WorkingWithHTMLTables12.pngIt has only one parameter – XPath expression. It is expected to take the XPath expression related to a given object and return an array of matching HTML Objects. So we have a table and a number of <tr> elements inside it. The following XPath will return all <tr>s inside this table:

./tr

This means "Give me all TDs that are direct children of this element". We expect it to find multiple children so the returned result will be an array. Let's put the array into a variable 'rows':

var rows = SeS('BalanceTable').DoDOMQueryXPath('./tr');

Guess how many rows are found? Let's print this value to the output using the Tester.Message() function:

var rows = SeS('BalanceTable').DoDOMQueryXPath('./tr');
Tester.Message ("Rows count:"+rows.length);

Press Play and see what we have as a result:

Rows count is 28! We see only 12, where is the rest? Let's print each row one-by-one and see contents.

// Get all rows from the table

var rows = SeS('BalanceTable').DoDOMQueryXPath('./tr');
Tester.Message("Rows count:"+rows.length);

// Print 'InnerText' for each row element (i.e. full row contents)

for(var i=0;i<rows.length;i++)
{
    Tester.Message("Row: "+i+" InnerText:"+rows[i].GetInnerText());
}

GetInnerText() is a property reflecting the well-known innerText property of DOM elements. Each HTMLObject has it. So, what we see after playing it back?

So we have 15 rows without any text. It is easy to learn more about available rows using 'Developer Tools' built into IE when you press F12 (or DOM Inspector from FireFox or 'Inspect Element' feature in Chrome). We see that Row 0 contains table header. This gives us 1 row:

Also we see that Rows 1, 3, 5, etc have empty text. If we highlight the row #6 (it has index 5 because indexing starts from 0) then we see:

That empty rows are those thin blue lines separating different rows. So we identified that each row with data is followed by another row. So we have:

1 Header
+       1 Header separator
+       12 Rows with data
+       12 Separators
---------------------------------------------------------------------------------
        26 Rows

But our experiment gives us 28 rows. Let's check what is there in last 2 rows:

So there is some spacing after the table data. And last 2 rows used for that spacing. So we found the meaning of last 2 rows and now identified all 28 items. Now it is easy to derive following facts:

  1. The number of visible data rows in table is (N-4)/2
  2. i-th data row has index: 2+2*i

Now we can discover the of cells inside the data row: Cell 0 is spacing:

Cell 1 is account #:

Cell 2 is Name, Cell 3 is Account type, Cell 4 is Balance and cell 5 is another separator. Now we can collect our knowledge into a function: ExtractTableData

// Extract all data from the Balance table

function ExtractTableData()
{
      // Array that will contain all rows
      var tableData = [];    

      // Get all rows from the table
      var rows = SeS('BalanceTable')._DoDOMQueryXPath('./tr');
      Tester.Message("Rows count:"+rows.length);

      // Extract valuable data from the table into 'TableData' structure
      for(var i=2; i<rows.length-2; i=i+2)
      {
            var cells = rows[i]._DoDOMQueryXPath('./td');

            // Each row is a structure with fields: id, name, type, balance
            // Create empty structure to contain row data
            var rowData={};

            // And fill it according to known structure
            if(cells.length==6)
            {

                  // Cell #1 - ACCT #
                  rowData.id = cells[1].GetInnerText();

                  // Cell #2 - NAME
                  rowData.name = cells[2].GetInnerText();

                  // Cell #3 - ACCT TYPE
                  rowData.type = cells[3].GetInnerText();

                  // Cell #4 - BALANCE
                  rowData.balance = cells[4].GetInnerText();
            }

            // Finally add the row to the array
            tableData.push(rowData);
      }

      return tableData;
}

So we got following the result of this function is an array of structures. Each array item is a row. Each field of structure is a text of certain cell. This way we get:

var tableData = ExtractTableData();

Array tableData now filled. The number of items in JavaScript array is:

tableData.length

So we may implement the check corresponding to Goal #1. We may verify that number of rows is not less than 10:

Tester.Assert("Goal #1: Number of rows >= 10", tableData.length>=10);

Goal #2: Check that account #190001 belongs to Jess Smith

Now we want to quickly use the results of our previous efforts. We put all table data into our array. Now the ACCT # of the row with number i is accessible as:

tableData[i].id

Name is

tableData[i].name

We can go through all rows and make sure that 190001 is Jess:

// Goal #2

for(var i=0;i<tableData.length;i++)
{
    if(tableData[i].id=="190001")
    {
         Tester.Assert("Account 190001 belongs to Jess Smith", tableData[i].name=="Jess Smith");
         break; // Break from the ‘for’ loop
    }
}

This code is quite simplified. Of course we also need to check the case when there is no 190001 in the list. We skip this check here to simplify the code but it is available in the sample project attached to this tutorial.

Goal #3: Verify that Anette Dellatolas has maximum amount of Savings among all accounts

We have all the data and can easily access the values. We can get the name in the ith row as:

tableData[i].name

and

tableData[i].balance

is textual representation of balance. We need to compare balance values between each other and thus what we need it to be able to convert text string like:

        $21,000.00

Into decimal representation:

        21000.00

It is quite an easy goal in JavaScript. We are going to get rid of $ and "," symbols and then convert the string to a number using built-in function parseInt:

var balance = tableData[i].balance;

// Get rid of "$" and "," using the ‘replace’ function:
balance = balance.replace("$","");
balance = balance.replace(",","");
var balanceNum = parseInt(balance);

 

This code snippet converts balance test into a number and puts it into balanceNum variable. So the whole way to Goal 3 is:

// Goal #3: Max deposit belongs to Anette Dellatolas

var maxBalance = -10000.0;
var maxBalanceOwner = "Not Found";

for(var i=0;i<tableData.length;i++)
{
    var balance = tableData[i].balance;

    // Get rid of "$" and "," using the ‘replace’ function:
    balance = balance.replace("$","");
    balance = balance.replace(",","");
    var balanceNum = parseInt(balance);

    if(balance>maxBalance)
    {
        maxBalance = balance;
        maxBalanceOwner = tableData[i].name;
    }
}

Tester.AssertEqual("Max deposit belongs to Anette Dellatolas", maxBalanceOwner,"Annette Dellatolas");

Tester.Message(‘Max balance value: ‘+maxBalance);

 

Now let’s summarize what we did.

Summary

We have an automated Rapise test with 3 testing goals.

  1. We learned how to handle the HTML Table with complex custom structure and how to extract and modify its data.
  2. We used the Object Properties editor to change the ID, name and XPath locator
  3. We utilized JavaScript arrays and structures to collect the table data
  4. We ended up with a working test script verifying all 3 testing goals: