/* eslint no-invalid-this: "off" */ import Nightmare from 'nightmare'; import {URL} from 'url'; import config from './config.js'; let currentUser; let asyncActions = { // Generic extensions clickIfExists: async function(selector) { let exists = await this.exists(selector); if (exists) await this.click(selector); }, // Salix specific extensions changeLanguageToEnglish: async function() { let langSelector = '.user-popover vn-autocomplete[ng-model="$ctrl.lang"]'; let lang = await this.waitToClick('#user') .wait(langSelector) .waitToGetProperty(`${langSelector} input`, 'value'); if (lang !== 'English') await this.autocompleteSearch(langSelector, 'English'); }, login: async function(userName) { if (currentUser !== userName) { this.clickIfExists('#logout') .clickIfExists('.vn-dialog.shown button[response=ACCEPT]'); try { await this.waitForURL('#!/login'); } catch (e) { this.goto(`${config.url}/#!/login`); } await this.wait(`vn-login input[name=user]`) .clearInput(`vn-login input[name=user]`) .write(`vn-login input[name=user]`, userName) .write(`vn-login input[name=password]`, 'nightmare') .click(`vn-login input[type=submit]`) .waitForURL('#!/') .changeLanguageToEnglish(); currentUser = userName; } else await this.waitToClick('vn-topbar a[ui-sref="home"]'); }, waitForLogin: async function(userName) { await this.login(userName); }, selectModule: async function(moduleName) { let snakeName = moduleName.replace(/[\w]([A-Z])/g, m => { return m[0] + '-' + m[1]; }).toLowerCase(); await this.waitToClick(`vn-home a[ui-sref="${moduleName}.index"]`) .waitForURL(snakeName); }, loginAndModule: async function(userName, moduleName) { await this.login(userName) .selectModule(moduleName); }, datePicker: async function(selector, changeMonth, day) { let date = new Date(); if (changeMonth) date.setMonth(date.getMonth() + changeMonth); date.setDate(day ? day : 16); date = date.toISOString().substr(0, 10); await this.wait(selector) .evaluate((selector, date) => { let input = document.querySelector(selector).$ctrl.input; input.value = date; input.dispatchEvent(new Event('change')); }, selector, date); }, pickTime: async function(selector, time) { await this.wait(selector) .evaluate((selector, time) => { let input = document.querySelector(selector); input.value = time; input.dispatchEvent(new Event('change')); }, selector, time); } }; let actions = { clearTextarea: function(selector, done) { this.wait(selector) .evaluate(inputSelector => { return document.querySelector(inputSelector).value = ''; }, selector) .then(done) .catch(done); }, clearInput: function(selector, done) { this.wait(selector) .evaluate(selector => { let $ctrl = document.querySelector(selector).closest('.vn-field').$ctrl; $ctrl.field = null; $ctrl.$.$apply(); $ctrl.input.dispatchEvent(new Event('change')); }, selector) .then(done) .catch(done); }, resetLogin: function(done) { this.then(() => { currentUser = undefined; done(); }) .catch(done); }, parsedUrl: function(done) { this.url() .then(url => { done(null, new URL(url)); }).catch(done); }, getProperty: function(selector, property, done) { this.evaluate_now((selector, property) => { return document.querySelector(selector)[property].replace(/\s+/g, ' ').trim(); }, done, selector, property); }, waitPropertyLength: function(selector, property, minLength, done) { this.wait((selector, property, minLength) => { const element = document.querySelector(selector); return element && element[property] != null && element[property] !== '' && element[property].length >= minLength; }, selector, property, minLength) .getProperty(selector, property) .then(result => done(null, result), done); }, waitPropertyValue: function(selector, property, status, done) { this.wait(selector) .wait((selector, property, status) => { const element = document.querySelector(selector); return element[property] === status; }, selector, property, status) .then(done) .catch(done); }, waitToGetProperty: function(selector, property, done) { this.wait((selector, property) => { const element = document.querySelector(selector); return element && element[property] != null && element[property] !== ''; }, selector, property) .getProperty(selector, property) .then(result => done(null, result), done); }, write: function(selector, text, done) { this.wait(selector) .type(selector, text) .then(done) .catch(done); }, waitToClick: function(selector, done) { this.wait(selector) .click(selector) .then(done) .catch(done); }, focusElement: function(selector, done) { this.wait(selector) .evaluate_now(selector => { let element = document.querySelector(selector); element.focus(); }, done, selector) .then(done) .catch(done); }, isVisible: function(selector, done) { this.wait(selector) .evaluate_now(elementSelector => { const selectorMatches = document.querySelectorAll(elementSelector); const element = selectorMatches[0]; if (selectorMatches.length > 1) throw new Error(`multiple matches of ${elementSelector} found`); let isVisible = false; if (element) { const eventHandler = event => { event.preventDefault(); isVisible = true; }; element.addEventListener('mouseover', eventHandler); const rect = element.getBoundingClientRect(); const x = rect.left + rect.width / 2; const y = rect.top + rect.height / 2; const elementInCenter = document.elementFromPoint(x, y); const elementInTopLeft = document.elementFromPoint(rect.left, rect.top); const elementInBottomRight = document.elementFromPoint(rect.right, rect.bottom); const e = new MouseEvent('mouseover', { view: window, bubbles: true, cancelable: true, }); if (elementInCenter) elementInCenter.dispatchEvent(e); if (elementInTopLeft) elementInTopLeft.dispatchEvent(e); if (elementInBottomRight) elementInBottomRight.dispatchEvent(e); element.removeEventListener('mouseover', eventHandler); } return isVisible; }, done, selector); }, waitImgLoad: function(selector, done) { this.wait(selector) .wait(selector => { const imageReady = document.querySelector(selector).complete; return imageReady; }, selector) .then(done) .catch(() => { done(new Error(`image ${selector}, load timed out`)); }); }, clickIfVisible: function(selector, done) { this.wait(selector) .isVisible(selector) .then(visible => { if (visible) return this.click(selector); throw new Error(`invisible selector: ${selector}`); }) .then(done) .catch(done); }, countElement: function(selector, done) { this.evaluate_now(selector => { return document.querySelectorAll(selector).length; }, done, selector); }, waitForNumberOfElements: function(selector, count, done) { this.wait((selector, count) => { return document.querySelectorAll(selector).length === count; }, selector, count) .then(done) .catch(() => { done(new Error(`.waitForNumberOfElements() for ${selector}, count ${count} timed out`)); }); }, waitForClassNotPresent: function(selector, className, done) { this.wait(selector) .wait((selector, className) => { if (!document.querySelector(selector).classList.contains(className)) return true; }, selector, className) .then(done) .catch(() => { done(new Error(`.waitForClassNotPresent() for ${selector}, class ${className} timed out`)); }); }, waitForClassPresent: function(selector, className, done) { this.wait(selector) .wait((elementSelector, targetClass) => { if (document.querySelector(elementSelector).classList.contains(targetClass)) return true; }, selector, className) .then(done) .catch(() => { done(new Error(`.waitForClassPresent() for ${selector}, class ${className} timed out`)); }); }, waitForTextInElement: function(selector, text, done) { this.wait(selector) .wait((selector, text) => { return document.querySelector(selector).innerText.toLowerCase().includes(text.toLowerCase()); }, selector, text) .then(done) .catch(done); }, waitForTextInInput: function(selector, text, done) { this.wait(selector) .wait((selector, text) => { return document.querySelector(selector).value.toLowerCase().includes(text.toLowerCase()); }, selector, text) .then(done) .catch(done); }, waitForInnerText: function(selector, done) { this.wait(selector) .wait(selector => { const innerText = document.querySelector(selector).innerText; return innerText != null && innerText != ''; }, selector) .evaluate_now(selector => { return document.querySelector(selector).innerText; }, done, selector); }, waitForEmptyInnerText: function(selector, done) { this.wait(selector => { return document.querySelector(selector).innerText == ''; }, selector) .then(done) .catch(done); }, waitForURL: function(hashURL, done) { this.wait(hash => { return document.location.hash.includes(hash); }, hashURL) .then(done) .catch(done); }, waitForShapes: function(selector, done) { this.wait(selector) .evaluate_now(selector => { const shapes = document.querySelectorAll(selector); const shapesList = []; for (const shape of shapes) shapesList.push(shape.innerText); return shapesList; }, done, selector); }, waitForSnackbar: function(done) { this.wait(500).waitForShapes('vn-snackbar .shape .text') .then(shapes => { done(null, shapes); }).catch(done); }, waitForLastShape: function(selector, done) { this.wait(selector) .evaluate_now(selector => { const shape = document.querySelector(selector); return shape.innerText; }, done, selector); }, waitForLastSnackbar: function(done) { this.wait(500).waitForLastShape('vn-snackbar .shape .text') .then(shapes => { done(null, shapes); }).catch(done); }, accessToSearchResult: function(searchValue, done) { this.clearInput('vn-searchbar input') .write('vn-searchbar input', searchValue) .click('vn-searchbar vn-icon[icon="search"]') .wait(100) .waitForNumberOfElements('.searchResult', 1) .evaluate(() => { return document.querySelector('ui-view vn-card vn-table') != null; }) .then(result => { if (result) return this.waitToClick('ui-view vn-card vn-td'); return this.waitToClick('ui-view vn-card a'); }) .then(done) .catch(done); }, accessToSection: function(sectionRoute, done) { this.wait(`vn-left-menu`) .evaluate(sectionRoute => { return document.querySelector(`vn-left-menu ul li ul li > a[ui-sref="${sectionRoute}"]`) != null; }, sectionRoute) .then(nested => { if (!nested) return this.waitToClick(`vn-left-menu li > a[ui-sref="${sectionRoute}"]`); return this.waitToClick('vn-left-menu .collapsed') .wait('vn-left-menu .expanded') .waitToClick(`vn-left-menu li > a[ui-sref="${sectionRoute}"]`); }) .then(done) .catch(done); }, autocompleteSearch: function(autocompleteSelector, searchValue, done) { this.waitToClick(`${autocompleteSelector} input`) .write(`.vn-popover.shown .vn-drop-down input`, searchValue) .waitToClick(`.vn-popover.shown .vn-drop-down li.active`) .wait((autocompleteSelector, searchValue) => { return document.querySelector(`${autocompleteSelector} input`).value .toLowerCase() .includes(searchValue.toLowerCase()); }, autocompleteSelector, searchValue) .then(done) .catch(() => { done(new Error(`.autocompleteSearch() for value ${searchValue} in ${autocompleteSelector} timed out`)); }); }, reloadSection: function(sectionRoute, done) { this.waitToClick('vn-icon[icon="desktop_windows"]') .wait('vn-card.summary') .waitToClick(`vn-left-menu li > a[ui-sref="${sectionRoute}"]`) .then(done) .catch(done); }, forceReloadSection: function(sectionRoute, done) { this.waitToClick('vn-icon[icon="desktop_windows"]') .waitToClick('button[response="ACCEPT"]') .wait('vn-card.summary') .waitToClick(`vn-left-menu li > a[ui-sref="${sectionRoute}"]`) .then(done) .catch(done); }, checkboxState: function(selector, done) { this.wait(selector) .evaluate(selector => { let checkbox = document.querySelector(selector); switch (checkbox.$ctrl.field) { case null: return 'intermediate'; case true: return 'checked'; default: return 'unchecked'; } }, selector) .then(res => done(null, res)) .catch(done); }, isDisabled: function(selector, done) { this.wait(selector) .evaluate(selector => { let element = document.querySelector(selector); return element.$ctrl.disabled; }, selector) .then(res => done(null, res)) .catch(done); }, waitForSpinnerLoad: function(done) { let spinnerSelector = 'vn-spinner > div'; this.waitForClassNotPresent(spinnerSelector, 'is-active') .then(done) .catch(done); }, }; Object.keys(actions).forEach(function(name) { let fn = actions[name]; Nightmare.action(name, fn); }); for (let name in asyncActions) { let fn = asyncActions[name]; Nightmare.action(name, function(...args) { let done = args[args.length - 1]; fn.apply(this, args) .then(res => done(null, res), done); }); }