node package manager

abacus-webdriver

abacus-webdriver

A light-weight Selenium framework for all your testing needs.

What is it?

Abacus is a test framework integrating Selenium in Node.js. Inspired by Cucumber and Protractor, Abacus integrates Gherkin syntax and requires adherence to the Page Object Model for creating tests.

Why use it?

Abacus offers many advantages over Protractor. It deeply integrates a more feature rich Gherkin syntax (Gherkin++), allowing for true parallelization, load balancing, and scaling that can't be accomplished using just Protractor. Gherkin++ offers more readable hooks, as well as random value generation and real time variable storage.

Abacus is integrated to work with Saucelabs, Selenium Grid, and local webdriver servers. The built in reporting feature automatically generates an html report of execution results, showing both images and errors of failed executions. The Page Object Model allows the user to quickly create a working suite of tests that are scalable and reusable.

How to use it

Folder structure

Abacus expects a specific folder structure, but can be reconfigured into whatever folder structure you prefer (See Configuration File). By default, the project should have the following folder structure:

- features
- hooks
- page_objects
- reports
- step_definitions

features: contains all Gherkin++ files, denoted with file extension *.feature.

hooks: contains all Gherkin++ hooks files, denoted as *-hooks.js

page_objects: contains all Page Object Model files, denoted as *-page.js

reports: the directory where all execution reports will be stored

step_definitions: contains all Gherkin++ step definitions, denoted as *-steps.js

Page Object Model

The Page Object Model requires you to define in advance the locators of every web element you plan to interact with. For each unique URL on your SUT, you should have an equivalent page object. Abacus automatically generates a dictionary containing all Page Objects created in your project, which is available through the global variable pageMap. Each web element is linked to a single page object, which allows the user to define multiple 'OK' buttons across multiple pages. The caveat is that you must specify when you are going from one page to the next. For example:

Given user goes to "https://www.google.com/"
Then user is taken to the "Google home" page
When user enters "abacus-webdriver" into the "Search Bar" field
And user clicks on the "Search" button
Then user is taken to the "Google Search Results" page
And user enters "abacus-webdriver npm" into the "Search Bar" field
And user clicks on the "Search" button
Then the top search result is for "npm"

Here, Abacus is employing the usage of two distinct "Search" web elements on two separate pages, and is able to distinguish between the two because it is explicitly told what page it is on using the following line:

Then user is taken to the "Google Search Results" page

Moreover, you cannot interact with any pre-defined web elements until you make use of the aforementioned step. The step serves two purposes: first, it tells Abacus which page object to look for future web elements, and second, it validates that the URL of the current page is equivalent to the URL of the specified page object.

File Syntax

Feature Files

Feature files in Abacus strictly follow Cucumber's Gherkin syntax. With Gherkin++, you can also integrate random value generation. Random values are generated by encapsulating statements within %, and are fetched using *

For example, you can generate a random value into the step of a feature file like this:

When user enters "%randomLastName%" into the "Last Name" field

You can then reuse the randomly generated value within the same scenario like this:

Then the "Last Name" element text is "*randomLastName*"

If you need to use two randomly generated values of the same type within the same scenario, you can generate and retrieve them like this:

When user enters "%randomLastName_1%" into the "Last Name 1" field
And user enters "%randomLastName_2%" into the "Last Name 2" field
Then the "Last Name 1" element text is "*randomLastName_1*"
And the "Last Name 2" element text is "*randomLastName_2*"

You can see a list of random value generators at the random-value-generator.js file.

You can also save values on runtime and access them using {}. For example, assuming that in a previous step, you saved userID as a global variable. You can access the variable directly like this:

Then the "Display ID" element text is "{userID}"

Page Objects

A Page Object file must follow the following format:

import { PageObject } from 'abacus-webdriver';
 
module.exports = class extends PageObject {
  constructor() {
    super('Google home', 'google.com');
    
    // place web elements here
  }
  
  // place page-specific function definitions here
 
};

The parameters in the super function are name and url, respectively. The first parameter refers to the name of the page object, which you will use to refer to the page within the context of feature files and step definitions. The second parameter is a regular expression(RegExp) that will be used to verify the url of the current page, assuring that when the user reaches the step Then user is taken to the "Google Home" page, the URL matches this field.

Web elements must be defined within the constructor, in all upper case, replacing spaces with underscores. For example, the "Search Bar" element would be defined as this.SEARCH_BAR = element(by.id('lst-ib'))

TODO: discuss Page Fragment Model practice

Step Definitions

Step Definition files must follow the following format:

module.exports = ({ Given, When, Then }) => {
 
  Given(/^a sample step definition with (.*) as a variable$/, (var1, callback) => {
    pageMap['Google home'].useVariable(var1, callback);
  });
 
};
 

This step definition calls the "Google Home" page object's method called useVariable. All step definitions must call callback after execution.

Hooks

A Hook file looks like this:

module.exports = ({ Shutdown }) => {
  Shutdown((callback) => {
      console.log('this is the last thing that will happen before an execution quits');
      callback();
  });
};

Much like step definitions, all hooks must call the callback method when complete. There are 6 different types of hooks.

  • Init: Run once before all parallel executions
  • BeforeAll: Run once before each parallel execution
  • BeforeEach: Run once before each scenario execution
  • AfterEach: Run once after each scenario execution
  • AfterAll: Run once after each parallel execution
  • Shutdown: Run once after all parallel executions

Configuration File

Abacus parses json files and converts them to environment variables. For example, if you supply a configuration file that looks like this:

{
  "cucumber": {
    "inclusive_tags": [
      "@Smoke"
    ],
    "exclusive_tags": []
  },
  "selenium": {
    "browser": "chrome",
    "browser_instances":"1"
  }
}

Abacus will create 4 environment variables:

CUCUMBER_INCLUSIVE_TAGS
CUCUMBER_EXCLUSIVE_TAGS
SELENIUM_BROWSER
SELENIUM_BROWSER_INSTANCES

These environment variables are exposed to Node.js through the process.env variable. Abacus supplies the following configuration by default. If you wish to change any of the values, supply your own configuration file.

{
  "cucumber": {
      "feature_directory": "./features/",
      "page_object_directory":"./page_objects/",
      "step_definition_directory":"./step_definitions/",
      "hook_directory":"./hooks/",
      "inclusive_tags": [],
      "exclusive_tags": [],
      "step_timeout": "20",
      "redirect_timeout":"10"
  },
  "selenium": {
      "browser": "chrome",
      "browser_instances":"1"
  }
}

Running your tests

There are 3 ways to execute abacus through the command line. The first, and most simple, is by globally installing abacus to your machine using npm i -g abacus-webdriver. Using this method allows you to execute your tests by simply typing abacus in the directory of your project. You can specify a config file, for example one called local.json, by typing abacus local.json

If you prefer not to globally install abacus, you can call execute your tests by typing ./node_modules/.bin/abacus. You can specify a config file, for example one called local.json, by typing ./node_modules/.bin/abacus local.json

The third (and my personal favorite) way is to add an npm script alias in your package.json file. In your scripts section, add the following line:

  "scripts"{
    "abacus": "abacus"
  }

You can then run your tests by typing npm run abacus. You can specify a config file, for example one called local.json, by typing npm run abacus -- local.json. In this case, the two hyphens (--) are used to tell npm that the following text will be command line arguments.

Example

Coming Soon!