/* eslint no-invalid-this: "off" */ import Nightmare from 'nightmare'; import {URL} from 'url'; import config from './config.js'; let currentUser; let actions = { // Generic extensions clickIfExists: async function(selector) { let exists = await this.exists(selector); if (exists) await this.click(selector); return exists; }, parsedUrl: async function() { return new URL(await this.url()); }, waitUntilNotPresent: async function(selector) { await this.wait(selector => { return document.querySelector(selector) == null; }, 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'); }, doLogin: async function(userName, password) { if (password == null) password = 'nightmare'; await this.wait(`vn-login [name=user]`) .clearInput(`vn-login [name=user]`) .write(`vn-login [name=user]`, userName) .write(`vn-login [name=password]`, password) .click(`vn-login button[type=submit]`); }, login: async function(userName) { if (currentUser !== userName) { let logoutClicked = await this.clickIfExists('#logout'); if (logoutClicked) { let buttonSelector = '.vn-dialog.shown button[response=accept]'; await this.wait(buttonSelector => { return document.querySelector(buttonSelector) != null || location.hash == '#!/login'; }, buttonSelector); await this.clickIfExists(buttonSelector); } try { await this.waitForURL('#!/login'); } catch (e) { await this.goto(`${config.url}/#!/login`); } await this.doLogin(userName, null) .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).$ctrl.input; input.value = time; input.dispatchEvent(new Event('change')); }, selector, time); }, clearTextarea: function(selector) { return this.wait(selector) .evaluate(inputSelector => { return document.querySelector(inputSelector).value = ''; }, selector); }, clearInput: function(selector) { return 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); }, getProperty: function(selector, property) { return this.evaluate((selector, property) => { return document.querySelector(selector)[property].replace(/\s+/g, ' ').trim(); }, selector, property); }, waitPropertyLength: function(selector, property, minLength) { return 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); }, waitPropertyValue: function(selector, property, status) { return this.wait(selector) .wait((selector, property, status) => { const element = document.querySelector(selector); return element[property] === status; }, selector, property, status); }, waitToGetProperty: function(selector, property) { return this.wait((selector, property) => { const element = document.querySelector(selector); return element && element[property] != null && element[property] !== ''; }, selector, property) .getProperty(selector, property); }, write: function(selector, text) { return this.wait(selector) .type(selector, text); }, waitToClick: function(selector) { return this.wait(selector) .click(selector); }, focusElement: function(selector) { return this.wait(selector) .evaluate(selector => { let element = document.querySelector(selector); element.focus(); }, selector); }, isVisible: function(selector) { return this.wait(selector) .evaluate(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; }, selector); }, waitImgLoad: function(selector) { return this.wait(selector) .wait(selector => { const imageReady = document.querySelector(selector).complete; return imageReady; }, selector); }, clickIfVisible: function(selector) { return this.wait(selector) .isVisible(selector) .then(visible => { if (visible) return this.click(selector); throw new Error(`invisible selector: ${selector}`); }); }, countElement: function(selector) { return this.evaluate(selector => { return document.querySelectorAll(selector).length; }, selector); }, waitForNumberOfElements: function(selector, count) { return this.wait((selector, count) => { return document.querySelectorAll(selector).length === count; }, selector, count); }, waitForClassNotPresent: function(selector, className) { return this.wait(selector) .wait((selector, className) => { if (!document.querySelector(selector).classList.contains(className)) return true; }, selector, className); }, waitForClassPresent: function(selector, className) { return this.wait(selector) .wait((elementSelector, targetClass) => { if (document.querySelector(elementSelector).classList.contains(targetClass)) return true; }, selector, className); }, waitForTextInElement: function(selector, text) { return this.wait(selector) .wait((selector, text) => { return document.querySelector(selector).innerText.toLowerCase().includes(text.toLowerCase()); }, selector, text); }, waitForTextInInput: function(selector, text) { return this.wait(selector) .wait((selector, text) => { return document.querySelector(selector).value.toLowerCase().includes(text.toLowerCase()); }, selector, text); }, waitForInnerText: function(selector) { return this.wait(selector) .wait(selector => { const innerText = document.querySelector(selector).innerText; return innerText != null && innerText != ''; }, selector) .evaluate(selector => { return document.querySelector(selector).innerText; }, selector); }, waitForEmptyInnerText: function(selector) { return this.wait(selector => { return document.querySelector(selector).innerText == ''; }, selector); }, waitForURL: function(hashURL) { return this.wait(hash => { return document.location.hash.includes(hash); }, hashURL); }, waitForShapes: function(selector) { return this.wait(selector) .evaluate(selector => { const shapes = document.querySelectorAll(selector); const shapesList = []; for (const shape of shapes) shapesList.push(shape.innerText); return shapesList; }, selector); }, waitForSnackbar: function() { return this.wait(500) .waitForShapes('vn-snackbar .shape .text'); }, waitForLastShape: function(selector) { return this.wait(selector) .evaluate(selector => { const shape = document.querySelector(selector); return shape.innerText; }, selector); }, waitForLastSnackbar: function() { return this.wait(500) .waitForLastShape('vn-snackbar .shape .text'); }, accessToSearchResult: function(searchValue) { return this.clearInput('vn-searchbar input') .write('vn-searchbar input', searchValue) .click('vn-searchbar vn-icon[icon="search"]') .wait(100) .waitForNumberOfElements('.search-result', 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'); }); }, accessToSection: function(sectionRoute) { return 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}"]`); }); }, autocompleteSearch: function(autocompleteSelector, searchValue) { return this.waitToClick(`${autocompleteSelector} input`) .write(`.vn-drop-down.shown input`, searchValue) .waitToClick(`.vn-drop-down.shown li.active`) .wait((autocompleteSelector, searchValue) => { return document.querySelector(`${autocompleteSelector} input`).value .toLowerCase() .includes(searchValue.toLowerCase()); }, autocompleteSelector, searchValue); }, reloadSection: function(sectionRoute) { return this.waitToClick('vn-icon[icon="desktop_windows"]') .wait('vn-card.summary') .waitToClick(`vn-left-menu li > a[ui-sref="${sectionRoute}"]`); }, forceReloadSection: function(sectionRoute) { return this.waitToClick('vn-icon[icon="desktop_windows"]') .waitToClick('button[response="accept"]') .wait('vn-card.summary') .waitToClick(`vn-left-menu li > a[ui-sref="${sectionRoute}"]`); }, checkboxState: function(selector) { return 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); }, isDisabled: function(selector) { return this.wait(selector) .evaluate(selector => { let element = document.querySelector(selector); return element.$ctrl.disabled; }, selector); }, waitForSpinnerLoad: function() { return this.waitForClassNotPresent('vn-spinner > div', 'is-active'); }, }; for (let name in actions) { Nightmare.action(name, function(...args) { let fnArgs = args.slice(0, args.length - 1); let done = args[args.length - 1]; actions[name].apply(this, fnArgs) .then(res => done(null, res)) .catch(err => { let stringArgs = fnArgs .map(i => typeof i == 'function' ? 'Function' : i) .join(', '); let orgMessage = err.message.startsWith('.wait()') ? '.wait() timed out' : err.message; done(new Error(`.${name}(${stringArgs}) failed: ${orgMessage}`)); }); }); }