-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
In-browser file download support #949
Comments
@kamituel did you solve this problem? |
@kutlaykural Sorry for the late response, I was on vacation. Yes, I did solve this problem in a sufficient way, at least for our needs. 1. Files that depend on more than URL to generate For the files which are being generated on the backend based on a parameters supplied by the frontend via means other than URL itself (e.g. over an out-of-band WebSockets connection), this approach works: <a id="download-file" href="/file?download-id=ABCDEFGH">Download</a>
<script>
document.getElementById('download-file').onclick = function(event) {
// In test, prevent the browser from actually downloading the file.
if (window.Cypress) {
event.preventDefault();
}
// But let it do all other things, most importantly talk to the backend
// out-of-band to configure the download.
setupFileOverWebSockets('ABCDEFGH');
}
</script> Then, in the test, I would do: cy.get('a').click().then((anchor) => {
const url = anchor.prop('href');
// cy.request() will work, because web app did talk to the backend out-of-band.
cy.request(url).then(/* do something, e.g. assert */);
}); 2. Files generated in the browser This technique involves using a temporary anchor element, which has a <button id="test">Download file!</button>
<script>
const button = document.getElementById('test');
button.addEventListener('click', () => {
const blob = new Blob(['lorem ipsum dolor sit amet']);
const anchor = document.createElement('a');
const url = URL.createObjectURL(blob);
anchor.download = 'test.txt';
anchor.href = url;
document.body.appendChild(anchor);
if (window.Cypress) {
// Do not attempt to actually download the file in test.
// Just leave the anchor in there. Ensure your code doesn't
// automatically remove it either.
return;
}
anchor.click();
});
</script> And the test: it.only('asserts in-browser generated file', () => {
// Click the button to start downloading the file. The web app
// will detect it's running within Cypress, and will create
// a temporary anchor, will set a correct href on it, but it won't
// click it and won't attempt to remove it.
cy.get('#test').click();
// Obtain a reference to that temporary anchor.
cy.get('a[download]')
.then((anchor) => (
new Cypress.Promise((resolve, reject) => {
// Use XHR to get the blob that corresponds to the object URL.
const xhr = new XMLHttpRequest();
xhr.open('GET', anchor.prop('href'), true);
xhr.responseType = 'blob';
// Once loaded, use FileReader to get the string back from the blob.
xhr.onload = () => {
if (xhr.status === 200) {
const blob = xhr.response;
const reader = new FileReader();
reader.onload = () => {
// Once we have a string, resolve the promise to let
// the Cypress chain continue, e.g. to assert on the result.
resolve(reader.result);
};
reader.readAsText(blob);
}
};
xhr.send();
})
))
// Now the regular Cypress assertions should work.
.should('equal', 'lorem ipsum dolor sit amet');
}) In summary, I use the first approach in our test already and it works well. I don't test the in-browser generated files yet using Cypress (we have legacy Selenium suite for that), but I quickly tested the approach I described here and it seems to be working. I'd love if Cypress would let us just download a file and assert on its contents, but in the meantime those approaches provide enough coverage for most, if not all, needs. |
@kamituel I tried the in 'test portion' of the code in the cypress test, and yet I still saw the 'save pop up' |
@Shubha-Alvares The code you posted: // '#download-file' here is an anchor that user clicks to download a file.
// It has to be the correct DOM element in order for the code to cancel
// the correct 'event' object.
document.getElementById('download-file').onclick = function(event) {
// ...
// In test, prevent the browser from actually downloading the file.
if (window.Cypress) {
event.preventDefault();
}
// ...
}); If you're seeing the popup, it might mean that:
Hard to be more definitive not seeing your code. But in general, this approach should work as long as you manage to cancel |
Just a note on solution 1 by @kamituel: cy.get('a').click().then((anchor) => {
const url = anchor.prop('href');
// cy.request() will work, because web app did talk to the backend out-of-band.
cy.request(url).then(/* do something, e.g. assert */);
}); ...didn't work for me, BUT using cy.get('a').click().then((anchor) => {
const url = anchor.attr('href');
// cy.request() will work, because web app did talk to the backend out-of-band.
cy.request(url).then(/* do something, e.g. assert */);
}); Thank you @kamituel! You solved a blocker for me. |
I apologize if this is obvious but based on @kamituel solution above, are we able to change the save location when cypress downloads a file? |
@natejcho No, because at no point the browser actually downloads the file using the native "download manager". I explicitly prevent that in test (when That's in fact the main reason the two approaches I described work. I prevent the browser from actually downloading the file, and instead I download the file, store its contents in a variable, and assert that (in the test code). |
@kamituel I realized this after playing around with your code more. But I solved my unique issue in which I wanted cypress to download a file and place it in a specific location. I used your solution so the test could receive a base64 string. Then created a plugin to handle the encoding and writing the file to a directory. Cypress plugins run in the node and I am able to utilize the fs library and write files at the project root level. See [https://github.com//issues/2029] for writing the plugin |
Hello, natejcho I want to implement the same way. I want to get the response of the file thats require to download. waiting for your reply. Thanks |
I have used cy.request(href).then((response) => {
expect(response.status).to.equal(200);
expect(response.body).not.to.null;
cy.exec("node cypress/support/Core/Parser/PDFParser.js " + response.body, {failOnNonZeroExit: false}).then((result: any) => {
console.log(result);
});
}); |
For a future solution to this problem in Cypress itself: Once #4628 is merged, we'll have access to the Chrome DevTools Protocol, which has these 2 functions:
Cypress could set the Maybe the API could look something like this: Cypress.on('download:will:begin', (url) => {
// cy.request(url) could be used here to assert on contents?
// potential problem: we don't have access to the request body or headers
// used to make this download happen
}) |
To add on top of this. I'd like to make a feature request
This issue has lots of activity and been mentioned in many different issues. Which I think indicates a good need for it |
Hello. |
Hello Any news to how to manage download in cypress? |
Ran the test in chrome and ended up with this error "cy.task('allowDownloads') failed with the following error: The 'task' event has not been registered in the plugins file. You must register it before using cy.task()" please advise |
@Sree2412 read the error, you need to add the |
I am very new to cypress, just started with it, going through cypress documentation changed my plugin file like this.. but still getting error.Please guide, yes I have added all the above mentioned code . this is how my plugin file looks now //// <reference types="cypress" />
// ***********************************************************
// reflect-metadata is required since PortsFileService and DebugLogger has decorator injectable
const ntlmAuth = require("cypress-ntlm-auth/dist/plugin");
const CDP = require('chrome-remote-interface')
const debug = require('debug')('cypress:server:protocol')
module.exports = (on, config) => {
config = ntlmAuth.initNtlmAuth(config);
return config;
function ensureRdpPort (args) {
const existing = args.find((arg) => arg.slice(0, 23) === '--remote-debugging-port')
if (existing) {
return Number(existing.split('=')[1])
}
const port = 40000 + Math.round(Math.random() * 25000)
args.push(`--remote-debugging-port=${port}`)
return port
}
let port = 0
let client = null
on('before:browser:launch', (browser, launchOptionsOrArgs) => {
debug('browser launch args or options %o', launchOptionsOrArgs)
const args = Array.isArray(launchOptionsOrArgs) ? launchOptionsOrArgs : launchOptionsOrArgs.args
port = ensureRdpPort(args)
debug('ensureRdpPort %d', port)
debug('Chrome arguments %o', args)
})
on('task', {
resetCRI: async () => {
if (client) {
debug('resetting CRI client')
await client.close()
client = null
}
return Promise.resolve(true)
},
allowDownload: async () => {
client = client || await CDP({ port })
return client.send('Browser.setDownloadBehavior', { behavior: 'allow', downloadPath: 'C:/Users/username/Downloads' })
}
});
} |
I have this code under test describe('Downloads', () => {
beforeEach(() => {
cy.task('resetCRI')
cy.task('allowDownload')
cy.visit("")
}) but still receiving "cy.task('resetCRI') failed with the following error: The 'task' event has not been registered in the plugins file. You must register it before using cy.task()" I think I registerd my task under my plugin index file, unable to figure out what I am missing. some one pls help! |
some one pls look at above issue and advise |
After "npm ci" on my Mac. I also got problems with chromium. It seems there is a problem in newer version. Now I'm using a fix revision of chromium by adding the file .npmrc with |
These settings worked for me and seem to be a better solution than anything you might stick in |
@nickbreid I tried above solution with my windows machine but it's giving following error, |
@nickbreid for me this works. But not in headless mode, which is a problem for me. |
This comment has been minimized.
This comment has been minimized.
I have switched back to this solution #949 (comment) But I have removed the browser config in plugins/index.js which adds the local chromium. This didn't work under windows. |
We're hoping to do some work to assess the effort it would take to have built-in file download support soon. Priorities can change as we move forward, but you can see the larger features we have planned here: https://on.cypress.io/roadmap |
@jennifer-shehane @kamituel @TomaszG @dmitry I am validating that a file was exported in cypress, but it gives me an error as the file name varies depending on the download as it has the download date and the sequence code. How can I validate without putting the specific name of the file? |
This comment has been minimized.
This comment has been minimized.
There's currently a PR open so that when you click to download a file, the download popup will prevent from showing and download the file directly to a Priorities can change as we move forward, but here is an outline of what we're planning for download file support.
|
Released in This comment thread has been locked. If you are still experiencing this issue after upgrading to |
Hey everyone, file download is now supported in Cypress 6.3.0. You can now test file downloads in Cypress without the download prompt displaying. Any files downloaded while testing file downloads will be stored in the This means that if you had any code written to prevent the download prompt or to configure the download location (like in a Our file download recipe has been updated, so you can see some ways to test different types of files after they've been downloaded there. If you're encountering any bugs while testing file downloads, please open a new issue. |
Current behavior:
In Electron, when an anchor is clicked that downloads a file from the server, a "Save" dialog is shown thus preventing the rest of the test case from completing / succeeding. No dialog is shown in Chrome, at least currently, but this appears to be just because that's Chrome's default behaviour.
Furthermore, there is no way of specifying the desired location of file downloads.
Desired behavior:
This should work both for files downloaded from the server (with
Content-Disposition
HTTP header), as well as for files downloaded using in-browserBlob
+ dynamically generated anchor approach.How to reproduce:
Given any site that contains an
<a href="">...</a>
pointing to a file served withContent-Disposition
header, use a test script similar to the one posted below:Test code:
Additional Info (images, stack traces, etc)
This functionality is needed in cases where it is not possible to verify validity of the download using other methods suggested in the FAQ:
https://bank/transaction-history/34964483209802/pdf
, where34964483209802
is a credit card number. This is unacceptable from the security / compliance perspective, because HTTP URL's are oftentimes logged in proxies or web servers. One solution is to send sensitive data over secure comms, i.e. WebSockets, just after user clicks the anchor. But this makes it impossible to just look at URL and issuecy.request()
.Blob
+ transient<a>
with data URI technique, in which case no request is made to the server at all.The text was updated successfully, but these errors were encountered: