Skip to content

Clicking anchor link to download file causes page load timeout #14857

Closed

Description

@hiatien19

i was update Cypress 6.4.0
but i cannot click button download
error_download_file

file source:
error_download_file_2

source:
cypress_download.zip

Activity

  1. jennifer-shehane commented on Feb 3, 2021

    @jennifer-shehane
    Member

    I can recreate this error with the test code below:

    it('download file', ({ pageLoadTimeout: 5000 }), () => {
      cy.visit('https://people.sc.fsu.edu/~jburkardt/data/csv/csv.html')
      cy.get('a[href="addresses.csv"]').click()
    })

    Screen Shot 2021-02-03 at 2 45 08 PM

    Screen Shot 2021-02-03 at 2 45 58 PM

  2. changed the title [-]Cannot click button download in cypress 6.4.0[/-] [+]Clicking anchor link to download file causes page load timeout[/+] on Feb 3, 2021
  3. sarja510 commented on Feb 18, 2021

    @sarja510

    I am facing the same problem. Clicking on the Download button change window.location.href to download the file, also triggers the beforeunload event. We are downloading the file only, not redirecting towards a page actually, but for the redirection, cypress wait for a load event to be triggered by the application but as no redirection actually is happening it gets page load timeout error. I tried stopPropagation on "window:before:unload" event but didn't work. If we can invoke the load event during the wait or can execute the stopPropagation on the correct state it can be resolved I guess.

    Cypress version: 6.5.0
    OS : Windows 10

  4. kiransurakanti commented on Feb 22, 2021

    @kiransurakanti

    Hi, @jennifer-shehane I'm also facing the same issue even after updating Cypress 6.5.0. Clicking on a button downloads the file, but after downloading the file, the cypress waiting for page load which is not the expected behavior. please suggest a workaround for this problem.

  5. sarja510 commented on Feb 25, 2021

    @sarja510

    I got a workaround with the following issue,

    What actually was happening in my case is that after clicking the download button a redirection was happening to download the file. From the application code: window.location.href was changing to download the file on click event. When cypress gets the redirection it waits for a page load event to get fired from AUT for continuing further. As no actual page redirection was happening so it got stuck and got page load timeout. So I tried the following workaround and it is working fine for my case:

    Solution/Workaround:

    All I needed was to fire a page load event to execute other cypress commands after clicking the download button rather than getting page load timeout. So before clicking the Download button or targeted file I added an event listener ‘click’ to listen out for the event and did something to trigger the page load event. Here is a tricky part, on event handling I triggered window.location.reload. But if I instantly trigger that on click event it will work but the file will not get downloaded. So I set a timeout of 5 sec, it may vary as per our requirement to download the file, after that, I am triggering window.document.location.reload(). This reloads the page and triggers a page load event. As soon as cypress get the page load event it continues executing further commands

    the following solution worked for me fine:

    cy.window().document().then(function (doc) {
      doc.addEventListener('click', () => {
        setTimeout(function () { doc.location.reload() }, 5000)
      })
      cy.get('[ng-click="vm.export()"]').click()
    })

    I hope cypress will give a proper solution for this case, till then anyone can use this workaround. @jennifer-shehane @hiatien19 @kiransurakanti

  6. AdrianHL commented on Mar 2, 2021

    @AdrianHL

    I'm experiencing the same issue here. The solution proposed by @sarja510 works until a new version fix it.

  7. mklepaczewski commented on Mar 4, 2021

    @mklepaczewski

    the following solution worked for me fine:

    cy.window().document().then(function (doc) {
      doc.addEventListener('click', () => {
        setTimeout(function () { doc.location.reload() }, 5000)
      })
      cy.get('[ng-click="vm.export()"]').click()
    })

    I hope cypress will give a proper solution for this case, till then anyone can use this workaround. @jennifer-shehane @hiatien19 @kiransurakanti

    Unfortunately, this doesn't seem to be enough. The test seems to succeed even if the file to be downloaded doesn't exist (404s etc.). You can confirm it by changing URL of the file to be downloaded:

    cy.intercept('/', (req) => {
        req.url += '-this-url-doesnt-exist';
    });

    I fixed it by intercepting the request and checking the status code of the response:

    cy.window().document().then(function (doc) {
      doc.addEventListener('click', () => {
        setTimeout(function () { doc.location.reload() }, 5000)
      })
      
      /* Make sure the file exists */
      cy.intercept('/', (req) => {
        req.reply((res) => {
          expect(res.statusCode).to.equal(200);
        });
      });
    
      cy.get('[ng-click="vm.export()"]').click()
    })
  8. JoeKellyFR commented on Mar 15, 2021

    @JoeKellyFR

    @mklepaczewski This solution worked for me, thanks, and also to @sarja510 for the original approach. huge relief to be unblocked!

  9. swatiamberkar commented on Mar 18, 2021

    @swatiamberkar

    @jennifer-shehane

    I got a workaround with the following issue,

    What actually was happening in my case is that after clicking the download button a redirection was happening to download the file. From the application code: window.location.href was changing to download the file on click event. When cypress gets the redirection it waits for a page load event to get fired from AUT for continuing further. As no actual page redirection was happening so it got stuck and got page load timeout. So I tried the following workaround and it is working fine for my case:

    Solution/Workaround:

    All I needed was to fire a page load event to execute other cypress commands after clicking the download button rather than getting page load timeout. So before clicking the Download button or targeted file I added an event listener ‘click’ to listen out for the event and did something to trigger the page load event. Here is a tricky part, on event handling I triggered window.location.reload. But if I instantly trigger that on click event it will work but the file will not get downloaded. So I set a timeout of 5 sec, it may vary as per our requirement to download the file, after that, I am triggering window.document.location.reload(). This reloads the page and triggers a page load event. As soon as cypress get the page load event it continues executing further commands

    the following solution worked for me fine:

    cy.window().document().then(function (doc) {
      doc.addEventListener('click', () => {
        setTimeout(function () { doc.location.reload() }, 5000)
      })
      cy.get('[ng-click="vm.export()"]').click()
    })

    I hope cypress will give a proper solution for this case, till then anyone can use this workaround. @jennifer-shehane @hiatien19 @kiransurakanti

    @sarja510 This solution is worked for me.Thank You.

  10. shilpajain09 commented on Mar 31, 2021

    @shilpajain09

    This might be a very stupid question, but can you please let me know what is this doing

    cy.get('[ng-click="vm.export()"]').click()

    @mklepaczewski

  11. mklepaczewski commented on Mar 31, 2021

    @mklepaczewski

    This might be a very stupid question, but can you please let me know what is this doing

    cy.get('[ng-click="vm.export()"]').click()

    @mklepaczewski

    ng-click="vm.export()" is just a selector. It clicks the first(?) element with attribute ng-click="vm.export()". So, for example if I had this in HTML:

    <a href="https://example.com/" ng-click="vm.export()">Click here</a>
    

    then the mentioned line would find this element and click it. If your link/button has id my-button then you would replace it with something like cy.get('#my-button').click()

  12. shilpajain09 commented on Mar 31, 2021

    @shilpajain09

    @mklepaczewski thanks a lot for your prompt response.

    Are we verifying somewhere that the file is downloaded successfully?

  13. 42 remaining items

  14. gvaatstra commented on Mar 9, 2023

    @gvaatstra

    I do understand it's a problem, but for the people asking for a fix or a patch, I'd like to hear how you would see this working. Cypress simply relies on a load event that never comes in this case and if it wouldn't do that, it would impact the 99,9% of the cases where they use it for stability.

    1. They have the downloadfile method you can use.
    2. Really consider what you're actually testing and what you expect

    If you know the path to the file, it's simple. Use the downloadfile method, because elseway you're simply testing browser functionality.
    If you don't know the path to the file, you can use multiple workarounds. For example, I change the headers to show content on the screen (and therefore have no issue with the load event). That works well for content that can be shown in the browser (like CSV). I posted that in this issue.
    Another way is validating that the content contains something you expect. f.e. that it's a docx. by using a cy.get

        cy.get("#button_download")
          .closest("form")
          .invoke("attr", "action")
          .then((actionLink) => {
            cy.api("GET", actionLink).then((response) => {
              expect(response.body).to.contain("word/document.xml");
            });
          });
    

    I think that is enough to test and I'd be curious what you want to test with Cypress on a downloaded file anyway.

  15. ntvinhit commented on Mar 22, 2023

    @ntvinhit

    For anyone who cannot afford page reloads in the middle of a test, I used the following to catch the error and continue.

    Cypress.on("uncaught:exception", (error) => {
      if (
        error.message.includes(
          "Timed out after waiting `5000ms` for your remote page to load",
        )
      ) {
        return false;
      }
    });
    
    Cypress.on("fail", (error) => {
      if (
        error.message.includes(
          "Timed out after waiting `5000ms` for your remote page to load",
        )
      ) {
        return false;
      }
    
      throw error;
    });

    The timeout 5000ms which is different from the default timeout was set within the test with the following and is being reset later down the test.

    Cypress.config("pageLoadTimeout", 5000);

    Hacky... but worked for me

    It's not work because it never goes to uncaught:exception and if we return false on fail event, the test will pass.

  16. zbjornson commented on Apr 18, 2023

    @zbjornson

    I spent a long time investigating this. Cypress sets up listeners for page "stability" in a beforeunload handler. beforeunload fires when a form is submitted, but if the form doesn't result in a page load (e.g. because the response has a Content-Disposition: attachment header), then Cypress hangs.

    These are the relevant parts of the HTML spec
    1. form submission
      • step 23.4.2 submit the form -> navigate
    2. beginning navigation
      • step 16.1 fires beforeunload
    3. 7.4.5 populating a session history entry
      • Step 10 covers handling of Content-Disposition: attachment responses. Compare to...
      • Step 11, which is a normal navigation that loads a document and causes the "load" event to fire (see e.g. this for content-type: text/html). There is no navigation from a download in step 10. This is what Cypress got wrong.

    This is the most reliable solution I came up with and the same logic I think Cypress should have in its core:

    // in your commands.mjs file
    Cypress.Commands.add("suppressWaitForPageLoad", function () {
    	cy.intercept("*", req => {
    		req.on("before:response", res => {
    			const isDownload = res.headers["content-disposition"]?.startsWith("attachment");
    			// Need to exclude requests not made by the application, such as
    			// background browser requests.
    			const origin = cy.getRemoteLocation("origin");
    			const isFromAUT = req.headers["referer"]?.startsWith(origin);
    			if (isDownload && isFromAUT) {
    				Cypress.log({
    					name: "suppressWaitForPageLoad",
    					message: "Bypassing wait for page load event because response has Content-Disposition: attachment"
    				});
    				cy.isStable(true, "load");
    			}
    		});
    	});
    });

    Then use it like:

    cy.suppressWaitForPageLoad();
    cy.contains("button", "Download").click();

    Firing a synthetic "load" event as shown in the post above can cause other issues. A page is only ever supposed to have one "load" event fired.

  17. gvaatstra commented on Apr 18, 2023

    @gvaatstra

    @zbjornson, that seems like very good research and a nice solution! Is the cy.isStable(true, "load") another custom function?
    I haven't tested it (and simply wanted to acknowledge the effort you put into the issue), but I can imagine that if people want to use this, it could be beneficial to add a check after the download. For example, that the file is fully downloaded). Also I'd go for adding a parameter for the path specifically (instead of the *) to limit the the risk of side-effects. That also helps in adding a unique alias and if you add an alias, you probably can also wait for the request to finish.

  18. added
    E2EIssue related to end-to-end testing
    TriagedIssue has been routed to backlog. This is not a commitment to have it prioritized by the team.
    and removed on Apr 19, 2023
  19. SwPeter123 commented on May 11, 2023

    @SwPeter123
    Contributor

    I do understand it's a problem, but for the people asking for a fix or a patch, I'd like to hear how you would see this working. Cypress simply relies on a load event that never comes in this case and if it wouldn't do that, it would impact the 99,9% of the cases where they use it for stability.

    1. They have the downloadfile method you can use.
    2. Really consider what you're actually testing and what you expect

    If you know the path to the file, it's simple. Use the downloadfile method, because elseway you're simply testing browser functionality. If you don't know the path to the file, you can use multiple workarounds. For example, I change the headers to show content on the screen (and therefore have no issue with the load event). That works well for content that can be shown in the browser (like CSV). I posted that in this issue. Another way is validating that the content contains something you expect. f.e. that it's a docx. by using a cy.get

        cy.get("#button_download")
          .closest("form")
          .invoke("attr", "action")
          .then((actionLink) => {
            cy.api("GET", actionLink).then((response) => {
              expect(response.body).to.contain("word/document.xml");
            });
          });
    

    I think that is enough to test and I'd be curious what you want to test with Cypress on a downloaded file anyway.

    @gvaatstra
    I think you probably would agree all the workarounds mentioned are not prefect.
    For example,

    1. If the webpage's state is updated by user actions which are not yet permanently saved. A reload call could destroy all the progress and force the test to redo all the actions before continue the tests
    2. Displaying files instead of downloading can only be applied to limited file types. Also, once displayed, a new tab is created and there is nothing we can do to get back to the old page unless we hack it with chrome-remote-interface
    3. We may also want to E2ely test the file content if they are dramatically generated so checking the url in html is not sufficient
    4. ...

    I think a good first step is to just add a flag to bypass the "load" check globally and advanced test writers can decide when and how to (dis)able it. We dont need it to be prefect, we just need a way to get stuff done

  20. Brugui7 commented on Oct 31, 2023

    @Brugui7

    I don't know if this has been said before by someone else but if you're sure that you're clicking on a download link, you can workaround the issue by adding the download attribute to the html element before clicking it:

    cy.get(a)
      .then(($a) => {
        $a.attr("download", ""); 
      })
    .click();

    That way the browser won't trigger the page change event and Cypress won't wait for a loadPage event.

  21. cypress-bot commented on Nov 8, 2023

    @cypress-bot
    Contributor

    Released in 13.5.0.

    This comment thread has been locked. If you are still experiencing this issue after upgrading to
    Cypress v13.5.0, please open a new issue.

  22. locked as resolved and limited conversation to collaborators on Nov 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    E2EIssue related to end-to-end testingTriagedIssue has been routed to backlog. This is not a commitment to have it prioritized by the team.prevent-stalemark an issue so it is ignored by stale[bot]topic: downloads ⬇️type: bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions