Extending Kakunin
Kakunin allows you to easily add a custom code in order to extend it's functionality.
Internal services
Regex builder
Regex builder is a special builder for creating RegExp
objects based on regexp name. Internally it has access to not only to all built-in
regular expression files, but also custom ones specified by user.
const { regexBuilder } = require('kakunin');
const myRegex = regexBuilder.buildRegex('r:number');
//myRegex will contain RegExp object that matches regular expression under the name "number" in regexes file.
Variable store
Variable store allows you to store and read some values to be used during given scenario.
const { variableStore } = require('kakunin');
variableStore.storeVariable('some-name', 'some-value');
const myValue = variableStore.getVariableValue('some-name'); //contains 'some-value'
User provider
Kakunin comes with functionality that allows you to easily load credentials for a given account type - UserProvider
.
In kakunin.conf.js
you can find a section accounts
.
The structure it has is very simple:
"accounts": {
"someAccount": {
"accounts": [
{
"email": "",
"password": ""
}
]
}
}
someAccount
- the name of accounts group
accounts
- an array of account credentials (in order to be able to check if a currentUser
got an email, this has to have an email
key, otherwise account can have any kind of
properties)
Use provider is accessible inside any kind of a step by calling this.userProvider
. It comes with a single method:
this.userProvider.getUser(groupName)
- returns an account credentials for a given user group.
It is a good practice to save a current user in this.currentUser
variable for a email checking service.
Adding custom code
Custom step
In order to add a custom step, you have to create inside of a directory specified as step_definitions
in kakunin configuration file default: /step_definitions
.
We're using cucumber-js 4.X
so in order to add custom step you have to use defineSupportCode
method like this:
const { defineSupportCode } = require('kakunin');
defineSupportCode(({ When }) => {
When(/^I use kakunin$/, function() {
expect(true).to.equal(true);
});
});
Page objects
Kakunin comes with some built-in page objects, that should be used as a base for your page objects.
In order to create a custom one, create a file inside the pages
directory and extend the BasePage
from kakunin package.
const { BasePage } = require('kakunin');
class MyPageObject extends BasePage {
constructor() {
this.myElement = element(by.css('.some-elemnt'));
}
}
module.exports = MyPageObject;
Matchers
Matchers are used to compare if given value is matching our expectation. For example if a value in table is a number.
You can add your own matcher as below:
const { matchers } = require('kakunin');
class MyMatcher {
isSatisfiedBy(prefix, name) {
return prefix === 'm:' && name === 'pending';
}
match(protractorElement, matcherName) {
return protractorElement.getText().then((value) => {
if (value === 'pending') {
return true;
}
return Promise.reject(`Matcher "MyMatcher" could not match value on element "${protractorElement.locator()}". Expected: "pending", given: "${value}"`);
});
}
}
matchers.addMatcher(new MyMatcher());
Dictionaries
Dictionaries allows you to present complicated values in much more readable way. For example if an element must be
in a form of IRI /some-resource/123-123-123-23
and you wish to use pending-resource
as it's alias.
You can add your own dictionary:
const { dictionaries } = require('kakunin');
const { BaseDictionary } = require('kakunin');
class TestDictionary extends BaseDictionary {
constructor() {
super('name-of-dictionary', {
'pending-resource': '/some-resource/123-123-123-23',
'test-value': 'some other value'
});
}
}
dictionaries.addDictionary(new TestDictionary());
Generators
Generators allows you to create random values
You can add your own generator:
const { generators } = require('kakunin');
class MyGeneerator{
isSatisfiedBy(name) {
return name === 'my-generator';
}
generate(params) {
return Promise.resolve('some-random-value');
}
}
generators.addGenerator(new MyGeneerator());
Comparators
Comparators allows you to check if a set of values has an expected order
You can add your own comparators:
const { comparators } = require('kakunin');
class MyComparator {
isSatisfiedBy(values) {
for(let i=0; i<values.length; i++) {
if (values[i] !== 'foo' && values[i] !== 'bar') {
return false;
}
}
return true;
}
compare(values, order) {
for (let i = 1; i < values.length; i++) {
const previousValue = values[i - 1];
const currentValue = values[i];
if (previousValue === currentValue) {
return Promise.reject('Wrong order');
}
}
return Promise.resolve('Foo bar!');
}
};
comparators.addComparator(new MyComparator());
Form handlers
Form handlers allows you to fill the form inputs and check value of filled fields
You can add your own handlers:
const { handlers } = require('kakunin');
const MyHandler {
constructor() {
this.registerFieldType = false;
this.fieldType = 'default';
}
isSatisfiedBy(element, elementName) {
return Promise.resolve(elementName === 'someElementName');
}
handleFill(page, elementName, desiredValue) {
return page[elementName].isDisplayed()
.then(function () {
return page[elementName].clear().then(function () {
return page[elementName].sendKeys(desiredValue);
});
}
);
}
handleCheck(page, elementName, desiredValue) {
return page[elementName].isDisplayed()
.then(function () {
return page[elementName].getAttribute('value').then(function (value) {
if (value === desiredValue) {
return Promise.resolve();
}
return Promise.reject(`Expected ${desiredValue} got ${value} for text input element ${elementName}`);
});
}
);
}
};
handlers.addHandler(new MyHandler());
Transformers
Transformers can be used in steps When I fill the "form" form with:
and And the "joinOurStoreForm" form is filled with:
.
Existing transformers:
- generators (prefix:
g:
) - dictionaries (prefix:
d:
) - variableStore (prefix:
v:
) Transformers can be used in mentioned steps by using specific 'prefix', parameters are sent after:
sign. Example:g:generatorName:param:param
You can add your own handlers:
const { transformers } = require('kakunin');
class MyTransformer {
isSatisfiedBy(prefix) {
return 'yourPrefix:' === prefix;
}
transform(value) {
//code
}
}
transformers.addTransformer(new MyTransformer());
Email checking service
You can easily check emails with Kakunin. By default we give you MailTrap client implementation, but you can easily add your own client.
const { emailService } = require('kakunin');
class MyEmailService {
//you have access to full kakunin config
isSatisfiedBy(config) {
return config.email.type === 'my-custom-email-service';
}
//method used to clear emails before tests
clearInbox() {
...
}
//method used to get emails - this method should return emails in format described below
getEmails() {
...
}
//method used to retrive atachments for given email - should return attachments in format described below
getAttachments(email) {
...
}
//method used to mark given email as read
markAsRead(email) {
...
}
}
emailService.addAdapter(new MyEmailService());
Emails should be returned as an array of objects with given schema:
[
{
"subject": "SMTP e-mail test",
"sent_at": "2013-08-25T19:32:07.567+03:00",
"from_email": "me@railsware.com",
"from_name": "Private Person",
"to_email": "test@railsware.com",
"to_name": "A Test User",
"html_body": "",
"text_body": "This is a test e-mail message.\r\n",
"email_size": 193,
"is_read": true,
"created_at": "2013-08-25T19:32:07.576+03:00",
"updated_at": "2013-08-25T19:32:09.232+03:00",
"sent_at_timestamp": 1377448326
}
]
this is MailTrap email format.
Attachments should be returned as an array of objects with given schema:
[
{
"id": 1737,
"message_id": 54508,
"filename": "Photos.png",
"attachment_type": "attachment",
"content_type": "image/png",
"content_id": "",
"transfer_encoding": "base64",
"attachment_size": 213855,
"created_at": "2013-08-16T00:39:34.677+03:00",
"updated_at": "2013-08-16T00:39:34.677+03:00",
"attachment_human_size": "210 KB",
"download_path": "/api/v1/inboxes/3/messages/54508/attachments/1737/download"
}
]
this is MailTrap attachment format.