Skip to content

Commit

Permalink
driver: move snapshots into cy, refactor and remove this context, exp…
Browse files Browse the repository at this point in the history
…ose constants, cy.createSnapshot
  • Loading branch information
brian-mann committed Jul 15, 2017
1 parent 13d991f commit f294f6a
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 177 deletions.
174 changes: 174 additions & 0 deletions packages/driver/src/cy/snapshots.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
_ = require("lodash")
$ = require("jquery")

HIGHLIGHT_ATTR = "data-cypress-el"

reduceText = (arr, fn) ->
_.reduce arr, ((memo, item) -> memo += fn(item)), ""

getCssRulesString = (stylesheet) ->
## some browsers may throw a SecurityError if the stylesheet is cross-domain
## https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet#Notes
## for others, it will just be null
try
if rules = stylesheet.rules or stylesheet.cssRules
reduceText rules, (rule) -> rule.cssText
else
null
catch e
null

getStylesFor = ($$, stylesheets, location) ->
_.map $$(location).find("link[rel='stylesheet'],style"), (stylesheet) =>
if stylesheet.href
## if there's an href, it's a link tag
## return the CSS rules as a string, or, if cross-domain,
## a reference to the stylesheet's href
getCssRulesString(stylesheets[stylesheet.href]) or {href: stylesheet.href}
else
## otherwise, it's a style tag, and we can just grab its content
$$(stylesheet).text()

getDocumentStylesheets = (document) ->
_.reduce document.styleSheets, (memo, stylesheet) ->
memo[stylesheet.href] = stylesheet
return memo
, {}

create = ($$, state) ->
getStyles = ->
stylesheets = getDocumentStylesheets(state("document"))

return {
headStyles: getStylesFor($$, stylesheets, "head")
bodyStyles: getStylesFor($$, stylesheets, "body")
}

replaceIframes = (body) ->
## remove iframes because we don't want extra requests made, JS run, etc
## when restoring a snapshot
## replace them so the lack of them doesn't cause layout issues
## use <iframe>s as the placeholders because iframes are inline, replaced
## elements (https://developer.mozilla.org/en-US/docs/Web/CSS/Replaced_element)
## so it's hard to simulate their box model
## attach class names and inline styles, so that CSS styles are applied
## as they would be on the user's page, but override some
## styles so it looks like a placeholder

## need to only replace the iframes in the cloned body, so grab those
$iframes = body.find("iframe")
## but query from the actual document, since the cloned body
## iframes don't have proper styles applied

$$("iframe").each (idx, iframe) =>
$iframe = $(iframe)

remove = ->
$iframes.eq(idx).remove()

## if we don't have access to window
## then just remove this $iframe...
try
if not $iframe.prop("contentWindow")
return remove()
catch e
return remove()

props = {
id: iframe.id
class: iframe.className
style: iframe.style.cssText
}

dimensions = (fn) ->
## jquery may throw here if we accidentally
## pass an old iframe reference where the
## document + window properties are unavailable
try
$iframe[fn]()
catch e
0

$placeholder = $("<iframe />", props).css({
background: "#f8f8f8"
border: "solid 1px #a3a3a3"
boxSizing: "border-box"
padding: "20px"
width: dimensions("outerWidth")
height: dimensions("outerHeight")
})

$iframes.eq(idx).replaceWith($placeholder)
contents = """
<style>
p { color: #888; font-family: sans-serif; line-height: 1.5; }
</style>
<p>&lt;iframe&gt; placeholder for #{iframe.src}</p>
"""
$placeholder[0].src = "data:text/html;charset=utf-8,#{encodeURI(contents)}"

createSnapshot = ($el) ->
## create a unique selector for this el
$el.attr(HIGHLIGHT_ATTR, true) if $el?.attr

## TODO: throw error here if cy is undefined!

body = $$("body").clone()

## for the head and body, get an array of all CSS,
## whether it's links or style tags
## if it's same-origin, it will get the actual styles as a string
## it it's cross-domain, it will get a reference to the link's href
{headStyles, bodyStyles} = getStyles()

## replaces iframes with placeholders
replaceIframes(body)

## remove tags we don't want in body
body.find("script,link[rel='stylesheet'],style").remove()

## here we need to figure out if we're in a remote manual environment
## if so we need to stringify the DOM:
## 1. grab all inputs / textareas / options and set their value on the element
## 2. convert DOM to string: body.prop("outerHTML")
## 3. send this string via websocket to our server
## 4. server rebroadcasts this to our client and its stored as a property

## its also possible for us to store the DOM string completely on the server
## without ever sending it back to the browser (until its requests).
## we could just store it in memory and wipe it out intelligently.
## this would also prevent having to store the DOM structure on the client,
## which would reduce memory, and some CPU operations

## now remove it after we clone
$el.removeAttr(HIGHLIGHT_ATTR) if $el?.removeAttr

tmpHtmlEl = document.createElement("html")

## preserve attributes on the <html> tag
htmlAttrs = _.reduce $$("html")[0].attributes, (memo, attr) ->
if attr.specified
try
## if we can successfully set the attribute
## then set it on memo because its possible
## the attribute is completely invalid
tmpHtmlEl.setAttribute(attr.name, attr.value)
memo[attr.name] = attr.value

memo
, {}

return {body, htmlAttrs, headStyles, bodyStyles}

return {
createSnapshot

## careful renaming or removing this method, the runner depends on it
getStyles
}

module.exports = {
HIGHLIGHT_ATTR

create
}
5 changes: 1 addition & 4 deletions packages/driver/src/cypress.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,12 @@ $LocalStorage = require("./cypress/local_storage")
$Mocha = require("./cypress/mocha")
$Runner = require("./cypress/runner")
$Server = require("./cypress/server")
$Snapshot = require("./cypress/snapshot")

$utils = require("./cypress/utils")

proxies = {
runner: "getStartTime getTestsState getEmissions countByTestState getDisplayPropsForLog getConsolePropsForLogById getSnapshotPropsForLogById getErrorByTestId normalizeAll".split(" ")
cy: "checkForEndedEarly onUncaughtException setRunnable".split(" ")
snapshot: "getStyles".split(" ")
cy: "checkForEndedEarly onUncaughtException setRunnable getStyles".split(" ")
}

throwDeprecatedCommandInterface = (key, method) ->
Expand Down Expand Up @@ -134,7 +132,6 @@ class $Cypress
@log.apply(@, arguments)

## create cy and expose globally
@snapshot = $Snapshot.create(@cy)
@cy = window.cy = $Cy.create(specWindow, @, @state, @config, logFn)
@log = $Log.create(@, @cy, @state, @config)
@mocha = $Mocha.create(specWindow, @)
Expand Down
5 changes: 5 additions & 0 deletions packages/driver/src/cypress/cy.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ $Listeners = require("../cy/listeners")
$Chainer = require("./chainer")
$Timeouts = require("../cy/timeouts")
$Retries = require("../cy/retries")
$Snapshots = require("../cy/snapshots")
$CommandQueue = require("./command_queue")
$SetterGetter = require("./setter_getter")

Expand Down Expand Up @@ -82,6 +83,7 @@ create = (specWindow, Cypress, state, config, log) ->

assertions = $Assertions.create(state, queue, retries.retry)
coordinates = $Coordinates.create(state, ensures.ensureValidPosition)
snapshots = $Snapshots.create($$, state)

commandFns = {}
# commandFnsBackup = {}
Expand Down Expand Up @@ -144,6 +146,9 @@ create = (specWindow, Cypress, state, config, log) ->
## alias sync methods
getAlias: aliases.getAlias

## snapshots sync methods
createSnapshot: snapshots.createSnapshot

## coordinates sync methods
getCoordinates: coordinates.getCoordinates
getElementAtCoordinates: coordinates.getElementAtCoordinates
Expand Down
6 changes: 4 additions & 2 deletions packages/driver/src/cypress/log.coffee
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
_ = require("lodash")

$Snapshots = require("../cy/snapshots")
$utils = require("./utils")

## adds class methods for command, route, and agent logging
Expand All @@ -12,6 +13,7 @@ SNAPSHOT_PROPS = "id snapshots $el url coords highlightAttr scrollBy viewportWi
DISPLAY_PROPS = "id alias aliasType callCount displayName end err event functionName hookName instrument isStubbed message method name numElements numResponses referencesAlias renderProps state testId type url visible".split(" ")
BLACKLIST_PROPS = "snapshots".split(" ")

HIGHLIGHT_ATTR = { $Snapshots }
delay = null

reduceMemory = (attrs) ->
Expand Down Expand Up @@ -277,7 +279,7 @@ create = (Cypress, cy, state, config) ->
at: null
next: null

{body, htmlAttrs, headStyles, bodyStyles} = snapshot.createSnapshot(@get("$el"))
{body, htmlAttrs, headStyles, bodyStyles} = cy.createSnapshot(@get("$el"))

obj = {
name: name
Expand Down Expand Up @@ -346,7 +348,7 @@ create = (Cypress, cy, state, config) ->

## make sure all $el elements are visible!
obj = {
highlightAttr: snapshot.getHighlightAttr()
highlightAttr: HIGHLIGHT_ATTR
numElements: $el.length
visible: $el.length is $el.filter(":visible").length
}
Expand Down

0 comments on commit f294f6a

Please sign in to comment.