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.
snaptest-cli
tool.node
and npm
installed and available on your terminal.snaptest
.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
You ran a custom generator (index.js) through the snaptest-cli
using the -c flag. The CLI did the following:
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();
}
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
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:
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;
}
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.
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:
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).
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);
}
}
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"
};