Repository: TheSoftwareHouse/Kakunin
Branch: master
Commit: 9348aef24a6f
Files: 409
Total size: 2.5 MB
Directory structure:
gitextract_s20x1q0_/
├── .dockerignore
├── .editorconfig
├── .gitignore
├── .npmignore
├── .prettierrc
├── .travis.yml
├── CHANGELOG.MD
├── CONTRIBUTING.MD
├── Dockerfile
├── LICENSE
├── MIGRATION-2.0.0.MD
├── MIGRATION-2.2.0.MD
├── MIGRATION-3.0.0.MD
├── ROADMAP.MD
├── build.sh
├── docker-compose.yml
├── docs/
│ ├── browserstack.md
│ ├── configuration.md
│ ├── cross-browser.md
│ ├── docker.md
│ ├── extending.md
│ ├── headless.md
│ ├── hooks.md
│ ├── how-it-works.md
│ ├── index.md
│ ├── matchers.md
│ ├── parallel-testing.md
│ ├── performance-testing.md
│ ├── quickstart.md
│ ├── steps-debug.md
│ ├── steps-elements.md
│ ├── steps-files.md
│ ├── steps-forms.md
│ ├── steps-generators.md
│ ├── steps-navigation.md
│ ├── steps-rest.md
│ ├── testing-rest-api.md
│ └── transformers.md
├── functional-tests/
│ ├── dictionaries/
│ │ └── test-dictionary.js
│ ├── downloads/
│ │ ├── .gitkeep
│ │ └── example.xlsx
│ ├── features/
│ │ ├── content/
│ │ │ ├── operations_on_stored_variables.feature
│ │ │ ├── validate_tabular_data.feature
│ │ │ ├── validate_tabular_data_css.feature
│ │ │ ├── wait_for_element_dissapear.feature
│ │ │ └── wait_for_element_dissapear_css.feature
│ │ ├── drag-and-drop/
│ │ │ ├── operations_on_elements.feature
│ │ │ └── operations_on_elements_css.feature
│ │ ├── forms/
│ │ │ ├── fill_and_check_form.feature
│ │ │ ├── fill_and_check_form_css.feature
│ │ │ └── fill_select.feature
│ │ ├── matchers/
│ │ │ ├── match_current_date.feature
│ │ │ ├── match_current_date_css.feature
│ │ │ ├── matchers.feature
│ │ │ └── matchers_css.feature
│ │ ├── navigation/
│ │ │ ├── navigate_to_given_page.feature
│ │ │ ├── navigate_to_given_page_css.feature
│ │ │ └── switch-between-tabs.feature
│ │ ├── pages/
│ │ │ └── verify_displayed_page.feature
│ │ ├── testing-api/
│ │ │ ├── testing_delete_request.feature
│ │ │ ├── testing_get_response.feature
│ │ │ ├── testing_headers_setting.feature
│ │ │ ├── testing_patch_request.feature
│ │ │ ├── testing_post_form_data.feature
│ │ │ └── testing_post_json.feature
│ │ └── wait-for-elements/
│ │ ├── wait_for_form.feature
│ │ ├── wait_for_form_css.feature
│ │ └── wait_for_table.feature
│ ├── kakunin.conf.js
│ ├── package.json
│ ├── pages/
│ │ ├── absolutePage.js
│ │ ├── additionalParams.js
│ │ ├── appearSimpleForm.js
│ │ ├── appearSimpleFormPost.js
│ │ ├── appearTabularData.js
│ │ ├── buttonForm.js
│ │ ├── dragAndDrop.js
│ │ ├── google.js
│ │ ├── main.js
│ │ ├── matchers.js
│ │ ├── navigationPages.js
│ │ ├── simpleForm.js
│ │ ├── simpleFormPost.js
│ │ ├── simpleSelectForm.js
│ │ └── tabularData.js
│ ├── regexes/
│ │ └── index.js
│ ├── step_definitions/
│ │ └── custom_json_parser.js
│ └── www/
│ ├── index.js
│ ├── jsonData/
│ │ └── xlsxData.router.js
│ └── views/
│ ├── absolute/
│ │ └── index.njs
│ ├── drag-and-drop/
│ │ └── index.njs
│ ├── form/
│ │ ├── disappear.njs
│ │ ├── select.njs
│ │ └── simple.njs
│ ├── index.njs
│ ├── layout/
│ │ └── default.njs
│ ├── matchers/
│ │ └── matchers.njs
│ ├── navigation/
│ │ └── page.njs
│ ├── table/
│ │ └── tabular-data.njs
│ └── wait-for-appear/
│ ├── form.njs
│ └── table.njs
├── package.json
├── readme.md
├── src/
│ ├── cli.ts
│ ├── comparators/
│ │ ├── comparator/
│ │ │ ├── date.comparator.spec.ts
│ │ │ ├── date.comparator.ts
│ │ │ ├── index.ts
│ │ │ ├── number.comparator.spec.ts
│ │ │ └── number.comparator.ts
│ │ ├── comparator.interface.ts
│ │ ├── comparators.spec.ts
│ │ ├── comparators.ts
│ │ └── index.ts
│ ├── core/
│ │ ├── cli/
│ │ │ ├── cli.helper.spec.ts
│ │ │ ├── cli.helper.ts
│ │ │ └── initializer.ts
│ │ ├── config.helper.ts
│ │ ├── fs/
│ │ │ ├── delete-files.helper.ts
│ │ │ └── prepare-catalogs.helper.ts
│ │ ├── modules-loader.helper.ts
│ │ └── prototypes.ts
│ ├── dictionaries/
│ │ ├── base.ts
│ │ ├── dictionaries.spec.ts
│ │ ├── dictionaries.ts
│ │ └── index.ts
│ ├── emails/
│ │ ├── adapter/
│ │ │ ├── mailtrap.client.spec.ts
│ │ │ └── mailtrap.client.ts
│ │ ├── email.service.spec.ts
│ │ ├── email.service.ts
│ │ ├── filter/
│ │ │ ├── current-user.filter.ts
│ │ │ ├── current-user.spec.ts
│ │ │ ├── index.ts
│ │ │ ├── minimal-email-size.filter.spec.ts
│ │ │ ├── minimal-email-size.filter.ts
│ │ │ ├── text-fields.filter.spec.ts
│ │ │ └── text-fields.filter.ts
│ │ ├── filters.spec.ts
│ │ ├── filters.ts
│ │ └── index.ts
│ ├── form-handlers/
│ │ ├── form-handler.interface.ts
│ │ ├── handler/
│ │ │ ├── checkbox.handler.ts
│ │ │ ├── ckeditor.handler.ts
│ │ │ ├── custom-angular-select.handler.ts
│ │ │ ├── default.handler.ts
│ │ │ ├── file.handler.ts
│ │ │ ├── index.ts
│ │ │ ├── radio.handler.ts
│ │ │ ├── select.handler.ts
│ │ │ └── uploaded-file.handler.ts
│ │ ├── handlers.ts
│ │ └── index.ts
│ ├── generators/
│ │ ├── generator/
│ │ │ ├── index.ts
│ │ │ ├── personalData.generator.spec.ts
│ │ │ ├── personalData.generator.ts
│ │ │ ├── string-with-length.generator.spec.ts
│ │ │ └── string-with-length.generator.ts
│ │ ├── generator.interface.ts
│ │ ├── generators.spec.ts
│ │ ├── generators.ts
│ │ └── index.ts
│ ├── index.ts
│ ├── kakunin.d.ts
│ ├── matchers/
│ │ ├── index.ts
│ │ ├── matcher/
│ │ │ ├── attribute.matcher.spec.ts
│ │ │ ├── attribute.matcher.ts
│ │ │ ├── clickable.matcher.spec.ts
│ │ │ ├── clickable.matcher.ts
│ │ │ ├── currentDate.matcher.spec.ts
│ │ │ ├── currentDate.matcher.ts
│ │ │ ├── index.ts
│ │ │ ├── invisible.matcher.spec.ts
│ │ │ ├── invisible.matcher.ts
│ │ │ ├── not-clickable.matcher.spec.ts
│ │ │ ├── not-clickable.matcher.ts
│ │ │ ├── present.matcher.spec.ts
│ │ │ ├── present.matcher.ts
│ │ │ ├── regex-matcher/
│ │ │ │ ├── index.spec.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── regex-builder.spec.ts
│ │ │ │ ├── regex-builder.ts
│ │ │ │ ├── regex.ts
│ │ │ │ └── regexes/
│ │ │ │ └── default.ts
│ │ │ ├── text.matcher.spec.ts
│ │ │ ├── text.matcher.ts
│ │ │ ├── visible.matcher.spec.ts
│ │ │ └── visible.matcher.ts
│ │ ├── matcher.interface.ts
│ │ ├── matchers.spec.ts
│ │ └── matchers.ts
│ ├── pages/
│ │ ├── base.ts
│ │ ├── form.ts
│ │ └── index.ts
│ ├── protractor.conf.ts
│ ├── rest/
│ │ ├── api-request.ts
│ │ ├── api-response.spec.ts
│ │ ├── api-response.ts
│ │ └── rest-api-service.ts
│ ├── step_definitions/
│ │ ├── api.ts
│ │ ├── debug.ts
│ │ ├── elements.ts
│ │ ├── email.ts
│ │ ├── file.ts
│ │ ├── form.ts
│ │ ├── generators.ts
│ │ ├── navigation.ts
│ │ ├── performance.ts
│ │ └── tabs.ts
│ ├── tests/
│ │ ├── dictionaries/
│ │ │ └── fake-dictionary.ts
│ │ └── init.ts
│ ├── transformers/
│ │ ├── index.ts
│ │ ├── transformer/
│ │ │ ├── dictionary.transformer.spec.ts
│ │ │ ├── dictionary.transformer.ts
│ │ │ ├── generator.transformer.spec.ts
│ │ │ ├── generator.transformer.ts
│ │ │ ├── variable-store.transformer.spec.ts
│ │ │ └── variable-store.transformer.ts
│ │ ├── transformer.interface.ts
│ │ ├── transformers.spec.ts
│ │ └── transformers.ts
│ └── web/
│ ├── browsers/
│ │ ├── browsers-config.helper.ts
│ │ ├── browserstack-config.helper.ts
│ │ ├── create-firefox-profile.helper.ts
│ │ ├── get-browser-drivers.helper.ts
│ │ └── safari-browser-configurator.helper.ts
│ ├── cucumber/
│ │ ├── config.ts
│ │ ├── hooks/
│ │ │ ├── clear-download.hook.ts
│ │ │ ├── clear-variables.hook.ts
│ │ │ ├── hook.interface.ts
│ │ │ ├── hooks.ts
│ │ │ ├── index.ts
│ │ │ ├── reload-fixtures.hook.ts
│ │ │ ├── reload-user.hook.ts
│ │ │ └── take-screenshots.hook.ts
│ │ ├── hooks.ts
│ │ └── wait-for-condition.helper.ts
│ ├── fixtures/
│ │ └── fixtures-loader.helper.ts
│ ├── fs/
│ │ ├── download-checker.helper.ts
│ │ └── file-manager.helper.ts
│ ├── parallel/
│ │ ├── chunk-specs.helper.spec.ts
│ │ ├── chunk-specs.helper.ts
│ │ └── prepare-browser-instance-specs.helper.ts
│ ├── parameters.ts
│ ├── performance/
│ │ ├── JSON-performance-report-parser.helper.spec.ts
│ │ ├── JSON-performance-report-parser.helper.ts
│ │ ├── time-to-first-byte-analyser.helper.spec.ts
│ │ └── time-to-first-byte-analyser.helper.ts
│ ├── url-parser.helper.spec.ts
│ ├── url-parser.helper.ts
│ ├── user-provider.helper.ts
│ ├── variable-store.helper.spec.ts
│ └── variable-store.helper.ts
├── templates/
│ ├── example.feature
│ ├── generator.js
│ ├── hook.js
│ ├── login.js
│ ├── matcher.js
│ ├── page.js
│ ├── regex.js
│ └── steps.js
├── tsconfig.json
├── tsconfig.test.json
├── tslint.json
└── website/
├── README.md
├── build/
│ └── Kakunin/
│ ├── css/
│ │ ├── main.css
│ │ └── prism.css
│ ├── docs/
│ │ ├── 2.4.0/
│ │ │ ├── configuration/
│ │ │ │ └── index.html
│ │ │ ├── configuration.html
│ │ │ ├── cross-browser/
│ │ │ │ └── index.html
│ │ │ ├── cross-browser.html
│ │ │ ├── docker/
│ │ │ │ └── index.html
│ │ │ ├── docker.html
│ │ │ ├── extending/
│ │ │ │ └── index.html
│ │ │ ├── extending.html
│ │ │ ├── how-it-works/
│ │ │ │ └── index.html
│ │ │ ├── how-it-works.html
│ │ │ ├── index.html
│ │ │ ├── matchers/
│ │ │ │ └── index.html
│ │ │ ├── matchers.html
│ │ │ ├── parallel-testing/
│ │ │ │ └── index.html
│ │ │ ├── parallel-testing.html
│ │ │ ├── performance-testing/
│ │ │ │ └── index.html
│ │ │ ├── performance-testing.html
│ │ │ ├── quickstart/
│ │ │ │ └── index.html
│ │ │ ├── quickstart.html
│ │ │ ├── steps-debug/
│ │ │ │ └── index.html
│ │ │ ├── steps-debug.html
│ │ │ ├── steps-elements/
│ │ │ │ └── index.html
│ │ │ ├── steps-elements.html
│ │ │ ├── steps-files/
│ │ │ │ └── index.html
│ │ │ ├── steps-files.html
│ │ │ ├── steps-forms/
│ │ │ │ └── index.html
│ │ │ ├── steps-forms.html
│ │ │ ├── steps-generators/
│ │ │ │ └── index.html
│ │ │ ├── steps-generators.html
│ │ │ ├── steps-navigation/
│ │ │ │ └── index.html
│ │ │ ├── steps-navigation.html
│ │ │ ├── transformers/
│ │ │ │ └── index.html
│ │ │ └── transformers.html
│ │ ├── configuration/
│ │ │ └── index.html
│ │ ├── configuration.html
│ │ ├── cross-browser/
│ │ │ └── index.html
│ │ ├── cross-browser.html
│ │ ├── docker/
│ │ │ └── index.html
│ │ ├── docker.html
│ │ ├── extending/
│ │ │ └── index.html
│ │ ├── extending.html
│ │ ├── how-it-works/
│ │ │ └── index.html
│ │ ├── how-it-works.html
│ │ ├── index.html
│ │ ├── matchers/
│ │ │ └── index.html
│ │ ├── matchers.html
│ │ ├── next/
│ │ │ ├── browserstack/
│ │ │ │ └── index.html
│ │ │ ├── browserstack.html
│ │ │ ├── configuration/
│ │ │ │ └── index.html
│ │ │ ├── configuration.html
│ │ │ ├── cross-browser/
│ │ │ │ └── index.html
│ │ │ ├── cross-browser.html
│ │ │ ├── docker/
│ │ │ │ └── index.html
│ │ │ ├── docker.html
│ │ │ ├── extending/
│ │ │ │ └── index.html
│ │ │ ├── extending.html
│ │ │ ├── headless/
│ │ │ │ └── index.html
│ │ │ ├── headless.html
│ │ │ ├── hooks/
│ │ │ │ └── index.html
│ │ │ ├── hooks.html
│ │ │ ├── how-it-works/
│ │ │ │ └── index.html
│ │ │ ├── how-it-works.html
│ │ │ ├── index.html
│ │ │ ├── matchers/
│ │ │ │ └── index.html
│ │ │ ├── matchers.html
│ │ │ ├── parallel-testing/
│ │ │ │ └── index.html
│ │ │ ├── parallel-testing.html
│ │ │ ├── performance-testing/
│ │ │ │ └── index.html
│ │ │ ├── performance-testing.html
│ │ │ ├── quickstart/
│ │ │ │ └── index.html
│ │ │ ├── quickstart.html
│ │ │ ├── steps-debug/
│ │ │ │ └── index.html
│ │ │ ├── steps-debug.html
│ │ │ ├── steps-elements/
│ │ │ │ └── index.html
│ │ │ ├── steps-elements.html
│ │ │ ├── steps-files/
│ │ │ │ └── index.html
│ │ │ ├── steps-files.html
│ │ │ ├── steps-forms/
│ │ │ │ └── index.html
│ │ │ ├── steps-forms.html
│ │ │ ├── steps-generators/
│ │ │ │ └── index.html
│ │ │ ├── steps-generators.html
│ │ │ ├── steps-navigation/
│ │ │ │ └── index.html
│ │ │ ├── steps-navigation.html
│ │ │ ├── steps-rest/
│ │ │ │ └── index.html
│ │ │ ├── steps-rest.html
│ │ │ ├── testing-rest-api/
│ │ │ │ └── index.html
│ │ │ ├── testing-rest-api.html
│ │ │ ├── transformers/
│ │ │ │ └── index.html
│ │ │ └── transformers.html
│ │ ├── parallel-testing/
│ │ │ └── index.html
│ │ ├── parallel-testing.html
│ │ ├── performance-testing/
│ │ │ └── index.html
│ │ ├── performance-testing.html
│ │ ├── quickstart/
│ │ │ └── index.html
│ │ ├── quickstart.html
│ │ ├── steps-debug/
│ │ │ └── index.html
│ │ ├── steps-debug.html
│ │ ├── steps-elements/
│ │ │ └── index.html
│ │ ├── steps-elements.html
│ │ ├── steps-files/
│ │ │ └── index.html
│ │ ├── steps-files.html
│ │ ├── steps-forms/
│ │ │ └── index.html
│ │ ├── steps-forms.html
│ │ ├── steps-generators/
│ │ │ └── index.html
│ │ ├── steps-generators.html
│ │ ├── steps-navigation/
│ │ │ └── index.html
│ │ ├── steps-navigation.html
│ │ ├── transformers/
│ │ │ └── index.html
│ │ └── transformers.html
│ ├── en/
│ │ ├── versions/
│ │ │ └── index.html
│ │ └── versions.html
│ ├── index.html
│ ├── js/
│ │ ├── codetabs.js
│ │ └── scrollSpy.js
│ ├── sitemap.xml
│ ├── versions/
│ │ └── index.html
│ └── versions.html
├── core/
│ └── Footer.js
├── package.json
├── pages/
│ └── en/
│ └── versions.js
├── sidebars.json
├── siteConfig.js
├── static/
│ ├── css/
│ │ └── custom.css
│ └── index.html
├── versioned_docs/
│ ├── version-2.4.0/
│ │ ├── configuration.md
│ │ ├── cross-browser.md
│ │ ├── docker.md
│ │ ├── extending.md
│ │ ├── how-it-works.md
│ │ ├── index.md
│ │ ├── matchers.md
│ │ ├── parallel-testing.md
│ │ ├── performance-testing.md
│ │ ├── quickstart.md
│ │ ├── steps-debug.md
│ │ ├── steps-elements.md
│ │ ├── steps-files.md
│ │ ├── steps-forms.md
│ │ ├── steps-generators.md
│ │ ├── steps-navigation.md
│ │ └── transformers.md
│ └── version-2.5.0/
│ └── steps-elements.md
├── versioned_sidebars/
│ └── version-2.4.0-sidebars.json
└── versions.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
*/node_modules
*.log
================================================
FILE: .editorconfig
================================================
root=true
[*]
charset=utf-8
end_of_line=lf
insert_final_newline=true
indent_style=space
indent_size=2
[Makefile]
indent_style = tab
[*.feature]
indent_style=space
indent_size=4
================================================
FILE: .gitignore
================================================
#build files
dist
# dependencies
node_modules
#tests
selenium-debug.log
reports
screenshots
testium
tmp-*
npm-debug.log
.idea
downloads/*
!functional-tests/downloads/.gitkeep
!functional-tests/downloads/example.xlsx
docker-compose.override.yml
!downloads/.gitkeep
!functional-tests/reports/.gitkeep
!functional-tests/reports/report/.gitkeep
!functional-tests/reports/report/features/.gitkeep
!functional-tests/performance/.gitkeep
!example/reports/.gitkeep
!reports/.gitkeep
.DS_Store
functional-tests/package-lock.json
website/yarn.lock
website/node_modules
website/i18n/*
local.log
browserstack.err
================================================
FILE: .npmignore
================================================
website
functional-tests
docs
================================================
FILE: .prettierrc
================================================
{
"useTabs": false,
"printWidth": 120,
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "es5",
"bracketSpacing": true
}
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- "lts/*"
addons:
chrome: stable
script:
- npm run test-ci
- npm run lint
================================================
FILE: CHANGELOG.MD
================================================
#### v3.0.0
- added support for Browserstack
- updated external libraries
- locked selenium-standalone-server to `3.14.0` to prevent bugs caused due to updated external libraries (we'll update it manually)
- coverted Kakunin from Javascript to Typescript
- added possibility to set priority of hooks Breaking change! Take a look to the MIGRATION-3.0.0.MD
#### v2.6.0
- added possibility to control `headless` by CLI by a command `npm run kakunin -- --headless`
- added a possibility to visit URL w query params
- added support for Internet Explorer
#### v2.5.0
- added prepublish script to run tests before publish on NPM
- improved logging errors in the console
- added docusaurus documentation
- added possibility to test REST API
#### v2.4.0
- `When I pause` step has been deleted as the `browser.pause()` method is not supported since Node 8.x.x
- added `how to debug` section to the documentation
- added example how to configure docker for Kakunin projects
- configured Travis CI - unit and functional tests
- added support for Safari and Firefox browsers (take a look at the `cross-browser` section in the documentation)
- added parallel functionality (take a look at the `parallel` section in the documentation)
- configured prettier and eslint on `git commit` action
- added a possibility to use `When I store` and `There is element` steps on `input`/`textarea` fields
- added `faker` generator (generate random names, cities etc.)
##### v2.3.0
- added a new step: `success if email not found`
- updated `emails section` in the documentation
- added a possibility to answer prompts by a bash command in the `init process`
- fixed `file.js` step and added functional tests covering `compare xlsx with stored data`
- updated dependencies
##### v2.2.0
- added migration rules to version `v2.2.0`
- fixed `wait for url change` error
- improved CLI scripts and added tests
- improved `When I click step` - added an extra wait until the element is clickable
- fixed the cache problem in the `local storage` (wait until the cache is cleared)
- improved how the matchers errors are displayed
- changed Mocca into Jest
- changed Chai for Jest syntax
- moved `maxEmailRepeats` to the configuration
- updated Mailtrap adapter due to changed API (take a look at the `MIGRATION-2.2.0.MD`)
- updated dependencies
##### v2.1.0
- multiple code refactors
- improved url comparing
- fixed bug: missing "reports/report/features" catalogs required to execute tests
- added functionality which deletes all report files before a test run
- added new matcher - `f:currentDate:YYYY-MM-DD` to generate a current date
- added a possibility to test performance with browserMob (save .har files) and compare TTFB timing values
- improvements for clicking element
- every element step waits for visibilityOf element (waiting before step is no longer required in common cases)
- added wait method to helpers, also is exported outside of kakunin
- added deprecated warning for is Present steps
- added matcher for currentDate
- code refactor for kakunin.conf.js
- replaced generator step with real generator supporting multiple params
- code cleanup (unused imports etc.),
- documentation update and test update (added useful undocumented and untested steps, removal of unused steps)
##### v2.0.0
- updated documentation
- added more functional tests
- added support for Windows
- added support for `relative` and `absolute` urls in Page Objects
- added `BaseDictionary` functionality
- added new step (support drag and drop) `I drag "elementName" element and drop over "dropOnElementName" element`
- fixed reports
- fixed step `I wait for the "elementName" element to disappear`
- updated libs (e.g. cucumber js)
- `isExternal` is no longer required in Page Objects (Angular)
- locators are no longer supported in Page Objects
- export `module.exports` has been changed in Page Objects
- removed `| element | value |` headers from first row in a steps
- `.gitkeep` is automatically created in reports catalog
- `RELOAD_FIXTURES_URL` has been moved to advanced configuration
- step `the "arrayElementName" element is visible` can be used now for an array element
##### v1.0.0
- updated documentation
- added license
- added example
##### v0.16.4
- updated documentation and readme
##### v0.16.3
- changed `There are "equal 4" following elements for element "rows":` error message to be more descriptive
- added express app to handle form submit tests
- added tests form html default field types and tabular content validation
##### v0.16.2
- added new step `I visit the "pageName" page with parameters:` which replaces wildcards with a values given in the table
- fixed step `I wait for "condition" of the "element" element`, currently timeout is set properly to `elementsVisibilityTimeout` key which is placed in kakunin.config.js
- improved step `I wait for "condition" of the "element" element`, currently singleElement and arrayElements can be checked
- change step implementation: `I click the "keyName" key` to `I press the "keyName" key`
##### v0.16.1
- added changelog
- added directory for mailing service adapters [`emails`] and connect it to modules loading system
- fixed a bug where exported mailing service and the one used internally where a different instances
================================================
FILE: CONTRIBUTING.MD
================================================
# Contributing to Kakunin
This section will guide you through the contribution process.
### Step 1: Fork
Fork the project [on GitHub](https://github.com/TheSoftwareHouse/Kakunin) and clone your fork
locally.
```bash
git clone git@github.com:TheSoftwareHouse/Kakunin.git
cd Kakunin
git remote add upstream https://github.com/TheSoftwareHouse/Kakunin.git
```
#### Which branch?
For developing new features and bug fixes, the `master` branch should be pulled
and built upon.
### Step 2: Branch
Create a branch and start hacking:
```bash
git checkout -b my-branch -t origin/master
```
### Step 3: Commit
Make sure git knows your name and email address:
```bash
git config --global user.name "Jan Kowalski"
git config --global user.email "jan@kowalski.com"
```
Add and commit:
```bash
$ git add my/changed/files
$ git commit
```
### Commit message guidelines
The commit message should describe what changed and why. We don't put any
constraints on message format although it should clearly describe the change.
4. If your patch fixes an open issue, you can add a reference to it at the end
of the log. Use the `Fixes:` prefix and the full issue URL. For other references
use `Refs:`.
Examples:
- `Fixes: https://github.com/TheSoftwareHouse/Kakunin/issues/1337`
- `Refs: http://eslint.org/docs/rules/space-in-parens.html`
- `Refs: https://github.com/TheSoftwareHouse/Kakunin/pull/1234`
### Step 4: Rebase
Use `git rebase` (not `git merge`) to synchronize your work with the main
repository.
```bash
$ git fetch upstream
$ git rebase upstream/master
```
### Step 5: Test
Bug fixes and features should come with tests. Looking at other tests to
see how they should be structured can help.
To run the tests just run the default command:
```bash
npm test
```
Make sure the linter does not report any issues and that all tests pass. Please
do not submit patches that fail either check.
### Step 6: Push
```bash
git push origin my-branch
```
Pull requests are usually reviewed within a few days.
### Step 7: Discuss and update
You will probably get feedback or requests for changes to your Pull Request.
This is a big part of the submission process so don't be discouraged!
To make changes to an existing Pull Request, make the changes to your branch.
When you push that branch to your fork, GitHub will automatically update the
Pull Request.
Feel free to post a comment in the Pull Request to ping reviewers if you are
awaiting an answer on something.
### Step 8: Landing
In order to land, a Pull Request needs to be reviewed and approved by
at least one Kakunin Collaborator and pass all test.
After that, as long as there are no objections from a Collaborator,
the Pull Request can be merged.
After you push new changes to your branch, you need to get
approval for these new changes again, even if GitHub shows "Approved"
because the reviewers have hit the buttons before.
## Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
* (a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
* (b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
* (c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
* (d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
================================================
FILE: Dockerfile
================================================
FROM node:8.11.4
WORKDIR /app/website
EXPOSE 3000 35729
COPY kakunin/docs /app/docs
COPY ./website /app/website
RUN yarn install
CMD ["yarn", "start"]
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017 The Software House
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: MIGRATION-2.0.0.MD
================================================
# Kakunin - automated testing framework
#### Migration to 2.0.0 version
What needs to be done:
1. Remove all locators from Page Objects
```javascript
this.exampleLocator = by.css('.unique-class');
```
needs to be changed to:
```javascript
this.exampleLocator = $('.unique-class');
```
2. Remove headers `| element | value |` from all steps
```gherkin
And there are "equal 1" following elements for element "rows":
| element | value |
| firstName | t:John |
| lastName | t:Doe |
```
needs to be changed to:
```gherkin
And there are "equal 1" following elements for element "rows":
| firstName | t:John |
| lastName | t:Doe |
```
3. Delete `isExternal` from all Page Objects, if you are using Angular application
```javascript
class ExamplePage extends BasePage {
constructor() {
super();
this.isExternal = true;
this.url = '/';
this.selector = $('.some-class');
}
```
needs to be changed to:
```javascript
class ExamplePage extends BasePage {
constructor() {
super();
this.url = '/';
this.selector = $('.some-class');
}
```
4. Change `exports` in Page Objects:
The last line in each of the page objects
```javascript
module.exports = new ExamplePage();
```
needs to be changed to:
```javascript
module.exports = ExamplePage;
```
5. Change `dictionaries`, example:
```javascript
const { dictionaries } = require('kakunin');
class TestDictionary {
constructor() {
this.values = {
'test-name': 'Janek',
'test-value': 'lux'
};
this.name = 'test-dictionary';
}
isSatisfiedBy(name) {
return this.name === name;
}
getMappedValue(key) {
return this.values[key];
}
}
dictionaries.addDictionary(new TestDictionary());
```
needs to be changed to:
```javascript
const { dictionaries } = require('kakunin');
const { BaseDictionary } = require('kakunin');
class TestDictionary extends BaseDictionary {
constructor() {
super('test-dictionary', {
'test-name': 'Janek',
'test-value': 'lux'
});
}
}
dictionaries.addDictionary(new TestDictionary());
```
================================================
FILE: MIGRATION-2.2.0.MD
================================================
# Kakunin - automated testing framework
#### Migration to 2.2.0 version
What needs to be done:
1. If you are using MailTrap client to tests emails.
Open `kakunin.conf.js` file and edit it:
```javascript
email: {
type: 'mailtrap',
config: {
apiKey: '',
inboxId: '',
url: 'https://mailtrap.io/api/v1',
},
},
```
needs to be changed to:
```javascript
email: {
type: 'mailtrap',
config: {
apiKey: '',
inboxId: '',
url: 'https://mailtrap.io',
},
},
```
This change is required due to API changes in MailTrap.
More details available under the link: https://mailtrap.io/blog/2018-06-06-mailtraps-upcoming-api-changes
Please note that, that the steps in scenarios do not require any changes!
You can still use `html_body` to check the email content.
This is a breaking change for users using MailTrap in tests but we did not want to change version to "3.0.0".
Just "minor" version has been increased by one.
2. Remove node_modules
3. Update protractor to newest version - `npm install protractor@latest --save`
4. If you have some custom matchers, make sure to make them return rejected promise on fail. This is done to improve error readability.
================================================
FILE: MIGRATION-3.0.0.MD
================================================
# Kakunin - automated testing framework
#### Migration to 3.0.0 version
What needs to be done:
1. Change Hooks
In the version before 3.0.0 we had a single file located in the `./hook` directory.
Example of `hook.js`:
```javascript
const { Before, After } = require('kakunin');
Before(() => {
console.log('If you can see this in console then hook is working properly.');
});
After(() => {
console.log('Console log after the scenario');
});
```
Currently, the interface to set priorities has been added. So we can control if the hook `FirstExample` will be executed before `SecondExample`.
Example of `first-example.hook.js`:
```javascript
const { hookHandlers, Before } = require('kakunin');
class FirstExampleHook {
initializeHook() {
Before(() => {
console.log('First example hook');
});
}
getPriority() {
return 990;
}
}
hookHandlers.addHook(new FirstExampleHook());
```
================================================
FILE: ROADMAP.MD
================================================
## Roadmap
### v2.1.0
* Allow to test REST endpoints against expected JSON Schema
### v2.0.0
* full support for Windows OS,
* we are going to drop the need for strict first row in gherkin tables. For example, instead of:
``` gherkin
Given there are "at least 5" following elements for element "items":
| element | value |
| viewButton | f:isVisible |
```
you will be able to write it like this:
``` gherkin
Given there are "at least 5" following elements for element "items":
| viewButton | f:isVisible |
```
this change will be introduced to all tables related steps.
* we are going to drop the need for defining `locators` instead of `selectors` for some table steps. For example:
``` gherkin
Given there are "at least 5" following elements for element "items":
| element | value |
| viewButton | f:isVisible |
```
Currently the `viewButton` must be defined as `this.viewButtonLocator = by.css('.viewButton')`. In case you wish to click on such locator, you have to create a
selector for it - `this.viewButton = element(this.viewButtonLocator)`.
We are aware of code duplication issue here and after investigation this will be fixed in upcoming major version.
From `v2.0.0` you'll be able to use `this.viewButton = element(by.css('.viewButton'))` (and even the `$()` shortcut) in every step.
================================================
FILE: build.sh
================================================
#!/bin/sh
git subtree push --prefix website/build/Kakunin origin gh-pages
================================================
FILE: docker-compose.yml
================================================
version: "3"
services:
docusaurus:
build: .
ports:
- 3000:3000
- 35729:35729
volumes:
- ./docs:/app/docs
- ./website/blog:/app/website/blog
- ./website/core:/app/website/core
- ./website/i18n:/app/website/i18n
- ./website/pages:/app/website/pages
- ./website/static:/app/website/static
- ./website/sidebars.json:/app/website/sidebars.json
- ./website/siteConfig.js:/app/website/siteConfig.js
working_dir: /app/website
================================================
FILE: docs/browserstack.md
================================================
---
id: browserstack
title: Browserstack integration
---
## Browserstack project configuration
1. Create a new account in Browserstack: https://www.browserstack.com/
2. Login and visit the website https://www.browserstack.com/accounts/settings
3. Scroll down to the `Automation` section and copy:
- `Username`
- `Access Key`
4. Add a new `browserstack` section to the `kakunin.conf.js` file in the repository.
This is an example of a configuration for IE8 on Windows 7.
```javascript
browserstack: {
seleniumAddress: 'http://hub-cloud.browserstack.com/wd/hub',
defaultPort: 45691,
capabilities: {
'browserstack.user': 'example-user',
'browserstack.key': 'example-key',
'browserstack.local': true,
nativeEvents: true,
'browserstack.ie.driver': '3.14.0',
'browserstack.selenium_version': '3.14.0',
browserName: 'IE',
browser_version: '8.0',
}
},
```
5. Set `'browserstack.user'` to the `Username` value that you copied from the `Automation` section
6. Set `'browserstack.key'` to the `Access Key` value that you copied from the `Automation` section
7. Visit the link https://www.browserstack.com/automate/capabilities if you want to find more capabilities for your project!.
## Run tests in Browserstack
Runs the application with the capabilities set in `kakunin.conf.js` file through the command line:
- `npm run kakunin -- --browserstack`
Keep in mind that all capabilities that you set via CLI will be ignored!
For example, `npm run kakunin -- --safari --browserstack` will ignore the `safari` part.
Only `--browserstack` matters in case of running tests in Browserstack.
## Example kakunin.conf.js configuration file
This is an example configuration for Internet Explorer 8 on Windows 7.
```javascript
module.exports = {
browserWidth: 1600,
browserHeight: 900,
timeout: 60,
elementsVisibilityTimeout: 5,
waitForPageTimeout: 5,
downloadTimeout: 30,
reports: '/reports',
downloads: '/downloads',
data: '/data',
features: ['/features'],
pages: ['/pages'],
matchers: ['/matchers'],
generators: ['/generators'],
form_handlers: ['/form_handlers'],
step_definitions: ['/step_definitions'],
comparators: ['/comparators'],
dictionaries: ['/dictionaries'],
transformers: ['/transformers'],
regexes: ['/regexes'],
hooks: ['/hooks'],
clearEmailInboxBeforeTests: false,
clearCookiesAfterScenario: true,
clearLocalStorageAfterScenario: true,
email: null,
headless: true,
noGpu: true,
type: 'otherWeb',
baseUrl: 'http://localhost:8080',
apiUrl: 'http://localhost:8080/',
browserstack: {
seleniumAddress: 'http://hub-cloud.browserstack.com/wd/hub',
defaultPort: 45691,
capabilities: {
'browserstack.user': 'example-user',
'browserstack.key': 'example-key',
'browserstack.local': true,
nativeEvents: true,
'browserstack.ie.driver': '3.14.0',
'browserstack.selenium_version': '3.14.0',
browserName: 'IE',
browser_version: '8.0',
}
},
};
```
================================================
FILE: docs/configuration.md
================================================
---
id: configuration
title: Configuration
---
## Kakunin config
```
module.exports = {
"browserWidth": 1600,
"browserHeight": 900,
"timeout": 60,
"maxEmailRepeats": 5,
"intervalEmail": 5,
"elementsVisibilityTimeout": 5,
"waitForPageTimeout": 5,
"downloadTimeout": 30,
"reports": "/reports",
"downloads": "/downloads",
"data": "/data",
"features": [
"/features"
],
"pages": [
"/pages"
],
"matchers": [
"/matchers"
],
"generators": [
"/generators"
],
"form_handlers": [
"/form_handlers"
],
"step_definitions": [
"/step_definitions"
],
"comparators": [
"/comparators"
],
"dictionaries": [
"/dictionaries"
],
"transformers": [
"/transformers"
],
"regexes": [
"/regexes"
],
"hooks": [
"/hooks"
],
"clearEmailInboxBeforeTests": false,
"clearCookiesAfterScenario": true,
"clearLocalStorageAfterScenario": true,
"email": null,
"headless": false,
"noGpu": false,
"type": "otherWeb",
"baseUrl": "http://localhost:8080",
"accounts": {
"someAccount": {
"accounts": [
{
"email": "",
"password": ""
}
]
}
}
}
```
## Configuration options
`browserWidth` - width of browser window `default: 1600`
`browserheight` - height of browser window `default: 900`
`timeout` - global timeout for a single step execution in seconds `default: 60`
`maxEmailRepeats` - maximum email repeats to catch email used in the email step
`intervalEmail` - interval for email checking step `default: 5` in seconds
`elementsVisibilityTimeout` - maximum wait timeout for element visibility `default: 5` seconds
`waitForPageTimeout` - maximum wait timeout for page visibility `default: 5` seconds
`downloadTimeout` - maximum wait timeout for file to be downloaded `default: 30` seconds
`emails` - array of paths to store emails related custom code
`reports` - path to store reports
`downloads` - path to store downloaded files
`data` - path to store test related files (for example files to be downloaded)
`feature` - array of paths to store features
`pages` - array of paths to store page objects
`matchers` - array of paths to store custom matchers
`generators` - array of paths to store custom generators
`form_handlers` - array of paths to store custom form handlers
`step_definitions` - array of paths to store custom steps
`comparators` - array of paths to store custom comparators
`dictionaries` - array of paths to store custom dictionaries
`transformers` - array of paths to store custom transformers
`regexes` - array of paths to store custom regexes
`hooks` - array of paths to store custom hooks
`clearEmailInboxBeforeTests` - flag to active clearing email inbox before tests are executed `default: false | true for apps with email checking functionality activated `
`clearCookiesAfterScenario` - flag to activate clearing cookies after every scenario `default: true`
`clearLocalStorageAfterScenario` - flag to activate clearing local storage after every scenario `default: true`
`email` - email configuration `default: null`
for mailtrap email checking system:
```javascript
"type": "mailtrap",
"config": {
"apiKey": "your-mailtrap-api-key",
"inboxId": "your-mailtrap-inbox",
"url": "https://mailtrap.io/api/v1"
}
```
for custom email checking system only type is required:
```
"type": "custom-type"
```
`headless` - flag to activate chrome headless browser `default: false`. Keep in mind that CLI command `-- --headless=false/true` has higher priority than the config file.
`noGpu` - flag to activate cpu only mode `default: false`
`type` - type of application either `ng1 | ng2 | otherWeb`
`baseUrl` - url of tested application
`accounts` - object to store accounts information. This is bound to `userProvider` and allows to use advanced email checking options like recipient checking.
```javascript
"someAccount": {
"accounts": [
{
"email": "",
"password": ""
}
]
}
```
## Environment variables
Kakunin uses a single `.env` file to load ENV variables. By default there is only one:
`FIXTURES_RELOAD_HOST` - allows you to specify host for fixtures reloading. This allows you to use `@reloadFixtures` tag on scenarios that should restore database to starting state, before the test is running
================================================
FILE: docs/cross-browser.md
================================================
---
id: cross-browser
title: Cross-browser testing
---
## To run tests with specified browser
There is a possibility to run Kakunin in various browsers:
- Google Chrome (by default) `npm run kakunin` or `npm run kakunin -- --chrome`
- Firefox `npm run kakunin -- --firefox`
- Safari `npm run kakunin -- --safari`
- Internet Explorer `npm run kakunin -- --ie` (supported versions IE8, IE9, IE10, IE11)
## To run tests in different browsers at once
There is a possibility to run more than one instance of WebDriver by giving an extra parameter to a command line:
- `npm run kakunin --chrome --safari`
Currently, there is a problem with running more than one instance of Firefox!
## Safari
### Run tests
1. Open Safari's preferences
2. Enable "Show Develop menu in menu bar"
3. Open "Develop" tab
4. Enable "Allow Remote Automation"
## Internet Explorer
### Configure the browser
1. Open Internet options and set:
- IE browser zoom level to 100 procent
- IE Security level: keep all of the tabs either checked / unchecked (Itnernet, Local internet, Trusted sites, Restricted sites)
### Troubleshooting
Safari version 12.0:
- drag & drop actions in Kakunin impossible (more details https://github.com/angular/protractor/issues/1526)
================================================
FILE: docs/docker.md
================================================
---
id: docker
title: Docker
---
# Docker for Kakunin tests
This section explains how to run kakunin tests inside docker, below examples of Dockerfile
and docker-compose.yml files let you build your first docker image and run tests.
## Dockerfile:
This file is responsible for building the whole environment for our e2e tests.
It will allow you to run tests on local and CI environments,
by configuring and copying the whole project inside the container.
Just simply place it inside your e2e project root.
Below an example of Dockerfile
### Example of Dockerfile:
```bash
# Downloading selenium image and setting privileges
FROM selenium/standalone-chrome:3.14.0
USER root
# Setting test directory
WORKDIR /app
# Install openjdk-8-jdk-headless
RUN apt-get update -qqy \
&& apt-get -qqy --no-install-recommends install \
xvfb \
openjdk-8-jdk-headless \
curl \
make \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/*
# Installing node 8 globally and setting paths
RUN set -x \
&& curl -sL https://deb.nodesource.com/setup_8.x | bash - \
&& apt-get install -y \
nodejs \
&& npm install -g npm@latest
RUN PATH=/usr/bin/node:$PATH
# Copy tests directory with ignored files from .dockerignore
COPY --chown=seluser:seluser . .
# Removing node_modules in case of existence or lack of .dockerignore and installing from package.json
RUN rm -rf ./node_modules \
&& npm install
# Setting Xvfb
RUN export DISPLAY=:99.0
USER seluser
```
##docker-compose.yml
Compose is a tool for defining and running multi-container Docker applications, which we use
for running our tests.
Running command: ``docker-compose up -d`` will start Dockerfile script, as a result, it builds the container.
Running command: ``docker-compose build `` or ``docker-compose up --build`` will
rebuild container, if there were made any changes.
Running command: ``docker-compose run --rm e2e`` will start running tests inside the container
Composition below allows you to run e2e tests inside the container and configure it locally or
in CI environments.
### Example of docker-compose.yml:
```bash
e2e:
build: .
working_dir: /app
command: sh -c "Xvfb -ac :99 -screen 0 1280x1024x16 & npm run kakunin"
```
### How to run step by step
1. Install docker (e.g Docker for Mac),
2. Create Dockerfile and docker-compose.yml in the root of your e2e project,
3. Run in command line `docker-compose up -d ` which will start docker and build image
if it's not build
4. Run in command line `docker-compose run --rm e2e` to run your tests
================================================
FILE: docs/extending.md
================================================
---
id: extending
title: 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.
```javascript
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.
```javascript
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:
```json
"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:
```javascript
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.
```javascript
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:
```javascript
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:
```javascript
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:
```javascript
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:
```javascript
const { comparators } = require('kakunin');
class MyComparator {
isSatisfiedBy(values) {
for(let i=0; iKeep in mind that CLI has greater prority than the cofig file (overides settings on runtime).
================================================
FILE: docs/hooks.md
================================================
---
id: hooks
title: Hooks
---
# Hooks for Kakunin tests
This section explains how to add priority hooks for kakunin tests based on the built-in adapter.
Hooks allow you to perform actions before and after scenario.
For example, it lets you clear all files from the downloads folder.
## How to add hook with priority:
##### initializeHook()
- this method is provided to execute hook logic.
##### getPriority()
- this method returns numeric value and then it's sorted in order.
```text
Remember that new Hook must contain these 2 methods to fulfill interface.
```
After your hook is ready to use method `hookHandlers.addHook(Hook object)`
### Example of example.hook.js:
```typescript
const { hookHandlers, Before } = require('kakunin');
class ExampleHook {
initializeHook() {
Before(() => {
console.log('This hook is going to be 5th in order');
});
}
getPriority() {
return 5;
}
}
hookHandlers.addHook(new ExampleHook());
```
### Build in hooks:
#### Clear download:
- Clears download folder before or/and after scenario. To use them add `@downloadClearBefore` or `@downloadClearAfter` tag.
Its priority is set to 1.
#### Reload fixtures:
- allows you to reload fixtures before the desired scenario, via URL provided in `.env` file.
Its priority is set to 2.
#### Take a screenshot and clear variables:
- These hooks are used by kakunin mechanism to clear variable store and take screenshots after scenarios.
Their priority is set to 1.
================================================
FILE: docs/how-it-works.md
================================================
---
id: how-it-works
title: How it works
---
Kakunin is built with `no-js` experience in mind. Because of that you're able to test even complicated apps just
by knowing Kakunin (Gherkin) steps and a few good practices.
## Concepts
Kakunin uses `cucumber-js` internally, because of that all tests (or rather scenarios) are using `Gherkin` as a "programming"
language.
A simple scenario could look like this:
```gherkin
Feature:
Scenario: Display user profile for logged user
Given I am logged in as a "user"
When the "dashboard" page is displayed
And I click the "profileButton" element
Then the "myProfile" page is displayed
And the "myName" element is visible
```
This is how most of Kakunin test scenarios look like.
There are a few concepts to be explained.
## Page objects
Page object is a code representation of a page displayed in browser. Kakunin has built-in `BasePage` page object, that you should extend.
Page object contains information about page url, its elements, locators, but can also have some custom methods if necessary.
A very simple example of Kakunin's Page Object could look like the following:
```javascript
const { BasePage } = require('kakunin');
class DashboardPage extends BasePage {
constructor() {
super();
this.url = '/dashboard';
}
}
module.exports = DashboardPage;
```
As you can see a basic Page Object must extend one of the Kakunin's Objects and needs to have url field defined (`this.url`).
This code should be saved inside `pages` directory in a file with `js` extension.
Note that a file name is very important, because we're going to use it as parameter for steps. For example, the following step:
```gherkin
When the "dashboard" page is displayed
```
expects that there is a file named `dashboard.js` inside the `pages` directory.
Every step that we are using is somehow connected to an object called `currentPage`. This object value is set to a
page object that we expect to be on.
This is done by two kinds of steps:
* `Then the "dashboard" page is displayed` - this one checks if current url in browser is the same as the one inside Page Object and changes a value of the `currentPage` field
to this page object
* `When I visit the "dashboard" page` - this one goes to the url specified in Page Object and attaches the Page Object to the `currentPage` field as above
This concept is a very simple and allows you to easily debug the framework. You can be sure that each subsequent step that declared below the ones above will be executed in context of a page object specified in those methods.
For example, having the following code:
```gherkin
Feature:
Scenario: Display user profile for logged user
Given I am logged in as a "user"
When the "dashboard" page is displayed
And I click the "profileButton" element
Then the "myProfile" page is displayed
And the "myName" element is visible
```
The step named `And I click the "profileButton" element` is executed in context of `dashboard` Page Object, thus we can assume that `profileButton` should be defined inside the
`pages/dashboard.js` file.
At the same time the step `And the "myName" element is visible` is executed in context of `myProfile`, so `myName` should be defined in `pages/myProfile.js` file.
## Elements and locators
The second concept that you have to understand are elements and locators.
Every element that you see on website can be represented as a element inside the page object. This allows us to use it as a parameter for a step, as we did in:
`And the "myName" element is visible`.
Defining elements is very simple. Let's say we have such page object:
```
const { BasePage } = require('kakunin');
class DashboardPage extends BasePage {
constructor() {
super();
this.url = '/dashboard';
}
}
module.exports = DashboardPage;
```
Elements should be defined inside `constructor` method. Let's add element for `myName`:
```
const { BasePage } = require('kakunin');
class DashboardPage extends BasePage {
constructor() {
super();
this.url = '/dashboard';
this.myName = element(by.css('.myName'));
}
}
module.exports = DashboardPage;
```
As you see we added a single line `this.myName = element(by.css('.myName'));`.
`by.css('.myName')` - is a locator, this is a standard protractor syntax, you can read more on protractors documentation
By joining `element` method with a locator, we created element to be used by our steps.
## Compare URLs examples:
| Page Object URL | Current Browser URL | Base URL - config file | Results |
| ----------------------------------------------------------- | ------------------------------------------------- | ------------------------- | --------- |
| http://localhost:8080/incorrect-data | http://localhost:8080/tabular-data | https://example-url.com | FALSE |
| http://localhost:8080/incorrect-data/ | http://localhost:8080/tabular-data | https://example-url.com | FALSE |
| http://google/incorrect-data | http://localhost:8080/tabular-data | https://example-url.com | FALSE |
| http://google/tabular-data | http://localhost:8080/tabular-data | https://example-url.com | FALSE |
| http://google/incorrect-data/ | http://localhost:8080/tabular-data | https://example-url.com | FALSE |
| /incorrect-data | http://website.com/tabular-data | https://example-url.com | FALSE |
| /incorrect-data/ | http://website.com/tabular-data | http://incorrect.com | FALSE |
| http://localhost:8080/tabular-data | http://localhost:8080/tabular-data | https://example-url.com | TRUE |
| http://localhost:8080/tabular-data/ | http://localhost:8080/tabular-data | http://localhost:8080 | TRUE |
| /tabular-data | http://localhost:8080/tabular-data | http://localhost:8080 | TRUE |
| /tabular-data/ | http://localhost:8080/tabular-data | http://localhost:8080 | TRUE |
| /tabular-data | http://localhost:8080/tabular-data | https://google.pl | FALSE |
| /tabular-data/ | http://localhost:8080/tabular-data | https://google.pl | FALSE |
| / | https://google.pl/new | https://google.pl | FALSE |
| | https://google.pl/new | https://google.pl | FALSE |
| | http://localhost:8080 | http://localhost:8080 | TRUE |
| / | https://google.pl | https://google.com | FALSE |
| https://google.com/:example/:name | https://google.com/example/janek | https://example-url.com | TRUE |
| https://google.com/:name | https://google.com/janek | https://example-url.com | TRUE |
| https://google.com/account/:username/settings/display | https://google.com/account/janek/settings/display | https://example-url.com | TRUE |
| /account/settings/:userType | https://incorrect-host/account/settings/admin | https://google.com | FALSE |
| /account/settings/:userType/something | https://incorrect-host/account/settings/admin | https://example-url.com | FALSE |
| https://incorrect-host/account/settings/:userType/something | https://incorrect-host/account/settings/admin | https://example-url.com | FALSE |
| /account/settings/:userType | https://google.com/account/settings/user | https://google.com | TRUE |
================================================
FILE: docs/index.md
================================================
---
id: index
title: Getting started
---
## About Kakunin
Kakunin is a Protractor extension created by The Software House sp. z o.o. and Takamol Holding. It allows you
to write e2e test scenarios with a help of Gherkin language and JavaScript for all kind of applications - Angular, React and others.
## Installation
In order to install Kakunin you have to make sure that you have installed:
```text
node.js - v7.8.0 min
JDK
Chrome
```
Create directory for your project
```bash
mkdir my_project
```
Go to project directory
```bash
cd my_project
```
Initialize JavaScript project
```bash
npm init
```
Install dependencies
```bash
npm install cross-env kakunin --save
```
Inside `package.json` file; add new script in `scripts` section:
```json
"kakunin": "cross-env NODE_ENV=prod kakunin"
```
## Configuration
* Create kakunin project
```bash
npm run kakunin init
```
The above command will run Kakunin's init script.
* Answer what kind of app you're going to test (`default: AngularJS`)
* Enter URL where your tested app will be running (`default: http://localhost:3000`)
* Choose if you plan to use some emails checking service (`default: none`)
Also, there is a possibility to answer these question by a command line.
```text
npm run kakunin init -- --baseUrl https://google.com --type otherWeb --emailType none
```
Available parameters: `baseUrl`, `type`, `emailType`, `emailApiKey`, `emailInboxId`.
You will not be asked about question that you already answered by a command.
After the init process, a project files should be automatically created in your directory.
This is an example of a console output after the init process is completed:
```text
Created file at path /Users/example-user/projects/test/kakunin.conf.js
Created directory at path /Users//TSHProjects/test/reports
Created directory at path /Users//TSHProjects/test/reports/report
Created directory at path /Users//TSHProjects/test/reports/report/features
Created directory at path /Users//TSHProjects/test/reports/performance
Created directory at path /Users//TSHProjects/test/downloads
Created directory at path /Users/example-user/projects/test/data
Created directory at path /Users/example-user/projects/test/features
Created directory at path /Users/example-user/projects/test/pages
Created directory at path /Users/example-user/projects/test/matchers
Created directory at path /Users/example-user/projects/test/generators
Created directory at path /Users/example-user/projects/test/form_handlers
Created directory at path /Users/example-user/projects/test/step_definitions
Created directory at path /Users/example-user/projects/test/comparators
Created directory at path /Users/example-user/projects/test/dictionaries
Created directory at path /Users/example-user/projects/test/regexes
Created directory at path /Users/example-user/projects/test/hooks
Created directory at path /Users/example-user/projects/test/transformers
Created directory at path /Users/example-user/projects/test/emails
Created file at path /Users/example-user/projects/test/downloads/.gitkeep
Created file at path /Users/example-user/projects/test/reports/report/.gitkeep
Created file at path /Users/example-user/projects/test/reports/report/features/.gitkeep
Created file at path /Users/example-user/projects/test/reports/performance/.gitkeep
Created file at path /Users/example-user/projects/test/features/example.feature
Created file at path /Users/example-user/projects/test/pages/page.js
Created file at path /Users/example-user/projects/test/matchers/matcher.js
Created file at path /Users/example-user/projects/test/generators/generator.js
Created file at path /Users/example-user/projects/test/step_definitions/steps.js
Created file at path /Users/example-user/projects/test/regexes/regex.js
Created file at path /Users/example-user/projects/test/hooks/hook.js
```
And you're set! Now you can run the tests using Kakunin:
```bash
npm run kakunin
```
## Commands
* Create a new project by answering few simple questions (you can pass additional parameter to enter advanced mode where you can configure all Kakunin options by yourself)
```bash
npm run kakunin init [-- --advanced]
```
* Run test scenarios
```bash
npm run kakunin
```
* Run only scenarios tagged by `@someTag`
```bash
npm run kakunin -- --tags @someTag
```
* Run only scenarios tagged by `@someTag` and `@otherTag` at the same time
```bash
npm run kakunin -- --tags "@someTag and @otherTag"
```
* Run only scenarios tagged by `@someTag` or `@otherTag`
```bash
npm run kakunin -- --tags "@someTag or @otherTag"
```
* Run only scenarios not tagged by `@someTag`
```bash
npm run kakunin -- --tags "not @someTag"
```
## Troubleshooting & Tips
In order to make cucumber steps autosuggestion work properly in JetBrains tools, make sure your project is `ECMAScript 6` compatible and you have `cucumberjs` plugin installed.
Due to non-resolved issue in Jetbrains editors ([see here](https://youtrack.jetbrains.com/issue/WEB-11505)) we'll have to do one more step:
Go to `step_definitions` directory
```bash
cd step_definitions
```
Paste this code into terminal and restart your IDE:
For Linux/MacOs:
```bash
ln -s ../node_modules/kakunin/src/step_definitions/elements.ts kakunin-elements.ts
ln -s ../node_modules/kakunin/src/step_definitions/debug.ts kakunin-debug.ts
ln -s ../node_modules/kakunin/src/step_definitions/file.ts kakunin-file.ts
ln -s ../node_modules/kakunin/src/step_definitions/form.ts kakunin-form.ts
ln -s ../node_modules/kakunin/src/step_definitions/email.ts kakunin-email.ts
ln -s ../node_modules/kakunin/src/step_definitions/generators.ts kakunin-generators.ts
ln -s ../node_modules/kakunin/src/step_definitions/navigation.ts kakunin-navigation.ts
ln -s ../node_modules/kakunin/src/step_definitions/performance.ts kakunin-performance.ts
```
For Windows 8+: (you have to do this as administrator)
```bash
mklink kakunin-elements.ts ../node_modules/kakunin/src/step_definitions/elements.ts
mklink kakunin-debug.ts ../node_modules/kakunin/src/step_definitions/debug.ts
mklink kakunin-file.ts ../node_modules/kakunin/src/step_definitions/file.ts
mklink kakunin-form.ts ../node_modules/kakunin/src/step_definitions/form.ts
mklink kakunin-email.ts ../node_modules/kakunin/src/step_definitions/email.ts
mklink kakunin-generators.ts ../node_modules/kakunin/src/step_definitions/generators.ts
mklink kakunin-navigation.ts ../node_modules/kakunin/src/step_definitions/navigation.ts
mklink kakunin-performance.ts ../node_modules/kakunin/src/step_definitions/performance.ts
```
Keep in mind that `mklink` is not available in older Windows distributions.
This will create symlinks inside `step_definitions` directory and make `cucumberjs` plugin recognize kakunin built-in steps.
================================================
FILE: docs/matchers.md
================================================
---
id: matchers
title: Matchers
---
Matchers allows you to check if a element content matches your expectation.
For example you can check if a value has a specified pattern or if a button is clickable.
Using matcher is very straightforward, for example: `f:isClickable`.
Matchers can be used in most of the steps related to checking content (with exception of checking form values).
Kakunin comes with a set of built in matchers:
## Visibility matcher
`f:isVisible` - checks if element is visible (must be in viewport and cannot be hidden behind any other element)
## Invisibility matcher
`f:isNotVisible` - checks if element is not visible
## Present matcher
`f:isPresent` - checks if element is in html code (does not have to be visible)
## Clickable matcher
`f:isClickable` - checks if element is clickable
## Not clickable matcher
`f:isNotClickable` - checks if element is not clickable
## Attribute matcher
`attribute:attributeName:regexName` - allows to check if element has attribute with a name specified by `attributeName` and it has to
have a format passing `regexName`
For example, if there is an element:
`
some value
`
you can check if attribute is an number by running: `attribute:custom-attribute:number`
## Regex matcher
`r:regexName` - allows you to run a `regexName` against a text value of element
Regexes have to be specified inside `regex` directory or be a kakunin built ones:
`notEmpty` - there must be a value
`number` - must be a number
You can add your own matchers. In order to do so please read `Extending Kakunin` section.
## Text matcher
`t:text you are looking for` - allows you to check if an element contains a expected text
## Current date matcher
`f:currentDate:{format}` - allows you to generate current date, `{format}` is optional, by default `DD-MM-YYYY`
================================================
FILE: docs/parallel-testing.md
================================================
---
id: parallel-testing
title: Parallel testing
---
There is a possibility to run tests in parallel.
## How to execute
Use a command `npm run kakunin -- --parallel ` where `number of instances` is a number.
Example:
- `npm run kakunin -- --chrome --parallel 2`
Keep in mind that the merged report is available in the `reports/report/index.html` file. text
## Specify pattern per each instance
- `npm run kakunin -- --parallel --pattern --pattern `
Keep in mind that:
- the number given in `parallel` must be equal to passed `patterns`
- `` is a number of instances of the specified browser
- `` is a pattern that is used to specify the list of specs that will be executed in each of the instances
-----------------------------------------------------------------------------------
## Troubleshooting
1. Running more than one instance in `Firefox` is not possible now (fix in-progress).
================================================
FILE: docs/performance-testing.md
================================================
---
id: performance-testing
title: Performance testing
---
Performance testing is possible thanks to `browsermob-proxy`.
It saves all data from network tab (Google Chrome console) which is generated during the test.
There is a possibility to compare `TTFB` value with a maximum given one.
`TTFB` (Time to first byte) measures the duration from the client making an HTTP request to the first byte of a response being received by the client's browser.
More details can be found in documentation - `Built-in steps` section.
# What needs to be done?
## Get started
1. Download `browsermob-proxy` from `https://github.com/lightbody/browsermob-proxy`
2. Navigate in terminal to the catalog
3. Use following command to start the REST API
```
./browsermob-proxy -port 8887
```
## Configuration
1. Add `browsermob-proxy` configuration to `kakunin.conf.js`
You can use one of the following methods to configure browsermob-proxy:
- `npm run kakunin init -- --advanced` and go through the process
- or add it manually to the config file:
```javascript
"browserMob": {
"serverPort": 8887,
"port": 8888,
"host": "localhost"
}
```
## Run tests
1. `performance steps` must be used in the scenario where you are testing performance
2. Scenario must have a tag `@performance`
3. Run tests with special parameter:
```
npm run kakunin -- --performance
```
## Results
1. `.har` files are saved in catalog `reports/performance/*.har`
================================================
FILE: docs/quickstart.md
================================================
---
id: quickstart
title: Quick start
---
As a quick demonstration of the framework let's test the
[React variant of TodoMVC](http://todomvc.com/examples/react/#/) project.
Of course other testing other frameworks is possible, you can try it
by yourself!
## Install packages
In order to install Kakunin you have to make sure that you have installed:
```text
node.js - v7.8.0 min
JDK
Chrome
```
Create directory for your project and enter it
```bash
$mkdir my_project
cd my_project
```
Initialize JavaScript project
```bash
npm init
```
Install dependencies
```bash
npm install cross-env kakunin --save
```
Inside `package.json` file add new script in `scripts` section:
```js
...
"scripts": {
"kakunin": "cross-env NODE_ENV=prod kakunin"
},
...
```
## Configure Kakunin
Run initialization command
```bash
npm run kakunin init
```
Answer literally few questions:
```text
What kind of application would you like to test? : otherWeb
What is base url? [http://localhost:3000]: http://todomvc.com
What kind of email service would you like to use?: none
```
And you're set! Now let's write some test!
## Test the app
Create a page object that will contain instructions on how to locate elements in the projects.
Create a file `pages/main.js`:
```javascript
const { BasePage } = require('kakunin');
class MainPage extends BasePage {
constructor() {
super();
// define the main url for the page
this.url = '/examples/react/#/';
// whole form tag
this.addTodoForm = $('.todoapp');
// input field
this.todoInput = $('input.new-todo');
// list of currently added todos
this.todos = $$('.todo-list .view');
this.todoLabel = by.css('label');
// first todo item in a list
this.firstTodoItem = this.todos.get(0);
}
}
module.exports = MainPage;
```
Now that we have prepared the locators, we can start writing our test. Let's test adding new todo item.
Create a file named: `features/adding_todo.feature` with the following contents:
```gherkin
Feature:
Scenario: Adding todo
Given I visit the "main" page
And I wait for "visibilityOf" of the "addTodoForm" element
And the "addTodoForm" element is visible
When I fill the "addTodoForm" form with:
| todoInput | My new todo |
And I press the "enter" key
Then there are "equal 1" "todos" elements
```
And that's it! All you have to do now is to run the test and watch the magic happens ;)
```bash
npm run kakunin
```
The tests may run quite fast so you might not been able to see that it
really works as expected. To check if the todo items has been really
added to the list, let's use a simple hack - let's pause the running
test right after the todo has been added.
To do that, let's upgrade our Scenario. Update the file:
```gherkin
Feature:
Scenario: Adding todo
Given I visit the "main" page
And I wait for "visibilityOf" of the "addTodoForm" element
And the "addTodoForm" element is visible
When I fill the "addTodoForm" form with:
| todoInput | My new todo |
And I wait for "1" seconds
And I press the "enter" key
When I fill the "addTodoForm" form with:
| todoInput | Another todo item! |
And I wait for "1" seconds
And I press the "enter" key
Then there are "equal 2" "todos" elements
Then I wait for "5" seconds
```
As you can see, we've added 1 new step that waits for a second before
"pressing" the `enter` key. We've also added a second todo item with
a short pause at the end of the test so you can see the changes.
If you want to see what can we do more with the TodoMVC project, take a look
at the `example` dir, where you'll find a complete set of test for the project.
================================================
FILE: docs/steps-debug.md
================================================
---
id: steps-debug
title: Debug
---
# Steps for debugging application:
## `I pause`
Pauses tests execution and allows to continue manually by pressing combination of `ctrl+c` inside terminal.
---
## `I wait for ":seconds" seconds`
Waits with execution of next step for an amount provided by parameter `:seconds`.
---
## `I start performance monitor mode`
It starts performance monitor mode.
Keep in mind that REST API must be started on the port which must configured in `kakunin.conf.js` - `serverPort: 8887`.
More details can be found in documentation file `performance-testing.md`.
---
## `I save performance report file as "fileName"`
It saves `.har` file with a name `fileName` in `reports/performance` catalog.
For example: `exampleReport-1511470954552.har`
Data is generated during the test - network tab in Chrome Chrome console.
Keep in mind:
* `I start performance monitor mode` must be used before this step
* `browserMob.port` must be configured in `kakunin.conf.js`
* `browserMob.host` must be configured in `kakunin.conf.js`
More details can be found in documentation file `performance-testing.md`.
---
## `the requests should take a maximum of "maxTiming" milliseconds`
It compares every `TTFB` timing value from previously saved `.har` report with a `maxTiming` value.
Slow requests are listed in your terminal in red colour.
Keep in mind that `I start performance monitor mode` and `I save performance report file as "fileName"` steps must be executed before this one!
---
================================================
FILE: docs/steps-elements.md
================================================
---
id: steps-elements
title: Elements
---
# Steps used to interact with elements:
## `I infinitely scroll to the ":elementName" element`
Allows to scroll through infinite scroll mechanism.
The `:elementName` is a name of a selector for loading trigger.
---
## `I wait for ":expectedConditionName" of the ":elementName" element`
Waits till element `:elementName` from `this.currentPage` meets criteria specified by `:expectedConditionName`.
You can use any of the Protractor's expected condition:
* `visibilityOf`
* `invisibilityOf`
etc.
Read more in Protractor's API documentation.
---
## `I wait for the ":elementName" element to disappear`
Waits till element `:elementName` disappears.
---
## `I scroll to the ":elementName" element`
Scrolls to element `:elementName` of `this.currentPage`. The element will be on bottom of the page.
---
## `I infinitely scroll to the ":elementName" element`
Allows to scroll till `:elementName` is visible. Useful for infinite scrolling functionality.
---
## `I press the ":keyName" key`
Performs a key press operation on `:keyName` key.
---
## `I click the ":elementName" element`
Performs a click action on element `:elementName` from `this.currentPage'
The child element must be specified by `:elementName` and must be available in `this.currentPage`.
---
## `I store the ":elementName" element text as ":variableName" variable`
Stores the text from element `:elementName` of `this.currentPage` under the `:variableName` so you can use it later.
---
## `I update the ":elementName" element text as ":variableName" variable`
Updates the variable `:variableName` value by value from element `:elementName` of `this.currentPage`.
---
## `I store the ":elementName" element text matched by ":matchingRegex" as ":variableName" variable`
Stores the part of the element `:elementName` text, that matches the `:matchingRegex` under the `:variableName` for later use.
---
## `the ":elementName"" element is visible`
Checks if element `:elementName` is visible and clickable
---
## `the ":elementName"" element is not visible`
Checks if element `:elementName` is available in HTML DOM but is not visible and clickable
---
## `the ":elementName" element is disabled`
Checks if element is disabled
---
## `I store table ":tableRow" rows as ":variableName" with columns:`
Allows to store a row specified columns from a table `:tableRow` and save it under `:variableName` as an array of objects.
This step requires a table of columns elements, for example:
```gherkin
I store table "someRow" rows as "someVariable" with columns:
| firstName |
| lastName |
| id |
```
In order to make it work there must be not only array element `this.someRow = $$('.rows')` in `this.currentPage`, but also
element `this.firstName = $('.firstName');` and so on.
The result of this step is an array of:
```javascript
[
[
'firsRowFirstNameValue',
'firsRowLastNameValue'
'firsRowIdValue'
]
...
]
```
---
## `there are following elements in table ":elementName":`
Allows to check if a child elements of `:elementName` have a specified content.
This steps allows you to specify an array of child elements that will be checked against expected values.
For example:
```gherkin
there are following elements in table "myTable":
| id | firstName | lastName |
| t:1 | t:Adam | t:Doe |
| t:2 | t:John | t:Doe |
```
First row must specify columns elements. Starting from second row we must provide a matchers for each row that must be displayed.
This step checks exact match, so if the table has 5 rows, there must be a 5 rows in this table.
We can specify only a set of columns (for example if a table has 5 columns, we can specify only 1).
---
## `there are "numberExpression" following elements for element ":elementName":`
Allows to check if a child elements of `:elementName` have a specified content. Element should be an array, for example:
```html
1
2
```
for this case the `:elementName` should be specified as `$$('table tr')`.
Allows to check if a number of elements is the one that we expect.
`numberExpression` is a supported expression from `chai.js` library:
* `equal N` where N is a number
* `at least N` where N is a number
* `above N` where N is a number
* `below N` where N is a number
* `within N M` where N and M are a numbers
and so on. You can check expressions on `chai.js` API dock for BDD.
This step requires an array of elements to be checked. For example:
```gherkin
there are "equal 5" following elements for element "myList":
| viewButton | f:isClickable |
| id | r:idRegex |
```
The child elements must be an elements, for example `this.viewButton = $('button.viewButton');`.
You can use all kind of matchers here.
---
## `there are ":elementName" dropdown list elements with following options:`
Allows to check if there is exact match to options provided in table for option selector.
```html
```
For example:
```gherkin
there are "personOption" dropdown list elements with following options:
| Person 1 |
| Person 2 |
| Person 3 |
| Person 4 |
```
The element must be for example:
`this.personOption = this.personForm.$$('option');`.
---
## `there is element ":elementName" with value ":matcher"`
Allows to check if `:elementName` has a value that matches the `:matcher`.
---
## `there is element ":elementName" containing ":matcher" text`
Allows to check if `:elementName` contains a text that matches the `:matcher`.
---
## `there is element ":elementName" matching ":matcher" matcher`
Allows to check if `:elementName` matches the given type of `:matcher`. For example:
```gherkin
there is element "button" matching "isClickable" matcher
```
---
## `there is element ":elementName" with regex ":matcher"`
Allows to check if `:elementName` matches given type of regex. For example:
```gherkin
there is element "input" with regex "notEmpty"
```
---
## `there is no element ":elementName" with value ":matcherName"`
Allows to check if there is no `:elementName` that matches the `:matcher`.
---
## `there is no element ":elementName" containing ":matcher" text`
Allows to check if `:elementName` doesn't contain a text that matches the `:matcher`.
---
## `there is no element ":elementName" matching ":matcher" matcher`
Allows to check if `:elementName` is not matching the given type of `:matcher`.
---
## `there is no element ":elementName" with regex ":matcher"`
Allows to check if `:elementName` is not matching given type of regex.
---
## `there are "numberExpression" ":elementName" elements`
Allows to check if a number of `:elementName` elements is the same as we expect.
`numberExpression` is a supported expression from `chai.js` library:
* `equal N` where N is a number
* `at least N` where N is a number
* `above N` where N is a number
* `below N` where N is a number
* `within N M` where N and M are a numbers
and so on. You can check expressions on `chai.js` API dock for BDD.
`:elementName` should be specified as an array, for example:
```html
1
2
```
for this case the `:elementName` should be specified as `$$('table tr')`.
---
## `every ":elementName" element should have the same value for element ":columnElementName"`
Allows to check if every row defined by `:elementName` has the same value for a column `:columnElementName`.
`:elementName` must be an array of elements
`:columnElementName` must be an element, for example:
```html
1
1
```
for this case the `:elementName` should be specified as `$$('table tr')` and we can specify column element
`this.myColumn = $('td');`. This allows us to write:
`every "myElement" element should have the same value for element "myColumn"`
---
## `the element ":elementName" should have an item with values:`
Allows to check if any of the child elements of `:elementName` have a specified content (one matching element is enough). Element should be an array, for example:
```html
1
2
```
for this case the `:elementName` should be specified as `$$('table tr')`.
This step requires an array of elements to be checked. For example:
```gherkin
the element "myList" should have an item with values:
| id | t:1 |
```
The child elements must be an elements, for example `this.id = $('td');`.
You can use all kind of matchers here.
---
## `the element ":elementName" should not have an item with values:`
Allows to check if the child elements of `:elementName` have a different content than that given in the table. Element should be an array, for example:
```html
1
2
```
for this case the `:elementName` should be specified as `$$('table tr')`.
This step requires an array of elements to be checked. For example:
```gherkin
the element "myList" should have an item with values:
| id | t:does-not-exist |
```
The child elements must be an elements, for example `this.id = $('td');`.
You can use all kind of matchers here.
---
## `I drag ":elementDrag" element and drop over ":elementDrop" element`
Clicks on `:elementDrag` and moves it onto `:elementDrop` while left mouse button is pressed, and then release it.
Note: This step is not working on HTML5!
---
================================================
FILE: docs/steps-files.md
================================================
---
id: steps-files
title: Files
---
# Steps used to interact with files:
## `the file ":fileName" should be downloaded`
Checks if a file with name `:fileName` was downloaded.
This step does not support matchers or regular expressions, so the name must be exact match. However you can use
variable store here.
Let's assume there is a variable `myFile` with a value `super-file` in variable store.
You can write `the file "v:myFile.zip" should be downloaded` to check if a file `super-file.zip` was downloaded.
---
## `the file ":fileName" contains table data stored under ":variableName" variable`
This step allows you to compare an xls/xlsx file `:fileName` with an existing data stored under `:variableName` variable.
The data under `:variableName` must be an array of objects representing each row of file.
---
================================================
FILE: docs/steps-forms.md
================================================
---
id: steps-forms
title: Forms
---
# Steps used to fill forms:
## `I fill the ":formName" form with:`
Allows to fill the form with the name `:formName` and values provided as an array of inputs and values. The element with name `:formName` must be defined inside the
`currentPage` page object.
Input and values should be provided as an array for example:
```gherkin
I fill the "myForm" form with:
| inputElement | value to be typed into field |
| textareaElement | value to be typed into textarea |
| radioElement | radio value to be selected |
| checkboxElement | checkbox label value to be selected |
```
By default we support all basic HTML field types (text inputs, checkboxes, radios, selects, files and textareas)
In order to use the default handlers the elements you use as input must follow pattern:
For inputs:
`this.element = $('input')` - element should point at input you want to fill
For textareas:
`this.element = $('textarea')` - element should point at textarea you want to fill
For file input:
`this.element = $('input')` - element should point at input you want to fill and value should a filename of file from `data` directory
For selects:
`this.element = $('select')` - element should point at select and value should be an value of expected option
For radios:
`this.element = $$('radio[name="name-of-radio"]')` - element should be an array of all radio input of given name and value should be an value of radio you wish to select
For checkboxes:
Checkbox should have a html like:
```html
```
`this.element = $$('checkbox[name="name-of-radio"]')` - element should be an array of all checkboxes of given name and value should be a text from label of checkbox you want to fill
You can use all kind of transformers to as a values for fields.
---
## `the ":formName" form is filled with:`
The same as `I fill the ":formName" form with:` but allows to check if a form is filled with a given set of values.
You can use all kind of transformers to as a expected values for fields.
The only difference is for file fields. You cannot check uploaded files just like that, however we prepared a special type of handler
that allow to check for some information related to a specific file.
Let's assume that after upload we display an information with a file name of a uploaded file.
You can use a special handler that requires to set a element with a postfix `Uploaded`. This will check if a value of that element is the same as you expected.
For example you can write a step like this:
```gherkin
the "myform" form is filled with:
| myFileUploaded | file.txt |
```
Keep in mind that the element name must end with `Uploaded` for example:
`this.myFileUploaded = $('p.some-file')`
---
## `the error messages should be displayed:`
Allows you to specify the error messages that should be displayed for a specific elements.
This step requires an array of format:
```gherkin
the error messages should be displayed:
| myElement | my error message |
```
You can use dictionaries in this step as follows:
```gherkin
the error messages should be displayed:
| myElement | d:dictionaryName:dictionaryKey |
```
---
================================================
FILE: docs/steps-generators.md
================================================
---
id: steps-generators
title: Generators
---
# Steps used to generate values:
## `I generate random ":generator:param:param" as ":variableName"`
Allows to generate a random value using the generator specified by `:generator:param:param`.
The generator must be defined inside the any of the `generators` directories specified in `kakunin.conf.js` file `default: generators`.
If the generator exists, then the value will be saved under the `:variableName` and can be accessed by:
* steps using variable store
* by calling `variableStore.getVariableValue(:variableName)`
* by using variable store transformer on supported steps `v:variableName`
---
================================================
FILE: docs/steps-navigation.md
================================================
---
id: steps-navigation
title: Navigation
---
# Steps used for navigation on page:
## `I visit the ":pageFileName" page`
Visits the url of the page object with `:pageFileName` name.
In order to make it work we create a page object file with a name of `:pageFileName`.
For example in case of: `I visit the "myPage" page` there should be a file `myPage.js` inside the `pages` directory.
If we have a page object with a name `somePageObject.js` defined inside `pages` directory then:
`Given I visit the "somePageObject" page`
will set `this.currentPage` variable to `somePageObject` page and we should end up on `somePageObject` url.
---
## `I visit the ":pageFileName" page with parameters:`
The same as `I visit the ":pageFileName" page` except allows to pass url parameters.
If url of `myPage` is defined as `this.url = /orders/:orderId/products/:productId` then we can use this step to visit this page by:
```gherkin
I visit the "myPage" page with parameters:
| orderId | 1 |
| productId | 2 |
```
this will result in visiting the `/orders/1/product/2` page.
---
## `the ":pageFileName" page is displayed`
Checks if current browser url matches url of `pageFileName` page object.
If the url matches expected pattern then
`this.currentPage` variable is set to `pageFileName` page object.
---
================================================
FILE: docs/steps-rest.md
================================================
---
id: steps-rest
title: Rest api
---
# Steps used for testing REST api:
In order to configure url for api, please change `apiUrl` field in `functional-tests/kakunin.conf.js`. This will set url of application api.
## `I send ":methodName" request on ":endpoint" endpoint`
Sends to the given request method to given website endpoint.
For example, in case of GET request for /posts endpoint it should look like:
```gherkin
I send "GET" request on "posts" endpoint
```
## `^I send ":methodName" request on ":endpoint" endpoint with JSON body:`
Sends request method to website endpoint requiring JSON body.
```gherkin
I send "POST" request on "posts" endpoint with JSON body:
"""
{
"title": "user",
"body": "test"
}
"""
```
## `^I send "methodName" request on ":endpoint" endpoint using form data:`
Sends request method to website endpoint using form data.
```gherkin
I send "POST" request on "posts" endpoint using form data:
| title | user |
```
## `the response code should be ":statusCode"`
Verifies if the server response code has match to given one.
## `the response should exact match to body:`
Verifies if the server response body has exact match to given one.
```gherkin
the response should exact match to body:
"""
{
"userId": 1,
"id": 1,
"title": "user",
"body": "test"
}
"""
```
## `the response should match JSON schema:`
Verifies if the server response body has exact match to given JSON schema.
```gherkin
the response should exact match JSON schema:
"""
{
"title": "Test schema",
"type": "object",
"properties": {
"id": {
"type": "integer"
}
},
"required": ["id"]
}
"""
```
## `I set request headers:`
Sets the request headers to given one till creating new request.
```gherkin
I set request headers:
| Content-type | application/json |
| accept | */* |
```
================================================
FILE: docs/testing-rest-api.md
================================================
---
id: testing-rest-api
title: REST API examples
---
# Testing REST API of your application
In this section examples of using steps provided for testing, REST API will be provided.
All examples can be checked on site https://reqres.in/ which is simple REST API service
# Available methods
At this moment Kakunin supports methods for REST API:
- GET
- POST
- DELETE
- PATCH
Also, You can set the headers for the request.
# Making GET request
In order to create get request and verify if the response is ok you need to create scenario step:
```gherkin
Given I send "GET" request on "/api/users/2" endpoint
Then the response code should be "200"
```
This scenario will create a get request to the application and verify if the response was 200.
The response is stored till creating another request. So if We want to test the response body of a server we can create a scenario like:
```gherkin
Given I send "GET" request on "/api/users/2" endpoint
Then the response code should be "200"
And the response should exact match to body:
"""
{
"data": {
"id": 2,
"first_name": "Janet",
"last_name": "Weaver",
"avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/josephstein/128.jpg"
}
}
"""
```
Based on this We can also check if the response matches schema that we have provided by using step:
```gherkin
Then the response should exact match JSON schema:
```
# Making POST request
In order to create post request and attach the JSON body to it You need to create a scenario:
```gherkin
Given I send "POST" request on "/api/users" endpoint with body:
"""
{
"name": "morpheus",
"job": "leader"
}
"""
Then the response code should be "201"
```
or you can create post request and attach the form data to it:
```gherkin
Given I send "POST" request on "/api/users" endpoint using form data:
| name | morpheus |
Then the response code should be "201"
```
This scenario will create a post request to the application and verify if response was 201 (created).
The response is stored till creating another request. So if We want to test the response body of a server we can create scenarios
like before:
```gherkin
Given I send "POST" request on "/api/users" endpoint with body:
"""
{
"name": "morpheus",
"job": "leader"
}
"""
Then the response code should be "201"
And the response should match JSON schema:
"""
{
"title": "Post schema",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"job": {
"type": "string"
}
},
"required": ["name", "job"]
}
"""
```
Scenario like that will verify if the post request was executed and response schema matches given one.
# Making DELETE request
Delete request works similarly to get request. Example of delete scenario:
```gherkin
Given I send "DELETE" request on "/api/users/2" endpoint
Then the response code should be "204"
```
# Making PATCH request
Patch request works similarly to post request. Example of patch scenario:
```gherkin
Given I send "PATCH" request on "/api/users/2" endpoint with JSON body:
"""
{
"name": "morpheus",
"job": "zion resident"
}
"""
Then the response code should be "200"
And the response should exact match to body:
"""
{
"name": "morpheus",
"job": "zion resident",
"updatedAt": "2019-02-12T18:25:06.001Z"
}
"""
```
# Setting headers for request
Sometimes We want to set the headers for next request. In order to achieve this, We can create scenario like:
```gherkin
Given I set request headers:
| User-Agent | Mozilla |
When I send "POST" request on "postTestEndpoint" endpoint with JSON body:
"""
{
"title": "adam",
"body": "test"
}
"""
Then the response code should be "403"
```
This scenario will set "User-Agent" header of next request to "Mozilla".
================================================
FILE: docs/transformers.md
================================================
---
id: transformers
title: Transformers
---
Transformers allow you to transform values passed to form steps.
For example a select requires to pass a value `/options/1b30f17e-e445-4d28-a30c-dedad95829ab`. This one is quite unreadable, but with the help of transformers you are
able to write it like this: `d:options:someOptionName`.
In real-life example it will look similar to:
```gherkin
I fill the "myForm" form with:
| inputElement | d:someDictionary:someKey |
| textareaElement | g:someGenerator |
| radioElement | v:someVariableName |
| checkboxElement | standard value |
```
There are 3 types of built-in transformers:
## Dictionaries
Dictionaries allows you to transform a value A to value B using a simple key->value transformation.
You can run a dictionary transformer by providing dictionary prefix `d:`, specifying the dictionary name and key that should be used as a value provider. For example:
`d:myDictionaryName:myDictionaryKey`
this example assumes that there is a dictionary that supports name `myDictionaryName` and it has `myDictionarKey` key.
You can read about dictionaries in `Extending Kakunin` section.
## Generators
Generators allows you to generate a value by using a specified generator.
This can be done by: `g:generatorName`.
If a generator supports parameters then you can specify them by:
`g:generatorName:param1:param2:...:paramN`
You can read more about generators in `Extending Kakunin` section.
## Variable store
Variable store allows you to fill the form with a value that was saved in previous steps of current running scenario.
This can be done by:
`v:variableName`
You can read more about variable store in `Extending Kakunin` section
================================================
FILE: functional-tests/dictionaries/test-dictionary.js
================================================
const { dictionaries } = require('kakunin');
const { BaseDictionary } = require('kakunin');
class TestDictionary extends BaseDictionary {
constructor() {
super('test-dictionary', {
'test-name': 'Janek',
'test-value': 'lux',
});
}
}
dictionaries.addDictionary(new TestDictionary());
================================================
FILE: functional-tests/downloads/.gitkeep
================================================
================================================
FILE: functional-tests/features/content/operations_on_stored_variables.feature
================================================
Feature: Store table and compare jsons
As a kakunin user
I want to store values as variables
Scenario: Store table and compare jsons
Given I visit the "main" page
When I click the "tabularDataLink" element
Then the "tabularData" page is displayed
When I store table "rows" rows as "tableValue" with columns:
| indexLocator |
| descendingIndex |
| viewButton |
Then compare given JSON string with stored "tableValue" JSON:
"""
[
["1", "4", "View"],
["2", "3", "View"],
["3", "2", "View"],
["4", "1", "View"]
]
"""
Scenario: Compare stored values with the content from a xlsx file - equal rows and four stored values
Given I store the content from "http://localhost:8080/xlsx/data-9rows" endpoint as "storedTable" variable
Then the file "example.xlsx" contains table data stored under "storedTable" variable
Scenario: Compare stored values with the content from a xlsx file - equal rows and one stored value
Given I store the content from "http://localhost:8080/xlsx/data-9rows-part" endpoint as "storedTable" variable
Then the file "example.xlsx" contains table data stored under "storedTable" variable
Scenario: Compare stored values with the content from a xlsx file - three rows and four stored values
Given I store the content from "http://localhost:8080/xlsx/data-3rows" endpoint as "storedTable" variable
Then the file "example.xlsx" contains table data stored under "storedTable" variable
Scenario: Compare stored values with the content from a xlsx file - three rows and two stored values
Given I store the content from "http://localhost:8080/xlsx/data-3rows-part" endpoint as "storedTable" variable
Then the file "example.xlsx" contains table data stored under "storedTable" variable
Scenario: Compare stored values with the content from a xlsx file - one row and four stored values
Given I store the content from "http://localhost:8080/xlsx/data-1row" endpoint as "storedTable" variable
Then the file "example.xlsx" contains table data stored under "storedTable" variable
Scenario: Compare stored values with the content from a xlsx file - one row and two stored values
Given I store the content from "http://localhost:8080/xlsx/data-1row-part" endpoint as "storedTable" variable
Then the file "example.xlsx" contains table data stored under "storedTable" variable
================================================
FILE: functional-tests/features/content/validate_tabular_data.feature
================================================
Feature: Tabular data
As a kakunin user
I want validate tabular data
Scenario: Validate tabular data count
Given I visit the "main" page
When I click the "tabularDataLink" element
Then the "tabularData" page is displayed
And there are "at least 1" "rows" elements
And there are "above 3" "rows" elements
And there are "below 5" "rows" elements
And there are "within 3 5" "rows" elements
And there are "equal 4" "rows" elements
Scenario: Validate tabular data count and content, also check sorting
Given I visit the "main" page
When I click the "tabularDataLink" element
Then the "tabularData" page is displayed
And the "rows" element is visible
And there are "at least 4" following elements for element "rows":
| indexLocator | r:validNumber |
And there are "above 3" following elements for element "rows":
| indexLocator | r:validNumber |
And there are "equal 4" following elements for element "rows":
| indexLocator | r:validNumber |
And there are "below 5" following elements for element "rows":
| indexLocator | r:validNumber |
And there are "within 3-5" following elements for element "rows":
| indexLocator | r:validNumber |
And there are "equal 4" following elements for element "rows":
| indexLocator | r:validNumber |
| idLocator | t:MY_CUSTOM_ID_ |
| nameLocator | r:notEmpty |
| viewButton | f:isVisible |
| viewButton | f:isClickable |
And every "rows" element should have the same value for element "viewButton"
And "indexLocator" value on the "rows" list is sorted in "ascending" order
And "descendingIndex" value on the "rows" list is sorted in "descending" order
Scenario: Validate exact tabular data by columns
Given I visit the "main" page
When I click the "tabularDataLink" element
Then the "tabularData" page is displayed
And there are following elements in table "rows":
| indexLocator | nameLocator |
| t:1 | t:Some custom name 1 |
| t:2 | t:Some custom name 2 |
| t:3 | t:Some custom name 3 |
| t:4 | t:Some custom name 4 |
And the element "rows" should have an item with values:
| indexLocator | t:1 |
| indexLocator | f:isVisible |
And the element "rows" should not have an item with values:
| indexLocator | t:incorrect-number-value |
Scenario: Navigate to pages by using click steps
Given I visit the "main" page
When I click the "valueToClick" element
Then the "tabularData" page is displayed
================================================
FILE: functional-tests/features/content/validate_tabular_data_css.feature
================================================
Feature: Tabular data
As a kakunin user
I want validate tabular data
Scenario: Validate tabular data count
Given I visit the "main" page
When I click the "a[href='/tabular-data']" element
Then the "tabularData" page is displayed
And there are "at least 1" "table tr" elements
And there are "above 3" "table tr" elements
And there are "below 5" "table tr" elements
And there are "within 3 5" "table tr" elements
And there are "equal 4" "table tr" elements
Scenario: Validate tabular data count and content, also check sorting
Given I visit the "main" page
When I click the "a[href='/tabular-data']" element
Then the "tabularData" page is displayed
And the "table tr" element is visible
And there are "at least 4" following elements for element "table tr":
| .index | r:validNumber |
And there are "above 3" following elements for element "table tr":
| .index | r:validNumber |
And there are "equal 4" following elements for element "table tr":
| .index | r:validNumber |
And there are "below 5" following elements for element "table tr":
| .index | r:validNumber |
And there are "within 3-5" following elements for element "table tr":
| .index | r:validNumber |
And there are "equal 4" following elements for element "table tr":
| .index | r:validNumber |
| .id | t:MY_CUSTOM_ID_ |
| .name | r:notEmpty |
| button.view | f:isVisible |
| button.view | f:isClickable |
And every "table tr" element should have the same value for element "button.view"
And ".index" value on the "table tr" list is sorted in "ascending" order
And ".descending-sort" value on the "table tr" list is sorted in "descending" order
Scenario: Validate exact tabular data by columns
Given I visit the "main" page
When I click the "a[href='/tabular-data']" element
Then the "tabularData" page is displayed
And there are following elements in table "table tr":
| .index | nameLocator |
| t:1 | t:Some custom name 1 |
| t:2 | t:Some custom name 2 |
| t:3 | t:Some custom name 3 |
| t:4 | t:Some custom name 4 |
And the element "table tr" should have an item with values:
| .index | t:1 |
| .index | f:isVisible |
And the element "table tr" should not have an item with values:
| .index | t:incorrect-number-value |
Scenario: Navigate to pages by using click steps
Given I visit the "main" page
When I click the ".valueForClickStep" element
Then the "tabularData" page is displayed
================================================
FILE: functional-tests/features/content/wait_for_element_dissapear.feature
================================================
Feature: Element visibility
As a kakunin user
I want to wait for element to disappear
Scenario: Check visibility - disappear step
Given I visit the "main" page
When I click the "buttonLink" element
Then the "buttonForm" page is displayed
When I click the "disappearBtn" element
Then I wait for the "disappearBtn" element to disappear
Scenario: Check visibility with - wait for condition step
Given I visit the "main" page
When I click the "buttonLink" element
Then the "buttonForm" page is displayed
When I click the "disappearBtn" element
And I wait for "invisibilityOf" of the "disappearBtn" element
Then the "disappearBtn" element is not visible
================================================
FILE: functional-tests/features/content/wait_for_element_dissapear_css.feature
================================================
Feature: Element visibility
As a kakunin user
I want to wait for element to disappear
Scenario: Check visibility - disappear step
Given I visit the "main" page
When I click the "a[href='/form/disappear']" element
Then the "buttonForm" page is displayed
When I click the "#button" element
Then I wait for the "#button" element to disappear
Scenario: Check visibility with - wait for condition step
Given I visit the "main" page
When I click the "buttonLink" element
Then the "buttonForm" page is displayed
When I click the "#button" element
And I wait for "invisibilityOf" of the "#button" element
Then the "#button" element is not visible
================================================
FILE: functional-tests/features/drag-and-drop/operations_on_elements.feature
================================================
Feature: Drag and drop
As a kakunin user
I want to be able to make operations on elements
Scenario: Drag element and drop on the other one
Given I visit the "dragAndDrop" page
When I drag "kittens" element and drop over "target" element
Then the "kittensInsideTarget" element is visible
================================================
FILE: functional-tests/features/drag-and-drop/operations_on_elements_css.feature
================================================
Feature: Drag and drop
As a kakunin user
I want to be able to make operations on elements
Scenario: Drag element and drop on the other one
Given I visit the "dragAndDrop" page
When I drag "#draggable" element and drop over "#droppable" element
Then the ".ui-state-highlight" element is visible
================================================
FILE: functional-tests/features/forms/fill_and_check_form.feature
================================================
Feature: Forms
As a kakunin user
I want fill and check form fields
Scenario: Fill and check form fields
Given I visit the "main" page
When I click the "formLink" element
Then the "simpleForm" page is displayed
When I generate random "stringWithLength:10" as "storedStringWithLength"
And I fill the "form" form with:
| nameInput | v:storedStringWithLength |
Then the "form" form is filled with:
| nameInput | v:storedStringWithLength |
When I fill the "form" form with:
| nameInput | d:test-dictionary:test-name |
| descriptionTextarea | some description |
| optionCheckboxes | Checkbox Option 2 |
| optionCheckboxes | Checkbox Option 3 |
| optionRadios | third-radio-option |
| statusSelect | unknown |
And I click the "submitButton" element
Then the "simpleFormPost" page is displayed
And the "form" form is filled with:
| nameInput | d:test-dictionary:test-name |
| descriptionTextarea | some description |
| optionCheckboxes | Checkbox Option 2 |
| optionCheckboxes | Checkbox Option 3 |
| optionRadios | third-radio-option |
| statusSelect | unknown |
Scenario: Fill input and textarea fields and then store the values and check if the form was filled with expected data
Given I visit the "main" page
When I click the "formLink" element
Then the "simpleForm" page is displayed
When I generate random "stringWithLength:6" as "storedStringWithLength"
And I fill the "form" form with:
| nameInput | v:storedStringWithLength |
And I store the "nameInput" element text as "storedInputValue" variable
Then the "form" form is filled with:
| nameInput | v:storedStringWithLength |
| nameInput | v:storedInputValue |
And there is element "nameInput" with value "t:v:storedInputValue"
And there is element "nameInput" with value "t:v:storedStringWithLength"
When I fill the "form" form with:
| descriptionTextarea | g:personalData:email |
And I store the "descriptionTextarea" element text as "storedTextareaValue" variable
Then the "form" form is filled with:
| descriptionTextarea | v:storedTextareaValue |
And there is element "descriptionTextarea" with value "t:v:storedTextareaValue"
And there is element "descriptionTextarea" with value "r:email"
================================================
FILE: functional-tests/features/forms/fill_and_check_form_css.feature
================================================
Feature: Forms
As a kakunin user
I want fill and check form fields
Scenario: Fill and check form fields
Given I visit the "main" page
When I click the "a[href='/form/simple']" element
Then the "simpleForm" page is displayed
When I generate random "stringWithLength:10" as "storedStringWithLength"
And I fill the "form" form with:
| nameInput | v:storedStringWithLength |
Then the "form" form is filled with:
| nameInput | v:storedStringWithLength |
When I fill the "form" form with:
| input[name="name"] | d:test-dictionary:test-name |
| textarea[name="description"] | some description |
| input[type="checkbox"] | Checkbox Option 2 |
| input[type="checkbox"] | Checkbox Option 3 |
| input[type="radio"] | third-radio-option |
| select[name="status"] | unknown |
And I click the "submitButton" element
Then the "simpleFormPost" page is displayed
And the "form" form is filled with:
| input[name="name"] | d:test-dictionary:test-name |
| textarea[name="description"] | some description |
| input[type="checkbox"] | Checkbox Option 2 |
| input[type="checkbox"] | Checkbox Option 3 |
| input[type="radio"] | third-radio-option |
| select[name="status"] | unknown |
================================================
FILE: functional-tests/features/forms/fill_select.feature
================================================
Feature: Forms
As a kakunin user
I want to check options
Scenario: Fill and check form fields
Given I visit the "main" page
When I click the "formSelectLink" element
Then the "simpleSelectForm" page is displayed
And there are "personOption" dropdown list elements with following options:
| Person3 |
| Person2 |
| Person1 |
| Person4 |
================================================
FILE: functional-tests/features/matchers/match_current_date.feature
================================================
Feature: Matchers
As a kakunin user
I want to navigate to matcher page and match current date
Scenario: I want to match current date with format
Given I visit the "main" page
When I click the "matchersLink" element
Then the "matchers" page is displayed
And there is element "dateElement" with value "f:isVisible"
And there is element "dateMatcherText" with value "t:Date/Time:"
And there is element "dateElement" with value "f:isClickable"
And there is element "dateElement" with value "f:isPresent"
And there is element "dateElement" with value "r:notEmpty"
And there is element "dateElement" with value "f:currentDate:YYYY-MM-DD"
Scenario: I want to match current date without additional parameters
Given I visit the "main" page
When I click the "matchersLink" element
Then the "matchers" page is displayed
And there is element "dateElement" with value "f:currentDate"
================================================
FILE: functional-tests/features/matchers/match_current_date_css.feature
================================================
Feature: Matchers
As a kakunin user
I want to navigate to matcher page and match current date
Scenario: I want to match current date with format
Given I visit the "main" page
When I click the ".matchers" element
Then the "matchers" page is displayed
And there is element "span.current_date" with value "f:isVisible"
And there is element "p.date-matcher" with value "t:Date/Time:"
And there is element "span.current_date" with value "f:isClickable"
And there is element "span.current_date" with value "f:isPresent"
And there is element "span.current_date" with value "r:notEmpty"
And there is element "span.current_date" with value "f:currentDate:YYYY-MM-DD"
Scenario: I want to match current date without additional parameters
Given I visit the "main" page
When I click the ".matchers" element
Then the "matchers" page is displayed
And there is element "span.current_date" with value "f:currentDate"
================================================
FILE: functional-tests/features/matchers/matchers.feature
================================================
Feature: Matchers
As a Kakunin user
I want to fill input and then check if the value matches the expected result
Scenario: Fill the input and check value
Given I visit the "main" page
When I click the "formLink" element
Then the "simpleForm" page is displayed
And I fill the "form" form with:
| nameInput | test |
And there is element "nameInput" with value "t:test"
And there is element "nameInput" containing "test" text
And there is no element "nameInput" with value "t:hello"
And there is no element "nameInput" containing "hello" text
And there is element "nameInput" matching "isVisible" matcher
And there is no element "nameInput" matching "isNotVisible" matcher
And there is element "nameInput" with "notEmpty" regex
And there is no element "nameInput" with "number" regex
================================================
FILE: functional-tests/features/matchers/matchers_css.feature
================================================
Feature: Matchers
As a Kakunin user
I want to fill input and then check if the value matches the expected result
Scenario: Fill the input and check value
Given I visit the "main" page
When I click the "a[href='/form/simple']" element
Then the "simpleForm" page is displayed
And I fill the "form" form with:
| input[name="name"] | test |
And there is element "input[name='name']" with value "t:test"
And there is element "input[name='name']" containing "test" text
And there is no element "input[name='name']" with value "t:hello"
And there is no element "input[name='name']" containing "hello" text
And there is element "input[name='name']" matching "isVisible" matcher
And there is no element "input[name='name']" matching "isNotVisible" matcher
And there is element "input[name='name']" with "notEmpty" regex
And there is no element "input[name='name']" with "number" regex
================================================
FILE: functional-tests/features/navigation/navigate_to_given_page.feature
================================================
Feature: Navigation
As a kakunin user
I want to navigate to selected page
Scenario: Navigate by link click
Given I visit the "main" page
When I click the "formLink" element
Then the "simpleForm" page is displayed
And the "form" element is visible
Scenario: Navigate to parametrized url
Given I visit the "navigationPages" page with parameters:
| pageId | myPageId |
| title | myPageTitle |
Then there is element "pageId" with value "t:myPageId"
And there is element "title" with value "t:myPageTitle"
Scenario: Navigate to parametrized url with additional params
Given I visit the "navigationPages" page with parameters:
| pageId | myPageId |
| additionalParam1 | value1 |
| title | myPageTitle |
| additionalParam2 | value2 |
Then the "additionalParams" page is displayed
# check again
Then I visit the "navigationPages" page with parameters:
| pageId | myPageId |
| additionalParam1 | value1 |
| title | myPageTitle |
| additionalParam2 | value2 |
Then the "additionalParams" page is displayed
================================================
FILE: functional-tests/features/navigation/navigate_to_given_page_css.feature
================================================
Feature: Navigation
As a kakunin user
I want to navigate to selected page
Scenario: Navigate by link click
Given I visit the "main" page
When I click the "a[href='/form/simple']" element
Then the "simpleForm" page is displayed
And the "form" element is visible
Scenario: Navigate to parametrized url
Given I visit the "navigationPages" page with parameters:
| pageId | myPageId |
| title | myPageTitle |
Then there is element "p.pageId" with value "t:myPageId"
And there is element "p.title" with value "t:myPageTitle"
================================================
FILE: functional-tests/features/navigation/switch-between-tabs.feature
================================================
Feature: Navigation
As a kakunin user
I want to switch between browser tabs
Scenario: Navigate by link click
Given I visit the "main" page
When I click the "matchersInNewTabLink" element
And I switch to window number "2" of a browser
Then the "matchers" page is displayed
And there is element "dateElement" with value "f:isVisible"
When I close the current browser tab
Then the "main" page is displayed
When I click the "matchersLink" element
Then the "matchers" page is displayed
================================================
FILE: functional-tests/features/pages/verify_displayed_page.feature
================================================
Feature: Verify displayed pge
As a kakunin user
I want to make sure I am on expected page
Scenario: Verify relative page
Given I visit the "main" page
When I click the "formLink" element
Then the "simpleForm" page is displayed
Scenario: Verify absolute url page
Given I visit the "main" page
When I click the "absolutePageLink" element
Then the "absolutePage" page is displayed
Scenario: Verify external url page
Given I visit the "main" page
When I click the "googleLink" element
Then the "google" page is displayed
================================================
FILE: functional-tests/features/testing-api/testing_delete_request.feature
================================================
Feature: Test server delete request
As a kakunin user
I want to test restApi delete request
Scenario: REST get example test
Given I send "delete" request on "deleteTestEndpoint" endpoint
Then the response code should be "200"
================================================
FILE: functional-tests/features/testing-api/testing_get_response.feature
================================================
Feature: Test server get response
As a kakunin user
I want to test restApi get response
Scenario: REST get example test
Given I send "GET" request on "getTestEndpoint" endpoint
Then the response code should be "200"
And the response should exact match to body:
"""
{
"id": 1,
"title": "Kaunin",
"body": "test"
}
"""
================================================
FILE: functional-tests/features/testing-api/testing_headers_setting.feature
================================================
Feature: Test setting headers
As a kakunin user
I want to set the headers
Scenario: Setting http headers
Given I set request headers:
| User-Agent | Mozilla |
When I send "POST" request on "postTestEndpoint" endpoint with JSON body:
"""
{
"title": "adam",
"body": "test"
}
"""
Then the response code should be "403"
================================================
FILE: functional-tests/features/testing-api/testing_patch_request.feature
================================================
Feature: Test patch endpoint
As a kakunin user
I want to set test the patch endpoint
Scenario: REST patch example test
Given I send "PATCH" request on "patchTestEndpoint" endpoint with JSON body:
"""
{
"first_name": "adam"
}
"""
Then the response code should be "200"
================================================
FILE: functional-tests/features/testing-api/testing_post_form_data.feature
================================================
Feature: Test server post request using form data
As a kakunin user
I want to test restApi post request
Scenario: REST get example test
Given I send "POST" request on "postFormDataEndpoint" endpoint using form data:
| name | adam1 |
Then the response code should be "201"
================================================
FILE: functional-tests/features/testing-api/testing_post_json.feature
================================================
Feature: Test server post response
As a kakunin user
I want to test restApi post request
Scenario: REST post example test
Given I send "POST" request on "postTestEndpoint" endpoint with JSON body:
"""
{
"name": "adam",
"title": "test"
}
"""
Then the response code should be "201"
Then the response should match JSON schema:
"""
{
"title": "Posts schema",
"type": "object",
"properties": {
"code": {
"type": "string"
},
"name": {
"type": "string"
},
"title": {
"type": "string"
}
},
"required": ["code", "name", "title"]
}
"""
================================================
FILE: functional-tests/features/wait-for-elements/wait_for_form.feature
================================================
Feature: Wait for forms
As a kakunin user
I want fill and check form fields
Scenario: Fill and check form fields
Given I visit the "main" page
When I click the "appearForm" element
Then the "appearSimpleForm" page is displayed
When I click the "formAppearBtn" element
And I fill the "form" form with:
| nameInput | d:test-dictionary:test-name |
| descriptionTextarea | some description |
| optionCheckboxes | Checkbox Option 2 |
| optionCheckboxes | Checkbox Option 3 |
| optionRadios | third-radio-option |
| statusSelect | unknown |
Then the "form" form is filled with:
| nameInput | d:test-dictionary:test-name |
| descriptionTextarea | some description |
| optionCheckboxes | Checkbox Option 2 |
| optionCheckboxes | Checkbox Option 3 |
| optionRadios | third-radio-option |
| statusSelect | unknown |
When I click the "submitButton" element
Then the "appearSimpleFormPost" page is displayed
================================================
FILE: functional-tests/features/wait-for-elements/wait_for_form_css.feature
================================================
Feature: Wait for forms
As a kakunin user
I want fill and check form fields
Scenario: Fill and check form fields
Given I visit the "main" page
When I click the ".appearForm" element
Then the "appearSimpleForm" page is displayed
When I click the ".colored" element
And I fill the "form" form with:
| input[name="name"] | d:test-dictionary:test-name |
| textarea[name="description"] | some description |
| input[type="checkbox"] | Checkbox Option 2 |
| input[type="checkbox"] | Checkbox Option 3 |
| input[type="radio"] | third-radio-option |
| select[name="status"] | unknown |
Then the "form" form is filled with:
| input[name="name"] | d:test-dictionary:test-name |
| textarea[name="description"] | some description |
| input[type="checkbox"] | Checkbox Option 2 |
| input[type="checkbox"] | Checkbox Option 3 |
| input[type="radio"] | third-radio-option |
| select[name="status"] | unknown |
When I click the "input[type='submit']" element
Then the "appearSimpleFormPost" page is displayed
================================================
FILE: functional-tests/features/wait-for-elements/wait_for_table.feature
================================================
Feature: Wait for Tabular data
As a kakunin user
I want validate tabular data which will appear in future
Scenario: Validate tabular data count and content, also check sorting
Given I visit the "main" page
When I click the "appearTable" element
Then the "appearTabularData" page is displayed
When I click the "tableAppearBtn" element
Then there are "equal 4" following elements for element "rows":
| indexLocator | r:validNumber |
| idLocator | t:MY_CUSTOM_ID_ |
| nameLocator | r:notEmpty |
| viewButton | f:isVisible |
| viewButton | f:isClickable |
And every "rows" element should have the same value for element "viewButton"
And "indexLocator" value on the "rows" list is sorted in "ascending" order
And "descendingIndex" value on the "rows" list is sorted in "descending" order
Scenario: Validate tabular data count and content, also check sorting
Given I visit the "main" page
When I click the "appearTable" element
Then the "appearTabularData" page is displayed
When I click the "tableAppearBtn" element
And I store table "rows" rows as "tableValue" with columns:
| indexLocator |
| idLocator |
| nameLocator |
Then compare given JSON string with stored "tableValue" JSON:
"""
[
["1","MY_CUSTOM_ID_1","Some custom name 1"],
["2","MY_CUSTOM_ID_2","Some custom name 2"],
["3","MY_CUSTOM_ID_3","Some custom name 3"],
["4","MY_CUSTOM_ID_4","Some custom name 4"]
]
"""
================================================
FILE: functional-tests/kakunin.conf.js
================================================
module.exports = {
browserWidth: 1600,
browserHeight: 900,
timeout: 60,
elementsVisibilityTimeout: 5,
waitForPageTimeout: 5,
downloadTimeout: 30,
reports: '/reports',
downloads: '/downloads',
data: '/data',
features: ['/features'],
pages: ['/pages'],
matchers: ['/matchers'],
generators: ['/generators'],
form_handlers: ['/form_handlers'],
step_definitions: ['/step_definitions'],
comparators: ['/comparators'],
dictionaries: ['/dictionaries'],
transformers: ['/transformers'],
regexes: ['/regexes'],
hooks: ['/hooks'],
clearEmailInboxBeforeTests: false,
clearCookiesAfterScenario: true,
clearLocalStorageAfterScenario: true,
email: null,
headless: true,
noGpu: true,
type: 'otherWeb',
baseUrl: 'http://localhost:8080',
apiUrl: 'http://localhost:8080/',
browserMob: {
serverPort: 8887,
port: 8888,
host: 'localhost',
},
browserstack: {
seleniumAddress: 'http://hub-cloud.browserstack.com/wd/hub',
defaultPort: 45691,
capabilities: {
'browserstack.user': '',
'browserstack.key': '',
'browserstack.local': true,
browserName: 'chrome',
}
},
accounts: {
someAccount: {
accounts: [
{
email: '',
password: '',
},
],
},
},
};
================================================
FILE: functional-tests/package.json
================================================
{
"name": "kakunin-functional-tests",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"kakunin": "cross-env NODE_ENV=prod kakunin",
"start": "pm2 update && pm2 start www",
"stop": "pm2 delete www",
"test-ci": "npm run start && npm run kakunin -- --parallel 4 && npm run stop",
"test": "npm run start && npm run kakunin && npm run stop"
},
"author": "",
"dependencies": {
"body-parser": "1.19.0",
"cross-env": "7.0.3",
"express": "4.17.1",
"kakunin": "file:../",
"node-fetch": "2.6.1",
"nunjucks": "3.2.3",
"protractor": "7.0.0"
},
"license": "ISC",
"devDependencies": {
"pm2": "4.5.5"
}
}
================================================
FILE: functional-tests/pages/absolutePage.js
================================================
'use strict';
const { BasePage } = require('kakunin');
class AbsolutePage extends BasePage {
constructor() {
super();
this.url = 'http://localhost:8080/absolute-page';
}
}
module.exports = AbsolutePage;
================================================
FILE: functional-tests/pages/additionalParams.js
================================================
'use strict';
const { BasePage } = require('kakunin');
class AdditionalParamsPage extends BasePage {
constructor() {
super();
this.url = '/navigation/pages/:id/titles/:title?additionalParam1=:value1&additionalParam2=:value2';
}
}
module.exports = AdditionalParamsPage;
================================================
FILE: functional-tests/pages/appearSimpleForm.js
================================================
'use strict';
const { BasePage } = require('kakunin');
class AppearSimpleForm extends BasePage {
constructor() {
super();
this.url = '/wait-for-appear/form';
this.formAppearBtn = $('.colored');
this.form = $('form');
this.nameInput = this.form.$('input[name="name"]');
this.descriptionTextarea = this.form.$('textarea[name="description"]');
this.optionCheckboxes = this.form.$$('input[type="checkbox"]');
this.optionRadios = this.form.$$('input[type="radio"]');
this.statusSelect = this.form.$('select[name="status"]');
this.submitButton = this.form.$('input[type="submit"]');
}
}
module.exports = AppearSimpleForm;
================================================
FILE: functional-tests/pages/appearSimpleFormPost.js
================================================
'use strict';
const { BasePage } = require('kakunin');
class AppearSimpleFormPost extends BasePage {
constructor() {
super();
this.url = '/wait-for-appear/form/post';
this.form = $('form');
this.nameInput = this.form.$('input[name="name"]');
this.descriptionTextarea = this.form.$('textarea[name="description"]');
this.optionCheckboxes = this.form.$$('input[type="checkbox"]');
this.optionRadios = this.form.$$('input[type="radio"]');
this.statusSelect = this.form.$('select[name="status"]');
}
}
module.exports = AppearSimpleFormPost;
================================================
FILE: functional-tests/pages/appearTabularData.js
================================================
'use strict';
const { BasePage } = require('kakunin');
class AppearTabularData extends BasePage {
constructor() {
super();
this.url = '/wait-for-appear/table';
this.tableAppearBtn = $('.colored');
this.rows = $$('table tr');
this.indexLocator = $('.index');
this.descendingIndex = $('.descending-sort');
this.idLocator = $('.id');
this.nameLocator = $('.name');
this.viewButton = $('button.view');
}
}
module.exports = AppearTabularData;
================================================
FILE: functional-tests/pages/buttonForm.js
================================================
'use strict';
const { BasePage } = require('kakunin');
class ButtonForm extends BasePage {
constructor() {
super();
this.url = '/form/disappear';
this.disappearBtn = $('#button');
}
}
module.exports = ButtonForm;
================================================
FILE: functional-tests/pages/dragAndDrop.js
================================================
'use strict';
const { BasePage } = require('kakunin');
class DragAndDropPage extends BasePage {
constructor() {
super();
this.url = '/drag-and-drop';
this.kittens = $('#draggable');
this.target = $('#droppable');
this.kittensInsideTarget = $('.ui-state-highlight');
}
}
module.exports = DragAndDropPage;
================================================
FILE: functional-tests/pages/google.js
================================================
'use strict';
const { BasePage } = require('kakunin');
class GooglePage extends BasePage {
constructor() {
super();
this.url = 'https://www.google.pl';
}
}
module.exports = GooglePage;
================================================
FILE: functional-tests/pages/main.js
================================================
'use strict';
const { BasePage } = require('kakunin');
class MainPage extends BasePage {
constructor() {
super();
this.url = '/';
this.linkDivs = $$('.available-examples-links');
this.formLink = $('a[href="/form/simple"]');
this.formSelectLink = $('a[href="/form/select"]');
this.absolutePageLink = $('a[href="/absolute-page"]');
this.googleLink = $('a[href="https://www.google.pl"]');
this.tabularDataLink = $('a[href="/tabular-data"]');
this.buttonLink = $('a[href="/form/disappear"]');
this.valueToClick = $('.valueForClickStep');
this.appearTable = $('.appearTable');
this.appearForm = $('.appearForm');
this.matchersLink = $('.matchers');
this.matchersInNewTabLink = $('.matchersInNewTab');
}
}
module.exports = MainPage;
================================================
FILE: functional-tests/pages/matchers.js
================================================
'use strict';
const { BasePage } = require('kakunin');
class MatchersPage extends BasePage {
constructor() {
super();
this.url = '/matchers';
this.dateMatcherText = $('p.date-matcher ');
this.dateElement = $('span.current_date ');
}
}
module.exports = MatchersPage;
================================================
FILE: functional-tests/pages/navigationPages.js
================================================
'use strict';
const { BasePage } = require('kakunin');
class NavigationPagesPage extends BasePage {
constructor() {
super();
this.url = '/navigation/pages/:pageId/titles/:title';
this.pageId = $('p.pageId');
this.title = $('p.title');
this.queryParam1 = $('p.queryParam1');
this.queryParam2 = $('p.queryParam2');
}
}
module.exports = NavigationPagesPage;
================================================
FILE: functional-tests/pages/simpleForm.js
================================================
'use strict';
const { BasePage } = require('kakunin');
class SimpleForm extends BasePage {
constructor() {
super();
this.url = '/form/simple';
this.form = $('form');
this.nameInput = this.form.$('input[name="name"]');
this.descriptionTextarea = this.form.$('textarea[name="description"]');
this.optionCheckboxes = this.form.$$('input[type="checkbox"]');
this.optionRadios = this.form.$$('input[type="radio"]');
this.statusSelect = this.form.$('select[name="status"]');
this.submitButton = this.form.$('input[type="submit"]');
}
}
module.exports = SimpleForm;
================================================
FILE: functional-tests/pages/simpleFormPost.js
================================================
'use strict';
const { BasePage } = require('kakunin');
class SimpleFormPost extends BasePage {
constructor() {
super();
this.url = '/form/simple/post';
this.form = $('form');
this.nameInput = this.form.$('input[name="name"]');
this.descriptionTextarea = this.form.$('textarea[name="description"]');
this.optionCheckboxes = this.form.$$('input[type="checkbox"]');
this.optionRadios = this.form.$$('input[type="radio"]');
this.statusSelect = this.form.$('select[name="status"]');
}
}
module.exports = SimpleFormPost;
================================================
FILE: functional-tests/pages/simpleSelectForm.js
================================================
'use strict';
const { BasePage } = require('kakunin');
class SimpleForm extends BasePage {
constructor() {
super();
this.url = '/form/select';
this.form = $('form');
this.selectPerson = $('#personlist');
this.personOption = this.selectPerson.$$('option');
this.submitButton = this.form.$('input[type="submit"]');
}
}
module.exports = SimpleForm;
================================================
FILE: functional-tests/pages/tabularData.js
================================================
'use strict';
const { BasePage } = require('kakunin');
class TabularData extends BasePage {
constructor() {
super();
this.url = '/tabular-data';
this.rows = $$('table tr');
this.indexLocator = $('.index');
this.descendingIndex = $('.descending-sort');
this.idLocator = $('.id');
this.nameLocator = $('.name');
this.viewButton = $('button.view');
}
}
module.exports = TabularData;
================================================
FILE: functional-tests/regexes/index.js
================================================
module.exports = {
validNumber: '\\d+',
};
================================================
FILE: functional-tests/step_definitions/custom_json_parser.js
================================================
const variableStore = require('kakunin').variableStore;
const fetch = require('node-fetch');
const { When } = require('kakunin');
When(/^compare given JSON string with stored "([^"]*)" JSON:$/, function(storedJsonArray, json) {
const removeNewLines = str => str.replace(/(\r\n|\n|\r)/gm, '');
const storedJsonString = JSON.stringify(variableStore.getVariableValue(storedJsonArray));
const expectedJsonString = JSON.stringify(JSON.parse(removeNewLines(json)));
if (storedJsonString === expectedJsonString) {
return Promise.resolve();
}
return Promise.reject('JSON strings are not the same!');
});
When(/^I store the content from "([^"]*)" endpoint as "([^"]*)" variable/, function(url, variableName) {
return fetch(url)
.then(res => res.json())
.then(data => variableStore.storeVariable(variableName, JSON.parse(data.content)));
});
================================================
FILE: functional-tests/www/index.js
================================================
const express = require('express');
const nunjucks = require('nunjucks');
const path = require('path');
const app = express();
const { xlsxDataRouting } = require('./jsonData/xlsxData.router');
app.set('views', path.join(__dirname, 'views'));
app.use('/assets', express.static(path.join(__dirname, 'assets')));
nunjucks.configure(app.get('views'), {
autoescape: true,
express: app,
});
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.get('/', function(req, res) {
res.render('index.njs');
});
app.get('/drag-and-drop', function(req, res) {
res.render('drag-and-drop/index.njs');
});
app.get('/tabular-data', function(req, res) {
res.render('table/tabular-data.njs');
});
app.get('/absolute-page', function(req, res) {
res.render('absolute/index.njs');
});
app.get('/form/simple', function(req, res) {
res.render('form/simple.njs');
});
app.get('/form/disappear', function(req, res) {
res.render('form/disappear.njs');
});
app.post('/form/simple/post', function(req, res) {
res.render('form/simple.njs', {
form: req.body,
});
});
app.get('/navigation/pages/:pageId/titles/:title', function(req, res) {
res.render('navigation/page.njs', {
pageId: req.params.pageId,
title: req.params.title,
queryParam1: req.query.queryParam1,
queryParam2: req.query.queryParam2
});
});
app.get('/wait-for-appear/table', function(req, res) {
res.render('wait-for-appear/table.njs');
});
app.get('/wait-for-appear/form', function(req, res) {
res.render('wait-for-appear/form.njs');
});
app.post('/wait-for-appear/form/post', function(req, res) {
res.render('wait-for-appear/form.njs', {
form: req.body,
});
});
app.get('/matchers', function(req, res) {
res.render('matchers/matchers.njs');
});
app.get('/form/select', function(req, res) {
res.render('form/select.njs');
});
app.post('/form/select/post', function(req, res) {
res.render('form/select.njs', {
form: req.body,
});
});
app.delete('/deleteTestEndpoint',function(req, res, next){
res.status(200);
return res.end();
});
app.get('/getTestEndpoint', function (req, res) {
res.setHeader('Content-Type', 'application/json');
const header = req.header('host');
if (header === 'localhost:8080') {
return res.send(JSON.stringify(
{ id: 1,
title: 'Kaunin',
body: 'test' }
));
}
res.status(403);
return res.end();
});
app.patch('/patchTestEndpoint', function (req, res) {
if(req.body.hasOwnProperty('first_name') === true) {
res.status(200);
return res.end();
}
res.status(400)
return res.end();
});
app.post('/postTestEndpoint', function (req, res) {
const name = req.body.name;
const title = req.body.title;
const header = req.header('User-Agent');
const object = { code: 'created', name: name, title: title };
if (header === 'Mozilla') {
res.status(403);
return res.end();
}
res.status(201);
return res.json(object);
});
app.post('/postFormDataEndpoint', function (req, res) {
const contentType = req.header('Content-Type');
if (contentType !== 'multipart/form-data') {
res.status(403);
return res.end();
}
res.status(201);
return res.end();
});
app.use('/xlsx', xlsxDataRouting());
app.listen(8080, function() {
console.log('Example app listening on port 8080!');
});
================================================
FILE: functional-tests/www/jsonData/xlsxData.router.js
================================================
const express = require('express');
function xlsxDataRouting() {
const router = express.Router();
router.get('/data-9rows', (req, res) => {
res.json({
content:
'[["Schamberger PLC","33333003158","25","Food & Consumable supply, IT, Medical Supplies and Equipment, Motors & Vehicles, Office Furniture, Office Supplies"],["JANEK","9912396963","2","Security Guards, Printing & Advertising, Food & Consumable supply, IT, Medical Supplies and Equipment, Motors & Vehicles, Office Furniture, Office Supplies"],["شركة تكامل القابضة","9912396963","2","Contracting & Maintenance, Food & Consumable supply, IT, Medical Supplies and Equipment, Motors & Vehicles, Office Furniture, Office Supplies"],["شركة تكامل القابضة","9912396963","2","Contracting & Maintenance, Food & Consumable supply, IT, Medical Supplies and Equipment, Motors & Vehicles, Office Furniture, Office Supplies"],["Bosco, Marks and Walker","9910412435","7","Contracting & Maintenance, Food & Consumable supply, IT, Medical Supplies and Equipment, Motors & Vehicles, Office Furniture, Office Supplies"],["Rippin-Torp","9912038835","7","Contracting & Maintenance, Food & Consumable supply, IT, Medical Supplies and Equipment, Motors & Vehicles, Office Furniture, Office Supplies"],["Kling-Bogan","9910412335","25","Contracting & Maintenance, Security Guards, Printing & Advertising, Food & Consumable supply, IT, Medical Supplies and Equipment, Motors & Vehicles, Office Furniture, Office Supplies"],["Hane, Bartoletti and Mitchell","9911038835","7","Contracting & Maintenance, Food & Consumable supply, IT, Medical Supplies and Equipment, Motors & Vehicles, Office Furniture, Office Supplies"],["Zemlak, Stiedemann and Green","9912496963","7","Contracting & Maintenance, Food & Consumable supply, IT, Medical Supplies and Equipment, Motors & Vehicles, Office Furniture, Office Supplies"]]',
});
});
router.get('/data-9rows-part', (req, res) => {
res.json({
content:
'[["Schamberger PLC"],["JANEK"],["شركة تكامل القابضة"],["شركة تكامل القابضة"],["Bosco, Marks and Walker"],["Rippin-Torp"],["Kling-Bogan"],["Hane, Bartoletti and Mitchell"],["Zemlak, Stiedemann and Green"]]',
});
});
router.get('/data-3rows', (req, res) => {
res.json({
content:
'[["Schamberger PLC","33333003158","25","Food & Consumable supply, IT, Medical Supplies and Equipment, Motors & Vehicles, Office Furniture, Office Supplies"],["JANEK","9912396963","2","Security Guards, Printing & Advertising, Food & Consumable supply, IT, Medical Supplies and Equipment, Motors & Vehicles, Office Furniture, Office Supplies"],["شركة تكامل القابضة","9912396963","2","Contracting & Maintenance, Food & Consumable supply, IT, Medical Supplies and Equipment, Motors & Vehicles, Office Furniture, Office Supplies"]]',
});
});
router.get('/data-3rows-part', (req, res) => {
res.json({
content:
'[["Rippin-Torp","9912038835"],["Kling-Bogan","9910412335"],["Hane, Bartoletti and Mitchell","9911038835"]]',
});
});
router.get('/data-1row', (req, res) => {
res.json({
content:
'[["Bosco, Marks and Walker","9910412435","7","Contracting & Maintenance, Food & Consumable supply, IT, Medical Supplies and Equipment, Motors & Vehicles, Office Furniture, Office Supplies"]]',
});
});
router.get('/data-1row-part', (req, res) => {
res.json({
content: '[["Bosco, Marks and Walker","9910412435"]]',
});
});
return router;
}
module.exports = {
xlsxDataRouting,
};
================================================
FILE: functional-tests/www/views/absolute/index.njs
================================================
{% extends 'layout/default.njs' %}
{% block content %}
### **Automated testing framework**
Kakunin is a Protractor extension created by The Software House sp. z o.o. and Takamol Holding. It allows you to write e2e test scenarios with a help of Gherkin language and JavaScript for all kind of applications - Angular, React and others.
---
### **Key Features:**
1. E2E testing
2. Performance testing
3. Parallel testing
4. Cross browser testing
5. Reports
---
### **Documentation:**
You can find documentation on the **[official page](https://kakunin.io)**.
---
### **Contributing:**
Feel free to contribute to this project! Just fork the code, make any updated and let us know!
---
### **Issues:**
If you notice any issues while using, let as know on **[github](https://github.com/TheSoftwareHouse/Kakunin/issues)**.
Security issues, please sent on email
---
### **You may also like our other projects:**
- **[Babelsheet-js](https://github.com/TheSoftwareHouse/babelsheet-js)**
- **[Fogger](https://github.com/TheSoftwareHouse/fogger)**
---
### **About us:**
browserWidth - width of browser window default: 1600
browserheight - height of browser window default: 900
timeout - global timeout for a single step execution in seconds default: 60
maxEmailRepeats - maximum email repeats to catch email used in the email step
intervalEmail - interval for email checking step default: 5 in seconds
elementsVisibilityTimeout - maximum wait timeout for element visibility default: 5 seconds
waitForPageTimeout - maximum wait timeout for page visibility default: 5 seconds
downloadTimeout - maximum wait timeout for file to be downloaded default: 30 seconds
emails - array of paths to store emails related custom code
reports - path to store reports
downloads - path to store downloaded files
data - path to store test related files (for example files to be downloaded)
feature - array of paths to store features
pages - array of paths to store page objects
matchers - array of paths to store custom matchers
generators - array of paths to store custom generators
form_handlers - array of paths to store custom form handlers
step_definitions - array of paths to store custom steps
comparators - array of paths to store custom comparators
dictionaries - array of paths to store custom dictionaries
transformers - array of paths to store custom transformers
regexes - array of paths to store custom regexes
hooks - array of paths to store custom hooks
clearEmailInboxBeforeTests - flag to active clearing email inbox before tests are executed default: false | true for apps with email checking functionality activated
clearCookiesAfterScenario - flag to activate clearing cookies after every scenario default: true
clearLocalStorageAfterScenario - flag to activate clearing local storage after every scenario default: true
for custom email checking system only type is required:
"type": "custom-type"
headless - flag to activate chrome headless browser default: false
noGpu - flag to activate cpu only mode default: false
type - type of application either ng1 | ng2 | otherWeb
baseUrl - url of tested application
accounts - object to store accounts information. This is bound to userProvider and allows to use advanced email checking options like recipient checking.
Kakunin uses a single .env file to load ENV variables. By default there is only one:
FIXTURES_RELOAD_HOST - allows you to specify host for fixtures reloading. This allows you to use @reloadFixtures tag on scenarios that should restore database to starting state, before the test is running
browserWidth - width of browser window default: 1600
browserheight - height of browser window default: 900
timeout - global timeout for a single step execution in seconds default: 60
maxEmailRepeats - maximum email repeats to catch email used in the email step
intervalEmail - interval for email checking step default: 5 in seconds
elementsVisibilityTimeout - maximum wait timeout for element visibility default: 5 seconds
waitForPageTimeout - maximum wait timeout for page visibility default: 5 seconds
downloadTimeout - maximum wait timeout for file to be downloaded default: 30 seconds
emails - array of paths to store emails related custom code
reports - path to store reports
downloads - path to store downloaded files
data - path to store test related files (for example files to be downloaded)
feature - array of paths to store features
pages - array of paths to store page objects
matchers - array of paths to store custom matchers
generators - array of paths to store custom generators
form_handlers - array of paths to store custom form handlers
step_definitions - array of paths to store custom steps
comparators - array of paths to store custom comparators
dictionaries - array of paths to store custom dictionaries
transformers - array of paths to store custom transformers
regexes - array of paths to store custom regexes
hooks - array of paths to store custom hooks
clearEmailInboxBeforeTests - flag to active clearing email inbox before tests are executed default: false | true for apps with email checking functionality activated
clearCookiesAfterScenario - flag to activate clearing cookies after every scenario default: true
clearLocalStorageAfterScenario - flag to activate clearing local storage after every scenario default: true
for custom email checking system only type is required:
"type": "custom-type"
headless - flag to activate chrome headless browser default: false
noGpu - flag to activate cpu only mode default: false
type - type of application either ng1 | ng2 | otherWeb
baseUrl - url of tested application
accounts - object to store accounts information. This is bound to userProvider and allows to use advanced email checking options like recipient checking.
Kakunin uses a single .env file to load ENV variables. By default there is only one:
FIXTURES_RELOAD_HOST - allows you to specify host for fixtures reloading. This allows you to use @reloadFixtures tag on scenarios that should restore database to starting state, before the test is running
This section explains how to run kakunin tests inside docker, below examples of Dockerfile
and docker-compose.yml files let you build your first docker image and run tests.
Dockerfile:
This file is responsible for building the whole environment for our e2e tests.
It will allow you to run tests on local and CI environments,
by configuring and copying the whole project inside the container.
Just simply place it inside your e2e project root.
Below an example of Dockerfile
Example of Dockerfile:
# Downloading selenium image and setting privileges
FROM selenium/standalone-chrome:3.14.0
USER root
# Setting test directory
WORKDIR /app
# Install openjdk-8-jdk-headless
RUN apt-get update -qqy \
&& apt-get -qqy --no-install-recommends install \
xvfb \
openjdk-8-jdk-headless \
curl \
make \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/*
# Installing node 8 globally and setting paths
RUN set -x \
&& curl -sL https://deb.nodesource.com/setup_8.x | bash - \
&& apt-get install -y \
nodejs \
&& npm install -g npm@latest
RUN PATH=/usr/bin/node:$PATH# Copy tests directory with ignored files from .dockerignore
COPY --chown=seluser:seluser . .
# Removing node_modules in case of existence or lack of .dockerignore and installing from package.json
RUN rm -rf ./node_modules \
&& npm install
# Setting Xvfb
RUN export DISPLAY=:99.0
USER seluser
##docker-compose.yml
Compose is a tool for defining and running multi-container Docker applications, which we use
for running our tests.
Running command: docker-compose up -d will start Dockerfile script, as a result, it builds the container.
Running command: docker-compose build or docker-compose up --build will
rebuild container, if there were made any changes.
Running command: docker-compose run --rm e2e will start running tests inside the container
Composition below allows you to run e2e tests inside the container and configure it locally or
in CI environments.
Example of docker-compose.yml:
e2e:
build: .
working_dir: /app
command: sh -c "Xvfb -ac :99 -screen 0 1280x1024x16 & npm run kakunin"
How to run step by step
Install docker (e.g Docker for Mac),
Create Dockerfile and docker-compose.yml in the root of your e2e project,
Run in command line docker-compose up -d which will start docker and build image
if it's not build
Run in command line docker-compose run --rm e2e to run your tests
This section explains how to run kakunin tests inside docker, below examples of Dockerfile
and docker-compose.yml files let you build your first docker image and run tests.
Dockerfile:
This file is responsible for building the whole environment for our e2e tests.
It will allow you to run tests on local and CI environments,
by configuring and copying the whole project inside the container.
Just simply place it inside your e2e project root.
Below an example of Dockerfile
Example of Dockerfile:
# Downloading selenium image and setting privileges
FROM selenium/standalone-chrome:3.14.0
USER root
# Setting test directory
WORKDIR /app
# Install openjdk-8-jdk-headless
RUN apt-get update -qqy \
&& apt-get -qqy --no-install-recommends install \
xvfb \
openjdk-8-jdk-headless \
curl \
make \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/*
# Installing node 8 globally and setting paths
RUN set -x \
&& curl -sL https://deb.nodesource.com/setup_8.x | bash - \
&& apt-get install -y \
nodejs \
&& npm install -g npm@latest
RUN PATH=/usr/bin/node:$PATH# Copy tests directory with ignored files from .dockerignore
COPY --chown=seluser:seluser . .
# Removing node_modules in case of existence or lack of .dockerignore and installing from package.json
RUN rm -rf ./node_modules \
&& npm install
# Setting Xvfb
RUN export DISPLAY=:99.0
USER seluser
##docker-compose.yml
Compose is a tool for defining and running multi-container Docker applications, which we use
for running our tests.
Running command: docker-compose up -d will start Dockerfile script, as a result, it builds the container.
Running command: docker-compose build or docker-compose up --build will
rebuild container, if there were made any changes.
Running command: docker-compose run --rm e2e will start running tests inside the container
Composition below allows you to run e2e tests inside the container and configure it locally or
in CI environments.
Example of docker-compose.yml:
e2e:
build: .
working_dir: /app
command: sh -c "Xvfb -ac :99 -screen 0 1280x1024x16 & npm run kakunin"
How to run step by step
Install docker (e.g Docker for Mac),
Create Dockerfile and docker-compose.yml in the root of your e2e project,
Run in command line docker-compose up -d which will start docker and build image
if it's not build
Run in command line docker-compose run --rm e2e to run your tests
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.
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.
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');
classMyMatcher{
isSatisfiedBy(prefix, name) {
return prefix === 'm:' && name === 'pending';
}
match(protractorElement, matcherName) {
return protractorElement.getText().then((value) => {
if (value === 'pending') {
returntrue;
}
returnPromise.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.
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 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');
classMyEmailService{
//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:
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.
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.
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');
classMyMatcher{
isSatisfiedBy(prefix, name) {
return prefix === 'm:' && name === 'pending';
}
match(protractorElement, matcherName) {
return protractorElement.getText().then((value) => {
if (value === 'pending') {
returntrue;
}
returnPromise.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.
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 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');
classMyEmailService{
//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:
================================================
FILE: website/build/Kakunin/docs/2.4.0/how-it-works/index.html
================================================
How it works · Kakunin
Kakunin is built with no-js experience in mind. Because of that you're able to test even complicated apps just
by knowing Kakunin (Gherkin) steps and a few good practices.
Concepts
Kakunin uses cucumber-js internally, because of that all tests (or rather scenarios) are using Gherkin as a "programming"
language.
A simple scenario could look like this:
Feature:
Scenario: Display user profile for logged user
Given I am logged in as a "user"When the "dashboard" page is displayed
And I click the "profileButton" element
Then the "myProfile" page is displayed
And the "myName" element is visible
This is how most of Kakunin test scenarios look like.
There are a few concepts to be explained.
Page objects
Page object is a code representation of a page displayed in browser. Kakunin has built-in BasePage page object, that you should extend.
Page object contains information about page url, its elements, locators, but can also have some custom methods if necessary.
A very simple example of Kakunin's Page Object could look like the following:
As you can see a basic Page Object must extend one of the Kakunin's Objects and needs to have url field defined (this.url).
This code should be saved inside pages directory in a file with js extension.
Note that a file name is very important, because we're going to use it as parameter for steps. For example, the following step:
When the "dashboard" page is displayed
expects that there is a file named dashboard.js inside the pages directory.
Every step that we are using is somehow connected to an object called currentPage. This object value is set to a
page object that we expect to be on.
This is done by two kinds of steps:
Then the "dashboard" page is displayed - this one checks if current url in browser is the same as the one inside Page Object and changes a value of the currentPage field
to this page object
When I visit the "dashboard" page - this one goes to the url specified in Page Object and attaches the Page Object to the currentPage field as above
This concept is a very simple and allows you to easily debug the framework. You can be sure that each subsequent step that declared below the ones above will be executed in context of a page object specified in those methods.
For example, having the following code:
Feature:
Scenario: Display user profile for logged user
Given I am logged in as a "user"When the "dashboard" page is displayed
And I click the "profileButton" element
Then the "myProfile" page is displayed
And the "myName" element is visible
The step named And I click the "profileButton" element is executed in context of dashboard Page Object, thus we can assume that profileButton should be defined inside the
pages/dashboard.js file.
At the same time the step And the "myName" element is visible is executed in context of myProfile, so myName should be defined in pages/myProfile.js file.
Elements and locators
The second concept that you have to understand are elements and locators.
Every element that you see on website can be represented as a element inside the page object. This allows us to use it as a parameter for a step, as we did in:
And the "myName" element is visible.
Defining elements is very simple. Let's say we have such page object:
================================================
FILE: website/build/Kakunin/docs/2.4.0/how-it-works.html
================================================
How it works · Kakunin
Kakunin is built with no-js experience in mind. Because of that you're able to test even complicated apps just
by knowing Kakunin (Gherkin) steps and a few good practices.
Concepts
Kakunin uses cucumber-js internally, because of that all tests (or rather scenarios) are using Gherkin as a "programming"
language.
A simple scenario could look like this:
Feature:
Scenario: Display user profile for logged user
Given I am logged in as a "user"When the "dashboard" page is displayed
And I click the "profileButton" element
Then the "myProfile" page is displayed
And the "myName" element is visible
This is how most of Kakunin test scenarios look like.
There are a few concepts to be explained.
Page objects
Page object is a code representation of a page displayed in browser. Kakunin has built-in BasePage page object, that you should extend.
Page object contains information about page url, its elements, locators, but can also have some custom methods if necessary.
A very simple example of Kakunin's Page Object could look like the following:
As you can see a basic Page Object must extend one of the Kakunin's Objects and needs to have url field defined (this.url).
This code should be saved inside pages directory in a file with js extension.
Note that a file name is very important, because we're going to use it as parameter for steps. For example, the following step:
When the "dashboard" page is displayed
expects that there is a file named dashboard.js inside the pages directory.
Every step that we are using is somehow connected to an object called currentPage. This object value is set to a
page object that we expect to be on.
This is done by two kinds of steps:
Then the "dashboard" page is displayed - this one checks if current url in browser is the same as the one inside Page Object and changes a value of the currentPage field
to this page object
When I visit the "dashboard" page - this one goes to the url specified in Page Object and attaches the Page Object to the currentPage field as above
This concept is a very simple and allows you to easily debug the framework. You can be sure that each subsequent step that declared below the ones above will be executed in context of a page object specified in those methods.
For example, having the following code:
Feature:
Scenario: Display user profile for logged user
Given I am logged in as a "user"When the "dashboard" page is displayed
And I click the "profileButton" element
Then the "myProfile" page is displayed
And the "myName" element is visible
The step named And I click the "profileButton" element is executed in context of dashboard Page Object, thus we can assume that profileButton should be defined inside the
pages/dashboard.js file.
At the same time the step And the "myName" element is visible is executed in context of myProfile, so myName should be defined in pages/myProfile.js file.
Elements and locators
The second concept that you have to understand are elements and locators.
Every element that you see on website can be represented as a element inside the page object. This allows us to use it as a parameter for a step, as we did in:
And the "myName" element is visible.
Defining elements is very simple. Let's say we have such page object:
Kakunin is a Protractor extension created by The Software House sp. z o.o. and Takamol Holding. It allows you
to write e2e test scenarios with a help of Gherkin language and JavaScript for all kind of applications - Angular, React and others.
Installation
In order to install Kakunin you have to make sure that you have installed:
Available parameters: baseUrl, type, emailType, emailApiKey, emailInboxId.
You will not be asked about question that you already answered by a command.
After the init process, a project files should be automatically created in your directory.
This is an example of a console output after the init process is completed:
Created file at path /Users/example-user/projects/test/kakunin.conf.js
Created directory at path /Users//TSHProjects/test/reports
Created directory at path /Users//TSHProjects/test/reports/report
Created directory at path /Users//TSHProjects/test/reports/report/features
Created directory at path /Users//TSHProjects/test/reports/performance
Created directory at path /Users//TSHProjects/test/downloads
Created directory at path /Users/example-user/projects/test/data
Created directory at path /Users/example-user/projects/test/features
Created directory at path /Users/example-user/projects/test/pages
Created directory at path /Users/example-user/projects/test/matchers
Created directory at path /Users/example-user/projects/test/generators
Created directory at path /Users/example-user/projects/test/form_handlers
Created directory at path /Users/example-user/projects/test/step_definitions
Created directory at path /Users/example-user/projects/test/comparators
Created directory at path /Users/example-user/projects/test/dictionaries
Created directory at path /Users/example-user/projects/test/regexes
Created directory at path /Users/example-user/projects/test/hooks
Created directory at path /Users/example-user/projects/test/transformers
Created directory at path /Users/example-user/projects/test/emails
Created file at path /Users/example-user/projects/test/downloads/.gitkeep
Created file at path /Users/example-user/projects/test/reports/report/.gitkeep
Created file at path /Users/example-user/projects/test/reports/report/features/.gitkeep
Created file at path /Users/example-user/projects/test/reports/performance/.gitkeep
Created file at path /Users/example-user/projects/test/features/example.feature
Created file at path /Users/example-user/projects/test/pages/page.js
Created file at path /Users/example-user/projects/test/matchers/matcher.js
Created file at path /Users/example-user/projects/test/generators/generator.js
Created file at path /Users/example-user/projects/test/step_definitions/steps.js
Created file at path /Users/example-user/projects/test/regexes/regex.js
Created file at path /Users/example-user/projects/test/hooks/hook.js
And you're set! Now you can run the tests using Kakunin:
npm run kakunin
Commands
Create a new project by answering few simple questions (you can pass additional parameter to enter advanced mode where you can configure all Kakunin options by yourself)
npm run kakunin init [-- --advanced]
Run test scenarios
npm run kakunin
Run only scenarios tagged by @someTag
npm run kakunin -- --tags @someTag
Run only scenarios tagged by @someTag and @otherTag at the same time
npm run kakunin -- --tags "@someTag and @otherTag"
Run only scenarios tagged by @someTag or @otherTag
npm run kakunin -- --tags "@someTag or @otherTag"
Run only scenarios not tagged by @someTag
npm run kakunin -- --tags "not @someTag"
Troubleshooting & Tips
In order to make cucumber steps autosuggestion work properly in JetBrains tools, make sure your project is ECMAScript 6 compatible and you have cucumberjs plugin installed.
Due to non-resolved issue in Jetbrains editors (see here) we'll have to do one more step:
Go to step_definitions directory
cd step_definitions
Paste this code into terminal and restart your IDE:
Matchers allows you to check if a element content matches your expectation.
For example you can check if a value has a specified pattern or if a button is clickable.
Using matcher is very straightforward, for example: f:isClickable.
Matchers can be used in most of the steps related to checking content (with exception of checking form values).
Kakunin comes with a set of built in matchers:
Visibility matcher
f:isVisible - checks if element is visible (must be in viewport and cannot be hidden behind any other element)
Invisibility matcher
f:isNotVisible - checks if element is not visible
Present matcher
f:isPresent - checks if element is in html code (does not have to be visible)
Clickable matcher
f:isClickable - checks if element is clickable
Not clickable matcher
f:isNotClickable - checks if element is not clickable
Attribute matcher
attribute:attributeName:regexName - allows to check if element has attribute with a name specified by attributeName and it has to
have a format passing regexName
For example, if there is an element:
<p custom-attribute="123123">some value</p>
you can check if attribute is an number by running: attribute:custom-attribute:number
Regex matcher
r:regexName - allows you to run a regexName against a text value of element
Regexes have to be specified inside regex directory or be a kakunin built ones:
notEmpty - there must be a value
number - must be a number
You can add your own matchers. In order to do so please read Extending Kakunin section.
Text matcher
t:text you are looking for - allows you to check if an element contains a expected text
Current date matcher
f:currentDate:{format} - allows you to generate current date, {format} is optional, by default DD-MM-YYYY
Matchers allows you to check if a element content matches your expectation.
For example you can check if a value has a specified pattern or if a button is clickable.
Using matcher is very straightforward, for example: f:isClickable.
Matchers can be used in most of the steps related to checking content (with exception of checking form values).
Kakunin comes with a set of built in matchers:
Visibility matcher
f:isVisible - checks if element is visible (must be in viewport and cannot be hidden behind any other element)
Invisibility matcher
f:isNotVisible - checks if element is not visible
Present matcher
f:isPresent - checks if element is in html code (does not have to be visible)
Clickable matcher
f:isClickable - checks if element is clickable
Not clickable matcher
f:isNotClickable - checks if element is not clickable
Attribute matcher
attribute:attributeName:regexName - allows to check if element has attribute with a name specified by attributeName and it has to
have a format passing regexName
For example, if there is an element:
<p custom-attribute="123123">some value</p>
you can check if attribute is an number by running: attribute:custom-attribute:number
Regex matcher
r:regexName - allows you to run a regexName against a text value of element
Regexes have to be specified inside regex directory or be a kakunin built ones:
notEmpty - there must be a value
number - must be a number
You can add your own matchers. In order to do so please read Extending Kakunin section.
Text matcher
t:text you are looking for - allows you to check if an element contains a expected text
Current date matcher
f:currentDate:{format} - allows you to generate current date, {format} is optional, by default DD-MM-YYYY
Performance testing is possible thanks to browsermob-proxy.
It saves all data from network tab (Google Chrome console) which is generated during the test.
There is a possibility to compare TTFB value with a maximum given one.
TTFB (Time to first byte) measures the duration from the client making an HTTP request to the first byte of a response being received by the client's browser.
More details can be found in documentation - Built-in steps section.
What needs to be done?
Get started
Download browsermob-proxy from https://github.com/lightbody/browsermob-proxy
Navigate in terminal to the catalog
Use following command to start the REST API
./browsermob-proxy -port 8887
Configuration
Add browsermob-proxy configuration to kakunin.conf.js
You can use one of the following methods to configure browsermob-proxy:
npm run kakunin init -- --advanced and go through the process
Performance testing is possible thanks to browsermob-proxy.
It saves all data from network tab (Google Chrome console) which is generated during the test.
There is a possibility to compare TTFB value with a maximum given one.
TTFB (Time to first byte) measures the duration from the client making an HTTP request to the first byte of a response being received by the client's browser.
More details can be found in documentation - Built-in steps section.
What needs to be done?
Get started
Download browsermob-proxy from https://github.com/lightbody/browsermob-proxy
Navigate in terminal to the catalog
Use following command to start the REST API
./browsermob-proxy -port 8887
Configuration
Add browsermob-proxy configuration to kakunin.conf.js
You can use one of the following methods to configure browsermob-proxy:
npm run kakunin init -- --advanced and go through the process
As a quick demonstration of the framework let's test the
React variant of TodoMVC project.
Of course other testing other frameworks is possible, you can try it
by yourself!
Install packages
In order to install Kakunin you have to make sure that you have installed:
What kind of application would you like to test? : otherWeb
What is base url? [http://localhost:3000]: http://todomvc.com
What kind of email service would you like to use?: none
And you're set! Now let's write some test!
Test the app
Create a page object that will contain instructions on how to locate elements in the projects.
Create a file pages/main.js:
const { BasePage } = require('kakunin');
classMainPageextendsBasePage{
constructor() {
super();
// define the main url for the pagethis.url = '/examples/react/#/';
// whole form tagthis.addTodoForm = $('.todoapp');
// input fieldthis.todoInput = $('input.new-todo');
// list of currently added todosthis.todos = $$('.todo-list .view');
this.todoLabel = by.css('label');
// first todo item in a listthis.firstTodoItem = this.todos.get(0);
}
}
module.exports = MainPage;
Now that we have prepared the locators, we can start writing our test. Let's test adding new todo item.
Create a file named: features/adding_todo.feature with the following contents:
Feature:
Scenario: Adding todo
Given I visit the "main" page
And I wait for "visibilityOf" of the "addTodoForm" element
And the "addTodoForm" element is visible
When I fill the "addTodoForm" form with:
| todoInput | My new todo |
And I press the "enter" key
Then there are "equal 1""todos" elements
And that's it! All you have to do now is to run the test and watch the magic happens ;)
npm run kakunin
The tests may run quite fast so you might not been able to see that it
really works as expected. To check if the todo items has been really
added to the list, let's use a simple hack - let's pause the running
test right after the todo has been added.
To do that, let's upgrade our Scenario. Update the file:
Feature:
Scenario: Adding todo
Given I visit the "main" page
And I wait for "visibilityOf" of the "addTodoForm" element
And the "addTodoForm" element is visible
When I fill the "addTodoForm" form with:
| todoInput | My new todo |
And I wait for "1" seconds
And I press the "enter" key
When I fill the "addTodoForm" form with:
| todoInput | Another todo item! |
And I wait for "1" seconds
And I press the "enter" key
Then there are "equal 2""todos" elements
Then I wait for "5" seconds
As you can see, we've added 1 new step that waits for a second before
"pressing" the enter key. We've also added a second todo item with
a short pause at the end of the test so you can see the changes.
If you want to see what can we do more with the TodoMVC project, take a look
at the example dir, where you'll find a complete set of test for the project.
As a quick demonstration of the framework let's test the
React variant of TodoMVC project.
Of course other testing other frameworks is possible, you can try it
by yourself!
Install packages
In order to install Kakunin you have to make sure that you have installed:
What kind of application would you like to test? : otherWeb
What is base url? [http://localhost:3000]: http://todomvc.com
What kind of email service would you like to use?: none
And you're set! Now let's write some test!
Test the app
Create a page object that will contain instructions on how to locate elements in the projects.
Create a file pages/main.js:
const { BasePage } = require('kakunin');
classMainPageextendsBasePage{
constructor() {
super();
// define the main url for the pagethis.url = '/examples/react/#/';
// whole form tagthis.addTodoForm = $('.todoapp');
// input fieldthis.todoInput = $('input.new-todo');
// list of currently added todosthis.todos = $$('.todo-list .view');
this.todoLabel = by.css('label');
// first todo item in a listthis.firstTodoItem = this.todos.get(0);
}
}
module.exports = MainPage;
Now that we have prepared the locators, we can start writing our test. Let's test adding new todo item.
Create a file named: features/adding_todo.feature with the following contents:
Feature:
Scenario: Adding todo
Given I visit the "main" page
And I wait for "visibilityOf" of the "addTodoForm" element
And the "addTodoForm" element is visible
When I fill the "addTodoForm" form with:
| todoInput | My new todo |
And I press the "enter" key
Then there are "equal 1""todos" elements
And that's it! All you have to do now is to run the test and watch the magic happens ;)
npm run kakunin
The tests may run quite fast so you might not been able to see that it
really works as expected. To check if the todo items has been really
added to the list, let's use a simple hack - let's pause the running
test right after the todo has been added.
To do that, let's upgrade our Scenario. Update the file:
Feature:
Scenario: Adding todo
Given I visit the "main" page
And I wait for "visibilityOf" of the "addTodoForm" element
And the "addTodoForm" element is visible
When I fill the "addTodoForm" form with:
| todoInput | My new todo |
And I wait for "1" seconds
And I press the "enter" key
When I fill the "addTodoForm" form with:
| todoInput | Another todo item! |
And I wait for "1" seconds
And I press the "enter" key
Then there are "equal 2""todos" elements
Then I wait for "5" seconds
As you can see, we've added 1 new step that waits for a second before
"pressing" the enter key. We've also added a second todo item with
a short pause at the end of the test so you can see the changes.
If you want to see what can we do more with the TodoMVC project, take a look
at the example dir, where you'll find a complete set of test for the project.
Allows to scroll through infinite scroll mechanism.
The :elementName is a name of a selector for loading trigger.
I wait for ":expectedConditionName" of the ":elementName" element
Waits till element :elementName from this.currentPage meets criteria specified by :expectedConditionName.
You can use any of the Protractor's expected condition:
visibilityOf
invisibilityOf
etc.
Read more in Protractor's API documentation.
I wait for the ":elementName" element to disappear
Waits till element :elementName disappears.
I scroll to the ":elementName" element
Scrolls to element :elementName of this.currentPage. The element will be on bottom of the page.
I infinitely scroll to the ":elementName" element
Allows to scroll till :elementName is visible. Useful for infinite scrolling functionality.
I press the ":keyName" key
Performs a key press operation on :keyName key.
I click the ":elementName" element
Performs a click action on element :elementName from `this.currentPage'
The child element must be specified by :elementName and must be available in this.currentPage.
I store the ":elementName" element text as ":variableName" variable
Stores the text from element :elementName of this.currentPage under the :variableName so you can use it later.
I update the ":elementName" element text as ":variableName" variable
Updates the variable :variableName value by value from element :elementName of this.currentPage.
I store the ":elementName" element text matched by ":matchingRegex" as ":variableName" variable
Stores the part of the element :elementName text, that matches the :matchingRegex under the :variableName for later use.
the ":elementName"" element is visible
Checks if element :elementName is visible and clickable
the ":elementName"" element is not visible
Checks if element :elementName is available in HTML DOM but is not visible and clickable
the ":elementName" element is disabled
Checks if element is disabled
I store table ":tableRow" rows as ":variableName" with columns:
Allows to store a row specified columns from a table :tableRow and save it under :variableName as an array of objects.
This step requires a table of columns elements, for example:
I store table "someRow" rows as "someVariable" with columns:
| firstName |
| lastName |
| id |
In order to make it work there must be not only array element this.someRow = $$('.rows') in this.currentPage, but also
element this.firstName = $('.firstName'); and so on.
for this case the :elementName should be specified as $$('table tr') and we can specify column element
this.myColumn = $('td');. This allows us to write:
every "myElement" element should have the same value for element "myColumn"
the element ":elementName" should have an item with values:
Allows to check if any of the child elements of :elementName have a specified content (one matching element is enough). Element should be an array, for example:
Allows to scroll through infinite scroll mechanism.
The :elementName is a name of a selector for loading trigger.
I wait for ":expectedConditionName" of the ":elementName" element
Waits till element :elementName from this.currentPage meets criteria specified by :expectedConditionName.
You can use any of the Protractor's expected condition:
visibilityOf
invisibilityOf
etc.
Read more in Protractor's API documentation.
I wait for the ":elementName" element to disappear
Waits till element :elementName disappears.
I scroll to the ":elementName" element
Scrolls to element :elementName of this.currentPage. The element will be on bottom of the page.
I infinitely scroll to the ":elementName" element
Allows to scroll till :elementName is visible. Useful for infinite scrolling functionality.
I press the ":keyName" key
Performs a key press operation on :keyName key.
I click the ":elementName" element
Performs a click action on element :elementName from `this.currentPage'
The child element must be specified by :elementName and must be available in this.currentPage.
I store the ":elementName" element text as ":variableName" variable
Stores the text from element :elementName of this.currentPage under the :variableName so you can use it later.
I update the ":elementName" element text as ":variableName" variable
Updates the variable :variableName value by value from element :elementName of this.currentPage.
I store the ":elementName" element text matched by ":matchingRegex" as ":variableName" variable
Stores the part of the element :elementName text, that matches the :matchingRegex under the :variableName for later use.
the ":elementName"" element is visible
Checks if element :elementName is visible and clickable
the ":elementName"" element is not visible
Checks if element :elementName is available in HTML DOM but is not visible and clickable
the ":elementName" element is disabled
Checks if element is disabled
I store table ":tableRow" rows as ":variableName" with columns:
Allows to store a row specified columns from a table :tableRow and save it under :variableName as an array of objects.
This step requires a table of columns elements, for example:
I store table "someRow" rows as "someVariable" with columns:
| firstName |
| lastName |
| id |
In order to make it work there must be not only array element this.someRow = $$('.rows') in this.currentPage, but also
element this.firstName = $('.firstName'); and so on.
for this case the :elementName should be specified as $$('table tr') and we can specify column element
this.myColumn = $('td');. This allows us to write:
every "myElement" element should have the same value for element "myColumn"
the element ":elementName" should have an item with values:
Allows to check if any of the child elements of :elementName have a specified content (one matching element is enough). Element should be an array, for example:
Allows to fill the form with the name :formName and values provided as an array of inputs and values. The element with name :formName must be defined inside the
currentPage page object.
Input and values should be provided as an array for example:
I fill the "myForm" form with:
| inputElement | value to be typed into field |
| textareaElement | value to be typed into textarea |
| radioElement | radio value to be selected |
| checkboxElement | checkbox label value to be selected |
By default we support all basic HTML field types (text inputs, checkboxes, radios, selects, files and textareas)
In order to use the default handlers the elements you use as input must follow pattern:
For inputs:
this.element = $('input') - element should point at input you want to fill
For textareas:
this.element = $('textarea') - element should point at textarea you want to fill
For file input:
this.element = $('input') - element should point at input you want to fill and value should a filename of file from data directory
For selects:
this.element = $('select') - element should point at select and value should be an value of expected option
For radios:
this.element = $$('radio[name="name-of-radio"]') - element should be an array of all radio input of given name and value should be an value of radio you wish to select
For checkboxes:
Checkbox should have a html like:
<label>
My checkbox
<inputtype="checkbox"name="some-name"/></label>
this.element = $$('checkbox[name="name-of-radio"]') - element should be an array of all checkboxes of given name and value should be a text from label of checkbox you want to fill
You can use all kind of transformers to as a values for fields.
the ":formName" form is filled with:
The same as I fill the ":formName" form with: but allows to check if a form is filled with a given set of values.
You can use all kind of transformers to as a expected values for fields.
The only difference is for file fields. You cannot check uploaded files just like that, however we prepared a special type of handler
that allow to check for some information related to a specific file.
Let's assume that after upload we display an information with a file name of a uploaded file.
You can use a special handler that requires to set a element with a postfix Uploaded. This will check if a value of that element is the same as you expected.
For example you can write a step like this:
the "myform" form is filled with:
| myFileUploaded | file.txt |
Keep in mind that the element name must end with Uploaded for example:
this.myFileUploaded = $('p.some-file')
the error messages should be displayed:
Allows you to specify the error messages that should be displayed for a specific elements.
This step requires an array of format:
the error messages should be displayed:
| myElement | my error message |
You can use dictionaries in this step as follows:
the error messages should be displayed:
| myElement | d:dictionaryName:dictionaryKey |
Allows to fill the form with the name :formName and values provided as an array of inputs and values. The element with name :formName must be defined inside the
currentPage page object.
Input and values should be provided as an array for example:
I fill the "myForm" form with:
| inputElement | value to be typed into field |
| textareaElement | value to be typed into textarea |
| radioElement | radio value to be selected |
| checkboxElement | checkbox label value to be selected |
By default we support all basic HTML field types (text inputs, checkboxes, radios, selects, files and textareas)
In order to use the default handlers the elements you use as input must follow pattern:
For inputs:
this.element = $('input') - element should point at input you want to fill
For textareas:
this.element = $('textarea') - element should point at textarea you want to fill
For file input:
this.element = $('input') - element should point at input you want to fill and value should a filename of file from data directory
For selects:
this.element = $('select') - element should point at select and value should be an value of expected option
For radios:
this.element = $$('radio[name="name-of-radio"]') - element should be an array of all radio input of given name and value should be an value of radio you wish to select
For checkboxes:
Checkbox should have a html like:
<label>
My checkbox
<inputtype="checkbox"name="some-name"/></label>
this.element = $$('checkbox[name="name-of-radio"]') - element should be an array of all checkboxes of given name and value should be a text from label of checkbox you want to fill
You can use all kind of transformers to as a values for fields.
the ":formName" form is filled with:
The same as I fill the ":formName" form with: but allows to check if a form is filled with a given set of values.
You can use all kind of transformers to as a expected values for fields.
The only difference is for file fields. You cannot check uploaded files just like that, however we prepared a special type of handler
that allow to check for some information related to a specific file.
Let's assume that after upload we display an information with a file name of a uploaded file.
You can use a special handler that requires to set a element with a postfix Uploaded. This will check if a value of that element is the same as you expected.
For example you can write a step like this:
the "myform" form is filled with:
| myFileUploaded | file.txt |
Keep in mind that the element name must end with Uploaded for example:
this.myFileUploaded = $('p.some-file')
the error messages should be displayed:
Allows you to specify the error messages that should be displayed for a specific elements.
This step requires an array of format:
the error messages should be displayed:
| myElement | my error message |
You can use dictionaries in this step as follows:
the error messages should be displayed:
| myElement | d:dictionaryName:dictionaryKey |
Transformers allow you to transform values passed to form steps.
For example a select requires to pass a value /options/1b30f17e-e445-4d28-a30c-dedad95829ab. This one is quite unreadable, but with the help of transformers you are
able to write it like this: d:options:someOptionName.
In real-life example it will look similar to:
I fill the "myForm" form with:
| inputElement | d:someDictionary:someKey |
| textareaElement | g:someGenerator |
| radioElement | v:someVariableName |
| checkboxElement | standard value |
There are 3 types of built-in transformers:
Dictionaries
Dictionaries allows you to transform a value A to value B using a simple key->value transformation.
You can run a dictionary transformer by providing dictionary prefix d:, specifying the dictionary name and key that should be used as a value provider. For example:
d:myDictionaryName:myDictionaryKey
this example assumes that there is a dictionary that supports name myDictionaryName and it has myDictionarKey key.
You can read about dictionaries in Extending Kakunin section.
Generators
Generators allows you to generate a value by using a specified generator.
This can be done by: g:generatorName.
If a generator supports parameters then you can specify them by:
g:generatorName:param1:param2:...:paramN
You can read more about generators in Extending Kakunin section.
Variable store
Variable store allows you to fill the form with a value that was saved in previous steps of current running scenario.
This can be done by:
v:variableName
You can read more about variable store in Extending Kakunin section
Transformers allow you to transform values passed to form steps.
For example a select requires to pass a value /options/1b30f17e-e445-4d28-a30c-dedad95829ab. This one is quite unreadable, but with the help of transformers you are
able to write it like this: d:options:someOptionName.
In real-life example it will look similar to:
I fill the "myForm" form with:
| inputElement | d:someDictionary:someKey |
| textareaElement | g:someGenerator |
| radioElement | v:someVariableName |
| checkboxElement | standard value |
There are 3 types of built-in transformers:
Dictionaries
Dictionaries allows you to transform a value A to value B using a simple key->value transformation.
You can run a dictionary transformer by providing dictionary prefix d:, specifying the dictionary name and key that should be used as a value provider. For example:
d:myDictionaryName:myDictionaryKey
this example assumes that there is a dictionary that supports name myDictionaryName and it has myDictionarKey key.
You can read about dictionaries in Extending Kakunin section.
Generators
Generators allows you to generate a value by using a specified generator.
This can be done by: g:generatorName.
If a generator supports parameters then you can specify them by:
g:generatorName:param1:param2:...:paramN
You can read more about generators in Extending Kakunin section.
Variable store
Variable store allows you to fill the form with a value that was saved in previous steps of current running scenario.
This can be done by:
v:variableName
You can read more about variable store in Extending Kakunin section
browserWidth - width of browser window default: 1600
browserheight - height of browser window default: 900
timeout - global timeout for a single step execution in seconds default: 60
maxEmailRepeats - maximum email repeats to catch email used in the email step
intervalEmail - interval for email checking step default: 5 in seconds
elementsVisibilityTimeout - maximum wait timeout for element visibility default: 5 seconds
waitForPageTimeout - maximum wait timeout for page visibility default: 5 seconds
downloadTimeout - maximum wait timeout for file to be downloaded default: 30 seconds
emails - array of paths to store emails related custom code
reports - path to store reports
downloads - path to store downloaded files
data - path to store test related files (for example files to be downloaded)
feature - array of paths to store features
pages - array of paths to store page objects
matchers - array of paths to store custom matchers
generators - array of paths to store custom generators
form_handlers - array of paths to store custom form handlers
step_definitions - array of paths to store custom steps
comparators - array of paths to store custom comparators
dictionaries - array of paths to store custom dictionaries
transformers - array of paths to store custom transformers
regexes - array of paths to store custom regexes
hooks - array of paths to store custom hooks
clearEmailInboxBeforeTests - flag to active clearing email inbox before tests are executed default: false | true for apps with email checking functionality activated
clearCookiesAfterScenario - flag to activate clearing cookies after every scenario default: true
clearLocalStorageAfterScenario - flag to activate clearing local storage after every scenario default: true
for custom email checking system only type is required:
"type": "custom-type"
headless - flag to activate chrome headless browser default: false
noGpu - flag to activate cpu only mode default: false
type - type of application either ng1 | ng2 | otherWeb
baseUrl - url of tested application
accounts - object to store accounts information. This is bound to userProvider and allows to use advanced email checking options like recipient checking.
Kakunin uses a single .env file to load ENV variables. By default there is only one:
FIXTURES_RELOAD_HOST - allows you to specify host for fixtures reloading. This allows you to use @reloadFixtures tag on scenarios that should restore database to starting state, before the test is running
browserWidth - width of browser window default: 1600
browserheight - height of browser window default: 900
timeout - global timeout for a single step execution in seconds default: 60
maxEmailRepeats - maximum email repeats to catch email used in the email step
intervalEmail - interval for email checking step default: 5 in seconds
elementsVisibilityTimeout - maximum wait timeout for element visibility default: 5 seconds
waitForPageTimeout - maximum wait timeout for page visibility default: 5 seconds
downloadTimeout - maximum wait timeout for file to be downloaded default: 30 seconds
emails - array of paths to store emails related custom code
reports - path to store reports
downloads - path to store downloaded files
data - path to store test related files (for example files to be downloaded)
feature - array of paths to store features
pages - array of paths to store page objects
matchers - array of paths to store custom matchers
generators - array of paths to store custom generators
form_handlers - array of paths to store custom form handlers
step_definitions - array of paths to store custom steps
comparators - array of paths to store custom comparators
dictionaries - array of paths to store custom dictionaries
transformers - array of paths to store custom transformers
regexes - array of paths to store custom regexes
hooks - array of paths to store custom hooks
clearEmailInboxBeforeTests - flag to active clearing email inbox before tests are executed default: false | true for apps with email checking functionality activated
clearCookiesAfterScenario - flag to activate clearing cookies after every scenario default: true
clearLocalStorageAfterScenario - flag to activate clearing local storage after every scenario default: true
for custom email checking system only type is required:
"type": "custom-type"
headless - flag to activate chrome headless browser default: false
noGpu - flag to activate cpu only mode default: false
type - type of application either ng1 | ng2 | otherWeb
baseUrl - url of tested application
accounts - object to store accounts information. This is bound to userProvider and allows to use advanced email checking options like recipient checking.
Kakunin uses a single .env file to load ENV variables. By default there is only one:
FIXTURES_RELOAD_HOST - allows you to specify host for fixtures reloading. This allows you to use @reloadFixtures tag on scenarios that should restore database to starting state, before the test is running
This section explains how to run kakunin tests inside docker, below examples of Dockerfile
and docker-compose.yml files let you build your first docker image and run tests.
Dockerfile:
This file is responsible for building the whole environment for our e2e tests.
It will allow you to run tests on local and CI environments,
by configuring and copying the whole project inside the container.
Just simply place it inside your e2e project root.
Below an example of Dockerfile
Example of Dockerfile:
# Downloading selenium image and setting privileges
FROM selenium/standalone-chrome:3.14.0
USER root
# Setting test directory
WORKDIR /app
# Install openjdk-8-jdk-headless
RUN apt-get update -qqy \
&& apt-get -qqy --no-install-recommends install \
xvfb \
openjdk-8-jdk-headless \
curl \
make \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/*
# Installing node 8 globally and setting paths
RUN set -x \
&& curl -sL https://deb.nodesource.com/setup_8.x | bash - \
&& apt-get install -y \
nodejs \
&& npm install -g npm@latest
RUN PATH=/usr/bin/node:$PATH# Copy tests directory with ignored files from .dockerignore
COPY --chown=seluser:seluser . .
# Removing node_modules in case of existence or lack of .dockerignore and installing from package.json
RUN rm -rf ./node_modules \
&& npm install
# Setting Xvfb
RUN export DISPLAY=:99.0
USER seluser
##docker-compose.yml
Compose is a tool for defining and running multi-container Docker applications, which we use
for running our tests.
Running command: docker-compose up -d will start Dockerfile script, as a result, it builds the container.
Running command: docker-compose build or docker-compose up --build will
rebuild container, if there were made any changes.
Running command: docker-compose run --rm e2e will start running tests inside the container
Composition below allows you to run e2e tests inside the container and configure it locally or
in CI environments.
Example of docker-compose.yml:
e2e:
build: .
working_dir: /app
command: sh -c "Xvfb -ac :99 -screen 0 1280x1024x16 & npm run kakunin"
How to run step by step
Install docker (e.g Docker for Mac),
Create Dockerfile and docker-compose.yml in the root of your e2e project,
Run in command line docker-compose up -d which will start docker and build image
if it's not build
Run in command line docker-compose run --rm e2e to run your tests
This section explains how to run kakunin tests inside docker, below examples of Dockerfile
and docker-compose.yml files let you build your first docker image and run tests.
Dockerfile:
This file is responsible for building the whole environment for our e2e tests.
It will allow you to run tests on local and CI environments,
by configuring and copying the whole project inside the container.
Just simply place it inside your e2e project root.
Below an example of Dockerfile
Example of Dockerfile:
# Downloading selenium image and setting privileges
FROM selenium/standalone-chrome:3.14.0
USER root
# Setting test directory
WORKDIR /app
# Install openjdk-8-jdk-headless
RUN apt-get update -qqy \
&& apt-get -qqy --no-install-recommends install \
xvfb \
openjdk-8-jdk-headless \
curl \
make \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/*
# Installing node 8 globally and setting paths
RUN set -x \
&& curl -sL https://deb.nodesource.com/setup_8.x | bash - \
&& apt-get install -y \
nodejs \
&& npm install -g npm@latest
RUN PATH=/usr/bin/node:$PATH# Copy tests directory with ignored files from .dockerignore
COPY --chown=seluser:seluser . .
# Removing node_modules in case of existence or lack of .dockerignore and installing from package.json
RUN rm -rf ./node_modules \
&& npm install
# Setting Xvfb
RUN export DISPLAY=:99.0
USER seluser
##docker-compose.yml
Compose is a tool for defining and running multi-container Docker applications, which we use
for running our tests.
Running command: docker-compose up -d will start Dockerfile script, as a result, it builds the container.
Running command: docker-compose build or docker-compose up --build will
rebuild container, if there were made any changes.
Running command: docker-compose run --rm e2e will start running tests inside the container
Composition below allows you to run e2e tests inside the container and configure it locally or
in CI environments.
Example of docker-compose.yml:
e2e:
build: .
working_dir: /app
command: sh -c "Xvfb -ac :99 -screen 0 1280x1024x16 & npm run kakunin"
How to run step by step
Install docker (e.g Docker for Mac),
Create Dockerfile and docker-compose.yml in the root of your e2e project,
Run in command line docker-compose up -d which will start docker and build image
if it's not build
Run in command line docker-compose run --rm e2e to run your tests
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.
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.
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');
classMyMatcher{
isSatisfiedBy(prefix, name) {
return prefix === 'm:' && name === 'pending';
}
match(protractorElement, matcherName) {
return protractorElement.getText().then((value) => {
if (value === 'pending') {
returntrue;
}
returnPromise.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.
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 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');
classMyEmailService{
//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:
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.
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.
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');
classMyMatcher{
isSatisfiedBy(prefix, name) {
return prefix === 'm:' && name === 'pending';
}
match(protractorElement, matcherName) {
return protractorElement.getText().then((value) => {
if (value === 'pending') {
returntrue;
}
returnPromise.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.
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 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');
classMyEmailService{
//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:
================================================
FILE: website/build/Kakunin/docs/how-it-works/index.html
================================================
How it works · Kakunin
Kakunin is built with no-js experience in mind. Because of that you're able to test even complicated apps just
by knowing Kakunin (Gherkin) steps and a few good practices.
Concepts
Kakunin uses cucumber-js internally, because of that all tests (or rather scenarios) are using Gherkin as a "programming"
language.
A simple scenario could look like this:
Feature:
Scenario: Display user profile for logged user
Given I am logged in as a "user"When the "dashboard" page is displayed
And I click the "profileButton" element
Then the "myProfile" page is displayed
And the "myName" element is visible
This is how most of Kakunin test scenarios look like.
There are a few concepts to be explained.
Page objects
Page object is a code representation of a page displayed in browser. Kakunin has built-in BasePage page object, that you should extend.
Page object contains information about page url, its elements, locators, but can also have some custom methods if necessary.
A very simple example of Kakunin's Page Object could look like the following:
As you can see a basic Page Object must extend one of the Kakunin's Objects and needs to have url field defined (this.url).
This code should be saved inside pages directory in a file with js extension.
Note that a file name is very important, because we're going to use it as parameter for steps. For example, the following step:
When the "dashboard" page is displayed
expects that there is a file named dashboard.js inside the pages directory.
Every step that we are using is somehow connected to an object called currentPage. This object value is set to a
page object that we expect to be on.
This is done by two kinds of steps:
Then the "dashboard" page is displayed - this one checks if current url in browser is the same as the one inside Page Object and changes a value of the currentPage field
to this page object
When I visit the "dashboard" page - this one goes to the url specified in Page Object and attaches the Page Object to the currentPage field as above
This concept is a very simple and allows you to easily debug the framework. You can be sure that each subsequent step that declared below the ones above will be executed in context of a page object specified in those methods.
For example, having the following code:
Feature:
Scenario: Display user profile for logged user
Given I am logged in as a "user"When the "dashboard" page is displayed
And I click the "profileButton" element
Then the "myProfile" page is displayed
And the "myName" element is visible
The step named And I click the "profileButton" element is executed in context of dashboard Page Object, thus we can assume that profileButton should be defined inside the
pages/dashboard.js file.
At the same time the step And the "myName" element is visible is executed in context of myProfile, so myName should be defined in pages/myProfile.js file.
Elements and locators
The second concept that you have to understand are elements and locators.
Every element that you see on website can be represented as a element inside the page object. This allows us to use it as a parameter for a step, as we did in:
And the "myName" element is visible.
Defining elements is very simple. Let's say we have such page object:
================================================
FILE: website/build/Kakunin/docs/how-it-works.html
================================================
How it works · Kakunin
Kakunin is built with no-js experience in mind. Because of that you're able to test even complicated apps just
by knowing Kakunin (Gherkin) steps and a few good practices.
Concepts
Kakunin uses cucumber-js internally, because of that all tests (or rather scenarios) are using Gherkin as a "programming"
language.
A simple scenario could look like this:
Feature:
Scenario: Display user profile for logged user
Given I am logged in as a "user"When the "dashboard" page is displayed
And I click the "profileButton" element
Then the "myProfile" page is displayed
And the "myName" element is visible
This is how most of Kakunin test scenarios look like.
There are a few concepts to be explained.
Page objects
Page object is a code representation of a page displayed in browser. Kakunin has built-in BasePage page object, that you should extend.
Page object contains information about page url, its elements, locators, but can also have some custom methods if necessary.
A very simple example of Kakunin's Page Object could look like the following:
As you can see a basic Page Object must extend one of the Kakunin's Objects and needs to have url field defined (this.url).
This code should be saved inside pages directory in a file with js extension.
Note that a file name is very important, because we're going to use it as parameter for steps. For example, the following step:
When the "dashboard" page is displayed
expects that there is a file named dashboard.js inside the pages directory.
Every step that we are using is somehow connected to an object called currentPage. This object value is set to a
page object that we expect to be on.
This is done by two kinds of steps:
Then the "dashboard" page is displayed - this one checks if current url in browser is the same as the one inside Page Object and changes a value of the currentPage field
to this page object
When I visit the "dashboard" page - this one goes to the url specified in Page Object and attaches the Page Object to the currentPage field as above
This concept is a very simple and allows you to easily debug the framework. You can be sure that each subsequent step that declared below the ones above will be executed in context of a page object specified in those methods.
For example, having the following code:
Feature:
Scenario: Display user profile for logged user
Given I am logged in as a "user"When the "dashboard" page is displayed
And I click the "profileButton" element
Then the "myProfile" page is displayed
And the "myName" element is visible
The step named And I click the "profileButton" element is executed in context of dashboard Page Object, thus we can assume that profileButton should be defined inside the
pages/dashboard.js file.
At the same time the step And the "myName" element is visible is executed in context of myProfile, so myName should be defined in pages/myProfile.js file.
Elements and locators
The second concept that you have to understand are elements and locators.
Every element that you see on website can be represented as a element inside the page object. This allows us to use it as a parameter for a step, as we did in:
And the "myName" element is visible.
Defining elements is very simple. Let's say we have such page object:
Kakunin is a Protractor extension created by The Software House sp. z o.o. and Takamol Holding. It allows you
to write e2e test scenarios with a help of Gherkin language and JavaScript for all kind of applications - Angular, React and others.
Installation
In order to install Kakunin you have to make sure that you have installed:
Available parameters: baseUrl, type, emailType, emailApiKey, emailInboxId.
You will not be asked about question that you already answered by a command.
After the init process, a project files should be automatically created in your directory.
This is an example of a console output after the init process is completed:
Created file at path /Users/example-user/projects/test/kakunin.conf.js
Created directory at path /Users//TSHProjects/test/reports
Created directory at path /Users//TSHProjects/test/reports/report
Created directory at path /Users//TSHProjects/test/reports/report/features
Created directory at path /Users//TSHProjects/test/reports/performance
Created directory at path /Users//TSHProjects/test/downloads
Created directory at path /Users/example-user/projects/test/data
Created directory at path /Users/example-user/projects/test/features
Created directory at path /Users/example-user/projects/test/pages
Created directory at path /Users/example-user/projects/test/matchers
Created directory at path /Users/example-user/projects/test/generators
Created directory at path /Users/example-user/projects/test/form_handlers
Created directory at path /Users/example-user/projects/test/step_definitions
Created directory at path /Users/example-user/projects/test/comparators
Created directory at path /Users/example-user/projects/test/dictionaries
Created directory at path /Users/example-user/projects/test/regexes
Created directory at path /Users/example-user/projects/test/hooks
Created directory at path /Users/example-user/projects/test/transformers
Created directory at path /Users/example-user/projects/test/emails
Created file at path /Users/example-user/projects/test/downloads/.gitkeep
Created file at path /Users/example-user/projects/test/reports/report/.gitkeep
Created file at path /Users/example-user/projects/test/reports/report/features/.gitkeep
Created file at path /Users/example-user/projects/test/reports/performance/.gitkeep
Created file at path /Users/example-user/projects/test/features/example.feature
Created file at path /Users/example-user/projects/test/pages/page.js
Created file at path /Users/example-user/projects/test/matchers/matcher.js
Created file at path /Users/example-user/projects/test/generators/generator.js
Created file at path /Users/example-user/projects/test/step_definitions/steps.js
Created file at path /Users/example-user/projects/test/regexes/regex.js
Created file at path /Users/example-user/projects/test/hooks/hook.js
And you're set! Now you can run the tests using Kakunin:
npm run kakunin
Commands
Create a new project by answering few simple questions (you can pass additional parameter to enter advanced mode where you can configure all Kakunin options by yourself)
npm run kakunin init [-- --advanced]
Run test scenarios
npm run kakunin
Run only scenarios tagged by @someTag
npm run kakunin -- --tags @someTag
Run only scenarios tagged by @someTag and @otherTag at the same time
npm run kakunin -- --tags "@someTag and @otherTag"
Run only scenarios tagged by @someTag or @otherTag
npm run kakunin -- --tags "@someTag or @otherTag"
Run only scenarios not tagged by @someTag
npm run kakunin -- --tags "not @someTag"
Troubleshooting & Tips
In order to make cucumber steps autosuggestion work properly in JetBrains tools, make sure your project is ECMAScript 6 compatible and you have cucumberjs plugin installed.
Due to non-resolved issue in Jetbrains editors (see here) we'll have to do one more step:
Go to step_definitions directory
cd step_definitions
Paste this code into terminal and restart your IDE:
Matchers allows you to check if a element content matches your expectation.
For example you can check if a value has a specified pattern or if a button is clickable.
Using matcher is very straightforward, for example: f:isClickable.
Matchers can be used in most of the steps related to checking content (with exception of checking form values).
Kakunin comes with a set of built in matchers:
Visibility matcher
f:isVisible - checks if element is visible (must be in viewport and cannot be hidden behind any other element)
Invisibility matcher
f:isNotVisible - checks if element is not visible
Present matcher
f:isPresent - checks if element is in html code (does not have to be visible)
Clickable matcher
f:isClickable - checks if element is clickable
Not clickable matcher
f:isNotClickable - checks if element is not clickable
Attribute matcher
attribute:attributeName:regexName - allows to check if element has attribute with a name specified by attributeName and it has to
have a format passing regexName
For example, if there is an element:
<p custom-attribute="123123">some value</p>
you can check if attribute is an number by running: attribute:custom-attribute:number
Regex matcher
r:regexName - allows you to run a regexName against a text value of element
Regexes have to be specified inside regex directory or be a kakunin built ones:
notEmpty - there must be a value
number - must be a number
You can add your own matchers. In order to do so please read Extending Kakunin section.
Text matcher
t:text you are looking for - allows you to check if an element contains a expected text
Current date matcher
f:currentDate:{format} - allows you to generate current date, {format} is optional, by default DD-MM-YYYY
Matchers allows you to check if a element content matches your expectation.
For example you can check if a value has a specified pattern or if a button is clickable.
Using matcher is very straightforward, for example: f:isClickable.
Matchers can be used in most of the steps related to checking content (with exception of checking form values).
Kakunin comes with a set of built in matchers:
Visibility matcher
f:isVisible - checks if element is visible (must be in viewport and cannot be hidden behind any other element)
Invisibility matcher
f:isNotVisible - checks if element is not visible
Present matcher
f:isPresent - checks if element is in html code (does not have to be visible)
Clickable matcher
f:isClickable - checks if element is clickable
Not clickable matcher
f:isNotClickable - checks if element is not clickable
Attribute matcher
attribute:attributeName:regexName - allows to check if element has attribute with a name specified by attributeName and it has to
have a format passing regexName
For example, if there is an element:
<p custom-attribute="123123">some value</p>
you can check if attribute is an number by running: attribute:custom-attribute:number
Regex matcher
r:regexName - allows you to run a regexName against a text value of element
Regexes have to be specified inside regex directory or be a kakunin built ones:
notEmpty - there must be a value
number - must be a number
You can add your own matchers. In order to do so please read Extending Kakunin section.
Text matcher
t:text you are looking for - allows you to check if an element contains a expected text
Current date matcher
f:currentDate:{format} - allows you to generate current date, {format} is optional, by default DD-MM-YYYY
Runs the application with the capabilities set in kakunin.conf.js file through the command line:
npm run kakunin -- --browserstack
Keep in mind that all capabilities that you set via CLI will be ignored!
For example, npm run kakunin -- --safari --browserstack will ignore the safari part.
Only --browserstack matters in case of running tests in Browserstack.
Example kakunin.conf.js configuration file
This is an example configuration for Internet Explorer 8 on Windows 7.
Runs the application with the capabilities set in kakunin.conf.js file through the command line:
npm run kakunin -- --browserstack
Keep in mind that all capabilities that you set via CLI will be ignored!
For example, npm run kakunin -- --safari --browserstack will ignore the safari part.
Only --browserstack matters in case of running tests in Browserstack.
Example kakunin.conf.js configuration file
This is an example configuration for Internet Explorer 8 on Windows 7.
browserWidth - width of browser window default: 1600
browserheight - height of browser window default: 900
timeout - global timeout for a single step execution in seconds default: 60
maxEmailRepeats - maximum email repeats to catch email used in the email step
intervalEmail - interval for email checking step default: 5 in seconds
elementsVisibilityTimeout - maximum wait timeout for element visibility default: 5 seconds
waitForPageTimeout - maximum wait timeout for page visibility default: 5 seconds
downloadTimeout - maximum wait timeout for file to be downloaded default: 30 seconds
emails - array of paths to store emails related custom code
reports - path to store reports
downloads - path to store downloaded files
data - path to store test related files (for example files to be downloaded)
feature - array of paths to store features
pages - array of paths to store page objects
matchers - array of paths to store custom matchers
generators - array of paths to store custom generators
form_handlers - array of paths to store custom form handlers
step_definitions - array of paths to store custom steps
comparators - array of paths to store custom comparators
dictionaries - array of paths to store custom dictionaries
transformers - array of paths to store custom transformers
regexes - array of paths to store custom regexes
hooks - array of paths to store custom hooks
clearEmailInboxBeforeTests - flag to active clearing email inbox before tests are executed default: false | true for apps with email checking functionality activated
clearCookiesAfterScenario - flag to activate clearing cookies after every scenario default: true
clearLocalStorageAfterScenario - flag to activate clearing local storage after every scenario default: true
for custom email checking system only type is required:
"type": "custom-type"
headless - flag to activate chrome headless browser default: false. Keep in mind that CLI command -- --headless=false/true has higher priority than the config file.
noGpu - flag to activate cpu only mode default: false
type - type of application either ng1 | ng2 | otherWeb
baseUrl - url of tested application
accounts - object to store accounts information. This is bound to userProvider and allows to use advanced email checking options like recipient checking.
Kakunin uses a single .env file to load ENV variables. By default there is only one:
FIXTURES_RELOAD_HOST - allows you to specify host for fixtures reloading. This allows you to use @reloadFixtures tag on scenarios that should restore database to starting state, before the test is running
browserWidth - width of browser window default: 1600
browserheight - height of browser window default: 900
timeout - global timeout for a single step execution in seconds default: 60
maxEmailRepeats - maximum email repeats to catch email used in the email step
intervalEmail - interval for email checking step default: 5 in seconds
elementsVisibilityTimeout - maximum wait timeout for element visibility default: 5 seconds
waitForPageTimeout - maximum wait timeout for page visibility default: 5 seconds
downloadTimeout - maximum wait timeout for file to be downloaded default: 30 seconds
emails - array of paths to store emails related custom code
reports - path to store reports
downloads - path to store downloaded files
data - path to store test related files (for example files to be downloaded)
feature - array of paths to store features
pages - array of paths to store page objects
matchers - array of paths to store custom matchers
generators - array of paths to store custom generators
form_handlers - array of paths to store custom form handlers
step_definitions - array of paths to store custom steps
comparators - array of paths to store custom comparators
dictionaries - array of paths to store custom dictionaries
transformers - array of paths to store custom transformers
regexes - array of paths to store custom regexes
hooks - array of paths to store custom hooks
clearEmailInboxBeforeTests - flag to active clearing email inbox before tests are executed default: false | true for apps with email checking functionality activated
clearCookiesAfterScenario - flag to activate clearing cookies after every scenario default: true
clearLocalStorageAfterScenario - flag to activate clearing local storage after every scenario default: true
for custom email checking system only type is required:
"type": "custom-type"
headless - flag to activate chrome headless browser default: false. Keep in mind that CLI command -- --headless=false/true has higher priority than the config file.
noGpu - flag to activate cpu only mode default: false
type - type of application either ng1 | ng2 | otherWeb
baseUrl - url of tested application
accounts - object to store accounts information. This is bound to userProvider and allows to use advanced email checking options like recipient checking.
Kakunin uses a single .env file to load ENV variables. By default there is only one:
FIXTURES_RELOAD_HOST - allows you to specify host for fixtures reloading. This allows you to use @reloadFixtures tag on scenarios that should restore database to starting state, before the test is running
This section explains how to run kakunin tests inside docker, below examples of Dockerfile
and docker-compose.yml files let you build your first docker image and run tests.
Dockerfile:
This file is responsible for building the whole environment for our e2e tests.
It will allow you to run tests on local and CI environments,
by configuring and copying the whole project inside the container.
Just simply place it inside your e2e project root.
Below an example of Dockerfile
Example of Dockerfile:
# Downloading selenium image and setting privileges
FROM selenium/standalone-chrome:3.14.0
USER root
# Setting test directory
WORKDIR /app
# Install openjdk-8-jdk-headless
RUN apt-get update -qqy \
&& apt-get -qqy --no-install-recommends install \
xvfb \
openjdk-8-jdk-headless \
curl \
make \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/*
# Installing node 8 globally and setting paths
RUN set -x \
&& curl -sL https://deb.nodesource.com/setup_8.x | bash - \
&& apt-get install -y \
nodejs \
&& npm install -g npm@latest
RUN PATH=/usr/bin/node:$PATH# Copy tests directory with ignored files from .dockerignore
COPY --chown=seluser:seluser . .
# Removing node_modules in case of existence or lack of .dockerignore and installing from package.json
RUN rm -rf ./node_modules \
&& npm install
# Setting Xvfb
RUN export DISPLAY=:99.0
USER seluser
##docker-compose.yml
Compose is a tool for defining and running multi-container Docker applications, which we use
for running our tests.
Running command: docker-compose up -d will start Dockerfile script, as a result, it builds the container.
Running command: docker-compose build or docker-compose up --build will
rebuild container, if there were made any changes.
Running command: docker-compose run --rm e2e will start running tests inside the container
Composition below allows you to run e2e tests inside the container and configure it locally or
in CI environments.
Example of docker-compose.yml:
e2e:
build: .
working_dir: /app
command: sh -c "Xvfb -ac :99 -screen 0 1280x1024x16 & npm run kakunin"
How to run step by step
Install docker (e.g Docker for Mac),
Create Dockerfile and docker-compose.yml in the root of your e2e project,
Run in command line docker-compose up -d which will start docker and build image
if it's not build
Run in command line docker-compose run --rm e2e to run your tests
This section explains how to run kakunin tests inside docker, below examples of Dockerfile
and docker-compose.yml files let you build your first docker image and run tests.
Dockerfile:
This file is responsible for building the whole environment for our e2e tests.
It will allow you to run tests on local and CI environments,
by configuring and copying the whole project inside the container.
Just simply place it inside your e2e project root.
Below an example of Dockerfile
Example of Dockerfile:
# Downloading selenium image and setting privileges
FROM selenium/standalone-chrome:3.14.0
USER root
# Setting test directory
WORKDIR /app
# Install openjdk-8-jdk-headless
RUN apt-get update -qqy \
&& apt-get -qqy --no-install-recommends install \
xvfb \
openjdk-8-jdk-headless \
curl \
make \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/*
# Installing node 8 globally and setting paths
RUN set -x \
&& curl -sL https://deb.nodesource.com/setup_8.x | bash - \
&& apt-get install -y \
nodejs \
&& npm install -g npm@latest
RUN PATH=/usr/bin/node:$PATH# Copy tests directory with ignored files from .dockerignore
COPY --chown=seluser:seluser . .
# Removing node_modules in case of existence or lack of .dockerignore and installing from package.json
RUN rm -rf ./node_modules \
&& npm install
# Setting Xvfb
RUN export DISPLAY=:99.0
USER seluser
##docker-compose.yml
Compose is a tool for defining and running multi-container Docker applications, which we use
for running our tests.
Running command: docker-compose up -d will start Dockerfile script, as a result, it builds the container.
Running command: docker-compose build or docker-compose up --build will
rebuild container, if there were made any changes.
Running command: docker-compose run --rm e2e will start running tests inside the container
Composition below allows you to run e2e tests inside the container and configure it locally or
in CI environments.
Example of docker-compose.yml:
e2e:
build: .
working_dir: /app
command: sh -c "Xvfb -ac :99 -screen 0 1280x1024x16 & npm run kakunin"
How to run step by step
Install docker (e.g Docker for Mac),
Create Dockerfile and docker-compose.yml in the root of your e2e project,
Run in command line docker-compose up -d which will start docker and build image
if it's not build
Run in command line docker-compose run --rm e2e to run your tests
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.
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.
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');
classMyMatcher{
isSatisfiedBy(prefix, name) {
return prefix === 'm:' && name === 'pending';
}
match(protractorElement, matcherName) {
return protractorElement.getText().then((value) => {
if (value === 'pending') {
returntrue;
}
returnPromise.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.
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 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');
classMyEmailService{
//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:
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.
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.
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');
classMyMatcher{
isSatisfiedBy(prefix, name) {
return prefix === 'm:' && name === 'pending';
}
match(protractorElement, matcherName) {
return protractorElement.getText().then((value) => {
if (value === 'pending') {
returntrue;
}
returnPromise.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.
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 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');
classMyEmailService{
//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:
This section explains how to add priority hooks for kakunin tests based on the built-in adapter.
Hooks allow you to perform actions before and after scenario.
For example, it lets you clear all files from the downloads folder.
How to add hook with priority:
initializeHook()
this method is provided to execute hook logic.
getPriority()
this method returns numeric value and then it's sorted in order.
Remember that new Hook must contain these 2 methods to fulfill interface.
After your hook is ready to use method hookHandlers.addHook(Hook object)
Example of example.hook.js:
const { hookHandlers, Before } = require('kakunin');
class ExampleHook {
initializeHook() {
Before(() => {
console.log('This hook is going to be 5th in order');
});
}
getPriority() {
return5;
}
}
hookHandlers.addHook(new ExampleHook());
Build in hooks:
Clear download:
Clears download folder before or/and after scenario. To use them add @downloadClearBefore or @downloadClearAfter tag.
Its priority is set to 1.
Reload fixtures:
allows you to reload fixtures before the desired scenario, via URL provided in .env file.
Its priority is set to 2.
Take a screenshot and clear variables:
These hooks are used by kakunin mechanism to clear variable store and take screenshots after scenarios.
Their priority is set to 1.
This section explains how to add priority hooks for kakunin tests based on the built-in adapter.
Hooks allow you to perform actions before and after scenario.
For example, it lets you clear all files from the downloads folder.
How to add hook with priority:
initializeHook()
this method is provided to execute hook logic.
getPriority()
this method returns numeric value and then it's sorted in order.
Remember that new Hook must contain these 2 methods to fulfill interface.
After your hook is ready to use method hookHandlers.addHook(Hook object)
Example of example.hook.js:
const { hookHandlers, Before } = require('kakunin');
class ExampleHook {
initializeHook() {
Before(() => {
console.log('This hook is going to be 5th in order');
});
}
getPriority() {
return5;
}
}
hookHandlers.addHook(new ExampleHook());
Build in hooks:
Clear download:
Clears download folder before or/and after scenario. To use them add @downloadClearBefore or @downloadClearAfter tag.
Its priority is set to 1.
Reload fixtures:
allows you to reload fixtures before the desired scenario, via URL provided in .env file.
Its priority is set to 2.
Take a screenshot and clear variables:
These hooks are used by kakunin mechanism to clear variable store and take screenshots after scenarios.
Their priority is set to 1.
================================================
FILE: website/build/Kakunin/docs/next/how-it-works/index.html
================================================
How it works · Kakunin
Kakunin is built with no-js experience in mind. Because of that you're able to test even complicated apps just
by knowing Kakunin (Gherkin) steps and a few good practices.
Concepts
Kakunin uses cucumber-js internally, because of that all tests (or rather scenarios) are using Gherkin as a "programming"
language.
A simple scenario could look like this:
Feature:
Scenario: Display user profile for logged user
Given I am logged in as a "user"When the "dashboard" page is displayed
And I click the "profileButton" element
Then the "myProfile" page is displayed
And the "myName" element is visible
This is how most of Kakunin test scenarios look like.
There are a few concepts to be explained.
Page objects
Page object is a code representation of a page displayed in browser. Kakunin has built-in BasePage page object, that you should extend.
Page object contains information about page url, its elements, locators, but can also have some custom methods if necessary.
A very simple example of Kakunin's Page Object could look like the following:
As you can see a basic Page Object must extend one of the Kakunin's Objects and needs to have url field defined (this.url).
This code should be saved inside pages directory in a file with js extension.
Note that a file name is very important, because we're going to use it as parameter for steps. For example, the following step:
When the "dashboard" page is displayed
expects that there is a file named dashboard.js inside the pages directory.
Every step that we are using is somehow connected to an object called currentPage. This object value is set to a
page object that we expect to be on.
This is done by two kinds of steps:
Then the "dashboard" page is displayed - this one checks if current url in browser is the same as the one inside Page Object and changes a value of the currentPage field
to this page object
When I visit the "dashboard" page - this one goes to the url specified in Page Object and attaches the Page Object to the currentPage field as above
This concept is a very simple and allows you to easily debug the framework. You can be sure that each subsequent step that declared below the ones above will be executed in context of a page object specified in those methods.
For example, having the following code:
Feature:
Scenario: Display user profile for logged user
Given I am logged in as a "user"When the "dashboard" page is displayed
And I click the "profileButton" element
Then the "myProfile" page is displayed
And the "myName" element is visible
The step named And I click the "profileButton" element is executed in context of dashboard Page Object, thus we can assume that profileButton should be defined inside the
pages/dashboard.js file.
At the same time the step And the "myName" element is visible is executed in context of myProfile, so myName should be defined in pages/myProfile.js file.
Elements and locators
The second concept that you have to understand are elements and locators.
Every element that you see on website can be represented as a element inside the page object. This allows us to use it as a parameter for a step, as we did in:
And the "myName" element is visible.
Defining elements is very simple. Let's say we have such page object:
================================================
FILE: website/build/Kakunin/docs/next/how-it-works.html
================================================
How it works · Kakunin
Kakunin is built with no-js experience in mind. Because of that you're able to test even complicated apps just
by knowing Kakunin (Gherkin) steps and a few good practices.
Concepts
Kakunin uses cucumber-js internally, because of that all tests (or rather scenarios) are using Gherkin as a "programming"
language.
A simple scenario could look like this:
Feature:
Scenario: Display user profile for logged user
Given I am logged in as a "user"When the "dashboard" page is displayed
And I click the "profileButton" element
Then the "myProfile" page is displayed
And the "myName" element is visible
This is how most of Kakunin test scenarios look like.
There are a few concepts to be explained.
Page objects
Page object is a code representation of a page displayed in browser. Kakunin has built-in BasePage page object, that you should extend.
Page object contains information about page url, its elements, locators, but can also have some custom methods if necessary.
A very simple example of Kakunin's Page Object could look like the following:
As you can see a basic Page Object must extend one of the Kakunin's Objects and needs to have url field defined (this.url).
This code should be saved inside pages directory in a file with js extension.
Note that a file name is very important, because we're going to use it as parameter for steps. For example, the following step:
When the "dashboard" page is displayed
expects that there is a file named dashboard.js inside the pages directory.
Every step that we are using is somehow connected to an object called currentPage. This object value is set to a
page object that we expect to be on.
This is done by two kinds of steps:
Then the "dashboard" page is displayed - this one checks if current url in browser is the same as the one inside Page Object and changes a value of the currentPage field
to this page object
When I visit the "dashboard" page - this one goes to the url specified in Page Object and attaches the Page Object to the currentPage field as above
This concept is a very simple and allows you to easily debug the framework. You can be sure that each subsequent step that declared below the ones above will be executed in context of a page object specified in those methods.
For example, having the following code:
Feature:
Scenario: Display user profile for logged user
Given I am logged in as a "user"When the "dashboard" page is displayed
And I click the "profileButton" element
Then the "myProfile" page is displayed
And the "myName" element is visible
The step named And I click the "profileButton" element is executed in context of dashboard Page Object, thus we can assume that profileButton should be defined inside the
pages/dashboard.js file.
At the same time the step And the "myName" element is visible is executed in context of myProfile, so myName should be defined in pages/myProfile.js file.
Elements and locators
The second concept that you have to understand are elements and locators.
Every element that you see on website can be represented as a element inside the page object. This allows us to use it as a parameter for a step, as we did in:
And the "myName" element is visible.
Defining elements is very simple. Let's say we have such page object:
Kakunin is a Protractor extension created by The Software House sp. z o.o. and Takamol Holding. It allows you
to write e2e test scenarios with a help of Gherkin language and JavaScript for all kind of applications - Angular, React and others.
Installation
In order to install Kakunin you have to make sure that you have installed:
node.js - v7.8.0 min
JDK
Chrome
Create directory for your project
mkdir my_project
Go to project directory
cd my_project
Initialize JavaScript project
npm init
Install dependencies
npm install cross-env kakunin --save
Inside package.json file; add new script in scripts section:
"kakunin": "cross-env NODE_ENV=prod kakunin"
Configuration
Create kakunin project
npm run kakunin init
The above command will run Kakunin's init script.
Answer what kind of app you're going to test (default: AngularJS)
Enter URL where your tested app will be running (default: http://localhost:3000)
Choose if you plan to use some emails checking service (default: none)
Also, there is a possibility to answer these question by a command line.
Available parameters: baseUrl, type, emailType, emailApiKey, emailInboxId.
You will not be asked about question that you already answered by a command.
After the init process, a project files should be automatically created in your directory.
This is an example of a console output after the init process is completed:
Created file at path /Users/example-user/projects/test/kakunin.conf.js
Created directory at path /Users//TSHProjects/test/reports
Created directory at path /Users//TSHProjects/test/reports/report
Created directory at path /Users//TSHProjects/test/reports/report/features
Created directory at path /Users//TSHProjects/test/reports/performance
Created directory at path /Users//TSHProjects/test/downloads
Created directory at path /Users/example-user/projects/test/data
Created directory at path /Users/example-user/projects/test/features
Created directory at path /Users/example-user/projects/test/pages
Created directory at path /Users/example-user/projects/test/matchers
Created directory at path /Users/example-user/projects/test/generators
Created directory at path /Users/example-user/projects/test/form_handlers
Created directory at path /Users/example-user/projects/test/step_definitions
Created directory at path /Users/example-user/projects/test/comparators
Created directory at path /Users/example-user/projects/test/dictionaries
Created directory at path /Users/example-user/projects/test/regexes
Created directory at path /Users/example-user/projects/test/hooks
Created directory at path /Users/example-user/projects/test/transformers
Created directory at path /Users/example-user/projects/test/emails
Created file at path /Users/example-user/projects/test/downloads/.gitkeep
Created file at path /Users/example-user/projects/test/reports/report/.gitkeep
Created file at path /Users/example-user/projects/test/reports/report/features/.gitkeep
Created file at path /Users/example-user/projects/test/reports/performance/.gitkeep
Created file at path /Users/example-user/projects/test/features/example.feature
Created file at path /Users/example-user/projects/test/pages/page.js
Created file at path /Users/example-user/projects/test/matchers/matcher.js
Created file at path /Users/example-user/projects/test/generators/generator.js
Created file at path /Users/example-user/projects/test/step_definitions/steps.js
Created file at path /Users/example-user/projects/test/regexes/regex.js
Created file at path /Users/example-user/projects/test/hooks/hook.js
And you're set! Now you can run the tests using Kakunin:
npm run kakunin
Commands
Create a new project by answering few simple questions (you can pass additional parameter to enter advanced mode where you can configure all Kakunin options by yourself)
npm run kakunin init [-- --advanced]
Run test scenarios
npm run kakunin
Run only scenarios tagged by @someTag
npm run kakunin -- --tags @someTag
Run only scenarios tagged by @someTag and @otherTag at the same time
npm run kakunin -- --tags "@someTag and @otherTag"
Run only scenarios tagged by @someTag or @otherTag
npm run kakunin -- --tags "@someTag or @otherTag"
Run only scenarios not tagged by @someTag
npm run kakunin -- --tags "not @someTag"
Troubleshooting & Tips
In order to make cucumber steps autosuggestion work properly in JetBrains tools, make sure your project is ECMAScript 6 compatible and you have cucumberjs plugin installed.
Due to non-resolved issue in Jetbrains editors (see here) we'll have to do one more step:
Go to step_definitions directory
cd step_definitions
Paste this code into terminal and restart your IDE:
Matchers allows you to check if a element content matches your expectation.
For example you can check if a value has a specified pattern or if a button is clickable.
Using matcher is very straightforward, for example: f:isClickable.
Matchers can be used in most of the steps related to checking content (with exception of checking form values).
Kakunin comes with a set of built in matchers:
Visibility matcher
f:isVisible - checks if element is visible (must be in viewport and cannot be hidden behind any other element)
Invisibility matcher
f:isNotVisible - checks if element is not visible
Present matcher
f:isPresent - checks if element is in html code (does not have to be visible)
Clickable matcher
f:isClickable - checks if element is clickable
Not clickable matcher
f:isNotClickable - checks if element is not clickable
Attribute matcher
attribute:attributeName:regexName - allows to check if element has attribute with a name specified by attributeName and it has to
have a format passing regexName
For example, if there is an element:
<p custom-attribute="123123">some value</p>
you can check if attribute is an number by running: attribute:custom-attribute:number
Regex matcher
r:regexName - allows you to run a regexName against a text value of element
Regexes have to be specified inside regex directory or be a kakunin built ones:
notEmpty - there must be a value
number - must be a number
You can add your own matchers. In order to do so please read Extending Kakunin section.
Text matcher
t:text you are looking for - allows you to check if an element contains a expected text
Current date matcher
f:currentDate:{format} - allows you to generate current date, {format} is optional, by default DD-MM-YYYY
Matchers allows you to check if a element content matches your expectation.
For example you can check if a value has a specified pattern or if a button is clickable.
Using matcher is very straightforward, for example: f:isClickable.
Matchers can be used in most of the steps related to checking content (with exception of checking form values).
Kakunin comes with a set of built in matchers:
Visibility matcher
f:isVisible - checks if element is visible (must be in viewport and cannot be hidden behind any other element)
Invisibility matcher
f:isNotVisible - checks if element is not visible
Present matcher
f:isPresent - checks if element is in html code (does not have to be visible)
Clickable matcher
f:isClickable - checks if element is clickable
Not clickable matcher
f:isNotClickable - checks if element is not clickable
Attribute matcher
attribute:attributeName:regexName - allows to check if element has attribute with a name specified by attributeName and it has to
have a format passing regexName
For example, if there is an element:
<p custom-attribute="123123">some value</p>
you can check if attribute is an number by running: attribute:custom-attribute:number
Regex matcher
r:regexName - allows you to run a regexName against a text value of element
Regexes have to be specified inside regex directory or be a kakunin built ones:
notEmpty - there must be a value
number - must be a number
You can add your own matchers. In order to do so please read Extending Kakunin section.
Text matcher
t:text you are looking for - allows you to check if an element contains a expected text
Current date matcher
f:currentDate:{format} - allows you to generate current date, {format} is optional, by default DD-MM-YYYY
Performance testing is possible thanks to browsermob-proxy.
It saves all data from network tab (Google Chrome console) which is generated during the test.
There is a possibility to compare TTFB value with a maximum given one.
TTFB (Time to first byte) measures the duration from the client making an HTTP request to the first byte of a response being received by the client's browser.
More details can be found in documentation - Built-in steps section.
What needs to be done?
Get started
Download browsermob-proxy from https://github.com/lightbody/browsermob-proxy
Navigate in terminal to the catalog
Use following command to start the REST API
./browsermob-proxy -port 8887
Configuration
Add browsermob-proxy configuration to kakunin.conf.js
You can use one of the following methods to configure browsermob-proxy:
npm run kakunin init -- --advanced and go through the process
Performance testing is possible thanks to browsermob-proxy.
It saves all data from network tab (Google Chrome console) which is generated during the test.
There is a possibility to compare TTFB value with a maximum given one.
TTFB (Time to first byte) measures the duration from the client making an HTTP request to the first byte of a response being received by the client's browser.
More details can be found in documentation - Built-in steps section.
What needs to be done?
Get started
Download browsermob-proxy from https://github.com/lightbody/browsermob-proxy
Navigate in terminal to the catalog
Use following command to start the REST API
./browsermob-proxy -port 8887
Configuration
Add browsermob-proxy configuration to kakunin.conf.js
You can use one of the following methods to configure browsermob-proxy:
npm run kakunin init -- --advanced and go through the process
As a quick demonstration of the framework let's test the
React variant of TodoMVC project.
Of course other testing other frameworks is possible, you can try it
by yourself!
Install packages
In order to install Kakunin you have to make sure that you have installed:
node.js - v7.8.0 min
JDK
Chrome
Create directory for your project and enter it
$mkdir my_project
cd my_project
Initialize JavaScript project
npm init
Install dependencies
npm install cross-env kakunin --save
Inside package.json file add new script in scripts section:
What kind of application would you like to test? : otherWeb
What is base url? [http://localhost:3000]: http://todomvc.com
What kind of email service would you like to use?: none
And you're set! Now let's write some test!
Test the app
Create a page object that will contain instructions on how to locate elements in the projects.
Create a file pages/main.js:
const { BasePage } = require('kakunin');
classMainPageextendsBasePage{
constructor() {
super();
// define the main url for the pagethis.url = '/examples/react/#/';
// whole form tagthis.addTodoForm = $('.todoapp');
// input fieldthis.todoInput = $('input.new-todo');
// list of currently added todosthis.todos = $$('.todo-list .view');
this.todoLabel = by.css('label');
// first todo item in a listthis.firstTodoItem = this.todos.get(0);
}
}
module.exports = MainPage;
Now that we have prepared the locators, we can start writing our test. Let's test adding new todo item.
Create a file named: features/adding_todo.feature with the following contents:
Feature:
Scenario: Adding todo
Given I visit the "main" page
And I wait for "visibilityOf" of the "addTodoForm" element
And the "addTodoForm" element is visible
When I fill the "addTodoForm" form with:
| todoInput | My new todo |
And I press the "enter" key
Then there are "equal 1""todos" elements
And that's it! All you have to do now is to run the test and watch the magic happens ;)
npm run kakunin
The tests may run quite fast so you might not been able to see that it
really works as expected. To check if the todo items has been really
added to the list, let's use a simple hack - let's pause the running
test right after the todo has been added.
To do that, let's upgrade our Scenario. Update the file:
Feature:
Scenario: Adding todo
Given I visit the "main" page
And I wait for "visibilityOf" of the "addTodoForm" element
And the "addTodoForm" element is visible
When I fill the "addTodoForm" form with:
| todoInput | My new todo |
And I wait for "1" seconds
And I press the "enter" key
When I fill the "addTodoForm" form with:
| todoInput | Another todo item! |
And I wait for "1" seconds
And I press the "enter" key
Then there are "equal 2""todos" elements
Then I wait for "5" seconds
As you can see, we've added 1 new step that waits for a second before
"pressing" the enter key. We've also added a second todo item with
a short pause at the end of the test so you can see the changes.
If you want to see what can we do more with the TodoMVC project, take a look
at the example dir, where you'll find a complete set of test for the project.
As a quick demonstration of the framework let's test the
React variant of TodoMVC project.
Of course other testing other frameworks is possible, you can try it
by yourself!
Install packages
In order to install Kakunin you have to make sure that you have installed:
node.js - v7.8.0 min
JDK
Chrome
Create directory for your project and enter it
$mkdir my_project
cd my_project
Initialize JavaScript project
npm init
Install dependencies
npm install cross-env kakunin --save
Inside package.json file add new script in scripts section:
What kind of application would you like to test? : otherWeb
What is base url? [http://localhost:3000]: http://todomvc.com
What kind of email service would you like to use?: none
And you're set! Now let's write some test!
Test the app
Create a page object that will contain instructions on how to locate elements in the projects.
Create a file pages/main.js:
const { BasePage } = require('kakunin');
classMainPageextendsBasePage{
constructor() {
super();
// define the main url for the pagethis.url = '/examples/react/#/';
// whole form tagthis.addTodoForm = $('.todoapp');
// input fieldthis.todoInput = $('input.new-todo');
// list of currently added todosthis.todos = $$('.todo-list .view');
this.todoLabel = by.css('label');
// first todo item in a listthis.firstTodoItem = this.todos.get(0);
}
}
module.exports = MainPage;
Now that we have prepared the locators, we can start writing our test. Let's test adding new todo item.
Create a file named: features/adding_todo.feature with the following contents:
Feature:
Scenario: Adding todo
Given I visit the "main" page
And I wait for "visibilityOf" of the "addTodoForm" element
And the "addTodoForm" element is visible
When I fill the "addTodoForm" form with:
| todoInput | My new todo |
And I press the "enter" key
Then there are "equal 1""todos" elements
And that's it! All you have to do now is to run the test and watch the magic happens ;)
npm run kakunin
The tests may run quite fast so you might not been able to see that it
really works as expected. To check if the todo items has been really
added to the list, let's use a simple hack - let's pause the running
test right after the todo has been added.
To do that, let's upgrade our Scenario. Update the file:
Feature:
Scenario: Adding todo
Given I visit the "main" page
And I wait for "visibilityOf" of the "addTodoForm" element
And the "addTodoForm" element is visible
When I fill the "addTodoForm" form with:
| todoInput | My new todo |
And I wait for "1" seconds
And I press the "enter" key
When I fill the "addTodoForm" form with:
| todoInput | Another todo item! |
And I wait for "1" seconds
And I press the "enter" key
Then there are "equal 2""todos" elements
Then I wait for "5" seconds
As you can see, we've added 1 new step that waits for a second before
"pressing" the enter key. We've also added a second todo item with
a short pause at the end of the test so you can see the changes.
If you want to see what can we do more with the TodoMVC project, take a look
at the example dir, where you'll find a complete set of test for the project.
Allows to scroll through infinite scroll mechanism.
The :elementName is a name of a selector for loading trigger.
I wait for ":expectedConditionName" of the ":elementName" element
Waits till element :elementName from this.currentPage meets criteria specified by :expectedConditionName.
You can use any of the Protractor's expected condition:
visibilityOf
invisibilityOf
etc.
Read more in Protractor's API documentation.
I wait for the ":elementName" element to disappear
Waits till element :elementName disappears.
I scroll to the ":elementName" element
Scrolls to element :elementName of this.currentPage. The element will be on bottom of the page.
I infinitely scroll to the ":elementName" element
Allows to scroll till :elementName is visible. Useful for infinite scrolling functionality.
I press the ":keyName" key
Performs a key press operation on :keyName key.
I click the ":elementName" element
Performs a click action on element :elementName from `this.currentPage'
The child element must be specified by :elementName and must be available in this.currentPage.
I store the ":elementName" element text as ":variableName" variable
Stores the text from element :elementName of this.currentPage under the :variableName so you can use it later.
I update the ":elementName" element text as ":variableName" variable
Updates the variable :variableName value by value from element :elementName of this.currentPage.
I store the ":elementName" element text matched by ":matchingRegex" as ":variableName" variable
Stores the part of the element :elementName text, that matches the :matchingRegex under the :variableName for later use.
the ":elementName"" element is visible
Checks if element :elementName is visible and clickable
the ":elementName"" element is not visible
Checks if element :elementName is available in HTML DOM but is not visible and clickable
the ":elementName" element is disabled
Checks if element is disabled
I store table ":tableRow" rows as ":variableName" with columns:
Allows to store a row specified columns from a table :tableRow and save it under :variableName as an array of objects.
This step requires a table of columns elements, for example:
I store table "someRow" rows as "someVariable" with columns:
| firstName |
| lastName |
| id |
In order to make it work there must be not only array element this.someRow = $$('.rows') in this.currentPage, but also
element this.firstName = $('.firstName'); and so on.
for this case the :elementName should be specified as $$('table tr') and we can specify column element
this.myColumn = $('td');. This allows us to write:
every "myElement" element should have the same value for element "myColumn"
the element ":elementName" should have an item with values:
Allows to check if any of the child elements of :elementName have a specified content (one matching element is enough). Element should be an array, for example:
Allows to scroll through infinite scroll mechanism.
The :elementName is a name of a selector for loading trigger.
I wait for ":expectedConditionName" of the ":elementName" element
Waits till element :elementName from this.currentPage meets criteria specified by :expectedConditionName.
You can use any of the Protractor's expected condition:
visibilityOf
invisibilityOf
etc.
Read more in Protractor's API documentation.
I wait for the ":elementName" element to disappear
Waits till element :elementName disappears.
I scroll to the ":elementName" element
Scrolls to element :elementName of this.currentPage. The element will be on bottom of the page.
I infinitely scroll to the ":elementName" element
Allows to scroll till :elementName is visible. Useful for infinite scrolling functionality.
I press the ":keyName" key
Performs a key press operation on :keyName key.
I click the ":elementName" element
Performs a click action on element :elementName from `this.currentPage'
The child element must be specified by :elementName and must be available in this.currentPage.
I store the ":elementName" element text as ":variableName" variable
Stores the text from element :elementName of this.currentPage under the :variableName so you can use it later.
I update the ":elementName" element text as ":variableName" variable
Updates the variable :variableName value by value from element :elementName of this.currentPage.
I store the ":elementName" element text matched by ":matchingRegex" as ":variableName" variable
Stores the part of the element :elementName text, that matches the :matchingRegex under the :variableName for later use.
the ":elementName"" element is visible
Checks if element :elementName is visible and clickable
the ":elementName"" element is not visible
Checks if element :elementName is available in HTML DOM but is not visible and clickable
the ":elementName" element is disabled
Checks if element is disabled
I store table ":tableRow" rows as ":variableName" with columns:
Allows to store a row specified columns from a table :tableRow and save it under :variableName as an array of objects.
This step requires a table of columns elements, for example:
I store table "someRow" rows as "someVariable" with columns:
| firstName |
| lastName |
| id |
In order to make it work there must be not only array element this.someRow = $$('.rows') in this.currentPage, but also
element this.firstName = $('.firstName'); and so on.
for this case the :elementName should be specified as $$('table tr') and we can specify column element
this.myColumn = $('td');. This allows us to write:
every "myElement" element should have the same value for element "myColumn"
the element ":elementName" should have an item with values:
Allows to check if any of the child elements of :elementName have a specified content (one matching element is enough). Element should be an array, for example:
Allows to fill the form with the name :formName and values provided as an array of inputs and values. The element with name :formName must be defined inside the
currentPage page object.
Input and values should be provided as an array for example:
I fill the "myForm" form with:
| inputElement | value to be typed into field |
| textareaElement | value to be typed into textarea |
| radioElement | radio value to be selected |
| checkboxElement | checkbox label value to be selected |
By default we support all basic HTML field types (text inputs, checkboxes, radios, selects, files and textareas)
In order to use the default handlers the elements you use as input must follow pattern:
For inputs:
this.element = $('input') - element should point at input you want to fill
For textareas:
this.element = $('textarea') - element should point at textarea you want to fill
For file input:
this.element = $('input') - element should point at input you want to fill and value should a filename of file from data directory
For selects:
this.element = $('select') - element should point at select and value should be an value of expected option
For radios:
this.element = $$('radio[name="name-of-radio"]') - element should be an array of all radio input of given name and value should be an value of radio you wish to select
For checkboxes:
Checkbox should have a html like:
<label>
My checkbox
<inputtype="checkbox"name="some-name"/></label>
this.element = $$('checkbox[name="name-of-radio"]') - element should be an array of all checkboxes of given name and value should be a text from label of checkbox you want to fill
You can use all kind of transformers to as a values for fields.
the ":formName" form is filled with:
The same as I fill the ":formName" form with: but allows to check if a form is filled with a given set of values.
You can use all kind of transformers to as a expected values for fields.
The only difference is for file fields. You cannot check uploaded files just like that, however we prepared a special type of handler
that allow to check for some information related to a specific file.
Let's assume that after upload we display an information with a file name of a uploaded file.
You can use a special handler that requires to set a element with a postfix Uploaded. This will check if a value of that element is the same as you expected.
For example you can write a step like this:
the "myform" form is filled with:
| myFileUploaded | file.txt |
Keep in mind that the element name must end with Uploaded for example:
this.myFileUploaded = $('p.some-file')
the error messages should be displayed:
Allows you to specify the error messages that should be displayed for a specific elements.
This step requires an array of format:
the error messages should be displayed:
| myElement | my error message |
You can use dictionaries in this step as follows:
the error messages should be displayed:
| myElement | d:dictionaryName:dictionaryKey |
Allows to fill the form with the name :formName and values provided as an array of inputs and values. The element with name :formName must be defined inside the
currentPage page object.
Input and values should be provided as an array for example:
I fill the "myForm" form with:
| inputElement | value to be typed into field |
| textareaElement | value to be typed into textarea |
| radioElement | radio value to be selected |
| checkboxElement | checkbox label value to be selected |
By default we support all basic HTML field types (text inputs, checkboxes, radios, selects, files and textareas)
In order to use the default handlers the elements you use as input must follow pattern:
For inputs:
this.element = $('input') - element should point at input you want to fill
For textareas:
this.element = $('textarea') - element should point at textarea you want to fill
For file input:
this.element = $('input') - element should point at input you want to fill and value should a filename of file from data directory
For selects:
this.element = $('select') - element should point at select and value should be an value of expected option
For radios:
this.element = $$('radio[name="name-of-radio"]') - element should be an array of all radio input of given name and value should be an value of radio you wish to select
For checkboxes:
Checkbox should have a html like:
<label>
My checkbox
<inputtype="checkbox"name="some-name"/></label>
this.element = $$('checkbox[name="name-of-radio"]') - element should be an array of all checkboxes of given name and value should be a text from label of checkbox you want to fill
You can use all kind of transformers to as a values for fields.
the ":formName" form is filled with:
The same as I fill the ":formName" form with: but allows to check if a form is filled with a given set of values.
You can use all kind of transformers to as a expected values for fields.
The only difference is for file fields. You cannot check uploaded files just like that, however we prepared a special type of handler
that allow to check for some information related to a specific file.
Let's assume that after upload we display an information with a file name of a uploaded file.
You can use a special handler that requires to set a element with a postfix Uploaded. This will check if a value of that element is the same as you expected.
For example you can write a step like this:
the "myform" form is filled with:
| myFileUploaded | file.txt |
Keep in mind that the element name must end with Uploaded for example:
this.myFileUploaded = $('p.some-file')
the error messages should be displayed:
Allows you to specify the error messages that should be displayed for a specific elements.
This step requires an array of format:
the error messages should be displayed:
| myElement | my error message |
You can use dictionaries in this step as follows:
the error messages should be displayed:
| myElement | d:dictionaryName:dictionaryKey |
In this section examples of using steps provided for testing, REST API will be provided.
All examples can be checked on site https://reqres.in/ which is simple REST API service
Available methods
At this moment Kakunin supports methods for REST API:
GET
POST
DELETE
PATCH
Also, You can set the headers for the request.
Making GET request
In order to create get request and verify if the response is ok you need to create scenario step:
Given I send "GET" request on "/api/users/2" endpoint
Then the response code should be "200"
This scenario will create a get request to the application and verify if the response was 200.
The response is stored till creating another request. So if We want to test the response body of a server we can create a scenario like:
Given I send "GET" request on "/api/users/2" endpoint
Then the response code should be "200"And the response should exact match to body:
"""
{
"data": {
"id": 2,
"first_name": "Janet",
"last_name": "Weaver",
"avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/josephstein/128.jpg"
}
}
"""
Based on this We can also check if the response matches schema that we have provided by using step:
Then the response should exact match JSON schema:
Making POST request
In order to create post request and attach the JSON body to it You need to create a scenario:
Given I send "POST" request on "/api/users" endpoint with body:
"""
{
"name": "morpheus",
"job": "leader"
}
"""Then the response code should be "201"
or you can create post request and attach the form data to it:
Given I send "POST" request on "/api/users" endpoint using form data:
| name | morpheus |
Then the response code should be "201"
This scenario will create a post request to the application and verify if response was 201 (created).
The response is stored till creating another request. So if We want to test the response body of a server we can create scenarios
like before:
Given I send "POST" request on "/api/users" endpoint with body:
"""
{
"name": "morpheus",
"job": "leader"
}
"""Then the response code should be "201"And the response should match JSON schema:
"""
{
"title": "Post schema",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"job": {
"type": "string"
}
},
"required": ["name", "job"]
}
"""
Scenario like that will verify if the post request was executed and response schema matches given one.
Making DELETE request
Delete request works similarly to get request. Example of delete scenario:
Given I send "DELETE" request on "/api/users/2" endpoint
Then the response code should be "204"
Making PATCH request
Patch request works similarly to post request. Example of patch scenario:
Given I send "PATCH" request on "/api/users/2" endpoint with JSON body:
"""
{
"name": "morpheus",
"job": "zion resident"
}
"""Then the response code should be "200"And the response should exact match to body:
"""
{
"name": "morpheus",
"job": "zion resident",
"updatedAt": "2019-02-12T18:25:06.001Z"
}
"""
Setting headers for request
Sometimes We want to set the headers for next request. In order to achieve this, We can create scenario like:
Given I set request headers:
| User-Agent | Mozilla |
When I send "POST" request on "postTestEndpoint" endpoint with JSON body:
"""
{
"title": "adam",
"body": "test"
}
"""Then the response code should be "403"
This scenario will set "User-Agent" header of next request to "Mozilla".
In this section examples of using steps provided for testing, REST API will be provided.
All examples can be checked on site https://reqres.in/ which is simple REST API service
Available methods
At this moment Kakunin supports methods for REST API:
GET
POST
DELETE
PATCH
Also, You can set the headers for the request.
Making GET request
In order to create get request and verify if the response is ok you need to create scenario step:
Given I send "GET" request on "/api/users/2" endpoint
Then the response code should be "200"
This scenario will create a get request to the application and verify if the response was 200.
The response is stored till creating another request. So if We want to test the response body of a server we can create a scenario like:
Given I send "GET" request on "/api/users/2" endpoint
Then the response code should be "200"And the response should exact match to body:
"""
{
"data": {
"id": 2,
"first_name": "Janet",
"last_name": "Weaver",
"avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/josephstein/128.jpg"
}
}
"""
Based on this We can also check if the response matches schema that we have provided by using step:
Then the response should exact match JSON schema:
Making POST request
In order to create post request and attach the JSON body to it You need to create a scenario:
Given I send "POST" request on "/api/users" endpoint with body:
"""
{
"name": "morpheus",
"job": "leader"
}
"""Then the response code should be "201"
or you can create post request and attach the form data to it:
Given I send "POST" request on "/api/users" endpoint using form data:
| name | morpheus |
Then the response code should be "201"
This scenario will create a post request to the application and verify if response was 201 (created).
The response is stored till creating another request. So if We want to test the response body of a server we can create scenarios
like before:
Given I send "POST" request on "/api/users" endpoint with body:
"""
{
"name": "morpheus",
"job": "leader"
}
"""Then the response code should be "201"And the response should match JSON schema:
"""
{
"title": "Post schema",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"job": {
"type": "string"
}
},
"required": ["name", "job"]
}
"""
Scenario like that will verify if the post request was executed and response schema matches given one.
Making DELETE request
Delete request works similarly to get request. Example of delete scenario:
Given I send "DELETE" request on "/api/users/2" endpoint
Then the response code should be "204"
Making PATCH request
Patch request works similarly to post request. Example of patch scenario:
Given I send "PATCH" request on "/api/users/2" endpoint with JSON body:
"""
{
"name": "morpheus",
"job": "zion resident"
}
"""Then the response code should be "200"And the response should exact match to body:
"""
{
"name": "morpheus",
"job": "zion resident",
"updatedAt": "2019-02-12T18:25:06.001Z"
}
"""
Setting headers for request
Sometimes We want to set the headers for next request. In order to achieve this, We can create scenario like:
Given I set request headers:
| User-Agent | Mozilla |
When I send "POST" request on "postTestEndpoint" endpoint with JSON body:
"""
{
"title": "adam",
"body": "test"
}
"""Then the response code should be "403"
This scenario will set "User-Agent" header of next request to "Mozilla".
Transformers allow you to transform values passed to form steps.
For example a select requires to pass a value /options/1b30f17e-e445-4d28-a30c-dedad95829ab. This one is quite unreadable, but with the help of transformers you are
able to write it like this: d:options:someOptionName.
In real-life example it will look similar to:
I fill the "myForm" form with:
| inputElement | d:someDictionary:someKey |
| textareaElement | g:someGenerator |
| radioElement | v:someVariableName |
| checkboxElement | standard value |
There are 3 types of built-in transformers:
Dictionaries
Dictionaries allows you to transform a value A to value B using a simple key->value transformation.
You can run a dictionary transformer by providing dictionary prefix d:, specifying the dictionary name and key that should be used as a value provider. For example:
d:myDictionaryName:myDictionaryKey
this example assumes that there is a dictionary that supports name myDictionaryName and it has myDictionarKey key.
You can read about dictionaries in Extending Kakunin section.
Generators
Generators allows you to generate a value by using a specified generator.
This can be done by: g:generatorName.
If a generator supports parameters then you can specify them by:
g:generatorName:param1:param2:...:paramN
You can read more about generators in Extending Kakunin section.
Variable store
Variable store allows you to fill the form with a value that was saved in previous steps of current running scenario.
This can be done by:
v:variableName
You can read more about variable store in Extending Kakunin section
Transformers allow you to transform values passed to form steps.
For example a select requires to pass a value /options/1b30f17e-e445-4d28-a30c-dedad95829ab. This one is quite unreadable, but with the help of transformers you are
able to write it like this: d:options:someOptionName.
In real-life example it will look similar to:
I fill the "myForm" form with:
| inputElement | d:someDictionary:someKey |
| textareaElement | g:someGenerator |
| radioElement | v:someVariableName |
| checkboxElement | standard value |
There are 3 types of built-in transformers:
Dictionaries
Dictionaries allows you to transform a value A to value B using a simple key->value transformation.
You can run a dictionary transformer by providing dictionary prefix d:, specifying the dictionary name and key that should be used as a value provider. For example:
d:myDictionaryName:myDictionaryKey
this example assumes that there is a dictionary that supports name myDictionaryName and it has myDictionarKey key.
You can read about dictionaries in Extending Kakunin section.
Generators
Generators allows you to generate a value by using a specified generator.
This can be done by: g:generatorName.
If a generator supports parameters then you can specify them by:
g:generatorName:param1:param2:...:paramN
You can read more about generators in Extending Kakunin section.
Variable store
Variable store allows you to fill the form with a value that was saved in previous steps of current running scenario.
This can be done by:
v:variableName
You can read more about variable store in Extending Kakunin section
Performance testing is possible thanks to browsermob-proxy.
It saves all data from network tab (Google Chrome console) which is generated during the test.
There is a possibility to compare TTFB value with a maximum given one.
TTFB (Time to first byte) measures the duration from the client making an HTTP request to the first byte of a response being received by the client's browser.
More details can be found in documentation - Built-in steps section.
What needs to be done?
Get started
Download browsermob-proxy from https://github.com/lightbody/browsermob-proxy
Navigate in terminal to the catalog
Use following command to start the REST API
./browsermob-proxy -port 8887
Configuration
Add browsermob-proxy configuration to kakunin.conf.js
You can use one of the following methods to configure browsermob-proxy:
npm run kakunin init -- --advanced and go through the process
Performance testing is possible thanks to browsermob-proxy.
It saves all data from network tab (Google Chrome console) which is generated during the test.
There is a possibility to compare TTFB value with a maximum given one.
TTFB (Time to first byte) measures the duration from the client making an HTTP request to the first byte of a response being received by the client's browser.
More details can be found in documentation - Built-in steps section.
What needs to be done?
Get started
Download browsermob-proxy from https://github.com/lightbody/browsermob-proxy
Navigate in terminal to the catalog
Use following command to start the REST API
./browsermob-proxy -port 8887
Configuration
Add browsermob-proxy configuration to kakunin.conf.js
You can use one of the following methods to configure browsermob-proxy:
npm run kakunin init -- --advanced and go through the process
As a quick demonstration of the framework let's test the
React variant of TodoMVC project.
Of course other testing other frameworks is possible, you can try it
by yourself!
Install packages
In order to install Kakunin you have to make sure that you have installed:
What kind of application would you like to test? : otherWeb
What is base url? [http://localhost:3000]: http://todomvc.com
What kind of email service would you like to use?: none
And you're set! Now let's write some test!
Test the app
Create a page object that will contain instructions on how to locate elements in the projects.
Create a file pages/main.js:
const { BasePage } = require('kakunin');
classMainPageextendsBasePage{
constructor() {
super();
// define the main url for the pagethis.url = '/examples/react/#/';
// whole form tagthis.addTodoForm = $('.todoapp');
// input fieldthis.todoInput = $('input.new-todo');
// list of currently added todosthis.todos = $$('.todo-list .view');
this.todoLabel = by.css('label');
// first todo item in a listthis.firstTodoItem = this.todos.get(0);
}
}
module.exports = MainPage;
Now that we have prepared the locators, we can start writing our test. Let's test adding new todo item.
Create a file named: features/adding_todo.feature with the following contents:
Feature:
Scenario: Adding todo
Given I visit the "main" page
And I wait for "visibilityOf" of the "addTodoForm" element
And the "addTodoForm" element is visible
When I fill the "addTodoForm" form with:
| todoInput | My new todo |
And I press the "enter" key
Then there are "equal 1""todos" elements
And that's it! All you have to do now is to run the test and watch the magic happens ;)
npm run kakunin
The tests may run quite fast so you might not been able to see that it
really works as expected. To check if the todo items has been really
added to the list, let's use a simple hack - let's pause the running
test right after the todo has been added.
To do that, let's upgrade our Scenario. Update the file:
Feature:
Scenario: Adding todo
Given I visit the "main" page
And I wait for "visibilityOf" of the "addTodoForm" element
And the "addTodoForm" element is visible
When I fill the "addTodoForm" form with:
| todoInput | My new todo |
And I wait for "1" seconds
And I press the "enter" key
When I fill the "addTodoForm" form with:
| todoInput | Another todo item! |
And I wait for "1" seconds
And I press the "enter" key
Then there are "equal 2""todos" elements
Then I wait for "5" seconds
As you can see, we've added 1 new step that waits for a second before
"pressing" the enter key. We've also added a second todo item with
a short pause at the end of the test so you can see the changes.
If you want to see what can we do more with the TodoMVC project, take a look
at the example dir, where you'll find a complete set of test for the project.
As a quick demonstration of the framework let's test the
React variant of TodoMVC project.
Of course other testing other frameworks is possible, you can try it
by yourself!
Install packages
In order to install Kakunin you have to make sure that you have installed:
What kind of application would you like to test? : otherWeb
What is base url? [http://localhost:3000]: http://todomvc.com
What kind of email service would you like to use?: none
And you're set! Now let's write some test!
Test the app
Create a page object that will contain instructions on how to locate elements in the projects.
Create a file pages/main.js:
const { BasePage } = require('kakunin');
classMainPageextendsBasePage{
constructor() {
super();
// define the main url for the pagethis.url = '/examples/react/#/';
// whole form tagthis.addTodoForm = $('.todoapp');
// input fieldthis.todoInput = $('input.new-todo');
// list of currently added todosthis.todos = $$('.todo-list .view');
this.todoLabel = by.css('label');
// first todo item in a listthis.firstTodoItem = this.todos.get(0);
}
}
module.exports = MainPage;
Now that we have prepared the locators, we can start writing our test. Let's test adding new todo item.
Create a file named: features/adding_todo.feature with the following contents:
Feature:
Scenario: Adding todo
Given I visit the "main" page
And I wait for "visibilityOf" of the "addTodoForm" element
And the "addTodoForm" element is visible
When I fill the "addTodoForm" form with:
| todoInput | My new todo |
And I press the "enter" key
Then there are "equal 1""todos" elements
And that's it! All you have to do now is to run the test and watch the magic happens ;)
npm run kakunin
The tests may run quite fast so you might not been able to see that it
really works as expected. To check if the todo items has been really
added to the list, let's use a simple hack - let's pause the running
test right after the todo has been added.
To do that, let's upgrade our Scenario. Update the file:
Feature:
Scenario: Adding todo
Given I visit the "main" page
And I wait for "visibilityOf" of the "addTodoForm" element
And the "addTodoForm" element is visible
When I fill the "addTodoForm" form with:
| todoInput | My new todo |
And I wait for "1" seconds
And I press the "enter" key
When I fill the "addTodoForm" form with:
| todoInput | Another todo item! |
And I wait for "1" seconds
And I press the "enter" key
Then there are "equal 2""todos" elements
Then I wait for "5" seconds
As you can see, we've added 1 new step that waits for a second before
"pressing" the enter key. We've also added a second todo item with
a short pause at the end of the test so you can see the changes.
If you want to see what can we do more with the TodoMVC project, take a look
at the example dir, where you'll find a complete set of test for the project.
Allows to scroll through infinite scroll mechanism.
The :elementName is a name of a selector for loading trigger.
I wait for ":expectedConditionName" of the ":elementName" element
Waits till element :elementName from this.currentPage meets criteria specified by :expectedConditionName.
You can use any of the Protractor's expected condition:
visibilityOf
invisibilityOf
etc.
Read more in Protractor's API documentation.
I wait for the ":elementName" element to disappear
Waits till element :elementName disappears.
I scroll to the ":elementName" element
Scrolls to element :elementName of this.currentPage. The element will be on bottom of the page.
I infinitely scroll to the ":elementName" element
Allows to scroll till :elementName is visible. Useful for infinite scrolling functionality.
I press the ":keyName" key
Performs a key press operation on :keyName key.
I click the ":elementName" element
Performs a click action on element :elementName from `this.currentPage'
The child element must be specified by :elementName and must be available in this.currentPage.
I store the ":elementName" element text as ":variableName" variable
Stores the text from element :elementName of this.currentPage under the :variableName so you can use it later.
I update the ":elementName" element text as ":variableName" variable
Updates the variable :variableName value by value from element :elementName of this.currentPage.
I store the ":elementName" element text matched by ":matchingRegex" as ":variableName" variable
Stores the part of the element :elementName text, that matches the :matchingRegex under the :variableName for later use.
the ":elementName"" element is visible
Checks if element :elementName is visible and clickable
the ":elementName"" element is not visible
Checks if element :elementName is available in HTML DOM but is not visible and clickable
the ":elementName" element is disabled
Checks if element is disabled
I store table ":tableRow" rows as ":variableName" with columns:
Allows to store a row specified columns from a table :tableRow and save it under :variableName as an array of objects.
This step requires a table of columns elements, for example:
I store table "someRow" rows as "someVariable" with columns:
| firstName |
| lastName |
| id |
In order to make it work there must be not only array element this.someRow = $$('.rows') in this.currentPage, but also
element this.firstName = $('.firstName'); and so on.
for this case the :elementName should be specified as $$('table tr') and we can specify column element
this.myColumn = $('td');. This allows us to write:
every "myElement" element should have the same value for element "myColumn"
the element ":elementName" should have an item with values:
Allows to check if any of the child elements of :elementName have a specified content (one matching element is enough). Element should be an array, for example:
Allows to scroll through infinite scroll mechanism.
The :elementName is a name of a selector for loading trigger.
I wait for ":expectedConditionName" of the ":elementName" element
Waits till element :elementName from this.currentPage meets criteria specified by :expectedConditionName.
You can use any of the Protractor's expected condition:
visibilityOf
invisibilityOf
etc.
Read more in Protractor's API documentation.
I wait for the ":elementName" element to disappear
Waits till element :elementName disappears.
I scroll to the ":elementName" element
Scrolls to element :elementName of this.currentPage. The element will be on bottom of the page.
I infinitely scroll to the ":elementName" element
Allows to scroll till :elementName is visible. Useful for infinite scrolling functionality.
I press the ":keyName" key
Performs a key press operation on :keyName key.
I click the ":elementName" element
Performs a click action on element :elementName from `this.currentPage'
The child element must be specified by :elementName and must be available in this.currentPage.
I store the ":elementName" element text as ":variableName" variable
Stores the text from element :elementName of this.currentPage under the :variableName so you can use it later.
I update the ":elementName" element text as ":variableName" variable
Updates the variable :variableName value by value from element :elementName of this.currentPage.
I store the ":elementName" element text matched by ":matchingRegex" as ":variableName" variable
Stores the part of the element :elementName text, that matches the :matchingRegex under the :variableName for later use.
the ":elementName"" element is visible
Checks if element :elementName is visible and clickable
the ":elementName"" element is not visible
Checks if element :elementName is available in HTML DOM but is not visible and clickable
the ":elementName" element is disabled
Checks if element is disabled
I store table ":tableRow" rows as ":variableName" with columns:
Allows to store a row specified columns from a table :tableRow and save it under :variableName as an array of objects.
This step requires a table of columns elements, for example:
I store table "someRow" rows as "someVariable" with columns:
| firstName |
| lastName |
| id |
In order to make it work there must be not only array element this.someRow = $$('.rows') in this.currentPage, but also
element this.firstName = $('.firstName'); and so on.
for this case the :elementName should be specified as $$('table tr') and we can specify column element
this.myColumn = $('td');. This allows us to write:
every "myElement" element should have the same value for element "myColumn"
the element ":elementName" should have an item with values:
Allows to check if any of the child elements of :elementName have a specified content (one matching element is enough). Element should be an array, for example:
Allows to fill the form with the name :formName and values provided as an array of inputs and values. The element with name :formName must be defined inside the
currentPage page object.
Input and values should be provided as an array for example:
I fill the "myForm" form with:
| inputElement | value to be typed into field |
| textareaElement | value to be typed into textarea |
| radioElement | radio value to be selected |
| checkboxElement | checkbox label value to be selected |
By default we support all basic HTML field types (text inputs, checkboxes, radios, selects, files and textareas)
In order to use the default handlers the elements you use as input must follow pattern:
For inputs:
this.element = $('input') - element should point at input you want to fill
For textareas:
this.element = $('textarea') - element should point at textarea you want to fill
For file input:
this.element = $('input') - element should point at input you want to fill and value should a filename of file from data directory
For selects:
this.element = $('select') - element should point at select and value should be an value of expected option
For radios:
this.element = $$('radio[name="name-of-radio"]') - element should be an array of all radio input of given name and value should be an value of radio you wish to select
For checkboxes:
Checkbox should have a html like:
<label>
My checkbox
<inputtype="checkbox"name="some-name"/></label>
this.element = $$('checkbox[name="name-of-radio"]') - element should be an array of all checkboxes of given name and value should be a text from label of checkbox you want to fill
You can use all kind of transformers to as a values for fields.
the ":formName" form is filled with:
The same as I fill the ":formName" form with: but allows to check if a form is filled with a given set of values.
You can use all kind of transformers to as a expected values for fields.
The only difference is for file fields. You cannot check uploaded files just like that, however we prepared a special type of handler
that allow to check for some information related to a specific file.
Let's assume that after upload we display an information with a file name of a uploaded file.
You can use a special handler that requires to set a element with a postfix Uploaded. This will check if a value of that element is the same as you expected.
For example you can write a step like this:
the "myform" form is filled with:
| myFileUploaded | file.txt |
Keep in mind that the element name must end with Uploaded for example:
this.myFileUploaded = $('p.some-file')
the error messages should be displayed:
Allows you to specify the error messages that should be displayed for a specific elements.
This step requires an array of format:
the error messages should be displayed:
| myElement | my error message |
You can use dictionaries in this step as follows:
the error messages should be displayed:
| myElement | d:dictionaryName:dictionaryKey |
Allows to fill the form with the name :formName and values provided as an array of inputs and values. The element with name :formName must be defined inside the
currentPage page object.
Input and values should be provided as an array for example:
I fill the "myForm" form with:
| inputElement | value to be typed into field |
| textareaElement | value to be typed into textarea |
| radioElement | radio value to be selected |
| checkboxElement | checkbox label value to be selected |
By default we support all basic HTML field types (text inputs, checkboxes, radios, selects, files and textareas)
In order to use the default handlers the elements you use as input must follow pattern:
For inputs:
this.element = $('input') - element should point at input you want to fill
For textareas:
this.element = $('textarea') - element should point at textarea you want to fill
For file input:
this.element = $('input') - element should point at input you want to fill and value should a filename of file from data directory
For selects:
this.element = $('select') - element should point at select and value should be an value of expected option
For radios:
this.element = $$('radio[name="name-of-radio"]') - element should be an array of all radio input of given name and value should be an value of radio you wish to select
For checkboxes:
Checkbox should have a html like:
<label>
My checkbox
<inputtype="checkbox"name="some-name"/></label>
this.element = $$('checkbox[name="name-of-radio"]') - element should be an array of all checkboxes of given name and value should be a text from label of checkbox you want to fill
You can use all kind of transformers to as a values for fields.
the ":formName" form is filled with:
The same as I fill the ":formName" form with: but allows to check if a form is filled with a given set of values.
You can use all kind of transformers to as a expected values for fields.
The only difference is for file fields. You cannot check uploaded files just like that, however we prepared a special type of handler
that allow to check for some information related to a specific file.
Let's assume that after upload we display an information with a file name of a uploaded file.
You can use a special handler that requires to set a element with a postfix Uploaded. This will check if a value of that element is the same as you expected.
For example you can write a step like this:
the "myform" form is filled with:
| myFileUploaded | file.txt |
Keep in mind that the element name must end with Uploaded for example:
this.myFileUploaded = $('p.some-file')
the error messages should be displayed:
Allows you to specify the error messages that should be displayed for a specific elements.
This step requires an array of format:
the error messages should be displayed:
| myElement | my error message |
You can use dictionaries in this step as follows:
the error messages should be displayed:
| myElement | d:dictionaryName:dictionaryKey |
Transformers allow you to transform values passed to form steps.
For example a select requires to pass a value /options/1b30f17e-e445-4d28-a30c-dedad95829ab. This one is quite unreadable, but with the help of transformers you are
able to write it like this: d:options:someOptionName.
In real-life example it will look similar to:
I fill the "myForm" form with:
| inputElement | d:someDictionary:someKey |
| textareaElement | g:someGenerator |
| radioElement | v:someVariableName |
| checkboxElement | standard value |
There are 3 types of built-in transformers:
Dictionaries
Dictionaries allows you to transform a value A to value B using a simple key->value transformation.
You can run a dictionary transformer by providing dictionary prefix d:, specifying the dictionary name and key that should be used as a value provider. For example:
d:myDictionaryName:myDictionaryKey
this example assumes that there is a dictionary that supports name myDictionaryName and it has myDictionarKey key.
You can read about dictionaries in Extending Kakunin section.
Generators
Generators allows you to generate a value by using a specified generator.
This can be done by: g:generatorName.
If a generator supports parameters then you can specify them by:
g:generatorName:param1:param2:...:paramN
You can read more about generators in Extending Kakunin section.
Variable store
Variable store allows you to fill the form with a value that was saved in previous steps of current running scenario.
This can be done by:
v:variableName
You can read more about variable store in Extending Kakunin section
Transformers allow you to transform values passed to form steps.
For example a select requires to pass a value /options/1b30f17e-e445-4d28-a30c-dedad95829ab. This one is quite unreadable, but with the help of transformers you are
able to write it like this: d:options:someOptionName.
In real-life example it will look similar to:
I fill the "myForm" form with:
| inputElement | d:someDictionary:someKey |
| textareaElement | g:someGenerator |
| radioElement | v:someVariableName |
| checkboxElement | standard value |
There are 3 types of built-in transformers:
Dictionaries
Dictionaries allows you to transform a value A to value B using a simple key->value transformation.
You can run a dictionary transformer by providing dictionary prefix d:, specifying the dictionary name and key that should be used as a value provider. For example:
d:myDictionaryName:myDictionaryKey
this example assumes that there is a dictionary that supports name myDictionaryName and it has myDictionarKey key.
You can read about dictionaries in Extending Kakunin section.
Generators
Generators allows you to generate a value by using a specified generator.
This can be done by: g:generatorName.
If a generator supports parameters then you can specify them by:
g:generatorName:param1:param2:...:paramN
You can read more about generators in Extending Kakunin section.
Variable store
Variable store allows you to fill the form with a value that was saved in previous steps of current running scenario.
This can be done by:
v:variableName
You can read more about variable store in Extending Kakunin section
================================================
FILE: website/build/Kakunin/en/versions/index.html
================================================
Kakunin · Are you looking for an automation testing tool? Here's Kakunin – an open-source framework for end-to-end, automated software testing.
You can find past versions of this project on GitHub.
================================================
FILE: website/build/Kakunin/en/versions.html
================================================
Kakunin · Are you looking for an automation testing tool? Here's Kakunin – an open-source framework for end-to-end, automated software testing.
You can find past versions of this project on GitHub.
================================================
FILE: website/build/Kakunin/index.html
================================================
Kakunin
If you are not redirected automatically, follow this link.
================================================
FILE: website/build/Kakunin/js/codetabs.js
================================================
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// Turn off ESLint for this file because it's sent down to users as-is.
/* eslint-disable */
window.addEventListener('load', function () {
// add event listener for all tab
document.querySelectorAll('.nav-link').forEach(function (el) {
el.addEventListener('click', function (e) {
var groupId = e.target.getAttribute('data-group');
document
.querySelectorAll('.nav-link[data-group='.concat(groupId, ']'))
.forEach(function (el) {
el.classList.remove('active');
});
document
.querySelectorAll('.tab-pane[data-group='.concat(groupId, ']'))
.forEach(function (el) {
el.classList.remove('active');
});
e.target.classList.add('active');
document
.querySelector('#'.concat(e.target.getAttribute('data-tab')))
.classList.add('active');
});
});
});
================================================
FILE: website/build/Kakunin/js/scrollSpy.js
================================================
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/* eslint-disable */
(function scrollSpy() {
var OFFSET = 10;
var timer;
var headingsCache;
var findHeadings = function findHeadings() {
return headingsCache || document.querySelectorAll('.toc-headings > li > a');
};
var onScroll = function onScroll() {
if (timer) {
// throttle
return;
}
timer = setTimeout(function () {
timer = null;
var activeNavFound = false;
var headings = findHeadings(); // toc nav anchors
/**
* On every call, try to find header right after <-- next header
* the one whose content is on the current screen <-- highlight this
*/
for (var i = 0; i < headings.length; i++) {
// headings[i] is current element
// if an element is already active, then current element is not active
// if no element is already active, then current element is active
var currNavActive = !activeNavFound;
/**
* Enter the following check up only when an active nav header is not yet found
* Then, check the bounding rectangle of the next header
* The headers that are scrolled passed will have negative bounding rect top
* So the first one with positive bounding rect top will be the nearest next header
*/
if (currNavActive && i < headings.length - 1) {
var heading = headings[i + 1];
var next = decodeURIComponent(heading.href.split('#')[1]);
var nextHeader = document.getElementById(next);
if (nextHeader) {
var top = nextHeader.getBoundingClientRect().top;
currNavActive = top > OFFSET;
} else {
console.error('Can not find header element', {
id: next,
heading: heading,
});
}
}
/**
* Stop searching once a first such header is found,
* this makes sure the highlighted header is the most current one
*/
if (currNavActive) {
activeNavFound = true;
headings[i].classList.add('active');
} else {
headings[i].classList.remove('active');
}
}
}, 100);
};
document.addEventListener('scroll', onScroll);
document.addEventListener('resize', onScroll);
document.addEventListener('DOMContentLoaded', function () {
// Cache the headings once the page has fully loaded.
headingsCache = findHeadings();
onScroll();
});
})();
================================================
FILE: website/build/Kakunin/sitemap.xml
================================================
https://thesoftwarehouse.github.io/versionsweekly0.5https://thesoftwarehouse.github.io/Kakunin/docs/next/browserstackhourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/configurationhourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/cross-browserhourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/dockerhourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/extendinghourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/headlesshourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/hookshourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/how-it-workshourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/hourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/matchershourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/parallel-testinghourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/performance-testinghourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/quickstarthourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/steps-debughourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/steps-elementshourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/steps-fileshourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/steps-formshourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/steps-generatorshourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/steps-navigationhourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/steps-resthourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/testing-rest-apihourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/next/transformershourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/configurationhourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/cross-browserhourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/dockerhourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/extendinghourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/how-it-workshourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/hourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/matchershourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/parallel-testinghourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/performance-testinghourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/quickstarthourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/steps-debughourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/steps-elementshourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/steps-fileshourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/steps-formshourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/steps-generatorshourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/steps-navigationhourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/transformershourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/2.4.0/configurationhourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/2.4.0/cross-browserhourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/2.4.0/dockerhourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/2.4.0/extendinghourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/2.4.0/how-it-workshourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/2.4.0/hourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/2.4.0/matchershourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/2.4.0/parallel-testinghourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/2.4.0/performance-testinghourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/2.4.0/quickstarthourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/2.4.0/steps-debughourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/2.4.0/steps-elementshourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/2.4.0/steps-fileshourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/2.4.0/steps-formshourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/2.4.0/steps-generatorshourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/2.4.0/steps-navigationhourly1.0https://thesoftwarehouse.github.io/Kakunin/docs/2.4.0/transformershourly1.0
================================================
FILE: website/build/Kakunin/versions/index.html
================================================
Kakunin · Are you looking for an automation testing tool? Here's Kakunin – an open-source framework for end-to-end, automated software testing.
You can find past versions of this project on GitHub.
================================================
FILE: website/build/Kakunin/versions.html
================================================
Kakunin · Are you looking for an automation testing tool? Here's Kakunin – an open-source framework for end-to-end, automated software testing.
`
you can check if attribute is an number by running: `attribute:custom-attribute:number`
## Regex matcher
`r:regexName` - allows you to run a `regexName` against a text value of element
Regexes have to be specified inside `regex` directory or be a kakunin built ones:
`notEmpty` - there must be a value
`number` - must be a number
You can add your own matchers. In order to do so please read `Extending Kakunin` section.
## Text matcher
`t:text you are looking for` - allows you to check if an element contains a expected text
## Current date matcher
`f:currentDate:{format}` - allows you to generate current date, `{format}` is optional, by default `DD-MM-YYYY`
================================================
FILE: website/versioned_docs/version-2.4.0/parallel-testing.md
================================================
---
id: version-2.4.0-parallel-testing
title: Parallel testing
original_id: parallel-testing
---
There is a possibility to run tests in parallel.
## How to execute
Use a command `npm run kakunin -- --parallel ` where `number of instances` is a number.
Example:
- `npm run kakunin -- --chrome --parallel 2`
Keep in mind that the merged report is available in the `reports/report/index.html` file. text
## Specify pattern per each instance
- `npm run kakunin -- --parallel --pattern --pattern `
Keep in mind that:
- the number given in `parallel` must be equal to passed `patterns`
- `` is a number of instances of the specified browser
- `` is a pattern that is used to specify the list of specs that will be executed in each of the instances
-----------------------------------------------------------------------------------
## Troubleshooting
1. Running more than one instance in `Firefox` is not possible now (fix in-progress).
================================================
FILE: website/versioned_docs/version-2.4.0/performance-testing.md
================================================
---
id: version-2.4.0-performance-testing
title: Performance testing
original_id: performance-testing
---
Performance testing is possible thanks to `browsermob-proxy`.
It saves all data from network tab (Google Chrome console) which is generated during the test.
There is a possibility to compare `TTFB` value with a maximum given one.
`TTFB` (Time to first byte) measures the duration from the client making an HTTP request to the first byte of a response being received by the client's browser.
More details can be found in documentation - `Built-in steps` section.
# What needs to be done?
## Get started
1. Download `browsermob-proxy` from `https://github.com/lightbody/browsermob-proxy`
2. Navigate in terminal to the catalog
3. Use following command to start the REST API
```
./browsermob-proxy -port 8887
```
## Configuration
1. Add `browsermob-proxy` configuration to `kakunin.conf.js`
You can use one of the following methods to configure browsermob-proxy:
- `npm run kakunin init -- --advanced` and go through the process
- or add it manually to the config file:
```javascript
"browserMob": {
"serverPort": 8887,
"port": 8888,
"host": "localhost"
}
```
## Run tests
1. `performance steps` must be used in the scenario where you are testing performance
2. Scenario must have a tag `@performance`
3. Run tests with special parameter:
```
npm run kakunin -- --performance
```
## Results
1. `.har` files are saved in catalog `reports/performance/*.har`
================================================
FILE: website/versioned_docs/version-2.4.0/quickstart.md
================================================
---
id: version-2.4.0-quickstart
title: Quick start
original_id: quickstart
---
As a quick demonstration of the framework let's test the
[React variant of TodoMVC](http://todomvc.com/examples/react/#/) project.
Of course other testing other frameworks is possible, you can try it
by yourself!
## Install packages
In order to install Kakunin you have to make sure that you have installed:
```text
node.js - v7.8.0 min
JDK
Chrome
```
Create directory for your project and enter it
```bash
$mkdir my_project
cd my_project
```
Initialize JavaScript project
```bash
npm init
```
Install dependencies
```bash
npm install cross-env protractor webdriver-manager kakunin --save
```
Inside `package.json` file add new script in `scripts` section:
```js
...
"scripts": {
"kakunin": "cross-env NODE_ENV=prod kakunin"
},
...
```
## Configure Kakunin
Run initialization command
```bash
npm run kakunin init
```
Answer literally few questions:
```text
What kind of application would you like to test? : otherWeb
What is base url? [http://localhost:3000]: http://todomvc.com
What kind of email service would you like to use?: none
```
And you're set! Now let's write some test!
## Test the app
Create a page object that will contain instructions on how to locate elements in the projects.
Create a file `pages/main.js`:
```javascript
const { BasePage } = require('kakunin');
class MainPage extends BasePage {
constructor() {
super();
// define the main url for the page
this.url = '/examples/react/#/';
// whole form tag
this.addTodoForm = $('.todoapp');
// input field
this.todoInput = $('input.new-todo');
// list of currently added todos
this.todos = $$('.todo-list .view');
this.todoLabel = by.css('label');
// first todo item in a list
this.firstTodoItem = this.todos.get(0);
}
}
module.exports = MainPage;
```
Now that we have prepared the locators, we can start writing our test. Let's test adding new todo item.
Create a file named: `features/adding_todo.feature` with the following contents:
```gherkin
Feature:
Scenario: Adding todo
Given I visit the "main" page
And I wait for "visibilityOf" of the "addTodoForm" element
And the "addTodoForm" element is visible
When I fill the "addTodoForm" form with:
| todoInput | My new todo |
And I press the "enter" key
Then there are "equal 1" "todos" elements
```
And that's it! All you have to do now is to run the test and watch the magic happens ;)
```bash
npm run kakunin
```
The tests may run quite fast so you might not been able to see that it
really works as expected. To check if the todo items has been really
added to the list, let's use a simple hack - let's pause the running
test right after the todo has been added.
To do that, let's upgrade our Scenario. Update the file:
```gherkin
Feature:
Scenario: Adding todo
Given I visit the "main" page
And I wait for "visibilityOf" of the "addTodoForm" element
And the "addTodoForm" element is visible
When I fill the "addTodoForm" form with:
| todoInput | My new todo |
And I wait for "1" seconds
And I press the "enter" key
When I fill the "addTodoForm" form with:
| todoInput | Another todo item! |
And I wait for "1" seconds
And I press the "enter" key
Then there are "equal 2" "todos" elements
Then I wait for "5" seconds
```
As you can see, we've added 1 new step that waits for a second before
"pressing" the `enter` key. We've also added a second todo item with
a short pause at the end of the test so you can see the changes.
If you want to see what can we do more with the TodoMVC project, take a look
at the `example` dir, where you'll find a complete set of test for the project.
================================================
FILE: website/versioned_docs/version-2.4.0/steps-debug.md
================================================
---
id: version-2.4.0-steps-debug
title: Debug
original_id: steps-debug
---
# Steps for debugging application:
## `I pause`
Pauses tests execution and allows to continue manually by pressing combination of `ctrl+c` inside terminal.
---
## `I wait for ":seconds" seconds`
Waits with execution of next step for an amount provided by parameter `:seconds`.
---
## `I start performance monitor mode`
It starts performance monitor mode.
Keep in mind that REST API must be started on the port which must configured in `kakunin.conf.js` - `serverPort: 8887`.
More details can be found in documentation file `performance-testing.md`.
---
## `I save performance report file as "fileName"`
It saves `.har` file with a name `fileName` in `reports/performance` catalog.
For example: `exampleReport-1511470954552.har`
Data is generated during the test - network tab in Chrome Chrome console.
Keep in mind:
* `I start performance monitor mode` must be used before this step
* `browserMob.port` must be configured in `kakunin.conf.js`
* `browserMob.host` must be configured in `kakunin.conf.js`
More details can be found in documentation file `performance-testing.md`.
---
## `the requests should take a maximum of "maxTiming" milliseconds`
It compares every `TTFB` timing value from previously saved `.har` report with a `maxTiming` value.
Slow requests are listed in your terminal in red colour.
Keep in mind that `I start performance monitor mode` and `I save performance report file as "fileName"` steps must be executed before this one!
---
================================================
FILE: website/versioned_docs/version-2.4.0/steps-elements.md
================================================
---
id: version-2.4.0-steps-elements
title: Elements
original_id: steps-elements
---
# Steps used to interact with elements:
## `I infinitely scroll to the ":elementName" element`
Allows to scroll through infinite scroll mechanism.
The `:elementName` is a name of a selector for loading trigger.
---
## `I wait for ":expectedConditionName" of the ":elementName" element`
Waits till element `:elementName` from `this.currentPage` meets criteria specified by `:expectedConditionName`.
You can use any of the Protractor's expected condition:
* `visibilityOf`
* `invisibilityOf`
etc.
Read more in Protractor's API documentation.
---
## `I wait for the ":elementName" element to disappear`
Waits till element `:elementName` disappears.
---
## `I scroll to the ":elementName" element`
Scrolls to element `:elementName` of `this.currentPage`. The element will be on bottom of the page.
---
## `I infinitely scroll to the ":elementName" element`
Allows to scroll till `:elementName` is visible. Useful for infinite scrolling functionality.
---
## `I press the ":keyName" key`
Performs a key press operation on `:keyName` key.
---
## `I click the ":elementName" element`
Performs a click action on element `:elementName` from `this.currentPage'
The child element must be specified by `:elementName` and must be available in `this.currentPage`.
---
## `I store the ":elementName" element text as ":variableName" variable`
Stores the text from element `:elementName` of `this.currentPage` under the `:variableName` so you can use it later.
---
## `I update the ":elementName" element text as ":variableName" variable`
Updates the variable `:variableName` value by value from element `:elementName` of `this.currentPage`.
---
## `I store the ":elementName" element text matched by ":matchingRegex" as ":variableName" variable`
Stores the part of the element `:elementName` text, that matches the `:matchingRegex` under the `:variableName` for later use.
---
## `the ":elementName"" element is visible`
Checks if element `:elementName` is visible and clickable
---
## `the ":elementName"" element is not visible`
Checks if element `:elementName` is available in HTML DOM but is not visible and clickable
---
## `the ":elementName" element is disabled`
Checks if element is disabled
---
## `I store table ":tableRow" rows as ":variableName" with columns:`
Allows to store a row specified columns from a table `:tableRow` and save it under `:variableName` as an array of objects.
This step requires a table of columns elements, for example:
```gherkin
I store table "someRow" rows as "someVariable" with columns:
| firstName |
| lastName |
| id |
```
In order to make it work there must be not only array element `this.someRow = $$('.rows')` in `this.currentPage`, but also
element `this.firstName = $('.firstName');` and so on.
The result of this step is an array of:
```javascript
[
[
'firsRowFirstNameValue',
'firsRowLastNameValue'
'firsRowIdValue'
]
...
]
```
---
## `there are following elements in table ":elementName":`
Allows to check if a child elements of `:elementName` have a specified content.
This steps allows you to specify an array of child elements that will be checked against expected values.
For example:
```gherkin
there are following elements in table "myTable":
| id | firstName | lastName |
| t:1 | t:Adam | t:Doe |
| t:2 | t:John | t:Doe |
```
First row must specify columns elements. Starting from second row we must provide a matchers for each row that must be displayed.
This step checks exact match, so if the table has 5 rows, there must be a 5 rows in this table.
We can specify only a set of columns (for example if a table has 5 columns, we can specify only 1).
---
## `there are "numberExpression" following elements for element ":elementName":`
Allows to check if a child elements of `:elementName` have a specified content. Element should be an array, for example:
```html
1
2
```
for this case the `:elementName` should be specified as `$$('table tr')`.
Allows to check if a number of elements is the one that we expect.
`numberExpression` is a supported expression from `chai.js` library:
* `equal N` where N is a number
* `at least N` where N is a number
* `above N` where N is a number
* `below N` where N is a number
* `within N M` where N and M are a numbers
and so on. You can check expressions on `chai.js` API dock for BDD.
This step requires an array of elements to be checked. For example:
```gherkin
there are "equal 5" following elements for element "myList":
| viewButton | f:isClickable |
| id | r:idRegex |
```
The child elements must be an elements, for example `this.viewButton = $('button.viewButton');`.
You can use all kind of matchers here.
---
## `there is element ":elementName" with value ":matcher"`
Allows to check if `:elementName` has a value that matches the `:matcher`.
---
## `there is no element ":elementName" with value ":matcherName"`
Allows to check if there is no `:elementName` that matches the `:matcher`.
---
## `there are "numberExpression" ":elementName" elements`
Allows to check if a number of `:elementName` elements is the same as we expect.
`numberExpression` is a supported expression from `chai.js` library:
* `equal N` where N is a number
* `at least N` where N is a number
* `above N` where N is a number
* `below N` where N is a number
* `within N M` where N and M are a numbers
and so on. You can check expressions on `chai.js` API dock for BDD.
`:elementName` should be specified as an array, for example:
```html
1
2
```
for this case the `:elementName` should be specified as `$$('table tr')`.
---
## `every ":elementName" element should have the same value for element ":columnElementName"`
Allows to check if every row defined by `:elementName` has the same value for a column `:columnElementName`.
`:elementName` must be an array of elements
`:columnElementName` must be an element, for example:
```html
1
1
```
for this case the `:elementName` should be specified as `$$('table tr')` and we can specify column element
`this.myColumn = $('td');`. This allows us to write:
`every "myElement" element should have the same value for element "myColumn"`
---
## `the element ":elementName" should have an item with values:`
Allows to check if any of the child elements of `:elementName` have a specified content (one matching element is enough). Element should be an array, for example:
```html
1
2
```
for this case the `:elementName` should be specified as `$$('table tr')`.
This step requires an array of elements to be checked. For example:
```gherkin
the element "myList" should have an item with values:
| id | t:1 |
```
The child elements must be an elements, for example `this.id = $('td');`.
You can use all kind of matchers here.
---
## `the element ":elementName" should not have an item with values:`
Allows to check if the child elements of `:elementName` have a different content than that given in the table. Element should be an array, for example:
```html
1
2
```
for this case the `:elementName` should be specified as `$$('table tr')`.
This step requires an array of elements to be checked. For example:
```gherkin
the element "myList" should have an item with values:
| id | t:does-not-exist |
```
The child elements must be an elements, for example `this.id = $('td');`.
You can use all kind of matchers here.
---
## `I drag ":elementDrag" element and drop over ":elementDrop" element`
Clicks on `:elementDrag` and moves it onto `:elementDrop` while left mouse button is pressed, and then release it.
Note: This step is not working on HTML5!
---
================================================
FILE: website/versioned_docs/version-2.4.0/steps-files.md
================================================
---
id: version-2.4.0-steps-files
title: Files
original_id: steps-files
---
# Steps used to interact with files:
## `the file ":fileName" should be downloaded`
Checks if a file with name `:fileName` was downloaded.
This step does not support matchers or regular expressions, so the name must be exact match. However you can use
variable store here.
Let's assume there is a variable `myFile` with a value `super-file` in variable store.
You can write `the file "v:myFile.zip" should be downloaded` to check if a file `super-file.zip` was downloaded.
---
## `the file ":fileName" contains table data stored under ":variableName" variable`
This step allows you to compare an xls/xlsx file `:fileName` with an existing data stored under `:variableName` variable.
The data under `:variableName` must be an array of objects representing each row of file.
---
================================================
FILE: website/versioned_docs/version-2.4.0/steps-forms.md
================================================
---
id: version-2.4.0-steps-forms
title: Forms
original_id: steps-forms
---
# Steps used to fill forms:
## `I fill the ":formName" form with:`
Allows to fill the form with the name `:formName` and values provided as an array of inputs and values. The element with name `:formName` must be defined inside the
`currentPage` page object.
Input and values should be provided as an array for example:
```gherkin
I fill the "myForm" form with:
| inputElement | value to be typed into field |
| textareaElement | value to be typed into textarea |
| radioElement | radio value to be selected |
| checkboxElement | checkbox label value to be selected |
```
By default we support all basic HTML field types (text inputs, checkboxes, radios, selects, files and textareas)
In order to use the default handlers the elements you use as input must follow pattern:
For inputs:
`this.element = $('input')` - element should point at input you want to fill
For textareas:
`this.element = $('textarea')` - element should point at textarea you want to fill
For file input:
`this.element = $('input')` - element should point at input you want to fill and value should a filename of file from `data` directory
For selects:
`this.element = $('select')` - element should point at select and value should be an value of expected option
For radios:
`this.element = $$('radio[name="name-of-radio"]')` - element should be an array of all radio input of given name and value should be an value of radio you wish to select
For checkboxes:
Checkbox should have a html like:
```html
```
`this.element = $$('checkbox[name="name-of-radio"]')` - element should be an array of all checkboxes of given name and value should be a text from label of checkbox you want to fill
You can use all kind of transformers to as a values for fields.
---
## `the ":formName" form is filled with:`
The same as `I fill the ":formName" form with:` but allows to check if a form is filled with a given set of values.
You can use all kind of transformers to as a expected values for fields.
The only difference is for file fields. You cannot check uploaded files just like that, however we prepared a special type of handler
that allow to check for some information related to a specific file.
Let's assume that after upload we display an information with a file name of a uploaded file.
You can use a special handler that requires to set a element with a postfix `Uploaded`. This will check if a value of that element is the same as you expected.
For example you can write a step like this:
```gherkin
the "myform" form is filled with:
| myFileUploaded | file.txt |
```
Keep in mind that the element name must end with `Uploaded` for example:
`this.myFileUploaded = $('p.some-file')`
---
## `the error messages should be displayed:`
Allows you to specify the error messages that should be displayed for a specific elements.
This step requires an array of format:
```gherkin
the error messages should be displayed:
| myElement | my error message |
```
You can use dictionaries in this step as follows:
```gherkin
the error messages should be displayed:
| myElement | d:dictionaryName:dictionaryKey |
```
---
================================================
FILE: website/versioned_docs/version-2.4.0/steps-generators.md
================================================
---
id: version-2.4.0-steps-generators
title: Generators
original_id: steps-generators
---
# Steps used to generate values:
## `I generate random ":generator:param:param" as ":variableName"`
Allows to generate a random value using the generator specified by `:generator:param:param`.
The generator must be defined inside the any of the `generators` directories specified in `kakunin.conf.js` file `default: generators`.
If the generator exists, then the value will be saved under the `:variableName` and can be accessed by:
* steps using variable store
* by calling `variableStore.getVariableValue(:variableName)`
* by using variable store transformer on supported steps `v:variableName`
---
================================================
FILE: website/versioned_docs/version-2.4.0/steps-navigation.md
================================================
---
id: version-2.4.0-steps-navigation
title: Navigation
original_id: steps-navigation
---
# Steps used for navigation on page:
## `I visit the ":pageFileName" page`
Visits the url of the page object with `:pageFileName` name.
In order to make it work we create a page object file with a name of `:pageFileName`.
For example in case of: `I visit the "myPage" page` there should be a file `myPage.js` inside the `pages` directory.
If we have a page object with a name `somePageObject.js` defined inside `pages` directory then:
`Given I visit the "somePageObject" page`
will set `this.currentPage` variable to `somePageObject` page and we should end up on `somePageObject` url.
---
## `I visit the ":pageFileName" page with parameters:`
The same as `I visit the ":pageFileName" page` except allows to pass url parameters.
If url of `myPage` is defined as `this.url = /orders/:orderId/products/:productId` then we can use this step to visit this page by:
```gherkin
I visit the "myPage" page with parameters:
| orderId | 1 |
| productId | 2 |
```
this will result in visiting the `/orders/1/product/2` page.
---
## `the ":pageFileName" page is displayed`
Checks if current browser url matches url of `pageFileName` page object.
If the url matches expected pattern then
`this.currentPage` variable is set to `pageFileName` page object.
---
================================================
FILE: website/versioned_docs/version-2.4.0/transformers.md
================================================
---
id: version-2.4.0-transformers
title: Transformers
original_id: transformers
---
Transformers allow you to transform values passed to form steps.
For example a select requires to pass a value `/options/1b30f17e-e445-4d28-a30c-dedad95829ab`. This one is quite unreadable, but with the help of transformers you are
able to write it like this: `d:options:someOptionName`.
In real-life example it will look similar to:
```gherkin
I fill the "myForm" form with:
| inputElement | d:someDictionary:someKey |
| textareaElement | g:someGenerator |
| radioElement | v:someVariableName |
| checkboxElement | standard value |
```
There are 3 types of built-in transformers:
## Dictionaries
Dictionaries allows you to transform a value A to value B using a simple key->value transformation.
You can run a dictionary transformer by providing dictionary prefix `d:`, specifying the dictionary name and key that should be used as a value provider. For example:
`d:myDictionaryName:myDictionaryKey`
this example assumes that there is a dictionary that supports name `myDictionaryName` and it has `myDictionarKey` key.
You can read about dictionaries in `Extending Kakunin` section.
## Generators
Generators allows you to generate a value by using a specified generator.
This can be done by: `g:generatorName`.
If a generator supports parameters then you can specify them by:
`g:generatorName:param1:param2:...:paramN`
You can read more about generators in `Extending Kakunin` section.
## Variable store
Variable store allows you to fill the form with a value that was saved in previous steps of current running scenario.
This can be done by:
`v:variableName`
You can read more about variable store in `Extending Kakunin` section
================================================
FILE: website/versioned_docs/version-2.5.0/steps-elements.md
================================================
---
id: version-2.5.0-steps-elements
title: Elements
original_id: steps-elements
---
# Steps used to interact with elements:
## `I infinitely scroll to the ":elementName" element`
Allows to scroll through infinite scroll mechanism.
The `:elementName` is a name of a selector for loading trigger.
---
## `I wait for ":expectedConditionName" of the ":elementName" element`
Waits till element `:elementName` from `this.currentPage` meets criteria specified by `:expectedConditionName`.
You can use any of the Protractor's expected condition:
* `visibilityOf`
* `invisibilityOf`
etc.
Read more in Protractor's API documentation.
---
## `I wait for the ":elementName" element to disappear`
Waits till element `:elementName` disappears.
---
## `I scroll to the ":elementName" element`
Scrolls to element `:elementName` of `this.currentPage`. The element will be on bottom of the page.
---
## `I infinitely scroll to the ":elementName" element`
Allows to scroll till `:elementName` is visible. Useful for infinite scrolling functionality.
---
## `I press the ":keyName" key`
Performs a key press operation on `:keyName` key.
---
## `I click the ":elementName" element`
Performs a click action on element `:elementName` from `this.currentPage'
The child element must be specified by `:elementName` and must be available in `this.currentPage`.
---
## `I store the ":elementName" element text as ":variableName" variable`
Stores the text from element `:elementName` of `this.currentPage` under the `:variableName` so you can use it later.
---
## `I update the ":elementName" element text as ":variableName" variable`
Updates the variable `:variableName` value by value from element `:elementName` of `this.currentPage`.
---
## `I store the ":elementName" element text matched by ":matchingRegex" as ":variableName" variable`
Stores the part of the element `:elementName` text, that matches the `:matchingRegex` under the `:variableName` for later use.
---
## `the ":elementName"" element is visible`
Checks if element `:elementName` is visible and clickable
---
## `the ":elementName"" element is not visible`
Checks if element `:elementName` is available in HTML DOM but is not visible and clickable
---
## `the ":elementName" element is disabled`
Checks if element is disabled
---
## `I store table ":tableRow" rows as ":variableName" with columns:`
Allows to store a row specified columns from a table `:tableRow` and save it under `:variableName` as an array of objects.
This step requires a table of columns elements, for example:
```gherkin
I store table "someRow" rows as "someVariable" with columns:
| firstName |
| lastName |
| id |
```
In order to make it work there must be not only array element `this.someRow = $$('.rows')` in `this.currentPage`, but also
element `this.firstName = $('.firstName');` and so on.
The result of this step is an array of:
```javascript
[
[
'firsRowFirstNameValue',
'firsRowLastNameValue'
'firsRowIdValue'
]
...
]
```
---
## `there are following elements in table ":elementName":`
Allows to check if a child elements of `:elementName` have a specified content.
This steps allows you to specify an array of child elements that will be checked against expected values.
For example:
```gherkin
there are following elements in table "myTable":
| id | firstName | lastName |
| t:1 | t:Adam | t:Doe |
| t:2 | t:John | t:Doe |
```
First row must specify columns elements. Starting from second row we must provide a matchers for each row that must be displayed.
This step checks exact match, so if the table has 5 rows, there must be a 5 rows in this table.
We can specify only a set of columns (for example if a table has 5 columns, we can specify only 1).
---
## `there are "numberExpression" following elements for element ":elementName":`
Allows to check if a child elements of `:elementName` have a specified content. Element should be an array, for example:
```html
1
2
```
for this case the `:elementName` should be specified as `$$('table tr')`.
Allows to check if a number of elements is the one that we expect.
`numberExpression` is a supported expression from `chai.js` library:
* `equal N` where N is a number
* `at least N` where N is a number
* `above N` where N is a number
* `below N` where N is a number
* `within N M` where N and M are a numbers
and so on. You can check expressions on `chai.js` API dock for BDD.
This step requires an array of elements to be checked. For example:
```gherkin
there are "equal 5" following elements for element "myList":
| viewButton | f:isClickable |
| id | r:idRegex |
```
The child elements must be an elements, for example `this.viewButton = $('button.viewButton');`.
You can use all kind of matchers here.
---
## `there is element ":elementName" with value ":matcher"`
Allows to check if `:elementName` has a value that matches the `:matcher`.
---
## `there is element ":elementName" containing ":matcher" text`
Allows to check if `:elementName` contains a text that matches the `:matcher`.
---
## `there is element ":elementName" matching ":matcher" matcher`
Allows to check if `:elementName` matches the given type of `:matcher`. For example:
```gherkin
there is element "button" matching "isClickable" matcher
```
---
## `there is element ":elementName" with regex ":matcher"`
Allows to check if `:elementName` matches given type of regex. For example:
```gherkin
there is element "input" with regex "notEmpty"
```
---
## `there is no element ":elementName" with value ":matcherName"`
Allows to check if there is no `:elementName` that matches the `:matcher`.
---
## `there is no element ":elementName" containing ":matcher" text`
Allows to check if `:elementName` doesn't contain a text that matches the `:matcher`.
---
## `there is no element ":elementName" matching ":matcher" matcher`
Allows to check if `:elementName` is not matching the given type of `:matcher`.
---
## `there is no element ":elementName" with regex ":matcher"`
Allows to check if `:elementName` is not matching given type of regex.
---
## `there are "numberExpression" ":elementName" elements`
Allows to check if a number of `:elementName` elements is the same as we expect.
`numberExpression` is a supported expression from `chai.js` library:
* `equal N` where N is a number
* `at least N` where N is a number
* `above N` where N is a number
* `below N` where N is a number
* `within N M` where N and M are a numbers
and so on. You can check expressions on `chai.js` API dock for BDD.
`:elementName` should be specified as an array, for example:
```html
1
2
```
for this case the `:elementName` should be specified as `$$('table tr')`.
---
## `every ":elementName" element should have the same value for element ":columnElementName"`
Allows to check if every row defined by `:elementName` has the same value for a column `:columnElementName`.
`:elementName` must be an array of elements
`:columnElementName` must be an element, for example:
```html
1
1
```
for this case the `:elementName` should be specified as `$$('table tr')` and we can specify column element
`this.myColumn = $('td');`. This allows us to write:
`every "myElement" element should have the same value for element "myColumn"`
---
## `the element ":elementName" should have an item with values:`
Allows to check if any of the child elements of `:elementName` have a specified content (one matching element is enough). Element should be an array, for example:
```html
1
2
```
for this case the `:elementName` should be specified as `$$('table tr')`.
This step requires an array of elements to be checked. For example:
```gherkin
the element "myList" should have an item with values:
| id | t:1 |
```
The child elements must be an elements, for example `this.id = $('td');`.
You can use all kind of matchers here.
---
## `the element ":elementName" should not have an item with values:`
Allows to check if the child elements of `:elementName` have a different content than that given in the table. Element should be an array, for example:
```html
1
2
```
for this case the `:elementName` should be specified as `$$('table tr')`.
This step requires an array of elements to be checked. For example:
```gherkin
the element "myList" should have an item with values:
| id | t:does-not-exist |
```
The child elements must be an elements, for example `this.id = $('td');`.
You can use all kind of matchers here.
---
## `I drag ":elementDrag" element and drop over ":elementDrop" element`
Clicks on `:elementDrag` and moves it onto `:elementDrop` while left mouse button is pressed, and then release it.
Note: This step is not working on HTML5!
---
================================================
FILE: website/versioned_sidebars/version-2.4.0-sidebars.json
================================================
{
"version-2.4.0-docs": {
"Documentation": [
"version-2.4.0-quickstart",
"version-2.4.0-index",
"version-2.4.0-configuration",
"version-2.4.0-how-it-works"
],
"Built in mechanisms": [
"version-2.4.0-matchers",
"version-2.4.0-transformers",
"version-2.4.0-extending"
],
"Features": [
"version-2.4.0-cross-browser",
"version-2.4.0-parallel-testing",
"version-2.4.0-performance-testing",
"version-2.4.0-docker"
],
"Steps": [
"version-2.4.0-steps-navigation",
"version-2.4.0-steps-forms",
"version-2.4.0-steps-elements",
"version-2.4.0-steps-files",
"version-2.4.0-steps-generators",
"version-2.4.0-steps-debug"
]
}
}
================================================
FILE: website/versions.json
================================================
[
"2.5.0",
"2.4.0"
]