Skip to content

Commit

Permalink
feat(intercept): add { log: false } to StaticResponse (#25547)
Browse files Browse the repository at this point in the history
* begin setting log with the backend

* revert backend changes

* update interface now that we are only doing static log

* change existing logging logic to run in proxy layer instead

* add tests, fix small bugs

run ci

* fix tests

* add changelog

* run ci

* run ci

* fix cl

run ci

* Update cli/CHANGELOG.md

---------

Co-authored-by: Matt Henkes <mjhenkes@gmail.com>
  • Loading branch information
flotwig and mjhenkes committed Mar 14, 2023
1 parent cacdb1d commit 039ebad
Show file tree
Hide file tree
Showing 15 changed files with 187 additions and 125 deletions.
2 changes: 2 additions & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ _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#Disabling-logs-for-a-request).

**Bugfixes:**

Expand Down
90 changes: 67 additions & 23 deletions packages/driver/cypress/e2e/cypress/proxy-logging.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,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)
})
})
})
})
})
Expand Down
9 changes: 5 additions & 4 deletions packages/driver/src/cy/net-stubbing/add-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -187,17 +188,17 @@ 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 = {
body: handler,
}
}

validateStaticResponse('cy.intercept', <StaticResponse>handler)
validateStaticResponse('cy.intercept', <StaticResponseWithOptions>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 } })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ export const onBeforeRequest: HandlerFn<CyHttpMessages.IncomingRequest> = (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)
Expand Down
15 changes: 13 additions & 2 deletions packages/driver/src/cy/net-stubbing/static-response-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import _ from 'lodash'

import type {
StaticResponse,
StaticResponseWithOptions,
BackendStaticResponseWithArrayBuffer,
FixtureOpts,
} from '@packages/net-stubbing/lib/types'
Expand All @@ -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 } })
Expand Down Expand Up @@ -102,8 +105,8 @@ function getFixtureOpts (fixture: string): FixtureOpts {
return { filePath, encoding: encoding === 'null' ? null : encoding }
}

export function getBackendStaticResponse (staticResponse: Readonly<StaticResponse>): BackendStaticResponseWithArrayBuffer {
const backendStaticResponse: BackendStaticResponseWithArrayBuffer = _.omit(staticResponse, 'body', 'fixture', 'delayMs')
export function getBackendStaticResponse (staticResponse: Readonly<StaticResponseWithOptions>): BackendStaticResponseWithArrayBuffer {
const backendStaticResponse: BackendStaticResponseWithArrayBuffer = _.omit(staticResponse, 'body', 'fixture', 'delayMs', 'log')

if (staticResponse.delayMs) {
// support deprecated `delayMs` usage
Expand Down Expand Up @@ -132,9 +135,17 @@ export function getBackendStaticResponse (staticResponse: Readonly<StaticRespons
}
}

if (!_.isUndefined(staticResponse.log)) {
backendStaticResponse.log = !!staticResponse.log
}

return backendStaticResponse
}

export function hasStaticResponseKeys (obj: any) {
return !_.isArray(obj) && (_.intersection(_.keys(obj), STATIC_RESPONSE_KEYS).length || _.isEmpty(obj))
}

export function hasStaticResponseWithOptionsKeys (obj: any) {
return !_.isArray(obj) && (_.intersection(_.keys(obj), STATIC_RESPONSE_WITH_OPTIONS_KEYS).length || _.isEmpty(obj))
}
57 changes: 7 additions & 50 deletions packages/driver/src/cypress/proxy-logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<E> (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,
Expand Down Expand Up @@ -128,10 +111,6 @@ function getRequestLogConfig (req: Omit<ProxyRequest, 'log'>): Partial<Cypress.I
}
}

function shouldLog (preRequest: BrowserPreRequest) {
return ['xhr', 'fetch'].includes(preRequest.resourceType)
}

class ProxyRequest {
log?: Cypress.Log
preRequest: BrowserPreRequest
Expand Down Expand Up @@ -231,7 +210,6 @@ class ProxyRequest {
}

export default class ProxyLogging {
unloggedPreRequests: Array<BrowserPreRequest> = []
proxyRequests: Array<ProxyRequest> = []

constructor (private Cypress: Cypress.Cypress) {
Expand All @@ -254,34 +232,19 @@ export default class ProxyLogging {
proxyRequest.log.end()
}
}
this.unloggedPreRequests = []
this.proxyRequests = []
})
}

/**
* 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)
logInterception (interception: Interception, route: Route): ProxyRequest | undefined {
const proxyRequest = _.find(this.proxyRequests, ({ preRequest }) => preRequest.requestId === interception.browserRequestId)

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 })
Expand Down Expand Up @@ -326,16 +289,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 {
Expand Down
12 changes: 11 additions & 1 deletion packages/net-stubbing/lib/external-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,17 @@ export interface RouteMatcherOptionsGeneric<S> {

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.
Expand Down
5 changes: 3 additions & 2 deletions packages/net-stubbing/lib/internal-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ import type {
GenericStaticResponse,
Subscription,
CyHttpMessages,
InterceptOptions,
} from './external-types'

export type FixtureOpts = {
encoding: string | null
filePath: string
}

export type BackendStaticResponse = GenericStaticResponse<FixtureOpts, string>
export type BackendStaticResponse = GenericStaticResponse<FixtureOpts, string> & InterceptOptions

export type BackendStaticResponseWithArrayBuffer = GenericStaticResponse<FixtureOpts, string | ArrayBuffer>
export type BackendStaticResponseWithArrayBuffer = GenericStaticResponse<FixtureOpts, string | ArrayBuffer> & InterceptOptions

export const SERIALIZABLE_REQ_PROPS = [
'headers',
Expand Down
2 changes: 1 addition & 1 deletion packages/net-stubbing/lib/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down

5 comments on commit 039ebad

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 039ebad Mar 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux arm64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.8.0/linux-arm64/develop-039ebad2205ab4ebc6070c2a726c6197f84a0b11/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 039ebad Mar 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.8.0/linux-x64/develop-039ebad2205ab4ebc6070c2a726c6197f84a0b11/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 039ebad Mar 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin arm64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.8.0/darwin-arm64/develop-039ebad2205ab4ebc6070c2a726c6197f84a0b11/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 039ebad Mar 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin x64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.8.0/darwin-x64/develop-039ebad2205ab4ebc6070c2a726c6197f84a0b11/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 039ebad Mar 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the win32 x64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.8.0/win32-x64/develop-039ebad2205ab4ebc6070c2a726c6197f84a0b11/cypress.tgz

Please sign in to comment.