Closed kkorus closed 6 years ago
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 😬
@jennifer-shehane
cy.visit()
- without a URL?A workaround I found was
// click (page starts reloading)
cy.wait(3000);
// check the changed content
@tivnet yeah, I saw that, but it looks like a hack for me rather then solution.
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.
@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
:
and works with it:
@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
@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.
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.
@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
@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
@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 🥇
@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
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
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:
@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.
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
Hey @darasandeep91, could you provide the full Cypress test code that is being run? The first and second step?
HI @jennifer-shehane
Here are the steps i am trying to perform:
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();
});
I believe the localstorage.read('CD-SessionID')
is evaluating to null
, so your assertion is accurately failing. Can you verify that?
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()
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?
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.
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
Nice - that's a great idea. Thanks for cypress, it's really alot of fun to work with!
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');
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.
Could be a feature request a method like page.waitForNavigation({ waitUntil: 'networkidle0' }),
from puppetter ? Any other 'solution' seems not correct for me.
I met the same problem, and I use cy.wait() https://docs.cypress.io/api/commands/wait.html#Syntax solved it.
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
})
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.
@vitorpiovezam There is an open issue proposing a 'waitForNetworkIdle' feature here: https://github.com/cypress-io/cypress/issues/1773
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 .
My use case:
what is my best option?
Im using svelte btw
If it's helpful to anyone else, I adapted https://github.com/cypress-io/cypress/issues/1805#issuecomment-525482440 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 !
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');
@emilong, by adding a timeout, it's possible to have a longer running request:
cy.window({timeout: 15000}).should("not.have.prop", "beforeReload");
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.
@kkoomen and @rzegnam what you are describing is built into Cypress already, see https://on.cypress.io/location
cy.location('pathname').should('equal', '/newPage')
@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?
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
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.
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.
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(); })
In my case, the only solution to mimic an 'async/await' behavior was to increase execTimeout in cypress.json
to a higher value
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.
@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
Hi, for my side, worked this:
cy.location({ timeout: 10000 }).should(($url) => { expect($url).to.match(/newpage\/.+$/);
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.
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.