Skip to main content

Help the suits write functional tests for Angular apps with Cucumber

You might call this Acceptance Test-Driven Development...


No matter what the fate of the AngularJS Web development framework may be in the near future, the fact of the matter is it's here now and many enterprises have, surprisingly, began to adopt it.  As such, it is increasingly important to unify the set of tests that developers care about with the set of tests that business people care about (and that the QA automation team really likes to code the most).  This is hard because while the QA team can go on and write their own scripts to their heart's content, the business folks really can't do the same thing -- they lack the time and/or the technical inclination.  Thus, the automation team should be responsible for helping out the business folks by providing a "common language" (ideally, a specific subset of English words) so that everyone can describe tests for a system regardless of their know-how.


What have I experienced so far in ATDD?


Thus far, I am familiar with two such systems -- Robot Framework, and Cucumber.  Both of these tools employ keywords (which usually people construct from English phrases) with which people can describe tests.  Both are simply layers above real code you write in a language of your choice (Ruby, JavaScript, Python, etc.)  However, Cucumber can take things a step further than I've seen people do with Robot: your keywords can include regular expressions.  (This isn't huge, since keyword arguments can do the same thing in Robot.)  The difference would look something like this:

Cucumber:  When I go to http://mysite.com
Robot:  Go To Website    http://mysite.com

In Robot scripts, you can plop in entire data structures as arguments to a keyword.  This might blow the minds of some people from the business side, so I haven't seen a direct corollary to this in Cucumber (which, for some reason, you don't really write "Cucumber", you write "Gherkin" syntax -- a type of cucumber, haha).  On the other hand, Cucumber does allow you to write repetition in an interesting manner.  Imagine you have a table of data representing inputs and outputs for identical operations that should be run on a particular row of data: the syntax described here allows you to run it without needing to understand anything about loops or indexes.

Robot has a plethora of built-in keywords and additional libraries that provide all sorts of functionality without requiring you to write code in the underlying language.  When I was working with Robot, it was layered on top of modules we wrote in Python.  I and many others on my team would choose to write new keywords using existing Robot keywords in order to avoid modifying the keyword file plus Python code and other libraries.  However, some of these built-in keywords are very tedious to use (Heaven help you if you want to do complex logic in Robot, for instance), and you can end up being led down a rabbit hole fairly fast if you have this mindset.  With Cucumber, you avoid rabbit holes because everything must be written in the underlying language.  Despite that this requires more files to get reviewed before check-in, I actually prefer this because as long as you like the underlying language (Python, JavaScript, etc.), it's generally more pleasant to work with than a limited set of keywords and operators.

Finally, Robot reads to me more like some old-time procedural language like BASIC, whereas Cucumber Gherkin reads more like a true outline for an acceptance test.  Gherkin statements always begin with "Given", "When", or "Then", and a whole test only contains these types of statements.  Robot code could read in a similar manner if you have created layers of keywords that abstract such given/when/then test requirement elements from any underlying operations (e.g. opening a webpage or file, comparing data, or doing arithmetic), but in my day-to-day role as a Robot & Python test developer, the stuff I wrote never quite read like Gherkin, but the stuff that the "suitesmith" made probably did. 

I didn't really intend to even bring Robot into this post at all, but figured it couldn't hurt. :-P  This is definitely not guaranteed to be an exhaustive analysis of Robot vs. Cucumber, but will get you the salient points from my own experience.


Get Me a Gherkin, Please



First, before getting right into Cucumber for Angular, you should know about the test framework that sits between them.  Protractor is a Node.js module that facilitates testing of Angular apps because it has the ability to access Angular variables attached to the scopes of all the objects in the DOM.  It provides the Selenium WebDriver library for you, thus offering wrapper functions for the WebDriver calls you are accustomed to writing in order to make your Web application's UI do your bidding.

The idea of this page is not to go deeply into writing test logic with Protractor, since other sites delve into the topic.  Instead, it covers the very basics of getting started with Cucumber with Protractor, and what it takes to run a basic test.

What environment setup does this require?


  1. Install Node.js.
  2. Using Node Package Manager (npm), install the packages "protractor", "cucumber", and "chai".  On a Mac, you must run npm as root.

How do I wire my Gherkin syntax into JavaScript functions?


First, let's step back and explain a little bit about Protractor.  When you install it, a binary called "protractor" is placed in the "node_modules" directory in wherever you're located (unless you ran "npm install -g" which puts it in a user folder such as /usr/local/lib/node_modules).  You use this binary with a configuration file passed in as an argument to kick off your test in Protractor.  A configuration file is a collection of declarations and objects in JavaScript.  A complete (but basic) Protractor config file looks like this:

conf.js:


var protractor = require('protractor');  // Include the Protractor JavaScript library
var path = require('path');              // Handle paths
var fs = require('fs');                  // Handle the filesystem

exports.config = {
  framework: 'cucumber',                  // Specify that Protractor should use Cucumber rather than the default Jasmine
  cucumberOpts: {
    // define your step definitions in this file
    require: 'steps.js',
    format: 'progress'
  },
  baseUrl:  'http://website-under-test',
  seleniumAddress: 'http://localhost:4444/wd/hub',
  specs: ['cucumberTest.feature'],        // An array of tests to run (wildcards and !'s are supported)
  multiCapabilities: [                    // Array of "capabilities" objects to define test environment settings
    {
      'browserName''chrome',
    }, {
      'browserName''internet explorer'
    }
  ],
  onPrepare: function() {}                // Optional function to load extra variables into your test
};

The file above will load a browser, point it to http://website-under-test, and use the Selenium WebDriver instance at localhost:4444 to drive the site under test in the manner described by the spec file, defined here as "cucumberTest.feature".  Before you can run protractor conf.js, you must also run webdriver-manager start from the same directory in order to start your Selenium instance.  You should also write your Gherkin syntax and underlying JavaScript routines as well.

Ok, how do I really wire my Gherkin syntax into JavaScript functions?


I hope you are already familiar with Gherkin syntax; after all, you are the one who defines it.  The JavaScript sets the boundaries for what names of Gherkin steps will be accepted by the test runner, but the desired names for these steps that get coded into the JavaScript might be defined by a business process rather than the software or test developers, depending on who the end users are.  Here is a short example file which defines several Gherkin steps, and some underlying logic:

steps.js:


var expect = require('chai').expect;

module.exports = function() {
  /************************************
  * These are your keywords in Gherkin
  ************************************/

  // Given I go to "____"
  this.Given(/^I go to "([^"]*)"$/function(url, next) {
    browser.get('http://website-under-test' + url);
    next();
  });

  // Then I click on "____"
  this.When(/^I click on "([^"]*)"$/function(url, next) {
    clickOn(selector, next);
  });

  // Then I should see "____"
  this.Then(/^I should see "([^"]*)"$/function(content, next) {
    assertPageContains(content, next);
  });

  // Then I should see "____" on "____"
  this.Then(/^I should see "([^"]*)" on "([^"]*)"$/function(content, selector, next) {
    assertElementContains(selector, content, next);
  });

  /********************************************************
  * This is the underlying code backing the Gherkin syntax
  * When this section gets big, split it into another file
  ********************************************************/

  function assertPageContains(content, next) {
    browser.getPageSource().then(function(source) {
      expect(source).to.contain(content);
      next();
    });
  }

  function assertElementContains(selector, content, next) {
    element(by.binding(selector)).getText().then(function(text) {
      expect(text).to.equal(content);
      next();
    });
  }

  function clickOn(selector, next) {
    element(by.xpath(selector)).click();
     next();
  }
};

First, you notice the requirement for Chai's "expect" module.  Chai is an assertion engine that provides the means to actually do analysis on the items under test (i.e. it provides expect(), to(), equal(), contain(), and so forth).  It is written in order to allow you to make English-looking statements with JavaScript, as you saw above with expect(source).to.contain(content).  Normally, Protractor would include the Jasmine framework which already incorporates assertion functions, but since you need to request the Cucumber framework to run Cucumber tests, you must also load your own assertion engine.

Several things in here are provided by Protractor, including the browser object, the element() function which looks for an element based on the means you specify (of most interest, notice where we chose by.binding() to find an element bound to an Angular variable specified by the user in the Gherkin text -- it's always fun when the UI team doesn't define IDs for all elements :-/), various means to interact with the element such as click(), and also then(), which relies on JavaScript Promises rather than callbacks in order to provide a bit more robust asynchronous method execution.

With these tasks in place, and the underlying logic to support them, you can then write Gherkin such as:

cucumberTest.feature:


Feature: Track time took for task
  As a user of Pomodoro.cc
  I should be able to see the dashboard
  So that I can track time

  Scenario: Visiting /
    Given I go to "/"
    Then I should see "Start Timer"
    Then I should see "00:00"



And you will be so happy to see the application successfully run these tasks.  Right?  

Reference: Thank goodness for this super-basic intro to Gherkin at http://christian.fei.ninja/Write-your-Protractor-tests-in-Cucumber/, without which I could not have started on Gherkin myself.  The reason for my post is to fill in some of the additional details regarding setting all this up in your environment, plus to expound upon some of the things JavaScript supports.



Comments

Popular posts from this blog

Making a ROM hack of an old arcade game

Start Azure Pipeline from another pipeline with ADO CLI & PowerShell

I/O 2021: New Decade, New Frontiers