UNPKG

@revoloo/cypress6

Version:

Cypress.io end to end testing tool

1,323 lines (1,097 loc) 39.9 kB
const { stripIndent } = require('common-tags') const { _, Promise } = Cypress const RESPONSE_TIMEOUT = 22222 describe('src/cy/commands/request', () => { context('#request', { responseTimeout: RESPONSE_TIMEOUT, }, () => { beforeEach(() => { cy.stub(Cypress, 'backend').callThrough() }) describe('argument signature', () => { beforeEach(function () { const backend = Cypress.backend .withArgs('http:request') .resolves({ isOkStatusCode: true, status: 200 }) this.expectOptionsToBe = function (opts) { _.defaults(opts, { failOnStatusCode: true, retryOnNetworkFailure: true, retryOnStatusCodeFailure: false, encoding: 'utf8', gzip: true, followRedirect: true, timeout: RESPONSE_TIMEOUT, method: 'GET', }) const options = backend.firstCall.args[1] _.each(options, (value, key) => { expect(options[key]).to.deep.eq(opts[key], `failed on property: (${key})`) }) _.each(opts, (value, key) => { expect(opts[key]).to.deep.eq(options[key], `failed on property: (${key})`) }) } }) it('accepts object with url', () => { cy.request({ url: 'http://localhost:8000/foo' }).then(function () { this.expectOptionsToBe({ url: 'http://localhost:8000/foo', }) }) }) it('accepts object with url, method, headers, body', () => { cy.request({ url: 'http://github.com/users', method: 'POST', body: { name: 'brian' }, headers: { 'x-token': 'abc123', }, }) .then(function () { this.expectOptionsToBe({ url: 'http://github.com/users', method: 'POST', json: true, body: { name: 'brian' }, headers: { 'x-token': 'abc123', }, }) }) }) it('accepts object with url + timeout', () => { cy.request({ url: 'http://localhost:8000/foo', timeout: 23456 }).then(function () { this.expectOptionsToBe({ url: 'http://localhost:8000/foo', timeout: 23456, }) }) }) it('accepts string url', () => { cy.request('http://localhost:8080/status').then(function () { this.expectOptionsToBe({ url: 'http://localhost:8080/status', }) }) }) it('accepts method + url', () => { cy.request('DELETE', 'http://localhost:1234/users/1').then(function () { this.expectOptionsToBe({ url: 'http://localhost:1234/users/1', method: 'DELETE', }) }) }) it('accepts method + url + body', () => { cy.request('POST', 'http://localhost:8080/users', { name: 'brian' }).then(function () { this.expectOptionsToBe({ url: 'http://localhost:8080/users', method: 'POST', body: { name: 'brian' }, json: true, }) }) }) it('accepts url + body', () => { cy.request('http://www.github.com/projects/foo', { commits: true }).then(function () { this.expectOptionsToBe({ url: 'http://www.github.com/projects/foo', body: { commits: true }, json: true, }) }) }) it('accepts url + string body', () => { cy.request('http://www.github.com/projects/foo', 'foo').then(function () { this.expectOptionsToBe({ url: 'http://www.github.com/projects/foo', body: 'foo', }) }) }) context('method normalization', () => { it('uppercases method', () => { cy.request('post', 'https://www.foo.com').then(function () { this.expectOptionsToBe({ url: 'https://www.foo.com/', method: 'POST', }) }) }) }) context('url normalization', () => { it('uses absolute urls and adds trailing slash', { baseUrl: 'http://localhost:8080/app', }, () => { cy.request('https://www.foo.com').then(function () { this.expectOptionsToBe({ url: 'https://www.foo.com/', }) }) }) it('uses localhost urls', () => { cy.request('localhost:1234').then(function () { this.expectOptionsToBe({ url: 'http://localhost:1234/', }) }) }) it('uses www urls', () => { cy.request('www.foo.com').then(function () { this.expectOptionsToBe({ url: 'http://www.foo.com/', }) }) }) it('prefixes with baseUrl when origin is empty', { baseUrl: 'http://localhost:8080/app', }, () => { cy.stub(cy, 'getRemoteLocation').withArgs('origin').returns('') cy.request('/foo/bar?cat=1').then(function () { this.expectOptionsToBe({ url: 'http://localhost:8080/app/foo/bar?cat=1', }) }) }) it('prefixes with baseUrl over current origin', { baseUrl: 'http://localhost:8080/app', }, () => { cy.stub(cy, 'getRemoteLocation').withArgs('origin').returns('http://localhost:1234') cy.request('foobar?cat=1').then(function () { this.expectOptionsToBe({ url: 'http://localhost:8080/app/foobar?cat=1', }) }) }) // https://github.com/cypress-io/cypress/issues/5274 it('encode url with ’ character in pathname', () => { cy.request({ url: 'http://localhost:1234/’' }).then(function () { this.expectOptionsToBe({ url: 'http://localhost:1234/%E2%80%99', }) }) }) it('dont re-encode url with ’ escaped in pathname', () => { cy.request({ url: encodeURI('http://localhost:1234/’') }).then(function () { this.expectOptionsToBe({ url: 'http://localhost:1234/%E2%80%99', }) }) }) it('encode url with % character in pathname', () => { cy.request({ url: 'http://localhost:1234/%' }).then(function () { this.expectOptionsToBe({ url: 'http://localhost:1234/%', }) }) }) it('dont re-encode url with % escaped in pathname', () => { cy.request({ url: encodeURI('http://localhost:1234/%') }).then(function () { this.expectOptionsToBe({ url: 'http://localhost:1234/%25', }) }) }) it('encode url with Astral Plane Unicode in pathname', () => { cy.request({ url: 'http://localhost:1234/😀' }).then(function () { this.expectOptionsToBe({ url: 'http://localhost:1234/%F0%9F%98%80', }) }) }) it('dont re-encode url with Astral Plane Unicode escaped character in pathname', () => { cy.request({ url: encodeURI('http://localhost:1234/😀') }).then(function () { this.expectOptionsToBe({ url: 'http://localhost:1234/%F0%9F%98%80', }) }) }) it('should percent escape Unicode in pathname and convert Unicode in domain name properly', () => { cy.request({ url: 'http://localhost😀:1234/😀' }).then(function () { this.expectOptionsToBe({ url: 'http://xn--localhost-ob26h:1234/%F0%9F%98%80', }) }) }) it('should percent escape Unicode in pathname and convert Unicode in domain name with URI encoded URL', () => { cy.request({ url: encodeURI('http://localhost😀:1234/😀') }).then(function () { this.expectOptionsToBe({ url: 'http://xn--localhost-ob26h:1234/%F0%9F%98%80', }) }) }) }) context('encoding', () => { it('lowercases encoding', () => { cy.request({ url: 'http://localhost:8080/', encoding: 'UtF8', }) .then(function () { this.expectOptionsToBe({ url: 'http://localhost:8080/', encoding: 'utf8', }) }) }) it('defaults encoding to `utf8`', () => { cy.request({ url: 'http://localhost:8080/', }) .then(function () { this.expectOptionsToBe({ url: 'http://localhost:8080/', encoding: 'utf8', }) }) }) }) context('gzip', () => { it('can turn off gzipping', () => { cy.request({ url: 'http://localhost:8080', gzip: false, }) .then(function () { this.expectOptionsToBe({ url: 'http://localhost:8080/', gzip: false, }) }) }) }) context('auth', () => { it('sends auth when it is an object', () => { cy.request({ url: 'http://localhost:8888', auth: { user: 'brian', pass: 'password', }, }) .then(function () { this.expectOptionsToBe({ url: 'http://localhost:8888/', auth: { user: 'brian', pass: 'password', }, }) }) }) }) context('followRedirect', () => { it('is true by default', () => { cy.request('http://localhost:8888') .then(function () { this.expectOptionsToBe({ url: 'http://localhost:8888/', }) }) }) it('can be set to false', () => { cy.request({ url: 'http://localhost:8888', followRedirect: false, }) .then(function () { this.expectOptionsToBe({ url: 'http://localhost:8888/', followRedirect: false, }) }) }) it('normalizes followRedirects -> followRedirect', () => { cy.request({ url: 'http://localhost:8888', followRedirects: false, }) .then(function () { this.expectOptionsToBe({ url: 'http://localhost:8888/', followRedirect: false, }) }) }) }) context('qs', () => { it('accepts an object literal', () => { cy.request({ url: 'http://localhost:8888', qs: { foo: 'bar', }, }) .then(function () { this.expectOptionsToBe({ url: 'http://localhost:8888/', qs: { foo: 'bar' }, }) }) }) }) context('form', () => { it('accepts an object literal for body', () => { cy.request({ url: 'http://localhost:8888', form: true, body: { foo: 'bar', }, }) .then(function () { this.expectOptionsToBe({ url: 'http://localhost:8888/', form: true, body: { foo: 'bar' }, }) }) }) it('accepts a string for body', () => { cy.request({ url: 'http://localhost:8888', form: true, body: 'foo=bar&baz=quux', }) .then(function () { this.expectOptionsToBe({ url: 'http://localhost:8888/', form: true, body: 'foo=bar&baz=quux', }) }) }) // https://github.com/cypress-io/cypress/issues/2923 it('application/x-www-form-urlencoded w/ an object body uses form: true', () => { cy.request({ url: 'http://localhost:8888', headers: { 'a': 'b', 'Content-type': 'application/x-www-form-urlencoded', }, body: { foo: 'bar' }, }) .then(function () { this.expectOptionsToBe({ url: 'http://localhost:8888/', form: true, headers: { 'a': 'b', 'Content-type': 'application/x-www-form-urlencoded', }, body: { foo: 'bar' }, }) }) }) }) }) describe('failOnStatusCode', () => { it('does not fail on status 401', () => { Cypress.backend .withArgs('http:request') .resolves({ isOkStatusCode: false, status: 401 }) cy.request({ url: 'http://localhost:1234/foo', failOnStatusCode: false, }) .then((resp) => { // make sure it really was 500! expect(resp.status).to.eq(401) }) }) }) describe('method', () => { it('can use M-SEARCH method', () => { cy.request({ url: 'http://localhost:3500/dump-method', method: 'm-Search', }) .then((res) => { expect(res.body).to.contain('M-SEARCH') }) }) }) describe('headers', () => { it('can send user-agent header', () => { cy.request({ url: 'http://localhost:3500/dump-headers', headers: { 'user-agent': 'something special', }, }) .then((res) => { expect(res.body).to.contain('"user-agent":"something special"') }) }) }) describe('subjects', () => { it('resolves with response obj', () => { const resp = { status: 200, isOkStatusCode: true, body: '<html>foo</html>', headers: { foo: 'bar' }, } Cypress.backend .withArgs('http:request') .resolves(resp) cy.request('http://www.foo.com').then((subject) => { expect(subject).to.deep.eq(resp) }) }) }) describe('timeout', () => { beforeEach(() => { Cypress.backend .withArgs('http:request') .resolves({ isOkStatusCode: true, status: 200 }) }) it('sets timeout to Cypress.config(responseTimeout)', { responseTimeout: 2500, }, () => { const timeout = cy.spy(Promise.prototype, 'timeout') cy.request('http://www.foo.com').then(() => { expect(timeout).to.be.calledWith(2500) }) }) it('can override timeout', () => { const timeout = cy.spy(Promise.prototype, 'timeout') cy.request({ url: 'http://www.foo.com', timeout: 1000 }).then(() => { expect(timeout).to.be.calledWith(1000) }) }) it('clears the current timeout and restores after success', () => { cy.timeout(100) cy.spy(cy, 'clearTimeout') cy.request('http://www.foo.com').then(() => { expect(cy.clearTimeout).to.be.calledWith('http:request') // restores the timeout afterwards expect(cy.timeout()).to.eq(100) }) }) }) describe('.log', () => { beforeEach(function () { cy.on('log:added', (attrs, log) => { if (attrs.name === 'request') { this.lastLog = log } }) return null }) it('can turn off logging', () => { Cypress.backend .withArgs('http:request') .resolves({ isOkStatusCode: true, status: 200 }) cy.request({ url: 'http://localhost:8080', log: false, }) .then(function () { expect(this.lastLog).to.be.undefined }) }) it('logs immediately before resolving', (done) => { Cypress.backend .withArgs('http:request') .resolves({ isOkStatusCode: true, status: 200 }) cy.on('log:added', (attrs, log) => { if (log.get('name') === 'request') { expect(log.get('state')).to.eq('pending') expect(log.get('message')).to.eq('') done() } }) cy.request('http://localhost:8080') }) it('snapshots after clicking', () => { Cypress.backend .withArgs('http:request') .resolves({ isOkStatusCode: true, status: 200 }) cy.request('http://localhost:8080').then(function () { const { lastLog } = this expect(lastLog.get('snapshots').length).to.eq(1) expect(lastLog.get('snapshots')[0]).to.be.an('object') }) }) it('.consoleProps', () => { const allRequestResponse = { 'Request URL': 'http://localhost:8080/foo', 'Request Headers': { 'x-token': 'ab123' }, 'Request Body': { first: 'brian' }, 'Response Headers': { 'Content-Type': 'application/json', }, 'Response Body': { id: 123 }, } Cypress.backend .withArgs('http:request') .resolves({ duration: 10, status: 201, isOkStatusCode: true, body: { id: 123 }, headers: { 'Content-Type': 'application/json', }, requestHeaders: { 'x-token': 'ab123' }, requestBody: { first: 'brian' }, allRequestResponses: [allRequestResponse], }) cy.request({ url: 'http://localhost:8080/foo', headers: { 'x-token': 'abc123' }, method: 'POST', body: { first: 'brian' }, }) .then(function () { expect(this.lastLog.invoke('consoleProps')).to.deep.eq({ Command: 'request', Request: allRequestResponse, Yielded: { duration: 10, status: 201, body: { id: 123 }, headers: { 'Content-Type': 'application/json', }, }, }) }) }) it('.consoleProps with array of allRequestResponses', () => { const allRequestResponses = [{ 'Request URL': 'http://localhost:8080/foo', 'Request Headers': { 'x-token': 'ab123' }, 'Request Body': { first: 'brian' }, 'Response Headers': { 'Content-Type': 'application/json', }, 'Response Body': { id: 123 }, }, { 'Request URL': 'http://localhost:8080/foo', 'Request Headers': { 'x-token': 'ab123' }, 'Request Body': { first: 'brian' }, 'Response Headers': { 'Content-Type': 'application/json', }, 'Response Body': { id: 123 }, }] Cypress.backend .withArgs('http:request') .resolves({ duration: 10, status: 201, isOkStatusCode: true, body: { id: 123 }, headers: { 'Content-Type': 'application/json', }, requestHeaders: { 'x-token': 'ab123' }, requestBody: { first: 'brian' }, allRequestResponses, }) cy.request({ url: 'http://localhost:8080/foo', headers: { 'x-token': 'abc123' }, method: 'POST', body: { first: 'brian' }, }) .then(function () { expect(this.lastLog.invoke('consoleProps')).to.deep.eq({ Command: 'request', Requests: allRequestResponses, Yielded: { duration: 10, status: 201, body: { id: 123 }, headers: { 'Content-Type': 'application/json', }, }, }) }) }) describe('.renderProps', () => { describe('in any case', () => { it('sends correct message', () => { Cypress.backend .withArgs('http:request') .resolves({ isOkStatusCode: true, status: 201 }) cy.request('http://localhost:8080/foo').then(function () { expect(this.lastLog.invoke('renderProps').message).to.equal('GET 201 http://localhost:8080/foo') }) }) }) describe('when response is successful', () => { it('sends correct indicator', () => { Cypress.backend .withArgs('http:request') .resolves({ isOkStatusCode: true, status: 201 }) cy.request('http://localhost:8080/foo').then(function () { expect(this.lastLog.invoke('renderProps').indicator).to.equal('successful') }) }) }) describe('when response is outside 200 range', () => { it('sends correct indicator', function (done) { cy.on('fail', (err) => { expect(this.lastLog.invoke('renderProps').indicator).to.equal('bad') done() }) Cypress.backend .withArgs('http:request') .resolves({ status: 500 }) cy.request('http://localhost:8080/foo') }) }) }) }) describe('errors', { defaultCommandTimeout: 50, }, () => { beforeEach(function () { this.logs = [] cy.on('log:added', (attrs, log) => { if (attrs.name === 'request') { this.lastLog = log this.logs.push(log) } }) return null }) it('throws when no url is passed', function (done) { cy.on('fail', (err) => { const { lastLog } = this expect(this.logs.length).to.eq(1) expect(lastLog.get('error')).to.eq(err) expect(lastLog.get('state')).to.eq('failed') expect(err.message).to.eq('`cy.request()` requires a `url`. You did not provide a `url`.') expect(err.docsUrl).to.eq('https://on.cypress.io/request') done() }) cy.request() }) it('throws when url is not FQDN', { baseUrl: '', }, function (done) { cy.stub(cy, 'getRemoteLocation').withArgs('origin').returns('') cy.on('fail', (err) => { const { lastLog } = this expect(this.logs.length).to.eq(1) expect(lastLog.get('error')).to.eq(err) expect(lastLog.get('state')).to.eq('failed') expect(err.message).to.eq('`cy.request()` must be provided a fully qualified `url` - one that begins with `http`. By default `cy.request()` will use either the current window\'s origin or the `baseUrl` in `cypress.json`. Neither of those values were present.') expect(err.docsUrl).to.eq('https://on.cypress.io/request') done() }) cy.request('/foo/bar') }) it('throws when url is not FQDN, notes that configFile is disabled', { baseUrl: '', configFile: false, }, function (done) { cy.stub(cy, 'getRemoteLocation').withArgs('origin').returns('') cy.on('fail', (err) => { const { lastLog } = this expect(this.logs.length).to.eq(1) expect(lastLog.get('error')).to.eq(err) expect(lastLog.get('state')).to.eq('failed') expect(err.message).to.eq('`cy.request()` must be provided a fully qualified `url` - one that begins with `http`. By default `cy.request()` will use either the current window\'s origin or the `baseUrl` in `cypress.json` (currently disabled by --config-file=false). Neither of those values were present.') done() }) cy.request('/foo/bar') }) it('throws when url is not FQDN, notes that configFile is non-default', { baseUrl: '', configFile: 'foo.json', }, function (done) { cy.stub(cy, 'getRemoteLocation').withArgs('origin').returns('') cy.on('fail', (err) => { const { lastLog } = this expect(this.logs.length).to.eq(1) expect(lastLog.get('error')).to.eq(err) expect(lastLog.get('state')).to.eq('failed') expect(err.message).to.eq('`cy.request()` must be provided a fully qualified `url` - one that begins with `http`. By default `cy.request()` will use either the current window\'s origin or the `baseUrl` in `foo.json`. Neither of those values were present.') done() }) cy.request('/foo/bar') }) it('throws when url isnt a string', function (done) { cy.on('fail', (err) => { const { lastLog } = this expect(this.logs.length).to.eq(1) expect(lastLog.get('error')).to.eq(err) expect(lastLog.get('state')).to.eq('failed') expect(err.message).to.eq('`cy.request()` requires the `url` to be a string.') expect(err.docsUrl).to.eq('https://on.cypress.io/request') done() }) cy.request({ url: [], }) }) it('throws when auth is truthy but not an object', function (done) { cy.on('fail', (err) => { const { lastLog } = this expect(this.logs.length).to.eq(1) expect(lastLog.get('error')).to.eq(err) expect(lastLog.get('state')).to.eq('failed') expect(err.message).to.eq('`cy.request()` must be passed an object literal for the `auth` option.') expect(err.docsUrl).to.eq('https://on.cypress.io/request') done() }) cy.request({ url: 'http://localhost:1234/foo', auth: 'foobar', }) }) it('throws when headers is truthy but not an object', function (done) { cy.on('fail', (err) => { const { lastLog } = this expect(this.logs.length).to.eq(1) expect(lastLog.get('error')).to.eq(err) expect(lastLog.get('state')).to.eq('failed') expect(err.message).to.eq('`cy.request()` requires the `headers` option to be an object literal.') expect(err.docsUrl).to.eq('https://on.cypress.io/request') done() }) cy.request({ url: 'http://localhost:1234/foo', headers: 'foo=bar', }) }) it('throws on invalid method', function (done) { cy.on('fail', (err) => { const { lastLog } = this expect(this.logs.length).to.eq(1) expect(lastLog.get('error')).to.eq(err) expect(lastLog.get('state')).to.eq('failed') expect(err.message).to.eq('`cy.request()` was called with an invalid method: `FOO`. Method can be: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`, or any other method supported by Node\'s HTTP parser.') expect(err.docsUrl).to.eq('https://on.cypress.io/request') done() }) cy.request({ url: 'http://localhost:1234/foo', method: 'FOO', }) }) it('throws when gzip is not boolean', function (done) { cy.on('fail', (err) => { const { lastLog } = this expect(this.logs.length).to.eq(1) expect(lastLog.get('error')).to.eq(err) expect(lastLog.get('state')).to.eq('failed') expect(err.message).to.eq('`cy.request()` requires the `gzip` option to be a boolean.') expect(err.docsUrl).to.eq('https://on.cypress.io/request') done() }) cy.request({ url: 'http://localhost:1234/foo', gzip: {}, }) }) it('throws when encoding is not valid', function (done) { cy.on('fail', (err) => { const { lastLog } = this expect(this.logs.length).to.eq(1) expect(lastLog.get('error')).to.eq(err) expect(lastLog.get('state')).to.eq('failed') expect(err.message).to.eq('`cy.request()` was called with invalid encoding: `binaryX`. Encoding can be: `utf8`, `utf16le`, `latin1`, `base64`, `hex`, `ascii`, `binary`, `latin1`, `ucs2`, `utf16le`, or any other encoding supported by Node\'s Buffer encoding.') expect(err.docsUrl).to.eq('https://on.cypress.io/request') done() }) cy.request({ url: 'http://localhost:1234/foo', encoding: 'binaryX', }) }) it('throws when form isnt a boolean', function (done) { cy.on('fail', (err) => { const { lastLog } = this expect(this.logs.length).to.eq(1) expect(lastLog.get('error')).to.eq(err) expect(lastLog.get('state')).to.eq('failed') expect(err.message).to.eq('`cy.request()` requires the `form` option to be a boolean.\n\nIf you\'re trying to send a `x-www-form-urlencoded` request then pass either a string or object literal to the `body` property.') expect(err.docsUrl).to.eq('https://on.cypress.io/request') done() }) cy.request({ url: 'http://localhost:1234/foo', form: { foo: 'bar' }, }) }) it('throws when failOnStatusCode is false and retryOnStatusCodeFailure is true', (done) => { cy.on('fail', (err) => { expect(err.message).to.contain('`cy.request()` was invoked with `{ failOnStatusCode: false, retryOnStatusCodeFailure: true }`.') expect(err.docsUrl).to.eq('https://on.cypress.io/request') done() }) cy.request({ url: 'http://foobarbaz', failOnStatusCode: false, retryOnStatusCodeFailure: true, }) }) it('throws when status code doesnt start with 2 and failOnStatusCode is true', function (done) { Cypress.backend .withArgs('http:request') .resolves({ isOkStatusCode: false, status: 500, statusText: 'Server Error', headers: { baz: 'quux', }, body: 'response body', requestHeaders: { foo: 'bar', }, requestBody: 'request body', redirects: [ '301: http://www.google.com', ], }) cy.on('fail', (err) => { const { lastLog } = this expect(this.logs.length).to.eq(1) expect(lastLog.get('error')).to.eq(err) expect(lastLog.get('state')).to.eq('failed') expect(err.docsUrl).to.eq('https://on.cypress.io/request') expect(err.message).to.include(stripIndent`\ \`cy.request()\` failed on: http://localhost:1234/foo The response we received from your web server was: > 500: Server Error This was considered a failure because the status code was not \`2xx\` or \`3xx\`. If you do not want status codes to cause failures pass the option: \`failOnStatusCode: false\` ----------------------------------------------------------- The request we sent was: Method: POST URL: http://localhost:1234/foo Headers: { \"foo\": \"bar\" } Body: request body Redirects: [ \"301: http://www.google.com\" ] ----------------------------------------------------------- The response we got was: Status: 500 - Server Error Headers: { \"baz\": \"quux\" } Body: response body`) done() }) cy.request({ method: 'POST', url: 'http://localhost:1234/foo', body: { username: 'cypress', }, }) }) // https://github.com/cypress-io/cypress/issues/4346 it('throws on network failure when nested', (done) => { cy.request('http://localhost:3500/dump-method') .then(() => { cy.request('http://0.0.0.0:12345') }) cy.on('fail', (err) => { expect(err.message).to.include('`cy.request()` failed trying to load:') done() }) }) it('displays body_circular when body is circular', function (done) { const foo = { bar: { baz: {}, }, } foo.bar.baz.quux = foo cy.request({ method: 'POST', url: 'http://foo.invalid/', body: foo, }) cy.on('fail', (err) => { const { lastLog } = this expect(this.logs.length).to.eq(1) expect(lastLog.get('error')).to.eq(err) expect(lastLog.get('state')).to.eq('failed') expect(err.message).to.eq(stripIndent`\ The \`body\` parameter supplied to \`cy.request()\` contained a circular reference at the path "bar.baz.quux". \`body\` can only be a string or an object with no circular references.`) expect(err.docsUrl).to.eq('https://on.cypress.io/request') done() }) }) it('does not include redirects when there were no redirects', function (done) { Cypress.backend .withArgs('http:request') .resolves({ isOkStatusCode: false, status: 500, statusText: 'Server Error', headers: { baz: 'quux', }, body: 'response body', requestHeaders: { foo: 'bar', }, requestBody: 'request body', }) cy.on('fail', (err) => { const { lastLog } = this expect(this.logs.length).to.eq(1) expect(lastLog.get('error')).to.eq(err) expect(lastLog.get('state')).to.eq('failed') expect(err.docsUrl).to.eq('https://on.cypress.io/request') expect(err.message).to.include(stripIndent`\ \`cy.request()\` failed on: http://localhost:1234/foo The response we received from your web server was: > 500: Server Error This was considered a failure because the status code was not \`2xx\` or \`3xx\`. If you do not want status codes to cause failures pass the option: \`failOnStatusCode: false\` ----------------------------------------------------------- The request we sent was: Method: POST URL: http://localhost:1234/foo Headers: { \"foo\": \"bar\" } Body: request body ----------------------------------------------------------- The response we got was: Status: 500 - Server Error Headers: { \"baz\": \"quux\" } Body: response body`) done() }) cy.request({ method: 'POST', url: 'http://localhost:1234/foo', body: { username: 'cypress', }, }) }) it('logs once on error', function (done) { const error = new Error('request failed') error.backend = true Cypress.backend .withArgs('http:request') .rejects(error) cy.on('fail', (err) => { const { lastLog } = this expect(this.logs.length).to.eq(1) expect(lastLog.get('error')).to.eq(err) expect(lastLog.get('state')).to.eq('failed') done() }) cy.request('http://localhost:1234/foo') }) // https://github.com/cypress-io/cypress/issues/5274 it('dont throw UNESCAPED_CHARACTERS error for url with ’ character in pathname', (done) => { cy.on('fail', (err) => { expect(err.message).to.contain('`cy.request()` failed trying to load:') expect(err.message).to.not.contain('ERR_UNESCAPED_CHARACTERS') done() }) cy.request('http://localhost:1234/’') }) it('dont throw UNESCAPED_CHARACTERS error for url with % character in pathname', (done) => { cy.on('fail', (err) => { expect(err.message).to.contain('`cy.request()` failed trying to load:') expect(err.message).to.not.contain('ERR_UNESCAPED_CHARACTERS') done() }) cy.request('http://localhost:1234/%') }) it('dont throw UNESCAPED_CHARACTERS error for url with ’ escaped in pathname', (done) => { cy.on('fail', (err) => { expect(err.message).to.contain('`cy.request()` failed trying to load:') expect(err.message).to.not.contain('ERR_UNESCAPED_CHARACTERS') done() }) cy.request(encodeURI('http://localhost:1234/’')) }) it('dont throw UNESCAPED_CHARACTERS error for url with Unicode in pathname from BMP to Astral Plane', (done) => { cy.on('fail', (err) => { expect(err.message).to.contain('`cy.request()` failed trying to load:') expect(err.message).to.not.contain('ERR_UNESCAPED_CHARACTERS') done() }) cy.request('http://localhost:1234/😀') }) it('dont throw UNESCAPED_CHARACTERS error for url with any Unicode escaped character in pathname', (done) => { cy.on('fail', (err) => { expect(err.message).to.contain('`cy.request()` failed trying to load:') expect(err.message).to.not.contain('ERR_UNESCAPED_CHARACTERS') done() }) cy.request(encodeURI('http://localhost:1234/😀')) }) context('displays error', () => { it('displays method and url in error', (done) => { const error = new Error('request failed') error.backend = true Cypress.backend .withArgs('http:request') .rejects(error) cy.on('fail', (err) => { expect(err.message).to.include(stripIndent`\ \`cy.request()\` failed trying to load: http://localhost:1234/foo We attempted to make an http request to this URL but the request failed without a response. We received this error at the network level: > request failed ----------------------------------------------------------- The request we sent was: Method: GET URL: http://localhost:1234/foo ----------------------------------------------------------- Common situations why this would fail: - you don't have internet access - you forgot to run / boot your web server - your web server isn't accessible - you have weird network configuration settings on your computer`) expect(err.docsUrl).to.eq('https://on.cypress.io/request') done() }) cy.request('http://localhost:1234/foo') }) it('throws after timing out', function (done) { Cypress.backend .withArgs('http:request') .resolves(Promise.delay(1000)) cy.on('fail', (err) => { const { lastLog, } = this expect(this.logs.length).to.eq(1) expect(lastLog.get('error')).to.eq(err) expect(lastLog.get('state')).to.eq('failed') expect(err.docsUrl).to.eq('https://on.cypress.io/request') expect(err.message).to.eq(stripIndent`\ \`cy.request()\` timed out waiting \`50ms\` for a response from your server. The request we sent was: Method: GET URL: http://localhost:1234/foo No response was received within the timeout.`) done() }) cy.request({ url: 'http://localhost:1234/foo', timeout: 50 }) }) }) }) }) })