From 0936a7cf8d567a6a4cd09a11bc1c776a2d183f44 Mon Sep 17 00:00:00 2001 From: DavertMik Date: Mon, 6 Jan 2025 01:11:35 +0200 Subject: [PATCH] added better pw logs --- lib/container.js | 338 +++++++++++++++++----------------- lib/helper/Playwright.js | 5 +- lib/utils.js | 380 ++++++++++++++++++++------------------- 3 files changed, 366 insertions(+), 357 deletions(-) diff --git a/lib/container.js b/lib/container.js index 186f12b53..6fe565369 100644 --- a/lib/container.js +++ b/lib/container.js @@ -1,16 +1,16 @@ -const glob = require('glob'); -const path = require('path'); -const { MetaStep } = require('./step'); -const { methodsOfObject, fileExists, isFunction, isAsyncFunction, installedLocally } = require('./utils'); -const Translation = require('./translation'); -const MochaFactory = require('./mocha/factory'); -const recorder = require('./recorder'); -const event = require('./event'); -const WorkerStorage = require('./workerStorage'); -const store = require('./store'); -const ai = require('./ai'); - -let asyncHelperPromise; +const glob = require('glob') +const path = require('path') +const { MetaStep } = require('./step') +const { methodsOfObject, fileExists, isFunction, isAsyncFunction, installedLocally } = require('./utils') +const Translation = require('./translation') +const MochaFactory = require('./mocha/factory') +const recorder = require('./recorder') +const event = require('./event') +const WorkerStorage = require('./workerStorage') +const store = require('./store') +const ai = require('./ai') + +let asyncHelperPromise let container = { helpers: {}, @@ -24,7 +24,7 @@ let container = { */ mocha: {}, translation: {}, -}; +} /** * Dependency Injection Container @@ -38,30 +38,30 @@ class Container { * @param {*} opts */ static create(config, opts) { - asyncHelperPromise = Promise.resolve(); + asyncHelperPromise = Promise.resolve() // dynamically create mocha instance - const mochaConfig = config.mocha || {}; - if (config.grep && !opts.grep) mochaConfig.grep = config.grep; - this.createMocha = () => (container.mocha = MochaFactory.create(mochaConfig, opts || {})); - this.createMocha(); + const mochaConfig = config.mocha || {} + if (config.grep && !opts.grep) mochaConfig.grep = config.grep + this.createMocha = () => (container.mocha = MochaFactory.create(mochaConfig, opts || {})) + this.createMocha() // create support objects - container.support = {}; - container.helpers = createHelpers(config.helpers || {}); - container.translation = loadTranslation(config.translation || null, config.vocabularies || []); - container.proxySupport = createSupportObjects(config.include || {}); - container.plugins = createPlugins(config.plugins || {}, opts); + container.support = {} + container.helpers = createHelpers(config.helpers || {}) + container.translation = loadTranslation(config.translation || null, config.vocabularies || []) + container.proxySupport = createSupportObjects(config.include || {}) + container.plugins = createPlugins(config.plugins || {}, opts) - createActor(config.include?.I); + createActor(config.include?.I) - if (opts && opts.ai) ai.enable(config.ai); // enable AI Assistant - if (config.gherkin) loadGherkinSteps(config.gherkin.steps || []); - if (opts && typeof opts.timeouts === 'boolean') store.timeouts = opts.timeouts; + if (opts && opts.ai) ai.enable(config.ai) // enable AI Assistant + if (config.gherkin) loadGherkinSteps(config.gherkin.steps || []) + if (opts && typeof opts.timeouts === 'boolean') store.timeouts = opts.timeouts } static actor() { - return container.support.I; + return container.support.I } /** @@ -73,9 +73,9 @@ class Container { */ static plugins(name) { if (!name) { - return container.plugins; + return container.plugins } - return container.plugins[name]; + return container.plugins[name] } /** @@ -87,9 +87,9 @@ class Container { */ static support(name) { if (!name) { - return container.proxySupport; + return container.proxySupport } - return container.support[name] || container.proxySupport[name]; + return container.support[name] || container.proxySupport[name] } /** @@ -101,9 +101,9 @@ class Container { */ static helpers(name) { if (!name) { - return container.helpers; + return container.helpers } - return container.helpers[name]; + return container.helpers[name] } /** @@ -112,7 +112,7 @@ class Container { * @api */ static translation() { - return container.translation; + return container.translation } /** @@ -122,7 +122,7 @@ class Container { * @returns { * } */ static mocha() { - return container.mocha; + return container.mocha } /** @@ -132,8 +132,8 @@ class Container { * @param {Object} newContainer */ static append(newContainer) { - const deepMerge = require('./utils').deepMerge; - container = deepMerge(container, newContainer); + const deepMerge = require('./utils').deepMerge + container = deepMerge(container, newContainer) } /** @@ -144,19 +144,23 @@ class Container { * @param {Object} newPlugins */ static clear(newHelpers, newSupport, newPlugins) { - container.helpers = newHelpers || {}; - container.translation = loadTranslation(); - container.proxySupport = createSupportObjects(newSupport || {}); - container.plugins = newPlugins || {}; - asyncHelperPromise = Promise.resolve(); - store.actor = null; + container.helpers = newHelpers || {} + container.translation = loadTranslation() + container.proxySupport = createSupportObjects(newSupport || {}) + container.plugins = newPlugins || {} + asyncHelperPromise = Promise.resolve() + store.actor = null } + /** + * @param {Function} fn + * @returns {Promise} + */ static async started(fn = null) { if (fn) { - asyncHelperPromise = asyncHelperPromise.then(fn); + asyncHelperPromise = asyncHelperPromise.then(fn) } - return asyncHelperPromise; + return asyncHelperPromise } /** @@ -166,119 +170,119 @@ class Container { * @param {Object} options - set {local: true} to not share among workers */ static share(data, options = {}) { - Container.append({ support: data }); + Container.append({ support: data }) if (!options.local) { - WorkerStorage.share(data); + WorkerStorage.share(data) } } static createMocha(config = {}, opts = {}) { - const mochaConfig = config?.mocha || {}; + const mochaConfig = config?.mocha || {} if (config?.grep && !opts?.grep) { - mochaConfig.grep = config.grep; + mochaConfig.grep = config.grep } - container.mocha = MochaFactory.create(mochaConfig, opts || {}); + container.mocha = MochaFactory.create(mochaConfig, opts || {}) } } -module.exports = Container; +module.exports = Container function createHelpers(config) { - const helpers = {}; + const helpers = {} for (let helperName in config) { try { - let HelperClass; + let HelperClass // ESM import if (helperName?.constructor === Function && helperName.prototype) { - HelperClass = helperName; - helperName = HelperClass.constructor.name; + HelperClass = helperName + helperName = HelperClass.constructor.name } // classical require if (!HelperClass) { - HelperClass = requireHelperFromModule(helperName, config); + HelperClass = requireHelperFromModule(helperName, config) } // handle async CJS modules that use dynamic import if (isAsyncFunction(HelperClass)) { - helpers[helperName] = {}; + helpers[helperName] = {} asyncHelperPromise = asyncHelperPromise .then(() => HelperClass()) .then(ResolvedHelperClass => { // Check if ResolvedHelperClass is a constructor function if (typeof ResolvedHelperClass?.constructor !== 'function') { - throw new Error(`Helper class from module '${helperName}' is not a class. Use CJS async module syntax.`); + throw new Error(`Helper class from module '${helperName}' is not a class. Use CJS async module syntax.`) } - helpers[helperName] = new ResolvedHelperClass(config[helperName]); - }); + helpers[helperName] = new ResolvedHelperClass(config[helperName]) + }) - continue; + continue } - checkHelperRequirements(HelperClass); + checkHelperRequirements(HelperClass) - helpers[helperName] = new HelperClass(config[helperName]); + helpers[helperName] = new HelperClass(config[helperName]) } catch (err) { - throw new Error(`Could not load helper ${helperName} (${err.message})`); + throw new Error(`Could not load helper ${helperName} (${err.message})`) } } for (const name in helpers) { - if (helpers[name]._init) helpers[name]._init(); + if (helpers[name]._init) helpers[name]._init() } - return helpers; + return helpers } function checkHelperRequirements(HelperClass) { if (HelperClass._checkRequirements) { - const requirements = HelperClass._checkRequirements(); + const requirements = HelperClass._checkRequirements() if (requirements) { - let install; + let install if (installedLocally()) { - install = `npm install --save-dev ${requirements.join(' ')}`; + install = `npm install --save-dev ${requirements.join(' ')}` } else { - console.log('WARNING: CodeceptJS is not installed locally. It is recommended to switch to local installation'); - install = `[sudo] npm install -g ${requirements.join(' ')}`; + console.log('WARNING: CodeceptJS is not installed locally. It is recommended to switch to local installation') + install = `[sudo] npm install -g ${requirements.join(' ')}` } - throw new Error(`Required modules are not installed.\n\nRUN: ${install}`); + throw new Error(`Required modules are not installed.\n\nRUN: ${install}`) } } } function requireHelperFromModule(helperName, config, HelperClass) { - const moduleName = getHelperModuleName(helperName, config); + const moduleName = getHelperModuleName(helperName, config) if (moduleName.startsWith('./helper/')) { - HelperClass = require(moduleName); + HelperClass = require(moduleName) } else { // check if the new syntax export default HelperName is used and loads the Helper, otherwise loads the module that used old syntax export = HelperName. try { - const mod = require(moduleName); + const mod = require(moduleName) if (!mod && !mod.default) { - throw new Error(`Helper module '${moduleName}' was not found. Make sure you have installed the package correctly.`); + throw new Error(`Helper module '${moduleName}' was not found. Make sure you have installed the package correctly.`) } - HelperClass = mod.default || mod; + HelperClass = mod.default || mod } catch (err) { if (err.code === 'MODULE_NOT_FOUND') { - throw new Error(`Helper module '${moduleName}' was not found. Make sure you have installed the package correctly.`); + throw new Error(`Helper module '${moduleName}' was not found. Make sure you have installed the package correctly.`) } - throw err; + throw err } } - return HelperClass; + return HelperClass } function createSupportObjects(config) { const asyncWrapper = function (f) { return function () { return f.apply(this, arguments).catch(e => { - recorder.saveFirstAsyncError(e); - throw e; - }); - }; - }; + recorder.saveFirstAsyncError(e) + throw e + }) + } + } function lazyLoad(name) { return new Proxy( @@ -286,199 +290,199 @@ function createSupportObjects(config) { { get(target, prop) { // behavr like array or - if (prop === 'length') return Object.keys(config).length; + if (prop === 'length') return Object.keys(config).length if (prop === Symbol.iterator) { return function* () { for (let i = 0; i < Object.keys(config).length; i++) { - yield target[i]; + yield target[i] } - }; + } } // load actual name from vocabulary if (container.translation.name) { - name = container.translation.name; + name = container.translation.name } if (name === 'I') { - const actor = createActor(config.I); - methodsOfObject(actor); - return actor[prop]; + const actor = createActor(config.I) + methodsOfObject(actor) + return actor[prop] } if (!container.support[name] && typeof config[name] === 'object') { - container.support[name] = config[name]; + container.support[name] = config[name] } if (!container.support[name]) { // Load object on first access - const supportObject = loadSupportObject(config[name]); - container.support[name] = supportObject; + const supportObject = loadSupportObject(config[name]) + container.support[name] = supportObject try { if (container.support[name]._init) { - container.support[name]._init(); + container.support[name]._init() } } catch (err) { - throw new Error(`Initialization failed for ${name}: ${container.support[name]}\n${err.message}\n${err.stack}`); + throw new Error(`Initialization failed for ${name}: ${container.support[name]}\n${err.message}\n${err.stack}`) } } - const currentObject = container.support[name]; - let currentValue = currentObject[prop]; + const currentObject = container.support[name] + let currentValue = currentObject[prop] if (isFunction(currentValue) || isAsyncFunction(currentValue)) { - const ms = new MetaStep(name, prop); - ms.setContext(currentObject); - if (isAsyncFunction(currentValue)) currentValue = asyncWrapper(currentValue); - return ms.run.bind(ms, currentValue); + const ms = new MetaStep(name, prop) + ms.setContext(currentObject) + if (isAsyncFunction(currentValue)) currentValue = asyncWrapper(currentValue) + return ms.run.bind(ms, currentValue) } - return currentValue; + return currentValue }, has(target, prop) { - container.support[name] = container.support[name] || loadSupportObject(config[name]); - return prop in container.support[name]; + container.support[name] = container.support[name] || loadSupportObject(config[name]) + return prop in container.support[name] }, getOwnPropertyDescriptor(target, prop) { - container.support[name] = container.support[name] || loadSupportObject(config[name]); + container.support[name] = container.support[name] || loadSupportObject(config[name]) return { enumerable: true, configurable: true, value: this.get(target, prop), - }; + } }, ownKeys() { - container.support[name] = container.support[name] || loadSupportObject(config[name]); - return Reflect.ownKeys(container.support[name]); + container.support[name] = container.support[name] || loadSupportObject(config[name]) + return Reflect.ownKeys(container.support[name]) }, }, - ); + ) } - const keys = Reflect.ownKeys(config); + const keys = Reflect.ownKeys(config) return new Proxy( {}, { has(target, key) { - return keys.includes(key); + return keys.includes(key) }, ownKeys() { - return keys; + return keys }, getOwnPropertyDescriptor(target, prop) { return { enumerable: true, configurable: true, value: this.get(target, prop), - }; + } }, get(target, key) { - return lazyLoad(key); + return lazyLoad(key) }, }, - ); + ) } function createActor(actorPath) { - if (container.support.I) return container.support.I; + if (container.support.I) return container.support.I if (actorPath) { - container.support.I = loadSupportObject(actorPath); + container.support.I = loadSupportObject(actorPath) } else { - const actor = require('./actor'); - container.support.I = actor(); + const actor = require('./actor') + container.support.I = actor() } - return container.support.I; + return container.support.I } function createPlugins(config, options = {}) { - const plugins = {}; + const plugins = {} - const enabledPluginsByOptions = (options.plugins || '').split(','); + const enabledPluginsByOptions = (options.plugins || '').split(',') for (const pluginName in config) { - if (!config[pluginName]) config[pluginName] = {}; + if (!config[pluginName]) config[pluginName] = {} if (!config[pluginName].enabled && enabledPluginsByOptions.indexOf(pluginName) < 0) { - continue; // plugin is disabled + continue // plugin is disabled } - let module; + let module try { if (config[pluginName].require) { - module = config[pluginName].require; + module = config[pluginName].require if (module.startsWith('.')) { // local - module = path.resolve(global.codecept_dir, module); // custom plugin + module = path.resolve(global.codecept_dir, module) // custom plugin } } else { - module = `./plugin/${pluginName}`; + module = `./plugin/${pluginName}` } - plugins[pluginName] = require(module)(config[pluginName]); + plugins[pluginName] = require(module)(config[pluginName]) } catch (err) { - throw new Error(`Could not load plugin ${pluginName} from module '${module}':\n${err.message}\n${err.stack}`); + throw new Error(`Could not load plugin ${pluginName} from module '${module}':\n${err.message}\n${err.stack}`) } } - return plugins; + return plugins } function loadGherkinSteps(paths) { - global.Before = fn => event.dispatcher.on(event.test.started, fn); - global.After = fn => event.dispatcher.on(event.test.finished, fn); - global.Fail = fn => event.dispatcher.on(event.test.failed, fn); + global.Before = fn => event.dispatcher.on(event.test.started, fn) + global.After = fn => event.dispatcher.on(event.test.finished, fn) + global.Fail = fn => event.dispatcher.on(event.test.failed, fn) // If gherkin.steps is string, then this will iterate through that folder and send all step def js files to loadSupportObject // If gherkin.steps is Array, it will go the old way // This is done so that we need not enter all Step Definition files under config.gherkin.steps if (Array.isArray(paths)) { for (const path of paths) { - loadSupportObject(path, `Step Definition from ${path}`); + loadSupportObject(path, `Step Definition from ${path}`) } } else { - const folderPath = paths.startsWith('.') ? path.join(global.codecept_dir, paths) : ''; + const folderPath = paths.startsWith('.') ? path.join(global.codecept_dir, paths) : '' if (folderPath !== '') { glob.sync(folderPath).forEach(file => { - loadSupportObject(file, `Step Definition from ${file}`); - }); + loadSupportObject(file, `Step Definition from ${file}`) + }) } } - delete global.Before; - delete global.After; - delete global.Fail; + delete global.Before + delete global.After + delete global.Fail } function loadSupportObject(modulePath, supportObjectName) { if (!modulePath) { - throw new Error(`Support object "${supportObjectName}" is not defined`); + throw new Error(`Support object "${supportObjectName}" is not defined`) } if (modulePath.charAt(0) === '.') { - modulePath = path.join(global.codecept_dir, modulePath); + modulePath = path.join(global.codecept_dir, modulePath) } try { - const obj = require(modulePath); + const obj = require(modulePath) // Handle different types of imports if (typeof obj === 'function') { // If it's a class (constructor function) if (obj.prototype && obj.prototype.constructor === obj) { - const ClassName = obj; - return new ClassName(); + const ClassName = obj + return new ClassName() } // If it's a regular function - return obj(); + return obj() } if (obj && Array.isArray(obj)) { - return obj; + return obj } // If it's a plain object if (obj && typeof obj === 'object') { - return obj; + return obj } - throw new Error(`Support object "${supportObjectName}" should be an object, class, or function, but got ${typeof obj}`); + throw new Error(`Support object "${supportObjectName}" should be an object, class, or function, but got ${typeof obj}`) } catch (err) { - throw new Error(`Could not include object ${supportObjectName} from module '${modulePath}'\n${err.message}\n${err.stack}`); + throw new Error(`Could not include object ${supportObjectName} from module '${modulePath}'\n${err.message}\n${err.stack}`) } } @@ -488,41 +492,41 @@ function loadSupportObject(modulePath, supportObjectName) { function loadTranslation(locale, vocabularies) { if (!locale) { - return Translation.createEmpty(); + return Translation.createEmpty() } - let translation; + let translation // check if it is a known translation if (Translation.langs[locale]) { - translation = new Translation(Translation.langs[locale]); + translation = new Translation(Translation.langs[locale]) } else if (fileExists(path.join(global.codecept_dir, locale))) { // get from a provided file instead - translation = Translation.createDefault(); - translation.loadVocabulary(locale); + translation = Translation.createDefault() + translation.loadVocabulary(locale) } else { - translation = Translation.createDefault(); + translation = Translation.createDefault() } - vocabularies.forEach(v => translation.loadVocabulary(v)); + vocabularies.forEach(v => translation.loadVocabulary(v)) - return translation; + return translation } function getHelperModuleName(helperName, config) { // classical require if (config[helperName].require) { if (config[helperName].require.startsWith('.')) { - return path.resolve(global.codecept_dir, config[helperName].require); // custom helper + return path.resolve(global.codecept_dir, config[helperName].require) // custom helper } - return config[helperName].require; // plugin helper + return config[helperName].require // plugin helper } // built-in helpers if (helperName.startsWith('@codeceptjs/')) { - return helperName; + return helperName } // built-in helpers - return `./helper/${helperName}`; + return `./helper/${helperName}` } diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js index 6eeb8098b..94ab68958 100644 --- a/lib/helper/Playwright.js +++ b/lib/helper/Playwright.js @@ -25,6 +25,7 @@ const { clearString, requireWithFallback, normalizeSpacesInString, + relativeDir, } = require('../utils') const { isColorProperty, convertColorToRGBA } = require('../colorUtils') const ElementNotFound = require('./errors/ElementNotFound') @@ -2290,7 +2291,7 @@ class Playwright extends Helper { const fullPageOption = fullPage || this.options.fullPageScreenshots let outputFile = screenshotOutputFolder(fileName) - this.debug(`Screenshot is saving to ${outputFile}`) + this.debugSection('Screenshot', relativeDir(outputFile)) await this.page.screenshot({ path: outputFile, @@ -2303,7 +2304,7 @@ class Playwright extends Helper { const activeSessionPage = this.sessionPages[sessionName] outputFile = screenshotOutputFolder(`${sessionName}_${fileName}`) - this.debug(`${sessionName} - Screenshot is saving to ${outputFile}`) + this.debugSection('Screenshot', `${sessionName} - ${relativeDir(outputFile)}`) if (activeSessionPage) { await activeSessionPage.screenshot({ diff --git a/lib/utils.js b/lib/utils.js index d52491f1d..ae1a44e3d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,93 +1,93 @@ -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'); +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 deepMerge(target, source) { - const merge = require('lodash.merge'); - return merge(target, source); + const merge = require('lodash.merge') + return merge(target, source) } module.exports.genTestId = test => { - return require('crypto').createHash('sha256').update(test.fullTitle()).digest('base64').slice(0, -2); -}; + return require('crypto').createHash('sha256').update(test.fullTitle()).digest('base64').slice(0, -2) +} -module.exports.deepMerge = deepMerge; +module.exports.deepMerge = deepMerge -module.exports.deepClone = deepClone; +module.exports.deepClone = deepClone module.exports.isGenerator = function (fn) { - return fn.constructor.name === 'GeneratorFunction'; -}; + return fn.constructor.name === 'GeneratorFunction' +} const isFunction = (module.exports.isFunction = function (fn) { - return typeof fn === 'function'; -}); + return typeof fn === 'function' +}) const isAsyncFunction = (module.exports.isAsyncFunction = function (fn) { - if (!fn) return false; - return fn[Symbol.toStringTag] === 'AsyncFunction'; -}); + if (!fn) return false + return fn[Symbol.toStringTag] === 'AsyncFunction' +}) module.exports.fileExists = function (filePath) { - return fs.existsSync(filePath); -}; + return fs.existsSync(filePath) +} module.exports.isFile = function (filePath) { - let filestat; + let filestat try { - filestat = fs.statSync(filePath); + filestat = fs.statSync(filePath) } catch (err) { - if (err.code === 'ENOENT') return false; + if (err.code === 'ENOENT') return false } - if (!filestat) return false; - return filestat.isFile(); -}; + if (!filestat) return false + return filestat.isFile() +} module.exports.getParamNames = function (fn) { - if (fn.isSinonProxy) return []; - return getFunctionArguments(fn); -}; + if (fn.isSinonProxy) return [] + return getFunctionArguments(fn) +} module.exports.installedLocally = function () { - return path.resolve(`${__dirname}/../`).indexOf(process.cwd()) === 0; -}; + return path.resolve(`${__dirname}/../`).indexOf(process.cwd()) === 0 +} module.exports.methodsOfObject = function (obj, className) { - const methods = []; + const methods = [] - const standard = ['constructor', 'toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'bind', 'apply', 'call', 'isPrototypeOf', 'propertyIsEnumerable']; + const standard = ['constructor', 'toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'bind', 'apply', 'call', 'isPrototypeOf', 'propertyIsEnumerable'] function pushToMethods(prop) { try { - if (!isFunction(obj[prop]) && !isAsyncFunction(obj[prop])) return; + if (!isFunction(obj[prop]) && !isAsyncFunction(obj[prop])) return } catch (err) { // can't access property - return; + return } - if (standard.indexOf(prop) >= 0) return; - if (prop.indexOf('_') === 0) return; - methods.push(prop); + 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); + Object.getOwnPropertyNames(obj).forEach(pushToMethods) + obj = Object.getPrototypeOf(obj) - if (!obj || !obj.constructor) break; + if (!obj || !obj.constructor) break } - return methods; -}; + 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(); - }); -}; + const r = data[b] + if (r === undefined) return '' + return r.toString() + }) +} /** * Make first char uppercase. @@ -95,8 +95,8 @@ module.exports.template = function (template, data) { * @returns {string} */ module.exports.ucfirst = function (str) { - return str.charAt(0).toUpperCase() + str.substr(1); -}; + return str.charAt(0).toUpperCase() + str.substr(1) +} /** * Make first char lowercase. @@ -104,25 +104,25 @@ module.exports.ucfirst = function (str) { * @returns {string} */ module.exports.lcfirst = function (str) { - return str.charAt(0).toLowerCase() + str.substr(1); -}; + return str.charAt(0).toLowerCase() + str.substr(1) +} module.exports.chunkArray = function (arr, chunk) { - let i; - let j; - const tmp = []; + let i + let j + const tmp = [] for (i = 0, j = arr.length; i < j; i += chunk) { - tmp.push(arr.slice(i, i + chunk)); + tmp.push(arr.slice(i, i + chunk)) } - return tmp; -}; + return tmp +} module.exports.clearString = function (str) { - if (!str) return ''; + if (!str) return '' /* Replace forbidden symbols in string */ if (str.endsWith('.')) { - str = str.slice(0, -1); + str = str.slice(0, -1) } return str .replace(/ /g, '_') @@ -135,14 +135,14 @@ module.exports.clearString = function (str) { .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))); -}; + return decodeURIComponent(decodeURIComponent(decodeURIComponent(url))) +} module.exports.xpathLocator = { /** @@ -154,10 +154,10 @@ module.exports.xpathLocator = { string = string .split("'", -1) .map(substr => `'${substr}'`) - .join(',"\'",'); - return `concat(${string})`; + .join(',"\'",') + return `concat(${string})` } - return `'${string}'`; + return `'${string}'` }, /** @@ -166,53 +166,53 @@ module.exports.xpathLocator = { * @returns {string} */ combine: locators => locators.join(' | '), -}; +} module.exports.test = { grepLines(array, startString, endString) { - let startIndex = 0; - let endIndex; + let startIndex = 0 + let endIndex array.every((elem, index) => { if (elem === startString) { - startIndex = index; - return true; + startIndex = index + return true } if (elem === endString) { - endIndex = index; - return false; + endIndex = index + return false } - return true; - }); - return array.slice(startIndex + 1, endIndex); + 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 + 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'); + throw new Error('Data file was not created in time') } - const data = JSON.parse(fs.readFileSync(dataFile, 'utf8')); + const data = JSON.parse(fs.readFileSync(dataFile, 'utf8')) if (key) { - return data.form[key]; + return data.form[key] } - return data; - }; + return data + } }, -}; +} function toCamelCase(name) { if (typeof name !== 'string') { - return name; + return name } return name.replace(/-(\w)/gi, (_word, letter) => { - return letter.toUpperCase(); - }); + return letter.toUpperCase() + }) } -module.exports.toCamelCase = toCamelCase; +module.exports.toCamelCase = toCamelCase function convertFontWeightToNumber(name) { const fontWeightPatterns = [ @@ -225,106 +225,110 @@ function convertFontWeightToNumber(name) { { 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); + return Number(name) } - const matches = fontWeightPatterns.filter(fontWeight => fontWeight.pattern.test(name)); + const matches = fontWeightPatterns.filter(fontWeight => fontWeight.pattern.test(name)) if (matches.length) { - return String(matches[0].num); + return String(matches[0].num) } - return name; + return name } function isFontWeightProperty(prop) { - return prop === 'fontWeight'; + return prop === 'fontWeight' } module.exports.convertCssPropertiesToCamelCase = function (props) { - const output = {}; + const output = {} Object.keys(props).forEach(key => { - const keyCamel = toCamelCase(key); + const keyCamel = toCamelCase(key) if (isFontWeightProperty(keyCamel)) { - output[keyCamel] = convertFontWeightToNumber(props[key]); + output[keyCamel] = convertFontWeightToNumber(props[key]) } else if (isColorProperty(keyCamel)) { - output[keyCamel] = convertColorToRGBA(props[key]); + output[keyCamel] = convertColorToRGBA(props[key]) } else { - output[keyCamel] = props[key]; + output[keyCamel] = props[key] } - }); - return output; -}; + }) + 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); + const entry_path = path.join(dir_path, entry) if (fs.lstatSync(entry_path).isDirectory()) { - this.deleteDir(entry_path); + this.deleteDir(entry_path) } else { - fs.unlinkSync(entry_path); + fs.unlinkSync(entry_path) } - }); - fs.rmdirSync(dir_path); + }) + fs.rmdirSync(dir_path) } -}; +} /** * Returns absolute filename to save screenshot. * @param fileName {string} - filename. */ module.exports.screenshotOutputFolder = function (fileName) { - const fileSep = path.sep; + const fileSep = path.sep if (!fileName.includes(fileSep) || fileName.includes('record_')) { - return path.resolve(global.output_dir, fileName); + return path.resolve(global.output_dir, fileName) } - return path.resolve(global.codecept_dir, fileName); -}; + return path.resolve(global.codecept_dir, fileName) +} + +module.exports.relativeDir = function (fileName) { + return fileName.replace(global.codecept_dir, '').replace(/^\//, '') +} module.exports.beautify = function (code) { - const format = require('js-beautify').js; - return format(code, { indent_size: 2, space_in_empty_paren: true }); -}; + const format = require('js-beautify').js + return format(code, { indent_size: 2, space_in_empty_paren: true }) +} function shouldAppendBaseUrl(url) { - return !/^\w+\:\/\//.test(url); + return !/^\w+\:\/\//.test(url) } function trimUrl(url) { - const firstChar = url.substr(1); + const firstChar = url.substr(1) if (firstChar === '/') { - url = url.slice(1); + url = url.slice(1) } - return url; + return url } function joinUrl(baseUrl, url) { - return shouldAppendBaseUrl(url) ? `${baseUrl}/${trimUrl(url)}` : 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}`); + 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}'.`); + 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); + const lastChar = baseUrl.substr(-1) if (lastChar === '/') { - baseUrl = baseUrl.slice(0, -1); + baseUrl = baseUrl.slice(0, -1) } if (!Array.isArray(oneOrMoreUrls)) { - return joinUrl(baseUrl, oneOrMoreUrls); + return joinUrl(baseUrl, oneOrMoreUrls) } - return oneOrMoreUrls.map(url => joinUrl(baseUrl, url)); -}; + return oneOrMoreUrls.map(url => joinUrl(baseUrl, url)) +} /** * Recursively search key in object and replace it's value. @@ -334,53 +338,53 @@ module.exports.appendBaseUrl = function (baseUrl = '', oneOrMoreUrls) { * @param {*} value value to set for key */ module.exports.replaceValueDeep = function replaceValueDeep(obj, key, value) { - if (!obj) return; + if (!obj) return if (obj instanceof Array) { for (const i in obj) { - replaceValueDeep(obj[i], key, value); + replaceValueDeep(obj[i], key, value) } } if (Object.prototype.hasOwnProperty.call(obj, key)) { - obj[key] = value; + obj[key] = value } if (typeof obj === 'object' && obj !== null) { - const children = Object.values(obj); + const children = Object.values(obj) for (const child of children) { - replaceValueDeep(child, key, value); + replaceValueDeep(child, key, value) } } - return obj; -}; + 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('|'); + 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'); -}; + return new RegExp(pattern, onlyFirst ? undefined : 'g') +} module.exports.tryOrDefault = function (fn, defaultValue) { try { - return fn(); + return fn() } catch (_) { - return defaultValue; + return defaultValue } -}; +} function normalizeKeyReplacer(match, prefix, key, suffix, offset, string) { if (typeof key !== 'string') { - return string; + return string } - const normalizedKey = key.charAt(0).toUpperCase() + key.substr(1).toLowerCase(); - let position = ''; + const normalizedKey = key.charAt(0).toUpperCase() + key.substr(1).toLowerCase() + let position = '' if (typeof prefix === 'string') { - position = prefix; + position = prefix } else if (typeof suffix === 'string') { - position = suffix; + position = suffix } - return normalizedKey + position.charAt(0).toUpperCase() + position.substr(1).toLowerCase(); + return normalizedKey + position.charAt(0).toUpperCase() + position.substr(1).toLowerCase() } /** @@ -390,77 +394,77 @@ function normalizeKeyReplacer(match, prefix, key, suffix, offset, string) { */ 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'); + 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); + 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; + 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); -}; + return modifierKeys.includes(key) +} module.exports.requireWithFallback = function (...packages) { const exists = function (pkg) { try { - require.resolve(pkg); + require.resolve(pkg) } catch (e) { - return false; + return false } - return true; - }; + return true + } for (const pkg of packages) { if (exists(pkg)) { - return require(pkg); + return require(pkg) } } - throw new Error(`Cannot find modules ${packages.join(',')}`); -}; + throw new Error(`Cannot find modules ${packages.join(',')}`) +} module.exports.isNotSet = function (obj) { - if (obj === null) return true; - if (obj === undefined) return true; - return false; -}; + if (obj === null) return true + if (obj === undefined) return true + return false +} module.exports.emptyFolder = async directoryPath => { - require('child_process').execSync(`rm -rf ${directoryPath}/*`); -}; + require('child_process').execSync(`rm -rf ${directoryPath}/*`) +} module.exports.printObjectProperties = obj => { if (typeof obj !== 'object' || obj === null) { - return obj; + return obj } - let result = ''; + let result = '' for (const [key, value] of Object.entries(obj)) { - result += `${key}: "${value}"; `; + result += `${key}: "${value}"; ` } - return `{${result}}`; -}; + return `{${result}}` +} module.exports.normalizeSpacesInString = string => { - return string.replace(/\s+/g, ' '); -}; + return string.replace(/\s+/g, ' ') +} module.exports.humanizeFunction = function (fn) { - const fnStr = fn.toString().trim(); + const fnStr = fn.toString().trim() // Remove arrow function syntax, async, and parentheses let simplified = fnStr .replace(/^async\s*/, '') @@ -472,11 +476,11 @@ module.exports.humanizeFunction = function (fn) { .replace(/return\s+/, '') // Remove trailing semicolon .replace(/;$/, '') - .trim(); + .trim() if (simplified.length > 100) { - simplified = simplified.slice(0, 97) + '...'; + simplified = simplified.slice(0, 97) + '...' } - return simplified; -}; + return simplified +}