简体   繁体   English

如何使用赛普拉斯测试文件输入?

[英]How to test file inputs with Cypress?

How can I write an e2e test of flow that requires interaction with the file Input DOM element?如何编写需要与文件输入 DOM 元素交互的 e2e 流测试?

If it's a text input I can interact with it (check value, set value) etc as its a DOM component.如果它是一个文本输入,我可以与它交互(检查值、设置值)等作为它的 DOM 组件。 But If I have a File Input element, I am guessing that the interaction is limited till I can open the dialog to select a File.但是如果我有一个文件输入元素,我猜测交互是有限的,直到我可以打开 select 一个文件的对话框。 I can't move forward and select the file I want to upload as the dialog would be native and not some browser element.我无法继续前进,select 我要上传的文件是本机对话框,而不是某些浏览器元素。

So how would I test that a user can correctly upload a file from my site?那么我将如何测试用户是否可以从我的站点正确上传文件? I am using Cypress to write my e2e tests.我正在使用Cypress编写我的 e2e 测试。

it('Testing picture uploading', () => {
    cy.fixture('testPicture.png').then(fileContent => {
        cy.get('input[type="file"]').attachFile({
            fileContent: fileContent.toString(),
            fileName: 'testPicture.png',
            mimeType: 'image/png'
        });
    });
});

Use cypress file upload package: https://www.npmjs.com/package/cypress-file-upload使用cypress文件上传包: https : //www.npmjs.com/package/cypress-file-upload

Note: testPicture.png must be in fixture folder of cypress注意:testPicture.png 必须在cypress的fixture文件夹中

With this approach/hack you can actually make it: https://github.com/javieraviles/cypress-upload-file-post-form通过这种方法/黑客,您实际上可以做到: https : //github.com/javieraviles/cypress-upload-file-post-form

It is based on different answers from the aformentioned thread https://github.com/cypress-io/cypress/issues/170它基于上述线程https://github.com/cypress-io/cypress/issues/170 的不同答案

First scenario (upload_file_to_form_spec.js):第一个场景(upload_file_to_form_spec.js):

I want to test a UI where a file has to be selected/uploaded before submitting the form. 我想测试一个用户界面,在提交表单之前必须选择/上传文件。 Include the following code in your "commands.js" file within the cypress support folder, so the command cy.upload_file() can be used from any test: 将以下代码包含在 cypress 支持文件夹内的“commands.js”文件中,以便可以在任何测试中使用命令 cy.upload_file():
Cypress.Commands.add('upload_file', (fileName, fileType, selector) => {
    cy.get(selector).then(subject => {
        cy.fixture(fileName, 'hex').then((fileHex) => {

            const fileBytes = hexStringToByte(fileHex);
            const testFile = new File([fileBytes], fileName, {
                type: fileType
            });
            const dataTransfer = new DataTransfer()
            const el = subject[0]

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

// UTILS
function hexStringToByte(str) {
    if (!str) {
        return new Uint8Array();
    }

    var a = [];
    for (var i = 0, len = str.length; i < len; i += 2) {
        a.push(parseInt(str.substr(i, 2), 16));
    }

    return new Uint8Array(a);
}

Then, in case you want to upload an excel file, fill in other inputs and submit the form, the test would be something like this:然后,如果你想上传一个 excel 文件,填写其他输入并提交表单,测试将是这样的:

describe('Testing the excel form', function () {
    it ('Uploading the right file imports data from the excel successfully', function() {

    const testUrl = 'http://localhost:3000/excel_form';
    const fileName = 'your_file_name.xlsx';
    const fileType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
    const fileInput = 'input[type=file]';

    cy.visit(testUrl);
    cy.upload_file(fileName, fileType, fileInput);
    cy.get('#other_form_input2').type('input_content2');
    .
    .
    .
    cy.get('button').contains('Submit').click();

    cy.get('.result-dialog').should('contain', 'X elements from the excel where successfully imported');
})

}) })

For me the easier way to do this is using this cypress file upload package对我来说,更简单的方法是使用这个cypress 文件上传包

Install it:安装它:

npm install --save-dev cypress-file-upload

Then add this line to your project's cypress/support/commands.js :然后将此行添加到您项目的cypress/support/commands.js

import 'cypress-file-upload';

Now you can do:现在你可以这样做:

const fixtureFile = 'photo.png';
cy.get('[data-cy="file-input"]').attachFile(fixtureFile);

photo.png must be in cypress/fixtures/ photo.png必须在cypress/fixtures/

For more examples checkout the Usage section on README of the package.有关更多示例,请查看软件包 README 上的使用部分。

Testing File Input elements is not yet supported in Cypress.赛普拉斯尚不支持测试文件输入元素。 The only way to test File Inputs is to:测试文件输入的唯一方法是:

  1. Issue native events (which Cypress has on their Roadmap ).发布本地事件(赛普拉斯在他们的路线图上有)。
  2. Understand how your application handles file uploads with File API and then stub it out.了解您的应用程序如何使用 File API 处理文件上传,然后将其存根。 It's possible but not generic enough to give any specific advice on.这是可能的,但不够通用,无法提供任何具体建议。

See this open issue for more detail .有关更多详细信息,请参阅此未解决的问题

In my case I had client & server side file validation to check if the file is JPEG or PDF.就我而言,我进行了客户端和服务器端文件验证以检查文件是 JPEG 还是 PDF。 So I had to create a upload command which would read the file in binary from Fixtures and prepare a blob with the file extension.所以我必须创建一个上传命令,它会从 Fixtures 读取二进制文件并准备一个带有文件扩展名的 blob。

Cypress.Commands.add('uploadFile', { prevSubject: true }, (subject, fileName, fileType = '') => {
  cy.fixture(fileName,'binary').then(content => {
    return Cypress.Blob.binaryStringToBlob(content, fileType).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;
      cy.wrap(subject).trigger('change', { force: true });
    });
  });
});

then use it as然后将其用作

cy.get('input[type=file]').uploadFile('smiling_pic.jpg', 'image/jpeg');

smiling_pic.jpg will be in fixtures folder smile_pic.jpg 将在 fixtures 文件夹中

The following function works for me,以下功能对我有用,

cy.getTestElement('testUploadFront').should('exist');

const fixturePath = 'test.png';
const mimeType = 'application/png';
const filename = 'test.png';

cy.getTestElement('testUploadFrontID')
  .get('input[type=file')
  .eq(0)
  .then(subject => {
    cy.fixture(fixturePath, 'base64').then(front => {
      Cypress.Blob.base64StringToBlob(front, 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 });
      });
    });
  });

// Cypress.Commands.add(`getTestElement`, selector =>
//   cy.get(`[data-testid="${selector}"]`)
// );

Also based on previously mentioned github issue , so big thanks to the folks there.同样基于前面提到的github 问题,非常感谢那里的人。

The upvoted answer worked initially for me, but I ran into string decoding issues trying to handle JSON files.赞成的答案最初对我有用,但我在尝试处理 JSON 文件时遇到了字符串解码问题。 It also felt like extra work having to deal with hex.这也感觉像是必须处理十六进制的额外工作。

The code below handles JSON files slightly differently to prevent encode/decode issues, and uses Cypress's built in Cypress.Blob.base64StringToBlob :下面的代码以稍微不同的方式处理 JSON 文件以防止编码/解码问题,并使用Cypress.Blob.base64StringToBlob内置的Cypress.Blob.base64StringToBlob

/**
 * Converts Cypress fixtures, including JSON, to a 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 (prevents issues with invalid string decoding).
 * @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'
    ? 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 el = subject[0]
        const nameSegments = fileUrl.split('/')
        const name = nameSegments[nameSegments.length - 1]
        const testFile = new win.File([blob], name, { type })
        const dataTransfer = new win.DataTransfer()
        dataTransfer.items.add(testFile)
        el.files = dataTransfer.files
        return subject
      })
    })
  })
})

in your commands.ts file within your test folder add:在测试文件夹中的 commands.ts 文件中添加:

//this is for typescript intellisense to recognize new command
declare namespace Cypress {
  interface Chainable<Subject> {
   attach_file(value: string, fileType: string): Chainable<Subject>;
  }
}

//new 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;
      });
  },
);

Usage:用法:

cy.get('[data-cy=upload_button_input]')
      .attach_file('./food.jpg', 'image/jpg')
      .trigger('change', { force: true });

another option is to use cypress-file-upload , which is buggy in version 4.0.7 (uploads files twice)另一种选择是使用cypress-file-upload ,它在 4.0.7 版中有问题(上传文件两次)

cy.fixture("image.jpg").then((fileContent) => {
   cy.get("#fsp-fileUpload").attachFile({
      fileContent,
      fileName: "image",
      encoding: "base64",
      mimeType: "image/jpg",
    });
  });

Here is the multiple file upload version:这是多文件上传版本:

Cypress.Commands.add('uploadMultiFiles',(args) => {
  const { dataJson, dirName, inputTag, mineType} = args
  const arr = []
  dataJson.files.forEach((file, i) => {
    cy.fixture(`${ dirName + file }`).as(`file${i}`)
  })
  cy.get(`${inputTag}`).then(function (el) {
    for(const prop in this) {
      if (prop.includes("file")) {
        arr.push(this[prop])
      }
    }
    const list = new DataTransfer()
  
    dataJson.files.forEach((item, i) => {
      // convert the logo base64 string to a blob
      const blob = Cypress.Blob.base64StringToBlob(arr[i], mineType)
  
      const file = new FileCopy([blob], `${item}`, { type: mineType }, `${ dirName + item }`)
      const pathName = dirName.slice(1)
      file.webkitRelativePath = `${ pathName + item}`
      console.log(file)
      list.items.add(file)
    })
  
    const myFileList = list.files
    
    el[0].files = myFileList
    el[0].dispatchEvent(new Event('change', { bubbles: true }))
  })

})

The usage:用法:

First, prepare a data.json file inside the fixtures folder, example:首先,在 fixtures 文件夹中准备一个 data.json 文件,例如:

data.json
{
  "files":[
    "1_TEST-JOHN-01.jpeg",
    "2_TEST-JOHN-01.jpeg",
    "3_TEST-JOHN-01.jpeg",
    "4_TEST-JOHN-01.jpeg",
    "5_TEST-JOHN-01.jpeg",
    "6_TEST-JOHN-01.jpeg",
    "7_TEST-JOHN-01.jpeg",
    "8_TEST-JOHN-01.jpeg",
    "9_TEST-JOHN-01.jpeg",
    "10_TEST-JOHN-01.jpeg"
  ]
}

Second, import the json data into your spec.js其次,将 json 数据导入您的 spec.js

import data from '../fixtures/data.json'

Third, Write a class to extend the File web API object with the functions to set and get webkitRelativePath value三、编写一个类来扩展 File web API 对象,其中包含设置和获取 webkitRelativePath 值的功能

class FileCopy extends File {
  constructor(bits, filename, options) {
    super(bits, filename, options)
    let webkitRelativePath
    Object.defineProperties(this, {

        webkitRelativePath : {

            enumerable : true,
            set : function(value){
                webkitRelativePath = value;
            },
            get : function(){
                return webkitRelativePath;
            } 
        },
    });
  }

}

Finally, call the cmd in the spec.js最后调用spec.js中的cmd

cy.uploadMultiFiles(
      {
        dataJson:data, // the data.json you imported.
        dirName:"/your/dirname/",
        inputTag:"input#upload",
        mineType:"image/jpeg"
      }
    )

You can do it with new Cypress command:您可以使用新的 Cypress 命令执行此操作:

cy.get('input[type=file]').selectFile('file.json')

This is now available within Cypress library itself from version 9.3 and above.现在可以在赛普拉斯库本身从9.3及更高版本中使用它。 Follow the migration guide on how to move from cypress-file-upload plugin to Cypress .selectFile() command:按照迁移指南了解如何从cypress-file-upload插件迁移到 Cypress .selectFile()命令:

Migrating-from-cypress-file-upload-to-selectFile 从 cypress-file-upload-to-selectFile 迁移

if your file input display: none;如果您的文件输入display: none; use利用

cy.get('[data-type=inputFile]').selectFile('cypress/fixtures/avatar.jpg', { force: true })

else别的

 cy.get('[data-type=inputFile]').selectFile('cypress/fixtures/avatar.jpg')

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM