Stubbing network request with cypress - cypress

I am trying to mock a server in some UI tests that I am writing using cypress. I am probably making some basic mistake and might not be understanding how cypress is stubbing requests. Here is an example app that I copied straight from expressjs -
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => res.send('Hello from /'));
app.get('/user', (req, res) => res.send('Hello from /user'));
app.listen(port, () => console.log(`Example app listening on port ${port}!`));
And then wrote a simple test using cypress -
describe('Stubbed request', () => {
it('sends whatever response you want', () => {
cy.visit('http://localhost:3000/');
cy.server();
cy.route({
method: 'GET',
url: '/user',
response: [],
}).as('bar');
cy.visit('http://localhost:3000/user'); // cy.request() has same behavior
cy.wait('#bar');
})
})
I was hoping that instead of 'Hello from user/', I should get an empty response since I have stubbed it with cypress. Even cy.wait fails with message - "CypressError: Timed out retrying: cy.wait() timed out waiting 5000ms for the 1st request to the route: 'bar'. No request ever occurred." I am obviously doing something wrong. Can someone please help me understand what am I doing wrong? Thanks in advance.

You can only stub/spy XHR requests that are created within your app. cy.visit will never make such a xhr request. neither you can stub a request made with cy.request because thise requests are intended to work indepentent from all active network stubs.
This means, you are able to stub /users but in your test you can make a real request to that route by using cy.request.
In your case, you must deliver a static html site with express that e.g. contains a button. And after the button is clicked, you can execute e.g. $.get(' /user'). Then this request will be stubbed.
Currently I am not at home so I can not provide a runnable example. But with the hints above you should be able to do this. Let me know if you need further assistance.
Also you can take a look at cypress XHR API call validation in test cases . Here I have provided a runnable example fir stubbing and spying.

Related

Cypress verify API request

I want to verify if the API request contains what it needs to contain, but not sure what I am doing wrong as I have always this error:
cy.wait() timed out waiting 5000ms for the 1st request to the route: apiCheck. No request ever occurred.
I have a link in the main menu, after clicking that link the new page opens and API call is iniciated immediately http://localhost:8081/currencies
So in Cypress I have this:
cy.get('ul li[title="Menu item"]>a').click();
cy.intercept({
method: 'GET',
url: '/currencies',
}).as('apiCheck')
cy.wait('#apiCheck').then((interception) => {
expect(interception.response.statusCode).to.equal(200);
expect(interception.response.body[0]).to.have.property('geographyName', 'EMEA')
})
As you can see from Cypress test runner screenshot, the request is there and has status 200, but still it says no request ever occurred.
ah, well, seems I need to call cy.intercept before the link is clicked, then it seems working.

capture websocket request in cypress

I'm trying to capture all the requests that occur during the test.
My application uses WebSocket and with the intercept command I can't catch wss requests.
Is there any way to do this?
I don't think web sockets can be caught directly by the intercept command.
One approach is to observe the results of ws communication, as shown here Testing a websocket application with Cypress between two instance of Cypress runner.
If your app talks between app and server, then start the server in /cypress/plugins/index.js and use cy.task() to get the server end of the communication.
There is also a library cypress-websocket-testing which uses rxjs, which is quite powerful but takes some getting used to.
cy.streamRequest(config, options).then(results => {
expect(results).to.not.be.undefined;
})
// When in need of a bit more flexibility
cy.stream(config).then(subject => {
subject
.pipe(
takeUntil(timer(1000)),
reduce((acc , val) => acc.concat([val]), [])
)
.subscribe({
next: (results) => {
expect(results).to.not.be.undefined;
},
error: (err) => {},
complete: done
});
});
Not supported in Cypress as of now only xhr requests are supported by cy.intercept, you need to create wesbsocket server and listen at a port and it needed to be started in another process and then using fixtures send the required response data

How to get POST API response in Cypress?

I am working on a project to automate using Cypress. In this project, I need to create an order for a patient. When I click on the submit button it will call the following API https://ibis-dev.droicelabs.us/api/dispenser/orders/ using the POST method and return one unique order that I want to get.
I have registered cy.intercept on top of my test like this:
cy.intercept({
method: 'POST',
url: 'https://ibis-dev.droicelabs.us/api/dispenser/orders/',
}).as('ordersCall')
And when the submit button is clicked I have used:
cy.clickOnElementUsingXpath(practicePageSelectors.submit_CreateOrderButton); // click on submit button
cy.wait('#ordersCall')
.its('response.body')
.then((body) => {
// parsing might be not needed always, depends on the api response
const bodyData = JSON.parse(body)
cy.log(bodyData)
})
But it returns the following error:
Timed out retrying after 5000ms: cy.wait() timed out waiting 5000ms for the 1st request to the route: ordersCall. No request ever occurred in cy.wait('#ordersCall')
Can anyone help me to get an orderID? Is there any other way to get the orderID?
After checking the provided images in the question comments, the error is as follows: Your intercept command in your Cypress test is waiting for requests to be made to your DEV environment, but looking at your last image from the console in the Cypress test runner your requests are being made to the QA environment.
So you either have to adjust your Interceptor like this:
cy.intercept({
method: 'POST',
url: 'https://ibis-qa.droicelabs.us/api/dispenser/orders/',
}).as('ordersCall')
or think about using relative paths for API calls to be independent from the environment:
cy.intercept({
method: 'POST',
url: '/api/dispenser/orders/',
}).as('ordersCall')

"Not allowed to request resource" in Safari and "Blocked loading mixed active content" in Firefox. Perfect functionality in Chrome

I am working on an app using a React frontend and Express backend, with GraphQL setup through Apollo (I am following and modifying tutorial https://www.youtube.com/playlist?list=PLN3n1USn4xlkdRlq3VZ1sT6SGW0-yajjL)
I am currently attempting deployment, and am doing so with Heroku. Everything functions perfectly on my local machine before deployment and on Heroku in Google Chrome. However, I get the aforementioned errors in Safari and Firefox, respectively. Wondering why this is happening in these browsers and how to fix.
I have spent about 10 hrs doing research on this. Things I tried that made no difference:
I tried adding CORS to my express backend
I tried serving the graphql endpoint as HTTPS
Moving app.use(express.static) in main app.js server file
I couldn't find many other things to try. Everywhere I looked seemed to say that CORS fixed the problem, but mine persists.
Github link: https://github.com/LucaProvencal/thedrumroom
Live Heroku App: https://powerful-shore-83650.herokuapp.com/
App.js (express backend):
const cors = require('cors')
// const fs = require('fs')
// const https = require('https')
// const http = require('http')
app.use(express.static(path.join(__dirname, 'client/build')));
app.use(cors('*')); //NEXT TRY app.use(cors('/login')) etc...
app.use(cors('/*'));
app.use(cors('/'));
app.use(cors('/register'));
app.use(cors('/login'));
app.get('/login', (req, res) => {
res.sendFile(path.join(__dirname, "client", "build", "index.html"));
});
app.get('/register', (req, res) => {
res.sendFile(path.join(__dirname, "client", "build", "index.html"));
});
server.applyMiddleware({ app }); // app is from the existing express app. allows apollo server to run on same listen command as app
const portVar = (process.env.PORT || 3001) // portVar cuz idk if it will screw with down low here im tired of dis
models.sequelize.sync(/*{ force: true }*/).then(() => { // syncs sequelize models to postgres, then since async call starts the server after
app.listen({ port: portVar }, () =>
console.log(`🚀 ApolloServer ready at http://localhost:3001${server.graphqlPath}`)
)
app.on('error', onError);
app.on('listening', onListening);
});
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
Full file is on Github, I tried to post only relevant parts above.
The expected result is that it works in all browsers. It seems from my research that since Heroku serves on HTTPS, Safari and Firefox do not allow requests to HTTP (which is where the graphql server is located, http://localhost:3001/graphql'). When I tried serving Apollo on HTTPS, Heroku just crashed, giving me H13 and 503 errors.
Thanks for any help...
This may also happen during local development when running the front end using HTTPS, but the back end using HTTP.
This is because CORS treats two URLs as having the same origin "only when the scheme, host, and port all match". Matching scheme means matching protocols e.g. both http, or both https.
One solution for local development is to proxy the back end using a tool such as ngrok.
Suppose the front end uses an environment variable which indicates the back end's URL:
BACK_END_API_URL=http://localhost:3005. Then do the following.
Install ngrok
Identify what port the back end is running on e.g. 3005
Run ngrok http 3005 at the command line, which will establish both http and https endpoints. Both will ultimately proxy the requests to the same back end endpoint: http://localhost:3005
After running ngrok it will display the http and https endpoints you can use. Put the one that matches the front end protocol you're using (e.g. https) into your front end environment variable that indicates the back end's URL e.g.
BACK_END_API_URL=https://1234asdf5678ghjk.ngrok.io
Was going to delete this because it is such a silly problem but maybe it will help someone in the future:
I simply replaced all of my 'http://localhost:PORT' endpoints in development with '/graphql'. I assumed that localhost meant local the machine running the code. But an app running on Heroku does not point to localhost. The express server is served on the url (https://powerful-shore-83650.herokuapp.com/) in our case...
At any rate I am so glad I came to a solution. I have a full stack app deployed and connected to a db. Hopefully this post can save someone lots of time.

Signin request failing due to invalid csrf

I am testing a signin page for keystonejs (http://demo.keystonejs.com/) and cannot get the request to succeed with cypress. A signin request without cypress has the following request headers:
while a request with a cypress test has the following request headers:
The only difference I can see is that the cookie is not set in the cypress test request. And because of that the request gets a 403. I am actually using a local version of that server in which I have the email/password configured as the one in the images. The demo site uses a different set provided in that page.
The test is simply:
describe('The Keystone Admin UI Signin Page', function () {
before(function() {
cy.visit('http://localhost:3000/keystone/signin')
})
it('should signin successfully with valid email/password', function () {
cy.get('#signin-view input[name=email]').clear().type('user#test.e2e');
cy.get('#signin-view input[name=password]').clear().type('test');
cy.get('#signin-view button[type=submit]').click();
cy.get('#react-root').should('be.visible');
})
})
Anyway to get around this?
I know this is an older question but I'll post this in case it helps anyone.
The key to fixing this issue for me was that Cypress clears all cookies before each test (https://docs.cypress.io/api/commands/clearcookies.html#).
So in the example above (and in my experience):
The first test (loading the page) is run and passes fine - at this point the browser has set the csrftoken cookie
The second test (submitting the form) runs but before this happens Cypress has deleted the csrftoken cookie - hence the error
To fix this I had to whitelist the csrftoken cookie in Cypress config which means that it will be exempt from this clearing process:
Cypress.Cookies.defaults({
whitelist: "csrftoken"
})
You can do this inline in your test file but the docs recommend using a support file:
A great place to put this configuration is in your cypress/support/index.js file, since it is loaded before any test files are evaluated.
Hope this helps, Stephen.
Following on from FlatSteve's answer, using with Laravel and updated prop names use;
Cypress.Cookies.defaults({
preserve: ["XSRF-TOKEN", "your_laravel_session_name", "remember_token"]
});

Resources