mirror of
https://github.com/cypress-io/cypress.git
synced 2026-04-28 19:00:03 -05:00
fix: can send FormData with File. (#16576)
This commit is contained in:
@@ -499,6 +499,32 @@ describe('src/cy/commands/request', () => {
|
||||
expect(dec.decode(response.body)).to.contain('1,2,3,4')
|
||||
})
|
||||
})
|
||||
|
||||
it('can send FormData with File', () => {
|
||||
const formData = new FormData()
|
||||
|
||||
formData.set('file', new File(['1,2,3,4'], 'upload.txt'), 'upload.txt')
|
||||
formData.set('name', 'Tony Stark')
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:3500/dump-form-data',
|
||||
body: formData,
|
||||
headers: {
|
||||
'content-type': 'multipart/form-data',
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
expect(response.status).to.equal(200)
|
||||
// When user-passed body to the Nodejs server is a Buffer,
|
||||
// Nodejs doesn't provide any decoder in the response.
|
||||
// So, we need to decode it ourselves.
|
||||
const dec = new TextDecoder()
|
||||
const result = dec.decode(response.body)
|
||||
|
||||
expect(result).to.contain('Tony Stark')
|
||||
expect(result).to.contain('upload.txt')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('subjects', () => {
|
||||
|
||||
@@ -6,6 +6,8 @@ const http = require('http')
|
||||
const httpsProxy = require('@packages/https-proxy')
|
||||
const path = require('path')
|
||||
const Promise = require('bluebird')
|
||||
const multer = require('multer')
|
||||
const upload = multer({ dest: 'cypress/_test-output/' })
|
||||
|
||||
const PATH_TO_SERVER_PKG = path.dirname(require.resolve('@packages/server'))
|
||||
const httpPorts = [3500, 3501]
|
||||
@@ -144,6 +146,10 @@ const createApp = (port) => {
|
||||
return res.send(`<html><body>it worked!<br>request body:<br>${req.body.toString()}</body></html>`)
|
||||
})
|
||||
|
||||
app.all('/dump-form-data', upload.single('file'), (req, res) => {
|
||||
return res.send(`<html><body>it worked!<br>request body:<br>${JSON.stringify(req.body)}<br>original name:<br>${req.file.originalname}</body></html>`)
|
||||
})
|
||||
|
||||
app.get('/status-404', (req, res) => {
|
||||
return res
|
||||
.status(404)
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"minimist": "1.2.5",
|
||||
"mocha": "7.0.1",
|
||||
"morgan": "1.9.1",
|
||||
"multer": "1.4.2",
|
||||
"ordinal": "1.0.3",
|
||||
"react-15.6.1": "npm:react@15.6.1",
|
||||
"react-16.0.0": "npm:react@16.0.0",
|
||||
|
||||
@@ -282,13 +282,61 @@ module.exports = (Commands, Cypress, cy, state, config) => {
|
||||
// Check if body is Blob.
|
||||
// construct.name is added because the parent of the Blob is not the same Blob
|
||||
// if it's generated from the test spec code.
|
||||
if (requestOpts.body instanceof Blob || requestOpts?.body?.constructor.name === 'Blob') {
|
||||
if (requestOpts.body instanceof Blob || requestOpts.body?.constructor.name === 'Blob') {
|
||||
requestOpts.bodyIsBase64Encoded = true
|
||||
|
||||
return Cypress.Blob.blobToBase64String(requestOpts.body).then((str) => {
|
||||
requestOpts.body = str
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/1647
|
||||
// Handle if body is FormData
|
||||
if (requestOpts.body instanceof FormData || requestOpts.body?.constructor.name === 'FormData') {
|
||||
const boundary = '----CypressFormDataBoundary'
|
||||
|
||||
// reset content-type
|
||||
if (requestOpts.headers) {
|
||||
delete requestOpts.headers[Object.keys(requestOpts).find((key) => key.toLowerCase() === 'content-type')]
|
||||
} else {
|
||||
requestOpts.headers = {}
|
||||
}
|
||||
|
||||
// boundary is required for form data
|
||||
// @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST
|
||||
requestOpts.headers['content-type'] = `multipart/form-data; boundary=${boundary}`
|
||||
|
||||
// socket.io ignores FormData.
|
||||
// So, we need to encode the data into base64 string format.
|
||||
const formBody = []
|
||||
|
||||
requestOpts.body.forEach((value, key) => {
|
||||
// HTTP line break style is \r\n.
|
||||
// @see https://stackoverflow.com/questions/5757290/http-header-line-break-style
|
||||
if (value instanceof File || value?.constructor.name === 'File') {
|
||||
formBody.push(`--${boundary}\r\n`)
|
||||
formBody.push(`Content-Disposition: form-data; name="${key}"; filename="${value.name}"\r\n`)
|
||||
formBody.push(`Content-Type: ${value.type || 'application/octet-stream'}\r\n`)
|
||||
formBody.push('\r\n')
|
||||
formBody.push(value)
|
||||
formBody.push('\r\n')
|
||||
} else {
|
||||
formBody.push(`--${boundary}\r\n`)
|
||||
formBody.push(`Content-Disposition: form-data; name="${key}"\r\n`)
|
||||
formBody.push('\r\n')
|
||||
formBody.push(value)
|
||||
formBody.push('\r\n')
|
||||
}
|
||||
})
|
||||
|
||||
formBody.push(`--${boundary}--\r\n`)
|
||||
|
||||
requestOpts.bodyIsBase64Encoded = true
|
||||
|
||||
return Cypress.Blob.blobToBase64String(new Blob(formBody)).then((str) => {
|
||||
requestOpts.body = str
|
||||
})
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
return Cypress.backend('http:request', requestOpts)
|
||||
|
||||
Reference in New Issue
Block a user