Introduction
There is currently one actively supported language available to write custom scripts in kapptivate Testing & Monitoring : JavaScript.
Deprecated actions
We used to offer other actions to write custom scripts in kapptivate Testing & Monitoring :
These actions are however deprecated at the time of writing, meaning that :
-
We still allow existing actions to be executed in kapptivate ;
-
We don't allow the creation of new actions of these kinds ;
-
The documentation for these actions is only left here for maintaining existing actions until they can be migrated to the new format.
If you are the author of such actions and need to maintain one, we strongly recommend you to migrate it to the new format, as it will give you better flexibility and features. In this page, we'll explore together the capabilities of the JavaScript actions.
Glossary
These JavaScript actions are divided depending on the specific use case :
-
Testing on real devices :
-
Web testing
-
Android app testing
-
Android web testing
-
iOS app testing
-
iOS web testing
-
- Scripting without any device :
All the actions under 1. require a real device (e.g. : a web or smartphone robot) to be performed.
The remaining action under 2. however does not require any specific device and is intended to be used for writing basic or even complex scripts to enhance the existing actions. A typical use case is to perform some transformations on the result of an API call from a previous action.
The actions under 1. will be referred to as ownerful actions in the rest of this documentation, whereas the remaining one under 2. will be referred to as ownerless action. Consider that an owner is a device. In short :
-
An ownerful action is an action that requires a device to be performed ;
-
An ownerless action is an action that does not require any device to be performed.
Projects used
Under the hood, those JavaScript actions use a combination of the following frameworks :
We will provide examples for some of the things you can do with these actions, especially for the features that are not offered out of the box by the projects above, and were added by kapptivate. We still strongly encourage you to read through each of these projects' documentation for more information and examples that might not have been documented here.
Basic skeleton
Every JavaScript test should follow a base skeleton like this one :
describe('My test suite', function () {
// Potential variables declaration to be used in multiple steps below.
it('My test step #1', function () {
// Actions to be performed for this step.
});
it('My test step #2', function () {
// Actions to be performed for this step.
});
// Potential additional steps...
});
This skeleton is part of Mocha's BDD interface.
Warning : To ensure correct behavior of the features, you should always put as much code as possible inside steps (i.e. : inside an it block). Here are a few exceptions to this rule :
-
declare variables that should be used by multiple steps ;
Actions parameters
When it comes to JavaScript actions, all actions share a common set of parameters :
-
Name : An optional name for your script ;
-
Description : An optional description for your script ;
-
Ignore errors : A checkbox indicating whether the script should stop its execution at the first failing step (i.e. : checkbox unchecked) or if it should keep going.
These common parameters are present at the top of every JavaScript action :
JavaScript editor common parameters
The Name will appear at the top of your results :
JavaScript editor common parameters name
The Description in the Summary tab :
JavaScript editor common parameters name & description
And the Ignore errors option in the Parameters tab :
JavaScript editor common parameters ignore errors
Ownerful actions parameters
Ownerful JavaScript actions all offer an additional option called Use custom capabilities :
JavaScript editor custom capabilities
This option allows you to overwrite or extend the capabilities passed by WebdriverIO to the underlying WebDriver server. It takes the form of a JSON-valid object with keys and values. For example, by checking the box on a Web Script action and entering the following text :
{"acceptInsecureCerts":true}
JavaScript editor custom capabilities example
You will effectively tell the browser that will be executing your script to ignore expired or invalid TLS certificates and continue the script's execution. This acceptInsecureCerts capability is one of the documented capabilities for the WebDriver protocol when it comes to web browsers.
You can find more information about capabilities here :
Android app specific parameters
The Android app JavaScript action offers three additional options :
-
Application package : The package name of the Android app you want to run, similar to iOS's Bundle ID (read more on Appium's documentation, look for appPackage) ;
-
Application activity : The activity name you want to launch from your package (read more on Appium's documentation, look for appActivity) ;
-
Reset the app : A checkbox indicating whether the app's state should be reset before running the script or not (read more on Appium's documentation, look for noReset).
JavaScript editor Android app
iOS app specific parameters
The iOS app JavaScript action offers two additional options :
-
Bundle ID : The bundle ID of the iOS app you want to run, similar to Android's Application package (read more on Appium's documentation, look for bundleId) ;
-
Reset the app : Identical to the Android option above.
JavaScript editor iOS app
Write to standard streams
You can write content to standard streams like you would in basic JavaScript:
-
Standard output:
console.log("Lorem Ipsum", 42);The content you printed will automatically show up in the System-out tab of your results
-
Standard error:
console.error("Lorem Ipsum", 42);The content you printed will automatically show up in the System-err tab of your results
Make a test fail
To make a test fail, you can simply throw a new error from your script (be careful not to use it in "after" or "afterEach" functions):
describe('My suite', function () {
it('Step 1', function () {
console.log("Step 1 is running")
});
it('Step 2', function () {
console.log("Step 2 is running")
throw new Error("This step should fail with this error message.")
});
});
JavaScript result fail test
Use hooks
Mocha's hooks is a powerful feature allowing you to execute code at given moments throughout the whole test. We support all of Mocha's hooks :
-
before() : executed before your entire suite (i.e. : what you put inside the describe block) ;
-
beforeEach() : executed before every test (i.e. : what you put inside the it block) ;
-
afterEach() : executed after every test (i.e. : what you put inside the it block) ;
-
after() : executed after your entire suite (i.e. : what you put inside the describe block).
Here is an example of using all four of Mocha's hooks for various applications.
describe('My suite', function () {
before(function(){
console.log("My suite is about to start");
});
beforeEach(function () {
console.log(`The step "${this.currentTest.title}" is about to start`);
// Set a dynamic custom error message for every step in case it fails, except for step 2.
if (this.currentTest.title !== "Step 2") {
test.errorMessage.set(`${this.currentTest.title} failed`);
}
});
afterEach(function(){
console.log(`The step "${this.currentTest.title}" just finished`);
if (this.currentTest.title === "Step 3") {
// Execute additional code only for one given step.
console.log("Doing more things after step 3");
// Set a variable's value conditionally after its state.
if (this.currentTest.state == 'failed') {
test.variables.set("STEP_3_SUCCEEDED", false);
} else {
test.variables.set("STEP_3_SUCCEEDED", true);
}
}
if (this.currentTest.title === "Step 5" && this.currentTest.state == 'failed') {
// Overwrite the error message once more for this particular step.
test.errorMessage.set("Step 5 failed and there's nothing we can do about it");
}
});
after(function(){
console.log("My suite just finished");
});
it('Step 1', function () {
console.log("Step 1 is running")
});
it('Step 2', function () {
console.log("Step 2 is running")
throw new Error("This test should fail with this error message.")
});
it('Step 3', function () {
console.log("Step 3 is running")
throw new Error("This test should fail and get its error message replaced in `beforeEach`.")
});
it('Step 4', function () {
console.log("Step 4 is running")
throw new Error("This test should fail and get its error message replaced in `beforeEach` too.")
});
it('Step 5', function () {
console.log("Step 5 is running")
throw new Error("This test should fail too but its error message should be overwritten in `afterEach`.")
});
it('Step 6', function () {
console.log("Step 6 is running")
});
});
Note 1 : At the time of writing, there is no way to show both the "Stacktrace" and the "System-out" tabs at the same time for failed steps. So we included two screenshots for this example : one with the "Stacktrace" tab highlighted, and one with the "System-out" tab highlighted.
Note 2 : The example above may not make much sense in terms of real world application, but it is sufficient to demonstrate the usage and potential of these hooks.
Manipulate timers
Notice that in the Create custom metrics section above we created a metric with static information. That's not very representative of a real world scenario : users are often interested in measuring the duration of some actions and creating KPIs with this information to troubleshoot their services. For that reason, we included a simple timer in all JavaScript actions.
To create a new timer, you should use the following code :
new Timer();
This returns a timer object and directly calls its start function (see below), so your timer is already running and you don't have to start it by hand.
The following methods are available on a timer object :
-
start : Calls reset and starts the timer by storing the current timestamp inside a "start" attribute ;
-
reset : Resets the timer's "start" and "stop" attributes (this method is mostly called internally so you shouldn't need to call it yourself in most cases) ;
-
stop : Stops the timer by storing the current timestamp inside a "stop" attribute and returns the result of getDuration, so you don't have to call it separately ;
-
getDuration : Returns the difference between the timer's "start" and "stop" attributes. If the "stop" attribute is not defined (i.e. : you didn't stop the timer), the current time will be used instead (consider this as a "lap" button on an actual timer).
Warning : getDuration will throw an error if the timer wasn't started. So make sure to always call start after calling reset if you want to read the timer again.
Simple example
Here is an example of using the timer inside a JavaScript action :
describe('My test suite', function () {
it('Should start a timer and measure the duration of some actions', function () {
console.log("Start timer");
let timer = new Timer();
console.log("Perform some actions taking 1s...");
const duration = timer.stop();
console.log(`Stop timer, pause duration = ${duration}s`);
});
});
Note : The example above simulates actions taking 1s to be completed. This code will obviously not take 1s to be completed but we wanted to keep the example straightforward. Read the Make pauses section below to learn how we actually created a step that takes 1s to be completed.
JavaScript result timer simple
Reuse timer example
Here is an example showing how a single timer can be reused over and over accross multiple steps :
describe('My test suite', function () {
let timer;
it('Step 1', async function () {
console.log("Start timer");
timer = new Timer();
console.log("Sleep for 1s...")
await test.pause(1000);
const duration = timer.stop();
// Should output roughly 1s.
console.log(`Stop timer, pause duration = ${duration}s`);
});
it('Step 2', async function () {
// Since the timer is still stopped, this pause should not impact the duration read below.
console.log("Sleep for 5s...")
await test.pause(5000);
console.log("Start timer");
// Here we reuse the same timer.
timer.start();
console.log("Sleep for 1s...")
await test.pause(1000);
// Here we read the timer without stopping it (i.e. : the "lap" button on a real timer).
const duration = timer.getDuration();
// Should still output roughly 1s.
console.log(`Stop timer, pause duration = ${duration}s`);
});
it('Step 3', async function () {
// The timer is still running.
console.log("Sleep for 2s...")
await test.pause(2000);
const duration = timer.stop();
// Should output roughly 3s.
console.log(`Stop timer, pause duration = ${duration}s`);
});
});
Note : Contrary to the simple example above, which was focused on using the timers, this one shows the use of pauses. Read the Make pauses section below to learn more.
JavaScript result timer reuse
Complex example
Here is a more complex example demonstrating the use of two timers concurrently and across multiple steps :
describe('My test suite', function () {
let timer1;
let timer2;
it('Timers (part 1)', async function () {
timer1 = new Timer();
timer2 = new Timer();
console.log("Start timer 1");
timer1.start();
console.log("Pause for 1s");
await test.pause(1000);
console.log("Start timer 2");
timer2.start();
console.log("Pause for 3s");
await test.pause(3000);
const duration = timer1.stop();
console.log(`Stop timer 1, duration = ${duration}s`);
});
it('Timers (part 2)', async function () {
console.log("Pause for 2s");
await test.pause(2000);
const duration = timer2.stop();
console.log(`Stop timer 2, duration = ${duration}s`);
});
});
Note : Contrary to the simple example above, which was focused on using the timers, this one shows the use of pauses. Read the Make pauses section below to learn more.
JavaScript result timer complex
Make pauses
Notice that in the Manipulate timers section above we simulated actions taking 1s to be completed. What we actually did was set up a pause of 1s. This is done in one of two ways depending on whether you are running an ownerless or ownerful action.
You will find below a complete example for each situation, but in short, here are the differences :
| Action kind | Need for using the async/await combo |
| ownerless | Yes |
| ownerful | No |
Warning : The two approaches are not interchangeable, meaning that you must choose the one matching the action you are running. In short :
-
Using async/await will not work as expected in an ownerful action ; and
-
Forgetting to use async/await will not work as expected in an ownerless action.
Making pauses with the ownerless action
When using the ownerless action, you need to prefix the function keyword of every it block that contains a pause by async, and every call to test.pause in that block by await :
describe('My test suite', function () {
it('Should make a pause of 1s', async function () {
console.log("Start timer");
const timer = new Timer();
console.log("Pause for 1s");
await test.pause(1000);
const duration = timer.stop();
console.log(`Stop timer, pause duration = ${duration}s`);
});
});
JavaScript result pause Mocha
Making pauses with an ownerful action
When using an ownerful action, all your it blocks can look the same (i.e. : without any async/await) regardless of whether you used a pause inside it or not :
describe('My test suite', function () {
it('Should make a pause of 1s', function () {
console.log("Start timer");
const timer = new Timer();
console.log("Pause for 1s");
test.pause(1000);
const duration = timer.stop();
console.log(`Stop timer, pause duration = ${duration}s`);
});
});
JavaScript result pause WebdriverIO
Make assertions
Please refer to the Chai section if you are doing ownerless actions, and to both the Chai and WebdriverIO's expect sections if you are doing ownerful actions.
Scroll until elements are visible
You can scroll in all directions until elements are visible, based on a given condition.
Usage
driver.scrollIntoView(value, { approach, direction });
Parameters
| Name | Type | Details |
| value | string | The "value" to which you want to scroll to. It is related to the approach parameter. For instance, if approach is text, value will represent the "text" to which you want to scroll. If approach is xpath, value should be a full xpath to which you want to scroll. |
| approach optional | string |
The approach to use for scrolling (default: text). Can be one of the following :
|
| direction optional | string |
The direction in which to scroll (default: down). Can be one of the following :
|
Examples
Scroll down to find a text
Here is an example of scrolling down to find an element based on its text :
describe('My suite', function () {
it(`Scroll down until element with text "some text" is visible`, function () {
elem = driver.scrollIntoView(`some text`, {
"approach": "text",
"direction": "down"
});
});
});
Scroll up to find a description
Here is an example of scrolling up to find an element based on its description :
describe('My suite', function () {
it(`Scroll up until element with description "some description" is visible`, function () {
elem = driver.scrollIntoView(`some description`, {
"approach": "description",
"direction": "up"
});
});
});
Scroll right to find an xpath
Here is an example of scrolling up to find an element based on its xpath :
describe('My suite', function () {
it(`Scroll right until element with xpath "//*[contains(@name,"some value")])" is visible`, function () {
elem = driver.scrollIntoView(`//*[contains(@name,"some value")])`, {
"approach": "xpath",
"direction": "right"
});
});
});
Take additional screenshots during your test steps
On top of the 2 screenshots taken at the beginning and end of each of the tests steps, you can trigger additional screenshots within these steps.
Usage
driver.screenshot(title);
Parameters
| Name | Type | Details |
| title | string | The title you want to set for your screenshot (optional) |
Examples
Take a screenshot for
Here is an example :
describe('My suite', function () {
it(`Enter pin code`, function () {
elem = $(`//*[contains(@text,"1")]`);
elem.click();
driver.screenshot();
elem = $(`//*[contains(@text,"2")]`);
elem.click();
driver.screenshot();
elem = $(`//*[contains(@text,"3")]`);
elem.click();
driver.screenshot();
elem = $(`//*[contains(@text,"4")]`);
elem.click();
});
});
Extract SMS content
You may be able to use a SMS content in a web / web mobile script, if the sim card receiving the SMS is available from the kapptivate product you are using for your web / web mobile script.
Usage
driver.getSMS(number,message)
Parameters
| Name | Type | Details |
| number | string | The national MSISDN of the SIM receiving the SMS |
| message | string | The expected message |
Examples
Here is an example if you want to extract an OTP in a simple case:
let receiverNationalMSISDN;
before(function() {
receiverNationalMSISDN = test.variables.get("RECEIVER_NATIONAL_MSISDN");
})
it(`Get SMS`, async function () {
const receivedMessage = await driver.getSMS(receiverNationalMSISDN, "OTP: ")
const otp = driver.extractOTP(receivedMessage)
console.log(`Received SMS : ${receivedMessage}`)
test.variables.set("OTP", otp)
});
Here is an example if you want to extract an OTP in a customized case:
let receiverNationalMSISDN;
before(function() {
receiverNationalMSISDN = test.variables.get("RECEIVER_NATIONAL_MSISDN");
otpRegex = test.variables.get("OTP_REGEX");
})
it(`Get SMS`, async function () {
const receivedMessage = await driver.getSMS(receiverNationalMSISDN, "Hi ! I'm Elfo !\nOTP: {OTP_REGEX-RECEIVED_OTP_AUTOMATICALLY_EXTRACTED}", {variables: {OTP_REGEX: /\d{4}/gm}})
const otp = driver.extractOTP("Hi ! I'm Elfo !\nOTP: 1234ABC5678, goodbye !", {otpRegex: /OTP: (.*), goodbye/gm})
test.variables.set("RECEIVED_OTP_MANUALLY_EXTRACTED", otp)
});
Custom error messages
We added the ability to easily define what should be the error message of a given step. All you have to do is call test.errorMessage.set with the error message of your liking :
describe('My suite', function () {
it('Custom error message #1', function () {
test.errorMessage.set("`true` is not a string");
assert.typeOf(true, 'string');
});
it('Custom error message #2', function () {
test.errorMessage.set("Something went wrong");
assert.typeOf(true, 'string');
});
it('Default error message', function () {
assert.typeOf(true, 'string');
});
});
JavaScript result custom error message
Libraries
The following public libraries are automatically included in every action (so you don't have to import them) and ready for you to use just like you are used to :
These libraries are common to both ownerless and ownerful actions :
-
Lodash
-
Moment.js
-
Moment Timezone
-
Chai
These libraries are only available for ownerful actions :
-
WebdriverIO's expect
Feel free to tell us if you'd like to see other libraries included as well in future versions.
Lodash
Here is an example of using Lodash inside a JavaScript action :
describe('My test suite', function () {
it('Lodash', function () {
console.log(_.defaults({ 'a': 1 }, { 'a': 3, 'b': 2 })); // { 'a': 1, 'b': 2 }
console.log(_.partition([1, 2, 3, 4], n = n % 2)); // [[1, 3], [2, 4]]
});
});
JavaScript result Lodash.js
Moment.js
Here is an example of using Moment.js inside a JavaScript action :
describe('My test suite', function () {
it('Moment.js', function () {
console.log(moment().format('MMMM Do YYYY, h:mm:ss a')); // April 3rd 2020, 10:31:22 am
console.log(moment().format('dddd')); // Friday
console.log(moment().format("MMM Do YY")); // Apr 3rd 20
console.log(moment().format('YYYY [escaped] YYYY')); // 2020 escaped 2020
console.log(moment().format()); // 2020-04-03T10:31:22+00:00
});
});
JavaScript result Moment.js
Moment Timezone
Here is an example of using Moment Timezone inside a JavaScript action :
describe('My test suite', function () {
it('Moment Timezone', function () {
var jun = moment("2014-06-01T12:00:00Z");
var dec = moment("2014-12-01T12:00:00Z");
console.log(jun.tz('America/Los_Angeles').format('ha z')); // 5am PDT
console.log(dec.tz('America/Los_Angeles').format('ha z')); // 4am PST
console.log(jun.tz('America/New_York').format('ha z')); // 8am EDT
console.log(dec.tz('America/New_York').format('ha z')); // 7am EST
console.log(jun.tz('Asia/Tokyo').format('ha z')); // 9pm JST
console.log(dec.tz('Asia/Tokyo').format('ha z')); // 9pm JST
console.log(jun.tz('Australia/Sydney').format('ha z')); // 10pm EST
console.log(dec.tz('Australia/Sydney').format('ha z')); // 11pm EST
});
});
JavaScript result Moment Timezone
Chai
Chai allows you to make assertions inside your steps. It provides 3 interfaces and allows you to use the one you're most comfortable with :
We used to include all 3 interfaces. However, WebdriverIO recently introduced their own assertion library called expect-webdriverio, which uses the same expect keyword as Chai's own Expect API (you can read more in the WebdriverIO's expect section). We decided to stop including Chai's version altogether for the following reasons :
- WebdriverIO's version is more fully featured than any of Chai's APIs when it comes to end-to-end testing on real devices ;
- We wanted to keep the expect keyword for WebdriverIO's API only, to avoid having to resort to aliases like wdioExpect or chaiExpect, which are less readable ;
- Using the same keyword for two different APIs depending on the action type (Chai's for ownerless actions and WebdriverIO's for ownerful ones) would have been confusing ;
- We recommend that you use Chai's Should API over the Expect and Assert ones, as it is the "highest level" of the three (i.e.: the one that is the closest to human language) and, therefore, fulfills the "goal" of an assertion library (i.e.: avoid having to write "complex" code to express "simple" assertions) the most ;
Should
describe('My test suite', function () {
it("should pass (Chai's Should API)", function () {
const variable = "some string";
variable.should.be.a("string");
});
it("should fail (Chai's Should API)", function () {
const variable = true;
variable.should.be.a("string");
});
});
JavaScript result Chai Should
Assert
describe('My test suite', function () {
it("should pass (Chai's Assert API)", function () {
const variable = "some string";
assert.typeOf(variable, 'string');
});
it("should fail (Chai's Assert API)", function () {
const variable = true;
assert.typeOf(variable, 'string');
});
});
JavaScript result Chai Assert
WebdriverIO's expect
WebdriverIO recently introduced their own assertion library called expect-webdriverio. The goal is the same as Chai's APIs, but providing an even higher level of abstraction regarding end-to-end testing on real devices. This allows you to write the same tests that you used to write combining WebdriverIO and Chai, but in an even simpler form. For this reason, we currently recommend to use WebdriverIO's expect API to write assertions in your ownerful actions.
Examples might be added later on in this documentation, but WebdriverIO already made an excellent work documenting their API and providing concrete exemples of frequent use cases. Therefore, we recommend you to refer to these for the time being :
xml-js
We included a small XML parsing library called xml-js, allowing you to easily convert XML formatted API responses to JSON for example.
Here is an example of using xml-js inside a JavaScript action :
describe('My suite', function () {
it('Convert XML to JSON', async function () {
const xml = `
World
`;
const compactJSON = xml2json(xml, {compact: true, spaces: 4});
const expendedJSON = xml2json(xml, {compact: false, spaces: 4});
console.log("compact JSON : \n", compactJSON);
console.log("\nexpended JSON : \n", expendedJSON);
});
});
JavaScript result xml-js