From ff5fbb8c5cbd803a4855f5c723c647af6f3fde0b Mon Sep 17 00:00:00 2001 From: Bill Glesias Date: Tue, 13 May 2025 17:12:59 -0400 Subject: [PATCH] misc: throw error when chrome 137 branded is used when the @cypress/puppeteeris used in headed mode or if --load-extension is added to launch options via a user testing an extension empty commit to run ci --- .circleci/workflows.yml | 10 ++--- cli/CHANGELOG.md | 1 + npm/puppeteer/README.md | 2 +- npm/puppeteer/src/plugin/setup.ts | 5 +++ npm/puppeteer/test/unit/setup.spec.ts | 11 ++++++ ...ROME_137_LOAD_EXTENSION_NOT_SUPPORTED.html | 38 +++++++++++++++++++ packages/errors/src/errors.ts | 4 ++ .../test/unit/visualSnapshotErrors_spec.ts | 5 +++ packages/graphql/schemas/schema.graphql | 1 + packages/server/lib/browsers/chrome.ts | 8 ++++ .../server/test/unit/browsers/chrome_spec.js | 26 +++++++++++++ 11 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 packages/errors/__snapshot-html__/CHROME_137_LOAD_EXTENSION_NOT_SUPPORTED.html diff --git a/.circleci/workflows.yml b/.circleci/workflows.yml index d274fd08db02..a4051bb8a8e8 100644 --- a/.circleci/workflows.yml +++ b/.circleci/workflows.yml @@ -38,7 +38,7 @@ mainBuildFilters: &mainBuildFilters - /^release\/\d+\.\d+\.\d+$/ # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - 'update-v8-snapshot-cache-on-develop' - - 'fix/top_framebust_window_location' + - 'misc/throw_error_on_extension_chrome_137' # 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 @@ -49,7 +49,7 @@ macWorkflowFilters: &darwin-workflow-filters - 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: [ 'fix/top_framebust_window_location', << pipeline.git.branch >> ] + - equal: [ 'misc/throw_error_on_extension_chrome_137', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -60,7 +60,7 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters - 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: [ 'fix/top_framebust_window_location', << pipeline.git.branch >> ] + - equal: [ 'misc/throw_error_on_extension_chrome_137', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -83,7 +83,7 @@ windowsWorkflowFilters: &windows-workflow-filters - 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: [ 'fix/top_framebust_window_location', << pipeline.git.branch >> ] + - equal: [ 'misc/throw_error_on_extension_chrome_137', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -157,7 +157,7 @@ commands: name: Set environment variable to determine whether or not to persist artifacts command: | echo "Setting SHOULD_PERSIST_ARTIFACTS variable" - echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "fix/top_framebust_window_location" ]]; then + echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "misc/throw_error_on_extension_chrome_137" ]]; then export SHOULD_PERSIST_ARTIFACTS=true fi' >> "$BASH_ENV" # You must run `setup_should_persist_artifacts` command and be using bash before running this command diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 366c2b4aa169..5d9dc73a08a1 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -15,6 +15,7 @@ _Released 5/20/2025 (PENDING)_ **Misc:** +- Chrome 137+ no longer supports `--load-extension` in branded Chrome, breaking the `@cypress/puppeteer` plugin in `open` mode and headed `run` mode and [`launchOptions.extensions`](https://docs.cypress.io/api/node-events/browser-launch-api#Add-browser-extensions). We recommend using Electron, Chrome for Testing or Chromium to continue using these features. See Cypress Docker image examples for [Chrome for Testing](https://github.com/cypress-io/cypress-docker-images/tree/master/examples/chrome-for-testing) and [Chromium](https://github.com/cypress-io/cypress-docker-images/tree/master/examples/chromium). Addresses [#31702](https://github.com/cypress-io/cypress/issues/31702) and [#31703](https://github.com/cypress-io/cypress/issues/31703). - Cursor is now available as an IDE option for opening files in Cypress, if it is installed on your system. Addressed in [#31691](https://github.com/cypress-io/cypress/pull/31691). - The error shown when the `--record` flag is missing has been updated to be shorter. Addressed in [#31676](https://github.com/cypress-io/cypress/pull/31676). diff --git a/npm/puppeteer/README.md b/npm/puppeteer/README.md index 80222c68a4f1..b4201eb7cf7a 100644 --- a/npm/puppeteer/README.md +++ b/npm/puppeteer/README.md @@ -46,7 +46,7 @@ Before using `@cypress/puppeteer`, ensure the following requirements are met: - Cypress 13.6.0+ is required. - Only Chromium-based browsers are supported, such as Chrome for Testing, Chromium, and Electron. -- Chrome-branded browsers (e.g., standard Chrome) are not supported in version 137+ due to Chrome's removal of the `--load-extension` flag. We recommend using Chrome for Testing or Chromium instead. See Cypress Docker image examples for [Chrome for Testing](https://github.com/cypress-io/cypress-docker-images/tree/master/examples/chrome-for-testing) and [Chromium](https://github.com/cypress-io/cypress-docker-images/tree/master/examples/chromium). +- Chrome-branded browsers (e.g., standard Chrome) are not supported in version 137+ due to Chrome's removal of the `--load-extension` flag. We recommend using Electron, Chrome for Testing or Chromium instead. See Cypress Docker image examples for [Chrome for Testing](https://github.com/cypress-io/cypress-docker-images/tree/master/examples/chrome-for-testing) and [Chromium](https://github.com/cypress-io/cypress-docker-images/tree/master/examples/chromium). Note this change only applies to headed applications such as `cypress open` or `cypress run --headed`. The plugin will work as expected in `cypress run` mode in any version of Chrome. ## Usage diff --git a/npm/puppeteer/src/plugin/setup.ts b/npm/puppeteer/src/plugin/setup.ts index 3f0d1f513379..e4122fce2ddc 100644 --- a/npm/puppeteer/src/plugin/setup.ts +++ b/npm/puppeteer/src/plugin/setup.ts @@ -63,6 +63,11 @@ export function setup (options: SetupOptions) { try { options.on('after:browser:launch', (browser: Cypress.Browser, options: Cypress.AfterBrowserLaunchDetails) => { + if (Number(browser.majorVersion) >= 137 && browser.name === 'chrome' && browser.isHeaded) { + // @see https://github.com/cypress-io/cypress/issues/31703 + throw pluginError('@cypress/puppeteer does not work in Google Chrome v137 and higher in cypress open mode (or headed run mode). If you need to use @cypress/puppeteer in headed mode, please use Electron, Chrome for Testing, Chromium, or another Chrome variant that supports loading extensions.') + } + cypressBrowser = browser debuggerUrl = options.webSocketDebuggerUrl }) diff --git a/npm/puppeteer/test/unit/setup.spec.ts b/npm/puppeteer/test/unit/setup.spec.ts index f33b4dc7f8b1..0a0009ed998a 100644 --- a/npm/puppeteer/test/unit/setup.spec.ts +++ b/npm/puppeteer/test/unit/setup.spec.ts @@ -274,6 +274,17 @@ describe('#setup', () => { await task({ name: testTask, args: [] }) expect(activateMainTabExport.activateMainTab).not.to.be.called }) + + it('catastrophically fails when the browser is Google Chrome Branded 137 and up and we are running in headed mode', async () => { + setup({ on, onMessage, puppeteer: mockPuppeteer as PuppeteerNode }) + expect(() => { + on.withArgs('after:browser:launch').yield({ family: 'chromium', isHeaded: true, name: 'chrome', majorVersion: '137' }, { webSocketDebuggerUrl: 'ws://debugger' }) + }).to.throw('@cypress/puppeteer does not work in Google Chrome v137 and higher in cypress open mode (or headed run mode). If you need to use @cypress/puppeteer in headed mode, please use Electron, Chrome for Testing, Chromium, or another Chrome variant that supports loading extensions.') + + expect(() => { + on.withArgs('after:browser:launch').yield({ family: 'chromium', isHeaded: true, name: 'chrome', majorVersion: '141' }, { webSocketDebuggerUrl: 'ws://debugger' }) + }).to.throw('@cypress/puppeteer does not work in Google Chrome v137 and higher in cypress open mode (or headed run mode). If you need to use @cypress/puppeteer in headed mode, please use Electron, Chrome for Testing, Chromium, or another Chrome variant that supports loading extensions.') + }) }) describe('validation', () => { diff --git a/packages/errors/__snapshot-html__/CHROME_137_LOAD_EXTENSION_NOT_SUPPORTED.html b/packages/errors/__snapshot-html__/CHROME_137_LOAD_EXTENSION_NOT_SUPPORTED.html new file mode 100644 index 000000000000..daa57bd90a33 --- /dev/null +++ b/packages/errors/__snapshot-html__/CHROME_137_LOAD_EXTENSION_NOT_SUPPORTED.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
Google Chrome v137 and higher does not allow loading extensions via --load-extension. If you need to load an extension to test with Cypress, please use Chrome for Testing, Chromium, or another Chrome variant that supports loading extensions.
+
\ No newline at end of file diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index 072b9f7a608b..ee189cb9cc55 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -98,6 +98,10 @@ export const AllCypressErrors = { This option will not have an effect in ${fmt.off(_.capitalize(browser))}. Tests that rely on web security being disabled will not run as expected.` }, + CHROME_137_LOAD_EXTENSION_NOT_SUPPORTED: () => { + return errTemplate`\ + Google Chrome v137 and higher does not allow loading extensions via --load-extension. If you need to load an extension to test with Cypress, please use Chrome for Testing, Chromium, or another Chrome variant that supports loading extensions.` + }, BROWSER_UNSUPPORTED_LAUNCH_OPTION: (browser: string, options: string[]) => { return errTemplate`\ Warning: The following browser launch options were provided but are not supported by ${fmt.highlightSecondary(browser)} diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index ff8cc62b4969..81dcb59463aa 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -346,6 +346,11 @@ describe('visual error templates', () => { default: [err], } }, + CHROME_137_LOAD_EXTENSION_NOT_SUPPORTED: () => { + return { + default: [], + } + }, CHROME_WEB_SECURITY_NOT_SUPPORTED: () => { return { default: ['firefox'], diff --git a/packages/graphql/schemas/schema.graphql b/packages/graphql/schemas/schema.graphql index 09092fc5534e..9a0e706ad44a 100644 --- a/packages/graphql/schemas/schema.graphql +++ b/packages/graphql/schemas/schema.graphql @@ -1132,6 +1132,7 @@ enum ErrorTypeEnum { CDP_FIREFOX_DEPRECATED CDP_RETRYING_CONNECTION CDP_VERSION_TOO_OLD + CHROME_137_LOAD_EXTENSION_NOT_SUPPORTED CHROME_WEB_SECURITY_NOT_SUPPORTED CLOUD_ALREADY_COMPLETE CLOUD_API_RESPONSE_FAILED_RETRYING diff --git a/packages/server/lib/browsers/chrome.ts b/packages/server/lib/browsers/chrome.ts index a891e26d35e2..c2d969d3b73b 100644 --- a/packages/server/lib/browsers/chrome.ts +++ b/packages/server/lib/browsers/chrome.ts @@ -145,6 +145,14 @@ const _normalizeArgExtensions = function (extPath, args, pluginExtensions, brows return arg.includes(LOAD_EXTENSION) }) + if (loadExtension || pluginExtensions.length > 0) { + // @see https://github.com/cypress-io/cypress/issues/31702 + if (Number(browser.majorVersion) >= 137 && browser.name === 'chrome') { + // eslint-disable-next-line no-console + errors.warning('CHROME_137_LOAD_EXTENSION_NOT_SUPPORTED') + } + } + if (loadExtension) { args = _.without(args, loadExtension) diff --git a/packages/server/test/unit/browsers/chrome_spec.js b/packages/server/test/unit/browsers/chrome_spec.js index 7e61c07da021..62436c7a4670 100644 --- a/packages/server/test/unit/browsers/chrome_spec.js +++ b/packages/server/test/unit/browsers/chrome_spec.js @@ -270,6 +270,32 @@ describe('lib/browsers/chrome', () => { }) }) + it('warns the user if `--load-extension` is passed into branded chrome 137 and up', async function () { + sinon.stub(console, 'log') + + plugins.registerEvent('before:browser:launch', (browser, config) => { + return Promise.resolve({ args: ['--foo=bar', '--load-extension=/foo/bar/baz.js,/quux.js'] }) + }) + + await chrome.open({ isHeaded: true, majorVersion: '137', name: 'chrome' }, 'http://', { onWarning: () => {}, onError: () => {} }, this.automation) + + // eslint-disable-next-line no-console + expect(console.log).to.have.been.calledWith(sinon.match('Google Chrome v137 and higher does not allow loading extensions via --load-extension. If you need to load an extension to test with Cypress, please use Chrome for Testing, Chromium, or another Chrome variant that supports loading extensions.')) + }) + + it('warns the user if launchOptions.extensions is passed into branded chrome 137 and up', async function () { + sinon.stub(console, 'log') + + plugins.registerEvent('before:browser:launch', (browser, config) => { + return Promise.resolve({ args: ['--foo=bar'], extensions: ['/foo/bar/baz.js', '/quux.js'] }) + }) + + await chrome.open({ isHeaded: true, majorVersion: '139', name: 'chrome' }, 'http://', { onWarning: () => {}, onError: () => {} }, this.automation) + + // eslint-disable-next-line no-console + expect(console.log).to.have.been.calledWith(sinon.match('Google Chrome v137 and higher does not allow loading extensions via --load-extension. If you need to load an extension to test with Cypress, please use Chrome for Testing, Chromium, or another Chrome variant that supports loading extensions.')) + }) + it('cleans up an unclean browser profile exit status', function () { this.readJson.withArgs('/profile/dir/Default/Preferences').resolves({ profile: {