Cypress - How to wait for XHR request - javascript

I'm a beginner to Cypress. I'm sure it is a simple question and I already read the documentation of Cypress, but something still seems to wrong in my Cypress test. I want to wait for an xhr request to be finished, when I click on a different language of the page I want to test.
It works, when I use wait(5000), but I think, there is a better way to wait for the xhr request to be finished than fix wait 5 secs.
This is my code:
describe('test',() => {
it('should open homepage, page "history", click on English language, click on German language',() => {
cy.server();
cy.route('POST','/ajax.php').as('request');
cy.visit('http://localhost:1234/history');
cy.wait('#request');
cy.get('div[class="cursorPointer flagSelect flag-icon-gb"]').click({force:true});
cy.route('POST','/ajax.php').as('request');
cy.wait(['#request']);
//cy.wait(5000); // <- this works, but seems to be not the best way
cy.get('h2').should(($res) => {
expect($res).to.contain('History');
})
cy.get('.dataContainer').find('.container').should('have.length', 8);
});
});
The last check
cy.get('.dataContainer').find('.container').should('have.length', 8);
is not successful, because the xhr request is not yet finished.
The xhr request is being fired, when the click on the icon is done:
cy.get('div[class="cursorPointer flagSelect flag-icon-gb"]').click({force:true});
Here an image of the xhr request, if that helps to find the error:

Are you sure that this line is correct? Otherwise the cy.wait won't function as you want.
cy.route('POST','/ajax.php').as('request');
I expect something like
cy.route('GET','/endpoint').as('request');
You can lookup what route is it via developer tools (F12 in Chrome).
Go to network to monitor what kind of XHRs load when you open your page.
Find out request URL and Method - example with bing.com
Also:
I prefer to include the cy.server() and cy.route() command in the beforeEach.
Then you only need the cy.wait() in the test itself.
See https://docs.cypress.io/guides/references/best-practices.html#2-Run-shared-code-before-each-test for more information about that.

you should do like that:
describe('test',() => { //no here async mode
it('should open homepage, page "history", click on English language, click on German language', async () => { //but here
cy.server();
cy.route('POST','/ajax.php').as('request').as('requestToWait); // as-construction
const requestToWait = await cy.wait('#requestToWait');//here we are waiting and getting response object
// any other code
});

Related

How to make puppeteer do a javascript function

So I am trying to make puppeteer lunch a page and then put a token inside a local storage.
.setItem not working it is just crushing my chromium.
so there is a page called discord
and if you have a user token you can log in to the page with a script
So I found out that someone has made a script that you can past in the console
and then when the code says "token here" you past your token and then it all happens
let token = "your token";
function login(token) {
setInterval(() => {
document.body.appendChild(document.createElement `iframe`).contentWindow.localStorage.token = `"${token}"`
}, 50);
setTimeout(() => {
location.reload();
}, 2500);
}
login(token);
This is the code.
so my idea is to make puppeteer run this code and then just login to the page after refresh
there is any option to do it?
If there is no option I have though about another solution,
maybe make puppeteer type in the console the whole code.
If you want to execute the JavaScript code in the browser context with Puppeteer, you need an evaluate method. For reference: https://github.com/puppeteer/puppeteer/blob/v10.4.0/docs/api.md#pageevaluatepagefunction-args or Google for similar and more user-friendly examples - there are plenty on web and SO.
Basically, to execute any JS code in a browser context, you put your code inside an evaluate method and it should look like this:
await page.evaluate(() => new Promise((resolve) => {
// your browser JS code goes here
}
As for the cookies part, they should persist even after reload (haven't tested, but check this for reference: Cookies gone after reload Puppeteer => page.setCookie(...cookies)).
Also, maybe unrelated, but be careful that everything is alright legal-wise, because bots and bot-like behavior is frowned upon by many sites and in breach of their ToS.

App in cypress redirects, outside does not

I am trying to write end to end tests for this application with Cypress: https://app.gotphoto.com/admin/auth/login
When I visit the above url from my browswer, a login form is showing, as expected.
When I visit the above url through Cypress:
cypress first navigates to https://app.gotphoto.com/admin/auth/login
immediately afterwards I am redirected to https://app.gotphoto.com/__/ and the login form is not showing
These are two screenshots from inside Cypress:
My question is: why is there a difference between how it runs in my browser and how it runs in Cypress / Cypress's browswer?
The browswer I am using is Chrome 89, both when running with and without Cypress.
The entirety of the test I am running is this:
describe('login screen', () => {
it('logs in', () => {
cy.visit('/admin/auth/login');
});
});
with a cypress.json:
{
"baseUrl": "https://app.gotphoto.com"
}
I created a repo with the above configuration so it's simple to reproduce.
The /__/ portion of https://app.gotphoto.com/__/ is called the clientRoute and is an internal configuration item in Cypress.
You can turn it off in your cypress.json configuration file
{
...
"clientRoute": "/"
}
This effectively keeps your original url and allows the page to load properly.
cy.visit('https://app.gotphoto.com/admin/auth/login')
cy.get('input#username', { timeout: 10000 }).type('admin') // long timeout
// wait for page to load
cy.get('input#password').type('password')
cy.intercept('POST', 'api.getphoto.io/v4/auth/login/user').as('user')
cy.contains('button', 'Submit').click()
cy.wait('#user').then(interception => {
// incorrect credentials
expect(interception.response.body.detail).to.eq('Login failed!')
})
I'm not sure of any bad side effects of changing clientRoute, will post more information if I find it.
That redirect to __/ sounds familiar to an issue I stumbled upon some time ago. I found this comment in one of Cypress' issues quite helpful.
So did you already try to use the configuration option experimentalSourceRewriting? In your cypress.json, it may look like this:
{
"baseUrl": "https://app.gotphoto.com"
"experimentalSourceRewriting": true
}
As it's labelled experimental, I'd recommend testing it carefully but maybe it helps a bit. I hope for the best! 🙏
why is there a difference between how it runs in my browser and how it runs in Cypress / Cypress's browser?
Your normal browser waits for the XHR requests to be completed and renders the final output created by whatever js magic you have written in there but cy.visit is not supposed to wait for those XHR / AJAX requests inside. It gets 200 in response and moves ahead. If you add a cypress command next to cy.visit, something like cy.get('h1'), you will notice that this command runs instantly after cy.visit, and after that, your XHR requests are resolved.
One work around here can be to use cy.intercept, for example (Cypress 6.8.0, Chrome 89):
describe("login screen", () => {
it("logs in", () => {
cy.intercept({
method: "GET",
url: "admin/version/master/index.html"
}).as("indexHTML"); // Similarly add other internal xhr requests
cy.visit("/admin/auth/login");
cy.wait("#indexHTML").then(interception => {
expect(interception.response.statusCode).to.be.eq(200);
});
});
});
Output:
It basically waits for your internal XHR requests to finish and allows you to play with the request and responses once they are resolved.
This issue will help you debug further: https://github.com/cypress-io/cypress/issues/4383
Also, this /__/ has no hand in rendering the blank page IMO.
An example of logging in. Ultimately this is a bit of a hacky solution as it fails on the very first try; however, it works on any subsequent attempt.
Add the following to your command.js
// -- Visit multiple domains in one test
Cypress.Commands.add('forceVisit', url => {
cy.window().then(win => {
return win.open(url, '_self');
});
});
login.spec.js
describe('login screen', () => {
it('logs in', {
retries: {
runMode: 1,
openMode: 1
}
}, () => {
cy.forceVisit('https://app.gotphoto.com/admin/auth/login');
cy.get('#username').should('exist');
});
});
Screenshot:

Cypress foolishly waits for the new page to load after the button has been clicked and even the new page's core actionable resource is ready

I know this has been asked on SO and Github already, but none of the solutions are suitable for the scenario I have because, I am looking for successful SSO login by doing keypress/button-click actions on Google Auth and Identity Auth SSO options.
In these options the final Login page URL is too long and unknown(in it's entirety) so not fully and reliably verifiable by Cypress URL match methods, so I want Cypress to just click the button and wait for the next page to load until certain core-actionable element is found/present on the new page and then not stupidly wait any further and type required credentials(username) on that long-URL login page and press enter to go to password page and fill the password and press enter and let the rest of the steps continue "naturally".
Below is the code that I have and as I mentioned in the comment in the code, after the button click, it waits for new page to fully load, even after the new page already has actionable item, and increasing the defaultCommandTimeout to 20000 also doesn't help:
describe('Test if <ProjectName> Search page is reachable', () => {
it('Visits the <ProjectName> Search page', () => {
cy.visit('/')
cy.window().then(w => w.beforeReload = true)
// Sign in with Google button, cypress timeouts after this button has been clicked,
// even if the new page has actionable item("identifier" field) ready for use
cy.get('button.<sso-class>').first().click()
cy.window().should('not.have.prop', 'beforeReload')
cy.get('input[name=identifier]').type('<GoogleUsername>').type('{enter}')
cy.get('input[name=password]', {timeout: 4000}).type('<GooglePassword>').type('{enter}')
cy.url().should('eq', '/search')
})
})
And after waiting, times out throwing the below error:
(page load)
--waiting for new page to load--
CypressError
Cypress command timeout of 20000ms exceeded.
Because this error occurred during a after all hook we are skipping all of the remaining tests.
As you can see in the code, I have already tried one fix mentioned in this GH(Github) post and the other fix is not suitable, as I said, I don't know the final Login page URL of Google/Identity server, and also it's too long with too many querystrings which makes it unreliable for Login page-load checks...
Anyone has any better suggestions ?

How to waitFor when page refreshes in Puppeteer?

I have an app I'm working with that is behaving like this... You visit a url /refresh, and it loads the page with a loader/spinner/bar showing for like 5 seconds, then it refreshes the page after it's done. It does this so it can load the latest data that was computed during /refresh.
Right now I am just setting a timeout longer than the loader will most likely stay around, but this is brittle because a bad network connection could put it over the line.
How can I instead "watch" for when the refresh happens? What technique would you recommend. It seems to start to get hairy pretty fast.
Into the nitty gritty, when the loader is showing, when it finishes it is gone for like a half a second before the page reload. So I can't just wait til the loader is gone. It seems like I need to keep some sort of state variable around in the DOM like in localStorage, but can't pinpoint it. Would love some help.
well you could "watch" for the element that display the data using page.$(selector), or if no such element you could also wait for the specific request 's response:
const waitForResponse = (page, url) => {
return new Promise(resolve => {
page.on("response", function callback(response){
if (response.url() === url) {
resolve(response);
page.removeListener("response",callback)
}
})
})
};
const res = await waitForResponse(page,"url of the request you want to wait for");
Wait for Network request before continuing process

Chrome requests taking much longer than other browser requests

One of the users of my website noticed a severe delay in loading times for a infinite scroll function which makes a GET request to my API. While testing it, it only seems to happen in Chrome and Opera, the other browsers provide a near seamless experience.
So after some testing, I decided to do some time logging (sorry I can't post inline images on here due to too little reputation):
These logs are from the same calls, and as you can see chrome takes about 3000ms while firefox is around 90ms. IE/Edge have the same performance as Firefox.
To further the mystery, the network tab seems to report the ms correctly (around 100ms with some exceptions, but nowhere near 3000ms):
Now for the actual code I'm calling:
loadMore() {
console.time('start load');
//some extra stuff here
console.timeEnd('start load');
console.time('state api call');
this.$APIService.getCards(this.deckId, this.page, this.searchInput, this.lastId)
.then(res => {
console.timeEnd('actual call');
console.timeEnd('state api call');
//some more stuff here
})
}
the "this.$APIService.getCards" call is just my apihandler:
getCards (deckId, page, searchInput,lastId) {
console.time('actual call');
return Api().get('cards?deckId=' + deckId + '&lastId='+lastId + '&search='+ searchInput)
},
where "Api()" is my axios object:
axios.create({
withCredentials: true,
baseURL: baseURL
})
Another thing to note is that my server only receives the request after the delay, so that would correspond with the network tabs stating it only takes around 100ms.
So, does anyone have any idea of why this might be happening? As you can see I literally have the time logs start right before the request and end them right upon receiving a result. Why would there be a delay before executing the call only in Chrome?
Thanks in advance!

Categories

Resources