-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add fetch collector #126
add fetch collector #126
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
const LOG_TYPE = require('../constants').LOG_TYPES; | ||
const CONSTANTS = require('../constants'); | ||
const LogFormat = require("./LogFormat"); | ||
|
||
module.exports = class LogCollectCypressFetch { | ||
|
||
constructor(collectorState, config) { | ||
this.config = config; | ||
this.collectorState = collectorState; | ||
|
||
this.format = new LogFormat(config); | ||
} | ||
|
||
register() { | ||
const formatFetch = (options) => (options.alias !== undefined ? '(' + options.alias + ') ' : '') + | ||
(options.consoleProps["Request went to origin?"] !== 'yes' ? 'STUBBED ' : '') + | ||
options.consoleProps.Method + ' ' + options.consoleProps.URL; | ||
|
||
const formatDuration = (durationInMs) => | ||
durationInMs < 1000 ? `${durationInMs} ms` : `${durationInMs / 1000} s`; | ||
|
||
Cypress.on('log:added', (options) => { | ||
if (options.instrument === 'command' && options.name === 'request' && options.displayName === 'fetch') { | ||
const log = formatFetch(options); | ||
const severity = options.state === 'failed' ? CONSTANTS.SEVERITY.WARNING : ''; | ||
this.collectorState.addLog([LOG_TYPE.CYPRESS_FETCH, log, severity], options.id); | ||
} | ||
}); | ||
|
||
Cypress.on('log:changed', async (options) => { | ||
if ( | ||
options.instrument === 'command' && options.name === 'request' && options.displayName === 'fetch' && | ||
options.state !== 'pending' | ||
) { | ||
let statusCode; | ||
|
||
statusCode = options.consoleProps["Response Status Code"]; | ||
|
||
const isSuccess = statusCode && (statusCode + '')[0] === '2'; | ||
const severity = isSuccess ? CONSTANTS.SEVERITY.SUCCESS : CONSTANTS.SEVERITY.WARNING; | ||
let log = formatFetch(options); | ||
|
||
if (options.consoleProps.Duration) { | ||
log += ` (${formatDuration(options.consoleProps.Duration)})`; | ||
} | ||
if (statusCode) { | ||
log += `\nStatus: ${statusCode}`; | ||
} | ||
if (options.err && options.err.message) { | ||
log += ' - ' + options.err.message; | ||
} | ||
|
||
if ( | ||
!isSuccess && | ||
options.consoleProps["Response Body"] | ||
) { | ||
log += `\nResponse body: ${await this.format.formatXhrBody(options.consoleProps["Response Body"])}`; | ||
} | ||
|
||
this.collectorState.updateLog(log, severity, options.id); | ||
} | ||
}); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ | |
"cons:error", | ||
"cy:log", | ||
"cy:xhr", | ||
"cy:fetch", | ||
"cy:request", | ||
"cy:intercept", | ||
"cy:route", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
describe('Fetch Api', () => { | ||
it('Stubbed failed fetch API', () => { | ||
cy.visit('/commands/network-requests'); | ||
|
||
cy.intercept( | ||
{ | ||
method: 'PUT', | ||
url: 'comments/*', | ||
}, | ||
{ | ||
statusCode: 404, | ||
body: {error: 'Test message.'}, | ||
delay: 500, | ||
} | ||
).as('putComment'); | ||
|
||
cy.window().then((w) => { | ||
fetch('/comments/10', { | ||
method: 'PUT', | ||
body: 'test', | ||
}); | ||
}); | ||
|
||
cy.wait('@putComment'); | ||
|
||
cy.get('.breaking-get', {timeout: 1}); | ||
}) | ||
|
||
context('Timeout', () => { | ||
|
||
it('forceNetworkError ', () => { | ||
cy.visit('/commands/network-requests'); | ||
|
||
cy.intercept( | ||
{ | ||
method: 'PUT', | ||
url: 'comments/*', | ||
|
||
}, | ||
{ | ||
forceNetworkError: true, | ||
} | ||
).as('putComment'); | ||
|
||
cy.window().then((w) => { | ||
fetch('/comments/10', { | ||
method: 'PUT', | ||
body: 'test', | ||
}); | ||
}); | ||
|
||
cy.wait('@putComment'); | ||
|
||
cy.get('.breaking-get', {timeout: 1}); | ||
}); | ||
|
||
// Currently Cypress can't handle fetch Abort properly. It produces an unhandled entry, while the request remains "pending": | ||
// cy:command ✘ uncaught exception AbortError: The user aborted a request. | ||
it('timeout using AbortController', () => { | ||
cy.visit('/commands/network-requests'); | ||
|
||
cy.intercept( | ||
{ | ||
method: 'PUT', | ||
url: 'comments/*', | ||
|
||
}, | ||
{ | ||
delay: 500, | ||
} | ||
).as('putComment'); | ||
|
||
cy.window().then((w) => { | ||
|
||
const controller = new AbortController(); | ||
setTimeout(() => controller.abort(), 100); | ||
|
||
fetch('/comments/10', { | ||
method: 'PUT', | ||
body: 'test', | ||
signal: controller.signal | ||
}); | ||
}); | ||
|
||
cy.wait('@putComment'); | ||
|
||
cy.get('.breaking-get', {timeout: 1}); | ||
}); | ||
}); | ||
|
||
|
||
context('Real Fetch Requests', () => { | ||
const testRealFetchRequest = (options) => { | ||
cy.visit('/commands/network-requests'); | ||
|
||
if (options.interceptPath) { | ||
// only intercepted fetch requests logs a response body. Read more at https://github.com/cypress-io/cypress/issues/17656 | ||
cy.intercept(options.interceptPath); | ||
} | ||
|
||
cy.window().then((window) => { | ||
const document = window.document; | ||
|
||
// Create a div to put a message to after receiving the fetch response | ||
const containerDiv = document.createElement('div'); | ||
containerDiv.className = 'network-request-message'; | ||
const networkComment = document.querySelector('.network-comment'); | ||
networkComment.after(containerDiv); | ||
|
||
// Crate a button that triggers the fetch | ||
const button = document.createElement('button'); | ||
button.className = 'network-request btn btn-primary'; | ||
button.innerHTML = 'Fetch Request '; | ||
button.addEventListener('click', () => | ||
fetch(options.url).then(() => { | ||
containerDiv.innerHTML = 'received response'; | ||
}) | ||
); | ||
containerDiv.before(button); | ||
}); | ||
|
||
cy.get('.network-request-message').should('not.contain', 'received response'); | ||
cy.get('.network-request').click(); | ||
cy.get('.network-request-message').should('contain', 'received response'); | ||
|
||
cy.get('.breaking-get', {timeout: 100}); // longer timeout to ensure fetch log update is included | ||
}; | ||
|
||
it('Fetch successful without interceptor', () => | ||
testRealFetchRequest({ | ||
url: 'https://jsonplaceholder.cypress.io/comments/1', | ||
})); | ||
|
||
it('Fetch failed without interceptors', () => | ||
testRealFetchRequest({ | ||
url: 'https://www.mocky.io/v2/5ec993803000009700a6ce1f', | ||
})); | ||
|
||
it('Fetch failed with interceptors', () => | ||
testRealFetchRequest({ | ||
url: 'https://www.mocky.io/v2/5ec993803000009700a6ce1f', | ||
interceptPath: 'https://www.mocky.io/**/*', | ||
})); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -132,6 +132,40 @@ describe('Commands logging.', () => { | |
}); | ||
}).timeout(60000); | ||
|
||
it('Should log fetch requests.', async () => { | ||
await runTest(commandBase([], [`fetchApi.spec.js`]), (error, stdout, stderr) => { | ||
const cleanStdout = clean(stdout, true); | ||
// cy.intercept stubbed aliased commands are logged | ||
expect(stdout).to.contain(`(putComment) STUBBED PUT https://example.cypress.io/comments/10\n`); | ||
expect(stdout).to.contain(`cy:fetch ${ICONS.warning}`); | ||
expect(stdout).to.contain(`Status: 404\n`); | ||
expect(stdout).to.contain(`Response body: {\n${PADDING} "error": "Test message."\n${PADDING}}\n`); | ||
|
||
// timeouts / abort | ||
expect(cleanStdout).to.contain( | ||
`(putComment) STUBBED PUT https://example.cypress.io/comments/10 - forceNetworkError called`, | ||
'network failed request contains failure message' | ||
); | ||
|
||
// test real fetch requests | ||
expect(cleanStdout).to.contain( | ||
`cy:fetch ${ICONS.route} GET https://jsonplaceholder.cypress.io/comments/1\n${PADDING} Status: 200\n`, | ||
'non-intercepted success fetch contains url and status' | ||
); | ||
|
||
expect(cleanStdout).to.contain( | ||
`cy:fetch ${ICONS.warning} GET https://www.mocky.io/v2/5ec993803000009700a6ce1f\n${PADDING} Status: 400\n`, | ||
'non-intercepted non-success fetch contains url and status' | ||
); | ||
|
||
expect(cleanStdout).to.contain( | ||
`cy:fetch ${ICONS.warning} GET https://www.mocky.io/v2/5ec993803000009700a6ce1f\n${PADDING} Status: 400\n${PADDING} Response body: {\n${PADDING} "status": "Wrong!",\n${PADDING} "data": {\n${PADDING} "corpo": "corpo da resposta",\n${PADDING} "titulo": "titulo da resposta"\n${PADDING} }\n${PADDING} }\n`, | ||
'intercepted non-success fetch contains url, status and a response body' | ||
); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be nice to have a case for aborted / timeout as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there's a way to force timeout but cypress itself doesn't handle that well (an error entry is added and the query command remains in a pending state). I can probably add now a test using forceNetworkError which seems to provide a better output. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If that doesn't affect other tests I say go for it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. test case added |
||
}); | ||
}).timeout(60000); | ||
|
||
it('Should only log XHR response body for non-successful requests not handled by cy.route.', async () => { | ||
await runTest(commandBase([], ['xhrTypes.spec.js']), (error, stdout, stderr) => { | ||
const cleanStdout = clean(stdout, true); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the purpose of the button? Wouldn't it have been more easier to directly trigger
window.fetch(..)
here? And using and intercept await its response?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
window.fetch would result in a xhr query because of fetch-polyfill. This is also testing the case of not-intercepted requests.