Mock network requests in Cypress - node.js

I've been tearing my hair out over this for ages now - and I hope someone can help me :)
I've been trying to stub a network request in Cypress for ages now.
commands.js
Cypress.Commands.add('login', (
email = 'email',
password = 'pass'
) => {
cy.server();
cy.visit('url');
cy.get('input[name="username"').type(email);
cy.get('form').submit();
cy.get('input[name="password"').type(password);
cy.get('form').submit();
});
mock.js
describe('mock', function() {
it('user can login', function() {
cy.login();
cy.get('main[role="main"]');
cy.route('GET',
'**/getIncentives*',
{info: {}, results: {}}
).as('oppty');
cy.wait('#oppty');
});
});
Chrome dev tools request
Cypress request failed
Any help here would be really appreciated - I've been going crazy!
Thanks so much,
Ollie

Currently, Cypress cy.route can only stub network requests that use XHRs
Native HTML form elements don't use XMLHTTPRequest, hence you cannot use cy.route to stub it.
Most people don't run into this issue because using native HTML forms is not as common nowadays
Edit:
You are also waiting on the route, but haven't actually done anything in your test. Your test should look something like this:
cy.route('GET',
'**/getIncentives*',
{info: {}, results: {}}
).as('oppty');
// cypress code that would cause a network request.
cy.wait('#oppty');
Also, make sure the request is of type:XHR:

Cypress will only find the network request after it's been aliased. The code in your question indicated you're not doing an action that would cause a network request:
cy.route('GET',
'**/getIncentives*',
{info: {}, results: {}}
).as('oppty');
// cypress expected something to cause a network request here
cy.wait('#oppty');
You should either move the call to route earlier in the test, or move the code that causes the request after the call to route.

Also note that cy.route() may not work with server side rendering (apparently).
I've had this problem when using NextJs, and solved it by first calling some other page, then navigate on the client to the page I actually want to test.
Like so:
describe('test cy.route', function() {
it( 'test 1', () => {
cy.server();
cy.route({
method: 'GET',
url: /.*\/api\/someApiCall/,
response: { 'someApiResponse': 'ok' },
status: 200
} ).as('myRouteAlias');
// go to start page: first page is server side rendered. cy.route doesn't work.
cy.visit('/');
// go to page to be tested: client side, cy.route works then.
cy.get( `a[href="/pageToBeTested"` ).should('exist').click();
// wait for page loaded
cy.url().should('include', 'pageToBeTested' );
// do something that requests '/api/someApiCall'.
invokeFunctionThatDoesTheRequest();
// wait for the request
cy.wait('#myRouteAlias');
});
});

Related

How do I use the the post method with fetch and koa?

This is a function on my front-end that makes the request.
function postNewUser(){
fetch(`http://12.0.0.1:8080/users/test`, {
method: 'POST',
body: {nome: name, email: "test#test.com.br", idade: 20}
})
}
This is my back-end code to receive the request.
router.post('/users/:id', koaBody(), ctx => {
ctx.set('Access-Control-Allow-Origin', '*');
users.push(ctx.request.body)
ctx.status = 201
ctx.body = ctx.params
console.log(users)
})
For some unknown reason I receive nothing. Not even a single error message. The "console.log()" on the back-end is also not triggered, so my theory is that the problem is on the front-end.
Edit
As sugested by gnososphere, I tested with Postman, and it worked. So now i know the problem must be on the fron-end code.
You can try your backend functionality with Postman. It's a great service for testing.
the request would look something like this
If the problem is on the frontend, double check your fetch method by posting to a website that will return data and logging that in your app.

Node, Express, Ajax, Form Submission

Help, I'm Stuck! I am playing with a CRUD setup with Node Express but with AJAX post request. I have the read form working fine.
The form has one input filed which is a lookup email. AJAX post the form data with the following code
if ($("#rsvp-search-form").length) {
$("#rsvp-search-form").validate({
rules: {
...
},
messages: {
...
},
submitHandler: function (form) {
$("#loader").css("display", "inline-block");
$.ajax({
type: "POST",
url: "/",
data: $(form).serialize(),
success: function (data, textStatus, jqXHR){
if (typeof data.redirect == 'string')
window.location = data.redirect;
}
,
error: function() {
$( "#loader").hide();
$( "#error").slideDown( "slow" );
setTimeout(function() {
$( "#error").slideUp( "slow" );
}, 5000);
}
});
return false; // required to block normal submit since you used ajax
}
});
}
I have a express post route to '/' that returns a status with res.status(#).send() and the proper success/error block is executed based on the whether status # is 400 or 200.
Now on the update form I have the same basic setup with many more form inputs, but the AJAX code does not process the res.status(#).send() response by executing the proper success or error block, instead it is just loading a new page with the same url as the request was processed from.
The AJAX code request is similar to the top with difference of url:
submitHandler: function (form) {
$("#loader").css("display", "inline-block");
$.ajax({
type: "POST",
//The website when loaded has an invitation
//object that is passed by express
url: "/rsvp/" +invitation._id,
data: $(form).serialize(),
dataType: 'application/json'
I verified that the proper post route is running and receiving the invitation._id. It returns res.staus(#).send() but the ajax does not process the success or error block it just redirects to the requesting url but does not actually render the url.
I don't know if it is just that the form is still processing the default action, if the response from express is not correct, etc etc
I hope I have been clear on my issue and someone knows what I am doing wrong here.
Regards!
Update!
I got it working. Though the url with variable was correct and express was receiving the proper id, JS was throwing an error causing everything to crash. I never caught the error because the page would reload to the blank page and clear the console. I fixed it by saving the id in a hidden field when rendering the form and used that instead. Seems to have fixed the problem.
Thanks for looking!

Sinon fake server not intercepting requests

Trying to use Sinon for the first time because of its fake server functionality that lets me stub an API response. Test itself is written for Mocha
However, the fake server doesn't seem to be intercepting the requests.
Code:
describe('when integrated', function() {
var server;
beforeEach(function() {
server = sinon.createFakeServer();
});
afterEach(function() {
server.restore();
});
it('can send a message to the notification service', function() {
server.respondWith("POST", new RegExp('.*/api/notificationmanager/messages.*'),
[200,
{ "Content-Type": "application/json" },
'{ "messageId":23561}'
]);
var messageOnly = new PushMessage(initMessageObj);
var originalUrl = PushMessage.serverUrl;
messageOnly.setServerAPI("http://a.fake.server/api/notificationmanager/messages");
console.log("fake server is: ", server);
messageOnly.notify()
.then(function(response) {
messageOnly.setServerAPI(originalUrl);
return response;
})
.then(function(response) {
response.should.be.above(0);
})
console.log(server.requests);
server.respond();
})
});
For reference, PushMessage is an object that has a static property serverUrl. I'm just setting the value to a fake URL & then resetting it.
The notify() function makes a post message using request-promise-native to the serverUrl set in the PushMessage's static property.
What seems to be happening, is that the POST request ends up being properly attempted against the URL of http://a.fake.server/api/notificationmanager/messages, resulting in an error that the address doesn't exist...
Any idea what I'm doing wrong...? Thanks!
There have been several issues on the Sinon GitHub repository about this. Sinon's fake server:
Provides a fake implementation of XMLHttpRequest and provides several interfaces for manipulating objects created by it.
Also fakes native XMLHttpRequest and ActiveXObject (when available, and only for XMLHTTP progids). Helps with testing requests made with XHR.
Node doesn't use XHR requests, so Sinon doesn't work for this use case. I wish it did too.
Here's an issue that breaks it down: https://github.com/sinonjs/sinon/issues/1049
Nock is a good alternative that works with Node: https://www.npmjs.com/package/nock

POST not working for Mocha/Chai tests on Express REST API

I'm having a problem with Mocha tests on my Express app. I have a REST API set up that I know works in the browser, but it's getting a bit large and therefore manual testing has become tedious.
Anyway, all my GET tests work just fine, but when I add tests for my POST requests, they fail:
Uncaught AssertionError: expected { Object (domain, _events, ...) } to have status code 200 but got 400
at testPostSingle (test-app.js:297:21)
at test-app.js:195:21
at Test.Request.callback (/home/jacobd/healthboard/node_modules/chai-http/node_modules/superagent/lib/node/index.js:603:3)
at Stream.<anonymous> (/home/jacobd/healthboard/node_modules/chai-http/node_modules/superagent/lib/node/index.js:767:18)
at Unzip.<anonymous> (/home/jacobd/healthboard/node_modules/chai-http/node_modules/superagent/lib/node/utils.js:108:12)
at _stream_readable.js:944:16
I'm using Mocha, with the Chai should library and chai-http for requests.
My relevant code:
models.forEach(function(model, i) {
var url = '/api/v1/' + model;
var list = lists[i];
/****************************** API POST TESTS ******************************/
describe(util.format('API: /%s POST', model), function() {
var minArgsObj = {
title: 'Test ' + model + ' Title'
};
// Initialize list at start
before(function(done) {
instances._init(function(err) {
if (!err) done();
});
});
// Nuke list before each test
beforeEach(function(done) {
list.clear();
done();
});
// Make sure POST single object works
it('should create and add model on ' + url + ' with minimal arguments', function(done) {
var req = {};
req.options = _.clone(minArgsObj);
chai.request('server')
.post(url)
.send(req)
.end(function(err, res) {
testPostSingle(res, minArgsObj);
list.list.length.should.equal(1);
res.body.should.eql(list.list[0]);
done();
});
});
});
});
function testPostSingle(res, ref) {
res.should.have.status(200);
...
}
When I put a log message in the POST route declaration, it doesn't show up, so that tells me that my request is getting stopped before even hitting my server. Maybe the route isn't getting mounted properly in Mocha? It works when I make the request outside of Mocha, but I can't figure out why it won't work in a test environment.
Thanks in advance for your help, and let me know if you need any more info!
The relevant code is in testPostSingle. Your call is returning 400 bad request. Are you sure the route is correct or is set up to handle POST as well as GET? Are you sure parameters are correct? Make testPostSingle print out the body of the HTTP response so you know the details. You can also add some debug code in the route for that request on your server.
I'm terrible and issue is very simple:
Instead of chai.request('server')
It needs to be chai.request(server)
Thanks to Jason Livesay for pointing me at the right line!

Modify HTTP responses from a Chrome extension

Is it possible to create a Chrome extension that modifies HTTP response bodies?
I have looked in the Chrome Extension APIs, but I haven't found anything to do this.
In general, you cannot change the response body of a HTTP request using the standard Chrome extension APIs.
This feature is being requested at 104058: WebRequest API: allow extension to edit response body. Star the issue to get notified of updates.
If you want to edit the response body for a known XMLHttpRequest, inject code via a content script to override the default XMLHttpRequest constructor with a custom (full-featured) one that rewrites the response before triggering the real event. Make sure that your XMLHttpRequest object is fully compliant with Chrome's built-in XMLHttpRequest object, or AJAX-heavy sites will break.
In other cases, you can use the chrome.webRequest or chrome.declarativeWebRequest APIs to redirect the request to a data:-URI. Unlike the XHR-approach, you won't get the original contents of the request. Actually, the request will never hit the server because redirection can only be done before the actual request is sent. And if you redirect a main_frame request, the user will see the data:-URI instead of the requested URL.
I just released a Devtools extension that does just that :)
It's called tamper, it's based on mitmproxy and it allows you to see all requests made by the current tab, modify them and serve the modified version next time you refresh.
It's a pretty early version but it should be compatible with OS X and Windows. Let me know if it doesn't work for you.
You can get it here http://dutzi.github.io/tamper/
How this works
As #Xan commented below, the extension communicates through Native Messaging with a python script that extends mitmproxy.
The extension lists all requests using chrome.devtools.network.onRequestFinished.
When you click on of the requests it downloads its response using the request object's getContent() method, and then sends that response to the python script which saves it locally.
It then opens file in an editor (using call for OSX or subprocess.Popen for windows).
The python script uses mitmproxy to listen to all communication made through that proxy, if it detects a request for a file that was saved it serves the file that was saved instead.
I used Chrome's proxy API (specifically chrome.proxy.settings.set()) to set a PAC as the proxy setting. That PAC file redirect all communication to the python script's proxy.
One of the greatest things about mitmproxy is that it can also modify HTTPs communication. So you have that also :)
Like #Rob w said, I've override XMLHttpRequest and this is a result for modification any XHR requests in any sites (working like transparent modification proxy):
var _open = XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function (method, URL) {
var _onreadystatechange = this.onreadystatechange,
_this = this;
_this.onreadystatechange = function () {
// catch only completed 'api/search/universal' requests
if (_this.readyState === 4 && _this.status === 200 && ~URL.indexOf('api/search/universal')) {
try {
//////////////////////////////////////
// THIS IS ACTIONS FOR YOUR REQUEST //
// EXAMPLE: //
//////////////////////////////////////
var data = JSON.parse(_this.responseText); // {"fields": ["a","b"]}
if (data.fields) {
data.fields.push('c','d');
}
// rewrite responseText
Object.defineProperty(_this, 'responseText', {value: JSON.stringify(data)});
/////////////// END //////////////////
} catch (e) {}
console.log('Caught! :)', method, URL/*, _this.responseText*/);
}
// call original callback
if (_onreadystatechange) _onreadystatechange.apply(this, arguments);
};
// detect any onreadystatechange changing
Object.defineProperty(this, "onreadystatechange", {
get: function () {
return _onreadystatechange;
},
set: function (value) {
_onreadystatechange = value;
}
});
return _open.apply(_this, arguments);
};
for example this code can be used successfully by Tampermonkey for making any modifications on any sites :)
Yes. It is possible with the chrome.debugger API, which grants extension access to the Chrome DevTools Protocol, which supports HTTP interception and modification through its Network API.
This solution was suggested by a comment on Chrome Issue 487422:
For anyone wanting an alternative which is doable at the moment, you can use chrome.debugger in a background/event page to attach to the specific tab you want to listen to (or attach to all tabs if that's possible, haven't tested all tabs personally), then use the network API of the debugging protocol.
The only problem with this is that there will be the usual yellow bar at the top of the tab's viewport, unless the user turns it off in chrome://flags.
First, attach a debugger to the target:
chrome.debugger.getTargets((targets) => {
let target = /* Find the target. */;
let debuggee = { targetId: target.id };
chrome.debugger.attach(debuggee, "1.2", () => {
// TODO
});
});
Next, send the Network.setRequestInterceptionEnabled command, which will enable interception of network requests:
chrome.debugger.getTargets((targets) => {
let target = /* Find the target. */;
let debuggee = { targetId: target.id };
chrome.debugger.attach(debuggee, "1.2", () => {
chrome.debugger.sendCommand(debuggee, "Network.setRequestInterceptionEnabled", { enabled: true });
});
});
Chrome will now begin sending Network.requestIntercepted events. Add a listener for them:
chrome.debugger.getTargets((targets) => {
let target = /* Find the target. */;
let debuggee = { targetId: target.id };
chrome.debugger.attach(debuggee, "1.2", () => {
chrome.debugger.sendCommand(debuggee, "Network.setRequestInterceptionEnabled", { enabled: true });
});
chrome.debugger.onEvent.addListener((source, method, params) => {
if(source.targetId === target.id && method === "Network.requestIntercepted") {
// TODO
}
});
});
In the listener, params.request will be the corresponding Request object.
Send the response with Network.continueInterceptedRequest:
Pass a base64 encoding of your desired HTTP raw response (including HTTP status line, headers, etc!) as rawResponse.
Pass params.interceptionId as interceptionId.
Note that I have not tested any of this, at all.
While Safari has this feature built-in, the best workaround I've found for Chrome so far is to use Cypress's intercept functionality. It cleanly allows me to stub HTTP responses in Chrome. I call cy.intercept then cy.visit(<URL>) and it intercepts and provides a stubbed response for a specific request the visited page makes. Here's an example:
cy.intercept('GET', '/myapiendpoint', {
statusCode: 200,
body: {
myexamplefield: 'Example value',
},
})
cy.visit('http://localhost:8080/mytestpage')
Note: You may also need to configure Cypress to disable some Chrome-specific security settings.
The original question was about Chrome extensions, but I notice that it has branched out into different methods, going by the upvotes on answers that have non-Chrome-extension methods.
Here's a way to kind of achieve this with Puppeteer. Note the caveat mentioned on the originalContent line - the fetched response may be different to the original response in some circumstances.
With Node.js:
npm install puppeteer node-fetch#2.6.7
Create this main.js:
const puppeteer = require("puppeteer");
const fetch = require("node-fetch");
(async function() {
const browser = await puppeteer.launch({headless:false});
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on('request', async (request) => {
let url = request.url().replace(/\/$/g, ""); // remove trailing slash from urls
console.log("REQUEST:", url);
let originalContent = await fetch(url).then(r => r.text()); // TODO: Pass request headers here for more accurate response (still not perfect, but more likely to be the same as the "actual" response)
if(url === "https://example.com") {
request.respond({
status: 200,
contentType: 'text/html; charset=utf-8', // For JS files: 'application/javascript; charset=utf-8'
body: originalContent.replace(/example/gi, "TESTING123"),
});
} else {
request.continue();
}
});
await page.goto("https://example.com");
})();
Run it:
node main.js
With Deno:
Install Deno:
curl -fsSL https://deno.land/install.sh | sh # linux, mac
irm https://deno.land/install.ps1 | iex # windows powershell
Download Chrome for Puppeteer:
PUPPETEER_PRODUCT=chrome deno run -A --unstable https://deno.land/x/puppeteer#16.2.0/install.ts
Create this main.js:
import puppeteer from "https://deno.land/x/puppeteer#16.2.0/mod.ts";
const browser = await puppeteer.launch({headless:false});
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on('request', async (request) => {
let url = request.url().replace(/\/$/g, ""); // remove trailing slash from urls
console.log("REQUEST:", url);
let originalContent = await fetch(url).then(r => r.text()); // TODO: Pass request headers here for more accurate response (still not perfect, but more likely to be the same as the "actual" response)
if(url === "https://example.com") {
request.respond({
status: 200,
contentType: 'text/html; charset=utf-8', // For JS files: 'application/javascript; charset=utf-8'
body: originalContent.replace(/example/gi, "TESTING123"),
});
} else {
request.continue();
}
});
await page.goto("https://example.com");
Run it:
deno run -A --unstable main.js
(I'm currently running into a TimeoutError with this that will hopefully be resolved soon: https://github.com/lucacasonato/deno-puppeteer/issues/65)
Yes, you can modify HTTP response in a Chrome extension. I built ModResponse (https://modheader.com/modresponse) that does that. It can record and replay your HTTP response, modify it, add delay, and even use the HTTP response from a different server (like from your localhost)
The way it works is to use the chrome.debugger API (https://developer.chrome.com/docs/extensions/reference/debugger/), which gives you access to Chrome DevTools Protocol (https://chromedevtools.github.io/devtools-protocol/). You can then intercept the request and response using the Fetch Domain API (https://chromedevtools.github.io/devtools-protocol/tot/Fetch/), then override the response you want. (You can also use the Network Domain, though it is deprecated in favor of the Fetch Domain)
The nice thing about this approach is that it will just work out of box. No desktop app installation required. No extra proxy setup. However, it will show a debugging banner in Chrome (which you can add an argument to Chrome to hide), and it is significantly more complicated to setup than other APIs.
For examples on how to use the debugger API, take a look at the chrome-extensions-samples: https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/mv2-archive/api/debugger/live-headers
I've just found this extension and it does a lot of other things but modifying api responses in the browser works really well: https://requestly.io/
Follow these steps to get it working:
Install the extension
Go to HttpRules
Add a new rule and add a url and a response
Enable the rule with the radio button
Go to Chrome and you should see the response is modified
You can have multiple rules with different responses and enable/disable as required. I've not found out how you can have a different response per request though if the url is the same unfortunately.

Resources