UNPKG

@cuppet/core

Version:

Core testing framework components for Cuppet - BDD framework based on Cucumber and Puppeteer

308 lines (287 loc) 11.5 kB
/** * @module dataStorage * @typedef {import('puppeteer').Page} Page */ const config = require('config'); const fs = require('fs'); const helper = require('./helperFunctions'); const moment = require('moment'); const jsonFilePath = config.get('jsonFilePath').toString(); module.exports = { /** * Create the JSON file in which test data will be stored * @returns {Promise<void>} */ createFile: async function () { if (jsonFilePath && !fs.existsSync(jsonFilePath)) { fs.writeFile(jsonFilePath, '', { flag: 'w+' }, (err) => { if (err) { console.error('File is not created, please check if the folder exists!'); } else { console.log('File Created'); } }); } }, /** * Clear the JSON file * @returns {void} */ clearJsonFile: function () { if (jsonFilePath) { fs.truncateSync(jsonFilePath, 0); console.log('JSON File Cleared successfully!'); } }, /** * Generate pa11y,lighthouse etc. html reports * @param fileName - string with the name of the file * @param data - HtmlReporter formatted result * @returns {Promise<void>} */ createHtmlReport: async function (fileName, data) { const profile = process.env.NODE_CONFIG_ENV || 'default'; fs.writeFile(`reports/${profile}/${fileName}.html`, data, (err) => { if (err) throw err; console.log(`Html report: ${fileName}.html is generated!`); }); }, /** * Load the JSON file * @param file * @returns {any|{}} */ getJsonFile: function (file = '') { file = file || jsonFilePath; const result = fs.readFileSync(file, 'utf-8'); return result ? JSON.parse(result) : {}; }, /** * Save variable to JSON file. Example: { * "varName":"Value" * } * @param data - the value of the variable * @param variable - the variable name * @returns {Promise<void>} */ iStoreVariableWithValueToTheJsonFile: async function (data, variable) { const tempJson = this.getJsonFile(); tempJson[variable] = data; fs.writeFileSync(jsonFilePath, JSON.stringify(tempJson), { flag: 'w+' }); }, /** * Get specific variable and throw error if missing * @param variable * @param {boolean} stringify - flag to be used when getting values which are object themselves * @returns {*} */ getVariable: function (variable, stringify = false) { const tempJson = this.getJsonFile(); if (!tempJson[variable]) { throw new Error(`Variable with name ${variable} not found in the json file!`); } if (stringify) { return JSON.stringify(tempJson[variable]); } return tempJson[variable]; }, /** * Check for variable existence or return the inputted value. * Do not use separately in step definitions, use checkForSavedVariable instead. * @param variable * @returns {*} */ checkForVariable: function (variable) { const tempJson = this.getJsonFile(); return tempJson[variable] ?? variable; }, /** * Replace single occurrence of %% pattern in a string with a stored variable. * Example - I go to "/node/%myStoredPage%" -> "/node/test-page-alias" * @param data * @returns {Promise<*>} */ checkForSavedVariable: async function (data) { // First check if the whole variable is stored const resolvedData = this.checkForVariable(data); // Then check for %% pattern replacements return resolvedData.replace(/%([a-zA-Z_-]+)%/g, (match, p1) => { return this.checkForVariable(p1); }); }, /** * Similar to checkForSavedVariable but it extracts multiple variable names from the following pattern: * Example - "Here are %var1% and %var2%" and replace * them with their values - "Here are valueOfVar1 and valueOfVar2" * @param text * @returns {Promise<*>} */ checkForMultipleVariables: async function (text) { const regex = /%([^%]+)%/g; const allVariables = this.getJsonFile(); // The convention dictates if function argument is not used, it can be replaced by "_". if (typeof text !== 'string') { throw new Error(`The value passed to checkForMultipleVariables is not a string: ${text}`); } const result = text.replace(regex, (_, group) => { return allVariables[group]; }); return result || text; }, /** * Re-save the JSON file with all it's values in lowercase. * @returns {Promise<void>} */ lowercaseAllVariables: async function () { const allVariables = this.getJsonFile(); const lowercaseJson = Object.fromEntries( Object.entries(allVariables).map(([key, value]) => [ key, typeof value === 'string' ? value.toLowerCase() : value, ]) ); fs.writeFileSync(jsonFilePath, JSON.stringify(lowercaseJson), { flag: 'w+' }); }, /** * Cut the value of a variable on special char occurrence. The regex can be changed via the config json. * Example - valueOf@Variable -> valueOf. * @param variable * @returns {Promise<void>} */ trimVariableOnFirstSpecialChar: async function (variable) { let regex = /[?&@$#:,;]/; if (config.has('trimRegex')) { const configRegex = config.get('trimRegex'); regex = new RegExp(configRegex.toString()); } const value = this.getVariable(variable); let splitArr = value.split(regex); const result = splitArr[0].trim(); await this.iStoreVariableWithValueToTheJsonFile(result, variable); }, /** * Save current page url in both relative and absolute url variants. Predefined json * property names are used for easier usage. * @param {Page} page - current tab in puppeteer * @returns {Promise<void>} */ saveCurrentPath: async function (page) { // Get the current URL const url = new URL(page.url()); const absolutePath = url.href; // Get the relative path const relativePath = url.pathname; // Store paths await this.iStoreVariableWithValueToTheJsonFile(absolutePath, 'path'); await this.iStoreVariableWithValueToTheJsonFile(relativePath, 'relativePath'); }, /** * Store ID (in case of Drupal) or the sequence of numbers in url path alias. * Example /node/123/edit -> 123 will be extracted and saved * @param {Page} page * @param variable * @returns {Promise<void>} */ iStoreEntityId: async function (page, variable) { const currentUrl = new URL(page.url()); const alias = currentUrl.pathname; const matches = /[1-9]\d*/.exec(alias); if (!matches) { throw new Error(`The url path doesn't contain an ID:${alias}`); } await this.iStoreVariableWithValueToTheJsonFile(matches[0], variable); }, /** * Saves the href from a link <a>. * TO DO: Can be done with more generic method to save specific attribute of element, instead of the hardcoded href. * @param {Page} page * @param selector * @param variable * @returns {Promise<void>} */ storeHrefOfElement: async function (page, selector, variable) { await page.waitForSelector(selector); let href = await page.$eval(selector, (el) => el.getAttribute('href')); href = encodeURI(href); await this.iStoreVariableWithValueToTheJsonFile(href, variable); }, /** * Save the text from a page element matching specific pattern. * Example: "This is your code for password reset: 123456" * You can create a regex to find the 123456 number and extract it from that text. * @param {Page} page - current puppeteer tab * @param pattern - regex * @param text - text in which this pattern needs to be searched for * @returns {Promise<void>} */ storeTextFromPattern: async function (page, pattern, text) { const element = await page.$('xpath/' + `//body//*[text()[contains(.,'${text}')]]`); let textValue = await (await page.evaluateHandle((el) => el.textContent, element)).jsonValue(); const regex = new RegExp(pattern); const matches = regex.exec(textValue); if (!matches) { throw new Error(`There isn't a string matching the pattern:${regex}`); } await this.iStoreVariableWithValueToTheJsonFile(matches[0], 'storedString'); }, /** * Store the value of the element (input, button, option, li etc.) * @param {Page} page * @param cssSelector * @param variable * @returns {Promise<void>} */ storeValueOfElement: async function (page, cssSelector, variable) { await page.waitForSelector(cssSelector); const value = await page.$eval(cssSelector, (el) => el.value); if (!value) { throw new Error(`Element with selector ${cssSelector} doesn't have value!`); } await this.iStoreVariableWithValueToTheJsonFile(value, variable); }, /** * Generate mail extension with specific length. * Example test@example.com -> test+zy62a@example.com * @param number * @param emailVariable * @param varName * @returns {Promise<void>} */ generateExtensionAndStoreVar: async function (number, emailVariable, varName) { let email = emailVariable; if (config.has(emailVariable)) { email = config.get(emailVariable); } let splitVar = email.split('@'); if (splitVar.length === 0) { throw new Error(`The provided string: ${email} is not an email!`); } let randomStr = helper.generateRandomString(number); const value = splitVar[0] + '+' + randomStr + '@' + splitVar[1]; await this.iStoreVariableWithValueToTheJsonFile(value, varName); }, /** * Generate date with custom format in local timezone and store it in a variable * @param format - date format * @param variable - variable name * @param days - number of days to add/subtract from the current date * @returns {Promise<void>} */ generateAndSaveDateWithCustomFormat: async function (format, variable, days = 0) { const date = moment().add(days, 'days').format(format); await this.iStoreVariableWithValueToTheJsonFile(date, variable); }, /** * Generate date with custom format and timezone and store it in a variable * THIS METHID IS NOT COMBINED WITH THE generateAndSaveDateWithCustomFormat due to ease of use in Gherkin steps. * @param format - date format * @param variable - variable name * @param days - number of days to add/subtract from the current date * @param offset - UTC offset in minutes (acceptable values are -240, -120, -60, 0, 60, 120, 240, etc.) * @returns {Promise<void>} */ generateAndSaveDateWithCustomFormatAndTz: async function (format, variable, days = 0, offset = 0) { const date = moment().utcOffset(offset).add(days, 'days').format(format); await this.iStoreVariableWithValueToTheJsonFile(date, variable); }, };