Managing CasperJS Tests With Go

By Silviu, on 2016-12-16

Using Go to coordinate CasperJS-based usability and regression testing.

Overview

CasperJS is an extremely versatile scraping and testing tool, while at the same time sticking to what it does best: pretending to be a browser, allowing us to replicate a web user experience, and to create complex navigational flows. It does not care, nor does it provide much in terms of automation awareness. This leaves it up to us to plug the Casper ecosystem into the larger project life cycle context, make it run a cluster of test scripts then have the results of those scripts sent to another entity in an automated fashion.

I had an opportunity to work on some custom continuous integration functionality for a large project, using Go for development. It is a language that, in my opinion, shines when used to build orchestration and automation components.

This article describes how to make use of existing Go open source packages to comb through a folder,identify and parse the script files, then launch Casper with each valid script file as input. It is a highly simplified take on the problem, and it is mainly meant to showcase the rich ecosystem of Go packages available out there.

Note that this is only one of the many approaches you could take. Other approaches include integrating Casper with continuous integration tools such as Jenkins, using a Groovy-based DSL controller, and probably countless other ways.

"All available personnel, please report to your stations"

Hup two three four
Keep it up, two three four

(The Sherman Brothers - Colonel Hathi's March, Disney's "The Jungle Book")

Our Go program is a basic command-line executable, and takes one input parameter, called folder that indicates the folder path to be searched for potential CasperJS scripts. The flag runtime package will take care of that, and it will default to "./samples":

// Collect the location of scripts from command line or default to "./scripts"
scriptFolder = flag.String("folder", "./samples", "Casper scripts location, defaults to ./scripts")
flag.Parse()

// Traverse and process the files in the folder
testsToRun := traverseFiles(*scriptFolder)

The traverseFiles function will tap into a simple yet sturdy GitHub package, called fs. It provides an easy way to iterate over the files in a given folder. Henceforth, it is our responsibility to weed out non-Javascript files, and subfolders:

walker := fs.Walk(scriptFolder)
for walker.Step() {
	if err := walker.Err(); err != nil {
		log.Println("Filesystem walker error: ", err)
		continue
	}

	// Filter out directories and files without a .js extension
	if walker.Stat().IsDir() || !strings.HasSuffix(strings.ToLower(walker.Path()), ".js") {
		continue
	}

	// Analyze the file and add it to the test suites collection
	// if it contains the required info
	testScript, ok := loadScriptFromFile(walker.Path())
	if ok {
		log.Println("Adding valid Casper test: ", testScript.Name)
		testsToRun = append(testsToRun, testScript)
	}
}

Any file with a .js extension is considered, and the file path is passed to the loadScriptFromFile function.

"Hi, my name is..."

In general, I stick to the "one test per script file" rule, which means that I end up with a whole bunch of these Javascript files. Having a lot of tests is (by itself) a nice problem to have, particularly when dealing with a complex project. To be able to automate and segment them programmatically, we need an uniform way to identify each test.

As described in the article CasperJS for User Experience and Regression Testing my approach is to have all those scripts "describe" themselves in a manifest section, at the beginning of each script, as below:

/* BEGIN: Script Manifest */
var MANIFEST_SCRIPT_ID = "google-search-page";
var MANIFEST_SCRIPT_NAME = "Google Search Page Test";
var MANIFEST_SCRIPT_DESC = "Tests a basic query on the Google search page";
/* END: Script Manifest */

I use uppercase variables, you can use a JSON object with properties; it doesn't really matter. All that it matters is that we keep a consistent approach about it. In our Go program, these variables are described inside an array of strings:

// Variable names that must be present in the CasperJS scripts in order to get parsed
var ManifestVariables = [...]string{"MANIFEST_SCRIPT_ID", "MANIFEST_SCRIPT_NAME",
	"MANIFEST_SCRIPT_DESC"}

The loadScriptFromFile function is tasked with ascertaining, based on these manifest variables, if the file is a valid CasperJS script file. The function has two parts: an optional, superficial string search of the manifest identifiers (to avoid running the full parser) and the very important parsing and validating of the content as legitimate Javascript.

The first part is only fun because we are trying out some bit masking. We can represent each variable as a bit flag in a binary number, based on their index in the ManifestVariables array. The value would be 0 for not-found, and 1 for found For example:

010

means MANIFEST_SCRIPT_NAME (position 1 in a zero-based array) not found, but MANIFEST_SCRIPT_ID (position 0) and MANIFEST_SCRIPT_DESC (position 2) were not found.

We start with a "000" (binary) value, meaning no variable has yet been counted out. In case all are found out, we get "111". That binary value is the decimal 7, and can be described as 20 + 21 + 22. Needless to say, every time we encounter a string matching a manifest variable name, we add the 2(index of the ManifestVariables element) to the total number:

file, err := os.Open(pathToFile)
if err != nil {
	log.Println("Error opening file at: ", err)
}
defer file.Close()

fileContents := bytes.Buffer{}

// Superficial and preliminary vetting of the file contents while reading it,
// to make sure that it contains the designated manifest variables
var manifestTokenCount = 0
var outstandingTokenCount = int(math.Pow(2, float64(len(ManifestVariables)))) - 1
scanner := bufio.NewScanner(file)
for scanner.Scan() {
	currentLine := scanner.Bytes()
	currentLine = append(currentLine, '\n') // Add back the newline that scanner.Scan "stole away"
	
	if manifestTokenCount >= outstandingTokenCount {
		continue
	}    
    
    if _, writeErr := fileContents.Write(currentLine); writeErr != nil {
		log.Println("Error writing file contents: ", writeErr)
		continue
	}

	for i, curManifestVar := range ManifestVariables {
		byteFlagPos := int(math.Pow(2, float64(i)))
		if bytes.Contains(currentLine, []byte(curManifestVar)) &&
			manifestTokenCount&byteFlagPos != byteFlagPos {
            
			manifestTokenCount = manifestTokenCount + byteFlagPos
			log.Printf("Found %s at index %d (byte flag pos %b), current bitmask map: %d (%b)",
				curManifestVar, i, byteFlagPos, manifestTokenCount, manifestTokenCount)
		}
	}
}

if manifestTokenCount < outstandingTokenCount {
	log.Println("Incomplete manifest definition")
	return nil, false
}

if err := scanner.Err(); err != nil {
	log.Println("Scanner error: ", err)
}

The second part involves a more thorough parsing of the contents. It has to be "legit" Javascript code, and we need to extract the value of the manifest variables. Fortunately, there are third party packages that will do the dirty work, allowing us to focus on solving our problems.

Otto To the Rescue

One of the Go packages that has been around for quite a while on GitHub is Otto. It is a full-fledged Javascript interpreter, and we are going to only use its parsing abilities.

A successful parsing of the source returns an ast.Program instance. We can use that instance to retrieve a slice of ast.Declaration (interface) items. Since we only care about variables, we iterate through the slice of declarations, and use Go's type assertion feature to detect which ones are variables. We then identify our manifest ones, then load their values into an instance of our own convenience CasperTest structure:

for _, declaration := range program.DeclarationList {

	// Only care about variables
	varDecl, ok := declaration.(*ast.VariableDeclaration)
	if ok {

		for _, varExpr := range varDecl.List {

			variableName := varExpr.Name

			for i, curManifestVar := range ManifestVariables {
				if variableName == string(curManifestVar) {

					// Get the value
					variableValue, okVal := varExpr.Initializer.(*ast.StringLiteral)
					if okVal {
						casperTest.SetPropertyByIndex(i, variableValue.Value)
					}
				}
			}
		}
	}
}