Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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 packages/driver/cypress/e2e/commands/cookies.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -1379,7 +1379,7 @@ describe('src/cy/commands/cookies', () => {

// @see https://bugzilla.mozilla.org/show_bug.cgi?id=1550032
// Firefox bidi returns "unspecified" for sameSite;
// webkit & firefox < 135 return "no_restriction" for sameSite;
// webkit returns "no_restriction" for sameSite;
// other browsers do not return sameSite at all
const sameSite = (
Cypress.isBrowser('webkit')
Expand Down
4 changes: 2 additions & 2 deletions packages/launcher/lib/known-browsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ const firefoxValidatorFn = (browser: FoundBrowser, platform: NodeJS.Platform): B
if (browser.majorVersion) {
const majorVersion = Number(browser.majorVersion)

if (majorVersion < 135) {
if (majorVersion < 140) {
return {
isSupported: false,
warningMessage: `Cypress does not support running ${browser.displayName} version ${browser.majorVersion} due to lack of WebDriver BiDi support. To use ${browser.displayName} with Cypress, install version 135 or newer.`,
warningMessage: `Cypress does not support running ${browser.displayName} version ${browser.majorVersion} due to an incomplete WebDriver BiDi implementation. To use ${browser.displayName} with Cypress, install version 140 or newer.`,
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions packages/launcher/test/unit/browsers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,15 @@ describe('browsers', () => {
})

FIREFOX_KNOWN_BROWSER_CHANNELS.forEach((browser) => {
it(`${browser.channel}: fails validation when Firefox major version is below 135`, () => {
it(`${browser.channel}: fails validation when Firefox major version is below 140`, () => {
// @ts-expect-error
const result = browser.validator({
majorVersion: '134',
majorVersion: '139',
displayName: 'Firefox',
})

expect(result.isSupported).toBe(false)
expect(result.warningMessage).toEqual('Cypress does not support running Firefox version 134 due to lack of WebDriver BiDi support. To use Firefox with Cypress, install version 135 or newer.')
expect(result.warningMessage).toEqual('Cypress does not support running Firefox version 139 due to an incomplete WebDriver BiDi implementation. To use Firefox with Cypress, install version 140 or newer.')
})
})
})
Expand Down
6 changes: 3 additions & 3 deletions packages/launcher/test/unit/linux.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,17 +127,17 @@ describe('linux browser detection', () => {
name: 'firefox',
family: 'firefox',
displayName: 'Firefox',
majorVersion: '135',
majorVersion: '140',
path: 'firefox',
profilePath: '/home/foo/snap/firefox/current',
version: '135.0.1',
version: '140.0.1',
}

beforeEach(() => {
cpSpawnCallback = (cmd, args, opts, cpSpawnMock) => {
if (cmd === 'firefox') {
setTimeout(() => {
cpSpawnMock.stdout.emit('data', 'Mozilla Firefox 135.0.1')
cpSpawnMock.stdout.emit('data', 'Mozilla Firefox 140.0.1')
}, 0)
}
}
Expand Down
26 changes: 11 additions & 15 deletions packages/server/lib/automation/cookie/converters/bidi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import toInteger from 'lodash/toInteger'
import isNumber from 'lodash/isNumber'
import type { NetworkCookie, NetworkSameSite } from 'webdriver/build/bidi/localTypes'
import { isHostOnlyCookie } from '../util'
import type { CyCookie } from '../util'
import type { CyCookie, ExtensionSameSiteStatus } from '../util'

const debugCookies = debugModule('cypress:server:browsers:bidi_automation:cookies')

// BiDi reports 'unspecified' where the extension shape uses `undefined`, so this widens
// CyCookie's sameSite back to the full extension vocabulary
export type BidiCyCookie = Omit<CyCookie, 'sameSite'> & {
sameSite: 'no_restriction' | 'lax' | 'strict' | 'unspecified'
sameSite: ExtensionSameSiteStatus
}

// if the filter is not an exact match OR, if looselyMatchCookiePath is enabled, doesn't include the path.
Expand All @@ -35,30 +37,24 @@ function convertSameSiteBiDiToExtension (str: NetworkSameSite | 'default') {
}

if (str === 'default') {
// put firefox version check here, under 140 we need to return 'no_restriction'
return 'unspecified'
}

return str
}

export function convertSameSiteExtensionToBiDi (str: BidiCyCookie['sameSite'], majorFirefoxVersion?: number) {
// @see https://www.w3.org/TR/webdriver-bidi/#type-network-Cookie
// BiDi expresses a cookie with no sameSite attribute as 'default'
export function convertSameSiteExtensionToBiDi (str: BidiCyCookie['sameSite']) {
if (str === 'no_restriction') {
return 'none'
}

if (str === 'unspecified') {
// put firefox version check here, under 140 we need to return 'no_restriction'
if (str === 'unspecified' || str === undefined) {
return 'default'
}

// @see https://www.w3.org/TR/webdriver-bidi/#type-network-Cookie
// in Firefox 140, BiDi added the 'default' value to be able to assign 'unspecified', which was also added in Firefox 140.
const defaultValue = majorFirefoxVersion && majorFirefoxVersion < 140 ? 'none' : 'default'

// if no value, default to 'none' as this is the browser default in firefox specifically.
// Every other browser defaults to 'lax'
return str === undefined ? defaultValue : str
return str
}

// used to normalize cookies to CyCookie before returning them through the automation client
Expand All @@ -80,7 +76,7 @@ export const convertBiDiCookieToCyCookie = (cookie: NetworkCookie): BidiCyCookie
return cyCookie
}

export const convertCyCookieToBiDiCookie = (cookie: BidiCyCookie, majorFirefoxVersion?: number): StoragePartialCookie => {
export const convertCyCookieToBiDiCookie = (cookie: BidiCyCookie): StoragePartialCookie => {
const cookieToSet: StoragePartialCookie = {
name: cookie.name,
value: {
Expand All @@ -91,7 +87,7 @@ export const convertCyCookieToBiDiCookie = (cookie: BidiCyCookie, majorFirefoxVe
path: cookie.path,
httpOnly: cookie.httpOnly,
secure: cookie.secure,
sameSite: convertSameSiteExtensionToBiDi(cookie.sameSite, majorFirefoxVersion),
sameSite: convertSameSiteExtensionToBiDi(cookie.sameSite),
// 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.
// If trying to set a float on the expiry time in BiDi, the setting silently fails.
expiry: (cookie.expirationDate === -Infinity ? 0 : (isNumber(cookie.expirationDate) ? toInteger(cookie.expirationDate) : null)) ?? undefined,
Expand Down
52 changes: 20 additions & 32 deletions packages/server/lib/automation/cookie/converters/cdp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@

import _ from 'lodash'
import type { Protocol } from 'devtools-protocol'
import { isHostOnlyCookie } from '../util'
import { isHostOnlyCookie, sameSiteExtensionToProtocolMap } from '../util'
import type { CyCookie } from '../util'

function convertSameSiteExtensionToCdp (str: CyCookie['sameSite']): Protocol.Network.CookieSameSite | undefined {
return str ? ({
'no_restriction': 'None',
'lax': 'Lax',
'strict': 'Strict',
})[str] as Protocol.Network.CookieSameSite : str as undefined
return str ? sameSiteExtensionToProtocolMap[str] : undefined
}

function convertSameSiteCdpToExtension (str: Protocol.Network.CookieSameSite): chrome.cookies.SameSiteStatus {
function convertSameSiteCdpToExtension (str: Protocol.Network.CookieSameSite | undefined): CyCookie['sameSite'] {
if (_.isUndefined(str)) {
return str
}
Expand All @@ -22,37 +18,34 @@ function convertSameSiteCdpToExtension (str: Protocol.Network.CookieSameSite): c
return 'no_restriction'
}

return str.toLowerCase() as chrome.cookies.SameSiteStatus
return str.toLowerCase() as CyCookie['sameSite']
}

const normalizeGetCookieProps = (cookie: Protocol.Network.Cookie): CyCookie => {
if (cookie.expires === -1) {
// @ts-ignore
delete cookie.expires
const convertCdpCookieToCyCookie = (cookie: Protocol.Network.Cookie): CyCookie => {
const cyCookie: CyCookie = {
name: cookie.name,
value: cookie.value,
domain: cookie.domain,
path: cookie.path,
secure: cookie.secure,
httpOnly: cookie.httpOnly,
sameSite: convertSameSiteCdpToExtension(cookie.sameSite),
// CDP signals a session cookie with a -1 expiry
expirationDate: cookie.expires === -1 ? undefined : cookie.expires,
}

if (isHostOnlyCookie(cookie)) {
// @ts-ignore
cookie.hostOnly = true
cyCookie.hostOnly = true
}

// @ts-ignore
cookie.sameSite = convertSameSiteCdpToExtension(cookie.sameSite)

// @ts-ignore
cookie.expirationDate = cookie.expires
// @ts-ignore
delete cookie.expires

// @ts-ignore
return cookie
return cyCookie
}

export const normalizeGetCookies = (cookies: Protocol.Network.Cookie[]) => {
return _.map(cookies, normalizeGetCookieProps)
export const convertCdpCookiesToCyCookies = (cookies: Protocol.Network.Cookie[]) => {
return _.map(cookies, convertCdpCookieToCyCookie)
}

export const normalizeSetCookieProps = (cookie: CyCookie): Protocol.Network.SetCookieRequest => {
export const convertCyCookieToCdpCookie = (cookie: CyCookie): Protocol.Network.SetCookieRequest => {
// this logic forms a SetCookie request that will be received by Chrome
// see MakeCookieFromProtocolValues for information on how this cookie data will be parsed
// @see https://cs.chromium.org/chromium/src/content/browser/devtools/protocol/network_handler.cc?l=246&rcl=786a9194459684dc7a6fded9cabfc0c9b9b37174
Expand Down Expand Up @@ -80,11 +73,6 @@ export const normalizeSetCookieProps = (cookie: CyCookie): Protocol.Network.SetC
setCookieRequest.domain = `.${cookie.domain}`
}

if (cookie.hostOnly && !isHostOnlyCookie(cookie)) {
// @ts-ignore
delete cookie.hostOnly
}

if (setCookieRequest.name.startsWith('__Host-')) {
setCookieRequest.url = `https://${cookie.domain}`
delete setCookieRequest.domain
Expand Down
17 changes: 6 additions & 11 deletions packages/server/lib/automation/cookie/converters/webkit.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import type playwright from 'playwright-webkit'
import { sameSiteExtensionToProtocolMap } from '../util'
import type { CyCookie } from '../util'

const extensionMap = {
'no_restriction': 'None',
'lax': 'Lax',
'strict': 'Strict',
} as const

function convertSameSiteExtensionToCypress (str: CyCookie['sameSite']): 'None' | 'Lax' | 'Strict' | undefined {
return str ? extensionMap[str] : undefined
function convertSameSiteExtensionToPlaywright (str: CyCookie['sameSite']): 'None' | 'Lax' | 'Strict' | undefined {
return str ? sameSiteExtensionToProtocolMap[str] : undefined
}

export const normalizeGetCookieProps = ({ name, value, domain, path, secure, httpOnly, sameSite, expires }: playwright.Cookie): CyCookie => {
export const convertPlaywrightCookieToCyCookie = ({ name, value, domain, path, secure, httpOnly, sameSite, expires }: playwright.Cookie): CyCookie => {
const cyCookie: CyCookie = {
name,
value,
Expand All @@ -33,7 +28,7 @@ export const normalizeGetCookieProps = ({ name, value, domain, path, secure, htt
return cyCookie
}

export const normalizeSetCookieProps = (cookie: CyCookie): playwright.Cookie => {
export const convertCyCookieToPlaywrightCookie = (cookie: CyCookie): playwright.Cookie => {
return {
name: cookie.name,
value: cookie.value,
Expand All @@ -42,6 +37,6 @@ export const normalizeSetCookieProps = (cookie: CyCookie): playwright.Cookie =>
secure: cookie.secure,
httpOnly: cookie.httpOnly,
expires: cookie.expirationDate!,
sameSite: convertSameSiteExtensionToCypress(cookie.sameSite)!,
sameSite: convertSameSiteExtensionToPlaywright(cookie.sameSite)!,
}
}
18 changes: 16 additions & 2 deletions packages/server/lib/automation/cookie/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,24 @@ import type playwright from 'playwright-webkit'
import { domainMatch, pathMatch } from 'tough-cookie'
import { parseDomain } from '@packages/network-tools'

// mirrors chrome.cookies.SameSiteStatus — the WebExtension-style sameSite vocabulary
// the automation layer speaks
export type ExtensionSameSiteStatus = 'unspecified' | 'no_restriction' | 'lax' | 'strict'

// maps the extension vocabulary to the PascalCase the protocols speak (CDP's
// Network.CookieSameSite and playwright's sameSite are the same three values)
export const sameSiteExtensionToProtocolMap = {
'no_restriction': 'None',
'lax': 'Lax',
'strict': 'Strict',
} as const

// @ts-ignore
export type CyCookie = Pick<chrome.cookies.Cookie, 'name' | 'value' | 'expirationDate' | 'hostOnly' | 'domain' | 'path' | 'secure' | 'httpOnly'> & {
export type CyCookie = Pick<chrome.cookies.Cookie, 'name' | 'value' | 'expirationDate' | 'domain' | 'path' | 'secure' | 'httpOnly'> & {
// use `undefined` instead of `unspecified`
sameSite?: 'no_restriction' | 'lax' | 'strict'
sameSite?: Exclude<ExtensionSameSiteStatus, 'unspecified'>
// unlike chrome.cookies.Cookie, hostOnly is optional — producers only set it when it applies
hostOnly?: boolean
}

// Cypress uses the webextension-style filtering
Expand Down
14 changes: 6 additions & 8 deletions packages/server/lib/browsers/bidi_automation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const normalizeResourceType = (type: RequestInitiatorType): ResourceType => {
}
}

const buildBiDiClearCookieFilterFromCyCookie = (cookie: CyCookie, majorFirefoxVersion?: number): StoragePartialCookie => {
const buildBiDiClearCookieFilterFromCyCookie = (cookie: CyCookie): StoragePartialCookie => {
const cookieToClearFilter: StoragePartialCookie = {
name: cookie.name,
value: {
Expand All @@ -91,7 +91,7 @@ const buildBiDiClearCookieFilterFromCyCookie = (cookie: CyCookie, majorFirefoxVe
path: cookie.path,
httpOnly: cookie.httpOnly,
secure: cookie.secure,
sameSite: convertSameSiteExtensionToBiDi(cookie.sameSite, majorFirefoxVersion),
sameSite: convertSameSiteExtensionToBiDi(cookie.sameSite),
}

if (!cookie.hostOnly && isHostOnlyCookie(cookie)) {
Expand Down Expand Up @@ -124,13 +124,11 @@ export class BidiAutomation {
// 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
private topLevelContextId: string | undefined = undefined
private interceptId: string | undefined = undefined
private majorFirefoxVersion: number | undefined

private constructor (webDriverClient: WebDriverClient, automation: Automation) {
debug('initializing bidi automation')
this.automation = automation
this.webDriverClient = webDriverClient
this.majorFirefoxVersion = parseInt(webDriverClient?.capabilities?.browserVersion || '') || undefined
// bind Bidi Events to update the standard automation client
// Error here is expected until webdriver adds initiatorType and destination to the request object
// @ts-expect-error
Expand Down Expand Up @@ -422,7 +420,7 @@ export class BidiAutomation {

// if it does, convert it to a BiDi cookie filter and delete the cookie
await this.webDriverClient.storageDeleteCookies({
filter: buildBiDiClearCookieFilterFromCyCookie(cookieToBeCleared, this.majorFirefoxVersion) as StorageCookieFilter,
filter: buildBiDiClearCookieFilterFromCyCookie(cookieToBeCleared) as StorageCookieFilter,
})

return cookieToBeCleared
Expand Down Expand Up @@ -464,7 +462,7 @@ export class BidiAutomation {
{
debugCookies(`set:cookie %o`, data)
await this.webDriverClient.storageSetCookie({
cookie: convertCyCookieToBiDiCookie(data, this.majorFirefoxVersion) as BidiStoragePartialCookie,
cookie: convertCyCookieToBiDiCookie(data) as BidiStoragePartialCookie,
})

const cookies = await this.getAllCookiesMatchingFilter(data)
Expand All @@ -476,7 +474,7 @@ export class BidiAutomation {
debugCookies(`add:cookies %o`, data)
await Promise.all(data.map((cookie) => {
return this.webDriverClient.storageSetCookie({
cookie: convertCyCookieToBiDiCookie(cookie, this.majorFirefoxVersion) as BidiStoragePartialCookie,
cookie: convertCyCookieToBiDiCookie(cookie) as BidiStoragePartialCookie,
})
}))

Expand All @@ -489,7 +487,7 @@ export class BidiAutomation {

await Promise.all(data.map((cookie) => {
return this.webDriverClient.storageSetCookie({
cookie: convertCyCookieToBiDiCookie(cookie, this.majorFirefoxVersion) as BidiStoragePartialCookie,
cookie: convertCyCookieToBiDiCookie(cookie) as BidiStoragePartialCookie,
})
}))

Expand Down
Loading
Loading