CasperJS testing PingFederate HTML form adapter

During a deployment of Ping Identity’s PingFederate product, we had a need to automate logging into the HTML Form Adapter. Whilst there are many ways to automate this, I really liked this one, and thought it worth sharing.

The solution has 2 components:

PhantomJS

PhantomJS is a headless browser this uses the WebKit browser by default, but can also use the Gecko engine courtesy of SlimerJS.

CasperJS

CasperJS started life as a bunch of scripts which drove PhantomJS & has since evolved into a full framework. This code utilizes version 1.1 of CasperJS, which despite being a development version is quite stable and is in fact the version the maintainers of CasperJS recommend.

Installation

Windows

Download phantomjs: http://phantomjs.org/download.html Unzip it to C:\phantomjs

Download the latest zip from this page: http://docs.casperjs.org/en/latest/installation.html and unzip to a safe & sensible location like C:\casperjs

Modify the PATH variable to make the binary accessible

setx PATH "%PATH%;C:\phantomjs\;C:\casperjs\bin"

Linux

$ wget https://phantomjs.googlecode.com/files/phantomjs-1.9.8-linux-x86_64.tar.bz2
$ tar xvjf phantomjs-1.9.8-linux-x86_64.tar.bz2
$ cd phantomjs-1.9.8-linux-x86_64
$ sudo ln -sf "$(pwd)"/bin/phantomjs /usr/local/bin/phantomjs
$ cd ..
$ git clone git://github.com/n1k0/casperjs.git
$ cd casperjs
$ sudo ln -sf "$(pwd)"/bin/casperjs /usr/local/bin/casperjs

Mac

brew update && brew install phantomjs
brew install casperjs --devel

Configuration

The configuration required is minimal.

PhantomJS by default utilizes SSLv3, so if you encounter the following warning and the script continually fails to complete:

[warning] [phantom] Loading resource failed with status=fail: https://fed.companyb.com/idp/prp.wsf?wa=wsignin1.0&wtrealm=https://applicationa.companyb.com/&wfresh=15&wctx=rm=0&id=passive&ru=%2fCIS%2f&wct=2014-11-19T05:34:06Z

The solution is to enable TLS using one of the following methods:

1) Specify the protocol to use each time you execute casperjs:

casperjs --ssl-protocol=tlsv1

2) Or, edit the file C:\casperjs\bin\casperjs and add the above switch to the ENGINE_ARGS

ENGINE_ARGS = ['ssl-protocol=tlsv1']

Solution code

"use strict";
 
var  url = 'https://applicationa.companyb.com/',
username = 'mattheww',
password = 'password',
   debug = true,
logLevel = null;
  
if (debug) {
  logLevel = 'debug';
} else {
  logLevel = 'info';
}
 
var casper = require('casper').create({
  pageSettings: {
    loadImages:  false,   // The WebPage instance used by Casper will
    loadPlugins: false,   // use these settings
    userAgent: 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.2 Safari/537.36'
  },
    logLevel: logLevel,   // debug; info; warning; error
    verbose: true,        // log messages will be printed out to the console
    options: {
      waitTimeout: 25000, // Timeout of 25 sec
    }
});

casper.start(url, function () {
  this.log('Attempting login to ' + url, 'info');
  this.fill('form', {
    'pf.username': username,
    'pf.pass': password
  }, true);
});

casper.then(function() {
  var currentURL = this.getCurrentUrl();
  if (currentURL === url) {
    console.log("SUCCESS!: We're in " + currentURL);
  } else if (/prp.ping$/i.test(currentURL)) {
    console.log("FAIL!: We're stuck on prp.ping");
      if (debug) {
        // Dump whole page for visual inspection
        var page = this.evaluate(function() {
                       return document;
        });
        this.echo(page.all[0].outerHTML);
      } else {
        if (this.exists('div.ping-body form div div')) {
          var msg = this.fetchText('div.ping-body form div div');
          var msgArray = msg.split('\t');
          this.echo(msgArray[0]);
        } else if (this.exists('div.ping-body')) {
          var pingBody = this.fetchText('div.ping-body');
          if (pingBody.indexOf('Access denied (403)') >= 0 ) {
            this.echo(pingBody);
          }
        }
      }
  }
});
 
casper.run();

When successful,the output should resemble below

C:\casperjs\bin>casperjs.exe ..\mw\run.js
[info] [phantom] Starting...
[info] [phantom] Running suite: 3 steps
[info] [phantom] Step anonymous 2/3 https://fed.companyb.com/idp/ZVlk9/resume/idp/prp.ping (HTTP 200)
[info] [phantom] Attempting login to https://applicationa.companyb.com/
[info] [remote] attempting to fetch form element from selector: 'form'
[info] [remote] submitting form to /idp/ZVlk9/resume/idp/prp.ping, HTTP POST
[info] [phantom] Step anonymous 2/3: done in 154ms.
[info] [phantom] Step anonymous 3/3 https://applicationa.companyb.com/ (HTTP 200)
SUCCESS!: We're in https://applicationa.companyb.com/
[info] [phantom] Step anonymous 3/3: done in 730ms.
[info] [phantom] Done 3 steps in 749ms

A failure will resemble the following:

C:\casperjs\bin>casperjs ..\mw\run.js 
[info] [phantom] Starting...
[info] [phantom] Running suite: 3 steps
[info] [phantom] Step anonymous 2/3 https://fed.companyb.com/idp/82l2u/resume/idp/prp.ping (HTTP 200)
[info] [phantom] Attempting login to https://applicationa.companyb.com/
[info] [remote] attempting to fetch form element from selector: 'form'
[info] [remote] submitting form to /idp/82l2u/resume/idp/prp.ping, HTTP POST
[info] [phantom] Step anonymous 2/3: done in 163ms.
[info] [phantom] Step anonymous 3/3 https://fed.companyb.com/idp/82l2u/resume/idp/prp.ping (HTTP 200)
FAIL!: We're stuck on prp.ping
Authentication failed. You have used an invalid username and/or password.
[info] [phantom] Step anonymous 3/3: done in 469ms.
[info] [phantom] Done 3 steps in 485ms
 
C:\casperjs\bin>