Showing preview only (2,736K chars total). Download the full file or copy to clipboard to get everything.
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 <span style="color:red">Breaking change! Take a look to the MIGRATION-3.0.0.MD</span>
#### 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`
<span style="color:red">Keep in mind that all capabilities that you set via CLI will be ignored!</span>
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` <span style="color:blue">(supported versions IE8, IE9, IE10, IE11)</span>
## 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; i<values.length; i++) {
if (values[i] !== 'foo' && values[i] !== 'bar') {
return false;
}
}
return true;
}
compare(values, order) {
for (let i = 1; i < values.length; i++) {
const previousValue = values[i - 1];
const currentValue = values[i];
if (previousValue === currentValue) {
return Promise.reject('Wrong order');
}
}
return Promise.resolve('Foo bar!');
}
};
comparators.addComparator(new MyComparator());
```
### Form handlers
Form handlers allows you to fill the form inputs and check value of filled fields
You can add your own handlers:
```javascript
const { handlers } = require('kakunin');
const MyHandler {
constructor() {
this.registerFieldType = false;
this.fieldType = 'default';
}
isSatisfiedBy(element, elementName) {
return Promise.resolve(elementName === 'someElementName');
}
handleFill(page, elementName, desiredValue) {
return page[elementName].isDisplayed()
.then(function () {
return page[elementName].clear().then(function () {
return page[elementName].sendKeys(desiredValue);
});
}
);
}
handleCheck(page, elementName, desiredValue) {
return page[elementName].isDisplayed()
.then(function () {
return page[elementName].getAttribute('value').then(function (value) {
if (value === desiredValue) {
return Promise.resolve();
}
return Promise.reject(`Expected ${desiredValue} got ${value} for text input element ${elementName}`);
});
}
);
}
};
handlers.addHandler(new MyHandler());
```
### Transformers
Transformers can be used in steps `When I fill the "form" form with:` and `And the "joinOurStoreForm" form is filled with:`.
Existing transformers:
- generators (prefix: `g:`)
- dictionaries (prefix: `d:`)
- variableStore (prefix: `v:`)
Transformers can be used in mentioned steps by using specific 'prefix', parameters are sent after `:` sign.
Example:
`g:generatorName:param:param`
You can add your own handlers:
```javascript
const { transformers } = require('kakunin');
class MyTransformer {
isSatisfiedBy(prefix) {
return 'yourPrefix:' === prefix;
}
transform(value) {
//code
}
}
transformers.addTransformer(new MyTransformer());
```
### Email checking service
You can easily check emails with Kakunin. By default we give you MailTrap client implementation, but you can easily add your own client.
```javascript
const { emailService } = require('kakunin');
class MyEmailService {
//you have access to full kakunin config
isSatisfiedBy(config) {
return config.email.type === 'my-custom-email-service';
}
//method used to clear emails before tests
clearInbox() {
...
}
//method used to get emails - this method should return emails in format described below
getEmails() {
...
}
//method used to retrive atachments for given email - should return attachments in format described below
getAttachments(email) {
...
}
//method used to mark given email as read
markAsRead(email) {
...
}
}
emailService.addAdapter(new MyEmailService());
```
Emails should be returned as an array of objects with given schema:
```javascript
[
{
"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:
```javascript
[
{
"id": 1737,
"message_id": 54508,
"filename": "Photos.png",
"attachment_type": "attachment",
"content_type": "image/png",
"content_id": "",
"transfer_encoding": "base64",
"attachment_size": 213855,
"created_at": "2013-08-16T00:39:34.677+03:00",
"updated_at": "2013-08-16T00:39:34.677+03:00",
"attachment_human_size": "210 KB",
"download_path": "/api/v1/inboxes/3/messages/54508/attachments/1737/download"
}
]
```
this is MailTrap attachment format.
================================================
FILE: docs/headless.md
================================================
---
id: headless
title: Headless
---
# Headless browser control
Currently only Firefox and Google Chrome browser can be run headless.
For the rest of the supported browsers this flag is ignored.
To control headless via command line:
- `npm run kakunin -- --headless` opens browser in headless mode
- `npm run kakunin -- --headless=true` opens browser in headless mode
- `npm run kakunin -- --headless=false` opens browser in normal mode
Also, there is a possibility to set `"headless: "true"` or `"headless: "false"` in the `kakunin.conf.js` config file.
<span style="color:red">Keep in mind that CLI has greater prority than the cofig file (overides settings on runtime).</span>
================================================
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/<user>/TSHProjects/test/reports
Created directory at path /Users/<user>/TSHProjects/test/reports/report
Created directory at path /Users/<user>/TSHProjects/test/reports/report/features
Created directory at path /Users/<user>/TSHProjects/test/reports/performance
Created directory at path /Users/<user>/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:
`<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`
================================================
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 <number of instances>` where `number of instances` is a number.
Example:
- `npm run kakunin -- --chrome --parallel 2`
<span style="color:red">Keep in mind that the merged report is available in the `reports/report/index.html` file. text</span>
## Specify pattern per each instance
- `npm run kakunin -- --parallel <number of instances> --pattern <regex to much feature> --pattern <regex to much feature>`
Keep in mind that:
- the number given in `parallel` must be equal to passed `patterns`
- `<number of instances>` is a number of instances of the specified browser
- `<regex>` 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
<table>
<tr>
<td>1</td>
</tr>
<tr>
<td>2</td>
</tr>
</table>
```
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
<select name="list" id="personlist">
<option value="1">Person 1</option>
<option value="2">Person 2</option>
<option value="3">Person 3</option>
<option value="4">Person 4</option>
</select>
```
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
<table>
<tr>
<td>1</td>
</tr>
<tr>
<td>2</td>
</tr>
</table>
```
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
<table>
<tr>
<td>1</td>
</tr>
<tr>
<td>1</td>
</tr>
</table>
```
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
<table>
<tr>
<td>1</td>
</tr>
<tr>
<td>2</td>
</tr>
</table>
```
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
<table>
<tr>
<td>1</td>
</tr>
<tr>
<td>2</td>
</tr>
</table>
```
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
<label>
My checkbox
<input type="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:
```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 %}
<p>Absolute page</p>
{% endblock %}
================================================
FILE: functional-tests/www/views/drag-and-drop/index.njs
================================================
{% extends 'layout/default.njs' %}
{% block content %}
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<style>
#draggable { width: 100px; height: 100px; padding: 0.5em; float: left; margin: 10px 10px 10px 0; }
#droppable { width: 150px; height: 150px; padding: 0.5em; float: left; margin: 10px; }
</style>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$( function() {
$( "#draggable" ).draggable();
$( "#droppable" ).droppable({
drop: function( event, ui ) {
$( this )
.addClass( "ui-state-highlight" );
}
});
} );
</script>
<div id="droppable"></div>
<img id="draggable" src="/assets/kittens.jpg" width="336" height="69">
{% endblock %}
================================================
FILE: functional-tests/www/views/form/disappear.njs
================================================
{% block content %}
<button id="button">Click me.</button>
<script>
document.getElementById("button").onclick = function() {myFunction()};
function myFunction() {
setTimeout(function(){
document.getElementById("button").remove();
}, 2000)
}
</script>
{% endblock %}
================================================
FILE: functional-tests/www/views/form/select.njs
================================================
{% extends 'layout/default.njs' %}
{% block content %}
<form method="post" action="/form/select/post">
<select name="list" id="personlist">
<option value="1">Person1</option>
<option value="2">Person2</option>
<option value="3">Person3</option>
<option value="4">Person4</option>
</select>
<input type="submit" value="Send"/>
</form>
{% endblock %}
================================================
FILE: functional-tests/www/views/form/simple.njs
================================================
{% extends 'layout/default.njs' %}
{% block content %}
<form method="post" action="/form/simple/post">
<label for="name">
Name
<input type="text" name="name" id="name" value="{{ form.name | default('') }}"/>
</label>
<label for="description">
Description
<textarea name="description" id="description">{{ form.description | default('') }}</textarea>
</label>
<label for="checkbox-option-1">
Checkbox Option 1
<input type="checkbox" name="checkboxes" id="checkbox-option-1" value="first-checkbox-option" {{ 'checked' if form.checkboxes and form.checkboxes.indexOf('first-checkbox-option') >= 0 }}/>
</label>
<label for="checkbox-option-2">
Checkbox Option 2
<input type="checkbox" name="checkboxes" id="checkbox-option-2" value="second-checkbox-option" {{ 'checked' if form.checkboxes and form.checkboxes.indexOf('second-checkbox-option') >= 0 }}/>
</label>
<label for="checkbox-option-3">
Checkbox Option 3
<input type="checkbox" name="checkboxes" id="checkbox-option-3" value="third-checkbox-option" {{ 'checked' if form.checkboxes and form.checkboxes.indexOf('third-checkbox-option') >= 0 }}/>
</label>
<label for="radio-option-1">
Radio Option 1
<input type="radio" name="radios" id="radio-option-1" value="first-radio-option" {{ 'checked' if form.radios === 'first-radio-option' }}/>
</label>
<label for="radio-option-2">
Radio Option 2
<input type="radio" name="radios" id="radio-option-2" value="second-radio-option" {{ 'checked' if form.radios === 'second-radio-option' }}/>
</label>
<label for="radio-option-3">
Radio Option 3
<input type="radio" name="radios" id="radio-option-3" value="third-radio-option" {{ 'checked' if form.radios === 'third-radio-option' }}/>
</label>
<label for="status">
Status
<select name="status">
<option value>Select option</option>
<option value="active" {{ 'selected' if form.status === 'active' }}>Active</option>
<option value="inactive" {{ 'selected' if form.status === 'inactive' }}>Inactive</option>
<option value="unknown" {{ 'selected' if form.status === 'unknown' }}>Unknown</option>
</select>
</label>
<input type="submit" value="Send"/>
</form>
{% endblock %}
================================================
FILE: functional-tests/www/views/index.njs
================================================
{% extends 'layout/default.njs' %}
{% block content %}
<div class="available-examples-links">
<a name="linkPage" href="/form/simple">Drag and drop example</a><br/>
<a name="linkPage" href="/form/simple">Simple form example</a><br/>
<a name="linkPage" href="/absolute-page">Absolute page</a><br/>
<a name="linkPage" href="https://www.google.pl">Google page</a><br/>
<a name="linkPage" class="valueForClickStep" href="/tabular-data">Simple table example</a><br/>
<a name="linkPage" class="valueForClickStep" href="/form/disappear"> Simple button disappear</a><br/>
<a name="linkPage" class="appearTable" href="/wait-for-appear/table"> Simple wait for table</a><br/>
<a name="linkPage" class="appearForm" href="/wait-for-appear/form"> Simple wait for form</a><br/>
<a name="linkPage" class="matchers" href="/matchers">Simple matchers</a><br/>
<a name="linkPage" class="matchersInNewTab" target="_blank" href="/matchers">Matchers in a new tab</a><br/>
<a name="linkPage" href="/form/select">Simple select example</a><br/>
</div>
{% endblock %}
================================================
FILE: functional-tests/www/views/layout/default.njs
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example app</title>
</head>
<body style="background: url('./assets/kittens.jpg')">
{% block content %}
{% endblock %}
</body>
</html>
================================================
FILE: functional-tests/www/views/matchers/matchers.njs
================================================
{% block content %}
<style>
.colored {
display: block;
background-color: #00b8d4;
outline: none;
}
</style>
<p class="date-matcher">Date/Time: <span class="current_date colored"id="datetime"></span></p>
{% endblock %}
{% block javascript %}
<script>
window.onload = function () {
let dt = new Date().toISOString().slice(0, 10);
document.getElementById('datetime').innerHTML = dt.toLocaleString();
};
</script>
{% endblock javascript %}
================================================
FILE: functional-tests/www/views/navigation/page.njs
================================================
{% extends 'layout/default.njs' %}
{% block content %}
<p class="pageId">{{ pageId }}</p>
<p class="title">{{ title }}</p>
<p class="queryParam1">{{ queryParam1 }}</p>
<p class="queryParam2">{{ queryParam2 }}</p>
{% endblock %}
================================================
FILE: functional-tests/www/views/table/tabular-data.njs
================================================
{% extends 'layout/default.njs' %}
{% block content %}
<table>
<tr>
<td class="index">1</td>
<td class="descending-sort">4</td>
<td class="id">MY_CUSTOM_ID_1</td>
<td class="name">Some custom name 1</td>
<td class="options">
<button class="view">View</button>
</td>
</tr>
<tr>
<td class="index">2</td>
<td class="descending-sort">3</td>
<td class="id">MY_CUSTOM_ID_2</td>
<td class="name">Some custom name 2</td>
<td class="options">
<button class="view">View</button>
</td>
</tr>
<tr>
<td class="index">3</td>
<td class="descending-sort">2</td>
<td class="id">MY_CUSTOM_ID_3</td>
<td class="name">Some custom name 3</td>
<td class="options">
<button class="view">View</button>
</td>
</tr>
<tr>
<td class="index">4</td>
<td class="descending-sort">1</td>
<td class="id">MY_CUSTOM_ID_4</td>
<td class="name">Some custom name 4</td>
<td class="options">
<button class="view">View</button>
</td>
</tr>
</table>
{% endblock %}
================================================
FILE: functional-tests/www/views/wait-for-appear/form.njs
================================================
{% block content %}
<style>
.hidden {
display: none;
}
.colored {
display: block;
background-color: #00b8d4;
outline: none;
}
</style>
<button id="hiddenContent" class="hidden" onclick="myFunction();">Render Form</button>
<hr>
<div id="hiddenForm" class="hidden">
<form method="post" action="/wait-for-appear/form/post">
<label for="name">
Name
<input type="text" name="name" id="name" value="{{ form.name | default('') }}"/>
</label>
<label for="description">
Description
<textarea name="description" id="description">{{ form.description | default('') }}</textarea>
</label>
<label for="checkbox-option-1">
Checkbox Option 1
<input type="checkbox" name="checkboxes" id="checkbox-option-1"
value="first-checkbox-option" {{ 'checked' if form.checkboxes and form.checkboxes.indexOf('first-checkbox-option') >= 0 }}/>
</label>
<label for="checkbox-option-2">
Checkbox Option 2
<input type="checkbox" name="checkboxes" id="checkbox-option-2"
value="second-checkbox-option" {{ 'checked' if form.checkboxes and form.checkboxes.indexOf('second-checkbox-option') >= 0 }}/>
</label>
<label for="checkbox-option-3">
Checkbox Option 3
<input type="checkbox" name="checkboxes" id="checkbox-option-3"
value="third-checkbox-option" {{ 'checked' if form.checkboxes and form.checkboxes.indexOf('third-checkbox-option') >= 0 }}/>
</label>
<label for="radio-option-1">
Radio Option 1
<input type="radio" name="radios" id="radio-option-1"
value="first-radio-option" {{ 'checked' if form.radios === 'first-radio-option' }}/>
</label>
<label for="radio-option-2">
Radio Option 2
<input type="radio" name="radios" id="radio-option-2"
value="second-radio-option" {{ 'checked' if form.radios === 'second-radio-option' }}/>
</label>
<label for="radio-option-3">
Radio Option 3
<input type="radio" name="radios" id="radio-option-3"
value="third-radio-option" {{ 'checked' if form.radios === 'third-radio-option' }}/>
</label>
<label for="status">
Status
<select name="status">
<option value>Select option</option>
<option value="active" {{ 'selected' if form.status === 'active' }}>Active</option>
<option value="inactive" {{ 'selected' if form.status === 'inactive' }}>Inactive</option>
<option value="unknown" {{ 'selected' if form.status === 'unknown' }}>Unknown</option>
</select>
</label>
<input type="submit" value="Send"/>
</form>
</div>
{% endblock %}
{% block javascript %}
<script>
window.onload = function() {
setTimeout(function() {
document.getElementById('hiddenContent').className = 'colored';
}, 4000);
};
function myFunction() {
setTimeout(function() {
document.getElementById('hiddenForm').removeAttribute('class');
}, 4000);
}
</script>
{% endblock javascript %}
================================================
FILE: functional-tests/www/views/wait-for-appear/table.njs
================================================
{% block content %}
<style>
.hidden {
display: none;
}
.colored {
display: block;
background-color: #00b8d4;
outline: none;
}
</style>
<button id="hiddenContent" class="hidden" onclick="myFunction()">Render Table</button>
<hr>
<table id="hiddenTable" class="hidden">
<tr>
<td class="index">1</td>
<td class="descending-sort">4</td>
<td class="id">MY_CUSTOM_ID_1</td>
<td class="name">Some custom name 1</td>
<td class="options">
<button class="view">View</button>
</td>
</tr>
<tr>
<td class="index">2</td>
<td class="descending-sort">3</td>
<td class="id">MY_CUSTOM_ID_2</td>
<td class="name">Some custom name 2</td>
<td class="options">
<button class="view">View</button>
</td>
</tr>
<tr>
<td class="index">3</td>
<td class="descending-sort">2</td>
<td class="id">MY_CUSTOM_ID_3</td>
<td class="name">Some custom name 3</td>
<td class="options">
<button class="view">View</button>
</td>
</tr>
<tr>
<td class="index">4</td>
<td class="descending-sort">1</td>
<td class="id">MY_CUSTOM_ID_4</td>
<td class="name">Some custom name 4</td>
<td class="options">
<button class="view">View</button>
</td>
</tr>
</table>
{% endblock %}
{% block javascript %}
<script>
window.onload = function(){
setTimeout(function() {
document.getElementById('hiddenContent').className = 'colored';
}, 4000)
}
function myFunction() {
setTimeout(function() {
document.getElementById('hiddenTable').removeAttribute("class")
}, 4000);
}
</script>
{% endblock javascript %}
================================================
FILE: package.json
================================================
{
"name": "kakunin",
"version": "3.0.3",
"description": "End-to-end testing framework",
"homepage": "https://thesoftwarehouse.github.io/Kakunin/",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/TheSoftwareHouse/Kakunin.git"
},
"author": {
"name": "The Software House",
"url": "http://tsh.io"
},
"contributors": [
{
"name": "Adam Polak"
},
{
"name": "Mariusz Richtscheid"
},
{
"name": "Tomasz Górski"
},
{
"name": "Jakub Paluch"
},
{
"name": "Szymon Stanisz"
},
{
"name": "Wojciech Wójcik"
},
{
"name": "Józef Szymala"
},
{
"name": "Adam Nowrot"
}
],
"bin": {
"kakunin": "./dist/cli.js"
},
"main": "./dist/index",
"scripts": {
"units": "cross-env jest --setupFiles ./src/tests/init.ts --no-cache",
"functional": "npm run build && cd functional-tests && rm -rf package-lock.json && rm -rf node_modules && npm i && npm run test",
"test": "npm run units && npm run functional",
"test-ci": "npm run units && npm run build && cd functional-tests && npm i && npm run test-ci",
"lint": "tslint -c tslint.json 'src/**/*.ts'",
"lint-fix": "tslint -c tslint.json 'src/**/*.ts' --fix",
"prettier": "prettier --write 'src/**/*.ts'",
"type-check": "tsc --noEmit",
"build": "tsc",
"prepublishOnly": "npm run build && npm run test"
},
"engines": {
"node": ">=7.7.3",
"npm": ">=4.1.2"
},
"devDependencies": {
"@types/extend": "3.0.1",
"@types/faker": "5.1.7",
"@types/glob": "7.1.3",
"@types/inquirer": "7.3.1",
"@types/jest": "26.0.22",
"@types/lodash": "4.14.168",
"@types/minimist": "1.2.1",
"@types/mkdirp": "1.0.1",
"@types/moment": "2.13.0",
"@types/node": "14.14.37",
"@types/node-fetch": "2.5.8",
"@types/protractor": "4.0.0",
"@types/shelljs": "0.8.8",
"@types/cucumber": "6.0.1",
"fetch-mock": "9.11.0",
"husky": "6.0.0",
"lint-staged": "10.5.4",
"prettier": "2.2.1",
"ts-jest": "26.5.4",
"tslint": "6.1.3",
"tslint-config-prettier": "1.18.0",
"tslint-sonarts": "1.9.0",
"typescript": "4.2.3"
},
"dependencies": {
"ajv": "8.0.1",
"browsermob-proxy": "1.0.10",
"browserstack-local": "1.4.8",
"chai": "4.3.4",
"chalk": "4.1.0",
"child_process": "1.0.2",
"cross-env": "7.0.3",
"cucumber": "6.0.5",
"extend": "3.0.2",
"faker": "5.5.1",
"glob": "7.1.6",
"inquirer": "8.0.0",
"jasmine": "3.7.0",
"jasmine-reporters": "2.4.0",
"jasmine-spec-reporter": "6.0.0",
"jest": "26.6.3",
"lodash": "4.17.21",
"minimist": "1.2.5",
"mkdirp": "1.0.4",
"moment": "2.29.1",
"node-env-file": "0.1.8",
"node-fetch": "2.6.1",
"node-xlsx": "0.16.1",
"path": "0.12.7",
"protractor": "7.0.0",
"protractor-cucumber-framework": "8.0.1",
"protractor-multiple-cucumber-html-reporter-plugin": "1.8.1",
"shelljs": "0.8.4",
"sugar-date": "2.0.6",
"webdriver-manager": "12.1.8"
},
"jest": {
"collectCoverageFrom": [
"src/**/*.ts",
"!src/**/index.ts",
"!src/**/*.d.ts"
],
"transform": {
"^.+\\.(t|j)sx?$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
],
"globals": {
"ts-jest": {
"tsConfig": "tsconfig.test.json"
}
}
},
"lint-staged": {
"src/**/*.ts": [
"prettier --write",
"npm run lint",
"git add"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}
}
================================================
FILE: readme.md
================================================
<p align="center">
<img src="/data/kakunin_logo.png" alt="kakunin.png" width="550"/>
</p>
<p align="center">
<img src="/data/travis.png" alt="travis.png" width="25" />
Current travis build:
<a href="https://travis-ci.org/TheSoftwareHouse/Kakunin"><img src="https://travis-ci.org/TheSoftwareHouse/Kakunin.svg?branch=master" alt="build status" height="18"></a>
 
<img src="/data/npm.png" alt="npm.png" width="25"/>
Current npm version:
<a href="https://badge.fury.io/js/kakunin"><img src="https://badge.fury.io/js/kakunin.svg" alt="npm version" height="18"></a>
</p>
<p align="center">
<a href="#key-features">Key Features</a> •
<a href="#documentation">Documentation</a> •
<a href="#contributing">Contributing</a> •
<a href="#issues">Issues</a> •
<a href="#you-may-also-like-our-other-projects">Related</a> •
<a href="#about-us">About us</a>
</p>
<p align="center">
<img src="/data/pageObjectFeature.gif" alt="pageObjectFeature.gif"/>
</p>
<h1>
</h1>
### **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 <a href="mailto:security.opensource@tsh.io"><b>email</b></a>
---
### **You may also like our other projects:**
- **[Babelsheet-js](https://github.com/TheSoftwareHouse/babelsheet-js)**
- **[Fogger](https://github.com/TheSoftwareHouse/fogger)**
---
### **About us:**
<p align="center">
<a href="https://tsh.io/pl"><b>The Software House</b></a>
 
<img src="/data/tsh.png" alt="tsh.png" width="50" />
</p>
================================================
FILE: src/cli.ts
================================================
#!/usr/bin/env node
import * as path from 'path';
import * as fs from 'fs';
import * as os from 'os';
import * as childProcess from 'child_process';
import * as envfile from 'node-env-file';
import { createTagsCLIArgument, filterCLIArguments, getConfigPath, isInitCommand } from './core/cli/cli.helper';
import initializer from './core/cli/initializer';
const commandArgs = require('minimist')(process.argv.slice(2));
envfile(process.cwd() + '/.env', { raise: false, overwrite: false });
if (isInitCommand(process.argv)) {
(async () => {
await initializer.initConfig(commandArgs);
await initializer.generateProjectStructure();
})();
} else {
const optionsToFilter = ['config', 'projectPath', 'disableChecks', 'tags'];
const argv = [
'./node_modules/kakunin/dist/protractor.conf.js',
`--config=${getConfigPath('kakunin.conf.js', commandArgs.config, process.cwd())}`,
`--projectPath=${process.cwd()}`,
'--disableChecks',
...createTagsCLIArgument(commandArgs),
...filterCLIArguments(optionsToFilter)(commandArgs),
];
const protractorExecutable = os.platform() === 'win32' ? 'protractor.cmd' : 'protractor';
if (
!fs.existsSync(
path.join(
process.cwd(),
'node_modules',
'protractor',
'node_modules',
'webdriver-manager',
'selenium',
'update-config.json'
)
)
) {
childProcess.execSync(
path.join(process.cwd(), 'node_modules', '.bin', 'webdriver-manager update --ie --versions.standalone=3.14.0')
);
}
childProcess
.spawn(path.join('node_modules', '.bin', protractorExecutable), argv, {
stdio: 'inherit',
cwd: process.cwd(),
})
.once('exit', code => {
console.log('Protractor has finished');
process.exit(code);
});
}
================================================
FILE: src/comparators/comparator/date.comparator.spec.ts
================================================
import { DateComparator, supportedFormats } from './date.comparator';
describe('Date comparator', () => {
it('is satisfied by dates', () => {
const dates = ['20-12-2017', '20-12-17', '20/12/2017', '20/12/17'];
expect(DateComparator.isSatisfiedBy(dates)).toEqual(true);
});
it('supports only specified date formats', () => {
const dates = ['12-20-2017', '20/20/17', 'not-a-date', 31337];
dates.forEach(date => expect(DateComparator.isSatisfiedBy([date])).toEqual(false));
});
it('is pluggable', () => {
supportedFormats.push('MM-DD-YYYY');
expect(DateComparator.isSatisfiedBy(['12-20-2017'])).toEqual(true);
});
it('it returns resolved promise if dates are in ascending order', done => {
const ascendingDates = ['10-12-2017', '11-12-2017', '01-01-2018'];
DateComparator.compare(ascendingDates, 'ascending').then(() => done());
});
it('it returns resolved promise if dates are in descending order', done => {
const descendingDates = ['10-12-2019', '11-12-2018', '01-01-2017'];
DateComparator.compare(descendingDates, 'descending').then(() => done());
});
it('it returns rejected promise if dates are not in ascending order for ascending order expectation', done => {
const descendingDates = ['10-12-2019', '11-12-2018', '01-01-2017'];
DateComparator.compare(descendingDates, 'ascending').catch(() => done());
});
it('it returns rejected promise if dates are not in descending order for descending order expectation', done => {
const ascendingDates = ['10-12-2017', '11-12-2017', '01-01-2018'];
DateComparator.compare(ascendingDates, 'descending').catch(() => done());
});
});
================================================
FILE: src/comparators/comparator/date.comparator.ts
================================================
import * as moment from 'moment';
import { Comparator } from '../comparator.interface';
export const supportedFormats = ['DD-MM-YYYY', 'DD-MM-YY', 'DD/MM/YYYY', 'DD/MM/YY'];
const isValidDate = date => {
for (const format of supportedFormats) {
if (moment(date, format).isValid()) {
return true;
}
}
return false;
};
export const DateComparator: Comparator = {
isSatisfiedBy: values => {
for (const date of values) {
const found = isValidDate(date);
if (!found) {
return false;
}
}
return true;
},
compare: (values, order) => {
for (let i = 1; i < values.length; i++) {
const datePrevious = values[i - 1];
const date = values[i];
const foundPrevious = moment(
datePrevious,
supportedFormats.find(format => moment(datePrevious, format).isValid())
);
const found = moment(date, supportedFormats.find(format => moment(date, format).isValid()));
const previousTimestamp = foundPrevious.unix();
const currentTimestamp = found.unix();
if (order === 'ascending') {
if (currentTimestamp < previousTimestamp) {
return Promise.reject(`Date ${foundPrevious[1]} should be before ${found[1]}.`);
}
} else if (currentTimestamp > previousTimestamp) {
return Promise.reject(`Date ${found[1]} should be after ${foundPrevious[1]}.`);
}
}
return Promise.resolve();
},
};
================================================
FILE: src/comparators/comparator/index.ts
================================================
export * from './date.comparator';
export * from './number.comparator';
================================================
FILE: src/comparators/comparator/number.comparator.spec.ts
================================================
import { NumberComparator } from './number.comparator';
describe('Number comparator', () => {
it('is satisfied by numbers', () => {
const numbers = ['10', '10.09', '0.3'];
expect(NumberComparator.isSatisfiedBy(numbers)).toEqual(true);
});
it('supports only specified number records', () => {
const numbers = ['string', '12-12-2017'];
numbers.forEach(number => expect(NumberComparator.isSatisfiedBy([number])).toEqual(false));
});
it('it returns resolved promise if numbers are in ascending order', done => {
const ascendingNumbers = ['0.25', '10', '102', '105', '125.32'];
NumberComparator.compare(ascendingNumbers, 'ascending').then(() => done());
});
it('it returns resolved promise if numbers are in descending order', done => {
const descendingNumbers = ['102', '100', '10.03'];
NumberComparator.compare(descendingNumbers, 'descending').then(() => done());
});
it('it returns rejected promise if numbers are not in ascending order for ascending order expectation', done => {
const descendingNumbers = [331, 223.03, '123', '11.04', 0.1];
NumberComparator.compare(descendingNumbers, 'ascending').catch(() => done());
});
it('it returns rejected promise if numbers are not in descending order for descending order expectation', done => {
const ascendingNumbers = ['312', '324.43', '1323.320', 1321, 0.5];
NumberComparator.compare(ascendingNumbers, 'descending').catch(() => done());
});
it('it returns rejected promise if numbers are in incorrect format', done => {
const containsIncorrectNumber = ['10', '999,32', '8dsa', '20/12/2018'];
NumberComparator.compare(containsIncorrectNumber, 'ascending').catch(() => done());
});
});
================================================
FILE: src/comparators/comparator/number.comparator.ts
================================================
import { Comparator } from '../comparator.interface';
export const NumberComparator: Comparator = {
isSatisfiedBy: values => {
for (const value of values) {
const numberValue = Number(value);
if (Number.isNaN(numberValue)) {
return false;
}
}
return true;
},
compare: (values, order) => {
for (let i = 1; i < values.length; i++) {
const previousValue = Number(values[i - 1]);
const currentValue = Number(values[i]);
if (Number.isNaN(previousValue) || Number.isNaN(currentValue)) {
return Promise.reject(`${values[i - 1]} and ${values[i]} cannot be NaN after conversion to Number`);
}
if (order === 'ascending') {
if (previousValue > currentValue) {
return Promise.reject(`${previousValue} should be lower than ${currentValue}`);
}
} else if (previousValue < currentValue) {
return Promise.reject(`${previousValue} should be higher than ${currentValue}`);
}
}
return Promise.resolve();
},
};
================================================
FILE: src/comparators/comparator.interface.ts
================================================
export interface Comparator {
isSatisfiedBy(value: any[]): boolean;
compare(values: any[], order: string): Promise<string | void>;
}
================================================
FILE: src/comparators/comparators.spec.ts
================================================
import { create } from './comparators';
const comparators = create();
describe('Comparators', () => {
it('throws an error when no comparator was found', () => {
const values = ['unspecified-value', 'other-value'];
expect(() => comparators.compare(values, 'ascending')).toThrow(`Could not find comparator for ${values}.`);
});
it('compares values using comparator that can handle given set of values', done => {
const numbers = [2, 3, 4];
comparators.compare(numbers, 'ascending').then(() => done());
});
it('add new comparator', done => {
const myComparator = {
isSatisfiedBy: values => {
for (let i = 0; i < values.length; i++) {
if (values[i] !== 'foo' && values[i] !== 'bar') {
return false;
}
}
return true;
},
compare: (values, order) => {
for (let i = 1; i < values.length; i++) {
const previousValue = values[i - 1];
const currentValue = values[i];
if (previousValue === currentValue) {
return Promise.reject('Wrong order');
}
}
return Promise.resolve('Foo bar!');
},
};
comparators.addComparator(myComparator);
const myValues = ['foo', 'bar', 'foo', 'bar', 'foo', 'bar'];
comparators.compare(myValues, 'any').then(value => {
expect(value).toEqual('Foo bar!');
done();
});
});
});
================================================
FILE: src/comparators/comparators.ts
================================================
import * as comparators from './comparator';
import { Comparator } from './comparator.interface';
class Comparators {
constructor(
private availableComparators: Comparator[] = [comparators.DateComparator, comparators.NumberComparator]
) {}
public compare(values: any[], order: string): Promise<string | void> {
const comparator = this.findComparator(values);
if (comparator === undefined) {
throw new Error(`Could not find comparator for ${values}.`);
}
return comparator.compare(values, order);
}
public findComparator(values: any[]): Comparator {
return this.availableComparators.find(comparator => comparator.isSatisfiedBy(values));
}
public addComparator(comparator: Comparator): void {
this.availableComparators.push(comparator);
}
}
export const create = () => new Comparators();
================================================
FILE: src/comparators/index.ts
================================================
import { create } from './comparators';
export const comparators = create();
================================================
FILE: src/core/cli/cli.helper.spec.ts
================================================
import { isInitCommand, createTagsCLIArgument, getConfigPath, filterCLIArguments } from './cli.helper';
describe('Cli helpers', () => {
it('returns false if missing parameters for init command', () => {
expect(isInitCommand()).toEqual(false);
});
it('returns false if args is not an array', () => {
expect(isInitCommand('some-args')).toEqual(false);
});
it('returns false when args length is lower than 2', () => {
expect(isInitCommand(['arg1'])).toEqual(false);
});
it('returns false when third argument is not init', () => {
expect(isInitCommand(['arg1', 'arg2', 'arg3'])).toEqual(false);
});
it('returns true when third argument is init', () => {
expect(isInitCommand(['arg1', 'arg2', 'init'])).toEqual(true);
});
it('returns default config path', () => {
expect(getConfigPath('some-file.config.js', undefined, '/my/path')).toEqual('/my/path/some-file.config.js');
});
it('returns config path by config file', () => {
expect(getConfigPath('some-file.config.js', 'other-config.ts', '/my/path')).toEqual('/my/path/other-config.ts');
});
it('creates empty tags cli argument if neither performance nor tags param is defined', () => {
expect(createTagsCLIArgument({})).toEqual([]);
});
it('creates cli argument without performance flag if only cucumber tags passed', () => {
expect(
createTagsCLIArgument({
tags: '@some-tag and @other-tag',
})
).toEqual(['--cucumberOpts.tags', '@some-tag and @other-tag']);
});
it('creates cli argument with performance flag but without performance tag and without cucumber tags', () => {
expect(
createTagsCLIArgument({
performance: true,
})
).toEqual(['--cucumberOpts.tags', '@performance']);
});
it('creates cli argument with performance flag but without performance tag and with cucumber tags', () => {
expect(
createTagsCLIArgument({
performance: true,
tags: '@some-tag and @other-tag',
})
).toEqual(['--cucumberOpts.tags', '@some-tag and @other-tag and @performance']);
});
it('creates cli argument with performance flag but with performance tag and with cucumber tags', () => {
expect(
createTagsCLIArgument({
performance: true,
tags: '@some-tag and @other-tag and @performance',
})
).toEqual(['--cucumberOpts.tags', '@some-tag and @other-tag and @performance']);
});
it('adds only not black listed arguments', () => {
expect(
filterCLIArguments(['myArg'])({
myArg: true,
otherArgument: false,
})
).toEqual(['--otherArgument']);
});
it('adds bool not black listed arguments without parameters', () => {
expect(
filterCLIArguments(['myArg'])({
myArg: true,
otherArgument: false,
})
).toEqual(['--otherArgument']);
});
it('adds string not black listed arguments with parameters', () => {
expect(
filterCLIArguments(['myArg'])({
myArg: 'some-value',
otherArgument: 'some-value',
})
).toEqual(['--otherArgument=some-value']);
});
});
================================================
FILE: src/core/cli/cli.helper.ts
================================================
import * as path from 'path';
export const isInitCommand = (args?: any[] | string) => {
if (Array.isArray(args)) {
return args.length > 2 && args[2] === 'init';
}
return false;
};
export const getConfigPath = (configFile, argsConfig, basePath) => {
return argsConfig ? path.join(basePath, argsConfig) : path.join(basePath, configFile);
};
export const createTagsCLIArgument = commandArgs => {
const tags = [];
if (commandArgs.performance) {
if (commandArgs.tags !== undefined && commandArgs.tags.indexOf('@performance') < 0) {
tags.push('--cucumberOpts.tags');
tags.push(`${commandArgs.tags} and @performance`);
} else if (commandArgs.tags === undefined) {
tags.push('--cucumberOpts.tags');
tags.push('@performance');
} else {
tags.push('--cucumberOpts.tags');
tags.push(commandArgs.tags);
}
} else if (commandArgs.tags !== undefined) {
tags.push('--cucumberOpts.tags');
tags.push(commandArgs.tags);
}
return tags;
};
export const filterCLIArguments = blackList => commandArgs => {
const commandLineArgs = [];
for (const prop in commandArgs) {
if (prop !== '_' && !blackList.includes(prop)) {
if (commandArgs[prop] === true || commandArgs[prop] === false) {
commandLineArgs.push(`--${prop}`);
} else {
commandLineArgs.push(`--${prop}=${commandArgs[prop]}`);
}
}
}
return commandLineArgs;
};
================================================
FILE: src/core/cli/initializer.ts
================================================
import * as fs from 'fs';
import * as inquirer from 'inquirer';
import * as mkdirp from 'mkdirp';
import * as path from 'path';
class Initializer {
public createProjectDirectory(dirPath) {
const projectPath = process.cwd() + dirPath;
mkdirp(projectPath, null);
console.log(`Created directory at path ${projectPath}`);
}
public createTemplateFile(templatePath, content) {
const filePath = process.cwd() + templatePath;
fs.writeFileSync(filePath, content);
console.log(`Created file at path ${filePath}`);
}
public createTemplateFileWithContentFrom(contentPath, file) {
const content = fs.readFileSync(path.join(__dirname, `../../../templates/${file}`));
this.createTemplateFile(contentPath, content);
}
public async promptFolders(message, defaultValue, type = 'input') {
let fullMessage = message;
if (defaultValue !== '') {
fullMessage += ` [${defaultValue}]`;
}
return inquirer
.prompt([
{
type,
name: 'input',
message: fullMessage,
},
])
.then((answer: any): any => (answer.input === '' ? defaultValue : answer.input));
}
public async initConfig(commandArgs) {
const conf = {
baseUrl: '',
type: 'otherWeb',
browserWidth: 1600,
browserHeight: 900,
timeout: 60,
intervalEmail: 5,
maxEmailRepeats: 5,
elementsVisibilityTimeout: 5,
waitForPageTimeout: 5,
downloadTimeout: 30,
emails: ['/emails'],
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,
browserMob: null,
accounts: null,
};
if (typeof commandArgs.baseUrl === 'undefined') {
conf.baseUrl = await this.promptFolders('What is base url?', 'http://localhost:3000');
} else {
conf.baseUrl = commandArgs.baseUrl;
}
if (typeof commandArgs.emailType === 'undefined') {
await inquirer
.prompt([
{
type: 'rawlist',
name: 'type',
message: 'What kind of email service would you like to use?',
choices: [
{ name: 'None', value: 'none' },
{ name: 'Custom (you will have to fill configuration on your own)', value: 'custom' },
{ name: 'MailTrap', value: 'mailtrap' },
],
},
])
.then((answer: any) => {
if (answer.type !== 'none') {
conf.email = {
type: answer.type,
};
}
});
} else {
conf.email = {
type: commandArgs.emailType,
};
}
if (conf.email && conf.email.type === 'mailtrap') {
conf.email = {
...conf.email,
config: {
url: 'https://mailtrap.io',
apiKey: '',
inboxId: '',
},
};
if (typeof commandArgs.emailApiKey === 'undefined') {
conf.email.config.apiKey = await this.promptFolders('Type in your mailtrap apikey:', conf.email.config.apiKey);
} else {
conf.email.config.apiKey = commandArgs.emailApiKey;
}
if (typeof commandArgs.emailInboxId === 'undefined') {
conf.email.config.inboxId = await this.promptFolders(
'Type in your mailtrap inboxId:',
conf.email.config.inboxId
);
} else {
conf.email.config.inboxId = commandArgs.emailInboxId;
}
}
if (commandArgs.advanced) {
await this.initEnv();
conf.browserWidth = parseInt(await this.promptFolders('What is desired browser width?', conf.browserWidth));
conf.browserHeight = parseInt(await this.promptFolders('What is desired browser height?', conf.browserHeight));
conf.timeout = parseInt(await this.promptFolders('What is desired step timeout in seconds?', conf.timeout));
conf.intervalEmail = parseInt(
await this.promptFolders('What is desired step email interval in seconds?', conf.intervalEmail)
);
conf.maxEmailRepeats = parseInt(
await this.promptFolders('How many times emails should be checked - maximum repeats?', conf.maxEmailRepeats)
);
conf.elementsVisibilityTimeout = parseInt(
await this.promptFolders(
'What is desired elements visibility timeout in seconds?',
conf.elementsVisibilityTimeout
)
);
conf.waitForPageTimeout = parseInt(
await this.promptFolders('How long should I wait for page to load in seconds?', conf.waitForPageTimeout)
);
conf.downloadTimeout = parseInt(
await this.promptFolders('How long should I wait for files to download in seconds?', conf.downloadTimeout)
);
conf.reports = await this.promptFolders('Where are your reports stored?', conf.reports);
conf.downloads = await this.promptFolders('Where are your downloads stored?', conf.downloads);
conf.data = await this.promptFolders('Where is your data stored?', conf.data);
conf.features = [await this.promptFolders('Where are your features stored?', conf.features[0])];
conf.pages = [await this.promptFolders('Where are your pages stored?', conf.pages[0])];
conf.matchers = [await this.promptFolders('Where are your matchers stored?', conf.matchers[0])];
conf.generators = [await this.promptFolders('Where are your generators stored?', conf.generators[0])];
conf.form_handlers = [await this.promptFolders('Where are your form handlers stored?', conf.form_handlers[0])];
conf.step_definitions = [
await this.promptFolders('Where are your step definitions stored?', conf.step_definitions[0]),
];
conf.comparators = [await this.promptFolders('Where are your comparators stored?', conf.comparators[0])];
conf.dictionaries = [await this.promptFolders('Where are your dictionaries stored?', conf.dictionaries[0])];
conf.regexes = [await this.promptFolders('Where are your regexes stored?', conf.regexes[0])];
conf.hooks = [await this.promptFolders('Where are your hooks stored?', conf.hooks[0])];
conf.transformers = [await this.promptFolders('Where are your transformers stored?', conf.transformers[0])];
conf.clearEmailInboxBeforeTests = await this.promptFolders(
'Should email inbox be cleared before tests?',
conf.clearEmailInboxBeforeTests,
'confirm'
);
conf.clearCookiesAfterScenario = await this.promptFolders(
'Should cookies be cleared after scenario?',
conf.clearCookiesAfterScenario,
'confirm'
);
conf.clearLocalStorageAfterScenario = await this.promptFolders(
'Should local storage be cleared after scenario?',
conf.clearLocalStorageAfterScenario,
'confirm'
);
conf.browserMob = {
serverPort: parseInt(await this.promptFolders('Define port where browsermob-proxy is running!', 8887)),
port: parseInt(await this.promptFolders('Define port where browsermob-proxy should be listening!', 8888)),
host: await this.promptFolders('Define host where browsermob-proxy is running!', 'localhost'),
};
}
conf.accounts = {
someAccount: {
accounts: [
{
email: '',
password: '',
},
],
},
};
this.createTemplateFile('/kakunin.conf.js', 'module.exports = ' + JSON.stringify(conf, null, 4));
}
public async initEnv() {
const envs = [];
envs.push('FIXTURES_RELOAD_HOST=' + (await this.promptFolders('Define FIXTURES_RELOAD_HOST', '')));
this.createTemplateFile('/.env', envs.join('\n'));
}
public async generateProjectStructure() {
const config = require(process.cwd() + '/kakunin.conf.js');
this.createProjectDirectory(config.reports);
this.createProjectDirectory(path.join(config.reports, 'report'));
this.createProjectDirectory(path.join(config.reports, 'json-output-folder'));
this.createProjectDirectory(path.join(config.reports, 'report', 'features'));
this.createProjectDirectory(path.join(config.reports, 'performance'));
this.createProjectDirectory(config.downloads);
this.createProjectDirectory(config.data);
this.createProjectDirectory(config.features[0]);
this.createProjectDirectory(config.pages[0]);
this.createProjectDirectory(config.matchers[0]);
this.createProjectDirectory(config.generators[0]);
this.createProjectDirectory(config.form_handlers[0]);
this.createProjectDirectory(config.step_definitions[0]);
this.createProjectDirectory(config.comparators[0]);
this.createProjectDirectory(config.dictionaries[0]);
this.createProjectDirectory(config.regexes[0]);
this.createProjectDirectory(config.hooks[0]);
this.createProjectDirectory(config.transformers[0]);
this.createProjectDirectory(config.emails[0]);
this.createTemplateFile(path.join(config.downloads, '.gitkeep'), '');
this.createTemplateFile(path.join(config.reports, 'json-output-folder', '.gitkeep'), '');
this.createTemplateFile(path.join(config.reports, 'report', '.gitkeep'), '');
this.createTemplateFile(path.join(config.reports, 'report', 'features', '.gitkeep'), '');
this.createTemplateFile(path.join(config.reports, 'performance', '.gitkeep'), '');
this.createTemplateFileWithContentFrom(config.features[0] + '/example.feature', 'example.feature');
this.createTemplateFileWithContentFrom(config.pages[0] + '/page.js', 'page.js');
this.createTemplateFileWithContentFrom(config.matchers[0] + '/matcher.js', 'matcher.js');
this.createTemplateFileWithContentFrom(config.generators[0] + '/generator.js', 'generator.js');
this.createTemplateFileWithContentFrom(config.step_definitions[0] + '/steps.js', 'steps.js');
this.createTemplateFileWithContentFrom(config.regexes[0] + '/regex.js', 'regex.js');
this.createTemplateFileWithContentFrom(config.hooks[0] + '/hook.js', 'hook.js');
}
}
export default new Initializer();
================================================
FILE: src/core/config.helper.ts
================================================
const commandArgs = require('minimist')(process.argv.slice(2));
let config;
if (process.env.NODE_ENV === 'test') {
config = {
projectPath: process.cwd(),
email: {
config: {},
},
};
} else {
const configFile = process.argv.find(name => name.indexOf('--config') >= 0);
const configFilePath = configFile.substr(configFile.indexOf('=') + 1);
const project = process.argv.find(name => name.indexOf('--projectPath') >= 0);
const projectPath = project.substr(project.indexOf('=') + 1);
config = require(configFilePath);
config.projectPath = projectPath;
config.performance = commandArgs.performance || false;
}
export default config;
================================================
FILE: src/core/fs/delete-files.helper.ts
================================================
import * as fs from 'fs';
import * as path from 'path';
export const deleteReports = directory => {
return fs
.readdirSync(directory)
.filter(file => fs.statSync(path.join(directory, file)).isFile() && file !== '.gitkeep')
.forEach(file => fs.unlinkSync(path.join(directory, file)));
};
================================================
FILE: src/core/fs/prepare-catalogs.helper.ts
================================================
import * as fs from 'fs';
import * as mkdirp from 'mkdirp';
export const prepareCatalogs = directory => {
if (fs.existsSync(directory)) {
return Promise.resolve();
}
mkdirp(directory, null);
console.log(`${directory} has been added!`);
fs.writeFileSync(`${directory}/.gitkeep`, '');
};
================================================
FILE: src/core/modules-loader.helper.ts
================================================
import * as fs from 'fs';
import * as path from 'path';
import config from './config.helper';
class ModulesLoader {
private paths: any;
constructor(configuration) {
this.paths = {
comparators: [],
dictionaries: [],
form_handlers: [],
generators: [],
matchers: [],
regexes: [],
transformers: [],
emails: [],
hooks: [],
};
Object.keys(this.paths).forEach(group => {
if (typeof config[group] !== 'undefined') {
configuration[group].forEach(groupPath => {
this.paths[group].push(path.join(configuration.projectPath + groupPath));
});
}
});
}
public getModules(group) {
return this.getFilePaths(this.paths[group]).map(file => require(file[1]));
}
public getModulesAsObject(projectFolders) {
const modules = {};
const filePaths = this.getFilePaths(projectFolders);
filePaths.forEach(file => {
modules[file[0]] = require(file[1]);
});
return modules;
}
public getFilePaths(folders) {
let files = [];
folders.forEach(folder => {
if (fs.existsSync(folder)) {
files = files.concat(
fs
.readdirSync(folder)
.filter(file => file !== '.gitkeep' && file.indexOf('.spec.js') < 0)
.map(file => [file.substr(0, file.indexOf('.')), `${folder}/${file}`])
);
} else {
console.log(`Directory ${folder} does not exist.`);
}
});
return files;
}
}
export const create = (configuration = config) => new ModulesLoader(configuration);
================================================
FILE: src/core/prototypes.ts
================================================
RegExp.escape = (text): string => {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
};
================================================
FILE: src/dictionaries/base.ts
================================================
class BaseDictionary {
public readonly name: string;
public readonly values: object;
constructor(name, values) {
this.name = name;
this.values = values;
}
public isSatisfiedBy(name: string): boolean {
return this.name === name;
}
public getMappedValue(key: string): string {
return this.values[key];
}
}
export default BaseDictionary;
================================================
FILE: src/dictionaries/dictionaries.spec.ts
================================================
import { createDictionary as create, Base as BaseDictionary } from
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
SYMBOL INDEX (311 symbols across 90 files)
FILE: functional-tests/dictionaries/test-dictionary.js
class TestDictionary (line 4) | class TestDictionary extends BaseDictionary {
method constructor (line 5) | constructor() {
FILE: functional-tests/pages/absolutePage.js
class AbsolutePage (line 5) | class AbsolutePage extends BasePage {
method constructor (line 6) | constructor() {
FILE: functional-tests/pages/additionalParams.js
class AdditionalParamsPage (line 5) | class AdditionalParamsPage extends BasePage {
method constructor (line 6) | constructor() {
FILE: functional-tests/pages/appearSimpleForm.js
class AppearSimpleForm (line 5) | class AppearSimpleForm extends BasePage {
method constructor (line 6) | constructor() {
FILE: functional-tests/pages/appearSimpleFormPost.js
class AppearSimpleFormPost (line 5) | class AppearSimpleFormPost extends BasePage {
method constructor (line 6) | constructor() {
FILE: functional-tests/pages/appearTabularData.js
class AppearTabularData (line 5) | class AppearTabularData extends BasePage {
method constructor (line 6) | constructor() {
FILE: functional-tests/pages/buttonForm.js
class ButtonForm (line 5) | class ButtonForm extends BasePage {
method constructor (line 6) | constructor() {
FILE: functional-tests/pages/dragAndDrop.js
class DragAndDropPage (line 5) | class DragAndDropPage extends BasePage {
method constructor (line 6) | constructor() {
FILE: functional-tests/pages/google.js
class GooglePage (line 5) | class GooglePage extends BasePage {
method constructor (line 6) | constructor() {
FILE: functional-tests/pages/main.js
class MainPage (line 5) | class MainPage extends BasePage {
method constructor (line 6) | constructor() {
FILE: functional-tests/pages/matchers.js
class MatchersPage (line 5) | class MatchersPage extends BasePage {
method constructor (line 6) | constructor() {
FILE: functional-tests/pages/navigationPages.js
class NavigationPagesPage (line 5) | class NavigationPagesPage extends BasePage {
method constructor (line 6) | constructor() {
FILE: functional-tests/pages/simpleForm.js
class SimpleForm (line 5) | class SimpleForm extends BasePage {
method constructor (line 6) | constructor() {
FILE: functional-tests/pages/simpleFormPost.js
class SimpleFormPost (line 5) | class SimpleFormPost extends BasePage {
method constructor (line 6) | constructor() {
FILE: functional-tests/pages/simpleSelectForm.js
class SimpleForm (line 5) | class SimpleForm extends BasePage {
method constructor (line 6) | constructor() {
FILE: functional-tests/pages/tabularData.js
class TabularData (line 5) | class TabularData extends BasePage {
method constructor (line 6) | constructor() {
FILE: functional-tests/www/jsonData/xlsxData.router.js
function xlsxDataRouting (line 3) | function xlsxDataRouting() {
FILE: src/comparators/comparator.interface.ts
type Comparator (line 1) | interface Comparator {
FILE: src/comparators/comparators.ts
class Comparators (line 4) | class Comparators {
method constructor (line 5) | constructor(
method compare (line 9) | public compare(values: any[], order: string): Promise<string | void> {
method findComparator (line 19) | public findComparator(values: any[]): Comparator {
method addComparator (line 23) | public addComparator(comparator: Comparator): void {
FILE: src/core/cli/initializer.ts
class Initializer (line 6) | class Initializer {
method createProjectDirectory (line 7) | public createProjectDirectory(dirPath) {
method createTemplateFile (line 15) | public createTemplateFile(templatePath, content) {
method createTemplateFileWithContentFrom (line 23) | public createTemplateFileWithContentFrom(contentPath, file) {
method promptFolders (line 29) | public async promptFolders(message, defaultValue, type = 'input') {
method initConfig (line 47) | public async initConfig(commandArgs) {
method initEnv (line 222) | public async initEnv() {
method generateProjectStructure (line 230) | public async generateProjectStructure() {
FILE: src/core/modules-loader.helper.ts
class ModulesLoader (line 5) | class ModulesLoader {
method constructor (line 8) | constructor(configuration) {
method getModules (line 30) | public getModules(group) {
method getModulesAsObject (line 34) | public getModulesAsObject(projectFolders) {
method getFilePaths (line 45) | public getFilePaths(folders) {
FILE: src/dictionaries/base.ts
class BaseDictionary (line 1) | class BaseDictionary {
method constructor (line 5) | constructor(name, values) {
method isSatisfiedBy (line 10) | public isSatisfiedBy(name: string): boolean {
method getMappedValue (line 14) | public getMappedValue(key: string): string {
FILE: src/dictionaries/dictionaries.ts
class Dictionaries (line 3) | class Dictionaries {
method constructor (line 4) | constructor(private availableDictionaries: Dictionary[] = []) {}
method getMappedValue (line 6) | public getMappedValue(dictionaryName: string, key: string): string {
method findDictionary (line 16) | public findDictionary(name: string): Dictionary {
method findMappedValueByPhrase (line 20) | public findMappedValueByPhrase(phrase: string): string {
method addDictionary (line 33) | public addDictionary(dictionary: Dictionary): void {
FILE: src/emails/adapter/mailtrap.client.ts
class MailTrapClient (line 4) | class MailTrapClient {
method constructor (line 8) | constructor(requestClient, config) {
method isSatisfiedBy (line 13) | public isSatisfiedBy(emailConfiguration) {
method getMailtrapConfig (line 22) | public getMailtrapConfig() {
method clearInbox (line 30) | public clearInbox() {
method getEmails (line 45) | public async getEmails() {
method getAttachments (line 73) | public getAttachments(email) {
method markAsRead (line 88) | public markAsRead(email) {
FILE: src/emails/email.service.ts
class EmailService (line 4) | class EmailService {
method constructor (line 8) | constructor(config, defaultAdapters) {
method clearInbox (line 14) | public clearInbox() {
method getEmails (line 20) | public getEmails() {
method getAttachments (line 26) | public getAttachments(email) {
method markAsRead (line 32) | public markAsRead(email) {
method addAdapter (line 38) | public addAdapter(adapter) {
method getAdapter (line 42) | public getAdapter() {
FILE: src/emails/filter/current-user.filter.ts
class CurrentUserFilter (line 3) | class CurrentUserFilter {
method isSatisfiedBy (line 4) | public isSatisfiedBy(type) {
method filter (line 8) | public filter(emails, type, value, world) {
FILE: src/emails/filter/minimal-email-size.filter.ts
class MinimalEmailSizeFilter (line 1) | class MinimalEmailSizeFilter {
method isSatisfiedBy (line 2) | public isSatisfiedBy(type) {
method filter (line 6) | public filter(emails, type, value) {
FILE: src/emails/filter/text-fields.filter.ts
class TextFieldFilter (line 4) | class TextFieldFilter {
method isSatisfiedBy (line 5) | public isSatisfiedBy(type) {
method filter (line 9) | public filter(emails, type, value) {
FILE: src/emails/filters.ts
class Filters (line 3) | class Filters {
method constructor (line 6) | constructor() {
method filter (line 14) | public filter(emails, type, value, world) {
method findFilter (line 24) | public findFilter(type) {
FILE: src/form-handlers/form-handler.interface.ts
type FormHandler (line 1) | interface FormHandler {
FILE: src/form-handlers/handler/checkbox.handler.ts
class CheckboxHandler (line 3) | class CheckboxHandler implements FormHandler {
method isSatisfiedBy (line 4) | public isSatisfiedBy(element) {
method handleFill (line 21) | public handleFill(page, elementName, desiredValue) {
method handleCheck (line 36) | public handleCheck(page, elementName, desiredValue) {
method getPriority (line 70) | public getPriority() {
FILE: src/form-handlers/handler/ckeditor.handler.ts
class CKEditorHandler (line 3) | class CKEditorHandler implements FormHandler {
method isSatisfiedBy (line 4) | public isSatisfiedBy(element, elementName) {
method handleFill (line 8) | public handleFill(page, elementName, desiredValue) {
method handleCheck (line 18) | public handleCheck(page, elementName, desiredValue) {
method getPriority (line 22) | public getPriority() {
FILE: src/form-handlers/handler/custom-angular-select.handler.ts
class CustomAngularSelectHandler (line 3) | class CustomAngularSelectHandler implements FormHandler {
method constructor (line 7) | constructor() {
method isSatisfiedBy (line 12) | public isSatisfiedBy(element, elementName) {
method handleFill (line 16) | public handleFill(page, elementName, desiredValue) {
method handleCheck (line 38) | public handleCheck(page, elementName, desiredValue) {
method getPriority (line 51) | public getPriority() {
FILE: src/form-handlers/handler/default.handler.ts
class DefaultHandler (line 3) | class DefaultHandler implements FormHandler {
method isSatisfiedBy (line 4) | public isSatisfiedBy() {
method handleFill (line 8) | public handleFill(page, elementName, desiredValue) {
method handleCheck (line 16) | public handleCheck(page, elementName, desiredValue) {
method getPriority (line 34) | public getPriority() {
FILE: src/form-handlers/handler/file.handler.ts
class FileHandler (line 5) | class FileHandler implements FormHandler {
method isSatisfiedBy (line 6) | public isSatisfiedBy(element) {
method handleFill (line 23) | public handleFill(page, elementName, desiredValue) {
method handleCheck (line 29) | public handleCheck(page, elementName, desiredValue) {
method getPriority (line 42) | public getPriority() {
FILE: src/form-handlers/handler/radio.handler.ts
class RadioHandler (line 3) | class RadioHandler implements FormHandler {
method isSatisfiedBy (line 4) | public isSatisfiedBy(element) {
method handleFill (line 21) | public handleFill(page, elementName, desiredValue) {
method handleCheck (line 36) | public handleCheck(page, elementName, desiredValue) {
method getPriority (line 61) | public getPriority() {
FILE: src/form-handlers/handler/select.handler.ts
class SelectHandler (line 3) | class SelectHandler implements FormHandler {
method constructor (line 6) | constructor() {
method isSatisfiedBy (line 10) | public isSatisfiedBy(element) {
method handleFill (line 14) | public handleFill(page, elementName, desiredValue) {
method handleCheck (line 45) | public handleCheck(page, elementName, desiredValue) {
method getPriority (line 60) | public getPriority() {
FILE: src/form-handlers/handler/uploaded-file.handler.ts
class UploadedFileHandler (line 3) | class UploadedFileHandler implements FormHandler {
method isSatisfiedBy (line 4) | public isSatisfiedBy(element, elementName) {
method handleFill (line 8) | public handleFill(page, elementName, desiredValue) {
method handleCheck (line 12) | public handleCheck(page, elementName, desiredValue) {
method getPriority (line 22) | public getPriority() {
FILE: src/form-handlers/handlers.ts
class FormHandlers (line 5) | class FormHandlers {
method constructor (line 6) | constructor(
method addHandler (line 19) | public addHandler(handler: FormHandler): void {
method handleFill (line 23) | public async handleFill(page: Base, elementName: string, desiredValue:...
method handleCheck (line 37) | public async handleCheck(page: Base, elementName: string, desiredValue...
method getHandlers (line 51) | public getHandlers(): FormHandler[] {
FILE: src/generators/generator.interface.ts
type Generator (line 1) | interface Generator {
FILE: src/generators/generator/personalData.generator.ts
method isSatisfiedBy (line 5) | isSatisfiedBy(name) {
method generate (line 9) | generate(options) {
FILE: src/generators/generator/string-with-length.generator.ts
method isSatisfiedBy (line 4) | isSatisfiedBy(name) {
method generate (line 8) | generate(generatorParam) {
FILE: src/generators/generators.ts
class Generators (line 4) | class Generators {
method constructor (line 5) | constructor(
method generate (line 9) | public generate(generatorName: string, ...params: any): Promise<any> {
method addGenerator (line 19) | public addGenerator(generator: Generator): void {
method findGenerator (line 23) | public findGenerator(name: string): Generator {
FILE: src/kakunin.d.ts
type Global (line 6) | interface Global {
type Console (line 12) | interface Console {
type RegExpConstructor (line 16) | interface RegExpConstructor {
FILE: src/matchers/matcher.interface.ts
type Matcher (line 1) | interface Matcher {
FILE: src/matchers/matcher/attribute.matcher.ts
class AttributeMatcher (line 4) | class AttributeMatcher implements Matcher {
method isSatisfiedBy (line 5) | public isSatisfiedBy(prefix) {
method match (line 9) | public match(element, attributeName, regexName) {
FILE: src/matchers/matcher/clickable.matcher.ts
class ClickableMatcher (line 3) | class ClickableMatcher implements Matcher {
method isSatisfiedBy (line 4) | public isSatisfiedBy(prefix, name) {
method match (line 8) | public match(element) {
FILE: src/matchers/matcher/currentDate.matcher.ts
class CurrentDateMatcher (line 4) | class CurrentDateMatcher implements Matcher {
method isSatisfiedBy (line 5) | public isSatisfiedBy(prefix, name) {
method match (line 9) | public match(element, name = null, params = 'DD-MM-YYYY') {
FILE: src/matchers/matcher/invisible.matcher.ts
class InvisibleMatcher (line 3) | class InvisibleMatcher implements Matcher {
method isSatisfiedBy (line 4) | public isSatisfiedBy(prefix, name) {
method match (line 8) | public async match(element) {
FILE: src/matchers/matcher/not-clickable.matcher.ts
class NotClickableMatcher (line 3) | class NotClickableMatcher implements Matcher {
method isSatisfiedBy (line 4) | public isSatisfiedBy(prefix, name) {
method match (line 8) | public match(element) {
FILE: src/matchers/matcher/present.matcher.ts
class PresentMatcher (line 3) | class PresentMatcher implements Matcher {
method isSatisfiedBy (line 4) | public isSatisfiedBy(prefix, name) {
method match (line 8) | public match(element) {
FILE: src/matchers/matcher/regex-matcher/index.ts
class RegexMatcher (line 5) | class RegexMatcher implements Matcher {
method isSatisfiedBy (line 6) | public isSatisfiedBy(prefix, name) {
method match (line 10) | public match(element, regexName) {
FILE: src/matchers/matcher/regex-matcher/regex-builder.ts
class RegexBuilder (line 3) | class RegexBuilder {
method buildRegex (line 4) | public buildRegex(regexTemplate: string): RegExp {
FILE: src/matchers/matcher/text.matcher.ts
class TextMatcher (line 4) | class TextMatcher implements Matcher {
method isSatisfiedBy (line 5) | public isSatisfiedBy(prefix) {
method match (line 9) | public match(element, ...params) {
FILE: src/matchers/matcher/visible.matcher.ts
class VisibleMatcher (line 3) | class VisibleMatcher implements Matcher {
method isSatisfiedBy (line 4) | public isSatisfiedBy(prefix, name) {
method match (line 8) | public match(element) {
FILE: src/matchers/matchers.ts
class Matchers (line 6) | class Matchers {
method constructor (line 7) | constructor(
method addMatcher (line 21) | public addMatcher(matcher: Matcher): void {
method match (line 25) | public match(element: object, matcherName: string): Promise<string | b...
method findMatcher (line 36) | public findMatcher(prefix: string, param: string): Matcher {
FILE: src/pages/base.ts
class Page (line 7) | class Page {
method visit (line 10) | public visit() {
method visitWithParameters (line 20) | public visitWithParameters(data) {
method isOn (line 41) | public async isOn() {
method click (line 53) | public click(elementName: string) {
method isDisabled (line 57) | public isDisabled(elementName: string) {
method isVisible (line 63) | public isVisible(elementName: string) {
method isPresent (line 67) | public isPresent(elementName: string) {
method getNumberOfElements (line 71) | public getNumberOfElements(elementName: string) {
method scrollIntoElement (line 75) | public scrollIntoElement(elementName: string, elementIndex?: string) {
method waitForVisibilityOf (line 88) | public waitForVisibilityOf(elementName: string) {
method waitForInvisibilityOf (line 92) | public waitForInvisibilityOf(elementName: string) {
method getElement (line 96) | public getElement(elementName: string) {
method getElements (line 104) | public getElements(elementName: string) {
FILE: src/pages/form.ts
class FormPage (line 5) | class FormPage extends Base {
method fillForm (line 6) | public async fillForm(formData) {
method checkForm (line 14) | public async checkForm(formData) {
method fillField (line 22) | public fillField(name, value) {
method checkField (line 26) | public checkField(name, value) {
FILE: src/protractor.conf.ts
method beforeLaunch (line 78) | async beforeLaunch() {
method afterLaunch (line 87) | async afterLaunch() {
method onPrepare (line 91) | onPrepare() {
FILE: src/rest/api-request.ts
type HeaderList (line 4) | interface HeaderList {
class ApiRequest (line 8) | class ApiRequest {
method constructor (line 15) | constructor() {
method addHeaders (line 21) | public addHeaders(headers: HeaderList) {
method addFormData (line 27) | public addFormData(payload) {
method body (line 34) | get body() {
method body (line 38) | set body(payload) {
FILE: src/rest/api-response.ts
class ApiResponse (line 5) | class ApiResponse {
method constructor (line 9) | constructor(responseStatus, body) {
method hasStatus (line 14) | public hasStatus(status) {
method hasBodyMatch (line 18) | public hasBodyMatch(body) {
method hasMatchingSchema (line 25) | public hasMatchingSchema(schema) {
FILE: src/rest/rest-api-service.ts
class RestApiService (line 4) | class RestApiService {
method constructor (line 7) | constructor(baseUrl) {
method fetch (line 11) | public fetch(request) {
method resolveUrl (line 25) | private resolveUrl(endpoint) {
FILE: src/step_definitions/elements.ts
function checkNumberOfElements (line 30) | function checkNumberOfElements(numberExpression, element) {
FILE: src/step_definitions/email.ts
function stopInterval (line 9) | function stopInterval(interval, callback) {
function checkAttachmentsInEmail (line 14) | function checkAttachmentsInEmail(email, filesExtensions, attachments) {
function filterEmails (line 43) | function filterEmails(emails, data) {
function getFilesExtensions (line 57) | function getFilesExtensions(data) {
function rejectIfMaxRepeatsReached (line 66) | function rejectIfMaxRepeatsReached(filteredEmails, maxRepeats) {
function rejectIfMoreThanOneEmailFound (line 74) | function rejectIfMoreThanOneEmailFound(filteredEmails) {
function rejectIfEmailFound (line 82) | function rejectIfEmailFound(filteredEmails) {
function validateEmailDate (line 90) | function validateEmailDate(filteredEmails) {
function validateEmailContentAndAttachments (line 98) | function validateEmailContentAndAttachments(filteredEmails, data, interv...
FILE: src/tests/dictionaries/fake-dictionary.ts
class FakeDictionary (line 3) | class FakeDictionary extends BaseDictionary {
method constructor (line 4) | constructor() {
FILE: src/transformers/transformer.interface.ts
type Transformer (line 1) | interface Transformer {
FILE: src/transformers/transformer/dictionary.transformer.ts
class DictionaryTransformer (line 4) | class DictionaryTransformer implements Transformer {
method constructor (line 5) | constructor(private dictionaries: Dictionaries) {}
method isSatisfiedBy (line 7) | public isSatisfiedBy(prefix) {
method transform (line 11) | public transform(value) {
FILE: src/transformers/transformer/generator.transformer.ts
class GeneratorTransformer (line 4) | class GeneratorTransformer implements Transformer {
method constructor (line 5) | constructor(public generator: Generators) {}
method isSatisfiedBy (line 7) | public isSatisfiedBy(prefix) {
method transform (line 11) | public transform(value) {
FILE: src/transformers/transformer/variable-store.transformer.ts
class VariableStoreTransformer (line 5) | class VariableStoreTransformer implements Transformer {
method constructor (line 6) | constructor(private variableStore: VariableStore) {}
method isSatisfiedBy (line 8) | public isSatisfiedBy(prefix) {
method transform (line 12) | public transform(value) {
FILE: src/transformers/transformers.ts
class Transformers (line 6) | class Transformers {
method constructor (line 7) | constructor(private availableTransformers: Transformer[]) {}
method transform (line 9) | public transform(value: string): any {
method findTransformer (line 19) | public findTransformer(prefix: string): Transformer {
method addTransformer (line 23) | public addTransformer(transformer: Transformer): void {
FILE: src/web/cucumber/hooks/clear-download.hook.ts
class ClearDownloadHook (line 17) | class ClearDownloadHook implements HookHandler {
method initializeHook (line 18) | public initializeHook() {
method getPriority (line 28) | public getPriority() {
FILE: src/web/cucumber/hooks/clear-variables.hook.ts
class ClearVariablesHook (line 6) | class ClearVariablesHook implements HookHandler {
method initializeHook (line 7) | public initializeHook() {
method getPriority (line 21) | public getPriority() {
FILE: src/web/cucumber/hooks/hook.interface.ts
type HookHandler (line 1) | interface HookHandler {
FILE: src/web/cucumber/hooks/hooks.ts
class HookHandlers (line 4) | class HookHandlers {
method constructor (line 5) | constructor(
method addHook (line 15) | public addHook(handler: HookHandler): void {
method initializeHook (line 19) | public initializeHook(): void {
method getHooks (line 27) | public getHooks(): HookHandler[] {
FILE: src/web/cucumber/hooks/reload-fixtures.hook.ts
class ReloadFixturesHook (line 13) | class ReloadFixturesHook implements HookHandler {
method initializeHook (line 14) | public initializeHook() {
method getPriority (line 43) | public getPriority() {
FILE: src/web/cucumber/hooks/reload-user.hook.ts
class ReloadUserHook (line 4) | class ReloadUserHook implements HookHandler {
method initializeHook (line 5) | public initializeHook() {
method getPriority (line 15) | public getPriority() {
FILE: src/web/cucumber/hooks/take-screenshots.hook.ts
class TakeScreenshotHook (line 40) | class TakeScreenshotHook implements HookHandler {
method initializeHook (line 41) | public initializeHook() {
method getPriority (line 53) | public getPriority() {
FILE: src/web/fixtures/fixtures-loader.helper.ts
method reloadFixtures (line 4) | reloadFixtures(endpoint) {
FILE: src/web/fs/download-checker.helper.ts
method wasDownloaded (line 6) | wasDownloaded(expectedFileName) {
FILE: src/web/fs/file-manager.helper.ts
method wasDownloaded (line 7) | wasDownloaded(expectedFileName) {
method parseXLS (line 13) | parseXLS(expectedFileName) {
FILE: src/web/parameters.ts
method getReloadFixturesEndpoint (line 2) | getReloadFixturesEndpoint() {
method getConfig (line 8) | getConfig() {
FILE: src/web/performance/JSON-performance-report-parser.helper.ts
class JSONPerformanceReportParser (line 7) | class JSONPerformanceReportParser {
method constructor (line 10) | constructor(path = 'reports/performance') {
method parse (line 14) | public parse(fileName) {
FILE: src/web/performance/time-to-first-byte-analyser.helper.ts
class TimeToFirstByteAnalyser (line 3) | class TimeToFirstByteAnalyser {
method constructor (line 6) | constructor(jsonPerformanceReportParser) {
method checkTiming (line 10) | public checkTiming(fileName, maxTiming) {
FILE: src/web/user-provider.helper.ts
method getUser (line 5) | getUser(userType) {
method lockUser (line 24) | lockUser(user, userType) {
FILE: src/web/variable-store.helper.ts
class VariableStore (line 1) | class VariableStore {
method constructor (line 2) | constructor(public variables: any[] = []) {}
method storeVariable (line 4) | public storeVariable(name: string, value: any): void {
method updateVariable (line 14) | public updateVariable(name: string, value: any): void {
method getVariableValue (line 24) | public getVariableValue(name: string): any {
method isStored (line 34) | public isStored(name: string): boolean {
method clearVariables (line 40) | public clearVariables(): void {
method replaceTextVariables (line 44) | public replaceTextVariables(text: string): any {
FILE: templates/generator.js
class Generator (line 3) | class Generator {
method isSatisfiedBy (line 4) | isSatisfiedBy(name) {
method generate (line 8) | generate() {
FILE: templates/hook.js
class TestHook (line 3) | class TestHook {
method initializeHook (line 4) | initializeHook() {
method getPriority (line 10) | getPriority() {
FILE: templates/page.js
class ExamplePage (line 3) | class ExamplePage extends BasePage {
method constructor (line 4) | constructor() {
FILE: website/core/Footer.js
class Footer (line 9) | class Footer extends React.Component {
method render (line 11) | render() {
FILE: website/pages/en/versions.js
constant CWD (line 14) | const CWD = process.cwd();
function Versions (line 19) | function Versions() {
Condensed preview — 409 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,792K chars).
[
{
"path": ".dockerignore",
"chars": 21,
"preview": "*/node_modules\n*.log\n"
},
{
"path": ".editorconfig",
"chars": 180,
"preview": "root=true\n\n[*]\ncharset=utf-8\nend_of_line=lf\ninsert_final_newline=true\nindent_style=space\nindent_size=2\n\n[Makefile]\ninden"
},
{
"path": ".gitignore",
"chars": 604,
"preview": "#build files\ndist\n\n# dependencies\nnode_modules\n\n#tests\nselenium-debug.log\nreports\nscreenshots\ntestium\ntmp-*\nnpm-debug.lo"
},
{
"path": ".npmignore",
"chars": 29,
"preview": "website\nfunctional-tests\ndocs"
},
{
"path": ".prettierrc",
"chars": 148,
"preview": "{\n \"useTabs\": false,\n \"printWidth\": 120,\n \"tabWidth\": 2,\n \"singleQuote\": true,\n \"trailingComma\": \"es5\",\n "
},
{
"path": ".travis.yml",
"chars": 103,
"preview": "language: node_js\nnode_js:\n- \"lts/*\"\naddons:\n chrome: stable\nscript:\n- npm run test-ci\n- npm run lint\n"
},
{
"path": "CHANGELOG.MD",
"chars": 5311,
"preview": "#### v3.0.0\n\n- added support for Browserstack\n- updated external libraries\n- locked selenium-standalone-server to `3.14."
},
{
"path": "CONTRIBUTING.MD",
"chars": 4037,
"preview": "# Contributing to Kakunin\n\n\nThis section will guide you through the contribution process.\n\n### Step 1: Fork\n\nFork the pr"
},
{
"path": "Dockerfile",
"chars": 154,
"preview": "FROM node:8.11.4\n\nWORKDIR /app/website\n\nEXPOSE 3000 35729\nCOPY kakunin/docs /app/docs\nCOPY ./website /app/website\nRUN ya"
},
{
"path": "LICENSE",
"chars": 1075,
"preview": "MIT License\n\nCopyright (c) 2017 The Software House\n\nPermission is hereby granted, free of charge, to any person obtainin"
},
{
"path": "MIGRATION-2.0.0.MD",
"chars": 2087,
"preview": "# Kakunin - automated testing framework\n#### Migration to 2.0.0 version\n\nWhat needs to be done:\n1. Remove all locators f"
},
{
"path": "MIGRATION-2.2.0.MD",
"chars": 1214,
"preview": "# Kakunin - automated testing framework\n#### Migration to 2.2.0 version\n\nWhat needs to be done:\n1. If you are using Mail"
},
{
"path": "MIGRATION-3.0.0.MD",
"chars": 918,
"preview": "# Kakunin - automated testing framework\n#### Migration to 3.0.0 version\n\nWhat needs to be done:\n1. Change Hooks\n\nIn the "
},
{
"path": "ROADMAP.MD",
"chars": 1402,
"preview": "## Roadmap\n\n### v2.1.0\n\n* Allow to test REST endpoints against expected JSON Schema\n\n### v2.0.0\n\n* full support for Wind"
},
{
"path": "build.sh",
"chars": 74,
"preview": "#!/bin/sh\ngit subtree push --prefix website/build/Kakunin origin gh-pages\n"
},
{
"path": "docker-compose.yml",
"chars": 497,
"preview": "version: \"3\"\n\nservices:\n docusaurus:\n build: .\n ports:\n - 3000:3000\n - 35729:35729\n volumes:\n -"
},
{
"path": "docs/browserstack.md",
"chars": 3068,
"preview": "---\nid: browserstack\ntitle: Browserstack integration\n---\n\n## Browserstack project configuration\n\n1. Create a new account"
},
{
"path": "docs/configuration.md",
"chars": 4544,
"preview": "---\nid: configuration\ntitle: Configuration\n---\n\n## Kakunin config\n\n```\nmodule.exports = {\n \"browserWidth\": 1600,\n "
},
{
"path": "docs/cross-browser.md",
"chars": 1269,
"preview": "---\nid: cross-browser\ntitle: Cross-browser testing\n---\n\n## To run tests with specified browser\nThere is a possibility to"
},
{
"path": "docs/docker.md",
"chars": 2566,
"preview": "---\nid: docker\ntitle: Docker\n---\n\n# Docker for Kakunin tests\n\nThis section explains how to run kakunin tests inside dock"
},
{
"path": "docs/extending.md",
"chars": 9330,
"preview": "---\nid: extending\ntitle: Extending Kakunin\n---\n\nKakunin allows you to easily add a custom code in order to extend it's f"
},
{
"path": "docs/headless.md",
"chars": 687,
"preview": "---\nid: headless\ntitle: Headless\n---\n\n# Headless browser control\n\nCurrently only Firefox and Google Chrome browser can b"
},
{
"path": "docs/hooks.md",
"chars": 1496,
"preview": "---\nid: hooks\ntitle: Hooks\n---\n# Hooks for Kakunin tests\n\nThis section explains how to add priority hooks for kakunin te"
},
{
"path": "docs/how-it-works.md",
"chars": 8728,
"preview": "---\nid: how-it-works\ntitle: How it works\n---\n\nKakunin is built with `no-js` experience in mind. Because of that you're a"
},
{
"path": "docs/index.md",
"chars": 6891,
"preview": "---\nid: index\ntitle: Getting started\n---\n\n## About Kakunin\n\nKakunin is a Protractor extension created by The Software Ho"
},
{
"path": "docs/matchers.md",
"chars": 1855,
"preview": "---\nid: matchers\ntitle: Matchers\n---\n\nMatchers allows you to check if a element content matches your expectation.\n\nFor e"
},
{
"path": "docs/parallel-testing.md",
"chars": 1043,
"preview": "---\nid: parallel-testing\ntitle: Parallel testing\n---\nThere is a possibility to run tests in parallel.\n\n## How to execute"
},
{
"path": "docs/performance-testing.md",
"chars": 1462,
"preview": "---\nid: performance-testing\ntitle: Performance testing\n---\n\nPerformance testing is possible thanks to `browsermob-proxy`"
},
{
"path": "docs/quickstart.md",
"chars": 3882,
"preview": "---\nid: quickstart\ntitle: Quick start\n---\nAs a quick demonstration of the framework let's test the \n[React variant of To"
},
{
"path": "docs/steps-debug.md",
"chars": 1518,
"preview": "---\nid: steps-debug\ntitle: Debug\n---\n\n# Steps for debugging application:\n\n## `I pause`\n\nPauses tests execution and allow"
},
{
"path": "docs/steps-elements.md",
"chars": 9760,
"preview": "---\nid: steps-elements\ntitle: Elements\n---\n\n# Steps used to interact with elements:\n## `I infinitely scroll to the \":ele"
},
{
"path": "docs/steps-files.md",
"chars": 825,
"preview": "---\nid: steps-files\ntitle: Files\n---\n\n\n# Steps used to interact with files:\n## `the file \":fileName\" should be downloade"
},
{
"path": "docs/steps-forms.md",
"chars": 3269,
"preview": "---\nid: steps-forms\ntitle: Forms\n---\n\n# Steps used to fill forms:\n\n## `I fill the \":formName\" form with:`\n\nAllows to fil"
},
{
"path": "docs/steps-generators.md",
"chars": 659,
"preview": "---\nid: steps-generators\ntitle: Generators\n---\n\n# Steps used to generate values:\n\n## `I generate random \":generator:para"
},
{
"path": "docs/steps-navigation.md",
"chars": 1321,
"preview": "---\nid: steps-navigation\ntitle: Navigation\n---\n\n# Steps used for navigation on page:\n\n## `I visit the \":pageFileName\" pa"
},
{
"path": "docs/steps-rest.md",
"chars": 2001,
"preview": "---\nid: steps-rest\ntitle: Rest api\n---\n\n# Steps used for testing REST api:\n\nIn order to configure url for api, please ch"
},
{
"path": "docs/testing-rest-api.md",
"chars": 3960,
"preview": "---\nid: testing-rest-api\ntitle: REST API examples\n---\n\n# Testing REST API of your application\n\nIn this section examples "
},
{
"path": "docs/transformers.md",
"chars": 1782,
"preview": "---\nid: transformers\ntitle: Transformers\n---\n\nTransformers allow you to transform values passed to form steps.\n\nFor exam"
},
{
"path": "functional-tests/dictionaries/test-dictionary.js",
"chars": 308,
"preview": "const { dictionaries } = require('kakunin');\nconst { BaseDictionary } = require('kakunin');\n\nclass TestDictionary extend"
},
{
"path": "functional-tests/downloads/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "functional-tests/features/content/operations_on_stored_variables.feature",
"chars": 2609,
"preview": "Feature: Store table and compare jsons\n As a kakunin user\n I want to store values as variables\n\n Scenario: Stor"
},
{
"path": "functional-tests/features/content/validate_tabular_data.feature",
"chars": 2889,
"preview": "Feature: Tabular data\n As a kakunin user\n I want validate tabular data\n\n Scenario: Validate tabular data count\n"
},
{
"path": "functional-tests/features/content/validate_tabular_data_css.feature",
"chars": 2895,
"preview": "Feature: Tabular data\n As a kakunin user\n I want validate tabular data\n\n Scenario: Validate tabular data count\n"
},
{
"path": "functional-tests/features/content/wait_for_element_dissapear.feature",
"chars": 755,
"preview": "Feature: Element visibility\n As a kakunin user\n I want to wait for element to disappear\n\n Scenario: Check visib"
},
{
"path": "functional-tests/features/content/wait_for_element_dissapear_css.feature",
"chars": 745,
"preview": "Feature: Element visibility\n As a kakunin user\n I want to wait for element to disappear\n\n Scenario: Check visib"
},
{
"path": "functional-tests/features/drag-and-drop/operations_on_elements.feature",
"chars": 324,
"preview": "Feature: Drag and drop\n As a kakunin user\n I want to be able to make operations on elements\n\n Scenario: Drag el"
},
{
"path": "functional-tests/features/drag-and-drop/operations_on_elements_css.feature",
"chars": 331,
"preview": "Feature: Drag and drop\n As a kakunin user\n I want to be able to make operations on elements\n\n Scenario: Drag el"
},
{
"path": "functional-tests/features/forms/fill_and_check_form.feature",
"chars": 2771,
"preview": "Feature: Forms\n As a kakunin user\n I want fill and check form fields\n\n Scenario: Fill and check form fields\n "
},
{
"path": "functional-tests/features/forms/fill_and_check_form_css.feature",
"chars": 1619,
"preview": "Feature: Forms\n As a kakunin user\n I want fill and check form fields\n\n Scenario: Fill and check form fields\n "
},
{
"path": "functional-tests/features/forms/fill_select.feature",
"chars": 429,
"preview": "Feature: Forms\n As a kakunin user\n I want to check options\n\n Scenario: Fill and check form fields\n Given"
},
{
"path": "functional-tests/features/matchers/match_current_date.feature",
"chars": 993,
"preview": "Feature: Matchers\n As a kakunin user\n I want to navigate to matcher page and match current date\n\n Scenario: I w"
},
{
"path": "functional-tests/features/matchers/match_current_date_css.feature",
"chars": 1022,
"preview": "Feature: Matchers\n As a kakunin user\n I want to navigate to matcher page and match current date\n\n Scenario: I w"
},
{
"path": "functional-tests/features/matchers/matchers.feature",
"chars": 905,
"preview": "Feature: Matchers\n As a Kakunin user\n I want to fill input and then check if the value matches the expected result"
},
{
"path": "functional-tests/features/matchers/matchers_css.feature",
"chars": 1000,
"preview": "Feature: Matchers\n As a Kakunin user\n I want to fill input and then check if the value matches the expected result"
},
{
"path": "functional-tests/features/navigation/navigate_to_given_page.feature",
"chars": 1307,
"preview": "Feature: Navigation\n As a kakunin user\n I want to navigate to selected page\n\n Scenario: Navigate by link click\n"
},
{
"path": "functional-tests/features/navigation/navigate_to_given_page_css.feature",
"chars": 622,
"preview": "Feature: Navigation\n As a kakunin user\n I want to navigate to selected page\n\n Scenario: Navigate by link click\n"
},
{
"path": "functional-tests/features/navigation/switch-between-tabs.feature",
"chars": 566,
"preview": "Feature: Navigation\n As a kakunin user\n I want to switch between browser tabs\n\n Scenario: Navigate by link clic"
},
{
"path": "functional-tests/features/pages/verify_displayed_page.feature",
"chars": 612,
"preview": "Feature: Verify displayed pge\n As a kakunin user\n I want to make sure I am on expected page\n\n Scenario: Verify "
},
{
"path": "functional-tests/features/testing-api/testing_delete_request.feature",
"chars": 255,
"preview": "Feature: Test server delete request\n As a kakunin user\n I want to test restApi delete request\n\n Scenario: REST "
},
{
"path": "functional-tests/features/testing-api/testing_get_response.feature",
"chars": 386,
"preview": "Feature: Test server get response\n As a kakunin user\n I want to test restApi get response\n\n Scenario: REST get "
},
{
"path": "functional-tests/features/testing-api/testing_headers_setting.feature",
"chars": 417,
"preview": "Feature: Test setting headers\n As a kakunin user\n I want to set the headers\n\n Scenario: Setting http headers\n "
},
{
"path": "functional-tests/features/testing-api/testing_patch_request.feature",
"chars": 342,
"preview": "Feature: Test patch endpoint\n As a kakunin user\n I want to set test the patch endpoint\n\n Scenario: REST patch e"
},
{
"path": "functional-tests/features/testing-api/testing_post_form_data.feature",
"chars": 309,
"preview": "Feature: Test server post request using form data\n As a kakunin user\n I want to test restApi post request\n\n Sce"
},
{
"path": "functional-tests/features/testing-api/testing_post_json.feature",
"chars": 872,
"preview": "Feature: Test server post response\n As a kakunin user\n I want to test restApi post request\n\n Scenario: REST pos"
},
{
"path": "functional-tests/features/wait-for-elements/wait_for_form.feature",
"chars": 1297,
"preview": "Feature: Wait for forms\n As a kakunin user\n I want fill and check form fields\n\n Scenario: Fill and check form f"
},
{
"path": "functional-tests/features/wait-for-elements/wait_for_form_css.feature",
"chars": 1409,
"preview": "Feature: Wait for forms\n As a kakunin user\n I want fill and check form fields\n\n Scenario: Fill and check form f"
},
{
"path": "functional-tests/features/wait-for-elements/wait_for_table.feature",
"chars": 1719,
"preview": "Feature: Wait for Tabular data\n As a kakunin user\n I want validate tabular data which will appear in future\n\n S"
},
{
"path": "functional-tests/kakunin.conf.js",
"chars": 1297,
"preview": "module.exports = {\n browserWidth: 1600,\n browserHeight: 900,\n timeout: 60,\n elementsVisibilityTimeout: 5,\n waitForP"
},
{
"path": "functional-tests/package.json",
"chars": 689,
"preview": "{\n \"name\": \"kakunin-functional-tests\",\n \"version\": \"1.0.0\",\n \"description\": \"\",\n \"main\": \"index.js\",\n \"scripts\": {\n"
},
{
"path": "functional-tests/pages/absolutePage.js",
"chars": 219,
"preview": "'use strict';\n\nconst { BasePage } = require('kakunin');\n\nclass AbsolutePage extends BasePage {\n constructor() {\n sup"
},
{
"path": "functional-tests/pages/additionalParams.js",
"chars": 285,
"preview": "'use strict';\n\nconst { BasePage } = require('kakunin');\n\nclass AdditionalParamsPage extends BasePage {\n constructor() {"
},
{
"path": "functional-tests/pages/appearSimpleForm.js",
"chars": 667,
"preview": "'use strict';\n\nconst { BasePage } = require('kakunin');\n\nclass AppearSimpleForm extends BasePage {\n constructor() {\n "
},
{
"path": "functional-tests/pages/appearSimpleFormPost.js",
"chars": 577,
"preview": "'use strict';\n\nconst { BasePage } = require('kakunin');\n\nclass AppearSimpleFormPost extends BasePage {\n constructor() {"
},
{
"path": "functional-tests/pages/appearTabularData.js",
"chars": 483,
"preview": "'use strict';\n\nconst { BasePage } = require('kakunin');\n\nclass AppearTabularData extends BasePage {\n constructor() {\n "
},
{
"path": "functional-tests/pages/buttonForm.js",
"chars": 234,
"preview": "'use strict';\n\nconst { BasePage } = require('kakunin');\n\nclass ButtonForm extends BasePage {\n constructor() {\n super"
},
{
"path": "functional-tests/pages/dragAndDrop.js",
"chars": 333,
"preview": "'use strict';\n\nconst { BasePage } = require('kakunin');\n\nclass DragAndDropPage extends BasePage {\n constructor() {\n "
},
{
"path": "functional-tests/pages/google.js",
"chars": 201,
"preview": "'use strict';\n\nconst { BasePage } = require('kakunin');\n\nclass GooglePage extends BasePage {\n constructor() {\n super"
},
{
"path": "functional-tests/pages/main.js",
"chars": 792,
"preview": "'use strict';\n\nconst { BasePage } = require('kakunin');\n\nclass MainPage extends BasePage {\n constructor() {\n super()"
},
{
"path": "functional-tests/pages/matchers.js",
"chars": 291,
"preview": "'use strict';\n\nconst { BasePage } = require('kakunin');\n\nclass MatchersPage extends BasePage {\n constructor() {\n sup"
},
{
"path": "functional-tests/pages/navigationPages.js",
"chars": 388,
"preview": "'use strict';\n\nconst { BasePage } = require('kakunin');\n\nclass NavigationPagesPage extends BasePage {\n constructor() {\n"
},
{
"path": "functional-tests/pages/simpleForm.js",
"chars": 605,
"preview": "'use strict';\n\nconst { BasePage } = require('kakunin');\n\nclass SimpleForm extends BasePage {\n constructor() {\n super"
},
{
"path": "functional-tests/pages/simpleFormPost.js",
"chars": 556,
"preview": "'use strict';\n\nconst { BasePage } = require('kakunin');\n\nclass SimpleFormPost extends BasePage {\n constructor() {\n s"
},
{
"path": "functional-tests/pages/simpleSelectForm.js",
"chars": 379,
"preview": "'use strict';\n\nconst { BasePage } = require('kakunin');\n\nclass SimpleForm extends BasePage {\n constructor() {\n super"
},
{
"path": "functional-tests/pages/tabularData.js",
"chars": 421,
"preview": "'use strict';\n\nconst { BasePage } = require('kakunin');\n\nclass TabularData extends BasePage {\n constructor() {\n supe"
},
{
"path": "functional-tests/regexes/index.js",
"chars": 45,
"preview": "module.exports = {\n validNumber: '\\\\d+',\n};\n"
},
{
"path": "functional-tests/step_definitions/custom_json_parser.js",
"chars": 864,
"preview": "const variableStore = require('kakunin').variableStore;\nconst fetch = require('node-fetch');\n\nconst { When } = require('"
},
{
"path": "functional-tests/www/index.js",
"chars": 3331,
"preview": "const express = require('express');\nconst nunjucks = require('nunjucks');\nconst path = require('path');\nconst app = expr"
},
{
"path": "functional-tests/www/jsonData/xlsxData.router.js",
"chars": 3526,
"preview": "const express = require('express');\n\nfunction xlsxDataRouting() {\n const router = express.Router();\n\n router.get('/dat"
},
{
"path": "functional-tests/www/views/absolute/index.njs",
"chars": 94,
"preview": "{% extends 'layout/default.njs' %}\n\n{% block content %}\n <p>Absolute page</p>\n{% endblock %}\n"
},
{
"path": "functional-tests/www/views/drag-and-drop/index.njs",
"chars": 865,
"preview": "{% extends 'layout/default.njs' %}\n\n{% block content %}\n <link rel=\"stylesheet\" href=\"//code.jquery.com/ui/1.12.1/theme"
},
{
"path": "functional-tests/www/views/form/disappear.njs",
"chars": 277,
"preview": "{% block content %}\n<button id=\"button\">Click me.</button>\n\n<script>\ndocument.getElementById(\"button\").onclick = functio"
},
{
"path": "functional-tests/www/views/form/select.njs",
"chars": 399,
"preview": "{% extends 'layout/default.njs' %}\n\n{% block content %}\n <form method=\"post\" action=\"/form/select/post\">\n <select na"
},
{
"path": "functional-tests/www/views/form/simple.njs",
"chars": 2348,
"preview": "{% extends 'layout/default.njs' %}\n\n{% block content %}\n <form method=\"post\" action=\"/form/simple/post\">\n <label for"
},
{
"path": "functional-tests/www/views/index.njs",
"chars": 1089,
"preview": "{% extends 'layout/default.njs' %}\n\n{% block content %}\n <div class=\"available-examples-links\">\n <a name=\"linkPage\" "
},
{
"path": "functional-tests/www/views/layout/default.njs",
"chars": 228,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <title>Example app</title>\n </head>\n <body st"
},
{
"path": "functional-tests/www/views/matchers/matchers.njs",
"chars": 498,
"preview": "{% block content %}\n\n <style>\n\n .colored {\n display: block;\n background-color: #00b8d4;\n outline: non"
},
{
"path": "functional-tests/www/views/navigation/page.njs",
"chars": 237,
"preview": "{% extends 'layout/default.njs' %}\n\n{% block content %}\n <p class=\"pageId\">{{ pageId }}</p>\n <p class=\"title\">{{ title"
},
{
"path": "functional-tests/www/views/table/tabular-data.njs",
"chars": 1136,
"preview": "{% extends 'layout/default.njs' %}\n\n{% block content %}\n <table>\n <tr>\n <td class=\"index\">1</td>\n <td clas"
},
{
"path": "functional-tests/www/views/wait-for-appear/form.njs",
"chars": 3191,
"preview": "{% block content %}\n\n <style>\n .hidden {\n display: none;\n }\n\n .colored {\n display: block;\n back"
},
{
"path": "functional-tests/www/views/wait-for-appear/table.njs",
"chars": 1777,
"preview": "{% block content %}\n\n <style>\n .hidden {\n display: none;\n }\n\n .colored {\n display: block;\n back"
},
{
"path": "package.json",
"chars": 3753,
"preview": "{\n \"name\": \"kakunin\",\n \"version\": \"3.0.3\",\n \"description\": \"End-to-end testing framework\",\n \"homepage\": \"https://the"
},
{
"path": "readme.md",
"chars": 2217,
"preview": "<p align=\"center\">\n <img src=\"/data/kakunin_logo.png\" alt=\"kakunin.png\" width=\"550\"/>\n</p>\n\n<p align=\"center\">\n <img s"
},
{
"path": "src/cli.ts",
"chars": 1802,
"preview": "#!/usr/bin/env node\nimport * as path from 'path';\nimport * as fs from 'fs';\nimport * as os from 'os';\nimport * as childP"
},
{
"path": "src/comparators/comparator/date.comparator.spec.ts",
"chars": 1672,
"preview": "import { DateComparator, supportedFormats } from './date.comparator';\n\ndescribe('Date comparator', () => {\n it('is sati"
},
{
"path": "src/comparators/comparator/date.comparator.ts",
"chars": 1448,
"preview": "import * as moment from 'moment';\nimport { Comparator } from '../comparator.interface';\n\nexport const supportedFormats ="
},
{
"path": "src/comparators/comparator/index.ts",
"chars": 72,
"preview": "export * from './date.comparator';\nexport * from './number.comparator';\n"
},
{
"path": "src/comparators/comparator/number.comparator.spec.ts",
"chars": 1729,
"preview": "import { NumberComparator } from './number.comparator';\n\ndescribe('Number comparator', () => {\n it('is satisfied by num"
},
{
"path": "src/comparators/comparator/number.comparator.ts",
"chars": 1037,
"preview": "import { Comparator } from '../comparator.interface';\n\nexport const NumberComparator: Comparator = {\n isSatisfiedBy: va"
},
{
"path": "src/comparators/comparator.interface.ts",
"chars": 137,
"preview": "export interface Comparator {\n isSatisfiedBy(value: any[]): boolean;\n compare(values: any[], order: string): Promise<s"
},
{
"path": "src/comparators/comparators.spec.ts",
"chars": 1418,
"preview": "import { create } from './comparators';\n\nconst comparators = create();\n\ndescribe('Comparators', () => {\n it('throws an "
},
{
"path": "src/comparators/comparators.ts",
"chars": 843,
"preview": "import * as comparators from './comparator';\nimport { Comparator } from './comparator.interface';\n\nclass Comparators {\n "
},
{
"path": "src/comparators/index.ts",
"chars": 78,
"preview": "import { create } from './comparators';\n\nexport const comparators = create();\n"
},
{
"path": "src/core/cli/cli.helper.spec.ts",
"chars": 3112,
"preview": "import { isInitCommand, createTagsCLIArgument, getConfigPath, filterCLIArguments } from './cli.helper';\n\ndescribe('Cli h"
},
{
"path": "src/core/cli/cli.helper.ts",
"chars": 1432,
"preview": "import * as path from 'path';\n\nexport const isInitCommand = (args?: any[] | string) => {\n if (Array.isArray(args)) {\n "
},
{
"path": "src/core/cli/initializer.ts",
"chars": 10505,
"preview": "import * as fs from 'fs';\nimport * as inquirer from 'inquirer';\nimport * as mkdirp from 'mkdirp';\nimport * as path from "
},
{
"path": "src/core/config.helper.ts",
"chars": 667,
"preview": "const commandArgs = require('minimist')(process.argv.slice(2));\nlet config;\n\nif (process.env.NODE_ENV === 'test') {\n co"
},
{
"path": "src/core/fs/delete-files.helper.ts",
"chars": 302,
"preview": "import * as fs from 'fs';\nimport * as path from 'path';\n\nexport const deleteReports = directory => {\n return fs\n .re"
},
{
"path": "src/core/fs/prepare-catalogs.helper.ts",
"chars": 302,
"preview": "import * as fs from 'fs';\nimport * as mkdirp from 'mkdirp';\n\nexport const prepareCatalogs = directory => {\n if (fs.exis"
},
{
"path": "src/core/modules-loader.helper.ts",
"chars": 1579,
"preview": "import * as fs from 'fs';\nimport * as path from 'path';\nimport config from './config.helper';\n\nclass ModulesLoader {\n p"
},
{
"path": "src/core/prototypes.ts",
"chars": 98,
"preview": "RegExp.escape = (text): string => {\n return text.replace(/[-[\\]{}()*+?.,\\\\^$|#\\s]/g, '\\\\$&');\n};\n"
},
{
"path": "src/dictionaries/base.ts",
"chars": 371,
"preview": "class BaseDictionary {\n public readonly name: string;\n public readonly values: object;\n\n constructor(name, values) {\n"
},
{
"path": "src/dictionaries/dictionaries.spec.ts",
"chars": 1118,
"preview": "import { createDictionary as create, Base as BaseDictionary } from './';\nimport fakeDictionary = require('../tests/dicti"
},
{
"path": "src/dictionaries/dictionaries.ts",
"chars": 1024,
"preview": "import Dictionary from './base';\n\nexport class Dictionaries {\n constructor(private availableDictionaries: Dictionary[] "
},
{
"path": "src/dictionaries/index.ts",
"chars": 240,
"preview": "import BaseDictionary from './base';\nimport { create } from './dictionaries';\n\nexport { Dictionaries } from './dictionar"
},
{
"path": "src/emails/adapter/mailtrap.client.spec.ts",
"chars": 4036,
"preview": "import { create } from './mailtrap.client';\nimport * as fetchMock from 'fetch-mock';\n\ndescribe('Mailtrap client', () => "
},
{
"path": "src/emails/adapter/mailtrap.client.ts",
"chars": 2928,
"preview": "import * as fetch from 'node-fetch';\nimport configuration from '../../core/config.helper';\n\nclass MailTrapClient {\n pri"
},
{
"path": "src/emails/email.service.spec.ts",
"chars": 2915,
"preview": "import { create } from './email.service';\n\ndescribe('Email service', () => {\n it('it returns adapter if found', () => {"
},
{
"path": "src/emails/email.service.ts",
"chars": 1327,
"preview": "import configuration from '../core/config.helper';\nimport { create as createMailtrapAdapter } from './adapter/mailtrap.c"
},
{
"path": "src/emails/filter/current-user.filter.ts",
"chars": 368,
"preview": "// TODO: stop injecting world here and use some kind of a user provider\n\nclass CurrentUserFilter {\n public isSatisfiedB"
},
{
"path": "src/emails/filter/current-user.spec.ts",
"chars": 980,
"preview": "const { currentUserFilter } = require('./current-user.filter');\n\nconst fakeWorld = {\n currentUser: {\n account: {\n "
},
{
"path": "src/emails/filter/index.ts",
"chars": 122,
"preview": "export * from './current-user.filter';\nexport * from './minimal-email-size.filter';\nexport * from './text-fields.filter'"
},
{
"path": "src/emails/filter/minimal-email-size.filter.spec.ts",
"chars": 810,
"preview": "const { minimalEmailSizeFilter } = require('./minimal-email-size.filter.ts');\n\ndescribe('Minimal email size filter', () "
},
{
"path": "src/emails/filter/minimal-email-size.filter.ts",
"chars": 293,
"preview": "class MinimalEmailSizeFilter {\n public isSatisfiedBy(type) {\n return type === 'minimalEmailSize';\n }\n\n public filt"
},
{
"path": "src/emails/filter/text-fields.filter.spec.ts",
"chars": 2651,
"preview": "import { textFieldFilter } from './text-fields.filter';\nimport variableStore from '../../web/variable-store.helper';\n\nde"
},
{
"path": "src/emails/filter/text-fields.filter.ts",
"chars": 799,
"preview": "import { regexBuilder } from '../../matchers';\nimport variableStore from '../../web/variable-store.helper';\n\nclass TextF"
},
{
"path": "src/emails/filters.spec.ts",
"chars": 944,
"preview": "import { filters } from './filters';\n\nconst world = {\n currentUser: {\n account: {\n email: 'some@email.com',\n "
},
{
"path": "src/emails/filters.ts",
"chars": 685,
"preview": "import * as defaultFilters from './filter';\n\nclass Filters {\n private availableFilters: any;\n\n constructor() {\n thi"
},
{
"path": "src/emails/index.ts",
"chars": 81,
"preview": "import { create } from './email.service';\n\nexport const emailService = create();\n"
},
{
"path": "src/form-handlers/form-handler.interface.ts",
"chars": 324,
"preview": "export interface FormHandler {\n isSatisfiedBy(element?: object, elementName?: string): Promise<boolean>;\n handleFill(p"
},
{
"path": "src/form-handlers/handler/checkbox.handler.ts",
"chars": 1888,
"preview": "import { FormHandler } from '../form-handler.interface';\n\nclass CheckboxHandler implements FormHandler {\n public isSati"
},
{
"path": "src/form-handlers/handler/ckeditor.handler.ts",
"chars": 728,
"preview": "import { FormHandler } from '../form-handler.interface';\n\nclass CKEditorHandler implements FormHandler {\n public isSati"
},
{
"path": "src/form-handlers/handler/custom-angular-select.handler.ts",
"chars": 1696,
"preview": "import { FormHandler } from '../form-handler.interface';\n\nclass CustomAngularSelectHandler implements FormHandler {\n pr"
},
{
"path": "src/form-handlers/handler/default.handler.ts",
"chars": 1025,
"preview": "import { FormHandler } from '../form-handler.interface';\n\nclass DefaultHandler implements FormHandler {\n public isSatis"
},
{
"path": "src/form-handlers/handler/file.handler.ts",
"chars": 1257,
"preview": "import * as path from 'path';\nimport config from '../../core/config.helper';\nimport { FormHandler } from '../form-handle"
},
{
"path": "src/form-handlers/handler/index.ts",
"chars": 296,
"preview": "export * from './checkbox.handler';\nexport * from './ckeditor.handler';\nexport * from './custom-angular-select.handler';"
},
{
"path": "src/form-handlers/handler/radio.handler.ts",
"chars": 1763,
"preview": "import { FormHandler } from '../form-handler.interface';\n\nclass RadioHandler implements FormHandler {\n public isSatisfi"
},
{
"path": "src/form-handlers/handler/select.handler.ts",
"chars": 1802,
"preview": "import { FormHandler } from '../form-handler.interface';\n\nclass SelectHandler implements FormHandler {\n private options"
},
{
"path": "src/form-handlers/handler/uploaded-file.handler.ts",
"chars": 769,
"preview": "import { FormHandler } from '../form-handler.interface';\n\nclass UploadedFileHandler implements FormHandler {\n public is"
},
{
"path": "src/form-handlers/handlers.ts",
"chars": 1741,
"preview": "import * as formHandler from './handler';\nimport { FormHandler } from './form-handler.interface';\nimport Base from '../p"
},
{
"path": "src/form-handlers/index.ts",
"chars": 54,
"preview": "export { default as fromHandlers } from './handlers';\n"
},
{
"path": "src/generators/generator/index.ts",
"chars": 90,
"preview": "export * from './string-with-length.generator';\nexport * from './personalData.generator';\n"
},
{
"path": "src/generators/generator/personalData.generator.spec.ts",
"chars": 1509,
"preview": "import { personalDataGenerator } from './personalData.generator';\n\ndescribe('Personal data', () => {\n it('returns true "
},
{
"path": "src/generators/generator/personalData.generator.ts",
"chars": 696,
"preview": "import * as faker from 'faker';\nimport { Generator } from '../generator.interface';\n\nexport const personalDataGenerator:"
},
{
"path": "src/generators/generator/string-with-length.generator.spec.ts",
"chars": 596,
"preview": "import { stringWithLengthGenerator } from './string-with-length.generator';\n\ndescribe('String with length', () => {\n it"
},
{
"path": "src/generators/generator/string-with-length.generator.ts",
"chars": 512,
"preview": "import { Generator } from '../generator.interface';\n\nexport const stringWithLengthGenerator: Generator = {\n isSatisfied"
},
{
"path": "src/generators/generator.interface.ts",
"chars": 113,
"preview": "export interface Generator {\n isSatisfiedBy(name: string): boolean;\n generate(...params: any): Promise<any>;\n}\n"
},
{
"path": "src/generators/generators.spec.ts",
"chars": 862,
"preview": "import { create } from './generators';\n\nconst generators = create();\n\ndescribe('Generators', () => {\n it('throws an err"
},
{
"path": "src/generators/generators.ts",
"chars": 830,
"preview": "import * as generators from './generator';\nimport { Generator } from './generator.interface';\n\nexport class Generators {"
},
{
"path": "src/generators/index.ts",
"chars": 119,
"preview": "import { create } from './generators';\n\nexport { Generators } from './generators';\nexport const generators = create();\n"
},
{
"path": "src/index.ts",
"chars": 876,
"preview": "// entry file\nimport * as dictionaries from './dictionaries';\nimport * as pages from './pages';\n\nexport { matchers, rege"
},
{
"path": "src/kakunin.d.ts",
"chars": 275,
"preview": "declare let browser: any;\ndeclare let by: any;\ndeclare let protractor: any;\n\ndeclare module NodeJS {\n interface Global "
},
{
"path": "src/matchers/index.ts",
"chars": 216,
"preview": "import { regexBuilder as builder } from './matcher/regex-matcher/regex-builder';\nimport { create as createMatchers } fro"
},
{
"path": "src/matchers/matcher/attribute.matcher.spec.ts",
"chars": 1274,
"preview": "import { attributeMatcher } from './attribute.matcher';\n\ndescribe('Attribute matcher', () => {\n it('is satisfied when t"
},
{
"path": "src/matchers/matcher/attribute.matcher.ts",
"chars": 837,
"preview": "import { regexBuilder } from './regex-matcher/regex-builder';\nimport { Matcher } from '../matcher.interface';\n\nclass Att"
},
{
"path": "src/matchers/matcher/clickable.matcher.spec.ts",
"chars": 1892,
"preview": "import { clickableMatcher } from './clickable.matcher';\n\ndescribe('Clickable matcher', () => {\n it('is satisfied when t"
},
{
"path": "src/matchers/matcher/clickable.matcher.ts",
"chars": 643,
"preview": "import { Matcher } from '../matcher.interface';\n\nclass ClickableMatcher implements Matcher {\n public isSatisfiedBy(pref"
},
{
"path": "src/matchers/matcher/currentDate.matcher.spec.ts",
"chars": 2163,
"preview": "import { currentDateMatcher } from './currentDate.matcher';\nimport * as moment from 'moment';\n\ndescribe('Current Date ma"
},
{
"path": "src/matchers/matcher/currentDate.matcher.ts",
"chars": 783,
"preview": "import * as moment from 'moment';\nimport { Matcher } from '../matcher.interface';\n\nclass CurrentDateMatcher implements M"
},
{
"path": "src/matchers/matcher/index.ts",
"chars": 489,
"preview": "export { clickableMatcher } from './clickable.matcher';\nexport { invisibleMatcher } from './invisible.matcher';\nexport {"
},
{
"path": "src/matchers/matcher/invisible.matcher.spec.ts",
"chars": 1167,
"preview": "import { invisibleMatcher } from './invisible.matcher';\n\ndescribe('Invisible matcher', () => {\n it('is satisfied when t"
},
{
"path": "src/matchers/matcher/invisible.matcher.ts",
"chars": 528,
"preview": "import { Matcher } from '../matcher.interface';\n\nclass InvisibleMatcher implements Matcher {\n public isSatisfiedBy(pref"
},
{
"path": "src/matchers/matcher/not-clickable.matcher.spec.ts",
"chars": 1976,
"preview": "import { notClickableMatcher } from './not-clickable.matcher';\n\ndescribe('Not clickable matcher', () => {\n it('is satis"
},
{
"path": "src/matchers/matcher/not-clickable.matcher.ts",
"chars": 655,
"preview": "import { Matcher } from '../matcher.interface';\n\nclass NotClickableMatcher implements Matcher {\n public isSatisfiedBy(p"
},
{
"path": "src/matchers/matcher/present.matcher.spec.ts",
"chars": 1141,
"preview": "import { presentMatcher } from './present.matcher';\n\ndescribe('Present matcher', () => {\n it('is satisfied when the pre"
},
{
"path": "src/matchers/matcher/present.matcher.ts",
"chars": 444,
"preview": "import { Matcher } from '../matcher.interface';\n\nclass PresentMatcher implements Matcher {\n public isSatisfiedBy(prefix"
},
{
"path": "src/matchers/matcher/regex-matcher/index.spec.ts",
"chars": 2420,
"preview": "import { regexMatcher } from '.';\n\ndescribe('Regex matcher', () => {\n it('is satisfied when the prefix is correct and r"
},
{
"path": "src/matchers/matcher/regex-matcher/index.ts",
"chars": 1532,
"preview": "import regex from './regex';\nimport { regexBuilder } from './regex-builder';\nimport { Matcher } from '../../matcher.inte"
},
{
"path": "src/matchers/matcher/regex-matcher/regex-builder.spec.ts",
"chars": 428,
"preview": "import { regexBuilder } from './regex-builder';\n\ndescribe('Regex builder', () => {\n it('throws an error when could not "
},
{
"path": "src/matchers/matcher/regex-matcher/regex-builder.ts",
"chars": 415,
"preview": "import regex from './regex';\n\nclass RegexBuilder {\n public buildRegex(regexTemplate: string): RegExp {\n for (const p"
},
{
"path": "src/matchers/matcher/regex-matcher/regex.ts",
"chars": 364,
"preview": "import { create } from '../../../core/modules-loader.helper';\nimport { regex } from './regexes/default';\n\nconst modulesL"
},
{
"path": "src/matchers/matcher/regex-matcher/regexes/default.ts",
"chars": 525,
"preview": "export const regex = {\n arabianCharacters: '\\u0621-\\u064A',\n arabianNumbers: '\\u0660-\\u0669',\n standardCharacters: 'a"
},
{
"path": "src/matchers/matcher/text.matcher.spec.ts",
"chars": 3177,
"preview": "import { textMatcher } from './text.matcher';\n\ndescribe('Text matcher', () => {\n it('is satisfied when the prefix is co"
},
{
"path": "src/matchers/matcher/text.matcher.ts",
"chars": 1176,
"preview": "import { separator } from '../matchers';\nimport { Matcher } from '../matcher.interface';\n\nclass TextMatcher implements M"
},
{
"path": "src/matchers/matcher/visible.matcher.spec.ts",
"chars": 1148,
"preview": "import { visibleMatcher } from './visible.matcher';\n\ndescribe('Visible matcher', () => {\n it('is satisfied when the pre"
},
{
"path": "src/matchers/matcher/visible.matcher.ts",
"chars": 472,
"preview": "import { Matcher } from '../matcher.interface';\n\nclass VisibleMatcher implements Matcher {\n public isSatisfiedBy(prefix"
},
{
"path": "src/matchers/matcher.interface.ts",
"chars": 173,
"preview": "export interface Matcher {\n isSatisfiedBy(prefix: string, name?: string): boolean;\n match(element: object, param1?: st"
},
{
"path": "src/matchers/matchers.spec.ts",
"chars": 5275,
"preview": "import { create } from './matchers';\n\nconst matchers = create();\n\ndescribe('Matchers', () => {\n it('throws an error whe"
},
{
"path": "src/matchers/matchers.ts",
"chars": 1183,
"preview": "import * as matchers from './matcher';\nimport { Matcher } from './matcher.interface';\n\nexport const separator = ':';\n\ncl"
},
{
"path": "src/pages/base.ts",
"chars": 3320,
"preview": "import config from '../core/config.helper';\nimport { waitForInvisibilityOf, waitForVisibilityOf } from '../web/cucumber/"
},
{
"path": "src/pages/form.ts",
"chars": 740,
"preview": "import { fromHandlers } from '../form-handlers';\nimport { transformers } from '../transformers';\nimport Base from './bas"
},
{
"path": "src/pages/index.ts",
"chars": 123,
"preview": "import BasePage from './base';\nimport FormPage from './form';\n\nexport const Base = BasePage;\nexport const Form = FormPag"
},
{
"path": "src/protractor.conf.ts",
"chars": 4135,
"preview": "require('./core/prototypes');\nimport * as jestExpect from 'expect';\nimport * as path from 'path';\nimport config from './"
},
{
"path": "src/rest/api-request.ts",
"chars": 954,
"preview": "import { Headers } from 'node-fetch';\nimport FormData = require('form-data');\n\ninterface HeaderList {\n [name: string]: "
},
{
"path": "src/rest/api-response.spec.ts",
"chars": 806,
"preview": "import { ApiResponse } from './api-response';\n\nconst response = new ApiResponse(200, { type: 'Fiat', model: '500', color"
},
{
"path": "src/rest/api-response.ts",
"chars": 745,
"preview": "import * as _ from 'lodash';\nimport Ajv from 'ajv';\nconst ajv = new Ajv({ allErrors: true });\n\nexport class ApiResponse "
},
{
"path": "src/rest/rest-api-service.ts",
"chars": 813,
"preview": "import fetch from 'node-fetch';\nimport { ApiResponse } from './api-response';\n\nexport class RestApiService {\n private r"
},
{
"path": "src/step_definitions/api.ts",
"chars": 2417,
"preview": "import { defineSupportCode } from 'cucumber';\nimport config from '../core/config.helper';\nimport { RestApiService } from"
},
{
"path": "src/step_definitions/debug.ts",
"chars": 138,
"preview": "import { Then } from 'cucumber';\n\nThen(/^I wait for \"([^\"]*)\" seconds$/, seconds => {\n return browser.sleep(Number(seco"
},
{
"path": "src/step_definitions/elements.ts",
"chars": 17672,
"preview": "import * as chai from 'chai';\nimport { When, Then } from 'cucumber';\nimport { comparators } from '../comparators';\nimpor"
},
{
"path": "src/step_definitions/email.ts",
"chars": 4953,
"preview": "import { Then } from 'cucumber';\nimport * as sugar from 'sugar-date/index';\nimport config from '../core/config.helper';\n"
}
]
// ... and 209 more files (download for full content)
About this extraction
This page contains the full source code of the TheSoftwareHouse/Kakunin GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 409 files (2.5 MB), approximately 684.3k tokens, and a symbol index with 311 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.