Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to wait for page finish reloading after click? #1805

Closed
kkorus opened this issue May 29, 2018 · 48 comments
Closed

How to wait for page finish reloading after click? #1805

kkorus opened this issue May 29, 2018 · 48 comments

Comments

@kkorus
Copy link

kkorus commented May 29, 2018

I got form and when I trigger click on submit button this cause to refresh current page. After that I would like to do some assertions. How I know that page finished refreshing and I can start to searching an element to do assertions?

Looking for something like page.waitForNavigation in Puppeteer.

cy.get('#formButton').click() // adds new item and refresh page
// do assertions on page
@jennifer-shehane
Copy link
Member

jennifer-shehane commented May 29, 2018

Hey @kkorus I think this FAQ should help answer your question, but basically cy.visit() automatically does was Puppeteer's page.page.waitForNavigation() does - waits until the load event fires.

https://on.cypress.io/using-cypress-faq#How-do-I-wait-for-my-application-to-load

Edit by @jennifer-shehane - Added link to FAQ. Sorry y'all 😬

@tivnet
Copy link

tivnet commented Jun 7, 2018

@jennifer-shehane

  • "this FAQ" - which one?
  • cy.visit() - without a URL?

@tivnet
Copy link

tivnet commented Jun 7, 2018

A workaround I found was

    // click (page starts reloading)
    cy.wait(3000);
    // check the changed content

@kkorus
Copy link
Author

kkorus commented Jun 7, 2018

@tivnet yeah, I saw that, but it looks like a hack for me rather then solution.

@brian-mann
Copy link
Member

Cypress automatically detects and waits for the page to finish loading. You don't need to be concerned with it. It will pause command execution, prevent them from timing out, and then begin re-running them once the page transition is complete.

@tivnet
Copy link

tivnet commented Jun 7, 2018

@brian-mann

The script below:

Cypress.config(
    {
        "baseUrl": "https://www.artbylena.com"
    }
);
describe("Test", () => {
    const DOM_PRICE_CURRENCY_SYMBOL = ".woocommerce-Price-amount > .woocommerce-Price-currencySymbol";
    it('Switches currencies', () => {
        cy.visit("/paintings/");
        cy.get(DOM_PRICE_CURRENCY_SYMBOL).contains("US$");
        cy.get('.wpglobus-currency-selector').select("CAD");
        // cy.wait(2000);
        cy.get(DOM_PRICE_CURRENCY_SYMBOL).contains("C$");
    });
});

fails without cy.wait:

image

and works with it:

image

@davidzambrana
Copy link

@tivnet I faced this sort of thing already, and even though it works setting a cy.wai() in between, you don't want to go that way as setting an implicit wait might delay your tests, you can read more here

Have also in mind that .contains('C$') is not an assertion, and .should('contain', 'C$') is.

Hope this is useful info

@tivnet
Copy link

tivnet commented Jun 7, 2018

@davidzambrana
That's a general "anti-pattern" notice. However, I am not sure how to avoid it in my specific case. When a click results in a different page, I do not need to wait. Here, it's the same page, reloaded by a JS call. As you see, without the wait, it results in a timeout.

P.S.
Regarding the .contains - yes, that's not an assertion, but it works perfectly. I use .should when I need more than one assertion within the same DOM node.

@davidzambrana
Copy link

In my case for example when I had to check a field that changed its value after some action, I also had to check that a toast showed up, so when I triggered the click, I included in a .then clause that the toast showed up and afterwards that the new value was loaded in the field. But this was a workaround that worked in my case.

@bahmutov
Copy link
Contributor

bahmutov commented Jun 7, 2018

@tivnet excellent example - in your test, you need something observable that shows how the new page is different after reload. Since the url stays the same, the only difference is that the new page sets the cookie wpglobus-forced_currency to a new value. So here is the test - just use getCookie https://docs.cypress.io/api/commands/getcookie.html and make an assertion.

/// <reference types="cypress" />
describe("Test", () => {
  const DOM_PRICE_CURRENCY_SYMBOL = ".woocommerce-Price-amount > .woocommerce-Price-currencySymbol";
  it('Switches currencies', () => {
      cy.visit("/paintings/");
      cy.contains(DOM_PRICE_CURRENCY_SYMBOL, "US$");
      cy.get('.wpglobus-currency-selector').select("CAD");
      // cy.wait(2000);
      cy.getCookie('wpglobus-forced_currency').should('have.property', 'value', 'CAD')
      cy.contains(DOM_PRICE_CURRENCY_SYMBOL, "C$");
  });
});

The GUI shows how this assertion only passes after the page reloads

screen shot 2018-06-07 at 10 57 25 am

@davidzambrana contains is a valid command that finds element that contains given text. But you can collapse get(selector).contains(text) to just cy.contains(selector, text) https://docs.cypress.io/api/commands/contains.html#Syntax

Bonus: notice /// <reference types="cypress" /> at the top of my JS spec file. In VSCode this will turn on correct IntelliSense which we highly recommend https://docs.cypress.io/guides/tooling/intelligent-code-completion.html#Triple-slash-directives

@tivnet
Copy link

tivnet commented Jun 8, 2018

@bahmutov Thanks, Gleb, it works perfectly and even adds an additional assertion for the cookie value.

Don't you think that Cypress should wait for the page reload anyway? It knows that the page is being reloaded, but tries to search the old DOM.


Intellisense works in *Storm IDE without additional setup.
Do you know if there an easy way to have it for the custom commands, too?


Thanks for the GREAT TESTING SUITE 🥇

@bahmutov
Copy link
Contributor

bahmutov commented Jun 8, 2018

@tivnet so the problem is that Cypress cannot know how long to wait for potential page reload. Maybe the page will reload in 50ms or 500ms or 5 seconds - there is no way to predict this. So when it can grab the element right away (and it is found!) then it just goes with that.

Glad to hear about WebStorm IDE doing good job understanding types, nice. For custom commands you need to describe them, which is extra work (I prefer just using functions instead of adding custom commands). See https://github.com/cypress-io/add-cypress-custom-command-in-typescript

Thanks a lot for positive feedback, appreciate this

@jennifer-shehane
Copy link
Member

Sorry, I failed to paste the FAQ link on loading 😬

https://on.cypress.io/using-cypress-faq#How-do-I-wait-for-my-application-to-load

@jindrake
Copy link

I have this page where a user can register and reloads into the same url with the user logged in. I'm doing an assertion with cy.getCookie('uid') after triggering signup requests and a page reload but the assertion triggers before it:

image

@jennifer-shehane
Copy link
Member

@jindrake Providing the test code run would be helpful.

Also try asking in our community chat, searching our existing GitHub issues, reading through our documentation, or searching Stack Overflow for relevant answers.

@darasandeep91
Copy link

darasandeep91 commented Feb 26, 2019

Hi @jennifer-shehane

I am also seeing the same issue as @jindrake mentioned

In my application SessionID is set in local storage after user clicks on login

I have Code Like This:

Given I login in to App

 And I navigate to addresses

in the step definition of " I navigate to addresses" i am checking if session is not null or not using this code

expect(localStorage.read('CD-SessionID')).not.to.be.null;

The Assertion is being executed right away with out waiting for first step

image

@jennifer-shehane
Copy link
Member

Hey @darasandeep91, could you provide the full Cypress test code that is being run? The first and second step?

@darasandeep91
Copy link

darasandeep91 commented Mar 11, 2019

HI @jennifer-shehane

Here are the steps i am trying to perform:

  1. I am passing user type to the step and fetching the details from fixtures
  2. Pass the user name and password to custom login command
  3. wait for the login [meaning wait for CD-SessionID to be set in local storage] and then navigate to addresses

when i run the test even before the login step i am getting the error stating expected null not to be null

Given(/^I have logged in to app as "([^"]*)"$/, (userType) => {
       cy.fixture('selfcareUsers').then((user) => {
        const { [userType]: { username } } = user;
        const { [userType]: { password } } = user;
        cy.Login(username, password);
    });
});
Given(/^I navigate to "([^"]*)" page$/, (pageName) => {
    expect(localstorage.read('CD-SessionID')).not.to.be.null;
    cy.fixture('pages').then((pages) => {
        cy.visit((pages[pageName]));
    });
});

Here is code for Cy.login()

Cypress.Commands.add('Login', (userName, password, environment, businessUnit) => {
    cy.visit('/login');
    setEnvironment(environment, businessUnit);
    cy.get('#login').type(userName);
    cy.get('#password').type(password);
    cy.get('button:contains("Sign In")').click();
});

@jennifer-shehane
Copy link
Member

I believe the localstorage.read('CD-SessionID') is evaluating to null, so your assertion is accurately failing. Can you verify that?

@msty
Copy link

msty commented Apr 3, 2019

The real solution to the problem in the original post is this:

cy.click('#someButtonToNavigateOnNewPage');

cy.location('pathname', {timeout: 10000})
  .should('include', '/newPage');

cy.click('#YouAreOnNewPage');

@jennifer-shehane Not sure why you link to docs about how cy.visit() works when the issue is clearly about waiting for a page to load after the cy.click()

@hawaiikaos
Copy link

I have the same problem except that I don't know the path ahead of time. I'm submitting a form that creates a new object whose id is in the location url, so I can't even use @msty's solution. The only option I have is to slap an arbitrary cy.wait() in there. Why isn't there a generic, 'wait until page loads' function? Or am I missing something?

@tarponjargon
Copy link

tarponjargon commented Aug 27, 2019

I'm new-ish to cypress but have been using it for a few months and this is still the thing I struggle with the most. I think testing that a page loads (refresh, nagivation, whatever) is something that should be easily testable. For example, if the application triggers a refresh upon user selection of a select menu, you should be able to easily test that it occurs.

So like:

cy.get('[data-test="shipping-select"]').select("UPS");
cy.pageRefreshed(); // <-- why not?

As mentioned before, I'm supposed to be looking for something that changed in the DOM rather than looking for the page refresh itself. But in this case, there is nothing different. If you try to assert that "UPS" is selected it will be true both before and after the page is refreshed.

Perhaps there is a way to do this or perhaps I am indulging in an anti-pattern.

@bahmutov
Copy link
Contributor

Here is a solution for anyone wondering what to do if the URL stays the same, and the page itself stays the same: add a property to the window object. That property will be gone after reload.

Example: here is the page markup

<body>
  <button id="button">Click to reload</button>
  <script>
    document.getElementById('button').addEventListener('click', () => {
      console.log('clicked')
      setTimeout(() => {
        console.log('reloading page after delay')
        location.reload()
      }, 2000)
    })
  </script>
</body>

and here is the test

it('reloads', () => {
  cy.visit('index.html')
  // mark our window object to "know" when it gets reloaded
  cy.window().then(w => w.beforeReload = true)
  // initially the new property is there
  cy.window().should('have.prop', 'beforeReload', true)
  cy.get('#button').click()
  // after reload the property should be gone
  cy.window().should('not.have.prop', 'beforeReload')
})

Notice how the test waits as long as necessary using .should and built-in https://on.cypress.io/retry-ability

reload

@tarponjargon
Copy link

Nice - that's a great idea. Thanks for cypress, it's really alot of fun to work with!

@kuisathaverat
Copy link

kuisathaverat commented Sep 5, 2019

I've used the @msty solution with one modification, I've to check that the body is there before to access anything cy.get('body');, I'm waiting for an API call after the change the location with cy.wait('@api') and this wait only works if I get the body first.

cy.click('#someButtonToNavigateOnNewPage');

cy.location('pathname', {timeout: 10000})
  .should('include', '/newPage');

cy.get('body');

@dark-swordsman
Copy link

dark-swordsman commented Nov 8, 2019

I also used the @msty solution but with a little bit of modification, which I think it a bit more specific and nice since you can do whatever logic you want with the function.

cy.location({ timeout: 15000 }).should(($url) => {
	expect($url).to.match(/http:\/\/localhost:3000\/event\/1dc49a776b63be235aecccfd\/1573165592600\/payment\/v2\/.{0,}\/confirmation/)
});

And I use the regex .{0,} for any object id, since the payment object is different for each payment.

@vitorpiovezam
Copy link

Could be a feature request a method like page.waitForNavigation({ waitUntil: 'networkidle0' }), from puppetter ? Any other 'solution' seems not correct for me.

@HeWei-imagineer
Copy link

I met the same problem, and I use cy.wait() https://docs.cypress.io/api/commands/wait.html#Syntax solved it.

@AllanPooley
Copy link

AllanPooley commented Feb 7, 2020

Here's another solution from the Cypress docs:

// Wait for the route aliased as 'getAccount' to respond
// without changing or stubbing its response
cy.server()
cy.route('/accounts/*').as('getAccount')
cy.visit('/accounts/123') // or, in our case: cy.get('.submit-button').click()
cy.wait('@getAccount').then((xhr) => {
  // Make assertions here
})

@RiZKiT
Copy link

RiZKiT commented Mar 3, 2020

My solution to check for a reload ist:

cy.window().its('performance.navigation.type').should('eq', 0); // checks for 'TYPE_NAVIGATE'
doReloadStuff();
cy.window().its('performance.navigation.type').should('eq', 1); // checks for 'TYPE_RELOAD'

Look here for further details: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigation/type., it's deprecated but still works. The newer option performance.getEntriesByType('navigation')[0].type will need more code.

@jennifer-shehane
Copy link
Member

@vitorpiovezam There is an open issue proposing a 'waitForNetworkIdle' feature here: #1773

stoksc added a commit to determined-ai/determined that referenced this issue Apr 7, 2020
With cypress, cy.visit() can be used to wait for a page to load.
More info cypress-io/cypress#1805.
@vijenderoutwork
Copy link

vijenderoutwork commented May 29, 2020

Cypress automatically detects and waits for the page to finish loading. You don't need to be concerned with it. It will pause command execution, prevent them from timing out, and then begin re-running them once the page transition is complete.

Its is not happening in my case I have to give wait cy.wait() to avoid timeout issue to navigate to another page after a click button .

@quantuminformation
Copy link

quantuminformation commented Oct 13, 2020

My use case:

  • I have a dynamic url
  • I want to submit a form on this url (which uses ajax with no refresh) which persists to a db
  • I want to refresh this url
  • check the values of the form match the previous form values to check the db update worked

what is my best option?

Im using svelte btw

@emilong
Copy link

emilong commented Oct 22, 2020

If it's helpful to anyone else, I adapted #1805 (comment) into this custom command:

/**
 * Given a function with some commands that cause the page to change or even
 * just reload, this command runs the command then waits for that page load.
 *
 * Ideally this command should be used sparingly, instead preferring to use
 * matching functionality to wait for reload.
 *
 * Adapted from:
 * https://github.com/cypress-io/cypress/issues/1805#issuecomment-525482440
 */
Cypress.Commands.add("waitForPageLoadAfter", block => {
  // mark our window object to "know" when it gets reloaded
  cy.window().then(win => {
    // eslint-disable-next-line no-param-reassign
    win.beforeReload = true;
  });
  // initially the new property is there
  cy.window().should("have.prop", "beforeReload", true);

  // Run the code that triggers the page reload/change
  block();

  // after reload the property should be gone
  cy.window().should("not.have.prop", "beforeReload");
});

Then use it like:

cy.waitForPageLoadAfter(() => { cy.contains("button", "Click here to reload"); });
cy.contains("page reloaded!").should("exist");

thanks, @bahmutov !

@rzegnam
Copy link

rzegnam commented Nov 26, 2020

How about:
npm i -D cypress-wait-until

In cypress/support/commands.js:
import 'cypress-wait-until';

And:

cy.click('#someButtonToNavigateOnNewPage');

cy.waitUntil(() => cy.url().should('contain', '/newPage'));

cy.click('#YouAreOnNewPage');

@saschanos
Copy link

saschanos commented Nov 26, 2020

@emilong, by adding a timeout, it's possible to have a longer running request:

cy.window({timeout: 15000}).should("not.have.prop", "beforeReload");

@kkoomen
Copy link

kkoomen commented Nov 30, 2020

How about:
npm i -D cypress-wait-until

In cypress/support/commands.js:
import 'cypress-wait-until';

And:

cy.click('#someButtonToNavigateOnNewPage');

cy.waitUntil(() => cy.url().should('contain', '/newPage'));

cy.click('#YouAreOnNewPage');

This worked for me, although I still rather have built-in function, because Cypress is able to know when a page refresh is being made.

@bahmutov
Copy link
Contributor

@kkoomen and @rzegnam what you are describing is built into Cypress already, see https://on.cypress.io/location

cy.location('pathname').should('equal', '/newPage')

@kkoomen
Copy link

kkoomen commented Nov 30, 2020

@bahmutov I was talking about the part where cypress should wait for the page to be reloaded. That part hasn't been solved yet natively by Cypress, right?

@bahmutov
Copy link
Contributor

No, so it all requires just observing something external like URL or even a property, no need to use cypress-wait-until. Another reading I recommend is https://www.cypress.io/blog/2020/11/17/when-can-the-test-submit-a-form/#waiting-for-page-load

@vivasaayi
Copy link

vivasaayi commented Dec 11, 2020

Anyone looking answers to this question, implement your own wait until behavior as you do in puppeteer, or webdriver.

When going through the intro & docs it gave an impression that cypress abstracted the behavior, by waiting till all the network request completes, or wait till a given DOM node is loaded.

But its not the case. As discussed in this question, you have add additional logic to handle this anyway.

Also, the logic with setting a property in the window object is not possible if you are writing an automated test for an existing application. And this is a bad hack.

@T3rm1
Copy link

T3rm1 commented Dec 21, 2020

It's really frustrating that such a simple and basic thing as click on an element and wait for the next page to load is so complicated with Cypress.

@mwalewsk
Copy link

Try this:
cy.contains('button', 'Save').click();
cy.get('[data-ng-show="user.manage"]', { timeout: 10000 }).should('be.visible').then(() => {
cy.get('[data-ng-show="user.manage"]').click();
})

@diaraujo13
Copy link

diaraujo13 commented Feb 22, 2021

In my case, the only solution to mimic an 'async/await' behavior was to increase execTimeout in cypress.json to a higher value

@Perustaja
Copy link

I know that this is REALLY situational but in my version of Vue setup with how my company does, the root html element has a class nprogress-busy that shows during network calls. This is easier for me than checking for a spinner or a skeleton element. Example

Cypress.Commands.add('waitForLoad', () => {
    const loaderTimeout = 60000;

    // Wait until loading is finished if necessary
    cy.get('.nprogress-busy', { timeout: loaderTimeout }).should('not.exist');
});

If not, usually other JS frameworks have similar tricks. My previous tests were for an Angular project and there was some specific state you could check on whether calls were finished. If not, waiting for spinner elements or skeleton elements to disappear was working for me previously. Not good but it gets the job done.

@harunurhan
Copy link

harunurhan commented Sep 3, 2021

@jennifer-shehane

Looks like only initial cy.visit is blocking within a cypress test.

cy.visit('/page');
doSomeActions();
assertResults();

// check results persist after that
cy.visit('/page'); // THIS ONE IS NOT BLOCKING
assertResults(); // SO THIS COULD PASS IMMEDIATELY FOR THE CURRENT PAGE

@nasimjontohirov
Copy link

Hi, for my side, worked this:
cy.location({ timeout: 10000 }).should(($url) => { expect($url).to.match(/newpage\/.+$/);

@Amyx000
Copy link

Amyx000 commented Feb 23, 2023

The hack is to change the default timeout for cypress, bydefault the timeout for cypress task is 4s, you can change it in cypress.config.js and you don't have to use cy.wait() for that.

const { defineConfig } = require('cypress')

module.exports = defineConfig({
  e2e: {
    defaultCommandTimeout: 10000,
  },
})

now each line will run for either the test condition match or 10s and not like the wait() function which wait for exactly the given time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests