This is just a technology testing project based on Create React App and TailwindCSS
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

549 lines
20KB

  1. const config = require('../config');
  2. const assert = require('assert');
  3. const scope = require('./scope');
  4. const pages = require('./pages');
  5. const selectors = require('./selectors');
  6. const pending = callback => {
  7. callback(null, 'pending');
  8. };
  9. const delay = duration => new Promise(resolve => setTimeout(resolve, duration));
  10. const wait = async timeInSeconds => {
  11. const time = Math.round(parseFloat((''+timeInSeconds).replace(',', '.')) * 1000);
  12. await delay(time);
  13. };
  14. const clearStorages = async () => {
  15. if (scope.context && scope.context.page) {
  16. await scope.context.page.evaluate(() => {
  17. localStorage.clear();
  18. sessionStorage.clear();
  19. });
  20. }
  21. }
  22. const visitPage = async (page) => {
  23. if (!scope.browser) {
  24. scope.browser = await scope.driver.launch({
  25. headless: config.headless,
  26. slowMo: config.slowMo,
  27. args: [
  28. ],
  29. });
  30. }
  31. scope.context.page = await scope.browser.newPage();
  32. scope.context.page.setViewport({ width: 1280, height: 1024 });
  33. const url = scope.host + pages[page];
  34. const visit = await scope.context.page.goto(url, {
  35. waitUntil: 'networkidle2'
  36. });
  37. return visit;
  38. };
  39. const visitPageIncognito = async (page) => {
  40. if (!scope.browser) {
  41. scope.browser = await scope.driver.launch({
  42. headless: config.headless,
  43. slowMo: config.slowMo,
  44. args: [
  45. '--incognito',
  46. ],
  47. });
  48. }
  49. scope.context.page = await scope.browser.newPage();
  50. scope.context.page.setViewport({ width: 1280, height: 1024 });
  51. const url = scope.host + pages[page];
  52. const visit = await scope.context.page.goto(url, {
  53. waitUntil: 'networkidle2'
  54. });
  55. return visit;
  56. };
  57. const clickLink = async (text) => {
  58. const { page } = scope.context;
  59. const [link] = await page.$x(`//button[contains(., '${text}')]|//a[contains(., '${text}')]`);
  60. if (link) {
  61. await link.click();
  62. } else {
  63. throw new Error(
  64. `Can not find link with text '${text}'`
  65. );
  66. }
  67. };
  68. const clickLinkAt = async (text, wrapper) => {
  69. const { page } = scope.context;
  70. const [link] = await page.$x(`//*[contains(@class, '${selectors.wrappers[wrapper]}')]//button[contains(., '${text}')]|a[contains(., '${text}')]`);
  71. if (link) {
  72. await link.click();
  73. } else {
  74. throw new Error(
  75. `Can not find link with text '${text}' in the wrapper ${wrapper}`
  76. );
  77. }
  78. };
  79. const pressButton = async text => {
  80. const { page } = scope.context;
  81. const [button] = await page.$x(`//button[contains(., '${text}')]|//a[contains(., '${text}')]`);
  82. if (button) {
  83. await button.click();
  84. } else {
  85. throw new Error(
  86. `Can not find button with text '${text}'`
  87. );
  88. }
  89. };
  90. const pressButtonAt = async (text, wrapper) => {
  91. const { page } = scope.context;
  92. const [button] = await page.$x(`//*[contains(@class, '${selectors.wrappers[wrapper]}')]//button[contains(., '${text}')]|a[contains(., '${text}')]`);
  93. if (button) {
  94. await button.click();
  95. } else {
  96. throw new Error(
  97. `Can not find button with text '${text}' in the wrapper ${wrapper}`
  98. );
  99. }
  100. };
  101. const modalOpened = async text => {
  102. await delay(250);
  103. const { page } = scope.context;
  104. const waitModal = await page.waitForSelector('.modal');
  105. if (!waitModal) {
  106. throw new Error(
  107. `Can not find modal`
  108. );
  109. }
  110. await waitModal;
  111. const [modalWithTitle] = await page.$x(`//*[contains(@class, 'modal')]//*[contains(@class, 'modal-title')][contains(., '${text}')]`);
  112. if (!modalWithTitle) {
  113. throw new Error(
  114. `Can not find modal with title '${text}'`
  115. );
  116. }
  117. }
  118. const modalClosed = async text => {
  119. await delay(250);
  120. const { page } = scope.context;
  121. const [modal] = await page.$x(`//*[contains(@class, 'modal')]`);
  122. if (modal) {
  123. const [modalWithTitle] = await page.$x(`//*[contains(@class, 'modal')]//*[contains(@class, 'modal-title')][contains(., '${text}')]`);
  124. if (modalWithTitle) {
  125. throw new Error(
  126. `Modal with title '${text}' still opened`
  127. );
  128. }
  129. }
  130. }
  131. const shouldSeeText = async text => {
  132. const { page } = scope.context;
  133. const content = String(await page.$eval('*', el => el.innerText)).replace(/[\t\s\n\r]+/g, ' ');
  134. if (!content.includes(text))
  135. throw new Error(
  136. `Expected page to contain text: ${text}, but page contains only: ${content}`
  137. );
  138. };
  139. const shouldSeeTextAt = async (text, wrapper) => {
  140. const { page } = scope.context;
  141. const content = await page.evaluate(selector => {
  142. const elements = document.querySelectorAll(selector);
  143. if (!elements) return '';
  144. const aTexts = [...elements].map(el => el.textContent);
  145. return aTexts.join(' | ').replace(/[\t\s\n\r]+/g, ' ');
  146. }, `.${selectors.wrappers[wrapper]}`);
  147. if (!content.includes(text)) {
  148. throw new Error(
  149. `Expected wrapper ${wrapper} to contain text: ${text}, but it contains only: ${content}`
  150. );
  151. }
  152. };
  153. const shouldNotSeeText = async text => {
  154. const { page } = scope.context;
  155. const content = String(await page.$eval('*', el => el.innerText)).replace(/[\t\s\n\r]+/g, ' ');
  156. if (content.includes(text))
  157. throw new Error(
  158. `Expected page to not contain text: ${text}, but page contains: ${content}`
  159. );
  160. };
  161. const shouldNotSeeTextAt = async (text, wrapper) => {
  162. const { page } = scope.context;
  163. const content = await page.evaluate(selector => {
  164. const elements = document.querySelectorAll(selector);
  165. if (!elements) return '';
  166. const aTexts = [...elements].map(el => el.innerText);
  167. return aTexts.join(' | ').replace(/[\t\s\n\r]+/g, ' ');
  168. }, `.${selectors.wrappers[wrapper]}:not(.sr-only)`);
  169. if (content.includes(text)) {
  170. throw new Error(
  171. `Expected wrapper ${wrapper} to not contain text: ${text}, but it contains: ${content}`
  172. );
  173. }
  174. };
  175. const buttonShouldBeDisabled = async text => {
  176. const { page } = scope.context;
  177. const [button] = await page.$x(`//button[contains(., '${text}')]`);
  178. if (button) {
  179. let valueHandle = await button.getProperty('disabled');
  180. assert.strictEqual(await valueHandle.jsonValue(), true);
  181. } else {
  182. throw new Error(
  183. `Can not find page button with text '${text}'`
  184. );
  185. }
  186. };
  187. const buttonShouldBeEnabled = async text => {
  188. const { page } = scope.context;
  189. const [button] = await page.$x(`//button[contains(., '${text}')]`);
  190. if (button) {
  191. let valueHandle = await button.getProperty('disabled');
  192. assert.strictEqual(await valueHandle.jsonValue(), false);
  193. } else {
  194. throw new Error(
  195. `Can not find page button with text '${text}'`
  196. );
  197. }
  198. };
  199. const buttonShouldBeDisabledAt = async (text, wrapper) => {
  200. const { page } = scope.context;
  201. const [button] = await page.$x(`//*[contains(@class, '${selectors.wrappers[wrapper]}')]//button[contains(., '${text}')]`);
  202. if (button) {
  203. let valueHandle = await button.getProperty('disabled');
  204. assert.strictEqual(await valueHandle.jsonValue(), true);
  205. } else {
  206. throw new Error(
  207. `Can not find page button with text '${text}' in the wrapper ${wrapper}`
  208. );
  209. }
  210. };
  211. const buttonShouldBeEnabledAt = async (text, wrapper) => {
  212. const { page } = scope.context;
  213. const [button] = await page.$x(`//*[contains(@class, '${selectors.wrappers[wrapper]}')]//button[contains(., '${text}')]`);
  214. if (button) {
  215. let valueHandle = await button.getProperty('disabled');
  216. assert.strictEqual(await valueHandle.jsonValue(), false);
  217. } else {
  218. throw new Error(
  219. `Can not find page button with text '${text}' in the wrapper ${wrapper}`
  220. );
  221. }
  222. };
  223. const fillInputField = async (value, field) => {
  224. const { page } = scope.context;
  225. await shouldSeeText(field);
  226. const fieldPresent = await page.waitForSelector(selectors.inputs[field]);
  227. await fieldPresent;
  228. await page.focus(`input[name='${field}']`);
  229. const inputValue = await page.$eval(selectors.inputs[field], el => el.value);
  230. for (let i = 0; i < inputValue.length; i++) {
  231. await page.keyboard.press('Backspace');
  232. }
  233. await page.type(selectors.inputs[field], value, { delay: 1 });
  234. await page.$eval(selectors.inputs[field], e => e.blur());
  235. return;
  236. };
  237. const fillInputFieldAt = async (value, field, wrapper) => {
  238. const { page } = scope.context;
  239. await shouldSeeText(field);
  240. const fieldPresent = await page.waitForSelector(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`);
  241. await fieldPresent;
  242. await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`);
  243. const inputValue = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, el => el.value);
  244. for (let i = 0; i < inputValue.length; i++) {
  245. await page.keyboard.press('Backspace');
  246. }
  247. await page.type(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, value, { delay: 1 });
  248. await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, e => e.blur());
  249. return;
  250. };
  251. const setCheckboxField = async (field) => {
  252. const { page } = scope.context;
  253. await shouldSeeText(field);
  254. const fieldPresent = await page.waitForSelector(selectors.inputs[field]);
  255. await fieldPresent;
  256. const isChecked = await page.$eval(selectors.inputs[field], el => el.checked)
  257. if (!isChecked) {
  258. await page.focus(selectors.inputs[field]);
  259. await page.$eval(selectors.inputs[field], check => check.click());
  260. await page.$eval(selectors.inputs[field], e => e.blur());
  261. }
  262. return;
  263. };
  264. const unsetCheckboxField = async (field) => {
  265. const { page } = scope.context;
  266. await shouldSeeText(field);
  267. const fieldPresent = await page.waitForSelector(selectors.inputs[field]);
  268. await fieldPresent;
  269. const isChecked = await page.$eval(selectors.inputs[field], el => el.checked)
  270. if (isChecked) {
  271. await page.focus(selectors.inputs[field]);
  272. await page.$eval(selectors.inputs[field], check => check.click());
  273. await page.$eval(selectors.inputs[field], e => e.blur());
  274. }
  275. return;
  276. };
  277. const setCheckboxFieldAt = async (field, wrapper) => {
  278. const { page } = scope.context;
  279. await shouldSeeText(field);
  280. const fieldPresent = await page.waitForSelector(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`);
  281. await fieldPresent;
  282. const isChecked = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, el => el.checked)
  283. if (!isChecked) {
  284. await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`);
  285. await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, check => check.click());
  286. await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, e => e.blur());
  287. }
  288. return;
  289. };
  290. const unsetCheckboxFieldAt = async (field, wrapper) => {
  291. const { page } = scope.context;
  292. await shouldSeeText(field);
  293. const fieldPresent = await page.waitForSelector(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`);
  294. await fieldPresent;
  295. const isChecked = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, el => el.checked)
  296. if (isChecked) {
  297. await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`);
  298. await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, check => check.click());
  299. await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, e => e.blur());
  300. }
  301. return;
  302. };
  303. const chooseRadioOption = async (option, field) => {
  304. const { page } = scope.context;
  305. await shouldSeeText(field);
  306. if (!selectors.radio_groups[field]) {
  307. throw new Error(
  308. `Radio group '${field}' has no registered selector`
  309. );
  310. }
  311. await shouldSeeText(option);
  312. if (!selectors.radio_groups[field].options[option]) {
  313. throw new Error(
  314. `Radio group '${field}' has no registered option ${option}`
  315. );
  316. }
  317. const isChecked = await page.$eval(`${selectors.radio_groups[field].options[option]}`, el => el.checked)
  318. if (!isChecked) {
  319. await page.focus(`${selectors.radio_groups[field].options[option]}`);
  320. await page.$eval(`${selectors.radio_groups[field].options[option]}`, check => check.click());
  321. await page.$eval(`${selectors.radio_groups[field].options[option]}`, e => e.blur());
  322. }
  323. };
  324. const chooseRadioOptionAt = async (option, field, wrapper) => {
  325. const { page } = scope.context;
  326. await shouldSeeText(field);
  327. if (!selectors.radio_groups[field]) {
  328. throw new Error(
  329. `Radio group '${field}' has no registered selector`
  330. );
  331. }
  332. await shouldSeeText(option);
  333. if (!selectors.radio_groups[field].options[option]) {
  334. throw new Error(
  335. `Radio group '${field}' has no registered option ${option}`
  336. );
  337. }
  338. const isChecked = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.radio_groups[field].options[option]}`, el => el.checked)
  339. if (!isChecked) {
  340. await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.radio_groups[field].options[option]}`);
  341. await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.radio_groups[field].options[option]}`, check => check.click());
  342. await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.radio_groups[field].options[option]}`, e => e.blur());
  343. }
  344. };
  345. const setCheckboxGroupOption = async (option, field) => {
  346. const { page } = scope.context;
  347. await shouldSeeText(field);
  348. if (!selectors.checkbox_groups[field]) {
  349. throw new Error(
  350. `Checkbox group '${field}' has no registered selector`
  351. );
  352. }
  353. await shouldSeeText(option);
  354. if (!selectors.checkbox_groups[field].options[option]) {
  355. throw new Error(
  356. `Checkbox group '${field}' has no registered option ${option}`
  357. );
  358. }
  359. const isChecked = await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, el => el.checked)
  360. if (!isChecked) {
  361. await page.focus(`${selectors.checkbox_groups[field].options[option]}`);
  362. await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, check => check.click());
  363. await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, e => e.blur());
  364. }
  365. };
  366. const unsetCheckboxGroupOption = async (option, field) => {
  367. const { page } = scope.context;
  368. await shouldSeeText(field);
  369. if (!selectors.checkbox_groups[field]) {
  370. throw new Error(
  371. `Checkbox group '${field}' has no registered selector`
  372. );
  373. }
  374. await shouldSeeText(option);
  375. if (!selectors.checkbox_groups[field].options[option]) {
  376. throw new Error(
  377. `Checkbox group '${field}' has no registered option ${option}`
  378. );
  379. }
  380. const isChecked = await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, el => el.checked)
  381. if (isChecked) {
  382. await page.focus(`${selectors.checkbox_groups[field].options[option]}`);
  383. await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, check => check.click());
  384. await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, e => e.blur());
  385. }
  386. };
  387. const setCheckboxGroupOptionAt = async (option, field, wrapper) => {
  388. const { page } = scope.context;
  389. await shouldSeeText(field);
  390. if (!selectors.checkbox_groups[field]) {
  391. throw new Error(
  392. `Checkbox group '${field}' has no registered selector`
  393. );
  394. }
  395. await shouldSeeText(option);
  396. if (!selectors.checkbox_groups[field].options[option]) {
  397. throw new Error(
  398. `Checkbox group '${field}' has no registered option ${option}`
  399. );
  400. }
  401. const isChecked = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, el => el.checked)
  402. if (!isChecked) {
  403. await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`);
  404. await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, check => check.click());
  405. await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, e => e.blur());
  406. }
  407. };
  408. const unsetCheckboxGroupOptionAt = async (option, field, wrapper) => {
  409. const { page } = scope.context;
  410. await shouldSeeText(field);
  411. if (!selectors.checkbox_groups[field]) {
  412. throw new Error(
  413. `Checkbox group '${field}' has no registered selector`
  414. );
  415. }
  416. await shouldSeeText(option);
  417. if (!selectors.checkbox_groups[field].options[option]) {
  418. throw new Error(
  419. `Checkbox group '${field}' has no registered option ${option}`
  420. );
  421. }
  422. const isChecked = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, el => el.checked)
  423. if (isChecked) {
  424. await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`);
  425. await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, check => check.click());
  426. await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, e => e.blur());
  427. }
  428. };
  429. const jsonViewContains = async (field, value) => {
  430. const { page } = scope.context;
  431. const jsonViewPresent = await page.waitForSelector('.react-json-view');
  432. await jsonViewPresent;
  433. const aRes = await page.$$eval('.variable-row', options => options.map(option => option.textContent))
  434. const keyWrap = isNaN(field) ? '"' : ''
  435. const valWrap = isNaN(value) ? '"' : ''
  436. let res = false;
  437. aRes.forEach(e => {
  438. if ('true' === value || 'false' === value) {
  439. if (e.includes(keyWrap + field + keyWrap) && e.includes('bool' + value)) {
  440. res = true;
  441. }
  442. } else {
  443. if (e.includes(keyWrap + field + keyWrap) && e.includes(valWrap + value + valWrap)) {
  444. res = true;
  445. }
  446. }
  447. });
  448. if (!res) {
  449. throw new Error(
  450. `Expected jsonView var: ${field} should contains ${value}, but given ${aRes}`
  451. );
  452. }
  453. };
  454. const jsonViewContainsAt = async (fieldName, rowKey, rowValue) => {
  455. const { page } = scope.context;
  456. const [fieldWrapper] = await page.$x(`//*[contains(@class, 'react-json-view')]//*[contains(., '${fieldName}')]//preceding::*[contains(@class, 'object-key-val')][1]`);
  457. const aRes = await fieldWrapper.$$eval('.variable-row', options => options.map(option => option.textContent))
  458. const keyWrap = isNaN(rowKey) ? '"' : ''
  459. const valWrap = isNaN(rowValue) ? '"' : ''
  460. let res = false;
  461. aRes.forEach(e => {
  462. if ('true' === rowValue || 'false' === rowValue) {
  463. if (e.includes(keyWrap + rowKey + keyWrap) && e.includes('bool' + rowValue)) {
  464. res = true;
  465. }
  466. } else {
  467. if (e.includes(keyWrap + rowKey + keyWrap) && e.includes(valWrap + rowValue + valWrap)) {
  468. res = true;
  469. }
  470. }
  471. });
  472. if (!res) {
  473. throw new Error(
  474. `Expected jsonView var: ${fieldName} should contains: ${rowKey} => ${rowValue}, but given ${aRes}`
  475. );
  476. }
  477. };
  478. module.exports = {
  479. pending,
  480. delay,
  481. wait,
  482. clearStorages,
  483. visitPage,
  484. visitPageIncognito,
  485. shouldSeeText,
  486. shouldSeeTextAt,
  487. shouldNotSeeText,
  488. shouldNotSeeTextAt,
  489. clickLink,
  490. clickLinkAt,
  491. pressButton,
  492. pressButtonAt,
  493. buttonShouldBeDisabled,
  494. buttonShouldBeEnabled,
  495. buttonShouldBeDisabledAt,
  496. buttonShouldBeEnabledAt,
  497. fillInputField,
  498. setCheckboxField,
  499. unsetCheckboxField,
  500. fillInputFieldAt,
  501. setCheckboxFieldAt,
  502. unsetCheckboxFieldAt,
  503. chooseRadioOption,
  504. chooseRadioOptionAt,
  505. setCheckboxGroupOption,
  506. unsetCheckboxGroupOption,
  507. setCheckboxGroupOptionAt,
  508. unsetCheckboxGroupOptionAt,
  509. modalOpened,
  510. modalClosed,
  511. jsonViewContains,
  512. jsonViewContainsAt,
  513. };