Skip to content

Commit

Permalink
fix: can send FormData with File. (#16576)
Browse files Browse the repository at this point in the history
  • Loading branch information
sainthkh authored May 24, 2021
1 parent c4aaa58 commit c258879
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 1 deletion.
26 changes: 26 additions & 0 deletions packages/driver/cypress/integration/commands/request_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
6 changes: 6 additions & 0 deletions packages/driver/cypress/plugins/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions packages/driver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:[email protected]",
"react-16.0.0": "npm:[email protected]",
Expand Down
50 changes: 49 additions & 1 deletion packages/driver/src/cy/commands/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

4 comments on commit c258879

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on c258879 May 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/7.4.0/circle-develop-c2588798a03aebcdb46bbdf7dd4a2056f88b3d8d/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on c258879 May 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppVeyor has built the win32 ia32 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/7.4.0/appveyor-develop-c2588798a03aebcdb46bbdf7dd4a2056f88b3d8d/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on c258879 May 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppVeyor has built the win32 x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/7.4.0/appveyor-develop-c2588798a03aebcdb46bbdf7dd4a2056f88b3d8d/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on c258879 May 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/7.4.0/circle-develop-c2588798a03aebcdb46bbdf7dd4a2056f88b3d8d/cypress.tgz

Please sign in to comment.