8

I'm new to Cypress. My app as a "routing system" manually changes window.location.hash. At some point, I click on a button that changes the hash and consequently should change the page during the test. I can see a "new url" entry appearing during the execution, but how can I make cypress visit that url?

enter image description here

In few words, what the problem is: you can see I type the password and then {enter}. Running the test I can see the hash change in the address bar, but the page doesn't change in accord to the hash change.

This is the testing code

context("Workflow", () => {

    it("login", () => {

        cy.visit("http://localhost:3000/src/#login")
        cy.get("#username").type("demo").should("have.value", "demouser")
        cy.get("#password").type("demo{enter}").should("have.value", "demo") // this should redirect to "/#home"
        //cy.wait(10000)
        cy.get(".subtitle").should("have.value", "Welcome") //this line fails as ".subtitle" is an element of "/#home"
    })
})

EDIT: Following tons of failed attempts, I came up with a partially working, clunky and hacky solution. I think I shouldn't need to use reload() to solve that (there must be a better solution..), but to make it works I have to wait for all the remote requests to be done (otherwise reload() cancels them). I say partially working because you can see from the comments in the code if I try to visit #login first, then follow the redirect in #home and then change the page to #browser, the last one doesn't work (I can see the hash changing to #browser, but the page is still #home).

import 'cypress-wait-until';

let i = 0;

context("Workflow", () => {
    it("login", () => {
        cy.server( {
            onRequest: () => {
                i++;
            },
            onResponse: () => {
                i--;
            }
        });

        cy.visit("http://localhost:3000/src/#login")
        cy.get("#username").type("demouser").should("have.value", "demouser")
        cy.get("#password").type("demouser").should("have.value", "demouser")
        cy.get("form#formLogin").submit()

        cy.waitUntil(() => i > 0)
        cy.waitUntil(() => i === 0)
        cy.reload(); // it correctly change the hash AND the page to #home!

        cy.url().should("include", "#home") 
        cy.get(".version").contains( "v2.0.0-beta") // it works!!

        cy.get("a[data-id=browser]").click({force: true}) // it correctly changes the hash to #browser
        cy.waitUntil(() => i > 0)
        cy.waitUntil(() => i === 0)
        cy.reload();

        // the hash in the address bar is #browser, but the web page is #home
    })
})
alfredopacino
  • 2,979
  • 9
  • 42
  • 68
  • 1
    Have you tried `cy.visit()`? – jmargolisvt Apr 30 '20 at 20:12
  • `cy.visit()` takes the url as argument. I don't need that. I simply need to visit the page the menu button is pointing to. – alfredopacino Apr 30 '20 at 21:03
  • So you're clicking a button, and it manually changes the anchor AKA the `window.location.hash`. When you manually click on the button in the webpage, are you redirected to the anchor? – natn2323 May 02 '20 at 06:17
  • of course it works, the website itself works fine. The whole problem is when I run it in cypress, I click a button using `cy.find("#mybutton").click() ` and cypress simply doesn't change the page. – alfredopacino May 02 '20 at 13:19
  • What's going on with the blurred out XHR requests in the image? Are any of them returning status code 302 or 303? – natn2323 May 03 '20 at 16:41
  • Also, can you add a `cy.wait(10000)` after the button is clicked to see if it ever redirects? – natn2323 May 03 '20 at 23:09
  • All the XHR requests returns a status `200`. I tried to `cy.wait(10000)` not solved. It is not matter of waiting, cypress simply doesn't follow that link. – alfredopacino May 04 '20 at 10:30
  • You never specified what is the issue? Your page is not loading or there is some other code afterwards which is failing? – Tarun Lalwani May 04 '20 at 13:30
  • Just that page is not loading. So the test fails if I do `cy.find()` on an element on that page. But it doesn't fails simply clicking on the menu button which is suppose to change the page. – alfredopacino May 04 '20 at 14:54
  • 1
    You said that you clicked a button, but that is not shown on the log above. Probably because `cy.find("#mybutton")` is invalid syntax, see [here](https://docs.cypress.io/api/commands/find.html#Usage). It would be much more helpful to show the test code. – Richard Matsen May 05 '20 at 12:22
  • edited with the real code. The selectors `#username` and `#password` aren't a problem, I see cypress fills these input fields. – alfredopacino May 05 '20 at 13:03
  • I start to think the issue is somehow related to `LitElement` itself (which I use in the web-app). – alfredopacino May 05 '20 at 13:24

3 Answers3

8

Cypress has an event called url:changed. See doc on Events here

Using this, something like this may work:

context("Workflow", () => {
    it("login", () => {

        cy.on('url:changed', url => {
            cy.location('hash').then(hash => {
                if (!url.endsWith(hash)) {
                    cy.visit(url);
                }
            });
        });

        cy.visit("http://localhost:3000/src/#login")
        cy.get("#username").type("demo").should("have.value", "demouser")
        cy.get("#password").type("demo{enter}").should("have.value", "demo") // this should redirect to "/#home"
        cy.get(".subtitle").should("have.value", "Welcome") //this line fails as ".subtitle" is an element of "/#home"

    })
})

Edit: just for troubleshooting purposes and to focus on your issue, can you try it like this without cy.location():

context("Workflow", () => {
    it("login", () => {

        cy.on('url:changed', url => {
            cy.visit(url);
        });

        cy.visit("http://localhost:3000/src/#login")
        cy.get("#username").type("demo").should("have.value", "demouser")
        cy.get("#password").type("demo{enter}").should("have.value", "demo") // this should redirect to "/#home"
        cy.get(".subtitle").should("have.value", "Welcome") //this line fails as ".subtitle" is an element of "/#home"

    })
})

Edit: Have you tried cy.reload()

context("Workflow", () => {

    it("login", () => {

        cy.visit("http://localhost:3000/src/#login")
        cy.get("#username").type("demo").should("have.value", "demouser")
        cy.get("#password").type("demo{enter}").should("have.value", "demo") // this should redirect to "/#home"
        cy.reload();
        cy.get(".subtitle").should("have.value", "Welcome");
    })
})
PeaceAndQuiet
  • 1,692
  • 8
  • 13
  • 1
    your code throws an error: `Cypress detected that you returned a promise from a command while also invoking one or more cy commands in that promise. The command that returned the promise was: > cy.visit() The cy command you invoked inside the promise was: > cy.location()` – alfredopacino May 06 '20 at 18:11
  • Did you have time to try without `cy.location()` – PeaceAndQuiet May 07 '20 at 14:38
  • Sorry I didn't answer, SO doesn't send notification in case of post edit. I tried and I get the pretty much the same error `The command that returned the promise was: > cy.visit() The cy command you invoked inside the promise was: > cy.visit()` – alfredopacino May 07 '20 at 14:50
  • 1
    One more thing to try :) – PeaceAndQuiet May 08 '20 at 13:25
  • With that I got another error. It seems the REST login request is cancelled if I use `cy.reload()` See screenshot https://postimg.cc/4K57Z4jW – alfredopacino May 08 '20 at 14:25
  • Do you have a button that you can click to submit your credentials instead of using `{enter}` from your password form. You might get a different outcome. – PeaceAndQuiet May 08 '20 at 14:32
  • 1
    There is also a `submit()` command that you can use on a `form` element like: `cy.get('form').submit()` – PeaceAndQuiet May 08 '20 at 14:35
  • I had already tried to explicitly click on submit, exactly same outcome. – alfredopacino May 08 '20 at 14:36
  • Anyway I noticed a strange behaviour. If I manually click on a button that is supposed to change the hash (and consequently change the page) from WITHIN the cypress browser, it does not actually change anything but the hash in the address bar. Of course, if I run my application outside cypress it works. – alfredopacino May 08 '20 at 17:14
  • 1
    Did you try running your cypress test in Chrome instead of Electron? – PeaceAndQuiet May 08 '20 at 19:34
  • 1
    Last comment and I give up after :) . I found this when searching for cypress with litElements and shadow DOM. https://github.com/abramenal/cypress-shadow-dom – PeaceAndQuiet May 09 '20 at 18:43
  • nop, I deactivated shadow-dom overriding `createRenderRoot` in ALL my webcomponents. Please see my edited question, with a partially working (yet basically useless) solution. – alfredopacino May 09 '20 at 20:53
  • I think I got the root of the problems: if I add `alert(1)` in the method `firstUpdated()` in the main LitElement component (to put in simple words that means as soon as the DOM is ready) I cannot see the alert! It's like javascript is "partially" disabled! What could be the reason of that? – alfredopacino May 15 '20 at 22:27
3

Thanks for all the attempts to solve, but they were all tricky workarounds to something as simple as "trigger the related listener when window.location.hash changes".

The root of the problem was a bug in Cypress. The bug on window.location.hash is present in 4.5.0, but it has been solved somewhere between 4.5.0 and 4.12.1.

In 4.12.1 the problem is solved.

alfredopacino
  • 2,979
  • 9
  • 42
  • 68
2

There are some ways to do that. One of the basic approach is like this:

  cy.visit(`your_login_page`)
  cy.get('[data-testid="input-username"]').type(`demo`, { force: true })
  cy.get('[data-testid="input-password"]')
    .type(`demo`, {
      force: true,
    })
    .wait(1000)
    .then(() => {
      cy.location().should((loc) => {
        expect(loc.pathname).to.eq(your_new_url)
      })
    })

You should add data-testid or findByTestId to your elements with a unique id. To visit the new URL, you can use cy.visit(loc.pathname)

msahin
  • 1,520
  • 1
  • 16
  • 22
  • Thanks, In my case would be `expect(loc.hash).to.eq("#home")` as if you change just the `hash` the `pathname` is the same. Anyway again, that kind of assertion doesnt' fails, but cypress actually doesn't change the page in accord to the hash change. So it will fail if I try to search an element on the home page like `cy.get(".subtitle")` – alfredopacino May 05 '20 at 15:36