Testing your app
As based on the KDK, The skeleton relies on the Mocha testing framework and the Chai assertion library.
API
From the root folder run the server-side tests:
WARNING
To run the server-side tests, the server must not be running.
$yarn test:server
This will lint and fix issues in the code according to JS standard, then execute tests using Mocha and compute code coverage using c8.
From the backend api
folder you can also run the server-side tests like for modules:
# with lint/coverage
$yarn test
# without lint/coverage
$yarn mocha
Client
From the root folder run the client-side tests:
WARNING
To run the client-side tests, you must first run the web app.
$yarn test:client
This will execute tests using Mocha/Puppeteer and compute code coverage using c8.
TIP
By default the tests will assume that youy web app run on the default 8080
port or the one provided through the CLIENT_PORT
environment variable. If a deployment flavor is defined it will target the application domain defined as https://${appName}.${process.env.SUBDOMAIN}
.
From the root folder you can also run the client-side tests without coverage like for the API:
$yarn mocha
TIP
If you need to run some specific tests suite only, you can use the -g
or --grep
option of the mocha
command:
$yarn mocha -g "suite:groups" # run the groups tests
Some tests, notably involving web mapping, might rely on screenshots comparison, as a consequence the tests can be run in different modes:
preview
mode to only run tests without comparing to reference screenshots, useful when tweaking the testrun
mode to run tests by comparing to reference screenshots, useful to perform non-regression testing (default mode)record
mode to run tests and update reference screenshots, useful to save reference images just before releasing
TIP
You can use the TEST_MODE
environment variable to change the test mode.
Writing client tests
In addition to Mocha and Chai helpers, the KDK provides helper functions to test your UI using Puppeteer:
test your UI components: the Runner class allows you to manage a Puppereer browser as well as the default page attached to this browser. In addition, it provides a useful inteface to override permissions, set items in local storage, catch warnings and error messages from the console, take screenshots and compare them to some reference images.
deal with your app's API: the Api class allows you to access the differents services exposed by the API.
structure your project test structure: the KDK assumes that a directory has been assigned to each test suite in order to store test data. These directories, are stored in a more general
data
directory and must be named with the test suite name. If you have any screenshot references then then they must stored in a decidacted sub directory namedscreenrefs
.
test
|_ data
| |_ suite#1
| |_ suite#2
| |_ screenrefs
| |_ suite#3
| |_ screenrefs
|_ suite1.test.js
|_ suite2.test.js
|_ suite3.test.js
When running the test data are generally generated in order to be compared to the static test data. The Runner creates a run
directory where it stores for the browser (e.g. chrome, firefox) the data for each test suite. For instance:
test
|_run
| |_ chrome
| |_ suite#1
| |_ suite#2
| |_ suite#3
TIP
The Runner clears its corresponding test suite run directory each time you run it.
Core
Runner
In pratice, a Runner instance has to be created in each test suite. You should declare the Runner in the before hook as well as starting it. Starting the Runner creates the Puppeteer Browser and open the default page. Obviously, you should stop the Runner which causes the Puppeteer Browser to be closed in the after hook.
import { core } from '@kalisio/kdk/test.client'
const suite = 'my-suite'
describe(suite, () => {
let runner
let page
before(async () => {
runner = new core.Runner(suite, { browser: { args: ['--lang=fr'] })
page = await runner.start()
})
it('my-test', async () => {
....
})
after(async () => {
await runner.stop()
})
})
A lot of options can be passed to the runner and some defaults provided using environment variables:
{
// computed by default as explained in the previous section
baseUrl: 'https://my.domain.com',
browser: { // Puppeteer browser options
// defaults to chrome or use BROWSER env var
product: 'chrome',
// defaults to false or use HEADLESS env var
headless: true,
// default to true if NODE_ENV env var equals 'development'
devtools: true,
defaultViewport: {
width: 1280, // defaults to 1024 or use VIEWPORT_WIDTH env var
height: 1024 // defaults to 768 or use VIEWPORT_HEIGHT env var
},
// defaults to [] or use BROWSER_ARGS env var split as comma separated list of arguments
args: ['--lang=fr']
},
// defaults directories
dataDir: './test/data',
runDir: './test/run',
screenrefsDir: './test/data/screenrefs',
screenshotsDir: './test/data/screenshots',
// defaults to run or use TEST_MODE env var
mode: 'record',
// defaults to false, enable to save difference images
writeDiffs: true,
// defaults to 0.1, increase/decrease to allow more/less tolerance when comparing screenshots
matchTreshold: 0.2,
// Accuracy might be required to get some desired behaviours
geolocation: {
accuracy: 1000 // in meters, defaults to 500
},
// Web push notifications allowed
notifications: true
}
The runner provides you with utility function to create and compare screenshots:
// Create a screenshot with an optional clip zone
runner.capture (screenshot_name, boundingBox)
// Create a screenshot and compare with reference screenshot with an optional tolerance and clip zone
runner.captureAndMatch (screenshot_name, diffTolerance, boundingBox)
// Compare two screenshot with an optional tolerance
runner.compareCaptures (screenshot_name1, screenshot_name2, threshold)
Api
In order to speedup testing and avoid to manually create all the required resources manually in your test, this class provides you functions to automatically create/remove:
- users
- organisations
- groups
- tags
For instance, the following code will setup a complete organisation with members for you:
const org = {
owner: {
name: 'Owner',
email: 'owner@kalisio.xyz',
password: 'xxx'
},
members: [{
name: 'Manager',
email: 'manager@kalisio.xyz',
password: 'xxx',
permissions: 'manager',
groups: [{ name: 'Group', permissions: 'member' }]
}],
groups: [{
name: 'Group',
description: 'A group'
}]
}
before(async function () {
...
// Prepare structure for current run
api = new core.Api({
appName: 'crisis'
})
client = api.createClient()
runner = new core.Runner(suite, { ... })
await client.createOrganisation(org)
await client.createMembers(org)
await client.createGroups(org)
await client.tagMembers(org)
await client.groupMembers(org)
page = await runner.start()
})
TIP
The Api class is useful to clear required resources in case your test fail.
For instance, the following code will clear the previously created complete organisation for you:
after(async function () {
await runner.stop()
// First remove groups in case removal test failed
await client.removeGroups(org)
// Then members
await client.removeMembers(org)
// Then organisation/owner
await client.removeOrganisation(org)
})
Utility functions
Explore the various utilities located in kdk/test/client
that will be helpful to manage:
- the application layout
- the items of a collection
- the account and login screens
- the map catalog and controls