/* eslint no-invalid-this: "off" */ import config from './config.js'; import Nightmare from 'nightmare'; import {URL} from 'url'; Nightmare.asyncAction = function(name, func) { Nightmare.action(name, function(...args) { let done = args[arguments.length - 1]; func.apply(this, args) .then(result => done(null, result)) .catch(done); }); }; Nightmare.asyncAction('clearInput', async function(selector) { const backSpaces = []; for (let i = 0; i < 50; i += 1) backSpaces.push('\u0008'); await this.wait(selector) .type(selector, backSpaces.join('')); }); let actions = { login: function(userName, done) { this.goto(`${config.url}auth/?apiKey=salix`) .wait(`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]`) // FIXME: Wait for dom to be ready: https://github.com/segmentio/nightmare/issues/481 .wait(1000) .then(done) .catch(done); }, changeLanguageToEnglish: function(done) { this.wait('#lang') .evaluate(selector => { return document.querySelector(selector).title; }, '#lang') .then(title => { if (title === 'Change language') { this.then(done) .catch(done); } else { this.click('#lang') .click('vn-main-menu [vn-id="langs-menu"] ul > li[name="en"]') .then(done) .catch(done); } }); }, waitForLogin: function(userName, done) { this.login(userName) .waitForURL('#!/') .url() .changeLanguageToEnglish() .then(done) .catch(done); }, selectModule: function(moduleName, done) { this.waitToClick(`vn-home a[ui-sref="${moduleName}.index"]`) .waitForURL(moduleName) .then(done) .catch(done); }, loginAndModule: function(userName, moduleName, done) { this.waitForLogin(userName) .selectModule(moduleName) .then(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); }, 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); }, 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 elementBoundaries = element.getBoundingClientRect(); const x = elementBoundaries.left + element.offsetWidth / 2; const y = elementBoundaries.top + element.offsetHeight / 2; const elementInCenter = document.elementFromPoint(x, y); const elementInTopLeft = document.elementFromPoint(elementBoundaries.left, elementBoundaries.top); const elementInBottomRight = document.elementFromPoint(elementBoundaries.right, elementBoundaries.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); }, 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(err => { 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((selector, className) => { if (document.querySelector(selector).classList.contains(className)) return true; }, selector, className) .then(done) .catch(done); }, waitForTextInElement: function(selector, name, done) { this.wait(selector) .wait((selector, name) => { return document.querySelector(selector).innerText.toLowerCase().includes(name.toLowerCase()); }, selector, name) .then(done) .catch(done); }, waitForTextInInput: function(selector, name, done) { this.wait(selector) .wait((selector, name) => { return document.querySelector(selector).value.toLowerCase().includes(name.toLowerCase()); }, selector, name) .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.wait(`vn-searchbar input`) .type(`vn-searchbar input`, searchValue) .click(`vn-searchbar vn-icon[icon="search"]`) .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.wait(autocompleteSelector) .waitToClick(`${autocompleteSelector} input`) .type(`${autocompleteSelector} vn-drop-down input`, searchValue) .waitToClick(`${autocompleteSelector} 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 ${autocompleteSelector}, timed out`)); }); }, datePicker: function(datePickerSelector, changeMonth, done) { this.wait(datePickerSelector) .mousedown(datePickerSelector) .wait('div.flatpickr-calendar.open'); if (changeMonth > 0) this.mousedown('body > div > div.flatpickr-months > span.flatpickr-next-month > svg'); if (changeMonth < 0) this.mousedown('body > div > div.flatpickr-months > span.flatpickr-prev-month > svg'); const daySelector = 'div.flatpickr-calendar.open span.flatpickr-day:nth-child(16)'; this.wait(selector => { return document.querySelector(selector); }, daySelector) .evaluate(selector => { let event = new MouseEvent('mousedown', { bubbles: true, cancelable: true, view: window }); document.querySelector(selector).dispatchEvent(event); }, daySelector) .then(done) .catch(() => { done(new Error(`.datePicker(), for ${daySelector} timed out`)); }); } }; Object.keys(actions).forEach(function(name) { let fn = actions[name]; Nightmare.action(name, fn); });