codeceptjs
Version:
Modern Era Acceptance Testing Framework for NodeJS
410 lines (351 loc) • 11.2 kB
JavaScript
const fs = require('fs');
const os = require('os');
const path = require('path');
const getFunctionArguments = require('fn-args');
const deepClone = require('lodash.clonedeep');
const { convertColorToRGBA, isColorProperty } = require('./colorUtils');
function isObject(item) {
return item && typeof item === 'object' && !Array.isArray(item);
}
function deepMerge(target, source) {
const merge = require('lodash.merge');
return merge(target, source);
}
module.exports.genTestId = (test) => {
return require('crypto').createHash('md5').update(test.fullTitle()).digest('base64')
.slice(0, -2);
};
module.exports.deepMerge = deepMerge;
module.exports.deepClone = deepClone;
const isGenerator = module.exports.isGenerator = function (fn) {
return fn.constructor.name === 'GeneratorFunction';
};
const isFunction = module.exports.isFunction = function (fn) {
return typeof fn === 'function';
};
const isAsyncFunction = module.exports.isAsyncFunction = function (fn) {
if (!fn) return false;
return fn[Symbol.toStringTag] === 'AsyncFunction';
};
module.exports.fileExists = function (filePath) {
try {
fs.statSync(filePath);
} catch (err) {
if (err.code === 'ENOENT') return false;
}
return true;
};
module.exports.isFile = function (filePath) {
let filestat;
try {
filestat = fs.statSync(filePath);
} catch (err) {
if (err.code === 'ENOENT') return false;
}
if (!filestat) return false;
return filestat.isFile();
};
module.exports.getParamNames = function (fn) {
if (fn.isSinonProxy) return [];
return getFunctionArguments(fn);
};
module.exports.installedLocally = function () {
return path.resolve(`${__dirname}/../`).indexOf(process.cwd()) === 0;
};
module.exports.methodsOfObject = function (obj, className) {
const methods = [];
const standard = [
'constructor',
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'bind',
'apply',
'call',
'isPrototypeOf',
'propertyIsEnumerable',
];
function pushToMethods(prop) {
try {
if (!isFunction(obj[prop]) && !isAsyncFunction(obj[prop])) return;
} catch (err) { // can't access property
return;
}
if (standard.indexOf(prop) >= 0) return;
if (prop.indexOf('_') === 0) return;
methods.push(prop);
}
while (obj.constructor.name !== className) {
Object.getOwnPropertyNames(obj).forEach(pushToMethods);
obj = Object.getPrototypeOf(obj);
if (!obj || !obj.constructor) break;
}
return methods;
};
module.exports.template = function (template, data) {
return template.replace(/{{([^{}]*)}}/g, (a, b) => {
const r = data[b];
if (r === undefined) return '';
return r.toString();
});
};
module.exports.ucfirst = function (str) {
return str.charAt(0).toUpperCase() + str.substr(1);
};
module.exports.lcfirst = function (str) {
return str.charAt(0).toLowerCase() + str.substr(1);
};
module.exports.chunkArray = function (arr, chunk) {
let i;
let j;
const tmp = [];
for (i = 0, j = arr.length; i < j; i += chunk) {
tmp.push(arr.slice(i, i + chunk));
}
return tmp;
};
module.exports.clearString = function (str) {
if (!str) return '';
/* Replace forbidden symbols in string
*/
return str
.replace(/ /g, '_')
.replace(/"/g, "'")
.replace(/\//g, '_')
.replace(/</g, '(')
.replace(/>/g, ')')
.replace(/:/g, '_')
.replace(/\\/g, '_')
.replace(/\|/g, '_')
.replace(/\?/g, '.')
.replace(/\*/g, '^')
.replace(/'/g, '');
};
module.exports.decodeUrl = function (url) {
/* Replace forbidden symbols in string
*/
return decodeURIComponent(decodeURIComponent(decodeURIComponent(url)));
};
module.exports.xpathLocator = {
literal: (string) => {
if (string.indexOf("'") > -1) {
string = string.split("'", -1).map(substr => `'${substr}'`).join(',"\'",');
return `concat(${string})`;
}
return `'${string}'`;
},
combine: locators => locators.join(' | '),
};
module.exports.test = {
grepLines(array, startString, endString) {
let startIndex = 0;
let endIndex;
array.every((elem, index) => {
if (elem === startString) {
startIndex = index;
return true;
}
if (elem === endString) {
endIndex = index;
return false;
}
return true;
});
return array.slice(startIndex + 1, endIndex);
},
submittedData(dataFile) {
return function (key) {
if (!fs.existsSync(dataFile)) {
const waitTill = new Date(new Date().getTime() + 1 * 1000); // wait for one sec for file to be created
while (waitTill > new Date()) {} // eslint-disable-line no-empty
}
if (!fs.existsSync(dataFile)) {
throw new Error('Data file was not created in time');
}
const data = JSON.parse(fs.readFileSync(dataFile, 'utf8'));
if (key) {
return data.form[key];
}
return data;
};
},
};
function toCamelCase(name) {
if (typeof name !== 'string') {
return name;
}
return name.replace(/-(\w)/gi, (word, letter) => {
return letter.toUpperCase();
});
}
function convertFontWeightToNumber(name) {
const fontWeightPatterns = [
{ num: 100, pattern: /^Thin$/i },
{ num: 200, pattern: /^(Extra|Ultra)-?light$/i },
{ num: 300, pattern: /^Light$/i },
{ num: 400, pattern: /^(Normal|Regular|Roman|Book)$/i },
{ num: 500, pattern: /^Medium$/i },
{ num: 600, pattern: /^(Semi|Demi)-?bold$/i },
{ num: 700, pattern: /^Bold$/i },
{ num: 800, pattern: /^(Extra|Ultra)-?bold$/i },
{ num: 900, pattern: /^(Black|Heavy)$/i },
];
if (/^[1-9]00$/.test(name)) {
return Number(name);
}
const matches = fontWeightPatterns.filter(fontWeight => fontWeight.pattern.test(name));
if (matches.length) {
return String(matches[0].num);
}
return name;
}
function isFontWeightProperty(prop) {
return ['fontWeight'].indexOf(prop) > -1;
}
module.exports.convertCssPropertiesToCamelCase = function (props) {
const output = {};
Object.keys(props).forEach((key) => {
const keyCamel = toCamelCase(key);
if (isFontWeightProperty(keyCamel)) {
output[keyCamel] = convertFontWeightToNumber(props[key]);
} else if (isColorProperty(keyCamel)) {
output[keyCamel] = convertColorToRGBA(props[key]);
} else {
output[keyCamel] = props[key];
}
});
return output;
};
module.exports.deleteDir = function (dir_path) {
if (fs.existsSync(dir_path)) {
fs.readdirSync(dir_path).forEach(function (entry) {
const entry_path = path.join(dir_path, entry);
if (fs.lstatSync(entry_path).isDirectory()) {
this.deleteDir(entry_path);
} else {
fs.unlinkSync(entry_path);
}
});
fs.rmdirSync(dir_path);
}
};
module.exports.screenshotOutputFolder = function (fileName) {
const fileSep = path.sep;
if (!fileName.includes(fileSep) || fileName.includes('record_')) {
return path.join(global.output_dir, fileName);
}
return path.join(global.codecept_dir, fileName);
};
module.exports.beautify = function (code) {
const format = require('js-beautify').js;
return format(code, { indent_size: 2, space_in_empty_paren: true });
};
function shouldAppendBaseUrl(url) {
return !/^\w+\:\/\//.test(url);
}
function trimUrl(url) {
const firstChar = url.substr(1);
if (firstChar === '/') {
url = url.slice(1);
}
return url;
}
function joinUrl(baseUrl, url) {
return shouldAppendBaseUrl(url) ? `${baseUrl}/${trimUrl(url)}` : url;
}
module.exports.appendBaseUrl = function (baseUrl = '', oneOrMoreUrls) {
if (typeof baseUrl !== 'string') {
throw new Error(`Invalid value for baseUrl: ${baseUrl}`);
}
if (!(typeof oneOrMoreUrls === 'string' || Array.isArray(oneOrMoreUrls))) {
throw new Error(`Expected type of Urls is 'string' or 'array', Found '${typeof oneOrMoreUrls}'.`);
}
// Remove '/' if it's at the end of baseUrl
const lastChar = baseUrl.substr(-1);
if (lastChar === '/') {
baseUrl = baseUrl.slice(0, -1);
}
if (!Array.isArray(oneOrMoreUrls)) {
return joinUrl(baseUrl, oneOrMoreUrls);
}
return oneOrMoreUrls.map(url => joinUrl(baseUrl, url));
};
/**
* Recursively search key in object and replace it's value.
*
* @param {*} obj source object for replacing
* @param {string} key key to search
* @param {*} value value to set for key
*/
module.exports.replaceValueDeep = function replaceValueDeep(obj, key, value) {
if (!obj) return;
if (obj instanceof Array) {
for (const i in obj) {
replaceValueDeep(obj[i], key, value);
}
}
if (Object.prototype.hasOwnProperty.call(obj, key)) {
obj[key] = value;
}
if (typeof obj === 'object' && obj !== null) {
const children = Object.values(obj);
for (const child of children) {
replaceValueDeep(child, key, value);
}
}
return obj;
};
module.exports.ansiRegExp = function ({ onlyFirst = false } = {}) {
const pattern = [
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))',
].join('|');
return new RegExp(pattern, onlyFirst ? undefined : 'g');
};
module.exports.tryOrDefault = function (fn, defaultValue) {
try {
return fn();
} catch (_) {
return defaultValue;
}
};
function normalizeKeyReplacer(match, prefix, key, suffix, offset, string) {
if (typeof key !== 'string') {
return string;
}
const normalizedKey = key.charAt(0).toUpperCase() + key.substr(1).toLowerCase();
let position = '';
if (typeof prefix === 'string') {
position = prefix;
} else if (typeof suffix === 'string') {
position = suffix;
}
return normalizedKey + position.charAt(0).toUpperCase() + position.substr(1).toLowerCase();
}
module.exports.getNormalizedKeyAttributeValue = function (key) {
// Use operation modifier key based on operating system
key = key.replace(/(Ctrl|Control|Cmd|Command)[ _]?Or[ _]?(Ctrl|Control|Cmd|Command)/i, os.platform() === 'darwin' ? 'Meta' : 'Control');
// Selection of keys (https://www.w3.org/TR/uievents-key/#named-key-attribute-values)
// which can be written in various ways and should be normalized.
// For example 'LEFT ALT', 'ALT_Left', 'alt left' or 'LeftAlt' will be normalized as 'AltLeft'.
key = key.replace(/^\s*(?:(Down|Left|Right|Up)[ _]?)?(Arrow|Alt|Ctrl|Control|Cmd|Command|Meta|Option|OS|Page|Shift|Super)(?:[ _]?(Down|Left|Right|Up|Gr(?:aph)?))?\s*$/i, normalizeKeyReplacer);
// Map alias to corresponding key value
key = key.replace(/^(Add|Divide|Decimal|Multiply|Subtract)$/, 'Numpad$1');
key = key.replace(/^AltGr$/, 'AltGraph');
key = key.replace(/^(Cmd|Command|Os|Super)/, 'Meta');
key = key.replace('Ctrl', 'Control');
key = key.replace('Option', 'Alt');
key = key.replace(/^(NumpadComma|Separator)$/, 'Comma');
return key;
};
const modifierKeys = [
'Alt', 'AltGraph', 'AltLeft', 'AltRight',
'Control', 'ControlLeft', 'ControlRight',
'Meta', 'MetaLeft', 'MetaRight',
'Shift', 'ShiftLeft', 'ShiftRight',
];
module.exports.modifierKeys = modifierKeys;
module.exports.isModifierKey = function (key) {
return modifierKeys.includes(key);
};