UNPKG

@revoloo/cypress6

Version:

Cypress.io end to end testing tool

1,685 lines (1,435 loc) 75.9 kB
describe('network stubbing', { retries: 2 }, function () { const { $, _, sinon, state, Promise } = Cypress beforeEach(function () { cy.spy(Cypress.utils, 'warning') }) context('cy.route2()', function () { it('emits a warning', function () { cy.route2('*') .then(() => expect(Cypress.utils.warning).to.be.calledWith('`cy.route2()` was renamed to `cy.intercept()` and will be removed in a future release. Please update usages of `cy.route2()` to use `cy.intercept()` instead.')) }) it('calls through to cy.intercept()', function (done) { cy.route2('*', 'hello world').then(() => { $.get('/abc123').done((responseText, _, xhr) => { expect(responseText).to.eq('hello world') done() }) }) }) it('can be used with cy.wait', function () { cy.route2('*', 'hello world').as('foo') .then(() => $.get('/abc123')).wait('@foo') }) }) context('cy.intercept()', function () { beforeEach(function () { // we don't use cy.spy() because it causes an infinite loop with logging events this.sandbox = sinon.createSandbox() this.emit = this.sandbox.spy(Cypress, 'emit').withArgs('backend:request', 'net', 'route:added') this.testRoute = function (options, handler, expectedEvent, expectedRoute) { cy.intercept(options, handler).then(function () { const handlerId = _.findKey(state('routes'), { handler }) const route = state('routes')[handlerId!] expectedEvent.handlerId = handlerId expect(this.emit).to.be.calledWith('backend:request', 'net', 'route:added', expectedEvent) expect(route.handler).to.deep.eq(expectedRoute.handler) expect(route.options).to.deep.eq(expectedRoute.options) }) } }) afterEach(function () { this.sandbox.restore() }) it('emits with url, body and stores Route', function () { const handler = 'bar' const url = 'http://foo.invalid' const expectedEvent = { routeMatcher: { url: { type: 'glob', value: url, }, matchUrlAgainstPath: true, }, staticResponse: { body: 'bar', }, hasInterceptor: false, } const expectedRoute = { options: { url, matchUrlAgainstPath: true }, handler, } this.testRoute(url, handler, expectedEvent, expectedRoute) }) it('emits with url, HTTPController and stores Route', function () { const handler = () => { return {} } const url = 'http://foo.invalid' const expectedEvent = { routeMatcher: { url: { type: 'glob', value: url, }, matchUrlAgainstPath: true, }, hasInterceptor: true, } const expectedRoute = { options: { url, matchUrlAgainstPath: true }, handler, } this.testRoute(url, handler, expectedEvent, expectedRoute) }) it('emits with regex values stringified and other values copied and stores Route', function () { const handler = () => { return {} } const options = { auth: { username: 'foo', password: /.*/, }, headers: { 'Accept-Language': /hylian/i, 'Content-Encoding': 'corrupted', }, hostname: /any.com/, https: true, method: 'POST', path: '/bing?foo', pathname: '/bazz', port: [1, 2, 3, 4, 5, 6], query: { bar: 'baz', quuz: /(.*)quux/gi, }, url: 'http://foo.invalid', } const expectedEvent = { routeMatcher: { auth: { username: { type: 'glob', value: options.auth.username, }, password: { type: 'regex', value: '/.*/', }, }, headers: { 'accept-language': { type: 'regex', value: '/hylian/i', }, 'content-encoding': { type: 'glob', value: options.headers['Content-Encoding'], }, }, hostname: { type: 'regex', value: '/any.com/', }, https: options.https, method: { type: 'glob', value: options.method, }, path: { type: 'glob', value: options.path, }, pathname: { type: 'glob', value: options.pathname, }, port: options.port, query: { bar: { type: 'glob', value: options.query.bar, }, quuz: { type: 'regex', value: '/(.*)quux/gi', }, }, url: { type: 'glob', value: options.url, }, }, hasInterceptor: true, } const expectedRoute = { options, handler, } this.testRoute(options, handler, expectedEvent, expectedRoute) }) // https://github.com/cypress-io/cypress/issues/8729 it('resolve ambiguity between overloaded definitions', () => { cy.intercept('POST', '/post-only').as('create') cy.window().then((win) => { win.eval( `fetch("/post-only", { method: 'POST', // *GET, POST, PUT, DELETE, etc. });`, ) }) cy.wait('@create') }) // https://github.com/cypress-io/cypress/issues/9313 it('lower-cased method works', () => { cy.intercept({ method: 'post', url: '/post-only', }).as('create') cy.window().then((win) => { win.eval( `fetch("/post-only", { method: 'post', // *GET, POST, PUT, DELETE, etc. });`, ) }) cy.wait('@create') }) // TODO: implement warning in cy.intercept if appropriate // https://github.com/cypress-io/cypress/issues/2372 it.skip('warns if a percent-encoded URL is used', function () { cy.intercept('GET', '/foo%25bar').then(function () { expect(Cypress.utils.warning).to.be.calledWith('A URL with percent-encoded characters was passed to cy.intercept(), but cy.intercept() expects a decoded URL.\n\nDid you mean to pass "/foo%bar"?') }) }) // NOTE: see todo on 'warns if a percent-encoded URL is used' it.skip('does not warn if an invalid percent-encoded URL is used', function () { cy.intercept('GET', 'http://example.com/%E0%A4%A').then(function () { expect(Cypress.utils.warning).to.not.be.called }) }) context('logging', function () { beforeEach(function () { this.logs = [] cy.on('log:added', (attrs, log) => { if (attrs.instrument === 'route') { this.lastLog = log this.logs.push(log) } }) }) it('has name of route', function () { cy.intercept('/foo', {}).then(function () { let lastLog lastLog = this.lastLog expect(lastLog.get('name')).to.eq('route') }) }) it('uses the wildcard URL', function () { cy.intercept('*', {}).then(function () { let lastLog lastLog = this.lastLog expect(lastLog.get('url')).to.eq('*') }) }) // TODO: implement log niceties it.skip('#consoleProps', function () { cy.intercept('*', { foo: 'bar', }).as('foo').then(function () { expect(this.lastLog.invoke('consoleProps')).to.deep.eq({ Command: 'route', Method: 'GET', URL: '*', Status: 200, Response: { foo: 'bar', }, Alias: 'foo', }) }) }) describe('numResponses', function () { it('is initially 0', function () { cy.intercept(/foo/, {}).then(() => { let lastLog lastLog = this.lastLog expect(lastLog.get('numResponses')).to.eq(0) }) }) it('is incremented to 2', function () { cy.intercept(/foo/, {}).then(function () { $.get('/foo') }).wrap(this).invoke('lastLog.get', 'numResponses').should('eq', 1) }) it('is incremented for each matching request', function () { cy.intercept(/foo/, {}).then(function () { return Promise.all([$.get('/foo'), $.get('/foo'), $.get('/foo')]) }).wrap(this).invoke('lastLog.get', 'numResponses').should('eq', 3) }) }) }) context('errors', function () { beforeEach(function () { this.logs = [] cy.on('log:added', (attrs, log) => { this.lastLog = log this.logs.push(log) }) }) it('url must be a string or regexp', function (done) { cy.on('fail', function (err) { expect(err.message).to.include('`url` must be a string or a regular expression') done() }) // @ts-ignore: should fail cy.intercept({ // @ts-ignore url: {}, }) }) // TODO: not currently implemented it.skip('fails when method is invalid', function (done) { cy.on('fail', function (err) { expect(err.message).to.include('cy.intercept() was called with an invalid method: \'POSTS\'.') done() }) cy.intercept('post', '/foo', {}) }) it('requires a url when given a response', function (done) { cy.on('fail', function (err) { expect(err.message).to.include('The RouteMatcher does not contain any keys. You must pass something to match on.') done() }) cy.intercept({}) }) it('requires arguments', function (done) { cy.on('fail', function (err) { expect(err.message).to.include('An invalid RouteMatcher was supplied to `cy.intercept()`. The RouteMatcher does not contain any keys. You must pass something to match on.') done() }) // @ts-ignore - should fail cy.intercept() }) context('with invalid RouteMatcher', function () { it('requires unique header names', function (done) { cy.on('fail', function (err) { expect(err.message).to.include('`FOO` was specified more than once in `headers`. Header fields can only be matched once (HTTP header field names are case-insensitive).') done() }) cy.intercept({ headers: { foo: 'bar', FOO: 'bar', }, }) }) it('requires StringMatcher header values', function (done) { cy.on('fail', function (err) { expect(err.message).to.include('`headers.wrong` must be a string or a regular expression.') done() }) // @ts-ignore this is invalid on purpose cy.intercept({ headers: { good: 'string', fine: /regexp/, // @ts-ignore wrong: 3, }, }) }) it('must set `url` with `matchUrlAgainstPath`', function (done) { cy.on('fail', function (err) { expect(err.message).to.include('`matchUrlAgainstPath` requires a `url` to be specified.') done() }) cy.intercept({ matchUrlAgainstPath: true, }) }) it('must not set `path` with `matchUrlAgainstPath`', function (done) { cy.on('fail', function (err) { expect(err.message).to.include('`matchUrlAgainstPath` and `path` cannot both be set.') done() }) cy.intercept({ matchUrlAgainstPath: true, path: '*', url: '*', }) }) }) context('with invalid handler', function () { [false, null].forEach(function (handler) { const name = String(handler) it(`${name} fails`, function (done) { cy.on('fail', (err) => { expect(err).to.eq(this.lastLog.get('error')) expect(err.message).to.contain(`You passed: ${name}`) done() }) // @ts-ignore - this should error cy.intercept('/', handler) }) }) }) context('with invalid StaticResponse', function () { [ [ 'forceNetworkError set but not alone', { forceNetworkError: true, body: 'aaa', }, 'must be the only option', ], [ 'statusCode out of range', { statusCode: -1, }, 'must be a number', ], [ 'headers invalid type', { headers: { a: { 1: 2, }, }, }, 'must be a map', ], ].forEach(function ([name, handler, expectedErr]) { it(`${name} fails`, function (done) { cy.on('fail', (err) => { expect(err).to.eq(this.lastLog.get('error')) expect(err.message).to.contain(expectedErr) expect(err.message).to.contain(`You passed: ${JSON.stringify(handler, null, 2)}`) done() }) // @ts-ignore - this should error cy.intercept('/', handler) }) }) }) }) }) context('events', function () { // @see https://github.com/cypress-io/cypress/issues/9170 it('gracefully handles request received without a known route', function () { cy.intercept('/valid.json', (req) => { req.reply({ bad: 'should not be received' }) }) .then(() => { const routeIds = _.keys(state('routes')) // delete the driver-side route - the server-side route will still exist and cause an event // to be emitted to the driver delete state('routes')[routeIds[0]] expect(state('routes')).to.deep.eq({}) return $.get('/fixtures/valid.json') }) .then((body) => { expect(body).to.include({ foo: 1 }) }) }) it('gracefully handles response received without a known route', function () { cy.intercept('/valid.json', (req) => { state('routes', {}) req.reply((res) => { res.send({ bad: 'should not be received' }) }) }) .then(() => { return $.get('/fixtures/valid.json') }) .then((body) => { expect(body).to.include({ foo: 1 }) }) }) it('gracefully handles response completed without a known route', function () { cy.intercept('/valid.json', (req) => { req.reply((res) => { state('routes', {}) res.send({ bar: 2 }) }) }) .then(() => { return $.get('/fixtures/valid.json') }) .then((body) => { expect(body).to.include({ bar: 2 }) }) }) }) context('network handling', function () { // @see https://github.com/cypress-io/cypress/issues/8497 it('can load transfer-encoding: chunked redirects', function () { cy.intercept('*') const url4 = 'http://localhost:3501/fixtures/generic.html' const url3 = `http://localhost:3501/redirect?href=${encodeURIComponent(url4)}` const url2 = `https://localhost:3502/redirect?chunked=1&href=${encodeURIComponent(url3)}` const url1 = `https://localhost:3502/redirect?chunked=1&href=${encodeURIComponent(url2)}` cy.visit(url1) .location('href').should('eq', url4) }) context('can intercept against any domain', function () { beforeEach(function () { // reset origin cy.visit('http://localhost:3500/fixtures/generic.html') }) it('different origin (HTTP)', function () { cy.intercept('/foo').as('foo') .then(() => { $.get('http://baz.foobar.com:3501/foo') }) .wait('@foo') }) it('different origin with response interception (HTTP)', function () { cy.intercept('/xhr.html', (req) => { req.reply((res) => { expect(res.body).to.include('xhr fixture') res.body = 'replaced the body' }) }).as('foo') .then(() => { return $.get('http://baz.foobar.com:3501/fixtures/xhr.html') .then((responseText) => { expect(responseText).to.eq('replaced the body') }) }) .wait('@foo').its('response.body').should('eq', 'replaced the body') }) // @see https://github.com/cypress-io/cypress/issues/8487 it('different origin (HTTPS)', function () { cy.intercept('/foo', 'somethin').as('foo') .then(() => { $.get('https://bar.foobar.com.invalid:3502/foo') }) .wait('@foo') }) it('different origin with response interception (HTTPS)', function () { cy.intercept('/xhr.html', (req) => { req.reply((res) => { expect(res.body).to.include('xhr fixture') res.body = 'replaced the body' }) }).as('foo') .then(() => { return $.get('https://bar.foobar.com:3502/fixtures/xhr.html') .then((responseText) => { expect(responseText).to.eq('replaced the body') }) }) .wait('@foo').its('response.body').should('eq', 'replaced the body') }) }) // @see https://github.com/cypress-io/cypress/issues/9599 context('cors preflight', function () { // a different domain from the page own domain // NOTE: this domain is redirected back to the local host test server // using "hosts" setting in the "cypress.json" file const corsUrl = 'http://diff.foobar.com:3501/no-cors' before(() => { cy.visit('http://127.0.0.1:3500/fixtures/dom.html') }) it('responds to OPTIONS requests', function () { // `/no-cors/` will respond with a 200, but missing valid preflight response cy.request({ method: 'OPTIONS', url: corsUrl }) .then((res) => { expect(res.headers).to.not.have.property('access-control-allow-origin') // so this ajax request should fail due to CORS return $.ajax({ method: 'DELETE', url: corsUrl }) .then(() => { throw new Error('should not succeed') }) .catch((res) => { expect(res).to.include({ statusText: 'error' }) }) }) .intercept('/no-cors', (req) => { req.reply(`intercepted ${req.method}`) }) .then(() => { // but now, the same ajax request succeeds, because the cy.intercept provides CORS return $.ajax({ method: 'DELETE', url: corsUrl }) .then((res) => { expect(res).to.eq('intercepted DELETE') }) }) }) it('can be overwritten', function () { cy.intercept('OPTIONS', '/no-cors', (req) => { req.reply({ headers: { 'access-control-allow-origin': 'http://wrong.invalid', }, }) }) .intercept('/no-cors', (req) => { req.reply(`intercepted ${req.method}`) }) .then(() => { return $.ajax({ method: 'DELETE', url: corsUrl }) .then(() => { throw new Error('should not succeed') }) .catch((res) => { expect(res).to.include({ statusText: 'error' }) }) }) }) }) }) context('stubbing with static responses', function () { it('can stub a response with static body as string', function (done) { cy.intercept({ url: '*', }, 'hello world').then(() => { $.get('/abc123').done((responseText, _, xhr) => { expect(xhr.status).to.eq(200) expect(responseText).to.eq('hello world') done() }) }) }) it('can stub a cy.visit with static body', function () { cy.intercept('/foo', '<html>hello cruel world</html>').visit('/foo').document().should('contain.text', 'hello cruel world') }) it('can stub a response with an empty StaticResponse', function (done) { cy.intercept('/', {}).then(() => { $.get('/').done((responseText, _, xhr) => { expect(xhr.status).to.eq(200) expect(responseText).to.eq('') done() }) }) }) it('can stub a response with a network error', function (done) { cy.intercept('/', { forceNetworkError: true, }).then(() => { $.get('/').fail((xhr) => { expect(xhr.statusText).to.eq('error') expect(xhr.status).to.eq(0) done() }) }) }) it('can use regular strings as response', function () { cy.intercept('/foo', 'foo bar baz').as('getFoo').then(function (win) { $.get('/foo') }).wait('@getFoo').then(function (res) { expect(res.response!.body).to.eq('foo bar baz') }) }) it('can stub requests with uncommon HTTP methods', function () { cy.intercept('PROPFIND', '/foo', 'foo bar baz').as('getFoo').then(function (win) { $.ajax({ url: '/foo', method: 'PROPFIND', }) }).wait('@getFoo').then(function (res) { expect(res.response!.body).to.eq('foo bar baz') }) }) it('can stub a response with an array', function (done) { const response = ['foo', 'bar', { foo: 'baz' }] cy.intercept({ url: '*', }, response).then(() => { $.get('/abc123').done((responseJson, _, xhr) => { expect(xhr.status).to.eq(200) expect(responseJson).to.deep.eq(response) expect(xhr.getResponseHeader('content-type')).to.eq('application/json') done() }) }) }) // TODO: flaky - unable to reproduce outside of CI it('still works after a cy.visit', { retries: 2 }, function () { cy.intercept(/foo/, { body: JSON.stringify({ foo: 'bar' }), headers: { 'content-type': 'application/json', }, }).as('getFoo').visit('http://localhost:3500/fixtures/jquery.html').window().then(function (win) { return new Promise(function (resolve) { $.get('/foo').done(_.ary(resolve, 0)) }) }).wait('@getFoo').its('request.url').should('include', '/foo').visit('http://localhost:3500/fixtures/generic.html').window().then(function (win) { return new Promise(function (resolve) { $.get('/foo').done(_.ary(resolve, 0)) }) }).wait('@getFoo').its('request.url').should('include', '/foo') }) // @see https://github.com/cypress-io/cypress/issues/8532 context('can stub a response with an empty array', function () { const assertEmptyArray = (done) => { return () => { $.get('/abc123').done((responseJson, _, xhr) => { expect(xhr.status).to.eq(200) expect(responseJson).to.deep.eq([]) expect(xhr.getResponseHeader('content-type')).to.eq('application/json') done() }) } } it('with explicit StaticResponse', function (done) { cy.intercept({ url: '*', }, { body: [], }).then(assertEmptyArray(done)) }) it('with body shorthand', function (done) { cy.intercept('*', []).then(assertEmptyArray(done)) }) it('with method, url, res shorthand', function (done) { cy.intercept('GET', '*', []).then(assertEmptyArray(done)) }) it('in req.reply', function (done) { cy.intercept('*', (req) => req.reply([])).then(assertEmptyArray(done)) }) it('in res.send', function (done) { cy.intercept('*', (req) => req.reply((res) => res.send(200, []))).then(assertEmptyArray(done)) }) }) context('fixtures', function () { it('can stub a response with a JSON object', function () { cy.intercept({ method: 'POST', url: '/test-xhr', }, { fixture: 'valid.json', }).visit('/fixtures/xhr-triggered.html').get('#trigger-xhr').click() cy.contains('#result', '{"foo":1,"bar":{"baz":"cypress"}}').should('be.visible') }) it('works with content-type override', function () { cy.intercept({ method: 'POST', url: '/test-xhr', }, { headers: { 'content-type': 'text/plain', }, fixture: 'valid.json', }).visit('/fixtures/xhr-triggered.html').get('#trigger-xhr').click() cy.contains('#result', '"{\\"foo\\":1,\\"bar\\":{\\"baz\\":\\"cypress\\"}}"').should('be.visible') }) it('works if the JSON file has null content', function () { cy.intercept({ method: 'POST', url: '/test-xhr', }, { fixture: 'null.json', }).visit('/fixtures/xhr-triggered.html').get('#trigger-xhr').click() cy.contains('#result', '""').should('be.visible') }) // @see https://github.com/cypress-io/cypress/issues/8623 it('works with images', function () { cy.visit('/fixtures/img-embed.html') .contains('div', 'error loading image') .intercept('non-existing-image.png', { fixture: 'media/cypress.png' }) .reload() .contains('div', 'it loaded') }) }) }) context('intercepting request', function () { it('receives the original request in handler', function (done) { cy.intercept('/def456', function (req) { req.reply({ statusCode: 404, }) expect(req).to.include({ method: 'GET', httpVersion: '1.1', }) expect(req.url).to.match(/^http:\/\/localhost:3500\/def456/) done() }).then(function () { $.get('/def456') }) }) it('receives the original request body in handler', function (done) { cy.intercept('/aaa', function (req) { expect(req.body).to.eq('foo-bar-baz') done() }).then(function () { $.post('/aaa', 'foo-bar-baz') }) }) it('can modify original request body and have it passed to next handler', function (done) { cy.intercept('/post-only', function (req) { expect(req.body).to.eq('foo-bar-baz') req.body = 'quuz' }).then(function () { cy.intercept('/post-only', function (req) { expect(req.body).to.eq('quuz') req.body = 'quux' }) }).then(function () { cy.intercept('/post-only', function (req) { expect(req.body).to.eq('quux') done() }) }).then(function () { $.post('/post-only', 'foo-bar-baz') }) }) it('can modify a cy.visit before it goes out', function () { cy.intercept('/dump-headers', function (req) { expect(req.headers['foo']).to.eq('bar') req.headers['foo'] = 'quux' }).then(function () { cy.visit({ url: '/dump-headers', headers: { 'foo': 'bar', }, }) cy.get('body').should('contain.text', '"foo":"quux"') }) }) it('can modify the request URL and headers', function (done) { cy.intercept('/does-not-exist', function (req) { expect(req.headers['foo']).to.eq('bar') req.url = 'http://localhost:3500/dump-headers' req.headers['foo'] = 'quux' }).then(function () { const xhr = new XMLHttpRequest() xhr.open('GET', '/does-not-exist') xhr.setRequestHeader('foo', 'bar') xhr.send() xhr.onload = () => { expect(xhr.responseText).to.contain('"foo":"quux"') done() } }) }) it('can modify the request method', function (done) { cy.intercept('/dump-method', function (req) { expect(req.method).to.eq('POST') req.method = 'PATCH' }).then(function () { $.post('/dump-method').done((responseText) => { expect(responseText).to.contain('request method: PATCH') done() }) }) }) it('can modify the request body', function (done) { const body = '{"foo":"bar"}' cy.intercept('/post-only', function (req) { expect(req.body).to.eq('quuz') req.headers['content-type'] = 'application/json' req.body = body }).then(function () { $.post('/post-only', 'quuz').done((responseText) => { expect(responseText).to.contain(body) done() }) }) }) it('can add a body to a request that does not have one', function (done) { const body = '{"foo":"bar"}' cy.intercept('/post-only', function (req) { expect(req.body).to.eq('') expect(req.method).to.eq('GET') req.method = 'POST' req.headers['content-type'] = 'application/json' req.body = body }).then(function () { $.get('/post-only').done((responseText) => { expect(responseText).to.contain(body) done() }) }) }) it('can reply with a JSON fixture', function () { cy.intercept({ method: 'POST', url: '/test-xhr', }, (req) => { req.reply({ fixture: 'valid.json', }) }).visit('/fixtures/xhr-triggered.html').get('#trigger-xhr').click() cy.contains('{"foo":1,"bar":{"baz":"cypress"}}') }) it('can delay and throttle a StaticResponse', function (done) { const payload = 'A'.repeat(10 * 1024) const throttleKbps = 10 const delay = 250 const expectedSeconds = payload.length / (1024 * throttleKbps) + delay / 1000 cy.intercept('/timeout', (req) => { this.start = Date.now() req.reply({ statusCode: 200, body: payload, throttleKbps, delay, }) }).then(() => { return $.get('/timeout').then((responseText) => { expect(Date.now() - this.start).to.be.closeTo(expectedSeconds * 1000 + 100, 100) expect(responseText).to.eq(payload) done() }) }) }) it('can delay with deprecated delayMs param', function (done) { const delayMs = 250 cy.intercept('/timeout', (req) => { this.start = Date.now() req.reply({ delayMs, }) }).then(() => { return $.get('/timeout').then((responseText) => { expect(Date.now() - this.start).to.be.closeTo(250 + 100, 100) done() }) }) }) // @see https://github.com/cypress-io/cypress/issues/14446 it('should delay the same amount on every response', () => { const delay = 250 const testDelay = () => { const start = Date.now() return $.get('/timeout').then((responseText) => { expect(Date.now() - start).to.be.closeTo(delay, 50) expect(responseText).to.eq('foo') }) } cy.intercept('/timeout', { statusCode: 200, body: 'foo', delay, }).as('get') .then(() => testDelay()).wait('@get') .then(() => testDelay()).wait('@get') .then(() => testDelay()).wait('@get') }) context('body parsing', function () { it('automatically parses JSON request bodies', function () { const p = Promise.defer() cy.intercept('/post-only', (req) => { expect(req.body).to.deep.eq({ foo: 'bar' }) p.resolve() }).as('post') .then(() => { return $.ajax({ url: '/post-only', method: 'POST', contentType: 'application/json', data: JSON.stringify({ foo: 'bar' }), }) }).then((responseText) => { expect(responseText).to.include('request body:<br>{"foo":"bar"}') return p }) .wait('@post').its('request.body').should('deep.eq', { foo: 'bar' }) }) it('doesn\'t automatically parse JSON request bodies if content-type is wrong', function () { const p = Promise.defer() cy.intercept('/post-only', (req) => { expect(req.body).to.deep.eq(JSON.stringify({ foo: 'bar' })) p.resolve() }).as('post') .then(() => { return $.ajax({ url: '/post-only', method: 'POST', contentType: 'text/html', data: JSON.stringify({ foo: 'bar' }), }) }).wrap(p) .wait('@post').its('request.body').should('eq', JSON.stringify({ foo: 'bar' })) }) it('sets body to string if JSON is malformed', function () { const p = Promise.defer() cy.intercept('/post-only', (req) => { expect(req.body).to.deep.eq('{ foo::: }') p.resolve() }).as('post') .then(() => { return $.ajax({ url: '/post-only', method: 'POST', contentType: 'application/json', // invalid JSON data: '{ foo::: }', }).catch(() => p) }) .wait('@post').its('request.body').should('deep.eq', '{ foo::: }') }) }) context('matches requests as expected', function () { it('handles querystrings as expected', function () { cy.intercept({ query: { foo: 'b*r', baz: /quu[x]/, }, }).as('first') .intercept({ path: '/abc?foo=bar&baz=qu*x*', }).as('second') .intercept({ pathname: '/abc', }).as('third') .intercept('*', 'it worked').as('final') .then(() => { return $.get('/abc?foo=bar&baz=quux') }) .wait('@first') .wait('@second') .wait('@third') .wait('@final') }) // @see https://github.com/cypress-io/cypress/issues/8921 it('with case-insensitive header matching', function () { cy.intercept({ headers: { 'X-some-Thing': 'foo', }, }) .as('foo') .then(() => { $.get({ url: '/foo', headers: { 'X-SOME-THING': 'foo', }, }) }) .wait('@foo') }) // @see https://github.com/cypress-io/cypress/issues/9379 context('falls back to matching by path if plain string is passed', function () { it('matches globs against path', function (done) { cy.intercept('/foo/*', (req) => { expect(req.url).to.include('/foo/1') done() }) .then(() => { $.ajax({ url: '/foo/1', cache: true }) }) }) it('matches nested globs against path', function (done) { cy.intercept('/foo/*/bar', (req) => { expect(req.url).to.match(/\/foo\/1\/bar$/) done() }) .then(() => { $.get({ url: '/foo/1/bar', cache: true }) }) }) }) }) context('with StaticResponse shorthand', function () { it('req.reply(body)', function () { cy.intercept('/foo', function (req) { req.reply('baz') }) .then(() => $.get('/foo')) .should('eq', 'baz') }) it('req.reply(json)', function () { cy.intercept('/foo', function (req) { req.reply({ baz: 'quux' }) }) .then(() => $.getJSON('/foo')) .should('deep.eq', { baz: 'quux' }) }) it('req.reply(status)', function () { cy.intercept('/foo', function (req) { req.reply(777) }) .then(() => { return new Promise((resolve) => { $.get('/foo').fail((x) => resolve(x.status)) }) }) .should('eq', 777) }) it('req.reply(status, body)', function () { cy.intercept('/foo', function (req) { req.reply(777, 'bar') }) .then(() => { return new Promise((resolve) => { $.get('/foo').fail((xhr) => resolve(_.pick(xhr, 'status', 'responseText'))) }) }).should('include', { status: 777, responseText: 'bar', }) }) it('req.reply(status, json)', function () { cy.intercept('/foo', function (req) { req.reply(777, { bar: 'baz' }) }) .then(() => { return new Promise((resolve) => { $.get('/foo').fail((xhr) => resolve(_.pick(xhr, 'status', 'responseJSON'))) }) }).should('deep.include', { status: 777, responseJSON: { bar: 'baz' }, }) }) it('req.reply(status, json, headers)', function () { cy.intercept('/foo', function (req) { req.reply(777, { bar: 'baz' }, { 'x-quux': 'quuz' }) }) .then(() => { return new Promise((resolve) => { $.get('/foo').fail((xhr) => resolve(_.pick(xhr, 'status', 'responseJSON', 'getAllResponseHeaders'))) }) }).should('deep.include', { status: 777, responseJSON: { bar: 'baz' }, }).invoke('getAllResponseHeaders') .should('include', 'x-quux: quuz') .and('include', 'content-type: application/json') }) it('can forceNetworkError', function (done) { cy.intercept('/foo', function (req) { req.reply({ forceNetworkError: true }) }) .then(() => { $.get('/foo').fail((xhr) => { expect(xhr).to.include({ status: 0, statusText: 'error', readyState: 0, }) done() }) }) }) }) context('request handler chaining', function () { it('passes request through in order', function () { cy.intercept('/dump-method', function (req) { expect(req.method).to.eq('GET') req.method = 'POST' }).intercept('/dump-method', function (req) { expect(req.method).to.eq('POST') req.method = 'PATCH' }).intercept('/dump-method', function (req) { expect(req.method).to.eq('PATCH') req.reply() }).visit('/dump-method').contains('PATCH') }) it('stops passing request through once req.reply called', function () { cy.intercept('/dump-method', function (req) { expect(req.method).to.eq('GET') req.method = 'POST' }).intercept('/dump-method', function (req) { expect(req.method).to.eq('POST') req.reply() }).visit('/dump-method').contains('POST') }) }) context('errors', function () { it('fails test if req.reply is called twice in req handler', function (done) { cy.on('fail', (err) => { expect(err.message).to.contain('`req.reply()` was called multiple times in a request handler, but a request can only be replied to once') done() }) cy.intercept('/dump-method', function (req) { req.reply() req.reply() }).visit('/dump-method') }) it('fails test if req.reply is called after req handler finishes', function (done) { cy.on('fail', (err) => { expect(err.message).to.contain('> `req.reply()` was called after the request handler finished executing') done() }) cy.intercept('/dump-method', function (req) { setTimeout(() => req.reply(), 50) }).visit('/dump-method') }) it('fails test if req.reply is called after req handler resolves', function (done) { cy.on('fail', (err) => { expect(err.message).to.contain('> `req.reply()` was called after the request handler finished executing') done() }) cy.intercept('/dump-method', function (req) { setTimeout(() => req.reply(), 50) return Promise.resolve() }).visit('/dump-method') }) it('fails test if an exception is thrown in req handler', function (done) { cy.on('fail', (err2) => { expect(err2.message).to.contain('A request callback passed to `cy.intercept()` threw an error while intercepting a request') .and.contain(err.message) done() }) const err = new Error('bar') cy.intercept('/foo', () => { throw err }).visit('/foo') }) it('fails test if req.reply is called with an invalid StaticResponse', function (done) { cy.on('fail', (err) => { expect(err.message).to.contain('A request callback passed to `cy.intercept()` threw an error while intercepting a request') .and.contain('must be a number between 100 and 999 (inclusive).') done() }) cy.intercept('/foo', (req) => { req.reply({ statusCode: 1 }) }).visit('/foo') }) it('can timeout in request handler', function (done) { cy.on('fail', (err) => { Cypress.config('defaultCommandTimeout', 5000) expect(err.message).to.match(/^A request callback passed to `cy.intercept\(\)` timed out after returning a Promise that took more than the `defaultCommandTimeout` of `50ms` to resolve\./) done() }) Cypress.config('defaultCommandTimeout', 50) cy.intercept('/foo', () => { return Promise.delay(200) }).visit('/foo') }) }) context('correctly determines the content-length of an intercepted request', function () { it('when body is empty', function (done) { cy.intercept('/post-only', function (req) { req.body = '' }).then(function () { cy.intercept('/post-only', function (req) { expect(req.headers['content-length']).to.eq('0') done() }) }) .then(() => { $.post('/post-only', 'foo') }) }) it('when body contains ascii', function (done) { cy.intercept('/post-only', function (req) { req.body = 'this is only ascii' }).then(function () { cy.intercept('/post-only', function (req) { expect(req.headers['content-length']).to.eq('18') done() }) }) .then(() => { $.post('/post-only', 'bar') }) }) it('when body contains unicode', function (done) { cy.intercept('/post-only', function (req) { req.body = '🙃🤔' }).then(function () { cy.intercept('/post-only', function (req) { expect(req.headers['content-length']).to.eq('8') done() }) }) .then(() => { $.post('/post-only', 'baz') }) }) }) }) context('intercepting response', function () { it('receives the original response in handler', function (done) { cy.intercept('/json-content-type', function (req) { req.reply(function (res) { expect(res.body).to.deep.eq({}) done() }) expect(req.url).to.match(/^http:\/\/localhost:3500\/json-content-type/) }).then(function () { $.get('/json-content-type') }) }) it('intercepts redirects as expected', function () { const href = `/fixtures/generic.html?t=${Date.now()}` const url = `/redirect?href=${encodeURIComponent(href)}` cy.intercept('/redirect', (req) => { req.reply((res) => { expect(res.statusCode).to.eq(301) expect(res.headers.location).to.eq(href) res.send() }) }) .as('redirect') .intercept('/fixtures/generic.html').as('dest') .then(() => fetch(url)) .wait('@redirect') .wait('@dest') }) it('can simply wait on redirects without intercepting', function () { const href = `/fixtures/generic.html?t=${Date.now()}` const url = `/redirect?href=${encodeURIComponent(href)}` cy.intercept('/redirect') .as('redirect') .intercept('/fixtures/generic.html').as('dest') .then(() => fetch(url)) .wait('@redirect') .wait('@dest') }) // @see https://github.com/cypress-io/cypress/issues/7967 it('can skip redirects via followRedirect', function () { const href = `/fixtures/generic.html?t=${Date.now()}` const url = `/redirect?href=${encodeURIComponent(href)}` cy.intercept('/redirect', (req) => { req.followRedirect = true req.reply((res) => { expect(res.body).to.include('Some generic content') expect(res.statusCode).to.eq(200) res.send() }) }) .then(() => fetch(url)) }) it('intercepts cached responses as expected', { browser: '!firefox', // TODO: why does firefox behave differently? transparently returns cached response }, function () { // use a queryparam to bust cache from previous runs of this test const url = `/fixtures/generic.html?t=${Date.now()}` let hits = 0 cy.intercept('/fixtures/generic.html', (req) => { req.reply((res) => { // the second time the request is sent, headers should have been passed // that result in Express serving a 304 // Cypress is not expected to understand cache mechanisms at this point - // if the user wants to break caching, they can DIY by editing headers const expectedStatusCode = [200, 304][hits] expect(expectedStatusCode).to.exist expect(res.statusCode).to.eq(expectedStatusCode) hits++ res.send() }) }) .as('foo') .then(() => _.times(2, () => fetch(url))) .wait('@foo') .wait('@foo') .then(() => { expect(hits).to.eq(2) }) }) it('can intercept a large proxy response', function (done) { cy.intercept('/1mb', (req) => { req.reply((res) => { res.send() }) }).then(() => { $.get('/1mb').done((responseText) => { // NOTE: the log from this when it fails is so long that it causes the browser to lock up :[ expect(responseText).to.eq('X'.repeat(1024 * 1024)) done() }) }) }) it('can delay a proxy response using res.delay', function (done) { cy.intercept('/timeout', (req) => { req.reply((res) => { this.start = Date.now() res.delay(1000).send('delay worked') }) }).then(() => { $.get('/timeout') .done((responseText) => { expect(Date.now() - this.start).to.be.closeTo(1100, 100) expect(responseText).to.include('delay worked') done() }) }) }) it('can \'delay\' a proxy response using Promise.delay', function (done) { cy.intercept('/timeout', (req) => { req.reply((res) => { this.start = Date.now() return Promise.delay(1000) .then(() => { res.send('Promise.delay worked') }) }) }).then(() => { $.get('/timeout').then((responseText) => { expect(Date.now() - this.start).to.be.closeTo(1000, 100) expect(responseText).to.eq('Promise.delay worked') done() }) }) }) it('can throttle a proxy response using res.throttle', function (done) { cy.intercept('/1mb', (req) => { // don't let gzip make response smaller and throw off the timing delete req.headers['accept-encoding'] req.reply((res) => { this.start = Date.now() res.throttle(1024).send() }) }).then(() => { $.get('/1mb').done((responseText) => { // 1MB @ 1MB/s = ~1 second expect(Date.now() - this.start).to.be.closeTo(1000, 250) expect(responseText).to.eq('X'.repeat(1024 * 1024)) done() }) }) }) it('can throttle a static response using res.throttle', function (done) { const payload = 'A'.repeat(10 * 1024) const kbps = 10 const expectedSeconds = payload.length / (1024 * kbps) cy.intercept('/timeout', (req) => { req.reply((res) => { this.start = Date.now() res.throttle(kbps).send(payload) }) }).then(() => { $.get('/timeout').done((responseText) => { expect(Date.now() - this.start).to.be.closeTo(expectedSeconds * 1000, 250) expect(responseText).to.eq(payload) done() }) }) }) it('can delay and throttle a static response', function (done) { const payload = 'A'.repeat(10 * 1024) const kbps = 20 let expectedSeconds = payload.length / (1024 * kbps) const delay = 500 expectedSeconds += delay / 1000 cy.intercept('/timeout', (req) => { req.reply((res) => { this.start = Date.now() res.throttle(kbps).delay(delay).send({ statusCode: 200, body: payload, }) }) }).then(() => { $.get('/timeout').done((responseText) => { expect(Date.now() - this.start).to.be.closeTo(expectedSeconds * 1000, 100) expect(responseText).to.eq(payload) done() }) }) }) it('can reply with a JSON fixture', function () { cy.intercept({ method: 'POST', url: '/test-xhr', }, (req) => { req.url = '/timeout' req.method = 'GET' req.reply((res) => {