CasperJS – functional testing for your Magento store

Every change should be carefully tested before the release on the live store to make sure that it does not break any functionality. The most important is to check the main places such as customer registration process, customer log in to the account, adding different types of products to the shopping cart, checkout steps etc. We need to check the mentioned parts for every modification made for the website, these repetitive actions take a lot of time. But what do you think – is it possible to speed up and improve this process? As one of the most obvious solutions is to develop functional tests for the website and, in such way, we will automate all the actions.

As a rule, the functional tests are imitating the real user’s behaviour on the website. Therefore, we can quickly find any critical issue using the automated tests for the main places of the website and solve it before deployment to a live store. There are many existing solutions for the functional tests development, we’ve decided to concentrate on one of the most popular – CasperJS testing framework.
So, to look deeper into the CasperJS role, we start with the explanation of what it is described on http://casperjs.org/:

“CasperJS is an open source testing tool written in JavaScript that helps to simplify the process of checking the changes by reproducing the full scenario of actions by providing the huge number of useful functions and methods.”

Basing on our experience, we can exactly say that CasperJS works without any problems with the projects based on Magento platform. For starting working with CasperJS, we need to install PhantomJS and CasperJS to our local computer. The installation is quite simple, it suits Mac, Linux, Windows and you can find the guide on their official website that also has a documentation – http://casperjs.readthedocs.org /en/latest/installation.html.

Now let’s review the tests for Magento websites. The basic information for our functional tests we took from the project presented on Magento Hackathon which took place in 2014. As an example for this article, we use the tests that have been created to show you how CasperJS works. So, we wrote few tests for Magento websites (ver. 1.9.2.2) with sample data and using them we are able to check the registration process, adding simple and configurable products to the shopping cart, creating order functionality (including all checkout steps).

After installing CasperJS on your computer, you can check the tests and see how they work. Here is what you need in the beginning – local environment with demo Magento website (ver. 1.9.2.2) with sample data and CLI (command line interface). Create a folder for your tests with a configuration file (config.js) inside where all the necessary data is placed.

// Configuration and some useful methods

/**
 * Debug/Verbose
 * ----------------------------------------------------------------------------
 */
var debug_mode = !!casper.cli.get('verbose');
if (debug_mode) {
    debug_mode = true;
    casper.options.verbose = true;
    casper.options.logLevel = 'debug';
}
var colorizer = require('colorizer').create('Colorizer');

/**
 * The view
 * ----------------------------------------------------------------------------
 */

// The viewport size
casper.options.viewportSize = {
    width: 1200,
    height: 900
};
casper.options.waitTimeout = 15000;
/**
 * The HTTP responses
 * ----------------------------------------------------------------------------
 */
casper.options.httpStatusHandlers = {
    200:  function(self, resource) {
        this.echo(resource.url + " is OK (200)", "INFO");
    },
    400:  function(self, resource) {
        this.echo(resource.url + " is nok (400)", "INFO");
    },
    404: function(self, resource) {
        this.echo("Resource at " + resource.url + " not found (404)", "COMMENT");
    },
    302: function(self, resource) {
        this.echo(resource.url + " has been redirected (302)", "INFO");
    }
};

/**
 * Login credentials
 * ----------------------------------------------------------------------------
 */
var login_user_firstname    = 'FirstName';
var login_user_lastname     = 'LastName';
var login_user_middlename   = 'MiddleName';
var login_user_username     = casper.cli.get("email");
if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(login_user_username))
{
    login_user_username
}
else{
    casper.die('Please enter valid email or add email to command line  --email="yourmail@example.com" ');
}

var login_user_password     = 'PassWord';
var login_user_password_bad = "badpassword";

var user_address_company    = 'Atwix';
var user_address_street     = '16243 Ivy Lake Dr.';
var user_address_city       = 'Odessa';
var user_address_region     = 'Florida';
var user_address_postcode   = '33556';
var user_address_telephone  = '1234567890';
var user_address_fax        = '12345678';
var user_address_coundry    = 'United States';

/**
 * Product credentials
 * ----------------------------------------------------------------------------
 */
var prod_url_simple = 'madison-overear-headphones.html';
var prod_name_simple = 'Madison Overear Headphones';
var prod_url_conf ='men/new-arrivals/chelsea-tee.html';
var prod_name_conf = 'Chelsea Tee';


 /* Utils, XPath, FileSystem
 * ----------------------------------------------------------------------------
 */
var utils   = require('utils');
var x       = casper.selectXPath;
var fs      = require('fs');


/**
 * URLs
 * ----------------------------------------------------------------------------
 */
var base_url = casper.cli.get("base_url");
if (!/\/$/.test(base_url)) {
    // We have not trailing slash: add it
    base_url = base_url + '/';
}


// Done for the test file
// ----------------------------------------------------------------------------
casper.test.done();

/**
 * Tear down and set up
 * ----------------------------------------------------------------------------
 */

// Tear down:
// - reset captures counter
casper.test.tearDown(function () {

    // Reset captures counter
    captures_counter = 0;
});

// Set up: nothing
casper.test.setUp(function () {});

/**
 * Steps
 * ----------------------------------------------------------------------------
 */

casper.on("load.failed", function() {
    casper.capturePage();
});

casper.on("load.finished", function() {
    casper.printTitle();
    casper.capturePage();
});

casper.on("fill", function() {
    casper.capturePage();
});

casper.on("mouse.down", function() {
    casper.capturePage();
});

casper.on("mouse.move", function() {
    casper.capturePage();
});

casper.on("mouse.move", function() {
    casper.capturePage();
});

casper.on("step.complete", function() {
    casper.capturePage();
});

casper.on("step.error", function() {
    casper.capturePage('error');
});

casper.on("http.status.500", function() {
    casper.capturePage('500');
});

casper.on("http.status.404", function() {
    casper.capturePage('404');
});

/**
 * Tools and cool methods :)
 * ----------------------------------------------------------------------------
 */

// Clear cookies
casper.clearCookies = function () {
    casper.test.info("Clear cookies");
    casper.page.clearCookies();
};


// Print the current page title
casper.printTitle = function () {
    this.echo('### ' + casper.getTitle() + ' ###', 'INFO_BAR');
};

// Capture the current test page
var captures_counter = 0;
casper.capturePage = function (debug_name) {
    var directory = 'captures/' + casper.test.currentSuite.name;
    if (captures_counter > 0) {
        var previous = directory + '/step-' + (captures_counter-1) + '.jpg';
        if (debug_name) {
            var current = directory + '/step-' + captures_counter + '-' + debug_name + '.jpg';
        } else {
            var current = directory + '/step-' + captures_counter + '.jpg';
        }
        casper.capture(current);

        // If previous is same as current (and no debug_name), remove current
        if (!debug_name && fs.isFile(previous) && fs.read(current) === fs.read(previous) && fs.isFile(current)) {
            fs.remove(current);
            captures_counter--;
            casper.log('Capture removed because same as previous', 'warning');
        }
    } else {
        // We remove the directory to cleanup
        fs.removeTree(directory);
    }
    captures_counter++;
};

Let’s write our first test to check the registration process – it will be testing the registration page and form by filling in all the required fields, creating new customer and verifying the log out process. For this, create a JavaScript file (for example, 01_registration.js) in the created before folder and add the below code:

casper.test.begin('001 Registration new customer ', function suite() {


    casper.start(function () {
        this.open(base_url);
        this.test.assertHttpStatus(200);
        this.test.comment('Open home page');
    });
    casper.thenOpen(base_url +'customer/account/create/', function(){
        this.test.comment('Open and check registration customer page');
        this.test.assertHttpStatus(200);
        this.test.assertUrlMatch(base_url +'customer/account/create/', 'You on the registration page');
        this.test.assertSelectorHasText('div.page-title', 'Create an Account', 'Page title - is present');
        this.test.comment('Check form input fields');
        this.test.assertExist('#firstname', 'First name input - is present');
        this.test.assertExist('#lastname', 'Last name input - is present');
        this.test.assertExist('#middlename', 'Middle name input - is present');
        this.test.assertExist('#password', 'Password input - is present');
        this.test.assertExist('#confirmation', 'Password confirmation name input - is present');
        this.test.assertExist('#email_address', 'E-mail input - is present');
        this.test.assertExist('#is_subscribed', 'Subscription checkbox - is present');
        this.test.assertExist('#form-validate div.buttons-set button.button', 'Submit button - is present');
        this.test.pass('Opened and checked registration customer page')
    });
    casper.then(function(){
        this.test.comment('Fill and submit registration form');
        this.fill('form#form-validate', {
            'firstname': login_user_firstname,
            'middlename': login_user_middlename,
            'lastname': login_user_lastname,
            'email': login_user_username,
            'password': login_user_password,
            'confirmation': login_user_password,
            'is_subscribed': true
        }, false);
        this.click('#form-validate div.buttons-set button.button');
        this.wait(400);
    });
    casper.then(function(){
        try {

            this.test.assertSelectorDoesntHaveText('li.error-msg span', 'There is already an account with this email address. If you are sure that it is your email address, ');

        }
        catch(e){
            casper.die(this.getElementInfo('li.error-msg span').text);
        }
        this.waitForUrl(base_url + 'customer/account/index/');
        this.test.pass('Fill and submit registration form');
    });

    casper.then(function(){
        this.test.comment('Check successful registration and Logout');
        this.test.assertHttpStatus(200);
        this.test.assertUrlMatch(base_url + 'customer/account/index/', 'You on the My account page');
        this.test.assertSelectorHasText('div.dashboard li.success-msg', 'Thank you for registering with Madison Island.', 'Success msg - is present');
        this.test.pass('Registration successful')
    });

    casper.then(function(){
        this.test.comment('Check customer logout');
        this.test.assertSelectorHasText('#header-account div.links', 'Log Out', 'Log Out link - is present');
        this.clickLabel('Log Out', 'a');
        this.waitForUrl(base_url + 'customer/account/logoutSuccess/');
    });
    casper.then(function(){
        this.test.assertHttpStatus(200);
        this.test.assertUrlMatch(base_url + 'customer/account/logoutSuccess/', 'You on the logout success page');
        this.test.assertSelectorHasText('div.col-main div.page-title', 'You are now logged out', 'You are now logged out');
        this.test.assertSelectorHasText('div.col-main p','You have logged out and will be redirected to our homepage in 5 seconds.', 'You have logged out and will be redirected to our homepage in 5 seconds.')
        this.test.pass('You have successfully logged out')
    });



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

As the result, if the tests have been executed successfully, you will see the following output in your CLI:

001

Also, you can find the automatically created screenshots in the folder named “capture”.

The second test is named “02_add_product_to_cart.js” – it is used to add a product to the cart. It checks the product view page, add the product to the shopping cart functionality and shopping cart page itself.

casper.test.begin('002 Add product to cart and check shopping cart', function suite() {


    casper.start(function () {
        this.open(base_url);
        this.test.assertHttpStatus(200);
        this.test.comment('Open home page');
    });

    casper.thenOpen(base_url + prod_url_simple, function(){
        this.test.assertHttpStatus(200);
        this.test.comment('Open simple product page ('+ prod_name_simple + ')');
        this.test.info('Current location is ' + this.getCurrentUrl());
        this.test.assertSelectorExist('div.add-to-cart-buttons button.btn-cart', 'Add to cart btn - is present');
        this.test.assertSelectorHasText('div.product-shop div.product-name span.h1', prod_name_simple, 'Product name - is present');
        this.test.assertSelectorExist('div.product-shop div.price-box span.price', 'Product price - is present');
        this.test.assertSelectorExist('#qty', 'Qty input field - is present');
        this.test.pass('Product page have been opened successfully');
    });
    casper.thenClick('div.add-to-cart-buttons button.btn-cart', function(){
        this.waitForUrl(base_url + 'checkout/cart/');
        this.test.info('Current location is ' + this.getCurrentUrl());
        this.test.assertHttpStatus(200);
        this.test.assertSelectorHasText('ul.messages li.success-msg', prod_name_simple +' was added to your shopping cart.', 'Success msg - is present');
        this.test.assertElementCount('#shopping-cart-table tbody tr', 1, '1 expected products have found ');
        this.test.assertExist('div.page  div.cart-totals-wrapper button.button.btn-proceed-checkout.btn-checkout', 'Checkout button - is present');
        this.test.assertExist('#discount-coupon-form', 'Discount coupon form - is present');
        this.test.assertExist('div.cart-forms div.shipping', 'ESTIMATE SHIPPING AND TAX form - is present');
        this.test.assertExist('#shopping-cart-totals-table', 'Totals table is present');
        this.test.pass('Shopping cart opened successfully')
    });

    casper.thenOpen(base_url + prod_url_conf, function(){
        this.test.assertHttpStatus(200);
        this.test.comment('Open configurable product page ('+ prod_url_conf + ')');
        this.test.info('Current location is ' + this.getCurrentUrl());
        this.test.assertSelectorExist('div.add-to-cart-buttons button.btn-cart', 'Add to cart btn - is present');
        this.test.assertSelectorHasText('div.product-shop div.product-name span.h1', prod_name_conf, 'Product name - is present');
        this.test.assertSelectorExist('div.product-shop div.price-box span.price', 'Product price - is present');
        this.test.assertSelectorExist('#qty', 'Qty input field - is present');
        this.test.comment('Set options and add product to the cart');
        this.test.assertExist('#product-options-wrapper', 'Product options - is present');
        //              Click on white color swatch
        this.test.assertExist('#configurable_swatch_color', 'Color swatch - is present');
        this.click('#configurable_swatch_color li.option-white a span.swatch-label');
        this.test.assertSelectorExist('#configurable_swatch_color li.option-white.selected', 'Color swatch has been selected');
        //              Click on L size
        this.test.assertExist('#configurable_swatch_size', 'Side swatch - is present');
        this.test.assertSelectorExist('#configurable_swatch_size li.option-s.not-available', 'S size is not available for the white color swatch');
        this.click('#configurable_swatch_size li.option-l a span.swatch-label');
        this.test.assertSelectorExist('#configurable_swatch_size li.option-l.selected');
        //fill monogram field
        this.sendKeys('#options_3_text', 'Atwix test');
        var test_msg = this.evaluate(function() {
            return jQuery("#options_3_text").val();
        });

        this.test.assertEqual(test_msg, "Atwix test", "Found expected text within the textarea");

        //select test custom option
        this.evaluate(function(){
            //jQuery('#select_2').val(1);
            document.querySelector('#select_2').selectedValue = 1; //Value - 1 = model 1 +$59.00
        });
    });
    casper.thenClick('div.add-to-cart-buttons button.btn-cart', function(){
        this.waitForUrl(base_url + 'checkout/cart/');
        this.test.info('Current location is ' + this.getCurrentUrl());
        this.test.assertHttpStatus(200);
        this.test.assertSelectorHasText('ul.messages li.success-msg', prod_name_conf +' was added to your shopping cart.', 'Success msg - is present');
        this.test.assertElementCount('#shopping-cart-table tbody tr', 2, '2 expected products have found ');
        this.test.assertExist('div.page  div.cart-totals-wrapper button.button.btn-proceed-checkout.btn-checkout', 'Checkout button - is present');
        this.test.assertExist('#discount-coupon-form', 'Discount coupon form - is present');
        this.test.assertExist('div.cart-forms div.shipping', 'ESTIMATE SHIPPING AND TAX form - is present');
        this.test.assertExist('#shopping-cart-totals-table', 'Totals table is present');
        this.test.pass('Shopping cart opened successfully')
    });
    casper.thenClick('#empty_cart_button', function(){
       this.test.comment('Click empty cart button');
        this.test.assertSelectorHasText('div.page div.page-title  h1', 'Shopping Cart is Empty', 'Shopping Cart is Empty');
        this.test.assertSelectorHasText('div.page div.cart-empty', 'You have no items in your shopping cart.', 'You have no items in your shopping cart.');
    });

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

002

Our third test is written for checkout, it is named “03_checkout.js”. It is testing the add a simple product to the cart functionality, opening checkout page and checking all the steps for creating an order as a guest.

casper.test.begin('003 Checkout - Guest - One Page checkout ', function suite() {


    casper.start(function () {
        this.open(base_url);
        this.test.assertHttpStatus(200);
        this.test.comment('Open home page');
    });
    //fast add simple product to the cart without check any additional functionality
    casper.thenOpen(base_url + 'madison-8gb-digital-media-player.html', function(){
        this.test.comment('fast add simple product to the cart without check any additional functionality');
        this.test.assertHttpStatus(200);
        this.test.info('Current location is ' + this.getCurrentUrl());
        this.click('div.add-to-cart-buttons button.btn-cart');
        this.waitForUrl(base_url + 'checkout/cart/');

    });
    //Checkout Method
    casper.thenClick('div.cart div.cart-totals button.btn-proceed-checkout.btn-checkout', function(){
        this.test.comment('check Checkout Method step');
        this.test.assertUrlMatch(base_url+'checkout/onepage/');
        this.test.assertExist('#checkout-step-login',               'Checkout method step -                     is present');
        this.test.assertExist('#login-email',                       'E-mail in;ut field -                       is present');
        this.test.assertExist('#login-password',                    'Password input field -                     is present');
        this.test.assertExist('#login-form a.f-left',               'Forgot Pass link -                         is present');
        this.test.assertExist('#checkout-step-login button.button', 'Login submit button -                      is present');
        this.test.assertExist('input[value="guest"]',               'Checkout as Guest radio button -           is present');
        this.test.assertExist('input[value="register"]',            'Registration and Checkout radio btn -      is present');
        this.test.assertExist('#onepage-guest-register-button',     'Continue button -                          is present');
        this.evaluate(function() {
            document.getElementById('login:guest').checked = true;
            checkout.setMethod();
        });
    });
    casper.thenClick('#onepage-guest-register-button', function(){
        this.test.comment('Submit Continue as a Guest ');
        this.test.assertExist('#checkout-progress-wrapper',             'Progress bar -                             is present');
        this.waitUntilVisible('li#opc-billing', function(){
            this.test.assertExists('input[name="billing[firstname]"]',  'First name input -                         is present');
            this.test.assertExists('input[name="billing[middlename]"]', 'Middle name input -                        is present');
            this.test.assertExists('input[name="billing[lastname]"]',   'Last name input -                          is present');
            this.test.assertExists('input[name="billing[company]"]',    'Company input -                            is present');
            this.test.assertExists('input[name="billing[email]"]',      'E-mail input -                             is present');
            this.test.assertExists('input[name="billing[street][]"]',   'Street input -                             is present');
            this.test.assertExists('input[name="billing[city]"]',       'City input -                               is present');
            this.test.assertExists('select[name="billing[region_id]"]', 'State select -                             is present');
            this.test.assertExists('input[name="billing[postcode]"]',   'Zip code input -                           is present');
            this.test.assertExists('select[name="billing[country_id]"]','Country select input -                     is present');
            this.test.assertExists('input[name="billing[telephone]"]',  'Phone input -                              is present');
            this.test.assertExists('input[name="billing[fax]"]',        'Fax input -                                is present');
            this.test.assertExists('input[name="billing[use_for_shipping]"]','Ship to radio btn  -                       is present');
            this.fill('form#co-billing-form', {
                'billing[firstname]'    : login_user_firstname,
                'billing[middlename]'   : login_user_middlename,
                'billing[lastname]'     : login_user_lastname,
                'billing[company]'      : user_address_company,
                'billing[email]'        : login_user_username,
                'billing[street][]'     : user_address_street,
                'billing[city]'         : user_address_city,
                'billing[postcode]'     : user_address_postcode,
                'billing[telephone]'    : user_address_telephone,
                'billing[fax]'          : user_address_fax
            }, false);
            this.evaluate(function(){
                document.querySelector('select[name="billing[country_id]"]').selectedValue = 'US'; //value  - us = USA
                document.querySelector('select[name="billing[region_id]"]').selectedIndex = 18;//id = 18 = florida
                document.getElementById('billing:use_for_shipping_yes').checked = true;
            });
        });
    });
    casper.thenClick('#billing-buttons-container button.button', function(){
        this.test.comment('Submit Billing informationt');
        this.waitUntilVisible('#checkout-step-shipping_method', function () {
            this.test.assertExist('#s_method_flatrate_flatrate', 'Flat Rate shipping is available');
            this.evaluate(function(){
                document.getElementById('s_method_flatrate_flatrate').checked = true;
            });
        })
    });
    casper.thenClick('#shipping-method-buttons-container button.button', function () {
       this.test.comment('Submit Shipping method');
        this.waitUntilVisible('#checkout-step-payment', function(){
            this.test.assertExist('#dt_method_cashondelivery');
            //this.test.click('#payment-buttons-container > button.button');
        })
    });
    casper.thenClick('#payment-buttons-container > button.button', function () {
       this.test.comment('Submit Payment');
        this.waitUntilVisible('#checkout-step-review', function () {
            this.test.assertExists('#checkout-review-table');
            this.test.pass('Order Grand total -> ' + this.getElementInfo('#checkout-review-table tr.last td.a-right.last span.price').text);
            this.click('#review-buttons-container button.btn-checkout');
        });
    });
    casper.waitForUrl(base_url + 'checkout/onepage/success', function () {
        this.test.assertHttpStatus(200);
        this.test.assertExists('.checkout-onepage-success', 'Success page is present');
        this.test.pass('The order has been placed successfully');
        this.test.comment(this.getElementInfo('div.page > div.main-container.col1-layout  p:nth-child(4)').text)
    });


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

003

To run this test you should launch a command from the folder with all tests.

casperjs --pre=config.js test --base_url="http://magento.store.url" --email="yourmail@example.com" test_name.js

Note that you need to change “–base_url” and “–email” parameters to your data and enter a test script name.

Below you can review the additional information about all used parameters:

-–pre=config.js – file with methods and CasperJS configurations such as selecting the browser resolution, a level of the debug mode, login credentials, pages screenshot, cookies cleaner, timeout setter etc.
-–base_url – URL of testing Magento website.
-–email – e-mail used in customer forms.
-–verbose – it runs a debugger (the debug level can be set in the config file).
TEST_NAME.js – name of your test script.

All of the tests were launched using Terminal on Mac OS X. In addition, you can find all the tests on GitHub.
We hope that this information will simplify the testing stage on your website. We will be glad to receive your feedback in the comments.