I'm having a problem with stubbing a simple request to an API using the cypress' cy.server() and cy.route().
Here's the failing test:
it.only("should show an error message for server errors", () => {
const name = "It doesnt matter";
const email = "takenemail#yopmail.com";
const pass = "123123";
// run the server and define the stubbed route
cy.server();
cy.route(
"POST",
`${serverBaseUrl}/auth/register`,
"fixture:register-fail.json"
).as("postRegister");
// fill in the registration form and hit submit
cy.visit("/auth/register");
cy.get(selectors.registerForm.name).type(name);
cy.get(selectors.registerForm.email).type(email);
cy.get(selectors.registerForm.password).type(pass);
cy.get(selectors.registerForm.registerButton).click();
// intercept the request and mock it
cy.wait("#postRegister"); // this fails.
cy.get(selectors.registerForm.genericErrors).contains(
"This email has already been taken"
);
});
and the error:
cy.wait() timed out waiting 5000ms for the 1st request to the route: postRegister. No request ever occurred.
Note: even though it says that No request ever occurred. I can still see the request being send and a response received in the console's Network tab (which means the stub has been bypassed and a regular request's been made).
Any ideas what's happening?
Thanks in advance.
Ok, seems like i have found the problem.
It turns out that using the fetch API is not supported by cypress.
The workaround - using whatwg-fetch, which is basically a polyfill for the fetch api, working with XHR behind the scenes.
Install the whatwg-fetch package: npm install whatwg-fetch --save
Import it in your project: import "whatwg-fetch";
Last, but very important - remove the fetch object from the window before every page load in the cypress environment like this:
// you can define this in the commands file for example...
Cypress.on("window:before:load", (win) => delete win.fetch);
or an alternative, per-visit approach:
it("some test", () => {
cy.visit("/url", {
onBeforeLoad: win => delete win.fetch // <---
});
// ...the rest of the test
});
Doing this will kick-in the polyfill and the stubbing should be working properly after this intervention.
Related
I am using the method described in the answer of this question Log network failures in Cypress to log network failures. I basically intercept every failing request and its response and log it in some array as follows:
cy.intercept('*', (request) => {
request.continue(response => {
if(response.statusMessage !== "OK") {
networkFails.push({request, response})
}
})
})
The tests run perfectly fine, the problem is at the end of the tests I get this error
How do I solve this problem?
I ran into the same issue, and it turns out using request.continue is what was causing it.
request.continue is expecting a response, but when you get a socket hangup (ConnResetException), or in my case the socket being closed before it was finished writing, it triggers a cypress error.
Instead of request.continue, you'll want to use request.on( 'response', response => {
Your full code snippet should look like this:
cy.intercept('*', (request) => {
request.on( 'response', response => {
if(response.statusMessage !== "OK") {
networkFails.push({request, response})
}
})
})
The cypress documentation does a great job of explaining all of the different options here: https://docs.cypress.io/api/commands/intercept#Request-events
For reference, this was the specific cypress error I was getting while using .continue(). The error message was a bit different from yours but essentially the same problem requiring the same solution.
A callback was provided to intercept the upstream response, but a network error occurred while making the request:
Error: Socket closed before finished writing response
I need to test some pages on a project and this project do some APIs call to external services.
I need to make sure that these calls are made and check if my page change accordingly to the response.
This is my test:
describe('A logged in user', () =>{
it('can see his subscriptions', () => {
...... some checks .......
cy.intercept('https://example.com/api/v2/user-panel/get-subscription').as('userSubscription');
cy.wait('#userSubscription', { timeout: 35000 }).then(() => {
cy.contains('some text');
});
});
});
When I run the code seems that it can't se the API call but the page content change correctly.
This is the cypress response:
Timed out retrying after 35000ms: cy.wait() timed out waiting 35000ms
for the 1st request to the route: userSubscription. No request ever
occurred.
I tried to increase the timeout, event if the page loads in 1 second, but the result is the same.
There is something am I missing?
Doing the cy.wait() right after the cy.intercept() is not going to work.
Whatever triggers the API calls (a cy.visit() or a .click()) must occur after the intercept has been set up, and it therefore is ready to catch the API call.
From the Network Requests docs
cy.intercept('/activities/*', { fixture: 'activities' }).as('getActivities')
cy.intercept('/messages/*', { fixture: 'messages' }).as('getMessages')
// visit the dashboard, which should make requests that match
// the two routes above
cy.visit('http://localhost:8888/dashboard')
// pass an array of Route Aliases that forces Cypress to wait
// until it sees a response for each request that matches
// each of these aliases
cy.wait(['#getActivities', '#getMessages'])
// these commands will not run until the wait command resolves above
cy.get('h1').should('contain', 'Dashboard')
To avoid adding cy.wait, am trying to user the explicit wait, but seems it is broken. What I missed here
const APIAccountPage= {
APIAccount(name) {
//cy.wait(14000);
cy.server();
cy.route('GET', '/api/accounts').as('Accounts');
cy.wait('#Accounts', { timeout: 10000 })
.its('status').should('eq', 200);
cy.get('.vbutton__baseline___3XNit.fonts__letterSpacing___3l5GB.styles__searchIconWrapper___19oVL.styles__iconButton___LLzft')
.should('be.visible')
.should('not.be.disabled');
cy.get('.vbutton__baseline___3XNit.fonts__letterSpacing___3l5GB.styles__searchIconWrapper___19oVL.styles__iconButton___LLzft').click();
cy.wait(4000);
cy.get('.styles__patientSearchClass___2iGOV').type(name);
cy.wait(4000);
cy.get('.styles__patientSearchClass___2iGOV').contains(name).click();
}
}
module.exports = APIAccountPage;
Error Details
Cypress Window
Try this following, if the /api/account is not working, there may be multiple reason, one of which I am thinking is a backend api (which happened in my case) for which you need to handle the authentication.
If it is same then try to provide the complete URL like,
cy.server()
cy.route(
'GET',
'https://<URL>/api/accounts',
'{"errors":[]}',
).as('account');
and call cy.wait('#account') whichever place is appropriate.
Just a sidenote cy.server() and cy.route has been deprecated since 6.0.0, you can read about it from here. Instead, a new method has been introduced called cy.intercept().
cy.intercept('GET', 'https://some-url/api/accounts').as('Accounts')
cy.wait('#Account') //Wait
I am trying to stub a response from an API. Following the Cypress docs, I landed on this:
cy.intercept('GET', '/v1/answers', { fixture: 'answers.json' }).as(
'getAnswers'
)
cy.wait('#getAnswers').then(console.log)
The console.log yields the correct response.
However the UI component does not appear to consume this data. Instead the data in the component comes back as empty. Is there something I am missing on the correct usage of intercept and fixtures in Cypress?
For anyone having this issue. In the Cypress app's browser console I noticed a CORS error and needed to add Access-Control-Allow-Origin.
cy.intercept('GET', '/v1/answers', {
fixture: 'answers.json',
headers: {
'Access-Control-Allow-Origin': '*', // <== This fixed it
},
}).as('getAnswers')
:)
I have stuck with Cypress fixtures. Can't intercept an XHR request with SSR and navigation routing.
cypress/integration/page.js:
const fetch = require("unfetch")
describe("/about", () => {
beforeEach(() => {
cy.visit("/", { // Visit home page to trigger SSR
onBeforeLoad (win) {
win.fetch = fetch // replace fetch with xhr implementation
},
})
})
it("Has a correct title", () => {
cy.server()
cy.fixture("about").then(about => {
// about object is correct here, like {title: "About+"}
cy.route("GET", "http://localhost:8080/api/documents/url", about) // Not sure where .route should be
cy.get(".main > :nth-child(1) > a").click() // Navigate to the /about page
cy.route("GET", "http://localhost:8080/api/documents/url", about) // Tried both ways
// This hits my server API without stubbing, getting {title: "About"}
cy.title().should("eq", "About+") // About != About+
})
})
})
cypress/fixtures/about.json:
{"title": "About+"}
I see an XHR request (type=xhr) in Dev Tools and it doesn't use the above about stub object but hits real API instead. Why? Double checked URL and method – 100% the same. Can it be that route is coupled to visit and ignores click-based routing?!
Rechecking this once again, I've found a solution. Let me share the details for everyone interested:
1) I use Next.js which is an excellent tool for SSR but it doesn't allow you to disable server-side rendering (yet) according to this and this issues.
2) You can use Cypress with SSR pages but, in this way, you're limited to testing real HTML. Which means you have to either couple tests to real data (not good in most cases) or stub the database itself (slow). In general, you want to stub HTTP requests.
3) Cypress can't stub fetch requests and mocking fetch with XHR-based implementation was trickier than I thought.
First you need to:
// cypress/integration/your-test.js
Cypress.on('window:before:load', (win) => {
delete win.fetch
})
Then:
// pages/your-page.js
Entry.getInitialProps = async function() {
window.fetch = require("unfetch").default
...
}
Other combinations of delete & update code lines I tried didn't yield positive results. For example, when I had window.fetch = line in the test file it didn't work and fetch.toString() gave "native code". Not sure why, no time to explore further.
Axios solves the above but I don't like to bloat my bundle with extra stuff. You can inject XHR-based fetch for tests only.
4) The most important missing piece. You need to wait for route.
it("Has a correct title", () => {
cy.visit("/")
cy.server()
cy.route("GET", "http://localhost:8080/api/documents/url/about", {title: "About+"}).as("about")
cy.get("[href='/about']").click()
cy.wait("#about") // !!!
cy.get("h1").contains("About+")
})