如何使用赛普拉斯测试文件输入?
How to test file inputs with Cypress?
如何编写需要与文件输入 DOM 元素交互的 e2e 流测试?
如果它是文本输入,我可以将其作为 DOM 组件与之交互(检查值、设置值)等。但是如果我有一个文件输入元素,我猜测交互是有限的,直到我可以打开对话框到 select 一个文件。我无法前进,select 我想上传的文件是本地对话框,而不是某些浏览器元素。
那么我将如何测试用户是否可以从我的站点正确上传文件?我正在使用 Cypress 来编写我的 e2e 测试。
赛普拉斯尚不支持测试文件输入元素。测试文件输入的唯一方法是:
- 发布原生事件(Cypress 在其 Roadmap 上有)。
- 了解您的应用程序如何使用 File API 处理文件上传,然后将其删除。这是可能的,但不够通用,无法给出任何具体建议。
有了这个 approach/hack 你实际上可以做到:
https://github.com/javieraviles/cypress-upload-file-post-form
它基于上述线程的不同答案https://github.com/cypress-io/cypress/issues/170
第一种情况(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.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');
})
})
同样基于前面提到的github issue,非常感谢那里的人。
赞成的答案最初对我有用,但我 运行 陷入试图处理 JSON 文件的字符串解码问题。处理十六进制也感觉像是额外的工作。
下面的代码处理 JSON 文件的方式略有不同,以防止出现 encode/decode 问题,并使用赛普拉斯的内置 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
})
})
})
})
以下功能对我有用,
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}"]`)
// );
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 文件夹中
在我的案例中,我进行了客户端和服务器端文件验证,以检查文件是 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');
smiling_pic.jpg 将在 fixtures 文件夹中
对我来说,更简单的方法是使用 cypress file upload package
安装它:
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/
有关更多示例,请查看 Usage section on README of the package.
在测试文件夹中的 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 中存在错误(上传文件两次)
cy.fixture("image.jpg").then((fileContent) => {
cy.get("#fsp-fileUpload").attachFile({
fileContent,
fileName: "image",
encoding: "base64",
mimeType: "image/jpg",
});
});
这里是多文件上传版本:
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'
三、写一个class扩展File webAPI对象,设置和获取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"
}
)
您可以使用新的赛普拉斯命令来完成:
cy.get('input[type=file]').selectFile('file.json')
现在可以在版本 9.3
及更高版本的 Cypress 库中使用。按照迁移指南了解如何从 cypress-file-upload
插件迁移到 Cypress .selectFile()
命令:
因为 9.3.0 你可以使用 selectFile
.
cy.get('input[type=file]').selectFile('cypress/fixtures/file.json')
参见:
如何编写需要与文件输入 DOM 元素交互的 e2e 流测试?
如果它是文本输入,我可以将其作为 DOM 组件与之交互(检查值、设置值)等。但是如果我有一个文件输入元素,我猜测交互是有限的,直到我可以打开对话框到 select 一个文件。我无法前进,select 我想上传的文件是本地对话框,而不是某些浏览器元素。
那么我将如何测试用户是否可以从我的站点正确上传文件?我正在使用 Cypress 来编写我的 e2e 测试。
赛普拉斯尚不支持测试文件输入元素。测试文件输入的唯一方法是:
- 发布原生事件(Cypress 在其 Roadmap 上有)。
- 了解您的应用程序如何使用 File API 处理文件上传,然后将其删除。这是可能的,但不够通用,无法给出任何具体建议。
有了这个 approach/hack 你实际上可以做到: https://github.com/javieraviles/cypress-upload-file-post-form
它基于上述线程的不同答案https://github.com/cypress-io/cypress/issues/170
第一种情况(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.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');
})
})
同样基于前面提到的github issue,非常感谢那里的人。
赞成的答案最初对我有用,但我 运行 陷入试图处理 JSON 文件的字符串解码问题。处理十六进制也感觉像是额外的工作。
下面的代码处理 JSON 文件的方式略有不同,以防止出现 encode/decode 问题,并使用赛普拉斯的内置 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
})
})
})
})
以下功能对我有用,
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}"]`)
// );
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 文件夹中
在我的案例中,我进行了客户端和服务器端文件验证,以检查文件是 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');
smiling_pic.jpg 将在 fixtures 文件夹中
对我来说,更简单的方法是使用 cypress file upload package
安装它:
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/
有关更多示例,请查看 Usage section on README of the package.
在测试文件夹中的 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 中存在错误(上传文件两次)
cy.fixture("image.jpg").then((fileContent) => {
cy.get("#fsp-fileUpload").attachFile({
fileContent,
fileName: "image",
encoding: "base64",
mimeType: "image/jpg",
});
});
这里是多文件上传版本:
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'
三、写一个class扩展File webAPI对象,设置和获取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
中调用cmdcy.uploadMultiFiles(
{
dataJson:data, // the data.json you imported.
dirName:"/your/dirname/",
inputTag:"input#upload",
mineType:"image/jpeg"
}
)
您可以使用新的赛普拉斯命令来完成:
cy.get('input[type=file]').selectFile('file.json')
现在可以在版本 9.3
及更高版本的 Cypress 库中使用。按照迁移指南了解如何从 cypress-file-upload
插件迁移到 Cypress .selectFile()
命令:
因为 9.3.0 你可以使用 selectFile
.
cy.get('input[type=file]').selectFile('cypress/fixtures/file.json')
参见: