From 6221c62ca00ee4fce880255435f0ae9358fbbbf4 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Mon, 23 Jan 2023 14:00:14 -0500 Subject: [PATCH 01/11] begin setting log with the backend --- packages/net-stubbing/lib/external-types.ts | 14 ++++++++++++++ packages/net-stubbing/lib/internal-types.ts | 1 + .../net-stubbing/lib/server/intercepted-request.ts | 7 +++++-- packages/proxy/lib/http/request-middleware.ts | 4 +++- packages/proxy/lib/types.ts | 4 ++++ 5 files changed, 27 insertions(+), 3 deletions(-) diff --git a/packages/net-stubbing/lib/external-types.ts b/packages/net-stubbing/lib/external-types.ts index cf89b834ddc8..9e2a9cf1800f 100644 --- a/packages/net-stubbing/lib/external-types.ts +++ b/packages/net-stubbing/lib/external-types.ts @@ -161,6 +161,13 @@ export namespace CyHttpMessages { * request via `cy.wait('@alias')`. */ alias?: string + /** + * If set to `false`, this request will not be logged. + * NOTE: To prevent accidentally ignoring `cy.intercept()` logs, all intercepts set + * `req.log = true` by default. `req.log` must be `false` at the end of the handler chain + * to hide the log. + */ + log: boolean } export interface IncomingHttpRequest extends IncomingRequest, RequestEvents { @@ -441,6 +448,13 @@ export interface GenericStaticResponse { * Milliseconds to delay before the response is sent. */ delay?: number + /** + * If set to `false`, this request will not be logged. + * NOTE: To prevent accidentally ignoring `cy.intercept()` logs, all intercepts set + * `req.log = true` by default. `req.log` must be `false` at the end of the handler chain + * to hide the log. + */ + log: boolean } /** diff --git a/packages/net-stubbing/lib/internal-types.ts b/packages/net-stubbing/lib/internal-types.ts index 8c7200f440b7..999fa8d24283 100644 --- a/packages/net-stubbing/lib/internal-types.ts +++ b/packages/net-stubbing/lib/internal-types.ts @@ -25,6 +25,7 @@ export const SERIALIZABLE_REQ_PROPS = [ 'followRedirect', 'resourceType', 'query', + 'log', ] export const SERIALIZABLE_RES_PROPS = _.concat( diff --git a/packages/net-stubbing/lib/server/intercepted-request.ts b/packages/net-stubbing/lib/server/intercepted-request.ts index cb79662d52b1..ce333a365fbc 100644 --- a/packages/net-stubbing/lib/server/intercepted-request.ts +++ b/packages/net-stubbing/lib/server/intercepted-request.ts @@ -153,11 +153,14 @@ export class InterceptedRequest { data, } - // https://github.com/cypress-io/cypress/issues/17139 - // Routes should be counted before they're sent. if (eventName === 'before:request') { const route = this.matchingRoutes.find(({ id }) => id === subscription.routeId) as BackendRoute + // To prevent users from accidentally setting `log: false` via fallthru, reset `req.log` on each interception. + this.req.log = !!(route.staticResponse?.log || this.req.log) + + // https://github.com/cypress-io/cypress/issues/17139 + // Routes should be counted before they're sent. route.matches++ if (route.routeMatcher.times && route.matches >= route.routeMatcher.times) { diff --git a/packages/proxy/lib/http/request-middleware.ts b/packages/proxy/lib/http/request-middleware.ts index ecd1c20a6dbc..364e7a00ce10 100644 --- a/packages/proxy/lib/http/request-middleware.ts +++ b/packages/proxy/lib/http/request-middleware.ts @@ -148,7 +148,9 @@ const CorrelateBrowserPreRequest: RequestMiddleware = async function () { } const SendToDriver: RequestMiddleware = function () { - const { browserPreRequest } = this.req + const { browserPreRequest, resourceType } = this.req + + this.req.log = (resourceType === 'fetch' || resourceType === 'xhr') if (browserPreRequest) { this.socket.toDriver('request:event', 'incoming:request', browserPreRequest) diff --git a/packages/proxy/lib/types.ts b/packages/proxy/lib/types.ts index 87508941372d..5fcb231e34aa 100644 --- a/packages/proxy/lib/types.ts +++ b/packages/proxy/lib/types.ts @@ -9,6 +9,10 @@ export type CypressIncomingRequest = Request & { proxiedUrl: string abort: () => void requestId: string + /** + * Should this request be logged in the Command Log? + */ + log?: boolean browserPreRequest?: BrowserPreRequest body?: string responseTimeout?: number From 0a2409af8efd569251eaa6cd3726734708c6b3b4 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Mon, 23 Jan 2023 14:07:37 -0500 Subject: [PATCH 02/11] revert backend changes --- packages/net-stubbing/lib/internal-types.ts | 1 - packages/net-stubbing/lib/server/intercepted-request.ts | 7 ++----- packages/proxy/lib/http/request-middleware.ts | 4 +--- packages/proxy/lib/types.ts | 4 ---- 4 files changed, 3 insertions(+), 13 deletions(-) diff --git a/packages/net-stubbing/lib/internal-types.ts b/packages/net-stubbing/lib/internal-types.ts index 999fa8d24283..8c7200f440b7 100644 --- a/packages/net-stubbing/lib/internal-types.ts +++ b/packages/net-stubbing/lib/internal-types.ts @@ -25,7 +25,6 @@ export const SERIALIZABLE_REQ_PROPS = [ 'followRedirect', 'resourceType', 'query', - 'log', ] export const SERIALIZABLE_RES_PROPS = _.concat( diff --git a/packages/net-stubbing/lib/server/intercepted-request.ts b/packages/net-stubbing/lib/server/intercepted-request.ts index ce333a365fbc..cb79662d52b1 100644 --- a/packages/net-stubbing/lib/server/intercepted-request.ts +++ b/packages/net-stubbing/lib/server/intercepted-request.ts @@ -153,14 +153,11 @@ export class InterceptedRequest { data, } + // https://github.com/cypress-io/cypress/issues/17139 + // Routes should be counted before they're sent. if (eventName === 'before:request') { const route = this.matchingRoutes.find(({ id }) => id === subscription.routeId) as BackendRoute - // To prevent users from accidentally setting `log: false` via fallthru, reset `req.log` on each interception. - this.req.log = !!(route.staticResponse?.log || this.req.log) - - // https://github.com/cypress-io/cypress/issues/17139 - // Routes should be counted before they're sent. route.matches++ if (route.routeMatcher.times && route.matches >= route.routeMatcher.times) { diff --git a/packages/proxy/lib/http/request-middleware.ts b/packages/proxy/lib/http/request-middleware.ts index 364e7a00ce10..ecd1c20a6dbc 100644 --- a/packages/proxy/lib/http/request-middleware.ts +++ b/packages/proxy/lib/http/request-middleware.ts @@ -148,9 +148,7 @@ const CorrelateBrowserPreRequest: RequestMiddleware = async function () { } const SendToDriver: RequestMiddleware = function () { - const { browserPreRequest, resourceType } = this.req - - this.req.log = (resourceType === 'fetch' || resourceType === 'xhr') + const { browserPreRequest } = this.req if (browserPreRequest) { this.socket.toDriver('request:event', 'incoming:request', browserPreRequest) diff --git a/packages/proxy/lib/types.ts b/packages/proxy/lib/types.ts index 5fcb231e34aa..87508941372d 100644 --- a/packages/proxy/lib/types.ts +++ b/packages/proxy/lib/types.ts @@ -9,10 +9,6 @@ export type CypressIncomingRequest = Request & { proxiedUrl: string abort: () => void requestId: string - /** - * Should this request be logged in the Command Log? - */ - log?: boolean browserPreRequest?: BrowserPreRequest body?: string responseTimeout?: number From 89f79933fe14bccc97f1c44cfd0a28c8d291aaf0 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Mon, 13 Feb 2023 12:35:49 -0500 Subject: [PATCH 03/11] update interface now that we are only doing static log --- .../driver/src/cy/net-stubbing/add-command.ts | 9 ++++--- .../cy/net-stubbing/static-response-utils.ts | 13 ++++++++-- packages/net-stubbing/lib/external-types.ts | 26 ++++++++----------- packages/net-stubbing/lib/internal-types.ts | 3 ++- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/packages/driver/src/cy/net-stubbing/add-command.ts b/packages/driver/src/cy/net-stubbing/add-command.ts index 40c32e1732d1..3237753c399d 100644 --- a/packages/driver/src/cy/net-stubbing/add-command.ts +++ b/packages/driver/src/cy/net-stubbing/add-command.ts @@ -15,11 +15,12 @@ import { StringMatcher, NumberMatcher, BackendStaticResponseWithArrayBuffer, + StaticResponseWithOptions, } from '@packages/net-stubbing/lib/types' import { validateStaticResponse, getBackendStaticResponse, - hasStaticResponseKeys, + hasStaticResponseWithOptionsKeys, } from './static-response-utils' import { getRouteMatcherLogConfig, @@ -187,7 +188,7 @@ export function addCommand (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, } else if (_.isString(handler)) { staticResponse = { body: handler } } else if (_.isObjectLike(handler)) { - if (!hasStaticResponseKeys(handler)) { + if (!hasStaticResponseWithOptionsKeys(handler)) { // the user has not supplied any of the StaticResponse keys, assume it's a JSON object // that should become the body property handler = { @@ -195,9 +196,9 @@ export function addCommand (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, } } - validateStaticResponse('cy.intercept', handler) + validateStaticResponse('cy.intercept', handler) - staticResponse = handler as StaticResponse + staticResponse = handler as StaticResponseWithOptions } else if (!_.isUndefined(handler)) { // a handler was passed but we dunno what it's supposed to be $errUtils.throwErrByPath('net_stubbing.intercept.invalid_handler', { args: { handler } }) diff --git a/packages/driver/src/cy/net-stubbing/static-response-utils.ts b/packages/driver/src/cy/net-stubbing/static-response-utils.ts index 6f0d2cdae7a4..564c823a683d 100644 --- a/packages/driver/src/cy/net-stubbing/static-response-utils.ts +++ b/packages/driver/src/cy/net-stubbing/static-response-utils.ts @@ -2,6 +2,7 @@ import _ from 'lodash' import type { StaticResponse, + StaticResponseWithOptions, BackendStaticResponseWithArrayBuffer, FixtureOpts, } from '@packages/net-stubbing/lib/types' @@ -13,6 +14,8 @@ import $errUtils from '../../cypress/error_utils' // user-facing StaticResponse only export const STATIC_RESPONSE_KEYS: (keyof StaticResponse)[] = ['body', 'fixture', 'statusCode', 'headers', 'forceNetworkError', 'throttleKbps', 'delay', 'delayMs'] +export const STATIC_RESPONSE_WITH_OPTIONS_KEYS: (keyof StaticResponseWithOptions)[] = [...STATIC_RESPONSE_KEYS, 'log'] + export function validateStaticResponse (cmd: string, staticResponse: StaticResponse): void { const err = (message) => { $errUtils.throwErrByPath('net_stubbing.invalid_static_response', { args: { cmd, message, staticResponse } }) @@ -102,8 +105,8 @@ function getFixtureOpts (fixture: string): FixtureOpts { return { filePath, encoding: encoding === 'null' ? null : encoding } } -export function getBackendStaticResponse (staticResponse: Readonly): BackendStaticResponseWithArrayBuffer { - const backendStaticResponse: BackendStaticResponseWithArrayBuffer = _.omit(staticResponse, 'body', 'fixture', 'delayMs') +export function getBackendStaticResponse (staticResponse: Readonly): BackendStaticResponseWithArrayBuffer { + const backendStaticResponse: BackendStaticResponseWithArrayBuffer = _.omit(staticResponse, 'body', 'fixture', 'delayMs', 'log') if (staticResponse.delayMs) { // support deprecated `delayMs` usage @@ -132,9 +135,15 @@ export function getBackendStaticResponse (staticResponse: Readonly { export type RouteHandlerController = HttpRequestInterceptor -export type RouteHandler = string | StaticResponse | RouteHandlerController | object +export type RouteHandler = string | StaticResponseWithOptions | RouteHandlerController | object + +export type InterceptOptions = { + /** + * If set to `false`, matching requests will not be shown in the Command Log. + * @default true + */ + log?: boolean +} + +export type StaticResponseWithOptions = StaticResponse & InterceptOptions /** * Describes a response that will be sent back to the browser to fulfill the request. @@ -448,13 +451,6 @@ export interface GenericStaticResponse { * Milliseconds to delay before the response is sent. */ delay?: number - /** - * If set to `false`, this request will not be logged. - * NOTE: To prevent accidentally ignoring `cy.intercept()` logs, all intercepts set - * `req.log = true` by default. `req.log` must be `false` at the end of the handler chain - * to hide the log. - */ - log: boolean } /** diff --git a/packages/net-stubbing/lib/internal-types.ts b/packages/net-stubbing/lib/internal-types.ts index 8c7200f440b7..5a9e64230e59 100644 --- a/packages/net-stubbing/lib/internal-types.ts +++ b/packages/net-stubbing/lib/internal-types.ts @@ -4,6 +4,7 @@ import type { GenericStaticResponse, Subscription, CyHttpMessages, + InterceptOptions, } from './external-types' export type FixtureOpts = { @@ -13,7 +14,7 @@ export type FixtureOpts = { export type BackendStaticResponse = GenericStaticResponse -export type BackendStaticResponseWithArrayBuffer = GenericStaticResponse +export type BackendStaticResponseWithArrayBuffer = GenericStaticResponse & InterceptOptions export const SERIALIZABLE_REQ_PROPS = [ 'headers', From e834ce6567e5bca576763e79375516f1900327d9 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Mon, 13 Feb 2023 13:04:27 -0500 Subject: [PATCH 04/11] change existing logging logic to run in proxy layer instead --- packages/driver/src/cypress/proxy-logging.ts | 43 ++----------------- packages/net-stubbing/lib/internal-types.ts | 2 +- packages/net-stubbing/lib/server/index.ts | 2 +- .../lib/server/intercepted-request.ts | 14 +++--- .../lib/server/middleware/request.ts | 30 +++++++------ packages/proxy/lib/http/request-middleware.ts | 29 ++++++++++--- packages/proxy/lib/types.ts | 5 +++ 7 files changed, 61 insertions(+), 64 deletions(-) diff --git a/packages/driver/src/cypress/proxy-logging.ts b/packages/driver/src/cypress/proxy-logging.ts index 82f83fa09bba..3897788bbc47 100644 --- a/packages/driver/src/cypress/proxy-logging.ts +++ b/packages/driver/src/cypress/proxy-logging.ts @@ -6,23 +6,6 @@ import Debug from 'debug' const debug = Debug('cypress:driver:proxy-logging') -/** - * Remove and return the first element from `array` for which `filterFn` returns a truthy value. - */ -function take (array: E[], filterFn: (data: E) => boolean) { - for (const i in array) { - const e = array[i] - - if (!filterFn(e)) continue - - array.splice(i as unknown as number, 1) - - return e - } - - return -} - function formatInterception ({ route, interception }: ProxyRequest['interceptions'][number]) { const ret = { 'RouteMatcher': route.options, @@ -128,10 +111,6 @@ function getRequestLogConfig (req: Omit): Partial = [] proxyRequests: Array = [] constructor (private Cypress: Cypress.Cypress) { @@ -254,7 +232,6 @@ export default class ProxyLogging { proxyRequest.log.end() } } - this.unloggedPreRequests = [] this.proxyRequests = [] }) } @@ -263,15 +240,9 @@ export default class ProxyLogging { * Update an existing proxy log with an interception, or create a new log if one was not created (like if shouldLog returned false) */ logInterception (interception: Interception, route: Route): ProxyRequest { - const unloggedPreRequest = take(this.unloggedPreRequests, ({ requestId }) => requestId === interception.browserRequestId) - - if (unloggedPreRequest) { - debug('interception matched an unlogged prerequest, logging %o', { unloggedPreRequest, interception }) - this.createProxyRequestLog(unloggedPreRequest) - } - let proxyRequest = _.find(this.proxyRequests, ({ preRequest }) => preRequest.requestId === interception.browserRequestId) + // TODO: we prob don't need to do this anymore? if (!proxyRequest) { // this can happen in a race condition, if user runs Network.disable, if the browser doesn't send pre-request for some reason... debug(`Missing pre-request/proxy log for cy.intercept to ${interception.request.url} %o`, { interception, route }) @@ -326,16 +297,10 @@ export default class ProxyLogging { } /** - * Create a Cypress.Log for an incoming proxy request, or store the metadata for later if it is ignored. + * Create a Cypress.Log for an incoming proxy request. */ - private logIncomingRequest (preRequest: BrowserPreRequest): void { - if (!shouldLog(preRequest)) { - this.unloggedPreRequests.push(preRequest) - - return - } - - this.createProxyRequestLog(preRequest) + private logIncomingRequest (browserPreRequest: BrowserPreRequest): void { + this.createProxyRequestLog(browserPreRequest) } private createProxyRequestLog (preRequest: BrowserPreRequest): ProxyRequest { diff --git a/packages/net-stubbing/lib/internal-types.ts b/packages/net-stubbing/lib/internal-types.ts index 5a9e64230e59..c31d6b98aa96 100644 --- a/packages/net-stubbing/lib/internal-types.ts +++ b/packages/net-stubbing/lib/internal-types.ts @@ -12,7 +12,7 @@ export type FixtureOpts = { filePath: string } -export type BackendStaticResponse = GenericStaticResponse +export type BackendStaticResponse = GenericStaticResponse & InterceptOptions export type BackendStaticResponseWithArrayBuffer = GenericStaticResponse & InterceptOptions diff --git a/packages/net-stubbing/lib/server/index.ts b/packages/net-stubbing/lib/server/index.ts index 0754c21ebc42..5a022b72f78f 100644 --- a/packages/net-stubbing/lib/server/index.ts +++ b/packages/net-stubbing/lib/server/index.ts @@ -2,7 +2,7 @@ export { onNetStubbingEvent } from './driver-events' export { InterceptError } from './middleware/error' -export { InterceptRequest } from './middleware/request' +export { SetMatchingRoutes, InterceptRequest } from './middleware/request' export { InterceptResponse } from './middleware/response' diff --git a/packages/net-stubbing/lib/server/intercepted-request.ts b/packages/net-stubbing/lib/server/intercepted-request.ts index cb79662d52b1..c9aa250a1c81 100644 --- a/packages/net-stubbing/lib/server/intercepted-request.ts +++ b/packages/net-stubbing/lib/server/intercepted-request.ts @@ -39,18 +39,16 @@ export class InterceptedRequest { continueResponse?: (newResStream?: Readable) => void req: CypressIncomingRequest res: CypressOutgoingResponse - matchingRoutes: BackendRoute[] state: NetStubbingState socket: CyServer.Socket - constructor (opts: Pick) { + constructor (opts: Pick) { this.id = _.uniqueId('interceptedRequest') this.req = opts.req this.res = opts.res this.continueRequest = opts.continueRequest this.onError = opts.onError this._onResponse = opts.onResponse - this.matchingRoutes = opts.matchingRoutes this.state = opts.state this.socket = opts.socket @@ -72,7 +70,11 @@ export class InterceptedRequest { throw new Error('cannot add default subscriptions to non-empty array') } - for (const route of this.matchingRoutes) { + if (!this.req.matchingRoutes) { + return + } + + for (const route of this.req.matchingRoutes) { const subscriptionsByRoute = { routeId: route.id, immediateStaticResponse: route.staticResponse, @@ -156,7 +158,9 @@ export class InterceptedRequest { // https://github.com/cypress-io/cypress/issues/17139 // Routes should be counted before they're sent. if (eventName === 'before:request') { - const route = this.matchingRoutes.find(({ id }) => id === subscription.routeId) as BackendRoute + const route = this.req.matchingRoutes?.find(({ id }) => id === subscription.routeId) as BackendRoute + + if (!route) throw new Error(`No route by ID ${subscription.routeId} for ${eventName}`) route.matches++ diff --git a/packages/net-stubbing/lib/server/middleware/request.ts b/packages/net-stubbing/lib/server/middleware/request.ts index bf334bed013c..a7012496db97 100644 --- a/packages/net-stubbing/lib/server/middleware/request.ts +++ b/packages/net-stubbing/lib/server/middleware/request.ts @@ -1,6 +1,5 @@ import _ from 'lodash' import { concatStream } from '@packages/network' -import Debug from 'debug' import url from 'url' import type { @@ -19,14 +18,13 @@ import { getBodyEncoding, } from '../util' import { InterceptedRequest } from '../intercepted-request' -import type { BackendRoute } from '../types' -const debug = Debug('cypress:net-stubbing:server:intercept-request') +// do not use a debug namespace in this file - use the per-request `this.debug` instead +// available as cypress-verbose:proxy:http +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const debug = null -/** - * Called when a new request is received in the proxy layer. - */ -export const InterceptRequest: RequestMiddleware = async function () { +export const SetMatchingRoutes: RequestMiddleware = async function () { if (matchesRoutePreflight(this.netStubbingState.routes, this.req)) { // send positive CORS preflight response return sendStaticResponse(this, { @@ -41,9 +39,16 @@ export const InterceptRequest: RequestMiddleware = async function () { }) } - const matchingRoutes: BackendRoute[] = [...getRoutesForRequest(this.netStubbingState.routes, this.req)] + this.req.matchingRoutes = [...getRoutesForRequest(this.netStubbingState.routes, this.req)] + + this.next() +} - if (!matchingRoutes.length) { +/** + * Called when a new request is received in the proxy layer. + */ +export const InterceptRequest: RequestMiddleware = async function () { + if (!this.req.matchingRoutes?.length) { // not intercepted, carry on normally... return this.next() } @@ -59,10 +64,9 @@ export const InterceptRequest: RequestMiddleware = async function () { res: this.res, socket: this.socket, state: this.netStubbingState, - matchingRoutes, }) - debug('intercepting request %o', { requestId: request.id, req: _.pick(this.req, 'url') }) + this.debug('cy.intercept: intercepting request') // attach requestId to the original req object for later use this.req.requestId = request.id @@ -82,7 +86,7 @@ export const InterceptRequest: RequestMiddleware = async function () { mergeChanges: _.noop, }) - debug('request/response finished, cleaning up %o', { requestId: request.id }) + this.debug('cy.intercept: request/response finished, cleaning up') delete this.netStubbingState.requests[request.id] }) @@ -124,7 +128,7 @@ export const InterceptRequest: RequestMiddleware = async function () { const bodyIsBinary = bodyEncoding === 'binary' if (bodyIsBinary) { - debug('req.body contained non-utf8 characters, treating as binary content %o', { requestId: request.id, req: _.pick(this.req, 'url') }) + this.debug('cy.intercept: req.body contained non-utf8 characters, treating as binary content') } // leave the requests that send a binary buffer unchanged diff --git a/packages/proxy/lib/http/request-middleware.ts b/packages/proxy/lib/http/request-middleware.ts index ecd1c20a6dbc..e751a2b5f4e7 100644 --- a/packages/proxy/lib/http/request-middleware.ts +++ b/packages/proxy/lib/http/request-middleware.ts @@ -1,9 +1,10 @@ import _ from 'lodash' import { blocked, cors } from '@packages/network' -import { InterceptRequest } from '@packages/net-stubbing' +import { InterceptRequest, SetMatchingRoutes } from '@packages/net-stubbing' import type { HttpMiddleware } from './' import { getSameSiteContext, addCookieJarCookiesToRequest, shouldAttachAndSetCookies } from './util/cookies' import { doesTopNeedToBeSimulated } from './util/top-simulation' +import type { CypressIncomingRequest } from '../types' // do not use a debug namespace in this file - use the per-request `this.debug` instead // available as cypress-verbose:proxy:http @@ -147,11 +148,28 @@ const CorrelateBrowserPreRequest: RequestMiddleware = async function () { })) } -const SendToDriver: RequestMiddleware = function () { - const { browserPreRequest } = this.req +function shouldLog (req: CypressIncomingRequest) { + // 1. Any matching `cy.intercept()` should cause `req` to be logged by default, unless `log: false` is passed explicitly. + if (req.matchingRoutes?.length) { + const lastMatchingRoute = req.matchingRoutes[req.matchingRoutes.length - 1] + + if (!lastMatchingRoute.staticResponse) { + // No StaticResponse is set, therefore the request must be logged. + return true + } - if (browserPreRequest) { - this.socket.toDriver('request:event', 'incoming:request', browserPreRequest) + if (lastMatchingRoute.staticResponse.log !== undefined) { + return Boolean(lastMatchingRoute.staticResponse.log) + } + } + + // 2. Otherwise, only log if it is an XHR or fetch. + return req.resourceType === 'fetch' || req.resourceType === 'xhr' +} + +const SendToDriver: RequestMiddleware = function () { + if (shouldLog(this.req) && this.req.browserPreRequest) { + this.socket.toDriver('request:event', 'incoming:request', this.req.browserPreRequest) } this.next() @@ -291,6 +309,7 @@ export default { MaybeSimulateSecHeaders, MaybeAttachCrossOriginCookies, MaybeEndRequestWithBufferedResponse, + SetMatchingRoutes, CorrelateBrowserPreRequest, SendToDriver, InterceptRequest, diff --git a/packages/proxy/lib/types.ts b/packages/proxy/lib/types.ts index 87508941372d..b7383f45076d 100644 --- a/packages/proxy/lib/types.ts +++ b/packages/proxy/lib/types.ts @@ -1,6 +1,7 @@ import type { Readable } from 'stream' import type { Request, Response } from 'express' import type { ResourceType } from '@packages/net-stubbing' +import type { BackendRoute } from '@packages/net-stubbing/lib/server/types' /** * An incoming request to the Cypress web server. @@ -20,6 +21,10 @@ export type CypressIncomingRequest = Request & { * Resource type from browserPreRequest. Copied to req so intercept matching can work. */ resourceType?: ResourceType + /** + * Ordered list of `cy.intercept()`s matching this request. + */ + matchingRoutes?: BackendRoute[] } export type RequestedWithHeader = 'fetch' | 'xhr' | 'true' From a72fc7dfd51253e343772e20a10774965aa45138 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Mon, 13 Feb 2023 14:04:45 -0500 Subject: [PATCH 05/11] add tests, fix small bugs run ci --- .../cypress/e2e/cypress/proxy-logging.cy.ts | 91 ++++++++++++++----- .../cy/net-stubbing/events/before-request.ts | 2 +- .../cy/net-stubbing/static-response-utils.ts | 4 +- packages/driver/src/cypress/proxy-logging.ts | 16 +--- .../lib/server/intercepted-request.ts | 7 +- packages/proxy/lib/http/request-middleware.ts | 4 +- packages/proxy/lib/types.ts | 2 +- 7 files changed, 83 insertions(+), 43 deletions(-) diff --git a/packages/driver/cypress/e2e/cypress/proxy-logging.cy.ts b/packages/driver/cypress/e2e/cypress/proxy-logging.cy.ts index 04c18c4a8e1a..66ed1bf210ce 100644 --- a/packages/driver/cypress/e2e/cypress/proxy-logging.cy.ts +++ b/packages/driver/cypress/e2e/cypress/proxy-logging.cy.ts @@ -163,7 +163,6 @@ describe('Proxy Logging', () => { .visit('/fixtures/empty.html') }) - // TODO: fix flaky test https://github.com/cypress-io/cypress/issues/23420 it('intercept log has consoleProps with intercept info', (done) => { cy.intercept('/some-url', 'stubbed response').as('alias') .then(() => { @@ -363,35 +362,79 @@ describe('Proxy Logging', () => { }, )) }) - }) - }) - context('Cypress.ProxyLogging', () => { - describe('.logInterception', () => { - it('creates a fake log for unmatched requests', () => { - const interception = { - id: 'request123', - request: { - url: 'http://foo', - method: 'GET', - headers: {}, - }, - } + context('with log prop', () => { + it('can hide an intercepted request for an image', () => { + const logs: any[] = [] + + cy.intercept('**/cypress.png*', { log: false }).as('image') + .then(() => { + cy.on('log:added', (log) => { + if (log.name !== 'request') return + + logs.push(log) + }) + + const img = new Image() + + img.src = `/fixtures/media/cypress.png?${Date.now()}` + }) + .wait('@image') + .then(() => { + expect(logs).to.have.length(0) + }) + }) + + it('uses the final interceptor to determine if a log should be made', (done) => { + const logs: any[] = [] + + cy.on('log:added', (log) => { + if (log.name !== 'request') return + + logs.push(log) + }) + + cy.intercept('**/cypress.png?*', { log: true }).as('log-me') + .intercept('**/cypress.png?dont-log-me-*', { log: false }).as('dont-log-me') + .then(() => { + const img = new Image() + + img.src = `/fixtures/media/cypress.png?dont-log-me-${Date.now()}` + }) + .wait('@dont-log-me') + .then(() => { + expect(logs).to.have.length(0) - const route = {} + const img = new Image() - const ret = Cypress.ProxyLogging.logInterception(interception, route) + img.src = `/fixtures/media/cypress.png?log-me-${Date.now()}` - expect(ret.preRequest).to.deep.eq({ - requestId: 'request123', - resourceType: 'other', - originalResourceType: 'Request with no browser pre-request', - url: 'http://foo', - method: 'GET', - headers: {}, + cy.once('log:added', (log) => { + expect(log.name).to.eq('request') + expect(log.displayName).to.eq('image') + done() + }) + }) }) - expect(ret.log.get('name')).to.eq('request') + it('can disable fetch logs', () => { + const logs: any[] = [] + + cy.intercept({ resourceType: 'fetch' }, { log: false }).as('fetch') + .then(() => { + cy.on('log:added', (log) => { + if (log.name !== 'request') return + + logs.push(log) + }) + + return fetch(`/foo?${Date.now()}`) + }) + .wait('@fetch') + .then(() => { + expect(logs).to.have.length(0) + }) + }) }) }) }) diff --git a/packages/driver/src/cy/net-stubbing/events/before-request.ts b/packages/driver/src/cy/net-stubbing/events/before-request.ts index b01bc4afbc9d..fae78c6c3a80 100644 --- a/packages/driver/src/cy/net-stubbing/events/before-request.ts +++ b/packages/driver/src/cy/net-stubbing/events/before-request.ts @@ -302,7 +302,7 @@ export const onBeforeRequest: HandlerFn = (Cypre resolve = _resolve }) - request.setLogFlag = Cypress.ProxyLogging.logInterception(request, route).setFlag + request.setLogFlag = Cypress.ProxyLogging.logInterception(request, route)?.setFlag || (() => {}) // TODO: this misnomer is a holdover from XHR, should be numRequests route.log.set('numResponses', (route.log.get('numResponses') || 0) + 1) diff --git a/packages/driver/src/cy/net-stubbing/static-response-utils.ts b/packages/driver/src/cy/net-stubbing/static-response-utils.ts index 564c823a683d..12cd1eb0d777 100644 --- a/packages/driver/src/cy/net-stubbing/static-response-utils.ts +++ b/packages/driver/src/cy/net-stubbing/static-response-utils.ts @@ -135,7 +135,9 @@ export function getBackendStaticResponse (staticResponse: Readonly preRequest.requestId === interception.browserRequestId) + logInterception (interception: Interception, route: Route): ProxyRequest | undefined { + const proxyRequest = _.find(this.proxyRequests, ({ preRequest }) => preRequest.requestId === interception.browserRequestId) - // TODO: we prob don't need to do this anymore? if (!proxyRequest) { - // this can happen in a race condition, if user runs Network.disable, if the browser doesn't send pre-request for some reason... - debug(`Missing pre-request/proxy log for cy.intercept to ${interception.request.url} %o`, { interception, route }) - - proxyRequest = this.createProxyRequestLog({ - requestId: interception.browserRequestId || interception.id, - resourceType: 'other', - originalResourceType: 'Request with no browser pre-request', - ..._.pick(interception.request, ['url', 'method', 'headers']), - }) + // request was never logged + return undefined } proxyRequest.interceptions.push({ interception, route }) diff --git a/packages/net-stubbing/lib/server/intercepted-request.ts b/packages/net-stubbing/lib/server/intercepted-request.ts index c9aa250a1c81..98665939acb6 100644 --- a/packages/net-stubbing/lib/server/intercepted-request.ts +++ b/packages/net-stubbing/lib/server/intercepted-request.ts @@ -201,8 +201,11 @@ export class InterceptedRequest { } } - if (eventName === 'before:request') { - if (immediateStaticResponse) { + if (eventName === 'before:request' && immediateStaticResponse) { + // Since StaticResponse is conflated with InterceptOptions, only send an immediate response if there are keys other than `log`. + const hasOnlyLog = _.isEqual(Object.keys(immediateStaticResponse), ['log']) + + if (!hasOnlyLog) { await sendStaticResponse(this, immediateStaticResponse) return data diff --git a/packages/proxy/lib/http/request-middleware.ts b/packages/proxy/lib/http/request-middleware.ts index e751a2b5f4e7..573b1aad4124 100644 --- a/packages/proxy/lib/http/request-middleware.ts +++ b/packages/proxy/lib/http/request-middleware.ts @@ -151,7 +151,7 @@ const CorrelateBrowserPreRequest: RequestMiddleware = async function () { function shouldLog (req: CypressIncomingRequest) { // 1. Any matching `cy.intercept()` should cause `req` to be logged by default, unless `log: false` is passed explicitly. if (req.matchingRoutes?.length) { - const lastMatchingRoute = req.matchingRoutes[req.matchingRoutes.length - 1] + const lastMatchingRoute = req.matchingRoutes[0] if (!lastMatchingRoute.staticResponse) { // No StaticResponse is set, therefore the request must be logged. @@ -309,8 +309,8 @@ export default { MaybeSimulateSecHeaders, MaybeAttachCrossOriginCookies, MaybeEndRequestWithBufferedResponse, - SetMatchingRoutes, CorrelateBrowserPreRequest, + SetMatchingRoutes, SendToDriver, InterceptRequest, RedirectToClientRouteIfUnloaded, diff --git a/packages/proxy/lib/types.ts b/packages/proxy/lib/types.ts index b7383f45076d..8c0020ebb62f 100644 --- a/packages/proxy/lib/types.ts +++ b/packages/proxy/lib/types.ts @@ -22,7 +22,7 @@ export type CypressIncomingRequest = Request & { */ resourceType?: ResourceType /** - * Ordered list of `cy.intercept()`s matching this request. + * Stack-ordered list of `cy.intercept()`s matching this request. */ matchingRoutes?: BackendRoute[] } From ac738c20d4265695332ea3c9b5c9447aea9deac5 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Mon, 13 Feb 2023 14:37:21 -0500 Subject: [PATCH 06/11] fix tests --- .../test/unit/intercepted-request-spec.ts | 36 +++++++++---------- .../test/unit/http/request-middleware.spec.ts | 1 + 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/packages/net-stubbing/test/unit/intercepted-request-spec.ts b/packages/net-stubbing/test/unit/intercepted-request-spec.ts index ab062376ecab..952157598de6 100644 --- a/packages/net-stubbing/test/unit/intercepted-request-spec.ts +++ b/packages/net-stubbing/test/unit/intercepted-request-spec.ts @@ -6,30 +6,30 @@ import { state as NetStubbingState } from '../../lib/server/state' describe('InterceptedRequest', () => { context('handleSubscriptions', () => { - it('handles subscriptions as expected', async () => { + it.only('handles subscriptions as expected', async () => { const socket = { toDriver: sinon.stub(), } const state = NetStubbingState() const interceptedRequest = new InterceptedRequest({ - // @ts-ignore - req: {}, + req: { + matchingRoutes: [ + // @ts-ignore + { + id: '1', + hasInterceptor: true, + routeMatcher: {}, + }, + // @ts-ignore + { + id: '2', + hasInterceptor: true, + routeMatcher: {}, + }, + ], + }, state, - socket, - matchingRoutes: [ - // @ts-ignore - { - id: '1', - hasInterceptor: true, - routeMatcher: {}, - }, - // @ts-ignore - { - id: '2', - hasInterceptor: true, - routeMatcher: {}, - }, - ], + socket }) interceptedRequest.addSubscription({ diff --git a/packages/proxy/test/unit/http/request-middleware.spec.ts b/packages/proxy/test/unit/http/request-middleware.spec.ts index 58995e35c758..c18b96f22d5a 100644 --- a/packages/proxy/test/unit/http/request-middleware.spec.ts +++ b/packages/proxy/test/unit/http/request-middleware.spec.ts @@ -18,6 +18,7 @@ describe('http/request-middleware', () => { 'MaybeAttachCrossOriginCookies', 'MaybeEndRequestWithBufferedResponse', 'CorrelateBrowserPreRequest', + 'SetMatchingRoutes', 'SendToDriver', 'InterceptRequest', 'RedirectToClientRouteIfUnloaded', From 14966e473d1c3960a38ed49c335387cd7a0f09e7 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Mon, 13 Feb 2023 14:40:48 -0500 Subject: [PATCH 07/11] add changelog --- cli/CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 79fb86832929..53014e3c2116 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -1,4 +1,14 @@ + +## 12.7.0 + +_Released 02/28/2023 (PENDING)_ + +**Features:** + +- Added the ability to control whether a request is logged to the command log via `cy.intercept()` by passing `log: false` or `log: true`. Addresses [#7362](https://github.com/cypress-io/cypress/issues/7362). + - This can be used to override Cypress's default behavior of logging all XHRs and fetches, see the [example](https://docs.cypress.io/api/commands/intercept#controlling-log-behavior). + ## 12.6.0 _Released 02/14/2023 (PENDING)_ From 6d8c19398062cc8ba4100d9450ca9b03352d46c9 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Mon, 13 Feb 2023 14:40:56 -0500 Subject: [PATCH 08/11] run ci From be294a6b8f83251cde782d02cead2ecaee4c8433 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Mon, 13 Feb 2023 14:41:56 -0500 Subject: [PATCH 09/11] run ci From 47c787f5ae930fb0fa4b215a29330f5e362ed98b Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Mon, 13 Feb 2023 15:05:33 -0500 Subject: [PATCH 10/11] fix cl run ci --- cli/CHANGELOG.md | 1 - packages/net-stubbing/test/unit/intercepted-request-spec.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 86d06316053c..f12994b66541 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -1,5 +1,4 @@ - ## 12.7.0 _Released 02/28/2023 (PENDING)_ diff --git a/packages/net-stubbing/test/unit/intercepted-request-spec.ts b/packages/net-stubbing/test/unit/intercepted-request-spec.ts index 952157598de6..84de5e468ecb 100644 --- a/packages/net-stubbing/test/unit/intercepted-request-spec.ts +++ b/packages/net-stubbing/test/unit/intercepted-request-spec.ts @@ -6,7 +6,7 @@ import { state as NetStubbingState } from '../../lib/server/state' describe('InterceptedRequest', () => { context('handleSubscriptions', () => { - it.only('handles subscriptions as expected', async () => { + it('handles subscriptions as expected', async () => { const socket = { toDriver: sinon.stub(), } @@ -29,7 +29,7 @@ describe('InterceptedRequest', () => { ], }, state, - socket + socket, }) interceptedRequest.addSubscription({ From 425496906d388567882ae7efcb191c9b23cd0b98 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Fri, 10 Mar 2023 21:02:04 +0000 Subject: [PATCH 11/11] Update cli/CHANGELOG.md --- cli/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 7c326e0c2a0c..ba7478e09464 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -8,7 +8,7 @@ _Released 03/14/2023 (PENDING)_ - It is now possible to control the number of connection attempts to the browser using the CYPRESS_CONNECT_RETRY_THRESHOLD Environment Variable. Learn more [here](https://docs.cypress.io/guides/references/advanced-installation#Environment-variables). Addressed in [#25848](https://github.com/cypress-io/cypress/pull/25848). - The Debug page is now able to show real-time results from in-progress runs. Addresses [#25759](https://github.com/cypress-io/cypress/issues/25759). - Added the ability to control whether a request is logged to the command log via `cy.intercept()` by passing `log: false` or `log: true`. Addresses [#7362](https://github.com/cypress-io/cypress/issues/7362). - - This can be used to override Cypress's default behavior of logging all XHRs and fetches, see the [example](https://docs.cypress.io/api/commands/intercept#controlling-log-behavior). + - This can be used to override Cypress's default behavior of logging all XHRs and fetches, see the [example](https://docs.cypress.io/api/commands/intercept#Disabling-logs-for-a-request). **Bugfixes:**