mirror of
https://github.com/cypress-io/cypress.git
synced 2026-02-10 09:10:12 -06:00
feat: proxy logging (#16730)
Co-authored-by: Chris Breiding <chrisbreiding@users.noreply.github.com>
This commit is contained in:
1
cli/types/cypress.d.ts
vendored
1
cli/types/cypress.d.ts
vendored
@@ -5517,6 +5517,7 @@ declare namespace Cypress {
|
||||
|
||||
interface Log {
|
||||
end(): Log
|
||||
error(error: Error): Log
|
||||
finish(): void
|
||||
get<K extends keyof LogConfig>(attr: K): LogConfig[K]
|
||||
get(): LogConfig
|
||||
|
||||
@@ -564,63 +564,6 @@ describe('network stubbing', { retries: 2 }, function () {
|
||||
})
|
||||
})
|
||||
|
||||
it('has displayName req for spies', function () {
|
||||
cy.intercept('/foo*').as('getFoo')
|
||||
.then(() => {
|
||||
$.get('/foo')
|
||||
})
|
||||
.wait('@getFoo')
|
||||
.then(() => {
|
||||
const log = _.last(cy.queue.logs()) as any
|
||||
|
||||
expect(log.get('displayName')).to.eq('req')
|
||||
})
|
||||
})
|
||||
|
||||
it('has displayName req stub for stubs', function () {
|
||||
cy.intercept('/foo*', { body: 'foo' }).as('getFoo')
|
||||
.then(() => {
|
||||
$.get('/foo')
|
||||
})
|
||||
.wait('@getFoo')
|
||||
.then(() => {
|
||||
const log = _.last(cy.queue.logs()) as any
|
||||
|
||||
expect(log.get('displayName')).to.eq('req stub')
|
||||
})
|
||||
})
|
||||
|
||||
it('has displayName req fn for request handlers', function () {
|
||||
cy.intercept('/foo*', () => {}).as('getFoo')
|
||||
.then(() => {
|
||||
$.get('/foo')
|
||||
})
|
||||
.wait('@getFoo')
|
||||
.then(() => {
|
||||
const log = _.last(cy.queue.logs()) as any
|
||||
|
||||
expect(log.get('displayName')).to.eq('req fn')
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: implement log niceties
|
||||
it.skip('#consoleProps', function () {
|
||||
cy.intercept('*', {
|
||||
foo: 'bar',
|
||||
}).as('foo').then(function () {
|
||||
expect(this.lastLog.invoke('consoleProps')).to.deep.eq({
|
||||
Command: 'route',
|
||||
Method: 'GET',
|
||||
URL: '*',
|
||||
Status: 200,
|
||||
Response: {
|
||||
foo: 'bar',
|
||||
},
|
||||
Alias: 'foo',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('numResponses', function () {
|
||||
it('is initially 0', function () {
|
||||
cy.intercept(/foo/, {}).then(() => {
|
||||
@@ -3241,7 +3184,7 @@ describe('network stubbing', { retries: 2 }, function () {
|
||||
$.get('/fixtures/app.json')
|
||||
}).wait('@getFoo').then(function (res) {
|
||||
const log = cy.queue.logs({
|
||||
displayName: 'req',
|
||||
displayName: 'xhr',
|
||||
})[0]
|
||||
|
||||
expect(log.get('alias')).to.eq('getFoo')
|
||||
|
||||
@@ -794,7 +794,7 @@ describe('src/cy/commands/xhr', () => {
|
||||
this.logs = []
|
||||
|
||||
cy.on('log:added', (attrs, log) => {
|
||||
if (attrs.name === 'xhr') {
|
||||
if (['xhr', 'request'].includes(attrs.name)) {
|
||||
this.lastLog = log
|
||||
this.logs.push(log)
|
||||
}
|
||||
@@ -817,13 +817,15 @@ describe('src/cy/commands/xhr', () => {
|
||||
|
||||
expect(lastLog.pick('name', 'displayName', 'event', 'alias', 'aliasType', 'state')).to.deep.eq({
|
||||
name: 'xhr',
|
||||
displayName: 'xhr stub',
|
||||
displayName: 'xhr',
|
||||
event: true,
|
||||
alias: 'getFoo',
|
||||
aliasType: 'route',
|
||||
state: 'pending',
|
||||
})
|
||||
|
||||
expect(lastLog.get('renderProps')()).to.include({ wentToOrigin: false })
|
||||
|
||||
const snapshots = lastLog.get('snapshots')
|
||||
|
||||
expect(snapshots.length).to.eq(1)
|
||||
@@ -846,13 +848,15 @@ describe('src/cy/commands/xhr', () => {
|
||||
|
||||
expect(lastLog.pick('name', 'displayName', 'event', 'alias', 'aliasType', 'state')).to.deep.eq({
|
||||
name: 'xhr',
|
||||
displayName: 'xhr stub',
|
||||
displayName: 'xhr',
|
||||
event: true,
|
||||
alias: 'getFoo',
|
||||
aliasType: 'route',
|
||||
state: 'pending',
|
||||
})
|
||||
|
||||
expect(lastLog.get('renderProps')()).to.include({ wentToOrigin: false })
|
||||
|
||||
const snapshots = lastLog.get('snapshots')
|
||||
|
||||
expect(snapshots.length).to.eq(1)
|
||||
@@ -971,7 +975,7 @@ describe('src/cy/commands/xhr', () => {
|
||||
it('logs obj', function () {
|
||||
const obj = {
|
||||
name: 'xhr',
|
||||
displayName: 'xhr stub',
|
||||
displayName: 'xhr',
|
||||
event: true,
|
||||
message: '',
|
||||
type: 'parent',
|
||||
@@ -1012,7 +1016,7 @@ describe('src/cy/commands/xhr', () => {
|
||||
this.logs = []
|
||||
|
||||
cy.on('log:added', (attrs, log) => {
|
||||
if (attrs.name === 'xhr') {
|
||||
if (['xhr', 'request'].includes(attrs.name)) {
|
||||
this.lastLog = log
|
||||
this.logs.push(log)
|
||||
}
|
||||
@@ -1026,7 +1030,7 @@ describe('src/cy/commands/xhr', () => {
|
||||
const { lastLog } = this
|
||||
|
||||
expect(this.logs.length).to.eq(1)
|
||||
expect(lastLog.get('name')).to.eq('xhr')
|
||||
expect(lastLog.get('name')).to.eq('request')
|
||||
expect(lastLog.get('error').message).contain('foo is not defined')
|
||||
|
||||
done()
|
||||
@@ -1049,7 +1053,7 @@ describe('src/cy/commands/xhr', () => {
|
||||
const { lastLog } = this
|
||||
|
||||
expect(this.logs.length).to.eq(1)
|
||||
expect(lastLog.get('name')).to.eq('xhr')
|
||||
expect(lastLog.get('name')).to.eq('request')
|
||||
expect(err.message).to.include(lastLog.get('error').message)
|
||||
expect(err.message).to.include(e.message)
|
||||
|
||||
@@ -1203,7 +1207,7 @@ describe('src/cy/commands/xhr', () => {
|
||||
this.logs = []
|
||||
|
||||
cy.on('log:added', (attrs, log) => {
|
||||
if (attrs.name === 'xhr') {
|
||||
if (['xhr', 'request'].includes(attrs.name)) {
|
||||
this.lastLog = log
|
||||
this.logs.push(log)
|
||||
}
|
||||
@@ -1751,7 +1755,7 @@ describe('src/cy/commands/xhr', () => {
|
||||
return null
|
||||
})
|
||||
.wait('@getFoo').then((xhr) => {
|
||||
const log = cy.queue.logs({ name: 'xhr' })[0]
|
||||
const log = cy.queue.logs({ name: 'request' })[0]
|
||||
|
||||
expect(log.get('displayName')).to.eq('xhr')
|
||||
expect(log.get('alias')).to.eq('getFoo')
|
||||
@@ -2182,7 +2186,7 @@ describe('src/cy/commands/xhr', () => {
|
||||
this.logs = []
|
||||
|
||||
cy.on('log:added', (attrs, log) => {
|
||||
if (attrs.name === 'xhr') {
|
||||
if (['xhr', 'request'].includes(attrs.name)) {
|
||||
this.lastLog = log
|
||||
this.logs.push(log)
|
||||
}
|
||||
@@ -2349,16 +2353,16 @@ describe('src/cy/commands/xhr', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('says Stubbed: No when request isnt forced 404', function () {
|
||||
expect(this.lastLog.invoke('consoleProps').Stubbed).to.eq('No')
|
||||
it('no status when request isnt forced 404', function () {
|
||||
expect(this.lastLog.invoke('consoleProps').Status).to.be.undefined
|
||||
})
|
||||
|
||||
it('logs request + response headers', () => {
|
||||
cy.then(function () {
|
||||
const consoleProps = this.lastLog.invoke('consoleProps')
|
||||
|
||||
expect(consoleProps.Request.headers).to.be.an('object')
|
||||
expect(consoleProps.Response.headers).to.be.an('object')
|
||||
cy.wrap(this).its('lastLog').invoke('invoke', 'consoleProps').should((consoleProps) => {
|
||||
expect(consoleProps['Request Headers']).to.be.an('object')
|
||||
expect(consoleProps['Response Headers']).to.be.an('object')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2366,26 +2370,28 @@ describe('src/cy/commands/xhr', () => {
|
||||
cy.then(function () {
|
||||
const { xhr } = cy.state('responses')[0]
|
||||
|
||||
const consoleProps = _.pick(this.lastLog.invoke('consoleProps'), 'Method', 'Status', 'URL', 'XHR')
|
||||
cy.wrap(this).its('lastLog').invoke('invoke', 'consoleProps').should((consoleProps) => {
|
||||
expect(consoleProps).to.include({
|
||||
Method: 'GET',
|
||||
URL: 'http://localhost:3500/fixtures/app.json',
|
||||
'Request went to origin?': 'yes',
|
||||
XHR: xhr.xhr,
|
||||
})
|
||||
|
||||
expect(consoleProps).to.deep.eq({
|
||||
Method: 'GET',
|
||||
URL: 'http://localhost:3500/fixtures/app.json',
|
||||
Status: '200 (OK)',
|
||||
XHR: xhr.xhr,
|
||||
expect(consoleProps['Response Status Code']).to.be.oneOf([200, 304])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('logs response', () => {
|
||||
cy.then(function () {
|
||||
const consoleProps = this.lastLog.invoke('consoleProps')
|
||||
|
||||
expect(consoleProps.Response.body).to.deep.eq({
|
||||
some: 'json',
|
||||
foo: {
|
||||
bar: 'baz',
|
||||
},
|
||||
cy.wrap(this).its('lastLog').invoke('invoke', 'consoleProps').should((consoleProps) => {
|
||||
expect(consoleProps['Response Body']).to.deep.eq({
|
||||
some: 'json',
|
||||
foo: {
|
||||
bar: 'baz',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -2410,7 +2416,7 @@ describe('src/cy/commands/xhr', () => {
|
||||
this.logs = []
|
||||
|
||||
cy.on('log:added', (attrs, log) => {
|
||||
if (attrs.name === 'xhr') {
|
||||
if (['xhr', 'request'].includes(attrs.name)) {
|
||||
this.lastLog = log
|
||||
this.logs.push(log)
|
||||
}
|
||||
@@ -2543,7 +2549,7 @@ describe('src/cy/commands/xhr', () => {
|
||||
let log = null
|
||||
|
||||
cy.on('log:changed', (attrs, l) => {
|
||||
if (attrs.name === 'xhr') {
|
||||
if (['xhr', 'request'].includes(attrs.name)) {
|
||||
if (!log) {
|
||||
log = l
|
||||
}
|
||||
@@ -2561,11 +2567,11 @@ describe('src/cy/commands/xhr', () => {
|
||||
|
||||
cy.wrap(null).should(() => {
|
||||
expect(log.get('state')).to.eq('failed')
|
||||
expect(log.invoke('renderProps')).to.deep.eq({
|
||||
message: 'GET (aborted) /timeout?ms=999',
|
||||
indicator: 'aborted',
|
||||
expect(log.invoke('renderProps')).to.include({
|
||||
message: 'GET /timeout?ms=999',
|
||||
})
|
||||
|
||||
expect(log.get('error')).to.be.an('Error')
|
||||
expect(xhr.aborted).to.be.true
|
||||
})
|
||||
})
|
||||
@@ -2576,7 +2582,7 @@ describe('src/cy/commands/xhr', () => {
|
||||
let log = null
|
||||
|
||||
cy.on('log:changed', (attrs, l) => {
|
||||
if (attrs.name === 'xhr') {
|
||||
if (['xhr', 'request'].includes(attrs.name)) {
|
||||
if (!log) {
|
||||
log = l
|
||||
}
|
||||
@@ -2605,7 +2611,7 @@ describe('src/cy/commands/xhr', () => {
|
||||
let log = null
|
||||
|
||||
cy.on('log:changed', (attrs, l) => {
|
||||
if (attrs.name === 'xhr') {
|
||||
if (['xhr', 'request'].includes(attrs.name)) {
|
||||
if (!log) {
|
||||
log = l
|
||||
}
|
||||
|
||||
@@ -0,0 +1,417 @@
|
||||
describe('Proxy Logging', () => {
|
||||
const { _ } = Cypress
|
||||
|
||||
const url = '/testFlag'
|
||||
const alias = 'aliasName'
|
||||
|
||||
function testFlag (expectStatus, expectInterceptions, setupFn, getFn) {
|
||||
return () => {
|
||||
setupFn()
|
||||
|
||||
let resolve
|
||||
const p = new Promise((_resolve) => resolve = _resolve)
|
||||
|
||||
function testLog (log) {
|
||||
expect(log.alias).to.eq(expectInterceptions.length ? alias : undefined)
|
||||
expect(log.renderProps).to.deep.include({
|
||||
interceptions: expectInterceptions,
|
||||
...(expectStatus ? { status: expectStatus } : {}),
|
||||
})
|
||||
|
||||
resolve()
|
||||
}
|
||||
|
||||
cy.then(() => {
|
||||
cy.on('log:changed', (log) => {
|
||||
if (['request', 'xhr'].includes(log.name)) {
|
||||
try {
|
||||
testLog(log)
|
||||
resolve()
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('assertions failed:', err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
getFn(url)
|
||||
}).then(() => p)
|
||||
|
||||
if (expectStatus) {
|
||||
cy.wait(`@${alias}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
// block race conditions caused by log update debouncing
|
||||
// @ts-ignore
|
||||
Cypress.config('logAttrsDelay', 0)
|
||||
})
|
||||
|
||||
context('request logging', () => {
|
||||
it('fetch log shows resource type, url, method, and status code and has expected snapshots and consoleProps', (done) => {
|
||||
fetch('/some-url')
|
||||
|
||||
// trigger: Cypress.Log() called
|
||||
cy.once('log:added', (log) => {
|
||||
expect(log.snapshots).to.be.undefined
|
||||
expect(log.displayName).to.eq('fetch')
|
||||
expect(log.renderProps).to.include({
|
||||
indicator: 'pending',
|
||||
message: 'GET /some-url',
|
||||
})
|
||||
|
||||
expect(log.consoleProps).to.include({
|
||||
Method: 'GET',
|
||||
'Resource Type': 'fetch',
|
||||
'Request went to origin?': 'yes',
|
||||
'URL': 'http://localhost:3500/some-url',
|
||||
})
|
||||
|
||||
// case depends on browser
|
||||
const refererKey = _.keys(log.consoleProps['Request Headers']).find((k) => k.toLowerCase() === 'referer') || 'referer'
|
||||
|
||||
expect(log.consoleProps['Request Headers']).to.include({
|
||||
[refererKey]: window.location.href,
|
||||
})
|
||||
|
||||
expect(log.consoleProps).to.not.have.property('Response Headers')
|
||||
expect(log.consoleProps).to.not.have.property('Matched `cy.intercept()`')
|
||||
|
||||
// trigger: .snapshot('request')
|
||||
cy.once('log:changed', (log) => {
|
||||
expect(log.snapshots.map((v) => v.name)).to.deep.eq(['request'])
|
||||
|
||||
// trigger: .snapshot('response')
|
||||
cy.once('log:changed', (log) => {
|
||||
expect(log.snapshots.map((v) => v.name)).to.deep.eq(['request', 'response'])
|
||||
expect(log.consoleProps['Response Headers']).to.include({
|
||||
'x-powered-by': 'Express',
|
||||
})
|
||||
|
||||
expect(log.consoleProps).to.not.have.property('Matched `cy.intercept()`')
|
||||
expect(log.renderProps).to.include({
|
||||
indicator: 'bad',
|
||||
message: 'GET 404 /some-url',
|
||||
})
|
||||
|
||||
expect(Object.keys(log.consoleProps)).to.deep.eq(
|
||||
['Event', 'Resource Type', 'Method', 'URL', 'Request went to origin?', 'Request Headers', 'Response Status Code', 'Response Headers'],
|
||||
)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('does not log an unintercepted non-xhr/fetch request', (done) => {
|
||||
const img = new Image()
|
||||
const logs: any[] = []
|
||||
let imgLoaded = false
|
||||
|
||||
cy.on('log:added', (log) => {
|
||||
if (imgLoaded) return
|
||||
|
||||
logs.push(log)
|
||||
})
|
||||
|
||||
img.onload = () => {
|
||||
imgLoaded = true
|
||||
expect(logs).to.have.length(0)
|
||||
done()
|
||||
}
|
||||
|
||||
img.src = `/fixtures/media/cypress.png?${Date.now()}`
|
||||
})
|
||||
|
||||
context('with cy.intercept()', () => {
|
||||
it('shows non-xhr/fetch log if intercepted', (done) => {
|
||||
const src = `/fixtures/media/cypress.png?${Date.now()}`
|
||||
|
||||
cy.intercept('/fixtures/**/*.png*')
|
||||
.then(() => {
|
||||
cy.once('log:added', (log) => {
|
||||
expect(log.displayName).to.eq('image')
|
||||
expect(log.renderProps).to.include({
|
||||
indicator: 'pending',
|
||||
message: `GET ${src}`,
|
||||
})
|
||||
|
||||
done()
|
||||
})
|
||||
|
||||
const img = new Image()
|
||||
|
||||
img.src = src
|
||||
})
|
||||
})
|
||||
|
||||
it('shows cy.visit if intercepted', () => {
|
||||
cy.intercept('/fixtures/empty.html')
|
||||
.then(() => {
|
||||
// trigger: cy.visit()
|
||||
cy.once('log:added', (log) => {
|
||||
expect(log.name).to.eq('visit')
|
||||
// trigger: intercept Cypress.Log
|
||||
cy.once('log:added', (log) => {
|
||||
expect(log.displayName).to.eq('document')
|
||||
})
|
||||
})
|
||||
})
|
||||
.visit('/fixtures/empty.html')
|
||||
})
|
||||
|
||||
it('intercept log has consoleProps with intercept info', (done) => {
|
||||
cy.intercept('/some-url', 'stubbed response').as('alias')
|
||||
.then(() => {
|
||||
fetch('/some-url')
|
||||
})
|
||||
|
||||
cy.on('log:changed', (log) => {
|
||||
if (log.displayName !== 'fetch') return
|
||||
|
||||
try {
|
||||
expect(log.renderProps).to.deep.include({
|
||||
message: 'GET 200 /some-url',
|
||||
indicator: 'successful',
|
||||
status: undefined,
|
||||
interceptions: [{
|
||||
alias: 'alias',
|
||||
command: 'intercept',
|
||||
type: 'stub',
|
||||
}],
|
||||
})
|
||||
|
||||
expect(Object.keys(log.consoleProps)).to.deep.eq(
|
||||
['Event', 'Resource Type', 'Method', 'URL', 'Request went to origin?', 'Matched `cy.intercept()`', 'Request Headers', 'Response Status Code', 'Response Headers', 'Response Body'],
|
||||
)
|
||||
|
||||
const interceptProps = log.consoleProps['Matched `cy.intercept()`']
|
||||
|
||||
expect(interceptProps).to.deep.eq({
|
||||
Alias: 'alias',
|
||||
Request: {
|
||||
method: 'GET',
|
||||
url: 'http://localhost:3500/some-url',
|
||||
body: '',
|
||||
httpVersion: '1.1',
|
||||
responseTimeout: Cypress.config('responseTimeout'),
|
||||
headers: interceptProps.Request.headers,
|
||||
},
|
||||
Response: {
|
||||
body: 'stubbed response',
|
||||
statusCode: 200,
|
||||
url: 'http://localhost:3500/some-url',
|
||||
headers: interceptProps.Response.headers,
|
||||
},
|
||||
RouteMatcher: {
|
||||
url: '/some-url',
|
||||
},
|
||||
RouteHandler: 'stubbed response',
|
||||
'RouteHandler Type': 'StaticResponse stub',
|
||||
})
|
||||
|
||||
done()
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('assertion failed:', err)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('works with forceNetworkError', () => {
|
||||
const logs: any[] = []
|
||||
|
||||
cy.on('log:added', (log) => {
|
||||
if (log.displayName === 'fetch') {
|
||||
logs.push(log)
|
||||
}
|
||||
})
|
||||
|
||||
cy.intercept('/foo', { forceNetworkError: true }).as('alias')
|
||||
.then(() => {
|
||||
return fetch('/foo')
|
||||
.catch(() => {})
|
||||
})
|
||||
.wrap(logs)
|
||||
.should((logs) => {
|
||||
// retries...
|
||||
expect(logs).to.have.length.greaterThan(2)
|
||||
|
||||
for (const log of logs) {
|
||||
expect(log.err).to.include({ name: 'Error' })
|
||||
expect(log.consoleProps['Error']).to.be.an('Error')
|
||||
expect(log.snapshots.map((v) => v.name)).to.deep.eq(['request', 'error'])
|
||||
expect(log.state).to.eq('failed')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
context('flags', () => {
|
||||
const testFlagFetch = (expectStatus, expectInterceptions, setupFn) => {
|
||||
return testFlag(expectStatus, expectInterceptions, setupFn, (url) => fetch(url))
|
||||
}
|
||||
|
||||
it('is unflagged when not intercepted', testFlagFetch(
|
||||
undefined,
|
||||
[],
|
||||
() => {},
|
||||
))
|
||||
|
||||
it('spied flagged as expected', testFlagFetch(
|
||||
undefined,
|
||||
[{
|
||||
command: 'intercept',
|
||||
alias,
|
||||
type: 'spy',
|
||||
}],
|
||||
() => {
|
||||
cy.intercept(url).as(alias)
|
||||
},
|
||||
))
|
||||
|
||||
it('spy function flagged as expected', testFlagFetch(
|
||||
undefined,
|
||||
[{
|
||||
command: 'intercept',
|
||||
alias,
|
||||
type: 'function',
|
||||
}],
|
||||
() => {
|
||||
cy.intercept(url, () => {}).as(alias)
|
||||
},
|
||||
))
|
||||
|
||||
it('stubbed flagged as expected', testFlagFetch(
|
||||
undefined,
|
||||
[{
|
||||
command: 'intercept',
|
||||
alias,
|
||||
type: 'stub',
|
||||
}],
|
||||
() => {
|
||||
cy.intercept(url, 'stubbed response').as(alias)
|
||||
},
|
||||
))
|
||||
|
||||
it('stubbed flagged as expected with req.reply', testFlagFetch(
|
||||
undefined,
|
||||
[{
|
||||
command: 'intercept',
|
||||
alias,
|
||||
type: 'function',
|
||||
}],
|
||||
() => {
|
||||
cy.intercept(url, (req) => {
|
||||
req.headers.foo = 'bar'
|
||||
req.reply('stubby mc stub')
|
||||
}).as(alias)
|
||||
},
|
||||
))
|
||||
|
||||
it('req modified flagged as expected', testFlagFetch(
|
||||
'req modified',
|
||||
[{
|
||||
command: 'intercept',
|
||||
alias,
|
||||
type: 'function',
|
||||
}],
|
||||
() => {
|
||||
cy.intercept(url, (req) => {
|
||||
req.headers.foo = 'bar'
|
||||
}).as(alias)
|
||||
},
|
||||
))
|
||||
|
||||
it('res modified flagged as expected', testFlagFetch(
|
||||
'res modified',
|
||||
[{
|
||||
command: 'intercept',
|
||||
alias,
|
||||
type: 'function',
|
||||
}],
|
||||
() => {
|
||||
cy.intercept(url, (req) => {
|
||||
req.continue((res) => {
|
||||
res.headers.foo = 'bar'
|
||||
})
|
||||
}).as(alias)
|
||||
},
|
||||
))
|
||||
|
||||
it('req + res modified flagged as expected', testFlagFetch(
|
||||
'req + res modified',
|
||||
[{
|
||||
command: 'intercept',
|
||||
alias,
|
||||
type: 'function',
|
||||
}],
|
||||
() => {
|
||||
cy.intercept(url, (req) => {
|
||||
req.headers.foo = 'bar'
|
||||
req.continue((res) => {
|
||||
res.headers.foo = 'bar'
|
||||
})
|
||||
}).as(alias)
|
||||
},
|
||||
))
|
||||
})
|
||||
})
|
||||
|
||||
context('with cy.route()', () => {
|
||||
context('flags', () => {
|
||||
let $XMLHttpRequest
|
||||
|
||||
const testFlagXhr = (expectStatus, expectInterceptions, setupFn) => {
|
||||
return testFlag(expectStatus, expectInterceptions, setupFn, (url) => {
|
||||
const xhr = new $XMLHttpRequest()
|
||||
|
||||
xhr.open('GET', url)
|
||||
xhr.send()
|
||||
})
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
cy.window()
|
||||
.then(({ XMLHttpRequest }) => {
|
||||
$XMLHttpRequest = XMLHttpRequest
|
||||
})
|
||||
})
|
||||
|
||||
it('is unflagged when not routed', testFlagXhr(
|
||||
undefined,
|
||||
[],
|
||||
() => {},
|
||||
))
|
||||
|
||||
it('spied flagged as expected', testFlagXhr(
|
||||
undefined,
|
||||
[{
|
||||
command: 'route',
|
||||
alias,
|
||||
type: 'spy',
|
||||
}],
|
||||
() => {
|
||||
cy.server()
|
||||
cy.route(`${url}`).as(alias)
|
||||
},
|
||||
))
|
||||
|
||||
it('stubbed flagged as expected', testFlagXhr(
|
||||
undefined,
|
||||
[{
|
||||
command: 'route',
|
||||
alias,
|
||||
type: 'stub',
|
||||
}],
|
||||
() => {
|
||||
cy.server()
|
||||
cy.route(url, 'stubbed response').as(alias)
|
||||
},
|
||||
))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -131,9 +131,9 @@ if (Cypress.isBrowser('chrome')) {
|
||||
|
||||
expect(stub).not.to.be.called
|
||||
expect(secondLog.get('state')).to.eq('failed')
|
||||
expect(secondLog.invoke('renderProps')).to.deep.eq({
|
||||
message: 'GET (canceled) /timeout?ms=2000',
|
||||
indicator: 'aborted',
|
||||
expect(secondLog.invoke('renderProps')).to.include({
|
||||
message: 'GET /timeout?ms=2000',
|
||||
indicator: 'pending',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -41,8 +41,6 @@ const unavailableErr = () => {
|
||||
return $errUtils.throwErrByPath('server.unavailable')
|
||||
}
|
||||
|
||||
const getDisplayName = (route) => _.isNil(route?.response) ? 'xhr' : 'xhr stub'
|
||||
|
||||
const stripOrigin = (url) => {
|
||||
const location = $Location.create(url)
|
||||
|
||||
@@ -109,10 +107,12 @@ const startXhrServer = (cy, state, config) => {
|
||||
rl.set('numResponses', numResponses + 1)
|
||||
}
|
||||
|
||||
const isStubbed = route && !_.isNil(route.response)
|
||||
|
||||
const log = logs[xhr.id] = Cypress.log({
|
||||
message: '',
|
||||
name: 'xhr',
|
||||
displayName: getDisplayName(route),
|
||||
displayName: 'xhr',
|
||||
alias,
|
||||
aliasType: 'route',
|
||||
type: 'parent',
|
||||
@@ -126,7 +126,7 @@ const startXhrServer = (cy, state, config) => {
|
||||
'Matched URL': route?.url,
|
||||
Status: xhr.statusMessage,
|
||||
Duration: xhr.duration,
|
||||
Stubbed: _.isNil(route?.response) ? 'No' : 'Yes',
|
||||
Stubbed: isStubbed ? 'Yes' : 'No',
|
||||
Request: xhr.request,
|
||||
Response: xhr.response,
|
||||
XHR: xhr._getXhr(),
|
||||
@@ -172,10 +172,20 @@ const startXhrServer = (cy, state, config) => {
|
||||
return {
|
||||
indicator,
|
||||
message: `${xhr.method} ${status} ${stripOrigin(xhr.url)}`,
|
||||
interceptions: route ? [
|
||||
{
|
||||
command: 'route',
|
||||
type: isStubbed ? 'stub' : 'spy',
|
||||
alias,
|
||||
},
|
||||
] : [],
|
||||
wentToOrigin: !isStubbed,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
Cypress.ProxyLogging.addXhrLog({ xhr, route, log, stack })
|
||||
|
||||
return log.snapshot('request')
|
||||
},
|
||||
|
||||
@@ -185,7 +195,14 @@ const startXhrServer = (cy, state, config) => {
|
||||
const log = logs[xhr.id]
|
||||
|
||||
if (log) {
|
||||
return log.snapshot('response').end()
|
||||
// the xhr log can already have a snapshot if it's been correlated with a proxy request (not xhr stubbed), so check first
|
||||
const hasResponseSnapshot = log.get('snapshots')?.find((v) => v.name === 'response')
|
||||
|
||||
if (!hasResponseSnapshot) {
|
||||
log.snapshot('response')
|
||||
}
|
||||
|
||||
log.end()
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -16,9 +16,6 @@ export const onAfterResponse: HandlerFn<CyHttpMessages.ResponseComplete> = async
|
||||
|
||||
request.state = 'Complete'
|
||||
|
||||
request.log.fireChangeEvent()
|
||||
request.log.end()
|
||||
|
||||
// @ts-ignore
|
||||
userHandler && await userHandler(request.response!)
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import _ from 'lodash'
|
||||
|
||||
import {
|
||||
Route,
|
||||
Interception,
|
||||
CyHttpMessages,
|
||||
SERIALIZABLE_REQ_PROPS,
|
||||
@@ -24,58 +23,16 @@ type Result = HandlerResult<CyHttpMessages.IncomingRequest>
|
||||
|
||||
const validEvents = ['before:response', 'response', 'after:response']
|
||||
|
||||
const getDisplayUrl = (url: string) => {
|
||||
if (url.startsWith(window.location.origin)) {
|
||||
return url.slice(window.location.origin.length)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
export const onBeforeRequest: HandlerFn<CyHttpMessages.IncomingRequest> = (Cypress, frame, userHandler, { getRoute, getRequest, emitNetEvent, sendStaticResponse }) => {
|
||||
function getRequestLog (route: Route, request: Omit<Interception, 'log'>) {
|
||||
const message = _.compact([
|
||||
request.request.method,
|
||||
request.response && request.response.statusCode,
|
||||
getDisplayUrl(request.request.url),
|
||||
request.state,
|
||||
]).join(' ')
|
||||
|
||||
const displayName = route.handler ? (_.isFunction(route.handler) ? 'req fn' : 'req stub') : 'req'
|
||||
|
||||
return Cypress.log({
|
||||
name: 'xhr',
|
||||
displayName,
|
||||
alias: route.alias,
|
||||
aliasType: 'route',
|
||||
type: 'parent',
|
||||
event: true,
|
||||
method: request.request.method,
|
||||
timeout: undefined,
|
||||
consoleProps: () => {
|
||||
return {
|
||||
Alias: route.alias,
|
||||
Method: request.request.method,
|
||||
URL: request.request.url,
|
||||
Matched: route.options,
|
||||
Handler: route.handler,
|
||||
}
|
||||
},
|
||||
renderProps: () => {
|
||||
return {
|
||||
indicator: request.state === 'Complete' ? 'successful' : 'pending',
|
||||
message,
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const { data: req, requestId, subscription } = frame
|
||||
const { routeId } = subscription
|
||||
const route = getRoute(routeId)
|
||||
|
||||
const bodyParsed = parseJsonBody(req)
|
||||
|
||||
req.responseTimeout = Cypress.config('responseTimeout')
|
||||
const reqClone = _.cloneDeep(req)
|
||||
|
||||
const subscribe = (eventName, handler) => {
|
||||
const subscription: Subscription = {
|
||||
id: _.uniqueId('Subscription'),
|
||||
@@ -94,27 +51,31 @@ export const onBeforeRequest: HandlerFn<CyHttpMessages.IncomingRequest> = (Cypre
|
||||
emitNetEvent('subscribe', { requestId, subscription } as NetEvent.ToServer.Subscribe)
|
||||
}
|
||||
|
||||
const getCanonicalRequest = (): Interception => {
|
||||
const existingRequest = getRequest(routeId, requestId)
|
||||
const getCanonicalInterception = (): Interception => {
|
||||
const existingInterception = getRequest(routeId, requestId)
|
||||
|
||||
if (existingRequest) {
|
||||
existingRequest.request = req
|
||||
if (existingInterception) {
|
||||
existingInterception.request = req
|
||||
|
||||
return existingRequest
|
||||
return existingInterception
|
||||
}
|
||||
|
||||
return {
|
||||
id: requestId,
|
||||
browserRequestId: frame.browserRequestId,
|
||||
routeId,
|
||||
request: req,
|
||||
state: 'Received',
|
||||
requestWaited: false,
|
||||
responseWaited: false,
|
||||
subscriptions: [],
|
||||
setLogFlag: () => {
|
||||
throw new Error('default setLogFlag reached')
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const request: Interception = getCanonicalRequest()
|
||||
const request: Interception = getCanonicalInterception()
|
||||
|
||||
let resolved = false
|
||||
let handlerCompleted = false
|
||||
@@ -243,8 +204,6 @@ export const onBeforeRequest: HandlerFn<CyHttpMessages.IncomingRequest> = (Cypre
|
||||
// allow `req` to be sent outgoing, then pass the response body to `responseHandler`
|
||||
subscribe('response:callback', responseHandler)
|
||||
|
||||
userReq.responseTimeout = userReq.responseTimeout || Cypress.config('responseTimeout')
|
||||
|
||||
return finish(true)
|
||||
},
|
||||
reply (responseHandler?, maybeBody?, maybeHeaders?) {
|
||||
@@ -274,6 +233,8 @@ export const onBeforeRequest: HandlerFn<CyHttpMessages.IncomingRequest> = (Cypre
|
||||
// `responseHandler` is a StaticResponse
|
||||
validateStaticResponse('req.reply', responseHandler)
|
||||
|
||||
request.setLogFlag('stubbed')
|
||||
|
||||
sendStaticResponse(requestId, responseHandler)
|
||||
|
||||
return updateRequest(req)
|
||||
@@ -290,7 +251,7 @@ export const onBeforeRequest: HandlerFn<CyHttpMessages.IncomingRequest> = (Cypre
|
||||
destroy () {
|
||||
userReq.reply({
|
||||
forceNetworkError: true,
|
||||
}) // TODO: this misnomer is a holdover from XHR, should be numRequests
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -301,7 +262,6 @@ export const onBeforeRequest: HandlerFn<CyHttpMessages.IncomingRequest> = (Cypre
|
||||
request.request = _.cloneDeep(req)
|
||||
|
||||
request.state = 'Intercepted'
|
||||
request.log && request.log.fireChangeEvent()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,6 +285,10 @@ export const onBeforeRequest: HandlerFn<CyHttpMessages.IncomingRequest> = (Cypre
|
||||
stringifyJsonBody(req)
|
||||
}
|
||||
|
||||
if (!_.isEqual(req, reqClone)) {
|
||||
request.setLogFlag('reqModified')
|
||||
}
|
||||
|
||||
resolve({
|
||||
changedData: req,
|
||||
stopPropagation,
|
||||
@@ -337,9 +301,7 @@ export const onBeforeRequest: HandlerFn<CyHttpMessages.IncomingRequest> = (Cypre
|
||||
resolve = _resolve
|
||||
})
|
||||
|
||||
if (!request.log) {
|
||||
request.log = getRequestLog(route, request as Omit<Interception, 'log'>)
|
||||
}
|
||||
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)
|
||||
|
||||
@@ -71,12 +71,12 @@ export function registerEvents (Cypress: Cypress.Cypress, cy: Cypress.cy) {
|
||||
state('aliasedRequests', [])
|
||||
})
|
||||
|
||||
Cypress.on('net:event', (eventName, frame: NetEvent.ToDriver.Event<any>) => {
|
||||
Cypress.on('net:stubbing:event', (eventName, frame: NetEvent.ToDriver.Event<any>) => {
|
||||
Bluebird.try(async () => {
|
||||
const handler = netEventHandlers[eventName]
|
||||
|
||||
if (!handler) {
|
||||
throw new Error(`received unknown net:event in driver: ${eventName}`)
|
||||
throw new Error(`received unknown net:stubbing:event in driver: ${eventName}`)
|
||||
}
|
||||
|
||||
const emitResolved = (result: HandlerResult<any>) => {
|
||||
|
||||
@@ -36,8 +36,6 @@ export const onNetworkError: HandlerFn<CyHttpMessages.NetworkError> = async (Cyp
|
||||
request.state = 'Errored'
|
||||
request.error = err
|
||||
|
||||
request.log.error(err)
|
||||
|
||||
if (isAwaitingResponse) {
|
||||
// the user is implicitly expecting there to be a successful response from the server, so fail the test
|
||||
// since a network error has occured
|
||||
|
||||
@@ -20,6 +20,7 @@ export const onResponse: HandlerFn<CyHttpMessages.IncomingResponse> = async (Cyp
|
||||
const { data: res, requestId, subscription } = frame
|
||||
const { routeId } = subscription
|
||||
const request = getRequest(routeId, frame.requestId)
|
||||
const resClone = _.cloneDeep(res)
|
||||
|
||||
const bodyParsed = parseJsonBody(res)
|
||||
|
||||
@@ -29,8 +30,6 @@ export const onResponse: HandlerFn<CyHttpMessages.IncomingResponse> = async (Cyp
|
||||
if (request) {
|
||||
request.state = 'ResponseReceived'
|
||||
|
||||
request.log.fireChangeEvent()
|
||||
|
||||
if (!userHandler) {
|
||||
// this is notification-only, update the request with the response attributes and end
|
||||
request.response = res
|
||||
@@ -41,9 +40,12 @@ export const onResponse: HandlerFn<CyHttpMessages.IncomingResponse> = async (Cyp
|
||||
|
||||
const finishResponseStage = (res) => {
|
||||
if (request) {
|
||||
if (!_.isEqual(resClone, res)) {
|
||||
request.setLogFlag('resModified')
|
||||
}
|
||||
|
||||
request.response = _.cloneDeep(res)
|
||||
request.state = 'ResponseIntercepted'
|
||||
request.log.fireChangeEvent()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { find } from 'lodash'
|
||||
import { CyHttpMessages } from '@packages/net-stubbing/lib/types'
|
||||
|
||||
export function hasJsonContentType (headers: { [k: string]: string }) {
|
||||
export function hasJsonContentType (headers: { [k: string]: string | string[] }) {
|
||||
const contentType = find(headers, (v, k) => /^content-type$/i.test(k))
|
||||
|
||||
if (Array.isArray(contentType)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return contentType && /^application\/.*json/i.test(contentType)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ const $SetterGetter = require('./cypress/setter_getter')
|
||||
const $Log = require('./cypress/log')
|
||||
const $Location = require('./cypress/location')
|
||||
const $LocalStorage = require('./cypress/local_storage')
|
||||
const { ProxyLogging } = require('./cypress/proxy-logging')
|
||||
const $Mocha = require('./cypress/mocha')
|
||||
const $Mouse = require('./cy/mouse')
|
||||
const $Runner = require('./cypress/runner')
|
||||
@@ -152,6 +153,8 @@ class $Cypress {
|
||||
|
||||
this.Cookies = $Cookies.create(config.namespace, d)
|
||||
|
||||
this.ProxyLogging = new ProxyLogging(this)
|
||||
|
||||
return this.action('cypress:config', config)
|
||||
}
|
||||
|
||||
|
||||
@@ -303,16 +303,6 @@ const Log = function (cy, state, config, obj) {
|
||||
return _.pick(attributes, args)
|
||||
},
|
||||
|
||||
publicInterface () {
|
||||
return {
|
||||
get: _.bind(this.get, this),
|
||||
on: _.bind(this.on, this),
|
||||
off: _.bind(this.off, this),
|
||||
pick: _.bind(this.pick, this),
|
||||
attributes,
|
||||
}
|
||||
},
|
||||
|
||||
snapshot (name, options = {}) {
|
||||
// bail early and don't snapshot if we're in headless mode
|
||||
// or we're not storing tests
|
||||
|
||||
360
packages/driver/src/cypress/proxy-logging.ts
Normal file
360
packages/driver/src/cypress/proxy-logging.ts
Normal file
@@ -0,0 +1,360 @@
|
||||
import { Interception, Route } from '@packages/net-stubbing/lib/types'
|
||||
import { BrowserPreRequest, BrowserResponseReceived, RequestError } from '@packages/proxy/lib/types'
|
||||
import { makeErrFromObj } from './error_utils'
|
||||
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,
|
||||
'RouteHandler Type': !_.isNil(route.handler) ? (_.isFunction(route.handler) ? 'Function' : 'StaticResponse stub') : 'Spy',
|
||||
'RouteHandler': route.handler,
|
||||
'Request': interception.request,
|
||||
}
|
||||
|
||||
if (interception.response) {
|
||||
ret['Response'] = _.omitBy(interception.response, _.isNil)
|
||||
}
|
||||
|
||||
const alias = interception.request.alias || route.alias
|
||||
|
||||
if (alias) ret['Alias'] = alias
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
function getDisplayUrl (url: string) {
|
||||
if (url.startsWith(window.location.origin)) {
|
||||
return url.slice(window.location.origin.length)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
function getDynamicRequestLogConfig (req: Omit<ProxyRequest, 'log'>): Partial<Cypress.LogConfig> {
|
||||
const last = _.last(req.interceptions)
|
||||
let alias = last ? last.interception.request.alias || last.route.alias : undefined
|
||||
|
||||
if (!alias && req.xhr && req.route) {
|
||||
alias = req.route.alias
|
||||
}
|
||||
|
||||
return {
|
||||
alias,
|
||||
aliasType: alias ? 'route' : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
function getRequestLogConfig (req: Omit<ProxyRequest, 'log'>): Partial<Cypress.LogConfig> {
|
||||
function getStatus (): string | undefined {
|
||||
const { stubbed, reqModified, resModified } = req.flags
|
||||
|
||||
if (stubbed) return
|
||||
|
||||
if (reqModified && resModified) return 'req + res modified'
|
||||
|
||||
if (reqModified) return 'req modified'
|
||||
|
||||
if (resModified) return 'res modified'
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return {
|
||||
...getDynamicRequestLogConfig(req),
|
||||
displayName: req.preRequest.resourceType,
|
||||
name: 'request',
|
||||
type: 'parent',
|
||||
event: true,
|
||||
url: req.preRequest.url,
|
||||
method: req.preRequest.method,
|
||||
timeout: 0,
|
||||
consoleProps: () => {
|
||||
// high-level request information
|
||||
const consoleProps = {
|
||||
'Resource Type': req.preRequest.resourceType,
|
||||
Method: req.preRequest.method,
|
||||
URL: req.preRequest.url,
|
||||
'Request went to origin?': req.flags.stubbed ? 'no (response was stubbed, see below)' : 'yes',
|
||||
}
|
||||
|
||||
if (req.flags.reqModified) consoleProps['Request modified?'] = 'yes'
|
||||
|
||||
if (req.flags.resModified) consoleProps['Response modified?'] = 'yes'
|
||||
|
||||
// details on matched XHR/intercept
|
||||
if (req.xhr) consoleProps['XHR'] = req.xhr.xhr
|
||||
|
||||
if (req.interceptions.length) {
|
||||
if (req.interceptions.length > 1) {
|
||||
consoleProps['Matched `cy.intercept()`s'] = req.interceptions.map(formatInterception)
|
||||
} else {
|
||||
consoleProps['Matched `cy.intercept()`'] = formatInterception(req.interceptions[0])
|
||||
}
|
||||
}
|
||||
|
||||
if (req.stack) {
|
||||
consoleProps['groups'] = () => {
|
||||
return [
|
||||
{
|
||||
name: 'Initiator',
|
||||
items: [req.stack],
|
||||
label: false,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// details on request/response/errors
|
||||
consoleProps['Request Headers'] = req.preRequest.headers
|
||||
|
||||
if (req.responseReceived) {
|
||||
_.assign(consoleProps, {
|
||||
'Response Status Code': req.responseReceived.status,
|
||||
'Response Headers': req.responseReceived.headers,
|
||||
})
|
||||
}
|
||||
|
||||
let resBody
|
||||
|
||||
if (req.xhr) {
|
||||
consoleProps['Response Body'] = req.xhr.responseBody
|
||||
} else if ((resBody = _.chain(req.interceptions).last().get('interception.response.body').value())) {
|
||||
consoleProps['Response Body'] = resBody
|
||||
}
|
||||
|
||||
if (req.error) {
|
||||
consoleProps['Error'] = req.error
|
||||
}
|
||||
|
||||
return consoleProps
|
||||
},
|
||||
renderProps: () => {
|
||||
function getIndicator (): 'aborted' | 'pending' | 'successful' | 'bad' {
|
||||
if (!req.responseReceived) {
|
||||
return 'pending'
|
||||
}
|
||||
|
||||
if (req.responseReceived.status >= 200 && req.responseReceived.status <= 299) {
|
||||
return 'successful'
|
||||
}
|
||||
|
||||
return 'bad'
|
||||
}
|
||||
|
||||
const message = _.compact([
|
||||
req.preRequest.method,
|
||||
req.responseReceived && req.responseReceived.status,
|
||||
getDisplayUrl(req.preRequest.url),
|
||||
]).join(' ')
|
||||
|
||||
return {
|
||||
indicator: getIndicator(),
|
||||
message,
|
||||
status: getStatus(),
|
||||
wentToOrigin: !req.flags.stubbed,
|
||||
interceptions: [
|
||||
...(req.interceptions.map(({ interception, route }) => {
|
||||
return {
|
||||
command: 'intercept',
|
||||
alias: interception.request.alias || route.alias,
|
||||
type: !_.isNil(route.handler) ? (_.isFunction(route.handler) ? 'function' : 'stub') : 'spy',
|
||||
}
|
||||
})),
|
||||
...(req.route ? [{
|
||||
command: 'route',
|
||||
alias: req.route?.alias,
|
||||
type: _.isNil(req.route?.response) ? 'spy' : 'stub',
|
||||
}] : []),
|
||||
],
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function shouldLog (preRequest: BrowserPreRequest) {
|
||||
return ['xhr', 'fetch'].includes(preRequest.resourceType)
|
||||
}
|
||||
|
||||
class ProxyRequest {
|
||||
log?: Cypress.Log
|
||||
preRequest: BrowserPreRequest
|
||||
responseReceived?: BrowserResponseReceived
|
||||
error?: Error
|
||||
xhr?: Cypress.WaitXHR
|
||||
route?: any
|
||||
stack?: string
|
||||
interceptions: Array<{ interception: Interception, route: Route }> = []
|
||||
displayInterceptions: Array<{ command: 'intercept' | 'route', alias?: string, type: 'stub' | 'spy' | 'function' }> = []
|
||||
flags: {
|
||||
spied?: boolean
|
||||
stubbed?: boolean
|
||||
reqModified?: boolean
|
||||
resModified?: boolean
|
||||
} = {}
|
||||
|
||||
constructor (preRequest: BrowserPreRequest, opts?: Partial<ProxyRequest>) {
|
||||
this.preRequest = preRequest
|
||||
opts && _.assign(this, opts)
|
||||
}
|
||||
|
||||
setFlag = (flag: keyof ProxyRequest['flags']) => {
|
||||
this.flags[flag] = true
|
||||
this.log?.set({})
|
||||
}
|
||||
}
|
||||
|
||||
type UnmatchedXhrLog = {
|
||||
xhr: Cypress.WaitXHR
|
||||
route?: any
|
||||
log: Cypress.Log
|
||||
stack?: string
|
||||
}
|
||||
|
||||
export class ProxyLogging {
|
||||
unloggedPreRequests: Array<BrowserPreRequest> = []
|
||||
unmatchedXhrLogs: Array<UnmatchedXhrLog> = []
|
||||
proxyRequests: Array<ProxyRequest> = []
|
||||
|
||||
constructor (private Cypress: Cypress.Cypress) {
|
||||
Cypress.on('request:event', (eventName, data) => {
|
||||
switch (eventName) {
|
||||
case 'incoming:request':
|
||||
return this.logIncomingRequest(data)
|
||||
case 'response:received':
|
||||
return this.updateRequestWithResponse(data)
|
||||
case 'request:error':
|
||||
return this.updateRequestWithError(data)
|
||||
default:
|
||||
throw new Error(`unrecognized request:event event ${eventName}`)
|
||||
}
|
||||
})
|
||||
|
||||
Cypress.on('test:before:run', () => {
|
||||
for (const proxyRequest of this.proxyRequests) {
|
||||
if (!proxyRequest.responseReceived && proxyRequest.log) {
|
||||
proxyRequest.log.end()
|
||||
}
|
||||
}
|
||||
this.unloggedPreRequests = []
|
||||
this.proxyRequests = []
|
||||
this.unmatchedXhrLogs = []
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* The `cy.route()` XHR stub functions will log before a proxy log is received, so this queues an XHR log to be overridden by a proxy log later.
|
||||
*/
|
||||
addXhrLog (xhrLog: UnmatchedXhrLog) {
|
||||
this.unmatchedXhrLogs.push(xhrLog)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 | undefined {
|
||||
const unloggedPreRequest = take(this.unloggedPreRequests, ({ requestId }) => requestId === interception.browserRequestId)
|
||||
|
||||
if (unloggedPreRequest) {
|
||||
debug('interception matched an unlogged prerequest, logging %o', { unloggedPreRequest, interception })
|
||||
this.createProxyRequestLog(unloggedPreRequest)
|
||||
}
|
||||
|
||||
const proxyRequest = _.find(this.proxyRequests, ({ preRequest }) => preRequest.requestId === interception.browserRequestId)
|
||||
|
||||
if (!proxyRequest) {
|
||||
throw new Error(`Missing pre-request/proxy log for cy.intercept to ${interception.request.url}`)
|
||||
}
|
||||
|
||||
proxyRequest.interceptions.push({ interception, route })
|
||||
|
||||
proxyRequest.log?.set(getDynamicRequestLogConfig(proxyRequest))
|
||||
|
||||
// consider a function to be 'spying' until it actually stubs/modifies the response
|
||||
proxyRequest.setFlag(!_.isNil(route.handler) && !_.isFunction(route.handler) ? 'stubbed' : 'spied')
|
||||
|
||||
return proxyRequest
|
||||
}
|
||||
|
||||
private updateRequestWithResponse (responseReceived: BrowserResponseReceived): void {
|
||||
const proxyRequest = _.find(this.proxyRequests, ({ preRequest }) => preRequest.requestId === responseReceived.requestId)
|
||||
|
||||
if (!proxyRequest) {
|
||||
return debug('unmatched responseReceived event %o', responseReceived)
|
||||
}
|
||||
|
||||
proxyRequest.responseReceived = responseReceived
|
||||
proxyRequest.log?.snapshot('response').end()
|
||||
}
|
||||
|
||||
private updateRequestWithError (error: RequestError): void {
|
||||
const proxyRequest = _.find(this.proxyRequests, ({ preRequest }) => preRequest.requestId === error.requestId)
|
||||
|
||||
if (!proxyRequest) {
|
||||
return debug('unmatched error event %o', error)
|
||||
}
|
||||
|
||||
proxyRequest.error = makeErrFromObj(error.error)
|
||||
proxyRequest.log?.snapshot('error').error(proxyRequest.error)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Cypress.Log for an incoming proxy request, or store the metadata for later if it is ignored.
|
||||
*/
|
||||
private logIncomingRequest (preRequest: BrowserPreRequest): void {
|
||||
// if this is an XHR, check to see if it matches an XHR log that is missing a pre-request
|
||||
if (preRequest.resourceType === 'xhr') {
|
||||
const unmatchedXhrLog = take(this.unmatchedXhrLogs, ({ xhr }) => xhr.url === preRequest.url && xhr.method === preRequest.method)
|
||||
|
||||
if (unmatchedXhrLog) {
|
||||
const { log, route } = unmatchedXhrLog
|
||||
const proxyRequest = new ProxyRequest(preRequest, unmatchedXhrLog)
|
||||
|
||||
if (route) {
|
||||
proxyRequest.setFlag(_.isNil(route.response) ? 'spied' : 'stubbed')
|
||||
}
|
||||
|
||||
log.set(getRequestLogConfig(proxyRequest))
|
||||
|
||||
this.proxyRequests.push(proxyRequest)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldLog(preRequest)) {
|
||||
this.unloggedPreRequests.push(preRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.createProxyRequestLog(preRequest)
|
||||
}
|
||||
|
||||
private createProxyRequestLog (preRequest: BrowserPreRequest) {
|
||||
const proxyRequest = new ProxyRequest(preRequest)
|
||||
const logConfig = getRequestLogConfig(proxyRequest as Omit<ProxyRequest, 'log'>)
|
||||
|
||||
proxyRequest.log = this.Cypress.log(logConfig).snapshot('request')
|
||||
|
||||
this.proxyRequests.push(proxyRequest as ProxyRequest)
|
||||
}
|
||||
}
|
||||
7
packages/driver/types/internal-types.d.ts
vendored
7
packages/driver/types/internal-types.d.ts
vendored
@@ -3,7 +3,8 @@
|
||||
|
||||
declare namespace Cypress {
|
||||
interface Actions {
|
||||
(action: 'net:event', frame: any)
|
||||
(action: 'net:stubbing:event', frame: any)
|
||||
(action: 'request:event', data: any)
|
||||
}
|
||||
|
||||
interface cy {
|
||||
@@ -19,6 +20,8 @@ declare namespace Cypress {
|
||||
|
||||
interface Cypress {
|
||||
backend: (eventName: string, ...args: any[]) => Promise<any>
|
||||
// TODO: how to pull this from proxy-logging.ts? can't import in a d.ts file...
|
||||
ProxyLogging: any
|
||||
// TODO: how to pull these from resolvers.ts? can't import in a d.ts file...
|
||||
resolveWindowReference: any
|
||||
resolveLocationReference: any
|
||||
@@ -44,6 +47,7 @@ declare namespace Cypress {
|
||||
isStubbed?: boolean
|
||||
alias?: string
|
||||
aliasType?: 'route'
|
||||
commandName?: string
|
||||
type?: 'parent'
|
||||
event?: boolean
|
||||
method?: string
|
||||
@@ -55,6 +59,7 @@ declare namespace Cypress {
|
||||
indicator?: 'aborted' | 'pending' | 'successful' | 'bad'
|
||||
message?: string
|
||||
}
|
||||
browserPreRequest?: any
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
||||
@@ -267,9 +267,11 @@ interface RequestEvents {
|
||||
*/
|
||||
export interface Interception {
|
||||
id: string
|
||||
/* @internal */
|
||||
browserRequestId?: string
|
||||
routeId: string
|
||||
/* @internal */
|
||||
log?: any
|
||||
setLogFlag: (flag: 'spied' | 'stubbed' | 'reqModified' | 'resModified') => void
|
||||
request: CyHttpMessages.IncomingRequest
|
||||
/**
|
||||
* Was `cy.wait()` used to wait on this request?
|
||||
|
||||
@@ -59,13 +59,15 @@ export declare namespace NetEvent {
|
||||
|
||||
export namespace ToDriver {
|
||||
export interface Event<D> extends Http {
|
||||
/**
|
||||
* If set, this is the browser's internal identifier for this request.
|
||||
*/
|
||||
browserRequestId?: string
|
||||
subscription: Subscription
|
||||
eventId: string
|
||||
data: D
|
||||
}
|
||||
|
||||
export interface Request extends Event<CyHttpMessages.IncomingRequest> {}
|
||||
|
||||
export interface Response extends Event<CyHttpMessages.IncomingResponse> {}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ export function _restoreMatcherOptionsTypes (options: AnnotatedRouteMatcherOptio
|
||||
return ret
|
||||
}
|
||||
|
||||
type OnNetEventOpts = {
|
||||
type OnNetStubbingEventOpts = {
|
||||
eventName: string
|
||||
state: NetStubbingState
|
||||
socket: CyServer.Socket
|
||||
@@ -115,7 +115,7 @@ type OnNetEventOpts = {
|
||||
frame: NetEvent.ToServer.AddRoute<BackendStaticResponse> | NetEvent.ToServer.EventHandlerResolved | NetEvent.ToServer.Subscribe | NetEvent.ToServer.SendStaticResponse
|
||||
}
|
||||
|
||||
export async function onNetEvent (opts: OnNetEventOpts): Promise<any> {
|
||||
export async function onNetStubbingEvent (opts: OnNetStubbingEventOpts): Promise<any> {
|
||||
const { state, getFixture, args, eventName, frame } = opts
|
||||
|
||||
debug('received driver event %o', { eventName, args })
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export { onNetEvent } from './driver-events'
|
||||
export { onNetStubbingEvent } from './driver-events'
|
||||
|
||||
export { InterceptError } from './middleware/error'
|
||||
|
||||
|
||||
@@ -148,6 +148,7 @@ export class InterceptedRequest {
|
||||
const eventFrame: NetEvent.ToDriver.Event<any> = {
|
||||
eventId,
|
||||
subscription,
|
||||
browserRequestId: this.req.browserPreRequest && this.req.browserPreRequest.requestId,
|
||||
requestId: this.id,
|
||||
data,
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export function emit (socket: CyServer.Socket, eventName: string, data: object)
|
||||
debug('sending event to driver %o', { eventName, data: _.chain(data).cloneDeep().omit('res.body').value() })
|
||||
}
|
||||
|
||||
socket.toDriver('net:event', eventName, data)
|
||||
socket.toDriver('net:stubbing:event', eventName, data)
|
||||
}
|
||||
|
||||
export function getAllStringMatcherFields (options: RouteMatcherOptionsGeneric<any>) {
|
||||
|
||||
@@ -12,6 +12,8 @@ describe('InterceptedRequest', () => {
|
||||
}
|
||||
const state = NetStubbingState()
|
||||
const interceptedRequest = new InterceptedRequest({
|
||||
// @ts-ignore
|
||||
req: {},
|
||||
state,
|
||||
socket,
|
||||
matchingRoutes: [
|
||||
@@ -39,7 +41,7 @@ describe('InterceptedRequest', () => {
|
||||
const data = { foo: 'bar' }
|
||||
|
||||
socket.toDriver.callsFake((eventName, subEventName, frame) => {
|
||||
expect(eventName).to.eq('net:event')
|
||||
expect(eventName).to.eq('net:stubbing:event')
|
||||
expect(subEventName).to.eq('before:request')
|
||||
expect(frame).to.deep.include({
|
||||
subscription: {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { HttpMiddleware } from '.'
|
||||
import { InterceptError } from '@packages/net-stubbing'
|
||||
import { Readable } from 'stream'
|
||||
import { Request } from '@cypress/request'
|
||||
import errors from '@packages/server/lib/errors'
|
||||
|
||||
const debug = debugModule('cypress:proxy:http:error-middleware')
|
||||
|
||||
@@ -22,6 +23,17 @@ const LogError: ErrorMiddleware = function () {
|
||||
this.next()
|
||||
}
|
||||
|
||||
const SendToDriver: ErrorMiddleware = function () {
|
||||
if (this.req.browserPreRequest) {
|
||||
this.socket.toDriver('request:event', 'request:error', {
|
||||
requestId: this.req.browserPreRequest.requestId,
|
||||
error: errors.clone(this.error),
|
||||
})
|
||||
}
|
||||
|
||||
this.next()
|
||||
}
|
||||
|
||||
export const AbortRequest: ErrorMiddleware = function () {
|
||||
if (this.outgoingReq) {
|
||||
debug('aborting outgoingReq')
|
||||
@@ -47,6 +59,7 @@ export const DestroyResponse: ErrorMiddleware = function () {
|
||||
|
||||
export default {
|
||||
LogError,
|
||||
SendToDriver,
|
||||
InterceptError,
|
||||
AbortRequest,
|
||||
UnpipeResponse,
|
||||
|
||||
@@ -176,6 +176,16 @@ export function _runStage (type: HttpStages, ctx: any, onError) {
|
||||
return runMiddlewareStack()
|
||||
}
|
||||
|
||||
function getUniqueRequestId (requestId: string) {
|
||||
const match = /^(.*)-retry-([\d]+)$/.exec(requestId)
|
||||
|
||||
if (match) {
|
||||
return `${match[1]}-retry-${Number(match[2]) + 1}`
|
||||
}
|
||||
|
||||
return `${requestId}-retry-1`
|
||||
}
|
||||
|
||||
export class Http {
|
||||
buffers: HttpBuffers
|
||||
config: CyServer.Config
|
||||
@@ -237,9 +247,14 @@ export class Http {
|
||||
const onError = () => {
|
||||
if (ctx.req.browserPreRequest) {
|
||||
// browsers will retry requests in the event of network errors, but they will not send pre-requests,
|
||||
// so try to re-use the current browserPreRequest for the next retry
|
||||
ctx.debug('Re-using pre-request data %o', ctx.req.browserPreRequest)
|
||||
this.addPendingBrowserPreRequest(ctx.req.browserPreRequest)
|
||||
// so try to re-use the current browserPreRequest for the next retry after incrementing the ID.
|
||||
const preRequest = {
|
||||
...ctx.req.browserPreRequest,
|
||||
requestId: getUniqueRequestId(ctx.req.browserPreRequest.requestId),
|
||||
}
|
||||
|
||||
ctx.debug('Re-using pre-request data %o', preRequest)
|
||||
this.addPendingBrowserPreRequest(preRequest)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +284,6 @@ export class Http {
|
||||
|
||||
reset () {
|
||||
this.buffers.reset()
|
||||
this.preRequests = new PreRequests()
|
||||
}
|
||||
|
||||
setBuffer (buffer) {
|
||||
|
||||
@@ -27,6 +27,25 @@ const CorrelateBrowserPreRequest: RequestMiddleware = async function () {
|
||||
if (this.req.headers['x-cypress-resolving-url']) {
|
||||
this.debug('skipping prerequest for resolve:url')
|
||||
delete this.req.headers['x-cypress-resolving-url']
|
||||
const requestId = `cy.visit-${Date.now()}`
|
||||
|
||||
this.req.browserPreRequest = {
|
||||
requestId,
|
||||
method: this.req.method,
|
||||
url: this.req.proxiedUrl,
|
||||
// @ts-ignore
|
||||
headers: this.req.headers,
|
||||
resourceType: 'document',
|
||||
originalResourceType: 'document',
|
||||
}
|
||||
|
||||
this.res.on('close', () => {
|
||||
this.socket.toDriver('request:event', 'response:received', {
|
||||
requestId,
|
||||
headers: this.res.getHeaders(),
|
||||
status: this.res.statusCode,
|
||||
})
|
||||
})
|
||||
|
||||
return this.next()
|
||||
}
|
||||
@@ -42,7 +61,7 @@ const SendToDriver: RequestMiddleware = function () {
|
||||
const { browserPreRequest } = this.req
|
||||
|
||||
if (browserPreRequest) {
|
||||
this.socket.toDriver('proxy:incoming:request', browserPreRequest)
|
||||
this.socket.toDriver('request:event', 'incoming:request', browserPreRequest)
|
||||
}
|
||||
|
||||
this.next()
|
||||
@@ -167,9 +186,9 @@ const SendRequestOutgoing: RequestMiddleware = function () {
|
||||
|
||||
export default {
|
||||
LogRequest,
|
||||
MaybeEndRequestWithBufferedResponse,
|
||||
CorrelateBrowserPreRequest,
|
||||
SendToDriver,
|
||||
MaybeEndRequestWithBufferedResponse,
|
||||
InterceptRequest,
|
||||
RedirectToClientRouteIfUnloaded,
|
||||
EndRequestsToBlockedHosts,
|
||||
|
||||
@@ -30,7 +30,7 @@ export { RequestMiddleware } from './http/request-middleware'
|
||||
|
||||
export { ResponseMiddleware } from './http/response-middleware'
|
||||
|
||||
export type ResourceType = 'fetch' | 'xhr' | 'websocket' | 'stylesheet' | 'script' | 'image' | 'font' | 'cspviolationreport' | 'ping' | 'manifest' | 'other'
|
||||
export type ResourceType = 'document' | 'fetch' | 'xhr' | 'websocket' | 'stylesheet' | 'script' | 'image' | 'font' | 'cspviolationreport' | 'ping' | 'manifest' | 'other'
|
||||
|
||||
/**
|
||||
* Metadata about an HTTP request, according to the browser's pre-request event.
|
||||
@@ -39,6 +39,21 @@ export type BrowserPreRequest = {
|
||||
requestId: string
|
||||
method: string
|
||||
url: string
|
||||
headers: { [key: string]: string | string[] }
|
||||
resourceType: ResourceType
|
||||
originalResourceType: string | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification that the browser has received a response for a request for which a pre-request may have been emitted.
|
||||
*/
|
||||
export type BrowserResponseReceived = {
|
||||
requestId: string
|
||||
status: number
|
||||
headers: { [key: string]: string | string[] }
|
||||
}
|
||||
|
||||
export type RequestError = {
|
||||
requestId: string
|
||||
error: any
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NetworkProxy } from '../../'
|
||||
import {
|
||||
netStubbingState as _netStubbingState,
|
||||
NetStubbingState,
|
||||
onNetEvent,
|
||||
onNetStubbingEvent,
|
||||
} from '@packages/net-stubbing'
|
||||
import { defaultMiddleware } from '../../lib/http'
|
||||
import express from 'express'
|
||||
@@ -166,7 +166,7 @@ context('network stubbing', () => {
|
||||
|
||||
socket.toDriver.callsFake((_, event, data) => {
|
||||
if (event === 'before:request') {
|
||||
onNetEvent({
|
||||
onNetStubbingEvent({
|
||||
eventName: 'send:static:response',
|
||||
// @ts-ignore
|
||||
frame: {
|
||||
@@ -234,7 +234,7 @@ context('network stubbing', () => {
|
||||
socket.toDriver.callsFake((_, event, data) => {
|
||||
if (event === 'before:request') {
|
||||
sendContentLength = data.data.headers['content-length']
|
||||
onNetEvent({
|
||||
onNetStubbingEvent({
|
||||
eventName: 'send:static:response',
|
||||
// @ts-ignore
|
||||
frame: {
|
||||
|
||||
@@ -14,6 +14,7 @@ describe('http/error-middleware', function () {
|
||||
it('exports the members in the correct order', function () {
|
||||
expect(_.keys(ErrorMiddleware)).to.have.ordered.members([
|
||||
'LogError',
|
||||
'SendToDriver',
|
||||
'InterceptError',
|
||||
'AbortRequest',
|
||||
'UnpipeResponse',
|
||||
|
||||
@@ -6,9 +6,9 @@ describe('http/request-middleware', function () {
|
||||
it('exports the members in the correct order', function () {
|
||||
expect(_.keys(RequestMiddleware)).to.have.ordered.members([
|
||||
'LogRequest',
|
||||
'MaybeEndRequestWithBufferedResponse',
|
||||
'CorrelateBrowserPreRequest',
|
||||
'SendToDriver',
|
||||
'MaybeEndRequestWithBufferedResponse',
|
||||
'InterceptRequest',
|
||||
'RedirectToClientRouteIfUnloaded',
|
||||
'EndRequestsToBlockedHosts',
|
||||
|
||||
@@ -113,8 +113,7 @@
|
||||
"event": true,
|
||||
"testId": "r3",
|
||||
"timeout": 4000,
|
||||
"type": "parent",
|
||||
"alias": "dup0"
|
||||
"type": "parent"
|
||||
},
|
||||
{
|
||||
"hookId": "r3",
|
||||
@@ -127,8 +126,7 @@
|
||||
"event": true,
|
||||
"testId": "r3",
|
||||
"timeout": 4000,
|
||||
"type": "parent",
|
||||
"alias": "dup1"
|
||||
"type": "parent"
|
||||
},
|
||||
{
|
||||
"hookId": "r3",
|
||||
|
||||
@@ -30,16 +30,122 @@ describe('aliases', () => {
|
||||
})
|
||||
})
|
||||
|
||||
context('interceptions + status', () => {
|
||||
it('shows only status if no alias or dupe', () => {
|
||||
addCommand(runner, {
|
||||
aliasType: 'route',
|
||||
renderProps: {
|
||||
wentToOrigin: true,
|
||||
status: 'some status',
|
||||
interceptions: [{
|
||||
type: 'spy',
|
||||
command: 'intercept',
|
||||
}],
|
||||
},
|
||||
})
|
||||
|
||||
cy.contains('.command-number', '1').parent().find('.command-interceptions')
|
||||
.should('have.text', 'some status no alias')
|
||||
.trigger('mouseover')
|
||||
.get('.cy-tooltip').should('have.text', 'This request matched:cy.intercept() spy with no alias')
|
||||
.percySnapshot()
|
||||
})
|
||||
|
||||
it('shows status and count if dupe', () => {
|
||||
addCommand(runner, {
|
||||
aliasType: 'route',
|
||||
renderProps: {
|
||||
wentToOrigin: true,
|
||||
status: 'some status',
|
||||
interceptions: [{
|
||||
type: 'spy',
|
||||
command: 'intercept',
|
||||
}, {
|
||||
type: 'spy',
|
||||
command: 'route',
|
||||
}],
|
||||
},
|
||||
})
|
||||
|
||||
cy.contains('.command-number', '1').parent().find('.command-interceptions')
|
||||
.should('have.text', 'some status no alias')
|
||||
.parent().find('.command-interceptions-count')
|
||||
.should('have.text', '2')
|
||||
.trigger('mouseover')
|
||||
.get('.cy-tooltip').should('have.text', 'This request matched:cy.intercept() spy with no aliascy.route() spy with no alias')
|
||||
.percySnapshot()
|
||||
})
|
||||
|
||||
it('shows status and alias and count if dupe', () => {
|
||||
addCommand(runner, {
|
||||
aliasType: 'route',
|
||||
alias: 'myAlias',
|
||||
renderProps: {
|
||||
wentToOrigin: true,
|
||||
status: 'some status',
|
||||
interceptions: [{
|
||||
type: 'spy',
|
||||
command: 'intercept',
|
||||
alias: 'firstAlias',
|
||||
}, {
|
||||
type: 'spy',
|
||||
command: 'intercept',
|
||||
alias: 'myAlias',
|
||||
}],
|
||||
},
|
||||
})
|
||||
|
||||
cy.contains('.command-number', '1').parent().find('.command-interceptions')
|
||||
.should('have.text', 'some status myAlias')
|
||||
.parent().find('.command-interceptions-count')
|
||||
.should('have.text', '2')
|
||||
.trigger('mouseover')
|
||||
.get('.cy-tooltip').should('have.text', 'This request matched:cy.intercept() spy with alias @firstAliascy.intercept() spy with alias @myAlias')
|
||||
.percySnapshot()
|
||||
})
|
||||
|
||||
it('shows status and alias', () => {
|
||||
addCommand(runner, {
|
||||
aliasType: 'route',
|
||||
alias: 'myAlias',
|
||||
renderProps: {
|
||||
wentToOrigin: true,
|
||||
status: 'some status',
|
||||
interceptions: [{
|
||||
type: 'spy',
|
||||
command: 'intercept',
|
||||
alias: 'myAlias',
|
||||
}],
|
||||
},
|
||||
})
|
||||
|
||||
cy.contains('.command-number', '1').parent().find('.command-interceptions')
|
||||
.should('have.text', 'some status myAlias')
|
||||
.trigger('mouseover')
|
||||
.get('.cy-tooltip').should('have.text', 'This request matched:cy.intercept() spy with alias @myAlias')
|
||||
.percySnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
context('route aliases', () => {
|
||||
describe('without duplicates', () => {
|
||||
beforeEach(() => {
|
||||
addCommand(runner, {
|
||||
alias: 'getUsers',
|
||||
aliasType: 'route',
|
||||
displayName: 'xhr stub',
|
||||
displayName: 'xhr',
|
||||
event: true,
|
||||
name: 'xhr',
|
||||
renderProps: { message: 'GET --- /users', indicator: 'passed' },
|
||||
renderProps: {
|
||||
message: 'GET --- /users',
|
||||
indicator: 'passed',
|
||||
wentToOrigin: false,
|
||||
interceptions: [{
|
||||
type: 'stub',
|
||||
command: 'route',
|
||||
alias: 'getUsers',
|
||||
}],
|
||||
},
|
||||
})
|
||||
|
||||
addCommand(runner, {
|
||||
@@ -84,19 +190,21 @@ describe('aliases', () => {
|
||||
addCommand(runner, {
|
||||
alias: 'getPosts',
|
||||
aliasType: 'route',
|
||||
displayName: 'xhr stub',
|
||||
displayName: 'xhr',
|
||||
event: true,
|
||||
name: 'xhr',
|
||||
renderProps: { message: 'GET --- /posts', indicator: 'passed' },
|
||||
// @ts-ignore
|
||||
renderProps: { message: 'GET --- /posts', indicator: 'passed', interceptions: [{ alias: 'getPosts' }] },
|
||||
})
|
||||
|
||||
addCommand(runner, {
|
||||
alias: 'getPosts',
|
||||
aliasType: 'route',
|
||||
displayName: 'xhr stub',
|
||||
displayName: 'xhr',
|
||||
event: true,
|
||||
name: 'xhr',
|
||||
renderProps: { message: 'GET --- /posts', indicator: 'passed' },
|
||||
// @ts-ignore
|
||||
renderProps: { message: 'GET --- /posts', indicator: 'passed', interceptions: [{ alias: 'getPosts' }] },
|
||||
})
|
||||
|
||||
addCommand(runner, {
|
||||
@@ -123,7 +231,7 @@ describe('aliases', () => {
|
||||
})
|
||||
|
||||
it('renders all aliases ', () => {
|
||||
cy.get('.command-alias').should('have.length', 3)
|
||||
cy.get('.command-alias').should('have.length', 2)
|
||||
cy.percySnapshot()
|
||||
})
|
||||
|
||||
@@ -161,7 +269,7 @@ describe('aliases', () => {
|
||||
.within(() => {
|
||||
cy.contains('.num-duplicates', '2')
|
||||
|
||||
cy.contains('.command-alias', 'getPosts')
|
||||
cy.contains('.command-interceptions', 'getPosts')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -175,7 +283,7 @@ describe('aliases', () => {
|
||||
.within(() => {
|
||||
cy.get('.num-duplicates').should('not.be.visible')
|
||||
|
||||
cy.contains('.command-alias', 'getPosts')
|
||||
cy.contains('.command-interceptions', 'getPosts')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -185,28 +293,55 @@ describe('aliases', () => {
|
||||
addCommand(runner, {
|
||||
alias: 'getPosts',
|
||||
aliasType: 'route',
|
||||
displayName: 'xhr stub',
|
||||
displayName: 'xhr',
|
||||
event: true,
|
||||
name: 'xhr',
|
||||
renderProps: { message: 'GET --- /posts', indicator: 'passed' },
|
||||
renderProps: {
|
||||
message: 'GET --- /users',
|
||||
indicator: 'passed',
|
||||
wentToOrigin: false,
|
||||
interceptions: [{
|
||||
type: 'stub',
|
||||
command: 'route',
|
||||
alias: 'getUsers',
|
||||
}],
|
||||
},
|
||||
})
|
||||
|
||||
addCommand(runner, {
|
||||
alias: 'getUsers',
|
||||
aliasType: 'route',
|
||||
displayName: 'xhr stub',
|
||||
displayName: 'xhr',
|
||||
event: true,
|
||||
name: 'xhr',
|
||||
renderProps: { message: 'GET --- /users', indicator: 'passed' },
|
||||
renderProps: {
|
||||
message: 'GET --- /users',
|
||||
indicator: 'passed',
|
||||
wentToOrigin: false,
|
||||
interceptions: [{
|
||||
type: 'stub',
|
||||
command: 'route',
|
||||
alias: 'getUsers',
|
||||
}],
|
||||
},
|
||||
})
|
||||
|
||||
addCommand(runner, {
|
||||
alias: 'getPosts',
|
||||
aliasType: 'route',
|
||||
displayName: 'xhr stub',
|
||||
displayName: 'xhr',
|
||||
event: true,
|
||||
name: 'xhr',
|
||||
renderProps: { message: 'GET --- /posts', indicator: 'passed' },
|
||||
renderProps: {
|
||||
message: 'GET --- /posts',
|
||||
indicator: 'passed',
|
||||
wentToOrigin: false,
|
||||
interceptions: [{
|
||||
type: 'stub',
|
||||
command: 'route',
|
||||
alias: 'getPosts',
|
||||
}],
|
||||
},
|
||||
})
|
||||
|
||||
addCommand(runner, {
|
||||
|
||||
@@ -228,11 +228,6 @@ describe('commands', () => {
|
||||
.should('have.text', '4')
|
||||
})
|
||||
|
||||
it('displays names of duplicates', () => {
|
||||
cy.contains('GET --- /dup').closest('.command').find('.command-alias')
|
||||
.should('have.text', 'dup0, dup1')
|
||||
})
|
||||
|
||||
it('expands all events after clicking arrow', () => {
|
||||
cy.contains('GET --- /dup').closest('.command').find('.command-expander').click()
|
||||
cy.get('.command-name-xhr').should('have.length', 6)
|
||||
@@ -240,15 +235,6 @@ describe('commands', () => {
|
||||
.should('be.visible')
|
||||
.find('.command').should('have.length', 3)
|
||||
})
|
||||
|
||||
it('splits up duplicate names when expanded', () => {
|
||||
cy.contains('GET --- /dup').closest('.command').as('cmd')
|
||||
|
||||
cy.get('@cmd').find('.command-expander').click()
|
||||
cy.get('@cmd').find('.command-alias').as('alias')
|
||||
cy.get('@alias').its(0).should('have.text', 'dup0')
|
||||
cy.get('@alias').its(1).should('have.text', 'dup1')
|
||||
})
|
||||
})
|
||||
|
||||
context('clicking', () => {
|
||||
|
||||
@@ -9,6 +9,13 @@ const LONG_RUNNING_THRESHOLD = 1000
|
||||
interface RenderProps {
|
||||
message?: string
|
||||
indicator?: string
|
||||
interceptions?: Array<{
|
||||
command: 'intercept' | 'route'
|
||||
alias?: string
|
||||
type: 'function' | 'stub' | 'spy'
|
||||
}>
|
||||
status?: string
|
||||
wentToOrigin?: boolean
|
||||
}
|
||||
|
||||
export interface CommandProps extends InstrumentProps {
|
||||
|
||||
@@ -77,6 +77,43 @@ const AliasesReferences = observer(({ model, aliasesWithDuplicates }: AliasesRef
|
||||
</span>
|
||||
))
|
||||
|
||||
interface InterceptionsProps {
|
||||
model: CommandModel
|
||||
}
|
||||
|
||||
const Interceptions = observer(({ model }: InterceptionsProps) => {
|
||||
if (!model.renderProps.interceptions?.length) return null
|
||||
|
||||
function getTitle () {
|
||||
return (
|
||||
<span>
|
||||
{model.renderProps.wentToOrigin ? '' : <>This request did not go to origin because the response was stubbed.<br/></>}
|
||||
This request matched:
|
||||
<ul>
|
||||
{model.renderProps.interceptions?.map(({ command, alias, type }, i) => {
|
||||
return (<li key={i}>
|
||||
<code>cy.{command}()</code> {type} with {alias ? <>alias <code>@{alias}</code></> : 'no alias'}
|
||||
</li>)
|
||||
})}
|
||||
</ul>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const count = model.renderProps.interceptions.length
|
||||
|
||||
const displayAlias = _.chain(model.renderProps.interceptions).last().get('alias').value()
|
||||
|
||||
return (
|
||||
<Tooltip placement='top' title={getTitle()} className='cy-tooltip'>
|
||||
<span>
|
||||
<span className={cs('command-interceptions', 'route', count > 1 && 'show-count')}>{model.renderProps.status ? <span className='status'>{model.renderProps.status} </span> : null}{displayAlias || <em className="no-alias">no alias</em>}</span>
|
||||
{count > 1 ? <span className={'command-interceptions-count'}>{count}</span> : null}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)
|
||||
})
|
||||
|
||||
interface AliasesProps {
|
||||
isOpen: boolean
|
||||
model: CommandModel
|
||||
@@ -84,7 +121,7 @@ interface AliasesProps {
|
||||
}
|
||||
|
||||
const Aliases = observer(({ model, aliasesWithDuplicates, isOpen }: AliasesProps) => {
|
||||
if (!model.alias) return null
|
||||
if (!model.alias || model.aliasType === 'route') return null
|
||||
|
||||
return (
|
||||
<span>
|
||||
@@ -113,7 +150,11 @@ interface MessageProps {
|
||||
|
||||
const Message = observer(({ model }: MessageProps) => (
|
||||
<span>
|
||||
<i className={`fas fa-circle ${model.renderProps.indicator}`} />
|
||||
<i className={cs(
|
||||
model.renderProps.wentToOrigin ? 'fas' : 'far',
|
||||
'fa-circle',
|
||||
model.renderProps.indicator,
|
||||
)} />
|
||||
<span
|
||||
className='command-message-text'
|
||||
dangerouslySetInnerHTML={{ __html: formattedMessage(model.displayMessage || '') }}
|
||||
@@ -222,9 +263,10 @@ class Command extends Component<Props> {
|
||||
<span className='num-elements'>{model.numElements}</span>
|
||||
</Tooltip>
|
||||
<span className='alias-container'>
|
||||
<Interceptions model={model} />
|
||||
<Aliases model={model} aliasesWithDuplicates={aliasesWithDuplicates} isOpen={this.isOpen} />
|
||||
<Tooltip placement='top' title={`This event occurred ${model.numDuplicates} times`} className='cy-tooltip'>
|
||||
<span className={cs('num-duplicates', { 'has-alias': model.alias, 'has-duplicates': model.numDuplicates > 1 })}>{model.numDuplicates}</span>
|
||||
<span className={cs('num-duplicates', { 'has-alias': model.alias })}>{model.numDuplicates}</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@@ -54,8 +54,6 @@
|
||||
}
|
||||
|
||||
.command-is-event {
|
||||
font-style: italic;
|
||||
|
||||
.command-method,
|
||||
.command-message {
|
||||
color: #9a9aaa !important;
|
||||
@@ -98,7 +96,16 @@
|
||||
flex-wrap: wrap;
|
||||
padding: 2px 5px 0;
|
||||
|
||||
.command-alias {
|
||||
.command-interceptions {
|
||||
font-style: normal;
|
||||
|
||||
.status {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.command-alias,
|
||||
.command-interceptions {
|
||||
border-radius: 10px;
|
||||
color: #777888;
|
||||
padding: 0 5px;
|
||||
@@ -139,7 +146,8 @@
|
||||
}
|
||||
|
||||
.num-duplicates,
|
||||
.command-alias-count {
|
||||
.command-alias-count,
|
||||
.command-interceptions-count {
|
||||
border-radius: 5px;
|
||||
color: #777;
|
||||
font-size: 90%;
|
||||
@@ -154,7 +162,8 @@
|
||||
padding: 3px 5px 3px 5px;
|
||||
}
|
||||
|
||||
.num-duplicates.has-alias.has-duplicates {
|
||||
.num-duplicates.has-alias.has-duplicates,
|
||||
.command-interceptions-count {
|
||||
border-radius: 0 10px 10px 0;
|
||||
padding: 4px 5px 2px 3px;
|
||||
}
|
||||
@@ -165,7 +174,8 @@
|
||||
}
|
||||
|
||||
.num-duplicates,
|
||||
.command-alias-count {
|
||||
.command-alias-count,
|
||||
.command-interceptions-count {
|
||||
background-color: darken(#ffdf9c, 8%) !important;
|
||||
}
|
||||
}
|
||||
@@ -439,7 +449,8 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.command-alias {
|
||||
.command-alias,
|
||||
.command-interceptions {
|
||||
font-family: $open-sans;
|
||||
font-size: 10px;
|
||||
line-height: 1.75;
|
||||
|
||||
@@ -16,4 +16,8 @@ $cy-tooltip-class: 'cy-tooltip';
|
||||
box-shadow: 0 0.5px 0 1px rgb(95, 95, 95);
|
||||
font-family: monospace, monospace;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: .8em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ const driverToSocketEvents = 'backend:request automation:request mocha recorder:
|
||||
const driverTestEvents = 'test:before:run:async test:after:run'.split(' ')
|
||||
const driverToLocalEvents = 'viewport:changed config stop url:changed page:loading visit:failed'.split(' ')
|
||||
const socketRerunEvents = 'runner:restart watched:file:changed'.split(' ')
|
||||
const socketToDriverEvents = 'net:event script:error'.split(' ')
|
||||
const socketToDriverEvents = 'net:stubbing:event request:event script:error'.split(' ')
|
||||
const localToReporterEvents = 'reporter:log:add reporter:log:state:changed reporter:log:remove'.split(' ')
|
||||
|
||||
const localBus = new EventEmitter()
|
||||
|
||||
@@ -3,12 +3,23 @@ import './setup'
|
||||
describe('cy.intercept', () => {
|
||||
const { $ } = Cypress
|
||||
|
||||
const emitProxyLog = () => Cypress.emit('request:event', 'incoming:request', {
|
||||
requestId: 1,
|
||||
method: 'GET',
|
||||
url: '',
|
||||
headers: {},
|
||||
resourceType: 'other',
|
||||
originalResourceType: 'other',
|
||||
})
|
||||
|
||||
it('assertion failure in req callback', () => {
|
||||
cy.intercept('/json-content-type', () => {
|
||||
expect('a').to.eq('b')
|
||||
})
|
||||
.then(() => {
|
||||
Cypress.emit('net:event', 'before:request', {
|
||||
emitProxyLog()
|
||||
Cypress.emit('net:stubbing:event', 'before:request', {
|
||||
browserRequestId: 1,
|
||||
eventId: '1',
|
||||
subscription: {
|
||||
// @ts-ignore
|
||||
@@ -30,7 +41,9 @@ describe('cy.intercept', () => {
|
||||
})
|
||||
})
|
||||
.then(() => {
|
||||
Cypress.emit('net:event', 'before:request', {
|
||||
emitProxyLog()
|
||||
Cypress.emit('net:stubbing:event', 'before:request', {
|
||||
browserRequestId: 1,
|
||||
eventId: '1',
|
||||
requestId: '1',
|
||||
subscription: {
|
||||
@@ -43,7 +56,7 @@ describe('cy.intercept', () => {
|
||||
},
|
||||
})
|
||||
|
||||
Cypress.emit('net:event', 'before:response', {
|
||||
Cypress.emit('net:stubbing:event', 'before:response', {
|
||||
eventId: '1',
|
||||
requestId: '1',
|
||||
subscription: {
|
||||
@@ -68,7 +81,9 @@ describe('cy.intercept', () => {
|
||||
})
|
||||
})
|
||||
.then(() => {
|
||||
Cypress.emit('net:event', 'before:request', {
|
||||
emitProxyLog()
|
||||
Cypress.emit('net:stubbing:event', 'before:request', {
|
||||
browserRequestId: 1,
|
||||
eventId: '1',
|
||||
requestId: '1',
|
||||
subscription: {
|
||||
@@ -81,7 +96,7 @@ describe('cy.intercept', () => {
|
||||
},
|
||||
})
|
||||
|
||||
Cypress.emit('net:event', 'network:error', {
|
||||
Cypress.emit('net:stubbing:event', 'network:error', {
|
||||
eventId: '1',
|
||||
requestId: '1',
|
||||
subscription: {
|
||||
|
||||
@@ -8,6 +8,8 @@ type NullableMiddlewareHook = (() => void) | null
|
||||
|
||||
export type OnBrowserPreRequest = (browserPreRequest: BrowserPreRequest) => void
|
||||
|
||||
export type onRequestEvent = (eventName: string, data: any) => void
|
||||
|
||||
interface IMiddleware {
|
||||
onPush: NullableMiddlewareHook
|
||||
onBeforeRequest: NullableMiddlewareHook
|
||||
@@ -22,7 +24,7 @@ export class Automation {
|
||||
private cookies: Cookies
|
||||
private screenshot: { capture: (data: any, automate: any) => any }
|
||||
|
||||
constructor (cyNamespace?: string, cookieNamespace?: string, screenshotsFolder?: string | false, public onBrowserPreRequest?: OnBrowserPreRequest) {
|
||||
constructor (cyNamespace?: string, cookieNamespace?: string, screenshotsFolder?: string | false, public onBrowserPreRequest?: OnBrowserPreRequest, public onRequestEvent?: onRequestEvent) {
|
||||
this.requests = {}
|
||||
|
||||
// set the middleware
|
||||
|
||||
@@ -6,7 +6,7 @@ import cdp from 'devtools-protocol'
|
||||
import { cors } from '@packages/network'
|
||||
import debugModule from 'debug'
|
||||
import { Automation } from '../automation'
|
||||
import { ResourceType, BrowserPreRequest } from '@packages/proxy'
|
||||
import { ResourceType, BrowserPreRequest, BrowserResponseReceived } from '@packages/proxy'
|
||||
|
||||
const debugVerbose = debugModule('cypress-verbose:server:browsers:cdp_automation')
|
||||
|
||||
@@ -156,6 +156,7 @@ const ffToStandardResourceTypeMap: { [ff: string]: ResourceType } = {
|
||||
export class CdpAutomation {
|
||||
constructor (private sendDebuggerCommandFn: SendDebuggerCommand, onFn: OnFn, private automation: Automation) {
|
||||
onFn('Network.requestWillBeSent', this.onNetworkRequestWillBeSent)
|
||||
onFn('Network.responseReceived', this.onResponseReceived)
|
||||
sendDebuggerCommandFn('Network.enable', {
|
||||
maxTotalBufferSize: 0,
|
||||
maxResourceBufferSize: 0,
|
||||
@@ -164,6 +165,7 @@ export class CdpAutomation {
|
||||
}
|
||||
|
||||
private onNetworkRequestWillBeSent = (params: cdp.Network.RequestWillBeSentEvent) => {
|
||||
debugVerbose('received networkRequestWillBeSent %o', params)
|
||||
let url = params.request.url
|
||||
|
||||
// in Firefox, the hash is incorrectly included in the URL: https://bugzilla.mozilla.org/show_bug.cgi?id=1715366
|
||||
@@ -175,6 +177,7 @@ export class CdpAutomation {
|
||||
requestId: params.requestId,
|
||||
method: params.request.method,
|
||||
url,
|
||||
headers: params.request.headers,
|
||||
resourceType: normalizeResourceType(params.type),
|
||||
originalResourceType: params.type,
|
||||
}
|
||||
@@ -182,6 +185,16 @@ export class CdpAutomation {
|
||||
this.automation.onBrowserPreRequest?.(browserPreRequest)
|
||||
}
|
||||
|
||||
private onResponseReceived = (params: cdp.Network.ResponseReceivedEvent) => {
|
||||
const browserResponseReceived: BrowserResponseReceived = {
|
||||
requestId: params.requestId,
|
||||
status: params.response.status,
|
||||
headers: params.response.headers,
|
||||
}
|
||||
|
||||
this.automation.onRequestEvent?.('response:received', browserResponseReceived)
|
||||
}
|
||||
|
||||
private getAllCookies = (filter: CyCookieFilter) => {
|
||||
return this.sendDebuggerCommandFn('Network.getAllCookies')
|
||||
.then((result: cdp.Network.GetAllCookiesResponse) => {
|
||||
|
||||
@@ -573,7 +573,11 @@ export class ProjectBase<TServer extends ServerE2E | ServerCt> extends EE {
|
||||
this.server.addBrowserPreRequest(browserPreRequest)
|
||||
}
|
||||
|
||||
this._automation = new Automation(namespace, socketIoCookie, screenshotsFolder, onBrowserPreRequest)
|
||||
const onRequestEvent = (eventName, data) => {
|
||||
this.server.emitRequestEvent(eventName, data)
|
||||
}
|
||||
|
||||
this._automation = new Automation(namespace, socketIoCookie, screenshotsFolder, onBrowserPreRequest, onRequestEvent)
|
||||
|
||||
this.server.startWebsockets(this.automation, this.cfg, {
|
||||
onReloadBrowser: options.onReloadBrowser,
|
||||
|
||||
@@ -327,6 +327,10 @@ export abstract class ServerBase<TSocket extends SocketE2E | SocketCt> {
|
||||
this.networkProxy.addPendingBrowserPreRequest(browserPreRequest)
|
||||
}
|
||||
|
||||
emitRequestEvent (eventName, data) {
|
||||
this.socket.toDriver('request:event', eventName, data)
|
||||
}
|
||||
|
||||
_createHttpServer (app): DestroyableHttpServer {
|
||||
const svr = http.createServer(httpUtils.lenientOptions, app)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Bluebird from 'bluebird'
|
||||
import Debug from 'debug'
|
||||
import _ from 'lodash'
|
||||
import { onNetEvent } from '@packages/net-stubbing'
|
||||
import { onNetStubbingEvent } from '@packages/net-stubbing'
|
||||
import * as socketIo from '@packages/socket'
|
||||
import firefoxUtil from './browsers/firefox-util'
|
||||
import errors from './errors'
|
||||
@@ -373,7 +373,7 @@ export class SocketBase {
|
||||
case 'write:file':
|
||||
return files.writeFile(config.projectRoot, args[0], args[1], args[2])
|
||||
case 'net':
|
||||
return onNetEvent({
|
||||
return onNetStubbingEvent({
|
||||
eventName: args[0],
|
||||
frame: args[1],
|
||||
state: options.netStubbingState,
|
||||
|
||||
Reference in New Issue
Block a user