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!
Related
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:
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
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
});
I am attempting to wait for a success toast after editing an item in a table.
It appears the selenium is not taking into account the timeout time that I have provided to the wait function.
What am I missing? I shouldn't have to provide a ridiculously high number(which only works sometimes) to make this work when I only need ~7 seconds.
driver.wait(until.elementIsVisible(driver.findElement(By.className('alert-success'))), 999999999999999999999999999999999999999999999999).then(function () {
return driver.findElement(By.className('table-striped')).getText().then(function (text) {
expect(tableState).toNotEqual(text);
done();
driver.quit();
});
});
My web app uses the 'long poll' method to keep up to date with the latest data from my server. The server only responds when it has new data, which can be many minutes apart. (It is a heating control system where you only see updates when room temperatures changes or somebody changes the settings).
var version = "0";
function updater() {
$.ajax({
type: "POST",
url: "/listen",
data: version,
success: function (data) {
version = handleUpdates(data);
updater();
},
error: function () {
setTimeout(updater, 1000);
}
});
}
It works fine on desktop browsers and on phones except in one case. I have found that on android phones with Chrome something odd happens after the phone has gone to sleep for more then about 10 minutes. The post request seems to be dropped, which I guess is reasonable since the phone is asleep. In the Chrome debugger's Network tab, the Status Text of the POST request says (canceled).
The problem is when I wake the phone up while the request is cancelled, neither the success() or error() function is called, and my web app never gets updated. $.ajax() has broken its promise to call me back.
The problem only happens on some devices. I have been able to do a few ad-hoc tests by borrowing devices off friends. So far I have only seen the problem on android phones. But not is the phone is connected to a charger. I have not seen it on any tablets, on apple devices or windows PCs.
I have tried adding a timeout to the ajax settings:
timeout: 120 * 1000,
This helps because the error() function is eventually called up to 2 minutes after the wake up. But I'd like the user to see updates within 1 or 2 seconds. I don't want to make the timeout so short because it would create unnecessary server traffic.
I have also tried detecting whether device is asleep by looking for lateness in a one second setInterval as described in Can any desktop browsers detect when the computer resumes from sleep?.
When I detect the wake up, I abort() the post and start another. This helps in most cases. But it turns out to be unreliable. Sometimes time events seem to keep ticking normally during sleep and the post request gets cancelled anyway. And it it does not feel like a reliable fix.
I am using latest version of jQuery: (2.1.2) and Chrome (47).
I not sure this will work or not, I cannot test it now but give it a try
$(window).focus(function() {
updater();
});
I've had problems in the past with JavaScript calls getting suspended when the phone goes to sleep. The solution I ended up with was to use window.setInterval() which seems to suspend, but come back to life when the phone is woken up.
So I would recommend setting an interval which cancels the call every so often and reinitiates it. This might help it survive through a phone sleep.
Something roughly like:
var myCall = $.ajax({...});
Window.setInterval (refreshCall(), 10000);
function refreshCall (){
myCall.abort ();
myCall = $.ajax({...});
}
How about a higher-level watcher function like this:
var restartTimer = null;
function updater(){
$.ajax({
type: "POST",
url: "/listen",
data: version,
success: function (data) {
version = handleUpdates(data);
clearTimeout(restartTimer);
updater();
},
error: function () {
clearTimeout(restartTimer);
setTimeout(updater, 1000);
}
});
}
// Kick it when the phone wakes up.
$(window).focus(function(){
restartTimer = setTimeout(function(){
initializeAll();
}, 6000);
updater();
});
You know that the $(window).focus will fire when the phone wakes up, so you try updater() as Almis suggests, but with a fail-safe timer. If updater fires (on laptops or iOS), the timer is canceled and all is well, but if updater is dead, the fail-safe timer fires in 6 seconds and reboots your entire app by calling initializeAll().
How about a setInterval, that stores the time it was called, then compares the last time it was called to the current time - if your interval is 10 seconds and the time passed since the last run was 5 minutes, you can assume you've just woken from sleep? Then abort the current ajax call, and restart it.
The best answer is "don't do that". You're having the server wait to respond while it tracks for changes at the server side. Just have the server respond and have the jQuery ping on an interval. You can include lastchanged or haschanged if you want to prevent actually refreshing when there's no status change, but if the server is doing the same work either way, just let it respond and wait for the next poll.
setInterval(function () {
$.post({"/listen", version, function (data) {
if(data.haschanged)
version = handleUpdates(data);
}).fail(function () {
// any error correction on failed call
});
}, 1000);