Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
- The default `keystrokeDelay` for `cy.type()` has been changed from `10` to `0` to improve performance of typing-heavy test runs. Users who relied on the implicit 10ms delay can restore it by setting `keystrokeDelay: 10` in their Cypress config or on `Cypress.Keyboard`. Addresses [#33523](https://github.com/cypress-io/cypress/issues/33523).
- Rewrote the experimental `experimentalFastVisibility` algorithm to delegate to the browser's native `Element.checkVisibility()` with a zero-dimension guard. Addressed in [#33781](https://github.com/cypress-io/cypress/pull/33781).
- The `experimentalFastVisibility` boolean flag has been replaced with a new `visibilityStrategy` config option that accepts `'legacy'` or `'modern'`, defaulting to `'modern'`. The modern visibility algorithm (based on `Element.checkVisibility()`) is now the default for all users. The option is deprecated — set `visibilityStrategy: 'legacy'` only if you need the old algorithm when migrating to the modern visibility algorithm. Addressed in [#33794](https://github.com/cypress-io/cypress/pull/33794).
- [`cy.getCookie()`](https://on.cypress.io/getcookie) and [`cy.getCookies()`](https://on.cypress.io/getcookies) are now query commands. They re-read the cookie(s) from the browser and retry any attached assertions until they pass or the command times out, so `cy.getCookie('token').should('exist')` now waits for a cookie that is set asynchronously (for example, after a login request resolves) instead of failing on the first read. As a result, their timeout now follows `defaultCommandTimeout` (default `4000`) rather than `responseTimeout` (default `30000`), and to overwrite these commands you must now use [`Cypress.Commands.overwriteQuery()`](https://on.cypress.io/api/cypress-api/custom-queries) instead of `Cypress.Commands.overwrite()`. Addresses [#4802](https://github.com/cypress-io/cypress/issues/4802).
- [`cy.getCookie()`](https://on.cypress.io/getcookie), [`cy.getCookies()`](https://on.cypress.io/getcookies) and [`cy.getAllCookies()`](https://on.cypress.io/getallcookies) are now query commands. They re-read the cookie(s) from the browser and retry any attached assertions until they pass or the command times out, so `cy.getCookie('token').should('exist')` now waits for a cookie that is set asynchronously (for example, after a login request resolves) instead of failing on the first read. As a result, their timeout now follows `defaultCommandTimeout` (default `4000`) rather than `responseTimeout` (default `30000`), and to overwrite these commands you must now use [`Cypress.Commands.overwriteQuery()`](https://on.cypress.io/api/cypress-api/custom-queries) instead of `Cypress.Commands.overwrite()`. Addresses [#4802](https://github.com/cypress-io/cypress/issues/4802).
- Removed the `experimentalSourceRewriting` configuration option. The experimental AST-based source rewriting was removed in favor of the default regex-based source rewriting, so default behavior is unchanged. You can safely remove this option from your config. Addresses [#34213](https://github.com/cypress-io/cypress/issues/34213).

**Deprecations:**
Expand Down
26 changes: 13 additions & 13 deletions packages/driver/cypress/e2e/commands/cookies.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -880,7 +880,7 @@ describe('src/cy/commands/cookies', () => {
})

describe('timeout', () => {
it('sets timeout to Cypress.config(responseTimeout)', { responseTimeout: 2500 }, () => {
it('sets timeout to Cypress.config(defaultCommandTimeout)', { defaultCommandTimeout: 2500 }, () => {
Cypress.automation.resolves([])

const timeout = cy.spy(Promise.prototype, 'timeout')
Expand All @@ -899,20 +899,20 @@ describe('src/cy/commands/cookies', () => {
expect(timeout).to.be.calledWith(1000)
})
})
})

it('clears the current timeout and restores after success', () => {
Cypress.automation.resolves([])

cy.timeout(100)

cy.spy(cy, 'clearTimeout')
// https://github.com/cypress-io/cypress/issues/4802
it('retries reading cookies until an assertion passes', () => {
const get = Cypress.automation.withArgs('get:cookies')

cy.getAllCookies().then(() => {
expect(cy.clearTimeout).to.be.calledWith('get:cookies')
get.resolves([])
get.onCall(2).resolves([
{ name: 'foo', value: 'bar', domain: 'localhost', path: '/', secure: true, httpOnly: false, hostOnly: false },
])

// restores the timeout afterwards
expect(cy.timeout()).to.eq(100)
})
cy.getAllCookies().should('have.length', 1).then((cookies) => {
expect(cookies[0].name).to.eq('foo')
expect(get.callCount).to.be.gte(3)
})
})

Expand Down Expand Up @@ -962,7 +962,7 @@ describe('src/cy/commands/cookies', () => {
expect(lastLog.get('state')).to.eq('failed')
expect(lastLog.get('name')).to.eq('getAllCookies')
expect(lastLog.get('message')).to.eq('')
expect(err.message).to.eq('`cy.getAllCookies()` timed out waiting `50ms` to complete.')
expect(err.message).to.eq('Timed out retrying after 50ms: `cy.getAllCookies()` timed out waiting `50ms` to complete.')
expect(err.docsUrl).to.eq('https://on.cypress.io/getallcookies')

done()
Expand Down
90 changes: 50 additions & 40 deletions packages/driver/src/cy/commands/cookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,15 +199,15 @@ export default function (Commands, Cypress: InternalCypress.Cypress, cy, state,
}
}

// getCookie and getCookies are query commands: they re-pull the cookie(s)
// from the browser and re-run any attached assertions until they pass or the
// command times out. This allows `cy.getCookie('foo').should('exist')` to wait
// for a cookie that is set asynchronously (e.g. after a login request resolves).
// getCookie, getCookies and getAllCookies are query commands: they re-pull
// the cookie(s) from the browser and re-run any attached assertions until
// they pass or the command times out. This allows `cy.getCookie('foo').should('exist')`
// to wait for a cookie that is set asynchronously (e.g. after a login request resolves).
// @see https://github.com/cypress-io/cypress/issues/4802
type CookieQuery = 'get:cookie' | 'get:cookies'

interface CookieQueryParams {
commandName: 'getCookie' | 'getCookies'
commandName: 'getCookie' | 'getCookies' | 'getAllCookies'
event: CookieQuery
action: string
buildOptions: () => AutomationEventsAndOptions[CookieQuery]
Expand All @@ -217,12 +217,16 @@ export default function (Commands, Cypress: InternalCypress.Cypress, cy, state,
// long a single automation round-trip may take. defaults to
// `defaultCommandTimeout`, consistent with other query commands.
timeout: number
// getAllCookies reads cookies across all domains without touching the
// AUT's location, so it opts out of the AUT communication check and keeps
// working when the AUT is cross-origin.
checkAUTCommunication?: boolean
}

// shared retry/automation plumbing for the getCookie(s) query commands.
// returns a `fetch` function that (re-)reads cookies in the background and a
// `getSubject` function suitable for returning from a query command.
function createCookieQuery ({ commandName, event, action, buildOptions, onResult, log, timeout }: CookieQueryParams) {
function createCookieQuery ({ commandName, event, action, buildOptions, onResult, log, timeout, checkAUTCommunication = true }: CookieQueryParams) {
let hasResult = false
let result: any
let pending: Promise<void> | null = null
Expand All @@ -245,8 +249,11 @@ export default function (Commands, Cypress: InternalCypress.Cypress, cy, state,
// mismatch here is retryable - keep retrying until the AUT is reachable
// or the command times out (matching the previous
// retryIfCommandAUTOriginMismatch behavior).
// @ts-expect-error
Cypress.ensure.commandCanCommunicateWithAUT(cy)
if (checkAUTCommunication) {
// @ts-expect-error
Cypress.ensure.commandCanCommunicateWithAUT(cy)
}

automationOptions = buildOptions()
} catch (err: any) {
mostRecentError = err
Expand Down Expand Up @@ -412,44 +419,47 @@ export default function (Commands, Cypress: InternalCypress.Cypress, cy, state,
})
})

return Commands.addAll({
getAllCookies (userOptions: Partial<Cypress.Loggable & Cypress.Timeoutable> = {}) {
const options: Cypress.CookieOptions = _.defaults({}, userOptions, {
log: true,
timeout: config('responseTimeout'),
})
Commands.addQuery('getAllCookies', function getAllCookies (userOptions: Partial<Cypress.Loggable & Cypress.Timeoutable> = {}) {
Comment thread
jennifer-shehane marked this conversation as resolved.
const options: Cypress.CookieOptions = _.defaults({}, userOptions, {
log: true,
})

let cookies: Cypress.Cookie[] = []
const log: Cypress.Log | undefined = Cypress.log({
message: '',
hidden: !options.log,
timeout: options.timeout,
consoleProps () {
const obj = {}
const timeout = options.timeout || config('defaultCommandTimeout')

if (cookies.length) {
obj['Yielded'] = cookies
obj['Num Cookies'] = cookies.length
}
this.set('timeout', timeout)

return obj
},
})
let cookies: Cypress.Cookie[] = []
const log: Cypress.Log | undefined = Cypress.log({
message: '',
hidden: !options.log,
timeout,
consoleProps () {
const obj = {}

return automateCookies({
event: 'get:cookies',
commandName: 'getAllCookies',
options: {},
timeout: options.timeout!,
log,
})
.then(pickCookieProps)
.tap((result: Cypress.Cookie[]) => {
if (cookies.length) {
obj['Yielded'] = cookies
obj['Num Cookies'] = cookies.length
}

return obj
},
})

return createCookieQuery({
commandName: 'getAllCookies',
event: 'get:cookies',
action: 'reading cookies from',
buildOptions: () => ({}),
onResult: (result: Cypress.Cookie[]) => {
cookies = result
})
.catch(handleBackendError('getAllCookies', 'reading cookies from', log))
},
},
log,
timeout,
checkAUTCommunication: false,
})
})

return Commands.addAll({
setCookie (name: string, value: string, userOptions: Partial<Cypress.SetCookieOptions> = {}) {
const options: Partial<Cypress.SetCookieOptions> = _.defaults({}, userOptions, {
path: '/',
Expand Down
Loading