Affichage des articles dont le libellé est casperjs. Afficher tous les articles
Affichage des articles dont le libellé est casperjs. Afficher tous les articles

mercredi 27 mai 2015

CasperJS UI testing best practices

CasperJS is an open source UI testing tool you can plug over PhantomJS, the famous webkit headless browser. From javascript code, you can simulate in a test all possible user interactions with your web interface. For example you can click on a link, fill and submit a form, and check your DOM elements.

Just to be clear : as you interact directly with the browser, you can use CasperJS whatever is your server side technology.

I started to use this tool approximatively two years ago and wanted to share some best practices I use in my UI tests.

#1 : structure your tests with Page Object Pattern


This tip is definitively the most important. UI tests are essential but have a main drawback : when your DOM changes, your tests are broken.

The concept of the Page Object Pattern is to have two kinds of objects in your tests : the Page objects and the scenarios :

  • Each page object represents a real page of your web application and is an abstraction of the page DOM
  • A scenario uses several page objects to describe the user navigation and has no idea on how are structured the web pages

Applying this pattern, you will centralize in a single place the CSS or XPATH selectors of a page. If you change the page DOM, you won't need to modify several tests but only the corresponding Page object.

I already covered this topic in another thread where you will find a complete example of the Page Object Pattern with CasperJS.

#2 : use Resurrectio recorder


Resurrectio is a useful Chrome plugin that records your actions in order to generate a CasperJS script : just activate it, do your actions on your web interface, then generate the script and your test is ready, or almost...

The plugin is not perfect and as for every recorder, you will need to refactor the code (especially with Page Object Pattern). Plus, some events are forgotten by the recorder and you will need to fix some CSS selectors. BUT it is still very useful and I use it as soon as I want to create a new test on new web pages.

#3 : empower your screenshots


With CasperJS, you can do at every moment in your test execution a screenshot of your web page. For example :

casper.then(function() {
    this.capture('beforeFillingTheConnectionForm.png');
});

You can still give a comprehensive name to your screenshot file like in my example but when you will debug a failing test, you will quickly need some additional information. The best you can do here is to create an utility method where you can customized all screenshot filenames. From that you will do very powerful screenshots :

exports.capture = function(screenshotName) {
   casper.then(function() {
    var testFile = this.test.currentSuite.file;
    if (testFile.indexOf('/') != -1) {
     testFile = testFile.substring(testFile.lastIndexOf('/') + 1);
    }
    testFile = testFile.replace('.js', '');

    var testName = this.test.currentSuite.name;

    var screenshotFile = 'screenshots/' + testFile + '/' + testName 
               + '/' + screenshotName + '.png';
    this.capture(screenshotFile);
   });
};

With this utility method, your screenshots are now saved in a folder screenshots/<testFilename>/<testName>. For example "screenshots/connectionScenario/Should connect then disconnect/".

As far as I know, this.test.currentSuite object is not documented and I cannot assure you that CasperJS contributors will keep this variable in the future CasperJS versions.

#4 : manage several environments


You surely need to execute your tests on several environments : local, tests, preproduction and so one. To manage that, you can create a config file per environment to store the environments urls or for example the user credentials. Then you set a variable telling which environment you want to use directly in CasperJS command line.

For example, you can have your local configuration in the file "config/env/local.js" :

{
  url: 'http://localhost:8080',
  login: 'dev',
  password: 'dev'
}

Your preproduction configuration in the file "config/env/preproduction.js" :

{
  url: 'http://preproduction',
  login: 'admin',
  password: 'admin'
}

Then in a file "config/config.js", you can load the good environment file according to the command line arguments :

var environment = casper.cli.get('env');

if (!environment) {
 console.log('Usage: "casperjs test --env= "');
 casper.exit(1);
}

console.log('Loading the ' + environment + ' config.');

var environmentConfig = require('./env/' + environment + '.js');
module.exports = environmentConfig;


#5 : use your favorite building tool


On a Java based web, project I started to execute CasperJS directly from Maven thanks to this thread. It is interesting because Maven directly handles PhantomJS and CasperJS installation. Quite appreciable for your development workstations and for your favorite continuous delivery tool.

I haven't tried yet Grunt or Gulp implementations but you will easily find that on Google ;-)

Conclusion


I hope you enjoyed these tips. Don't hesitate to give me your opinion on this thread or to share your own best practices with me.

Happy UI testing !



dimanche 9 mars 2014

Page Object pattern with CasperJS

In this article I will quickly introduce an UI test framework, CasperJS, and how we can improve UI tests maintainability using the Page Object Pattern. The goal is not to cover all CasperJS features but to show how to write maintainable tests.

CasperJS is an open source tool used to test your web application user interface. From javascript code, you can simulate in a test all possible users interactions with your interface. For example you can click on a link, fill and submit a form, and finally check your DOM elements.

To start, let's see a concrete example of a CasperJS test and after we will see how to refactor the written tests using the Page Object pattern.

Our example

We will test three pages of the spring travel application :
  • the login page
  • the hotels search page & bookings listing
  • and the hotels search result page

Login page

Hotels search page & bookings listing


Hotels search result page

For our first CasperJS test, we want to cover the following scenario :
  • the user fills and submits the login form
  • the user arrives on the bookings listing
  • the user can see his last bookings
Here is the CasperJS test :

casper.test.begin('When I connect myself I should see my bookings', function (test) {
  casper.start(casper.cli.options.baseUrl + '/login');

  casper.then(function () {
    test.assertExists('form[name="f"]', 'Is on login page');
  });

  casper.then(function () {
    this.fill('form[name="f"]', {
      'j_username': 'scott',
      'j_password': 'rochester'
      }, false);
  });

  casper.then(function () {
    this.click('form[name="f"] button[type="submit"]', 'Login submit button clicked');
  });

  casper.then(function () {
    test.assertUrlMatch('hotels/search', 'Is on search page');
    test.assertTextExists('Current Hotel Bookings', 'bookings title are displayed');
    test.assertExists('#bookings > table > tbody > tr', 'bookings are displayed');
  });

  casper.run(function () {
    test.done();
  });
});

As you can see this test is very fluent and easily readable. Some explanations :
  • casper.start starts the scenario on a given url
  • casper.then sections describe a specific user action or some assertions.
  • fill and click methods allow to simulate user actions
  • assertExists, assertUrlMatch and assertTestExists allow to check the DOM content
  • casper.cli.options.baseUrl allows to get a custom parameter passed on the casper js command line
Now let's cover a little bit more complex scenario in a new CasperJS test :
  • the user fills and submits the login form
  • the user arrives on the hotels search page
  • the user fills and submits the hotels search page
  • the user can see several hotels in Atlanta

casper.test.begin('When I connect myself and search hotels in Atlanta 
Then should find three hotels', function (test) {
  casper.start(casper.cli.options.baseUrl + '/login');

  casper.then(function () {
    test.assertExists('form[name="f"]', 'Is on login page');
  });

  casper.then(function () {
    this.fill('form[name="f"]', {
      'j_username': 'scott',
      'j_password': 'rochester'
      }, false);
  });

  casper.then(function () {
    this.click('form[name="f"] button[type="submit"]', 'Login submit button clicked');
  });

  casper.then(function () {
    test.assertUrlMatch('hotels/search', 'Is on search page');
  });

  casper.then(function () {
    this.fill('form[id="searchCriteria"]', {
      'searchString': 'Atlanta'
      }, false);
  });

  casper.then(function () {
    this.click('form[id="searchCriteria"] button[type="submit"]');
  });

  casper.then(function () {
    test.assertUrlMatch('hotels?searchString=Atlanta', 'Is on search result page');
    test.assertElementCount('#hotelResults > table > tbody > tr', 3, '3 hotels have been found');
  });

  casper.run(function () {
    test.done();
  });
});


Again the test is fluent and readable. But a lot of code has just been copy/paste and we have now several duplicated lines of code.

How can we factorize that? By creating some utils methods? Not exactly, it is here that comes the famous Page Object pattern!

Page Object pattern

Page Object pattern is described on Martin Fowler website.

Page Object pattern by Martin Fowler


The main ideas are :

The test must not manipulate directly the page UI elements. This manipulation must be done within a Page Object which represents an UI page or an UI page fragment. The Page Object makes a complete abstraction of the underlying UI and it becomes an API where it is possible to easily find and manipulate the page data.

This encapsulation has two benefits : the test logic is about user intentions and not about UI details, so it is easier to understand. Plus, if the UI is modified, this will affect only the Page Objects and not the tests.

Asynchronism behavior of the pages must also be hidden by the Page Object. It is a specific behavior of your UI and you don't want to make it appear in your tests.

For a single page, you can have several Page Objects if there are several significant elements on the page. For example you can have a Page Object for the header and one for the body.

Assertions responsibility can be in the Page Object or in the test. In the Page Object it helps avoid duplication of assertions in the tests, but the Page Object responsibility becomes more complicated as it is responsible to give access to the page data, plus to have the assertion logic.

This pattern can be apply for any UI technologies : HTML pages, java swing interfaces or others UI.

Now, let's use the Page Object pattern for our CasperJS tests.

CasperJS with Page Object pattern

In our test, we navigate through three pages, so we will create three Page Objects : LoginPage, SearchPage and SearchResultPage.

Our first page is the login page. Here we must be able to start the scenario on this page, to check that the page is correct, and to fill and submit the login form. We will do that with four methods : startOnLoginPage, checkPage, fillForm and submitForm. All of these methods are created in a LoginPage object in a LoginPage.js file :

function LoginPage() {

  this.startOnLoginPage = function () {
    casper.echo("base url is : " + casper.cli.options.baseUrl);
    casper.start(casper.cli.options.baseUrl + '/login');
  };

  this.checkPage = function () {
    casper.then(function () {
      casper.test.assertUrlMatch('login', 'Is on login page');
      casper.test.assertExists('form[name="f"]', 'Login page form has been found');
    });
  };

  this.fillForm = function (username, password) {
    casper.then(function () {
      this.fill('form[name="f"]', {
        'j_username': username,
        'j_password': password
      }, false);
    });
  };

  this.submitForm = function () {
    casper.then(function () {
      this.click('form[name="f"] button[type="submit"]', 'Login submit button clicked');
    });
  };
}

Now we need a page for the search. We need to check the page, to check that the user bookings are displayed, and to fill and submit the search form. We will do that on a SearchPage object in the file SearchPage.js :

function SearchPage() {

  this.checkPage = function () {
    casper.then(function () {
      casper.test.assertUrlMatch('hotels/search', 'Is on search page');
    });
  };

  this.checkThatBookingsAreDisplayed = function() {
    casper.then(function () {
      casper.test.assertTextExists('Current Hotel Bookings', 'bookings title are displayed');
      casper.test.assertExists('#bookings > table > tbody > tr', 'bookings are displayed');
    });
  };

  this.fillSearchForm = function(searchTerms) {
    casper.then(function () {
      this.fill('form[id="searchCriteria"]', {
        'searchString': searchTerms
        }, false);
    });
  };

  this.submitSearchForm = function() {
    casper.then(function () {
      this.click('form[id="searchCriteria"] button[type="submit"]');
    });
  };
}

Finally we need a Page Object for our SearchResultPage. Here we just want to check the page and to check that the results are correctly displayed :

function SearchResultPage() {

  this.checkPage = function () {
    casper.then(function () {
      casper.test.assertUrlMatch('hotels?searchString=', 'Is on search result page');
    });
  };

  this.checkThatResultsAreDisplayed = function(expectedCount) {
    casper.then(function () {
      casper.test.assertElementCount('#hotelResults > table > tbody > tr', expectedCount, expectedCount + ' hotels have been found');
    });
  };
}

Now we can use these three Page Objects in our test :

phantom.page.injectJs('LoginPage.js');
phantom.page.injectJs('SearchPage.js');
phantom.page.injectJs('SearchResultPage.js');

var loginPage = new LoginPage();
var searchPage = new SearchPage();
var searchResultPage = new SearchResultPage();

casper.test.begin('When I connect myself I should see my bookings', function (test) {
  loginPage.startOnLoginPage();
  loginPage.checkPage();
  loginPage.fillForm('scott', 'rochester');
  loginPage.submitForm();

  searchPage.checkPage();
  searchPage.checkThatBookingsAreDisplayed();

  casper.run(function () {
    test.done();
  });
});

casper.test.begin('When I connect myself and search hotels in Atlanta 
Then should find three hotels', function (test) {
  loginPage.startOnLoginPage();
  loginPage.checkPage();
  loginPage.fillForm('scott', 'rochester');
  loginPage.submitForm();

  searchPage.checkPage();
  searchPage.fillSearchForm('Atlanta');
  searchPage.submitSearchForm();

  searchResultPage.checkPage();
  searchResultPage.checkThatResultsAreDisplayed(3);

  casper.run(function () {
    test.done();
  });
});

Our two tests are now more readable and there is a complete abstraction of UI elements. If you modify your HTML code, you will easily identify which page to modify and you won't have impacts on your tests.

Variant n°1 : use four Page Objects

The search page provides the search form and the bookings listing. I choose to modelize that in a single Page Object. But another option is to create two Page Object : one for the search and one for the listing. The BookingListingPage.js is now :

function BookingListingPage() {

  this.checkPage = function () {
    casper.then(function () {
      casper.test.assertUrlMatch('hotels/search', 'Is on booking listing page');
    });
  };

  this.checkThatBookingsAreDisplayed = function() {
    casper.then(function () {
      casper.test.assertTextExists('Current Hotel Bookings', 'bookings title are displayed');
      casper.test.assertExists('#bookings > table > tbody > tr', 'bookings are displayed');
    });
  };
}

And our first test becomes :

casper.test.begin('When I connect myself I should see my bookings', function (test) {
  loginPage.startOnLoginPage();
  loginPage.checkPage();
  loginPage.fillForm('scott', 'rochester');
  loginPage.submitForm();

  bookingListingPage.checkPage();
  bookingListingPage.checkThatBookingsAreDisplayed();

  casper.run(function () {
    test.done();
  });
});

Variant n°2 : keep assertions in tests

I choose to write the assertions directly in the Page Objects. Thess objects have then two responsibilities : give an access to the page data and provide assertions. Martin Fowler recommends to distinguish these responsitilibies and to keep assertions in the test. In that case the Page Object provides only an accessor to the page element and it is the test responsibility to check its content. For example for the SearchResultPage Object, the method :

// test
searchResultPage.checkThatResultsAreDisplayed(3);

// page object
this.checkThatResultsAreDisplayed = function(expectedCount) {
    casper.then(function () {
      casper.test.assertElementCount('#hotelResults > table > tbody > tr', expectedCount, expectedCount + ' hotels have been found');
    });
  };

Becomes :

// test
casper.test.assertEquals(searchResultPage.getResultsCount(), 3, '3 hotels have been found');

// page object
  this.getResultsCount = function() {
    return casper.evaluate(function() {
      return __utils__.findAll('#hotelResults > table > tbody > tr').length;
    });
  };


Conclusion

When you code your features, you don’t hesitate to separate the different layers in different objects. It is exactly what the Page Object pattern recommend to do : separate the test logic from the UI layer. In this article I applied it to CasperJS tests but this pattern is also relevant with other tools like for example Selenium, ZombieJS or even Gatling.

Please find the complete code on my github repository about tests.
Please also find my CasperJS best practices here.