const config = require('../config'); const assert = require('assert'); const scope = require('./scope'); const pages = require('./pages'); const selectors = require('./selectors'); const pending = callback => { callback(null, 'pending'); }; const delay = duration => new Promise(resolve => setTimeout(resolve, duration)); const wait = async timeInSeconds => { const time = Math.round(parseFloat((''+timeInSeconds).replace(',', '.')) * 1000); await delay(time); }; const clearStorages = async () => { if (scope.context && scope.context.page) { await scope.context.page.evaluate(() => { localStorage.clear(); sessionStorage.clear(); }); } } const visitPage = async (page) => { if (!scope.browser) { scope.browser = await scope.driver.launch({ headless: config.headless, slowMo: config.slowMo, args: [ ], }); } scope.context.page = await scope.browser.newPage(); scope.context.page.setViewport({ width: 1280, height: 1024 }); const url = scope.host + pages[page]; const visit = await scope.context.page.goto(url, { waitUntil: 'networkidle2' }); return visit; }; const visitPageIncognito = async (page) => { if (!scope.browser) { scope.browser = await scope.driver.launch({ headless: config.headless, slowMo: config.slowMo, args: [ '--incognito', ], }); } scope.context.page = await scope.browser.newPage(); scope.context.page.setViewport({ width: 1280, height: 1024 }); const url = scope.host + pages[page]; const visit = await scope.context.page.goto(url, { waitUntil: 'networkidle2' }); return visit; }; const clickLink = async (text) => { const { page } = scope.context; const [link] = await page.$x(`//button[contains(., '${text}')]|//a[contains(., '${text}')]`); if (link) { await link.click(); } else { throw new Error( `Can not find link with text '${text}'` ); } }; const clickLinkAt = async (text, wrapper) => { const { page } = scope.context; const [link] = await page.$x(`//*[contains(@class, '${selectors.wrappers[wrapper]}')]//button[contains(., '${text}')]|a[contains(., '${text}')]`); if (link) { await link.click(); } else { throw new Error( `Can not find link with text '${text}' in the wrapper ${wrapper}` ); } }; const pressButton = async text => { const { page } = scope.context; const [button] = await page.$x(`//button[contains(., '${text}')]|//a[contains(., '${text}')]`); if (button) { await button.click(); } else { throw new Error( `Can not find button with text '${text}'` ); } }; const pressButtonAt = async (text, wrapper) => { const { page } = scope.context; const [button] = await page.$x(`//*[contains(@class, '${selectors.wrappers[wrapper]}')]//button[contains(., '${text}')]|a[contains(., '${text}')]`); if (button) { await button.click(); } else { throw new Error( `Can not find button with text '${text}' in the wrapper ${wrapper}` ); } }; const modalOpened = async text => { await delay(250); const { page } = scope.context; const waitModal = await page.waitForSelector('.modal'); if (!waitModal) { throw new Error( `Can not find modal` ); } await waitModal; const [modalWithTitle] = await page.$x(`//*[contains(@class, 'modal')]//*[contains(@class, 'modal-title')][contains(., '${text}')]`); if (!modalWithTitle) { throw new Error( `Can not find modal with title '${text}'` ); } } const modalClosed = async text => { await delay(250); const { page } = scope.context; const [modal] = await page.$x(`//*[contains(@class, 'modal')]`); if (modal) { const [modalWithTitle] = await page.$x(`//*[contains(@class, 'modal')]//*[contains(@class, 'modal-title')][contains(., '${text}')]`); if (modalWithTitle) { throw new Error( `Modal with title '${text}' still opened` ); } } } const shouldSeeText = async text => { const { page } = scope.context; const content = String(await page.$eval('*', el => el.innerText)).replace(/[\t\s\n\r]+/g, ' '); if (!content.includes(text)) throw new Error( `Expected page to contain text: ${text}, but page contains only: ${content}` ); }; const shouldSeeTextAt = async (text, wrapper) => { const { page } = scope.context; const content = await page.evaluate(selector => { const elements = document.querySelectorAll(selector); if (!elements) return ''; const aTexts = [...elements].map(el => el.textContent); return aTexts.join(' | ').replace(/[\t\s\n\r]+/g, ' '); }, `.${selectors.wrappers[wrapper]}`); if (!content.includes(text)) { throw new Error( `Expected wrapper ${wrapper} to contain text: ${text}, but it contains only: ${content}` ); } }; const shouldNotSeeText = async text => { const { page } = scope.context; const content = String(await page.$eval('*', el => el.innerText)).replace(/[\t\s\n\r]+/g, ' '); if (content.includes(text)) throw new Error( `Expected page to not contain text: ${text}, but page contains: ${content}` ); }; const shouldNotSeeTextAt = async (text, wrapper) => { const { page } = scope.context; const content = await page.evaluate(selector => { const elements = document.querySelectorAll(selector); if (!elements) return ''; const aTexts = [...elements].map(el => el.innerText); return aTexts.join(' | ').replace(/[\t\s\n\r]+/g, ' '); }, `.${selectors.wrappers[wrapper]}:not(.sr-only)`); if (content.includes(text)) { throw new Error( `Expected wrapper ${wrapper} to not contain text: ${text}, but it contains: ${content}` ); } }; const buttonShouldBeDisabled = async text => { const { page } = scope.context; const [button] = await page.$x(`//button[contains(., '${text}')]`); if (button) { let valueHandle = await button.getProperty('disabled'); assert.strictEqual(await valueHandle.jsonValue(), true); } else { throw new Error( `Can not find page button with text '${text}'` ); } }; const buttonShouldBeEnabled = async text => { const { page } = scope.context; const [button] = await page.$x(`//button[contains(., '${text}')]`); if (button) { let valueHandle = await button.getProperty('disabled'); assert.strictEqual(await valueHandle.jsonValue(), false); } else { throw new Error( `Can not find page button with text '${text}'` ); } }; const buttonShouldBeDisabledAt = async (text, wrapper) => { const { page } = scope.context; const [button] = await page.$x(`//*[contains(@class, '${selectors.wrappers[wrapper]}')]//button[contains(., '${text}')]`); if (button) { let valueHandle = await button.getProperty('disabled'); assert.strictEqual(await valueHandle.jsonValue(), true); } else { throw new Error( `Can not find page button with text '${text}' in the wrapper ${wrapper}` ); } }; const buttonShouldBeEnabledAt = async (text, wrapper) => { const { page } = scope.context; const [button] = await page.$x(`//*[contains(@class, '${selectors.wrappers[wrapper]}')]//button[contains(., '${text}')]`); if (button) { let valueHandle = await button.getProperty('disabled'); assert.strictEqual(await valueHandle.jsonValue(), false); } else { throw new Error( `Can not find page button with text '${text}' in the wrapper ${wrapper}` ); } }; const fillInputField = async (value, field) => { const { page } = scope.context; await shouldSeeText(field); const fieldPresent = await page.waitForSelector(selectors.inputs[field]); await fieldPresent; await page.focus(`input[name='${field}']`); const inputValue = await page.$eval(selectors.inputs[field], el => el.value); for (let i = 0; i < inputValue.length; i++) { await page.keyboard.press('Backspace'); } await page.type(selectors.inputs[field], value, { delay: 1 }); await page.$eval(selectors.inputs[field], e => e.blur()); return; }; const fillInputFieldAt = async (value, field, wrapper) => { const { page } = scope.context; await shouldSeeText(field); const fieldPresent = await page.waitForSelector(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`); await fieldPresent; await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`); const inputValue = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, el => el.value); for (let i = 0; i < inputValue.length; i++) { await page.keyboard.press('Backspace'); } await page.type(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, value, { delay: 1 }); await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, e => e.blur()); return; }; const setCheckboxField = async (field) => { const { page } = scope.context; await shouldSeeText(field); const fieldPresent = await page.waitForSelector(selectors.inputs[field]); await fieldPresent; const isChecked = await page.$eval(selectors.inputs[field], el => el.checked) if (!isChecked) { await page.focus(selectors.inputs[field]); await page.$eval(selectors.inputs[field], check => check.click()); await page.$eval(selectors.inputs[field], e => e.blur()); } return; }; const unsetCheckboxField = async (field) => { const { page } = scope.context; await shouldSeeText(field); const fieldPresent = await page.waitForSelector(selectors.inputs[field]); await fieldPresent; const isChecked = await page.$eval(selectors.inputs[field], el => el.checked) if (isChecked) { await page.focus(selectors.inputs[field]); await page.$eval(selectors.inputs[field], check => check.click()); await page.$eval(selectors.inputs[field], e => e.blur()); } return; }; const setCheckboxFieldAt = async (field, wrapper) => { const { page } = scope.context; await shouldSeeText(field); const fieldPresent = await page.waitForSelector(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`); await fieldPresent; const isChecked = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, el => el.checked) if (!isChecked) { await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`); await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, check => check.click()); await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, e => e.blur()); } return; }; const unsetCheckboxFieldAt = async (field, wrapper) => { const { page } = scope.context; await shouldSeeText(field); const fieldPresent = await page.waitForSelector(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`); await fieldPresent; const isChecked = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, el => el.checked) if (isChecked) { await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`); await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, check => check.click()); await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, e => e.blur()); } return; }; const chooseRadioOption = async (option, field) => { const { page } = scope.context; await shouldSeeText(field); if (!selectors.radio_groups[field]) { throw new Error( `Radio group '${field}' has no registered selector` ); } await shouldSeeText(option); if (!selectors.radio_groups[field].options[option]) { throw new Error( `Radio group '${field}' has no registered option ${option}` ); } const isChecked = await page.$eval(`${selectors.radio_groups[field].options[option]}`, el => el.checked) if (!isChecked) { await page.focus(`${selectors.radio_groups[field].options[option]}`); await page.$eval(`${selectors.radio_groups[field].options[option]}`, check => check.click()); await page.$eval(`${selectors.radio_groups[field].options[option]}`, e => e.blur()); } }; const chooseRadioOptionAt = async (option, field, wrapper) => { const { page } = scope.context; await shouldSeeText(field); if (!selectors.radio_groups[field]) { throw new Error( `Radio group '${field}' has no registered selector` ); } await shouldSeeText(option); if (!selectors.radio_groups[field].options[option]) { throw new Error( `Radio group '${field}' has no registered option ${option}` ); } const isChecked = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.radio_groups[field].options[option]}`, el => el.checked) if (!isChecked) { await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.radio_groups[field].options[option]}`); await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.radio_groups[field].options[option]}`, check => check.click()); await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.radio_groups[field].options[option]}`, e => e.blur()); } }; const setCheckboxGroupOption = async (option, field) => { const { page } = scope.context; await shouldSeeText(field); if (!selectors.checkbox_groups[field]) { throw new Error( `Checkbox group '${field}' has no registered selector` ); } await shouldSeeText(option); if (!selectors.checkbox_groups[field].options[option]) { throw new Error( `Checkbox group '${field}' has no registered option ${option}` ); } const isChecked = await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, el => el.checked) if (!isChecked) { await page.focus(`${selectors.checkbox_groups[field].options[option]}`); await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, check => check.click()); await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, e => e.blur()); } }; const unsetCheckboxGroupOption = async (option, field) => { const { page } = scope.context; await shouldSeeText(field); if (!selectors.checkbox_groups[field]) { throw new Error( `Checkbox group '${field}' has no registered selector` ); } await shouldSeeText(option); if (!selectors.checkbox_groups[field].options[option]) { throw new Error( `Checkbox group '${field}' has no registered option ${option}` ); } const isChecked = await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, el => el.checked) if (isChecked) { await page.focus(`${selectors.checkbox_groups[field].options[option]}`); await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, check => check.click()); await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, e => e.blur()); } }; const setCheckboxGroupOptionAt = async (option, field, wrapper) => { const { page } = scope.context; await shouldSeeText(field); if (!selectors.checkbox_groups[field]) { throw new Error( `Checkbox group '${field}' has no registered selector` ); } await shouldSeeText(option); if (!selectors.checkbox_groups[field].options[option]) { throw new Error( `Checkbox group '${field}' has no registered option ${option}` ); } const isChecked = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, el => el.checked) if (!isChecked) { await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`); await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, check => check.click()); await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, e => e.blur()); } }; const unsetCheckboxGroupOptionAt = async (option, field, wrapper) => { const { page } = scope.context; await shouldSeeText(field); if (!selectors.checkbox_groups[field]) { throw new Error( `Checkbox group '${field}' has no registered selector` ); } await shouldSeeText(option); if (!selectors.checkbox_groups[field].options[option]) { throw new Error( `Checkbox group '${field}' has no registered option ${option}` ); } const isChecked = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, el => el.checked) if (isChecked) { await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`); await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, check => check.click()); await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, e => e.blur()); } }; const jsonViewContains = async (field, value) => { const { page } = scope.context; const jsonViewPresent = await page.waitForSelector('.react-json-view'); await jsonViewPresent; const aRes = await page.$$eval('.variable-row', options => options.map(option => option.textContent)) const keyWrap = isNaN(field) ? '"' : '' const valWrap = isNaN(value) ? '"' : '' let res = false; aRes.forEach(e => { if ('true' === value || 'false' === value) { if (e.includes(keyWrap + field + keyWrap) && e.includes('bool' + value)) { res = true; } } else { if (e.includes(keyWrap + field + keyWrap) && e.includes(valWrap + value + valWrap)) { res = true; } } }); if (!res) { throw new Error( `Expected jsonView var: ${field} should contains ${value}, but given ${aRes}` ); } }; const jsonViewContainsAt = async (fieldName, rowKey, rowValue) => { const { page } = scope.context; const [fieldWrapper] = await page.$x(`//*[contains(@class, 'react-json-view')]//*[contains(., '${fieldName}')]//preceding::*[contains(@class, 'object-key-val')][1]`); const aRes = await fieldWrapper.$$eval('.variable-row', options => options.map(option => option.textContent)) const keyWrap = isNaN(rowKey) ? '"' : '' const valWrap = isNaN(rowValue) ? '"' : '' let res = false; aRes.forEach(e => { if ('true' === rowValue || 'false' === rowValue) { if (e.includes(keyWrap + rowKey + keyWrap) && e.includes('bool' + rowValue)) { res = true; } } else { if (e.includes(keyWrap + rowKey + keyWrap) && e.includes(valWrap + rowValue + valWrap)) { res = true; } } }); if (!res) { throw new Error( `Expected jsonView var: ${fieldName} should contains: ${rowKey} => ${rowValue}, but given ${aRes}` ); } }; module.exports = { pending, delay, wait, clearStorages, visitPage, visitPageIncognito, shouldSeeText, shouldSeeTextAt, shouldNotSeeText, shouldNotSeeTextAt, clickLink, clickLinkAt, pressButton, pressButtonAt, buttonShouldBeDisabled, buttonShouldBeEnabled, buttonShouldBeDisabledAt, buttonShouldBeEnabledAt, fillInputField, setCheckboxField, unsetCheckboxField, fillInputFieldAt, setCheckboxFieldAt, unsetCheckboxFieldAt, chooseRadioOption, chooseRadioOptionAt, setCheckboxGroupOption, unsetCheckboxGroupOption, setCheckboxGroupOptionAt, unsetCheckboxGroupOptionAt, modalOpened, modalClosed, jsonViewContains, jsonViewContainsAt, };