breaking: remove CDP from firefox with Cypress 15 (#31200)

* chore: updating v8 snapshot cache (#31422)

Co-authored-by: cypress-bot[bot] <+cypress-bot[bot]@users.noreply.github.com>

* breaking: remove CDP from firefox with Cypress 15 [run ci]

* chore: code review updates

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: cypress-bot[bot] <+cypress-bot[bot]@users.noreply.github.com>
This commit is contained in:
Bill Glesias
2025-04-03 13:42:04 -04:00
committed by GitHub
parent 534324fe23
commit 25f0fd0a11
17 changed files with 136 additions and 727 deletions

View File

@@ -656,11 +656,6 @@ commands:
description: run subset of tests with injectDocumentDomain config enabled
type: boolean
default: false
is-firefox-cdp:
description: whether or not the group should be associated to the firefox CDP
run or not. This is determined by the browser version.
type: boolean
default: false
steps:
- restore_cached_workspace
@@ -701,9 +696,6 @@ commands:
if << parameters.inject-document-domain >> ; then
YARN_CMD="cypress:run:inject-document-domain"
PARALLEL="--parallel --group 5x-driver-inject-document-domain-<<parameters.browser>>"
elif << parameters.is-firefox-cdp >> ; then
YARN_CMD="cypress:run"
PARALLEL="--parallel --group 5x-driver-cdp-<<parameters.browser>>"
else
YARN_CMD="cypress:run"
PARALLEL="--parallel --group 5x-driver-<<parameters.browser>>"
@@ -2136,18 +2128,6 @@ jobs:
- run-driver-integration-tests:
browser: firefox
# Runs the driver tests using firefox 134, which does NOT use WebDriver BiDi
# This is to test and make sure there aren't regressions with the old CDP driver
driver-integration-tests-firefox-cdp:
<<: *defaults
resource_class: medium+
parallelism: 5
steps:
- run-driver-integration-tests:
browser: firefox
firefox-version: "134.0.2"
is-firefox-cdp: true
driver-integration-tests-electron:
<<: *defaults
parallelism: 5
@@ -2888,7 +2868,6 @@ linux-x64-workflow: &linux-x64-workflow
- run-webpack-dev-server-integration-tests
- run-vite-dev-server-integration-tests
- driver-integration-tests-firefox
- driver-integration-tests-firefox-cdp
- driver-integration-tests-chrome
- driver-integration-tests-chrome-inject-document-domain
- driver-integration-tests-chrome-beta-inject-document-domain
@@ -2964,10 +2943,6 @@ linux-x64-workflow: &linux-x64-workflow
context: test-runner:cypress-record-key
requires:
- build
- driver-integration-tests-firefox-cdp:
context: test-runner:cypress-record-key
requires:
- build
- driver-integration-tests-electron:
context: test-runner:cypress-record-key
requires:
@@ -3110,7 +3085,6 @@ linux-x64-workflow: &linux-x64-workflow
- linux-lint
- percy-finalize
- driver-integration-tests-firefox
- driver-integration-tests-firefox-cdp
- driver-integration-tests-chrome
- driver-integration-tests-chrome-beta
- driver-integration-tests-chrome-inject-document-domain
@@ -3366,10 +3340,6 @@ linux-x64-contributor-workflow: &linux-x64-contributor-workflow
context: test-runner:cypress-record-key
requires:
- contributor-pr
- driver-integration-tests-firefox-cdp:
context: test-runner:cypress-record-key
requires:
- contributor-pr
- driver-integration-tests-electron:
context: test-runner:cypress-record-key
requires:
@@ -3511,7 +3481,6 @@ linux-x64-contributor-workflow: &linux-x64-contributor-workflow
- linux-lint
- percy-finalize
- driver-integration-tests-firefox
- driver-integration-tests-firefox-cdp
- driver-integration-tests-chrome
- driver-integration-tests-chrome-beta
- driver-integration-tests-electron

View File

@@ -1,11 +1,12 @@
<!-- See the ../guides/writing-the-cypress-changelog.md for details on writing the changelog. -->
## 15.0.0
_Released 7/1/2025 (PENDING)_
_Released 07/01/2025 (PENDING)_
**Breaking Changes:**
- Removed support for Node.js 18 and Node.js 23. Addresses [#31302](https://github.com/cypress-io/cypress/issues/31302).
- Removed support for [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol) with the [firefox](https://www.mozilla.org/) browser. Addresses [#31189](https://github.com/cypress-io/cypress/issues/31189).
## 14.2.2

View File

@@ -1193,9 +1193,6 @@ export const AllCypressErrors = {
CDP_RETRYING_CONNECTION: (attempt: string | number, browserName: string, connectRetryThreshold: number) => {
return errTemplate`Still waiting to connect to ${fmt.off(_.capitalize(browserName))}, retrying in 1 second ${fmt.meta(`(attempt ${attempt}/${connectRetryThreshold})`)}`
},
CDP_FIREFOX_DEPRECATED: () => {
return errTemplate`Since Firefox 129, Chrome DevTools Protocol (CDP) has been deprecated in Firefox. In Firefox 135 and above, Cypress defaults to automating the Firefox browser with WebDriver BiDi. Cypress will no longer support CDP within Firefox in the future and is planned for removal in Cypress 15.`
},
BROWSER_PROCESS_CLOSED_UNEXPECTEDLY: (browserName: string) => {
return errTemplate`\
We detected that the ${fmt.highlight(browserName)} browser process closed unexpectedly.

View File

@@ -1112,11 +1112,6 @@ describe('visual error templates', () => {
default: [1, 'chrome', 62],
}
},
CDP_FIREFOX_DEPRECATED: () => {
return {
default: [],
}
},
BROWSER_PROCESS_CLOSED_UNEXPECTEDLY: () => {
return {
default: ['chrome'],

View File

@@ -31,23 +31,6 @@ const checkIfFirefox = async () => {
return name === 'Firefox'
}
// this check only applies to firefox versioning!
const isBiDiEnabled = async (config) => {
if (!browser || !get(browser, 'runtime.getBrowserInfo') || config.IS_CDP_FORCED_FOR_FIREFOX) {
return false
}
const { version } = await browser.runtime.getBrowserInfo()
if (version) {
const [majorVersion] = version.split('.').map(Number)
return majorVersion >= 135
}
return false
}
const connect = function (host, path, extraOpts) {
const listenToCookieChanges = once(() => {
return browser.cookies.onChanged.addListener((info) => {
@@ -84,30 +67,6 @@ const connect = function (host, path, extraOpts) {
})
})
const listenToOnBeforeHeaders = once(() => {
// adds a header to the request to mark it as a request for the AUT frame
// itself, so the proxy can utilize that for injection purposes
browser.webRequest.onBeforeSendHeaders.addListener((details) => {
if (
// parentFrameId: 0 means the parent is the top-level, so if it isn't
// 0, it's nested inside the AUT and can't be the AUT itself
details.parentFrameId !== 0
// is the spec frame, not the AUT
|| details.url.includes('__cypress')
) return
return {
requestHeaders: [
...details.requestHeaders,
{
name: 'X-Cypress-Is-AUT-Frame',
value: 'true',
},
],
}
}, { urls: ['<all_urls>'], types: ['sub_frame'] }, ['blocking', 'requestHeaders'])
})
const fail = (id, err) => {
return ws.emit('automation:response', id, {
__error: err.message,
@@ -167,13 +126,6 @@ const connect = function (host, path, extraOpts) {
if (isFirefox) {
// Non-Firefox browsers use CDP for this instead
listenToDownloads()
// if BiDi is enabled, BiDi will handle the network interception.
// Otherwise, CDP does not support it for Firefox and we need to listen for it here.
const isBiDiTurnedOn = await isBiDiEnabled(config)
if (!isBiDiTurnedOn) {
listenToOnBeforeHeaders()
}
}
})

View File

@@ -293,120 +293,6 @@ describe('app/background', () => {
})
})
context('add header to aut iframe requests', () => {
beforeEach(() => {
browser.runtime.getBrowserInfo = sinon.stub().resolves({ name: 'Firefox', version: '135.0.1' })
})
it('allows for CDP to be used as an escape hatch if BiDi would otherwise be enabled', async function () {
sinon.stub(browser.webRequest.onBeforeSendHeaders, 'addListener')
await this.connect({
IS_CDP_FORCED_FOR_FIREFOX: true,
})
expect(browser.webRequest.onBeforeSendHeaders.addListener).to.be.called
})
context('BiDi enabled', () => {
it('does not attach onBeforeSendHeaders listener if BiDi is enabled', async function () {
sinon.stub(browser.webRequest.onBeforeSendHeaders, 'addListener')
await this.connect()
expect(browser.webRequest.onBeforeSendHeaders.addListener).not.to.be.called
})
})
context('CDP enabled', () => {
beforeEach(() => {
browser.runtime.getBrowserInfo = sinon.stub().resolves({ name: 'Firefox', version: '134' })
})
it('does not add header if it is the top frame', async function () {
const details = {
parentFrameId: -1,
}
sinon.stub(browser.webRequest.onBeforeSendHeaders, 'addListener')
await this.connect()
const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details)
expect(result).to.be.undefined
})
it('does not add header if it is a nested frame', async function () {
const details = {
parentFrameId: 12345,
}
sinon.stub(browser.webRequest.onBeforeSendHeaders, 'addListener')
await this.connect()
const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details)
expect(result).to.be.undefined
})
it('does not add header if it is a spec frame request', async function () {
const details = {
parentFrameId: 0,
type: 'sub_frame',
url: '/__cypress/integration/spec.js',
}
sinon.stub(browser.webRequest.onBeforeSendHeaders, 'addListener')
await this.connect()
const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details)
expect(result).to.be.undefined
})
it('appends X-Cypress-Is-AUT-Frame header to AUT iframe request', async function () {
const details = {
parentFrameId: 0,
type: 'sub_frame',
url: 'http://localhost:3000/index.html',
requestHeaders: [
{ name: 'X-Foo', value: 'Bar' },
],
}
sinon.stub(browser.webRequest.onBeforeSendHeaders, 'addListener')
await this.connect()
const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details)
expect(result).to.deep.equal({
requestHeaders: [
{
name: 'X-Foo',
value: 'Bar',
},
{
name: 'X-Cypress-Is-AUT-Frame',
value: 'true',
},
],
})
})
it('does not add before-headers listener if in non-Firefox browser', async function () {
browser.runtime.getBrowserInfo = undefined
const onBeforeSendHeaders = sinon.stub(browser.webRequest.onBeforeSendHeaders, 'addListener')
await this.connect()
expect(onBeforeSendHeaders).not.to.be.called
})
})
})
context('.getAll', () => {
it('resolves with specific cookie properties', () => {
sinon.stub(browser.cookies, 'getAll').resolves([

View File

@@ -1129,7 +1129,6 @@ enum ErrorTypeEnum {
CANNOT_TRASH_ASSETS
CDP_COULD_NOT_CONNECT
CDP_COULD_NOT_RECONNECT
CDP_FIREFOX_DEPRECATED
CDP_RETRYING_CONNECTION
CDP_VERSION_TOO_OLD
CHROME_WEB_SECURITY_NOT_SUPPORTED

View File

@@ -1,5 +1,24 @@
import type { Browser, BrowserValidatorResult, FoundBrowser } from '@packages/types'
const firefoxValidatorFn = (browser: FoundBrowser, platform: NodeJS.Platform): BrowserValidatorResult => {
try {
if (browser.majorVersion) {
const majorVersion = Number(browser.majorVersion)
if (majorVersion < 135) {
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.`,
}
}
}
} catch (e) { /* empty */ }
return {
isSupported: true,
}
}
/** list of the browsers we can detect and use by default */
export const knownBrowsers: Browser[] = [
{
@@ -62,9 +81,10 @@ export const knownBrowsers: Browser[] = [
family: 'firefox',
channel: 'stable',
displayName: 'Firefox',
// Mozilla Firefox 70.0.1
// Mozilla Firefox 135.0.1
versionRegex: /^Mozilla Firefox ([^\sab]+)$/m,
binary: 'firefox',
validator: firefoxValidatorFn,
},
{
name: 'firefox',
@@ -75,6 +95,7 @@ export const knownBrowsers: Browser[] = [
versionRegex: /^Mozilla Firefox (\S+b\S*)$/m,
// ubuntu PPAs install it as firefox
binary: ['firefox-developer-edition', 'firefox'],
validator: firefoxValidatorFn,
},
{
name: 'firefox',
@@ -85,6 +106,7 @@ export const knownBrowsers: Browser[] = [
versionRegex: /^Mozilla Firefox (\S+a\S*)$/m,
// ubuntu PPAs install it as firefox-trunk
binary: ['firefox-nightly', 'firefox-trunk'],
validator: firefoxValidatorFn,
},
{
name: 'edge',

View File

@@ -71,6 +71,25 @@ describe('browsers', () => {
expect(result.isSupported).to.be.true
expect(result.warningMessage).to.be.undefined
})
describe('firefox validation', () => {
const FIREFOX_KNOWN_BROWSER_CHANNELS = knownBrowsers.filter((browser) => {
return browser.family === 'firefox'
})
FIREFOX_KNOWN_BROWSER_CHANNELS.forEach((browser) => {
it(`${browser.channel}: fails validation when Firefox major version is below 135`, () => {
// @ts-expect-error
const result = browser.validator({
majorVersion: '134',
displayName: 'Firefox',
})
expect(result.isSupported).to.be.false
expect(result.warningMessage).to.equal('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.')
})
})
})
})
})
})

View File

@@ -85,15 +85,15 @@ describe('linux browser detection', () => {
name: 'firefox',
family: 'firefox',
displayName: 'Firefox',
majorVersion: '99',
majorVersion: '135',
path: 'firefox',
profilePath: '/home/foo/snap/firefox/current',
version: '99.2.3',
version: '135.0.1',
}
beforeEach(() => {
execa.withArgs('firefox', ['--version'])
.resolves({ stdout: 'Mozilla Firefox 99.2.3' })
.resolves({ stdout: 'Mozilla Firefox 135.0.1' })
sinon.stub(os, 'homedir').returns('/home/foo')
})

View File

@@ -1,10 +1,7 @@
import Debug from 'debug'
import { CdpAutomation } from './cdp_automation'
import { BidiAutomation } from './bidi_automation'
import { BrowserCriClient } from './browser-cri-client'
import type { Client as WebDriverClient } from 'webdriver'
import type { Automation } from '../automation'
import type { CypressError } from '@packages/errors'
const debug = Debug('cypress:server:browsers:firefox-util')
@@ -27,36 +24,6 @@ async function connectToNewSpecBiDi (options, automation: Automation, browserBiD
})
}
async function connectToNewSpecCDP (options, automation: Automation, browserCriClient: BrowserCriClient) {
debug('firefox: reconnecting to blank tab')
// Firefox keeps a blank tab open in versions of Firefox 123 and lower when the last tab is closed.
// For versions 124 and above, a new tab is not created, so @packages/extension creates one for us.
// Since the tab is always available on our behalf,
// we can connect to it here and navigate it to about:blank to set it up for CDP connection
const handles = await webdriverClient.getWindowHandles()
await webdriverClient.switchToWindow(handles[0])
await webdriverClient.navigateTo('about:blank')
debug('firefox: reconnecting CDP')
if (browserCriClient) {
await browserCriClient.currentlyAttachedTarget?.close().catch(() => {})
// Strictly speaking this shouldn't ever happen in firefox, but to future proof adding it in case.
await browserCriClient.currentlyAttachedProtocolTarget?.close().catch(() => {})
const pageCriClient = await browserCriClient.attachToTargetUrl('about:blank')
await CdpAutomation.create(pageCriClient.send, pageCriClient.on, pageCriClient.off, browserCriClient.resetBrowserTargets, automation)
}
await options.onInitializeNewBrowserTab()
debug(`firefox: navigating to ${options.url}`)
await webdriverClient.navigateTo(options.url)
}
async function setupBiDi (webdriverClient: WebDriverClient, automation: Automation) {
// webdriver needs to subscribe to the correct BiDi events or else the events we are expecting to stream in will not be sent
await webdriverClient.sessionSubscribe({ events: BidiAutomation.BIDI_EVENTS })
@@ -66,63 +33,39 @@ async function setupBiDi (webdriverClient: WebDriverClient, automation: Automati
return biDiClient
}
async function setupCDP (remotePort: number, automation: Automation, onError?: (err: Error) => void): Promise<BrowserCriClient> {
const browserCriClient = await BrowserCriClient.create({ hosts: ['127.0.0.1', '::1'], port: remotePort, browserName: 'Firefox', onAsynchronousError: onError as (err: CypressError) => void, onServiceWorkerClientEvent: automation.onServiceWorkerClientEvent })
const pageCriClient = await browserCriClient.attachToTargetUrl('about:blank')
await CdpAutomation.create(pageCriClient.send, pageCriClient.on, pageCriClient.off, browserCriClient.resetBrowserTargets, automation)
return browserCriClient
}
export default {
async setup ({
automation,
onError,
url,
remotePort,
webdriverClient: wdInstance,
useWebDriverBiDi,
}: {
automation: Automation
onError?: (err: Error) => void
url: string
remotePort: number | undefined
webdriverClient: WebDriverClient
useWebDriverBiDi: boolean
}): Promise<BrowserCriClient | BidiAutomation> {
}): Promise<BidiAutomation> {
// set the WebDriver classic instance instantiated from geckodriver
webdriverClient = wdInstance
let client: BrowserCriClient | BidiAutomation
let client: BidiAutomation
if (useWebDriverBiDi) {
client = await setupBiDi(webdriverClient, automation)
// use the BiDi commands to visit the url as opposed to classic webdriver
const { contexts } = await webdriverClient.browsingContextGetTree({})
client = await setupBiDi(webdriverClient, automation)
// use the BiDi commands to visit the url as opposed to classic webdriver
const { contexts } = await webdriverClient.browsingContextGetTree({})
// at this point there should only be one context: the top level context.
// we need to set this to bind our AUT intercepts correctly. Hopefully we can move this in the future on a more sure implementation
client.setTopLevelContextId(contexts[0].context)
// at this point there should only be one context: the top level context.
// we need to set this to bind our AUT intercepts correctly. Hopefully we can move this in the future on a more sure implementation
client.setTopLevelContextId(contexts[0].context)
await webdriverClient.browsingContextNavigate({
context: contexts[0].context,
url,
})
} else {
client = await setupCDP(remotePort as number, automation, onError)
// uses webdriver classic to navigate
await webdriverClient.navigateTo(url)
}
await webdriverClient.browsingContextNavigate({
context: contexts[0].context,
url,
})
return client
},
connectToNewSpecBiDi,
connectToNewSpecCDP,
setupBiDi,
setupCDP,
}

View File

@@ -14,28 +14,15 @@ import utils from './utils'
import type { Browser, BrowserInstance, GracefulShutdownOptions } from './types'
import os from 'os'
import mimeDb from 'mime-db'
import { BrowserCriClient } from './browser-cri-client'
import type { BidiAutomation } from './bidi_automation'
import type { Automation } from '../automation'
import { getCtx } from '@packages/data-context'
import { getError, SerializedError, CypressError } from '@packages/errors'
import { getError, CypressError } from '@packages/errors'
import type { BrowserLaunchOpts, BrowserNewTabOpts, RunModeVideoApi } from '@packages/types'
import type { RemoteConfig } from 'webdriver'
import type { GeckodriverParameters } from 'geckodriver'
import { WebDriver } from './webdriver'
export class CDPFailedToStartFirefox extends Error {
private static readonly ERROR_NAME = 'CDPFailedToStartFirefox'
constructor (message) {
super(message)
this.name = CDPFailedToStartFirefox.ERROR_NAME
}
public static isCDPFailedToStartFirefoxError (error?: SerializedError): error is CDPFailedToStartFirefox {
return error?.name === CDPFailedToStartFirefox.ERROR_NAME
}
}
const debug = Debug('cypress:server:browsers:firefox')
const debugVerbose = Debug('cypress-verbose:server:browsers:firefox')
@@ -335,20 +322,18 @@ const defaultPreferences = {
'browser.helperApps.neverAsk.saveToDisk': downloadMimeTypes,
}
// CDP is deprecated in Firefox 129 and up.
// CDP was deprecated in Firefox 129 and up and was removed in Firefox 141.
// To enable BiDi (without CDP), we need to set
// remote.active-protocol=1
// In order to enable CDP (without BiDi), we need to set
// Cypress no longer supports CDP within Firefox. However, it can be enabled if needed (but only on Firefox 141 and lower) by setting
// remote.active-protocol=2
// both can be enabled via
// remote.active-protocol=3
// @see https://fxdx.dev/deprecating-cdp-support-in-firefox-embracing-the-future-with-webdriver-bidi/
// @see https://fxdx.dev/webdriver-bidi-becomes-the-default-for-cypress-in-firefox/
// @see https://github.com/cypress-io/cypress/issues/29713
const ACTIVE_PROTOCOLS = Object.freeze({
BIDI: 1,
CDP: 2,
// this key isn't actively used but checked in here if we need to turn it on for internal debugging
CDP_AND_BIDI: 3,
})
const FIREFOX_HEADED_USERCSS = `\
@@ -387,7 +372,6 @@ toolbox {
}
`
let browserCriClient: BrowserCriClient | undefined
let browserBidiClient: BidiAutomation | undefined
/**
@@ -396,12 +380,6 @@ let browserBidiClient: BidiAutomation | undefined
export function clearInstanceState (options: GracefulShutdownOptions = {}) {
debug('clearing instance state')
if (browserCriClient) {
debug('closing remote interface client')
browserCriClient.close(options.gracefulShutdown).catch(() => {})
browserCriClient = undefined
}
if (browserBidiClient) {
debug('unbinding bidi client events')
browserBidiClient.close()
@@ -409,21 +387,8 @@ export function clearInstanceState (options: GracefulShutdownOptions = {}) {
}
}
function shouldUseBiDi (browser: Browser): boolean {
try {
// Gating on firefox version 135 to turn on BiDi as this is when all of our internal Cypress tests were able to pass.
return (browser.family === 'firefox' && !process.env.FORCE_FIREFOX_CDP && Number(browser.majorVersion) >= 135)
} catch (err: unknown) {
return false
}
}
export async function connectToNewSpec (browser: Browser, options: BrowserNewTabOpts, automation: Automation) {
if (shouldUseBiDi(browser)) {
await firefoxUtil.connectToNewSpecBiDi(options, automation, browserBidiClient!)
} else {
await firefoxUtil.connectToNewSpecCDP(options, automation, browserCriClient!)
}
await firefoxUtil.connectToNewSpecBiDi(options, automation, browserBidiClient!)
}
export function connectToExisting () {
@@ -445,12 +410,6 @@ async function recordVideo (videoApi: RunModeVideoApi) {
}
export async function open (browser: Browser, url: string, options: BrowserLaunchOpts, automation: Automation): Promise<BrowserInstance> {
const USE_WEBDRIVER_BIDI = shouldUseBiDi(browser)
if (!USE_WEBDRIVER_BIDI) {
errors.warning('CDP_FIREFOX_DEPRECATED')
}
const defaultLaunchOptions = utils.getDefaultLaunchOptions({
extensions: [] as string[],
preferences: _.extend({}, defaultPreferences),
@@ -463,7 +422,7 @@ export async function open (browser: Browser, url: string, options: BrowserLaunc
],
})
defaultLaunchOptions.preferences['remote.active-protocols'] = USE_WEBDRIVER_BIDI ? ACTIVE_PROTOCOLS.BIDI : ACTIVE_PROTOCOLS.CDP
defaultLaunchOptions.preferences['remote.active-protocols'] = ACTIVE_PROTOCOLS.BIDI
if (browser.isHeadless) {
defaultLaunchOptions.args.push('-headless')
@@ -558,26 +517,6 @@ export async function open (browser: Browser, url: string, options: BrowserLaunc
debug('firefox directories %o', { path: profile.path(), cacheDir, extensionDest })
const xulStorePath = path.join(profile.path(), 'xulstore.json')
// if user has set custom window.sizemode pref or it's the first time launching on this profile, write to xulStore.
if (!await fs.pathExists(xulStorePath)) {
// this causes the browser to launch maximized, which chrome does by default
// otherwise an arbitrary size will be picked for the window size
// this used to not have an effect after first launch in 'interactive' mode.
// However, since Cypress 13.15.1,
// geckodriver creates unique profile names that copy over the xulstore.json to the used profile.
// The copy is ultimately updated on the unique profile name and is destroyed when the browser is torn down,
// so the values are not persisted. Cypress could hypothetically determine the profile is in use, copy the xulstore.json
// out of the profile and try to persist it in the next created profile, but this method is likely error prone as it requires
// moving/copying of files while creation/deletion of profiles occur, plus the ability to correlate the correct profile to the current run,
// which there are not guarantees we can deterministically do this in open mode.
const sizemode = 'maximized'
await fs.writeJSON(xulStorePath, { 'chrome://browser/content/browser.xhtml': { 'main-window': { 'width': 1280, 'height': 1024, sizemode } } })
}
launchOptions.preferences['browser.cache.disk.parent_directory'] = cacheDir
const userCSSPath = path.join(profileDir, 'chrome')
@@ -669,7 +608,7 @@ export async function open (browser: Browser, url: string, options: BrowserLaunc
alwaysMatch: {
browserName: 'firefox',
acceptInsecureCerts: true,
webSocketUrl: USE_WEBDRIVER_BIDI,
webSocketUrl: true,
// @see https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities/firefoxOptions
'moz:firefoxOptions': {
profile: base64EncodedProfile,
@@ -677,9 +616,6 @@ export async function open (browser: Browser, url: string, options: BrowserLaunc
args: launchOptions.args,
prefs: launchOptions.preferences,
},
// @see https://firefox-source-docs.mozilla.org/testing/geckodriver/Capabilities.html#moz-debuggeraddress
// we specify the debugger address option for Webdriver, which will return us the CDP address when the capability is returned.
'moz:debuggerAddress': !USE_WEBDRIVER_BIDI,
// @see https://webdriver.io/docs/capabilities/#wdiogeckodriveroptions
// webdriver starts geckodriver with the correct options on behalf of Cypress
'wdio:geckodriverOptions': geckoDriverOptions,
@@ -749,26 +685,12 @@ export async function open (browser: Browser, url: string, options: BrowserLaunc
return browserReturnStatus || driverReturnStatus
}
let cdpPort: number | undefined
if (!USE_WEBDRIVER_BIDI) {
// In some cases, the webdriver session will NOT return the moz:debuggerAddress capability even though
// we set it to true in the capabilities. This is out of our control, so when this happens, we fail the browser
// and gracefully terminate the related processes and attempt to relaunch the browser in the hopes we get a
// CDP address. @see https://github.com/cypress-io/cypress/issues/30352#issuecomment-2405701867 for more details.
if (!webdriverClient.capabilities['moz:debuggerAddress']) {
debugVerbose(`firefox failed to spawn with CDP connection. Failing current instance and retrying`)
// since this fails before the instance is created, we need to kill the processes here or else they will stay open
browserInstanceWrapper.kill()
throw new CDPFailedToStartFirefox(`webdriver session failed to start CDP even though "moz:debuggerAddress" was provided. Please try to relaunch the browser`)
}
cdpPort = parseInt(new URL(`ws://${webdriverClient.capabilities['moz:debuggerAddress']}`).port)
debug(`CDP running on port ${cdpPort}`)
// makes it so get getRemoteDebuggingPort() is calculated correctly
process.env.CYPRESS_REMOTE_DEBUGGING_PORT = cdpPort.toString()
// maximize the window if running headful and no width or height args are provided.
// NOTE: We used to do this with xulstore.json, but this is no longer possible with geckodriver
// as firefox will create the profile under the profile root that we cannot control and we cannot consistently provide
// a base 64 encoded profile.
if (!browser.isHeadless && (!launchOptions.args.includes('-width') || !launchOptions.args.includes('-height'))) {
await webdriverClient.maximizeWindow()
}
// install the browser extensions
@@ -782,16 +704,7 @@ export async function open (browser: Browser, url: string, options: BrowserLaunc
}))
debug('setting up firefox utils')
const client = await firefoxUtil.setup({ automation, url, webdriverClient, remotePort: cdpPort, useWebDriverBiDi: USE_WEBDRIVER_BIDI, onError: options.onError })
if (client instanceof BrowserCriClient) {
browserCriClient = client
await utils.executeAfterBrowserLaunch(browser, {
webSocketDebuggerUrl: browserCriClient.getWebSocketDebuggerUrl(),
})
} else {
browserBidiClient = client
}
browserBidiClient = await firefoxUtil.setup({ automation, url, webdriverClient })
} catch (err: unknown) {
errors.throwErr('FIREFOX_COULD_NOT_CONNECT', err as Error)
}

View File

@@ -27,7 +27,6 @@ import * as printResults from '../util/print-run'
import { telemetry } from '@packages/telemetry'
import { CypressRunResult, createPublicBrowser, createPublicConfig, createPublicRunResults, createPublicSpec, createPublicSpecResults } from './results'
import { EarlyExitTerminator } from '../util/graceful_crash_handling'
import { CDPFailedToStartFirefox } from '../browsers/firefox'
import type { CypressError } from '@packages/errors'
type SetScreenshotMetadata = (data: TakeScreenshotProps) => void
@@ -513,11 +512,7 @@ async function waitForBrowserToConnect (options: { project: Project, socketId: s
// try again up to 3 attempts
const word = browserLaunchAttempt === 1 ? 'Retrying...' : 'Retrying again...'
if (CDPFailedToStartFirefox.isCDPFailedToStartFirefoxError(err?.originalError)) {
errors.warning('FIREFOX_CDP_FAILED_TO_CONNECT', word)
} else {
errors.warning('TESTS_DID_NOT_START_RETRYING', word)
}
errors.warning('TESTS_DID_NOT_START_RETRYING', word)
browserLaunchAttempt += 1
@@ -534,19 +529,7 @@ async function waitForBrowserToConnect (options: { project: Project, socketId: s
waitForSocketConnection(project, socketId),
// TODO: remove the need to extend options and coerce this type
launchBrowser(options as typeof options & { setScreenshotMetadata: SetScreenshotMetadata }),
]).catch((e: CypressError) => {
// if the error wrapped is a CDPFailedToStartFirefox, try to relaunch the browser
if (CDPFailedToStartFirefox.isCDPFailedToStartFirefoxError(e?.originalError)) {
// if CDP fails to connect, which is ultimately out of our control and in the hands of webdriver
// we retry launching the browser in the hopes the session is spawned correctly
debug(`Caught in launchBrowser: ${e.details}`)
return retryOnError(e)
}
// otherwise, fail
throw e
})
])
.timeout(browserTimeout)
.then(() => {
telemetry.getSpan(`waitForBrowserToConnect:attempt:${browserLaunchAttempt}`)?.end()

View File

@@ -226,9 +226,7 @@ export class SocketBase {
debug('automation:client connected')
// only send the necessary config
automationClient.emit('automation:config', {
IS_CDP_FORCED_FOR_FIREFOX: !!process.env.FORCE_FIREFOX_CDP,
})
automationClient.emit('automation:config', {})
// if our automation disconnects then we're
// in trouble and should probably bomb everything

View File

@@ -6,10 +6,6 @@ import os from 'os'
import sinon from 'sinon'
import fsExtra from 'fs-extra'
import * as firefox from '../../../lib/browsers/firefox'
import firefoxUtil from '../../../lib/browsers/firefox-util'
import { CdpAutomation } from '../../../lib/browsers/cdp_automation'
import { BrowserCriClient } from '../../../lib/browsers/browser-cri-client'
import { ICriClient } from '../../../lib/browsers/cri-client'
import { type Client as WebDriverClient, default as webdriver } from 'webdriver'
import { EventEmitter } from 'stream'
import { BidiAutomation } from '../../../lib/browsers/bidi_automation'
@@ -22,23 +18,13 @@ const plugins = require('../../../lib/plugins')
const specUtil = require('../../specUtils')
describe('lib/browsers/firefox', () => {
const port = 3333
const mockContextId = '1234-5678'
let wdInstance: sinon.SinonStubbedInstance<WebDriverClient>
let browserCriClient: BrowserCriClient
let bidiAutomationClient: BidiAutomation
afterEach(() => {
return mockfs.restore()
})
beforeEach(function () {
sinon.stub(utils, 'getProfileDir').returns('/path/to/appData/firefox-stable/interactive')
mockfs({
'/path/to/appData/firefox-stable/interactive': {},
})
wdInstance = {
maximizeWindow: sinon.stub(),
installAddOn: sinon.stub(),
@@ -49,7 +35,6 @@ describe('lib/browsers/firefox', () => {
browsingContextGetTree: sinon.stub(),
browsingContextNavigate: sinon.stub(),
capabilities: {
'moz:debuggerAddress': '127.0.0.1:12345',
// @ts-expect-error
'moz:processID': 1234,
'wdio:driverPID': 5678,
@@ -79,9 +64,8 @@ describe('lib/browsers/firefox', () => {
context('#open', () => {
beforeEach(function () {
// majorVersion >= 86 indicates CDP support for Firefox, which provides
// the CDP debugger URL for the after:browser:launch tests
this.browser = { name: 'firefox', channel: 'stable', majorVersion: 100, path: '/path/to/binary' }
// majorVersion >= 135 indicates BiDi support for Firefox
this.browser = { name: 'firefox', channel: 'stable', majorVersion: 135, path: '/path/to/binary' }
this.automation = {
use: sinon.stub().returns({}),
}
@@ -107,14 +91,6 @@ describe('lib/browsers/firefox', () => {
sinon.stub(fsExtra, 'writeJSON').resolves(undefined)
sinon.stub(fsExtra, 'writeFile').returns(undefined)
browserCriClient = sinon.createStubInstance(BrowserCriClient)
browserCriClient.attachToTargetUrl = sinon.stub().resolves({})
browserCriClient.getWebSocketDebuggerUrl = sinon.stub().returns('ws://debugger')
browserCriClient.close = sinon.stub().resolves()
sinon.stub(BrowserCriClient, 'create').resolves(browserCriClient)
sinon.stub(CdpAutomation, 'create').resolves()
bidiAutomationClient = sinon.createStubInstance(BidiAutomation)
bidiAutomationClient.setTopLevelContextId = sinon.stub().returns(undefined)
@@ -122,32 +98,13 @@ describe('lib/browsers/firefox', () => {
sinon.stub(BidiAutomation, 'create').returns(bidiAutomationClient)
})
context('#connectToNewSpec', () => {
context('#connectToNewSpecBiDi', () => {
beforeEach(function () {
this.options.onError = () => {}
this.options.onInitializeNewBrowserTab = sinon.stub()
})
it('CDP: calls connectToNewSpecCDP in firefoxUtil', async function () {
wdInstance.getWindowHandles.resolves(['mock-context-id'])
await firefox.open(this.browser, 'http://', this.options, this.automation)
this.options.url = 'next-spec-url'
await firefox.connectToNewSpec(this.browser, this.options, this.automation)
expect(this.options.onInitializeNewBrowserTab).to.have.been.called
expect(wdInstance.getWindowHandles).to.have.been.called
expect(wdInstance.switchToWindow).to.have.been.calledWith('mock-context-id')
// first time when connecting a new tab
expect(wdInstance.navigateTo).to.have.been.calledWith('about:blank')
// second time when navigating to the spec
expect(wdInstance.navigateTo).to.have.been.calledWith('next-spec-url')
})
it('BiDi: calls connectToNewSpecBiDi in firefoxUtil', async function () {
this.browser.family = 'firefox'
this.browser.majorVersion = '135'
await firefox.open(this.browser, 'http://', this.options, this.automation)
this.options.url = 'next-spec-url'
@@ -247,13 +204,10 @@ describe('lib/browsers/firefox', () => {
describe(`webdriver capabilities`, () => {
const getExpectedCapabilities = ({
shouldUseBiDi,
isDebugEnabled,
}: {
shouldUseBiDi?: boolean
isDebugEnabled?: boolean
} = {
shouldUseBiDi: false,
isDebugEnabled: false,
}) => {
return {
@@ -261,7 +215,7 @@ describe('lib/browsers/firefox', () => {
capabilities: sinon.match({
alwaysMatch: {
browserName: 'firefox',
webSocketUrl: !!shouldUseBiDi,
webSocketUrl: true,
acceptInsecureCerts: true,
// @see https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities/firefoxOptions
'moz:firefoxOptions': {
@@ -274,11 +228,10 @@ describe('lib/browsers/firefox', () => {
],
// only partially match the preferences object because it is so large
prefs: {
'remote.active-protocols': shouldUseBiDi ? 1 : 2,
'remote.active-protocols': 1,
'remote.enabled': true,
},
},
'moz:debuggerAddress': !shouldUseBiDi,
'wdio:geckodriverOptions': {
host: '127.0.0.1',
marionetteHost: '127.0.0.1',
@@ -304,71 +257,47 @@ describe('lib/browsers/firefox', () => {
}
}
describe(`creates the WebDriver session and geckodriver instance through capabilities, installs the extension, and passes the correct port to CDP`, function () {
it('for CDP', async function () {
await firefox.open(this.browser, 'http://', this.options, this.automation)
expect(webdriver.newSession).to.have.been.calledWith((getExpectedCapabilities({ shouldUseBiDi: false })))
it('creates the WebDriver session and geckodriver instance through capabilities and installs the extension', async function () {
this.browser.family = 'firefox'
this.browser.majorVersion = '135'
await firefox.open(this.browser, 'http://', this.options, this.automation)
expect(webdriver.newSession).to.have.been.calledWith((getExpectedCapabilities()))
expect(wdInstance.installAddOn).to.have.been.calledWith('/path/to/ext', true)
expect(wdInstance.installAddOn).to.have.been.calledWith('/path/to/ext', true)
expect(wdInstance.navigateTo).to.have.been.calledWith('http://')
expect(wdInstance.sessionSubscribe).to.be.calledWith({ events: [
'network.beforeRequestSent',
'network.responseStarted',
'network.responseCompleted',
'network.fetchError',
'browsingContext.contextCreated',
'browsingContext.contextDestroyed',
] })
// make sure CDP gets the expected port
expect(BrowserCriClient.create).to.be.calledWith({ hosts: ['127.0.0.1', '::1'], port: 12345, browserName: 'Firefox', onAsynchronousError: undefined, onServiceWorkerClientEvent: undefined })
expect(wdInstance.browsingContextGetTree).to.be.calledWith({})
expect(wdInstance.browsingContextNavigate).to.have.been.calledWith({
context: mockContextId,
url: 'http://',
})
it('for BiDi', async function () {
this.browser.family = 'firefox'
this.browser.majorVersion = '135'
await firefox.open(this.browser, 'http://', this.options, this.automation)
expect(webdriver.newSession).to.have.been.calledWith((getExpectedCapabilities({ shouldUseBiDi: true })))
expect(wdInstance.installAddOn).to.have.been.calledWith('/path/to/ext', true)
expect(wdInstance.sessionSubscribe).to.be.calledWith({ events: [
'network.beforeRequestSent',
'network.responseStarted',
'network.responseCompleted',
'network.fetchError',
'browsingContext.contextCreated',
'browsingContext.contextDestroyed',
] })
expect(wdInstance.browsingContextGetTree).to.be.calledWith({})
expect(wdInstance.browsingContextNavigate).to.have.been.calledWith({
context: mockContextId,
url: 'http://',
})
// make sure Bidi gets created
expect(BidiAutomation.create).to.be.calledWith(wdInstance, this.automation)
expect(bidiAutomationClient.setTopLevelContextId).to.be.calledWith(mockContextId)
})
// make sure Bidi gets created
expect(BidiAutomation.create).to.be.calledWith(wdInstance, this.automation)
expect(bidiAutomationClient.setTopLevelContextId).to.be.calledWith(mockContextId)
})
describe('debugging: sets additional arguments if "DEBUG=cypress-verbose:server:browsers:geckodriver" and "DEBUG=cypress-verbose:server:browsers:webdriver" is set', () => {
afterEach(() => {
debug.disable()
})
afterEach(() => {
debug.disable()
})
it('for CDP', async function () {
debug.enable('cypress-verbose:server:browsers:geckodriver,cypress-verbose:server:browsers:webdriver')
it('debugging: sets additional arguments if "DEBUG=cypress-verbose:server:browsers:geckodriver" and "DEBUG=cypress-verbose:server:browsers:webdriver" is set', async function () {
this.browser.family = 'firefox'
this.browser.majorVersion = '135'
debug.enable('cypress-verbose:server:browsers:geckodriver,cypress-verbose:server:browsers:webdriver')
await firefox.open(this.browser, 'http://', this.options, this.automation)
await firefox.open(this.browser, 'http://', this.options, this.automation)
expect(webdriver.newSession).to.have.been.calledWith((getExpectedCapabilities({ isDebugEnabled: true })))
})
it('for BiDi', async function () {
this.browser.family = 'firefox'
this.browser.majorVersion = '135'
debug.enable('cypress-verbose:server:browsers:geckodriver,cypress-verbose:server:browsers:webdriver')
await firefox.open(this.browser, 'http://', this.options, this.automation)
expect(webdriver.newSession).to.have.been.calledWith((getExpectedCapabilities({ isDebugEnabled: true, shouldUseBiDi: true })))
})
expect(webdriver.newSession).to.have.been.calledWith((getExpectedCapabilities({ isDebugEnabled: true })))
})
})
@@ -418,7 +347,6 @@ describe('lib/browsers/firefox', () => {
},
[path.resolve(`${__dirname }/../../extension`)]: { 'abc': 'test' },
'/path/to/appData/firefox-stable/interactive': {
'xulstore.json': '[foo xulstore.json]',
'chrome': { 'userChrome.css': '[foo userChrome.css]' },
},
})
@@ -493,26 +421,11 @@ describe('lib/browsers/firefox', () => {
})
describe('sets "remote.active-protocols"', function () {
// CDP is deprecated in Firefox 129 and up.
// In order to enable CDP, we need to set
// remote.active-protocol=2
// CDP was deprecated in Firefox 129 and up and was removed in Firefox 141.
// @see https://fxdx.dev/deprecating-cdp-support-in-firefox-embracing-the-future-with-webdriver-bidi/
// @see https://fxdx.dev/webdriver-bidi-becomes-the-default-for-cypress-in-firefox/
// @see https://github.com/cypress-io/cypress/issues/29713
it('=2 to enable only CDP', async function () {
const executeBeforeBrowserLaunchSpy = sinon.spy(utils, 'executeBeforeBrowserLaunch')
await firefox.open(this.browser, 'http://', this.options, this.automation)
expect(executeBeforeBrowserLaunchSpy).to.have.been.calledWith(this.browser, sinon.match({
preferences: {
'remote.active-protocols': 2,
},
}), this.options)
})
it('=1 to enable only BiDi', async function () {
this.browser.family = 'firefox'
this.browser.majorVersion = '135'
const executeBeforeBrowserLaunchSpy = sinon.spy(utils, 'executeBeforeBrowserLaunch')
await firefox.open(this.browser, 'http://', this.options, this.automation)
@@ -532,78 +445,40 @@ describe('lib/browsers/firefox', () => {
expect(result.kill).to.be.an.instanceof(Function)
})
it('always clear user profile if it already exists', async function () {
mockfs({
'/path/to/appData/firefox-stable/interactive/': {
'xulstore.json': '[foo xulstore.json]',
'chrome': { 'userChrome.css': '[foo userChrome.css]' },
},
context('profile/extension', () => {
afterEach(() => {
return mockfs.restore()
})
await firefox.open(this.browser, 'http://', this.options, this.automation)
expect(specUtil.getFsPath('/path/to/appData/firefox-stable/interactive')).to.be.undefined
})
it('creates xulstore.json if not exist', async function () {
await firefox.open(this.browser, 'http://', this.options, this.automation)
expect(fsExtra.writeJSON).to.have.been.calledWith('/path/to/appData/firefox-stable/interactive/xulstore.json', {
'chrome://browser/content/browser.xhtml':
{
'main-window':
{
'width': 1280,
'height': 1024,
'sizemode': 'maximized',
},
it('always clear user profile if it already exists', async function () {
mockfs({
'/path/to/appData/firefox-stable/interactive/': {
'chrome': { 'userChrome.css': '[foo userChrome.css]' },
},
})
})
})
await firefox.open(this.browser, 'http://', this.options, this.automation)
it('creates chrome/userChrome.css if not exist', async function () {
await firefox.open(this.browser, 'http://', this.options, this.automation)
expect(fsExtra.writeFile).to.have.been.calledWith('/path/to/appData/firefox-stable/interactive/chrome/userChrome.css')
})
it('clears browser cache', async function () {
mockfs({
'/path/to/appData/firefox-stable/interactive/': {
'CypressCache': { 'foo': 'bar' },
},
expect(specUtil.getFsPath('/path/to/appData/firefox-stable/interactive')).to.be.undefined
})
this.options.isTextTerminal = false
it('creates chrome/userChrome.css if not exist', async function () {
await firefox.open(this.browser, 'http://', this.options, this.automation)
await firefox.open(this.browser, 'http://', this.options, this.automation)
expect(specUtil.getFsPath('/path/to/appData/firefox-stable/interactive')).to.be.undefined
})
it('wraps errors when failing to connect to firefox (CDP failure)', async function () {
const err = new Error('failed to connect to CDP')
// BrowserCriClient.create is stubbed above. restore it and re-stub BrowserCriClient.create
// @ts-expect-error
BrowserCriClient.create.restore()
sinon.stub(BrowserCriClient, 'create').rejects(err)
await expect(firefox.open(this.browser, 'http://', this.options, this.automation)).to.be.rejectedWith()
.then((wrapperErr) => {
expect(wrapperErr.message).to.include('Cypress failed to make a connection to Firefox.')
expect(wrapperErr.details).to.include(err.message)
expect(fsExtra.writeFile).to.have.been.calledWith('/path/to/appData/firefox-stable/interactive/chrome/userChrome.css')
})
})
it('executes after:browser:launch if registered', async function () {
plugins.has.withArgs('after:browser:launch').returns(true)
plugins.execute.resolves(null)
it('clears browser cache', async function () {
mockfs({
'/path/to/appData/firefox-stable/interactive/': {
'CypressCache': { 'foo': 'bar' },
},
})
await firefox.open(this.browser, 'http://', this.options, this.automation)
this.options.isTextTerminal = false
expect(plugins.execute).to.be.calledWith('after:browser:launch', this.browser, {
webSocketDebuggerUrl: 'ws://debugger',
await firefox.open(this.browser, 'http://', this.options, this.automation)
expect(specUtil.getFsPath('/path/to/appData/firefox-stable/interactive')).to.be.undefined
})
})
@@ -633,7 +508,6 @@ describe('lib/browsers/firefox', () => {
expect(process.kill).to.have.been.calledWith(1234)
// kills the webdriver process/ geckodriver process
expect(process.kill).to.have.been.calledWith(5678)
expect(browserCriClient.close).to.have.been.called
// makes sure the exit event is called to signal to the rest of cypress server that the processes are killed
expect(instance.emit).to.have.been.calledWith('exit')
})
@@ -653,26 +527,9 @@ describe('lib/browsers/firefox', () => {
expect(process.kill).to.have.been.calledWith(1234)
// kills the webdriver process/ geckodriver process
expect(process.kill).to.have.been.calledWith(5678)
expect(browserCriClient.close).to.have.been.called
// makes sure the exit event is called to signal to the rest of cypress server that the processes are killed
expect(instance.emit).to.have.been.calledWith('exit')
})
it('throws CDPFailedToStartFirefox if the mox:debuggerAddress capability is not returned by webdriver', function () {
delete wdInstance.capabilities['moz:debuggerAddress']
sinon.stub(process, 'kill').returns(true)
return firefox.open(this.browser, 'http://', this.options, this.automation).catch((err) => {
// make sure we through the correct error here to prompt @packages/server/lib/modes/run.ts
// to retry the browser connection
expect(err.details).to.include('CDPFailedToStartFirefox: webdriver session failed to start CDP even though "moz:debuggerAddress" was provided. Please try to relaunch the browser')
expect(err.type).to.equal('FIREFOX_COULD_NOT_CONNECT')
// kills the browser
expect(process.kill).to.have.been.calledWith(1234)
// kills the webdriver process / geckodriver process
expect(process.kill).to.have.been.calledWith(5678)
})
})
})
})
@@ -681,56 +538,4 @@ describe('lib/browsers/firefox', () => {
expect(firefox.connectProtocolToBrowser).to.throw('Protocol is not yet supported in firefox.')
})
})
context('#closeProtocolConnection', () => {
it('throws error', () => {
expect(firefox.closeProtocolConnection).to.throw('Protocol is not yet supported in firefox.')
})
})
context('firefox-util', () => {
context('#setupCDP', function () {
it('correctly sets up the remote agent', async function () {
const criClientStub: ICriClient = {
targetId: '',
send: sinon.stub(),
on: sinon.stub(),
off: sinon.stub(),
close: sinon.stub(),
ws: sinon.stub() as any,
queue: {
enableCommands: [],
enqueuedCommands: [],
subscriptions: [],
},
closed: false,
connected: false,
}
const automationStub = {
onServiceWorkerClientEvent: sinon.stub(),
}
const browserCriClient: BrowserCriClient = sinon.createStubInstance(BrowserCriClient)
browserCriClient.attachToTargetUrl = sinon.stub().resolves(criClientStub)
sinon.stub(BrowserCriClient, 'create').resolves(browserCriClient)
sinon.stub(CdpAutomation, 'create').resolves()
const actual = await firefoxUtil.setupCDP(port, automationStub, null)
expect(actual).to.equal(browserCriClient)
expect(browserCriClient.attachToTargetUrl).to.be.calledWith('about:blank')
expect(BrowserCriClient.create).to.be.calledWith({ hosts: ['127.0.0.1', '::1'], port, browserName: 'Firefox', onAsynchronousError: null, onServiceWorkerClientEvent: automationStub.onServiceWorkerClientEvent })
expect(CdpAutomation.create).to.be.calledWith(
criClientStub.send,
criClientStub.on,
criClientStub.off,
browserCriClient.resetBrowserTargets,
automationStub,
)
})
})
})
})

View File

@@ -1,55 +0,0 @@
exports['CDP deprecated in Firefox / logs a warning to the user that CDP is deprecated and will be removed in Cypress 15'] = `
====================================================================================================
(Run Starting)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Cypress: 1.2.3 │
│ Browser: FooBrowser 88 │
│ Specs: 1 found (simple_passing.cy.js) │
│ Searched: cypress/e2e/simple_passing.cy.js │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: simple_passing.cy.js (1 of 1)
Since Firefox 129, Chrome DevTools Protocol (CDP) has been deprecated in Firefox. In Firefox 135 and above, Cypress defaults to automating the Firefox browser with WebDriver BiDi. Cypress will no longer support CDP within Firefox in the future and is planned for removal in Cypress 15.
simple passing spec
✓ passes
1 passing
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 1 │
│ Passing: 1 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: false │
│ Duration: X seconds │
│ Spec Ran: simple_passing.cy.js │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
====================================================================================================
(Run Finished)
Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ✔ simple_passing.cy.js XX:XX 1 1 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
✔ All specs passed! XX:XX 1 1 - - -
`

View File

@@ -1,18 +0,0 @@
import systemTests from '../lib/system-tests'
describe('CDP deprecated in Firefox', () => {
systemTests.setup()
systemTests.it('logs a warning to the user that CDP is deprecated and will be removed in Cypress 15', {
browser: 'firefox',
processEnv: {
FORCE_FIREFOX_CDP: '1',
},
expectedExitCode: 0,
snapshot: true,
spec: 'simple_passing.cy.js',
onStdout: (stdout) => {
expect(stdout).to.include('Since Firefox 129, Chrome DevTools Protocol (CDP) has been deprecated in Firefox. In Firefox 135 and above, Cypress defaults to automating the Firefox browser with WebDriver BiDi. Cypress will no longer support CDP within Firefox in the future and is planned for removal in Cypress 15.')
},
})
})