mirror of
https://github.com/cypress-io/cypress.git
synced 2026-05-06 23:10:22 -05:00
feat(net-stubbing): allow waiting on network errors/forceNetworkError (#9603)
This commit is contained in:
@@ -1839,13 +1839,7 @@ describe('network stubbing', { retries: 2 }, function () {
|
||||
it('doesn\'t fail test if network error occurs retrieving response and response is not intercepted', {
|
||||
// TODO: for some reason, this test is busted in FF
|
||||
browser: '!firefox',
|
||||
}, function (done) {
|
||||
cy.on('fail', (err) => {
|
||||
// the test should have failed due to cy.wait, as opposed to because of a network error
|
||||
expect(err.message).to.contain('Timed out retrying')
|
||||
done()
|
||||
})
|
||||
|
||||
}, function () {
|
||||
cy.intercept('/should-err', function (req) {
|
||||
req.reply()
|
||||
})
|
||||
@@ -2149,6 +2143,21 @@ describe('network stubbing', { retries: 2 }, function () {
|
||||
})
|
||||
})
|
||||
|
||||
// @see https://github.com/cypress-io/cypress/issues/9062
|
||||
it('can spy on a request using forceNetworkError', function () {
|
||||
cy.intercept('/foo', { forceNetworkError: true })
|
||||
.as('err')
|
||||
.then(() => {
|
||||
$.get('/foo')
|
||||
})
|
||||
.wait('@err').should('have.property', 'error')
|
||||
.and('include', {
|
||||
message: 'forceNetworkError called',
|
||||
name: 'Error',
|
||||
})
|
||||
.get('@err').should('not.have.property', 'response')
|
||||
})
|
||||
|
||||
context('with an intercepted request', function () {
|
||||
it('can dynamically alias the request', function () {
|
||||
cy.intercept('/foo', (req) => {
|
||||
|
||||
@@ -11,20 +11,28 @@ export const onRequestComplete: HandlerFn<NetEventFrames.HttpRequestComplete> =
|
||||
}
|
||||
|
||||
if (frame.error) {
|
||||
let err = makeErrFromObj(frame.error)
|
||||
// does this request have a responseHandler that has not run yet?
|
||||
const isAwaitingResponse = !!request.responseHandler && ['Received', 'Intercepted'].includes(request.state)
|
||||
const isTimeoutError = frame.error.code && ['ESOCKETTIMEDOUT', 'ETIMEDOUT'].includes(frame.error.code)
|
||||
const errorName = isTimeoutError ? 'timeout' : 'network_error'
|
||||
|
||||
const err = errByPath(`net_stubbing.request_error.${errorName}`, {
|
||||
innerErr: makeErrFromObj(frame.error),
|
||||
req: request.request,
|
||||
route: get(getRoute(frame.routeHandlerId), 'options'),
|
||||
})
|
||||
if (isAwaitingResponse || isTimeoutError) {
|
||||
const errorName = isTimeoutError ? 'timeout' : 'network_error'
|
||||
|
||||
err = errByPath(`net_stubbing.request_error.${errorName}`, {
|
||||
innerErr: err,
|
||||
req: request.request,
|
||||
route: get(getRoute(frame.routeHandlerId), 'options'),
|
||||
})
|
||||
}
|
||||
|
||||
request.state = 'Errored'
|
||||
request.error = err
|
||||
|
||||
if (request.responseHandler) {
|
||||
// if req.reply was used to register a response handler, the user is implicitly
|
||||
// expecting there to be a successful response from the server, so fail the test
|
||||
request.log.error(err)
|
||||
|
||||
if (isAwaitingResponse) {
|
||||
// the user is implicitly expecting there to be a successful response from the server, so fail the test
|
||||
// since a network error has occured
|
||||
return failCurrentTest(err)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
} from './types'
|
||||
import { getAliasedRequests } from './aliasing'
|
||||
|
||||
const RESPONSE_WAITED_STATES: InterceptionState[] = ['ResponseIntercepted', 'Complete']
|
||||
const RESPONSE_WAITED_STATES: InterceptionState[] = ['ResponseIntercepted', 'Complete', 'Errored']
|
||||
|
||||
function getPredicateForSpecifier (specifier: string): Partial<Interception> {
|
||||
if (specifier === 'request') {
|
||||
|
||||
@@ -192,6 +192,10 @@ export interface Interception {
|
||||
response?: CyHttpMessages.IncomingResponse
|
||||
/* @internal */
|
||||
responseHandler?: HttpResponseInterceptor
|
||||
/**
|
||||
* The error that occurred during this request.
|
||||
*/
|
||||
error?: Error
|
||||
/**
|
||||
* Was `cy.wait()` used to wait on the response to this request?
|
||||
* @internal
|
||||
|
||||
@@ -51,6 +51,7 @@ export const InterceptRequest: RequestMiddleware = function () {
|
||||
requestId,
|
||||
route,
|
||||
continueRequest: this.next,
|
||||
onError: this.onError,
|
||||
onResponse: (incomingRes, resStream) => {
|
||||
setDefaultHeaders(this.req, incomingRes)
|
||||
this.onResponse(incomingRes, resStream)
|
||||
@@ -109,7 +110,7 @@ function _interceptRequest (state: NetStubbingState, request: BackendRequest, ro
|
||||
|
||||
return ensureBody(() => {
|
||||
emitReceived()
|
||||
sendStaticResponse(request.res, staticResponse, request.onResponse!)
|
||||
sendStaticResponse(request, staticResponse)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -183,7 +184,7 @@ export async function onRequestContinue (state: NetStubbingState, frame: NetEven
|
||||
if (frame.staticResponse) {
|
||||
await setResponseFromFixture(backendRequest.route.getFixture, frame.staticResponse)
|
||||
|
||||
return sendStaticResponse(backendRequest.res, frame.staticResponse, backendRequest.onResponse!)
|
||||
return sendStaticResponse(backendRequest, frame.staticResponse)
|
||||
}
|
||||
|
||||
backendRequest.continueRequest()
|
||||
|
||||
@@ -111,7 +111,7 @@ export async function onResponseContinue (state: NetStubbingState, frame: NetEve
|
||||
|
||||
const staticResponse = _.chain(frame.staticResponse).clone().assign({ continueResponseAt, throttleKbps }).value()
|
||||
|
||||
return sendStaticResponse(res, staticResponse, backendRequest.onResponse!)
|
||||
return sendStaticResponse(backendRequest, staticResponse)
|
||||
}
|
||||
|
||||
// merge the changed response attributes with our response and continue
|
||||
|
||||
@@ -25,6 +25,7 @@ export interface BackendRequest {
|
||||
* The route that matched this request.
|
||||
*/
|
||||
route: BackendRoute
|
||||
onError: (err: Error) => void
|
||||
/**
|
||||
* A callback that can be used to make the request go outbound.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import _ from 'lodash'
|
||||
import Debug from 'debug'
|
||||
import isHtml from 'is-html'
|
||||
import { ServerResponse, IncomingMessage } from 'http'
|
||||
import { IncomingMessage } from 'http'
|
||||
import {
|
||||
RouteMatcherOptionsGeneric,
|
||||
STRING_MATCHER_FIELDS,
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { Readable, PassThrough } from 'stream'
|
||||
import CyServer from '@packages/server'
|
||||
import { Socket } from 'net'
|
||||
import { GetFixtureFn } from './types'
|
||||
import { GetFixtureFn, BackendRequest } from './types'
|
||||
import ThrottleStream from 'throttle'
|
||||
import MimeTypes from 'mime-types'
|
||||
|
||||
@@ -142,17 +142,17 @@ export async function setResponseFromFixture (getFixtureFn: GetFixtureFn, static
|
||||
|
||||
/**
|
||||
* Using an existing response object, send a response shaped by a StaticResponse object.
|
||||
* @param res Response object.
|
||||
* @param backendRequest BackendRequest object.
|
||||
* @param staticResponse BackendStaticResponse object.
|
||||
* @param onResponse Will be called with the response metadata + body stream
|
||||
* @param resStream Optionally, provide a Readable stream to be used as the response body (overrides staticResponse.body)
|
||||
*/
|
||||
export function sendStaticResponse (res: ServerResponse, staticResponse: BackendStaticResponse, onResponse: (incomingRes: IncomingMessage, stream: Readable) => void) {
|
||||
if (staticResponse.forceNetworkError) {
|
||||
res.connection.destroy()
|
||||
res.destroy()
|
||||
export function sendStaticResponse (backendRequest: BackendRequest, staticResponse: BackendStaticResponse) {
|
||||
const { onError, onResponse } = backendRequest
|
||||
|
||||
return
|
||||
if (staticResponse.forceNetworkError) {
|
||||
debug('forcing network error')
|
||||
const err = new Error('forceNetworkError called')
|
||||
|
||||
return onError(err)
|
||||
}
|
||||
|
||||
const statusCode = staticResponse.statusCode || 200
|
||||
@@ -167,7 +167,7 @@ export function sendStaticResponse (res: ServerResponse, staticResponse: Backend
|
||||
|
||||
const bodyStream = getBodyStream(body, _.pick(staticResponse, 'throttleKbps', 'continueResponseAt'))
|
||||
|
||||
onResponse(incomingRes, bodyStream)
|
||||
onResponse!(incomingRes, bodyStream)
|
||||
}
|
||||
|
||||
export function getBodyStream (body: Buffer | string | Readable | undefined, options: { continueResponseAt?: number, throttleKbps?: number }): Readable {
|
||||
|
||||
Reference in New Issue
Block a user