Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Kakunin E2E framework showcase

514 views

Published on

A showcase of E2E testing framework created by TSH team.

https://github.com/TheSoftwareHouse/Kakunin

Published in: Software
  • Be the first to comment

  • Be the first to like this

Kakunin E2E framework showcase

  1. 1. KAKUNIN E2E REINVENTED
  2. 2. ADAM POLAK PHP DEVELOPER JS DEVELOPER E2E DEVELOPER E2E DEVELOPER JS DEVELOPER TOMASZ GÓRSKI
  3. 3. KAKUNIN WORKSHOPS AGENDA ▸ HOW TO INSTALL AND CONFIGURE KAKUNIN? ▸ HOW TO NAVIGATE BETWEEN PAGES AND VALIDATE CURRENT PAGE? ▸ HOW TO INTERACT WITH ELEMENTS AND VALIDATE CONTENT? ▸ HOW TO FILL IN, CHECK OUT ITS VALUES AND VALIDATE FORMS? ▸ HOW TO CHECK EMAILS CONTENT? ▸ HOW TO DEBUG KAKUNIN?
  4. 4. KAKUNIN WORKSHOPS ▸ HOW TO EXTEND KAKUNIN WITH CUSTOM STEPS? ▸ HOW TO HANDLE NON-BUILT-IN FORM FIELD TYPES? ▸ HOW TO ADD SCENARIO SPECIFIC HOOKS? ▸ HOW TO CONNECT DIFFERENT MAILING SERVICE? AGENDA
  5. 5. WHAT IS KAKUNIN? KAKUNIN WORKSHOPS
  6. 6. WHAT IS KAKUNIN? ▸ PROTRACTOR EXTENSION ▸ GHERKIN AS A PROGRAMMING LANGUAGE ▸ BUILT-IN STEPS TO SOLVE MOST COMMON CASES ▸ EASLY EXTENDABLE ▸ HANDLES ANGULAR AND NON-ANGULAR APPS OUT OF THE BOX KAKUNIN
  7. 7. WHAT ARE WE GOING TO TEST? KAKUNIN WORKSHOPS
  8. 8. INSTALATION HOW TO INSTALL? { "name": "workshop", "version": "0.0.1", "description": "", "main": "index.js", "scripts": { "test": "echo "Error: no test specified" && exit 1" }, "author": "", "license": "ISC" } cd kakunin-workshop mkdir kakunin-workshop npm init
  9. 9. INSTALATION HOW TO INSTALL? npm install webdriver-manager
 npm install protractor
 npm install kakunin { ... "scripts": { ... "kakunin": "NODE_ENV=prod kakunin" }, ... "devDependencies": { ...
 "webdriver-manager": "12.0.6", "protractor": "5.1.2", "kakunin": "1.0.1" } }
  10. 10. INSTALATION HOW TO INSTALL? npm run kakunin init cd step_definitions
 ln -s ../node_modules/kakunin/dist/step_definitions/elements.js kakunin-elements.js ln -s ../node_modules/kakunin/dist/step_definitions/debug.js kakunin-debug.js ln -s ../node_modules/kakunin/dist/step_definitions/file.js kakunin-file.js ln -s ../node_modules/kakunin/dist/step_definitions/form.js kakunin-form.js ln -s ../node_modules/kakunin/dist/step_definitions/email.js kakunin-email.js ln -s ../node_modules/kakunin/dist/step_definitions/generators.js kakunin-generators.js ln -s ../node_modules/kakunin/dist/step_definitions/navigation.js kakunin-navigation.js
  11. 11. INSTALATION HOW TO INSTALL? npm run kakunin Scenario: If you can see this in console then hook is working properly. ✔ When I visit the "page" page ✔ And I generate random "name" as "myName" ✔ Then my matcher "e:name" matches "v:myName" ✔ And my matcher "e:name" matches "Bob" 1 scenario (1 passed) 4 steps (4 passed) 0m00.757s
  12. 12. INSTALATION HOW TO RUN A SINGLE SCENARIO? npm run kakunin -- --tags @someTag npm run kakunin -- --tags="@someTag and @otherTag" npm run kakunin -- --tags="@someTag or @otherTag" npm run kakunin -- --tags="not @someTag"
  13. 13. NAVIGATE BETWEEN PAGES KAKUNIN WORKSHOPS
  14. 14. NAVIGATE BETWEEN PAGES PAGE OBJECT const { BasePage } = require('kakunin'); class MainPage extends BasePage { constructor() { super(); this.url = '/'; } } module.exports = new MainPage();
  15. 15. NAVIGATE BETWEEN PAGES SCENARIO - NAVIGATING Feature: Load main page Scenario: Load main page as homepage Given/When I visit the "main" page The name of a page object file. For this case there has to be a file "main.js" in pages directory.
  16. 16. NAVIGATE BETWEEN PAGES SCENARIO - CHECKING OUT ON WHAT PAGE WE ARE Then the "main" page is displayed The name of a page object file. For this case there has to be a file "main.js" in pages directory.
  17. 17. NAVIGATE BETWEEN PAGES SCENARIO - DEBUGGING Give/When/Then I wait for "20" seconds Give/When/Then I pause
  18. 18. NAVIGATE BETWEEN PAGES WHAT ABOUT URL WITH PARAMETERS ? /suppliers/some-supplier-id/clients/some-client-id /suppliers/:supplierId/clients/:clientId ... this.url = '/suppliers/:supplierId/clients/:clientId'; ...
  19. 19. NAVIGATE BETWEEN PAGES PAGE CONTEXT Then the "main" page is displayed EVERY BUILT-IN STEP (EXCEPT NAVIGATION ONES) WILL HAVE AN ACCESS TO MAIN PAGE OBJECT.
  20. 20. INTERACTING WITH ELEMENTS INTERACTING WITH ELEMENTS
  21. 21. INTERACTING WITH ELEMENTS Then the "showActive" element is visible The name of a selector from current page object file. ELEMENT VISIBILITY
  22. 22. INTERACTING WITH ELEMENTS SELECTORS ▸ defines access to element ▸ support for protractor locators
 http://www.protractortest.org/#/api? view=ProtractorBy ▸ support for angular only attributes
  23. 23. INTERACTING WITH ELEMENTS SELECTORS ▸ id attribute, custom attribute, custom class, unique class - GOOD ▸ for angular app: ng-model, ng-repeat - GOOD ▸ html tag - BAD ▸ angular validation classes (ng-valid, ng-pristine etc) - VERY BAD
  24. 24. INTERACTING WITH ELEMENTS SELECTORS this.mySelectorName = $('.css-selector'); this.mySelectorName = $('html-tag'); this.mySelectorName = $('html-tag.css-selector'); this.mySelectorName = element(by.repeater('project in projects'));
  25. 25. INTERACTING WITH ELEMENTS Then the "showActive" element is visible ELEMENT VISIBILITY - PROBLEM THE ELEMENT MUST BE VISIBLE RIGHT AWAY. IT DOES NOT WAIT FOR ELEMENT TO BE VISIBLE.
  26. 26. INTERACTING WITH ELEMENTS When/Given/Then I wait for "visibilityOf" of the "showActive" element ELEMENT VISIBILITY - SOLUTION uses protractors expected conditions internally, for example visibilityOf invisibilityOf
  27. 27. INTERACTING WITH ELEMENTS ELEMENT VISIBILITY - WHAT ELSE? When/Given/Then the "showCompleted" element is present When/Given/Then the "showCompleted" element is not visible When/Given/Then the "showCompleted" element is not present
  28. 28. INTERACTING WITH ELEMENTS When I fill the "myForm" form with: | fieldSelector | value to be used | Form selector Input selector this.myForm = $('.add-todo'); this.fieldSelector = $('.add-todo input[name="add-todo"]'); FILLING UP FORM
  29. 29. INTERACTING WITH ELEMENTS Then the "myForm" form is filled with: | fieldSelector | value to be used | Form selector Input selector CHECKING OUT FORM VALUE
  30. 30. INTERACTING WITH ELEMENTS WHAT ABOUT DIFFERENT FIELD TYPES? ▸ supports all of the basic html field types (radio, checkbox, textarea, select, file, text) ▸ allows to add custom made handlers for field types
  31. 31. INTERACTING WITH ELEMENTS HOW ABOUT SUBMITTING ? When I click the "addTodoButton" element The name of a selector from current page object file.
  32. 32. CUSTOM STEPS CUSTOM STEPS
  33. 33. CUSTOM STEPS OUR STEP Given I am logged in as an "submitter" User role
  34. 34. CUSTOM STEPS CUCUMBER 2.0 - STEP DEFINITION const { defineSupportCode } = require('kakunin'); defineSupportCode(({ Given }) => { Given(/^I am logged in as an "([^"]*)"$/, function (user) { return Promise.resolve(); }); });
  35. 35. CUSTOM STEPS ACCESS PAGE FROM CODE const myPage = browser.page.myPage;
  36. 36. CUSTOM STEPS BASE PAGE METHODS visit() click(elementSelectorName) isDisabled(elementSelectorName) isPresent(elementSelectorName) isVisible(elementSelectorName) isOn()
  37. 37. CUSTOM STEPS BASE PAGE METHODS getNumberOfElements(elementSelectorName) scrollIntoElement(elementSelectorName, optionalIndexForArrayElement)
  38. 38. CUSTOM STEPS BASE PAGE METHODS fillForm(formData) [ [ 'fieldSelector', 'fieldValue' ] ] checkForm(formData) [ [ 'fieldSelector', 'fieldValue' ] ]
  39. 39. CUSTOM STEPS BASE PAGE METHODS fillField(fieldSelector, fiedlValue) checkField(fieldSelector, fiedlValue) EVERY METHOD RETURNS A PROMISE
  40. 40. CUSTOM STEPS USER PROVIDER this.userProvider.getUser(userName); accounts: { userName: { accounts: [ { email: 'some@email.com', password: 'somePassword' } ] }
 }
  41. 41. CUSTOM STEPS USER PROVIDER this.currentUser = { account: { email: 'some@email.com', password: 'somePassword' }, type: "userName" }
  42. 42. HOOKS HOOKS
  43. 43. HOOKS HOOK DEFINITION const { defineSupportCode } = require('kakunin'); defineSupportCode(({ After }) => { After(() => {
 return Promise.resolve();
 }); }); defineSupportCode(({ After }) => { After('@someTag', () => {
 return Promise.resolve();
 }); });
  44. 44. HOOKS HOOK DEFINITION - EXECUTE REMOTE JS CODE protractor.browser.executeScript('your js code to be run inside a browser');
  45. 45. HOOKS HOOK DEFINITION - CLEAR COOKIES protractor.browser.manage().deletAllCookies();
  46. 46. HOOKS RESTORE APP STATE FIXTURES_RELOAD_HOST=http://192.168.99.100/app_e2e.php/_e2e/load_fixtures @reloadFixtures
  47. 47. CUSTOM HANDLERS HANDLERS
  48. 48. CUSTOM HANDLERS INTERFACE isSatisfiedBy(element, elementName) { return Promise.resolve(); } handleFill(page, elementName, desiredValue) { return Promise.resolve(); } handleCheck(page, elementName, desiredValue) { return Promise.resolve(); } getPriority() { return 1000; }
  49. 49. CUSTOM HANDLERS HANDLER SERVICE - ADDING NEW HANDLER const { handlers } = require('kakunin'); class MyHandler {} handler.addHandler(new MyHandler());
  50. 50. CUSTOM HANDLERS CUSTOM SELECT HTML <div class="form__field serviceMarket form__field--danger"> <label class="form__label form__label--required">Service Market</label> <div class="Select Select--single is-clearable is-focused is-open"> <div class="Select-control"> </div> <div class="Select-menu-outer"> <div role="listbox" class="Select-menu"> <div style="overflow: visible; width: 0px;"> <div aria-label="grid" class="ReactVirtualized__Grid ReactVirtualized__List VirtualSelectGrid"> <div class="ReactVirtualized__Grid__innerScrollContainer"> <div class="select__option" data-e2e="/app_e2e.php/service_markets/ 80a06089-2f94-4992-8f7c-1d30fc7b8a3e">SME Services</div> </div> </div> </div> </div> </div> </div> </div>
  51. 51. CUSTOM HANDLERS CUSTOM SELECT HTML - SELECTED OPTION <div class="form__field serviceMarket"> <label class="form__label form__label--required">Service Market</label> <div class="Select Select--single is-clearable has-value"> <input type="hidden" name="serviceMarket" value="/app_e2e.php/service_markets/ 80a06089-2f94-4992-8f7c-1d30fc7b8a3e"> </div> </div>
  52. 52. TRANSFORMERS TRANSFORMERS
  53. 53. TRANSFORMERS A WAY TO GENERATE OR TRANSFORM VALUES FROM
 HUMAN READABLE VERSION TO APP USABLE.
  54. 54. TRANSFORMERS g:generatorName:param1:param2:param...N AVAILABLE TRANSFORMERS d:dictionaryName:dictionaryKeyName v:variableName
  55. 55. TRANSFORMERS When I fill the "myForm" form with: | projectUrl | http://some-url.com | | projectType | 1234 | WITHOUT TRANSFORMERS When I fill the "myForm" form with: | projectUrl | g:projectUrl | | projectType | d:projectTypes:website | WITH TRANSFORMERS
  56. 56. TRANSFORMERS GENERATORS const { generators } = require('kakunin'); class ProjectUrlGenerator{ isSatisfiedBy(name) { return this.name === 'projectUrl'; } generate(params) { return Promise.resolve('http://some-url.pl'); } } generators.addGenerator(new ProjectUrlGenerator());
  57. 57. TRANSFORMERS DICTIONARIES const { dictionaries } = require('kakunin'); class ProjectTypesDictionary{ constructor() { this.values = { 'myServiceMarket':'/service_markets/80a06089-2f94-4992-8f7c-1d30fc7b8a3e' }; this.name = 'projectTypes'; } isSatisfiedBy(name) { return this.name === name; } getMappedValue(key) { return this.values[key]; } } dictionaries.addDictionary(new ProjectTypesDictionary());
  58. 58. EMAILS EMAILS
  59. 59. EMAILS CONFIGURATION email: { type: 'mailtrap', config: { apiKey: '81279e9fb9351ebdf7aefef89e6a5701', inboxId: '203459', url: 'https://mailtrap.io/api/v1' } }, clearEmailInboxBeforeTests: true mailer_host: smtp.mailtrap.io mailer_user: efb44191c15785 mailer_password: 821a734f231165 mailer_port: 2525 mailer_default_from: example@example.com
  60. 60. EMAILS CHECKING EMAILS Then the email has been sent: | currentUser | | | | | subject | some subject | | | | html_body | some id | | | | file | some file | PDF | 9000 | ALLOWS TO FILTER EMAIL CONTENT BY MULTIPLE FILEDS
  61. 61. EMAILS CHECKING EMAILS - FILTERS ▸ currentUser - checks if email was sent to current logged user, requires integration with Kakunin User Provider ▸ subject - allows to check email subject ▸ html_body - allows to check content of email ▸ file - allows to check attachments, it's type, name and size ALL FILTERS SUPPORT MATCHERS
  62. 62. REGULAR EXPRESSIONS REGULAR EXPRESSIONS
  63. 63. REGULAR EXPRESSIONS CUSTOM REGEX module.exports = { matchingStoreId: '(STORE-[0-9]{6}-[0-9]{4})', emailSubject: '(Join Our Store - STORE-[0-9]{6}-[0-9]{4}‫اﻟﯿﻨﺎ‬ ‫اﻧﻀﻢ‬ -)' }; USAGE r:matchingStoreId
  64. 64. CONTENT VALIDATION CONTENT VALIDATION
  65. 65. CONTENT VALIDATION FORM VALIDATIONS Then the error messages should be displayed: | element | errorMessage | | nameValidationMsg | This value should not be blank. | | targetMarketSegmentValidationMsg | This value should not be blank. | this.nameValidationMsg = $('.name .form__error');
  66. 66. CONTENT VALIDATION VALIDATE ELEMENT CONTENT Then there are "equal 1" "todos" elements uses chai.js expectations equal X above X between X Y at least X
  67. 67. CONTENT VALIDATION VALIDATE ELEMENT CONTENT Then there are "equal 1" "todos" elements REQUIRES LIST SELECTOR
  68. 68. CONTENT VALIDATION VALIDATE ELEMENT CONTENT Then there are "equal 1" "todos" elements this.todos = $$('ul li'); this.todos = element.all(by.css('ul li'));
  69. 69. CONTENT VALIDATION VALIDATE ELEMENT CONTENT Then there is element "myElement" with value "r:notEmpty" Then there is element "myElement" with value "t:some text" Then there is element "myElement" with value "f:isClickable"
  70. 70. MATCHERS CONTENT VALIDATION
  71. 71. ALLOW TO VALIDATE FILED VALUE, BUT ALSO BEHAVIOUR (FOR EXAMPLE IF ELEMENT IS
 CLICKABLE) CONTENT VALIDATION
  72. 72. CUSTOM MATCHER const { matchers } = require('kakunin'); class MyMatcher { isSatisfiedBy(prefix, name) { return prefix === 'm:' && name === 'pending'; } match(protractorElement, matcherName) { return protractorElement.getText().then((value) => value === 'pending'); } } matchers.addMatcher(new MyMatcher()); CONTENT VALIDATION
  73. 73. CONTENT VALIDATION VALIDATE ELEMENT CONTENT - DETAILED CHECK Then there are "equal 1" following elements for element "todos": | element | value | | removeButton | f:isVisible | REQUIRES LIST SELECTOR CHILD ELEMENTS MUST BE LOCATORS, NOT ELEMENTS
  74. 74. CONTENT VALIDATION this.todos = $$('ul li'); Then there are "equal 1" following elements for element "todos": | element | value | | removeButton | f:isVisible | this.removeButton = by.css('.remove'); VALIDATE ELEMENT CONTENT - DETAILED CHECK
  75. 75. FILE DOWNLOAD CONTENT VALIDATION
  76. 76. CONTENT VALIDATION FILE DOWNLOAD Then the file "fileName.extension" should be downloaded downloaded file name, the same as the one normally have in browser
  77. 77. CONTENT VALIDATION FILE DOWNLOAD - CLEAR DOWNLOAD DIRECTORY @downloadClearAfter @downloadClearBefore
  78. 78. QUESTIONS ? KAKUNIN WORKSHOPS
  79. 79. THANK YOU

×