From ab60ed4079a7f1fb59f56b82956603d71a1e0003 Mon Sep 17 00:00:00 2001 From: Bill Glesias Date: Thu, 16 Feb 2023 09:34:03 -0500 Subject: [PATCH 1/3] fix: duplicate and expired cookies (#25761) * chore: add regression tests for duplicate cookies and bad expiry times * avoid prepending domain with dot for cookies that are set with the server side jar. This is to avoid the cookie being duplicated if it is set or overridden in a different context (request that can actually set the cookie or via document.domain) * feat: use cookie.toString() in the cookie patch to more accurately set cookies on the document, which should include other properties besides key=value * fix: add logic to handle expired cookies in the document.cookie patch, as well as in CDP * chore: build binary for cookie fixes for users to test * chore: change name of fixture to something more accurate * chore: comment why we are using the toughCookie toString method in the patch * [run ci] * chore: add changelog entry * [run ci] * fix: revert back to key=value when getting document.cookie as those are the only values are displayed (oversight on my end) * [run ci] * chore: make compatible with cypress.require * fix: add tests for hostOnly/non hostOnly cookies to make sure property gets sent up to automation client correctly. No longer need custom cookie prop to determine destination * [run ci] * fix: stale unit test * chore: adjust comments * [run ci] * fix: bad domain logic * [run ci] * chore: remove irrelevant comment * [run ci] * fix: adjust cookie login text to spec hostOnly cookie within the cookie patch. This should yield the same behavior as we are bound to same origin within the spec bridge * [run ci] * [run ci] * fix: allow for cookies on request of same key to take precedence over cookies in the jar, regardless of how many hierachy cookies exist in the jar * chore: fix cookie misc tests for cy.origin (dont run cy.origin) * [run ci] * chore: skip misc cookie tests in webkit as headless behavior doesn't clear cookies between tests correctly * Revert "fix: allow for cookies on request of same key to take precedence over cookies in the jar, regardless of how many hierachy cookies exist in the jar" This reverts commit 17de1883ab709669922de63793db8539fefa8067. * [run ci] * chore: split changelog entry into two parts * chore: update logic to remove else statement and add comments * [run ci] * chore: readd windows snapshot branch in workflows * [run ci] * chore: fix workflows from bad merge * [run ci] * Revert "chore: split changelog entry into two parts" This reverts commit 4352ef5f3ec4bed55ef54933c65966d351d830b7. * [run ci] --- .circleci/workflows.yml | 10 +- cli/CHANGELOG.md | 8 + .../cypress/e2e/e2e/origin/cookie_login.cy.ts | 4 +- .../cypress/e2e/e2e/origin/cookie_misc.cy.ts | 208 ++++++++++++++++++ ...trigger-cross-origin-redirect-to-self.html | 21 ++ packages/driver/cypress/plugins/server.js | 11 + packages/runner/injection/patches/cookies.ts | 33 ++- packages/server/lib/automation/cookies.ts | 16 +- packages/server/lib/util/cookies.ts | 20 +- .../test/unit/browsers/cdp_automation_spec.ts | 17 ++ 10 files changed, 336 insertions(+), 12 deletions(-) create mode 100644 packages/driver/cypress/e2e/e2e/origin/cookie_misc.cy.ts create mode 100644 packages/driver/cypress/fixtures/trigger-cross-origin-redirect-to-self.html diff --git a/.circleci/workflows.yml b/.circleci/workflows.yml index 939160e8b4e8..566e6cd6363f 100644 --- a/.circleci/workflows.yml +++ b/.circleci/workflows.yml @@ -28,7 +28,7 @@ mainBuildFilters: &mainBuildFilters only: - develop - /^release\/\d+\.\d+\.\d+$/ - - 'tgriesser/spike/spike' + - 'fix-duplicate-and-expired-cookies' # usually we don't build Mac app - it takes a long time # but sometimes we want to really confirm we are doing the right thing @@ -37,7 +37,7 @@ macWorkflowFilters: &darwin-workflow-filters when: or: - equal: [ develop, << pipeline.git.branch >> ] - - equal: [ 'tgriesser/spike/spike', << pipeline.git.branch >> ] + - equal: [ 'fix-duplicate-and-expired-cookies', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -46,7 +46,7 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters when: or: - equal: [ develop, << pipeline.git.branch >> ] - - equal: [ 'tgriesser/spike/spike', << pipeline.git.branch >> ] + - equal: [ 'fix-duplicate-and-expired-cookies', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -65,7 +65,7 @@ windowsWorkflowFilters: &windows-workflow-filters or: - equal: [ develop, << pipeline.git.branch >> ] # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] + - equal: [ 'update-v8-snapshot-cache-on-develop', 'fix-duplicate-and-expired-cookies', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -131,7 +131,7 @@ commands: - run: name: Check current branch to persist artifacts command: | - if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "tgriesser/spike/spike" ]]; then + if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "fix-duplicate-and-expired-cookies" ]]; then echo "Not uploading artifacts or posting install comment for this branch." circleci-agent step halt fi diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 4fb680375b25..974f0ad1ab56 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -1,4 +1,12 @@ +## 12.6.1 + +_Released 03/1/2023 (PENDING)_ + +**Bugfixes:** + +- Fixed an issue where cookies were being duplicated with the same hostname, but a prepended dot. Fixed an issue where cookies may not be expiring correctly. Fixes [#25174](https://github.com/cypress-io/cypress/issues/25174), [#25205](https://github.com/cypress-io/cypress/issues/25205) and [#25495](https://github.com/cypress-io/cypress/issues/25495). + ## 12.6.0 _Released 02/15/2023_ diff --git a/packages/driver/cypress/e2e/e2e/origin/cookie_login.cy.ts b/packages/driver/cypress/e2e/e2e/origin/cookie_login.cy.ts index 679bb32419cc..e54da5957830 100644 --- a/packages/driver/cypress/e2e/e2e/origin/cookie_login.cy.ts +++ b/packages/driver/cypress/e2e/e2e/origin/cookie_login.cy.ts @@ -723,7 +723,7 @@ describe('cy.origin - cookie login', { browser: '!webkit' }, () => { cy.getCookie('key').then((cookie) => { expect(Cypress._.omit(cookie, 'expiry')).to.deep.equal({ - domain: '.www.foobar.com', + domain: 'www.foobar.com', httpOnly: false, name: 'key', path: '/fixtures', @@ -851,7 +851,7 @@ describe('cy.origin - cookie login', { browser: '!webkit' }, () => { cy.get('[data-cy="doc-cookie"]').invoke('text').should('equal', 'name=value') cy.getCookie('name').then((cookie) => { expect(Cypress._.omit(cookie, 'expiry')).to.deep.equal({ - domain: '.www.foobar.com', + domain: 'www.foobar.com', httpOnly: false, name: 'name', path: '/', diff --git a/packages/driver/cypress/e2e/e2e/origin/cookie_misc.cy.ts b/packages/driver/cypress/e2e/e2e/origin/cookie_misc.cy.ts new file mode 100644 index 000000000000..cf5aee56a85d --- /dev/null +++ b/packages/driver/cypress/e2e/e2e/origin/cookie_misc.cy.ts @@ -0,0 +1,208 @@ +// FIXME: currently cookies aren't cleared properly in headless mode with webkit between tests, as the below tests (excluding cy.origin) pass headfully locally. +describe('misc cookie tests', { browser: '!webkit' }, () => { + // NOTE: For this test to work correctly, we need to have a FQDN, not localhost (www.foobar.com). + // FIXES: https://github.com/cypress-io/cypress/issues/25174 (cookies are duplicated with prepended dot (.)) + it('does not duplicate cookies with a prepended dot for cookies that are stored inside the server side cookie jar (host only)', () => { + cy.visit('https://www.foobar.com:3502/fixtures/trigger-cross-origin-redirect-to-self.html') + + // does a 302 redirect back to www.foobar.com primary-origin page, but sets a sameSite=None cookie + cy.get('[data-cy="cookie-cross-origin-redirects-host-only"]').click() + + cy.getCookies({ domain: 'www.foobar.com' }).then((cookies) => { + expect(cookies).to.have.length(1) + + const singleCookie = cookies[0] + + expect(singleCookie).to.have.property('name', 'foo') + expect(singleCookie).to.have.property('value', 'bar') + expect(singleCookie).to.have.property('domain', 'www.foobar.com') + }) + }) + + it('does not duplicate cookies with a prepended dot for cookies that are stored inside the server side cookie jar (non-host only)', () => { + cy.visit('https://www.foobar.com:3502/fixtures/trigger-cross-origin-redirect-to-self.html') + + // does a 302 redirect back to www.foobar.com primary-origin page, but sets a sameSite=None cookie + cy.get('[data-cy="cookie-cross-origin-redirects"]').click() + + cy.getCookies({ domain: 'www.foobar.com' }).then((cookies) => { + expect(cookies).to.have.length(1) + + const singleCookie = cookies[0] + + expect(singleCookie).to.have.property('name', 'foo') + expect(singleCookie).to.have.property('value', 'bar') + expect(singleCookie).to.have.property('domain', '.www.foobar.com') + }) + }) + + /** + * FIXES: + * https://github.com/cypress-io/cypress/issues/25205 (cookies set with expired time with value deleted show up as set with value deleted) + * https://github.com/cypress-io/cypress/issues/25495 (session cookies set with expired time with value deleted show up as set with value deleted) + * https://github.com/cypress-io/cypress/issues/25148 (cannot log into azure, shows cookies are disabled/blocked) + */ + describe('expiring cookies', { browser: '!webkit' }, () => { + before(() => { + cy.origin(`https://app.foobar.com:3503`, () => { + window.makeRequest = Cypress.require('../../../support/utils').makeRequestForCookieBehaviorTests + }) + }) + + describe('removes cookies that are set with an expired expiry time from the server side cookie jar / browser via CDP', () => { + it('works with Max-Age=0', () => { + cy.visit(`https://www.foobar.com:3502/fixtures/primary-origin.html`) + + cy.visit(`https://app.foobar.com:3503/fixtures/secondary-origin.html`) + cy.origin(`https://app.foobar.com:3503`, () => { + cy.window().then((win) => { + return cy.wrap(window.makeRequest(win, `/set-cookie?cookie=foo=bar; Domain=.foobar.com;`)) + }) + + cy.getCookie('foo').its('value').should('eq', 'bar') + + cy.window().then((win) => { + return cy.wrap(window.makeRequest(win, `/set-cookie?cookie=foo=deleted; Domain=.foobar.com; Max-Age=0;`)) + }) + + cy.getCookie('foo').should('eq', null) + }) + }) + + it('works with expires=Thu, 01-Jan-1970 00:00:01 GMT', () => { + cy.visit(`https://www.foobar.com:3502/fixtures/primary-origin.html`) + + cy.visit(`https://app.foobar.com:3503/fixtures/secondary-origin.html`) + cy.origin(`https://app.foobar.com:3503`, () => { + cy.window().then((win) => { + return cy.wrap(window.makeRequest(win, `/set-cookie?cookie=foo=bar; Domain=.foobar.com;`)) + }) + + cy.getCookie('foo').its('value').should('eq', 'bar') + + cy.window().then((win) => { + return cy.wrap(window.makeRequest(win, `/set-cookie?cookie=foo=deleted; Domain=.foobar.com; expires=Thu, 01-Jan-1970 00:00:01 GMT;`)) + }) + + cy.getCookie('foo').should('eq', null) + }) + }) + + it('works with expires=Tues, 01-Jan-1980 00:00:01 GMT', () => { + cy.visit(`https://www.foobar.com:3502/fixtures/primary-origin.html`) + + cy.visit(`https://app.foobar.com:3503/fixtures/secondary-origin.html`) + cy.origin(`https://app.foobar.com:3503`, () => { + cy.window().then((win) => { + return cy.wrap(window.makeRequest(win, `/set-cookie?cookie=foo=bar; Domain=.foobar.com;`)) + }) + + cy.getCookie('foo').its('value').should('eq', 'bar') + + cy.window().then((win) => { + return cy.wrap(window.makeRequest(win, `/set-cookie?cookie=foo=deleted; Domain=.foobar.com; expires=Tues, 01-Jan-1980 00:00:01 GMT; Max-Age=0;`)) + }) + + cy.getCookie('foo').should('eq', null) + }) + }) + + it('work with expires=Thu, 01-Jan-1970 00:00:01 GMT and Max-Age=0', () => { + cy.visit(`https://www.foobar.com:3502/fixtures/primary-origin.html`) + + cy.visit(`https://app.foobar.com:3503/fixtures/secondary-origin.html`) + cy.origin(`https://app.foobar.com:3503`, () => { + cy.window().then((win) => { + return cy.wrap(window.makeRequest(win, `/set-cookie?cookie=foo=bar; Domain=.foobar.com;`)) + }) + + cy.getCookie('foo').its('value').should('eq', 'bar') + + cy.window().then((win) => { + return cy.wrap(window.makeRequest(win, `/set-cookie?cookie=foo=deleted; Domain=.foobar.com; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0;`)) + }) + + cy.getCookie('foo').should('eq', null) + }) + }) + }) + + describe('removes cookies that are set with an expired expiry time from the document.cookie patch / browser via CDP', () => { + it('works with Max-Age=0', () => { + cy.visit(`https://www.foobar.com:3502/fixtures/primary-origin.html`) + + cy.visit(`https://app.foobar.com:3503/fixtures/secondary-origin.html`) + cy.origin(`https://app.foobar.com:3503`, () => { + cy.window().then((win) => { + win.document.cookie = 'foo=bar' + }) + + cy.getCookie('foo').its('value').should('eq', 'bar') + + cy.window().then((win) => { + win.document.cookie = 'foo=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0;' + }) + + cy.getCookie('foo').should('eq', null) + }) + }) + + it('works with expires=Thu, 01-Jan-1970 00:00:01 GMT', () => { + cy.visit(`https://www.foobar.com:3502/fixtures/primary-origin.html`) + + cy.visit(`https://app.foobar.com:3503/fixtures/secondary-origin.html`) + cy.origin(`https://app.foobar.com:3503`, () => { + cy.window().then((win) => { + win.document.cookie = 'foo=bar' + }) + + cy.getCookie('foo').its('value').should('eq', 'bar') + + cy.window().then((win) => { + win.document.cookie = 'foo=deleted; Max-Age=0' + }) + + cy.getCookie('foo').should('eq', null) + }) + }) + + it('works with expires=Tues, 01-Jan-1980 00:00:01 GMT', () => { + cy.visit(`https://www.foobar.com:3502/fixtures/primary-origin.html`) + + cy.visit(`https://app.foobar.com:3503/fixtures/secondary-origin.html`) + cy.origin(`https://app.foobar.com:3503`, () => { + cy.window().then((win) => { + win.document.cookie = 'foo=bar' + }) + + cy.getCookie('foo').its('value').should('eq', 'bar') + + cy.window().then((win) => { + win.document.cookie = 'foo=deleted; expires=Tues, 01-Jan-1980 00:00:01 GMT' + }) + + cy.getCookie('foo').should('eq', null) + }) + }) + + it('expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0', () => { + cy.visit(`https://www.foobar.com:3502/fixtures/primary-origin.html`) + + cy.visit(`https://app.foobar.com:3503/fixtures/secondary-origin.html`) + cy.origin(`https://app.foobar.com:3503`, () => { + cy.window().then((win) => { + win.document.cookie = 'foo=bar' + }) + + cy.getCookie('foo').its('value').should('eq', 'bar') + + cy.window().then((win) => { + win.document.cookie = 'foo=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0' + }) + + cy.getCookie('foo').should('eq', null) + }) + }) + }) + }) +}) diff --git a/packages/driver/cypress/fixtures/trigger-cross-origin-redirect-to-self.html b/packages/driver/cypress/fixtures/trigger-cross-origin-redirect-to-self.html new file mode 100644 index 000000000000..3dc647db6925 --- /dev/null +++ b/packages/driver/cypress/fixtures/trigger-cross-origin-redirect-to-self.html @@ -0,0 +1,21 @@ + + + + + + Trigger Cross Origin Redirect Loop that sets host only cookie + Trigger Cross Origin Redirect Loop that sets non host only cookie + + + diff --git a/packages/driver/cypress/plugins/server.js b/packages/driver/cypress/plugins/server.js index a27eba20791d..66efc48a5d08 100644 --- a/packages/driver/cypress/plugins/server.js +++ b/packages/driver/cypress/plugins/server.js @@ -296,6 +296,17 @@ const createApp = (port) => { .sendStatus(200) }) + app.get('/set-same-site-none-cookie-on-redirect', (req, res) => { + const { redirect, cookie } = req.query + const cookieDecoded = decodeURIComponent(cookie) + + const cookieVal = `${cookieDecoded}; SameSite=None; Secure` + + res + .header('Set-Cookie', cookieVal) + .redirect(302, redirect) + }) + app.get('/test-request-credentials', (req, res) => { const origin = cors.getOrigin(req['headers']['referer']) diff --git a/packages/runner/injection/patches/cookies.ts b/packages/runner/injection/patches/cookies.ts index 3193d062f72e..586191311ded 100644 --- a/packages/runner/injection/patches/cookies.ts +++ b/packages/runner/injection/patches/cookies.ts @@ -6,18 +6,25 @@ import { import { Cookie as ToughCookie } from 'tough-cookie' import type { AutomationCookie } from '@packages/server/lib/automation/cookies' +function isHostOnlyCookie (domain) { + return domain[0] !== '.' +} + const parseDocumentCookieString = (documentCookieString: string): AutomationCookie[] => { if (!documentCookieString || !documentCookieString.trim().length) return [] return documentCookieString.split(';').map((cookieString) => { const [name, value] = cookieString.split('=') + let cookieDomain = location.hostname + return { name: name.trim(), value: value.trim(), - domain: location.hostname, + domain: cookieDomain, expiry: null, httpOnly: false, + hostOnly: isHostOnlyCookie(cookieDomain), maxAge: null, path: null, sameSite: 'lax', @@ -88,9 +95,33 @@ export const patchDocumentCookie = (requestCookies: AutomationCookie[]) => { const stringValue = `${newValue}` const parsedCookie = CookieJar.parse(stringValue) + if (parsedCookie?.hostOnly === null) { + // we want to make sure the hostOnly property is respected when syncing with CDP/extension to prevent duplicates. + // in the case it is not set, we need to calculate it + parsedCookie.hostOnly = isHostOnlyCookie(parsedCookie.domain || domain) + } + // if result is undefined, it was invalid and couldn't be parsed if (!parsedCookie) return getDocumentCookieValue() + // if the cookie is expired, remove it in our cookie jar + // and via setting it inside our automation client with the correct expiry. + // This will have the effect of removing the cookie + if (parsedCookie.expiryTime() < Date.now()) { + cookieJar.removeCookie({ + name: parsedCookie.key, + path: parsedCookie.path || '/', + domain: parsedCookie.domain as string, + }) + + // send the cookie to the server so it can be removed from the browser + // via aututomation. If the cookie expiry is set inside the server-side cookie jar, + // the cookie will be automatically removed. + sendCookieToServer(toughCookieToAutomationCookie(parsedCookie, domain)) + + return getDocumentCookieValue() + } + // we should be able to pass in parsedCookie here instead of the string // value, but tough-cookie doesn't recognize it using an instanceof // check and throws an error. because we can't, we have to massage diff --git a/packages/server/lib/automation/cookies.ts b/packages/server/lib/automation/cookies.ts index ee45dcc89704..a1211d89d6cb 100644 --- a/packages/server/lib/automation/cookies.ts +++ b/packages/server/lib/automation/cookies.ts @@ -5,8 +5,9 @@ import { isHostOnlyCookie } from '../browsers/cdp_automation' export interface AutomationCookie { domain: string - expiry: number | null + expiry: 'Infinity' | '-Infinity' | number | null httpOnly: boolean + hostOnly: boolean maxAge: 'Infinity' | '-Infinity' | number | null name: string path: string | null @@ -31,9 +32,20 @@ const normalizeCookieProps = function (props) { return props } + // if the cookie is stored inside the server side cookie jar, + // we want to make the automation client aware so the domain property + // isn't mutated to prevent duplicate setting of cookies from different contexts. + // This should be handled by the hostOnly property + const cookie = _.pick(props, COOKIE_PROPERTIES) - if (props.expiry != null) { + if (props.expiry === '-Infinity') { + cookie.expiry = -Infinity + // set the cookie to expired so when set, the cookie is removed + cookie.expirationDate = 0 + } else if (props.expiry === 'Infinity') { + cookie.expiry = null + } else if (props.expiry != null) { // when sending cookie props we need to convert // expiry to expirationDate delete cookie.expiry diff --git a/packages/server/lib/util/cookies.ts b/packages/server/lib/util/cookies.ts index 650c8c7b9277..f0f46c17138a 100644 --- a/packages/server/lib/util/cookies.ts +++ b/packages/server/lib/util/cookies.ts @@ -16,8 +16,11 @@ export const toughCookieToAutomationCookie = (toughCookie: Cookie, defaultDomain return { domain: toughCookie.domain || defaultDomain, - expiry: isFinite(expiry) ? expiry / 1000 : null, + // if expiry is Infinity or -Infinity, this operation is a no-op + expiry: (expiry === Infinity || expiry === -Infinity) ? expiry.toString() as '-Infinity' | 'Infinity' : expiry / 1000, httpOnly: toughCookie.httpOnly, + // we want to make sure the hostOnly property is respected when syncing with CDP/extension to prevent duplicates + hostOnly: toughCookie.hostOnly || false, maxAge: toughCookie.maxAge, name: toughCookie.key, path: toughCookie.path, @@ -28,10 +31,23 @@ export const toughCookieToAutomationCookie = (toughCookie: Cookie, defaultDomain } export const automationCookieToToughCookie = (automationCookie: AutomationCookie, defaultDomain: string): Cookie => { + let expiry: Date | undefined = undefined + + if (automationCookie.expiry != null) { + if (isFinite(automationCookie.expiry as number)) { + expiry = new Date(automationCookie.expiry as number * 1000) + } else if (automationCookie.expiry === '-Infinity') { + // if negative Infinity, the cookie is Date(0), has expired and is slated to be removed + expiry = new Date(0) + } + } + return new Cookie({ domain: automationCookie.domain || defaultDomain, - expires: automationCookie.expiry != null && isFinite(automationCookie.expiry) ? new Date(automationCookie.expiry * 1000) : undefined, + expires: expiry, httpOnly: automationCookie.httpOnly, + // we want to make sure the hostOnly property is respected when syncing with CDP/extension to prevent duplicates + hostOnly: automationCookie.hostOnly || false, maxAge: automationCookie.maxAge || 'Infinity', key: automationCookie.name, path: automationCookie.path || undefined, diff --git a/packages/server/test/unit/browsers/cdp_automation_spec.ts b/packages/server/test/unit/browsers/cdp_automation_spec.ts index 8a7a03704b39..b54e262b4788 100644 --- a/packages/server/test/unit/browsers/cdp_automation_spec.ts +++ b/packages/server/test/unit/browsers/cdp_automation_spec.ts @@ -204,6 +204,23 @@ context('lib/browsers/cdp_automation', () => { }) }) + it('resolves with the cookie props (host only)', function () { + this.sendDebuggerCommand + .withArgs('Network.setCookie', { domain: 'google.com', name: 'session', value: 'key', path: '/' }) + .resolves({ success: true }) + .withArgs('Network.getAllCookies') + .resolves({ + cookies: [ + { name: 'session', value: 'key', path: '/', domain: 'google.com', secure: false, httpOnly: false }, + ], + }) + + return this.onRequest('set:cookie', { domain: 'google.com', name: 'session', value: 'key', path: '/', hostOnly: true }) + .then((resp) => { + expect(resp).to.deep.eq({ domain: 'google.com', expirationDate: undefined, hostOnly: true, httpOnly: false, name: 'session', value: 'key', path: '/', secure: false, sameSite: undefined }) + }) + }) + it('rejects with error', function () { return this.onRequest('set:cookie', { domain: 'foo', path: '/bar' }) .then(() => { From 5fb2167bb46de170614a144d2e2d904cdb3eee03 Mon Sep 17 00:00:00 2001 From: Podles <78863563+podlesny-j@users.noreply.github.com> Date: Thu, 16 Feb 2023 20:43:55 +0100 Subject: [PATCH 2/3] fix: Fix type definitions for cy.reload() (#25779) Co-authored-by: Emily Rohrbough --- cli/CHANGELOG.md | 1 + cli/types/cypress.d.ts | 26 ++++++++++++++++++- .../get-binary-release-data.js | 11 +++++--- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 974f0ad1ab56..4db5e5cee841 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -6,6 +6,7 @@ _Released 03/1/2023 (PENDING)_ **Bugfixes:** - Fixed an issue where cookies were being duplicated with the same hostname, but a prepended dot. Fixed an issue where cookies may not be expiring correctly. Fixes [#25174](https://github.com/cypress-io/cypress/issues/25174), [#25205](https://github.com/cypress-io/cypress/issues/25205) and [#25495](https://github.com/cypress-io/cypress/issues/25495). +- Added missing TypeScript type definitions for the [`cy.reload()`](https://docs.cypress.io/api/commands/reload) command. Addressed in [#25779](https://github.com/cypress-io/cypress/pull/25779). ## 12.6.0 diff --git a/cli/types/cypress.d.ts b/cli/types/cypress.d.ts index 60ad410061e2..17a27f2b3eda 100644 --- a/cli/types/cypress.d.ts +++ b/cli/types/cypress.d.ts @@ -1816,9 +1816,21 @@ declare namespace Cypress { * * @see https://on.cypress.io/reload * @example + * cy.visit('http://localhost:3000/admin') * cy.reload() */ - reload(options?: Partial): Chainable + reload(): Chainable + /** + * Reload the page. + * + * @see https://on.cypress.io/reload + * @param {Partial} options Pass in an options object to modify the default behavior of cy.reload() + * @example + * // Reload the page, do not log it in the command log and timeout after 15s + * cy.visit('http://localhost:3000/admin') + * cy.reload({log: false, timeout: 15000}) + */ + reload(options: Partial): Chainable /** * Reload the page without cache * @@ -1830,6 +1842,18 @@ declare namespace Cypress { * cy.reload(true) */ reload(forceReload: boolean): Chainable + /** + * Reload the page without cache and with log and timeout options + * + * @see https://on.cypress.io/reload + * @param {Boolean} forceReload Whether to reload the current page without using the cache. true forces the reload without cache. + * @param {Partial} options Pass in an options object to modify the default behavior of cy.reload() + * @example + * // Reload the page without using the cache, do not log it in the command log and timeout after 15s + * cy.visit('http://localhost:3000/admin') + * cy.reload(true, {log: false, timeout: 15000}) + */ + reload(forceReload: boolean, options: Partial): Chainable /** * Make an HTTP GET request. diff --git a/scripts/semantic-commits/get-binary-release-data.js b/scripts/semantic-commits/get-binary-release-data.js index 6e5d1f9dcf63..a2a4dad8aad5 100644 --- a/scripts/semantic-commits/get-binary-release-data.js +++ b/scripts/semantic-commits/get-binary-release-data.js @@ -7,12 +7,12 @@ const { getCurrentReleaseData } = require('./get-current-release-data') const { getNextVersionForBinary } = require('../get-next-version') const { getLinkedIssues } = require('./get-linked-issues') -if (process.env.CIRCLECI && !process.env.GH_TOKEN) { - throw new Error('The GITHUB_TOKEN env is not set.') +const ensureAuth = () => { + if (!process.env.GH_TOKEN) { + throw new Error('The GH_TOKEN env is not set.') + } } -const octokit = new Octokit({ auth: process.env.GH_TOKEN }) - /** * Get the list of file names that have been added, deleted or changed since the git * sha associated with the latest tag published on npm. @@ -46,6 +46,9 @@ const getChangedFilesSinceLastRelease = (latestReleaseInfo) => { * @param {string} latestReleaseInfo.buildSha - git commit associated with published content */ const getReleaseData = async (latestReleaseInfo) => { + ensureAuth() + const octokit = new Octokit({ auth: process.env.GH_TOKEN }) + let { nextVersion, commits: semanticCommits, From af21a3a59a079ab7f4d631012d4d0c38d840d19a Mon Sep 17 00:00:00 2001 From: Adam Stone-Lord Date: Thu, 16 Feb 2023 16:53:10 -0500 Subject: [PATCH 3/3] misc: Debug header updates (#25823) --- cli/CHANGELOG.md | 4 ++++ packages/app/cypress/e2e/debug.cy.ts | 4 ++-- .../fixtures/debug-Failing/gql-Debug.json | 1 + .../fixtures/debug-Passing/gql-Debug.json | 3 ++- packages/app/src/debug/DebugPageHeader.cy.tsx | 21 ++++++++++++++++++- packages/app/src/debug/DebugPageHeader.vue | 17 +++++++++++---- 6 files changed, 42 insertions(+), 8 deletions(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 4db5e5cee841..81d87119a487 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -8,6 +8,10 @@ _Released 03/1/2023 (PENDING)_ - Fixed an issue where cookies were being duplicated with the same hostname, but a prepended dot. Fixed an issue where cookies may not be expiring correctly. Fixes [#25174](https://github.com/cypress-io/cypress/issues/25174), [#25205](https://github.com/cypress-io/cypress/issues/25205) and [#25495](https://github.com/cypress-io/cypress/issues/25495). - Added missing TypeScript type definitions for the [`cy.reload()`](https://docs.cypress.io/api/commands/reload) command. Addressed in [#25779](https://github.com/cypress-io/cypress/pull/25779). +**Misc:** + + - Made updates to the way that the Debug Page header displays information. Addresses [#25796](https://github.com/cypress-io/cypress/issues/25796) and [#25798](https://github.com/cypress-io/cypress/issues/25798). + ## 12.6.0 _Released 02/15/2023_ diff --git a/packages/app/cypress/e2e/debug.cy.ts b/packages/app/cypress/e2e/debug.cy.ts index b39c4ca19ddd..76fce07b3ac0 100644 --- a/packages/app/cypress/e2e/debug.cy.ts +++ b/packages/app/cypress/e2e/debug.cy.ts @@ -77,7 +77,7 @@ describe('App - Debug Page', () => { cy.findByTestId('debug-header-branch').contains('main') cy.findByTestId('debug-header-commitHash').contains('e9d176f') cy.findByTestId('debug-header-author').contains('Lachlan Miller') - cy.findByTestId('debug-header-createdAt').contains('01:18') + cy.findByTestId('debug-header-createdAt').contains('02h 00m 10s') }) cy.findByTestId('debug-passed').contains('Well Done!') @@ -148,7 +148,7 @@ describe('App - Debug Page', () => { cy.findByTestId('debug-header-branch').contains('main') cy.findByTestId('debug-header-commitHash').contains('commit1') cy.findByTestId('debug-header-author').contains('Lachlan Miller') - cy.findByTestId('debug-header-createdAt').contains('00:19') + cy.findByTestId('debug-header-createdAt').contains('00m 19s') }) cy.findByTestId('spec-contents').within(() => { diff --git a/packages/app/cypress/fixtures/debug-Failing/gql-Debug.json b/packages/app/cypress/fixtures/debug-Failing/gql-Debug.json index b3af1afc3f90..c522ac0f0f98 100644 --- a/packages/app/cypress/fixtures/debug-Failing/gql-Debug.json +++ b/packages/app/cypress/fixtures/debug-Failing/gql-Debug.json @@ -14,6 +14,7 @@ "commitInfo": { "sha": "commit1", "authorName": "Lachlan Miller", + "authorEmail": "hello@cypress.io", "summary": "chore: testing cypress", "branch": "main", "__typename": "CloudRunCommitInfo" diff --git a/packages/app/cypress/fixtures/debug-Passing/gql-Debug.json b/packages/app/cypress/fixtures/debug-Passing/gql-Debug.json index 770ddfb7f052..82ce76f8c171 100644 --- a/packages/app/cypress/fixtures/debug-Passing/gql-Debug.json +++ b/packages/app/cypress/fixtures/debug-Passing/gql-Debug.json @@ -10,10 +10,11 @@ "runNumber": 2, "createdAt": "2023-01-30T08:10:59.720Z", "status": "PASSED", - "totalDuration": 78898, + "totalDuration": 7210000, "commitInfo": { "sha": "e9d176f0c00c0428c9945577aec37cb6d48c5a26", "authorName": "Lachlan Miller", + "authorEmail": "asdf", "summary": "update projectId", "branch": "main", "__typename": "CloudRunCommitInfo" diff --git a/packages/app/src/debug/DebugPageHeader.cy.tsx b/packages/app/src/debug/DebugPageHeader.cy.tsx index fea3d08ea611..9ac73bbf5838 100644 --- a/packages/app/src/debug/DebugPageHeader.cy.tsx +++ b/packages/app/src/debug/DebugPageHeader.cy.tsx @@ -6,7 +6,7 @@ const defaults = [ { attr: 'debug-header-branch', text: 'Branch Name: feature/DESIGN-183' }, { attr: 'debug-header-commitHash', text: 'Commit Hash: b5e6fde' }, { attr: 'debug-header-author', text: 'Commit Author: cypressDTest' }, - { attr: 'debug-header-createdAt', text: 'Run Total Duration: 01:00 (an hour ago) ' }, + { attr: 'debug-header-createdAt', text: 'Run Total Duration: 01m 00s (an hour ago) ' }, ] describe('', { @@ -22,6 +22,7 @@ describe('', { result.commitInfo.summary = 'Adding a hover state to the button component' result.commitInfo.branch = 'feature/DESIGN-183' result.commitInfo.authorName = 'cypressDTest' + result.commitInfo.authorEmail = 'adams@cypress.io' result.commitInfo.sha = 'b5e6fde' } } @@ -144,4 +145,22 @@ describe('', { cy.findByTestId('debug-commitsAhead').should('not.exist') }) + + it('renders duration over 1 hour', () => { + cy.mountFragment(DebugPageHeaderFragmentDoc, { + onResult (result) { + if (result) { + result.totalDuration = 3602000000 + } + }, + render: (gqlVal) => { + return ( + + ) + }, + }) + + cy.findByTestId('debug-header-createdAt') + .should('have.text', 'Run Total Duration: 16h 33m 20s (an hour ago) ') + }) }) diff --git a/packages/app/src/debug/DebugPageHeader.vue b/packages/app/src/debug/DebugPageHeader.vue index 498f4e011044..8d9155884ce0 100644 --- a/packages/app/src/debug/DebugPageHeader.vue +++ b/packages/app/src/debug/DebugPageHeader.vue @@ -81,8 +81,9 @@ v-if="debug?.commitInfo?.authorName" data-cy="debug-header-author" > - Commit Author: {{ debug.commitInfo.authorName }} @@ -113,8 +114,8 @@ import CommitIcon from '~icons/cy/commit_x14' import { gql } from '@urql/core' import { dayjs } from '../runs/utils/day.js' import { useI18n } from 'vue-i18n' -import { useDurationFormat } from '../composables/useDurationFormat' import DebugRunNumber from './DebugRunNumber.vue' +import UserAvatar from '@cy/gql-components/topnav/UserAvatar.vue' const { t } = useI18n() @@ -132,6 +133,7 @@ fragment DebugPageHeader on CloudRun { ...RunResults commitInfo { authorName + authorEmail summary branch } @@ -147,7 +149,14 @@ const debug = computed(() => props.gql) const relativeCreatedAt = computed(() => dayjs(new Date(debug.value.createdAt!)).fromNow()) -const totalDuration = useDurationFormat(debug.value.totalDuration ?? 0) +/* + Format duration to in HH[h] mm[m] ss[s] format. The `totalDuration` field is milliseconds. Remove the leading "00h" if the value is less + than an hour. Currently, there is no expectation that a run duration will be greater 24 hours or greater, so it is okay that + this format would "roll-over" in that scenario. + Ex: 1 second which is 1000ms = 00m 01s + Ex: 1 hour and 1 second which is 3601000ms = 01h 00m 01s +*/ +const totalDuration = computed(() => dayjs.duration(debug.value.totalDuration ?? 0).format('HH[h] mm[m] ss[s]').replace(/^0+h /, ''))