448 lines
15 KiB
JavaScript
448 lines
15 KiB
JavaScript
/* 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('.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');
|
|
});
|
|
},
|
|
|
|
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}`));
|
|
});
|
|
});
|
|
}
|