mirror of
https://github.com/cypress-io/cypress.git
synced 2026-02-06 15:20:25 -06:00
Visit with a POST and/or custom headers (#3489)
* driver, server: visit with a POST [wip] * driver, server: allow sending body, headers, method in .visit * driver: test: doublequotes * driver: api cleanup, error handling * driver, server: tests * driver: only recognize visit(opts) if options is sole argument * server: don't confuse options * driver: validate method passed to 'visit' * driver: validate that headers is an object * driver: shows URL and not object in command log (fixes part of #678) * cli: add new cy.visit(opts) invocation
This commit is contained in:
5
cli/types/index.d.ts
vendored
5
cli/types/index.d.ts
vendored
@@ -1588,9 +1588,14 @@ declare namespace Cypress {
|
||||
* @example
|
||||
* cy.visit('http://localhost:3000')
|
||||
* cy.visit('/somewhere') // opens ${baseUrl}/somewhere
|
||||
* cy.visit({
|
||||
* url: 'http://google.com',
|
||||
* method: 'POST'
|
||||
* })
|
||||
*
|
||||
*/
|
||||
visit(url: string, options?: Partial<VisitOptions>): Chainable<Window>
|
||||
visit(options: Partial<VisitOptions> & { url: string }): Chainable<Window>
|
||||
|
||||
/**
|
||||
* Wait for a number of milliseconds.
|
||||
|
||||
@@ -28,6 +28,11 @@ reset = (test = {}) ->
|
||||
|
||||
id = test.id
|
||||
|
||||
VALID_VISIT_METHODS = ['GET', 'POST']
|
||||
|
||||
isValidVisitMethod = (method) ->
|
||||
_.includes(VALID_VISIT_METHODS, method)
|
||||
|
||||
timedOutWaitingForPageLoad = (ms, log) ->
|
||||
$utils.throwErrByPath("navigation.timed_out", {
|
||||
onFail: log
|
||||
@@ -260,7 +265,7 @@ module.exports = (Commands, Cypress, cy, state, config) ->
|
||||
Cypress.backend(
|
||||
"resolve:url",
|
||||
url,
|
||||
_.pick(options, "failOnStatusCode", "auth")
|
||||
_.pick(options, "auth", "failOnStatusCode", "method", "body", "headers")
|
||||
)
|
||||
.then (resp = {}) ->
|
||||
switch
|
||||
@@ -456,22 +461,45 @@ module.exports = (Commands, Cypress, cy, state, config) ->
|
||||
$utils.throwErrByPath("go.invalid_argument", { onFail: options._log })
|
||||
|
||||
visit: (url, options = {}) ->
|
||||
if options.url and url
|
||||
$utils.throwErrByPath("visit.no_duplicate_url", { args: { optionsUrl: options.url, url: url }})
|
||||
|
||||
if _.isObject(url) and _.isEqual(options, {})
|
||||
## options specified as only argument
|
||||
options = url
|
||||
url = options.url
|
||||
|
||||
if not _.isString(url)
|
||||
$utils.throwErrByPath("visit.invalid_1st_arg")
|
||||
|
||||
_.defaults(options, {
|
||||
auth: null
|
||||
failOnStatusCode: true
|
||||
method: 'GET'
|
||||
body: null
|
||||
headers: {}
|
||||
log: true
|
||||
timeout: config("pageLoadTimeout")
|
||||
onBeforeLoad: ->
|
||||
onLoad: ->
|
||||
})
|
||||
|
||||
if not isValidVisitMethod(options.method)
|
||||
$utils.throwErrByPath("visit.invalid_method", { args: { method: options.method }})
|
||||
|
||||
if not _.isObject(options.headers)
|
||||
$utils.throwErrByPath("visit.invalid_headers")
|
||||
|
||||
consoleProps = {}
|
||||
|
||||
if options.log
|
||||
message = url
|
||||
|
||||
if options.method != 'GET'
|
||||
message = "#{options.method} #{message}"
|
||||
|
||||
options._log = Cypress.log({
|
||||
message: message
|
||||
consoleProps: -> consoleProps
|
||||
})
|
||||
|
||||
@@ -598,11 +626,13 @@ module.exports = (Commands, Cypress, cy, state, config) ->
|
||||
if url isnt originalUrl
|
||||
consoleProps["Original Url"] = originalUrl
|
||||
|
||||
if options.log and redirects and redirects.length
|
||||
indicateRedirects = ->
|
||||
[originalUrl].concat(redirects).join(" -> ")
|
||||
if options.log
|
||||
message = options._log.get('message')
|
||||
|
||||
options._log.set({message: indicateRedirects()})
|
||||
if redirects and redirects.length
|
||||
message = [message].concat(redirects).join(" -> ")
|
||||
|
||||
options._log.set({message: message})
|
||||
|
||||
consoleProps["Resolved Url"] = url
|
||||
consoleProps["Redirects"] = redirects
|
||||
|
||||
@@ -845,7 +845,15 @@ module.exports = {
|
||||
missing_preset: "#{cmd('viewport')} could not find a preset for: '{{preset}}'. Available presets are: {{presets}}"
|
||||
|
||||
visit:
|
||||
invalid_1st_arg: "#{cmd('visit')} must be called with a string as its 1st argument"
|
||||
invalid_1st_arg: "#{cmd('visit')} must be called with a URL or an options object containing a URL as its 1st argument"
|
||||
invalid_method: "#{cmd('visit')} was called with an invalid method: '{{method}}'. Method can only be GET or POST."
|
||||
invalid_headers: "#{cmd('visit')} requires the 'headers' option to be an object."
|
||||
no_duplicate_url: """
|
||||
#{cmd('visit')} must be called with only one URL. You specified two URLs:
|
||||
|
||||
URL from the `options` object: {{optionsUrl}}
|
||||
URL from the `url` parameter: {{url}}
|
||||
"""
|
||||
cannot_visit_2nd_domain: """
|
||||
#{cmd('visit')} failed because you are attempting to visit a second unique domain.
|
||||
|
||||
|
||||
@@ -580,6 +580,37 @@ describe "src/cy/commands/navigation", ->
|
||||
expect(win.bar).to.not.exist
|
||||
expect(onLoad).not.to.have.been.called
|
||||
|
||||
it "can send headers", ->
|
||||
cy.visit({
|
||||
url: "http://localhost:3500/dump-headers",
|
||||
headers: {
|
||||
"x-foo-baz": "bar-quux"
|
||||
}
|
||||
})
|
||||
cy.contains('"x-foo-baz":"bar-quux"')
|
||||
|
||||
describe "can send a POST request", ->
|
||||
it "automatically urlencoded using an object body", ->
|
||||
cy.visit("http://localhost:3500/post-only", {
|
||||
method: "POST",
|
||||
body: {
|
||||
bar: "baz"
|
||||
}
|
||||
})
|
||||
cy.contains("it worked!").contains("{\"bar\":\"baz\"}")
|
||||
|
||||
it "with any string body and headers", ->
|
||||
cy.visit("http://localhost:3500/post-only", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json"
|
||||
}
|
||||
body: JSON.stringify({
|
||||
bar: "baz"
|
||||
})
|
||||
})
|
||||
cy.contains("it worked!").contains("{\"bar\":\"baz\"}")
|
||||
|
||||
describe "when origins don't match", ->
|
||||
beforeEach ->
|
||||
Cypress.emit("test:before:run", { id: 888 })
|
||||
@@ -856,6 +887,16 @@ describe "src/cy/commands/navigation", ->
|
||||
"http://localhost:3500/foo -> 1 -> 2"
|
||||
)
|
||||
|
||||
it "indicates POST in the message", ->
|
||||
cy.visit("http://localhost:3500/post-only", {
|
||||
method: "POST"
|
||||
}).then ->
|
||||
lastLog = @lastLog
|
||||
|
||||
expect(lastLog.get("message")).to.eq(
|
||||
"POST http://localhost:3500/post-only"
|
||||
)
|
||||
|
||||
it "displays note in consoleProps when visiting the same page with a hash", ->
|
||||
cy.visit("http://localhost:3500/fixtures/generic.html#foo")
|
||||
.visit("http://localhost:3500/fixtures/generic.html#foo")
|
||||
@@ -934,11 +975,40 @@ describe "src/cy/commands/navigation", ->
|
||||
|
||||
it "throws when url isnt a string", (done) ->
|
||||
cy.on "fail", (err) ->
|
||||
expect(err.message).to.eq "cy.visit() must be called with a string as its 1st argument"
|
||||
expect(err.message).to.eq "cy.visit() must be called with a URL or an options object containing a URL as its 1st argument"
|
||||
done()
|
||||
|
||||
cy.visit()
|
||||
|
||||
it "throws when url is specified twice", (done) ->
|
||||
cy.on "fail", (err) ->
|
||||
expect(err.message).to.contain "cy.visit() must be called with only one URL. You specified two URLs"
|
||||
done()
|
||||
|
||||
cy.visit("http://foobarbaz", {
|
||||
url: "http://foobarbaz"
|
||||
})
|
||||
|
||||
it "throws when method is unsupported", (done) ->
|
||||
cy.on "fail", (err) ->
|
||||
expect(err.message).to.contain "cy.visit() was called with an invalid method: 'FOO'"
|
||||
done()
|
||||
|
||||
cy.visit({
|
||||
url: "http://foobarbaz",
|
||||
method: "FOO"
|
||||
})
|
||||
|
||||
it "throws when headers is not an object", (done) ->
|
||||
cy.on "fail", (err) ->
|
||||
expect(err.message).to.contain "cy.visit() requires the 'headers' option to be an object"
|
||||
done()
|
||||
|
||||
cy.visit({
|
||||
url: "http://foobarbaz",
|
||||
headers: "quux"
|
||||
})
|
||||
|
||||
it "throws when attempting to visit a 2nd domain on different port", (done) ->
|
||||
cy.on "fail", (err) =>
|
||||
lastLog = @lastLog
|
||||
@@ -1259,7 +1329,7 @@ describe "src/cy/commands/navigation", ->
|
||||
|
||||
## https://github.com/cypress-io/cypress/issues/3101
|
||||
[{
|
||||
contentType: 'application/json',
|
||||
contentType: 'application/json',
|
||||
pathName: 'json-content-type'
|
||||
}, {
|
||||
contentType: 'text/html; charset=utf-8,text/html',
|
||||
|
||||
@@ -72,6 +72,12 @@ 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.post '/post-only', (req, res) ->
|
||||
res.send("<html><body>it worked!<br>request body:<br>#{JSON.stringify(req.body)}</body></html>")
|
||||
|
||||
app.get '/dump-headers', (req, res) ->
|
||||
res.send("<html><body>request headers:<br>#{JSON.stringify(req.headers)}</body></html>")
|
||||
|
||||
app.get "/status-404", (req, res) ->
|
||||
res
|
||||
.status(404)
|
||||
|
||||
@@ -458,15 +458,19 @@ class Server
|
||||
@_remoteDomainName = previousState.domainName
|
||||
@_remoteVisitingUrl = previousState.visiting
|
||||
|
||||
request.sendStream(headers, automationRequest, {
|
||||
# if they're POSTing an object, querystringify their POST body
|
||||
if options.method == 'POST' and _.isObject(options.body)
|
||||
options.form = options.body
|
||||
delete options.body
|
||||
|
||||
_.assign(options, {
|
||||
## turn off gzip since we need to eventually
|
||||
## rewrite these contents
|
||||
auth: options.auth
|
||||
gzip: false
|
||||
url: urlFile ? urlStr
|
||||
headers: {
|
||||
headers: _.assign({
|
||||
accept: "text/html,*/*"
|
||||
}
|
||||
}, options.headers)
|
||||
followRedirect: (incomingRes) ->
|
||||
status = incomingRes.statusCode
|
||||
next = incomingRes.headers.location
|
||||
@@ -479,6 +483,10 @@ class Server
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
debug('sending request with options %o', options)
|
||||
|
||||
request.sendStream(headers, automationRequest, options)
|
||||
.then(handleReqStream)
|
||||
.catch(error)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user