mirror of
https://github.com/cypress-io/cypress.git
synced 2026-01-08 07:29:44 -06:00
perf: no longer pause every single request through CDP and only pause requests needed for AUT document [run ci] (#26623)
This commit is contained in:
@@ -30,7 +30,7 @@ mainBuildFilters: &mainBuildFilters
|
||||
- /^release\/\d+\.\d+\.\d+$/
|
||||
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
|
||||
- 'update-v8-snapshot-cache-on-develop'
|
||||
- 'revert-runner'
|
||||
- 'reduce_requests_paused'
|
||||
|
||||
# usually we don't build Mac app - it takes a long time
|
||||
# but sometimes we want to really confirm we are doing the right thing
|
||||
@@ -41,7 +41,7 @@ macWorkflowFilters: &darwin-workflow-filters
|
||||
- equal: [ develop, << pipeline.git.branch >> ]
|
||||
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
|
||||
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
|
||||
- equal: [ 'revert-runner', << pipeline.git.branch >> ]
|
||||
- equal: [ 'reduce_requests_paused', << pipeline.git.branch >> ]
|
||||
- matches:
|
||||
pattern: /^release\/\d+\.\d+\.\d+$/
|
||||
value: << pipeline.git.branch >>
|
||||
@@ -52,7 +52,7 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters
|
||||
- equal: [ develop, << pipeline.git.branch >> ]
|
||||
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
|
||||
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
|
||||
- equal: [ 'revert-runner', << pipeline.git.branch >> ]
|
||||
- equal: [ 'reduce_requests_paused', << pipeline.git.branch >> ]
|
||||
- matches:
|
||||
pattern: /^release\/\d+\.\d+\.\d+$/
|
||||
value: << pipeline.git.branch >>
|
||||
@@ -72,7 +72,7 @@ windowsWorkflowFilters: &windows-workflow-filters
|
||||
- equal: [ develop, << pipeline.git.branch >> ]
|
||||
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
|
||||
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
|
||||
- equal: [ 'revert-runner', << pipeline.git.branch >> ]
|
||||
- equal: [ 'reduce_requests_paused', << pipeline.git.branch >> ]
|
||||
- matches:
|
||||
pattern: /^release\/\d+\.\d+\.\d+$/
|
||||
value: << pipeline.git.branch >>
|
||||
@@ -139,7 +139,7 @@ commands:
|
||||
- run:
|
||||
name: Check current branch to persist artifacts
|
||||
command: |
|
||||
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "update-v8-snapshot-cache-on-develop" && "$CIRCLE_BRANCH" != "revert-runner" ]]; then
|
||||
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "update-v8-snapshot-cache-on-develop" && "$CIRCLE_BRANCH" != "reduce_requests_paused" ]]; then
|
||||
echo "Not uploading artifacts or posting install comment for this branch."
|
||||
circleci-agent step halt
|
||||
fi
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
<!-- See the ../guides/writing-the-cypress-changelog.md for details on writing the changelog. -->
|
||||
## 12.17.2
|
||||
|
||||
_Released 08/01/2023 (PENDING)_
|
||||
|
||||
**Performance:**
|
||||
|
||||
- Fixed an issue where unnecessary requests were being paused. No longer sends `X-Cypress-Is-XHR-Or-Fetch` header and infers resource type off of the server pre-request object. Fixes [#26620](https://github.com/cypress-io/cypress/issues/26620) and [#26622](https://github.com/cypress-io/cypress/issues/26622).
|
||||
## 12.17.2
|
||||
|
||||
_Released 07/20/2023_
|
||||
|
||||
**Bugfixes:**
|
||||
|
||||
@@ -89,7 +89,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout
|
||||
cy.then(() => {
|
||||
expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', {
|
||||
url: 'http://www.foobar.com:3500/test-request',
|
||||
requestedWith: 'fetch',
|
||||
resourceType: 'fetch',
|
||||
credentialStatus: assertCredentialStatus,
|
||||
})
|
||||
})
|
||||
@@ -109,7 +109,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout
|
||||
cy.then(() => {
|
||||
expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', {
|
||||
url: 'http://www.foobar.com:3500/test-request',
|
||||
requestedWith: 'fetch',
|
||||
resourceType: 'fetch',
|
||||
credentialStatus: assertCredentialStatus,
|
||||
})
|
||||
})
|
||||
@@ -129,7 +129,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout
|
||||
cy.then(() => {
|
||||
expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', {
|
||||
url: 'http://www.foobar.com:3500/test-request',
|
||||
requestedWith: 'fetch',
|
||||
resourceType: 'fetch',
|
||||
credentialStatus: assertCredentialStatus,
|
||||
})
|
||||
})
|
||||
@@ -173,7 +173,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout
|
||||
cy.then(() => {
|
||||
expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', {
|
||||
url: 'http://app.foobar.com:3500/test-request',
|
||||
requestedWith: 'fetch',
|
||||
resourceType: 'fetch',
|
||||
credentialStatus: 'include',
|
||||
})
|
||||
})
|
||||
@@ -218,7 +218,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout
|
||||
cy.then(() => {
|
||||
expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', {
|
||||
url: 'http://www.foobar.com:3500/test-request-credentials',
|
||||
requestedWith: 'fetch',
|
||||
resourceType: 'fetch',
|
||||
credentialStatus: assertCredentialStatus,
|
||||
})
|
||||
})
|
||||
@@ -248,7 +248,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout
|
||||
cy.then(() => {
|
||||
expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', {
|
||||
url: 'http://www.foobar.com:3500/test-request-credentials',
|
||||
requestedWith: 'fetch',
|
||||
resourceType: 'fetch',
|
||||
credentialStatus: assertCredentialStatus,
|
||||
})
|
||||
})
|
||||
@@ -276,7 +276,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout
|
||||
cy.then(() => {
|
||||
expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', {
|
||||
url: 'http://www.foobar.com:3500/test-request-credentials',
|
||||
requestedWith: 'fetch',
|
||||
resourceType: 'fetch',
|
||||
credentialStatus: assertCredentialStatus,
|
||||
})
|
||||
})
|
||||
@@ -327,7 +327,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout
|
||||
cy.then(() => {
|
||||
expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', {
|
||||
url: 'http://app.foobar.com:3500/test-request',
|
||||
requestedWith: 'fetch',
|
||||
resourceType: 'fetch',
|
||||
credentialStatus: 'include',
|
||||
})
|
||||
})
|
||||
@@ -346,7 +346,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout
|
||||
cy.then(() => {
|
||||
expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', {
|
||||
url: 'http://localhost:3500/foo.bar.baz.json',
|
||||
requestedWith: 'fetch',
|
||||
resourceType: 'fetch',
|
||||
credentialStatus: 'same-origin',
|
||||
})
|
||||
})
|
||||
@@ -410,7 +410,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout
|
||||
cy.then(() => {
|
||||
expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', {
|
||||
url: 'http://www.foobar.com:3500/test-request',
|
||||
requestedWith: 'xhr',
|
||||
resourceType: 'xhr',
|
||||
credentialStatus: withCredentials,
|
||||
})
|
||||
})
|
||||
@@ -450,7 +450,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout
|
||||
cy.then(() => {
|
||||
expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', {
|
||||
url: 'http://app.foobar.com:3500/test-request',
|
||||
requestedWith: 'xhr',
|
||||
resourceType: 'xhr',
|
||||
credentialStatus: true,
|
||||
})
|
||||
})
|
||||
@@ -502,7 +502,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout
|
||||
cy.then(() => {
|
||||
expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', {
|
||||
url: 'http://www.foobar.com:3500/test-request-credentials',
|
||||
requestedWith: 'xhr',
|
||||
resourceType: 'xhr',
|
||||
credentialStatus: withCredentials,
|
||||
})
|
||||
})
|
||||
@@ -554,7 +554,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout
|
||||
cy.then(() => {
|
||||
expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', {
|
||||
url: 'http://app.foobar.com:3500/test-request',
|
||||
requestedWith: 'xhr',
|
||||
resourceType: 'xhr',
|
||||
credentialStatus: true,
|
||||
})
|
||||
})
|
||||
@@ -574,7 +574,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout
|
||||
cy.then(() => {
|
||||
expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', {
|
||||
url: 'http://localhost:3500/foo.bar.baz.json',
|
||||
requestedWith: 'xhr',
|
||||
resourceType: 'xhr',
|
||||
credentialStatus: false,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -63,43 +63,24 @@ const connect = function (host, path, extraOpts) {
|
||||
// adds a header to the request to mark it as a request for the AUT frame
|
||||
// itself, so the proxy can utilize that for injection purposes
|
||||
browser.webRequest.onBeforeSendHeaders.addListener((details) => {
|
||||
const requestModifications = {
|
||||
requestHeaders: [
|
||||
...(details.requestHeaders || []),
|
||||
/**
|
||||
* Unlike CDP, the web extensions onBeforeSendHeaders resourceType cannot discern the difference
|
||||
* between fetch or xhr resource types, but classifies both as 'xmlhttprequest'. Because of this,
|
||||
* we set X-Cypress-Is-XHR-Or-Fetch to true if the request is made with 'xhr' or 'fetch' so the
|
||||
* middleware doesn't incorrectly assume which request type is being sent
|
||||
* @see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/ResourceType
|
||||
*/
|
||||
...(details.type === 'xmlhttprequest' ? [{
|
||||
name: 'X-Cypress-Is-XHR-Or-Fetch',
|
||||
value: 'true',
|
||||
}] : []),
|
||||
],
|
||||
}
|
||||
|
||||
if (
|
||||
// parentFrameId: 0 means the parent is the top-level, so if it isn't
|
||||
// 0, it's nested inside the AUT and can't be the AUT itself
|
||||
details.parentFrameId !== 0
|
||||
// isn't an iframe
|
||||
|| details.type !== 'sub_frame'
|
||||
// is the spec frame, not the AUT
|
||||
|| details.url.includes('__cypress')
|
||||
) return requestModifications
|
||||
) return
|
||||
|
||||
return {
|
||||
requestHeaders: [
|
||||
...requestModifications.requestHeaders,
|
||||
...details.requestHeaders,
|
||||
{
|
||||
name: 'X-Cypress-Is-AUT-Frame',
|
||||
value: 'true',
|
||||
},
|
||||
],
|
||||
}
|
||||
}, { urls: ['<all_urls>'] }, ['blocking', 'requestHeaders'])
|
||||
}, { urls: ['<all_urls>'], types: ['sub_frame'] }, ['blocking', 'requestHeaders'])
|
||||
})
|
||||
|
||||
const fail = (id, err) => {
|
||||
|
||||
@@ -285,7 +285,7 @@ describe('app/background', () => {
|
||||
|
||||
const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details)
|
||||
|
||||
expect(result).to.deep.equal({ requestHeaders: [] })
|
||||
expect(result).to.be.undefined
|
||||
})
|
||||
|
||||
it('does not add header if it is a nested frame', async function () {
|
||||
@@ -299,22 +299,7 @@ describe('app/background', () => {
|
||||
|
||||
const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details)
|
||||
|
||||
expect(result).to.deep.equal({ requestHeaders: [] })
|
||||
})
|
||||
|
||||
it('does not add header if it is not a sub frame request', async function () {
|
||||
const details = {
|
||||
parentFrameId: 0,
|
||||
type: 'stylesheet',
|
||||
}
|
||||
|
||||
sinon.stub(browser.webRequest.onBeforeSendHeaders, 'addListener')
|
||||
|
||||
await this.connect()
|
||||
|
||||
const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details)
|
||||
|
||||
expect(result).to.deep.equal({ requestHeaders: [] })
|
||||
expect(result).to.be.undefined
|
||||
})
|
||||
|
||||
it('does not add header if it is a spec frame request', async function () {
|
||||
@@ -329,7 +314,7 @@ describe('app/background', () => {
|
||||
await this.connect()
|
||||
const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details)
|
||||
|
||||
expect(result).to.deep.equal({ requestHeaders: [] })
|
||||
expect(result).to.be.undefined
|
||||
})
|
||||
|
||||
it('appends X-Cypress-Is-AUT-Frame header to AUT iframe request', async function () {
|
||||
@@ -361,60 +346,6 @@ describe('app/background', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('appends X-Cypress-Is-XHR-Or-Fetch header to request if the resourceType is "xmlhttprequest"', async function () {
|
||||
const details = {
|
||||
parentFrameId: 0,
|
||||
type: 'xmlhttprequest',
|
||||
url: 'http://localhost:3000/index.html',
|
||||
requestHeaders: [
|
||||
{ name: 'X-Foo', value: 'Bar' },
|
||||
],
|
||||
}
|
||||
|
||||
sinon.stub(browser.webRequest.onBeforeSendHeaders, 'addListener')
|
||||
|
||||
await this.connect()
|
||||
const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details)
|
||||
|
||||
expect(result).to.deep.equal({
|
||||
requestHeaders: [
|
||||
{
|
||||
name: 'X-Foo',
|
||||
value: 'Bar',
|
||||
},
|
||||
{
|
||||
name: 'X-Cypress-Is-XHR-Or-Fetch',
|
||||
value: 'true',
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it('does not append X-Cypress-Is-XHR-Or-Fetch header to request if the resourceType is not an "xmlhttprequest"', async function () {
|
||||
const details = {
|
||||
parentFrameId: 0,
|
||||
type: 'sub_frame',
|
||||
url: 'http://localhost:3000/index.html',
|
||||
requestHeaders: [
|
||||
{ name: 'X-Foo', value: 'Bar' },
|
||||
],
|
||||
}
|
||||
|
||||
sinon.stub(browser.webRequest.onBeforeSendHeaders, 'addListener')
|
||||
|
||||
await this.connect()
|
||||
const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details)
|
||||
|
||||
expect(result).to.not.deep.equal({
|
||||
requestHeaders: [
|
||||
{
|
||||
name: 'X-Cypress-Is-XHR-Or-Fetch',
|
||||
value: 'true',
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it('does not add before-headers listener if in non-Firefox browser', async function () {
|
||||
browser.runtime.getBrowserInfo = undefined
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import type { Readable } from 'stream'
|
||||
import type { Request, Response } from 'express'
|
||||
import type { RemoteStates } from '@packages/server/lib/remote_states'
|
||||
import type { CookieJar, SerializableAutomationCookie } from '@packages/server/lib/util/cookies'
|
||||
import type { RequestedWithAndCredentialManager } from '@packages/server/lib/util/requestedWithAndCredentialManager'
|
||||
import type { ResourceTypeAndCredentialManager } from '@packages/server/lib/util/resourceTypeAndCredentialManager'
|
||||
|
||||
function getRandomColorFn () {
|
||||
return chalk.hex(`#${Number(
|
||||
@@ -82,7 +82,7 @@ export type ServerCtx = Readonly<{
|
||||
getFileServerToken: () => string | undefined
|
||||
getCookieJar: () => CookieJar
|
||||
remoteStates: RemoteStates
|
||||
requestedWithAndCredentialManager: RequestedWithAndCredentialManager
|
||||
resourceTypeAndCredentialManager: ResourceTypeAndCredentialManager
|
||||
getRenderedHTMLOrigins: Http['getRenderedHTMLOrigins']
|
||||
netStubbingState: NetStubbingState
|
||||
middleware: HttpMiddlewareStacks
|
||||
@@ -258,7 +258,7 @@ export class Http {
|
||||
request: any
|
||||
socket: CyServer.Socket
|
||||
serverBus: EventEmitter
|
||||
requestedWithAndCredentialManager: RequestedWithAndCredentialManager
|
||||
resourceTypeAndCredentialManager: ResourceTypeAndCredentialManager
|
||||
renderedHTMLOrigins: {[key: string]: boolean} = {}
|
||||
autUrl?: string
|
||||
getCookieJar: () => CookieJar
|
||||
@@ -275,7 +275,7 @@ export class Http {
|
||||
this.socket = opts.socket
|
||||
this.request = opts.request
|
||||
this.serverBus = opts.serverBus
|
||||
this.requestedWithAndCredentialManager = opts.requestedWithAndCredentialManager
|
||||
this.resourceTypeAndCredentialManager = opts.resourceTypeAndCredentialManager
|
||||
this.getCookieJar = opts.getCookieJar
|
||||
|
||||
if (typeof opts.middleware === 'undefined') {
|
||||
@@ -303,7 +303,7 @@ export class Http {
|
||||
netStubbingState: this.netStubbingState,
|
||||
socket: this.socket,
|
||||
serverBus: this.serverBus,
|
||||
requestedWithAndCredentialManager: this.requestedWithAndCredentialManager,
|
||||
resourceTypeAndCredentialManager: this.resourceTypeAndCredentialManager,
|
||||
getCookieJar: this.getCookieJar,
|
||||
simulatedCookies: [],
|
||||
debug: (formatter, ...args) => {
|
||||
|
||||
@@ -31,7 +31,6 @@ const ExtractCypressMetadataHeaders: RequestMiddleware = function () {
|
||||
const span = telemetry.startSpan({ name: 'extract:cypress:metadata:headers', parentSpan: this.reqMiddlewareSpan, isVerbose })
|
||||
|
||||
this.req.isAUTFrame = !!this.req.headers['x-cypress-is-aut-frame']
|
||||
const requestIsXhrOrFetch = this.req.headers['x-cypress-is-xhr-or-fetch']
|
||||
|
||||
span?.setAttributes({
|
||||
isAUTFrame: this.req.isAUTFrame,
|
||||
@@ -41,34 +40,6 @@ const ExtractCypressMetadataHeaders: RequestMiddleware = function () {
|
||||
delete this.req.headers['x-cypress-is-aut-frame']
|
||||
}
|
||||
|
||||
if (this.req.headers['x-cypress-is-xhr-or-fetch']) {
|
||||
this.debug(`found x-cypress-is-xhr-or-fetch header. Deleting x-cypress-is-xhr-or-fetch header.`)
|
||||
delete this.req.headers['x-cypress-is-xhr-or-fetch']
|
||||
}
|
||||
|
||||
if (!doesTopNeedToBeSimulated(this) ||
|
||||
// this should be unreachable, as the x-cypress-is-xhr-or-fetch header is only attached if
|
||||
// the resource type is 'xhr' or 'fetch or 'true' (in the case of electron|extension).
|
||||
// This is only needed for defensive purposes.
|
||||
(requestIsXhrOrFetch !== 'true' && requestIsXhrOrFetch !== 'xhr' && requestIsXhrOrFetch !== 'fetch')) {
|
||||
this.next()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.debug(`looking up credentials for ${this.req.proxiedUrl}`)
|
||||
const { requestedWith, credentialStatus } = this.requestedWithAndCredentialManager.get(this.req.proxiedUrl, requestIsXhrOrFetch !== 'true' ? requestIsXhrOrFetch : undefined)
|
||||
|
||||
this.debug(`credentials calculated for ${requestedWith}:${credentialStatus}`)
|
||||
|
||||
this.req.requestedWith = requestedWith
|
||||
this.req.credentialsLevel = credentialStatus
|
||||
|
||||
span?.setAttributes({
|
||||
calculatedResourceType: this.req.resourceType,
|
||||
credentialsLevel: credentialStatus,
|
||||
})
|
||||
|
||||
span?.end()
|
||||
this.next()
|
||||
}
|
||||
@@ -103,74 +74,6 @@ const MaybeSimulateSecHeaders: RequestMiddleware = function () {
|
||||
this.next()
|
||||
}
|
||||
|
||||
const MaybeAttachCrossOriginCookies: RequestMiddleware = function () {
|
||||
const span = telemetry.startSpan({ name: 'maybe:attach:cross:origin:cookies', parentSpan: this.reqMiddlewareSpan, isVerbose })
|
||||
|
||||
const doesTopNeedSimulation = doesTopNeedToBeSimulated(this)
|
||||
|
||||
span?.setAttributes({
|
||||
doesTopNeedToBeSimulated: doesTopNeedSimulation,
|
||||
resourceType: this.req.resourceType,
|
||||
})
|
||||
|
||||
if (!doesTopNeedSimulation) {
|
||||
span?.end()
|
||||
|
||||
return this.next()
|
||||
}
|
||||
|
||||
// Top needs to be simulated since the AUT is in a cross origin state. Get the "requested with" and credentials and see what cookies need to be attached
|
||||
const currentAUTUrl = this.getAUTUrl()
|
||||
const shouldCookiesBeAttachedToRequest = shouldAttachAndSetCookies(this.req.proxiedUrl, currentAUTUrl, this.req.requestedWith, this.req.credentialsLevel, this.req.isAUTFrame)
|
||||
|
||||
span?.setAttributes({
|
||||
currentAUTUrl,
|
||||
shouldCookiesBeAttachedToRequest,
|
||||
})
|
||||
|
||||
this.debug(`should cookies be attached to request?: ${shouldCookiesBeAttachedToRequest}`)
|
||||
if (!shouldCookiesBeAttachedToRequest) {
|
||||
span?.end()
|
||||
|
||||
return this.next()
|
||||
}
|
||||
|
||||
const sameSiteContext = getSameSiteContext(
|
||||
currentAUTUrl,
|
||||
this.req.proxiedUrl,
|
||||
this.req.isAUTFrame,
|
||||
)
|
||||
|
||||
span?.setAttributes({
|
||||
sameSiteContext,
|
||||
currentAUTUrl,
|
||||
isAUTFrame: this.req.isAUTFrame,
|
||||
})
|
||||
|
||||
const applicableCookiesInCookieJar = this.getCookieJar().getCookies(this.req.proxiedUrl, sameSiteContext)
|
||||
const cookiesOnRequest = (this.req.headers['cookie'] || '').split('; ')
|
||||
|
||||
const existingCookiesInJar = applicableCookiesInCookieJar.join('; ')
|
||||
const addedCookiesFromHeader = cookiesOnRequest.join('; ')
|
||||
|
||||
this.debug('existing cookies on request from cookie jar: %s', existingCookiesInJar)
|
||||
this.debug('add cookies to request from header: %s', addedCookiesFromHeader)
|
||||
|
||||
// if the cookie header is empty (i.e. ''), set it to undefined for expected behavior
|
||||
this.req.headers['cookie'] = addCookieJarCookiesToRequest(applicableCookiesInCookieJar, cookiesOnRequest) || undefined
|
||||
|
||||
span?.setAttributes({
|
||||
existingCookiesInJar,
|
||||
addedCookiesFromHeader,
|
||||
cookieHeader: this.req.headers['cookie'],
|
||||
})
|
||||
|
||||
this.debug('cookies being sent with request: %s', this.req.headers['cookie'])
|
||||
|
||||
span?.end()
|
||||
this.next()
|
||||
}
|
||||
|
||||
const CorrelateBrowserPreRequest: RequestMiddleware = async function () {
|
||||
const span = telemetry.startSpan({ name: 'correlate:prerequest', parentSpan: this.reqMiddlewareSpan, isVerbose })
|
||||
|
||||
@@ -231,6 +134,93 @@ const CorrelateBrowserPreRequest: RequestMiddleware = async function () {
|
||||
}))
|
||||
}
|
||||
|
||||
const CalculateCredentialLevelIfApplicable: RequestMiddleware = function () {
|
||||
if (!doesTopNeedToBeSimulated(this) ||
|
||||
(this.req.resourceType !== undefined && this.req.resourceType !== 'xhr' && this.req.resourceType !== 'fetch')) {
|
||||
this.next()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.debug(`looking up credentials for ${this.req.proxiedUrl}`)
|
||||
const { credentialStatus, resourceType } = this.resourceTypeAndCredentialManager.get(this.req.proxiedUrl, this.req.resourceType)
|
||||
|
||||
this.debug(`credentials calculated for ${resourceType}:${credentialStatus}`)
|
||||
|
||||
// if for some reason the resourceType is not set by the prerequest, have a fallback in place
|
||||
this.req.resourceType = !this.req.resourceType ? resourceType : this.req.resourceType
|
||||
this.req.credentialsLevel = credentialStatus
|
||||
this.next()
|
||||
}
|
||||
|
||||
const MaybeAttachCrossOriginCookies: RequestMiddleware = function () {
|
||||
const span = telemetry.startSpan({ name: 'maybe:attach:cross:origin:cookies', parentSpan: this.reqMiddlewareSpan, isVerbose })
|
||||
|
||||
const doesTopNeedSimulation = doesTopNeedToBeSimulated(this)
|
||||
|
||||
span?.setAttributes({
|
||||
doesTopNeedToBeSimulated: doesTopNeedSimulation,
|
||||
resourceType: this.req.resourceType,
|
||||
})
|
||||
|
||||
if (!doesTopNeedSimulation) {
|
||||
span?.end()
|
||||
|
||||
return this.next()
|
||||
}
|
||||
|
||||
// Top needs to be simulated since the AUT is in a cross origin state. Get the "requested with" and credentials and see what cookies need to be attached
|
||||
const currentAUTUrl = this.getAUTUrl()
|
||||
const shouldCookiesBeAttachedToRequest = shouldAttachAndSetCookies(this.req.proxiedUrl, currentAUTUrl, this.req.resourceType, this.req.credentialsLevel, this.req.isAUTFrame)
|
||||
|
||||
span?.setAttributes({
|
||||
currentAUTUrl,
|
||||
shouldCookiesBeAttachedToRequest,
|
||||
})
|
||||
|
||||
this.debug(`should cookies be attached to request?: ${shouldCookiesBeAttachedToRequest}`)
|
||||
if (!shouldCookiesBeAttachedToRequest) {
|
||||
span?.end()
|
||||
|
||||
return this.next()
|
||||
}
|
||||
|
||||
const sameSiteContext = getSameSiteContext(
|
||||
currentAUTUrl,
|
||||
this.req.proxiedUrl,
|
||||
this.req.isAUTFrame,
|
||||
)
|
||||
|
||||
span?.setAttributes({
|
||||
sameSiteContext,
|
||||
currentAUTUrl,
|
||||
isAUTFrame: this.req.isAUTFrame,
|
||||
})
|
||||
|
||||
const applicableCookiesInCookieJar = this.getCookieJar().getCookies(this.req.proxiedUrl, sameSiteContext)
|
||||
const cookiesOnRequest = (this.req.headers['cookie'] || '').split('; ')
|
||||
|
||||
const existingCookiesInJar = applicableCookiesInCookieJar.join('; ')
|
||||
const addedCookiesFromHeader = cookiesOnRequest.join('; ')
|
||||
|
||||
this.debug('existing cookies on request from cookie jar: %s', existingCookiesInJar)
|
||||
this.debug('add cookies to request from header: %s', addedCookiesFromHeader)
|
||||
|
||||
// if the cookie header is empty (i.e. ''), set it to undefined for expected behavior
|
||||
this.req.headers['cookie'] = addCookieJarCookiesToRequest(applicableCookiesInCookieJar, cookiesOnRequest) || undefined
|
||||
|
||||
span?.setAttributes({
|
||||
existingCookiesInJar,
|
||||
addedCookiesFromHeader,
|
||||
cookieHeader: this.req.headers['cookie'],
|
||||
})
|
||||
|
||||
this.debug('cookies being sent with request: %s', this.req.headers['cookie'])
|
||||
|
||||
span?.end()
|
||||
this.next()
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -525,9 +515,10 @@ export default {
|
||||
LogRequest,
|
||||
ExtractCypressMetadataHeaders,
|
||||
MaybeSimulateSecHeaders,
|
||||
CorrelateBrowserPreRequest,
|
||||
CalculateCredentialLevelIfApplicable,
|
||||
MaybeAttachCrossOriginCookies,
|
||||
MaybeEndRequestWithBufferedResponse,
|
||||
CorrelateBrowserPreRequest,
|
||||
SetMatchingRoutes,
|
||||
SendToDriver,
|
||||
InterceptRequest,
|
||||
|
||||
@@ -580,7 +580,7 @@ const MaybeCopyCookiesFromIncomingRes: ResponseMiddleware = async function () {
|
||||
url: this.req.proxiedUrl,
|
||||
isAUTFrame: this.req.isAUTFrame,
|
||||
doesTopNeedSimulating,
|
||||
requestedWith: this.req.requestedWith,
|
||||
resourceType: this.req.resourceType,
|
||||
credentialLevel: this.req.credentialsLevel,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -4,7 +4,8 @@ import { URL } from 'url'
|
||||
import { cors } from '@packages/network'
|
||||
import { urlOriginsMatch, urlSameSiteMatch } from '@packages/network/lib/cors'
|
||||
import { SerializableAutomationCookie, Cookie, CookieJar, toughCookieToAutomationCookie } from '@packages/server/lib/util/cookies'
|
||||
import type { RequestCredentialLevel, RequestedWithHeader } from '../../types'
|
||||
import type { RequestCredentialLevel } from '../../types'
|
||||
import type { ResourceType } from 'cypress/types/net-stubbing'
|
||||
|
||||
type SiteContext = 'same-origin' | 'same-site' | 'cross-site'
|
||||
|
||||
@@ -12,7 +13,7 @@ interface RequestDetails {
|
||||
url: string
|
||||
isAUTFrame: boolean
|
||||
doesTopNeedSimulating: boolean
|
||||
requestedWith?: RequestedWithHeader
|
||||
resourceType?: ResourceType
|
||||
credentialLevel?: RequestCredentialLevel
|
||||
}
|
||||
|
||||
@@ -23,18 +24,18 @@ interface RequestDetails {
|
||||
* which is critical for lax cookies
|
||||
* @param {string} requestUrl - the url of the request
|
||||
* @param {string} AUTUrl - The current url of the app under test
|
||||
* @param {requestedWith} [requestedWith] -
|
||||
* @param {resourceType} [resourceType] - the request resourceType
|
||||
* @param {RequestCredentialLevel} [credentialLevel] - The credentialLevel of the request. For `fetch` this is `omit|same-origin|include` (defaults to same-origin)
|
||||
* and for `XmlHttpRequest` it is `true|false` (defaults to false)
|
||||
* @param {isAutFrame} [boolean] - whether or not the request is from the AUT Iframe or not
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const shouldAttachAndSetCookies = (requestUrl: string, AUTUrl: string | undefined, requestedWith?: RequestedWithHeader, credentialLevel?: RequestCredentialLevel, isAutFrame?: boolean): boolean => {
|
||||
export const shouldAttachAndSetCookies = (requestUrl: string, AUTUrl: string | undefined, resourceType?: ResourceType, credentialLevel?: RequestCredentialLevel, isAutFrame?: boolean): boolean => {
|
||||
if (!AUTUrl) return false
|
||||
|
||||
const siteContext = calculateSiteContext(requestUrl, AUTUrl)
|
||||
|
||||
switch (requestedWith) {
|
||||
switch (resourceType) {
|
||||
case 'fetch':
|
||||
// never attach cookies regardless of siteContext if omit is optioned
|
||||
if (credentialLevel === 'omit') {
|
||||
@@ -59,7 +60,7 @@ export const shouldAttachAndSetCookies = (requestUrl: string, AUTUrl: string | u
|
||||
|
||||
return false
|
||||
default:
|
||||
// if we cannot determine a resource level, we likely should store the cookie as it is a navigation or another event as long as the context is same-origin
|
||||
// if we cannot determine a resource level or it isn't applicable,, we likely should store the cookie as it is a navigation or another event as long as the context is same-origin
|
||||
if (siteContext === 'same-origin' || isAutFrame) {
|
||||
return true
|
||||
}
|
||||
@@ -220,7 +221,9 @@ export class CookiesHelper {
|
||||
// cross site cookies cannot set lax/strict cookies in the browser for xhr/fetch requests (but ok with navigation/document requests)
|
||||
// NOTE: This is allowable in firefox as the default cookie behavior is no_restriction (none). However, this shouldn't
|
||||
// impact what is happening in the server-side cookie jar as Set-Cookie is still called and firefox will allow it to be set in the browser
|
||||
if (this.request.requestedWith && this.siteContext === 'cross-site' && toughCookie.sameSite !== 'none') {
|
||||
const isXhrOrFetchRequest = this.request.resourceType === 'fetch' || this.request.resourceType === 'xhr'
|
||||
|
||||
if (isXhrOrFetchRequest && this.siteContext === 'cross-site' && toughCookie.sameSite !== 'none') {
|
||||
this.debug(`cannot set cookie with SameSite=${toughCookie.sameSite} when site context is ${this.siteContext}`)
|
||||
|
||||
return
|
||||
@@ -228,10 +231,10 @@ export class CookiesHelper {
|
||||
|
||||
// don't set the cookie in our own cookie jar if the cookie would otherwise fail being set in the browser if the AUT Url
|
||||
// was actually top. This prevents cookies from being applied to our cookie jar when they shouldn't, preventing possible security implications.
|
||||
const shouldSetCookieGivenSiteContext = shouldAttachAndSetCookies(this.request.url, this.currentAUTUrl, this.request.requestedWith, this.request.credentialLevel, this.request.isAUTFrame)
|
||||
const shouldSetCookieGivenSiteContext = shouldAttachAndSetCookies(this.request.url, this.currentAUTUrl, this.request.resourceType, this.request.credentialLevel, this.request.isAUTFrame)
|
||||
|
||||
if (!shouldSetCookieGivenSiteContext) {
|
||||
this.debug(`not setting cookie for ${this.request.url} with simulated top ${ this.currentAUTUrl} for ${ this.request.requestedWith}:${this.request.credentialLevel}, cookie: ${toughCookie}`)
|
||||
this.debug(`not setting cookie for ${this.request.url} with simulated top ${ this.currentAUTUrl} for ${ this.request.resourceType}:${this.request.credentialLevel}, cookie: ${toughCookie}`)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ export type CypressIncomingRequest = Request & {
|
||||
responseTimeout?: number
|
||||
followRedirect?: boolean
|
||||
isAUTFrame: boolean
|
||||
requestedWith?: RequestedWithHeader
|
||||
credentialsLevel?: RequestCredentialLevel
|
||||
/**
|
||||
* Resource type from browserPreRequest. Copied to req so intercept matching can work.
|
||||
@@ -27,8 +26,6 @@ export type CypressIncomingRequest = Request & {
|
||||
matchingRoutes?: BackendRoute[]
|
||||
}
|
||||
|
||||
export type RequestedWithHeader = 'fetch' | 'xhr' | 'true'
|
||||
|
||||
export type RequestCredentialLevel = 'same-origin' | 'include' | 'omit' | boolean
|
||||
|
||||
export type CypressWantsInjection = 'full' | 'fullCrossOrigin' | 'partial' | false
|
||||
|
||||
@@ -49,10 +49,10 @@ context('network stubbing', () => {
|
||||
request: new Request(),
|
||||
getRenderedHTMLOrigins: () => ({}),
|
||||
serverBus: new EventEmitter(),
|
||||
requestedWithAndCredentialManager: {
|
||||
resourceTypeAndCredentialManager: {
|
||||
get () {
|
||||
return {
|
||||
requestedWith: 'xhr',
|
||||
resourceType: 'xhr',
|
||||
credentialStatus: 'same-origin',
|
||||
}
|
||||
},
|
||||
|
||||
@@ -15,9 +15,10 @@ describe('http/request-middleware', () => {
|
||||
'LogRequest',
|
||||
'ExtractCypressMetadataHeaders',
|
||||
'MaybeSimulateSecHeaders',
|
||||
'CorrelateBrowserPreRequest',
|
||||
'CalculateCredentialLevelIfApplicable',
|
||||
'MaybeAttachCrossOriginCookies',
|
||||
'MaybeEndRequestWithBufferedResponse',
|
||||
'CorrelateBrowserPreRequest',
|
||||
'SetMatchingRoutes',
|
||||
'SendToDriver',
|
||||
'InterceptRequest',
|
||||
@@ -77,58 +78,16 @@ describe('http/request-middleware', () => {
|
||||
expect(ctx.req.isAUTFrame).to.be.false
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('removes x-cypress-is-xhr-or-fetch header when it exists', async () => {
|
||||
const ctx = {
|
||||
getAUTUrl: sinon.stub().returns('http://localhost:8080'),
|
||||
remoteStates: {
|
||||
isPrimarySuperDomainOrigin: sinon.stub().returns(true),
|
||||
},
|
||||
req: {
|
||||
headers: {
|
||||
'x-cypress-is-xhr-or-fetch': 'true',
|
||||
},
|
||||
} as Partial<CypressIncomingRequest>,
|
||||
res: {
|
||||
on: (event, listener) => {},
|
||||
off: (event, listener) => {},
|
||||
},
|
||||
}
|
||||
describe('CalculateCredentialLevelIfApplicable', () => {
|
||||
const { CalculateCredentialLevelIfApplicable } = RequestMiddleware
|
||||
|
||||
await testMiddleware([ExtractCypressMetadataHeaders], ctx)
|
||||
.then(() => {
|
||||
expect(ctx.req.headers['x-cypress-is-xhr-or-fetch']).not.to.exist
|
||||
})
|
||||
})
|
||||
|
||||
it('removes x-cypress-is-xhr-or-fetch header when it does not exist', async () => {
|
||||
const ctx = {
|
||||
getAUTUrl: sinon.stub().returns('http://localhost:8080'),
|
||||
remoteStates: {
|
||||
isPrimarySuperDomainOrigin: sinon.stub().returns(false),
|
||||
},
|
||||
req: {
|
||||
headers: {},
|
||||
} as Partial<CypressIncomingRequest>,
|
||||
res: {
|
||||
on: (event, listener) => {},
|
||||
off: (event, listener) => {},
|
||||
},
|
||||
}
|
||||
|
||||
await testMiddleware([ExtractCypressMetadataHeaders], ctx)
|
||||
.then(() => {
|
||||
expect(ctx.req.headers['x-cypress-is-xhr-or-fetch']).not.to.exist
|
||||
})
|
||||
})
|
||||
|
||||
it('does not set requestedWith or credentialLevel on the request if top does NOT need to be simulated', async () => {
|
||||
it('does not set credentialLevel on the request if top does NOT need to be simulated', async () => {
|
||||
const ctx = {
|
||||
getAUTUrl: sinon.stub().returns(undefined),
|
||||
req: {
|
||||
headers: {
|
||||
'x-cypress-is-xhr-or-fetch': 'true',
|
||||
},
|
||||
resourceType: 'xhr',
|
||||
} as Partial<CypressIncomingRequest>,
|
||||
res: {
|
||||
on: (event, listener) => {},
|
||||
@@ -136,23 +95,20 @@ describe('http/request-middleware', () => {
|
||||
},
|
||||
}
|
||||
|
||||
await testMiddleware([ExtractCypressMetadataHeaders], ctx)
|
||||
await testMiddleware([CalculateCredentialLevelIfApplicable], ctx)
|
||||
.then(() => {
|
||||
expect(ctx.req.requestedWith).not.to.exist
|
||||
expect(ctx.req.credentialsLevel).not.to.exist
|
||||
})
|
||||
})
|
||||
|
||||
it('does not set requestedWith or credentialLevel on the request if x-cypress-is-xhr-or-fetch has invalid values', async () => {
|
||||
it('does not set credentialLevel on the request if resourceType has invalid value', async () => {
|
||||
const ctx = {
|
||||
getAUTUrl: sinon.stub().returns('http://localhost:8080'),
|
||||
remoteStates: {
|
||||
isPrimarySuperDomainOrigin: sinon.stub().returns(false),
|
||||
},
|
||||
req: {
|
||||
headers: {
|
||||
'x-cypress-is-xhr-or-fetch': 'sub_frame',
|
||||
},
|
||||
resourceType: 'document',
|
||||
} as Partial<CypressIncomingRequest>,
|
||||
res: {
|
||||
on: (event, listener) => {},
|
||||
@@ -160,28 +116,25 @@ describe('http/request-middleware', () => {
|
||||
},
|
||||
}
|
||||
|
||||
await testMiddleware([ExtractCypressMetadataHeaders], ctx)
|
||||
await testMiddleware([CalculateCredentialLevelIfApplicable], ctx)
|
||||
.then(() => {
|
||||
expect(ctx.req.requestedWith).not.to.exist
|
||||
expect(ctx.req.credentialsLevel).not.to.exist
|
||||
})
|
||||
})
|
||||
|
||||
// CDP can determine whether or not the request is xhr | fetch, but the extension or electron cannot
|
||||
it('provides requestedWithAndCredentialManager with requestedWith if able to determine from header (xhr)', async () => {
|
||||
it('provides resourceTypeAndCredentialManager with resourceType if able to determine from prerequest (xhr)', async () => {
|
||||
const ctx = {
|
||||
getAUTUrl: sinon.stub().returns('http://localhost:8080'),
|
||||
remoteStates: {
|
||||
isPrimarySuperDomainOrigin: sinon.stub().returns(false),
|
||||
},
|
||||
requestedWithAndCredentialManager: {
|
||||
resourceTypeAndCredentialManager: {
|
||||
get: sinon.stub().returns({}),
|
||||
},
|
||||
req: {
|
||||
resourceType: 'xhr',
|
||||
proxiedUrl: 'http://localhost:8080',
|
||||
headers: {
|
||||
'x-cypress-is-xhr-or-fetch': 'xhr',
|
||||
},
|
||||
} as Partial<CypressIncomingRequest>,
|
||||
res: {
|
||||
on: (event, listener) => {},
|
||||
@@ -189,27 +142,25 @@ describe('http/request-middleware', () => {
|
||||
},
|
||||
}
|
||||
|
||||
await testMiddleware([ExtractCypressMetadataHeaders], ctx)
|
||||
await testMiddleware([CalculateCredentialLevelIfApplicable], ctx)
|
||||
.then(() => {
|
||||
expect(ctx.requestedWithAndCredentialManager.get).to.have.been.calledWith('http://localhost:8080', `xhr`)
|
||||
expect(ctx.resourceTypeAndCredentialManager.get).to.have.been.calledWith('http://localhost:8080', `xhr`)
|
||||
})
|
||||
})
|
||||
|
||||
// CDP can determine whether or not the request is xhr | fetch, but the extension or electron cannot
|
||||
it('provides requestedWithAndCredentialManager with requestedWith if able to determine from header (fetch)', async () => {
|
||||
it('provides resourceTypeAndCredentialManager with resourceType if able to determine from prerequest (fetch)', async () => {
|
||||
const ctx = {
|
||||
getAUTUrl: sinon.stub().returns('http://localhost:8080'),
|
||||
remoteStates: {
|
||||
isPrimarySuperDomainOrigin: sinon.stub().returns(false),
|
||||
},
|
||||
requestedWithAndCredentialManager: {
|
||||
resourceTypeAndCredentialManager: {
|
||||
get: sinon.stub().returns({}),
|
||||
},
|
||||
req: {
|
||||
resourceType: 'fetch',
|
||||
proxiedUrl: 'http://localhost:8080',
|
||||
headers: {
|
||||
'x-cypress-is-xhr-or-fetch': 'fetch',
|
||||
},
|
||||
} as Partial<CypressIncomingRequest>,
|
||||
res: {
|
||||
on: (event, listener) => {},
|
||||
@@ -217,29 +168,27 @@ describe('http/request-middleware', () => {
|
||||
},
|
||||
}
|
||||
|
||||
await testMiddleware([ExtractCypressMetadataHeaders], ctx)
|
||||
await testMiddleware([CalculateCredentialLevelIfApplicable], ctx)
|
||||
.then(() => {
|
||||
expect(ctx.requestedWithAndCredentialManager.get).to.have.been.calledWith('http://localhost:8080', `fetch`)
|
||||
expect(ctx.resourceTypeAndCredentialManager.get).to.have.been.calledWith('http://localhost:8080', `fetch`)
|
||||
})
|
||||
})
|
||||
|
||||
it('sets the requestedWith and credentialsLevel on the request from whatever is returned by requestedWithAndCredentialManager if conditions apply', async () => {
|
||||
it('sets the resourceType and credentialsLevel on the request from whatever is returned by resourceTypeAndCredentialManager if conditions apply, assuming resourceType does NOT exist on the request', async () => {
|
||||
const ctx = {
|
||||
getAUTUrl: sinon.stub().returns('http://localhost:8080'),
|
||||
remoteStates: {
|
||||
isPrimarySuperDomainOrigin: sinon.stub().returns(false),
|
||||
},
|
||||
requestedWithAndCredentialManager: {
|
||||
resourceTypeAndCredentialManager: {
|
||||
get: sinon.stub().returns({
|
||||
requestedWith: 'fetch',
|
||||
resourceType: 'fetch',
|
||||
credentialStatus: 'same-origin',
|
||||
}),
|
||||
},
|
||||
req: {
|
||||
resourceType: undefined,
|
||||
proxiedUrl: 'http://localhost:8080',
|
||||
headers: {
|
||||
'x-cypress-is-xhr-or-fetch': 'true',
|
||||
},
|
||||
} as Partial<CypressIncomingRequest>,
|
||||
res: {
|
||||
on: (event, listener) => {},
|
||||
@@ -247,9 +196,9 @@ describe('http/request-middleware', () => {
|
||||
},
|
||||
}
|
||||
|
||||
await testMiddleware([ExtractCypressMetadataHeaders], ctx)
|
||||
await testMiddleware([CalculateCredentialLevelIfApplicable], ctx)
|
||||
.then(() => {
|
||||
expect(ctx.req.requestedWith).to.equal('fetch')
|
||||
expect(ctx.req.resourceType).to.equal('fetch')
|
||||
expect(ctx.req.credentialsLevel).to.equal('same-origin')
|
||||
})
|
||||
})
|
||||
@@ -373,7 +322,7 @@ describe('http/request-middleware', () => {
|
||||
it('is a noop if cookies do NOT need to be attached to request', async () => {
|
||||
const ctx = await getContext(['request=cookie'], ['jar=cookie'], 'http://foobar.com', 'http://app.foobar.com')
|
||||
|
||||
ctx.req.requestedWith = 'fetch'
|
||||
ctx.req.resourceType = 'fetch'
|
||||
ctx.req.credentialsLevel = 'omit'
|
||||
|
||||
await testMiddleware([MaybeAttachCrossOriginCookies], ctx)
|
||||
@@ -384,7 +333,7 @@ describe('http/request-middleware', () => {
|
||||
it(`allows setting cookies on request if resource type cannot be determined, but comes from the AUT frame (likely in the case of documents or redirects)`, async function () {
|
||||
const ctx = await getContext([], ['jar=cookie'], 'http://foobar.com/index.html', 'http://app.foobar.com/index.html')
|
||||
|
||||
ctx.req.requestedWith = undefined
|
||||
ctx.req.resourceType = undefined
|
||||
ctx.req.credentialsLevel = undefined
|
||||
ctx.req.isAUTFrame = true
|
||||
await testMiddleware([MaybeAttachCrossOriginCookies], ctx)
|
||||
@@ -395,7 +344,7 @@ describe('http/request-middleware', () => {
|
||||
it(`otherwise, does not allow setting cookies if request type cannot be determined and is not from the AUT and is cross-origin`, async function () {
|
||||
const ctx = await getContext([], ['jar=cookie'], 'http://foobar.com/index.html', 'http://app.foobar.com/index.html')
|
||||
|
||||
ctx.req.requestedWith = undefined
|
||||
ctx.req.resourceType = undefined
|
||||
ctx.req.credentialsLevel = undefined
|
||||
ctx.req.isAUTFrame = false
|
||||
await testMiddleware([MaybeAttachCrossOriginCookies], ctx)
|
||||
@@ -406,7 +355,7 @@ describe('http/request-middleware', () => {
|
||||
it('sets the cookie header to undefined if no cookies exist on the request, none in the jar, but cookies should be attached', async () => {
|
||||
const ctx = await getContext([], [], 'http://foobar.com', 'http://app.foobar.com')
|
||||
|
||||
ctx.req.requestedWith = 'xhr'
|
||||
ctx.req.resourceType = 'xhr'
|
||||
ctx.req.credentialsLevel = true
|
||||
|
||||
await testMiddleware([MaybeAttachCrossOriginCookies], ctx)
|
||||
@@ -417,7 +366,7 @@ describe('http/request-middleware', () => {
|
||||
it('prepends cookie jar cookies to request', async () => {
|
||||
const ctx = await getContext(['request=cookie'], ['jar=cookie'], 'http://foobar.com', 'http://app.foobar.com')
|
||||
|
||||
ctx.req.requestedWith = 'fetch'
|
||||
ctx.req.resourceType = 'fetch'
|
||||
ctx.req.credentialsLevel = 'include'
|
||||
|
||||
await testMiddleware([MaybeAttachCrossOriginCookies], ctx)
|
||||
|
||||
@@ -1104,7 +1104,7 @@ describe('http/response-middleware', function () {
|
||||
},
|
||||
req: {
|
||||
// a same-site request that has the ability to set first-party cookies in the browser
|
||||
requestedWith: 'fetch',
|
||||
resourceType: 'fetch',
|
||||
credentialsLevel: credentialLevel,
|
||||
proxiedUrl: 'https://www.foobar.com/test-request',
|
||||
},
|
||||
@@ -1159,7 +1159,7 @@ describe('http/response-middleware', function () {
|
||||
},
|
||||
req: {
|
||||
// a same-site request that has the ability to set first-party cookies in the browser
|
||||
requestedWith: 'xhr',
|
||||
resourceType: 'xhr',
|
||||
credentialsLevel: credentialLevel,
|
||||
proxiedUrl: 'https://www.foobar.com/test-request',
|
||||
},
|
||||
@@ -1213,7 +1213,7 @@ describe('http/response-middleware', function () {
|
||||
},
|
||||
req: {
|
||||
// a same-site request that has the ability to set first-party cookies in the browser
|
||||
requestedWith: 'fetch',
|
||||
resourceType: 'fetch',
|
||||
credentialsLevel: 'omit',
|
||||
proxiedUrl: 'https://www.foobar.com/test-request',
|
||||
},
|
||||
@@ -1269,7 +1269,7 @@ describe('http/response-middleware', function () {
|
||||
},
|
||||
req: {
|
||||
// a same-site request that has the ability to set first-party cookies in the browser
|
||||
requestedWith: 'fetch',
|
||||
resourceType: 'fetch',
|
||||
credentialsLevel: 'include',
|
||||
proxiedUrl: 'https://app.foobar.com/test-request',
|
||||
},
|
||||
@@ -1322,7 +1322,7 @@ describe('http/response-middleware', function () {
|
||||
},
|
||||
req: {
|
||||
// a same-site request that has the ability to set first-party cookies in the browser
|
||||
requestedWith: 'xhr',
|
||||
resourceType: 'xhr',
|
||||
credentialsLevel: true,
|
||||
proxiedUrl: 'https://app.foobar.com/test-request',
|
||||
},
|
||||
@@ -1376,7 +1376,7 @@ describe('http/response-middleware', function () {
|
||||
},
|
||||
req: {
|
||||
// a same-site request that has the ability to set first-party cookies in the browser
|
||||
requestedWith: 'fetch',
|
||||
resourceType: 'fetch',
|
||||
credentialsLevel: credentialLevel,
|
||||
proxiedUrl: 'https://app.foobar.com/test-request',
|
||||
},
|
||||
@@ -1415,7 +1415,7 @@ describe('http/response-middleware', function () {
|
||||
},
|
||||
req: {
|
||||
// a cross-site request that has the ability to set cookies in the browser
|
||||
requestedWith: 'fetch',
|
||||
resourceType: 'fetch',
|
||||
credentialsLevel: 'include',
|
||||
proxiedUrl: 'https://www.barbaz.com/test-request',
|
||||
},
|
||||
@@ -1466,7 +1466,7 @@ describe('http/response-middleware', function () {
|
||||
},
|
||||
req: {
|
||||
// a cross-site request that has the ability to set cookies in the browser
|
||||
requestedWith: 'fetch',
|
||||
resourceType: 'fetch',
|
||||
credentialsLevel: credentialLevel,
|
||||
proxiedUrl: 'https://www.barbaz.com/test-request',
|
||||
},
|
||||
@@ -1500,7 +1500,7 @@ describe('http/response-middleware', function () {
|
||||
},
|
||||
req: {
|
||||
// a cross-site request that has the ability to set cookies in the browser
|
||||
requestedWith: 'xhr',
|
||||
resourceType: 'xhr',
|
||||
credentialsLevel: true,
|
||||
proxiedUrl: 'https://www.barbaz.com/test-request',
|
||||
},
|
||||
@@ -1550,7 +1550,7 @@ describe('http/response-middleware', function () {
|
||||
},
|
||||
req: {
|
||||
// a cross-site request that has the ability to set cookies in the browser
|
||||
requestedWith: 'xhr',
|
||||
resourceType: 'xhr',
|
||||
credentialsLevel: false,
|
||||
proxiedUrl: 'https://www.barbaz.com/test-request',
|
||||
},
|
||||
@@ -1584,7 +1584,7 @@ describe('http/response-middleware', function () {
|
||||
},
|
||||
req: {
|
||||
// a cross-site request that has the ability to set cookies in the browser
|
||||
requestedWith: 'xhr',
|
||||
resourceType: 'xhr',
|
||||
credentialsLevel: true,
|
||||
proxiedUrl: 'https://www.barbaz.com/test-request',
|
||||
},
|
||||
|
||||
@@ -33,10 +33,10 @@ export const patchFetch = (window) => {
|
||||
|
||||
credentials = credentials || 'same-origin'
|
||||
// if the option is specified, communicate it to the the server to the proxy can make the request aware if it needs to potentially apply cross origin cookies
|
||||
// if the option isn't set, we can imply the default as we know the requestedWith in the proxy
|
||||
// if the option isn't set, we can imply the default as we know the resourceType in the proxy
|
||||
await requestSentWithCredentials({
|
||||
url,
|
||||
requestedWith: 'fetch',
|
||||
resourceType: 'fetch',
|
||||
credentialStatus: credentials,
|
||||
})
|
||||
} finally {
|
||||
|
||||
@@ -57,10 +57,10 @@ export const postMessagePromise = <T>({ event, data = {}, timeout }: {event: str
|
||||
|
||||
/**
|
||||
* Returns a promise from the backend request for the 'request:sent:with:credentials' event.
|
||||
* @param args - an object containing a url, requestedWith and Credential status.
|
||||
* @param args - an object containing a url, resourceType and Credential status.
|
||||
* @returns A Promise or null depending on the url parameter.
|
||||
*/
|
||||
export const requestSentWithCredentials = <T>(args: {url?: string, requestedWith: 'xhr' | 'fetch', credentialStatus: string | boolean}): Promise<T> | undefined => {
|
||||
export const requestSentWithCredentials = <T>(args: {url?: string, resourceType: 'xhr' | 'fetch', credentialStatus: string | boolean}): Promise<T> | undefined => {
|
||||
if (args.url) {
|
||||
// If cypress is enabled on the window use that, otherwise use post message to call out to the primary cypress instance.
|
||||
// cypress may be found on the window if this is either the primary cypress instance or if a spec bridge has already been created for this spec bridge.
|
||||
|
||||
@@ -20,10 +20,10 @@ export const patchXmlHttpRequest = (window: Window) => {
|
||||
window.XMLHttpRequest.prototype.send = async function (...args) {
|
||||
try {
|
||||
// if the option is specified, communicate it to the the server to the proxy can make the request aware if it needs to potentially apply cross origin cookies
|
||||
// if the option isn't set, we can imply the default as we know the "requestedWith" in the proxy
|
||||
// if the option isn't set, we can imply the default as we know the "resourceType" in the proxy
|
||||
await requestSentWithCredentials({
|
||||
url: this._url,
|
||||
requestedWith: 'xhr',
|
||||
resourceType: 'xhr',
|
||||
credentialStatus: this.withCredentials,
|
||||
})
|
||||
} finally {
|
||||
|
||||
@@ -293,19 +293,19 @@ export class CdpAutomation {
|
||||
})
|
||||
}
|
||||
|
||||
private _continueRequest = (client, params, headers?) => {
|
||||
private _continueRequest = (client, params, header?) => {
|
||||
const details: Protocol.Fetch.ContinueRequestRequest = {
|
||||
requestId: params.requestId,
|
||||
}
|
||||
|
||||
if (headers && headers.length) {
|
||||
if (header) {
|
||||
// headers are received as an object but need to be an array
|
||||
// to modify them
|
||||
const currentHeaders = _.map(params.request.headers, (value, name) => ({ name, value }))
|
||||
|
||||
details.headers = [
|
||||
...currentHeaders,
|
||||
...headers,
|
||||
header,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -346,46 +346,26 @@ export class CdpAutomation {
|
||||
|
||||
_handlePausedRequests = async (client) => {
|
||||
// NOTE: only supported in chromium based browsers
|
||||
await client.send('Fetch.enable')
|
||||
await client.send('Fetch.enable', {
|
||||
// only enable request pausing for documents to determine the AUT iframe
|
||||
patterns: [{
|
||||
resourceType: 'Document',
|
||||
}],
|
||||
})
|
||||
|
||||
// adds a header to the request to mark it as a request for the AUT frame
|
||||
// itself, so the proxy can utilize that for injection purposes
|
||||
client.on('Fetch.requestPaused', async (params: Protocol.Fetch.RequestPausedEvent) => {
|
||||
const addedHeaders: {
|
||||
name: string
|
||||
value: string
|
||||
}[] = []
|
||||
if (await this._isAUTFrame(params.frameId)) {
|
||||
debugVerbose('add X-Cypress-Is-AUT-Frame header to: %s', params.request.url)
|
||||
|
||||
/**
|
||||
* Unlike the the web extension or Electrons's onBeforeSendHeaders, CDP can discern the difference
|
||||
* between fetch or xhr resource types. Because of this, we set X-Cypress-Is-XHR-Or-Fetch to either
|
||||
* 'xhr' or 'fetch' with CDP so the middleware can assume correct defaults in case credential/resourceTypes
|
||||
* are not sent to the server.
|
||||
* @see https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-ResourceType
|
||||
*/
|
||||
if (params.resourceType === 'XHR' || params.resourceType === 'Fetch') {
|
||||
debugVerbose('add X-Cypress-Is-XHR-Or-Fetch header to: %s', params.request.url)
|
||||
addedHeaders.push({
|
||||
name: 'X-Cypress-Is-XHR-Or-Fetch',
|
||||
value: params.resourceType.toLowerCase(),
|
||||
return this._continueRequest(client, params, {
|
||||
name: 'X-Cypress-Is-AUT-Frame',
|
||||
value: 'true',
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
// is a script, stylesheet, image, etc
|
||||
params.resourceType !== 'Document'
|
||||
|| !(await this._isAUTFrame(params.frameId))
|
||||
) {
|
||||
return this._continueRequest(client, params, addedHeaders)
|
||||
}
|
||||
|
||||
debugVerbose('add X-Cypress-Is-AUT-Frame header to: %s', params.request.url)
|
||||
addedHeaders.push({
|
||||
name: 'X-Cypress-Is-AUT-Frame',
|
||||
value: 'true',
|
||||
})
|
||||
|
||||
return this._continueRequest(client, params, addedHeaders)
|
||||
return this._continueRequest(client, params)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ import type { FoundSpec } from '@packages/types'
|
||||
import type { Server as WebSocketServer } from 'ws'
|
||||
import { RemoteStates } from './remote_states'
|
||||
import { cookieJar, SerializableAutomationCookie } from './util/cookies'
|
||||
import { requestedWithAndCredentialManager, RequestedWithAndCredentialManager } from './util/requestedWithAndCredentialManager'
|
||||
import { resourceTypeAndCredentialManager, ResourceTypeAndCredentialManager } from './util/resourceTypeAndCredentialManager'
|
||||
|
||||
const debug = Debug('cypress:server:server-base')
|
||||
|
||||
@@ -116,7 +116,7 @@ export abstract class ServerBase<TSocket extends SocketE2E | SocketCt> {
|
||||
protected request: Request
|
||||
protected isListening: boolean
|
||||
protected socketAllowed: SocketAllowed
|
||||
protected requestedWithAndCredentialManager: RequestedWithAndCredentialManager
|
||||
protected resourceTypeAndCredentialManager: ResourceTypeAndCredentialManager
|
||||
protected _fileServer: FileServer | null
|
||||
protected _baseUrl: string | null
|
||||
protected _server?: DestroyableHttpServer
|
||||
@@ -147,7 +147,7 @@ export abstract class ServerBase<TSocket extends SocketE2E | SocketCt> {
|
||||
}
|
||||
})
|
||||
|
||||
this.requestedWithAndCredentialManager = requestedWithAndCredentialManager
|
||||
this.resourceTypeAndCredentialManager = resourceTypeAndCredentialManager
|
||||
}
|
||||
|
||||
ensureProp = ensureProp
|
||||
@@ -189,7 +189,7 @@ export abstract class ServerBase<TSocket extends SocketE2E | SocketCt> {
|
||||
this.socket.toDriver('cross:origin:cookies', cookies)
|
||||
})
|
||||
|
||||
this.socket.localBus.on('request:sent:with:credentials', this.requestedWithAndCredentialManager.set)
|
||||
this.socket.localBus.on('request:sent:with:credentials', this.resourceTypeAndCredentialManager.set)
|
||||
}
|
||||
|
||||
abstract createServer (
|
||||
@@ -229,7 +229,7 @@ export abstract class ServerBase<TSocket extends SocketE2E | SocketCt> {
|
||||
this.createNetworkProxy({
|
||||
config,
|
||||
remoteStates: this._remoteStates,
|
||||
requestedWithAndCredentialManager: this.requestedWithAndCredentialManager,
|
||||
resourceTypeAndCredentialManager: this.resourceTypeAndCredentialManager,
|
||||
shouldCorrelatePreRequests,
|
||||
})
|
||||
|
||||
@@ -323,7 +323,7 @@ export abstract class ServerBase<TSocket extends SocketE2E | SocketCt> {
|
||||
return e
|
||||
}
|
||||
|
||||
createNetworkProxy ({ config, remoteStates, requestedWithAndCredentialManager, shouldCorrelatePreRequests }) {
|
||||
createNetworkProxy ({ config, remoteStates, resourceTypeAndCredentialManager, shouldCorrelatePreRequests }) {
|
||||
const getFileServerToken = () => {
|
||||
return this._fileServer?.token
|
||||
}
|
||||
@@ -340,7 +340,7 @@ export abstract class ServerBase<TSocket extends SocketE2E | SocketCt> {
|
||||
netStubbingState: this.netStubbingState,
|
||||
request: this.request,
|
||||
serverBus: this._eventBus,
|
||||
requestedWithAndCredentialManager,
|
||||
resourceTypeAndCredentialManager,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -354,7 +354,7 @@ export abstract class ServerBase<TSocket extends SocketE2E | SocketCt> {
|
||||
this.networkProxy.reset()
|
||||
this.netStubbingState.reset()
|
||||
this._remoteStates.reset()
|
||||
this.requestedWithAndCredentialManager.clear()
|
||||
this.resourceTypeAndCredentialManager.clear()
|
||||
}
|
||||
|
||||
const io = this.socket.startListening(this.server, automation, config, options)
|
||||
@@ -489,7 +489,7 @@ export abstract class ServerBase<TSocket extends SocketE2E | SocketCt> {
|
||||
|
||||
reset () {
|
||||
this._networkProxy?.reset()
|
||||
this.requestedWithAndCredentialManager.clear()
|
||||
this.resourceTypeAndCredentialManager.clear()
|
||||
const baseUrl = this._baseUrl ?? '<root>'
|
||||
|
||||
return this._remoteStates.set(baseUrl)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import md5 from 'md5'
|
||||
import Debug from 'debug'
|
||||
import type { RequestCredentialLevel, RequestedWithHeader } from '@packages/proxy'
|
||||
import type { RequestCredentialLevel } from '@packages/proxy'
|
||||
import type { ResourceType } from '@packages/net-stubbing'
|
||||
|
||||
type AppliedCredentialByUrlAndResourceMap = Map<string, Array<{
|
||||
requestedWith: RequestedWithHeader
|
||||
resourceType: ResourceType
|
||||
credentialStatus: RequestCredentialLevel
|
||||
}>>
|
||||
|
||||
@@ -16,16 +17,16 @@ const hashUrl = (url: string): string => {
|
||||
// leverage a singleton Map throughout the server to prevent clashes with this context bindings
|
||||
const _appliedCredentialByUrlAndResourceMap: AppliedCredentialByUrlAndResourceMap = new Map()
|
||||
|
||||
class RequestedWithAndCredentialManagerClass {
|
||||
get (url: string, optionalRequestedWith?: RequestedWithHeader): {
|
||||
requestedWith: RequestedWithHeader
|
||||
class ResourceTypeAndCredentialManagerClass {
|
||||
get (url: string, optionalResourceType?: ResourceType): {
|
||||
resourceType: ResourceType
|
||||
credentialStatus: RequestCredentialLevel
|
||||
} {
|
||||
const hashKey = hashUrl(url)
|
||||
|
||||
debug(`credentials request received for request url ${url}, hashKey ${hashKey}`)
|
||||
let value: {
|
||||
requestedWith: RequestedWithHeader
|
||||
resourceType: ResourceType
|
||||
credentialStatus: RequestCredentialLevel
|
||||
} | undefined
|
||||
|
||||
@@ -37,12 +38,12 @@ class RequestedWithAndCredentialManagerClass {
|
||||
debug(`credential value found ${value}`)
|
||||
}
|
||||
|
||||
// if value is undefined for any reason, apply defaults and assume xhr if no optionalRequestedWith
|
||||
// optionalRequestedWith should be provided with CDP based browsers, so at least we have a fallback that is more accurate
|
||||
// if value is undefined for any reason, apply defaults and assume xhr if no optionalResourceType
|
||||
// optionalResourceType should be provided by the prerequest resourceType, so at least we have a fallback that is more accurate
|
||||
if (value === undefined) {
|
||||
value = {
|
||||
requestedWith: optionalRequestedWith || 'xhr',
|
||||
credentialStatus: optionalRequestedWith === 'fetch' ? 'same-origin' : false,
|
||||
resourceType: optionalResourceType || 'xhr',
|
||||
credentialStatus: optionalResourceType === 'fetch' ? 'same-origin' : false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,27 +51,27 @@ class RequestedWithAndCredentialManagerClass {
|
||||
}
|
||||
|
||||
set ({ url,
|
||||
requestedWith,
|
||||
resourceType,
|
||||
credentialStatus,
|
||||
}: {
|
||||
url: string
|
||||
requestedWith: RequestedWithHeader
|
||||
resourceType: ResourceType
|
||||
credentialStatus: RequestCredentialLevel
|
||||
}) {
|
||||
const hashKey = hashUrl(url)
|
||||
|
||||
debug(`credentials request stored for request url ${url}, requestedWith ${requestedWith}, hashKey ${hashKey}`)
|
||||
debug(`credentials request stored for request url ${url}, resourceType ${resourceType}, hashKey ${hashKey}`)
|
||||
|
||||
let urlHashValue = _appliedCredentialByUrlAndResourceMap.get(hashKey)
|
||||
|
||||
if (!urlHashValue) {
|
||||
_appliedCredentialByUrlAndResourceMap.set(hashKey, [{
|
||||
requestedWith,
|
||||
resourceType,
|
||||
credentialStatus,
|
||||
}])
|
||||
} else {
|
||||
urlHashValue.push({
|
||||
requestedWith,
|
||||
resourceType,
|
||||
credentialStatus,
|
||||
})
|
||||
}
|
||||
@@ -82,7 +83,7 @@ class RequestedWithAndCredentialManagerClass {
|
||||
}
|
||||
|
||||
// export as a singleton
|
||||
export const requestedWithAndCredentialManager = new RequestedWithAndCredentialManagerClass()
|
||||
export const resourceTypeAndCredentialManager = new ResourceTypeAndCredentialManagerClass()
|
||||
|
||||
// export but only as a type. We do NOT want others to create instances of the class as it is intended to be a singleton
|
||||
export type RequestedWithAndCredentialManager = RequestedWithAndCredentialManagerClass
|
||||
export type ResourceTypeAndCredentialManager = ResourceTypeAndCredentialManagerClass
|
||||
@@ -399,10 +399,14 @@ describe('lib/browsers/chrome', () => {
|
||||
this.pageCriClient.send.withArgs('Page.getFrameTree').resolves(frameTree)
|
||||
})
|
||||
|
||||
it('sends Fetch.enable', async function () {
|
||||
it('sends Fetch.enable only for Document ResourceType', async function () {
|
||||
await chrome.open('chrome', 'http://', openOpts, this.automation)
|
||||
|
||||
expect(this.pageCriClient.send).to.have.been.calledWith('Fetch.enable')
|
||||
expect(this.pageCriClient.send).to.have.been.calledWith('Fetch.enable', {
|
||||
patterns: [{
|
||||
resourceType: 'Document',
|
||||
}],
|
||||
})
|
||||
})
|
||||
|
||||
it('does not add header when not a document', async function () {
|
||||
@@ -413,9 +417,7 @@ describe('lib/browsers/chrome', () => {
|
||||
resourceType: 'Script',
|
||||
})
|
||||
|
||||
expect(this.pageCriClient.send).to.be.calledWith('Fetch.continueRequest', {
|
||||
requestId: '1234',
|
||||
})
|
||||
expect(this.pageCriClient.send).not.to.be.calledWith('Fetch.continueRequest')
|
||||
})
|
||||
|
||||
it('does not add header when it is a spec frame request', async function () {
|
||||
@@ -469,70 +471,6 @@ describe('lib/browsers/chrome', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('appends X-Cypress-Is-XHR-Or-Fetch header to fetch request', async function () {
|
||||
await chrome.open('chrome', 'http://', openOpts, this.automation)
|
||||
|
||||
this.pageCriClient.on.withArgs('Page.frameAttached').yield()
|
||||
|
||||
await this.pageCriClient.on.withArgs('Fetch.requestPaused').args[0][1]({
|
||||
frameId: 'aut-frame-id',
|
||||
requestId: '1234',
|
||||
resourceType: 'Fetch',
|
||||
request: {
|
||||
url: 'http://localhost:3000/test-request',
|
||||
headers: {
|
||||
'X-Foo': 'Bar',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(this.pageCriClient.send).to.be.calledWith('Fetch.continueRequest', {
|
||||
requestId: '1234',
|
||||
headers: [
|
||||
{
|
||||
name: 'X-Foo',
|
||||
value: 'Bar',
|
||||
},
|
||||
{
|
||||
name: 'X-Cypress-Is-XHR-Or-Fetch',
|
||||
value: 'fetch',
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it('appends X-Cypress-Is-XHR-Or-Fetch header to xhr request', async function () {
|
||||
await chrome.open('chrome', 'http://', openOpts, this.automation)
|
||||
|
||||
this.pageCriClient.on.withArgs('Page.frameAttached').yield()
|
||||
|
||||
await this.pageCriClient.on.withArgs('Fetch.requestPaused').args[0][1]({
|
||||
frameId: 'aut-frame-id',
|
||||
requestId: '1234',
|
||||
resourceType: 'XHR',
|
||||
request: {
|
||||
url: 'http://localhost:3000/test-request',
|
||||
headers: {
|
||||
'X-Foo': 'Bar',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(this.pageCriClient.send).to.be.calledWith('Fetch.continueRequest', {
|
||||
requestId: '1234',
|
||||
headers: [
|
||||
{
|
||||
name: 'X-Foo',
|
||||
value: 'Bar',
|
||||
},
|
||||
{
|
||||
name: 'X-Cypress-Is-XHR-Or-Fetch',
|
||||
value: 'xhr',
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it('gets frame tree on Page.frameAttached', async function () {
|
||||
await chrome.open('chrome', 'http://', openOpts, this.automation)
|
||||
|
||||
|
||||
@@ -370,10 +370,14 @@ describe('lib/browsers/electron', () => {
|
||||
this.pageCriClient.send.withArgs('Page.getFrameTree').resolves(frameTree)
|
||||
})
|
||||
|
||||
it('sends Fetch.enable', async function () {
|
||||
it('sends Fetch.enable only for Document ResourceType', async function () {
|
||||
await electron._launch(this.win, this.url, this.automation, this.options)
|
||||
|
||||
expect(this.pageCriClient.send).to.have.been.calledWith('Fetch.enable')
|
||||
expect(this.pageCriClient.send).to.have.been.calledWith('Fetch.enable', {
|
||||
patterns: [{
|
||||
resourceType: 'Document',
|
||||
}],
|
||||
})
|
||||
})
|
||||
|
||||
it('does not add header when not a document', async function () {
|
||||
@@ -384,9 +388,7 @@ describe('lib/browsers/electron', () => {
|
||||
resourceType: 'Script',
|
||||
})
|
||||
|
||||
expect(this.pageCriClient.send).to.be.calledWith('Fetch.continueRequest', {
|
||||
requestId: '1234',
|
||||
})
|
||||
expect(this.pageCriClient.send).not.to.be.calledWith('Fetch.continueRequest')
|
||||
})
|
||||
|
||||
it('does not add header when it is a spec frame request', async function () {
|
||||
@@ -440,70 +442,6 @@ describe('lib/browsers/electron', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('appends X-Cypress-Is-XHR-Or-Fetch header to fetch request', async function () {
|
||||
await electron._launch(this.win, this.url, this.automation, this.options)
|
||||
|
||||
this.pageCriClient.on.withArgs('Page.frameAttached').yield()
|
||||
|
||||
await this.pageCriClient.on.withArgs('Fetch.requestPaused').args[0][1]({
|
||||
frameId: 'aut-frame-id',
|
||||
requestId: '1234',
|
||||
resourceType: 'Fetch',
|
||||
request: {
|
||||
url: 'http://localhost:3000/test-request',
|
||||
headers: {
|
||||
'X-Foo': 'Bar',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(this.pageCriClient.send).to.be.calledWith('Fetch.continueRequest', {
|
||||
requestId: '1234',
|
||||
headers: [
|
||||
{
|
||||
name: 'X-Foo',
|
||||
value: 'Bar',
|
||||
},
|
||||
{
|
||||
name: 'X-Cypress-Is-XHR-Or-Fetch',
|
||||
value: 'fetch',
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it('appends X-Cypress-Is-XHR-Or-Fetch header to xhr request', async function () {
|
||||
await electron._launch(this.win, this.url, this.automation, this.options)
|
||||
|
||||
this.pageCriClient.on.withArgs('Page.frameAttached').yield()
|
||||
|
||||
await this.pageCriClient.on.withArgs('Fetch.requestPaused').args[0][1]({
|
||||
frameId: 'aut-frame-id',
|
||||
requestId: '1234',
|
||||
resourceType: 'XHR',
|
||||
request: {
|
||||
url: 'http://localhost:3000/test-request',
|
||||
headers: {
|
||||
'X-Foo': 'Bar',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(this.pageCriClient.send).to.be.calledWith('Fetch.continueRequest', {
|
||||
requestId: '1234',
|
||||
headers: [
|
||||
{
|
||||
name: 'X-Foo',
|
||||
value: 'Bar',
|
||||
},
|
||||
{
|
||||
name: 'X-Cypress-Is-XHR-Or-Fetch',
|
||||
value: 'xhr',
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it('gets frame tree on Page.frameAttached', async function () {
|
||||
await electron._launch(this.win, this.url, this.automation, this.options)
|
||||
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import { expect } from 'chai'
|
||||
import { requestedWithAndCredentialManager } from '../../../lib/util/requestedWithAndCredentialManager'
|
||||
|
||||
context('requestedWithAndCredentialManager Singleton', () => {
|
||||
beforeEach(() => {
|
||||
requestedWithAndCredentialManager.clear()
|
||||
requestedWithAndCredentialManager.set({
|
||||
url: 'www.foobar.com/test-request',
|
||||
requestedWith: 'xhr',
|
||||
credentialStatus: true,
|
||||
})
|
||||
|
||||
requestedWithAndCredentialManager.set({
|
||||
url: 'www.foobar.com%2Ftest-request-2',
|
||||
requestedWith: 'fetch',
|
||||
credentialStatus: 'same-origin',
|
||||
})
|
||||
|
||||
requestedWithAndCredentialManager.set({
|
||||
url: 'www.foobar.com/test-request-2',
|
||||
requestedWith: 'fetch',
|
||||
credentialStatus: 'include',
|
||||
})
|
||||
|
||||
requestedWithAndCredentialManager.set({
|
||||
url: 'www.foobar.com/test-request',
|
||||
requestedWith: 'fetch',
|
||||
credentialStatus: 'omit',
|
||||
})
|
||||
|
||||
requestedWithAndCredentialManager.set({
|
||||
url: 'www.foobar.com/test-request',
|
||||
requestedWith: 'fetch',
|
||||
credentialStatus: 'include',
|
||||
})
|
||||
})
|
||||
|
||||
it('gets the first record out of the queue matching the absolute url and removes it', () => {
|
||||
expect(requestedWithAndCredentialManager.get('www.foobar.com/test-request')).to.deep.equal({
|
||||
requestedWith: 'xhr',
|
||||
credentialStatus: true,
|
||||
})
|
||||
|
||||
expect(requestedWithAndCredentialManager.get('www.foobar.com/test-request')).to.deep.equal({
|
||||
requestedWith: 'fetch',
|
||||
credentialStatus: 'omit',
|
||||
})
|
||||
|
||||
expect(requestedWithAndCredentialManager.get('www.foobar.com/test-request')).to.deep.equal({
|
||||
requestedWith: 'fetch',
|
||||
credentialStatus: 'include',
|
||||
})
|
||||
|
||||
// the default as no other records should exist in the map for this URL
|
||||
expect(requestedWithAndCredentialManager.get('www.foobar.com/test-request')).to.deep.equal({
|
||||
requestedWith: 'xhr',
|
||||
credentialStatus: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('can locate a record hash even when the URL is encoded', () => {
|
||||
expect(requestedWithAndCredentialManager.get('www.foobar.com%2Ftest-request')).to.deep.equal({
|
||||
requestedWith: 'xhr',
|
||||
credentialStatus: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('applies defaults if a record cannot be found without a requestedWith', () => {
|
||||
expect(requestedWithAndCredentialManager.get('www.barbaz.com/test-request')).to.deep.equal({
|
||||
requestedWith: 'xhr',
|
||||
credentialStatus: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('applies defaults if a record cannot be found with a requestedWith', () => {
|
||||
expect(requestedWithAndCredentialManager.get('www.barbaz.com/test-request', 'xhr')).to.deep.equal({
|
||||
requestedWith: 'xhr',
|
||||
credentialStatus: false,
|
||||
})
|
||||
|
||||
expect(requestedWithAndCredentialManager.get('www.barbaz.com/test-request', 'fetch')).to.deep.equal({
|
||||
requestedWith: 'fetch',
|
||||
credentialStatus: 'same-origin',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,86 @@
|
||||
import { expect } from 'chai'
|
||||
import { resourceTypeAndCredentialManager } from '../../../lib/util/resourceTypeAndCredentialManager'
|
||||
|
||||
context('resourceTypeAndCredentialManager Singleton', () => {
|
||||
beforeEach(() => {
|
||||
resourceTypeAndCredentialManager.clear()
|
||||
resourceTypeAndCredentialManager.set({
|
||||
url: 'www.foobar.com/test-request',
|
||||
resourceType: 'xhr',
|
||||
credentialStatus: true,
|
||||
})
|
||||
|
||||
resourceTypeAndCredentialManager.set({
|
||||
url: 'www.foobar.com%2Ftest-request-2',
|
||||
resourceType: 'fetch',
|
||||
credentialStatus: 'same-origin',
|
||||
})
|
||||
|
||||
resourceTypeAndCredentialManager.set({
|
||||
url: 'www.foobar.com/test-request-2',
|
||||
resourceType: 'fetch',
|
||||
credentialStatus: 'include',
|
||||
})
|
||||
|
||||
resourceTypeAndCredentialManager.set({
|
||||
url: 'www.foobar.com/test-request',
|
||||
resourceType: 'fetch',
|
||||
credentialStatus: 'omit',
|
||||
})
|
||||
|
||||
resourceTypeAndCredentialManager.set({
|
||||
url: 'www.foobar.com/test-request',
|
||||
resourceType: 'fetch',
|
||||
credentialStatus: 'include',
|
||||
})
|
||||
})
|
||||
|
||||
it('gets the first record out of the queue matching the absolute url and removes it', () => {
|
||||
expect(resourceTypeAndCredentialManager.get('www.foobar.com/test-request')).to.deep.equal({
|
||||
resourceType: 'xhr',
|
||||
credentialStatus: true,
|
||||
})
|
||||
|
||||
expect(resourceTypeAndCredentialManager.get('www.foobar.com/test-request')).to.deep.equal({
|
||||
resourceType: 'fetch',
|
||||
credentialStatus: 'omit',
|
||||
})
|
||||
|
||||
expect(resourceTypeAndCredentialManager.get('www.foobar.com/test-request')).to.deep.equal({
|
||||
resourceType: 'fetch',
|
||||
credentialStatus: 'include',
|
||||
})
|
||||
|
||||
// the default as no other records should exist in the map for this URL
|
||||
expect(resourceTypeAndCredentialManager.get('www.foobar.com/test-request')).to.deep.equal({
|
||||
resourceType: 'xhr',
|
||||
credentialStatus: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('can locate a record hash even when the URL is encoded', () => {
|
||||
expect(resourceTypeAndCredentialManager.get('www.foobar.com%2Ftest-request')).to.deep.equal({
|
||||
resourceType: 'xhr',
|
||||
credentialStatus: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('applies defaults if a record cannot be found without a resourceType', () => {
|
||||
expect(resourceTypeAndCredentialManager.get('www.barbaz.com/test-request')).to.deep.equal({
|
||||
resourceType: 'xhr',
|
||||
credentialStatus: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('applies defaults if a record cannot be found with a resourceType', () => {
|
||||
expect(resourceTypeAndCredentialManager.get('www.barbaz.com/test-request', 'xhr')).to.deep.equal({
|
||||
resourceType: 'xhr',
|
||||
credentialStatus: false,
|
||||
})
|
||||
|
||||
expect(resourceTypeAndCredentialManager.get('www.barbaz.com/test-request', 'fetch')).to.deep.equal({
|
||||
resourceType: 'fetch',
|
||||
credentialStatus: 'same-origin',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -4435,7 +4435,7 @@
|
||||
"./packages/server/lib/util/print-run.ts",
|
||||
"./packages/server/lib/util/proxy.ts",
|
||||
"./packages/server/lib/util/random.js",
|
||||
"./packages/server/lib/util/requestedWithAndCredentialManager.ts",
|
||||
"./packages/server/lib/util/resourceTypeAndCredentialManager.ts",
|
||||
"./packages/server/lib/util/server_destroy.ts",
|
||||
"./packages/server/lib/util/shell.js",
|
||||
"./packages/server/lib/util/socket_allowed.ts",
|
||||
|
||||
@@ -4434,7 +4434,7 @@
|
||||
"./packages/server/lib/util/print-run.ts",
|
||||
"./packages/server/lib/util/proxy.ts",
|
||||
"./packages/server/lib/util/random.js",
|
||||
"./packages/server/lib/util/requestedWithAndCredentialManager.ts",
|
||||
"./packages/server/lib/util/resourceTypeAndCredentialManager.ts",
|
||||
"./packages/server/lib/util/server_destroy.ts",
|
||||
"./packages/server/lib/util/shell.js",
|
||||
"./packages/server/lib/util/socket_allowed.ts",
|
||||
|
||||
@@ -4434,7 +4434,7 @@
|
||||
"./packages/server/lib/util/print-run.ts",
|
||||
"./packages/server/lib/util/proxy.ts",
|
||||
"./packages/server/lib/util/random.js",
|
||||
"./packages/server/lib/util/requestedWithAndCredentialManager.ts",
|
||||
"./packages/server/lib/util/resourceTypeAndCredentialManager.ts",
|
||||
"./packages/server/lib/util/server_destroy.ts",
|
||||
"./packages/server/lib/util/shell.js",
|
||||
"./packages/server/lib/util/socket_allowed.ts",
|
||||
|
||||
Reference in New Issue
Block a user