Cypress: Adding Ability to Submit File to Input Element From Local Filesystem (file upload)

Created on 15 Jun 2016  ·  171Comments  ·  Source: cypress-io/cypress

Description
Our app takes a file from an user, does some work with it, and produces outputs based on the input the user provides. A bunch of things happen in the system during the processing and we expect project pages to be generated in a certain fashion after a project successfully completes. We need to be able to submit files through our webpage to test the affects of this process.

3️⃣ proposal 💡 file upload ⏫ native events feature

Most helpful comment

Hey everyone!

I fixed this for DropZone using this piece of code:

const dropEvent = {
    dataTransfer: {
        files: [
        ],
    },
};

cy.fixture('Path to picture fixture').then((picture) => {
    return Cypress.Blob.base64StringToBlob(picture, 'image/jpeg').then((blob) => {
        dropEvent.dataTransfer.files.push(blob);
    });
});

cy.get('Your dropzone element').trigger('drop', dropEvent);

I hope this will work for you too!

All 171 comments

This requires sending native events.

We've been holding off on adding native event support because of the way the debugger protocol works in Chrome. It can only accept one connection which means dev tools cannot be open. Forcing users to close their dev tools is a big reason why the testing ecosystem is so awful.

We've been investigating the user experience around this and we have some pretty good ideas about how to improve this. Once we add native events you'll be able to not just upload files but send native keyboard events, mobile events, and a slew of other things.

Should also note that it is possible to upload files in your application but its different based on how you've written your own upload code.

For instance if you use the new fileReader API's you could use cy.readFile or cy.fixture to read in data, and then convert it to a Blob and then trigger the specific event your application is looking for and send in the Blob as a property.

Because the implementation is different per application there is no way for us to suggest how to do it. Once we implement native API's we will be able to show a recipe handling this.

Wanted to add that I got this working by doing the following:

cy.fixture('path/to/image.png').as('logo')
  .get('input[type=file]').then(function(el) {
    return Cypress.Blob.base64StringToBlob(this.logo, 'image/png')
      .then(blob => {
        el[0].files[0] = blob
        el[0].dispatchEvent(new Event('change', {bubbles: true}))
      })
  })

I've tried everything and I can't get dropzone on my react app to recognise the file.

cy.get('input[type=file]', { force: true }).then(function(el) {
    const xml = new Blob([this.xbrl], { type: 'text/xml' } )

    el[0].files[0] = xml
    el[0].dispatchEvent(new Event('change', {
        bubbles: true,
        target: {
            files: [xml],
        }
    }))
})

@brian-mann

Once we add native events you'll be able to not just upload files but send native keyboard events, mobile events, and a slew of other things.

Is there any progress on the road to native events? Having to hack around for file uploading, hovering and tabbing is a big deal for us.

Chromium team is still working on multiplex'd debugger support. Implementing native events without this is significantly difficult and there are many edge cases and UI changes we'd have to account for, and then once they do land this, we'd have to fork the code and handle it in a separate way.

I'm still on the side of waiting until this is implemented properly by them. You can essentially achieve most things without native event by firing events directly. You can't do things like tab, but you could always use a different tool just for those tests, while letting Cypress handle all the rest.

You can test file uploads using events directly if your app is using HTML5 API's and its not like an oldschool traditional form.

Thanks for the detailed response. While I could get away without tabbing and work around file upload, this involved a lot of boilerplate, complexity and changes in the application code. Nevertheless, great product anyway and good to know that you are looking forward to switching to native events when things get stable on chrome side!

Using el[0].files[0] = blob as @wescarr recommends worked until few weeks back (I don't recall what Chrome version it was).

But now, at least in v.61+ this no longer works and errors out with "TypeError: Failed to set an indexed property on 'FileList': Index property setter is not supported."

This is not an issue on side of Cypress. I just felt mentioning it might save time to someone running into this thread. Unfortunately, I didn't find a way around this yet.

Haven't looked into this, but oftentimes you can overwrite things manually using Object.defineProperty which can forcibly change the descriptor for values.

That is of course if it's configurable: true

I'm having the same issue @rdamborsky mentioned and can't find a way around it.

@brian-mann I tried to overwrite but it seems not possible:
https://stackoverflow.com/questions/1711357/how-would-you-overload-the-operator-in-javascript

The FileList descriptor is empty
Object.getOwnPropertyDescriptors($input[0].files) // => {}

Any suggestion to get a file upload tested? Perhaps we could pass an option to the cypress run command to turn off chrome security sandbox?

As mentioned above - there is no way to set values in file upload inputs, and there are no flags that chrome exposes to force this. You have to use native events. I commented on this recently yesterday.

https://github.com/cypress-io/cypress/issues/311#issuecomment-339824191

The only other way to get file uploads working today is to understand how your application handles them with File API and then stub it out. It's possible but not generic enough to give any advice on it.

tks @brian-mann 👍

I think Cypress is really awesome and am already using it within my company. We previously used Protractor and were able to add a file to an input[type="file"] element. Would really love to be able to this as well with Cypress, because it is sometimes an essential step when doing full e2e tests for our applications.

Hey everyone!

I fixed this for DropZone using this piece of code:

const dropEvent = {
    dataTransfer: {
        files: [
        ],
    },
};

cy.fixture('Path to picture fixture').then((picture) => {
    return Cypress.Blob.base64StringToBlob(picture, 'image/jpeg').then((blob) => {
        dropEvent.dataTransfer.files.push(blob);
    });
});

cy.get('Your dropzone element').trigger('drop', dropEvent);

I hope this will work for you too!

@anned20 this is really good - thank you for this.

One tidbit - you're missing a return statement on Cypress.blob.base64StringToBlob because that returns a promise and the cypress cy.then needs to know about it else it will not properly await it. This is working in your example by chance because the blob promise is resolving faster than the later cy.get and cy.trigger resolve.

Return the promise will always finish the promise chain first before moving on.

@brian-mann I updated the example, is this what you meant?

Yup 👍

Is there any workaround (other than using Electron) for file inputs (the dropzone example is not working for me) or we have to wait for the native events implementation?

The workaround is however your application is built. You fire the events and provide the object value properties and/or methods that you application uses to respond to the upload events.

There is no generic solution - you have to understand how your application works. Then however it works you fire what it needs to respond.

I'm new to Cypress.io and encounter issue when testing upload file implemented by Vue.js
We have a component like following:

<b-form-file id="upload-file" v-model="files[0]" @change="upload(payload)" >
</b-form-file>

I tried
cy.get('#upload-file').trigger('change', somepayload)
but nothing happened

is there any way to trigger change or proper event for application implemented by Vue?

Thanks for any respond.
Cheers.

@danceric0919 can you just call upload() method on the Vue component around this element? Like load the context from Cypress fixture then call the method? I should maybe make a recipe for this that uses Vue.js - where is b-form-file coming from? Is it a public component?

Ok found it - https://bootstrap-vue.js.org/docs/components/form-file/

@danceric0919 can you take a look how I test file upload in https://github.com/bahmutov/vue-vuex-todomvc/commit/deff47a63f5e0b7bd8148f0a5089ece39c7a24b5 ? Just create a File in your test, set it in the component and trigger change event.

@bahmutov thanks for the reply.
I'll try it later today or tomorrow.
Appreciate 👍

2017/11/29 update
After updating application slightly, your example works in my site.
Really appreciate for the example, it saved my days.

@bahmutov Used the same approach for faking a file upload in react. In essence it boils down to the following steps:

cy.fixture('kitten.gif').then(file => {
        cy.get('#elementWithmodel').then(([element]) => {
          const reactDebugKey = Object.keys(element).find(key => key.startsWith('__reactInternalInstance'));
          element[reactDebugKey]._debugOwner.stateNode.props.model.file = {
            name: 'kitten.gif',
            image: file,
          };
        });
      });

I find the element containing my model (a simple object with a name and an image property) and override its contents. Would love to see this solved properly of course, but just thought I'd share my findings.

I made a small recipe for people testing file upload (in React, but same approach could be used for other frameworks) https://github.com/cypress-io/cypress-example-recipes/tree/master/examples/file-upload-react

@brian-mann any idea on the timeline / roadmap for native events? My company is trying to choose an e2e testing framework, and Cypress looks really nice—we are very impressed. But we're going to need to submit files through forms. If you think this functionality would be coming soon, we might be able to bite off a work-around…but if it's still several months away, we might have to try another framework. 😢

@nrutman can I ask why can't you get around loading files natively - you can set the file to be uploaded from test code https://github.com/cypress-io/cypress-example-recipes/tree/master/examples/file-upload-react - is this not a good work around for you?

It is a hassle to use test code to submit a file. I spent a lot of time figuring how to correctly encode the file to be sent, i studied the examples but none of them worked out of the box for me. Another point, if one is using upload libraries, he'd have to dig into the lib internals to understand how to correctly provide a file to it. I had to for

@bahmutov I'm going to try that today. I've tried a few other workarounds and they have not panned out. But I will try the one you referenced.

I'm sure you're aware but Chrome 63 now supports multiple simultaneous debug sessions which I think was one of the original problem with supporting file uploads.

I was thinking of making a utility NPM module to properly encode any file / fixture for mocking uploads. Any particular examples to show?

@tamlyn yes, it's documented here https://github.com/cypress-io/cypress/issues/311

@nrutman Native events is likely months out still. We are adding cross browser support, rewriting the network stubbing layer, and adding lifecycle events. Then native events after.

@bahmutov Microsoft Office doc/docx files where giving me hard time. Just couldn't make them work. I managed to make an upload somehow, but server-side complained that files are corrupted. An example would be great!

@bahmutov does the code sample you linked to work for web forms? I have a standard HTML file input control, and I'm trying to use your example code. I get the file to load from the fixture just fine, but it won't actually associate it with the file input in the form.

Here's the code I'm using if it helps:

let testFile;

cy.fixture('resume.pdf').then((fileContents) => {
    testFile = new File([fileContents], 'resume.pdf');
});

// ... then it goes to the page

cy.get('#my-file-ID').trigger('change', { testFile });

If I console.log(testFile) I'll see the file in memory...but it never loads it in the web form file input.

do you get the change event in the form? Then inside the handler function grab the test file from the event onchange = (e) => { e.testFile || input file}

@bahmutov No, I missed that part in the example. So it looks like this requires us to update our form to be an AJAX form post instead of a standard web form submission, is that correct? I'm part of a team working on a sizable web app. I don't think I can change the source code to accommodate a test runner.

I thought your code example would work on a standard web form. Am I missing something?

I have played with examples for uploads in react and vue. If your form is standard form submit then make an example repo and we can see the best way to go about it. If there is a good way we can add to the recipe

Sent from my iPhone

On Jan 16, 2018, at 17:53, Nate Rutman notifications@github.com wrote:

@bahmutov No, I missed that part in the example. So it looks like this requires us to update our form to be an AJAX form post instead of a standard web form submission, is that correct? I'm part of a team working on a sizable web app. I don't think I can change the source code to accommodate a test runner.

I thought your code example would work on a standard web form. Am I missing something?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

My React recipe and Vue recipes both have Ajax form with file upload where we can grab and insert test file. For standard HTML forms, I have written the following hack https://github.com/bahmutov/cypress-form-data-with-file-upload - see if this works where https://github.com/cypress-io/cypress-example-recipes#vue--vuex--rest-testing and https://github.com/cypress-io/cypress-example-recipes#file-upload-in-react fall short.

For any bugs and shortcomings related to cypress-form-data-with-file-upload, please file issues in its repo https://github.com/bahmutov/cypress-form-data-with-file-upload/issues

a workaround that work in firefox and chrome is to set on the input files property a DataTransfer object instead of a FileList object:

const dT = new ClipboardEvent('').clipboardData || new DataTransfer();

dT.items.add(new File(['foo'], 'programmatically_created.txt'));

yourInput.files = dT.files;

@giolf I got that solution working in Chrome, but does it work in the provided headless Electron browser?

@nrutman Nope, it works only in chrome and firefox.

BTW i don't really get the point. Framework as Protractor can handle input file.
I think cypress could handle it as well.
Protractor is a free solution while cypress not completely.
So ... when i read by the cypress team to provide only _workaround/hacky_ solutions i don't feel as much as comfortable to pick it as e2e framework for an enterprise company.
But this is only a personal opinion. On the other hand it's a great tool (especially on the APIs level)

@giolf Protractor is built on Selenium, which means it gets a lot out of the box. One of the things I really appreciate about Cypress is that they are building the test runner from the ground up…which is great since my team has been plagued by selenium bugs and there aren't really any good alternatives out there.

That being said, there is a cost to using Cypress while it's in beta (like native events / file uploads still being on the roadmap). There are some features still under development. But the dev team seems to be really responsive, and I'm super impressed with what they've built so far. I'm excited to see where Cypress goes in the future. Really good stuff.

We are adding native events relatively soon - we have an internal proof of concept done. So when we do this, all of these issues will subsequently be fixed - and our native event behavior will match Selenium.

With that said, we will retain the ability to do simulated events (because there are still considerable advantages) - the developer will choose which ones to use. Certain events like file uploads will only ever be native, but other things like clicking can be both. In order words we'll have the best of both worlds.

Chrome 63 is what was the blocker for us for years - and with that working we can now adopt Native Events.

For those interested in a workaround, eere's how I got it working for our app with cypress 2.0.0, without using events. Of course, YMMV:

cy.get('#someElement').then(subject => {
  cy.fixture(filePath)
    .then((content) => {
      const el = subject[0];
      const fileName = path.basename(filePath);
      const testFile = new File([content], fileName);
      const dataTransfer = new DataTransfer();

      dataTransfer.items.add(testFile);
      el.files = dataTransfer.files;
    })
});

@fcurella Tried your workaround, but the UI component says "file is undefined or not an image"
Here's the code:

cy.get('.js-cover-image input[type=file]').first().then(subject => {
      cy.fixture('cover.png')
        .then((content) => {
          const el = subject[0]
          const fileName = path.basename('cover.png')
          const testFile = new File([content], fileName)
          const dataTransfer = new DataTransfer()
          dataTransfer.items.add(testFile)
          el.files = dataTransfer.files
        })
    })

I have also tried it that way. I have put the encoding of fixture to be ascii. The size of the data sent seems to be the same but still it doesn't work. I am using what @fcurella put. Any other workaround of ideas?

I used a similar workaround, and I found that when reading in the file contents they were coming in as string data instead of binary data. To fix this, I found a function online (can't find the original source at the moment) and modified it…

Here it is (Typescript):

/**
 * Decode base64 data to a binary File object
 * @param  b64Data     The base-64 encoded data
 * @param  filename    The filename for the file
 * @param  contentType The content type for the fle
 * @param  sliceSize   The size for the byte arrays
 * @return             The resulting file object
 */
function b64toFile(b64Data, filename: string, contentType: string = '', sliceSize: number = 512): File {
    let byteCharacters = atob(b64Data);
    let byteArrays = [];

    for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
        let slice = byteCharacters.slice(offset, offset + sliceSize);

        let byteNumbers = new Array(slice.length);
        for (let i = 0; i < slice.length; i++) {
            byteNumbers[i] = slice.charCodeAt(i);
        }

        let byteArray = new Uint8Array(byteNumbers);

        byteArrays.push(byteArray);
    }

    let file = <any>(new Blob(byteArrays, {
        type: contentType
    }));

    // give the blob object the missing data members for a file
    file.lastModifiedDate = new Date();
    file.name = filename;

    return <File>file;
}

Then do something like this:

        var fileContents;
        cy.fixture('file.pdf', 'base64').then(file => {
            fileContents = b64toFile(file, 'file.pdf', 'application/pdf');
        });

        cy.get('#my-file-input').then($fileInput => {
            const fileData = new ClipboardEvent('').clipboardData || new DataTransfer();
            fileData.items.add(new File([fileContents], 'resume.pdf', {type: 'application/pdf'}));
            (<any>$fileInput.get(0)).files = fileData.files;
        });

@nrutman Your function (working for me) probably comes from here: https://ourcodeworld.com/articles/read/322/how-to-convert-a-base64-image-into-a-image-file-and-upload-it-with-an-asynchronous-form-using-jquery
I used it because the Cypress one base64StringToBlob (https://docs.cypress.io/api/utilities/blob.html#) didn't work for me.

@alagane great find! Yes, I think that was where I saw the original source! Thanks for documenting it.

Hi Everyone, I have been working on using cypress with docker compose for e2e testing. While testing a csv upload feature, I had some trouble passing the csv fixture to the data property of the event, but eventually found a workaround:

getUploadFile (e) {
    if (e.nativeEvent.data) {
      return new File([e.nativeEvent.data], 'upload.csv', {type: 'text/csv'})
    }
    return e.target.files[0]
  }

use it like so:

const file = this.getUploadFile(e)

then in your test:

cy.fixture('./test.csv').as('csv')
cy.get('input[type=file]').trigger('change', {force: true, data: this.csv})

I hope this is the right place to add this, i spent some time figuring out how to pass the data to the event, maybe it helps someone.

I know the code could be improved, but it works :)

Hi. In my case, @gsrai's answer was really helpful. I generalized it like below.

In your jsx (or html):

<input
  type='file'
  onChange={this.onChange}
/>

The change event handler function:

onChange = e => {
  // usual way to get a file from an input
  let file = e.target.files[0]
  // add this for cypress
  if (e.nativeEvent.file) {
    const { name, type, data } = e.nativeEvent.file
    file = new File([data], name, { type })
  }
  // do whatever with the file
}

In your test:

cy.fixture('iris')
  .then(data => {
    const file = {
      name: 'iris.csv',
      type: 'text/csv',
      data
    }
    // pass the file object as an event property
    cy.get('input[type=file]')
      .trigger('change', { force: true, file })
  })

Here is my working example, what I use.

Enter code bellow to "cypress/support/commands.js"

Cypress.Commands.add('upload_file', (fileName, selector) => {
    cy.get(selector).then(subject => {
        cy.fixture(fileName).then((content) => {
            const el = subject[0]
            const testFile = new File([content], fileName)
            const dataTransfer = new DataTransfer()

            dataTransfer.items.add(testFile)
            el.files = dataTransfer.files
        })
    })
})

This is how I use it in my test:
cy.writeFile('cypress/fixtures/notice.pdf', 'Hi, this content is created by cypress!')
cy.upload_file('notice.pdf', 'input[name=file1]')

Big thanks to @fcurella who code this is from above (love you man! :D).
You have no idea how many hours I tried to get this working.

@MysteriousNothing Thanks. This also worked for me!

Trying to work out how to adapt it so it works with images?

@JonRobinsonLush are you getting a broken tile? If so, I was able to solve by using Cypress.Blob.base64StringToBlob. Example below:

cy.fixture('image.jpg').then(content => {
    Cypress.Blob.base64StringToBlob(content, 'image/jpeg').then(blob => {
            const testFile = new File([blob], fileName);
           // the rest of @MysteriousNothing's code goes here...
    });
});

@brian-mann have your team got any estimates on when would you ship native events?

@JonRobinsonLush

Here's my adapted version (thanks to everyone in the thread) that works with images and other files (placed inside commands.js for easy access):

Cypress.Commands.add('upload_file', (fileName, selector) => {
    cy.get(selector).then(subject => {
        cy.fixture(fileName, 'base64').then((content) => {
            const el = subject[0];
            const blob = b64toBlob(content);
            const testFile = new File([blob], fileName);
            const dataTransfer = new DataTransfer();

            dataTransfer.items.add(testFile);
            el.files = dataTransfer.files;
        });
    });
});

function b64toBlob(b64Data, contentType, sliceSize) {
    contentType = contentType || '';
    sliceSize = sliceSize || 512;

    var byteCharacters = atob(b64Data);
    var byteArrays = [];

    for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
        var slice = byteCharacters.slice(offset, offset + sliceSize);

        var byteNumbers = new Array(slice.length);
        for (var i = 0; i < slice.length; i++) {
            byteNumbers[i] = slice.charCodeAt(i);
        }

        var byteArray = new Uint8Array(byteNumbers);

        byteArrays.push(byteArray);
    }

    var blob = new Blob(byteArrays, { type: contentType });
    blob.lastModifiedDate = new Date();
    return blob;
}

use like this in your tests:
cy.upload_file('building.jpg', '#building [type="file"]');

@faktotum85 Thank you for this command. However in my case it failed to pass the content type verification on side of app that I was testing. The problem appeared to be caused by the fact that content type is not set on the step of file composing:
const testFile = new File([blob], fileName);

According to MDN it's possible to pass contentType to File too:
https://developer.mozilla.org/en-US/docs/Web/API/File/File

..so I have added content type as parameter to main function too and passed it to File:

Cypress.Commands.add('upload_file', (fileName, selector, contentType) => {
  contentType = contentType || '';
  ...
  const testFile = new File([blob], fileName, {type: contentType});

Hope that it helps someone.

FYI, @faktotum85's solution will not work with JSON files. It appears that cy.fixture('example.json', 'base64') ignores the encoding param and still gives the raw JSON. I don't personally need to upload JSON files. So, I haven't looked into an update that would allow JSON files to work, but I encountered this issue when playing with the script provided. You'll need to find a way to verify that the content is base64 encoded before running the atob().

I've went ahead and added support to provide multiple files as well as added a catch like I described above. I'm going to wrap everything in an NPM package sometime this week to make it easier to implement this.

Will leave this here in case it helps somebody
https://github.com/javieraviles/cypress-upload-file-post-form

Just to clarify, you can use Cypress.Blob.base64StringToBlob to convert the fixture's base64 response to a blob. That returns a promise :) here is the whole thing. Bonus jsdoc + its chainable, so you can do other stuff to the input if needed.

/**
 * Uploads a file to an input
 * @memberOf Cypress.Chainable#
 * @name upload_file
 * @function
 * @param {String} selector - element to target
 * @param {String} fileUrl - The file url to upload
 * @param {String} type - content type of the uploaded file
 */
Cypress.Commands.add('upload_file', (selector, fileUrl, type = '') => {
  return cy.get(selector).then(subject => {
    return cy.fixture(fileUrl, 'base64')
      .then(Cypress.Blob.base64StringToBlob)
      .then(blob => {
        const el = subject[0]
        const nameSegments = fileUrl.split('/')
        const name = nameSegments[nameSegments.length - 1]
        const testFile = new File([blob], name, { type })
        const dataTransfer = new DataTransfer()
        dataTransfer.items.add(testFile)
        el.files = dataTransfer.files
        return subject
      })
  })
})

PS: The above code does not work inside the Electron browser. Electron does not support the instantiation of DataTransfer. We need to find another way around this.

Because its 2018 and everyone should use drag and drop uploaders, please use a simple drag event handler on an outer div. Then you can do this:

Cypress.Commands.add('upload_file', (selector, fileUrl, type = '') => {
  return cy.fixture(fileUrl, 'base64')
    .then(Cypress.Blob.base64StringToBlob)
    .then(blob => {
      const nameSegments = fileUrl.split('/')
      const name = nameSegments[nameSegments.length - 1]
      const testFile = new File([blob], name, { type })
      const event = { dataTransfer: { files: [testFile] } }
      return cy.get(selector).trigger('drop', event)
    })
})

@brian-mann - I noticed electron recently did a release, did they upgrade chromium enabling cypress to start using native events?

I had a look but was unclear.

I'm keen to start automating input type='file' for csv's, pdfs etc.

Maybe I could have a play with the pre-release work and start to put together a recipe? Any pointers would be awesome as im fairly new to the project

I had a specific need to upload excel files to my application. Just wanted to share the code that worked for me in chrome browser.

Cypress.Commands.add('upload_file', (fileName, selector) => {
    return cy.get(selector).then(subject => {
        return cy.fixture(fileName, 'base64')
        .then(Cypress.Blob.base64StringToBlob)
        .then(blob => {
            const el = subject[0]
            const testFile = new File([blob], fileName, { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
            const dataTransfer = new DataTransfer()
            dataTransfer.items.add(testFile)
            el.files = dataTransfer.files
            return subject;
        })
    })
})

example usage:

cy.upload_file('Test.xlsx', '#fileInput')

Test.xlsx needs to be in your fixtures folder in order for this to work

nice @danreale you just solved my problem with multi file upload

We were struggling with this for quit a while, also on the part where contributers mention knowing how your application works. Here's is what worked for our application, many thanks to above contributors. It's may be written a bit at dummy level at some points, but with the expectation you understand some about functions (if not I'm sure your dev colleague would be happy to assist:-)). But for some who don't have that much experience yet, maybe it will send you in the right direction.

What we stumbled across were authorization errors/login popups and all sorts. On our old application which had poor architecture, we had to do a lot of GET/PUT/POST requests (couldn't get stubbing to work) in order to set the required state of the application and get the right tokens for the headers in order to be able and mostly authorized to upload a file to a database. You can check out https://docs.cypress.io/api/commands/request.html#URL on how to do that. We ended up feeding the database with a POST call so that the frontend site would know the doc was uploaded and allowed the next button to actually go the next page. Which basically didn't really tested our upload functionality in the frontend.

When we build our new application, we realized we needed to simplify the architecture in order to make it more (automate) testable. Understanding how to get the upload functionality to work did again take some time but we finally figured it out and got the below fixture to work.

Add this in your commands.js

Cypress.Commands.add('upload_file', (fileName, fileType = ' ', selector) => {
    return cy.get(selector).then(subject => {
      cy.fixture(fileName, 'base64')
        .then(Cypress.Blob.base64StringToBlob)
        .then(blob => {
          const el = subject[0];
          const testFile = new File([blob], fileName, { type: fileType });
          const dataTransfer = new DataTransfer();
          dataTransfer.items.add(testFile);
          el.files = dataTransfer.files;
        });
    });
  });

In cypress\fixtures folder make sure the document you want to upload is placed in that folder.

In the testfile it looks like below. In a supporting file I created a setUp function that would call to our backend with a POST request, parse the url from it's response body so it could do a cy.visit that testurl which will be the base for our test.
The preserve cookie in the beforeEach finally did the trick, as another required cookie got lost in the process before doing the upload test.

describe('Test upload functionality, () => {
   before(() => {
    setUp({  page: '/example-upload-page' })
  });

  beforeEach(() => {
    Cypress.Cookies.preserveOnce("requiredCookie3");
  });
  it('Should upload a document', () => {
    cy.setCookie('cookie1name', cookie1value)
    cy.setCookie('cookie2name', cookie2value)

    const fileName = 'PDF-sample-8KB.pdf';
    const fileType = 'application/pdf';
    const fileInput = 'input[type=file]';
    cy.upload_file(fileName, fileType, fileInput)
  });
});

Btw, using Postman in all of this was helpful, it gave us feedback whether our call was valid or not, how to format the body etc etc. and therefore do troubleshooting why a call in Cypress wasn't working.

Hope this might help to some!

@dobromir-hristov's solution worked great for me. Except, I had another lib doing a instanceof File check, which would normally pass, but failed because cross-frame/window instanceof checks don't work.

I tweaked it to use the AUT's File constructor:

/**
 * Uploads a file to an input
 * @memberOf Cypress.Chainable#
 * @name upload_file
 * @function
 * @param {String} selector - element to target
 * @param {String} fileUrl - The file url to upload
 * @param {String} type - content type of the uploaded file
 */
Cypress.Commands.add('uploadFile', (selector, fileUrl, type = '') => {
  return cy.get(selector).then(subject => {
    return cy
      .fixture(fileUrl, 'base64')
      .then(Cypress.Blob.base64StringToBlob)
      .then(blob => {
        return cy.window().then(win => {
          const el = subject[0]
          const nameSegments = fileUrl.split('/')
          const name = nameSegments[nameSegments.length - 1]
          const testFile = new win.File([blob], name, { type })
          const dataTransfer = new DataTransfer()
          dataTransfer.items.add(testFile)
          el.files = dataTransfer.files
          return subject
        })
      })
  })
})

@mattblackdev Thanks, I had a similar problem and this resolved it very nicely! 👍

@mattblackdev I also had a similar problem, thanks so much for posting! Got close by combining stuff posted by @anned20, @dobromir-hristov, and others, but your solution solved the instanceof checks.

Ran into an issues when trying to use json fixtures (full details below), but that was easily solvable by just adding a function to get the blob from the fixture:

import path from 'path'

/**
 * Converts fixture to Blob. All file types are converted to base64 then
 * converted to a Blob using Cypress expect application/json. Json files are
 * just stringified then converted to a blob (fixes issue invalid Blob issues).
 * @param {String} fileUrl - The file url to upload
 * @param {String} type - content type of the uploaded file
 * @return {Promise} Resolves with blob containing fixture contents
 */
function getFixtureBlob(fileUrl, type) {
  return type === 'application/json' || path.extname(fileUrl) === 'json'
    ? cy
        .fixture(fileUrl)
        .then(JSON.stringify)
        .then(jsonStr => new Blob([jsonStr], { type: 'application/json' }))
    : cy.fixture(fileUrl, 'base64').then(Cypress.Blob.base64StringToBlob)
}

/**
 * Uploads a file to an input
 * @memberOf Cypress.Chainable#
 * @name uploadFile
 * @function
 * @param {String} selector - element to target
 * @param {String} fileUrl - The file url to upload
 * @param {String} type - content type of the uploaded file
 */
Cypress.Commands.add('uploadFile', (selector, fileUrl, type = '') => {
  return cy.get(selector).then(subject => {
    return getFixtureBlob(fileUrl, type).then(blob => {
      return cy.window().then(win => {
        const name = fileUrl.split('/').pop()
        const testFile = new win.File([blob], name, { type: blob.type })
        const dataTransfer = new win.DataTransfer()
        dataTransfer.items.add(testFile)
        const el = subject[0]
        el.files = dataTransfer.files
        return subject
      })
    })
  })
})

NOTE: Mostly Copied from @mattblackdev's and @dobromir-hristov's solutions with just a small separate case for JSON files

Full Error Details
The error was saying that the string was not correctly encoded/decode:

InvalidCharacterError: Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.

Awesome! Seems like we are slowly building up a general solution to this! It might even add up to a PR if the maintainers are interested. Or perhaps a separate package?

@mattblackdev the problem is that the solution isn't cross-browser (except for ff and chrome), and to my knowledge isn't supported by a browser spec, so it could cease to work in any browser version release. However we are definitely implementing this with Native Browser events, which will be a cross browser and fully supported solution. Details here: #311

For now I would recommend one of the workarounds mentioned above - if you are comfortable with the instability of the solution

My code:

cy.fixture("logo.jpg").as("logo")
cy.get('input[type=file]').then(subject => {
  // From Cypress document: https://docs.cypress.io/api/utilities/blob.html#Examples  
  return Cypress.Blob.base64StringToBlob(this.logo, "image/jpeg").then((blob) => {
    const el = subject[0]
    const testFile = new File([blob], 'logo.jpg', { type: 'image/jpeg' })
    const dataTransfer = new DataTransfer()
    dataTransfer.items.add(testFile)
    el.files = dataTransfer.files
    })
  })

Thanks @fcurella

This works for me. just call this method and replace the fileUrl with your file path and element selector

uploadFile(){
   let fileUrl = "../fixtures/image37.png";
    let selector = '#imageLoader';
    return cy.get(selector).then(subject => {
      return cy
        .fixture(fileUrl, 'base64')
        .then(Cypress.Blob.base64StringToBlob)
        .then(blob => {
          return cy.window().then(win => {
            const el = subject[0]
            const nameSegments = fileUrl.split('/')
            const name = nameSegments[nameSegments.length - 1]
            const testFile = new win.File([blob], name, {type:'image/png'})
            const dataTransfer = new DataTransfer()
            dataTransfer.items.add(testFile)
            el.files = dataTransfer.files
            return subject
          })
   })
  })
}

When I wanted use a code:

cy.fixture("logo.jpg").as("logo")
cy.get('.stylecolor-images-container > :nth-child(1) > .dropzone').then(subject => {
     return Cypress.Blob.base64StringToBlob(this.logo, "image/jpeg").then((blob) => {
                   const el = subject[0]
                   const testFile = new File([blob], 'logo.jpg', { type: 'image/jpeg' })
                   const dataTransfer = new DataTransfer()
                   dataTransfer.items.add(testFile)
                   el.files = dataTransfer.files
       })
})

Then I got an error: TypeError: Cannot read property 'logo' of undefined.
When I gave cy.fixture("logo.jpg") into a const variable then I got an error: InvalidCharacterError: Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.

Similar situation I have when I used code from the documentation:

cy.fixture("images/logo.png").as("logo")
cy.get("input[type=file]").then(($input) => {
     // convert the logo base64 string to a blob
     return Cypress.Blob.base64StringToBlob(this.logo, "image/png").then((blob) => {
     // pass the blob to the fileupload jQuery plugin
     // used in your application's code
     // which initiates a programmatic upload
     $input.fileupload("add", { files: blob })
     })
})

Does anyone has a similar problems?

This works for me. just call this method and replace the fileUrl with your file path and element selector 
uploadFile(){
   let fileUrl = "../fixtures/image37.png";
    let selector = '#imageLoader';
    return cy.get(selector).then(subject => {
      return cy
        .fixture(fileUrl, 'base64')
        .then(Cypress.Blob.base64StringToBlob)
        .then(blob => {
          return cy.window().then(win => {
            const el = subject[0]
            const nameSegments = fileUrl.split('/')
            const name = nameSegments[nameSegments.length - 1]
            const testFile = new win.File([blob], name, {type:'image/png'})
            const dataTransfer = new DataTransfer()
            dataTransfer.items.add(testFile)
            el.files = dataTransfer.files
            return subject
          })
   })
  })
}

to support for all file types except JSON file, just keep the type value empty and in below comments added code to support JSON file
i.e in the above code replace this {type:'image/png'} with {type:''}

@faktotum85 thanks for post It help me a lot

Used @simonpweller (@faktotum85) code and added few lines to support for JSON file and splitting of file to just get the filename

```js
newUpload() {
cy.visit("the url")
let fileName = "../fixtures/test.json";
let selector = '#upload-input';

    cy.get(selector).then(subject => {
        cy.fixture(fileName).then((logo) => {
            let json = JSON.stringify(logo);
            const el = subject[0];
            let objJsonB64 = Buffer.from(json).toString("base64");
            const blob = this.b64toBlob(objJsonB64);
            const nameSegments = fileName.split('/')
            const name = nameSegments[nameSegments.length - 1];
            const testFile = new File([blob], name);
            const dataTransfer = new DataTransfer();
            dataTransfer.items.add(testFile);
            el.files = dataTransfer.files;
        })
    });
}

b64toBlob(b64Data, contentType, sliceSize) {
    contentType = contentType || '';
    sliceSize = sliceSize || 512;
    var byteCharacters = atob(b64Data);
    var byteArrays = [];
    for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
        var slice = byteCharacters.slice(offset, offset + sliceSize);
        var byteNumbers = new Array(slice.length);
        for (var i = 0; i < slice.length; i++) {
            byteNumbers[i] = slice.charCodeAt(i);
        }
        var byteArray = new Uint8Array(byteNumbers);
        byteArrays.push(byteArray);
    }
    var blob = new Blob(byteArrays, { type: contentType });
    blob.lastModifiedDate = new Date();
    return blob;
}

`

@JonRobinsonLush

Here's my adapted version (thanks to everyone in the thread) that works with images and other files (placed inside commands.js for easy access):

Cypress.Commands.add('upload_file', (fileName, selector) => {
    cy.get(selector).then(subject => {
        cy.fixture(fileName, 'base64').then((content) => {
            const el = subject[0];
            const blob = b64toBlob(content);
            const testFile = new File([blob], fileName);
            const dataTransfer = new DataTransfer();

            dataTransfer.items.add(testFile);
            el.files = dataTransfer.files;
        });
    });
});

function b64toBlob(b64Data, contentType, sliceSize) {
    contentType = contentType || '';
    sliceSize = sliceSize || 512;

    var byteCharacters = atob(b64Data);
    var byteArrays = [];

    for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
        var slice = byteCharacters.slice(offset, offset + sliceSize);

        var byteNumbers = new Array(slice.length);
        for (var i = 0; i < slice.length; i++) {
            byteNumbers[i] = slice.charCodeAt(i);
        }

        var byteArray = new Uint8Array(byteNumbers);

        byteArrays.push(byteArray);
    }

    var blob = new Blob(byteArrays, { type: contentType });
    blob.lastModifiedDate = new Date();
    return blob;
}

use like this in your tests:
cy.upload_file('building.jpg', '#building [type="file"]');

I am able to upload file with this code but its ending 500 error

I am currently using this setup:

Cypress.Commands.add('uploadFile', (fileName, selector) =>
  cy.get(selector).then(subject => {
    return cy
      .fixture(fileName, 'base64')
      .then(Cypress.Blob.base64StringToBlob)
      .then(blob => {
        const el = subject[0];
        const testFile = new File([blob], fileName, {
          type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        });
        const dataTransfer = new DataTransfer();
        dataTransfer.items.add(testFile);
        el.files = dataTransfer.files;
        return subject;
    });
  })
);

This is working sporadically. The reason seems to be that it sometimes calls the onChange handler on the input, and sometimes it is not (which is where we have the upload magic actually happen). Has anyone had issues with the onChange handler?

The various upload_file commands work just fine for me, except when the input field is required. My form will not submit and thinks the value is missing.

Has anyone had luck with a required file input field?

I had a specific need to upload excel files to my application. Just wanted to share the code that worked for me in chrome browser.

Cypress.Commands.add('upload_file', (fileName, selector) => {
    return cy.get(selector).then(subject => {
        return cy.fixture(fileName, 'base64')
        .then(Cypress.Blob.base64StringToBlob)
        .then(blob => {
            const el = subject[0]
            const testFile = new File([blob], fileName, { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
            const dataTransfer = new DataTransfer()
            dataTransfer.items.add(testFile)
            el.files = dataTransfer.files
            return subject;
        })
    })
})

example usage:

cy.upload_file('Test.xlsx', '#fileInput')

Test.xlsx needs to be in your fixtures folder in order for this to work

I know you posted this a while back, but I honestly want to say, you're a life saver :)

I had a specific need to upload excel files to my application. Just wanted to share the code that worked for me in chrome browser.

Cypress.Commands.add('upload_file', (fileName, selector) => {
    return cy.get(selector).then(subject => {
        return cy.fixture(fileName, 'base64')
        .then(Cypress.Blob.base64StringToBlob)
        .then(blob => {
            const el = subject[0]
            const testFile = new File([blob], fileName, { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
            const dataTransfer = new DataTransfer()
            dataTransfer.items.add(testFile)
            el.files = dataTransfer.files
            return subject;
        })
    })
})

example usage:

cy.upload_file('Test.xlsx', '#fileInput')

Test.xlsx needs to be in your fixtures folder in order for this to work

I know you posted this a while back, but I honestly want to say, you're a life saver :)

Same 🔥The upload is working. Except, how would you test if the upload is done? Like interacting with your database. Is it possible to go further as soon as the upload is done? cc @musangowope

@glenngijsberts and @musangowope Have either of you tested this with a <input type="file" required> required field?

Update:

I've settled on using a combination of both selectors, to satisfy the UI and data requirements to submit our form:

cy
  .get(selector)
  .then(subject => {
    const element = subject.find('input')[0];
    element.files = dataTransfer.files;

    return subject;
  })
  .trigger('drop', { dataTransfer });

Thanks everyone! 🔥

I come from #933, because my application code failed on File and FileList instance checking (e.g. node[key] instanceof FileList). The problem was that cypress window and application window use different instances of File.

This works for me:

Cypress.Commands.add(
  'dropFile',
  { prevSubject: 'element' },
  (subject, fileName) => {
    return cy
      .fixture(fileName, 'base64')
      .then(Cypress.Blob.base64StringToBlob)
      .then(blob => {
        // instantiate File from `application` window, not cypress window
        return cy.window().then(win => {
          const file = new win.File([blob], fileName)
          const dataTransfer = new win.DataTransfer()
          dataTransfer.items.add(file)

          return cy.wrap(subject).trigger('drop', {
            dataTransfer,
          })
        })
      })
  }
)

Usage:

cy.get('[test=dropzone]').dropFile('images/cypress.png')

Thanks @dobromir-hristov - worked perfect 👍

Thanks @mattblackdev, the papaparse module was doing an instanceOf check causing us to hit this error:

TypeError: Cannot read property ‘stream’ of null

Using the correct window object as suggested in
https://github.com/cypress-io/cypress/issues/170#issuecomment-411289023
did the trick!

Cypress.Commands.add('uploadFile', (selector, fileUrl, type = '') => {
  return cy
    .fixture(fileUrl, 'base64')
    .then(Cypress.Blob.base64StringToBlob)
    .then(blob => {
      return cy.window().then(win => {
        //papaparse was doing an instanceOf window.File check that was failing so we needed 
        //https://github.com/cypress-io/cypress/issues/170#issuecomment-411289023 
        const nameSegments = fileUrl.split('/');
        const name = nameSegments[nameSegments.length - 1];
        const testFile = new win.File([blob], name, { type });
        const event = { dataTransfer: { files: [testFile] } };
        // return subject
        return cy.get(selector).trigger('drop', event);
      });
    });
});

@tricoder42 That solution worked well for me. I had to change it somewhat since our upload attached to the document window instead of a chained element.

Cypress.Commands.add(
  'dropFile', { prevSubject: false }, ( fileName) => {
    return cy
      .fixture(fileName, 'base64')
      .then(Cypress.Blob.base64StringToBlob)
      .then(blob => {
        // instantiate File from `application` window, not cypress window
        return cy.window().then(win => {
          const file = new win.File([blob], fileName)
          const dataTransfer = new win.DataTransfer()
          dataTransfer.items.add(file)

          return cy.document().trigger('drop', {
            dataTransfer,
          })
        })
      })

js cy.document().trigger('dragenter') cy.dropFile('test.csv')

hi everyone i have a question about how can upload a pdf file to that, please help me 👍
captura de pantalla 2019-01-23 a la s 17 17 40

hi everyone i have a question about how can upload a pdf file to that, please help me 👍
captura de pantalla 2019-01-23 a la s 17 17 40

If someone has the problem of uploading a pdf, simply include these lines in your project

const fileName = 'pdfArchive.pdf';
const fileType = 'application/pdf';
const fileInput = 'input[type=file]';
cy.upload_file(fileName, fileType, fileInput)

and in the command.js include these lines

Cypress.Commands.add('upload_file', (fileName, fileType = ' ', selector) => {
    return cy.get(selector).then(subject => {
        cy.fixture(fileName, 'base64')
            .then(Cypress.Blob.base64StringToBlob)
            .then(blob => {
                const el = subject[0];
                const testFile = new File([blob], fileName, {
                    type: fileType
                });
                const dataTransfer = new DataTransfer();
                dataTransfer.items.add(testFile);
                el.files = dataTransfer.files;
            });
    });
});

I think this issue should have been locked long ago... shall we extract the most sensible solution, call it user land implementation and call it a day?

While waiting for the built-in support, we can stop copy-pasting the hack and create a reusable package out of it.
Feel free to open issues so we can make it suitable for the most popular use cases.

https://www.npmjs.com/package/cypress-file-upload

Thanks @abramenal 💯 please also open an issue/PR to add this plugin to our documentation for increased exposure. https://on.cypress.io/plugins

Also, anyone else here is free to do this if you have some 'hack' utilizing Cypress you want shared and easily reused. 😄

Hi!
I'm trying to drop an image into a drop-label but all the solutions proposed are failing to me.

I got to upload de image but on the website, the image is loading, but never ends:

cy1

I'm using this code:

Cypress.Commands.add('upload_file', (selector, fileUrl, type = '') => {
    return cy.fixture(fileUrl, 'base64')
      .then(Cypress.Blob.base64StringToBlob)
      .then(blob => {
        const nameSegments = fileUrl.split('/')
        const name = nameSegments[nameSegments.length - 1]
        const testFile = new File([blob], name, { type })
        const event = { dataTransfer: { files: [testFile] } }
        return cy.contains(selector).trigger('drop', event)
      })
  });

And this to run in the test:

cy.upload_file('Drop', 'image.jpg')

I also tried this solution:

Cypress.Commands.add('uploadFile', (selector, fileUrl, type = '') => {
  return cy
    .fixture(fileUrl, 'base64')
    .then(Cypress.Blob.base64StringToBlob)
    .then(blob => {
      return cy.window().then(win => {
        //papaparse was doing an instanceOf window.File check that was failing so we needed 
        //https://github.com/cypress-io/cypress/issues/170#issuecomment-411289023 
        const nameSegments = fileUrl.split('/');
        const name = nameSegments[nameSegments.length - 1];
        const testFile = new win.File([blob], name, { type });
        const event = { dataTransfer: { files: [testFile] } };
        // return subject
        return cy.contains(selector).trigger('drop', event);
      });
    });
});

And this to run in the test:

cy.uploadFile('Drop', 'image.png')

But this is the result:

cy2

Please, can anybody help me?

Thanks!

Thanks everyone for sharing your snippets, I was able to figure out a working solution from that for my testcase. I am not using a library like dropzone here, instead just a hidden file input and a listener on the change event. I was able to trigger it inside a test like this, maybe it can help someone else as well:

<input hidden 
       data-cy="hidden-file-input"
       @change="onFileInputChanged($event.target)">
cy.request({
  method: 'GET',
  url: fetchBase64ImageUrl,
})
.then(response => {
  const base64image = response.body;

  cy.get('[data-cy=hidden-file-input]')
    .then((subject) => {
      const type = 'image/png';
      Cypress.Blob.base64StringToBlob(base64image, type).then((blob) => {
        const testfile = new File([blob], 'testfile.png', { type: type });
        const dataTransfer = new DataTransfer();
        dataTransfer.items.add(testfile);
        const input = subject[0];
        input.files = dataTransfer.files;
        input.dispatchEvent(new Event('change'));
      });
    });
});

UPDATE: Had to trigger the change event explicitly in recent Chrome versions.

@cb109 have you tried it with https://www.npmjs.com/package/cypress-file-upload ?
It should simplify the code a bit.

This works for me. just call this method and replace the fileUrl with your file path and element selector

uploadFile(){
   let fileUrl = "../fixtures/image37.png";
    let selector = '#imageLoader';
    return cy.get(selector).then(subject => {
      return cy
        .fixture(fileUrl, 'base64')
        .then(Cypress.Blob.base64StringToBlob)
        .then(blob => {
          return cy.window().then(win => {
            const el = subject[0]
            const nameSegments = fileUrl.split('/')
            const name = nameSegments[nameSegments.length - 1]
            const testFile = new win.File([blob], name, {type:'image/png'})
            const dataTransfer = new DataTransfer()
            dataTransfer.items.add(testFile)
            el.files = dataTransfer.files
            return subject
          })
   })
  })
}

This Worked for me. Basically the image loader is any element with

@gsrai

it.only('uploads a csv file, adds the contacts and navigates to the list', () => {
    cy.fixture('./LinkedInContacts.csv').as('csv');
    cy.get('input[type=file]').trigger('change', {
      force: true,
      data: this.csv
    });
  });

Throws:

TypeError: Cannot read property 'csv' of undefined

for me. In which context is your code? Because this seems to be undefined for me...

Thought of pasting my working code as I spent a couple of days trying to figure it out:

// logo.png file is located in fixtures folder
cy.fixture('logo.png').then((picture) =>
  // converting image to blob
  Cypress.Blob.base64StringToBlob(picture, 'image/png').then((blob) => {
    const testFile = new File([blob], 'logo.png');
    // display property is none for input[type=file] so I force trigger it
    cy.get('input[type=file]').trigger('change', {
      force: true,
      data: testFile,
    });
  }),
);

this stopped working for some reason, but could get it up again by adding this line:

cy.wrap(subject).trigger('change', { force: true })
  const selector = '#my-file-upload'
  const fixturePath = 'dummy.pdf' // fixtures/dummy.pdf
  const type = 'application/pdf' // image/png, etc (content_type)

  cy.get(selector).then(subject => cy.window().then(win => cy
    .fixture(fixturePath, 'base64')
    .then(Cypress.Blob.base64StringToBlob)
    .then((blob) => {
      const el = subject[0]
      const testFile = new win.File([blob], name, { type })
      const dataTransfer = new win.DataTransfer()
      dataTransfer.items.add(testFile)
      el.files = dataTransfer.files
      cy.wrap(subject).trigger('change', { force: true })
    })))

Thanks to all on this thread keeping it up to date with working solutions. I managed to put together my own, based on a few of these, which I'll add, as maybe part of it can help someone else.

    const selector = ".some-file-input";
    const fixturePath = "audio/test.mp3"; // relative to the cypress/fixtures directory
    const mimeType = 'audio/mpeg';
    const filename = "test.mp3";

    cy.get(selector)
      .then((subject) => {
        cy.fixture(fixturePath, 'base64').then((base64String) => {
          Cypress.Blob.base64StringToBlob(base64String, mimeType)
            .then(function (blob) {
              var testfile = new File([blob], filename, { type: mimeType });
              var dataTransfer = new DataTransfer();
              var fileInput = subject[0];

              dataTransfer.items.add(testfile);
              // This triggers the @change event on the input.
              fileInput.files = dataTransfer.files;

              cy.wrap(subject).trigger('change', { force: true });
            });
        });
      });

Fixtures are relative to the cypress/fixtures directory. It's not finding your fixture file, so you've likely put in the wrong path. It should be located at one of the paths listed.

The upload - datatransfer does not work for image. A button when pressed , should allow choosing image. No method works in cypress. Could anyone help ?

This works for me. just call this method and replace the fileUrl with your file path and element selector

uploadFile(){
   let fileUrl = "../fixtures/image37.png";
    let selector = '#imageLoader';
    return cy.get(selector).then(subject => {
      return cy
        .fixture(fileUrl, 'base64')
        .then(Cypress.Blob.base64StringToBlob)
        .then(blob => {
          return cy.window().then(win => {
            const el = subject[0]
            const nameSegments = fileUrl.split('/')
            const name = nameSegments[nameSegments.length - 1]
            const testFile = new win.File([blob], name, {type:'image/png'})
            const dataTransfer = new DataTransfer()
            dataTransfer.items.add(testFile)
            el.files = dataTransfer.files
            return subject
          })
   })
  })
}

Hi everyone,

I tried to use this fix for using my file with Papaparse but I don't access to new win.File() constructor in my code even if I use the cy.window(). I use Cypress with TypeScript and I don't know if I forgot to enable something in configuration. I tried to move this code to command file location but I have the same issue. The win.File is not accessible.

I also tried to use the upload-file extension but I didn't find a way to import/load correctly without break my own command.

My implementation of drop file well worked before I introduce Papaparse.
Is someone can help me to access to win.File constructor or explain me what is the configuration that works with this patch?

Thanks.

@gpascual4qima Where are you defining the uploadFile method? What file?

@gpascual4qima Where are you defining the uploadFile method? What file?

I defined uploadFile method in a file in my source folder where I put all my cypress tests. The file that I mentioned is the file loaded by cy.fixture. This file is perfectly loaded and the it is not correctly recognized Papaparse as a type File.

i took this method uploadFile it worked, but after download new version cypress 3.3.2 cypress began to fall on this step.

Cypress is closing when mimeType = 'image/png'. When mimeType = 'video/mp4' all is ok.

it is my command:

Cypress.Commands.add('uploadFile', (element, eq, fixturePath, mimeType, filename) => {
    cy.get(element).eq(eq)
        .then((subject) => {
            cy.fixture(fixturePath, 'base64').then((base64String) => {
            Cypress.Blob.base64StringToBlob(base64String, mimeType)
                .then(function (blob) {
                    var testfile = new File([blob], filename, { type: mimeType })
                    var dataTransfer = new DataTransfer()
                    var fileInput = subject[0]

                    dataTransfer.items.add(testfile)
                    fileInput.files = dataTransfer.files

                    cy.wrap(subject).trigger('change', { force: true })
                })
            })
        })
})

Help me please

hello, can i get answer for ask ?

Сan anybody explain how to upload a video? please)

I don't have specific steps for a video, but I did find that with audio files, if you are not running HTTPS, the tests will fail. Is the site on HTTPS?

@jaypan Hello. Its true. The site on HTTP and Cypress is breaking on this step. Too bad i must install cypress v. 3.3.0 for test this component

Hi Jen

This isn't actually a Cypress limitation - it's a Chrome limitation. Try running your code directly in Chrome - it probably will not work if it is not on HTTPS. If it works on HTTP in Chrome, then I'm pointing you in the wrong direction.

That's very strange how going back two patch versions will change anything.. @jenchek

I started experiencing flakiness with the uploadFile command that's been posted in these threads. I added an explicit wait to then end up of the command and my tests seem to reliably pass now. This is my full command:

Cypress.Commands.add('uploadFile', (fixtureFileName, inputSelector, mimeType = '') => {
    return cy.get(inputSelector).then(subject => {
        return cy.fixture(fixtureFileName, 'base64')
            .then(Cypress.Blob.base64StringToBlob)
            .then(blob => {
                const el = subject[0];
                const nameSegments = fixtureFileName.split('/');
                const name = nameSegments[nameSegments.length - 1];
                const testFile = new File([blob], name, { type: mimeType });
                const dataTransfer = new DataTransfer();
                dataTransfer.items.add(testFile);
                el.files = dataTransfer.files;
                el.dispatchEvent(new Event('change'));
                return subject;
            })
            .then(_ => cy.wait(1000));
    });
});

@kylethebaker @jennifer-shehane
when i used code of @kylethebaker cypress broke down after video upload like after my code
it is error in console:

TypeError: Cannot read property 'length' of null
    at Writable.onWrite [as _write] (/Users/jake/Library/Caches/Cypress/3.4.1/Cypress.app/Contents/Resources/app/packages/server/lib/util/stream_buffer.js:13:46)

Has anyone here been able to get @kylethebaker's approach to work with AJAX?

We were able to get around this problem using cypress-file-upload. Here is a piece of code that will help if you are need to upload something different than images:

cy.fixture(fileName, 'binary')
        .then(Cypress.Blob.binaryStringToBlob)
        .then((fileContent) =>
                cy.get('.file-input').upload({
                    fileContent,
                    fileName,
                    mimeType: getMimeType(fileName),
                    encoding: 'utf8'
                })
        );

We use this variant of command in order to simplify intest code:

Cypress.Commands.add('uploadFile', { prevSubject: true }, (subject, fileName) => {
      cy.fixture(fileName).then((content) => {
          const el = subject[0]
          const testFile = new File([content], fileName)
          const dataTransfer = new DataTransfer()

          dataTransfer.items.add(testFile)
          el.files = dataTransfer.files
          cy.wrap(subject).trigger('change', { force: true })
      })
})

It gives us a chance to use it oneline in tests

cy.get('input').uploadFile('usersUploadCsv.csv')

I share the way I distributed it to the computer, you must configure the command.js file with the following code:

Cypress.Commands.add('upload_file', (fileName, fileType = ' ', selector) => {
    if (fileType = "pdf") {
        fileType = 'application/pdf';
    } else if (fileType = "rar") {
        fileType = 'application/x-rar-compressed';
    } else if (fileType = "xls") {
        fileType = 'application / vnd.ms - excel';
    } else if (fileType = "doc") {
        fileType = 'application / msword'
    } else if (fileType = "docx") {
        fileType = 'application / msword'
    }
    return cy.get(selector).then(subject => {
        cy.fixture(fileName, 'base64')
            .then(Cypress.Blob.base64StringToBlob)
            .then(blob => {
                const el = subject[0];
                const testFile = new File([blob], fileName, {
                    type: fileType
                });
                const dataTransfer = new DataTransfer();
                dataTransfer.items.add(testFile);
                el.files = dataTransfer.files;
            });
    });

Thanks @JesusLopezCh!!

this solved all my issues:

https://www.npmjs.com/package/cypress-file-upload

I also had a need to modify the data before uploading it so that I could have dynamic data being uploaded.
Here is what I ended up with (in case anyone else has a need for what I needed):

cy.fixture(fileName).then((contents) => { // The file is a csv that I made as a template for my dynamic data
    let fileContent = [];
    contents.split('\n').forEach((row, index) => {
        fileContent[index] = row
            .replace(/EMAIL_TO_REPLACE/g, getEmail(teamName + 'user' + index))
            .replace(/DOB_TO_REPLACE/g, Cypress.env('dateOfBirth'));
    });
    fileContent = fileContent.join('\n');
    cy.getCy('census').upload({ fileContent, fileName, mimeType: 'text/plain' });
});

In my case I was uploading users via a csv. Hope this helps someone.

I started experiencing flakiness with the uploadFile command that's been posted in these threads. I added an explicit wait to then end up of the command and my tests seem to reliably pass now. This is my full command:

Cypress.Commands.add('uploadFile', (fixtureFileName, inputSelector, mimeType = '') => {
    return cy.get(inputSelector).then(subject => {
        return cy.fixture(fixtureFileName, 'base64')
            .then(Cypress.Blob.base64StringToBlob)
            .then(blob => {
                const el = subject[0];
                const nameSegments = fixtureFileName.split('/');
                const name = nameSegments[nameSegments.length - 1];
                const testFile = new File([blob], name, { type: mimeType });
                const dataTransfer = new DataTransfer();
                dataTransfer.items.add(testFile);
                el.files = dataTransfer.files;
                el.dispatchEvent(new Event('change'));
                return subject;
            })
            .then(_ => cy.wait(1000));
    });
});

I am using this command to upload a video

  const fileName = cy.fixture('video-test.mp4', 'binary')
  const inputSelector = '#input-file-id'
  const mimeType = 'video/mp4'
  cy.uploadFile(fileName,inputSelector,mimeType)

My test keeps executing and never enters the function or abort. Am I missing sth?

We were able to get around this problem using cypress-file-upload. Here is a piece of code that will help if you are need to upload something different than images:

cy.fixture(fileName, 'binary')
        .then(Cypress.Blob.binaryStringToBlob)
        .then((fileContent) =>
                cy.get('.file-input').upload({
                    fileContent,
                    fileName,
                    mimeType: getMimeType(fileName),
                    encoding: 'utf8'
                })
        );

thank you @jdcl32 it works for me also using this workaround !!!

I needed it for an "oldschool traditional form." cypress-file-upload indeed works, but I wanted to invetigate the issue. So, here's my test repo. Generally, I recommend using cypress-file-upload, but here's my solution:

cypress/support/commands.js:

Cypress.Commands.add('attachFile', {
    prevSubject: 'element',
}, ($input, fname, type) => {
    cy.fixture(fname).then(content => {
        return Cypress.Blob.base64StringToBlob(content).then(blob => {
            const file = new File([blob], fname, {type});
            const dt = new DataTransfer;
            dt.items.add(file);
            $input[0].files = dt.files;
        });
    });
});

cypress/integration/1.spec.js:

cy.get('input[type="file"]')
    .attachFile('upload.png', 'image/png');
cy.get('input[type="submit"]').click()

As for the cypress-form-data-with-file-upload package, it might potentially negatively affect the other tests. Since it writes to the iframe where the app (AUT) runs. That discards event handlers that cypress attaches to it, and at least it stops adding "FORM SUB", "PAGE LOAD" log entries for all the following tests that are going to be run in the runner window (until you reopen it).

P.S. I wonder if it makes sense for @pihish to add the link to cypress-file-upload to the original post...

with the following code i am able to upload the video but it is not displayed or playing the video on my application, it just shows black screen when i click on it.

Cypress.Commands.add('VideoFile', () => {
        const fileName = 'LocalVideo.mp4'
        cy.fixture(fileName).then(fileContent => {
        cy.get('input[name="uploadfilevideo"]').upload({ fileContent, fileName, mimeType: 'video/mp4', encoding: 'utf8' });
        })
    })

i am using cypress-file-upload lib.
cypress version :- 3.7.0
please help me out .

@suryamowku Do you really need to specify encoding?

@x-yuri because i am getting this error with out specifying the encoding value.
videoError

@suryamowku Give me a simple way to reproduce it. A repository I can clone, npm i, cypress open and see the result.

@x-yuri i may not be able to share the rapo , sorry for that.
without specifying encoding value the image is uploaded and it is reflecting on my mobile device as well, when it comes to video it is uploaded,but not reflected on the mobile device when i am specifying encoding value, if i don't specify the encoding value i am getting this error.
error

@suryamowku Did I say "share your repo"? I need a simple way to reproduce it. Ideally a repository where there's as little code as possible, and it still reproduces.

https://stackoverflow.com/help/minimal-reproducible-example
http://sscce.org/

I used cypress-file-upload to test upload video, and it worked for me! Here's my code snippet, and hopefully it will help anyone who has issue testing uploading video.

cy.fixture('bear.mp4')
            .then(fileContent => {
                cy.get('#upload-video').upload({
                    fileContent,
                    fileName: 'bear.mp4',
                    mimeType: 'video/mp4',
                    encoding: 'utf8'
                })
            })

Hello All,

My Application using devextreme controls as shown below

image

image

I tried cypress-file-upload , and it uploaded file

Hi all,
please help me, I am trying to upload file. But in cypress window shown only file name. But file is not shown and I can't to check file presence. Please say me, what I should do, to see my file.

I am using this function

Cypress.Commands.add('uploadFile', (fileName, fileType = ' ', selector) => {
    return cy.get(selector).then(subject => {
        cy.fixture(fileName, 'base64')
            .then(Cypress.Blob.base64StringToBlob)
            .then(blob => {
                const el = subject[0];
                const testFile = new File([blob], fileName, {
                    type: fileType
                });
                const dataTransfer = new DataTransfer();
                dataTransfer.items.add(testFile);
                el.files = dataTransfer.files;
            });
    });
});

And in my test file

 const audioPosterFileName = 'JPG.jpg';
  const audioPosterFileType = 'aplication/image';
  const audioPosterFileInput = '.uploader-input';
  cy.uploadFile(audioPosterFileName, audioPosterFileType, audioPosterFileInput);

In image my result

Screenshot from 2020-01-13 15-19-43

@spsingham1902 can you share your code pls.

@narinepoghosyan

cy.fixture('bear.mp4', 'binary')
  .then(Cypress.Blob.binaryStringToBlob)
  .then(fileContent => {
    cy.get('#upload-video').upload({
      fileContent,
      fileName: 'bear.mp4',
      mimeType: 'video/mp4',
      encoding: 'utf8'
    })
})

this worked for me to upload video file. I used the cypress upload plugin instead of custom command. Maybe you can give it a try.

@CuriousSoul230 Thanks a lot for Your answer. But I have a problem yet. In request shown file uploaded, but in web page is not shown. What can I do to see file in web page too. If any option I can use. I am using Chrome 79
Screenshot from 2020-01-14 21-56-02

I've tried solutions above and using the cypress-file-upload plugin...

I am getting an error when I submit form:

InvalidStateError: Failed to set the 'value' property on 'HTMLInputElement': This input element accepts a filename, which may only be programmatically set to the empty string.

Form:

<form action="/?action=foo.save" method="post" enctype="multipart/form-data">
    <label>Attachment Name
      <input type="text" name="attachmentName" id="attachmentName" data-testid="attachmentName" value="" maxlength="255" required="true"/>
    </label>

    <label>Attachment File
      <input type="file" name="uploadfile" id="uploadFile" data-testid="uploadFile" value="" maxlength="255" required="true" />
    </label>

    <button type="submit" name="save" id="save" data-testid="btn_save_ext_attachment" >Save</button>
</form>

Spec:

cy.fixture( po.attachmentfile ).then(fileContent => {
  cy.gettestid('uploadFile').upload({ 
    fileContent
    , fileName: 'test_file.txt'
    , mimeType: 'text/plain' 
  });
});

cy.gettestid('btn_save_ext_attachment').click()

This works! The file is uploaded... But my test fails with this message in Test Runner:

InvalidStateError: Failed to set the 'value' property on 'HTMLInputElement': 
This input element accepts a filename, which may only be programmatically set to the empty string.

Versions

  • Cypress 3.8.2
  • Chrome 79

@brian-mann

Because the implementation is different per application there is no way for us to suggest how to do it. Once we implement native API's we will be able to show a recipe handling this.

Considering the minimal use case:

<input type="file" />

I understand the cypress community recommendation is to use cypress-file-upload, it would work for most of the cases, and it also have good troubleshooting examples in it's readme.

There is also https://github.com/cypress-io/cypress-example-recipes/tree/master/examples/file-upload-react which demostrate it.

@jennifer-shehane I'd suggest closing this issue, since it is hard to understand what is the best answer between so many others.

Thank you all for helping me today since 2016.

To follow up on this - I did find a work around using this old solution from @bahmutov : https://github.com/bahmutov/cypress-form-data-with-file-upload

That uploads file w/no weird error.

It works but I'd still be curious why the recommended cypress-file-upload throws that weird error (while correctly uploading the file)

I'd still be curious why the recommended cypress-file-upload throws that weird error (while correctly uploading the file)

@jimpriest I'm curious too. If you create a test repository that I can git clone ... && npm i && npx cypress open and see the issue, I'll take a look.

Also, do note that cypress-form-data-with-file-upload discards event handlers that cypress attaches. At least, after invoking attachFiles() cypress will stop adding FORM SUB and PAGE LOAD log entries. Until you close the runner window.

@x-yuri Ugh. So I setup a simple PHP form on my personal site and wrote a bare bones test and using cypress-file-upload everything worked fine :\

So it must be something with my server/environment that is different. Next I'll setup a similar simple example on this server and see if I can recreate.

Could make it work like this:

Cypress.Commands.add('upload', { prevSubject: 'element' },
  (subject, fileContents, contentType, fileName) => {
    Cypress.Blob.base64StringToBlob(fileContents, contentType).then((blob) => {
      const testFile = new File([blob], fileName, { type: contentType })
      const dataTransfer = new DataTransfer()
      dataTransfer.items.add(testFile)
      cy.wrap(subject).trigger('drop', { dataTransfer })
    })
  }
)

Usage example:

cy.fixture('sample.pdf', 'base64').then((content) => {
  cy.get('[data-testid=dropzone]').upload(
    content,
    'application/pdf',
    'file.pdf'
  )
})

Iterating off of @x-yuri's comment, this is what worked for me. I needed to trigger an input element to upload a file, and couldn't use a fixture since it extracted the file _contents_, rather than the multipart file itself.

// Custom command
Cypress.Commands.add(
  "attach_file",
  {
    prevSubject: "element"
  },
  ($input, fileName) => {
    const file = new File([], fileName);
    const dt = new DataTransfer();
    dt.items.add(file);
    $input[0].files = dt.files;
    return $input;
  }
);

// Implementation in spec file
    cy.get("input.resume-upload-input") // Your target element here
      .attach_file("resume.pdf") // Your file name here
      .trigger("change", { force: true });

We were able to get around this problem using cypress-file-upload. Here is a piece of code that will help if you are need to upload something different than images:

cy.fixture(fileName, 'binary')
        .then(Cypress.Blob.binaryStringToBlob)
        .then((fileContent) =>
                cy.get('.file-input').upload({
                    fileContent,
                    fileName,
                    mimeType: getMimeType(fileName),
                    encoding: 'utf8'
                })
        );

thank you @jdcl32 it works for me also using this workaround !!!

Thank you, worked for me for PDF upload. instead of 'getMimeType' had 'application/pdf'.

We were able to get around this problem using cypress-file-upload. Here is a piece of code that will help if you are need to upload something different than images:

cy.fixture(fileName, 'binary')
        .then(Cypress.Blob.binaryStringToBlob)
        .then((fileContent) =>
                cy.get('.file-input').upload({
                    fileContent,
                    fileName,
                    mimeType: getMimeType(fileName),
                    encoding: 'utf8'
                })
        );

This was a huge help for us, thanks!

I tried the above solutions, however I am getting: TypeError: cy.get(...).upload is not a function with the latest version of cypress-file-upload. Any suggestions?

Read the documentation. The upload() command was renamed to attachFile().

Without using plugins and reusing code from other responses here's a fully working solution

In the test:

    cy.get('[name="topic[photo]"]')
      .attach_file('files/forum-attachment.jpg', 'image/jpg')
      .trigger('change', { force: true })

as custom cypress command:

Cypress.Commands.add(
  'attach_file',
  {
    prevSubject: 'element',
  },
  (input, fileName, fileType) => {
    cy.fixture(fileName)
      .then(content => Cypress.Blob.base64StringToBlob(content, fileType))
      .then(blob => {
        const testFile = new File([blob], fileName)
        const dataTransfer = new DataTransfer()

        dataTransfer.items.add(testFile)
        input[0].files = dataTransfer.files
        return input
      })
  }
)

@guillem-catala Is it any better than cypress-file-upload?

@x-yuri probably not, but in my use case I cannot add another dependency.

@guillem-catala It works just fine! A million thanks, you made my day <3

For anyone who runs into the same problem: I'm making assertions against the file's type within a callback function, therefore the @guillem-catala answer almost works, but fails my validation test because it sets the file type as an empty string.

Updated code below to include file type to the test file...

Cypress.Commands.add(
  'attach_file',
  {
    prevSubject: 'element',
  },
  (input, fileName, fileType) => {
    cy.fixture(fileName)
      .then(content => Cypress.Blob.base64StringToBlob(content, fileType))
      .then(blob => {
        const testFile = new File([blob], fileName, { type: fileType }) // append fileType to new file
        const dataTransfer = new DataTransfer()

        dataTransfer.items.add(testFile)
        input[0].files = dataTransfer.files
        return input
      })
  }
)

@mattcarlotta Didn't cypress-file-upload work for you?

Thanks to @guillem-catala and @mattcarlotta for providing a solution.

I had to change cy.fixture(fileName) into cy.fixture(fileName, 'base64) in order to get .xlsx files to upload. This is the only solution I have found that is working. Tried a few others mentioned here, and 'cypress-file-upload' package that didn't work at all.

In the test:

cy.get('[name="topic[photo]"]')
  .attach_file('files/forum-attachment.jpg', 'image/jpg')
  .trigger('change', { force: true });

as custom cypress command in commands.js:

Cypress.Commands.add(
  'attach_file',
  {
    prevSubject: 'element',
  },
  (input, fileName, fileType) => {
    cy.fixture(fileName, 'base64')
    .then(content => Cypress.Blob.base64StringToBlob(content, fileType))
    .then(blob => {
      const testFile = new File([blob], fileName);
      const dataTransfer = new DataTransfer();

      dataTransfer.items.add(testFile);
      input[0].files = dataTransfer.files;
      return input;
    })
  }
)

@epaaj I have an idea why [email protected] didn't work for you with *.xlsx files. The thing is that cy.fixture() handles images specially. It reads them as base64. And so does cypress-file-upload. It falls back to base64 if encoding wasn't passed.

But in case of Excel files, cy.fixture() defaults to utf8, which is not an option. You can't read a binary as utf8 and expect a result. So, first you've got to pass the desired encoding to cy.fixture(). Next, cypress-file-upload has no defaults for every possible extension, and even if it had, the encodings must match (passed to cy.fixture() and to cypress-file-upload).

it('works', () => {
    const fileName = 'spreadsheet.xlsx';
    const encoding = 'base64';
    cy.visit('/');
    cy.fixture(fileName, encoding).then(fileContent => {
        cy.get('input[type="file"]')
            .upload({fileContent, fileName, mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', encoding});
    });
    cy.get('input[type="submit"]').click();
});

Can you please check? I think it'd be of great help to people here.

@guillem-catala

Is it also possible to upload an array of items? Two or more?

uploadAllFiles(nameJPG, namePNG, namePSD, nameTIF, nameMP4, nameDOC) {
            cy.fixture('tif.tif').then(tif => {
            cy.fixture('mp4.mp4').then(mp4 => {
            cy.fixture('png.png').then(png => {
            cy.fixture('jpg.jpg').then(jpg => {
            cy.fixture('docx.docx', 'base64').then(doc => {
            cy.fixture('psd.psd', 'base64').then(psd =>  {
                const files = [
                    { fileName: `${namePNG}.png`, fileContent: png, mimeType: 'image/png' },
                    { fileName: `${nameJPG}.jpg`, fileContent: jpg, mimeType: 'image/jpg' },
                    { fileName: `${nameTIF}.tif`, fileContent: tif, mimeType: 'image/tiff' },
                    { fileName: `${nameMP4}.mp4`, fileContent: mp4, encoding: 'utf8', mimeType: 'video/mp4' },
                    { fileName: `${namePSD}.psd`, fileContent: psd, encoding: 'base64', mimeType: "image/vnd.adobe.photoshop"},
                    { fileName: `${nameDOC}.docx`, fileContent: doc, encoding: 'base64', mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'}
                ];
            cy.get('input[type="file"]').upload(files);
            cy.get('.notification h1', {timeout: 60000}).should('be.visible');
               })
             });
            });
          });
        });
      });

Any news from this issue. File upload has not working after chrome 70 + versions

Any news from this issue. File upload has not working after chrome 70 + versions

Just tried the solution @guillem-catala provided. Works flawlessly on images (tested png and jpg)

Any help please. I have to upload a image on clicking a button

Until Cypress has a good implementation about it, this library works for me.

cypress-file-upload

Hello @guillem-catala @epaaj ,

With the changes on blob in cypress 5 https://docs.cypress.io/guides/references/changelog.html#5-0-0 , will there any code changes on your solution? I'm a bit worried to update to cypress 5 if my test will break? It's working well on Cypress 4.12.1 by the way.

Thank you,

Nevermind, here's what works for me on Cypress 5

Cypress.Commands.add(
  "attach_img",
  {
    prevSubject: "element",
  },
  (input, fileName, fileType) => {
    cy.fixture(fileName).then((content) => {
      const blob = Cypress.Blob.base64StringToBlob(content, fileType);
      const testFile = new File([blob], fileName);
      const dataTransfer = new DataTransfer();
      dataTransfer.items.add(testFile);
      input[0].files = dataTransfer.files;
      return input;
    });
  }
);

Apology, I'm new at this. Don't even know to make the code shows properly with line break here.

In 5.0.0, Cypress updated the Blob utility from 1.3.3 to 2.0.2, which itself had a broken change underneath.

The return type of the Cypress.Blob methods arrayBufferToBlob, base64StringToBlob, binaryStringToBlob, and dataURLToBlob have changed from Promise to Blob.

You will need to be update your code which uses Cypress.Blob to reflect this change outlined in our migration guide: https://on.cypress.io/migration-guide#Return-type-of-Cypress-Blob-changed

~The cypress-file-upload plugin is currently not working in 5.0 as outlined here: https://github.com/abramenal/cypress-file-upload/issues/214 They have a PR open to fix the issue: https://github.com/abramenal/cypress-file-upload/pull/215, so you may want to hold off on updating to 5.0.0 if you use that plugin until that fix is merged.~

The cypress-file-upload plugin was fixed in its latest release: https://github.com/abramenal/cypress-file-upload/releases/tag/v4.1.0

I'm sorry if it's a stupid question... I didn't get what I should set for 'input' parameter, if someone can help me!

Thanks!

Nevermind, here's what works for me on Cypress 5

Cypress.Commands.add(
  "attach_img",
  {
    prevSubject: "element",
  },
  (input, fileName, fileType) => {
    cy.fixture(fileName).then((content) => {
      const blob = Cypress.Blob.base64StringToBlob(content, fileType);
      const testFile = new File([blob], fileName);
      const dataTransfer = new DataTransfer();
      dataTransfer.items.add(testFile);
      input[0].files = dataTransfer.files;
      return input;
    });
  }
);

Apology, I'm new at this. Don't even know to make the code shows properly with line break here.

The cypress-file-upload plugin has fixed the compatibility issue with Cypress 5.0 in its latest release: https://github.com/abramenal/cypress-file-upload/releases/tag/v4.1.0

I used a similar workaround, and I found that when reading in the file contents they were coming in as string data instead of binary data. To fix this, I found a function online (can't find the original source at the moment) and modified it…

Here it is (Typescript):

/**
 * Decode base64 data to a binary File object
 * @param  b64Data     The base-64 encoded data
 * @param  filename    The filename for the file
 * @param  contentType The content type for the fle
 * @param  sliceSize   The size for the byte arrays
 * @return             The resulting file object
 */
function b64toFile(b64Data, filename: string, contentType: string = '', sliceSize: number = 512): File {
    let byteCharacters = atob(b64Data);
    let byteArrays = [];

    for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
        let slice = byteCharacters.slice(offset, offset + sliceSize);

        let byteNumbers = new Array(slice.length);
        for (let i = 0; i < slice.length; i++) {
            byteNumbers[i] = slice.charCodeAt(i);
        }

        let byteArray = new Uint8Array(byteNumbers);

        byteArrays.push(byteArray);
    }

    let file = <any>(new Blob(byteArrays, {
        type: contentType
    }));

    // give the blob object the missing data members for a file
    file.lastModifiedDate = new Date();
    file.name = filename;

    return <File>file;
}

Then do something like this:

        var fileContents;
        cy.fixture('file.pdf', 'base64').then(file => {
            fileContents = b64toFile(file, 'file.pdf', 'application/pdf');
        });

        cy.get('#my-file-input').then($fileInput => {
            const fileData = new ClipboardEvent('').clipboardData || new DataTransfer();
            fileData.items.add(new File([fileContents], 'resume.pdf', {type: 'application/pdf'}));
            (<any>$fileInput.get(0)).files = fileData.files;
        });

can you please let me know this code in javascript plesae

I used a similar workaround, and I found that when reading in the file contents they were coming in as string data instead of binary data. To fix this, I found a function online (can't find the original source at the moment) and modified it…
Here it is (Typescript):

/**
 * Decode base64 data to a binary File object
 * @param  b64Data     The base-64 encoded data
 * @param  filename    The filename for the file
 * @param  contentType The content type for the fle
 * @param  sliceSize   The size for the byte arrays
 * @return             The resulting file object
 */
function b64toFile(b64Data, filename: string, contentType: string = '', sliceSize: number = 512): File {
    let byteCharacters = atob(b64Data);
    let byteArrays = [];

    for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
        let slice = byteCharacters.slice(offset, offset + sliceSize);

        let byteNumbers = new Array(slice.length);
        for (let i = 0; i < slice.length; i++) {
            byteNumbers[i] = slice.charCodeAt(i);
        }

        let byteArray = new Uint8Array(byteNumbers);

        byteArrays.push(byteArray);
    }

    let file = <any>(new Blob(byteArrays, {
        type: contentType
    }));

    // give the blob object the missing data members for a file
    file.lastModifiedDate = new Date();
    file.name = filename;

    return <File>file;
}

Then do something like this:

        var fileContents;
        cy.fixture('file.pdf', 'base64').then(file => {
            fileContents = b64toFile(file, 'file.pdf', 'application/pdf');
        });

        cy.get('#my-file-input').then($fileInput => {
            const fileData = new ClipboardEvent('').clipboardData || new DataTransfer();
            fileData.items.add(new File([fileContents], 'resume.pdf', {type: 'application/pdf'}));
            (<any>$fileInput.get(0)).files = fileData.files;
        });

can you please let me know this code in javascript plesae

I think you can use the plugin now since they've fixed them.

Reinventing a wheel we all came to love
Like warm tube sound
But the tide will go out without fail :)

I think you can use the plugin now since they've fixed them.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

carloscheddar picture carloscheddar  ·  3Comments

weskor picture weskor  ·  3Comments

dkreft picture dkreft  ·  3Comments

brian-mann picture brian-mann  ·  3Comments

tahayk picture tahayk  ·  3Comments