diff --git a/packages/driver/cypress/integration/commands/cookies_spec.js b/packages/driver/cypress/integration/commands/cookies_spec.js index 09a7b6f29f..0cbd2efaf1 100644 --- a/packages/driver/cypress/integration/commands/cookies_spec.js +++ b/packages/driver/cypress/integration/commands/cookies_spec.js @@ -8,12 +8,6 @@ describe('src/cy/commands/cookies', () => { }) context('test:before:run:async', () => { - it('can test unstubbed, real server', () => { - Cypress.automation.restore() - - cy.setCookie('foo', 'bar') - }) - it('clears cookies before each test run', () => { Cypress.automation .withArgs('get:cookies', { domain: 'localhost' }) diff --git a/packages/driver/cypress/integration/e2e/e2e_cookies_spec.js b/packages/driver/cypress/integration/e2e/e2e_cookies_spec.js new file mode 100644 index 0000000000..29ef98201a --- /dev/null +++ b/packages/driver/cypress/integration/e2e/e2e_cookies_spec.js @@ -0,0 +1,86 @@ +describe('e2e cookies spec', () => { + it('simple cookie', () => { + cy.setCookie('foo', 'bar') + cy.getCookie('foo', 'bar') + .then((cookie) => expect(cookie).exist) + }) + + context('__Host- prefix', () => { + // https://github.com/cypress-io/cypress/issues/8261 + it('can set __Host- cookie', () => { + cy.visit('https://example.com') + cy.setCookie('__Host-foobar', 'someval', { + domain: 'example.com', + sameSite: 'strict', + secure: true, + }) + + cy.getCookie('__Host-foobar').then(((cookie) => { + expect(cookie).exist + expect(cookie.domain).match(/^\.?example\.com$/) + expect(cookie.path).eq('/') + expect(cookie.secure).is.true + })) + }) + + it('errors when __Host- cookie and secure:false', (done) => { + cy.visit('https://example.com') + cy.setCookie('__Host-foobar', 'someval') + + cy.on('fail', (err) => { + expect(err.message) + .contain('__Host-') + .contain('must be set with `{ secure: true }`') + + done() + }) + }) + + it('errors when __Host- cookie and path', (done) => { + cy.visit('https://example.com') + cy.setCookie('__Host-foobar', 'someval', { + secure: true, + path: '/foo', + }) + + cy.on('fail', (err) => { + expect(err.message).contain('__Host-').contain('the path must be') + done() + }) + }) + }) + + context('__Secure- prefix', () => { + it('can set __Secure- cookie', () => { + cy.visit('https://example.com') + cy.setCookie('__Secure-foobar', 'someval', { + domain: 'example.com', + path: '/foo', + secure: true, + }) + + cy.getCookie('__Secure-foobar').then(((cookie) => { + expect(cookie).exist + expect(cookie.domain).match(/^\.?example\.com$/) + expect(cookie.path).eq('/foo') + expect(cookie.secure).is.true + })) + }) + + it('errors when __Secure- cookie secure:false', (done) => { + cy.visit('https://example.com') + cy.setCookie('__Secure-foobar', 'someval', { + domain: 'example.com', + path: '/foo', + }) + + cy.on('fail', (err) => { + expect(err.message) + .contain('__Secure-') + .contain('must be set with `{ secure: true }`') + + done() + }) + }) + }) +}) diff --git a/packages/driver/src/cy/commands/cookies.js b/packages/driver/src/cy/commands/cookies.js index 85c3170b7b..c222377e41 100644 --- a/packages/driver/src/cy/commands/cookies.js +++ b/packages/driver/src/cy/commands/cookies.js @@ -57,6 +57,14 @@ const normalizeSameSite = (sameSite) => { return sameSite } +// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Attributes +function cookieValidatesHostPrefix (options) { + return options.secure === false || (options.path && options.path !== '/') +} +function cookieValidatesSecurePrefix (options) { + return options.secure === false +} + module.exports = function (Commands, Cypress, cy, state, config) { const automateCookies = function (event, obj = {}, log, timeout) { const automate = () => { @@ -285,6 +293,14 @@ module.exports = function (Commands, Cypress, cy, state, config) { $errUtils.throwErrByPath('setCookie.invalid_arguments', { onFail }) } + if (options.name.startsWith('__Secure-') && cookieValidatesSecurePrefix(options)) { + $errUtils.throwErrByPath('setCookie.secure_prefix', { onFail }) + } + + if (options.name.startsWith('__Host-') && cookieValidatesHostPrefix(options)) { + $errUtils.throwErrByPath('setCookie.host_prefix', { onFail }) + } + return automateCookies('set:cookie', cookie, options._log, options.timeout) .then((resp) => { options.cookie = resp diff --git a/packages/driver/src/cypress/error_messages.js b/packages/driver/src/cypress/error_messages.js index be34557ec0..39ab21809e 100644 --- a/packages/driver/src/cypress/error_messages.js +++ b/packages/driver/src/cypress/error_messages.js @@ -1480,6 +1480,14 @@ module.exports = { docsUrl: 'https://on.cypress.io/setcookie', } }, + host_prefix: { + message: 'Cookies starting with the `__Host-` prefix must be set with `{ secure: true }`, and the path must be `/`', + docsUrl: 'https://on.cypress.io/setcookie', + }, + secure_prefix: { + message: 'Cookies starting with the `__Secure-` prefix must be set with `{ secure: true }`', + docsUrl: 'https://on.cypress.io/setcookie', + }, }, shadow: { diff --git a/packages/server/lib/browsers/cdp_automation.ts b/packages/server/lib/browsers/cdp_automation.ts index a83866f75a..ee482ca709 100644 --- a/packages/server/lib/browsers/cdp_automation.ts +++ b/packages/server/lib/browsers/cdp_automation.ts @@ -116,6 +116,11 @@ export const CdpAutomation = (sendDebuggerCommandFn: SendDebuggerCommand) => { } } + if (setCookieRequest.name.startsWith('__Host-')) { + setCookieRequest.url = `https://${cookie.domain}` + delete setCookieRequest.domain + } + return setCookieRequest }