From e0113a30d467fff751bd2bcbcb30b4dec8590f82 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Sun, 11 Mar 2018 11:03:15 -0300 Subject: [PATCH 01/45] More code cleaning --- src/helpers/objects.processor.js | 6 +++--- test/features/window.tab.manipulation.feature | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/helpers/objects.processor.js b/src/helpers/objects.processor.js index 1f38768..66a941b 100644 --- a/src/helpers/objects.processor.js +++ b/src/helpers/objects.processor.js @@ -16,17 +16,17 @@ const dictionaryObjectsParts = '^(?:([a-zA-Z0-9_-]+) from ([a-zA-Z0-9_-]+) dicti // Todo do we need this in csp-qa function injectInto (locator, injection) { const lastInjectionSymbol = injection.slice(-1); - const lastLocatorSumbol = locator.slice(-1); + const lastLocatorSymbol = locator.slice(-1); if (lastInjectionSymbol !== ']') { // Add ']' to the end of injection only if missing (for backward compatibility) injection += ']'; } - if (lastLocatorSumbol === ')') { + if (lastLocatorSymbol === ')') { // If our locator ends with round brackets return injectInto(locator.replace(/\)$/, ''), injection) + ')'; } - if (lastLocatorSumbol === ']') { + if (lastLocatorSymbol === ']') { if (locator.match(/\[[0-9]+\]$/)) { // Locator ends with brackets, which contains some xpath num const nums = locator.match(/\[[0-9]+\]$/)[0]; diff --git a/test/features/window.tab.manipulation.feature b/test/features/window.tab.manipulation.feature index feff72d..e475231 100644 --- a/test/features/window.tab.manipulation.feature +++ b/test/features/window.tab.manipulation.feature @@ -19,4 +19,3 @@ Feature: Window/tab When I open new window When I close current tab Then txtHeader from main page should be present - From 2cad97b928a75f98556e918e2c43d5fd21120e24 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Sun, 11 Mar 2018 11:12:00 -0300 Subject: [PATCH 02/45] Added waitForLoaded function --- src/helpers/wait.for.loaded.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/helpers/wait.for.loaded.js diff --git a/src/helpers/wait.for.loaded.js b/src/helpers/wait.for.loaded.js new file mode 100644 index 0000000..315c796 --- /dev/null +++ b/src/helpers/wait.for.loaded.js @@ -0,0 +1,32 @@ +module.exports = function waitForLoaded (callback) { + /** + * Wait for page to get fully loaded + * @param {callback} callback - A callback to run + */ + const timeout = 100; + let loaderSelectors = global.loaderSelectors; + + let finishedLoadingConditions = function () { + // Check if jQuery is present on the page and if any XMLHttpRequests (AJAX) are in progress + if (typeof $ !== 'undefined' && $.active) { + return false; + } + + // Check if any loaders are still present on the page + return !loaderSelectors.some((selector) => document.querySelector(selector)); + }; + + // If loading of the page was finished - launch callback function + if (finishedLoadingConditions()) { + callback(); + } + + // If loading of the page was not finished - relaunch finishedLoadingConditions() each 100 ms + let interval = setInterval(function () { + if (finishedLoadingConditions()) { + clearInterval(interval); + callback(); + } + }, timeout); + +}; From 15b41882afdddd844fd524ea6ec17bb1fd3184b2 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Sun, 11 Mar 2018 11:13:17 -0300 Subject: [PATCH 03/45] Refactored wdio.conf.js to use steps.conf.js --- test/steps.conf.js | 24 ++++++++++++++++++++++++ test/wdio.conf.js | 21 ++++++++++++--------- 2 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 test/steps.conf.js diff --git a/test/steps.conf.js b/test/steps.conf.js new file mode 100644 index 0000000..a2343b3 --- /dev/null +++ b/test/steps.conf.js @@ -0,0 +1,24 @@ +// Array of selectors for loaders that appear when page is loaded or XHR/AJAX requests are done +let loaderSelectors = [ + 'div:not([style*="display: none"])[class*="test-loader"]', + 'div:not([style*="visibility: hidden"])[class*="test-loader"]' +]; + +// Object that contains pathes to all page objects used for tests +let pages = { + main: require('./features/page_objects/main'), + values: require('./features/dictionary_objects/values') +}; + +// Object that contains methods for random id generation +let id = { + value: '', + regenerate: () => this.value === (new Date()).getTime(), + getId: () => this.value +}; + +module.exports = { + loaderSelectors, + pages, + id +}; diff --git a/test/wdio.conf.js b/test/wdio.conf.js index c296d25..d4056c6 100644 --- a/test/wdio.conf.js +++ b/test/wdio.conf.js @@ -192,19 +192,22 @@ exports.config = { global.assert = chai.assert; global.should = chai.should(); - global.pages = { - main: require('./features/page_objects/main'), - values: require('./features/dictionary_objects/values') - }; + let stepsConfig = require('./steps.conf.js'); - global.id = { - value: '', - regenerate: () => this.value === (new Date()).getTime(), - getId: () => this.value - }; + global.loaderSelectors = stepsConfig.loaderSelectors || [ + 'div:not([style*="display: none"])[class*="loader"]', + 'div:not([style*="visibility: hidden"])[class*="loader"]' + ]; + + global.pages = stepsConfig.pages; + + global.id = stepsConfig.id; global.objectsProcessor = {}; + // Add browser.waitForLoaded() command to wait for page to get fully loaded + browser.addCommand('waitForLoaded', require('../src/helpers/wait.for.loaded.js')); + } /** * Runs before a WebdriverIO command gets executed. From 13ab29ae87cbb009f03fd72c8995690ae9e517f6 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Sun, 11 Mar 2018 11:16:34 -0300 Subject: [PATCH 04/45] Added 'I click' step and test --- src/steps/when.js | 12 +++++++++++- test/features/show.hide.timeout.feature | 7 ++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/steps/when.js b/src/steps/when.js index 6801464..7218e04 100644 --- a/src/steps/when.js +++ b/src/steps/when.js @@ -1,13 +1,23 @@ /* eslint new-cap: 0 */ const { defineSupportCode } = require('cucumber'); -const { dictionaryObject, getDictionaryObject } = require('../helpers/objects.processor'); +const { pageObject, getPageObject, dictionaryObject, getDictionaryObject } = require('../helpers/objects.processor'); const { _r, getInteger } = require('../helpers/utils'); module.exports = function () { defineSupportCode(({ When }) => { + When(_r(`I click ${pageObject}$`), (element) => { + /** + * Click on the element + * @param {pageObject} element - String or "page"."object" to select the element + */ + const locator = getPageObject(element); + + browser.waitForExist(locator).waitForLoaded().focus(locator).click(locator); + }); + When(_r(`I wait ${dictionaryObject} ms$`), (timeObject) => { /** * Wait for specified amount of milliseconds diff --git a/test/features/show.hide.timeout.feature b/test/features/show.hide.timeout.feature index 7078e1f..c78e908 100644 --- a/test/features/show.hide.timeout.feature +++ b/test/features/show.hide.timeout.feature @@ -14,4 +14,9 @@ Feature: Present / Not present Then txtHeader from main page should be present Then I wait txtTimeout from values dictionary ms Then divTimeout from main page should not be present - Then txtHeader from main page should be present \ No newline at end of file + Then txtHeader from main page should be present + + Scenario: I click step should click on the element + Given I open "http://localhost:9000" + Then I click txtHeader from main page + When I wait "5000" ms From 377ee6f49bbb265cb321e1e82938019dcc51f853 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Mon, 12 Mar 2018 22:22:56 -0300 Subject: [PATCH 05/45] Renamed waitForPageToLoad() --- src/helpers/{wait.for.loaded.js => wait.for.page.to.load.js} | 2 +- src/steps/when.js | 3 +-- test/wdio.conf.js | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) rename src/helpers/{wait.for.loaded.js => wait.for.page.to.load.js} (94%) diff --git a/src/helpers/wait.for.loaded.js b/src/helpers/wait.for.page.to.load.js similarity index 94% rename from src/helpers/wait.for.loaded.js rename to src/helpers/wait.for.page.to.load.js index 315c796..613e6c2 100644 --- a/src/helpers/wait.for.loaded.js +++ b/src/helpers/wait.for.page.to.load.js @@ -1,4 +1,4 @@ -module.exports = function waitForLoaded (callback) { +module.exports = function waitForPageToLoad (callback) { /** * Wait for page to get fully loaded * @param {callback} callback - A callback to run diff --git a/src/steps/when.js b/src/steps/when.js index 7218e04..e79fc02 100644 --- a/src/steps/when.js +++ b/src/steps/when.js @@ -4,7 +4,6 @@ const { defineSupportCode } = require('cucumber'); const { pageObject, getPageObject, dictionaryObject, getDictionaryObject } = require('../helpers/objects.processor'); const { _r, getInteger } = require('../helpers/utils'); - module.exports = function () { defineSupportCode(({ When }) => { @@ -15,7 +14,7 @@ module.exports = function () { */ const locator = getPageObject(element); - browser.waitForExist(locator).waitForLoaded().focus(locator).click(locator); + browser.waitForExist(locator).waitForPageToLoad().focus(locator).click(locator); }); When(_r(`I wait ${dictionaryObject} ms$`), (timeObject) => { diff --git a/test/wdio.conf.js b/test/wdio.conf.js index d4056c6..93eda84 100644 --- a/test/wdio.conf.js +++ b/test/wdio.conf.js @@ -205,8 +205,8 @@ exports.config = { global.objectsProcessor = {}; - // Add browser.waitForLoaded() command to wait for page to get fully loaded - browser.addCommand('waitForLoaded', require('../src/helpers/wait.for.loaded.js')); + // Add browser.waitForPageToLoad() command to wait for page to get fully loaded + browser.addCommand('waitForPageToLoad', require('../src/helpers/wait.for.page.to.load.js')); } /** From 5e3c0e900b35e18e7ab817e94160150858e92960 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Tue, 13 Mar 2018 21:43:32 -0300 Subject: [PATCH 06/45] Refactored stepsConfig for global usage --- src/helpers/objects.processor.js | 17 +++++++++-------- src/helpers/wait.for.page.to.load.js | 4 +++- test/wdio.conf.js | 14 +++----------- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/helpers/objects.processor.js b/src/helpers/objects.processor.js index 66a941b..226e085 100644 --- a/src/helpers/objects.processor.js +++ b/src/helpers/objects.processor.js @@ -1,5 +1,6 @@ /* eslint no-param-reassign: 0 */ /* eslint no-undef: 0 */ +/* global stepsConfig */ const { _r } = require('./utils'); @@ -49,13 +50,13 @@ function pageObjectGetter (str) { const page = match[2]; const object = match[1]; - if (!pages[page]) { + if (!stepsConfig.pages[page]) { throw new Error(`"${page}" page is missing`); } - if (!pages[page][object]) { + if (!stepsConfig.pages[page][object]) { throw new Error(`"${object}" page object is missing for the "${page}" page`); } - return pages[page][object]; + return stepsConfig.pages[page][object]; } throw new Error(`Unknown Page Object type for "${str}"`); } @@ -63,7 +64,7 @@ function pageObjectGetter (str) { function getPageObject (str) { const pageObjectGetterFunc = objectsProcessor.pageObjectGetter || pageObjectGetter; const value = pageObjectGetterFunc(str); - const idValue = value.replace(_r(regDynamicId, 'g'), id.getId()); + const idValue = value.replace(_r(regDynamicId, 'g'), stepsConfig.id.getId()); const injection = `not(ancestor-or-self::*[contains(@style,"visibility: hidden;") or contains(@style,"display: none") or contains(@class,"x-hide-offsets")])`; const injectedValue = injectInto(idValue, injection); @@ -81,13 +82,13 @@ function dictionaryGetter (str) { const dictionary = match[2]; const object = match[1]; - if (!pages[dictionary]) { + if (!stepsConfig.pages[dictionary]) { throw new Error(`"${dictionary}" page is missing`); } - if (!pages[dictionary][object]) { + if (!stepsConfig.pages[dictionary][object]) { throw new Error(`"${object}" page object is missing for the "${dictionary}" page`); } - return pages[dictionary][object]; + return stepsConfig.pages[dictionary][object]; } if (match[3] !== undefined) { return match[3]; @@ -98,7 +99,7 @@ function dictionaryGetter (str) { function getDictionaryObject (str) { const dictionaryGetterFunc = objectsProcessor.dictionaryGetter || dictionaryGetter; const value = dictionaryGetterFunc(str); - const idValue = value.replace(_r(regDynamicId, 'g'), id.getId()); + const idValue = value.replace(_r(regDynamicId, 'g'), stepsConfig.id.getId()); return idValue; } diff --git a/src/helpers/wait.for.page.to.load.js b/src/helpers/wait.for.page.to.load.js index 613e6c2..000073e 100644 --- a/src/helpers/wait.for.page.to.load.js +++ b/src/helpers/wait.for.page.to.load.js @@ -1,10 +1,12 @@ +/* global stepsConfig */ + module.exports = function waitForPageToLoad (callback) { /** * Wait for page to get fully loaded * @param {callback} callback - A callback to run */ const timeout = 100; - let loaderSelectors = global.loaderSelectors; + let loaderSelectors = stepsConfig.loaderSelectors; let finishedLoadingConditions = function () { // Check if jQuery is present on the page and if any XMLHttpRequests (AJAX) are in progress diff --git a/test/wdio.conf.js b/test/wdio.conf.js index 93eda84..712e68d 100644 --- a/test/wdio.conf.js +++ b/test/wdio.conf.js @@ -192,20 +192,12 @@ exports.config = { global.assert = chai.assert; global.should = chai.should(); - let stepsConfig = require('./steps.conf.js'); - - global.loaderSelectors = stepsConfig.loaderSelectors || [ - 'div:not([style*="display: none"])[class*="loader"]', - 'div:not([style*="visibility: hidden"])[class*="loader"]' - ]; - - global.pages = stepsConfig.pages; - - global.id = stepsConfig.id; + // Makes stepsConfig visible globally to use stepsConfig.pages, stepsConfig.id, stepsConfig.loaderSelectors + global.stepsConfig = require('./steps.conf.js'); global.objectsProcessor = {}; - // Add browser.waitForPageToLoad() command to wait for page to get fully loaded + // Adds browser.waitForPageToLoad() command to wait for page to get fully loaded browser.addCommand('waitForPageToLoad', require('../src/helpers/wait.for.page.to.load.js')); } From e323c9b0124ac517600485c8c8e06e878d4de4e9 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Tue, 13 Mar 2018 22:01:10 -0300 Subject: [PATCH 07/45] Fixed mocha tests to include stepsConfig --- test/mocha/objects.processor.spec.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/mocha/objects.processor.spec.js b/test/mocha/objects.processor.spec.js index 33f6e0a..60fc3b3 100644 --- a/test/mocha/objects.processor.spec.js +++ b/test/mocha/objects.processor.spec.js @@ -12,7 +12,10 @@ const { _r } = require('../../src/helpers/utils'); const realId = 12345; -global.pages = { +// Makes stepsConfig visible globally to use stepsConfig.pages, stepsConfig.id, stepsConfig.loaderSelectors +global.stepsConfig = {}; + +global.stepsConfig.pages = { main: { object: `//div[@id='${dynamicId}']` }, @@ -21,7 +24,7 @@ global.pages = { } }; -global.id = { +global.stepsConfig.id = { getId: () => realId }; From 5d262b480ef81deb10e46abf4a89b88e1f1bc8f2 Mon Sep 17 00:00:00 2001 From: Alex Krechik Date: Sat, 17 Mar 2018 12:59:54 +0100 Subject: [PATCH 08/45] We should not ignore .vscode/settings.json file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2643cda..d639e79 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ downloads *~ .idea .vscode +!.vscode/settings.json errorShots/ From 2942a0b36fd07fdab0da043db193d82d082bd4ec Mon Sep 17 00:00:00 2001 From: Alex Krechik Date: Sat, 17 Mar 2018 13:50:31 +0100 Subject: [PATCH 09/45] Fixed finishedLoadingConditions function --- src/helpers/wait.for.page.to.load.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/wait.for.page.to.load.js b/src/helpers/wait.for.page.to.load.js index 000073e..54a07fd 100644 --- a/src/helpers/wait.for.page.to.load.js +++ b/src/helpers/wait.for.page.to.load.js @@ -15,7 +15,7 @@ module.exports = function waitForPageToLoad (callback) { } // Check if any loaders are still present on the page - return !loaderSelectors.some((selector) => document.querySelector(selector)); + return !loaderSelectors.some((selector) => browser.execute((s) => document.querySelector(s), selector)); }; // If loading of the page was finished - launch callback function From fa124c4d573d18371d41cce43d4b06f93f81e54a Mon Sep 17 00:00:00 2001 From: Alex Krechik Date: Sat, 17 Mar 2018 14:01:21 +0100 Subject: [PATCH 10/45] Fixed I click ${pageObject}$ method --- src/steps/when.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/steps/when.js b/src/steps/when.js index e79fc02..03cdca1 100644 --- a/src/steps/when.js +++ b/src/steps/when.js @@ -13,8 +13,12 @@ module.exports = function () { * @param {pageObject} element - String or "page"."object" to select the element */ const locator = getPageObject(element); + const el = $(locator); - browser.waitForExist(locator).waitForPageToLoad().focus(locator).click(locator); + // TODO - using of chain here ?? + el.waitForExist(); + el.waitForPageToLoad(); + el.click(); }); When(_r(`I wait ${dictionaryObject} ms$`), (timeObject) => { From 082857412300875026a801e50c3f24ccae3fcc69 Mon Sep 17 00:00:00 2001 From: Alex Krechik Date: Sat, 17 Mar 2018 14:06:46 +0100 Subject: [PATCH 11/45] Attempt to add .vscode folder --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index d639e79..a0fcc82 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,4 @@ node_modules downloads *~ .idea -.vscode -!.vscode/settings.json errorShots/ From 7cb8cd9dbc623b88bb1f150e7ac68a5cbf1620c8 Mon Sep 17 00:00:00 2001 From: Alex Krechik Date: Sat, 17 Mar 2018 14:12:34 +0100 Subject: [PATCH 12/45] Added vscode settings.json file --- .gitignore | 1 + .vscode/settings.json | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index a0fcc82..fa517f2 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ node_modules !.travis.yml !.editorconfig !.eslintrc.* +!.vscode *.xml .settings downloads diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c8d5f55 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,23 @@ +{ + "cucumberautocomplete.steps": [ + "src/steps/*.js" + ], + "cucumberautocomplete.syncfeatures": "test/features/*feature", + "cucumberautocomplete.strictGherkinCompletion": true, + "cucumberautocomplete.smartSnippets": true, + "cucumberautocomplete.stepsInvariants": true, + "cucumberautocomplete.customParameters": [ + { + "parameter":"(_r(", + "value":"(" + }, + { + "parameter":"${dictionaryObject}", + "value":"([a-zA-Z0-9_-]+ from [a-zA-Z0-9_-]+ dictionary|\"[^\"]*\")" + }, + { + "parameter":"${pageObject}", + "value":"([a-zA-Z0-9_-]+ from [a-zA-Z0-9_-]+ page)" + } + ], +} \ No newline at end of file From 9bb36e3d70787f4de5a59841be9e7bc8b0cfc491 Mon Sep 17 00:00:00 2001 From: Alex Krechik Date: Sun, 18 Mar 2018 13:22:56 +0100 Subject: [PATCH 13/45] Removed unnecessary waiter in the end of feature --- test/features/show.hide.timeout.feature | 1 - 1 file changed, 1 deletion(-) diff --git a/test/features/show.hide.timeout.feature b/test/features/show.hide.timeout.feature index c78e908..e2a7cd1 100644 --- a/test/features/show.hide.timeout.feature +++ b/test/features/show.hide.timeout.feature @@ -19,4 +19,3 @@ Feature: Present / Not present Scenario: I click step should click on the element Given I open "http://localhost:9000" Then I click txtHeader from main page - When I wait "5000" ms From 7a127c2aebd898b790b8c2e7c190741fdc5367aa Mon Sep 17 00:00:00 2001 From: Alex Krechik Date: Sun, 18 Mar 2018 15:55:12 +0100 Subject: [PATCH 14/45] Separate steps config logic and values provided. --- src/helpers/objects.processor.js | 4 ++-- src/steps.conf.js | 36 ++++++++++++++++++++++++++++++++ test/steps.conf.js | 14 +++---------- test/wdio.conf.js | 7 ++++--- 4 files changed, 45 insertions(+), 16 deletions(-) create mode 100644 src/steps.conf.js diff --git a/src/helpers/objects.processor.js b/src/helpers/objects.processor.js index 226e085..7405dd9 100644 --- a/src/helpers/objects.processor.js +++ b/src/helpers/objects.processor.js @@ -62,7 +62,7 @@ function pageObjectGetter (str) { } function getPageObject (str) { - const pageObjectGetterFunc = objectsProcessor.pageObjectGetter || pageObjectGetter; + const pageObjectGetterFunc = stepsConfig.objectsProcessor.pageObjectGetter || pageObjectGetter; const value = pageObjectGetterFunc(str); const idValue = value.replace(_r(regDynamicId, 'g'), stepsConfig.id.getId()); const injection = `not(ancestor-or-self::*[contains(@style,"visibility: hidden;") @@ -97,7 +97,7 @@ function dictionaryGetter (str) { } function getDictionaryObject (str) { - const dictionaryGetterFunc = objectsProcessor.dictionaryGetter || dictionaryGetter; + const dictionaryGetterFunc = stepsConfig.objectsProcessor.dictionaryGetter || dictionaryGetter; const value = dictionaryGetterFunc(str); const idValue = value.replace(_r(regDynamicId, 'g'), stepsConfig.id.getId()); diff --git a/src/steps.conf.js b/src/steps.conf.js new file mode 100644 index 0000000..7ad14cb --- /dev/null +++ b/src/steps.conf.js @@ -0,0 +1,36 @@ +const defaultFinishedLoadingConditions = (loaderSelectors) => { + // Check if jQuery is present on the page and if any XMLHttpRequests (AJAX) are in progress + if (typeof $ !== 'undefined' && $.active) { + return false; + } + + // Check if any loaders are still present on the page + return !loaderSelectors.some((selector) => browser.execute((s) => document.querySelector(s), selector)); +}; + +const defaultIdGenerator = () => (new Date()).getTime(); + +module.exports = function ({ + loaderSelectors = [], // [] - array of xpath selectors, that will be used by finishedLoadingConditions. + finishedLoadingConditions = defaultFinishedLoadingConditions, // <(loaderSelectors) => >. + pages = {}, // <{[key: string]: {[key: string]: }}> - object, that contains "key" -> "Page object" pairs. + defaultIdValue = '', // Default value of steps config Id. + idGenerator = defaultIdGenerator, // <() => > - function, that will return new generated id. + objectsProcessor = {} // objectsProcessor childs could be previded here - see objects.processor.js for more details. +}) { + const id = { + value: defaultIdValue || idGenerator.call(this), + getId: () => this.value, + regenerate: () => { + this.value = idGenerator.call(this); + } + }; + + return { + loaderSelectors, + finishedLoadingConditions: finishedLoadingConditions.bind(this, loaderSelectors), + pages, + id, + objectsProcessor + }; +}; diff --git a/test/steps.conf.js b/test/steps.conf.js index a2343b3..c1adce0 100644 --- a/test/steps.conf.js +++ b/test/steps.conf.js @@ -1,24 +1,16 @@ // Array of selectors for loaders that appear when page is loaded or XHR/AJAX requests are done -let loaderSelectors = [ +const loaderSelectors = [ 'div:not([style*="display: none"])[class*="test-loader"]', 'div:not([style*="visibility: hidden"])[class*="test-loader"]' ]; // Object that contains pathes to all page objects used for tests -let pages = { +const pages = { main: require('./features/page_objects/main'), values: require('./features/dictionary_objects/values') }; -// Object that contains methods for random id generation -let id = { - value: '', - regenerate: () => this.value === (new Date()).getTime(), - getId: () => this.value -}; - module.exports = { loaderSelectors, - pages, - id + pages }; diff --git a/test/wdio.conf.js b/test/wdio.conf.js index 712e68d..3598268 100644 --- a/test/wdio.conf.js +++ b/test/wdio.conf.js @@ -192,10 +192,11 @@ exports.config = { global.assert = chai.assert; global.should = chai.should(); - // Makes stepsConfig visible globally to use stepsConfig.pages, stepsConfig.id, stepsConfig.loaderSelectors - global.stepsConfig = require('./steps.conf.js'); + // Makes stepsConfig visible globally to use stepsConfig vars + const stepsConfigGenerator = require('../src/steps.conf.js'); + const stepsConfigPresets = require('./steps.conf.js'); - global.objectsProcessor = {}; + global.stepsConfig = stepsConfigGenerator(stepsConfigPresets); // Adds browser.waitForPageToLoad() command to wait for page to get fully loaded browser.addCommand('waitForPageToLoad', require('../src/helpers/wait.for.page.to.load.js')); From c0d1f18b542b58b818b7dedc691f6bf41e8f9dd2 Mon Sep 17 00:00:00 2001 From: Alex Krechik Date: Sun, 18 Mar 2018 16:37:31 +0100 Subject: [PATCH 15/45] Apply commands via applyStepsCommands function --- src/commands/index.js | 5 +++++ src/{helpers => commands}/wait.for.page.to.load.js | 0 test/wdio.conf.js | 4 +++- 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 src/commands/index.js rename src/{helpers => commands}/wait.for.page.to.load.js (100%) diff --git a/src/commands/index.js b/src/commands/index.js new file mode 100644 index 0000000..5972fc5 --- /dev/null +++ b/src/commands/index.js @@ -0,0 +1,5 @@ +const waitForPageToLoad = require('./wait.for.page.to.load'); + +module.exports = function applyCommands () { + browser.addCommand('waitForPageToLoad', waitForPageToLoad); +}; diff --git a/src/helpers/wait.for.page.to.load.js b/src/commands/wait.for.page.to.load.js similarity index 100% rename from src/helpers/wait.for.page.to.load.js rename to src/commands/wait.for.page.to.load.js diff --git a/test/wdio.conf.js b/test/wdio.conf.js index 3598268..9ed2717 100644 --- a/test/wdio.conf.js +++ b/test/wdio.conf.js @@ -199,7 +199,9 @@ exports.config = { global.stepsConfig = stepsConfigGenerator(stepsConfigPresets); // Adds browser.waitForPageToLoad() command to wait for page to get fully loaded - browser.addCommand('waitForPageToLoad', require('../src/helpers/wait.for.page.to.load.js')); + const applyStepsCommands = require('../src/commands'); + + applyStepsCommands(); } /** From 81bbe4e80b510b10b183dce43f8b34d1facb80df Mon Sep 17 00:00:00 2001 From: Alex Krechik Date: Sun, 18 Mar 2018 16:41:57 +0100 Subject: [PATCH 16/45] Using of config finishedLoadingConditions --- src/commands/wait.for.page.to.load.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/commands/wait.for.page.to.load.js b/src/commands/wait.for.page.to.load.js index 54a07fd..c0d16bc 100644 --- a/src/commands/wait.for.page.to.load.js +++ b/src/commands/wait.for.page.to.load.js @@ -6,19 +6,10 @@ module.exports = function waitForPageToLoad (callback) { * @param {callback} callback - A callback to run */ const timeout = 100; - let loaderSelectors = stepsConfig.loaderSelectors; - - let finishedLoadingConditions = function () { - // Check if jQuery is present on the page and if any XMLHttpRequests (AJAX) are in progress - if (typeof $ !== 'undefined' && $.active) { - return false; - } - - // Check if any loaders are still present on the page - return !loaderSelectors.some((selector) => browser.execute((s) => document.querySelector(s), selector)); - }; + const finishedLoadingConditions = stepsConfig.finishedLoadingConditions; // If loading of the page was finished - launch callback function + // TODO - add some waiter here if (finishedLoadingConditions()) { callback(); } From 46c7cc974486fad1fcb88823ef523d2a0b137d64 Mon Sep 17 00:00:00 2001 From: Alex Krechik Date: Sun, 18 Mar 2018 16:43:07 +0100 Subject: [PATCH 17/45] Using of const were possible --- src/commands/wait.for.page.to.load.js | 2 +- test/features/dictionary_objects/values.js | 2 +- test/features/page_objects/main.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commands/wait.for.page.to.load.js b/src/commands/wait.for.page.to.load.js index c0d16bc..98e767e 100644 --- a/src/commands/wait.for.page.to.load.js +++ b/src/commands/wait.for.page.to.load.js @@ -15,7 +15,7 @@ module.exports = function waitForPageToLoad (callback) { } // If loading of the page was not finished - relaunch finishedLoadingConditions() each 100 ms - let interval = setInterval(function () { + const interval = setInterval(function () { if (finishedLoadingConditions()) { clearInterval(interval); callback(); diff --git a/test/features/dictionary_objects/values.js b/test/features/dictionary_objects/values.js index c03390b..cb2529b 100644 --- a/test/features/dictionary_objects/values.js +++ b/test/features/dictionary_objects/values.js @@ -1,4 +1,4 @@ -let e = {}; +const e = {}; e.txtTimeout = '500'; diff --git a/test/features/page_objects/main.js b/test/features/page_objects/main.js index c95e845..24c2782 100644 --- a/test/features/page_objects/main.js +++ b/test/features/page_objects/main.js @@ -1,4 +1,4 @@ -let e = {}; +const e = {}; e.txtHeader = '//*[@id="header"]'; e.txtNewHeader = '//*[@id="new-header"]'; From d1fed7e80a05d67183398c6949ce3978addd1d06 Mon Sep 17 00:00:00 2001 From: Alex Krechik Date: Sun, 18 Mar 2018 17:24:28 +0100 Subject: [PATCH 18/45] Using Class for Id. Fixed mocha tests. --- src/helpers/objects.processor.js | 4 ++-- src/steps.conf.js | 14 +++++++++----- test/mocha/objects.processor.spec.js | 12 ++++-------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/helpers/objects.processor.js b/src/helpers/objects.processor.js index 7405dd9..272113e 100644 --- a/src/helpers/objects.processor.js +++ b/src/helpers/objects.processor.js @@ -64,7 +64,7 @@ function pageObjectGetter (str) { function getPageObject (str) { const pageObjectGetterFunc = stepsConfig.objectsProcessor.pageObjectGetter || pageObjectGetter; const value = pageObjectGetterFunc(str); - const idValue = value.replace(_r(regDynamicId, 'g'), stepsConfig.id.getId()); + const idValue = value.replace(_r(regDynamicId, 'g'), stepsConfig.id.id); const injection = `not(ancestor-or-self::*[contains(@style,"visibility: hidden;") or contains(@style,"display: none") or contains(@class,"x-hide-offsets")])`; const injectedValue = injectInto(idValue, injection); @@ -99,7 +99,7 @@ function dictionaryGetter (str) { function getDictionaryObject (str) { const dictionaryGetterFunc = stepsConfig.objectsProcessor.dictionaryGetter || dictionaryGetter; const value = dictionaryGetterFunc(str); - const idValue = value.replace(_r(regDynamicId, 'g'), stepsConfig.id.getId()); + const idValue = value.replace(_r(regDynamicId, 'g'), stepsConfig.id.id); return idValue; } diff --git a/src/steps.conf.js b/src/steps.conf.js index 7ad14cb..d425d68 100644 --- a/src/steps.conf.js +++ b/src/steps.conf.js @@ -18,10 +18,14 @@ module.exports = function ({ idGenerator = defaultIdGenerator, // <() => > - function, that will return new generated id. objectsProcessor = {} // objectsProcessor childs could be previded here - see objects.processor.js for more details. }) { - const id = { - value: defaultIdValue || idGenerator.call(this), - getId: () => this.value, - regenerate: () => { + const Id = class { + constructor () { + this.value = defaultIdValue || idGenerator.call(this); + } + get id () { + return this.value; + } + regenerate () { this.value = idGenerator.call(this); } }; @@ -30,7 +34,7 @@ module.exports = function ({ loaderSelectors, finishedLoadingConditions: finishedLoadingConditions.bind(this, loaderSelectors), pages, - id, + id: new Id(), objectsProcessor }; }; diff --git a/test/mocha/objects.processor.spec.js b/test/mocha/objects.processor.spec.js index 60fc3b3..269b742 100644 --- a/test/mocha/objects.processor.spec.js +++ b/test/mocha/objects.processor.spec.js @@ -9,13 +9,12 @@ const { dynamicId } = require('../../src/helpers/objects.processor'); const { _r } = require('../../src/helpers/utils'); +const stepsConfigGenerator = require('../../src/steps.conf'); const realId = 12345; // Makes stepsConfig visible globally to use stepsConfig.pages, stepsConfig.id, stepsConfig.loaderSelectors -global.stepsConfig = {}; - -global.stepsConfig.pages = { +const pages = { main: { object: `//div[@id='${dynamicId}']` }, @@ -23,12 +22,9 @@ global.stepsConfig.pages = { word: `dictionaryObject${dynamicId}` } }; +const idGenerator = () => realId; -global.stepsConfig.id = { - getId: () => realId -}; - -global.objectsProcessor = {}; +global.stepsConfig = stepsConfigGenerator({ pages, idGenerator }); describe('injectInto', () => { const data = [ From 21ce93990ce99994dd53885661fddefb8d9362a5 Mon Sep 17 00:00:00 2001 From: Alex Krechik Date: Sun, 18 Mar 2018 18:57:51 +0100 Subject: [PATCH 19/45] Some fixes due to Dima's review --- src/helpers/objects.processor.js | 4 ++-- src/{steps.conf.js => steps.conf.generator.js} | 2 +- test/mocha/objects.processor.spec.js | 2 +- test/wdio.conf.js | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) rename src/{steps.conf.js => steps.conf.generator.js} (98%) diff --git a/src/helpers/objects.processor.js b/src/helpers/objects.processor.js index 272113e..4c64ccd 100644 --- a/src/helpers/objects.processor.js +++ b/src/helpers/objects.processor.js @@ -64,7 +64,7 @@ function pageObjectGetter (str) { function getPageObject (str) { const pageObjectGetterFunc = stepsConfig.objectsProcessor.pageObjectGetter || pageObjectGetter; const value = pageObjectGetterFunc(str); - const idValue = value.replace(_r(regDynamicId, 'g'), stepsConfig.id.id); + const idValue = value.replace(_r(regDynamicId, 'g'), stepsConfig.id.idValue); const injection = `not(ancestor-or-self::*[contains(@style,"visibility: hidden;") or contains(@style,"display: none") or contains(@class,"x-hide-offsets")])`; const injectedValue = injectInto(idValue, injection); @@ -99,7 +99,7 @@ function dictionaryGetter (str) { function getDictionaryObject (str) { const dictionaryGetterFunc = stepsConfig.objectsProcessor.dictionaryGetter || dictionaryGetter; const value = dictionaryGetterFunc(str); - const idValue = value.replace(_r(regDynamicId, 'g'), stepsConfig.id.id); + const idValue = value.replace(_r(regDynamicId, 'g'), stepsConfig.id.idValue); return idValue; } diff --git a/src/steps.conf.js b/src/steps.conf.generator.js similarity index 98% rename from src/steps.conf.js rename to src/steps.conf.generator.js index d425d68..da4eee4 100644 --- a/src/steps.conf.js +++ b/src/steps.conf.generator.js @@ -22,7 +22,7 @@ module.exports = function ({ constructor () { this.value = defaultIdValue || idGenerator.call(this); } - get id () { + get idValue () { return this.value; } regenerate () { diff --git a/test/mocha/objects.processor.spec.js b/test/mocha/objects.processor.spec.js index 269b742..87e1e33 100644 --- a/test/mocha/objects.processor.spec.js +++ b/test/mocha/objects.processor.spec.js @@ -9,7 +9,7 @@ const { dynamicId } = require('../../src/helpers/objects.processor'); const { _r } = require('../../src/helpers/utils'); -const stepsConfigGenerator = require('../../src/steps.conf'); +const stepsConfigGenerator = require('../../src/steps.conf.generator'); const realId = 12345; diff --git a/test/wdio.conf.js b/test/wdio.conf.js index 9ed2717..ed39236 100644 --- a/test/wdio.conf.js +++ b/test/wdio.conf.js @@ -193,8 +193,8 @@ exports.config = { global.should = chai.should(); // Makes stepsConfig visible globally to use stepsConfig vars - const stepsConfigGenerator = require('../src/steps.conf.js'); - const stepsConfigPresets = require('./steps.conf.js'); + const stepsConfigGenerator = require('../src/steps.conf.generator'); + const stepsConfigPresets = require('./steps.conf'); global.stepsConfig = stepsConfigGenerator(stepsConfigPresets); From 475207bc48baa8819f8b899b8e25c3ecc67c8583 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Mon, 19 Mar 2018 22:51:22 -0300 Subject: [PATCH 20/45] Use ecmaVersion: 2017 for linting --- .eslintrc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index fdfe09a..a8b4ddb 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,6 +1,6 @@ { "parserOptions": { - "ecmaVersion": 6, + "ecmaVersion": 2017, "sourceType": "module" }, "env": { From bfdd711db5d89673b75771180c63e7ce0ecd5842 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Mon, 19 Mar 2018 22:52:43 -0300 Subject: [PATCH 21/45] Refactored 'I click' step to use async await --- src/steps/when.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/steps/when.js b/src/steps/when.js index 03cdca1..04b96e8 100644 --- a/src/steps/when.js +++ b/src/steps/when.js @@ -7,18 +7,20 @@ const { _r, getInteger } = require('../helpers/utils'); module.exports = function () { defineSupportCode(({ When }) => { - When(_r(`I click ${pageObject}$`), (element) => { + When(_r(`I click ${pageObject}$`), async function async (element) { /** * Click on the element * @param {pageObject} element - String or "page"."object" to select the element */ const locator = getPageObject(element); - const el = $(locator); - // TODO - using of chain here ?? - el.waitForExist(); - el.waitForPageToLoad(); - el.click(); + try { + await browser.waitForExist(locator); + await browser.waitForPageToLoad(); + return await browser.click(locator); + } catch (err) { + throw new Error(err); + } }); When(_r(`I wait ${dictionaryObject} ms$`), (timeObject) => { From 32073a5df8b62a16bea2e2889db203cca47561ad Mon Sep 17 00:00:00 2001 From: Marketionist Date: Mon, 19 Mar 2018 22:58:28 -0300 Subject: [PATCH 22/45] Switched to node.js 7 to use async await --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 03a9b8d..9fbf245 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ dist: trusty # http://docs.travis-ci.com/user/languages/javascript-with-nodejs/ language: node_js node_js: - - "6" + - "7" # http://docs.travis-ci.com/user/gui-and-headless-browsers before_install: From ad087277f24dc1067fdb74cb49d147c9a9a9efbf Mon Sep 17 00:00:00 2001 From: Marketionist Date: Tue, 20 Mar 2018 19:43:00 -0300 Subject: [PATCH 23/45] Updated tests to be more descriptive --- test/features/show.hide.timeout.feature | 8 +++++++- .../{url.validation.feature => url.manipulations.feature} | 4 ++-- ...ipulation.feature => window.tab.manipulations.feature} | 0 3 files changed, 9 insertions(+), 3 deletions(-) rename test/features/{url.validation.feature => url.manipulations.feature} (61%) rename test/features/{window.tab.manipulation.feature => window.tab.manipulations.feature} (100%) diff --git a/test/features/show.hide.timeout.feature b/test/features/show.hide.timeout.feature index e2a7cd1..199fecb 100644 --- a/test/features/show.hide.timeout.feature +++ b/test/features/show.hide.timeout.feature @@ -5,7 +5,13 @@ Feature: Present / Not present As a developer I want to check that certain value is present and not present after time - Scenario: Check I write method + Scenario: Check that certain element is present and not present after time + Given I open "http://localhost:9000" + And divTimeout from main page should be present + When I wait "5000" ms + Then divTimeout from main page should not be present + + Scenario: I wait step should wait for specified amount of milliseconds Given I open "http://localhost:9000" Then txtHeader from main page should be present Then divTimeout from main page should be present diff --git a/test/features/url.validation.feature b/test/features/url.manipulations.feature similarity index 61% rename from test/features/url.validation.feature rename to test/features/url.manipulations.feature index 93254e4..04400fa 100644 --- a/test/features/url.validation.feature +++ b/test/features/url.manipulations.feature @@ -4,8 +4,8 @@ Feature: URL In order to check steps with URL As a developer - I want to open URL and check that is a certain value + I want to perform different operations with URLs - Scenario: Check I write method + Scenario: I open step should open the specified page Given I open "http://localhost:9000" Then txtHeader from main page should be present diff --git a/test/features/window.tab.manipulation.feature b/test/features/window.tab.manipulations.feature similarity index 100% rename from test/features/window.tab.manipulation.feature rename to test/features/window.tab.manipulations.feature From 09d9d3b1076a39bf685b2f387074a1f4b7158880 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Tue, 20 Mar 2018 19:56:48 -0300 Subject: [PATCH 24/45] Refactored When steps to use async await --- src/steps/when.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/steps/when.js b/src/steps/when.js index 04b96e8..5909e6a 100644 --- a/src/steps/when.js +++ b/src/steps/when.js @@ -23,7 +23,7 @@ module.exports = function () { } }); - When(_r(`I wait ${dictionaryObject} ms$`), (timeObject) => { + When(_r(`I wait ${dictionaryObject} ms$`), async function async (timeObject) { /** * Wait for specified amount of milliseconds * @param {String} timeObject - String with specified amount of milliseconds @@ -31,21 +31,33 @@ module.exports = function () { const timeValue = getDictionaryObject.call(this, timeObject); const time = getInteger(timeValue); - browser.pause(time); + try { + return await browser.pause(time); + } catch (err) { + throw new Error(err); + } }); - When(_r('I close current tab$'), () => { + When(_r('I close current tab$'), async function async () { /** * Close current tab */ - browser.close(); + try { + return await browser.close(); + } catch (err) { + throw new Error(err); + } }); - When(_r('I open new window$'), () => { + When(_r('I open new window$'), async function async () { /** * Open default empty ('about:blank') tab */ - browser.newWindow('about:blank'); + try { + return await browser.newWindow('about:blank'); + } catch (err) { + throw new Error(err); + } }); }); From fd00403efda71bb79d52f4c825c077736754c04e Mon Sep 17 00:00:00 2001 From: Marketionist Date: Tue, 20 Mar 2018 19:57:29 -0300 Subject: [PATCH 25/45] Refactored Then steps to use async await --- src/steps/then.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/steps/then.js b/src/steps/then.js index b8bedbb..133b421 100644 --- a/src/steps/then.js +++ b/src/steps/then.js @@ -7,24 +7,32 @@ const { _r } = require('../helpers/utils'); module.exports = function () { defineSupportCode(({ Then }) => { - Then(_r(`^${pageObject} should be present$`), (element) => { + Then(_r(`^${pageObject} should be present$`), async function async (element) { /** * The element should be present * @param {pageObject} element - String or "page"."object" to select the element */ const locator = getPageObject(element); - browser.$(locator).waitForExist(); + try { + return await browser.waitForExist(locator); + } catch (err) { + throw new Error(err); + } }); - Then(_r(`^${pageObject} should not be present$`), (element) => { + Then(_r(`^${pageObject} should not be present$`), async function async (element) { /** * The element should not be present * @param {pageObject} element - String or "page"."object" to select the element */ const locator = getPageObject(element); - browser.$(locator).waitForExist(null, true); + try { + return await browser.waitForExist(locator, null, true); + } catch (err) { + throw new Error(err); + } }); }); From 0e4594a2629457efae0a05f63d25f293181081ce Mon Sep 17 00:00:00 2001 From: Marketionist Date: Tue, 20 Mar 2018 19:57:55 -0300 Subject: [PATCH 26/45] Refactored Given steps to use async await --- src/steps/given.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/steps/given.js b/src/steps/given.js index 36bbf7c..4db8a9a 100644 --- a/src/steps/given.js +++ b/src/steps/given.js @@ -7,14 +7,18 @@ const { _r } = require('../helpers/utils'); module.exports = function () { defineSupportCode(({ Given }) => { - Given(_r(`^I open ${dictionaryObject}$`), (urlDictionary) => { + Given(_r(`^I open ${dictionaryObject}$`), async function async (urlDictionary) { /** * The URL to navigate to * @type {String} or {DictionaryObject} */ const url = getDictionaryObject.call(this, urlDictionary); - browser.url(url); + try { + return await browser.url(url); + } catch (err) { + throw new Error(err); + } }); }); From c535e88a542c6ddbee32c386ab0402502a558ba0 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Tue, 20 Mar 2018 20:05:30 -0300 Subject: [PATCH 27/45] Added moveToObject for click --- src/steps/when.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/steps/when.js b/src/steps/when.js index 5909e6a..12e3dec 100644 --- a/src/steps/when.js +++ b/src/steps/when.js @@ -17,6 +17,7 @@ module.exports = function () { try { await browser.waitForExist(locator); await browser.waitForPageToLoad(); + await browser.moveToObject(locator); return await browser.click(locator); } catch (err) { throw new Error(err); From 032f67ef3bf45e9890797ccccfbe1b989a89c41f Mon Sep 17 00:00:00 2001 From: Marketionist Date: Tue, 20 Mar 2018 20:11:29 -0300 Subject: [PATCH 28/45] Added 'I doubleclick' step --- src/steps/when.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/steps/when.js b/src/steps/when.js index 12e3dec..db21e61 100644 --- a/src/steps/when.js +++ b/src/steps/when.js @@ -24,6 +24,23 @@ module.exports = function () { } }); + When(_r(`I doubleclick ${pageObject}$`), async function async (element) { + /** + * Double-click on the element + * @param {pageObject} element - String or "page"."object" to select the element + */ + const locator = getPageObject(element); + + try { + await browser.waitForExist(locator); + await browser.waitForPageToLoad(); + await browser.moveToObject(locator); + return await browser.doubleClick(locator); + } catch (err) { + throw new Error(err); + } + }); + When(_r(`I wait ${dictionaryObject} ms$`), async function async (timeObject) { /** * Wait for specified amount of milliseconds From 8fe45b307ee93316119f48a395933555795007d1 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Tue, 20 Mar 2018 22:50:46 -0300 Subject: [PATCH 29/45] Used scroll and 2 clicks for steps with clicks --- src/steps/when.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/steps/when.js b/src/steps/when.js index db21e61..f49ca15 100644 --- a/src/steps/when.js +++ b/src/steps/when.js @@ -17,7 +17,7 @@ module.exports = function () { try { await browser.waitForExist(locator); await browser.waitForPageToLoad(); - await browser.moveToObject(locator); + await browser.scroll(locator); return await browser.click(locator); } catch (err) { throw new Error(err); @@ -34,8 +34,9 @@ module.exports = function () { try { await browser.waitForExist(locator); await browser.waitForPageToLoad(); - await browser.moveToObject(locator); - return await browser.doubleClick(locator); + await browser.scroll(locator); + await browser.click(locator); + return await browser.click(locator); } catch (err) { throw new Error(err); } From a6d411aeba05b838cda893cab5a749eebc7d4f76 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Wed, 21 Mar 2018 08:16:46 -0300 Subject: [PATCH 30/45] Bumped up wdio-cucumber-framework: ^1.1.0, wdio-selenium-standalone-service: 0.0.10, webdriverio: ^4.12.0 --- package.json | 6 ++-- yarn.lock | 81 ++++++++++++++++++++++++++++++++++------------------ 2 files changed, 56 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index 16f97d3..cb8cf8a 100644 --- a/package.json +++ b/package.json @@ -41,9 +41,9 @@ "eslint": "^4.11.0", "http-server": "^0.10.0", "mocha": "^4.0.1", - "wdio-cucumber-framework": "^1.0.2", - "wdio-selenium-standalone-service": "0.0.9", + "wdio-cucumber-framework": "^1.1.0", + "wdio-selenium-standalone-service": "0.0.10", "wdio-spec-reporter": "^0.1.2", - "webdriverio": "^4.9.8" + "webdriverio": "^4.12.0" } } diff --git a/yarn.lock b/yarn.lock index 0bbadcc..f71d814 100644 --- a/yarn.lock +++ b/yarn.lock @@ -391,6 +391,16 @@ cross-spawn@^5.1.0: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^6.0.0: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + cryptiles@2.x.x: version "2.0.5" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" @@ -478,13 +488,13 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -debug@3.1.0, debug@^3.0.1: +debug@3.1.0, debug@^3.0.0, debug@^3.0.1: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" dependencies: ms "2.0.0" -debug@^2.2.0, debug@^2.6.3: +debug@^2.2.0: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: @@ -1011,6 +1021,10 @@ inquirer@^3.0.6, inquirer@~3.3.0: strip-ansi "^4.0.0" through "^2.3.6" +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" @@ -1019,6 +1033,12 @@ is-generator@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/is-generator/-/is-generator-1.0.3.tgz#c14c21057ed36e328db80347966c693f886389f3" +is-glob@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" + dependencies: + is-extglob "^2.1.1" + is-my-json-valid@^2.12.4: version "2.16.1" resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11" @@ -1235,6 +1255,10 @@ natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" +nice-try@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4" + normalize-path@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -1317,6 +1341,10 @@ path-is-inside@^1.0.1, path-is-inside@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" @@ -1367,11 +1395,7 @@ process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" -progress@1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" - -progress@^2.0.0: +progress@2.0.0, progress@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" @@ -1549,18 +1573,18 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" -selenium-standalone@^6.5.0: - version "6.11.0" - resolved "https://registry.yarnpkg.com/selenium-standalone/-/selenium-standalone-6.11.0.tgz#eb21ff65f3b2ece75061b35d4f9e7572ac0280c2" +selenium-standalone@^6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/selenium-standalone/-/selenium-standalone-6.13.0.tgz#d61d2994e196b2ff61a19de498c6711375f5a51b" dependencies: async "^2.1.4" commander "^2.9.0" - cross-spawn "^5.1.0" - debug "^2.6.3" + cross-spawn "^6.0.0" + debug "^3.0.0" lodash "^4.17.4" minimist "^1.2.0" mkdirp "^0.5.1" - progress "1.1.8" + progress "2.0.0" request "2.79.0" tar-stream "1.5.2" urijs "^1.18.4" @@ -1571,6 +1595,10 @@ semver@^5.3.0: version "5.4.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" +semver@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -1857,10 +1885,6 @@ uuid@^3.0.0, uuid@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" -validator@~9.1.1: - version "9.1.1" - resolved "https://registry.yarnpkg.com/validator/-/validator-9.1.1.tgz#3bdd1065cbd28f9d96ac806dee01030d32fd97ef" - verror@1.10.0, verror@^1.9.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" @@ -1869,12 +1893,14 @@ verror@1.10.0, verror@^1.9.0: core-util-is "1.0.2" extsprintf "^1.2.0" -wdio-cucumber-framework@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wdio-cucumber-framework/-/wdio-cucumber-framework-1.0.2.tgz#96cad58a0dde6af132dfe645a52c1f9dd849b19a" +wdio-cucumber-framework@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/wdio-cucumber-framework/-/wdio-cucumber-framework-1.1.0.tgz#b817cc15fb91a60ff537db55354a024b004cd9d4" dependencies: babel-runtime "~6.25.0" cucumber "~2.3.1" + glob "^7.1.2" + is-glob "^4.0.0" mockery "~2.1.0" wdio-sync "0.7.0" @@ -1882,12 +1908,12 @@ wdio-dot-reporter@~0.0.8: version "0.0.9" resolved "https://registry.yarnpkg.com/wdio-dot-reporter/-/wdio-dot-reporter-0.0.9.tgz#929b2adafd49d6b0534fda068e87319b47e38fe5" -wdio-selenium-standalone-service@0.0.9: - version "0.0.9" - resolved "https://registry.yarnpkg.com/wdio-selenium-standalone-service/-/wdio-selenium-standalone-service-0.0.9.tgz#c80d4ff489744d5a0b91d5189764916d4c0c8564" +wdio-selenium-standalone-service@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/wdio-selenium-standalone-service/-/wdio-selenium-standalone-service-0.0.10.tgz#cdb64d9a53fa3ea0ed3cf0ebf4fd3bdca93dcf05" dependencies: fs-extra "^0.30.0" - selenium-standalone "^6.5.0" + selenium-standalone "^6.13.0" wdio-spec-reporter@^0.1.2: version "0.1.2" @@ -1904,9 +1930,9 @@ wdio-sync@0.7.0: fibers "~2.0.0" object.assign "^4.0.3" -webdriverio@^4.9.8: - version "4.9.9" - resolved "https://registry.yarnpkg.com/webdriverio/-/webdriverio-4.9.9.tgz#695de325166528e162d5b6d29a7f2c18aafdd3aa" +webdriverio@^4.12.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/webdriverio/-/webdriverio-4.12.0.tgz#e340def272183c8168a4dd0b382322f9d7bee10d" dependencies: archiver "~2.1.0" babel-runtime "^6.26.0" @@ -1927,7 +1953,6 @@ webdriverio@^4.9.8: safe-buffer "~5.1.1" supports-color "~5.0.0" url "~0.11.0" - validator "~9.1.1" wdio-dot-reporter "~0.0.8" wgxpath "~1.0.0" From 07437d0eaa988a36e0ce1cf76af865b63fd488e1 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Wed, 21 Mar 2018 08:17:47 -0300 Subject: [PATCH 31/45] Added checkbox with some js for tests --- test/demo-app/new-page.html | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/demo-app/new-page.html b/test/demo-app/new-page.html index 9004b9d..0d2fa41 100644 --- a/test/demo-app/new-page.html +++ b/test/demo-app/new-page.html @@ -3,9 +3,24 @@ NEW PAGE FOR THE WDIO-STEPS +
New page header
New page
+
+ + +
From 493fab53234b27bc8bd70f7504548433fd692b4f Mon Sep 17 00:00:00 2001 From: Marketionist Date: Wed, 21 Mar 2018 08:18:58 -0300 Subject: [PATCH 32/45] Added tests for 'I click', 'I doubleclick' steps --- test/features/click.feature | 19 +++++++++++++++++++ test/features/page_objects/main.js | 3 +++ test/features/show.hide.timeout.feature | 11 +++-------- 3 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 test/features/click.feature diff --git a/test/features/click.feature b/test/features/click.feature new file mode 100644 index 0000000..9b324bc --- /dev/null +++ b/test/features/click.feature @@ -0,0 +1,19 @@ +@Fast +@Click + +Feature: Click + In order to check steps with clicking on the elements + As a developer + I want to check that clicking works properly + + Scenario: I click step should click on the element + Given I open "http://localhost:9000/new-page.html" + And checkboxTestUnchecked from main page should be present + When I click checkboxTestUnchecked from main page + Then checkboxTestChecked from main page should be present + + Scenario: I doubleclick step should double-click on the element + Given I open "http://localhost:9000/new-page.html" + And checkboxTestUnchecked from main page should be present + When I doubleclick checkboxTest from main page + Then checkboxTestUnchecked from main page should be present diff --git a/test/features/page_objects/main.js b/test/features/page_objects/main.js index 24c2782..91c3475 100644 --- a/test/features/page_objects/main.js +++ b/test/features/page_objects/main.js @@ -4,5 +4,8 @@ e.txtHeader = '//*[@id="header"]'; e.txtNewHeader = '//*[@id="new-header"]'; e.divTimeout = '//*[@id="div_timeout"]'; e.div = '//div'; +e.checkboxTest = '//*[@id="cbx-test"]'; +e.checkboxTestUnchecked = '//*[@id="cbx-test" and not(@checked="checked")]'; +e.checkboxTestChecked = '//*[@id="cbx-test" and @checked="checked"]'; module.exports = e; diff --git a/test/features/show.hide.timeout.feature b/test/features/show.hide.timeout.feature index 199fecb..a1260f2 100644 --- a/test/features/show.hide.timeout.feature +++ b/test/features/show.hide.timeout.feature @@ -5,11 +5,10 @@ Feature: Present / Not present As a developer I want to check that certain value is present and not present after time - Scenario: Check that certain element is present and not present after time + Scenario: Check that certain element is present and the other is not present on the page Given I open "http://localhost:9000" - And divTimeout from main page should be present - When I wait "5000" ms - Then divTimeout from main page should not be present + Then divTimeout from main page should be present + And checkboxTest from main page should not be present Scenario: I wait step should wait for specified amount of milliseconds Given I open "http://localhost:9000" @@ -21,7 +20,3 @@ Feature: Present / Not present Then I wait txtTimeout from values dictionary ms Then divTimeout from main page should not be present Then txtHeader from main page should be present - - Scenario: I click step should click on the element - Given I open "http://localhost:9000" - Then I click txtHeader from main page From d551cbd7b6d229d99f8b2ae7deb9c8461ec3a1b9 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Wed, 21 Mar 2018 20:16:44 -0300 Subject: [PATCH 33/45] Updated new-page with text block for doubleclick --- test/demo-app/new-page.html | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/demo-app/new-page.html b/test/demo-app/new-page.html index 0d2fa41..0742088 100644 --- a/test/demo-app/new-page.html +++ b/test/demo-app/new-page.html @@ -8,9 +8,14 @@ const checkboxTest = document.getElementById("cbx-test"); checkboxTest.addEventListener("click", function () { - checkboxTest.checked - ? checkboxTest.setAttribute("checked", "checked") - : checkboxTest.removeAttribute("checked"); + this.checked + ? this.setAttribute("checked", "checked") + : this.removeAttribute("checked"); + }); + + document.getElementById('block-text').addEventListener('dblclick', function () { + this.innerHTML = 'Test text after double-click'; + this.style.background = 'green'; }); } @@ -22,5 +27,6 @@ +
Double-click before
From 9b9cbff73d3f0aee559a65ecf7654bea9a6bf7bf Mon Sep 17 00:00:00 2001 From: Marketionist Date: Wed, 21 Mar 2018 20:19:10 -0300 Subject: [PATCH 34/45] Returned to moveToObject and doubleClick as it is not depricated --- src/steps/when.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/steps/when.js b/src/steps/when.js index f49ca15..db21e61 100644 --- a/src/steps/when.js +++ b/src/steps/when.js @@ -17,7 +17,7 @@ module.exports = function () { try { await browser.waitForExist(locator); await browser.waitForPageToLoad(); - await browser.scroll(locator); + await browser.moveToObject(locator); return await browser.click(locator); } catch (err) { throw new Error(err); @@ -34,9 +34,8 @@ module.exports = function () { try { await browser.waitForExist(locator); await browser.waitForPageToLoad(); - await browser.scroll(locator); - await browser.click(locator); - return await browser.click(locator); + await browser.moveToObject(locator); + return await browser.doubleClick(locator); } catch (err) { throw new Error(err); } From e53491e17634ff29fc88f4ebfa8b629921cf9a61 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Wed, 21 Mar 2018 20:22:11 -0300 Subject: [PATCH 35/45] Updated tests for click and doubleclick --- test/features/click.feature | 7 ++++--- test/features/page_objects/main.js | 3 ++- test/features/show.hide.timeout.feature | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/test/features/click.feature b/test/features/click.feature index 9b324bc..05aab9d 100644 --- a/test/features/click.feature +++ b/test/features/click.feature @@ -14,6 +14,7 @@ Feature: Click Scenario: I doubleclick step should double-click on the element Given I open "http://localhost:9000/new-page.html" - And checkboxTestUnchecked from main page should be present - When I doubleclick checkboxTest from main page - Then checkboxTestUnchecked from main page should be present + And blockTextBefore from main page should be present + When I doubleclick blockTextBefore from main page + Then blockTextBefore from main page should not be present + And blockTextAfter from main page should be present diff --git a/test/features/page_objects/main.js b/test/features/page_objects/main.js index 91c3475..9e8b5f0 100644 --- a/test/features/page_objects/main.js +++ b/test/features/page_objects/main.js @@ -4,8 +4,9 @@ e.txtHeader = '//*[@id="header"]'; e.txtNewHeader = '//*[@id="new-header"]'; e.divTimeout = '//*[@id="div_timeout"]'; e.div = '//div'; -e.checkboxTest = '//*[@id="cbx-test"]'; e.checkboxTestUnchecked = '//*[@id="cbx-test" and not(@checked="checked")]'; e.checkboxTestChecked = '//*[@id="cbx-test" and @checked="checked"]'; +e.blockTextBefore = '//*[@id="block-text" and contains(text(), "Double-click before")]'; +e.blockTextAfter = '//*[@id="block-text" and contains(text(), "after double-click")]'; module.exports = e; diff --git a/test/features/show.hide.timeout.feature b/test/features/show.hide.timeout.feature index a1260f2..05baa6e 100644 --- a/test/features/show.hide.timeout.feature +++ b/test/features/show.hide.timeout.feature @@ -8,7 +8,7 @@ Feature: Present / Not present Scenario: Check that certain element is present and the other is not present on the page Given I open "http://localhost:9000" Then divTimeout from main page should be present - And checkboxTest from main page should not be present + And checkboxTestUnchecked from main page should not be present Scenario: I wait step should wait for specified amount of milliseconds Given I open "http://localhost:9000" From d0b0266008c78929ee2e2ea5c5e32e115ea2ed97 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Thu, 22 Mar 2018 09:03:44 -0300 Subject: [PATCH 36/45] Added loaders to new-page --- test/demo-app/new-page.html | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/demo-app/new-page.html b/test/demo-app/new-page.html index 0742088..15a9f01 100644 --- a/test/demo-app/new-page.html +++ b/test/demo-app/new-page.html @@ -3,6 +3,8 @@ NEW PAGE FOR THE WDIO-STEPS + +
New page header
-
New page
+
New page
Double-click before
+
+ Click to get text +
From f906ad98532094c03e0b993f9669b3770d630a14 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Thu, 22 Mar 2018 09:05:20 -0300 Subject: [PATCH 37/45] Added loaders scripts, styles, images --- test/demo-app/images/loader1.gif | Bin 0 -> 8238 bytes test/demo-app/images/loader2.gif | Bin 0 -> 10819 bytes test/demo-app/scripts/loaders.js | 22 ++++++++++++++++++++++ test/demo-app/styles/loaders.css | 15 +++++++++++++++ 4 files changed, 37 insertions(+) create mode 100644 test/demo-app/images/loader1.gif create mode 100644 test/demo-app/images/loader2.gif create mode 100644 test/demo-app/scripts/loaders.js create mode 100644 test/demo-app/styles/loaders.css diff --git a/test/demo-app/images/loader1.gif b/test/demo-app/images/loader1.gif new file mode 100644 index 0000000000000000000000000000000000000000..cc70a7a8b3d426c30e76686fac70c0dcd4c70125 GIT binary patch literal 8238 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 literal 0 HcmV?d00001 diff --git a/test/demo-app/images/loader2.gif b/test/demo-app/images/loader2.gif new file mode 100644 index 0000000000000000000000000000000000000000..d84f653789e5008da64ff04ee109471284a9e284 GIT binary patch literal 10819 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 { + + const timeout1 = 5000; + const timeout2 = 8000; + + launchLoader(timeout1, 'loader1'); + launchLoader(timeout2, 'loader2'); + +}); diff --git a/test/demo-app/styles/loaders.css b/test/demo-app/styles/loaders.css new file mode 100644 index 0000000..2069d91 --- /dev/null +++ b/test/demo-app/styles/loaders.css @@ -0,0 +1,15 @@ +#loader1 { + width: 100%; + height: 100%; + position: fixed; + z-index: 9999; + background: url("../images/loader1.gif") no-repeat center center rgba(0,0,0,0.2) +} + +#loader2 { + width: 100%; + height: 100%; + position: fixed; + z-index: 9998; + background: url("../images/loader2.gif") no-repeat center center rgba(0,0,0,0.1) +} From 297edb9ab77eaa870f1c77d06713183f8ce7ef9e Mon Sep 17 00:00:00 2001 From: Marketionist Date: Thu, 22 Mar 2018 09:12:58 -0300 Subject: [PATCH 38/45] Added tests for waitForPageToLoad --- test/features/click.feature | 5 +++++ test/features/page_objects/main.js | 2 ++ test/steps.conf.js | 4 +++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/test/features/click.feature b/test/features/click.feature index 05aab9d..6d17db6 100644 --- a/test/features/click.feature +++ b/test/features/click.feature @@ -18,3 +18,8 @@ Feature: Click When I doubleclick blockTextBefore from main page Then blockTextBefore from main page should not be present And blockTextAfter from main page should be present + + Scenario: waitForPageToLoad should wait for all loaders to finish + Given I open "http://localhost:9000/new-page.html" + When I click buttonLaunch from main page + Then I click buttonWasClicked from main page diff --git a/test/features/page_objects/main.js b/test/features/page_objects/main.js index 9e8b5f0..d5ce8a4 100644 --- a/test/features/page_objects/main.js +++ b/test/features/page_objects/main.js @@ -8,5 +8,7 @@ e.checkboxTestUnchecked = '//*[@id="cbx-test" and not(@checked="checked")]'; e.checkboxTestChecked = '//*[@id="cbx-test" and @checked="checked"]'; e.blockTextBefore = '//*[@id="block-text" and contains(text(), "Double-click before")]'; e.blockTextAfter = '//*[@id="block-text" and contains(text(), "after double-click")]'; +e.buttonLaunch = '//*[@id="button-launch"]'; +e.buttonWasClicked = '//*[@id="button-launch" and contains(text(), "Button was clicked")]'; module.exports = e; diff --git a/test/steps.conf.js b/test/steps.conf.js index c1adce0..b6c5875 100644 --- a/test/steps.conf.js +++ b/test/steps.conf.js @@ -1,7 +1,9 @@ // Array of selectors for loaders that appear when page is loaded or XHR/AJAX requests are done const loaderSelectors = [ 'div:not([style*="display: none"])[class*="test-loader"]', - 'div:not([style*="visibility: hidden"])[class*="test-loader"]' + 'div:not([style*="visibility: hidden"])[class*="test-loader"]', + '[id*="loader1"]', + '[id*="loader2"]' ]; // Object that contains pathes to all page objects used for tests From 96bfce48178b0eccb47d937561ff8f31b65bd6c1 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Thu, 22 Mar 2018 09:43:52 -0300 Subject: [PATCH 39/45] Used executeAsync in waitForPageToLoad --- src/steps.conf.generator.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/steps.conf.generator.js b/src/steps.conf.generator.js index da4eee4..f438021 100644 --- a/src/steps.conf.generator.js +++ b/src/steps.conf.generator.js @@ -5,7 +5,10 @@ const defaultFinishedLoadingConditions = (loaderSelectors) => { } // Check if any loaders are still present on the page - return !loaderSelectors.some((selector) => browser.execute((s) => document.querySelector(s), selector)); + return !loaderSelectors.some((selector) => browser.executeAsync( + function (s, callback) { + callback(document.querySelector(s)); + }, selector)); }; const defaultIdGenerator = () => (new Date()).getTime(); From 3883fac3614cc14379f6bfedd00e138b5ff1fbf8 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Fri, 23 Mar 2018 08:48:07 -0300 Subject: [PATCH 40/45] Added promise to waitForPageToLoad --- src/commands/wait.for.page.to.load.js | 32 +++++++++++++++------------ src/steps.conf.generator.js | 10 +++++---- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/commands/wait.for.page.to.load.js b/src/commands/wait.for.page.to.load.js index 98e767e..a964ca1 100644 --- a/src/commands/wait.for.page.to.load.js +++ b/src/commands/wait.for.page.to.load.js @@ -1,25 +1,29 @@ /* global stepsConfig */ -module.exports = function waitForPageToLoad (callback) { +module.exports = function waitForPageToLoad () { /** * Wait for page to get fully loaded * @param {callback} callback - A callback to run */ - const timeout = 100; - const finishedLoadingConditions = stepsConfig.finishedLoadingConditions; + return new Promise(function (resolve, reject) { + const timeout = 100; + const finishedLoadingConditions = stepsConfig.finishedLoadingConditions; - // If loading of the page was finished - launch callback function - // TODO - add some waiter here - if (finishedLoadingConditions()) { - callback(); - } - - // If loading of the page was not finished - relaunch finishedLoadingConditions() each 100 ms - const interval = setInterval(function () { + // If loading of the page was finished - launch callback function + // TODO - add some waiter here if (finishedLoadingConditions()) { - clearInterval(interval); - callback(); + return resolve(); } - }, timeout); + + // If loading of the page was not finished - relaunch finishedLoadingConditions() each 100 ms + const interval = setInterval(function () { + if (finishedLoadingConditions()) { + clearInterval(interval); + return resolve(); + } + }, timeout); + }).catch((err) => { + throw new Error(err.message); + }); }; diff --git a/src/steps.conf.generator.js b/src/steps.conf.generator.js index f438021..faded4d 100644 --- a/src/steps.conf.generator.js +++ b/src/steps.conf.generator.js @@ -5,10 +5,12 @@ const defaultFinishedLoadingConditions = (loaderSelectors) => { } // Check if any loaders are still present on the page - return !loaderSelectors.some((selector) => browser.executeAsync( - function (s, callback) { - callback(document.querySelector(s)); - }, selector)); + return !loaderSelectors.some(function (selector) { + browser.executeAsync( + function (s, callback) { + callback(document.querySelector(s)); + }, selector); + }); }; const defaultIdGenerator = () => (new Date()).getTime(); From 352a59bf4fcaab7ac59c9a792e41b7dbb68172dc Mon Sep 17 00:00:00 2001 From: Marketionist Date: Fri, 23 Mar 2018 08:54:53 -0300 Subject: [PATCH 41/45] Added loadingFinished --- src/commands/wait.for.page.to.load.js | 2 +- src/steps.conf.generator.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/commands/wait.for.page.to.load.js b/src/commands/wait.for.page.to.load.js index a964ca1..daf9967 100644 --- a/src/commands/wait.for.page.to.load.js +++ b/src/commands/wait.for.page.to.load.js @@ -5,7 +5,7 @@ module.exports = function waitForPageToLoad () { * Wait for page to get fully loaded * @param {callback} callback - A callback to run */ - return new Promise(function (resolve, reject) { + return new Promise(function (resolve) { const timeout = 100; const finishedLoadingConditions = stepsConfig.finishedLoadingConditions; diff --git a/src/steps.conf.generator.js b/src/steps.conf.generator.js index faded4d..729c17a 100644 --- a/src/steps.conf.generator.js +++ b/src/steps.conf.generator.js @@ -5,12 +5,14 @@ const defaultFinishedLoadingConditions = (loaderSelectors) => { } // Check if any loaders are still present on the page - return !loaderSelectors.some(function (selector) { + const loadingFinished = !loaderSelectors.some(function (selector) { browser.executeAsync( function (s, callback) { callback(document.querySelector(s)); }, selector); }); + + return loadingFinished; }; const defaultIdGenerator = () => (new Date()).getTime(); From 821262f0ff0045e0236d57c047fa1db26c3a3f53 Mon Sep 17 00:00:00 2001 From: Marketionist Date: Sat, 24 Mar 2018 10:38:08 -0300 Subject: [PATCH 42/45] Refactored defaultFinishedLoadingConditions --- src/steps.conf.generator.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/steps.conf.generator.js b/src/steps.conf.generator.js index 729c17a..e811d51 100644 --- a/src/steps.conf.generator.js +++ b/src/steps.conf.generator.js @@ -5,14 +5,14 @@ const defaultFinishedLoadingConditions = (loaderSelectors) => { } // Check if any loaders are still present on the page - const loadingFinished = !loaderSelectors.some(function (selector) { - browser.executeAsync( - function (s, callback) { - callback(document.querySelector(s)); - }, selector); + browser.executeAsync(function (callback) { + return !loaderSelectors.some(function (selector) { + document.querySelector(selector); + }).then(function (loadingFinished) { + return callback(loadingFinished); + }); }); - return loadingFinished; }; const defaultIdGenerator = () => (new Date()).getTime(); From c6f95935591fb66246d516678023e19d243b1d15 Mon Sep 17 00:00:00 2001 From: Alex Krechik Date: Sun, 25 Mar 2018 16:47:09 +0200 Subject: [PATCH 43/45] Fixed loaderSelectore visibility issues. --- src/steps.conf.generator.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/steps.conf.generator.js b/src/steps.conf.generator.js index e811d51..e5c4d7f 100644 --- a/src/steps.conf.generator.js +++ b/src/steps.conf.generator.js @@ -5,13 +5,11 @@ const defaultFinishedLoadingConditions = (loaderSelectors) => { } // Check if any loaders are still present on the page - browser.executeAsync(function (callback) { - return !loaderSelectors.some(function (selector) { - document.querySelector(selector); - }).then(function (loadingFinished) { - return callback(loadingFinished); - }); - }); + return browser.executeAsync((selectors, done) => { + done(!selectors.some((selector) => { + return document.querySelector(selector); + })); + }, loaderSelectors); }; From 61bc704df65c9c3b67cda0c17adb4913cb22aa10 Mon Sep 17 00:00:00 2001 From: Alex Krechik Date: Sun, 25 Mar 2018 16:57:12 +0200 Subject: [PATCH 44/45] Fixed defaultFinishedLoadingConditions returnvalue --- src/steps.conf.generator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/steps.conf.generator.js b/src/steps.conf.generator.js index e5c4d7f..0053cac 100644 --- a/src/steps.conf.generator.js +++ b/src/steps.conf.generator.js @@ -9,7 +9,7 @@ const defaultFinishedLoadingConditions = (loaderSelectors) => { done(!selectors.some((selector) => { return document.querySelector(selector); })); - }, loaderSelectors); + }, loaderSelectors).value; }; From 069847e54ba2884782b07f3e6c3d4e0f39b5d8f1 Mon Sep 17 00:00:00 2001 From: Alex Krechik Date: Sun, 25 Mar 2018 16:57:32 +0200 Subject: [PATCH 45/45] Fixed waitForPageToLoad function --- src/commands/wait.for.page.to.load.js | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/src/commands/wait.for.page.to.load.js b/src/commands/wait.for.page.to.load.js index daf9967..104b3c8 100644 --- a/src/commands/wait.for.page.to.load.js +++ b/src/commands/wait.for.page.to.load.js @@ -5,25 +5,5 @@ module.exports = function waitForPageToLoad () { * Wait for page to get fully loaded * @param {callback} callback - A callback to run */ - return new Promise(function (resolve) { - const timeout = 100; - const finishedLoadingConditions = stepsConfig.finishedLoadingConditions; - - // If loading of the page was finished - launch callback function - // TODO - add some waiter here - if (finishedLoadingConditions()) { - return resolve(); - } - - // If loading of the page was not finished - relaunch finishedLoadingConditions() each 100 ms - const interval = setInterval(function () { - if (finishedLoadingConditions()) { - clearInterval(interval); - return resolve(); - } - }, timeout); - }).catch((err) => { - throw new Error(err.message); - }); - + return browser.waitUntil(stepsConfig.finishedLoadingConditions); };