56

如何编写需要与文件输入 DOM 元素交互的 e2e 流测试?

如果它是一个文本输入,我可以与它交互(检查值、设置值)等作为它的 DOM 组件。但是如果我有一个文件输入元素,我猜测交互是有限的,直到我可以打开对话框来选择一个文件。我无法前进并选择我要上传的文件,因为对话框将是本机的,而不是某些浏览器元素。

那么我将如何测试用户是否可以从我的站点正确上传文件?我正在使用Cypress编写我的 e2e 测试。

4

12 回答 12

62
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'
        });
    });
});

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

注意:testPicture.png 必须在 cypress 的 fixture 文件夹中

于 2019-11-20T06:48:29.560 回答
25

对我来说,更简单的方法是使用这个柏树文件上传包

安装它:

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

然后将此行添加到您的项目的cypress/support/commands.js

import 'cypress-file-upload';

现在你可以这样做:

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

photo.png必须在cypress/fixtures/

有关更多示例,请查看软件包自述文件中的使用部分。

于 2020-06-04T21:31:46.797 回答
21

使用这种方法/hack,您实际上可以做到: https ://github.com/javieraviles/cypress-upload-file-post-form

它基于上述线程https://github.com/cypress-io/cypress/issues/170的不同答案

第一个场景(upload_file_to_form_spec.js):

我想测试一个在提交表单之前必须选择/上传文件的 UI。在 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);
}

然后,如果您想上传一个 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');
})

})

于 2018-05-14T20:15:23.560 回答
13

赛普拉斯尚不支持测试文件输入元素。测试文件输入的唯一方法是:

  1. 发布本地事件(赛普拉斯在其路线图上已发布)。
  2. 了解您的应用程序如何使用 File API 处理文件上传,然后将其存根。这是可能的,但不够通用,无法提供任何具体建议。

有关更多详细信息,请参阅此未解决的问题

于 2017-11-02T19:18:55.170 回答
8

就我而言,我进行了客户端和服务器端文件验证,以检查文件是 JPEG 还是 PDF。所以我必须创建一个上传命令,它会从 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 });
    });
  });
});

然后将其用作

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

微笑图片.jpg 将在fixtures 文件夹中

于 2019-11-27T14:14:19.993 回答
5

9.3.0 开始,您可以使用selectFile.

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

看:

于 2022-01-20T16:03:03.417 回答
4

以下功能对我有用,

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}"]`)
// );
于 2019-08-28T07:45:34.517 回答
3

也基于前面提到的github 问题,非常感谢那里的人们。

赞成的答案最初对我有用,但我在尝试处理 JSON 文件时遇到了字符串解码问题。处理十六进制也感觉像是额外的工作。

下面的代码处理 JSON 文件的方式略有不同,以防止编码/解码问题,并使用 Cypress 的内置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
      })
    })
  })
})
于 2018-08-16T02:42:41.780 回答
0

在测试文件夹中的 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;
      });
  },
);

用法:

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

另一种选择是使用cypress-file-upload,这在 4.0.7 版中有错误(上传文件两次)

于 2020-08-11T07:38:27.893 回答
0
cy.fixture("image.jpg").then((fileContent) => {
   cy.get("#fsp-fileUpload").attachFile({
      fileContent,
      fileName: "image",
      encoding: "base64",
      mimeType: "image/jpg",
    });
  });
于 2021-07-23T20:54:42.037 回答
0

您可以使用新的 Cypress 命令执行此操作:

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

这现在可以在赛普拉斯库本身的版本9.3及更高版本中使用。按照迁移指南了解如何从cypress-file-upload插件迁移到 Cypress.selectFile()命令:

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

于 2022-01-19T13:07:59.887 回答
0

这是多文件上传版本:

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 }))
  })

})

用法:

首先,在 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"
  ]
}

其次,将 json 数据导入您的 spec.js

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

三、编写一个类来扩展 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;
            } 
        },
    });
  }

}

最后调用spec.js中的cmd

cy.uploadMultiFiles(
      {
        dataJson:data, // the data.json you imported.
        dirName:"/your/dirname/",
        inputTag:"input#upload",
        mineType:"image/jpeg"
      }
    )
于 2021-09-17T07:39:21.623 回答