Skip to content

Commit 9ef239e

Browse files
committed
fix: update bidi automation client to handle unspecified cookie option in firefox and keep behavior for firefox 139 and under as the 'default' sameSite value does not exist in this version
1 parent a392407 commit 9ef239e

File tree

5 files changed

+102
-24
lines changed

5 files changed

+102
-24
lines changed

packages/server/lib/browsers/bidi_automation.ts

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ import type {
2020
BrowsingContextInfo,
2121
NetworkSameSite,
2222
} from 'webdriver/build/bidi/localTypes'
23-
import type { CyCookie } from './webkit-automation'
23+
import type { CyCookie as CyBaseCookie } from '../automation/util'
2424
import { bidiGetUrl } from '../automation/commands/get_url'
2525
import { bidiReloadFrame } from '../automation/commands/reload_frame'
2626
import { bidiNavigateHistory } from '../automation/commands/navigate_history'
2727
import { bidiGetFrameTitle } from '../automation/commands/get_frame_title'
28+
import type { StorageCookieFilter, StoragePartialCookie as BidiStoragePartialCookie } from 'webdriver/build/bidi/remoteTypes'
2829

2930
const BIDI_DEBUG_NAMESPACE = 'cypress:server:browsers:bidi_automation'
3031
const BIDI_COOKIE_DEBUG_NAMESPACE = `${BIDI_DEBUG_NAMESPACE}:cookies`
@@ -34,6 +35,10 @@ const debug = debugModule(BIDI_DEBUG_NAMESPACE)
3435
const debugCookies = debugModule(BIDI_COOKIE_DEBUG_NAMESPACE)
3536
const debugScreenshot = debugModule(BIDI_SCREENSHOT_DEBUG_NAMESPACE)
3637

38+
type CyCookie = Omit<CyBaseCookie, 'sameSite'> & {
39+
sameSite: 'no_restriction' | 'lax' | 'strict' | 'unspecified'
40+
}
41+
3742
// if the filter is not an exact match OR, if looselyMatchCookiePath is enabled, doesn't include the path.
3843
// ex: /foo/bar/baz path should include cookies for /foo/bar/baz, /foo/bar, /foo, and /
3944
// this is shipped in remoteTypes within webdriver but it isn't exported, so we need to redefine the type
@@ -48,7 +53,7 @@ interface StoragePartialCookie extends Record<string, unknown> {
4853
httpOnly: boolean
4954
hostOnly?: boolean
5055
secure: boolean
51-
sameSite: NetworkSameSite
56+
sameSite: NetworkSameSite | 'default'
5257
expiry?: number
5358
}
5459

@@ -98,22 +103,36 @@ const normalizeResourceType = (type: RequestInitiatorType): ResourceType => {
98103
}
99104
}
100105

101-
function convertSameSiteBiDiToExtension (str: NetworkSameSite) {
106+
function convertSameSiteBiDiToExtension (str: NetworkSameSite | 'default') {
102107
if (str === 'none') {
103108
return 'no_restriction'
104109
}
105110

111+
if (str === 'default') {
112+
// put firefox version check here, under 140 we need to return 'no_restriction'
113+
return 'unspecified'
114+
}
115+
106116
return str
107117
}
108118

109-
function convertSameSiteExtensionToBiDi (str: CyCookie['sameSite']) {
119+
function convertSameSiteExtensionToBiDi (str: CyCookie['sameSite'], majorFirefoxVersion?: number) {
110120
if (str === 'no_restriction') {
111121
return 'none'
112122
}
113123

124+
if (str === 'unspecified') {
125+
// put firefox version check here, under 140 we need to return 'no_restriction'
126+
return 'default'
127+
}
128+
129+
// @see https://www.w3.org/TR/webdriver-bidi/#type-network-Cookie
130+
// in Firefox 140, BiDi added the 'default' value to be able to assign 'unspecified', which was also added in Firefox 140.
131+
const defaultValue = majorFirefoxVersion && majorFirefoxVersion < 140 ? 'none' : 'default'
132+
114133
// if no value, default to 'none' as this is the browser default in firefox specifically.
115134
// Every other browser defaults to 'lax'
116-
return str === undefined ? 'none' : str
135+
return str === undefined ? defaultValue : str
117136
}
118137

119138
// used to normalize cookies to CyCookie before returning them through the automation client
@@ -135,7 +154,7 @@ const convertBiDiCookieToCyCookie = (cookie: NetworkCookie): CyCookie => {
135154
return cyCookie
136155
}
137156

138-
const convertCyCookieToBiDiCookie = (cookie: CyCookie): StoragePartialCookie => {
157+
const convertCyCookieToBiDiCookie = (cookie: CyCookie, majorFirefoxVersion?: number): StoragePartialCookie => {
139158
const cookieToSet: StoragePartialCookie = {
140159
name: cookie.name,
141160
value: {
@@ -146,7 +165,7 @@ const convertCyCookieToBiDiCookie = (cookie: CyCookie): StoragePartialCookie =>
146165
path: cookie.path,
147166
httpOnly: cookie.httpOnly,
148167
secure: cookie.secure,
149-
sameSite: convertSameSiteExtensionToBiDi(cookie.sameSite),
168+
sameSite: convertSameSiteExtensionToBiDi(cookie.sameSite, majorFirefoxVersion),
150169
// BiDi cookie expiry is in seconds from EPOCH, but sometimes the automation client feeds in a float and BiDi does not know how to handle it.
151170
// If trying to set a float on the expiry time in BiDi, the setting silently fails.
152171
expiry: (cookie.expirationDate === -Infinity ? 0 : (isNumber(cookie.expirationDate) ? toInteger(cookie.expirationDate) : null)) ?? undefined,
@@ -165,7 +184,7 @@ const convertCyCookieToBiDiCookie = (cookie: CyCookie): StoragePartialCookie =>
165184
return cookieToSet
166185
}
167186

168-
const buildBiDiClearCookieFilterFromCyCookie = (cookie: CyCookie): StoragePartialCookie => {
187+
const buildBiDiClearCookieFilterFromCyCookie = (cookie: CyCookie, majorFirefoxVersion?: number): StoragePartialCookie => {
169188
const cookieToClearFilter: StoragePartialCookie = {
170189
name: cookie.name,
171190
value: {
@@ -176,7 +195,7 @@ const buildBiDiClearCookieFilterFromCyCookie = (cookie: CyCookie): StoragePartia
176195
path: cookie.path,
177196
httpOnly: cookie.httpOnly,
178197
secure: cookie.secure,
179-
sameSite: convertSameSiteExtensionToBiDi(cookie.sameSite),
198+
sameSite: convertSameSiteExtensionToBiDi(cookie.sameSite, majorFirefoxVersion),
180199
}
181200

182201
if (!cookie.hostOnly && isHostOnlyCookie(cookie)) {
@@ -208,11 +227,13 @@ export class BidiAutomation {
208227
// set in firefox-utils when creating the webdriver session initially and in the 'reset:browser:tabs:for:next:spec' automation hook for subsequent tests when the top level context is recreated
209228
private topLevelContextId: string | undefined = undefined
210229
private interceptId: string | undefined = undefined
230+
private majorFirefoxVersion: number | undefined
211231

212232
private constructor (webDriverClient: WebDriverClient, automation: Automation) {
213233
debug('initializing bidi automation')
214234
this.automation = automation
215235
this.webDriverClient = webDriverClient
236+
this.majorFirefoxVersion = parseInt(webDriverClient?.capabilities?.browserVersion || '') || undefined
216237
// bind Bidi Events to update the standard automation client
217238
// Error here is expected until webdriver adds initiatorType and destination to the request object
218239
// @ts-expect-error
@@ -464,7 +485,7 @@ export class BidiAutomation {
464485
// because of the above comment on the BiDi API, we get ALL cookies not filtering by domain
465486
// (name filter is safe to reduce the payload coming back)
466487
// and filter out all cookies that apply to the given domain, path, and name (which should already be done)
467-
const filteredCookies = normalizedCookies.filter((cookie) => cookieMatches(cookie, filter))
488+
const filteredCookies = normalizedCookies.filter((cookie) => cookieMatches(cookie as CyBaseCookie, filter))
468489

469490
debugCookies(`filtered additional cookies based on domain, path, or name: %o`, filteredCookies)
470491

@@ -495,7 +516,7 @@ export class BidiAutomation {
495516

496517
// if it does, convert it to a BiDi cookie filter and delete the cookie
497518
await this.webDriverClient.storageDeleteCookies({
498-
filter: buildBiDiClearCookieFilterFromCyCookie(cookieToBeCleared),
519+
filter: buildBiDiClearCookieFilterFromCyCookie(cookieToBeCleared, this.majorFirefoxVersion) as StorageCookieFilter,
499520
})
500521

501522
return cookieToBeCleared
@@ -537,7 +558,7 @@ export class BidiAutomation {
537558
{
538559
debugCookies(`set:cookie %o`, data)
539560
await this.webDriverClient.storageSetCookie({
540-
cookie: convertCyCookieToBiDiCookie(data),
561+
cookie: convertCyCookieToBiDiCookie(data, this.majorFirefoxVersion) as BidiStoragePartialCookie,
541562
})
542563

543564
const cookies = await this.getAllCookiesMatchingFilter(data)
@@ -549,7 +570,7 @@ export class BidiAutomation {
549570
debugCookies(`add:cookies %o`, data)
550571
await Promise.all(data.map((cookie) => {
551572
return this.webDriverClient.storageSetCookie({
552-
cookie: convertCyCookieToBiDiCookie(cookie),
573+
cookie: convertCyCookieToBiDiCookie(cookie, this.majorFirefoxVersion) as BidiStoragePartialCookie,
553574
})
554575
}))
555576

@@ -562,7 +583,7 @@ export class BidiAutomation {
562583

563584
await Promise.all(data.map((cookie) => {
564585
return this.webDriverClient.storageSetCookie({
565-
cookie: convertCyCookieToBiDiCookie(cookie),
586+
cookie: convertCyCookieToBiDiCookie(cookie, this.majorFirefoxVersion) as BidiStoragePartialCookie,
566587
})
567588
}))
568589

packages/server/lib/browsers/cdp_automation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ function convertSameSiteExtensionToCdp (str: CyCookie['sameSite']): Protocol.Net
3939
'no_restriction': 'None',
4040
'lax': 'Lax',
4141
'strict': 'Strict',
42-
})[str] : str as any
42+
})[str] as Protocol.Network.CookieSameSite : str as undefined
4343
}
4444

4545
function convertSameSiteCdpToExtension (str: Protocol.Network.CookieSameSite): chrome.cookies.SameSiteStatus {

packages/server/lib/browsers/webkit-automation.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,10 @@ import path from 'path'
88
import mime from 'mime'
99
import { cookieMatches, CyCookieFilter } from '../automation/util'
1010
import utils from './utils'
11+
import type { CyCookie } from '../automation/util'
1112

1213
const debug = Debug('cypress:server:browsers:webkit-automation')
1314

14-
export type CyCookie = Pick<chrome.cookies.Cookie, 'name' | 'value' | 'expirationDate' | 'hostOnly' | 'domain' | 'path' | 'secure' | 'httpOnly'> & {
15-
// use `undefined` instead of `unspecified`
16-
sameSite?: 'no_restriction' | 'lax' | 'strict'
17-
}
18-
1915
const extensionMap = {
2016
'no_restriction': 'None',
2117
'lax': 'Lax',

packages/server/test/unit/browsers/bidi_automation_spec.ts

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,7 +1295,7 @@ describe('lib/browsers/bidi_automation', () => {
12951295

12961296
describe('parsing', () => {
12971297
// NOTE: unique to Firefox. Chromium defaults to 'lax'
1298-
it('defaults sameSite to "none"', async () => {
1298+
it('defaults sameSite to "default"', async () => {
12991299
const cyCookie = {
13001300
name: 'testCookie',
13011301
value: 'testValue',
@@ -1314,7 +1314,7 @@ describe('lib/browsers/bidi_automation', () => {
13141314
expiry: undefined,
13151315
name: 'testCookie',
13161316
path: '/',
1317-
sameSite: 'none',
1317+
sameSite: 'default',
13181318
secure: true,
13191319
size: 10,
13201320
value: {
@@ -1326,6 +1326,67 @@ describe('lib/browsers/bidi_automation', () => {
13261326

13271327
const cookie = await bidiAutomationInstance.automationMiddleware.onRequest('set:cookie', cyCookie)
13281328

1329+
expect(mockWebdriverClient.storageSetCookie).to.have.been.calledWith({
1330+
cookie: {
1331+
name: 'testCookie',
1332+
value: { type: 'string', value: 'testValue' },
1333+
domain: '.foobar.com',
1334+
path: '/',
1335+
httpOnly: true,
1336+
secure: true,
1337+
sameSite: 'default',
1338+
expiry: undefined,
1339+
},
1340+
})
1341+
1342+
expect(cookie).to.deep.equal({
1343+
name: 'testCookie',
1344+
value: 'testValue',
1345+
domain: '.foobar.com',
1346+
path: '/',
1347+
secure: true,
1348+
httpOnly: true,
1349+
hostOnly: false,
1350+
sameSite: 'unspecified',
1351+
expirationDate: undefined,
1352+
})
1353+
})
1354+
1355+
it('defaults sameSite to "none" on Firefox 139 and under', async () => {
1356+
const cyCookie = {
1357+
name: 'testCookie',
1358+
value: 'testValue',
1359+
domain: '.foobar.com',
1360+
path: '/',
1361+
secure: true,
1362+
httpOnly: true,
1363+
}
1364+
1365+
mockWebdriverClient.storageSetCookie = sinon.stub().resolves()
1366+
1367+
mockWebdriverClient.storageGetCookies = sinon.stub().resolves({
1368+
cookies: [{
1369+
domain: '.foobar.com',
1370+
httpOnly: true,
1371+
expiry: undefined,
1372+
name: 'testCookie',
1373+
path: '/',
1374+
sameSite: 'no_restriction',
1375+
secure: true,
1376+
size: 10,
1377+
value: {
1378+
type: 'string',
1379+
value: 'testValue',
1380+
},
1381+
}],
1382+
})
1383+
1384+
// force firefox 139
1385+
// @ts-expect-error
1386+
bidiAutomationInstance.majorFirefoxVersion = 139
1387+
1388+
const cookie = await bidiAutomationInstance.automationMiddleware.onRequest('set:cookie', cyCookie)
1389+
13291390
expect(mockWebdriverClient.storageSetCookie).to.have.been.calledWith({
13301391
cookie: {
13311392
name: 'testCookie',

system-tests/projects/e2e/cypress/e2e/cookies_spec_baseurl.cy.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ const otherHttpsUrl = Cypress.env('otherHttpsUrl')
1111
let defaultSameSite = undefined
1212

1313
if (Cypress.isBrowser('firefox')) {
14-
// firefox will default to "unspecified"
14+
// firefox will default to "unspecified" on firefox 140 and up. Versions below this will default to 'no_restriction'.
1515
// @see https://bugzilla.mozilla.org/show_bug.cgi?id=1624668
16-
defaultSameSite = 'unspecified'
16+
defaultSameSite = Cypress.browser.majorVersion < 140 ? 'no_restriction' : 'unspecified'
1717
}
1818

1919
describe('cookies', () => {

0 commit comments

Comments
 (0)