diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index f9f6612fd29..a5143b839a5 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -3,6 +3,10 @@ _Released 09/16/2025 (PENDING)_ +**Features:** + +- Added support for using [@cypress/grep](https://www.npmjs.com/package/@cypress/grep) with Cypress Studio. Addresses [#32292](https://github.com/cypress-io/cypress/issues/32292). + **Dependency Updates:** - Updated [`better-sqlite3`](https://www.npmjs.com/package/better-sqlite3) from `11.9.1` to `11.10.0`. Addressed in [#32404](https://github.com/cypress-io/cypress/pull/32404). diff --git a/packages/app/src/store/studio-store.ts b/packages/app/src/store/studio-store.ts index 9e70ee26b05..654b3b75061 100644 --- a/packages/app/src/store/studio-store.ts +++ b/packages/app/src/store/studio-store.ts @@ -111,6 +111,7 @@ interface StudioRecorderState { sessionId?: string _isStudioCreatedTest: boolean newTestLineNumber?: number + _originalGrepSettings: Record } function getUrlParams () { @@ -148,6 +149,7 @@ export const useStudioStore = defineStore('studioRecorder', { sessionId: persistedSessionId, newTestLineNumber: undefined, _isStudioCreatedTest: false, + _originalGrepSettings: {}, } }, @@ -254,6 +256,14 @@ export const useStudioStore = defineStore('studioRecorder', { this.sessionId = studio.sessionId } + // if the user has any settings related to @cypress/grep, we need to temporarily remove them + // so that studio can run all of the tests regardless of whether they match the grep filters + if (studio.newTestLineNumber || studio.testId) { + if (this.detectAndStoreGrepSettings()) { + this.clearGrepSettings() + } + } + // if we have an existing test or are creating a new test, we need to start loading // otherwise if we have a suite, we can just set the studio active if (this.testId || studio.newTestLineNumber) { @@ -276,6 +286,62 @@ export const useStudioStore = defineStore('studioRecorder', { } }, + detectAndStoreGrepSettings () { + const grepEnvVars = [ + 'grep', + 'grepTags', 'grep-tags', + 'grepUntagged', 'grep-untagged', + 'grepOmitFiltered', 'grep-omit-filtered', + ] + + this._originalGrepSettings = {} + + try { + const cypress = getCypress() + + for (const envVar of grepEnvVars) { + const value = cypress.env(envVar) + + if (value != null) { + this._originalGrepSettings[envVar] = value + } + } + + return Object.keys(this._originalGrepSettings).length > 0 + } catch { + return false + } + }, + + clearGrepSettings () { + try { + const cypress = getCypress() + + for (const envVar of Object.keys(this._originalGrepSettings)) { + cypress.env(envVar, null) + } + } catch { + // Cypress not ready, skip + } + }, + + restoreGrepSettings () { + // Only restore if we have settings to restore + if (Object.keys(this._originalGrepSettings).length === 0) { + return + } + + try { + const cypress = getCypress() + + for (const [envVar, value] of Object.entries(this._originalGrepSettings)) { + cypress.env(envVar, value) + } + } catch { + // Cypress not ready, skip + } + }, + interceptTest (test) { // if this test is the one we created, we can just set the test id if ((this.newTestLineNumber && test.invocationDetails?.line === this.newTestLineNumber) || (this.suiteId && this._hasStarted)) { @@ -319,6 +385,8 @@ export const useStudioStore = defineStore('studioRecorder', { reset () { this.stop() + this.restoreGrepSettings() + this.logs = [] this.url = undefined this._hasStarted = false @@ -326,6 +394,7 @@ export const useStudioStore = defineStore('studioRecorder', { this.isFailed = false this.showUrlPrompt = true this._isStudioCreatedTest = false + this._originalGrepSettings = {} this._maybeResetRunnables() }, diff --git a/packages/app/src/studio/studio-app-types.ts b/packages/app/src/studio/studio-app-types.ts index 68fec5e8fb4..c8a529f0811 100644 --- a/packages/app/src/studio/studio-app-types.ts +++ b/packages/app/src/studio/studio-app-types.ts @@ -31,6 +31,13 @@ CyEventEmitter & { getRootSuite: () => Suite } areSourceMapsAvailable?: boolean + stackUtils?: { + getSourceDetailsForFirstLine: (stack: string, projectRoot: string) => { + line: number + column: number + file: string + } + } } export interface TestBlock { diff --git a/packages/driver/src/cypress.ts b/packages/driver/src/cypress.ts index e86ee87db2d..dfdf6a170c1 100644 --- a/packages/driver/src/cypress.ts +++ b/packages/driver/src/cypress.ts @@ -30,6 +30,7 @@ import $SetterGetter from './cypress/setter_getter' import { validateConfig } from './util/config' import $utils from './cypress/utils' +import $stackUtils from './cypress/stack_utils' import { $Chainer } from './cypress/chainer' import { $Cookies, ICookies } from './cypress/cookies' import { $Command } from './cypress/command' @@ -127,6 +128,7 @@ class $Cypress { specBridgeCommunicator: SpecBridgeCommunicator isCrossOriginSpecBridge: boolean on: any + stackUtils: typeof $stackUtils | null = null // attach to $Cypress to access // all of the constructors @@ -363,6 +365,7 @@ class $Cypress { this.mocha = $Mocha.create(specWindow, this, this.config) this.runner = $Runner.create(specWindow, this.mocha, this, this.cy, this.state) this.downloads = $Downloads.create(this) + this.stackUtils = $stackUtils // wire up command create to cy // @ts-expect-error