Skip to content

Commit

Permalink
Merge pull request #126 from Erik-Outreach/develop
Browse files Browse the repository at this point in the history
add fetch collector
  • Loading branch information
archfz authored Nov 3, 2021
2 parents c482c55 + 729c28c commit c11efdc
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 1 deletion.
65 changes: 65 additions & 0 deletions src/collector/LogCollectCypressFetch.js
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);
}
});
}

}
1 change: 1 addition & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module.exports = {

CYPRESS_LOG: 'cy:log',
CYPRESS_XHR: 'cy:xhr',
CYPRESS_FETCH: 'cy:fetch',
CYPRESS_REQUEST: 'cy:request',
CYPRESS_ROUTE: 'cy:route',
CYPRESS_INTERCEPT: 'cy:intercept',
Expand Down
2 changes: 1 addition & 1 deletion src/installLogsCollector.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ interface SupportOptions {
* What types of logs to collect and print.
* By default all types are enabled.
* The 'cy:command' is the general type that contain all types of commands that are not specially treated.
* @default ['cons:log','cons:info', 'cons:warn', 'cons:error', 'cy:log', 'cy:xhr', 'cy:request', 'cy:route', 'cy:command']
* @default ['cons:log','cons:info', 'cons:warn', 'cons:error', 'cy:log', 'cy:xhr', 'cy:fetch', 'cy:request', 'cy:route', 'cy:command']
*/
collectTypes?: readonly string[];

Expand Down
4 changes: 4 additions & 0 deletions src/installLogsCollector.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const LogCollectCypressRequest = require("./collector/LogCollectCypressRequest")
const LogCollectCypressRoute = require("./collector/LogCollectCypressRoute");
const LogCollectCypressIntercept = require("./collector/LogCollectCypressIntercept");
const LogCollectCypressXhr = require("./collector/LogCollectCypressXhr");
const LogCollectCypressFetch = require("./collector/LogCollectCypressFetch");
const LogCollectCypressLog = require("./collector/LogCollectCypressLog");

const LogCollectorState = require("./collector/LogCollectorState");
Expand Down Expand Up @@ -50,6 +51,9 @@ function registerLogCollectorTypes(logCollectorState, config) {
if (config.collectTypes.includes(LOG_TYPE.CYPRESS_XHR)) {
(new LogCollectCypressXhr(logCollectorState, config)).register();
}
if (config.collectTypes.includes(LOG_TYPE.CYPRESS_FETCH)) {
(new LogCollectCypressFetch(logCollectorState, config)).register();
}
if (config.collectTypes.includes(LOG_TYPE.CYPRESS_REQUEST)) {
(new LogCollectCypressRequest(logCollectorState, config)).register();
}
Expand Down
1 change: 1 addition & 0 deletions src/installLogsCollector.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"cons:error",
"cy:log",
"cy:xhr",
"cy:fetch",
"cy:request",
"cy:intercept",
"cy:route",
Expand Down
4 changes: 4 additions & 0 deletions src/installLogsPrinter.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ function logToTerminal(messages, options, data) {
color = 'green';
icon = LOG_SYMBOLS.route;
trim = options.routeTrimLength || 5000;
} else if (type === LOG_TYPES.CYPRESS_FETCH) {
color = 'green';
icon = LOG_SYMBOLS.route;
trim = options.routeTrimLength || 5000;
} else if (type === LOG_TYPES.CYPRESS_ROUTE) {
color = 'green';
icon = LOG_SYMBOLS.route;
Expand Down
145 changes: 145 additions & 0 deletions test/cypress/integration/fetchApi.spec.js
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/**/*',
}));
});
});
34 changes: 34 additions & 0 deletions test/specs/commandsLogging.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
);

});
}).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);
Expand Down

0 comments on commit c11efdc

Please sign in to comment.