UNPKG

@revoloo/cypress6

Version:

Cypress.io end to end testing tool

399 lines (313 loc) 9.09 kB
const _ = require('lodash') const capitalize = require('underscore.string/capitalize') const methods = require('methods') const moment = require('moment') const $ = require('jquery') const $jquery = require('../dom/jquery') const $Location = require('./location') const tagOpen = /\[([a-z\s='"-]+)\]/g const tagClosed = /\[\/([a-z]+)\]/g const quotesRe = /('|")/g const defaultOptions = { delay: 10, force: false, timeout: null, interval: null, multiple: false, waitForAnimations: true, animationDistanceThreshold: 5, scrollBehavior: 'top', } const USER_FRIENDLY_TYPE_DETECTORS = _.map([ [_.isUndefined, 'undefined'], [_.isNull, 'null'], [_.isBoolean, 'boolean'], [_.isNumber, 'number'], [_.isString, 'string'], [_.isRegExp, 'regexp'], [_.isSymbol, 'symbol'], [_.isElement, 'element'], [_.isError, 'error'], [_.isSet, 'set'], [_.isWeakSet, 'set'], [_.isMap, 'map'], [_.isWeakMap, 'map'], [_.isFunction, 'function'], [_.isArrayLikeObject, 'array'], [_.isBuffer, 'buffer'], [_.isDate, 'date'], [_.isObject, 'object'], [_.stubTrue, 'unknown'], ], ([fn, type]) => { return [fn, _.constant(type)] }) module.exports = { warning (msg) { // eslint-disable-next-line no-console return console.warn(`Cypress Warning: ${msg}`) }, log (...msgs) { // eslint-disable-next-line no-console return console.log(...msgs) }, monkeypatchBefore (origFn, fn) { return function () { const newArgs = fn.apply(this, arguments) if (newArgs !== undefined) { return origFn.apply(this, newArgs) } return origFn.apply(this, arguments) } }, unwrapFirst (val) { // this method returns the first item in an array // and if its still a jquery object, then we return // the first() jquery element const item = [].concat(val)[0] if ($jquery.isJquery(item)) { return item.first() } return item }, switchCase (value, casesObj, defaultKey = 'default') { if (_.has(casesObj, value)) { return _.result(casesObj, value) } if (_.has(casesObj, defaultKey)) { return _.result(casesObj, defaultKey) } const keys = _.keys(casesObj) throw new Error(`The switch/case value: '${value}' did not match any cases: ${keys.join(', ')}.`) }, reduceProps (obj, props = []) { if (!obj) { return null } return _.reduce(props, (memo, prop) => { if (_.has(obj, prop) || obj[prop] !== undefined) { memo[prop] = _.result(obj, prop) } return memo }, {}) }, normalizeObjWithLength (obj) { // lodash shits the bed if our object has a 'length' // property so we have to normalize that if (_.has(obj, 'length')) { obj.Length = obj.length delete obj.length } return obj }, // return a new object if the obj // contains the properties of filter // and the values are different filterOutOptions (obj, filter = {}) { _.defaults(filter, defaultOptions) this.normalizeObjWithLength(filter) const whereFilterHasSameKeyButDifferentValue = (value, key) => { const upperKey = capitalize(key) return (_.has(filter, key) || _.has(filter, upperKey)) && filter[key] !== value } obj = _.pickBy(obj, whereFilterHasSameKeyButDifferentValue) if (_.isEmpty(obj)) { return undefined } return obj }, stringifyActualObj (obj) { obj = this.normalizeObjWithLength(obj) const str = _.reduce(obj, (memo, value, key) => { memo.push(`${`${key}`.toLowerCase()}: ${this.stringifyActual(value)}`) return memo }, []) return `{${str.join(', ')}}` }, stringifyActual (value) { const $dom = require('../dom') if ($dom.isDom(value)) { return $dom.stringify(value, 'short') } if (_.isFunction(value)) { return 'function(){}' } if (_.isArray(value)) { const len = value.length if (len > 3) { return `Array[${len}]` } return `[${_.map(value, _.bind(this.stringifyActual, this)).join(', ')}]` } if (_.isRegExp(value)) { return value.toString() } if (_.isObject(value)) { // Cannot use $dom.isJquery here because it causes infinite recursion. if (value instanceof $) { return `jQuery{${value.length}}` } const len = _.keys(value).length if (len > 2) { return `Object{${len}}` } return this.stringifyActualObj(value) } if (_.isSymbol(value)) { return 'Symbol' } if (_.isUndefined(value)) { return undefined } return `${value}` }, // give us some user-friendly "types" stringifyFriendlyTypeof: _.cond(USER_FRIENDLY_TYPE_DETECTORS), stringify (values) { // if we already have an array // then nest it again so that // its formatted properly values = [].concat(values) return _ .chain(values) .map(_.bind(this.stringifyActual, this)) .without(undefined) .join(', ') .value() }, stringifyArg (arg) { if (_.isString(arg) || _.isNumber(arg) || _.isBoolean(arg)) { return JSON.stringify(arg) } if (_.isNull(arg)) { return 'null' } if (_.isUndefined(arg)) { return 'undefined' } return this.stringifyActual(arg) }, plural (obj, plural, singular) { obj = _.isNumber(obj) ? obj : obj.length if (obj > 1) { return plural } return singular }, convertHtmlTags (html) { return html .replace(tagOpen, '<$1>') .replace(tagClosed, '</$1>') }, isInstanceOf (instance, constructor) { try { return instance instanceof constructor } catch (e) { return false } }, escapeQuotes (text) { // convert to str and escape any single // or double quotes return (`${text}`).replace(quotesRe, '\\$1') }, normalizeNumber (num) { const parsed = Number(num) // return num if this isNaN else return parsed if (_.isNaN(parsed)) { return num } return parsed }, isValidHttpMethod (str) { return _.isString(str) && _.includes(methods, str.toLowerCase()) }, addTwentyYears () { return moment().add(20, 'years').unix() }, locReload (forceReload, win) { return win.location.reload(forceReload) }, locHref (url, win) { win.location.href = url }, locToString (win) { return win.location.toString() }, locExisting () { return $Location.create(window.location.href) }, iframeSrc ($autIframe, url) { return $autIframe.prop('src', url) }, getDistanceBetween (point1, point2) { const deltaX = point1.x - point2.x const deltaY = point1.y - point2.y return Math.sqrt((deltaX * deltaX) + (deltaY * deltaY)) }, getTestFromRunnable (r) { return r.ctx.currentTest || r }, memoize (func, cacheInstance = new Map()) { const memoized = function (...args) { const key = args[0] const { cache } = memoized if (cache.has(key)) { return cache.get(key) } const result = func.apply(this, args) memoized.cache = cache.set(key, result) || cache return result } memoized.cache = cacheInstance return memoized }, indent (str, indentAmount) { const indentStr = _.repeat(' ', indentAmount) str = str.replace(/\n/g, `\n${indentStr}`) return `${indentStr}${str}` }, // normalize more than {maxNewLines} new lines into // exactly {replacementNumLines} new lines normalizeNewLines (str, maxNewLines, replacementNumLines) { const moreThanMaxNewLinesRe = new RegExp(`\\n{${maxNewLines},}`) const replacementWithNumLines = replacementNumLines ?? maxNewLines return _.chain(str) .split(moreThanMaxNewLinesRe) .compact() .join(_.repeat('\n', replacementWithNumLines)) .value() }, /** * Correctly decodes Unicode string in encoded in base64 * @see https://github.com/cypress-io/cypress/issues/5435 * @see https://github.com/cypress-io/cypress/issues/7507 * @see https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings * * @example ``` Buffer.from(JSON.stringify({state: '🙂'})).toString('base64') // 'eyJzdGF0ZSI6IvCfmYIifQ==' // "window.atob" does NOT work // atob('eyJzdGF0ZSI6IvCfmYIifQ==') // "{"state":"🙂"}" // but this function works b64DecodeUnicode('eyJzdGF0ZSI6IvCfmYIifQ==') '{"state":"🙂"}' ``` */ decodeBase64Unicode (str) { return decodeURIComponent(atob(str).split('').map((char) => { return `%${(`00${char.charCodeAt(0).toString(16)}`).slice(-2)}` }).join('')) }, /** * Correctly encodes Unicode string to base64 * @see https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings */ encodeBase64Unicode (str) { return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) => { return String.fromCharCode(`0x${p1}`) })) }, }