blog

Let's speed up Protractor's data lookup

posted Jun 20, 2014, 4:18 AM by Max Tardiveau

Protractor is the favored testing framework for end-to-end testing of AngularJS apps. It's quite good, and I especially like the smooth integration with Sauce Labs.

But Protractor can sometimes be very slow. For instance, if we have a page that shows a data table with (say) 3 columns (name, balance and creditLimit) and 20 rows, and I want to get all the values displayed in that table, the "normal" way to retrieve the data in that table would be something like:

// Parameters:
// tableSelector: the css selector for the table rows, e.g. "#leftGridContainer .ngRow"
// columnSelector: the additional css selector for the columns in a row, e.g. ".ng-binding"
// colNames: an array of strings with the names of the columns

var getTableValues = function(tableSelector, columnSelector, colNames) {
  return element.all(by.css(tableSelector)).map(function(row, index) {
    var columns = row.all(by.css(columnSelector));
    return columns.then(function(cols){
      var result = {};
      cols.forEach(function(col, idx) {
        result[colNames[idx]] = col.getText();
        result.rowElm = row;
      });
      return result;
    });
  });
};

This works. The problem is that it takes about 7 seconds -- and that's every time we want to get these values. Because these values may change, we do need to re-fetch them every time we want a fresh look at them. That makes our test script run like molasses on a cold day -- there is a 7 second delay every time we need to check something in that table.

There is a workaround, which is to bypass WebDriver and go straight to the browser. This is more convoluted, but it's well worth it. The idea is to generate a piece of code that will then be run (carefully -- we don't want to disturb it) in the browser. We'll then parse its output.

var getTableValues = function(tableSelector, columnSelector, colNames) {
  return browser.driver.executeScript("return (function(){" +
    "var rows = []; var row = {}; var colNames = " + JSON.stringify(colNames) + ";" +
    "angular.element('" + tableSelector + " " + columnSelector + "')" +
    ".each(function(idx, c) {" +
    "  var colIdx = idx % colNames.length;" +
    "  row[colNames[colIdx]] = $(c).text();" +
    "  if (colIdx == colNames.length - 1) {" +
    "    rows.push(row);" +
    "    row = {};" +
    "  }" +
    "});" +
    "return JSON.stringify(rows);" +
    "})();").
  then(function(s) {
  var data = JSON.parse(s);
  // We have the table data, now supplement it with the WebElement for each row
  return element.all(by.css(tableSelector)).then(function(rows) {
    _.each(rows, function(row, idx) {
      data[idx].rowElm = row;
    });
    return data;
  });
});
};

This can be called like this:

var tableDataPromise = getTableValues('#leftTableDiv .ngRow', '.ngCellText .ng-binding', ["name", "balance", "creditLimit"]);

On my machine, this runs in under 100ms. That is a lot quicker than 7 seconds.

For a relatively simple script, this brought our total execution time from 2.5 minutes to under a minute -- a big improvement!

I wish WebDriver wasn't this sluggish when navigating the DOM, but with this approach, at least, there is a way to address the biggest bottlenecks.

Handling links in JSON

posted May 8, 2013, 12:12 AM by Max Tardiveau

We've been working on our handling of links, and a lot of question have arisen.

There is a lot of confusion over how to represent links>.

One possibility:

   "customer" : "http://rest.logicbeam.com/v1/customer/123"

The problem with that is that it's not immediately obvious to a program that this is a link (it could be a piece of text that just happens to be a URI), and even worse, this tells us nothing about what's on the other side of this link, or how we're related to it.

Another possibility is to have composite links, e.g.:
{
  "href" : "http://rest.logicbeam.com/v1/employee/456",
  "rel" : "owner",
  "title" : "Owner",
  "type" : "http://rest.logicbean.com/v1/employee"
}

This is much more descriptive, although there is still a lot of uncertainty regarding the "proper" values for rel and type.

There is in fact a registry of "legal" values for the rel attribute, but a quick look at it should convince you that it's not all that useful in a REST/JSON context.



It's become quite common now for JSON objects to include a links section, e.g.:

{
  "name" : "Billy Bob's bait shop",
  "address" : "123 Main st, Anytown, USA"
  "links" : [
    {
      "href" : "http://rest.logicbeam.com/v1/employee/456",
      "rel" : "owner",
      "title" : "Owner",
      "type" : "http://rest.logicbean.com/v1/employee"
    }
  ]
}

This makes it very clear, but it's also rather verbose.


http://www.mnot.net/blog/2011/11/25/linking_in_json


1-2 of 2