In the previous post we've built a simple automation system to test the front end of an express.js application.

http://cuneyt.aliustaoglu.biz/en/end-to-end-javascript-testing-with-selenium-mocha-chai/

This was for TDD (Test-Driven Development). The difference between the two, is not the scope of this post. My honest explanation would be; BDT is for the developers who may need to report to or collobrate with stakeholders, the development team or less tecnhical people. Using tools for BDT, you can create reports and everyone can (sort of) read the test cases.

Anyway, for this application we will replace mocha with cucumber. And write tests with GIVEN...WHEN...THEN syntax with a language called Gherkin. Gherkin is rather a parser or even a mapper than a transpiler. Of course we will still be writing our test cases with JavaScript. Gherkin will point the human readable text to the necessary JavaScript function.

So let's start:

Components

Below is the same as the previous post (except no mocha and added cucumber)

chromedriver : We won't be using this library directly but selenium-webdriver will.

selenium-webdriver : We need this to open up the Chrome browser, send some keys to input elements (eg. '#username') and send clicks to buttons etc.

cucumber : Cucumber is our testing framework so we don't need mocha anymore. Cucumber will map the human readable features to machine readable javascript functions.

chai : This is our assertion library. We will execute some code manipulating selenium-webdriver. Selenium will execute these and send us the result. We will check if this result matches our result with chai. Chai will signal mocha if the tests passed or failed.

chai-as-expected : Chai is not pretty good for asyncronous requests. You might be thinking that reading a value from a DOM element is not asyncronous. For example $('#username') in jQuery is syncronous but the similar request in selenium-webdriver is not syncronous. Webdriver returns a promise, not the actual response. This plugin will help us to use assertion commands on promises.

npm install --save-dev chromedriver selenium-webdriver cucumber chai chai-as-promised

Sample App

We need a simple web app to execute some tests on. I don't want to setup a whole React App that will install lots of dependencies. I'll create the simplest express.js app using node.

npm install --save express

and then create below application. The application starts with login page. When you enter your username and password (there is no verification for the sake of simplicity) it will take you to the main page.

const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send(`
    <html>
    <form action="/main">
      username: <input type="text" id="username" /><br />
      password: <input type="text" id="password" />
      <input id="btn" type="submit" />
    </form>
    </html>
  `);
});

app.get('/main', (req, res) => {
  res.send(`
    <html>
    <title>
      My awesome page
    </title>
    <body>
    <div id="message">
      Welcome to my page!
    </div>
    </body>
    </html>
  `);
});

app.listen(3003, () => console.log('Example app listening on port 3003!'));

Add below to package.json. Default timeout of 2 seconds is too low for mocha so I changed it to 20,000 milliseconds.

{
    "start": "node index.js",
    "test:e2e": "mocha --timeout 20000 e2e/tests.js"
}

and run npm start and let the application run while we execute the tests.

By default cucumber will look under the /features folder. Minimum files we need are:

  • .feature file (main.feature)
  • world constructor (/support/world.js)
  • Test file (main-steps.js)

File names do not have to match (but it is good to have a good convention). But the text in GIVEN...WHEN...THEN cases have to match.

Let's first create the world of cucumber:

const webdriver = require('selenium-webdriver');
const By = webdriver.By;
const until = webdriver.until;

var { setWorldConstructor, Before, After, setDefaultTimeout } = require('cucumber');
setDefaultTimeout(20 * 1000);

function CustomWorld(callback) {
  this.driver = new webdriver.Builder().forBrowser('chrome').build();
}

Before(function(testCase, callback) {
  console.log('Cucumber initializing login');
  const driver = this.driver;
  driver.get('http://localhost:3003').then(function(res) {
    driver
      .findElement(By.id('username'))
      .sendKeys('cuneyt')
      .then(() => driver.findElement(By.id('password')).sendKeys('sifre123'))
      .then(() => driver.findElement(By.id('btn')).click())
      .then(() => {
        driver.wait(until.elementLocated(By.id('message'))).then(() => {
          console.log('Page login is successful. Ready to test features');
          callback();
        }, 1000);
      });
  });
});

After(function() {
  console.log('Cucumber finished all features. Closing browser');
  const driver = this.driver;
  return driver.close();
});

setWorldConstructor(CustomWorld);

Above script initiates Selenium and launches a Chrome browser. Tells the browser to fill in the username and password fields, then clicks the button that will take it to the next page. Browser will hang until the <div id="message" /> is located.

Now we are ready to create our tests on main page:

const { Given, When, Then, Before } = require('cucumber');
const webdriver = require('selenium-webdriver');
const By = webdriver.By;
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const expect = chai.expect;

Given(/^I want to login to my awesome page$/, { timeout: 20 * 1000 }, function() {
  return 'foo';
});

When(/^I type my username and password$/, { timeout: 20 * 1000 }, function() {
  return 'bar';
});

Then(/^I can see the main page$/, { timeout: 20 * 1000 }, function() {
  var chaiAsPromised = require('chai-as-promised');
  chai.use(chaiAsPromised);
  return expect(this.driver.findElement(By.id('message')).getAttribute('innerHTML'))
    .to.eventually.contain('Welcome to my page!');
});

As you can see our GIVEN...WHEN...THEN... titles match the feature file. Most important thing here is that we need to return the expect statement. webdriver works like jquer or document.getElementById but it returns a promise instead of an actual result. That's why we used chai-as-promised plugin. If we don't return expect statement, your test application will fail not the test result. So it is very important to return the statement.

This boilerplate can be found here:

git clone https://github.com/aliustaoglu/cucumber-selenium-boilerplate.git