如何使用Cypress测试文件输入?

72

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

如果是文本输入,我可以与之交互(检查值、设置值)等,因为它是一个DOM组件。但是,如果我有一个文件输入元素,我猜想交互仅限于打开对话框以选择文件。我无法继续并选择要上传的文件,因为对话框是本地的而不是浏览器元素。

那么,如何测试用户能否正确地从我的网站上上传文件? 我正在使用Cypress编写我的e2e测试。


3
请注意,从9.3.0版本开始,该功能已通过cy.selectFile本地支持。 - Alex Beardsley
13个回答

69

注意:Cypress 的最新版本支持 selectFile - 请参见其他答案

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文件夹中。

5
安装过程遇到问题。代码似乎有所更改。现在,函数名变为 cy.get( ... ).attachFile(),而不是 cy.get( ... ).upload()。我已编辑原始答案。 - Jules Colle
1
是的,Jules Colle,我刚刚查看了官方文档,你是正确的,.upload已被更改为.attachFile。谢谢。 - Muhammad Bilal
2
我也遇到了问题。为了解决它,我不得不将then语句链接在一起,就像cy.fixture(...).then( fc => {return Cypress.Blob.base64StringToBlob( fc ) }).then(fileContentasBlob => { cy.get('input[type="file"]').attachFile({ ...... - Chadd Frasier
1
我使用这种方法遇到了麻烦。上传成功,但是服务器无法处理上传的图像:(PIL.UnidentifiedImageError: cannot identify image file)。我能够通过Lucas Andrade的方法以仅使用cy.get('['input[type="file"]']').attachFile(fixtureFile)(没有有关mimeType等信息)来避免此错误。 - Jasmin
1
请注意,从9.3.0版本开始,此功能已通过cy.selectFile本地支持,并且在Cypress网站上有来自此插件的迁移指南。 - Alex Beardsley
显示剩余2条评论


28

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

安装它:

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/ 目录中。

有关更多示例,请查看包的README中的使用部分。


2
哇!我尝试了包的 README 中所有(更复杂的)示例,但这个 - 最简单的一个 - 是唯一有效的!谢谢! - Adrien Joly
这对我非常有效!然而,在尝试在files[]数组上使用客户端jQuery验证时,它无法验证文件类型。我有一个验证来检查您不能上传除图像文件以外的任何内容。但是当我在“attachfile”中指定mimetype时,上传到服务器的文件上传文件为空? - Gweaths
这个解决方案可行(需要从答案中链接的npm包作者那里获取额外的细节),但为了让我的智能感知识别'attachFile',我必须在规范文件的顶部添加/// <reference types="cypress-file-upload" /> - tengen
请使用@thisismydesign的解决方案! - ismaestro
这个已经过时了 - 请删除下面讨论的selectFile。 - Patrick

21

使用这种方法/技巧,您实际上可以做到:

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

})


1
我尝试了几十种方法,这是第一个奏效的。干得好! - Ryan Battistone
1
我很高兴它对你有用 :) 我喜欢这个社区。 - Javier Aviles
自定义的hexStringToByte方法可以被本地方法Blob替换 .then(Cypress.Blob.binaryStringToBlob) .then(blob => { const file = new File([blob], fileName);...这只是一种更简单的方式。 - Alexey Nikonov
没有尝试过,但听起来合理。 - Javier Aviles
cy.upload_file 不是一个函数。 - Sanan Ali
使用@thisismydesign的解决方案! - ismaestro

14

目前,Cypress尚不支持测试文件上传元素。测试文件上传的唯一方法是:

  1. 发出本地事件(Cypress在其路线图中提到了这一点)。
  2. 了解您的应用程序如何使用文件API处理文件上传,然后将其存根化。虽然这是可能的,但不通用,无法提供任何具体的建议。

请参见此开放问题以获取更多详细信息


我现在会暂时桩接文件上传,并会跟踪该问题。谢谢。 - sidoshi
1
问题中描述的解决方法已经在 https://github.com/abramenal/cypress-file-upload 中被概括了。 - Toby
这已经使用新版本的Cypress部署:https://dev59.com/M1YN5IYBdhLWcg3wq5wo#70771129 - t_dom93
使用@thisismydesign的解决方案! - ismaestro

8
在我的情况下,我需要进行客户端和服务器端文件验证,以检查文件是否为JPEG或PDF格式。因此,我必须创建一个上传命令,该命令将从固定的位置读取二进制文件,并准备一个带有文件扩展名的数据块。
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');

smiling_pic.jpg将位于fixtures文件夹中。


1
这是我能找到的唯一一个与我特定的XML文件格式一起使用的工作示例(否则编码会很奇怪)。唯一的变化是 Cypress.Blob.binaryStringToBlob(...) 不再返回 Promise<Blob>,而是直接返回 Blob,这意味着现在的用法是 const blob = Cypress.Blob.binaryStringToBlob(content, fileType); 更多内容请参见https://docs.cypress.io/api/utilities/blob。 - Lesley.Oakey
使用@thisismydesign的解决方案! - ismaestro

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}"]`)
// );

我正在寻找这个“刷新”功能,通过更改事件,帮了我很多,谢谢! - Kapcash

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

2

您可以使用新的Cypress命令来完成此操作:

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

这个功能现在已经包含在Cypress库本身的版本9.3及以上中。请按照迁移指南,从cypress-file-upload插件迁移到Cypress的.selectFile()命令: 从cypress-file-upload迁移到selectFile

0

如果您的文件输入 display: none;,请使用以下代码:

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

否则

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接