diff --git a/cli/types/index.d.ts b/cli/types/index.d.ts index 9da85484134..02d7a98a2b8 100644 --- a/cli/types/index.d.ts +++ b/cli/types/index.d.ts @@ -2349,6 +2349,11 @@ declare namespace Cypress { * }) */ auth: Auth + + /** + * Query parameters to append to the `url` of the request. + */ + qs: object } /** diff --git a/packages/driver/src/cy/commands/navigation.coffee b/packages/driver/src/cy/commands/navigation.coffee index b4b22ca25ac..3b0dd037b50 100644 --- a/packages/driver/src/cy/commands/navigation.coffee +++ b/packages/driver/src/cy/commands/navigation.coffee @@ -522,6 +522,9 @@ module.exports = (Commands, Cypress, cy, state, config) -> onLoad: -> }) + if !_.isUndefined(options.qs) and not _.isObject(options.qs) + $utils.throwErrByPath("visit.invalid_qs", { args: { qs: String(options.qs) }}) + if options.retryOnStatusCodeFailure and not options.failOnStatusCode $utils.throwErrByPath("visit.status_code_flags_invalid") @@ -550,6 +553,9 @@ module.exports = (Commands, Cypress, cy, state, config) -> if baseUrl = config("baseUrl") url = $Location.qualifyWithBaseUrl(baseUrl, url) + if qs = options.qs + url = $Location.mergeUrlWithParams(url, qs) + cleanup = null ## clear the current timeout @@ -610,7 +616,7 @@ module.exports = (Commands, Cypress, cy, state, config) -> existing = $utils.locExisting() ## TODO: $Location.resolve(existing.origin, url) - + if $Location.isLocalFileUrl(url) return specifyFileByRelativePath(url, options._log) diff --git a/packages/driver/src/cypress/error_messages.coffee b/packages/driver/src/cypress/error_messages.coffee index 5288c1de442..58740aed096 100644 --- a/packages/driver/src/cypress/error_messages.coffee +++ b/packages/driver/src/cypress/error_messages.coffee @@ -998,6 +998,7 @@ module.exports = { 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." + invalid_qs: "#{cmd('visit')} requires the 'qs' option to be an object, but received: '{{qs}}'" no_duplicate_url: """ #{cmd('visit')} must be called with only one URL. You specified two URLs: @@ -1090,7 +1091,7 @@ module.exports = { #{cmd('request')} will automatically get and set cookies and enable you to parse responses. """ - + specify_file_by_relative_path: """ #{cmd('visit')} failed because the 'file://...' protocol is not supported by Cypress. diff --git a/packages/driver/src/cypress/location.coffee b/packages/driver/src/cypress/location.coffee index 8fa727511d9..78965e2dc38 100644 --- a/packages/driver/src/cypress/location.coffee +++ b/packages/driver/src/cypress/location.coffee @@ -155,6 +155,11 @@ class $Location url = new UrlParse(url, existing.origin) url.toString() + @mergeUrlWithParams = (url, params) -> + url = new UrlParse(url, null, true) + url.set("query", _.merge(url.query || {}, params)) + url.toString() + @normalize = (url) -> ## A properly formed URL will always have a trailing ## slash at the end of it diff --git a/packages/driver/test/cypress/integration/commands/navigation_spec.coffee b/packages/driver/test/cypress/integration/commands/navigation_spec.coffee index 66790037148..956d2be9a18 100644 --- a/packages/driver/test/cypress/integration/commands/navigation_spec.coffee +++ b/packages/driver/test/cypress/integration/commands/navigation_spec.coffee @@ -543,11 +543,11 @@ describe "src/cy/commands/navigation", -> it "does not support file:// protocol", (done) -> Cypress.config("baseUrl", "") - + cy.on "fail", (err) -> expect(err.message).to.contain("cy.visit() failed because the 'file://...' protocol is not supported by Cypress.") done() - + cy.visit("file:///cypress/fixtures/generic.html") ## https://github.com/cypress-io/cypress/issues/1727 @@ -619,6 +619,15 @@ describe "src/cy/commands/navigation", -> }) cy.contains('"user-agent":"something special"') + it "can send querystring params", -> + qs = { "foo bar": "baz quux" } + + cy + .visit("http://localhost:3500/dump-qs", { qs }) + .then -> + cy.contains(JSON.stringify(qs)) + cy.url().should('eq', 'http://localhost:3500/dump-qs?foo%20bar=baz%20quux') + describe "can send a POST request", -> it "automatically urlencoded using an object body", -> cy.visit("http://localhost:3500/post-only", { @@ -1060,6 +1069,23 @@ describe "src/cy/commands/navigation", -> headers: "quux" }) + [ + "foo", + null, + false, + ].forEach (qs) => + str = String(qs) + + it "throws when qs is #{str}", (done) -> + cy.on "fail", (err) -> + expect(err.message).to.contain "cy.visit() requires the 'qs' option to be an object, but received: '#{str}'" + done() + + cy.visit({ + url: "http://foobarbaz", + qs + }) + it "throws when failOnStatusCode is false and retryOnStatusCodeFailure is true", (done) -> cy.on "fail", (err) -> expect(err.message).to.contain "cy.visit() was invoked with { failOnStatusCode: false, retryOnStatusCodeFailure: true }." diff --git a/packages/driver/test/cypress/integration/cypress/location_spec.coffee b/packages/driver/test/cypress/integration/cypress/location_spec.coffee index 016701d5d5e..2034e5883dc 100644 --- a/packages/driver/test/cypress/integration/cypress/location_spec.coffee +++ b/packages/driver/test/cypress/integration/cypress/location_spec.coffee @@ -183,6 +183,21 @@ describe "src/cypress/location", -> obj = Location.create(urls.signin) expect(obj.toString()).to.eq("http://localhost:2020/signin") + context ".mergeUrlWithParams", -> + beforeEach -> + @url = (str, expected, params) -> + url = Location.mergeUrlWithParams(str, params) + expect(url).to.eq(expected) + + it "merges params into a URL", -> + @url "http://example.com/a", "http://example.com/a?foo=bar", { foo: 'bar' } + + it "overrides existing queryparams", -> + @url "http://example.com/a?foo=quux", "http://example.com/a?foo=bar", { foo: 'bar' } + + it "appends and overrides existing queryparams", -> + @url "http://example.com/a?foo=quux", "http://example.com/a?foo=bar&baz=quuz", { foo: 'bar', baz: 'quuz' } + context ".normalize", -> beforeEach -> @url = (source, expected) -> diff --git a/packages/driver/test/support/server.coffee b/packages/driver/test/support/server.coffee index e2641857c49..2ba2721df1a 100644 --- a/packages/driver/test/support/server.coffee +++ b/packages/driver/test/support/server.coffee @@ -78,6 +78,9 @@ niv.install("react-dom@15.6.1") app.all '/dump-method', (req, res) -> res.send("request method: #{req.method}") + app.all '/dump-qs', (req, res) -> + res.send("it worked!
request querystring:
#{JSON.stringify(req.query)}") + app.post '/post-only', (req, res) -> res.send("it worked!
request body:
#{JSON.stringify(req.body)}")