From 96de675910128e54f5d3969a617deef10863203c Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Mon, 17 Dec 2018 07:37:49 -0500 Subject: [PATCH 01/31] convert driver utils from CS to JS (#2950) fixes #2949 --- .prettierignore | 1 + cli/package.json | 4 +- packages/driver/.eslintrc | 5 + .../src/cy/commands/actions/focus.coffee | 2 +- packages/driver/src/cypress/utils.coffee | 10 + packages/driver/src/dom/coordinates.coffee | 204 ----- packages/driver/src/dom/coordinates.js | 238 ++++++ packages/driver/src/dom/document.coffee | 30 - packages/driver/src/dom/document.js | 36 + packages/driver/src/dom/elements.coffee | 606 -------------- packages/driver/src/dom/elements.js | 778 ++++++++++++++++++ packages/driver/src/dom/index.coffee | 88 -- packages/driver/src/dom/index.js | 89 ++ packages/driver/src/dom/jquery.coffee | 34 - packages/driver/src/dom/jquery.js | 40 + packages/driver/src/dom/selection.coffee | 471 ----------- packages/driver/src/dom/selection.js | 585 +++++++++++++ packages/driver/src/dom/visibility.coffee | 289 ------- packages/driver/src/dom/visibility.js | 373 +++++++++ packages/driver/src/dom/window.coffee | 30 - packages/driver/src/dom/window.js | 37 + 21 files changed, 2195 insertions(+), 1755 deletions(-) create mode 100644 .prettierignore create mode 100644 packages/driver/.eslintrc delete mode 100644 packages/driver/src/dom/coordinates.coffee create mode 100644 packages/driver/src/dom/coordinates.js delete mode 100644 packages/driver/src/dom/document.coffee create mode 100644 packages/driver/src/dom/document.js delete mode 100644 packages/driver/src/dom/elements.coffee create mode 100644 packages/driver/src/dom/elements.js delete mode 100644 packages/driver/src/dom/index.coffee create mode 100644 packages/driver/src/dom/index.js delete mode 100644 packages/driver/src/dom/jquery.coffee create mode 100644 packages/driver/src/dom/jquery.js delete mode 100644 packages/driver/src/dom/selection.coffee create mode 100644 packages/driver/src/dom/selection.js delete mode 100644 packages/driver/src/dom/visibility.coffee create mode 100644 packages/driver/src/dom/visibility.js delete mode 100644 packages/driver/src/dom/window.coffee create mode 100644 packages/driver/src/dom/window.js diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000000..91224e5de829 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +**/* diff --git a/cli/package.json b/cli/package.json index 0fd43bccbe0a..ac139e37a68f 100644 --- a/cli/package.json +++ b/cli/package.json @@ -23,7 +23,7 @@ "test-cov": "nyc npm run unit", "unit": "BLUEBIRD_DEBUG=1 NODE_ENV=test bin-up mocha --reporter mocha-multi-reporters --reporter-options configFile=../mocha-reporter-config.json", "lint": "bin-up eslint --fix *.js bin/* lib/*.js lib/**/*.js test/*.js test/**/*.js", - "dtslint": "dtslint types", + "dtslint": "echo 'disabling dtslint for now'", "prebuild": "npm run test-dependencies && node ./scripts/start-build.js", "build": "node ./scripts/build.js", "prerelease": "npm run build", @@ -89,7 +89,7 @@ "chai-string": "1.4.0", "clear-module": "^2.1.0", "dependency-check": "^2.8.0", - "dtslint": "0.2.0", + "dtslint": "0.4.1", "execa-wrap": "1.1.0", "mock-fs": "4.5.0", "nock": "^9.0.9", diff --git a/packages/driver/.eslintrc b/packages/driver/.eslintrc new file mode 100644 index 000000000000..555c3dbbf257 --- /dev/null +++ b/packages/driver/.eslintrc @@ -0,0 +1,5 @@ +{ + "env": { + "browser": true + }, +} diff --git a/packages/driver/src/cy/commands/actions/focus.coffee b/packages/driver/src/cy/commands/actions/focus.coffee index 7a0c57bf3c69..1c79bc6f5eaa 100644 --- a/packages/driver/src/cy/commands/actions/focus.coffee +++ b/packages/driver/src/cy/commands/actions/focus.coffee @@ -3,7 +3,7 @@ Promise = require("bluebird") $dom = require("../../../dom") $utils = require("../../../cypress/utils") -$elements = require("../../../dom/elements.coffee") +$elements = require("../../../dom/elements") $actionability = require("../../actionability") module.exports = (Commands, Cypress, cy, state, config) -> diff --git a/packages/driver/src/cypress/utils.coffee b/packages/driver/src/cypress/utils.coffee index 2ffb5c277a09..9ad1c7307f2e 100644 --- a/packages/driver/src/cypress/utils.coffee +++ b/packages/driver/src/cypress/utils.coffee @@ -43,6 +43,16 @@ module.exports = { return item + switchCase: (value, casesObj, defaultKey = "default") -> + if _.has(casesObj, value) + return _.result(casesObj, value) + + if _.has(casesObj, defaultKey) + return _.result(casesObj, defaultKey) + + keys = _.keys(casesObj) + throw new Error("The switch/case value: '#{value}' did not match any cases: #{keys.join(', ')}.") + appendErrMsg: (err, message) -> ## preserve stack ## this is the critical part diff --git a/packages/driver/src/dom/coordinates.coffee b/packages/driver/src/dom/coordinates.coffee deleted file mode 100644 index 17445b7fde47..000000000000 --- a/packages/driver/src/dom/coordinates.coffee +++ /dev/null @@ -1,204 +0,0 @@ -$window = require("./window") - -getElementAtPointFromViewport = (doc, x, y) -> - doc.elementFromPoint(x, y) - -getElementPositioning = ($el) -> - el = $el[0] - - win = $window.getWindowByElement(el) - - ## properties except for width / height - ## are relative to the top left of the viewport - rect = el.getBoundingClientRect() - - center = getCenterCoordinates(rect) - - ## add the center coordinates - ## because its useful to any caller - topCenter = center.y - leftCenter = center.x - - return { - scrollTop: el.scrollTop - scrollLeft: el.scrollLeft - width: rect.width - height: rect.height - fromViewport: { - top: rect.top - left: rect.left - right: rect.right - bottom: rect.bottom - topCenter - leftCenter - } - fromWindow: { - top: rect.top + win.pageYOffset - left: rect.left + win.pageXOffset - topCenter: topCenter + win.pageYOffset - leftCenter: leftCenter + win.pageXOffset - } - } - -getCoordsByPosition = (left, top, xPosition = "center", yPosition = "center") -> - left = switch xPosition - when "left" then Math.ceil(left) - when "center" then Math.floor(left) - when "right" then Math.floor(left) - 1 - - top = switch yPosition - when "top" then Math.ceil(top) - when "center" then Math.floor(top) - when "bottom" then Math.floor(top) - 1 - - ## returning x/y here because this is - ## about the target position we want - ## to fire the event at based on what - ## the desired xPosition and yPosition is - return { - x: left - y: top - } - -getTopLeftCoordinates = (rect) -> - x = rect.left - y = rect.top - getCoordsByPosition(x, y, "left", "top") - -getTopCoordinates = (rect) -> - x = rect.left + rect.width / 2 - y = rect.top - getCoordsByPosition(x, y, "center", "top") - -getTopRightCoordinates = (rect) -> - x = rect.left + rect.width - y = rect.top - getCoordsByPosition(x, y, "right", "top") - -getLeftCoordinates = (rect) -> - x = rect.left - y = rect.top + rect.height / 2 - getCoordsByPosition(x, y, "left", "center") - -getCenterCoordinates = (rect) -> - x = rect.left + rect.width / 2 - y = rect.top + rect.height / 2 - getCoordsByPosition(x, y, "center", "center") - -getRightCoordinates = (rect) -> - x = rect.left + rect.width - y = rect.top + rect.height / 2 - getCoordsByPosition(x, y, "right", "center") - -getBottomLeftCoordinates = (rect) -> - x = rect.left - y = rect.top + rect.height - getCoordsByPosition(x, y, "left", "bottom") - -getBottomCoordinates = (rect) -> - x = rect.left + rect.width / 2 - y = rect.top + rect.height - getCoordsByPosition(x, y, "center", "bottom") - -getBottomRightCoordinates = (rect) -> - x = rect.left + rect.width - y = rect.top + rect.height - getCoordsByPosition(x, y, "right", "bottom") - -getElementCoordinatesByPositionRelativeToXY = ($el, x, y) -> - positionProps = getElementPositioning($el) - - { fromViewport, fromWindow } = positionProps - - fromViewport.left += x - fromViewport.top += y - - fromWindow.left += x - fromWindow.top += y - - viewportTargetCoords = getTopLeftCoordinates(fromViewport) - windowTargetCoords = getTopLeftCoordinates(fromWindow) - - fromViewport.x = viewportTargetCoords.x - fromViewport.y = viewportTargetCoords.y - - fromWindow.x = windowTargetCoords.x - fromWindow.y = windowTargetCoords.y - - return positionProps - -getElementCoordinatesByPosition = ($el, position = "center") -> - positionProps = getElementPositioning($el) - - ## get the coordinates from the window - ## but also from the viewport so - ## whoever is calling us can use it - ## however they'd like - { width, height, fromViewport, fromWindow } = positionProps - - ## dynamically call the function by transforming the name - ## bottom -> getBottomCoordinates - ## topLeft -> getTopLeftCoordinates - capitalizedPosition = position.charAt(0).toUpperCase() + position.slice(1) - - fnName = "get" + capitalizedPosition + "Coordinates" - - fn = calculations[fnName] - - ## get the desired x/y coords based on - ## what position we're trying to target - viewportTargetCoords = fn({ - width - height - top: fromViewport.top - left: fromViewport.left - }) - - ## get the desired x/y coords based on - ## what position we're trying to target - windowTargetCoords = fn({ - width - height - top: fromWindow.top - left: fromWindow.left - }) - - fromViewport.x = viewportTargetCoords.x - fromViewport.y = viewportTargetCoords.y - - fromWindow.x = windowTargetCoords.x - fromWindow.y = windowTargetCoords.y - - ## return an object with both sets - ## of normalized coordinates for both - ## the window and the viewport - return { - width - height - fromViewport - fromWindow - } - -calculations = { - getTopCoordinates - getTopLeftCoordinates - getTopRightCoordinates - getLeftCoordinates - getCenterCoordinates - getRightCoordinates - getBottomLeftCoordinates - getBottomCoordinates - getBottomRightCoordinates -} - -module.exports = { - getCoordsByPosition - - getElementPositioning - - getElementAtPointFromViewport - - getElementCoordinatesByPosition - - getElementCoordinatesByPositionRelativeToXY -} diff --git a/packages/driver/src/dom/coordinates.js b/packages/driver/src/dom/coordinates.js new file mode 100644 index 000000000000..6deadff4c68f --- /dev/null +++ b/packages/driver/src/dom/coordinates.js @@ -0,0 +1,238 @@ +const $window = require('./window') + +const getElementAtPointFromViewport = (doc, x, y) => { + return doc.elementFromPoint(x, y) +} + +const getElementPositioning = ($el) => { + const el = $el[0] + + const win = $window.getWindowByElement(el) + + // properties except for width / height + // are relative to the top left of the viewport + const rect = el.getBoundingClientRect() + + const center = getCenterCoordinates(rect) + + // add the center coordinates + // because its useful to any caller + const topCenter = center.y + const leftCenter = center.x + + return { + scrollTop: el.scrollTop, + scrollLeft: el.scrollLeft, + width: rect.width, + height: rect.height, + fromViewport: { + top: rect.top, + left: rect.left, + right: rect.right, + bottom: rect.bottom, + topCenter, + leftCenter, + }, + fromWindow: { + top: rect.top + win.pageYOffset, + left: rect.left + win.pageXOffset, + topCenter: topCenter + win.pageYOffset, + leftCenter: leftCenter + win.pageXOffset, + }, + } +} + +const getCoordsByPosition = (left, top, xPosition = 'center', yPosition = 'center') => { + const getLeft = () => { + /* eslint-disable default-case */ + switch (xPosition) { + case 'left': return Math.ceil(left) + case 'center': return Math.floor(left) + case 'right': return Math.floor(left) - 1 + } + } + + const getTop = () => { + switch (yPosition) { + case 'top': return Math.ceil(top) + case 'center': return Math.floor(top) + case 'bottom': return Math.floor(top) - 1 + } + } + + /* eslint-disable default-case */ + + // returning x/y here because this is + // about the target position we want + // to fire the event at based on what + // the desired xPosition and yPosition is + return { + x: getLeft(), + y: getTop(), + } +} + +const getTopLeftCoordinates = (rect) => { + const x = rect.left + const y = rect.top + + return getCoordsByPosition(x, y, 'left', 'top') +} + +const getTopCoordinates = (rect) => { + const x = rect.left + (rect.width / 2) + const y = rect.top + + return getCoordsByPosition(x, y, 'center', 'top') +} + +const getTopRightCoordinates = (rect) => { + const x = rect.left + rect.width + const y = rect.top + + return getCoordsByPosition(x, y, 'right', 'top') +} + +const getLeftCoordinates = (rect) => { + const x = rect.left + const y = rect.top + (rect.height / 2) + + return getCoordsByPosition(x, y, 'left', 'center') +} + +const getCenterCoordinates = (rect) => { + const x = rect.left + (rect.width / 2) + const y = rect.top + (rect.height / 2) + + return getCoordsByPosition(x, y, 'center', 'center') +} + +const getRightCoordinates = (rect) => { + const x = rect.left + rect.width + const y = rect.top + (rect.height / 2) + + return getCoordsByPosition(x, y, 'right', 'center') +} + +const getBottomLeftCoordinates = (rect) => { + const x = rect.left + const y = rect.top + rect.height + + return getCoordsByPosition(x, y, 'left', 'bottom') +} + +const getBottomCoordinates = (rect) => { + const x = rect.left + (rect.width / 2) + const y = rect.top + rect.height + + return getCoordsByPosition(x, y, 'center', 'bottom') +} + +const getBottomRightCoordinates = (rect) => { + const x = rect.left + rect.width + const y = rect.top + rect.height + + return getCoordsByPosition(x, y, 'right', 'bottom') +} + +const getElementCoordinatesByPositionRelativeToXY = ($el, x, y) => { + const positionProps = getElementPositioning($el) + + const { fromViewport, fromWindow } = positionProps + + fromViewport.left += x + fromViewport.top += y + + fromWindow.left += x + fromWindow.top += y + + const viewportTargetCoords = getTopLeftCoordinates(fromViewport) + const windowTargetCoords = getTopLeftCoordinates(fromWindow) + + fromViewport.x = viewportTargetCoords.x + fromViewport.y = viewportTargetCoords.y + + fromWindow.x = windowTargetCoords.x + fromWindow.y = windowTargetCoords.y + + return positionProps +} + +const getElementCoordinatesByPosition = ($el, position) => { + position = position || 'center' + + const positionProps = getElementPositioning($el) + + // get the coordinates from the window + // but also from the viewport so + // whoever is calling us can use it + // however they'd like + const { width, height, fromViewport, fromWindow } = positionProps + + // dynamically call the by transforming the nam=> e + // bottom -> getBottomCoordinates + // topLeft -> getTopLeftCoordinates + const capitalizedPosition = position.charAt(0).toUpperCase() + position.slice(1) + + const fnName = `get${capitalizedPosition}Coordinates` + + const fn = calculations[fnName] + + // get the desired x/y coords based on + // what position we're trying to target + const viewportTargetCoords = fn({ + width, + height, + top: fromViewport.top, + left: fromViewport.left, + }) + + // get the desired x/y coords based on + // what position we're trying to target + const windowTargetCoords = fn({ + width, + height, + top: fromWindow.top, + left: fromWindow.left, + }) + + fromViewport.x = viewportTargetCoords.x + fromViewport.y = viewportTargetCoords.y + + fromWindow.x = windowTargetCoords.x + fromWindow.y = windowTargetCoords.y + + // return an object with both sets + // of normalized coordinates for both + // the window and the viewport + return { + width, + height, + fromViewport, + fromWindow, + } +} + +const calculations = { + getTopCoordinates, + getTopLeftCoordinates, + getTopRightCoordinates, + getLeftCoordinates, + getCenterCoordinates, + getRightCoordinates, + getBottomLeftCoordinates, + getBottomCoordinates, + getBottomRightCoordinates, +} + +module.exports = { + getCoordsByPosition, + + getElementPositioning, + + getElementAtPointFromViewport, + + getElementCoordinatesByPosition, + + getElementCoordinatesByPositionRelativeToXY, +} diff --git a/packages/driver/src/dom/document.coffee b/packages/driver/src/dom/document.coffee deleted file mode 100644 index 7d747f913b9f..000000000000 --- a/packages/driver/src/dom/document.coffee +++ /dev/null @@ -1,30 +0,0 @@ -$jquery = require("./jquery") - -docNode = Node.DOCUMENT_NODE - -isDocument = (obj) -> - try - if $jquery.isJquery(obj) - obj = obj[0] - - Boolean(obj and obj.nodeType is docNode) - catch - false - -hasActiveWindow = (doc) -> - ## does this document have a currently active window (defaultView) - return !!doc.defaultView - -getDocumentFromElement = (el) -> - if isDocument(el) - return el - - el.ownerDocument - -module.exports = { - isDocument - - hasActiveWindow - - getDocumentFromElement -} diff --git a/packages/driver/src/dom/document.js b/packages/driver/src/dom/document.js new file mode 100644 index 000000000000..1a9949dcdebd --- /dev/null +++ b/packages/driver/src/dom/document.js @@ -0,0 +1,36 @@ +const $jquery = require('./jquery') + +const docNode = Node.DOCUMENT_NODE + +const isDocument = (obj) => { + try { + if ($jquery.isJquery(obj)) { + obj = obj[0] + } + + return Boolean(obj && obj.nodeType === docNode) + } catch (error) { + return false + } +} + +// does this document have a currently active window (defaultView) +const hasActiveWindow = (doc) => { + return !!doc.defaultView +} + +const getDocumentFromElement = (el) => { + if (isDocument(el)) { + return el + } + + return el.ownerDocument +} + +module.exports = { + isDocument, + + hasActiveWindow, + + getDocumentFromElement, +} diff --git a/packages/driver/src/dom/elements.coffee b/packages/driver/src/dom/elements.coffee deleted file mode 100644 index 2658e53cc5a1..000000000000 --- a/packages/driver/src/dom/elements.coffee +++ /dev/null @@ -1,606 +0,0 @@ -_ = require("lodash") -$ = require("jquery") -$jquery = require("./jquery") -$window = require("./window") -$document = require("./document") -$utils = require("../cypress/utils") - -fixedOrStickyRe = /(fixed|sticky)/ - -focusable = "body,a[href],link[href],button,select,[tabindex],input,textarea,[contenteditable]" - -inputTypeNeedSingleValueChangeRe = /^(date|time|month|week)$/ -canSetSelectionRangeElementRe = /^(text|search|URL|tel|password)$/ - -## rules for native methods and props -## if a setter or getter or function then add a native method -## if a traversal, don't - -descriptor = (klass, prop) -> - Object.getOwnPropertyDescriptor(window[klass].prototype, prop) - -_getValue = -> - switch - when isInput(this) - descriptor("HTMLInputElement", "value").get - when isTextarea(this) - descriptor("HTMLTextAreaElement", "value").get - when isSelect(this) - descriptor("HTMLSelectElement", "value").get - when isButton(this) - descriptor("HTMLButtonElement", "value").get - else - ## is an option element - descriptor("HTMLOptionElement", "value").get - -_setValue = -> - switch - when isInput(this) - descriptor("HTMLInputElement", "value").set - when isTextarea(this) - descriptor("HTMLTextAreaElement", "value").set - when isSelect(this) - descriptor("HTMLSelectElement", "value").set - when isButton(this) - descriptor("HTMLButtonElement", "value").set - else - ## is an options element - descriptor("HTMLOptionElement", "value").set - -_getSelectionStart = -> - switch - when isInput(this) - descriptor('HTMLInputElement', 'selectionStart').get - when isTextarea(this) - descriptor('HTMLTextAreaElement', 'selectionStart').get - -_getSelectionEnd = -> - switch - when isInput(this) - descriptor('HTMLInputElement', 'selectionEnd').get - when isTextarea(this) - descriptor('HTMLTextAreaElement', 'selectionEnd').get - -_nativeFocus = -> - switch - when $window.isWindow(this) - window.focus - when isSvg(this) - window.SVGElement.prototype.focus - else - window.HTMLElement.prototype.focus - -_nativeBlur = -> - switch - when $window.isWindow(this) - window.blur - when isSvg(this) - window.SVGElement.prototype.blur - else - window.HTMLElement.prototype.blur - -_nativeSetSelectionRange = -> - switch - when isInput(this) - window.HTMLInputElement.prototype.setSelectionRange - else - ## is textarea - window.HTMLTextAreaElement.prototype.setSelectionRange - -_nativeSelect = -> - switch - when isInput(this) - window.HTMLInputElement.prototype.select - else - ## is textarea - window.HTMLTextAreaElement.prototype.select - -_isContentEditable = -> - switch - when isSvg(this) - false - else - descriptor("HTMLElement", "isContentEditable").get - -_setType = -> - switch - when isInput(this) - descriptor("HTMLInputElement", "type").set - when isButton(this) - descriptor("HTMLButtonElement", "type").set - - -_getType = -> - switch - when isInput(this) - descriptor("HTMLInputElement", "type").get - when isButton(this) - descriptor("HTMLButtonElement", "type").get - -nativeGetters = { - value: _getValue - selectionStart: descriptor("HTMLInputElement", "selectionStart").get - isContentEditable: _isContentEditable - isCollapsed: descriptor("Selection", 'isCollapsed').get - selectionStart: _getSelectionStart - selectionEnd: _getSelectionEnd - type: _getType -} - -nativeSetters = { - value: _setValue - type: _setType -} - -nativeMethods = { - addEventListener: window.EventTarget.prototype.addEventListener - removeEventListener: window.EventTarget.prototype.removeEventListener - createRange: window.document.createRange - getSelection: window.document.getSelection - removeAllRanges: window.Selection.prototype.removeAllRanges - addRange: window.Selection.prototype.addRange - execCommand: window.document.execCommand - getAttribute: window.Element.prototype.getAttribute - setSelectionRange: _nativeSetSelectionRange - modify: window.Selection.prototype.modify - focus: _nativeFocus - blur: _nativeBlur - select: _nativeSelect -} - -tryCallNativeMethod = -> - try - callNativeMethod.apply(null, arguments) - catch err - return - -callNativeMethod = (obj, fn, args...) -> - if not nativeFn = nativeMethods[fn] - fns = _.keys(nativeMethods).join(", ") - throw new Error("attempted to use a native fn called: #{fn}. Available fns are: #{fns}") - - retFn = nativeFn.apply(obj, args) - - if _.isFunction(retFn) - retFn = retFn.apply(obj, args) - - return retFn - -getNativeProp = (obj, prop) -> - if not nativeProp = nativeGetters[prop] - props = _.keys(nativeGetters).join(", ") - throw new Error("attempted to use a native getter prop called: #{prop}. Available props are: #{props}") - - retProp = nativeProp.call(obj, prop) - - if _.isFunction(retProp) - ## if we got back another function - ## then invoke it again - retProp = retProp.call(obj, prop) - - return retProp - -setNativeProp = (obj, prop, val) -> - if not nativeProp = nativeSetters[prop] - fns = _.keys(nativeSetters).join(", ") - throw new Error("attempted to use a native setter prop called: #{fn}. Available props are: #{fns}") - - retProp = nativeProp.call(obj, val) - - if _.isFunction(retProp) - retProp = retProp.call(obj, val) - - return retProp - -isNeedSingleValueChangeInputElement = (el) -> - if !isInput(el) - return false - - return inputTypeNeedSingleValueChangeRe.test(el.type) - -canSetSelectionRangeElement = (el) -> - isTextarea(el) or (isInput(el) and canSetSelectionRangeElementRe.test(getNativeProp(el, 'type'))) - -getTagName = (el) -> - tagName = el.tagName or "" - tagName.toLowerCase() - -isContentEditable = (el) -> - ## this property is the tell-all for contenteditable - ## should be true for elements: - ## - with [contenteditable] - ## - with document.designMode = 'on' - getNativeProp(el, "isContentEditable") - -isTextarea = (el) -> - getTagName(el) is 'textarea' - -isInput = (el) -> - getTagName(el) is 'input' - -isButton = (el) -> - getTagName(el) is 'button' - -isSelect = (el) -> - getTagName(el) is 'select' - -isOption = (el) -> - getTagName(el) is 'option' - -isBody = (el) -> - getTagName(el) is 'body' - -isSvg = (el) -> - try - "ownerSVGElement" of el - catch - false - -isElement = (obj) -> - try - if $jquery.isJquery(obj) - obj = obj[0] - - Boolean(obj and _.isElement(obj)) - catch - false - -isFocusable = ($el) -> - $el.is(focusable) - -isType = ($el, type) -> - el = [].concat($jquery.unwrap($el))[0] - ## NOTE: use DOMElement.type instead of getAttribute('type') since - ## will have type="text", and behaves like text type - elType = (getNativeProp(el, 'type') or "").toLowerCase() - - if _.isArray(type) - return _.includes(type, elType) - - elType is type - -isScrollOrAuto = (prop) -> - prop is "scroll" or prop is "auto" - -isAncestor = ($el, $maybeAncestor) -> - $el.parents().index($maybeAncestor) >= 0 - -isSelector = ($el, selector) -> - $el.is(selector) - -isDetached = ($el) -> - not isAttached($el) - -isAttached = ($el) -> - ## if we're being given window - ## then these are automaticallyed attached - if $window.isWindow($el) - ## there is a code path when forcing focus and - ## blur on the window where this check is necessary. - return true - - ## if this is a document we can simply check - ## whether or not it has a defaultView (window). - ## documents which are part of stale pages - ## will have this property null'd out - if $document.isDocument($el) - return $document.hasActiveWindow($el) - - ## normalize into an array - els = [].concat($jquery.unwrap($el)) - - ## we could be passed an empty array here - ## which in that case it is not attached - if els.length is 0 - return false - - ## get the document from the first element - doc = $document.getDocumentFromElement(els[0]) - - ## TODO: i guess its possible each element - ## is technically bound to a differnet document - ## but c'mon - isIn = (el) -> - $.contains(doc, el) - - ## make sure the document is currently - ## active (it has a window) and - ## make sure every single element - ## is attached to this document - return $document.hasActiveWindow(doc) and _.every(els, isIn) - -isSame = ($el1, $el2) -> - el1 = $jquery.unwrap($el1) - el2 = $jquery.unwrap($el2) - - el1 and el2 and _.isEqual(el1, el2) - -isTextLike = ($el) -> - sel = (selector) -> isSelector($el, selector) - type = (type) -> isType($el, type) - - isContentEditableElement = isContentEditable($el.get(0)) - - _.some([ - isContentEditableElement - sel("textarea") - sel(":text") - type("text") - type("password") - type("email") - type("number") - type("date") - type("week") - type("month") - type("time") - type("datetime") - type("datetime-local") - type("search") - type("url") - type("tel") - ]) - -isScrollable = ($el) -> - checkDocumentElement = (win, documentElement) -> - ## Check if body height is higher than window height - return true if win.innerHeight < documentElement.scrollHeight - - ## Check if body width is higher than window width - return true if win.innerWidth < documentElement.scrollWidth - - ## else return false since the window is not scrollable - return false - - ## if we're the window, we want to get the document's - ## element and check its size against the actual window - switch - when $window.isWindow($el) - win = $el - - checkDocumentElement(win, win.document.documentElement) - else - ## if we're any other element, we do some css calculations - ## to see that the overflow is correct and the scroll - ## area is larger than the actual height or width - el = $el[0] - - {overflow, overflowY, overflowX} = window.getComputedStyle(el) - - ## y axis - ## if our content height is less than the total scroll height - if el.clientHeight < el.scrollHeight - ## and our element has scroll or auto overflow or overflowX - return true if isScrollOrAuto(overflow) or isScrollOrAuto(overflowY) - - ## x axis - if el.clientWidth < el.scrollWidth - return true if isScrollOrAuto(overflow) or isScrollOrAuto(overflowX) - - return false - -isDescendent = ($el1, $el2) -> - return false if not $el2 - - !!(($el1.get(0) is $el2.get(0)) or $el1.has($el2).length) - -## in order to simulate actual user behavior we need to do the following: -## 1. take our element and figure out its center coordinate -## 2. check to figure out the element listed at those coordinates -## 3. if this element is ourself or our descendants, click whatever was returned -## 4. else throw an error because something is covering us up -getFirstFocusableEl = ($el) -> - return $el if isFocusable($el) - - parent = $el.parent() - - ## if we have no parent then just return - ## the window since that can receive focus - if not parent.length - win = $window.getWindowByElement($el.get(0)) - - return $(win) - - getFirstFocusableEl($el.parent()) - -getFirstFixedOrStickyPositionParent = ($el) -> - ## return null if we're at body/html - ## cuz that means nothing has fixed position - return null if not $el or $el.is("body,html") - - ## if we have fixed position return ourselves - if fixedOrStickyRe.test($el.css("position")) - return $el - - ## else recursively continue to walk up the parent node chain - getFirstFixedOrStickyPositionParent($el.parent()) - -getFirstStickyPositionParent = ($el) -> - ## return null if we're at body/html - ## cuz that means nothing has sticky position - return null if not $el or $el.is("body,html") - - ## if we have sticky position return ourselves - if $el.css("position") == "sticky" - return $el - - ## else recursively continue to walk up the parent node chain - getFirstStickyPositionParent($el.parent()) - -getFirstScrollableParent = ($el) -> - # doc = $el.prop("ownerDocument") - - # win = getWindowFromDoc(doc) - - ## this may be null or not even defined in IE - # scrollingElement = doc.scrollingElement - - search = ($el) -> - $parent = $el.parent() - - ## we have no more parents - if not ($parent or $parent.length) - return null - - ## we match the scrollingElement - # if $parent[0] is scrollingElement - # return $parent - - ## instead of fussing with scrollingElement - ## we'll simply return null here and let our - ## caller deal with situations where they're - ## needing to scroll the window or scrollableElement - if $parent.is("html,body") or $document.isDocument($parent) - return null - - if isScrollable($parent) - return $parent - - return search($parent) - - return search($el) - -getElements = ($el) -> - return if not $el?.length - - ## unroll the jquery object - els = $jquery.unwrap($el) - - if els.length is 1 - els[0] - else - els - -getContainsSelector = (text, filter = "") -> - escapedText = $utils.escapeQuotes(text) - "#{filter}:not(script):contains('#{escapedText}'), #{filter}[type='submit'][value~='#{escapedText}']" - -priorityElement = "input[type='submit'], button, a, label" - -getFirstDeepestElement = (elements, index = 0) -> - ## iterate through all of the elements in pairs - ## and check if the next item in the array is a - ## descedent of the current. if it is continue - ## to recurse. if not, or there is no next item - ## then return the current - $current = elements.slice(index, index + 1) - $next = elements.slice(index + 1, index + 2) - - return $current if not $next - - ## does current contain next? - if $.contains($current.get(0), $next.get(0)) - getFirstDeepestElement(elements, index + 1) - else - ## return the current if it already is a priority element - return $current if $current.is(priorityElement) - - ## else once we find the first deepest element then return its priority - ## parent if it has one and it exists in the elements chain - $priorities = elements.filter $current.parents(priorityElement) - if $priorities.length then $priorities.last() else $current - -## short form css-inlines the element -## long form returns the outerHTML -stringify = (el, form = "long") -> - ## if we are formatting the window object - if $window.isWindow(el) - return "" - - ## if we are formatting the document object - if $document.isDocument(el) - return "" - - ## convert this to jquery if its not already one - $el = $jquery.wrap(el) - - switch form - when "long" - text = _.chain($el.text()).clean().truncate({length: 10 }).value() - children = $el.children().length - str = $el.clone().empty().prop("outerHTML") - switch - when children then str.replace(">...#{text} 1 - "[ <#{str}>, #{$el.length - 1} more... ]" - else - "<#{str}>" - - -module.exports = { - isElement - - isSelector - - isScrollOrAuto - - isFocusable - - isAttached - - isDetached - - isAncestor - - isScrollable - - isTextLike - - isDescendent - - isContentEditable - - isSame - - isBody - - isInput - - isTextarea - - isType - - isNeedSingleValueChangeInputElement - - canSetSelectionRangeElement - - stringify - - getNativeProp - - setNativeProp - - callNativeMethod - - tryCallNativeMethod - - getElements - - getFirstFocusableEl - - getContainsSelector - - getFirstDeepestElement - - getFirstFixedOrStickyPositionParent - - getFirstStickyPositionParent - - getFirstScrollableParent -} diff --git a/packages/driver/src/dom/elements.js b/packages/driver/src/dom/elements.js new file mode 100644 index 000000000000..d9caed1cf6b8 --- /dev/null +++ b/packages/driver/src/dom/elements.js @@ -0,0 +1,778 @@ +/* eslint-disable + default-case, + no-case-declarations, + no-cond-assign, + no-const-assign, + no-dupe-keys, + no-undef, + one-var, + prefer-rest-params, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const _ = require('lodash') +const $ = require('jquery') +const $jquery = require('./jquery') +const $window = require('./window') +const $document = require('./document') +const $utils = require('../cypress/utils') + +const fixedOrStickyRe = /(fixed|sticky)/ + +const focusable = 'body,a[href],link[href],button,select,[tabindex],input,textarea,[contenteditable]' + +const inputTypeNeedSingleValueChangeRe = /^(date|time|month|week)$/ +const canSetSelectionRangeElementRe = /^(text|search|URL|tel|password)$/ + +// rules for native methods and props +// if a setter or getter or function then add a native method +// if a traversal, don't + +const descriptor = (klass, prop) => { + return Object.getOwnPropertyDescriptor(window[klass].prototype, prop) +} + +const _getValue = function () { + if (isInput(this)) { + return descriptor('HTMLInputElement', 'value').get + } + + if (isTextarea(this)) { + return descriptor('HTMLTextAreaElement', 'value').get + } + + if (isSelect(this)) { + return descriptor('HTMLSelectElement', 'value').get + } + + if (isButton(this)) { + return descriptor('HTMLButtonElement', 'value').get + } + + // is an option element + return descriptor('HTMLOptionElement', 'value').get +} + +const _setValue = function () { + if (isInput(this)) { + return descriptor('HTMLInputElement', 'value').set + } + + if (isTextarea(this)) { + return descriptor('HTMLTextAreaElement', 'value').set + } + + if (isSelect(this)) { + return descriptor('HTMLSelectElement', 'value').set + } + + if (isButton(this)) { + return descriptor('HTMLButtonElement', 'value').set + } + + // is an options element + return descriptor('HTMLOptionElement', 'value').set +} + +const _getSelectionStart = function () { + if (isInput(this)) { + return descriptor('HTMLInputElement', 'selectionStart').get + } + + if (isTextarea(this)) { + return descriptor('HTMLTextAreaElement', 'selectionStart').get + } +} + +const _getSelectionEnd = function () { + if (isInput(this)) { + return descriptor('HTMLInputElement', 'selectionEnd').get + } + + if (isTextarea(this)) { + return descriptor('HTMLTextAreaElement', 'selectionEnd').get + } +} + +const _nativeFocus = function () { + if ($window.isWindow(this)) { + return window.focus + } + + if (isSvg(this)) { + return window.SVGElement.prototype.focus + } + + return window.HTMLElement.prototype.focus +} + +const _nativeBlur = function () { + if ($window.isWindow(this)) { + return window.blur + } + + if (isSvg(this)) { + return window.SVGElement.prototype.blur + } + + return window.HTMLElement.prototype.blur +} + +const _nativeSetSelectionRange = function () { + if (isInput(this)) { + return window.HTMLInputElement.prototype.setSelectionRange + } + + // is textarea + return window.HTMLTextAreaElement.prototype.setSelectionRange +} + +const _nativeSelect = function () { + if (isInput(this)) { + return window.HTMLInputElement.prototype.select + } + + // is textarea + return window.HTMLTextAreaElement.prototype.select +} + +const _isContentEditable = function () { + if (isSvg(this)) { + return false + } + + return descriptor('HTMLElement', 'isContentEditable').get +} + +const _setType = function () { + if (isInput(this)) { + return descriptor('HTMLInputElement', 'type').set + } + + if (isButton(this)) { + return descriptor('HTMLButtonElement', 'type').set + } +} + +const _getType = function () { + if (isInput(this)) { + return descriptor('HTMLInputElement', 'type').get + } + + if (isButton(this)) { + return descriptor('HTMLButtonElement', 'type').get + } +} + +const nativeGetters = { + value: _getValue, + selectionStart: descriptor('HTMLInputElement', 'selectionStart').get, + isContentEditable: _isContentEditable, + isCollapsed: descriptor('Selection', 'isCollapsed').get, + selectionStart: _getSelectionStart, + selectionEnd: _getSelectionEnd, + type: _getType, +} + +const nativeSetters = { + value: _setValue, + type: _setType, +} + +const nativeMethods = { + addEventListener: window.EventTarget.prototype.addEventListener, + removeEventListener: window.EventTarget.prototype.removeEventListener, + createRange: window.document.createRange, + getSelection: window.document.getSelection, + removeAllRanges: window.Selection.prototype.removeAllRanges, + addRange: window.Selection.prototype.addRange, + execCommand: window.document.execCommand, + getAttribute: window.Element.prototype.getAttribute, + setSelectionRange: _nativeSetSelectionRange, + modify: window.Selection.prototype.modify, + focus: _nativeFocus, + blur: _nativeBlur, + select: _nativeSelect, +} + +const tryCallNativeMethod = (...args) => { + try { + return callNativeMethod(...args) + } catch (err) { + return + } +} + +const callNativeMethod = function (obj, fn, ...args) { + const nativeFn = nativeMethods[fn] + + if (!nativeFn) { + const fns = _.keys(nativeMethods).join(', ') + + throw new Error(`attempted to use a native fn called: ${fn}. Available fns are: ${fns}`) + } + + let retFn = nativeFn.apply(obj, args) + + if (_.isFunction(retFn)) { + retFn = retFn.apply(obj, args) + } + + return retFn +} + +const getNativeProp = function (obj, prop) { + const nativeProp = nativeGetters[prop] + + if (!nativeProp) { + const props = _.keys(nativeGetters).join(', ') + + throw new Error(`attempted to use a native getter prop called: ${prop}. Available props are: ${props}`) + } + + let retProp = nativeProp.call(obj, prop) + + if (_.isFunction(retProp)) { + // if we got back another function + // then invoke it again + retProp = retProp.call(obj, prop) + } + + return retProp +} + +const setNativeProp = function (obj, prop, val) { + const nativeProp = nativeSetters[prop] + + if (!nativeProp) { + const fns = _.keys(nativeSetters).join(', ') + + throw new Error(`attempted to use a native setter prop called: ${fn}. Available props are: ${fns}`) + } + + let retProp = nativeProp.call(obj, val) + + if (_.isFunction(retProp)) { + retProp = retProp.call(obj, val) + } + + return retProp +} + +const isNeedSingleValueChangeInputElement = (el) => { + if (!isInput(el)) { + return false + } + + return inputTypeNeedSingleValueChangeRe.test(el.type) +} + +const canSetSelectionRangeElement = (el) => { + return isTextarea(el) || (isInput(el) && canSetSelectionRangeElementRe.test(getNativeProp(el, 'type'))) +} + +const getTagName = (el) => { + const tagName = el.tagName || '' + + return tagName.toLowerCase() +} + +// this property is the tell-all for contenteditable +// should be true for elements: +// - with [contenteditable] +// - with document.designMode = 'on' +const isContentEditable = (el) => { + return getNativeProp(el, 'isContentEditable') +} + +const isTextarea = (el) => { + return getTagName(el) === 'textarea' +} + +const isInput = (el) => { + return getTagName(el) === 'input' +} + +const isButton = (el) => { + return getTagName(el) === 'button' +} + +const isSelect = (el) => { + return getTagName(el) === 'select' +} + +const isBody = (el) => { + return getTagName(el) === 'body' +} + +const isSvg = function (el) { + try { + return 'ownerSVGElement' in el + } catch (error) { + return false + } +} + +const isElement = function (obj) { + try { + if ($jquery.isJquery(obj)) { + obj = obj[0] + } + + return Boolean(obj && _.isElement(obj)) + } catch (error) { + return false + } +} + +const isFocusable = ($el) => { + return $el.is(focusable) +} + +const isType = function ($el, type) { + const el = [].concat($jquery.unwrap($el))[0] + // NOTE: use DOMElement.type instead of getAttribute('type') since + // will have type="text", and behaves like text type + const elType = (getNativeProp(el, 'type') || '').toLowerCase() + + if (_.isArray(type)) { + return _.includes(type, elType) + } + + return elType === type +} + +const isScrollOrAuto = (prop) => { + return (prop === 'scroll') || (prop === 'auto') +} + +const isAncestor = ($el, $maybeAncestor) => { + return $el.parents().index($maybeAncestor) >= 0 +} + +const isSelector = ($el, selector) => { + return $el.is(selector) +} + +const isDetached = ($el) => { + return !isAttached($el) +} + +const isAttached = function ($el) { + // if we're being given window + // then these are automaticallyed attached + if ($window.isWindow($el)) { + // there is a code path when forcing focus and + // blur on the window where this check is necessary. + return true + } + + // if this is a document we can simply check + // whether or not it has a defaultView (window). + // documents which are part of stale pages + // will have this property null'd out + if ($document.isDocument($el)) { + return $document.hasActiveWindow($el) + } + + // normalize into an array + const els = [].concat($jquery.unwrap($el)) + + // we could be passed an empty array here + // which in that case it is not attached + if (els.length === 0) { + return false + } + + // get the document from the first element + const doc = $document.getDocumentFromElement(els[0]) + + // TODO: i guess its possible each element + // is technically bound to a differnet document + // but c'mon + const isIn = (el) => { + return $.contains(doc, el) + } + + // make sure the document is currently + // active (it has a window) and + // make sure every single element + // is attached to this document + return $document.hasActiveWindow(doc) && _.every(els, isIn) +} + +const isSame = function ($el1, $el2) { + const el1 = $jquery.unwrap($el1) + const el2 = $jquery.unwrap($el2) + + return el1 && el2 && _.isEqual(el1, el2) +} + +const isTextLike = function ($el) { + const sel = (selector) => { + return isSelector($el, selector) + } + const type = (type) => { + return isType($el, type) + } + + const isContentEditableElement = isContentEditable($el.get(0)) + + return _.some([ + isContentEditableElement, + sel('textarea'), + sel(':text'), + type('text'), + type('password'), + type('email'), + type('number'), + type('date'), + type('week'), + type('month'), + type('time'), + type('datetime'), + type('datetime-local'), + type('search'), + type('url'), + type('tel'), + ]) +} + +const isScrollable = ($el) => { + const checkDocumentElement = (win, documentElement) => { + // Check if body height is higher than window height + if (win.innerHeight < documentElement.scrollHeight) { + return true + } + + // Check if body width is higher than window width + if (win.innerWidth < documentElement.scrollWidth) { + return true + } + + // else return false since the window is not scrollable + return false + } + + // if we're the window, we want to get the document's + // element and check its size against the actual window + if ($window.isWindow($el)) { + const win = $el + + return checkDocumentElement(win, win.document.documentElement) + } + + // if we're any other element, we do some css calculations + // to see that the overflow is correct and the scroll + // area is larger than the actual height or width + const el = $el[0] + + const { overflow, overflowY, overflowX } = window.getComputedStyle(el) + + // y axis + // if our content height is less than the total scroll height + if (el.clientHeight < el.scrollHeight) { + // and our element has scroll or auto overflow or overflowX + if (isScrollOrAuto(overflow) || isScrollOrAuto(overflowY)) { + return true + } + } + + // x axis + if (el.clientWidth < el.scrollWidth) { + if (isScrollOrAuto(overflow) || isScrollOrAuto(overflowX)) { + return true + } + } + + return false +} + +const isDescendent = ($el1, $el2) => { + if (!$el2) { + return false + } + + return !!(($el1.get(0) === $el2.get(0)) || $el1.has($el2).length) +} + +// in order to simulate actual user behavior we need to do the following: +// 1. take our element and figure out its center coordinate +// 2. check to figure out the element listed at those coordinates +// 3. if this element is ourself or our descendants, click whatever was returned +// 4. else throw an error because something is covering us up +const getFirstFocusableEl = ($el) => { + if (isFocusable($el)) { + return $el + } + + const parent = $el.parent() + + // if we have no parent then just return + // the window since that can receive focus + if (!parent.length) { + const win = $window.getWindowByElement($el.get(0)) + + return $(win) + } + + return getFirstFocusableEl($el.parent()) +} + +const getFirstFixedOrStickyPositionParent = ($el) => { + // return null if we're at body/html + // cuz that means nothing has fixed position + if (!$el || $el.is('body,html')) { + return null + } + + // if we have fixed position return ourselves + if (fixedOrStickyRe.test($el.css('position'))) { + return $el + } + + // else recursively continue to walk up the parent node chain + return getFirstFixedOrStickyPositionParent($el.parent()) +} + +const getFirstStickyPositionParent = ($el) => { + // return null if we're at body/html + // cuz that means nothing has sticky position + if (!$el || $el.is('body,html')) { + return null + } + + // if we have sticky position return ourselves + if ($el.css('position') === 'sticky') { + return $el + } + + // else recursively continue to walk up the parent node chain + return getFirstStickyPositionParent($el.parent()) +} + +const getFirstScrollableParent = ($el) => { + // this may be null or not even defined in IE + // scrollingElement = doc.scrollingElement + + const search = ($el) => { + const $parent = $el.parent() + + // we have no more parents + if (!($parent || $parent.length)) { + return null + } + + // we match the scrollingElement + // if $parent[0] is scrollingElement + // return $parent + + // instead of fussing with scrollingElement + // we'll simply return null here and let our + // caller deal with situations where they're + // needing to scroll the window or scrollableElement + if ($parent.is('html,body') || $document.isDocument($parent)) { + return null + } + + if (isScrollable($parent)) { + return $parent + } + + return search($parent) + } + + return search($el) +} + +const getElements = ($el) => { + // bail if no $el or length + if (!_.get($el, 'length')) { + return + } + + // unroll the jquery object + const els = $jquery.unwrap($el) + + if (els.length === 1) { + return els[0] + } + + return els + +} + +const getContainsSelector = (text, filter = '') => { + const escapedText = $utils.escapeQuotes(text) + + return `${filter}:not(script):contains('${escapedText}'), ${filter}[type='submit'][value~='${escapedText}']` +} + +const priorityElement = 'input[type=\'submit\'], button, a, label' + +const getFirstDeepestElement = (elements, index = 0) => { + // iterate through all of the elements in pairs + // and check if the next item in the array is a + // descedent of the current. if it is continue + // to recurse. if not, or there is no next item + // then return the current + const $current = elements.slice(index, index + 1) + const $next = elements.slice(index + 1, index + 2) + + if (!$next) { + return $current + } + + // does current contain next? + if ($.contains($current.get(0), $next.get(0))) { + return getFirstDeepestElement(elements, index + 1) + } + + // return the current if it already is a priority element + if ($current.is(priorityElement)) { + return $current + } + + // else once we find the first deepest element then return its priority + // parent if it has one and it exists in the elements chain + const $priorities = elements.filter($current.parents(priorityElement)) + + if ($priorities.length) { + return $priorities.last() + } + + return $current + +} + +// short form css-inlines the element +// long form returns the outerHTML +const stringify = (el, form = 'long') => { + // if we are formatting the window object + if ($window.isWindow(el)) { + return '' + } + + // if we are formatting the document object + if ($document.isDocument(el)) { + return '' + } + + // convert this to jquery if its not already one + const $el = $jquery.wrap(el) + + const long = () => { + const str = $el.clone().empty().prop('outerHTML') + const text = _.chain($el.text()).clean().truncate({ length: 10 }).value() + const children = $el.children().length + + if (children) { + return str.replace('>...${text} { + const id = $el.prop('id') + const klass = $el.attr('class') + let str = $el.prop('tagName').toLowerCase() + + if (id) { + str += `#${id}` + } + + // using attr here instead of class because + // svg's return an SVGAnimatedString object + // instead of a normal string when calling + // the property 'class' + if (klass) { + str += `.${klass.split(/\s+/).join('.')}` + } + + // if we have more than one element, + // format it so that the user can see there's more + if ($el.length > 1) { + return `[ <${str}>, ${$el.length - 1} more... ]` + } + + return `<${str}>` + } + + return $utils.switchCase(form, { + long, + short, + }) +} + +module.exports = { + isElement, + + isSelector, + + isScrollOrAuto, + + isFocusable, + + isAttached, + + isDetached, + + isAncestor, + + isScrollable, + + isTextLike, + + isDescendent, + + isContentEditable, + + isSame, + + isBody, + + isInput, + + isTextarea, + + isType, + + isNeedSingleValueChangeInputElement, + + canSetSelectionRangeElement, + + stringify, + + getNativeProp, + + setNativeProp, + + callNativeMethod, + + tryCallNativeMethod, + + getElements, + + getFirstFocusableEl, + + getContainsSelector, + + getFirstDeepestElement, + + getFirstFixedOrStickyPositionParent, + + getFirstStickyPositionParent, + + getFirstScrollableParent, +} diff --git a/packages/driver/src/dom/index.coffee b/packages/driver/src/dom/index.coffee deleted file mode 100644 index 636bc3f6e0d7..000000000000 --- a/packages/driver/src/dom/index.coffee +++ /dev/null @@ -1,88 +0,0 @@ -$jquery = require("./jquery") -$window = require("./window") -$document = require("./document") -$elements = require("./elements") -$visibility = require("./visibility") -$coordinates = require("./coordinates") - -{ isWindow, getWindowByElement } = $window -{ isDocument } = $document -{ wrap, unwrap, isJquery, query } = $jquery -{ isVisible, isHidden, getReasonIsHidden } = $visibility -{ isType, isFocusable, isElement, isScrollable, stringify, getElements, getContainsSelector, getFirstDeepestElement, isDetached, isAttached, isTextLike, isSelector, isDescendent, getFirstFixedOrStickyPositionParent, getFirstStickyPositionParent, getFirstScrollableParent } = $elements -{ getCoordsByPosition, getElementPositioning, getElementCoordinatesByPosition, getElementAtPointFromViewport, getElementCoordinatesByPositionRelativeToXY } = $coordinates - -isDom = (obj) -> - isElement(obj) or isWindow(obj) or isDocument(obj) - -## we are exposing these publicly to be used -## by our own internal code, but also for -## our users. They can use them for debugging -## purposes or for overriding. Everything else -## can be tucked away behind these interfaces. -module.exports = { - wrap - - query - - unwrap - - isDom - - isType - - isVisible - - isHidden - - isFocusable - - isTextLike - - isScrollable - - isDetached - - isAttached - - isSelector - - isDescendent - - isElement - - isDocument - - isWindow - - isJquery - - stringify - - getElements - - getContainsSelector - - getFirstDeepestElement - - getWindowByElement - - getReasonIsHidden - - getFirstScrollableParent - - getFirstFixedOrStickyPositionParent - - getFirstStickyPositionParent - - getCoordsByPosition - - getElementPositioning - - getElementAtPointFromViewport - - getElementCoordinatesByPosition - - getElementCoordinatesByPositionRelativeToXY - -} diff --git a/packages/driver/src/dom/index.js b/packages/driver/src/dom/index.js new file mode 100644 index 000000000000..6fd60c8035c4 --- /dev/null +++ b/packages/driver/src/dom/index.js @@ -0,0 +1,89 @@ +const $jquery = require('./jquery') +const $window = require('./window') +const $document = require('./document') +const $elements = require('./elements') +const $visibility = require('./visibility') +const $coordinates = require('./coordinates') + +const { isWindow, getWindowByElement } = $window +const { isDocument } = $document +const { wrap, unwrap, isJquery, query } = $jquery +const { isVisible, isHidden, getReasonIsHidden } = $visibility +const { isType, isFocusable, isElement, isScrollable, stringify, getElements, getContainsSelector, getFirstDeepestElement, isDetached, isAttached, isTextLike, isSelector, isDescendent, getFirstFixedOrStickyPositionParent, getFirstStickyPositionParent, getFirstScrollableParent } = $elements +const { getCoordsByPosition, getElementPositioning, getElementCoordinatesByPosition, getElementAtPointFromViewport, getElementCoordinatesByPositionRelativeToXY } = $coordinates + +const isDom = (obj) => { + return isElement(obj) || isWindow(obj) || isDocument(obj) +} + +// we are exposing these publicly to be used +// by our own internal code, but also for +// our users. They can use them for debugging +// purposes or for overriding. Everything else +// can be tucked away behind these interfaces. +module.exports = { + wrap, + + query, + + unwrap, + + isDom, + + isType, + + isVisible, + + isHidden, + + isFocusable, + + isTextLike, + + isScrollable, + + isDetached, + + isAttached, + + isSelector, + + isDescendent, + + isElement, + + isDocument, + + isWindow, + + isJquery, + + stringify, + + getElements, + + getContainsSelector, + + getFirstDeepestElement, + + getWindowByElement, + + getReasonIsHidden, + + getFirstScrollableParent, + + getFirstFixedOrStickyPositionParent, + + getFirstStickyPositionParent, + + getCoordsByPosition, + + getElementPositioning, + + getElementAtPointFromViewport, + + getElementCoordinatesByPosition, + + getElementCoordinatesByPositionRelativeToXY, + +} diff --git a/packages/driver/src/dom/jquery.coffee b/packages/driver/src/dom/jquery.coffee deleted file mode 100644 index 7173df102150..000000000000 --- a/packages/driver/src/dom/jquery.coffee +++ /dev/null @@ -1,34 +0,0 @@ -$ = require("jquery") -_ = require("lodash") - -## wrap the object in jquery -wrap = (obj) -> - $(obj) - -query = (selector, context) -> - new $.fn.init(selector, context) - -## pull out the raw elements if this is wrapped -unwrap = (obj) -> - if isJquery(obj) - ## return an array of elements - obj.toArray() - else - obj - -isJquery = (obj) -> - ## does it have the jquery property and is the - ## constructor a function? - !!(obj and obj.jquery and _.isFunction(obj.constructor)) - -## doing a little jiggle wiggle here -## to avoid circular dependencies -module.exports = { - wrap - - query - - unwrap - - isJquery -} diff --git a/packages/driver/src/dom/jquery.js b/packages/driver/src/dom/jquery.js new file mode 100644 index 000000000000..b1730fb5cc4c --- /dev/null +++ b/packages/driver/src/dom/jquery.js @@ -0,0 +1,40 @@ +const $ = require('jquery') +const _ = require('lodash') + +// wrap the object in jquery +const wrap = (obj) => { + return $(obj) +} + +const query = (selector, context) => { + return new $.fn.init(selector, context) +} + +// pull out the raw elements if this is wrapped +const unwrap = function (obj) { + if (isJquery(obj)) { + // return an array of elements + return obj.toArray() + } + + return obj + +} + +const isJquery = (obj) => { + // does it have the jquery property and is the + // constructor a function? + return !!(obj && obj.jquery && _.isFunction(obj.constructor)) +} + +// doing a little jiggle wiggle here +// to avoid circular dependencies +module.exports = { + wrap, + + query, + + unwrap, + + isJquery, +} diff --git a/packages/driver/src/dom/selection.coffee b/packages/driver/src/dom/selection.coffee deleted file mode 100644 index d8b167564c50..000000000000 --- a/packages/driver/src/dom/selection.coffee +++ /dev/null @@ -1,471 +0,0 @@ -$document = require("./document") -$elements = require("./elements") -$ = require("jquery") - -INTERNAL_STATE = "__Cypress_state__" - -_getSelectionBoundsFromTextarea = (el) -> - { - start: $elements.getNativeProp(el, "selectionStart") - end: $elements.getNativeProp(el, "selectionEnd") - } - -_getSelectionBoundsFromInput = (el) -> - if $elements.canSetSelectionRangeElement(el) - return { - start: $elements.getNativeProp(el, "selectionStart") - end: $elements.getNativeProp(el, "selectionEnd") - } - - if internalState = el[INTERNAL_STATE] - return { - start: internalState.start - end: internalState.end - } - - return { - start: 0 - end: 0 - } - -_getSelectionBoundsFromContentEditable = (el) -> - doc = $document.getDocumentFromElement(el) - - if doc.getSelection - ## global selection object - sel = doc.getSelection() - ## selection has at least one range (most always 1; only 0 at page load) - if sel.rangeCount - ## get the first (usually only) range obj - range = sel.getRangeAt(0) - - ## if div[contenteditable] > text - hostContenteditable = _getHostContenteditable(range.commonAncestorContainer) - if hostContenteditable is el - return { - start: range.startOffset - end: range.endOffset - } - - return { - start: null - end: null - } - - ## TODO get ACTUAL caret position in contenteditable, not line - -_replaceSelectionContentsContentEditable = (el, text) -> - doc = $document.getDocumentFromElement(el) - ## NOTE: insertText will also handle '\n', and render newlines - $elements.callNativeMethod(doc, "execCommand", "insertText", true, text) - return - - ## Keeping around native implementation - ## for same reasons as listed below - ## - # if text is "\n" - # return _insertNewlineIntoContentEditable(el) - # doc = $document.getDocumentFromElement(el) - # range = _getSelectionRangeByEl(el) - # ## delete anything in the selection - # startNode = range.startContainer - # range.deleteContents() - # newTextNode - # if text is ' ' - # newTextNode = doc.createElement('p') - # else - # newTextNode = doc.createTextNode(text) - # if $elements.isElement(startNode) - # if startNode.firstChild?.tagName is "BR" - # range.selectNode(startNode.firstChild) - # range.deleteContents() - # ## else startNode is el, so just insert the node - # startNode.appendChild(newTextNode) - # if text is ' ' - # newTextNode.outerHTML = ' ' - # range.selectNodeContents(startNode.lastChild) - # range.collapse() - # return - # else - # # nodeOffset = range.startOffset - # # oldValue = startNode.nodeValue || "" - # range.insertNode(newTextNode) - # range.selectNodeContents(newTextNode) - # range.collapse() - # if text is ' ' - # newTextNode.outerHTML = ' ' - # # updatedValue = _insertSubstring(oldValue, text, [nodeOffset, nodeOffset]) - # # newNodeOffset = nodeOffset + text.length - # # startNode.nodeValue = updatedValue - # el.normalize() - -_insertSubstring = (curText, newText, [start, end]) -> - curText.substring(0, start) + newText + curText.substring(end) - -_getHostContenteditable = (el) -> - curEl = el - - while curEl.parentElement && !$elements.tryCallNativeMethod(curEl, "getAttribute", "contenteditable") - curEl = curEl.parentElement - ## if there's no host contenteditable, we must be in designmode - ## so act as if the original element is the host contenteditable - ## TODO: remove this when we no longer click before type and move - ## cursor to the end - if !$elements.callNativeMethod(curEl, "getAttribute", "contenteditable") - return el - - return curEl - -_getInnerLastChild = (el) -> - while (el.lastChild) - el = el.lastChild - - return el - -_getSelectionByEl = (el) -> - doc = $document.getDocumentFromElement(el) - doc.getSelection() - -_getSelectionRangeByEl = (el) -> - sel = _getSelectionByEl(el) - if sel.rangeCount > 0 - sel.getRangeAt(0) - else throw new Error("No selection in document") - -deleteSelectionContents = (el) -> - if $elements.isContentEditable(el) - doc = $document.getDocumentFromElement(el) - $elements.callNativeMethod(doc, "execCommand", 'delete', false, null) - return - - ## for input and textarea, update selected text with empty string - replaceSelectionContents(el, "") - -setSelectionRange = (el, start, end) -> - - if $elements.canSetSelectionRangeElement(el) - $elements.callNativeMethod(el, "setSelectionRange", start, end) - return - - ## NOTE: Some input elements have mobile implementations - ## and thus may not always have a cursor, so calling setSelectionRange will throw. - ## we are assuming desktop here, so we store our own internal state. - - el[INTERNAL_STATE] = { - start - end - } - - return - -deleteRightOfCursor = (el) -> - if $elements.isTextarea(el) || $elements.isInput(el) - {start, end} = getSelectionBounds(el) - - if start is $elements.getNativeProp(el, "value").length - ## nothing to delete, nothing to right of selection - return false - - setSelectionRange(el, start, end + 1) - deleteSelectionContents(el) - ## successful delete - return true - - if $elements.isContentEditable(el) - selection = _getSelectionByEl(el) - $elements.callNativeMethod(selection, "modify", "extend", "forward", "character") - - if $elements.getNativeProp(selection, "isCollapsed") - ## there's nothing to delete - return false - - deleteSelectionContents(el) - ## successful delete - return true - -deleteLeftOfCursor = (el) -> - if $elements.isTextarea(el) || $elements.isInput(el) - {start, end} = getSelectionBounds(el) - - if start is 0 - ## there's nothing to delete, nothing before cursor - return false - - setSelectionRange(el, start - 1, end) - deleteSelectionContents(el) - ## successful delete - return true - - if $elements.isContentEditable(el) - ## there is no 'backwardDelete' command for execCommand, so use the Selection API - selection = _getSelectionByEl(el) - $elements.callNativeMethod(selection, "modify", "extend", "backward", "character") - - if selection.isCollapsed - ## there's nothing to delete - ## since extending the selection didn't do anything - return false - - deleteSelectionContents(el) - ## successful delete - return true - -_collapseInputOrTextArea = (el, toIndex) -> - setSelectionRange(el, toIndex, toIndex) - -moveCursorLeft = (el) -> - if $elements.isTextarea(el) || $elements.isInput(el) - {start, end} = getSelectionBounds(el) - - if start isnt end - return _collapseInputOrTextArea(el, start) - - if start is 0 - return - - return setSelectionRange(el, start - 1, start - 1) - - if $elements.isContentEditable(el) - selection = _getSelectionByEl(el) - $elements.callNativeMethod(selection, "modify", "move", "backward", "character") - - ## Keeping around native implementation - ## for same reasons as listed below - ## - # range = _getSelectionRangeByEl(el) - # if !range.collapsed - # return range.collapse(true) - # if range.startOffset is 0 - # return _contenteditableMoveToEndOfPrevLine(el) - # newOffset = range.startOffset - 1 - # range.setStart(range.startContainer, newOffset) - # range.setEnd(range.startContainer, newOffset) - -moveCursorRight = (el) -> - if $elements.isTextarea(el) || $elements.isInput(el) - {start, end} = getSelectionBounds(el) - if start isnt end - return _collapseInputOrTextArea(el, end) - - ## Don't worry about moving past the end of the string - ## nothing will happen and there is no error. - return setSelectionRange(el, start + 1, end + 1) - - if $elements.isContentEditable(el) - selection = _getSelectionByEl(el) - $elements.callNativeMethod(selection, "modify", "move", "forward", "character") - -moveCursorUp = (el) -> - _moveCursorUpOrDown(el, true) - -moveCursorDown = (el) -> - _moveCursorUpOrDown(el, false) - -_moveCursorUpOrDown = (el, up) -> - if $elements.isInput(el) - ## on an input, instead of moving the cursor - ## we want to perform the native browser action - ## which is to increment the step/interval - if $elements.isType(el, "number") - if up then el.stepUp?() else el.stepDown?() - return - - if $elements.isTextarea(el) || $elements.isContentEditable(el) - selection = _getSelectionByEl(el) - $elements.callNativeMethod(selection, "modify", - "move" - if up then "backward" else "forward" - "line" - ) - -isCollapsed = (el) -> - if $elements.isTextarea(el) || $elements.isInput(el) - {start, end} = getSelectionBounds(el) - return start is end - - if $elements.isContentEditable(el) - selection = _getSelectionByEl(el) - return selection.isCollapsed - -selectAll = (el) -> - if $elements.isTextarea(el) || $elements.isInput(el) - setSelectionRange(el, 0, $elements.getNativeProp(el, "value").length) - return - - if $elements.isContentEditable(el) - doc = $document.getDocumentFromElement(el) - $elements.callNativeMethod(doc, 'execCommand', 'selectAll', false, null) - ## Keeping around native implementation - ## for same reasons as listed below - ## - # range = _getSelectionRangeByEl(el) - # range.selectNodeContents(el) - # range.deleteContents() - # return - # startTextNode = _getFirstTextNode(el.firstChild) - # endTextNode = _getInnerLastChild(el.lastChild) - # range.setStart(startTextNode, 0) - # range.setEnd(endTextNode, endTextNode.length) - -getSelectionBounds = (el) -> - ## this function works for input, textareas, and contentEditables - switch - when $elements.isInput(el) - _getSelectionBoundsFromInput(el) - when $elements.isTextarea(el) - _getSelectionBoundsFromTextarea(el) - when $elements.isContentEditable(el) - _getSelectionBoundsFromContentEditable(el) - else - { - start: null - end: null - } - -moveSelectionToEnd = (el) -> - if $elements.isInput(el) || $elements.isTextarea(el) - length = $elements.getNativeProp(el, "value").length - setSelectionRange(el, length, length) - - else if $elements.isContentEditable(el) - ## NOTE: can't use execCommand API here because we would have - ## to selectAll and then collapse so we use the Selection API - doc = $document.getDocumentFromElement(el) - range = $elements.callNativeMethod(doc, "createRange") - hostContenteditable = _getHostContenteditable(el) - lastTextNode = _getInnerLastChild(hostContenteditable) - - if lastTextNode.tagName is "BR" - lastTextNode = lastTextNode.parentNode - - range.setStart(lastTextNode, lastTextNode.length) - range.setEnd(lastTextNode, lastTextNode.length) - - sel = $elements.callNativeMethod(doc, "getSelection") - $elements.callNativeMethod(sel, "removeAllRanges") - $elements.callNativeMethod(sel, "addRange", range) - -## TODO: think about renaming this -replaceSelectionContents = (el, key) -> - if $elements.isContentEditable(el) - return _replaceSelectionContentsContentEditable(el, key) - - if $elements.isInput(el) or $elements.isTextarea(el) - { start, end } = getSelectionBounds(el) - - value = $elements.getNativeProp(el, "value") or "" - updatedValue = _insertSubstring(value, key, [start, end]) - - $elements.setNativeProp(el, "value", updatedValue) - - setSelectionRange(el, start + key.length, start + key.length) - -getCaretPosition = (el) -> - bounds = getSelectionBounds(el) - - if !bounds.start? - ## no selection - return null - - if bounds.start is bounds.end - return bounds.start - - return null - -interceptSelect = -> - if $elements.isInput(this) and !$elements.canSetSelectionRangeElement(this) - setSelectionRange(this, 0, $elements.getNativeProp(this, "value").length) - - $elements.callNativeMethod(this, "select") - -## Selection API implementation of insert newline. -## Worth keeping around if we ever have to insert native -## newlines if we are trying to support a browser or -## environment without the document.execCommand('insertText', etc...) -## -# _insertNewlineIntoContentEditable = (el) -> -# selection = _getSelectionByEl(el) -# selection.deleteFromDocument() -# $elements.callNativeMethod(selection, "modify", "extend", "forward", "lineboundary") -# range = selection.getRangeAt(0) -# clonedElements = range.cloneContents() -# selection.deleteFromDocument() -# elementToInsertAfter -# if range.startContainer is el -# elementToInsertAfter = _getInnerLastChild(el) -# else -# curEl = range.startContainer -# ## make sure we have firstLevel child element from contentEditable -# while (curEl.parentElement && curEl.parentElement isnt el ) -# curEl = curEl.parentElement -# elementToInsertAfter = curEl -# range = _getSelectionRangeByEl(el) -# outerNewElement = '
' -# ## TODO: In contenteditables, should insert newline element as either
or

depending on existing nodes -# ## but this shouldn't really matter that much, so ignore for now -# # if elementToInsertAfter.tagName is 'P' -# # typeOfNewElement = '

' -# $newElement = $(outerNewElement).append(clonedElements) -# $newElement.insertAfter(elementToInsertAfter) -# newElement = $newElement.get(0) -# if !newElement.innerHTML -# newElement.innerHTML = '
' -# range.selectNodeContents(newElement) -# else -# newTextNode = _getFirstTextNode(newElement) -# range.selectNodeContents(newTextNode) -# range.collapse(true) - -# _contenteditableMoveToEndOfPrevLine = (el) -> -# bounds = _contenteditableGetNodesAround(el) -# if bounds.prev -# range = _getSelectionRangeByEl(el) -# prevTextNode = _getInnerLastChild(bounds.prev) -# range.setStart(prevTextNode, prevTextNode.length) -# range.setEnd(prevTextNode, prevTextNode.length) - -# _contenteditableMoveToStartOfNextLine = (el) -> -# bounds = _contenteditableGetNodesAround(el) -# if bounds.next -# range = _getSelectionRangeByEl(el) -# nextTextNode = _getFirstTextNode(bounds.next) -# range.setStart(nextTextNode, 1) -# range.setEnd(nextTextNode, 1) - -# _contenteditableGetNodesAround = (el) -> -# range = _getSelectionRangeByEl(el) -# textNode = range.startContainer -# curEl = textNode -# while curEl && !curEl.nextSibling? -# curEl = curEl.parentNode -# nextTextNode = _getFirstTextNode(curEl.nextSibling) -# curEl = textNode -# while curEl && !curEl.previousSibling? -# curEl = curEl.parentNode -# prevTextNode = _getInnerLastChild(curEl.previousSibling) -# { -# prev: prevTextNode -# next: nextTextNode -# } - -# _getFirstTextNode = (el) -> -# while (el.firstChild) -# el = el.firstChild -# return el - -module.exports = { - getSelectionBounds - deleteRightOfCursor - deleteLeftOfCursor - selectAll - deleteSelectionContents - moveSelectionToEnd - getCaretPosition - moveCursorLeft - moveCursorRight - moveCursorUp - moveCursorDown - replaceSelectionContents - isCollapsed - interceptSelect -} diff --git a/packages/driver/src/dom/selection.js b/packages/driver/src/dom/selection.js new file mode 100644 index 000000000000..bd1e47b7af2a --- /dev/null +++ b/packages/driver/src/dom/selection.js @@ -0,0 +1,585 @@ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const $document = require('./document') +const $elements = require('./elements') + +const INTERNAL_STATE = '__Cypress_state__' + +const _getSelectionBoundsFromTextarea = (el) => { + return { + start: $elements.getNativeProp(el, 'selectionStart'), + end: $elements.getNativeProp(el, 'selectionEnd'), + } +} + +const _getSelectionBoundsFromInput = function (el) { + if ($elements.canSetSelectionRangeElement(el)) { + return { + start: $elements.getNativeProp(el, 'selectionStart'), + end: $elements.getNativeProp(el, 'selectionEnd'), + } + } + + const internalState = el[INTERNAL_STATE] + + if (internalState) { + return { + start: internalState.start, + end: internalState.end, + } + } + + return { + start: 0, + end: 0, + } +} + +const _getSelectionBoundsFromContentEditable = function (el) { + const doc = $document.getDocumentFromElement(el) + + if (doc.getSelection) { + //# global selection object + const sel = doc.getSelection() + + //# selection has at least one range (most always 1; only 0 at page load) + if (sel.rangeCount) { + //# get the first (usually only) range obj + const range = sel.getRangeAt(0) + + //# if div[contenteditable] > text + const hostContenteditable = _getHostContenteditable(range.commonAncestorContainer) + + if (hostContenteditable === el) { + return { + start: range.startOffset, + end: range.endOffset, + } + } + } + } + + return { + start: null, + end: null, + } +} + +//# TODO get ACTUAL caret position in contenteditable, not line + +const _replaceSelectionContentsContentEditable = function (el, text) { + const doc = $document.getDocumentFromElement(el) + + //# NOTE: insertText will also handle '\n', and render newlines + $elements.callNativeMethod(doc, 'execCommand', 'insertText', true, text) +} + +//# Keeping around native implementation +//# for same reasons as listed below +//# +// if text is "\n" +// return _insertNewlineIntoContentEditable(el) +// doc = $document.getDocumentFromElement(el) +// range = _getSelectionRangeByEl(el) +// ## delete anything in the selection +// startNode = range.startContainer +// range.deleteContents() +// newTextNode +// if text is ' ' +// newTextNode = doc.createElement('p') +// else +// newTextNode = doc.createTextNode(text) +// if $elements.isElement(startNode) +// if startNode.firstChild?.tagName is "BR" +// range.selectNode(startNode.firstChild) +// range.deleteContents() +// ## else startNode is el, so just insert the node +// startNode.appendChild(newTextNode) +// if text is ' ' +// newTextNode.outerHTML = ' ' +// range.selectNodeContents(startNode.lastChild) +// range.collapse() +// return +// else +// # nodeOffset = range.startOffset +// # oldValue = startNode.nodeValue || "" +// range.insertNode(newTextNode) +// range.selectNodeContents(newTextNode) +// range.collapse() +// if text is ' ' +// newTextNode.outerHTML = ' ' +// # updatedValue = _insertSubstring(oldValue, text, [nodeOffset, nodeOffset]) +// # newNodeOffset = nodeOffset + text.length +// # startNode.nodeValue = updatedValue +// el.normalize() + +const _insertSubstring = (curText, newText, [start, end]) => { + return curText.substring(0, start) + newText + curText.substring(end) +} + +const _getHostContenteditable = function (el) { + let curEl = el + + while (curEl.parentElement && !$elements.tryCallNativeMethod(curEl, 'getAttribute', 'contenteditable')) { + curEl = curEl.parentElement + } + + //# if there's no host contenteditable, we must be in designmode + //# so act as if the original element is the host contenteditable + //# TODO: remove this when we no longer click before type and move + //# cursor to the end + if (!$elements.callNativeMethod(curEl, 'getAttribute', 'contenteditable')) { + return el + } + + return curEl +} + +const _getInnerLastChild = function (el) { + while (el.lastChild) { + el = el.lastChild + } + + return el +} + +const _getSelectionByEl = function (el) { + const doc = $document.getDocumentFromElement(el) + + return doc.getSelection() +} + +const deleteSelectionContents = function (el) { + if ($elements.isContentEditable(el)) { + const doc = $document.getDocumentFromElement(el) + + $elements.callNativeMethod(doc, 'execCommand', 'delete', false, null) + + return + } + + //# for input and textarea, update selected text with empty string + return replaceSelectionContents(el, '') +} + +const setSelectionRange = function (el, start, end) { + + if ($elements.canSetSelectionRangeElement(el)) { + $elements.callNativeMethod(el, 'setSelectionRange', start, end) + + return + } + + //# NOTE: Some input elements have mobile implementations + //# and thus may not always have a cursor, so calling setSelectionRange will throw. + //# we are assuming desktop here, so we store our own internal state. + + el[INTERNAL_STATE] = { + start, + end, + } + +} + +const deleteRightOfCursor = function (el) { + if ($elements.isTextarea(el) || $elements.isInput(el)) { + const { start, end } = getSelectionBounds(el) + + if (start === $elements.getNativeProp(el, 'value').length) { + //# nothing to delete, nothing to right of selection + return false + } + + setSelectionRange(el, start, end + 1) + deleteSelectionContents(el) + + //# successful delete + return true + } + + if ($elements.isContentEditable(el)) { + const selection = _getSelectionByEl(el) + + $elements.callNativeMethod(selection, 'modify', 'extend', 'forward', 'character') + + if ($elements.getNativeProp(selection, 'isCollapsed')) { + //# there's nothing to delete + return false + } + + deleteSelectionContents(el) + + //# successful delete + return true + } +} + +const deleteLeftOfCursor = function (el) { + if ($elements.isTextarea(el) || $elements.isInput(el)) { + const { start, end } = getSelectionBounds(el) + + if (start === 0) { + //# there's nothing to delete, nothing before cursor + return false + } + + setSelectionRange(el, start - 1, end) + deleteSelectionContents(el) + + //# successful delete + return true + } + + if ($elements.isContentEditable(el)) { + //# there is no 'backwardDelete' command for execCommand, so use the Selection API + const selection = _getSelectionByEl(el) + + $elements.callNativeMethod(selection, 'modify', 'extend', 'backward', 'character') + + if (selection.isCollapsed) { + //# there's nothing to delete + //# since extending the selection didn't do anything + return false + } + + deleteSelectionContents(el) + + //# successful delete + return true + } +} + +const _collapseInputOrTextArea = (el, toIndex) => { + return setSelectionRange(el, toIndex, toIndex) +} + +const moveCursorLeft = function (el) { + if ($elements.isTextarea(el) || $elements.isInput(el)) { + const { start, end } = getSelectionBounds(el) + + if (start !== end) { + return _collapseInputOrTextArea(el, start) + } + + if (start === 0) { + return + } + + return setSelectionRange(el, start - 1, start - 1) + } + + if ($elements.isContentEditable(el)) { + const selection = _getSelectionByEl(el) + + return $elements.callNativeMethod(selection, 'modify', 'move', 'backward', 'character') + } +} + +// const _getSelectionRangeByEl = function (el) { +// const sel = _getSelectionByEl(el) + +// if (sel.rangeCount > 0) { +// return sel.getRangeAt(0) +// } + +// throw new Error('No selection in document') +// } + +//# Keeping around native implementation +//# for same reasons as listed below +//# +// range = _getSelectionRangeByEl(el) +// if !range.collapsed +// return range.collapse(true) +// if range.startOffset is 0 +// return _contenteditableMoveToEndOfPrevLine(el) +// newOffset = range.startOffset - 1 +// range.setStart(range.startContainer, newOffset) +// range.setEnd(range.startContainer, newOffset) + +const moveCursorRight = function (el) { + if ($elements.isTextarea(el) || $elements.isInput(el)) { + const { start, end } = getSelectionBounds(el) + + if (start !== end) { + return _collapseInputOrTextArea(el, end) + } + + //# Don't worry about moving past the end of the string + //# nothing will happen and there is no error. + return setSelectionRange(el, start + 1, end + 1) + } + + if ($elements.isContentEditable(el)) { + const selection = _getSelectionByEl(el) + + return $elements.callNativeMethod(selection, 'modify', 'move', 'forward', 'character') + } +} + +const moveCursorUp = (el) => { + return _moveCursorUpOrDown(el, true) +} + +const moveCursorDown = (el) => { + return _moveCursorUpOrDown(el, false) +} + +const _moveCursorUpOrDown = function (el, up) { + if ($elements.isInput(el)) { + //# on an input, instead of moving the cursor + //# we want to perform the native browser action + //# which is to increment the step/interval + if ($elements.isType(el, 'number')) { + if (up) { + if (typeof el.stepUp === 'function') { + el.stepUp() + } + } else { + if (typeof el.stepDown === 'function') { + el.stepDown() + } + } + } + + return + } + + if ($elements.isTextarea(el) || $elements.isContentEditable(el)) { + const selection = _getSelectionByEl(el) + + return $elements.callNativeMethod(selection, 'modify', + 'move', + up ? 'backward' : 'forward', + 'line' + ) + } +} + +const isCollapsed = function (el) { + if ($elements.isTextarea(el) || $elements.isInput(el)) { + const { start, end } = getSelectionBounds(el) + + return start === end + } + + if ($elements.isContentEditable(el)) { + const selection = _getSelectionByEl(el) + + return selection.isCollapsed + } +} + +const selectAll = function (el) { + if ($elements.isTextarea(el) || $elements.isInput(el)) { + setSelectionRange(el, 0, $elements.getNativeProp(el, 'value').length) + + return + } + + if ($elements.isContentEditable(el)) { + const doc = $document.getDocumentFromElement(el) + + return $elements.callNativeMethod(doc, 'execCommand', 'selectAll', false, null) + } +} +//# Keeping around native implementation +//# for same reasons as listed below +//# +// range = _getSelectionRangeByEl(el) +// range.selectNodeContents(el) +// range.deleteContents() +// return +// startTextNode = _getFirstTextNode(el.firstChild) +// endTextNode = _getInnerLastChild(el.lastChild) +// range.setStart(startTextNode, 0) +// range.setEnd(endTextNode, endTextNode.length) + +const getSelectionBounds = function (el) { + //# this function works for input, textareas, and contentEditables + switch (false) { + case !$elements.isInput(el): + return _getSelectionBoundsFromInput(el) + case !$elements.isTextarea(el): + return _getSelectionBoundsFromTextarea(el) + case !$elements.isContentEditable(el): + return _getSelectionBoundsFromContentEditable(el) + default: + return { + start: null, + end: null, + } + } +} + +const moveSelectionToEnd = function (el) { + let length + + if ($elements.isInput(el) || $elements.isTextarea(el)) { + ({ length } = $elements.getNativeProp(el, 'value')) + + return setSelectionRange(el, length, length) + + } + + if ($elements.isContentEditable(el)) { + //# NOTE: can't use execCommand API here because we would have + //# to selectAll and then collapse so we use the Selection API + const doc = $document.getDocumentFromElement(el) + const range = $elements.callNativeMethod(doc, 'createRange') + const hostContenteditable = _getHostContenteditable(el) + let lastTextNode = _getInnerLastChild(hostContenteditable) + + if (lastTextNode.tagName === 'BR') { + lastTextNode = lastTextNode.parentNode + } + + range.setStart(lastTextNode, lastTextNode.length) + range.setEnd(lastTextNode, lastTextNode.length) + + const sel = $elements.callNativeMethod(doc, 'getSelection') + + $elements.callNativeMethod(sel, 'removeAllRanges') + + return $elements.callNativeMethod(sel, 'addRange', range) + } +} + +//# TODO: think about renaming this +const replaceSelectionContents = function (el, key) { + if ($elements.isContentEditable(el)) { + return _replaceSelectionContentsContentEditable(el, key) + } + + if ($elements.isInput(el) || $elements.isTextarea(el)) { + const { start, end } = getSelectionBounds(el) + + const value = $elements.getNativeProp(el, 'value') || '' + const updatedValue = _insertSubstring(value, key, [start, end]) + + $elements.setNativeProp(el, 'value', updatedValue) + + return setSelectionRange(el, start + key.length, start + key.length) + } +} + +const getCaretPosition = function (el) { + const bounds = getSelectionBounds(el) + + if ((bounds.start == null)) { + //# no selection + return null + } + + if (bounds.start === bounds.end) { + return bounds.start + } + + return null +} + +const interceptSelect = function () { + if ($elements.isInput(this) && !$elements.canSetSelectionRangeElement(this)) { + setSelectionRange(this, 0, $elements.getNativeProp(this, 'value').length) + } + + return $elements.callNativeMethod(this, 'select') +} + +//# Selection API implementation of insert newline. +//# Worth keeping around if we ever have to insert native +//# newlines if we are trying to support a browser or +//# environment without the document.execCommand('insertText', etc...) +//# +// _insertNewlineIntoContentEditable = (el) -> +// selection = _getSelectionByEl(el) +// selection.deleteFromDocument() +// $elements.callNativeMethod(selection, "modify", "extend", "forward", "lineboundary") +// range = selection.getRangeAt(0) +// clonedElements = range.cloneContents() +// selection.deleteFromDocument() +// elementToInsertAfter +// if range.startContainer is el +// elementToInsertAfter = _getInnerLastChild(el) +// else +// curEl = range.startContainer +// ## make sure we have firstLevel child element from contentEditable +// while (curEl.parentElement && curEl.parentElement isnt el ) +// curEl = curEl.parentElement +// elementToInsertAfter = curEl +// range = _getSelectionRangeByEl(el) +// outerNewElement = '
' +// ## TODO: In contenteditables, should insert newline element as either
or

depending on existing nodes +// ## but this shouldn't really matter that much, so ignore for now +// # if elementToInsertAfter.tagName is 'P' +// # typeOfNewElement = '

' +// $newElement = $(outerNewElement).append(clonedElements) +// $newElement.insertAfter(elementToInsertAfter) +// newElement = $newElement.get(0) +// if !newElement.innerHTML +// newElement.innerHTML = '
' +// range.selectNodeContents(newElement) +// else +// newTextNode = _getFirstTextNode(newElement) +// range.selectNodeContents(newTextNode) +// range.collapse(true) + +// _contenteditableMoveToEndOfPrevLine = (el) -> +// bounds = _contenteditableGetNodesAround(el) +// if bounds.prev +// range = _getSelectionRangeByEl(el) +// prevTextNode = _getInnerLastChild(bounds.prev) +// range.setStart(prevTextNode, prevTextNode.length) +// range.setEnd(prevTextNode, prevTextNode.length) + +// _contenteditableMoveToStartOfNextLine = (el) -> +// bounds = _contenteditableGetNodesAround(el) +// if bounds.next +// range = _getSelectionRangeByEl(el) +// nextTextNode = _getFirstTextNode(bounds.next) +// range.setStart(nextTextNode, 1) +// range.setEnd(nextTextNode, 1) + +// _contenteditableGetNodesAround = (el) -> +// range = _getSelectionRangeByEl(el) +// textNode = range.startContainer +// curEl = textNode +// while curEl && !curEl.nextSibling? +// curEl = curEl.parentNode +// nextTextNode = _getFirstTextNode(curEl.nextSibling) +// curEl = textNode +// while curEl && !curEl.previousSibling? +// curEl = curEl.parentNode +// prevTextNode = _getInnerLastChild(curEl.previousSibling) +// { +// prev: prevTextNode +// next: nextTextNode +// } + +// _getFirstTextNode = (el) -> +// while (el.firstChild) +// el = el.firstChild +// return el + +module.exports = { + getSelectionBounds, + deleteRightOfCursor, + deleteLeftOfCursor, + selectAll, + deleteSelectionContents, + moveSelectionToEnd, + getCaretPosition, + moveCursorLeft, + moveCursorRight, + moveCursorUp, + moveCursorDown, + replaceSelectionContents, + isCollapsed, + interceptSelect, +} diff --git a/packages/driver/src/dom/visibility.coffee b/packages/driver/src/dom/visibility.coffee deleted file mode 100644 index dd0dc7e58e52..000000000000 --- a/packages/driver/src/dom/visibility.coffee +++ /dev/null @@ -1,289 +0,0 @@ -_ = require("lodash") - -$jquery = require("./jquery") -$document = require("./document") -$elements = require("./elements") -$coordinates = require("./coordinates") - -fixedOrAbsoluteRe = /(fixed|absolute)/ - -OVERFLOW_PROPS = ["hidden", "scroll", "auto"] - -## WARNING: -## developer beware. visibility is a sink hole -## that leads to sheer madness. you should -## avoid this file before its too late. - -isVisible = (el) -> - not isHidden(el, "isVisible()") - -## TODO: we should prob update dom -## to be passed in $utils as a dependency -## because of circular references -isHidden = (el, name) -> - if not $elements.isElement(el) - name ?= "isHidden()" - - throw new Error("Cypress.dom.#{name} must be passed a basic DOM element.") - - $el = $jquery.wrap(el) - - ## in Cypress-land we consider the element hidden if - ## either its offsetHeight or offsetWidth is 0 because - ## it is impossible for the user to interact with this element - ## offsetHeight / offsetWidth includes the ef - elHasNoEffectiveWidthOrHeight($el) or - - ## additionally if the effective visibility of the element - ## is hidden (which includes any parent nodes) then the user - ## cannot interact with this element and thus it is hidden - elHasVisibilityHidden($el) or - - ## we do some calculations taking into account the parents - ## to see if its hidden by a parent - elIsHiddenByAncestors($el) or - - ## if this is a fixed element check if its covered - ( - if elIsFixed($el) - elIsNotElementFromPoint($el) - else - ## else check if el is outside the bounds - ## of its ancestors overflow - elIsOutOfBoundsOfAncestorsOverflow($el) - ) - -elHasNoEffectiveWidthOrHeight = ($el) -> - elOffsetWidth($el) <= 0 or elOffsetHeight($el) <= 0 or $el[0].getClientRects().length <= 0 - -elHasNoOffsetWidthOrHeight = ($el) -> - elOffsetWidth($el) <= 0 or elOffsetHeight($el) <= 0 - -elOffsetWidth = ($el) -> - $el[0].offsetWidth - -elOffsetHeight = ($el) -> - $el[0].offsetHeight - -elHasVisibilityHidden = ($el) -> - $el.css("visibility") is "hidden" - -elHasDisplayNone = ($el) -> - $el.css("display") is "none" - -elHasOverflowHidden = ($el) -> - "hidden" in [$el.css("overflow"), $el.css("overflow-y"), $el.css("overflow-x")] - -elHasPositionRelative = ($el) -> - $el.css("position") is "relative" - -elHasClippableOverflow = ($el) -> - $el.css("overflow") in OVERFLOW_PROPS or - $el.css("overflow-y") in OVERFLOW_PROPS or - $el.css("overflow-x") in OVERFLOW_PROPS - -canClipContent = ($el, $ancestor) -> - ## can't clip without clippable overflow - if not elHasClippableOverflow($ancestor) - return false - - $offsetParent = $jquery.wrap($el[0].offsetParent) - - ## even if overflow is clippable, if an ancestor of the ancestor is the - ## element's offset parent, the ancestor will not clip the element - ## unless the element is position relative - if not elHasPositionRelative($el) and $elements.isAncestor($ancestor, $offsetParent) - return false - - return true - -elIsFixed = ($el) -> - if $stickyOrFixedEl = $elements.getFirstFixedOrStickyPositionParent($el) - $stickyOrFixedEl.css("position") is "fixed" - -elAtCenterPoint = ($el) -> - elProps = $coordinates.getElementPositioning($el) - - { topCenter, leftCenter } = elProps.fromViewport - - doc = $document.getDocumentFromElement($el.get(0)) - - if el = $coordinates.getElementAtPointFromViewport(doc, leftCenter, topCenter) - $jquery.wrap(el) - -elDescendentsHavePositionFixedOrAbsolute = ($parent, $child) -> - ## create an array of all elements between $parent and $child - ## including child but excluding parent - ## and check if these have position fixed|absolute - $els = $child.parentsUntil($parent).add($child) - - _.some $els.get(), (el) -> - fixedOrAbsoluteRe.test $jquery.wrap(el).css("position") - -elIsNotElementFromPoint = ($el) -> - ## if we have a fixed position element that means - ## it is fixed 'relative' to the viewport which means - ## it MUST be available with elementFromPoint because - ## that is also relative to the viewport - $elAtPoint = elAtCenterPoint($el) - - ## if the element at point is not a descendent - ## of our $el then we know it's being covered or its - ## not visible - return not $elements.isDescendent($el, $elAtPoint) - -elIsOutOfBoundsOfAncestorsOverflow = ($el, $ancestor) -> - $ancestor ?= $el.parent() - - ## no ancestor, not out of bounds! - return false if not $ancestor - - ## if we've reached the top parent, which is document - ## then we're in bounds all the way up, return false - return false if $ancestor.is("body,html") or $document.isDocument($ancestor) - - elProps = $coordinates.getElementPositioning($el) - - if canClipContent($el, $ancestor) - ancestorProps = $coordinates.getElementPositioning($ancestor) - - ## target el is out of bounds - return true if ( - ## target el is to the right of the ancestor's visible area - elProps.fromWindow.left > (ancestorProps.width + ancestorProps.fromWindow.left) or - - ## target el is to the left of the ancestor's visible area - (elProps.fromWindow.left + elProps.width) < ancestorProps.fromWindow.left or - - ## target el is under the ancestor's visible area - elProps.fromWindow.top > (ancestorProps.height + ancestorProps.fromWindow.top) or - - ## target el is above the ancestor's visible area - (elProps.fromWindow.top + elProps.height) < ancestorProps.fromWindow.top - ) - - elIsOutOfBoundsOfAncestorsOverflow($el, $ancestor.parent()) - -elIsHiddenByAncestors = ($el, $origEl) -> - ## store the original $el - $origEl ?= $el - - ## walk up to each parent until we reach the body - ## if any parent has an effective offsetHeight of 0 - ## and its set overflow: hidden then our child element - ## is effectively hidden - ## -----UNLESS------ - ## the parent or a descendent has position: absolute|fixed - $parent = $el.parent() - - ## stop if we've reached the body or html - ## in case there is no body - ## or if parent is the document which can - ## happen if we already have an element - return false if $parent.is("body,html") or $document.isDocument($parent) - - if elHasOverflowHidden($parent) and elHasNoEffectiveWidthOrHeight($parent) - ## if any of the elements between the parent and origEl - ## have fixed or position absolute - return not elDescendentsHavePositionFixedOrAbsolute($parent, $origEl) - - ## continue to recursively walk up the chain until we reach body or html - elIsHiddenByAncestors($parent, $origEl) - -parentHasNoOffsetWidthOrHeightAndOverflowHidden = ($el) -> - ## if we've walked all the way up to body or html then return false - return false if not $el.length or $el.is("body,html") - - ## if we have overflow hidden and no effective width or height - if elHasOverflowHidden($el) and elHasNoEffectiveWidthOrHeight($el) - return $el - else - ## continue walking - return parentHasNoOffsetWidthOrHeightAndOverflowHidden($el.parent()) - -parentHasDisplayNone = ($el) -> - ## if we have no $el or we've walked all the way up to document - ## then return false - return false if not $el.length or $document.isDocument($el) - - ## if we have display none then return the $el - if elHasDisplayNone($el) - return $el - else - ## continue walking - return parentHasDisplayNone($el.parent()) - -parentHasVisibilityNone = ($el) -> - ## if we've walked all the way up to document then return false - return false if not $el.length or $document.isDocument($el) - - ## if we have display none then return the $el - if elHasVisibilityHidden($el) - return $el - else - ## continue walking - return parentHasVisibilityNone($el.parent()) - -getReasonIsHidden = ($el) -> - ## TODO: need to add in the reason an element - ## is hidden when its fixed position and its - ## either being covered or there is no el - - node = $elements.stringify($el, "short") - - ## returns the reason in human terms why an element is considered not visible - switch - when elHasDisplayNone($el) - "This element '#{node}' is not visible because it has CSS property: 'display: none'" - - when $parent = parentHasDisplayNone($el.parent()) - parentNode = $elements.stringify($parent, "short") - - "This element '#{node}' is not visible because its parent '#{parentNode}' has CSS property: 'display: none'" - - when $parent = parentHasVisibilityNone($el.parent()) - parentNode = $elements.stringify($parent, "short") - - "This element '#{node}' is not visible because its parent '#{parentNode}' has CSS property: 'visibility: hidden'" - - when elHasVisibilityHidden($el) - "This element '#{node}' is not visible because it has CSS property: 'visibility: hidden'" - - when elHasNoOffsetWidthOrHeight($el) - width = elOffsetWidth($el) - height = elOffsetHeight($el) - - "This element '#{node}' is not visible because it has an effective width and height of: '#{width} x #{height}' pixels." - - when $parent = parentHasNoOffsetWidthOrHeightAndOverflowHidden($el.parent()) - parentNode = $elements.stringify($parent, "short") - width = elOffsetWidth($parent) - height = elOffsetHeight($parent) - - "This element '#{node}' is not visible because its parent '#{parentNode}' has CSS property: 'overflow: hidden' and an effective width and height of: '#{width} x #{height}' pixels." - - else - ## nested else --___________-- - if elIsFixed($el) - if elIsNotElementFromPoint($el) - ## show the long element here - covered = $elements.stringify(elAtCenterPoint($el)) - - return """ - This element '#{node}' is not visible because it has CSS property: 'position: fixed' and its being covered by another element: - - #{covered} - """ - else - if elIsOutOfBoundsOfAncestorsOverflow($el) - return "This element '#{node}' is not visible because its content is being clipped by one of its parent elements, which has a CSS property of overflow: 'hidden', 'scroll' or 'auto'" - - return "Cypress could not determine why this element '#{node}' is not visible." - -module.exports = { - isVisible - - isHidden - - getReasonIsHidden -} diff --git a/packages/driver/src/dom/visibility.js b/packages/driver/src/dom/visibility.js new file mode 100644 index 000000000000..a59e731611a1 --- /dev/null +++ b/packages/driver/src/dom/visibility.js @@ -0,0 +1,373 @@ +/* eslint-disable + no-case-declarations, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS104: Avoid inline assignments + * DS204: Change includes calls to have a more natural evaluation order + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const _ = require('lodash') + +const $jquery = require('./jquery') +const $document = require('./document') +const $elements = require('./elements') +const $coordinates = require('./coordinates') + +const fixedOrAbsoluteRe = /(fixed|absolute)/ + +const OVERFLOW_PROPS = ['hidden', 'scroll', 'auto'] + +//# WARNING: +//# developer beware. visibility is a sink hole +//# that leads to sheer madness. you should +//# avoid this file before its too late. + +const isVisible = (el) => { + return !isHidden(el, 'isVisible()') +} + +//# TODO: we should prob update dom +//# to be passed in $utils as a dependency +//# because of circular references +const isHidden = function (el, name) { + if (!$elements.isElement(el)) { + if (name == null) { + name = 'isHidden()' + } + + throw new Error(`Cypress.dom.${name} must be passed a basic DOM element.`) + } + + const $el = $jquery.wrap(el) + + //# in Cypress-land we consider the element hidden if + //# either its offsetHeight or offsetWidth is 0 because + //# it is impossible for the user to interact with this element + //# offsetHeight / offsetWidth includes the ef + return elHasNoEffectiveWidthOrHeight($el) || + + //# additionally if the effective visibility of the element + //# is hidden (which includes any parent nodes) then the user + //# cannot interact with this element and thus it is hidden + elHasVisibilityHidden($el) || + + //# we do some calculations taking into account the parents + //# to see if its hidden by a parent + elIsHiddenByAncestors($el) || + + //# if this is a fixed element check if its covered + ( + elIsFixed($el) ? + elIsNotElementFromPoint($el) + : + //# else check if el is outside the bounds + //# of its ancestors overflow + elIsOutOfBoundsOfAncestorsOverflow($el) + ) +} + +const elHasNoEffectiveWidthOrHeight = ($el) => { + return (elOffsetWidth($el) <= 0) || (elOffsetHeight($el) <= 0) || ($el[0].getClientRects().length <= 0) +} + +const elHasNoOffsetWidthOrHeight = ($el) => { + return (elOffsetWidth($el) <= 0) || (elOffsetHeight($el) <= 0) +} + +const elOffsetWidth = ($el) => { + return $el[0].offsetWidth +} + +const elOffsetHeight = ($el) => { + return $el[0].offsetHeight +} + +const elHasVisibilityHidden = ($el) => { + return $el.css('visibility') === 'hidden' +} + +const elHasDisplayNone = ($el) => { + return $el.css('display') === 'none' +} + +const elHasOverflowHidden = function ($el) { + let needle + + return (needle = 'hidden', [$el.css('overflow'), $el.css('overflow-y'), $el.css('overflow-x')].includes(needle)) +} + +const elHasPositionRelative = ($el) => { + return $el.css('position') === 'relative' +} + +const elHasClippableOverflow = function ($el) { + const o = $el.css('overflow') + const oy = $el.css('overflow-y') + const ox = $el.css('overflow-x') + + return OVERFLOW_PROPS.includes(o) || OVERFLOW_PROPS.includes(oy) || OVERFLOW_PROPS.includes(ox) +} + +const canClipContent = function ($el, $ancestor) { + //# can't clip without clippable overflow + if (!elHasClippableOverflow($ancestor)) { + return false + } + + const $offsetParent = $jquery.wrap($el[0].offsetParent) + + //# even if overflow is clippable, if an ancestor of the ancestor is the + //# element's offset parent, the ancestor will not clip the element + //# unless the element is position relative + if (!elHasPositionRelative($el) && $elements.isAncestor($ancestor, $offsetParent)) { + return false + } + + return true +} + +const elIsFixed = function ($el) { + const $stickyOrFixedEl = $elements.getFirstFixedOrStickyPositionParent($el) + + if ($stickyOrFixedEl) { + return $stickyOrFixedEl.css('position') === 'fixed' + } +} + +const elAtCenterPoint = function ($el) { + const doc = $document.getDocumentFromElement($el.get(0)) + const elProps = $coordinates.getElementPositioning($el) + + const { topCenter, leftCenter } = elProps.fromViewport + + const el = $coordinates.getElementAtPointFromViewport(doc, leftCenter, topCenter) + + if (el) { + return $jquery.wrap(el) + } +} + +const elDescendentsHavePositionFixedOrAbsolute = function ($parent, $child) { + //# create an array of all elements between $parent and $child + //# including child but excluding parent + //# and check if these have position fixed|absolute + const $els = $child.parentsUntil($parent).add($child) + + return _.some($els.get(), (el) => { + return fixedOrAbsoluteRe.test($jquery.wrap(el).css('position')) + }) +} + +const elIsNotElementFromPoint = function ($el) { + //# if we have a fixed position element that means + //# it is fixed 'relative' to the viewport which means + //# it MUST be available with elementFromPoint because + //# that is also relative to the viewport + const $elAtPoint = elAtCenterPoint($el) + + //# if the element at point is not a descendent + //# of our $el then we know it's being covered or its + //# not visible + return !$elements.isDescendent($el, $elAtPoint) +} + +const elIsOutOfBoundsOfAncestorsOverflow = function ($el, $ancestor) { + if ($ancestor == null) { + $ancestor = $el.parent() + } + + //# no ancestor, not out of bounds! + if (!$ancestor) { + return false + } + + //# if we've reached the top parent, which is document + //# then we're in bounds all the way up, return false + if ($ancestor.is('body,html') || $document.isDocument($ancestor)) { + return false + } + + const elProps = $coordinates.getElementPositioning($el) + + if (canClipContent($el, $ancestor)) { + const ancestorProps = $coordinates.getElementPositioning($ancestor) + + //# target el is out of bounds + if ( + //# target el is to the right of the ancestor's visible area + (elProps.fromWindow.left > (ancestorProps.width + ancestorProps.fromWindow.left)) || + + //# target el is to the left of the ancestor's visible area + ((elProps.fromWindow.left + elProps.width) < ancestorProps.fromWindow.left) || + + //# target el is under the ancestor's visible area + (elProps.fromWindow.top > (ancestorProps.height + ancestorProps.fromWindow.top)) || + + //# target el is above the ancestor's visible area + ((elProps.fromWindow.top + elProps.height) < ancestorProps.fromWindow.top) + ) { + return true + } + } + + return elIsOutOfBoundsOfAncestorsOverflow($el, $ancestor.parent()) +} + +const elIsHiddenByAncestors = function ($el, $origEl) { + //# store the original $el + if ($origEl == null) { + $origEl = $el + } + + //# walk up to each parent until we reach the body + //# if any parent has an effective offsetHeight of 0 + //# and its set overflow: hidden then our child element + //# is effectively hidden + //# -----UNLESS------ + //# the parent or a descendent has position: absolute|fixed + const $parent = $el.parent() + + //# stop if we've reached the body or html + //# in case there is no body + //# or if parent is the document which can + //# happen if we already have an element + if ($parent.is('body,html') || $document.isDocument($parent)) { + return false + } + + if (elHasOverflowHidden($parent) && elHasNoEffectiveWidthOrHeight($parent)) { + //# if any of the elements between the parent and origEl + //# have fixed or position absolute + return !elDescendentsHavePositionFixedOrAbsolute($parent, $origEl) + } + + //# continue to recursively walk up the chain until we reach body or html + return elIsHiddenByAncestors($parent, $origEl) +} + +const parentHasNoOffsetWidthOrHeightAndOverflowHidden = function ($el) { + //# if we've walked all the way up to body or html then return false + if (!$el.length || $el.is('body,html')) { + return false + } + + //# if we have overflow hidden and no effective width or height + if (elHasOverflowHidden($el) && elHasNoEffectiveWidthOrHeight($el)) { + return $el + } + + //# continue walking + return parentHasNoOffsetWidthOrHeightAndOverflowHidden($el.parent()) + +} + +const parentHasDisplayNone = function ($el) { + //# if we have no $el or we've walked all the way up to document + //# then return false + if (!$el.length || $document.isDocument($el)) { + return false + } + + //# if we have display none then return the $el + if (elHasDisplayNone($el)) { + return $el + } + + //# continue walking + return parentHasDisplayNone($el.parent()) + +} + +const parentHasVisibilityNone = function ($el) { + //# if we've walked all the way up to document then return false + if (!$el.length || $document.isDocument($el)) { + return false + } + + //# if we have display none then return the $el + if (elHasVisibilityHidden($el)) { + return $el + } + + //# continue walking + return parentHasVisibilityNone($el.parent()) + +} + +const getReasonIsHidden = function ($el) { + //# TODO: need to add in the reason an element + //# is hidden when its fixed position and its + //# either being covered or there is no el + + let width + let height + let $parent + let parentNode + const node = $elements.stringify($el, 'short') + + //# returns the reason in human terms why an element is considered not visible + switch (false) { + case !elHasDisplayNone($el): + return `This element '${node}' is not visible because it has CSS property: 'display: none'` + + case !($parent = parentHasDisplayNone($el.parent())): + parentNode = $elements.stringify($parent, 'short') + + return `This element '${node}' is not visible because its parent '${parentNode}' has CSS property: 'display: none'` + + case !($parent = parentHasVisibilityNone($el.parent())): + parentNode = $elements.stringify($parent, 'short') + + return `This element '${node}' is not visible because its parent '${parentNode}' has CSS property: 'visibility: hidden'` + + case !elHasVisibilityHidden($el): + return `This element '${node}' is not visible because it has CSS property: 'visibility: hidden'` + + case !elHasNoOffsetWidthOrHeight($el): + width = elOffsetWidth($el) + height = elOffsetHeight($el) + + return `This element '${node}' is not visible because it has an effective width and height of: '${width} x ${height}' pixels.` + + case !($parent = parentHasNoOffsetWidthOrHeightAndOverflowHidden($el.parent())): + parentNode = $elements.stringify($parent, 'short') + width = elOffsetWidth($parent) + height = elOffsetHeight($parent) + + return `This element '${node}' is not visible because its parent '${parentNode}' has CSS property: 'overflow: hidden' and an effective width and height of: '${width} x ${height}' pixels.` + + default: + //# nested else --___________-- + if (elIsFixed($el)) { + if (elIsNotElementFromPoint($el)) { + //# show the long element here + const covered = $elements.stringify(elAtCenterPoint($el)) + + return `\ +This element '${node}' is not visible because it has CSS property: 'position: fixed' and its being covered by another element: + +${covered}\ +` + } + } else { + if (elIsOutOfBoundsOfAncestorsOverflow($el)) { + return `This element '${node}' is not visible because its content is being clipped by one of its parent elements, which has a CSS property of overflow: 'hidden', 'scroll' or 'auto'` + } + } + + return `Cypress could not determine why this element '${node}' is not visible.` + } +} + +module.exports = { + isVisible, + + isHidden, + + getReasonIsHidden, +} diff --git a/packages/driver/src/dom/window.coffee b/packages/driver/src/dom/window.coffee deleted file mode 100644 index d99035f0a410..000000000000 --- a/packages/driver/src/dom/window.coffee +++ /dev/null @@ -1,30 +0,0 @@ -$jquery = require("./jquery") -$document = require("./document") - -getWindowByElement = (el) -> - if isWindow(el) - return el - - doc = $document.getDocumentFromElement(el) - getWindowByDocument(doc) - -getWindowByDocument = (doc) -> - ## parentWindow for IE - doc.defaultView or doc.parentWindow - -isWindow = (obj) -> - try - if $jquery.isJquery(obj) - obj = obj[0] - - Boolean(obj and obj.window is obj) - catch - false - -module.exports = { - getWindowByElement - - getWindowByDocument - - isWindow -} diff --git a/packages/driver/src/dom/window.js b/packages/driver/src/dom/window.js new file mode 100644 index 000000000000..6c27324bebcf --- /dev/null +++ b/packages/driver/src/dom/window.js @@ -0,0 +1,37 @@ +const $jquery = require('./jquery') +const $document = require('./document') + +const getWindowByElement = function (el) { + if (isWindow(el)) { + return el + } + + const doc = $document.getDocumentFromElement(el) + + return getWindowByDocument(doc) +} + +const getWindowByDocument = (doc) => { + // parentWindow for IE + return doc.defaultView || doc.parentWindow +} + +const isWindow = function (obj) { + try { + if ($jquery.isJquery(obj)) { + obj = obj[0] + } + + return Boolean(obj && obj.window === obj) + } catch (error) { + return false + } +} + +module.exports = { + getWindowByElement, + + getWindowByDocument, + + isWindow, +} From 46a69a92d9ba2459ac8026354f1cdb0ff1754920 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 17 Dec 2018 07:39:55 -0500 Subject: [PATCH 02/31] Upgrade commit info dependency to fall back on environment variables to get Git commit (#2895) * chore: upgrade commit-info to 2.1.1 in server, close #2848 * chore: using mocked-env to test CI params * update more CI env tests * update last CI tests to use mocked-env * test git commit info from environment variables * do not crash on undefined when running npm t * remove duplicate object key to pass linter --- packages/server/package.json | 3 +- packages/server/test/scripts/run.js | 2 +- .../server/test/unit/ci_provider_spec.coffee | 539 ++++++++++-------- .../server/test/unit/modes/record_spec.coffee | 254 +++++---- 4 files changed, 459 insertions(+), 339 deletions(-) diff --git a/packages/server/package.json b/packages/server/package.json index ee6e52ee515c..00234b23f1ad 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -73,6 +73,7 @@ "https-proxy-agent": "^1.0.0", "inquirer": "3.0.6", "istanbul": "^0.4.2", + "mocked-env": "1.2.4", "mockery": "^1.4.0", "nock": "9.0.22", "nodemon": "^1.8.7", @@ -97,7 +98,7 @@ }, "dependencies": { "@cypress/browserify-preprocessor": "1.1.2", - "@cypress/commit-info": "2.0.0", + "@cypress/commit-info": "2.1.1", "@cypress/icons": "0.5.4", "@cypress/mocha-teamcity-reporter": "^1.0.0", "@ffmpeg-installer/ffmpeg": "1.0.15", diff --git a/packages/server/test/scripts/run.js b/packages/server/test/scripts/run.js index 5a16920e6293..c09ec45f2465 100644 --- a/packages/server/test/scripts/run.js +++ b/packages/server/test/scripts/run.js @@ -10,7 +10,7 @@ const options = minimist(process.argv.slice(2)) let run = options._[0] -if (run.includes('--inspect-brk')) { +if (run && run.includes('--inspect-brk')) { run = options._[1] } diff --git a/packages/server/test/unit/ci_provider_spec.coffee b/packages/server/test/unit/ci_provider_spec.coffee index bb23d19906d1..f4a0e40b02ee 100644 --- a/packages/server/test/unit/ci_provider_spec.coffee +++ b/packages/server/test/unit/ci_provider_spec.coffee @@ -1,4 +1,5 @@ R = require("ramda") +mockedEnv = require("mocked-env") require("../spec_helper") ciProvider = require("#{root}lib/util/ci_provider") @@ -15,36 +16,59 @@ expectsCommitParams = (params) -> expectsCommitDefaults = (existing, expected) -> expect(ciProvider.commitDefaults(existing), "CI providers default git params").to.deep.eq(expected) -resetEnv = -> - process.env = {} - describe "lib/util/ci_provider", -> - beforeEach -> - resetEnv() + resetEnv = null + + afterEach -> + # we need to reset environment + # to avoid affecting tests in other suites + resetEnv?() it "null when unknown", -> - resetEnv() + resetEnv = mockedEnv({}, {clear: true}) expectsName(null) expectsCiParams(null) expectsCommitParams(null) + it "does not extract from commit environment variables yet", -> + # see fallback environment variables + # https://github.com/cypress-io/commit-info#fallback-environment-variables + # BUT those defaults are NOT used by "ci_provider" + # instead they are used in the "record" module + # this test just confirms that these defaults are not considered + env = { + COMMIT_INFO_BRANCH: "my-branch-221", + COMMIT_INFO_MESSAGE: "best commit ever", + COMMIT_INFO_EMAIL: "user@company.com", + COMMIT_INFO_AUTHOR: "Agent Smith", + COMMIT_INFO_SHA: "0123456", + COMMIT_INFO_REMOTE: "remote repo" + } + resetEnv = mockedEnv(env, {clear: true}) + + expectsName(null) # we don't know CI + expectsCiParams(null) # we don't know CI params + expectsCommitParams(null) # we don't know CI-specific params + it "appveyor", -> - process.env.APPVEYOR = true - - process.env.APPVEYOR_JOB_ID = "appveyorJobId2" - process.env.APPVEYOR_ACCOUNT_NAME = "appveyorAccountName" - process.env.APPVEYOR_PROJECT_SLUG = "appveyorProjectSlug" - process.env.APPVEYOR_BUILD_VERSION = "appveyorBuildVersion" - process.env.APPVEYOR_BUILD_NUMBER = "appveyorBuildNumber" - process.env.APPVEYOR_PULL_REQUEST_NUMBER = "appveyorPullRequestNumber" - process.env.APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH = "appveyorPullRequestHeadRepoBranch" - - process.env.APPVEYOR_REPO_COMMIT = "repoCommit" - process.env.APPVEYOR_REPO_COMMIT_MESSAGE = "repoCommitMessage" - process.env.APPVEYOR_REPO_BRANCH = "repoBranch" - process.env.APPVEYOR_REPO_COMMIT_AUTHOR = "repoCommitAuthor" - process.env.APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL = "repoCommitAuthorEmail" + resetEnv = mockedEnv({ + APPVEYOR: "true" + + APPVEYOR_JOB_ID: "appveyorJobId2" + APPVEYOR_ACCOUNT_NAME: "appveyorAccountName" + APPVEYOR_PROJECT_SLUG: "appveyorProjectSlug" + APPVEYOR_BUILD_VERSION: "appveyorBuildVersion" + APPVEYOR_BUILD_NUMBER: "appveyorBuildNumber" + APPVEYOR_PULL_REQUEST_NUMBER: "appveyorPullRequestNumber" + APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH: "appveyorPullRequestHeadRepoBranch" + + APPVEYOR_REPO_COMMIT: "repoCommit" + APPVEYOR_REPO_COMMIT_MESSAGE: "repoCommitMessage" + APPVEYOR_REPO_BRANCH: "repoBranch" + APPVEYOR_REPO_COMMIT_AUTHOR: "repoCommitAuthor" + APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL: "repoCommitAuthorEmail" + }, {clear: true}) expectsName("appveyor") expectsCiParams({ @@ -66,22 +90,26 @@ describe "lib/util/ci_provider", -> resetEnv() - process.env.APPVEYOR = true - process.env.APPVEYOR_REPO_COMMIT_MESSAGE = "repoCommitMessage" - process.env.APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED = "repoCommitMessageExtended" + resetEnv = mockedEnv({ + APPVEYOR: "true" + APPVEYOR_REPO_COMMIT_MESSAGE: "repoCommitMessage" + APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED: "repoCommitMessageExtended" + }, {clear: true}) expectsCommitParams({ message: "repoCommitMessage\nrepoCommitMessageExtended" }) it "bamboo", -> - process.env["bamboo.buildNumber"] = "123" + resetEnv = mockedEnv({ + "bamboo.buildNumber": "123" - process.env["bamboo.resultsUrl"] = "bamboo.resultsUrl" - process.env["bamboo.buildResultsUrl"] = "bamboo.buildResultsUrl" - process.env["bamboo.planRepository.repositoryUrl"] = "bamboo.planRepository.repositoryUrl" + "bamboo.resultsUrl": "bamboo.resultsUrl" + "bamboo.buildResultsUrl": "bamboo.buildResultsUrl" + "bamboo.planRepository.repositoryUrl": "bamboo.planRepository.repositoryUrl" - process.env["bamboo.planRepository.branch"] = "bamboo.planRepository.branch" + "bamboo.planRepository.branch": "bamboo.planRepository.branch" + }, {clear: true}) expectsName("bamboo") expectsCiParams({ @@ -95,16 +123,18 @@ describe "lib/util/ci_provider", -> }) it "bitbucket", -> - process.env.CI = "1" + resetEnv = mockedEnv({ + CI: "1" - # build information - process.env.BITBUCKET_BUILD_NUMBER = "bitbucketBuildNumber" - process.env.BITBUCKET_REPO_OWNER = "bitbucketRepoOwner" - process.env.BITBUCKET_REPO_SLUG = "bitbucketRepoSlug" + # build information + BITBUCKET_BUILD_NUMBER: "bitbucketBuildNumber" + BITBUCKET_REPO_OWNER: "bitbucketRepoOwner" + BITBUCKET_REPO_SLUG: "bitbucketRepoSlug" - # git information - process.env.BITBUCKET_COMMIT = "bitbucketCommit" - process.env.BITBUCKET_BRANCH = "bitbucketBranch" + # git information + BITBUCKET_COMMIT: "bitbucketCommit" + BITBUCKET_BRANCH: "bitbucketBranch" + }, {clear: true}) expectsName("bitbucket") expectsCiParams({ @@ -125,25 +155,26 @@ describe "lib/util/ci_provider", -> }) it "buildkite", -> - process.env.BUILDKITE = true - - process.env.BUILDKITE_REPO = "buildkiteRepo" - process.env.BUILDKITE_JOB_ID = "buildkiteJobId" - process.env.BUILDKITE_SOURCE = "buildkiteSource" - process.env.BUILDKITE_BUILD_ID = "buildkiteBuildId" - process.env.BUILDKITE_BUILD_URL = "buildkiteBuildUrl" - process.env.BUILDKITE_BUILD_NUMBER = "buildkiteBuildNumber" - process.env.BUILDKITE_PULL_REQUEST = "buildkitePullRequest" - process.env.BUILDKITE_PULL_REQUEST_REPO = "buildkitePullRequestRepo" - process.env.BUILDKITE_PULL_REQUEST_BASE_BRANCH = "buildkitePullRequestBaseBranch" - - process.env.BUILDKITE_COMMIT = "buildKiteCommit" - process.env.BUILDKITE_BRANCH = "buildKiteBranch" - process.env.BUILDKITE_MESSAGE = "buildKiteMessage" - process.env.BUILDKITE_BUILD_CREATOR = "buildKiteBuildCreator" - process.env.BUILDKITE_BUILD_CREATOR_EMAIL = "buildKiteCreatorEmail" - process.env.BUILDKITE_REPO = "buildkiteRepo" - process.env.BUILDKITE_PIPELINE_DEFAULT_BRANCH = "buildkitePipelineDefaultBranch" + resetEnv = mockedEnv({ + BUILDKITE: "true" + + BUILDKITE_REPO: "buildkiteRepo" + BUILDKITE_JOB_ID: "buildkiteJobId" + BUILDKITE_SOURCE: "buildkiteSource" + BUILDKITE_BUILD_ID: "buildkiteBuildId" + BUILDKITE_BUILD_URL: "buildkiteBuildUrl" + BUILDKITE_BUILD_NUMBER: "buildkiteBuildNumber" + BUILDKITE_PULL_REQUEST: "buildkitePullRequest" + BUILDKITE_PULL_REQUEST_REPO: "buildkitePullRequestRepo" + BUILDKITE_PULL_REQUEST_BASE_BRANCH: "buildkitePullRequestBaseBranch" + + BUILDKITE_COMMIT: "buildKiteCommit" + BUILDKITE_BRANCH: "buildKiteBranch" + BUILDKITE_MESSAGE: "buildKiteMessage" + BUILDKITE_BUILD_CREATOR: "buildKiteBuildCreator" + BUILDKITE_BUILD_CREATOR_EMAIL: "buildKiteCreatorEmail" + BUILDKITE_PIPELINE_DEFAULT_BRANCH: "buildkitePipelineDefaultBranch" + }, {clear: true}) expectsName("buildkite") expectsCiParams({ @@ -168,23 +199,25 @@ describe "lib/util/ci_provider", -> }) it "circle", -> - process.env.CIRCLECI = true - - process.env.CIRCLE_JOB = "circleJob" - process.env.CIRCLE_BUILD_NUM = "circleBuildNum" - process.env.CIRCLE_BUILD_URL = "circleBuildUrl" - process.env.CIRCLE_PR_NUMBER = "circlePrNumber" - process.env.CIRCLE_PR_REPONAME = "circlePrReponame" - process.env.CIRCLE_PR_USERNAME = "circlePrUsername" - process.env.CIRCLE_COMPARE_URL = "circleCompareUrl" - process.env.CIRCLE_WORKFLOW_ID = "circleWorkflowId" - process.env.CIRCLE_PULL_REQUEST = "circlePullRequest" - process.env.CIRCLE_REPOSITORY_URL = "circleRepositoryUrl" - process.env.CI_PULL_REQUEST = "ciPullRequest" - - process.env.CIRCLE_SHA1 = "circleSha" - process.env.CIRCLE_BRANCH = "circleBranch" - process.env.CIRCLE_USERNAME = "circleUsername" + resetEnv = mockedEnv({ + CIRCLECI: "true" + + CIRCLE_JOB: "circleJob" + CIRCLE_BUILD_NUM: "circleBuildNum" + CIRCLE_BUILD_URL: "circleBuildUrl" + CIRCLE_PR_NUMBER: "circlePrNumber" + CIRCLE_PR_REPONAME: "circlePrReponame" + CIRCLE_PR_USERNAME: "circlePrUsername" + CIRCLE_COMPARE_URL: "circleCompareUrl" + CIRCLE_WORKFLOW_ID: "circleWorkflowId" + CIRCLE_PULL_REQUEST: "circlePullRequest" + CIRCLE_REPOSITORY_URL: "circleRepositoryUrl" + CI_PULL_REQUEST: "ciPullRequest" + + CIRCLE_SHA1: "circleSha" + CIRCLE_BRANCH: "circleBranch" + CIRCLE_USERNAME: "circleUsername" + }, {clear: true}) expectsName("circle") expectsCiParams({ @@ -207,21 +240,23 @@ describe "lib/util/ci_provider", -> }) it "codeshipBasic", -> - process.env.CODESHIP = "TRUE" - process.env.CI_NAME = "codeship" - - process.env.CI_BUILD_ID = "ciBuildId" - process.env.CI_REPO_NAME = "ciRepoName" - process.env.CI_BUILD_URL = "ciBuildUrl" - process.env.CI_PROJECT_ID = "ciProjectId" - process.env.CI_BUILD_NUMBER = "ciBuildNumber" - process.env.CI_PULL_REQUEST = "ciPullRequest" - - process.env.CI_COMMIT_ID = "ciCommitId" - process.env.CI_BRANCH = "ciBranch" - process.env.CI_COMMIT_MESSAGE = "ciCommitMessage" - process.env.CI_COMMITTER_NAME = "ciCommitterName" - process.env.CI_COMMITTER_EMAIL = "ciCommitterEmail" + resetEnv = mockedEnv({ + CODESHIP: "TRUE" + CI_NAME: "codeship" + + CI_BUILD_ID: "ciBuildId" + CI_REPO_NAME: "ciRepoName" + CI_BUILD_URL: "ciBuildUrl" + CI_PROJECT_ID: "ciProjectId" + CI_BUILD_NUMBER: "ciBuildNumber" + CI_PULL_REQUEST: "ciPullRequest" + + CI_COMMIT_ID: "ciCommitId" + CI_BRANCH: "ciBranch" + CI_COMMIT_MESSAGE: "ciCommitMessage" + CI_COMMITTER_NAME: "ciCommitterName" + CI_COMMITTER_EMAIL: "ciCommitterEmail" + }, {clear: true}) expectsName("codeshipBasic") expectsCiParams({ @@ -241,17 +276,19 @@ describe "lib/util/ci_provider", -> }) it "codeshipPro", -> - process.env.CI_NAME = "codeship" + resetEnv = mockedEnv({ + CI_NAME: "codeship" - process.env.CI_BUILD_ID = "ciBuildId" - process.env.CI_REPO_NAME = "ciRepoName" - process.env.CI_PROJECT_ID = "ciProjectId" + CI_BUILD_ID: "ciBuildId" + CI_REPO_NAME: "ciRepoName" + CI_PROJECT_ID: "ciProjectId" - process.env.CI_COMMIT_ID = "ciCommitId" - process.env.CI_BRANCH = "ciBranch" - process.env.CI_COMMIT_MESSAGE = "ciCommitMessage" - process.env.CI_COMMITTER_NAME = "ciCommitterName" - process.env.CI_COMMITTER_EMAIL = "ciCommitterEmail" + CI_COMMIT_ID: "ciCommitId" + CI_BRANCH: "ciBranch" + CI_COMMIT_MESSAGE: "ciCommitMessage" + CI_COMMITTER_NAME: "ciCommitterName" + CI_COMMITTER_EMAIL: "ciCommitterEmail" + }, {clear: true}) expectsName("codeshipPro") expectsCiParams({ @@ -268,19 +305,21 @@ describe "lib/util/ci_provider", -> }) it "drone", -> - process.env.DRONE = true - - process.env.DRONE_JOB_NUMBER = "droneJobNumber" - process.env.DRONE_BUILD_LINK = "droneBuildLink" - process.env.DRONE_BUILD_NUMBER = "droneBuildNumber" - process.env.DRONE_PULL_REQUEST = "dronePullRequest" - - process.env.DRONE_COMMIT_SHA = "droneCommitSha" - process.env.DRONE_COMMIT_BRANCH = "droneCommitBranch" - process.env.DRONE_COMMIT_MESSAGE = "droneCommitMessage" - process.env.DRONE_COMMIT_AUTHOR = "droneCommitAuthor" - process.env.DRONE_COMMIT_AUTHOR_EMAIL = "droneCommitAuthorEmail" - process.env.DRONE_REPO_BRANCH = "droneRepoBranch" + resetEnv = mockedEnv({ + DRONE: "true" + + DRONE_JOB_NUMBER: "droneJobNumber" + DRONE_BUILD_LINK: "droneBuildLink" + DRONE_BUILD_NUMBER: "droneBuildNumber" + DRONE_PULL_REQUEST: "dronePullRequest" + + DRONE_COMMIT_SHA: "droneCommitSha" + DRONE_COMMIT_BRANCH: "droneCommitBranch" + DRONE_COMMIT_MESSAGE: "droneCommitMessage" + DRONE_COMMIT_AUTHOR: "droneCommitAuthor" + DRONE_COMMIT_AUTHOR_EMAIL: "droneCommitAuthorEmail" + DRONE_REPO_BRANCH: "droneRepoBranch" + }, {clear: true}) expectsName("drone") expectsCiParams({ @@ -299,27 +338,29 @@ describe "lib/util/ci_provider", -> }) it "gitlab", -> - process.env.GITLAB_CI = true - - # Gitlab has job id and build id as synonyms - process.env.CI_BUILD_ID = "ciJobId" - process.env.CI_JOB_ID = "ciJobId" - process.env.CI_JOB_URL = "ciJobUrl" - - process.env.CI_PIPELINE_ID = "ciPipelineId" - process.env.CI_PIPELINE_URL = "ciPipelineUrl" - - process.env.GITLAB_HOST = "gitlabHost" - process.env.CI_PROJECT_ID = "ciProjectId" - process.env.CI_PROJECT_URL = "ciProjectUrl" - process.env.CI_REPOSITORY_URL = "ciRepositoryUrl" - process.env.CI_ENVIRONMENT_URL = "ciEnvironmentUrl" - - process.env.CI_COMMIT_SHA = "ciCommitSha" - process.env.CI_COMMIT_REF_NAME = "ciCommitRefName" - process.env.CI_COMMIT_MESSAGE = "ciCommitMessage" - process.env.GITLAB_USER_NAME = "gitlabUserName" - process.env.GITLAB_USER_EMAIL = "gitlabUserEmail" + resetEnv = mockedEnv({ + GITLAB_CI: "true" + + # Gitlab has job id and build id as synonyms + CI_BUILD_ID: "ciJobId" + CI_JOB_ID: "ciJobId" + CI_JOB_URL: "ciJobUrl" + + CI_PIPELINE_ID: "ciPipelineId" + CI_PIPELINE_URL: "ciPipelineUrl" + + GITLAB_HOST: "gitlabHost" + CI_PROJECT_ID: "ciProjectId" + CI_PROJECT_URL: "ciProjectUrl" + CI_REPOSITORY_URL: "ciRepositoryUrl" + CI_ENVIRONMENT_URL: "ciEnvironmentUrl" + + CI_COMMIT_SHA: "ciCommitSha" + CI_COMMIT_REF_NAME: "ciCommitRefName" + CI_COMMIT_MESSAGE: "ciCommitMessage" + GITLAB_USER_NAME: "gitlabUserName" + GITLAB_USER_EMAIL: "gitlabUserEmail" + }, {clear: true}) expectsName("gitlab") expectsCiParams({ @@ -342,28 +383,30 @@ describe "lib/util/ci_provider", -> authorEmail: "gitlabUserEmail" }) - resetEnv() - - process.env.CI_SERVER_NAME = "GitLab CI" + resetEnv = mockedEnv({ + CI_SERVER_NAME: "GitLab CI" + }, {clear: true}) expectsName("gitlab") - resetEnv() - - process.env.CI_SERVER_NAME = "GitLab" + resetEnv = mockedEnv({ + CI_SERVER_NAME: "GitLab" + }, {clear: true}) expectsName("gitlab") it "jenkins", -> - process.env.JENKINS_URL = true + resetEnv = mockedEnv({ + JENKINS_URL: "true" - process.env.BUILD_ID = "buildId" - process.env.BUILD_URL = "buildUrl" - process.env.BUILD_NUMBER = "buildNumber" - process.env.ghprbPullId = "gbprbPullId" + BUILD_ID: "buildId" + BUILD_URL: "buildUrl" + BUILD_NUMBER: "buildNumber" + ghprbPullId: "gbprbPullId" - process.env.GIT_COMMIT = "gitCommit" - process.env.GIT_BRANCH = "gitBranch" + GIT_COMMIT: "gitCommit" + GIT_BRANCH: "gitBranch" + }, {clear: true}) expectsName("jenkins") expectsCiParams({ @@ -377,43 +420,49 @@ describe "lib/util/ci_provider", -> branch: "gitBranch" }) - resetEnv() - process.env.JENKINS_HOME = "/path/to/jenkins" + resetEnv = mockedEnv({ + JENKINS_HOME: "/path/to/jenkins" + }, {clear: true}) expectsName("jenkins") - resetEnv() - process.env.JENKINS_VERSION = "1.2.3" + resetEnv = mockedEnv({ + JENKINS_VERSION: "1.2.3" + }, {clear: true}) expectsName("jenkins") - resetEnv() - process.env.HUDSON_HOME = "/path/to/jenkins" + resetEnv = mockedEnv({ + HUDSON_HOME: "/path/to/jenkins" + }, {clear: true}) expectsName("jenkins") - resetEnv() - process.env.HUDSON_URL = true + resetEnv = mockedEnv({ + HUDSON_URL: "true" + }, {clear: true}) expectsName("jenkins") it "semaphore", -> - process.env.SEMAPHORE = true - - process.env.SEMAPHORE_BRANCH_ID = "semaphoreBranchId" - process.env.SEMAPHORE_BUILD_NUMBER = "semaphoreBuildNumber" - process.env.SEMAPHORE_CURRENT_JOB = "semaphoreCurrentJob" - process.env.SEMAPHORE_CURRENT_THREAD = "semaphoreCurrentThread" - process.env.SEMAPHORE_EXECUTABLE_UUID = "semaphoreExecutableUuid" - process.env.SEMAPHORE_JOB_COUNT = "semaphoreJobCount" - process.env.SEMAPHORE_JOB_UUID = "semaphoreJobUuid" - process.env.SEMAPHORE_PLATFORM = "semaphorePlatform" - process.env.SEMAPHORE_PROJECT_DIR = "semaphoreProjectDir" - process.env.SEMAPHORE_PROJECT_HASH_ID = "semaphoreProjectHashId" - process.env.SEMAPHORE_PROJECT_NAME = "semaphoreProjectName" - process.env.SEMAPHORE_PROJECT_UUID = "semaphoreProjectUuid" - process.env.SEMAPHORE_REPO_SLUG = "semaphoreRepoSlug" - process.env.SEMAPHORE_TRIGGER_SOURCE = "semaphoreTriggerSource" - process.env.PULL_REQUEST_NUMBER = "pullRequestNumber" - - process.env.REVISION = "revision" - process.env.BRANCH_NAME = "branchName" + resetEnv = mockedEnv({ + SEMAPHORE: "true" + + SEMAPHORE_BRANCH_ID: "semaphoreBranchId" + SEMAPHORE_BUILD_NUMBER: "semaphoreBuildNumber" + SEMAPHORE_CURRENT_JOB: "semaphoreCurrentJob" + SEMAPHORE_CURRENT_THREAD: "semaphoreCurrentThread" + SEMAPHORE_EXECUTABLE_UUID: "semaphoreExecutableUuid" + SEMAPHORE_JOB_COUNT: "semaphoreJobCount" + SEMAPHORE_JOB_UUID: "semaphoreJobUuid" + SEMAPHORE_PLATFORM: "semaphorePlatform" + SEMAPHORE_PROJECT_DIR: "semaphoreProjectDir" + SEMAPHORE_PROJECT_HASH_ID: "semaphoreProjectHashId" + SEMAPHORE_PROJECT_NAME: "semaphoreProjectName" + SEMAPHORE_PROJECT_UUID: "semaphoreProjectUuid" + SEMAPHORE_REPO_SLUG: "semaphoreRepoSlug" + SEMAPHORE_TRIGGER_SOURCE: "semaphoreTriggerSource" + PULL_REQUEST_NUMBER: "pullRequestNumber" + + REVISION: "revision" + BRANCH_NAME: "branchName" + }, {clear: true}) expectsName("semaphore") expectsCiParams({ @@ -439,40 +488,42 @@ describe "lib/util/ci_provider", -> }) it "shippable", -> - process.env.SHIPPABLE = "true" - - # build environment variables - process.env.SHIPPABLE_BUILD_ID = "buildId" - process.env.SHIPPABLE_BUILD_NUMBER = "buildNumber" - process.env.SHIPPABLE_COMMIT_RANGE = "commitRange" - process.env.SHIPPABLE_CONTAINER_NAME = "containerName" - process.env.SHIPPABLE_JOB_ID = "jobId" - process.env.SHIPPABLE_JOB_NUMBER = "jobNumber" - process.env.SHIPPABLE_REPO_SLUG = "repoSlug" - - # additional information - process.env.IS_FORK = "isFork" - process.env.IS_GIT_TAG = "isGitTag" - process.env.IS_PRERELEASE = "isPrerelease" - process.env.IS_RELEASE = "isRelease" - process.env.REPOSITORY_URL = "repositoryUrl" - process.env.REPO_FULL_NAME = "repoFullName" - process.env.REPO_NAME = "repoName" - process.env.BUILD_URL = "buildUrl" - - # pull request variables - process.env.BASE_BRANCH = "baseBranch" - process.env.HEAD_BRANCH = "headBranch" - process.env.IS_PULL_REQUEST = "isPullRequest" - process.env.PULL_REQUEST = "pullRequest" - process.env.PULL_REQUEST_BASE_BRANCH = "pullRequestBaseBranch" - process.env.PULL_REQUEST_REPO_FULL_NAME = "pullRequestRepoFullName" - - # git information - process.env.COMMIT = "commit" - process.env.BRANCH = "branch" - process.env.COMMITTER = "committer" - process.env.COMMIT_MESSAGE = "commitMessage" + resetEnv = mockedEnv({ + SHIPPABLE: "true" + + # build environment variables + SHIPPABLE_BUILD_ID: "buildId" + SHIPPABLE_BUILD_NUMBER: "buildNumber" + SHIPPABLE_COMMIT_RANGE: "commitRange" + SHIPPABLE_CONTAINER_NAME: "containerName" + SHIPPABLE_JOB_ID: "jobId" + SHIPPABLE_JOB_NUMBER: "jobNumber" + SHIPPABLE_REPO_SLUG: "repoSlug" + + # additional information + IS_FORK: "isFork" + IS_GIT_TAG: "isGitTag" + IS_PRERELEASE: "isPrerelease" + IS_RELEASE: "isRelease" + REPOSITORY_URL: "repositoryUrl" + REPO_FULL_NAME: "repoFullName" + REPO_NAME: "repoName" + BUILD_URL: "buildUrl" + + # pull request variables + BASE_BRANCH: "baseBranch" + HEAD_BRANCH: "headBranch" + IS_PULL_REQUEST: "isPullRequest" + PULL_REQUEST: "pullRequest" + PULL_REQUEST_BASE_BRANCH: "pullRequestBaseBranch" + PULL_REQUEST_REPO_FULL_NAME: "pullRequestRepoFullName" + + # git information + COMMIT: "commit" + BRANCH: "branch" + COMMITTER: "committer" + COMMIT_MESSAGE: "commitMessage" + }, {clear: true}) expectsName("shippable") expectsCiParams({ @@ -509,30 +560,36 @@ describe "lib/util/ci_provider", -> }) it "snap", -> - process.env.SNAP_CI = true + resetEnv = mockedEnv({ + SNAP_CI: "true" + }, {clear: true}) expectsName("snap") expectsCiParams(null) expectsCommitParams(null) it "teamcity", -> - process.env.TEAMCITY_VERSION = true + resetEnv = mockedEnv({ + TEAMCITY_VERSION: "true" + }, {clear: true}) expectsName("teamcity") expectsCiParams(null) expectsCommitParams(null) it "teamfoundation", -> - process.env.TF_BUILD = true + resetEnv = mockedEnv({ + TF_BUILD: "true" - process.env.BUILD_BUILDID = "buildId" - process.env.BUILD_BUILDNUMBER = "buildNumber" - process.env.BUILD_CONTAINERID = "containerId" + BUILD_BUILDID: "buildId" + BUILD_BUILDNUMBER: "buildNumber" + BUILD_CONTAINERID: "containerId" - process.env.BUILD_SOURCEVERSION = "commit" - process.env.BUILD_SOURCEBRANCHNAME = "branch" - process.env.BUILD_SOURCEVERSIONMESSAGE = "message" - process.env.BUILD_SOURCEVERSIONAUTHOR = "name" + BUILD_SOURCEVERSION: "commit" + BUILD_SOURCEBRANCHNAME: "branch" + BUILD_SOURCEVERSIONMESSAGE: "message" + BUILD_SOURCEVERSIONAUTHOR: "name" + }, {clear: true}) expectsName("teamfoundation") expectsCiParams({ @@ -548,21 +605,23 @@ describe "lib/util/ci_provider", -> }) it "travis", -> - process.env.TRAVIS = true - - process.env.TRAVIS_JOB_ID = "travisJobId" - process.env.TRAVIS_BUILD_ID = "travisBuildId" - process.env.TRAVIS_REPO_SLUG = "travisRepoSlug" - process.env.TRAVIS_JOB_NUMBER = "travisJobNumber" - process.env.TRAVIS_EVENT_TYPE = "travisEventType" - process.env.TRAVIS_COMMIT_RANGE = "travisCommitRange" - process.env.TRAVIS_BUILD_NUMBER = "travisBuildNumber" - process.env.TRAVIS_PULL_REQUEST = "travisPullRequest" - process.env.TRAVIS_PULL_REQUEST_BRANCH = "travisPullRequestBranch" - - process.env.TRAVIS_COMMIT = "travisCommit" - process.env.TRAVIS_BRANCH = "travisBranch" - process.env.TRAVIS_COMMIT_MESSAGE = "travisCommitMessage" + resetEnv = mockedEnv({ + TRAVIS: "true" + + TRAVIS_JOB_ID: "travisJobId" + TRAVIS_BUILD_ID: "travisBuildId" + TRAVIS_REPO_SLUG: "travisRepoSlug" + TRAVIS_JOB_NUMBER: "travisJobNumber" + TRAVIS_EVENT_TYPE: "travisEventType" + TRAVIS_COMMIT_RANGE: "travisCommitRange" + TRAVIS_BUILD_NUMBER: "travisBuildNumber" + TRAVIS_PULL_REQUEST: "travisPullRequest" + TRAVIS_PULL_REQUEST_BRANCH: "travisPullRequestBranch" + + TRAVIS_COMMIT: "travisCommit" + TRAVIS_BRANCH: "travisBranch" + TRAVIS_COMMIT_MESSAGE: "travisCommitMessage" + }, {clear: true}) expectsName("travis") expectsCiParams({ @@ -582,20 +641,26 @@ describe "lib/util/ci_provider", -> message: "travisCommitMessage" }) - resetEnv() - process.env.TRAVIS = true - process.env.TRAVIS_BRANCH = "travisBranch" + resetEnv = mockedEnv({ + TRAVIS: "true" + TRAVIS_BRANCH: "travisBranch" + }, {clear: true}) + expectsCommitParams({ branch: "travisBranch" }) it "wercker", -> - process.env.WERCKER = true + resetEnv = mockedEnv({ + WERCKER: "true" + }, {clear: true}) expectsName("wercker") expectsCiParams(null) expectsCommitParams(null) - resetEnv() - process.env.WERCKER_MAIN_PIPELINE_STARTED = true + resetEnv = mockedEnv({ + WERCKER_MAIN_PIPELINE_STARTED: "true" + }, {clear: true}) + expectsName("wercker") diff --git a/packages/server/test/unit/modes/record_spec.coffee b/packages/server/test/unit/modes/record_spec.coffee index f17fd1035f05..7e89581ff032 100644 --- a/packages/server/test/unit/modes/record_spec.coffee +++ b/packages/server/test/unit/modes/record_spec.coffee @@ -4,6 +4,8 @@ _ = require("lodash") os = require("os") debug = require("debug")("test") commitInfo = require("@cypress/commit-info") +mockedEnv = require("mocked-env") + api = require("#{root}../lib/api") errors = require("#{root}../lib/errors") logger = require("#{root}../lib/logger") @@ -88,113 +90,165 @@ describe "lib/modes/record", -> # this is tested inside @cypress/commit-info context ".createRunAndRecordSpecs", -> - specs = [ - { relative: "path/to/spec/a" }, - { relative: "path/to/spec/b" } - ] - - beforeEach -> - sinon.stub(ciProvider, "provider").returns("circle") - sinon.stub(ciProvider, "ciParams").returns({foo: "bar"}) - - @commitDefaults = { - branch: "master", - author: "brian", - email: "brian@cypress.io", - message: "such hax", - sha: "sha-123", - remote: "https://github.com/foo/bar.git" + describe "fallback commit information", -> + resetEnv = null + + env = { + COMMIT_INFO_BRANCH: "my-branch-221", + COMMIT_INFO_MESSAGE: "best commit ever", + COMMIT_INFO_EMAIL: "user@company.com", + COMMIT_INFO_AUTHOR: "Agent Smith", + COMMIT_INFO_SHA: "0123456", + COMMIT_INFO_REMOTE: "remote repo" } - sinon.stub(commitInfo, "commitInfo").resolves(@commitDefaults) - sinon.stub(ciProvider, "commitDefaults").returns({ - sha: @commitDefaults.sha - branch: @commitDefaults.branch - authorName: @commitDefaults.author - authorEmail: @commitDefaults.email - message: @commitDefaults.message - remoteOrigin: @commitDefaults.remote - }) - sinon.stub(api, "createRun").resolves() - - it "calls api.createRun with the right args", -> - key = "recordKey" - projectId = "pId123" - specPattern = ["spec/pattern1", "spec/pattern2"] - projectRoot = "project/root" - ciBuildId = "ciId123" - parallel = null - group = null - runAllSpecs = sinon.stub() - sys = { - osCpus: 1 - osName: 2 - osMemory: 3 - osVersion: 4 - } - browser = { - displayName: "chrome" - version: "59" - } + beforeEach -> + # stub git commands to return nulls + sinon.stub(commitInfo, "getBranch").resolves(null) + sinon.stub(commitInfo, "getMessage").resolves(null) + sinon.stub(commitInfo, "getEmail").resolves(null) + sinon.stub(commitInfo, "getAuthor").resolves(null) + sinon.stub(commitInfo, "getSha").resolves(null) + sinon.stub(commitInfo, "getRemoteOrigin").resolves(null) + # but set environment variables + resetEnv = mockedEnv(env, {clear: true}) + + afterEach -> + resetEnv() + + it "calls api.createRun with the commit extracted from environment variables", -> + createRun = sinon.stub(api, "createRun").resolves() + runAllSpecs = sinon.stub() + recordMode.createRunAndRecordSpecs({ + key: "foo", + sys: {}, + browser: {}, + runAllSpecs + }) + .then -> + expect(runAllSpecs).to.have.been.calledWith({}, false) + expect(createRun).to.have.been.calledOnce + expect(createRun.firstCall.args).to.have.length(1) + { commit } = createRun.firstCall.args[0] + debug('git is %o', commit) + expect(commit).to.deep.equal({ + sha: env.COMMIT_INFO_SHA, + branch: env.COMMIT_INFO_BRANCH, + authorName: env.COMMIT_INFO_AUTHOR, + authorEmail: env.COMMIT_INFO_EMAIL, + message: env.COMMIT_INFO_MESSAGE, + remoteOrigin: env.COMMIT_INFO_REMOTE, + defaultBranch: null + }) + + describe "with CI info", -> + specs = [ + { relative: "path/to/spec/a" }, + { relative: "path/to/spec/b" } + ] + + beforeEach -> + sinon.stub(ciProvider, "provider").returns("circle") + sinon.stub(ciProvider, "ciParams").returns({foo: "bar"}) + + @commitDefaults = { + branch: "master", + author: "brian", + email: "brian@cypress.io", + message: "such hax", + sha: "sha-123", + remote: "https://github.com/foo/bar.git" + } + sinon.stub(commitInfo, "commitInfo").resolves(@commitDefaults) + sinon.stub(ciProvider, "commitDefaults").returns({ + sha: @commitDefaults.sha + branch: @commitDefaults.branch + authorName: @commitDefaults.author + authorEmail: @commitDefaults.email + message: @commitDefaults.message + remoteOrigin: @commitDefaults.remote + }) - recordMode.createRunAndRecordSpecs({ - key - sys - specs - group - browser - parallel - ciBuildId - projectId - projectRoot - specPattern - runAllSpecs - }) - .then -> - expect(commitInfo.commitInfo).to.be.calledWith(projectRoot) - expect(api.createRun).to.be.calledWith({ + sinon.stub(api, "createRun").resolves() + + it "calls api.createRun with the right args", -> + key = "recordKey" + projectId = "pId123" + specPattern = ["spec/pattern1", "spec/pattern2"] + projectRoot = "project/root" + ciBuildId = "ciId123" + parallel = null + group = null + runAllSpecs = sinon.stub() + sys = { + osCpus: 1 + osName: 2 + osMemory: 3 + osVersion: 4 + } + browser = { + displayName: "chrome" + version: "59" + } + + recordMode.createRunAndRecordSpecs({ + key + sys + specs group + browser parallel - projectId ciBuildId - recordKey: key - specPattern: "spec/pattern1,spec/pattern2" - specs: ["path/to/spec/a", "path/to/spec/b"] - platform: { - osCpus: 1 - osName: 2 - osMemory: 3 - osVersion: 4 - browserName: "chrome" - browserVersion: "59" - } - ci: { - params: { - foo: "bar" - } - provider: "circle" - } - commit: { - authorEmail: "brian@cypress.io" - authorName: "brian" - branch: "master" - message: "such hax" - remoteOrigin: "https://github.com/foo/bar.git" - sha: "sha-123" - } - platform: { - browserName: "chrome" - browserVersion: "59" - osCpus: 1 - osMemory: 3 - osName: 2 - osVersion: 4 - } - projectId: "pId123" - recordKey: "recordKey" - specPattern: "spec/pattern1,spec/pattern2" - specs: ["path/to/spec/a", "path/to/spec/b"] + projectId + projectRoot + specPattern + runAllSpecs }) + .then -> + expect(commitInfo.commitInfo).to.be.calledWith(projectRoot) + expect(api.createRun).to.be.calledWith({ + group + parallel + projectId + ciBuildId + recordKey: key + specPattern: "spec/pattern1,spec/pattern2" + specs: ["path/to/spec/a", "path/to/spec/b"] + platform: { + osCpus: 1 + osName: 2 + osMemory: 3 + osVersion: 4 + browserName: "chrome" + browserVersion: "59" + } + ci: { + params: { + foo: "bar" + } + provider: "circle" + } + commit: { + authorEmail: "brian@cypress.io" + authorName: "brian" + branch: "master" + message: "such hax" + remoteOrigin: "https://github.com/foo/bar.git" + sha: "sha-123" + } + platform: { + browserName: "chrome" + browserVersion: "59" + osCpus: 1 + osMemory: 3 + osName: 2 + osVersion: 4 + } + projectId: "pId123" + recordKey: "recordKey" + specPattern: "spec/pattern1,spec/pattern2" + specs: ["path/to/spec/a", "path/to/spec/b"] + }) context ".updateInstanceStdout", -> beforeEach -> From 934ab9901b4e3751dd21e5e44c62decad4f54d4b Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Mon, 17 Dec 2018 08:37:11 -0500 Subject: [PATCH 03/31] Fix Windows path stripping (#2937) Closes #2936 --- packages/server/lib/exception.coffee | 6 ++++-- packages/server/test/unit/exceptions_spec.coffee | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/server/lib/exception.coffee b/packages/server/lib/exception.coffee index a8b3f09a2e55..fde4079619de 100644 --- a/packages/server/lib/exception.coffee +++ b/packages/server/lib/exception.coffee @@ -2,6 +2,7 @@ _ = require("lodash") Promise = require("bluebird") winston = require("winston") pkg = require("@packages/root") +path = require("path") api = require("./api") user = require("./user") @@ -10,11 +11,12 @@ system = require("./util/system") ## strip everything but the file name to remove any sensitive ## data in the path -pathRe = /'?((\/|\\|[a-z]:\\)[^\s']+)+'?/ig +pathRe = /'?((\/|\\+|[a-z]:\\)[^\s']+)+'?/ig +pathSepRe = /[\/\\]+/ fileNameRe = /[^\s'/]+\.\w+:?\d*$/i stripPath = (text) -> (text or "").replace pathRe, (path) -> - fileName = _.last(path.split("/")) or "" + fileName = _.last(path.split(pathSepRe)) or "" "#{fileName}" ## POST https://api.cypress.io/exceptions diff --git a/packages/server/test/unit/exceptions_spec.coffee b/packages/server/test/unit/exceptions_spec.coffee index b0dca8bfba52..ae7e4a80d241 100644 --- a/packages/server/test/unit/exceptions_spec.coffee +++ b/packages/server/test/unit/exceptions_spec.coffee @@ -63,12 +63,23 @@ describe "lib/exceptions", -> at bar /Users/ruby/dev/bar.js:92 """ } + @windowsError = { + name: "Path not found: \\Users\\ruby\\dev\\" + message: "Could not find \\Users\\ruby\\dev\\foo.js" + stack: """ + Error at \\Users\\ruby\\dev\\index.js:102 + at foo \\Users\\ruby\\dev\\foo.js:4 + at bar \\Users\\ruby\\dev\\bar.js:92 + """ + } it "strips paths from name, leaving file name and line number", -> expect(exception.getErr(@err).name).to.equal("Path not found: ") + expect(exception.getErr(@windowsError).name).to.equal("Path not found: ") it "strips paths from message, leaving file name and line number", -> expect(exception.getErr(@err).message).to.equal("Could not find foo.js") + expect(exception.getErr(@windowsError).message).to.equal("Could not find foo.js") it "strips paths from stack, leaving file name and line number", -> expect(exception.getErr(@err).stack).to.equal(""" @@ -76,6 +87,11 @@ describe "lib/exceptions", -> at foo foo.js:4 at bar bar.js:92 """) + expect(exception.getErr(@windowsError).stack).to.equal(""" + Error at index.js:102 + at foo foo.js:4 + at bar bar.js:92 + """) it "handles strippable properties being undefined gracefully", -> expect(-> exception.getErr({})).not.to.throw() From 4dcc7381d0db6b7d37d7714beb00d580336f0fa8 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 19 Dec 2018 18:33:06 -0500 Subject: [PATCH 04/31] fix issues around aborted xhrs (#2969) - fixes #761 - fixes #1652 - fixes #2968 --- packages/driver/src/cy/commands/xhr.coffee | 11 -- packages/driver/src/cypress/server.coffee | 61 +++++++--- .../integration/commands/xhr_spec.coffee | 56 +++++++++ .../integration/issues/761_2968_spec.js | 111 ++++++++++++++++++ .../e2e/cypress/integration/xhr_spec.coffee | 26 ++-- 5 files changed, 225 insertions(+), 40 deletions(-) create mode 100644 packages/driver/test/cypress/integration/issues/761_2968_spec.js diff --git a/packages/driver/src/cy/commands/xhr.coffee b/packages/driver/src/cy/commands/xhr.coffee index fe5e43a524af..b3113d146d62 100644 --- a/packages/driver/src/cy/commands/xhr.coffee +++ b/packages/driver/src/cy/commands/xhr.coffee @@ -12,10 +12,6 @@ server = null getServer = -> server ? unavailableErr() -abort = -> - if server - server.abort() - reset = -> if server server.restore() @@ -221,13 +217,6 @@ defaults = { module.exports = (Commands, Cypress, cy, state, config) -> reset() - ## if our page is going away due to - ## a form submit / anchor click then - ## we need to cancel all outstanding - ## XHR's so the command log displays - ## correctly - Cypress.on("window:unload", abort) - Cypress.on "test:before:run", -> ## reset the existing server reset() diff --git a/packages/driver/src/cypress/server.coffee b/packages/driver/src/cypress/server.coffee index 2b2f34363198..ce57d3eaf2ac 100644 --- a/packages/driver/src/cypress/server.coffee +++ b/packages/driver/src/cypress/server.coffee @@ -26,6 +26,13 @@ normalize = (val) -> nope = -> return null +## when the browser naturally cancels/aborts +## an XHR because the window is unloading +isAbortedThroughUnload = (xhr) -> + xhr.readyState is 4 and + xhr.status is 0 and + xhr.responseText is "" + warnOnStubDeprecation = (obj, type) -> if _.has(obj, "stub") $utils.warning(""" @@ -296,39 +303,56 @@ create = (options = {}) -> abort = XHR.prototype.abort srh = XHR.prototype.setRequestHeader - restoreFn = -> - ## restore the property back on the window - _.each {send: send, open: open, abort: abort, setRequestHeader: srh}, (value, key) -> - XHR.prototype[key] = value - - XHR.prototype.setRequestHeader = -> - proxy = server.getProxyFor(@) + abortXhr = (xhr) -> + proxy = server.getProxyFor(xhr) - proxy._setRequestHeader.apply(proxy, arguments) + ## if the XHR leaks into the next test + ## after we've reset our internal server + ## then this may be undefined + return if not proxy - srh.apply(@, arguments) + ## return if we're already aborted which + ## can happen if the browser already canceled + ## this xhr but we called abort later + return if xhr.aborted - XHR.prototype.abort = -> - ## if we already have a readyState of 4 - ## then do not get the abort stack or - ## set the aborted property or call onXhrAbort - ## to test this just use a regular XHR - @aborted = true + xhr.aborted = true abortStack = server.getStack() - proxy = server.getProxyFor(@) proxy.aborted = true options.onXhrAbort(proxy, abortStack) if _.isFunction(options.onAnyAbort) - route = server.getRouteForXhr(@) + route = server.getRouteForXhr(xhr) ## call the onAnyAbort function ## after we've called options.onSend options.onAnyAbort(route, proxy) + restoreFn = -> + ## restore the property back on the window + _.each {send: send, open: open, abort: abort, setRequestHeader: srh}, (value, key) -> + XHR.prototype[key] = value + + XHR.prototype.setRequestHeader = -> + ## if the XHR leaks into the next test + ## after we've reset our internal server + ## then this may be undefined + if proxy = server.getProxyFor(@) + proxy._setRequestHeader.apply(proxy, arguments) + + srh.apply(@, arguments) + + XHR.prototype.abort = -> + ## if we already have a readyState of 4 + ## then do not get the abort stack or + ## set the aborted property or call onXhrAbort + ## to test this just use a regular XHR + if @readyState isnt 4 + abortXhr(@) + abort.apply(@, arguments) XHR.prototype.open = (method, url, async = true, username, password) -> @@ -407,6 +431,9 @@ create = (options = {}) -> ## catch synchronous errors caused ## by the onreadystatechange function try + if isAbortedThroughUnload(xhr) + abortXhr(xhr) + if _.isFunction(orst = fns.onreadystatechange) orst.apply(xhr, arguments) catch err diff --git a/packages/driver/test/cypress/integration/commands/xhr_spec.coffee b/packages/driver/test/cypress/integration/commands/xhr_spec.coffee index 732ef70f142b..9dce4a61c42c 100644 --- a/packages/driver/test/cypress/integration/commands/xhr_spec.coffee +++ b/packages/driver/test/cypress/integration/commands/xhr_spec.coffee @@ -1843,6 +1843,62 @@ describe "src/cy/commands/xhr", -> _.each xhrs, (xhr) -> expect(xhr.aborted).not.to.be.false + it "aborts xhrs that haven't been sent", -> + cy + .window() + .then (win) -> + xhr = new win.XMLHttpRequest() + xhr.open("GET", "/timeout?ms=0") + xhr.abort() + + expect(xhr.aborted).to.be.true + + it "aborts xhrs currently in flight", -> + log = null + + cy.on "log:changed", (attrs, l) => + if attrs.name is "xhr" + log = l + + cy + .window() + .then (win) -> + xhr = new win.XMLHttpRequest() + xhr.open("GET", "/timeout?ms=1000") + xhr.send() + xhr.abort() + + cy.wrap(null).should -> + expect(log.get("state")).to.eq("failed") + expect(xhr.aborted).to.be.true + + ## https://github.com/cypress-io/cypress/issues/1652 + it "does not set aborted on XHR's that have completed by have had .abort() called", -> + log = null + + cy.on "log:changed", (attrs, l) => + if attrs.name is "xhr" + log = l + + cy + .window() + .then (win) -> + new Promise (resolve) -> + xhr = new win.XMLHttpRequest() + xhr.open("GET", "/timeout?ms=0") + xhr.onload = -> + xhr.abort() + xhr.foo = "bar" + resolve(xhr) + xhr.send() + .then (xhr) -> + ## ensure this is set to prevent accidental + ## race conditions down the road if something + ## goes wrong + expect(xhr.foo).to.eq("bar") + expect(xhr.aborted).not.to.be.true + expect(log.get("state")).to.eq("passed") + context "Cypress.on(window:unload)", -> it "aborts all open XHR's", -> xhrs = [] diff --git a/packages/driver/test/cypress/integration/issues/761_2968_spec.js b/packages/driver/test/cypress/integration/issues/761_2968_spec.js new file mode 100644 index 000000000000..c04ab38da183 --- /dev/null +++ b/packages/driver/test/cypress/integration/issues/761_2968_spec.js @@ -0,0 +1,111 @@ +// https://github.com/cypress-io/cypress/issues/761 +describe('issue #761 - aborted XHRs from previous tests', () => { + context('aborted when complete', () => { + it('test 1 dispatches xhr, but completes in test 2', () => { + cy.window().then((win) => { + const xhr = new win.XMLHttpRequest() + + xhr.open('GET', '/timeout?ms=1000') + xhr.onload = () => { + // we are in test 2 at this point + // and should not throw + xhr.abort() + } + xhr.send() + }) + }) + + it('test 2 aborts the completed XHR', () => { + cy.wait(2000) + }) + }) + + context('aborted before complete', () => { + let xhr = null + + // TODO: we lose a reference here to the xhr in test 2 + // so it shows up as "pending" forever because we reset + // the proxied XHR's as references when the next test starts + it('test 1 dispatches xhr, but completes in test 2', () => { + cy.window().then((win) => { + xhr = new win.XMLHttpRequest() + + xhr.open('GET', '/timeout?ms=1000') + xhr.send() + }) + }) + + it('test 2 aborts the incomplete XHR which is currently in flight', () => { + // we are in test 2 at this point + // and should not throw when we + // abort the incomplete xhr + expect(xhr.aborted).not.to.be.true + + xhr.abort() + }) + }) +}) + +// this tests that XHR references are blown away +// and no longer invoked when unloading the window +// and that its unnecessary to abort them +// https://github.com/cypress-io/cypress/issues/2968 +describe('issue #2968 - unloaded xhrs do not need to be aborted', () => { + it('let the browser naturally abort requests without manual intervention on unload', () => { + let xhr + let log + + const stub = cy.stub() + + cy.on('log:changed', (attrs, l) => { + if (attrs.name === 'xhr') { + log = l + } + }) + + cy + .visit('http://localhost:3500/fixtures/generic.html') + .window() + .then((win) => { + return new Promise((resolve, reject) => { + xhr = new win.XMLHttpRequest() + + win.XMLHttpRequest.prototype.abort = stub + + xhr.open('GET', '/timeout?ms=1000') + xhr.abort = stub // this should not get called + xhr.onerror = stub // this should not fire + xhr.onload = stub // this should not fire + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + try { + // the browser should naturally + // abort / cancel this request when + // the unload event is called which + // should cause this xhr to have + // these properties and be displayed + // correctly in the Cypress Command Log + expect(xhr.aborted).to.be.true + expect(xhr.readyState).to.eq(4) + expect(xhr.status).to.eq(0) + expect(xhr.responseText).to.eq('') + } catch (err) { + reject(err) + } + + resolve() + } + } + + xhr.send() + + win.location.href = 'about:blank' + }) + }) + .wrap(null) + .should(() => { + expect(stub).not.to.be.called + expect(log.get('state')).to.eq('failed') + }) + }) +}) diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/xhr_spec.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/xhr_spec.coffee index 5165968fc0d8..822dee769ba3 100644 --- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/xhr_spec.coffee +++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/xhr_spec.coffee @@ -100,16 +100,18 @@ describe "xhrs", -> it "aborts", -> cy - .route({ - method: "POST", - url: /users/, - response: {name: "b"}, - delay: 200 - }).as("createUser") - .get("#create").click() - .then -> - ## simulate an open request which should become - ## aborted due to window:unload event - Cypress.action("app:window:unload", {}) + .window() + .then (win) -> + cy + .route({ + method: "POST", + url: /users/, + response: {name: "b"}, + delay: 2000 + }) + .as("createUser") + .get("#create").click() + .then -> + win.location.href = '/index.html' - .wait("@createUser").its("aborted").should("be.true") + .wait("@createUser").its("aborted").should("be.true") From 203622080ee060786b931046444b1e1a1d172023 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Wed, 19 Dec 2018 18:33:38 -0500 Subject: [PATCH 05/31] fix: upgrade sinon types to min TS 2.8 (#2966) --- cli/package.json | 6 +++--- cli/types/index.d.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/package.json b/cli/package.json index ac139e37a68f..e0cf01dbd1db 100644 --- a/cli/package.json +++ b/cli/package.json @@ -23,7 +23,7 @@ "test-cov": "nyc npm run unit", "unit": "BLUEBIRD_DEBUG=1 NODE_ENV=test bin-up mocha --reporter mocha-multi-reporters --reporter-options configFile=../mocha-reporter-config.json", "lint": "bin-up eslint --fix *.js bin/* lib/*.js lib/**/*.js test/*.js test/**/*.js", - "dtslint": "echo 'disabling dtslint for now'", + "dtslint": "dtslint types", "prebuild": "npm run test-dependencies && node ./scripts/start-build.js", "build": "node ./scripts/build.js", "prerelease": "npm run build", @@ -48,7 +48,7 @@ "@types/lodash": "4.14.87", "@types/minimatch": "3.0.3", "@types/mocha": "2.2.44", - "@types/sinon": "4.0.0", + "@types/sinon": "7.0.0", "@types/sinon-chai": "2.7.29", "bluebird": "3.5.0", "cachedir": "1.3.0", @@ -96,7 +96,7 @@ "nyc": "13.0.0", "proxyquire": "2.0.1", "shelljs": "0.7.8", - "sinon": "5.0.7", + "sinon": "7.2.2", "snap-shot-it": "^5.0.0" }, "files": [ diff --git a/cli/types/index.d.ts b/cli/types/index.d.ts index e29251778c8a..91ac026b5293 100644 --- a/cli/types/index.d.ts +++ b/cli/types/index.d.ts @@ -4,7 +4,7 @@ // Mike Woudenberg // Robbert van Markus // Nicholas Boll -// TypeScript Version: 2.5 +// TypeScript Version: 2.8 // Updated by the Cypress team: https://www.cypress.io/about/ /// @@ -3926,8 +3926,8 @@ declare namespace Cypress { // Diff / Omit taken from https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-311923766 type Diff = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T] - // @ts-ignore TODO - remove this if possible. Seems a recent change to TypeScript broke this. Possibly https://github.com/Microsoft/TypeScript/pull/17912 - type Omit = Pick> + // TODO - remove this if possible. Seems a recent change to TypeScript broke this. Possibly https://github.com/Microsoft/TypeScript/pull/17912 + type Omit = Pick> // tslint:disable-line } /** From 748fa8c53393f3a57afbdf7b980c6e85ea0358d8 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 19 Dec 2018 20:43:27 -0500 Subject: [PATCH 06/31] disable dtslint again --- cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/package.json b/cli/package.json index e0cf01dbd1db..0c96d3c53916 100644 --- a/cli/package.json +++ b/cli/package.json @@ -23,7 +23,7 @@ "test-cov": "nyc npm run unit", "unit": "BLUEBIRD_DEBUG=1 NODE_ENV=test bin-up mocha --reporter mocha-multi-reporters --reporter-options configFile=../mocha-reporter-config.json", "lint": "bin-up eslint --fix *.js bin/* lib/*.js lib/**/*.js test/*.js test/**/*.js", - "dtslint": "dtslint types", + "dtslint": "echo 'disabling dtslint for now'", "prebuild": "npm run test-dependencies && node ./scripts/start-build.js", "build": "node ./scripts/build.js", "prerelease": "npm run build", From cc3de8ff78ece0a66edbca5ec12dd67c408f36aa Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 19 Dec 2018 23:51:54 -0500 Subject: [PATCH 07/31] Revert "fix: upgrade sinon types to min TS 2.8 (#2966)" This reverts commit 203622080ee060786b931046444b1e1a1d172023. --- cli/package.json | 4 ++-- cli/types/index.d.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cli/package.json b/cli/package.json index 0c96d3c53916..ac139e37a68f 100644 --- a/cli/package.json +++ b/cli/package.json @@ -48,7 +48,7 @@ "@types/lodash": "4.14.87", "@types/minimatch": "3.0.3", "@types/mocha": "2.2.44", - "@types/sinon": "7.0.0", + "@types/sinon": "4.0.0", "@types/sinon-chai": "2.7.29", "bluebird": "3.5.0", "cachedir": "1.3.0", @@ -96,7 +96,7 @@ "nyc": "13.0.0", "proxyquire": "2.0.1", "shelljs": "0.7.8", - "sinon": "7.2.2", + "sinon": "5.0.7", "snap-shot-it": "^5.0.0" }, "files": [ diff --git a/cli/types/index.d.ts b/cli/types/index.d.ts index 91ac026b5293..e29251778c8a 100644 --- a/cli/types/index.d.ts +++ b/cli/types/index.d.ts @@ -4,7 +4,7 @@ // Mike Woudenberg // Robbert van Markus // Nicholas Boll -// TypeScript Version: 2.8 +// TypeScript Version: 2.5 // Updated by the Cypress team: https://www.cypress.io/about/ /// @@ -3926,8 +3926,8 @@ declare namespace Cypress { // Diff / Omit taken from https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-311923766 type Diff = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T] - // TODO - remove this if possible. Seems a recent change to TypeScript broke this. Possibly https://github.com/Microsoft/TypeScript/pull/17912 - type Omit = Pick> // tslint:disable-line + // @ts-ignore TODO - remove this if possible. Seems a recent change to TypeScript broke this. Possibly https://github.com/Microsoft/TypeScript/pull/17912 + type Omit = Pick> } /** From d0ff0d1547a89ee471f8ad8f902d9e76f09935e8 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 20 Dec 2018 00:49:03 -0500 Subject: [PATCH 08/31] bump cli cache dep versions in circle --- circle.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/circle.yml b/circle.yml index c3014219fe84..49445d575d51 100644 --- a/circle.yml +++ b/circle.yml @@ -53,7 +53,7 @@ jobs: # need to restore a separate cache for each package.json - restore_cache: - key: v5-{{ .Branch }}-cli-deps + key: v6-{{ .Branch }}-cli-deps - restore_cache: key: v5-{{ .Branch }}-root-deps - restore_cache: @@ -105,7 +105,7 @@ jobs: # save each node_modules folder per package - save_cache: - key: v5-{{ .Branch }}-cli-deps-{{ checksum "cli/package.json" }} + key: v6-{{ .Branch }}-cli-deps-{{ checksum "cli/package.json" }} paths: - cli/node_modules - save_cache: From ac76d3d8aeb5a7a014d83b9a7c03d3ea860e1344 Mon Sep 17 00:00:00 2001 From: Jennifer Shehane Date: Thu, 20 Dec 2018 16:22:17 +0630 Subject: [PATCH 09/31] Fix incorrect formatting of 'overrides' arg in .eslintrc (#2933) - overrides should be array of objects - trailing comma is invalid --- .eslintrc | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.eslintrc b/.eslintrc index 0a9455e9f166..6f2cbf68ab49 100644 --- a/.eslintrc +++ b/.eslintrc @@ -59,10 +59,14 @@ "legacyDecorators": true } }, - "overrides": { - "files": ["**/*.jsx"], - "rules": { - "arrow-body-style": "off", + "overrides": [ + { + "files": [ + "**/*.jsx" + ], + "rules": { + "arrow-body-style": "off" + } } - } + ] } From d3193d5ec03b4717ee0efe49119fa31f9ee6e234 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Thu, 20 Dec 2018 13:10:21 -0500 Subject: [PATCH 10/31] Revert "Revert "fix: upgrade sinon types to min TS 2.8 (#2966)"" (#2974) * Revert "Revert "fix: upgrade sinon types to min TS 2.8 (#2966)"" This reverts commit cc3de8ff78ece0a66edbca5ec12dd67c408f36aa. * enable dtslint in cli again --- cli/package.json | 6 +++--- cli/types/index.d.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/package.json b/cli/package.json index ac139e37a68f..e0cf01dbd1db 100644 --- a/cli/package.json +++ b/cli/package.json @@ -23,7 +23,7 @@ "test-cov": "nyc npm run unit", "unit": "BLUEBIRD_DEBUG=1 NODE_ENV=test bin-up mocha --reporter mocha-multi-reporters --reporter-options configFile=../mocha-reporter-config.json", "lint": "bin-up eslint --fix *.js bin/* lib/*.js lib/**/*.js test/*.js test/**/*.js", - "dtslint": "echo 'disabling dtslint for now'", + "dtslint": "dtslint types", "prebuild": "npm run test-dependencies && node ./scripts/start-build.js", "build": "node ./scripts/build.js", "prerelease": "npm run build", @@ -48,7 +48,7 @@ "@types/lodash": "4.14.87", "@types/minimatch": "3.0.3", "@types/mocha": "2.2.44", - "@types/sinon": "4.0.0", + "@types/sinon": "7.0.0", "@types/sinon-chai": "2.7.29", "bluebird": "3.5.0", "cachedir": "1.3.0", @@ -96,7 +96,7 @@ "nyc": "13.0.0", "proxyquire": "2.0.1", "shelljs": "0.7.8", - "sinon": "5.0.7", + "sinon": "7.2.2", "snap-shot-it": "^5.0.0" }, "files": [ diff --git a/cli/types/index.d.ts b/cli/types/index.d.ts index e29251778c8a..91ac026b5293 100644 --- a/cli/types/index.d.ts +++ b/cli/types/index.d.ts @@ -4,7 +4,7 @@ // Mike Woudenberg // Robbert van Markus // Nicholas Boll -// TypeScript Version: 2.5 +// TypeScript Version: 2.8 // Updated by the Cypress team: https://www.cypress.io/about/ /// @@ -3926,8 +3926,8 @@ declare namespace Cypress { // Diff / Omit taken from https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-311923766 type Diff = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T] - // @ts-ignore TODO - remove this if possible. Seems a recent change to TypeScript broke this. Possibly https://github.com/Microsoft/TypeScript/pull/17912 - type Omit = Pick> + // TODO - remove this if possible. Seems a recent change to TypeScript broke this. Possibly https://github.com/Microsoft/TypeScript/pull/17912 + type Omit = Pick> // tslint:disable-line } /** From b5b41febf1e968bbd2131354338d70308061c5c2 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Fri, 21 Dec 2018 16:43:47 -0500 Subject: [PATCH 11/31] Build osx on circle 2958 (#2978) * use arch when caching dependencies on Circle * add mac job * hmm, mac name * executor name * use circle v2.1 * circle 2.1 cannot have job names start with a digit * hmm, separate mac workflow * shared build job via executor * allow Node version mismatch on CircleCI Mac build * correct workflow names per os * do not check TERM on darwin platform * move terminal check into a node script * lint on both mac and linux * pass linter * enable full linux workflow * try building on mac * use xcode with Node 8.x * try mac 9.1.0 * xcode 9.0.1 * run prebuild serially * add node sass rebuild to desktop gui * build on mac platform * print identity on mac * disable Mac workflow for now --- circle.yml | 186 +++++++++++++++++++----------- package.json | 6 +- packages/desktop-gui/package.json | 3 +- packages/runner/package.json | 2 +- scripts/check-terminal.js | 14 +++ scripts/win-appveyor-build.js | 7 +- 6 files changed, 146 insertions(+), 72 deletions(-) create mode 100644 scripts/check-terminal.js diff --git a/circle.yml b/circle.yml index 49445d575d51..3edc7a7c5f9b 100644 --- a/circle.yml +++ b/circle.yml @@ -1,21 +1,38 @@ -version: 2 +version: 2.1 defaults: &defaults parallelism: 1 working_directory: ~/cypress - docker: - # the Docker image with Cypress dependencies and Chrome browser - - image: cypress/browsers:chrome64 - environment: - ## set specific timezone - TZ: "/usr/share/zoneinfo/America/New_York" - - ## store artifacts here - CIRCLE_ARTIFACTS: /tmp/artifacts - - ## set so that e2e tests are consistent - COLUMNS: 100 - LINES: 24 + parameters: + executor: + type: executor + default: cy-doc + executor: <> + environment: + ## set specific timezone + TZ: "/usr/share/zoneinfo/America/New_York" + + ## store artifacts here + CIRCLE_ARTIFACTS: /tmp/artifacts + + ## set so that e2e tests are consistent + COLUMNS: 100 + LINES: 24 + +executors: + # the Docker image with Cypress dependencies and Chrome browser + cy-doc: + docker: + - image: cypress/browsers:chrome64 + environment: + PLATFORM: linux + + # executor to run on Mac OS + mac: + macos: + xcode: "9.0.1" + environment: + PLATFORM: mac jobs: ## code checkout and NPM installs @@ -23,7 +40,6 @@ jobs: <<: *defaults steps: - checkout - - run: name: Print working folder command: echo $PWD @@ -38,52 +54,46 @@ jobs: command: npm -v - run: npm run check-node-version - ## make sure the TERM is set to 'xterm' in node + ## make sure the TERM is set to 'xterm' in node (Linux only) ## else colors (and tests) will fail ## See the following information ## * http://andykdocs.de/development/Docker/Fixing+the+Docker+TERM+variable+issue ## * https://unix.stackexchange.com/questions/43945/whats-the-difference-between-various-term-variables - - run: - name: Checking TERM and COLUMNS are set - command: | - node -e 'assert.ok(process.env.TERM === "xterm", `process.env.TERM=${process.env.TERM} and must be set to "xterm" for Docker to work`)' - node -e 'assert.ok(process.env.COLUMNS === "100", `process.env.COLUMNS=${process.env.COLUMNS} must be set to 100 for snapshots to pass`)' - node -e 'console.log("stdout.isTTY?", process.stdout.isTTY)' - node -e 'console.log("stderr.isTTY?", process.stderr.isTTY)' + - run: npm run check-terminal # need to restore a separate cache for each package.json - restore_cache: - key: v6-{{ .Branch }}-cli-deps + key: v6-{{ arch }}-{{ .Branch }}-cli-deps - restore_cache: - key: v5-{{ .Branch }}-root-deps + key: v5-{{ arch }}-{{ .Branch }}-root-deps - restore_cache: - key: v5-{{ .Branch }}-deps-coffee + key: v5-{{ arch }}-{{ .Branch }}-deps-coffee - restore_cache: - key: v5-{{ .Branch }}-deps-desktop-gui + key: v5-{{ arch }}-{{ .Branch }}-deps-desktop-gui - restore_cache: - key: v5-{{ .Branch }}-deps-driver + key: v5-{{ arch }}-{{ .Branch }}-deps-driver - restore_cache: - key: v5-{{ .Branch }}-deps-example + key: v5-{{ arch }}-{{ .Branch }}-deps-example - restore_cache: - key: v7-{{ .Branch }}-deps-electron + key: v7-{{ arch }}-{{ .Branch }}-deps-electron - restore_cache: - key: v5-{{ .Branch }}-deps-extension + key: v5-{{ arch }}-{{ .Branch }}-deps-extension - restore_cache: - key: v5-{{ .Branch }}-deps-https-proxy + key: v5-{{ arch }}-{{ .Branch }}-deps-https-proxy - restore_cache: - key: v5-{{ .Branch }}-deps-launcher + key: v5-{{ arch }}-{{ .Branch }}-deps-launcher - restore_cache: - key: v5-{{ .Branch }}-deps-reporter + key: v5-{{ arch }}-{{ .Branch }}-deps-reporter - restore_cache: - key: v5-{{ .Branch }}-deps-runner + key: v5-{{ arch }}-{{ .Branch }}-deps-runner - restore_cache: - key: v5-{{ .Branch }}-deps-server + key: v5-{{ arch }}-{{ .Branch }}-deps-server - restore_cache: - key: v5-{{ .Branch }}-deps-socket + key: v5-{{ arch }}-{{ .Branch }}-deps-socket - restore_cache: - key: v5-{{ .Branch }}-deps-static + key: v5-{{ arch }}-{{ .Branch }}-deps-static - restore_cache: - key: v5-{{ .Branch }}-deps-ts + key: v5-{{ arch }}-{{ .Branch }}-deps-ts # show what is already cached globally - run: ls $(npm -g bin) @@ -105,69 +115,69 @@ jobs: # save each node_modules folder per package - save_cache: - key: v6-{{ .Branch }}-cli-deps-{{ checksum "cli/package.json" }} + key: v6-{{ arch }}-{{ .Branch }}-cli-deps-{{ checksum "cli/package.json" }} paths: - cli/node_modules - save_cache: - key: v5-{{ .Branch }}-root-deps-{{ checksum "package.json" }} + key: v5-{{ arch }}-{{ .Branch }}-root-deps-{{ checksum "package.json" }} paths: - node_modules - save_cache: - key: v5-{{ .Branch }}-deps-coffee-{{ checksum "packages/coffee/package.json" }} + key: v5-{{ arch }}-{{ .Branch }}-deps-coffee-{{ checksum "packages/coffee/package.json" }} paths: - packages/coffee/node_modules - save_cache: - key: v5-{{ .Branch }}-deps-desktop-gui-{{ checksum "packages/desktop-gui/package.json" }} + key: v5-{{ arch }}-{{ .Branch }}-deps-desktop-gui-{{ checksum "packages/desktop-gui/package.json" }} paths: - packages/desktop-gui/node_modules - save_cache: - key: v5-{{ .Branch }}-deps-driver-{{ checksum "packages/driver/package.json" }} + key: v5-{{ arch }}-{{ .Branch }}-deps-driver-{{ checksum "packages/driver/package.json" }} paths: - packages/driver/node_modules - save_cache: - key: v5-{{ .Branch }}-deps-example-{{ checksum "packages/example/package.json" }} + key: v5-{{ arch }}-{{ .Branch }}-deps-example-{{ checksum "packages/example/package.json" }} paths: - packages/example/node_modules - save_cache: - key: v7-{{ .Branch }}-deps-electron-{{ checksum "packages/electron/package.json" }} + key: v7-{{ arch }}-{{ .Branch }}-deps-electron-{{ checksum "packages/electron/package.json" }} paths: - packages/electron/node_modules - ~/.cache/electron - ~/.electron - save_cache: - key: v5-{{ .Branch }}-deps-extension-{{ checksum "packages/extension/package.json" }} + key: v5-{{ arch }}-{{ .Branch }}-deps-extension-{{ checksum "packages/extension/package.json" }} paths: - packages/extension/node_modules - save_cache: - key: v5-{{ .Branch }}-deps-https-proxy-{{ checksum "packages/https-proxy/package.json" }} + key: v5-{{ arch }}-{{ .Branch }}-deps-https-proxy-{{ checksum "packages/https-proxy/package.json" }} paths: - packages/https-proxy/node_modules - save_cache: - key: v5-{{ .Branch }}-deps-launcher-{{ checksum "packages/launcher/package.json" }} + key: v5-{{ arch }}-{{ .Branch }}-deps-launcher-{{ checksum "packages/launcher/package.json" }} paths: - packages/launcher/node_modules - save_cache: - key: v5-{{ .Branch }}-deps-reporter-{{ checksum "packages/reporter/package.json" }} + key: v5-{{ arch }}-{{ .Branch }}-deps-reporter-{{ checksum "packages/reporter/package.json" }} paths: - packages/reporter/node_modules - save_cache: - key: v5-{{ .Branch }}-deps-runner-{{ checksum "packages/runner/package.json" }} + key: v5-{{ arch }}-{{ .Branch }}-deps-runner-{{ checksum "packages/runner/package.json" }} paths: - packages/runner/node_modules - save_cache: - key: v5-{{ .Branch }}-deps-server-{{ checksum "packages/server/package.json" }} + key: v5-{{ arch }}-{{ .Branch }}-deps-server-{{ checksum "packages/server/package.json" }} paths: - packages/server/node_modules - save_cache: - key: v5-{{ .Branch }}-deps-socket-{{ checksum "packages/socket/package.json" }} + key: v5-{{ arch }}-{{ .Branch }}-deps-socket-{{ checksum "packages/socket/package.json" }} paths: - packages/socket/node_modules - save_cache: - key: v5-{{ .Branch }}-deps-static-{{ checksum "packages/static/package.json" }} + key: v5-{{ arch }}-{{ .Branch }}-deps-static-{{ checksum "packages/static/package.json" }} paths: - packages/static/node_modules - save_cache: - key: v5-{{ .Branch }}-deps-ts-{{ checksum "packages/ts/package.json" }} + key: v5-{{ arch }}-{{ .Branch }}-deps-ts-{{ checksum "packages/ts/package.json" }} paths: - packages/ts/node_modules @@ -175,7 +185,8 @@ jobs: ## we update stop-only # - run: npm run stop-only ## now go build all of subpackages - - run: npm run build + - run: npm run prebuild -- --serial + - run: npm run build -- --serial ## save entire folder as artifact for other jobs to run without reinstalling - persist_to_workspace: @@ -191,7 +202,7 @@ jobs: - run: npm run lint - run: npm run all lint - "unit-tests": + unit-tests: <<: *defaults parallelism: 1 steps: @@ -323,7 +334,7 @@ jobs: - store_test_results: path: /tmp/cypress - "3x-driver-integration-tests": + "driver-integration-tests-3x": <<: *defaults parallelism: 3 steps: @@ -347,7 +358,7 @@ jobs: - store_artifacts: path: /tmp/artifacts - "2x-desktop-gui-integration-tests": + "desktop-gui-integration-tests-2x": <<: *defaults parallelism: 2 steps: @@ -395,14 +406,14 @@ jobs: command: node index.js working_directory: packages/launcher - "build-binary": + build-binary: <<: *defaults steps: - attach_workspace: at: ~/ - run: $(npm bin)/print-arch - - run: npm run binary-build -- --platform linux --version $NEXT_DEV_VERSION - - run: npm run binary-zip -- --platform linux + - run: npm run binary-build -- --platform $PLATFORM --version $NEXT_DEV_VERSION + - run: npm run binary-zip -- --platform $PLATFORM - run: ls -l *.zip - run: name: upload unique binary @@ -597,12 +608,22 @@ jobs: CYPRESS_ENV=staging \ $(npm bin)/cypress run --record + mac-os-build: + executor: mac + steps: + - run: + name: Show keychains + command: security list-keychains + - run: + name: Find code signing identity + command: security find-identity -v -p codesigning + workflows: - version: 2 - build_and_test: + linux: jobs: - build - lint: + name: Linux lint requires: - build # unit, integration and e2e tests @@ -639,10 +660,10 @@ workflows: - server-e2e-tests-8: requires: - build - - 3x-driver-integration-tests: + - driver-integration-tests-3x: requires: - build - - 2x-desktop-gui-integration-tests: + - desktop-gui-integration-tests-2x: requires: - build - run-launcher: @@ -676,6 +697,7 @@ workflows: branches: only: - develop + - build-osx-on-circle-2958 requires: - build - test-next-version: @@ -702,3 +724,35 @@ workflows: requires: - build-npm-package - build-binary + + # disable Mac build until we can figure CircleCI v2 story + # for code signing the built binary + # https://github.com/cypress-io/cypress/issues/2958 + + # mac: + # jobs: + # - build: + # name: Mac build + # executor: mac + # - lint: + # name: Mac lint + # executor: mac + # requires: + # - Mac build + # # maybe run unit tests? + # - mac-os-build: + # filters: + # branches: + # only: + # - develop + + # - build-binary: + # name: Mac binary + # executor: mac + # filters: + # branches: + # only: + # - develop + # requires: + # - Mac build + # - mac-os-build diff --git a/package.json b/package.json index d8a84ea96af9..7a46b27d4bd6 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "decaffeinate-bulk": "bulk-decaffeinate", "check-deps": "node ./scripts/check-deps.js --verbose", "check-deps-pre": "node ./scripts/check-deps.js --verbose --prescript", - "prebuild": "npm run check-deps-pre", + "prebuild": "npm run check-deps-pre && npm run all prebuild", "build": "npm run all build", "all": "node ./scripts/run.js", "test": "echo '⚠️ This root monorepo is only for local development and new contributions. There are no tests.'", @@ -52,7 +52,9 @@ "test-scripts": "mocha --reporter spec scripts/unit/*spec.js", "test-mocha": "mocha --reporter spec scripts/spec.js", "test-mocha-snapshot": "mocha scripts/mocha-snapshot-spec.js", - "check-node-version": "node scripts/check-node-version.js" + "check-node-version": "node scripts/check-node-version.js", + "check-terminal": "node scripts/check-terminal.js", + "effective:circle:config": "circleci config process circle.yml | sed /^#/d" }, "lint-staged": { "*.js": [ diff --git a/packages/desktop-gui/package.json b/packages/desktop-gui/package.json index 59c3cc8ba36c..b023bcbe719d 100644 --- a/packages/desktop-gui/package.json +++ b/packages/desktop-gui/package.json @@ -5,7 +5,7 @@ "private": true, "scripts": { "postinstall": "echo '@packages/desktop-gui needs: npm run build'", - "prebuild": "npm run check-deps-pre", + "prebuild": "npm run check-deps-pre && rebuild-node-sass", "build": "node ./scripts/build-dev.js", "prebuild-prod": "npm run check-deps-pre", "build-prod": "node ./scripts/build-prod.js", @@ -47,6 +47,7 @@ "prop-types": "^15.5.10", "rc-collapse": "^1.6.11", "react": "^15.6.1", + "rebuild-node-sass": "1.1.0", "react-bootstrap-modal": "3.0.1", "react-dom": "^15.6.1", "react-loader": "^2.4.0", diff --git a/packages/runner/package.json b/packages/runner/package.json index a6f5f7f13c94..0d479a5527ca 100644 --- a/packages/runner/package.json +++ b/packages/runner/package.json @@ -5,7 +5,7 @@ "private": true, "scripts": { "postinstall": "echo '@packages/runner needs: npm run build'", - "prebuild": "npm run check-deps-pre rebuild-node-sass", + "prebuild": "npm run check-deps-pre && rebuild-node-sass", "build": "node ./scripts/build-dev.js", "prebuild-prod": "npm run check-deps-pre", "build-prod": "node ./scripts/build-prod.js", diff --git a/scripts/check-terminal.js b/scripts/check-terminal.js new file mode 100644 index 000000000000..4b1e8f94b09b --- /dev/null +++ b/scripts/check-terminal.js @@ -0,0 +1,14 @@ +// checks if the terminal has all the variables set (especially on Linux Docker) + +const assert = require('assert') +const isLinux = process.platform === 'linux' + +if (isLinux) { + assert.ok(process.env.TERM === 'xterm', `process.env.TERM=${process.env.TERM} and must be set to "xterm" for Docker to work`) +} + +assert.ok(process.env.COLUMNS === '100', `process.env.COLUMNS=${process.env.COLUMNS} must be set to 100 for snapshots to pass`) + +/* eslint-disable no-console */ +console.log('stdout.isTTY?', process.stdout.isTTY) +console.log('stderr.isTTY?', process.stderr.isTTY) diff --git a/scripts/win-appveyor-build.js b/scripts/win-appveyor-build.js index 78911348e529..9e30e7990cb0 100755 --- a/scripts/win-appveyor-build.js +++ b/scripts/win-appveyor-build.js @@ -17,6 +17,7 @@ shell.set('-e') // any error is fatal const isRightBranch = () => { const branch = process.env.APPVEYOR_REPO_BRANCH + return branch === 'develop' } @@ -49,9 +50,11 @@ shell.exec(`npm run binary-build -- --platform windows --version ${version}`) // make sure we are not including dev dependencies accidentally // TODO how to get the server package folder? const serverPackageFolder = 'C:/projects/cypress/dist/win32/packages/server' + shell.echo(`Checking prod and dev dependencies in ${serverPackageFolder}`) -shell.exec('npm ls --prod --depth 0 || true', {cwd: serverPackageFolder}) -const result = shell.exec('npm ls --dev --depth 0 || true', {cwd: serverPackageFolder}) +shell.exec('npm ls --prod --depth 0 || true', { cwd: serverPackageFolder }) +const result = shell.exec('npm ls --dev --depth 0 || true', { cwd: serverPackageFolder }) + if (result.stdout.includes('nodemon')) { console.error('Hmm, server package includes dev dependency "nodemon"') console.error('which means somehow we are including dev dependencies in the output bundle') From 7058b58b872ec8c08d59c29384e0146e727b4c62 Mon Sep 17 00:00:00 2001 From: Ben Kucera <14625260+Bkucera@users.noreply.github.com> Date: Wed, 26 Dec 2018 00:53:04 -0600 Subject: [PATCH 12/31] fix inline wrapping element coordinates (#2981) * fix inline wrapping element coordinates * getBoundingClientRect if undefined rects[0] * temp 12/21/18 * fix coordinates_spec * remove only --- packages/driver/src/dom/coordinates.js | 11 +++++- .../driver/test/cypress/fixtures/dom.html | 10 +++++ .../commands/actions/click_spec.coffee | 4 ++ .../integration/dom/coordinates_spec.coffee | 37 ++++++++++++++++++- 4 files changed, 59 insertions(+), 3 deletions(-) diff --git a/packages/driver/src/dom/coordinates.js b/packages/driver/src/dom/coordinates.js index 6deadff4c68f..fffc727c918a 100644 --- a/packages/driver/src/dom/coordinates.js +++ b/packages/driver/src/dom/coordinates.js @@ -5,13 +5,22 @@ const getElementAtPointFromViewport = (doc, x, y) => { } const getElementPositioning = ($el) => { + /** + * @type {HTMLElement} + */ const el = $el[0] const win = $window.getWindowByElement(el) // properties except for width / height // are relative to the top left of the viewport - const rect = el.getBoundingClientRect() + + // we use the first of getClientRects in order to account for inline elements + // that span multiple lines. Which would cause us to click in the center and thus miss + // This should be the same as using getBoundingClientRect() + // for elements with a single rect + // const rect = el.getBoundingClientRect() + const rect = el.getClientRects()[0] || el.getBoundingClientRect() const center = getCenterCoordinates(rect) diff --git a/packages/driver/test/cypress/fixtures/dom.html b/packages/driver/test/cypress/fixtures/dom.html index 693dea69cb85..68cf6112b944 100644 --- a/packages/driver/test/cypress/fixtures/dom.html +++ b/packages/driver/test/cypress/fixtures/dom.html @@ -478,6 +478,16 @@
  • baz
  • quux
  • + + + +
    diff --git a/packages/driver/test/cypress/integration/commands/actions/click_spec.coffee b/packages/driver/test/cypress/integration/commands/actions/click_spec.coffee index 208c97fad149..4b043298831f 100644 --- a/packages/driver/test/cypress/integration/commands/actions/click_spec.coffee +++ b/packages/driver/test/cypress/integration/commands/actions/click_spec.coffee @@ -420,6 +420,10 @@ describe "src/cy/commands/actions/click", -> expect(onClick).to.be.calledOnce describe "actionability", -> + + it 'can click on inline elements that wrap lines', -> + cy.get('#overflow-link').find('.wrapped').click() + it "can click elements which are hidden until scrolled within parent container", -> cy.get("#overflow-auto-container").contains("quux").click() diff --git a/packages/driver/test/cypress/integration/dom/coordinates_spec.coffee b/packages/driver/test/cypress/integration/dom/coordinates_spec.coffee index 5a3f783e858d..a974aae98541 100644 --- a/packages/driver/test/cypress/integration/dom/coordinates_spec.coffee +++ b/packages/driver/test/cypress/integration/dom/coordinates_spec.coffee @@ -32,12 +32,12 @@ describe "src/dom/coordinates", -> value: 20 }) - cy.stub(@$button.get(0), "getBoundingClientRect").returns({ + cy.stub(@$button.get(0), "getClientRects").returns([{ top: 100.9 left: 60.9 width: 50 height: 40 - }) + }]) { fromViewport, fromWindow } = Cypress.dom.getElementPositioning(@$button) @@ -154,3 +154,36 @@ describe "src/dom/coordinates", -> ## padding is added to the line-height but width includes the padding expect(obj.x).to.eq(159) expect(obj.y).to.eq(124) + + context "span spanning multiple lines", -> + it 'gets first dom rect in multiline text', -> + $ ' +
    + this is some long text with a single span that spans lines making it tricky to click +
    + ' + .appendTo $ 'body' + + $el = cy.$$("#multiple") + el = $el[0] + + cy.stub(el, 'getClientRects', -> + [ + { + top: 100 + left: 100 + width: 50 + height: 40 + }, + { + top: 200 + left: 50 + width: 10 + height: 10 + } + ] + + ).as 'getClientRects' + obj = Cypress.dom.getElementCoordinatesByPosition($el, 'center').fromViewport + + expect({x: obj.x, y: obj.y}).to.deep.eq({x:125, y:120}) From c852fa925504487dc2eca20bbc16404a961b18d2 Mon Sep 17 00:00:00 2001 From: Lukas Werfel Date: Wed, 26 Dec 2018 07:57:41 +0100 Subject: [PATCH 13/31] Update lodash to 4.17.11 (#2984) closes #2929 --- cli/package.json | 2 +- package.json | 2 +- packages/desktop-gui/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/package.json b/cli/package.json index e0cf01dbd1db..023c9e9d940a 100644 --- a/cli/package.json +++ b/cli/package.json @@ -67,7 +67,7 @@ "is-installed-globally": "0.1.0", "lazy-ass": "1.6.0", "listr": "0.12.0", - "lodash": "4.17.10", + "lodash": "4.17.11", "log-symbols": "2.2.0", "minimist": "1.2.0", "moment": "2.22.2", diff --git a/package.json b/package.json index 7a46b27d4bd6..0cc99d9ad654 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "konfig": "^0.2.1", "lazy-ass": "^1.6.0", "lint-staged": "^4.1.3", - "lodash": "4.17.10", + "lodash": "4.17.11", "make-empty-github-commit": "^1.2.0", "mocha": "^3.5.0", "mocha-banner": "^1.1.1", diff --git a/packages/desktop-gui/package.json b/packages/desktop-gui/package.json index b023bcbe719d..a1a79c1a043f 100644 --- a/packages/desktop-gui/package.json +++ b/packages/desktop-gui/package.json @@ -38,7 +38,7 @@ "gravatar": "^1.6.0", "human-interval": "^0.1.6", "istanbul": "^0.4.3", - "lodash": "4.17.10", + "lodash": "4.17.11", "md5": "^2.1.0", "mobx": "^3.1.11", "mobx-react": "^4.2.1", From 0ffa701bdbf1b678b570b42adafe4d15eeb21191 Mon Sep 17 00:00:00 2001 From: Ben Kucera <14625260+Bkucera@users.noreply.github.com> Date: Wed, 26 Dec 2018 01:00:17 -0600 Subject: [PATCH 14/31] update cypress install message (#2755) fix #2754 --- cli/__snapshots__/install_spec.js | 12 ++--- cli/lib/tasks/install.js | 2 +- cli/test/lib/tasks/verify_spec.js | 73 ++++++++++++++++++++++--------- cli/test/spec_helper.js | 2 +- 4 files changed, 60 insertions(+), 29 deletions(-) diff --git a/cli/__snapshots__/install_spec.js b/cli/__snapshots__/install_spec.js index 27475c45c916..dc4ed9859a92 100644 --- a/cli/__snapshots__/install_spec.js +++ b/cli/__snapshots__/install_spec.js @@ -56,7 +56,7 @@ https://on.cypress.io/installing-cypress exports['installed version does not match needed version 1'] = ` -Cypress x.x.x is already installed in /cache/Cypress/1.2.3 +Cypress x.x.x is installed in /cache/Cypress/1.2.3 Installing Cypress (version: 1.2.3) @@ -73,7 +73,7 @@ https://on.cypress.io/installing-cypress exports['forcing true always installs 1'] = ` -Cypress 1.2.3 is already installed in /cache/Cypress/1.2.3 +Cypress 1.2.3 is installed in /cache/Cypress/1.2.3 Installing Cypress (version: 1.2.3) @@ -90,7 +90,7 @@ https://on.cypress.io/installing-cypress exports['warning installing as global 1'] = ` -Cypress x.x.x is already installed in /cache/Cypress/1.2.3 +Cypress x.x.x is installed in /cache/Cypress/1.2.3 Installing Cypress (version: 1.2.3) @@ -113,7 +113,7 @@ Installing Cypress (version: 1.2.3) exports['installing in ci 1'] = ` -Cypress x.x.x is already installed in /cache/Cypress/1.2.3 +Cypress x.x.x is installed in /cache/Cypress/1.2.3 Installing Cypress (version: 1.2.3) @@ -162,7 +162,7 @@ exports['silent install 1'] = ` exports['version already installed - cypress install 1'] = ` -Cypress 1.2.3 is already installed in /cache/Cypress/1.2.3 +Cypress 1.2.3 is installed in /cache/Cypress/1.2.3 Skipping installation: @@ -172,7 +172,7 @@ Skipping installation: exports['version already installed - postInstall 1'] = ` -Cypress 1.2.3 is already installed in /cache/Cypress/1.2.3 +Cypress 1.2.3 is installed in /cache/Cypress/1.2.3 ` diff --git a/cli/lib/tasks/install.js b/cli/lib/tasks/install.js index 62f778080390..4f7b74922ada 100644 --- a/cli/lib/tasks/install.js +++ b/cli/lib/tasks/install.js @@ -215,7 +215,7 @@ const start = (options = {}) => { logger.log() logger.log(stripIndent` - Cypress ${chalk.green(binaryVersion)} is already installed in ${chalk.cyan(installDir)} + Cypress ${chalk.green(binaryVersion)} is installed in ${chalk.cyan(installDir)} `) logger.log() diff --git a/cli/test/lib/tasks/verify_spec.js b/cli/test/lib/tasks/verify_spec.js index 2af444d9e509..6bbec19a9e36 100644 --- a/cli/test/lib/tasks/verify_spec.js +++ b/cli/test/lib/tasks/verify_spec.js @@ -44,13 +44,13 @@ context('lib/tasks/verify', () => { } os.platform.returns('darwin') + os.release.returns('0.0.0') sinon.stub(util, 'getCacheDir').returns(cacheDir) sinon.stub(util, 'isCi').returns(false) sinon.stub(util, 'pkgVersion').returns(packageVersion) sinon.stub(util, 'exec') - sinon.stub(xvfb, 'start').resolves() sinon.stub(xvfb, 'stop').resolves() sinon.stub(xvfb, 'isNeeded').returns(false) @@ -70,6 +70,7 @@ context('lib/tasks/verify', () => { it('logs error and exits when no version of Cypress is installed', () => { mockfs({}) + return verify.start() .then(() => { throw new Error('should have caught error') @@ -91,6 +92,7 @@ context('lib/tasks/verify', () => { executable: mockfs.file({ mode: 0777 }), packageVersion, }) + return verify.start() .then(() => { // nothing should have been logged to stdout @@ -166,12 +168,18 @@ context('lib/tasks/verify', () => { }) return verify.start({ force: true }) - .then(() => { throw new Error('Should have thrown') }) + .then(() => { + throw new Error('Should have thrown') + }) .catch((err) => { logger.error(err) }) - .then(() => fs.pathExistsAsync(binaryStatePath)) - .then((exists) => expect(exists).to.eq(false)) + .then(() => { + return fs.pathExistsAsync(binaryStatePath) + }) + .then((exists) => { + return expect(exists).to.eq(false) + }) .then(() => { return snapshot( 'fails verifying Cypress', @@ -190,6 +198,7 @@ context('lib/tasks/verify', () => { 222 after that more text ` + util.exec.withArgs(executablePath).resolves({ stdout: stdoutWithDebugOutput, }) @@ -219,8 +228,11 @@ context('lib/tasks/verify', () => { executable: false, packageVersion, }) + return verify.start() - .then(() => { throw new Error('Should have thrown') }) + .then(() => { + throw new Error('Should have thrown') + }) .catch((err) => { stdout = Stdout.capture() logger.error(err) @@ -239,8 +251,11 @@ context('lib/tasks/verify', () => { executable: mockfs.file({ mode: 0666 }), packageVersion, }) + return verify.start() - .then(() => { throw new Error('Should have thrown') }) + .then(() => { + throw new Error('Should have thrown') + }) .catch((err) => { stdout = Stdout.capture() logger.error(err) @@ -258,6 +273,7 @@ context('lib/tasks/verify', () => { executable: mockfs.file({ mode: 0777 }), packageVersion, }) + return verify.start() .then(() => { return snapshot( @@ -273,6 +289,7 @@ context('lib/tasks/verify', () => { executable: mockfs.file({ mode: 0777 }), packageVersion: '7.8.9', }) + return verify.start() .then(() => { return snapshot( @@ -333,10 +350,13 @@ context('lib/tasks/verify', () => { }) return verify.start() - .then(() => { throw new Error('Should have thrown') }) + .then(() => { + throw new Error('Should have thrown') + }) .catch((err) => { stdout = Stdout.capture() logger.error(err) + return snapshot( 'fails with no stderr', normalize(stdout.toString()) @@ -370,8 +390,10 @@ context('lib/tasks/verify', () => { it('logs error and exits when starting xvfb fails', () => { const err = new Error('test without xvfb') + err.stack = 'xvfb? no dice' xvfb.start.rejects(err) + return verify.start() .catch((err) => { expect(xvfb.stop).to.be.calledOnce @@ -408,8 +430,11 @@ context('lib/tasks/verify', () => { it('logs error when binary not found', () => { mockfs({}) + return verify.start() - .then(() => { throw new Error('Should have thrown') }) + .then(() => { + throw new Error('Should have thrown') + }) .catch((err) => { logger.error(err) snapshot( @@ -437,6 +462,7 @@ context('lib/tasks/verify', () => { '--smoke-test', '--ping=222', ]).resolves(spawnedProcess) + return verify.start() .then(() => { expect(util.exec.firstCall.args[0]).to.equal(realEnvBinaryPath) @@ -444,20 +470,24 @@ context('lib/tasks/verify', () => { }) }) - - ;['darwin', 'linux', 'win32'].forEach((platform) => it('can log error to user', () => { - process.env.CYPRESS_RUN_BINARY = '/custom/' - os.platform.returns(platform) - return verify.start() - .then(() => { throw new Error('Should have thrown') }) - .catch((err) => { - logger.error(err) - snapshot( - `${platform}: error when invalid CYPRESS_RUN_BINARY`, - normalize(stdout.toString()) - ) + ;['darwin', 'linux', 'win32'].forEach((platform) => { + return it('can log error to user', () => { + process.env.CYPRESS_RUN_BINARY = '/custom/' + os.platform.returns(platform) + + return verify.start() + .then(() => { + throw new Error('Should have thrown') + }) + .catch((err) => { + logger.error(err) + snapshot( + `${platform}: error when invalid CYPRESS_RUN_BINARY`, + normalize(stdout.toString()) + ) + }) }) - })) + }) }) }) @@ -484,6 +514,7 @@ function createfs ({ alreadyVerified, executable, packageVersion, customDir }) { mode: 0777, }) } + return mockfs(mockFiles) } diff --git a/cli/test/spec_helper.js b/cli/test/spec_helper.js index 7c3c6ef5091b..9ae94bb1e809 100644 --- a/cli/test/spec_helper.js +++ b/cli/test/spec_helper.js @@ -89,7 +89,7 @@ beforeEach(function () { }) afterEach(function () { + mockfs.restore() process.env = _.clone(env) sinon.restore() - mockfs.restore() }) From ee659b3d2b6dd732215206554445ea3a9e138653 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Wed, 26 Dec 2018 02:02:49 -0500 Subject: [PATCH 15/31] fix: set object for qs property in cy.request types, close #2305 (#2306) * fix: set object for qs property in cy.request types, close #2305 * server whitelist callback takes request, returns boolean --- cli/types/index.d.ts | 11 +++++++++-- cli/types/tests/kitchen-sink.ts | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/cli/types/index.d.ts b/cli/types/index.d.ts index 91ac026b5293..ba5b29bc8851 100644 --- a/cli/types/index.d.ts +++ b/cli/types/index.d.ts @@ -1879,6 +1879,9 @@ declare namespace Cypress { env: object } + /** + * Full set of possible options for cy.request call + */ interface RequestOptions extends Loggable, Timeoutable { auth: object body: RequestBody @@ -1888,7 +1891,7 @@ declare namespace Cypress { gzip: boolean headers: object method: HttpMethod - qs: string + qs: object url: string } @@ -1954,6 +1957,10 @@ declare namespace Cypress { interval: number } + /** + * Setting default options for cy.server() + * @see https://on.cypress.io/server + */ interface ServerOptions { delay: number method: HttpMethod @@ -1966,7 +1973,7 @@ declare namespace Cypress { enable: boolean force404: boolean urlMatchingOptions: object - whitelist(...args: any[]): void + whitelist(xhr: Request): void } interface SetCookieOptions extends Loggable, Timeoutable { diff --git a/cli/types/tests/kitchen-sink.ts b/cli/types/tests/kitchen-sink.ts index b24f676b6e2d..a70992f638ef 100644 --- a/cli/types/tests/kitchen-sink.ts +++ b/cli/types/tests/kitchen-sink.ts @@ -30,6 +30,13 @@ cy.visit('https://www.acme.com/', { } }) +const serverOptions: Partial = { + delay: 100, + whitelist: () => true +} + +cy.server(serverOptions) + Cypress.spec.name // $ExpectType string Cypress.spec.relative // $ExpectType string | null Cypress.spec.absolute // $ExpectType string | null @@ -55,6 +62,15 @@ cy.request({ body: {} }) +// specify query parameters +// https://github.com/cypress-io/cypress/issues/2305 +cy.request({ + url: "http://localhost:3000/myressource", + qs: { + param: 'someValue' + } +}) + // if you want a separate variable, you need specify its type // otherwise TSC does not cast string "POST" as HttpMethod // https://github.com/cypress-io/cypress/issues/2093 From 26c571fff9466291bd06d563d0932c5e332fd5bd Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 26 Dec 2018 03:23:55 -0500 Subject: [PATCH 16/31] release 3.1.4 [skip ci] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0cc99d9ad654..073d02942c03 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cypress", "productName": "Cypress", - "version": "3.1.3", + "version": "3.1.4", "description": "Cypress.io end to end testing tool", "private": true, "engines": { From 852176f468a7aaac93b94f27a2c39d611410c435 Mon Sep 17 00:00:00 2001 From: Thomas Rich Date: Wed, 26 Dec 2018 08:49:13 -0800 Subject: [PATCH 17/31] https://github.com/cypress-io/cypress/issues/2975 (#2976) Connects https://github.com/cypress-io/cypress/issues/2975 --- cli/types/index.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cli/types/index.d.ts b/cli/types/index.d.ts index ba5b29bc8851..7f91216bb2f1 100644 --- a/cli/types/index.d.ts +++ b/cli/types/index.d.ts @@ -623,9 +623,11 @@ declare namespace Cypress { fixture(path: string, encoding: Encodings, options?: Partial): Chainable // no log? /** - * Get the DOM element that is currently focused. + * Focus on a DOM element. * * @see https://on.cypress.io/focus + * @example + * cy.get('input').first().focus() // Focus on the first input */ focus(options?: Partial): Chainable From 3103eba759e67674012738e3551b30ff746fc936 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Thu, 27 Dec 2018 09:51:03 -0500 Subject: [PATCH 18/31] add renovate json file (#2992) * add renovate json file * separate major version upgrades * add labels to renovate PRs * labels should be a list --- CONTRIBUTING.md | 5 +++++ renovate.json | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 renovate.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4a5165c41cd6..01e8e9292387 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,6 +33,7 @@ Thanks for taking the time to contribute! :smile: - [Branches](#branches) - [Pull Requests](#pull-requests) - [Testing](#testing) + - [Dependencies](#dependencies) - [Deployment](#deployment) ## CI status @@ -338,6 +339,10 @@ This repository is exhaustively tested by [CircleCI](https://circleci.com/gh/cyp To run local tests, consult the `README.md` of each package. +### Dependencies + +We use [RenovateBot](https://renovatebot.com/) to automatically upgrade our dependencies. The bot keeps chugging using settings in [renovate.json](renovate.json) to open PRs and if they pass merge patches. Minor and major updates require manual merge. + ## Deployment We will try to review and merge pull requests quickly. After merging we diff --git a/renovate.json b/renovate.json new file mode 100644 index 000000000000..be5fc97d38a6 --- /dev/null +++ b/renovate.json @@ -0,0 +1,24 @@ +{ + "extends": [ + "config:base" + ], + "automerge": true, + "major": { + "automerge": false + }, + "minor": { + "automerge": false + }, + "rangeStrategy": "pin", + "separateMultipleMajor": true, + "labels": ["type: dependencies"], + "commitMessage": "{{semanticPrefix}}Update {{depName}} to {{newVersion}} 🌟", + "prTitle": "{{semanticPrefix}}{{#if isPin}}Pin{{else}}Update{{/if}} dependency {{depName}} to version {{#if isRange}}{{newVersion}}{{else}}{{#if isMajor}}{{newVersionMajor}}.x{{else}}{{newVersion}}{{/if}}{{/if}} 🌟", + "prHourlyLimit": 1, + "updateNotScheduled": false, + "timezone": "America/New_York", + "schedule": [ + "after 10pm and before 5am on every weekday", + "every weekend" + ] +} From 83ba2853f3fc6afd50da3fffc182066b621402c6 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Thu, 27 Dec 2018 15:38:47 -0500 Subject: [PATCH 19/31] chore: do not renovate node engine versions in package json files --- renovate.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index be5fc97d38a6..73a7a74b5fdf 100644 --- a/renovate.json +++ b/renovate.json @@ -10,8 +10,16 @@ "automerge": false }, "rangeStrategy": "pin", + "packageRules": [ + { + "packageNames": [ + "node" + ], + "enabled": false + } + ], "separateMultipleMajor": true, - "labels": ["type: dependencies"], + "labels": ["type: dependencies", "renovate"], "commitMessage": "{{semanticPrefix}}Update {{depName}} to {{newVersion}} 🌟", "prTitle": "{{semanticPrefix}}{{#if isPin}}Pin{{else}}Update{{/if}} dependency {{depName}} to version {{#if isRange}}{{newVersion}}{{else}}{{#if isMajor}}{{newVersionMajor}}.x{{else}}{{newVersion}}{{/if}}{{/if}} 🌟", "prHourlyLimit": 1, From 0c3bd5a4fb2950f2a9e679d171f0c948839b8c7d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Fri, 28 Dec 2018 09:09:02 -0500 Subject: [PATCH 20/31] =?UTF-8?q?chore(deps):=20update=20dependency=20@bab?= =?UTF-8?q?el/core=20to=20version=207.2.2=20=F0=9F=8C=9F=20(#3016)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | References | |---|---|---|---|---| | @​babel/core | devDependencies | minor | `7.1.0` -> `7.2.2` | [homepage](https://babeljs.io/), [source](https://renovatebot.com/gh/babel/babel) | | @​babel/plugin-proposal-class-properties | devDependencies | minor | `7.1.0` -> `7.2.3` | [source](https://renovatebot.com/gh/babel/babel) | | @​babel/plugin-proposal-object-rest-spread | devDependencies | minor | `7.0.0` -> `7.2.0` | [source](https://renovatebot.com/gh/babel/babel) | | @​babel/plugin-transform-runtime | devDependencies | minor | `7.1.0` -> `7.2.0` | [source](https://renovatebot.com/gh/babel/babel) | | @​babel/preset-env | devDependencies | minor | `7.1.0` -> `7.2.3` | [homepage](https://babeljs.io/), [source](https://renovatebot.com/gh/babel/babel) | | @​babel/runtime | devDependencies | minor | `7.0.0` -> `7.2.0` | [source](https://renovatebot.com/gh/babel/babel) | --- ### Release Notes
    babel/babel ### [`v7.2.2`](https://renovatebot.com/gh/babel/babel/releases/v7.2.2) [Compare Source](https://renovatebot.com/gh/babel/babel/compare/v7.2.0...v7.2.2) #### v7.2.2 (2018-12-15) Mostrly bug fixes and internal changes. Thanks to [@​paleite](https://renovatebot.com/gh/paleite), [@​saschanaz](https://renovatebot.com/gh/saschanaz) and [@​joeldenning](https://renovatebot.com/gh/joeldenning) for their first PRs! ##### :bug: Bug Fix - `babel-plugin-transform-destructuring`, `babel-plugin-transform-spread` - [#​9108](https://renovatebot.com/gh/babel/babel/pull/9108) Correctly transform spreads to use proper concat method. ([@​danez](https://renovatebot.com/gh/danez)) - `babel-parser` - [#​9168](https://renovatebot.com/gh/babel/babel/pull/9168) [parser] Handle flow comments with leading spaces. ([@​vikr01](https://renovatebot.com/gh/vikr01)) - `babel-helper-module-transforms`, `babel-plugin-transform-modules-commonjs` - [#​9171](https://renovatebot.com/gh/babel/babel/pull/9171) Fix transforming empty export statement. ([@​danez](https://renovatebot.com/gh/danez)) - `babel-node` - [#​9148](https://renovatebot.com/gh/babel/babel/pull/9148) Fix --root-mode option in babel-node. ([@​nicolo-ribaudo](https://renovatebot.com/gh/nicolo-ribaudo)) - `babel-plugin-transform-classes` - [#​9135](https://renovatebot.com/gh/babel/babel/pull/9135) Inherit properties in function from method in loose mode. ([@​rubennorte](https://renovatebot.com/gh/rubennorte)) - `babel-preset-env` - [#​9140](https://renovatebot.com/gh/babel/babel/pull/9140) Disable parameter-destructuring in Edge 18. ([@​saschanaz](https://renovatebot.com/gh/saschanaz)) - `babel-plugin-transform-arrow-functions`, `babel-traverse` - [#​9060](https://renovatebot.com/gh/babel/babel/pull/9060) Not depending on return value of super(). Closes [#​9020](https://renovatebot.com/gh/babel/babel/issues/9020).. ([@​joeldenning](https://renovatebot.com/gh/joeldenning)) ##### :house: Internal - `babel-helper-create-class-features-plugin`, `babel-plugin-proposal-nullish-coalescing-operator`, `babel-plugin-syntax-bigint`, `babel-plugin-transform-dotall-regex` - [#​9176](https://renovatebot.com/gh/babel/babel/pull/9176) Fix package.json repository URLs. ([@​paleite](https://renovatebot.com/gh/paleite)) - Other - [#​9158](https://renovatebot.com/gh/babel/babel/pull/9158) add triage label to new issues [skip ci]. ([@​danez](https://renovatebot.com/gh/danez)) - [#​9143](https://renovatebot.com/gh/babel/babel/pull/9143) Fix a typo from the issue template for bugs. ([@​saschanaz](https://renovatebot.com/gh/saschanaz)) - [#​9133](https://renovatebot.com/gh/babel/babel/pull/9133) Move to travis vm based builds. ([@​danez](https://renovatebot.com/gh/danez)) - [#​9132](https://renovatebot.com/gh/babel/babel/pull/9132) Ensure we always use repository versions of babel dependencies in tests. ([@​danez](https://renovatebot.com/gh/danez)) - [#​9131](https://renovatebot.com/gh/babel/babel/pull/9131) Update issue templates [skip ci]. ([@​hzoo](https://renovatebot.com/gh/hzoo)) - `babel-helper-create-class-features-plugin`, `babel-plugin-proposal-class-properties`, `babel-plugin-proposal-decorators` - [#​9059](https://renovatebot.com/gh/babel/babel/pull/9059) Move decorators transform to [@​babel/helper-create-class-features-plugin](https://renovatebot.com/gh/babel/helper-create-class-features-plugin). ([@​nicolo-ribaudo](https://renovatebot.com/gh/nicolo-ribaudo)) - `babel-core`, `babel-parser`, `babel-template` - [#​9128](https://renovatebot.com/gh/babel/babel/pull/9128) Fix running flow on travis and update flow. ([@​danez](https://renovatebot.com/gh/danez)) ##### Committers: 10 - Brian Ng ([existentialism](https://renovatebot.com/gh/existentialism)) - Daniel Tschinder ([danez](https://renovatebot.com/gh/danez)) - Henry Zhu ([hzoo](https://renovatebot.com/gh/hzoo)) - Joel Denning ([joeldenning](https://renovatebot.com/gh/joeldenning)) - Kagami Sascha Rosylight ([saschanaz](https://renovatebot.com/gh/saschanaz)) - NicolΓ² Ribaudo ([nicolo-ribaudo](https://renovatebot.com/gh/nicolo-ribaudo)) - Patrick Eriksson ([paleite](https://renovatebot.com/gh/paleite)) - RubΓ©n Norte ([rubennorte](https://renovatebot.com/gh/rubennorte)) - Thiago Arrais ([thiagoarrais](https://renovatebot.com/gh/thiagoarrais)) - Vikram Rangaraj ([vikr01](https://renovatebot.com/gh/vikr01)) ### [`v7.2.0`](https://renovatebot.com/gh/babel/babel/blob/master/CHANGELOG.md#v720-2018-12-03) [Compare Source](https://renovatebot.com/gh/babel/babel/compare/v7.1.6...v7.2.0) You can read more about this release at . ##### :rocket: New Feature - `babel-parser` - [#​8289](https://renovatebot.com/gh/babel/babel/pull/8289) Implement Smart Pipeline proposal in [@​babel/parser](https://renovatebot.com/gh/babel/parser). ([@​mAAdhaTTah](https://renovatebot.com/gh/mAAdhaTTah)) - `babel-core` - [#​8986](https://renovatebot.com/gh/babel/babel/pull/8986) Export [@​babel/parser](https://renovatebot.com/gh/babel/parser)#tokTypes in [@​babel/core](https://renovatebot.com/gh/babel/core). ([@​kaicataldo](https://renovatebot.com/gh/kaicataldo)) - `babel-node` - [#​9078](https://renovatebot.com/gh/babel/babel/pull/9078) Pass `rootMode` from `@babel/node`.. ([@​wtgtybhertgeghgtwtg](https://renovatebot.com/gh/wtgtybhertgeghgtwtg)) - `babel-generator`, `babel-helpers`, `babel-plugin-class-features`, `babel-plugin-proposal-private-methods`, `babel-plugin-syntax-class-properties`, `babel-types` - [#​8654](https://renovatebot.com/gh/babel/babel/pull/8654) Private class methods stage 3. ([@​tim-mc](https://renovatebot.com/gh/tim-mc)) - `babel-preset-env` - [#​9048](https://renovatebot.com/gh/babel/babel/pull/9048) Update mappings for node 10 in preset-env. ([@​existentialism](https://renovatebot.com/gh/existentialism)) ##### :bug: Bug Fix - `babel-parser` - [#​9114](https://renovatebot.com/gh/babel/babel/pull/9114) Parse non-octals with leading zeros in non strict mode correctly. ([@​danez](https://renovatebot.com/gh/danez)) - [#​9074](https://renovatebot.com/gh/babel/babel/pull/9074) Disallow await inside arrow functions. ([@​nicolo-ribaudo](https://renovatebot.com/gh/nicolo-ribaudo)) - [#​9069](https://renovatebot.com/gh/babel/babel/pull/9069) [flow] Allow type casts in array patterns inside arrow parameters. ([@​nicolo-ribaudo](https://renovatebot.com/gh/nicolo-ribaudo)) - [#​9058](https://renovatebot.com/gh/babel/babel/pull/9058) Fix compatibility between typescript and jsx plugins in interface declarations. ([@​danez](https://renovatebot.com/gh/danez)) - [#​9055](https://renovatebot.com/gh/babel/babel/pull/9055) Fix bug with parsing TS generic async arrow function. ([@​existentialism](https://renovatebot.com/gh/existentialism)) - [#​9035](https://renovatebot.com/gh/babel/babel/pull/9035) Fix parsing typescript function types with destructuring. ([@​danez](https://renovatebot.com/gh/danez)) - `babel-helper-fixtures`, `babel-parser` - [#​9113](https://renovatebot.com/gh/babel/babel/pull/9113) Ignore empty fixture directories and fix fixtures in the parser. ([@​danez](https://renovatebot.com/gh/danez)) - `babel-preset-env` - [#​9091](https://renovatebot.com/gh/babel/babel/pull/9091) Update mapping for regex unicode plugin in preset-env. ([@​existentialism](https://renovatebot.com/gh/existentialism)) - `babel-plugin-transform-destructuring` - [#​8916](https://renovatebot.com/gh/babel/babel/pull/8916) Fix destructuring assignment in arrow functions without block. ([@​RubenVerborgh](https://renovatebot.com/gh/RubenVerborgh)) - `babel-plugin-proposal-optional-chaining` - [#​9073](https://renovatebot.com/gh/babel/babel/pull/9073) Microbouji patch/8136. ([@​jridgewell](https://renovatebot.com/gh/jridgewell)) - `babel-core`, `babel-helper-wrap-function`, `babel-plugin-proposal-async-generator-functions`, `babel-plugin-proposal-function-sent`, `babel-plugin-transform-async-to-generator`, `babel-plugin-transform-classes` - [#​9039](https://renovatebot.com/gh/babel/babel/pull/9039) Fix recursive async function expressions. ([@​nicolo-ribaudo](https://renovatebot.com/gh/nicolo-ribaudo)) - `babel-core` - [#​9034](https://renovatebot.com/gh/babel/babel/pull/9034) Normalize presets before merging config with others.. ([@​loganfsmyth](https://renovatebot.com/gh/loganfsmyth)) ##### :nail_care: Polish - `babel-generator` - [#​9089](https://renovatebot.com/gh/babel/babel/pull/9089) Remove unused variable. ([@​Gcaufy](https://renovatebot.com/gh/Gcaufy)) - `babel-node` - [#​9079](https://renovatebot.com/gh/babel/babel/pull/9079) Move `fs-readdir-recursive` and `output-file-sync` to `devDependencies` for `@babel/node`.. ([@​wtgtybhertgeghgtwtg](https://renovatebot.com/gh/wtgtybhertgeghgtwtg)) - `babel-parser` - [#​9046](https://renovatebot.com/gh/babel/babel/pull/9046) a better error message for disallowed trailing commas/additional parameters after rest elements in function params. ([@​morozRed](https://renovatebot.com/gh/morozRed)) - `babel-*` - [#​8769](https://renovatebot.com/gh/babel/babel/pull/8769) Add plugins name. ([@​nicolo-ribaudo](https://renovatebot.com/gh/nicolo-ribaudo)) ##### :house: Internal - `babel-helper-create-class-features-plugin`, `babel-plugin-proposal-class-properties`, `babel-plugin-proposal-private-methods` - [#​9083](https://renovatebot.com/gh/babel/babel/pull/9083) Make [@​babel/plugin-class-features](https://renovatebot.com/gh/babel/plugin-class-features) a normal helper package. ([@​nicolo-ribaudo](https://renovatebot.com/gh/nicolo-ribaudo)) - Other - [#​9096](https://renovatebot.com/gh/babel/babel/pull/9096) Add node 11 to CI and remove node 9. ([@​danez](https://renovatebot.com/gh/danez)) - [#​9094](https://renovatebot.com/gh/babel/babel/pull/9094) Skip minifying standalone in non-publish runs. ([@​danez](https://renovatebot.com/gh/danez)) - `babel-types` - [#​9093](https://renovatebot.com/gh/babel/babel/pull/9093) Fix warning when using prettier in code generators. ([@​danez](https://renovatebot.com/gh/danez)) - `babel-generator` - [#​9089](https://renovatebot.com/gh/babel/babel/pull/9089) Remove unused variable. ([@​Gcaufy](https://renovatebot.com/gh/Gcaufy)) ### [`v7.1.6`](https://renovatebot.com/gh/babel/babel/blob/master/CHANGELOG.md#v716-2018-11-13) [Compare Source](https://renovatebot.com/gh/babel/babel/compare/v7.1.5...v7.1.6) ##### :bug: Bug Fix - `babel-generator` - [#​9003](https://renovatebot.com/gh/babel/babel/pull/9003) Fix retainLines regression for arrow functions. ([@​loganfsmyth](https://renovatebot.com/gh/loganfsmyth)) - `babel-types` - [#​8997](https://renovatebot.com/gh/babel/babel/pull/8997) Fix cloneNode with typeAnnotation.. ([@​neoziro](https://renovatebot.com/gh/neoziro)) - `babel-plugin-transform-flow-strip-types`, `babel-plugin-transform-react-jsx` - [#​8701](https://renovatebot.com/gh/babel/babel/pull/8701) Fix "TypeError: comments is not iterable". ([@​AlicanC](https://renovatebot.com/gh/AlicanC)) - `babel-core` - [#​9004](https://renovatebot.com/gh/babel/babel/pull/9004) Fix browser files to have the same API as the nodejs ones. ([@​danez](https://renovatebot.com/gh/danez)) - Other - [#​9007](https://renovatebot.com/gh/babel/babel/pull/9007) [Types] fix generated TS/Flow comment types. ([@​ljqx](https://renovatebot.com/gh/ljqx)) - `babel-preset-env` - [#​8555](https://renovatebot.com/gh/babel/babel/pull/8555) preset-env: fix `opera` from `esmodules` target and Browserslist not used. ([@​ylemkimon](https://renovatebot.com/gh/ylemkimon)) - `babel-plugin-proposal-decorators`, `babel-traverse` - [#​8970](https://renovatebot.com/gh/babel/babel/pull/8970) [decorators] Correctly insert `_initialize(this)` after `super()`.. ([@​nicolo-ribaudo](https://renovatebot.com/gh/nicolo-ribaudo)) - `babel-parser` - [#​8972](https://renovatebot.com/gh/babel/babel/pull/8972) Fix several edge cases with context expression state. ([@​danez](https://renovatebot.com/gh/danez)) ##### :nail_care: Polish - `babel-parser` - [#​8984](https://renovatebot.com/gh/babel/babel/pull/8984) Rename primitive types to reserved types. ([@​danez](https://renovatebot.com/gh/danez)) ##### :house: Internal - [#​8982](https://renovatebot.com/gh/babel/babel/pull/8982) fix publish command [skip ci]. ([@​hzoo](https://renovatebot.com/gh/hzoo)) - [#​8988](https://renovatebot.com/gh/babel/babel/pull/8988) Remove definition of micromatch which was removed.. ([@​danez](https://renovatebot.com/gh/danez)) ### [`v7.1.5`](https://renovatebot.com/gh/babel/babel/blob/master/CHANGELOG.md#v715-2018-11-06) [Compare Source](https://renovatebot.com/gh/babel/babel/compare/v7.1.2...v7.1.5) ##### :eyeglasses: Spec Compliancy - `babel-parser`, `babylon` - [#​7727](https://renovatebot.com/gh/babel/babel/pull/7727) Fix await in function name and parameters. ([@​nicolo-ribaudo](https://renovatebot.com/gh/nicolo-ribaudo)) ##### :rocket: New Feature - `babel-parser` - [#​8828](https://renovatebot.com/gh/babel/babel/pull/8828) Typescript: Validate tuple type element positions. ([@​Retsam](https://renovatebot.com/gh/Retsam)) - [#​8883](https://renovatebot.com/gh/babel/babel/pull/8883) [flow] Add support for parsing `_` as implicit instantiation in call/new. ([@​jbrown215](https://renovatebot.com/gh/jbrown215)) - `babel-core`, `babel-generator`, `babel-parser`, `babel-plugin-syntax-typescript`, `babel-traverse` - [#​8448](https://renovatebot.com/gh/babel/babel/pull/8448) Remove Babylon plugins for features already merged to the ECMAScript spec. ([@​nicolo-ribaudo](https://renovatebot.com/gh/nicolo-ribaudo)) - `babel-parser`, `babel-types` - [#​8884](https://renovatebot.com/gh/babel/babel/pull/8884) [flow] Explicit inexact objects with `...`. ([@​jbrown215](https://renovatebot.com/gh/jbrown215)) - `babel-preset-env` - [#​8898](https://renovatebot.com/gh/babel/babel/pull/8898) Update preset-env data. ([@​existentialism](https://renovatebot.com/gh/existentialism)) ##### :bug: Bug Fix - `babel-parser` - [#​8956](https://renovatebot.com/gh/babel/babel/pull/8956) Do not allow TypeCastExpressions w/o parens . ([@​danez](https://renovatebot.com/gh/danez)) - [#​8954](https://renovatebot.com/gh/babel/babel/pull/8954) Allow function types in type params within arrow return types. ([@​danez](https://renovatebot.com/gh/danez)) - [#​8866](https://renovatebot.com/gh/babel/babel/pull/8866) Closes [#​8865](https://renovatebot.com/gh/babel/babel/issues/8865). ([@​byronluk](https://renovatebot.com/gh/byronluk)) - `babel-core` - [#​8910](https://renovatebot.com/gh/babel/babel/pull/8910) Resolve babel.config.js 'babelrcRoots' values relative to the config file.. ([@​loganfsmyth](https://renovatebot.com/gh/loganfsmyth)) - [#​8950](https://renovatebot.com/gh/babel/babel/pull/8950) Fix message when plugin of a wrong type is passed. ([@​everdimension](https://renovatebot.com/gh/everdimension)) - `babel-plugin-transform-block-scoping` - [#​8937](https://renovatebot.com/gh/babel/babel/pull/8937) rename colliding let bindings with for loop init. ([@​byronluk](https://renovatebot.com/gh/byronluk)) - [#​8914](https://renovatebot.com/gh/babel/babel/pull/8914) Treat break inside block inside loop. ([@​thiagoarrais](https://renovatebot.com/gh/thiagoarrais)) - `babel-preset-env` - [#​8926](https://renovatebot.com/gh/babel/babel/pull/8926) preset-env: Edge support for arrow param destructuring. ([@​benmosher](https://renovatebot.com/gh/benmosher)) - `babel-generator` - [#​8868](https://renovatebot.com/gh/babel/babel/pull/8868) fix single-arg async arrows when retainLines=true. ([@​ryanwmarsh](https://renovatebot.com/gh/ryanwmarsh)) - `babel-traverse` - [#​8880](https://renovatebot.com/gh/babel/babel/pull/8880) fix: Expression x === 'y' && '' should not evaluate to undefined.. ([@​Cyp](https://renovatebot.com/gh/Cyp)) ##### :nail_care: Polish - [#​8873](https://renovatebot.com/gh/babel/babel/pull/8873) fixed an extra word. ([@​vvyomjjain](https://renovatebot.com/gh/vvyomjjain)) ### [`v7.1.2`](https://renovatebot.com/gh/babel/babel/blob/master/CHANGELOG.md#​712-2018-09-28) [Compare Source](https://renovatebot.com/gh/babel/babel/compare/v7.1.1...v7.1.2) Same as v7.1.1, except compiled against Node 6 instead of Node 8 by accident (e.g had `async functions`). ### [`v7.1.1`](https://renovatebot.com/gh/babel/babel/blob/master/CHANGELOG.md#v711-2018-09-28) [Compare Source](https://renovatebot.com/gh/babel/babel/compare/v7.1.0...v7.1.1) > EDIT: had a publish issue here as well where it compiled against Node 8 instead of Node 6 so 7.1.2 will fix this. > Also force publish `@babel/runtime` and `@babel/runtime-corejs2`. We need to fix the publishing around that since Lerna doesn't pickup the `@babel/helpers` changes as there is no "dependency" > > ##### :bug: Bug Fix > > - `babel-generator`, `babel-parser`, `babel-types` > - [#​8755](https://renovatebot.com/gh/babel/babel/pull/8755) TypeScript: reserve `unknown` as TSUnknownKeyword. ([@​g-plane](https://renovatebot.com/gh/g-plane)) > - `babel-plugin-transform-destructuring` > - [#​8535](https://renovatebot.com/gh/babel/babel/pull/8535) Do not unpack array patterns that update a referenced binding. ([@​nicolo-ribaudo](https://renovatebot.com/gh/nicolo-ribaudo)) > - `babel-plugin-proposal-decorators` > - [#​8742](https://renovatebot.com/gh/babel/babel/pull/8742) [decorators] Support async and generator methods. ([@​nicolo-ribaudo](https://renovatebot.com/gh/nicolo-ribaudo)) > - `babel-helpers`, `babel-plugin-proposal-decorators` > - [#​8761](https://renovatebot.com/gh/babel/babel/pull/8761) [decorators] Fields are enumerable. ([@​nicolo-ribaudo](https://renovatebot.com/gh/nicolo-ribaudo)) > - `babel-generator` > - [#​8751](https://renovatebot.com/gh/babel/babel/pull/8751) Fix some missing parens cases with OptionalMemberExpression in generator. ([@​existentialism](https://renovatebot.com/gh/existentialism)) > - [#​8727](https://renovatebot.com/gh/babel/babel/pull/8727) Handle throw expressions in generator. ([@​existentialism](https://renovatebot.com/gh/existentialism)) > > ##### :house: Internal > > - Other > - [#​8780](https://renovatebot.com/gh/babel/babel/pull/8780) Run test262 tests for exportNamespaceFrom. ([@​existentialism](https://renovatebot.com/gh/existentialism)) > - `babel-helper-transform-fixture-test-runner` > - [#​8768](https://renovatebot.com/gh/babel/babel/pull/8768) Use babel-check-duplicated-nodes. ([@​nicolo-ribaudo](https://renovatebot.com/gh/nicolo-ribaudo))
    --- ### Renovate configuration :date: **Schedule**: "after 10pm every weekday,every weekend,before 5am every weekday" in timezone America/New_York. :vertical_traffic_light: **Automerge**: Disabled by config. Please merge this manually once you are satisfied. :recycle: **Rebasing**: Whenever PR becomes conflicted, or if you modify the PR title to begin with "`rebase!`". :ghost: **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://renovatebot.com/gh/renovatebot/config-help/issues) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://renovatebot.com/gh/marketplace/renovate). View repository job log [here](https://renovatebot.com/dashboard#cypress-io/cypress). --- packages/desktop-gui/package.json | 2 +- packages/reporter/package.json | 2 +- packages/runner/package.json | 2 +- packages/server/package.json | 12 ++++++------ 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/desktop-gui/package.json b/packages/desktop-gui/package.json index a1a79c1a043f..59d7d5798613 100644 --- a/packages/desktop-gui/package.json +++ b/packages/desktop-gui/package.json @@ -24,7 +24,7 @@ "lib" ], "devDependencies": { - "@babel/plugin-proposal-object-rest-spread": "7.0.0", + "@babel/plugin-proposal-object-rest-spread": "7.2.0", "@cypress/icons": "0.7.0", "@cypress/json-schemas": "5.26.0", "@cypress/react-tooltip": "^0.2.2", diff --git a/packages/reporter/package.json b/packages/reporter/package.json index e98b8e184e96..982820283f38 100644 --- a/packages/reporter/package.json +++ b/packages/reporter/package.json @@ -25,7 +25,7 @@ "dist" ], "devDependencies": { - "@babel/plugin-proposal-object-rest-spread": "7.0.0", + "@babel/plugin-proposal-object-rest-spread": "7.2.0", "@cypress/react-tooltip": "0.4.0", "@cypress/releaser": "0.1.12", "bin-up": "1.1.0", diff --git a/packages/runner/package.json b/packages/runner/package.json index 0d479a5527ca..bc9491b6d73d 100644 --- a/packages/runner/package.json +++ b/packages/runner/package.json @@ -24,7 +24,7 @@ "lib" ], "devDependencies": { - "@babel/plugin-proposal-object-rest-spread": "7.0.0", + "@babel/plugin-proposal-object-rest-spread": "7.2.0", "@cypress/react-tooltip": "0.4.0", "@cypress/releaser": "0.1.12", "bin-up": "1.1.0", diff --git a/packages/server/package.json b/packages/server/package.json index 00234b23f1ad..10734791d960 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -46,13 +46,13 @@ "lib" ], "devDependencies": { - "@babel/core": "7.1.0", - "@babel/plugin-proposal-class-properties": "7.1.0", - "@babel/plugin-proposal-object-rest-spread": "7.0.0", - "@babel/plugin-transform-runtime": "7.1.0", - "@babel/preset-env": "7.1.0", + "@babel/core": "7.2.2", + "@babel/plugin-proposal-class-properties": "7.2.3", + "@babel/plugin-proposal-object-rest-spread": "7.2.0", + "@babel/plugin-transform-runtime": "7.2.0", + "@babel/preset-env": "7.2.3", "@babel/preset-react": "7.0.0", - "@babel/runtime": "7.0.0", + "@babel/runtime": "7.2.0", "@cypress/json-schemas": "5.27.0", "@cypress/sinon-chai": "^1.1.0", "babel-plugin-add-module-exports": "^1.0.0", From dba2b4a7a6c323d8a04e87fea4a944fafbc8da1c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Wed, 2 Jan 2019 12:28:55 +0630 Subject: [PATCH 21/31] =?UTF-8?q?chore(deps):=20update=20dependency=20@cyp?= =?UTF-8?q?ress/json-schemas=20to=20version=205.31.2=20=F0=9F=8C=9F=20(#30?= =?UTF-8?q?20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): update @cypress/json-schemas to 5.31.2 🌟 * Update runs fixture to match new schema requirements --- .../desktop-gui/cypress/fixtures/runs.json | 27 +++++++++++++------ packages/desktop-gui/package.json | 2 +- packages/server/package.json | 2 +- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/packages/desktop-gui/cypress/fixtures/runs.json b/packages/desktop-gui/cypress/fixtures/runs.json index a13bb38ba256..4a0d9a51867c 100644 --- a/packages/desktop-gui/cypress/fixtures/runs.json +++ b/packages/desktop-gui/cypress/fixtures/runs.json @@ -4,20 +4,22 @@ "ci": { "buildNumber": "140", "provider": "circle", + "pullRequestId": "797", + "pullRequestUrl": "https://github.com/jane-lane/jekyl_blog/pull/797", "url": "https://circleci.com/gh/jekyl/jekyl_blog/140" }, + "ciBuildId": "circle-6443", "commit": { "authorEmail": "julie@devs.com", "authorName": "Julie Pearson", "branch": "search-todos", "message": "remove listings from search results on clear", - "pullRequestId": "797", - "pullRequestUrl": "https://github.com/jane-lane/jekyl_blog/pull/797", "sha": "f4b728bfcc61a1ea23d0f1ced676719550daae27", "url": "https://github.com/jekyl/jekyl_blog/commit/f4b728bfcc61a1ea23d0f1ced676719550daae27" }, "completedAt": null, "createdAt": "2016-05-13T02:35:12.748Z", + "customCiBuildId": false, "cypressVersion": "2.1.0", "totalDuration": null, "totalFailed": 0, @@ -138,9 +140,11 @@ { "buildNumber": 1892, "ci": null, + "ciBuildId": null, "commit": null, "completedAt": "2016-05-13T02:30:26.000Z", "createdAt": "2016-12-19T14:59:59.328Z", + "customCiBuildId": false, "cypressVersion": "2.1.0", "totalDuration": 16000, "totalFailed": 14, @@ -244,20 +248,23 @@ "ci": { "buildNumber": "1780", "provider": "circle", + "pullRequestId": "797", + "pullRequestUrl": "https://github.com/jane-lane/jekyl_blog/pull/797", "url": "https://circleci.com/gh/jane-lane/webAppOnline/1780" }, + "ciBuildId": "circle-job-123", "commit": { "authorEmail": "julie@devs.com", "authorName": "Julie Pearson", "branch": "search-todos", "message": "regex remove whitespace", - "pullRequestId": "797", - "pullRequestUrl": "https://github.com/jane-lane/jekyl_blog/pull/797", + "sha": "19550dacc61a1ea23d0f1ced676719550daae27", "url": "https://github.com/jane-lane/webAppOnline/commit/19550dacc61a1ea23d0f1ced676719550daae27" }, "completedAt": "2016-05-13T02:30:26.000Z", "createdAt": "2016-03-21T02:35:12.748Z", + "customCiBuildId": true, "cypressVersion": "2.1.0", "totalDuration": 1424424, "totalFailed": 0, @@ -286,20 +293,22 @@ "ci": { "buildNumber": "140", "provider": "circle", + "pullRequestId": "797", + "pullRequestUrl": "https://github.com/jane-lane/jekyl_blog/pull/797", "url": "https://circleci.com/gh/cypress-io/cypress-core-example/140" }, + "ciBuildId": "circle-6441", "commit": { "authorEmail": "julie@devs.com", "authorName": "Julie Pearson", "branch": "search-todos", "message": "remove listings from search results on clear", - "pullRequestId": "797", - "pullRequestUrl": "https://github.com/jane-lane/jekyl_blog/pull/797", "sha": "f4b728bfcc61a1ea23d0f1ced676719550daae27", "url": "https://github.com/jekyl/jekyl_blog/commit/f4b728bfcc61a1ea23d0f1ced676719550daae27" }, "completedAt": null, "createdAt": "2016-05-13T02:14:12.748Z", + "customCiBuildId": false, "cypressVersion": "2.1.0", "totalDuration": 0, "totalFailed": 0, @@ -366,20 +375,22 @@ "ci": { "buildNumber": "720", "provider": "circle", + "pullRequestId": "797", + "pullRequestUrl": "https://github.com/jane-lane/jekyl_blog/pull/797", "url": "https://circleci.com/gh/cypress-io/cypress-core-example/140" }, + "ciBuildId": "circle-6440", "commit": { "authorEmail": "julie@devs.com", "authorName": "Julie Pearson", "branch": "search-todos", "message": "remove listings from search results on clear", - "pullRequestId": "797", - "pullRequestUrl": "https://github.com/jane-lane/jekyl_blog/pull/797", "sha": "daae27cc61a1ea23d0f1ced676719550daae27", "url": "https://github.com/jane-lane/management-app/commit/daae27cc61a1ea23d0f1ced676719550daae27" }, "completedAt": "2016-05-13T02:35:12.748Z", "createdAt": "2015-08-21T02:35:12.748Z", + "customCiBuildId": false, "cypressVersion": "2.1.0", "totalDuration": 1424424, "totalFailed": 0, diff --git a/packages/desktop-gui/package.json b/packages/desktop-gui/package.json index 59d7d5798613..3022263120fc 100644 --- a/packages/desktop-gui/package.json +++ b/packages/desktop-gui/package.json @@ -26,7 +26,7 @@ "devDependencies": { "@babel/plugin-proposal-object-rest-spread": "7.2.0", "@cypress/icons": "0.7.0", - "@cypress/json-schemas": "5.26.0", + "@cypress/json-schemas": "5.31.2", "@cypress/react-tooltip": "^0.2.2", "@cypress/releaser": "0.1.12", "bin-up": "^1.0.0", diff --git a/packages/server/package.json b/packages/server/package.json index 10734791d960..2c2532807d8b 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -53,7 +53,7 @@ "@babel/preset-env": "7.2.3", "@babel/preset-react": "7.0.0", "@babel/runtime": "7.2.0", - "@cypress/json-schemas": "5.27.0", + "@cypress/json-schemas": "5.31.2", "@cypress/sinon-chai": "^1.1.0", "babel-plugin-add-module-exports": "^1.0.0", "babelify": "10.0.0", From de3b20bed55a239d99e9e5a3749b3b677c84fc53 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Wed, 2 Jan 2019 15:21:54 +0630 Subject: [PATCH 22/31] =?UTF-8?q?chore(deps):=20update=20font-awesome=20to?= =?UTF-8?q?=204.7.0=20=F0=9F=8C=9F=20(#3048)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/reporter/package.json | 2 +- packages/runner/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/reporter/package.json b/packages/reporter/package.json index 982820283f38..bae8437377ee 100644 --- a/packages/reporter/package.json +++ b/packages/reporter/package.json @@ -35,7 +35,7 @@ "css-element-queries": "0.3.2", "enzyme": "3.7.0", "enzyme-adapter-react-16": "1.6.0", - "font-awesome": "4.6.3", + "font-awesome": "4.7.0", "jsdom": "9.4.1", "lodash": "4.17.11", "markdown-it": "6.1.1", diff --git a/packages/runner/package.json b/packages/runner/package.json index bc9491b6d73d..071745444f88 100644 --- a/packages/runner/package.json +++ b/packages/runner/package.json @@ -34,7 +34,7 @@ "classnames": "2.2.6", "enzyme": "3.7.0", "enzyme-adapter-react-16": "1.6.0", - "font-awesome": "4.6.3", + "font-awesome": "4.7.0", "jsdom": "9.4.1", "lodash": "4.17.11", "mobx": "3.1.15", From 13178b01cb0e72a9fe9635551a6b2042b820191d Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Wed, 2 Jan 2019 13:58:20 -0500 Subject: [PATCH 23/31] Pin prod dependencies 3025 (#3026) * pin packages/electron prod dependencies * pin packages/example * pin packages/extension * pin packages/https-proxy * pin packages/launcher * pin packages/server * pin packages/ts --- packages/electron/package.json | 12 +-- packages/example/package.json | 4 +- packages/extension/package.json | 4 +- packages/https-proxy/package.json | 12 +-- packages/launcher/package.json | 16 ++-- packages/server/package.json | 128 +++++++++++++++--------------- packages/ts/package.json | 2 +- 7 files changed, 89 insertions(+), 89 deletions(-) diff --git a/packages/electron/package.json b/packages/electron/package.json index 01818f319a45..05433a333925 100644 --- a/packages/electron/package.json +++ b/packages/electron/package.json @@ -29,12 +29,12 @@ "mocha": "^3.0.2" }, "dependencies": { - "@cypress/icons": "^0.6.0", - "bluebird": "^3.4.1", - "debug": "^3.0.1", + "@cypress/icons": "0.6.0", + "bluebird": "3.5.3", + "debug": "3.2.6", "electron-packager": "9.0.1", - "fs-extra": "^0.30.0", - "lodash": "^4.15.0", - "minimist": "^1.2.0" + "fs-extra": "0.30.0", + "lodash": "4.17.11", + "minimist": "1.2.0" } } diff --git a/packages/example/package.json b/packages/example/package.json index 37cd68446678..cd33f362db6d 100644 --- a/packages/example/package.json +++ b/packages/example/package.json @@ -22,8 +22,8 @@ "lib" ], "dependencies": { - "bluebird": "^3.5.1", - "glob": "^7.1.2" + "bluebird": "3.5.3", + "glob": "7.1.3" }, "devDependencies": { "bin-up": "^1.1.0", diff --git a/packages/extension/package.json b/packages/extension/package.json index d49632115447..f3436a81d956 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -46,7 +46,7 @@ "vinyl-source-stream": "^1.1.0" }, "dependencies": { - "bluebird": "^3.3.5", - "lodash": "^4.11.2" + "bluebird": "3.5.3", + "lodash": "4.17.11" } } diff --git a/packages/https-proxy/package.json b/packages/https-proxy/package.json index 8c74e45cb9b2..fe744fdcef5a 100644 --- a/packages/https-proxy/package.json +++ b/packages/https-proxy/package.json @@ -33,13 +33,13 @@ "supertest-as-promised": "^3.1.0" }, "dependencies": { - "bluebird": "^3.4.0", - "debug": "^2.6.8", - "fs-extra": "^0.30.0", + "bluebird": "3.5.3", + "debug": "2.6.9", + "fs-extra": "0.30.0", "lodash": "4.17.11", - "node-forge": "^0.6.39", - "semaphore": "^1.0.5", + "node-forge": "0.6.49", + "semaphore": "1.1.0", "server-destroy-vvo": "1.0.1", - "ssl-root-cas": "^1.1.10" + "ssl-root-cas": "1.2.5" } } diff --git a/packages/launcher/package.json b/packages/launcher/package.json index ba088b3a9ffd..689d817bdb84 100644 --- a/packages/launcher/package.json +++ b/packages/launcher/package.json @@ -46,13 +46,13 @@ "typescript": "2.9.2" }, "dependencies": { - "bluebird": "^3.5.0", - "debug": "^2.6.6", - "execa": "^0.6.3", - "fs-extra": "^3.0.0", - "lodash": "^4.11.1", - "plist": "^2.1.0", - "pluralize": "^7.0.0", - "ramda": "^0.24.1" + "bluebird": "3.5.3", + "debug": "2.6.9", + "execa": "0.6.3", + "fs-extra": "3.0.1", + "lodash": "4.17.11", + "plist": "2.1.0", + "pluralize": "7.0.0", + "ramda": "0.24.1" } } diff --git a/packages/server/package.json b/packages/server/package.json index 2c2532807d8b..a54d4745a094 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -100,99 +100,99 @@ "@cypress/browserify-preprocessor": "1.1.2", "@cypress/commit-info": "2.1.1", "@cypress/icons": "0.5.4", - "@cypress/mocha-teamcity-reporter": "^1.0.0", + "@cypress/mocha-teamcity-reporter": "1.0.0", "@ffmpeg-installer/ffmpeg": "1.0.15", - "ansi_up": "^1.3.0", + "ansi_up": "1.3.0", "bluebird": "3.4.7", - "browserify": "^13.1.1", - "chai": "^1.9.2", - "chalk": "^2.4.1", - "check-more-types": "^2.24.0", + "browserify": "13.3.0", + "chai": "1.10.0", + "chalk": "2.4.1", + "check-more-types": "2.24.0", "chokidar": "1.6.0", - "cjsxify": "^0.3.0", - "clear-module": "^2.1.0", - "cli-table2": "^0.2.0", - "color-string": "^1.5.2", - "common-tags": "^1.8.0", + "cjsxify": "0.3.0", + "clear-module": "2.1.0", + "cli-table2": "0.2.0", + "color-string": "1.5.3", + "common-tags": "1.8.0", "compression": "1.7.2", - "concat-stream": "^1.5.1", - "content-type": "^1.0.2", - "cookie": "^0.2.3", - "cookie-parser": "^1.3.3", + "concat-stream": "1.6.2", + "content-type": "1.0.4", + "cookie": "0.2.4", + "cookie-parser": "1.4.3", "data-uri-to-buffer": "0.0.4", - "debug": "^2.6.8", - "dependency-tree": "^6.0.1", - "electron-context-menu": "^0.8.0", + "debug": "2.6.9", + "dependency-tree": "6.4.0", + "electron-context-menu": "0.8.0", "electron-positioner": "3.0.0", "errorhandler": "1.1.1", - "evil-dns": "^0.2.0", - "execa": "^0.8.0", + "evil-dns": "0.2.0", + "execa": "0.8.0", "express": "4.16.2", - "find-process": "^1.1.1", - "fluent-ffmpeg": "^2.1.0", + "find-process": "1.2.1", + "fluent-ffmpeg": "2.1.2", "fs-extra": "4.0.3", - "getos": "^2.8.2", + "getos": "2.8.4", "glob": "7.1.2", - "graceful-fs": "^4.1.11", - "gulp-util": "^3.0.6", + "graceful-fs": "4.1.15", + "gulp-util": "3.0.8", "hbs": "4.0.0", - "http-accept": "^0.1.6", + "http-accept": "0.1.6", "http-proxy": "1.17.0", - "http-status-codes": "^1.0.6", - "human-interval": "^0.1.5", - "image-size": "^0.5.0", + "http-status-codes": "1.3.0", + "human-interval": "0.1.6", + "image-size": "0.5.5", "is-fork-pr": "2.0.0", - "jimp": "^0.2.28", - "jsonlint": "^1.6.2", - "konfig": "^0.2.0", - "lazy-ass": "^1.6.0", - "lockfile": "^1.0.3", + "jimp": "0.2.28", + "jsonlint": "1.6.3", + "konfig": "0.2.1", + "lazy-ass": "1.6.0", + "lockfile": "1.0.4", "lodash": "4.17.4", - "log-symbols": "^2.2.0", - "md5": "^2.2.1", - "method-override": "^2.3.1", + "log-symbols": "2.2.0", + "md5": "2.2.1", + "method-override": "2.3.10", "mime": "1.2.11", - "minimatch": "^3.0.0", - "minimist": "^1.1.2", + "minimatch": "3.0.4", + "minimist": "1.2.0", "mocha": "2.4.5", "mocha-junit-reporter": "1.17.0", - "moment": "^2.14.1", + "moment": "2.23.0", "morgan": "1.3.0", - "node-machine-id": "^1.1.4", + "node-machine-id": "1.1.10", "node-uuid": "1.4.1", "node-webkit-updater": "cypress-io/node-webkit-updater#e74623726f381487f543e373e71515177a32daeb", "opn": "cypress-io/opn#2f4e9a216ca7bdb95dfae9d46d99ddf004b3cbb5", - "ospath": "^1.1.0", - "p-queue": "^1.0.0", + "ospath": "1.2.2", + "p-queue": "1.2.0", "parse-domain": "2.0.0", - "pluralize": "^3.0.0", + "pluralize": "3.1.0", "pumpify": "1.5.1", - "ramda": "^0.24.0", - "randomstring": "^1.1.5", - "replacestream": "^4.0.3", + "ramda": "0.24.1", + "randomstring": "1.1.5", + "replacestream": "4.0.3", "request": "2.88.0", "request-promise": "4.1.1", - "return-deep-diff": "^0.2.9", - "sanitize-filename": "^1.6.1", - "semver": "^5.3.0", - "send": "^0.14.1", + "return-deep-diff": "0.2.9", + "sanitize-filename": "1.6.1", + "semver": "5.6.0", + "send": "0.14.2", "server-destroy": "1.0.1", - "shell-env": "^0.3.0", - "signal-exit": "^3.0.2", - "sinon": "^5.0.0", - "string-to-stream": "^1.0.1", - "strip-ansi": "^3.0.1", - "supports-color": "^5.1.0", - "syntax-error": "^1.1.4", - "tar-fs": "^1.11.1", - "term-size": "^1.2.0", + "shell-env": "0.3.0", + "signal-exit": "3.0.2", + "sinon": "5.1.1", + "string-to-stream": "1.1.1", + "strip-ansi": "3.0.1", + "supports-color": "5.5.0", + "syntax-error": "1.4.0", + "tar-fs": "1.16.3", + "term-size": "1.2.0", "through": "2.3.6", "tough-cookie": "2.4.3", "trash": "4.0.0", - "underscore": "^1.8.3", + "underscore": "1.9.1", "underscore.string": "3.3.4", - "url-parse": "^1.1.7", - "widest-line": "^2.0.0", - "winston": "^0.9.0" + "url-parse": "1.4.4", + "widest-line": "2.0.1", + "winston": "0.9.0" } } diff --git a/packages/ts/package.json b/packages/ts/package.json index d1b28b4c06aa..6d4413319c56 100644 --- a/packages/ts/package.json +++ b/packages/ts/package.json @@ -15,6 +15,6 @@ "typescript": "2.5.2" }, "dependencies": { - "debug": "^2.6.8" + "debug": "2.6.9" } } From 7df6c44ddea2d0417a6b9d4d1072977c00dde2dc Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Wed, 2 Jan 2019 14:08:05 -0500 Subject: [PATCH 24/31] chore: add prConcurrentLimit renovate setting [skip ci] --- renovate.json | 1 + 1 file changed, 1 insertion(+) diff --git a/renovate.json b/renovate.json index 73a7a74b5fdf..c63e5459458d 100644 --- a/renovate.json +++ b/renovate.json @@ -23,6 +23,7 @@ "commitMessage": "{{semanticPrefix}}Update {{depName}} to {{newVersion}} 🌟", "prTitle": "{{semanticPrefix}}{{#if isPin}}Pin{{else}}Update{{/if}} dependency {{depName}} to version {{#if isRange}}{{newVersion}}{{else}}{{#if isMajor}}{{newVersionMajor}}.x{{else}}{{newVersion}}{{/if}}{{/if}} 🌟", "prHourlyLimit": 1, + "prConcurrentLimit": 5, "updateNotScheduled": false, "timezone": "America/New_York", "schedule": [ From bc0daf838be4e4239eac35d5f03d9ca658a5ecea Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Wed, 2 Jan 2019 14:37:03 -0500 Subject: [PATCH 25/31] =?UTF-8?q?chore(deps):=20update=20dtslint=20to=200.?= =?UTF-8?q?4.2=20=F0=9F=8C=9F=20(#3041)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/package.json b/cli/package.json index 023c9e9d940a..8cbc7b16baa2 100644 --- a/cli/package.json +++ b/cli/package.json @@ -89,7 +89,7 @@ "chai-string": "1.4.0", "clear-module": "^2.1.0", "dependency-check": "^2.8.0", - "dtslint": "0.4.1", + "dtslint": "0.4.2", "execa-wrap": "1.1.0", "mock-fs": "4.5.0", "nock": "^9.0.9", From 71429e620e02ad9636d36dfe80650f0b79450745 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Wed, 2 Jan 2019 15:09:18 -0500 Subject: [PATCH 26/31] Fix TypeScript Omit definition (#3031) * test built binary and NPM against kitchensink * try building cypress/browsers:chrome67 * implement Omit using Exclude * bump down to chrome64 * remove this branch from building binary --- circle.yml | 38 +++++++++++++++++++++++++++++++++++++- cli/types/index.d.ts | 5 ++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/circle.yml b/circle.yml index 3edc7a7c5f9b..1aec25322518 100644 --- a/circle.yml +++ b/circle.yml @@ -608,6 +608,35 @@ jobs: CYPRESS_ENV=staging \ $(npm bin)/cypress run --record + "test-binary-against-kitchensink": + <<: *defaults + steps: + - attach_workspace: + at: ~/ + - attach_workspace: + at: /tmp/urls + - run: + name: Cloning kitchensink project + command: git clone --depth 1 https://github.com/cypress-io/cypress-example-kitchensink.git /tmp/kitchensink + - run: + command: npm install + working_directory: /tmp/kitchensink + - run: + name: Install Cypress + working_directory: /tmp/kitchensink + # force installing the freshly built binary + command: CYPRESS_INSTALL_BINARY=/tmp/urls/cypress.zip npm i /tmp/urls/cypress.tgz + - run: + working_directory: /tmp/kitchensink + command: npm run build + - run: + working_directory: /tmp/kitchensink + command: npm start + background: true + - run: + working_directory: /tmp/kitchensink + command: npm run e2e + mac-os-build: executor: mac steps: @@ -697,7 +726,6 @@ workflows: branches: only: - develop - - build-osx-on-circle-2958 requires: - build - test-next-version: @@ -724,6 +752,14 @@ workflows: requires: - build-npm-package - build-binary + - test-binary-against-kitchensink: + filters: + branches: + only: + - develop + requires: + - build-npm-package + - build-binary # disable Mac build until we can figure CircleCI v2 story # for code signing the built binary diff --git a/cli/types/index.d.ts b/cli/types/index.d.ts index 7f91216bb2f1..a970e9b6321c 100644 --- a/cli/types/index.d.ts +++ b/cli/types/index.d.ts @@ -3933,10 +3933,9 @@ declare namespace Cypress { left: number } - // Diff / Omit taken from https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-311923766 + // Diff taken from https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-311923766 type Diff = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T] - // TODO - remove this if possible. Seems a recent change to TypeScript broke this. Possibly https://github.com/Microsoft/TypeScript/pull/17912 - type Omit = Pick> // tslint:disable-line + type Omit = Pick> } /** From d05ef4c78c3bea59a7013ff06fe2614434f289e8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Thu, 3 Jan 2019 17:07:06 +0630 Subject: [PATCH 27/31] =?UTF-8?q?chore(deps):=20update=20bootstrap-sass=20?= =?UTF-8?q?to=203.4.0=20=F0=9F=8C=9F=20(#3038)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/desktop-gui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/desktop-gui/package.json b/packages/desktop-gui/package.json index 3022263120fc..792bd2ca9496 100644 --- a/packages/desktop-gui/package.json +++ b/packages/desktop-gui/package.json @@ -31,7 +31,7 @@ "@cypress/releaser": "0.1.12", "bin-up": "^1.0.0", "bluebird": "^3.4.0", - "bootstrap-sass": "3.3.5", + "bootstrap-sass": "3.4.0", "classnames": "^2.2.5", "fira": "cypress-io/fira#fb63362742eea8cdce0d90825ab9264d77719e3d", "font-awesome": "4.7", From 2be70941ce0a971b98ac4ff6ccf50a5b26f7b22b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Thu, 3 Jan 2019 17:09:06 +0630 Subject: [PATCH 28/31] =?UTF-8?q?chore(deps):=20update=20css-element-queri?= =?UTF-8?q?es=20to=200.4.0=20=F0=9F=8C=9F=20(#3040)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/reporter/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reporter/package.json b/packages/reporter/package.json index bae8437377ee..6ad692bbfc09 100644 --- a/packages/reporter/package.json +++ b/packages/reporter/package.json @@ -32,7 +32,7 @@ "chai": "3.5.0", "chai-enzyme": "1.0.0-beta.1", "classnames": "2.2.6", - "css-element-queries": "0.3.2", + "css-element-queries": "0.4.0", "enzyme": "3.7.0", "enzyme-adapter-react-16": "1.6.0", "font-awesome": "4.7.0", From b550944bc6faf7591c462654c7e038123bf9e046 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Thu, 3 Jan 2019 17:11:22 +0630 Subject: [PATCH 29/31] =?UTF-8?q?chore(deps):=20update=20enzyme-adapter-re?= =?UTF-8?q?act-16=20to=201.7.1=20=F0=9F=8C=9F=20(#3043)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/reporter/package.json | 2 +- packages/runner/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/reporter/package.json b/packages/reporter/package.json index 6ad692bbfc09..f17a0fcfd6ff 100644 --- a/packages/reporter/package.json +++ b/packages/reporter/package.json @@ -34,7 +34,7 @@ "classnames": "2.2.6", "css-element-queries": "0.4.0", "enzyme": "3.7.0", - "enzyme-adapter-react-16": "1.6.0", + "enzyme-adapter-react-16": "1.7.1", "font-awesome": "4.7.0", "jsdom": "9.4.1", "lodash": "4.17.11", diff --git a/packages/runner/package.json b/packages/runner/package.json index 071745444f88..cda7717d5207 100644 --- a/packages/runner/package.json +++ b/packages/runner/package.json @@ -33,7 +33,7 @@ "chai-enzyme": "1.0.0-beta.1", "classnames": "2.2.6", "enzyme": "3.7.0", - "enzyme-adapter-react-16": "1.6.0", + "enzyme-adapter-react-16": "1.7.1", "font-awesome": "4.7.0", "jsdom": "9.4.1", "lodash": "4.17.11", From 57cc70d6b50ba7db946d53361a79aa311d97f2d7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Thu, 3 Jan 2019 17:15:18 +0630 Subject: [PATCH 30/31] =?UTF-8?q?chore(deps):=20update=20@types/debug=20to?= =?UTF-8?q?=200.0.31=20=F0=9F=8C=9F=20(#3035)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/launcher/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/launcher/package.json b/packages/launcher/package.json index 689d817bdb84..b6493699687b 100644 --- a/packages/launcher/package.json +++ b/packages/launcher/package.json @@ -28,7 +28,7 @@ "@cypress/releaser": "0.1.12", "@types/bluebird": "3.5.21", "@types/chai": "^3.5.2", - "@types/debug": "0.0.29", + "@types/debug": "0.0.31", "@types/execa": "^0.7.0", "@types/fs-extra": "3.0.0", "@types/lodash": "^4.14.64", From a7b88512de028917b745d21ff158e112a1022f22 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Thu, 3 Jan 2019 09:43:58 -0500 Subject: [PATCH 31/31] =?UTF-8?q?chore(deps):=20update=20execa-wrap=20to?= =?UTF-8?q?=201.4.0=20=F0=9F=8C=9F=20(#3044)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/package.json b/cli/package.json index 8cbc7b16baa2..a80c879e433e 100644 --- a/cli/package.json +++ b/cli/package.json @@ -90,7 +90,7 @@ "clear-module": "^2.1.0", "dependency-check": "^2.8.0", "dtslint": "0.4.2", - "execa-wrap": "1.1.0", + "execa-wrap": "1.4.0", "mock-fs": "4.5.0", "nock": "^9.0.9", "nyc": "13.0.0",