Custom Generators

In addition to the official code generators, SnapTest gives you the ability to design your own custom generator.

This is helpful when you need to tweak an official generator, or if there's a framework/style that doesn't exist yet in the community.

Basics generator info:

  1. They are written in NodeJS.
  2. Custom generators are executed via the snaptest-cli tool.
  3. Generators output a "folder" of the tests - not the configuration.

Prerequisites to this tutorial:

  1. node and npm installed and available on your terminal.
  2. snaptest-cli installed globally and available by typing snaptest.

Steps:

  1. Step 1: Setup new generator project
  2. Step 2: Generate directory folders and test files
  3. Step 3: Generate test content
  4. Step 4: Generate drivers & components
  5. (Optional) Step 5: Setup different styles
  6. (Optional) Step 6: Setup a harness

Step 1: Setup new generator project.

Let's create a project folder for your generator:

mkdir mygenerator
cd mygenerator
npm init

Create an index.js file with the following content:

module.exports.generate = function() {
  console.log("Hello world!");
  this.onComplete();
}

Execute snaptest -c ./index.js -t iHrsRTzgENFUVI1TPAtIFqyd0QElssxy1TA0X9yNzg

If everything is working, you should see:

Hello world!
SnapTest generation complete.

...and also, you should see a newly generated "snaptests" folder.

mygenerator
| - /node_modules
| - /snaptests (your generated tests, empty for now)
| - index.js
| - package.json

What just happened?

You ran a custom generator (index.js) through the snaptest-cli using the -c flag. The CLI did the following:

  1. Downloaded the required test data.
  2. Added some helpful metadata and helper methods
  3. Regenerated the top-level folder (default is "snaptests").

The context of the generate method (this) contains all the test data. Check it out by typing:

module.exports.generate = function() {
   console.log(this);
   this.onComplete();
}

Step 2: Generate folders and test files.

Let's generate the file and folder structure as they are on the extension tool. So first of all, let's install the snaptest-cli library locally to access some extra utilities, and also the the mkdirp library for easily creating folders.

npm install mkdirp --save
npm install snaptest-cli --save

Next, let's utilize these libraries in your index.js file.

var mkdirp = require("mkdirp");
var fs = require("fs");
var util = require("snaptest-cli/utils");

module.exports.generate = function() {

  const sharedFolder = this.topDirPath + "/common";    
  const testsFolder = this.topDirPath + "/tests";

  // generate directories.
  mkdirp.sync(sharedFolder);
  mkdirp.sync(testsFolder);  
  this.folders.forEach((folderPath) => mkdirp.sync(this.topDirPath + "/tests/" + folderPath));

  // generate empty test files.
  this.tests.forEach((test) => fs.writeFileSync(testsFolder + "/" + test.folderPath + util.sanitizeForFilename(test.name) + ".js", "hello world!"));

  this.onComplete();

}

Run the generator again with the same command: snaptest -c ./index.js -t iHrsRTzgENFUVI1TPAtIFqyd0QElssxy1TA0X9yNzg

If successful, you should see the following folder/file structure generated locally:

mygenerator
| - /node_modules
| - /snaptests
   | - /common (shared folder for components and drivers)
   | - /tests (since most test runners can run a folder, lets make one of just the tests.)
      | - /production
         | - Basic Snaptest Walkthrough.js
      | - /staging
         | - Form inputs.js
| - index.js
| - package.json
The folder structure is entirely up to you - Feel free to organize however you'd like. Some may want a folder to be a file, with the tests inside. Go crazy!

Step 3: Generating test content.

Generating what is actually in the tests is up to you, but in general it involves walking through the action list for each test, translating them into the proper Selenium code, spitting out a string, beautifying it, and writing it to a file.

Things each test must implement:

  • Every test must be able to start with a "baseUrl" variable.
  • Ideally, the baseUrl should be configurable from external configuration files or environment variables.
  • All officially supported variables should be in-scope and injected into action values/selectors.
  • All components should be able to chainable in the test execution.
  • You should be able to pass different variables into a component.
  • If making a "full project generator", implement ALL the required actions following the Generator action contract.
Use the official generators as your examples on how to generate the test content.

Example generating for webdriver.io:

From your index.js file, let's loop through the tests and call a generateTestCode(test) method on each one, which will return a string that we can write to a file:

var mkdirp = require("mkdirp");
var fs = require("fs");
var util = require("snaptest-cli/utils");
var Actions = require("snaptest-cli/constants").Actions;

module.exports.generate = function() {

  const sharedFolder = this.topDirPath + "/common";
  const testsFolder = this.topDirPath + "/tests";

  // generate directories.
  mkdirp.sync(sharedFolder);
  mkdirp.sync(testsFolder);  
  this.folders.forEach((folderPath) => mkdirp.sync(this.topDirPath + "/tests/" + folderPath));

  // Generate contents of tests in memory.
 this.tests.forEach((test) => test.testCode = generateTestCode(test, this.components));

  // generate empty test files.
  this.tests.forEach((test) => fs.writeFileSync(testsFolder + "/" + test.folderPath + util.sanitizeForFilename(test.name) + ".js", test.testCode));

  this.onComplete();

}

Next, let's implement the generateTestCode method in the same file directly below:

function generateTestCode(test) {

   var testCode = `
        var webdriverio = require('webdriverio');
        var options = { desiredCapabilities: { browserName: 'chrome'}};
        var client = webdriverio.remote(options);

        client.init()
   `;

   test.actions.forEach((action) => {
      if (action.type === Actions.FULL_PAGELOAD) {
         testCode += `.url("${action.value}")`;
      }
   });

   testCode += ".end();"

   return testCode;

}

Step 4: Generate drivers & components.

components

When rendering a test, you need to account for the COMPONENT action type which is an instance of a component. Please check the official generator code for examples on how to generate components.

drivers

To be considered a "full-project generator", each generator needs to meet the Generator Action Contract. In order to accomplish this, sometimes you'll need to enhance the framework you're working on with with a driver file.

For example, let's say you're implementing "TEXT_ASSERT". According to the action contract for this action type, you need to:

  1. ...wait for the element to exist,
  2. ...wait for the elements text to match the assert value.
  3. ...be able to accept a regular expression as the assert value.

In order to accomplish these things, you may need to extend the framework you're generating for, and that's what the driver file/files can be for - your own custom actions (depending on how the framework allows extending the default actions).

If a framework doesn't meet the action contract, you should really consider whether it is an appropriate framework for you project.

(Optional) Step 5: Setup different styles.

Sometimes, a single generator can have different "styles". Perhaps, you're generating for webdriver.io, which is agnostic about the assertion style. You can create a webdriver.io framework with mocha or chai assertions.

To do this, simply export an extra field in your index.js:

module.exports = {
    generate: generate,
    styles: ["mocha", "chai"]
};

...and switch between the two styles in your generate method:

function generate() {

  // when there are more styles, do the switching here:
  if (this.style === "mocha") {
      mochaGenerator.call(this);
  }

  if (this.style === "chai") {
      chaiGenerator.call(this);
  }

}
The snaptest-cli will make sure that a user specifies a style when generating when there are more than 1 styles available.

(Optional) Step 6: Set up a harness

In the spirit of helping each other out, it's helpful to make a harness project with starter configuration setup to run the generated tests. If you have a separate repository for this harness, you can specify it as follows:

module.exports = {
   generate: generate,
   styles: ["flat"],
   harness: "git@github.com:ozymandias547/snaptest-harness.git"
};
Group 6Record it…Play it…Generate it…GeneratorsSelector AlgorithmsPrivate modeTest organizationTeamsWorkspacesDedicated supportTest data storageDebugger toolsAdaptable TestsCLI tools and APIWhite-labeled codeVariablesMulti-test playbackManual testsComponents