Use is-html as a fallback to check if cy.visit() response is HTML (#4321)

* use is-html as a fallback to check if response is HTML

* end response with passthru stream

* clean up network_failures

* add test that visit passes with undef content-type

* handle empty responses too

* try to fix dtslint: bump package version 2
02-00000876

* fix failing tests via develop merge


Co-authored-by: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
Co-authored-by: Brian Mann <brian.mann86@gmail.com>
This commit is contained in:
Zach Bloomquist
2019-06-19 03:05:38 -04:00
committed by Brian Mann
parent 0c8df29def
commit 60318a7f0f
8 changed files with 84 additions and 17 deletions

View File

@@ -100,4 +100,4 @@
"index.js",
"types/**/*.d.ts"
]
}
}

View File

@@ -541,6 +541,11 @@ describe "src/cy/commands/navigation", ->
.then ->
expect(backend).to.be.calledWithMatch("resolve:url", "http://localhost:3500/timeout", { auth })
## https://github.com/cypress-io/cypress/issues/1727
it "can visit a page with undefined content type and html-shaped body", ->
cy
.visit("http://localhost:3500/undefined-content-type")
describe "when only hashes are changing", ->
it "short circuits the visit if the page will not refresh", ->
count = 0

View File

@@ -72,6 +72,9 @@ niv.install("react-dom@15.6.1")
res.setHeader('Content-Type', 'text/html; charset=utf-8,text/html')
res.end("<html><head><title>Test</title></head><body><center>Hello</center></body></html>")
app.get '/undefined-content-type', (req, res) ->
res.end("<html>some stuff that looks like<span>html</span></html>")
app.all '/dump-method', (req, res) ->
res.send("<html><body>request method: #{req.method}</body></html>")

View File

@@ -11,7 +11,6 @@ buffers = require("../util/buffers")
rewriter = require("../util/rewriter")
blacklist = require("../util/blacklist")
conditional = require("../util/conditional_stream")
networkFailures = require("../util/network_failures")
REDIRECT_STATUS_CODES = [301, 302, 303, 307, 308]
NO_BODY_STATUS_CODES = [204, 304]

View File

@@ -29,6 +29,7 @@ onRequest = (req, res, fileServerFolder) ->
})
.on "error", (err) ->
res.setHeader("x-cypress-file-server-error", true)
res.setHeader("content-type", "text/html")
res.statusCode = err.status
res.end(networkFailures.get(file, err.status))
.pipe(res)

View File

@@ -2,11 +2,13 @@ _ = require("lodash")
exphbs = require("express-handlebars")
url = require("url")
http = require("http")
concatStream = require("concat-stream")
cookie = require("cookie")
stream = require("stream")
express = require("express")
Promise = require("bluebird")
evilDns = require("evil-dns")
isHtml = require("is-html")
httpProxy = require("http-proxy")
la = require("lazy-ass")
check = require("check-more-types")
@@ -34,6 +36,15 @@ fileServer = require("./file_server")
DEFAULT_DOMAIN_NAME = "localhost"
fullyQualifiedRe = /^https?:\/\//
isResponseHtml = (contentType, responseBuffer) ->
if contentType
return contentType is "text/html"
if body = _.invoke(responseBuffer, 'toString')
return isHtml(body)
return false
setProxiedUrl = (req) ->
## bail if we've already proxied the url
return if req.proxiedUrl
@@ -418,11 +429,9 @@ class Server
isOk = statusIs2xxOrAllowedFailure()
contentType = headersUtil.getContentType(incomingRes)
isHtml = contentType is "text/html"
details = {
isOkStatusCode: isOk
isHtml
contentType
url: newUrl
status: incomingRes.statusCode
@@ -439,19 +448,22 @@ class Server
debug("setting details resolving url %o", details)
## this will allow us to listen to `str`'s `end` event by putting it in flowing mode
responseBuffer = stream.PassThrough({
## buffer forever - node's default is only to buffer 16kB
highWaterMark: Infinity
})
str.pipe(responseBuffer)
str.on "end", =>
concatStr = concatStream (responseBuffer) =>
## buffer the entire response before resolving.
## this allows us to detect & reject ETIMEDOUT errors
## where the headers have been sent but the
## connection hangs before receiving a body.
if !_.get(responseBuffer, 'length')
## concatStream can yield an empty array, which is
## not a valid chunk
responseBuffer = undefined
## if there is not a content-type, try to determine
## if the response content is HTML-like
## https://github.com/cypress-io/cypress/issues/1727
details.isHtml = isResponseHtml(contentType, responseBuffer)
debug("resolve:url response ended, setting buffer %o", { newUrl, details })
details.totalTime = new Date() - startTime
@@ -460,15 +472,21 @@ class Server
## frontend so that the driver can be in control of
## when the server should cache the request buffer
## and set the domain vs not
if isOk and isHtml
if isOk and details.isHtml
## reset the domain to the new url if we're not
## handling a local file
@_onDomainSet(newUrl, options) if not handlingLocalFile
responseBufferStream = new stream.PassThrough({
highWaterMark: Number.MAX_SAFE_INTEGER
})
responseBufferStream.end(responseBuffer)
buffers.set({
url: newUrl
jar: jar
stream: responseBuffer
stream: responseBufferStream
details: details
originalUrl: originalUrl
response: incomingRes
@@ -479,6 +497,8 @@ class Server
restorePreviousState()
resolve(details)
str.pipe(concatStr)
.catch(onReqError)
restorePreviousState = =>

View File

@@ -143,6 +143,7 @@
"human-interval": "0.1.6",
"image-size": "0.7.4",
"is-fork-pr": "2.3.0",
"is-html": "2.0.0",
"jimp": "0.6.4",
"jsonlint": "1.6.3",
"konfig": "0.2.1",

View File

@@ -240,8 +240,8 @@ describe "Server", ->
.then (obj = {}) ->
expectToEqDetails(obj, {
isOkStatusCode: false
isHtml: false
contentType: undefined
isHtml: true
contentType: "text/html"
url: "http://localhost:2000/does-not-exist"
originalUrl: "/does-not-exist"
filePath: Fixtures.projectPath("no-server/dev/does-not-exist")
@@ -406,6 +406,44 @@ describe "Server", ->
cookies: []
})
it "yields isHtml true for HTML-shaped responses", ->
nock("http://example.com")
.get("/")
.reply(200, "<html>foo</html>")
@server._onResolveUrl("http://example.com", {}, @automationRequest)
.then (obj = {}) ->
expectToEqDetails(obj, {
isOkStatusCode: true
isHtml: true
contentType: undefined
url: "http://example.com/"
originalUrl: "http://example.com/"
status: 200
statusText: "OK"
redirects: []
cookies: []
})
it "yields isHtml false for non-HTML-shaped responses", ->
nock("http://example.com")
.get("/")
.reply(200, '{ foo: "bar" }')
@server._onResolveUrl("http://example.com", {}, @automationRequest)
.then (obj = {}) ->
expectToEqDetails(obj, {
isOkStatusCode: true
isHtml: false
contentType: undefined
url: "http://example.com/"
originalUrl: "http://example.com/"
status: 200
statusText: "OK"
redirects: []
cookies: []
})
it "can follow multiple http redirects", ->
nock("http://espn.com")
.get("/")