feat(net-stubbing): allow waiting on network errors/forceNetworkError (#9603)

This commit is contained in:
Zach Bloomquist
2020-12-10 12:28:24 -05:00
committed by GitHub
parent 4eb2ce5689
commit 2a66d2cbd0
8 changed files with 54 additions and 31 deletions
@@ -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.
*/
+11 -11
View File
@@ -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 {