mirror of
https://github.com/cypress-io/cypress.git
synced 2026-02-09 16:50:23 -06:00
feat: add resource type header to CDP, extension, and electron (#23821)
* feat: add X-Cypress-Request header in extension * feat: add X-Cypress-Request header in CDP * feat: add X-Cypress-Request header in electron * feat: add ExtractRequestedWithAndCredentialsIfApplicable middleware stub to remove the newly added x-cypress-request header * chore: change defaultHeaders variable name to requestModifications to more accurately reflect usage * chore: condense ExtractIsAUTFrameHeader and ExtractRequestedWithAndCredentialsIfApplicable into ExtractCypressMetadataHeaders middleware * test: add anti assertion for x-cypress-request and remove setting request verbage (as it does nothing yet)
This commit is contained in:
@@ -61,6 +61,16 @@ 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 || []),
|
||||
...(details.type === 'xmlhttprequest' ? [{
|
||||
name: 'X-Cypress-Request',
|
||||
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
|
||||
@@ -69,11 +79,11 @@ const connect = function (host, path, extraOpts) {
|
||||
|| details.type !== 'sub_frame'
|
||||
// is the spec frame, not the AUT
|
||||
|| details.url.includes('__cypress')
|
||||
) return
|
||||
) return requestModifications
|
||||
|
||||
return {
|
||||
requestHeaders: [
|
||||
...details.requestHeaders,
|
||||
...requestModifications.requestHeaders,
|
||||
{
|
||||
name: 'X-Cypress-Is-AUT-Frame',
|
||||
value: 'true',
|
||||
|
||||
@@ -297,7 +297,7 @@ describe('app/background', () => {
|
||||
|
||||
const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details)
|
||||
|
||||
expect(result).to.be.undefined
|
||||
expect(result).to.deep.equal({ requestHeaders: [] })
|
||||
})
|
||||
|
||||
it('does not add header if it is a nested frame', async function () {
|
||||
@@ -311,7 +311,7 @@ describe('app/background', () => {
|
||||
|
||||
const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details)
|
||||
|
||||
expect(result).to.be.undefined
|
||||
expect(result).to.deep.equal({ requestHeaders: [] })
|
||||
})
|
||||
|
||||
it('does not add header if it is not a sub frame request', async function () {
|
||||
@@ -326,7 +326,7 @@ describe('app/background', () => {
|
||||
|
||||
const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details)
|
||||
|
||||
expect(result).to.be.undefined
|
||||
expect(result).to.deep.equal({ requestHeaders: [] })
|
||||
})
|
||||
|
||||
it('does not add header if it is a spec frame request', async function () {
|
||||
@@ -341,7 +341,7 @@ describe('app/background', () => {
|
||||
await this.connect(withExperimentalFlagOn)
|
||||
const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details)
|
||||
|
||||
expect(result).to.be.undefined
|
||||
expect(result).to.deep.equal({ requestHeaders: [] })
|
||||
})
|
||||
|
||||
it('appends X-Cypress-Is-AUT-Frame header to AUT iframe request', async function () {
|
||||
@@ -373,6 +373,60 @@ describe('app/background', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('appends X-Cypress-Request 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(withExperimentalFlagOn)
|
||||
const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details)
|
||||
|
||||
expect(result).to.deep.equal({
|
||||
requestHeaders: [
|
||||
{
|
||||
name: 'X-Foo',
|
||||
value: 'Bar',
|
||||
},
|
||||
{
|
||||
name: 'X-Cypress-Request',
|
||||
value: 'true',
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it('does not append X-Cypress-Request 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(withExperimentalFlagOn)
|
||||
const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details)
|
||||
|
||||
expect(result).to.not.deep.equal({
|
||||
requestHeaders: [
|
||||
{
|
||||
name: 'X-Cypress-Request',
|
||||
value: 'true',
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it('does not add before-headers listener if in non-Firefox browser', async function () {
|
||||
browser.runtime.getBrowserInfo = undefined
|
||||
|
||||
|
||||
@@ -21,13 +21,18 @@ const LogRequest: RequestMiddleware = function () {
|
||||
this.next()
|
||||
}
|
||||
|
||||
const ExtractIsAUTFrameHeader: RequestMiddleware = function () {
|
||||
const ExtractCypressMetadataHeaders: RequestMiddleware = function () {
|
||||
this.req.isAUTFrame = !!this.req.headers['x-cypress-is-aut-frame']
|
||||
|
||||
if (this.req.headers['x-cypress-is-aut-frame']) {
|
||||
delete this.req.headers['x-cypress-is-aut-frame']
|
||||
}
|
||||
|
||||
if (this.req.headers['x-cypress-request']) {
|
||||
this.debug(`found x-cypress-request header. Deleting x-cypress-request header.`)
|
||||
delete this.req.headers['x-cypress-request']
|
||||
}
|
||||
|
||||
this.next()
|
||||
}
|
||||
|
||||
@@ -247,7 +252,7 @@ const SendRequestOutgoing: RequestMiddleware = function () {
|
||||
|
||||
export default {
|
||||
LogRequest,
|
||||
ExtractIsAUTFrameHeader,
|
||||
ExtractCypressMetadataHeaders,
|
||||
MaybeSimulateSecHeaders,
|
||||
MaybeAttachCrossOriginCookies,
|
||||
MaybeEndRequestWithBufferedResponse,
|
||||
|
||||
@@ -11,7 +11,7 @@ describe('http/request-middleware', () => {
|
||||
it('exports the members in the correct order', () => {
|
||||
expect(_.keys(RequestMiddleware)).to.have.ordered.members([
|
||||
'LogRequest',
|
||||
'ExtractIsAUTFrameHeader',
|
||||
'ExtractCypressMetadataHeaders',
|
||||
'MaybeSimulateSecHeaders',
|
||||
'MaybeAttachCrossOriginCookies',
|
||||
'MaybeEndRequestWithBufferedResponse',
|
||||
@@ -26,8 +26,8 @@ describe('http/request-middleware', () => {
|
||||
])
|
||||
})
|
||||
|
||||
describe('ExtractIsAUTFrameHeader', () => {
|
||||
const { ExtractIsAUTFrameHeader } = RequestMiddleware
|
||||
describe('ExtractCypressMetadataHeaders', () => {
|
||||
const { ExtractCypressMetadataHeaders } = RequestMiddleware
|
||||
|
||||
it('removes x-cypress-is-aut-frame header when it exists, sets in on the req', async () => {
|
||||
const ctx = {
|
||||
@@ -38,7 +38,7 @@ describe('http/request-middleware', () => {
|
||||
} as Partial<CypressIncomingRequest>,
|
||||
}
|
||||
|
||||
await testMiddleware([ExtractIsAUTFrameHeader], ctx)
|
||||
await testMiddleware([ExtractCypressMetadataHeaders], ctx)
|
||||
.then(() => {
|
||||
expect(ctx.req.headers['x-cypress-is-aut-frame']).not.to.exist
|
||||
expect(ctx.req.isAUTFrame).to.be.true
|
||||
@@ -52,12 +52,40 @@ describe('http/request-middleware', () => {
|
||||
} as Partial<CypressIncomingRequest>,
|
||||
}
|
||||
|
||||
await testMiddleware([ExtractIsAUTFrameHeader], ctx)
|
||||
await testMiddleware([ExtractCypressMetadataHeaders], ctx)
|
||||
.then(() => {
|
||||
expect(ctx.req.headers['x-cypress-is-aut-frame']).not.to.exist
|
||||
expect(ctx.req.isAUTFrame).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
it('removes x-cypress-request header when it exists', async () => {
|
||||
const ctx = {
|
||||
req: {
|
||||
headers: {
|
||||
'x-cypress-request': 'true',
|
||||
},
|
||||
} as Partial<CypressIncomingRequest>,
|
||||
}
|
||||
|
||||
await testMiddleware([ExtractCypressMetadataHeaders], ctx)
|
||||
.then(() => {
|
||||
expect(ctx.req.headers['x-cypress-request']).not.to.exist
|
||||
})
|
||||
})
|
||||
|
||||
it('removes x-cypress-request header when it does not exist', async () => {
|
||||
const ctx = {
|
||||
req: {
|
||||
headers: {},
|
||||
} as Partial<CypressIncomingRequest>,
|
||||
}
|
||||
|
||||
await testMiddleware([ExtractCypressMetadataHeaders], ctx)
|
||||
.then(() => {
|
||||
expect(ctx.req.headers['x-cypress-request']).not.to.exist
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('MaybeSimulateSecHeaders', () => {
|
||||
|
||||
@@ -342,19 +342,19 @@ const _listenForFrameTreeChanges = (client) => {
|
||||
client.on('Page.frameDetached', _updateFrameTree(client, 'Page.frameDetached'))
|
||||
}
|
||||
|
||||
const _continueRequest = (client, params, header?) => {
|
||||
const _continueRequest = (client, params, headers?) => {
|
||||
const details: Protocol.Fetch.ContinueRequestRequest = {
|
||||
requestId: params.requestId,
|
||||
}
|
||||
|
||||
if (header) {
|
||||
if (headers && headers.length) {
|
||||
// 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,
|
||||
header,
|
||||
...headers,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -403,20 +403,34 @@ const _handlePausedRequests = async (client) => {
|
||||
// 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 (params.resourceType === 'XHR' || params.resourceType === 'Fetch') {
|
||||
debug('add X-Cypress-Request header to: %s', params.request.url)
|
||||
addedHeaders.push({
|
||||
name: 'X-Cypress-Request',
|
||||
value: params.resourceType.toLowerCase(),
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
// is a script, stylesheet, image, etc
|
||||
params.resourceType !== 'Document'
|
||||
|| !(await _isAUTFrame(params.frameId))
|
||||
) {
|
||||
return _continueRequest(client, params)
|
||||
return _continueRequest(client, params, addedHeaders)
|
||||
}
|
||||
|
||||
debug('add X-Cypress-Is-AUT-Frame header to: %s', params.request.url)
|
||||
|
||||
_continueRequest(client, params, {
|
||||
addedHeaders.push({
|
||||
name: 'X-Cypress-Is-AUT-Frame',
|
||||
value: 'true',
|
||||
})
|
||||
|
||||
return _continueRequest(client, params, addedHeaders)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -384,6 +384,15 @@ export = {
|
||||
// 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
|
||||
win.webContents.session.webRequest.onBeforeSendHeaders((details, cb) => {
|
||||
const requestModifications = {
|
||||
requestHeaders: {
|
||||
...details.requestHeaders,
|
||||
...(details.resourceType === 'xhr') ? {
|
||||
'X-Cypress-Request': 'true',
|
||||
} : {},
|
||||
},
|
||||
}
|
||||
|
||||
if (
|
||||
// isn't an iframe
|
||||
details.resourceType !== 'subFrame'
|
||||
@@ -392,14 +401,14 @@ export = {
|
||||
// is the spec frame, not the AUT
|
||||
|| details.url.includes('__cypress')
|
||||
) {
|
||||
cb({})
|
||||
cb(requestModifications)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
cb({
|
||||
requestHeaders: {
|
||||
...details.requestHeaders,
|
||||
...requestModifications.requestHeaders,
|
||||
'X-Cypress-Is-AUT-Frame': 'true',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -464,6 +464,70 @@ describe('lib/browsers/chrome', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('appends X-Cypress-Request header to fetch request', async function () {
|
||||
await chrome.open('chrome', 'http://', withExperimentalFlagOn, 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-Request',
|
||||
value: 'fetch',
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it('appends X-Cypress-Request header to xhr request', async function () {
|
||||
await chrome.open('chrome', 'http://', withExperimentalFlagOn, 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-Request',
|
||||
value: 'xhr',
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it('gets frame tree on Page.frameAttached', async function () {
|
||||
await chrome.open('chrome', 'http://', withExperimentalFlagOn, this.automation)
|
||||
|
||||
|
||||
@@ -358,7 +358,9 @@ describe('lib/browsers/electron', () => {
|
||||
this.win.webContents.session.webRequest.onBeforeSendHeaders.lastCall.args[0](details, cb)
|
||||
|
||||
expect(cb).to.be.calledOnce
|
||||
expect(cb).to.be.calledWith({})
|
||||
expect(cb).to.be.calledWith({
|
||||
requestHeaders: {},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -378,7 +380,9 @@ describe('lib/browsers/electron', () => {
|
||||
this.win.webContents.session.webRequest.onBeforeSendHeaders.lastCall.args[0](details, cb)
|
||||
|
||||
expect(cb).to.be.calledOnce
|
||||
expect(cb).to.be.calledWith({})
|
||||
expect(cb).to.be.calledWith({
|
||||
requestHeaders: {},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -402,7 +406,9 @@ describe('lib/browsers/electron', () => {
|
||||
this.win.webContents.session.webRequest.onBeforeSendHeaders.lastCall.args[0](details, cb)
|
||||
|
||||
expect(cb).to.be.calledOnce
|
||||
expect(cb).to.be.calledWith({})
|
||||
expect(cb).to.be.calledWith({
|
||||
requestHeaders: {},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -424,7 +430,9 @@ describe('lib/browsers/electron', () => {
|
||||
|
||||
this.win.webContents.session.webRequest.onBeforeSendHeaders.lastCall.args[0](details, cb)
|
||||
|
||||
expect(cb).to.be.calledWith({})
|
||||
expect(cb).to.be.calledWith({
|
||||
requestHeaders: {},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -441,7 +449,9 @@ describe('lib/browsers/electron', () => {
|
||||
|
||||
this.win.webContents.session.webRequest.onBeforeSendHeaders.lastCall.args[0](details, cb)
|
||||
|
||||
expect(cb).to.be.calledWith({})
|
||||
expect(cb).to.be.calledWith({
|
||||
requestHeaders: {},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -475,6 +485,37 @@ describe('lib/browsers/electron', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('adds X-Cypress-Request header if xhr request (includes fetch)', function () {
|
||||
sinon.stub(this.win.webContents.session.webRequest, 'onBeforeSendHeaders')
|
||||
|
||||
return electron._launch(this.win, this.url, this.automation, this.options)
|
||||
.then(() => {
|
||||
const details = {
|
||||
resourceType: 'xhr',
|
||||
frame: {
|
||||
parent: {
|
||||
parent: null,
|
||||
},
|
||||
},
|
||||
url: 'http://localhost:3000/test-request',
|
||||
requestHeaders: {
|
||||
'X-Foo': 'Bar',
|
||||
},
|
||||
}
|
||||
const cb = sinon.stub()
|
||||
|
||||
this.win.webContents.session.webRequest.onBeforeSendHeaders.lastCall.args[0](details, cb)
|
||||
|
||||
expect(cb).to.be.calledOnce
|
||||
expect(cb).to.be.calledWith({
|
||||
requestHeaders: {
|
||||
'X-Foo': 'Bar',
|
||||
'X-Cypress-Request': 'true',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user