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
4 changes: 4 additions & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

_Released 11/18/2025 (PENDING)_

**Features:**

- The `experimentalRunAllSpecs` option can now be used for component testing as well as e2e testing. Addresses [#25636](https://github.com/cypress-io/cypress/issues/25636).

**Bugfixes:**

- Fixed an issue where [`cy.wrap()`](https://docs.cypress.io/api/commands/wrap) would cause infinite recursion and freeze the Cypress App when called with objects containing circular references. Fixes [#24715](https://github.com/cypress-io/cypress/issues/24715). Addressed in [#32917](https://github.com/cypress-io/cypress/pull/32917).
Expand Down
50 changes: 36 additions & 14 deletions npm/vite-dev-server/client/initCypressTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,42 @@ if (supportFile) {
})
}

// Using relative path wouldn't allow to load tests outside Vite project root folder
// So we use the "@fs" bit to load the test file using its absolute path
// Normalize path to not include a leading slash (different on Win32 vs Unix)
const normalizedAbsolutePath = CypressInstance.spec.absolute.replace(/^\//, '')
const testFileAbsolutePathRoute = `${devServerPublicPathBase}/@fs/${normalizedAbsolutePath}`

/* Spec file import logic */
// We need a slash before /src/my-spec.js, this does not happen by default.
importsToLoad.push({
load: () => import(testFileAbsolutePathRoute),
absolute: CypressInstance.spec.absolute,
relative: CypressInstance.spec.relative,
relativeUrl: testFileAbsolutePathRoute,
})
const specPath = new URLSearchParams(document.location.search).get('specPath')

if (specPath === '__all' || CypressInstance.spec.relative === '__all') {
const runAllSpecs = window.parent.__RUN_ALL_SPECS__ || []
const allSpecs = window.parent.__RUN_MODE_SPECS__ || []

runAllSpecs.forEach((specRelative) => {
const specObj = allSpecs.find((s) => s.relative === specRelative)

if (specObj) {
const normalizedPath = specObj.absolute.replace(/^\//, '')
const specRoute = `${devServerPublicPathBase}/@fs/${normalizedPath}`

importsToLoad.push({
load: () => import(specRoute),
absolute: specObj.absolute,
relative: specObj.relative,
relativeUrl: specRoute,
})
}
})
} else {
// Using relative path wouldn't allow to load tests outside Vite project root folder
// So we use the "@fs" bit to load the test file using its absolute path
// Normalize path to not include a leading slash (different on Win32 vs Unix)
const normalizedAbsolutePath = CypressInstance.spec.absolute.replace(/^\//, '')
const testFileAbsolutePathRoute = `${devServerPublicPathBase}/@fs/${normalizedAbsolutePath}`

// We need a slash before /src/my-spec.js, this does not happen by default.
importsToLoad.push({
load: () => import(testFileAbsolutePathRoute),
absolute: CypressInstance.spec.absolute,
relative: CypressInstance.spec.relative,
relativeUrl: testFileAbsolutePathRoute,
})
}

if (!CypressInstance) {
throw new Error('Tests cannot run without a reference to Cypress!')
Expand Down
241 changes: 240 additions & 1 deletion npm/vite-dev-server/test/initCypressTests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,26 @@ describe('initCypressTests', () => {

global.import = vi.fn()
// @ts-expect-error
global.window = {}
global.document = {
location: {
search: '',
},
body: {
querySelectorAll: vi.fn().mockReturnValue([]),
},
}

// @ts-expect-error
global.parent = {}
// @ts-expect-error
global.parent.Cypress = mockCypressInstance
// @ts-expect-error
global.window = { parent: global.parent }
})

afterEach(() => {
// @ts-expect-error
delete global.document
// @ts-expect-error
delete global.window
// @ts-expect-error
Expand Down Expand Up @@ -199,4 +211,231 @@ describe('initCypressTests', () => {
})
})
})

describe('run all specs file loading', () => {
let mockRunAllSpecs: string[]
let mockRunModeSpecs: Array<{ relative: string, absolute: string }>

beforeEach(() => {
mockRunAllSpecs = ['src/Test1.cy.jsx', 'src/Test2.cy.jsx', 'src/Test3.cy.jsx']
mockRunModeSpecs = [
{ relative: 'src/Test1.cy.jsx', absolute: '/users/mock_dir/mock_project/src/Test1.cy.jsx' },
{ relative: 'src/Test2.cy.jsx', absolute: '/users/mock_dir/mock_project/src/Test2.cy.jsx' },
{ relative: 'src/Test3.cy.jsx', absolute: '/users/mock_dir/mock_project/src/Test3.cy.jsx' },
]

// @ts-expect-error
global.parent.__RUN_ALL_SPECS__ = mockRunAllSpecs
// @ts-expect-error
global.parent.__RUN_MODE_SPECS__ = mockRunModeSpecs
})

describe('when specPath is "__all"', () => {
beforeEach(() => {
// @ts-expect-error
global.document.location.search = '?specPath=__all'
})

it('doesn\'t load the support file if one is not provided', async () => {
mockSupportFile = undefined
await import('../client/initCypressTests.js')
// just includes the spec imports
expect(mockCypressInstance.onSpecWindow).toHaveBeenCalledWith(global.window, [
{
load: expect.any(Function),
absolute: '/users/mock_dir/mock_project/src/Test1.cy.jsx',
relative: 'src/Test1.cy.jsx',
relativeUrl: '/__cypress/src/@fs/users/mock_dir/mock_project/src/Test1.cy.jsx',
},
{
load: expect.any(Function),
absolute: '/users/mock_dir/mock_project/src/Test2.cy.jsx',
relative: 'src/Test2.cy.jsx',
relativeUrl: '/__cypress/src/@fs/users/mock_dir/mock_project/src/Test2.cy.jsx',
},
{
load: expect.any(Function),
absolute: '/users/mock_dir/mock_project/src/Test3.cy.jsx',
relative: 'src/Test3.cy.jsx',
relativeUrl: '/__cypress/src/@fs/users/mock_dir/mock_project/src/Test3.cy.jsx',
},
])
})

it('loads support file along with all specs', async () => {
await import('../client/initCypressTests.js')

expect(mockCypressInstance.onSpecWindow).toHaveBeenCalledWith(global.window, [
{
load: expect.any(Function),
absolute: '/users/mock_dir/mock_project/cypress/support/component.js',
relative: '/cypress/support/component.js',
relativeUrl: '/__cypress/src/cypress/support/component.js',
},
{
load: expect.any(Function),
absolute: '/users/mock_dir/mock_project/src/Test1.cy.jsx',
relative: 'src/Test1.cy.jsx',
relativeUrl: '/__cypress/src/@fs/users/mock_dir/mock_project/src/Test1.cy.jsx',
},
{
load: expect.any(Function),
absolute: '/users/mock_dir/mock_project/src/Test2.cy.jsx',
relative: 'src/Test2.cy.jsx',
relativeUrl: '/__cypress/src/@fs/users/mock_dir/mock_project/src/Test2.cy.jsx',
},
{
load: expect.any(Function),
absolute: '/users/mock_dir/mock_project/src/Test3.cy.jsx',
relative: 'src/Test3.cy.jsx',
relativeUrl: '/__cypress/src/@fs/users/mock_dir/mock_project/src/Test3.cy.jsx',
},
])
})

describe('empty devServerPublicPathRoute', () => {
it('load the support file along with all specs', async () => {
mockDevServerPublicPathRoute = ''
await import('../client/initCypressTests.js')

expect(mockCypressInstance.onSpecWindow).toHaveBeenCalledWith(global.window, [
{
load: expect.any(Function),
absolute: '/users/mock_dir/mock_project/cypress/support/component.js',
relative: '/cypress/support/component.js',
relativeUrl: './cypress/support/component.js',
},
{
load: expect.any(Function),
absolute: '/users/mock_dir/mock_project/src/Test1.cy.jsx',
relative: 'src/Test1.cy.jsx',
relativeUrl: './@fs/users/mock_dir/mock_project/src/Test1.cy.jsx',
},
{
load: expect.any(Function),
absolute: '/users/mock_dir/mock_project/src/Test2.cy.jsx',
relative: 'src/Test2.cy.jsx',
relativeUrl: './@fs/users/mock_dir/mock_project/src/Test2.cy.jsx',
},
{
load: expect.any(Function),
absolute: '/users/mock_dir/mock_project/src/Test3.cy.jsx',
relative: 'src/Test3.cy.jsx',
relativeUrl: './@fs/users/mock_dir/mock_project/src/Test3.cy.jsx',
},
])
})
})

describe('windows', () => {
beforeEach(() => {
mockPlatform = 'win32'
mockProjectRoot = 'C:\\users\\mock_user\\mock_dir\\mock_project'
mockSupportFile = 'C:\\users\\mock_user\\mock_dir\\mock_project\\cypress\\support\\component.js'
mockDevServerPublicPathRoute = '/__cypress/src'
mockRelativePath = '__all'
mockRunAllSpecs = ['src\\Test1.cy.jsx', 'src\\Test2.cy.jsx']
mockRunModeSpecs = [
{ relative: 'src\\Test1.cy.jsx', absolute: 'C:/users/mock_user/mock_dir/mock_project/src/Test1.cy.jsx' },
{ relative: 'src\\Test2.cy.jsx', absolute: 'C:/users/mock_user/mock_dir/mock_project/src/Test2.cy.jsx' },
]

mockCypressInstance.spec.relative = '__all'
// @ts-expect-error
global.parent.__RUN_ALL_SPECS__ = mockRunAllSpecs
// @ts-expect-error
global.parent.__RUN_MODE_SPECS__ = mockRunModeSpecs
})

it('loads support file along with all specs', async () => {
await import('../client/initCypressTests.js')

expect(mockCypressInstance.onSpecWindow).toHaveBeenCalledWith(global.window, [
{
load: expect.any(Function),
absolute: 'C:\\users\\mock_user\\mock_dir\\mock_project\\cypress\\support\\component.js',
relative: '/cypress/support/component.js',
relativeUrl: '/__cypress/src/cypress/support/component.js',
},
{
load: expect.any(Function),
absolute: 'C:/users/mock_user/mock_dir/mock_project/src/Test1.cy.jsx',
relative: 'src\\Test1.cy.jsx',
relativeUrl: '/__cypress/src/@fs/C:/users/mock_user/mock_dir/mock_project/src/Test1.cy.jsx',
},
{
load: expect.any(Function),
absolute: 'C:/users/mock_user/mock_dir/mock_project/src/Test2.cy.jsx',
relative: 'src\\Test2.cy.jsx',
relativeUrl: '/__cypress/src/@fs/C:/users/mock_user/mock_dir/mock_project/src/Test2.cy.jsx',
},
])
})
})
})

describe('when CypressInstance.spec.relative is "__all"', () => {
beforeEach(() => {
mockRelativePath = '__all'
mockCypressInstance.spec.relative = '__all'
})

it('doesn\'t load the support file if one is not provided', async () => {
mockSupportFile = undefined
await import('../client/initCypressTests.js')

expect(mockCypressInstance.onSpecWindow).toHaveBeenCalledWith(global.window, [
{
load: expect.any(Function),
absolute: '/users/mock_dir/mock_project/src/Test1.cy.jsx',
relative: 'src/Test1.cy.jsx',
relativeUrl: '/__cypress/src/@fs/users/mock_dir/mock_project/src/Test1.cy.jsx',
},
{
load: expect.any(Function),
absolute: '/users/mock_dir/mock_project/src/Test2.cy.jsx',
relative: 'src/Test2.cy.jsx',
relativeUrl: '/__cypress/src/@fs/users/mock_dir/mock_project/src/Test2.cy.jsx',
},
{
load: expect.any(Function),
absolute: '/users/mock_dir/mock_project/src/Test3.cy.jsx',
relative: 'src/Test3.cy.jsx',
relativeUrl: '/__cypress/src/@fs/users/mock_dir/mock_project/src/Test3.cy.jsx',
},
])
})

it('loads support file along with all specs', async () => {
await import('../client/initCypressTests.js')

expect(mockCypressInstance.onSpecWindow).toHaveBeenCalledWith(global.window, [
{
load: expect.any(Function),
absolute: '/users/mock_dir/mock_project/cypress/support/component.js',
relative: '/cypress/support/component.js',
relativeUrl: '/__cypress/src/cypress/support/component.js',
},
{
load: expect.any(Function),
absolute: '/users/mock_dir/mock_project/src/Test1.cy.jsx',
relative: 'src/Test1.cy.jsx',
relativeUrl: '/__cypress/src/@fs/users/mock_dir/mock_project/src/Test1.cy.jsx',
},
{
load: expect.any(Function),
absolute: '/users/mock_dir/mock_project/src/Test2.cy.jsx',
relative: 'src/Test2.cy.jsx',
relativeUrl: '/__cypress/src/@fs/users/mock_dir/mock_project/src/Test2.cy.jsx',
},
{
load: expect.any(Function),
absolute: '/users/mock_dir/mock_project/src/Test3.cy.jsx',
relative: 'src/Test3.cy.jsx',
relativeUrl: '/__cypress/src/@fs/users/mock_dir/mock_project/src/Test3.cy.jsx',
},
])
})
})
})
})
5 changes: 4 additions & 1 deletion npm/webpack-dev-server/src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ const makeImport = (file: Cypress.Cypress['spec'], filename: string, chunkName:
const magicComments = chunkName ? `/* webpackChunkName: "${chunkName}" */` : ''

return `"${filename}": {
shouldLoad: () => new URLSearchParams(document.location.search).get("specPath") === "${file.absolute}",
shouldLoad: () => {
const specPath = new URLSearchParams(document.location.search).get("specPath")
return specPath === "__all" || specPath === "${file.absolute}"
},
load: () => import("${file.absolute}" ${magicComments}),
absolute: "${file.absolute.split(path.sep).join(path.posix.sep)}",
relative: "${file.relative.split(path.sep).join(path.posix.sep)}",
Expand Down
6 changes: 2 additions & 4 deletions packages/app/src/store/run-all-specs-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,13 @@ export const useRunAllSpecsStore = defineStore('runAllSpecs', () => {
directoryChildrenRef.value = directoryChildren
}

const query = useQuery({ query: RunAllSpecsDataDocument, pause: isRunMode || window.__CYPRESS_TESTING_TYPE__ === 'component' })
const query = useQuery({ query: RunAllSpecsDataDocument, pause: isRunMode })

const isRunAllSpecsAllowed = computed(() => {
const isE2E = query.data.value?.currentProject?.currentTestingType === 'e2e'

const config: ResolvedConfig = query.data.value?.currentProject?.config || []
const hasExperiment = config.some(({ field, value }) => field === 'experimentalRunAllSpecs' && value === true)

return (isE2E && hasExperiment)
return hasExperiment
})

return {
Expand Down
10 changes: 0 additions & 10 deletions packages/config/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -664,11 +664,6 @@ export const breakingRootOptions: Array<BreakingOption> = [
errorKey: 'CONFIG_FILE_INVALID_ROOT_CONFIG',
isWarning: false,
testingTypes: ['e2e'],
}, {
name: 'experimentalRunAllSpecs',
errorKey: 'EXPERIMENTAL_RUN_ALL_SPECS_E2E_ONLY',
isWarning: false,
testingTypes: ['e2e'],
},
{
name: 'experimentalOriginDependencies',
Expand Down Expand Up @@ -724,11 +719,6 @@ export const testingTypeBreakingOptions: { e2e: Array<BreakingOption>, component
errorKey: 'CONFIG_FILE_INVALID_TESTING_TYPE_CONFIG_COMPONENT',
isWarning: false,
},
{
name: 'experimentalRunAllSpecs',
errorKey: 'EXPERIMENTAL_RUN_ALL_SPECS_E2E_ONLY',
isWarning: false,
},
{
name: 'experimentalOriginDependencies',
errorKey: 'EXPERIMENTAL_ORIGIN_DEPENDENCIES_E2E_ONLY',
Expand Down
1 change: 0 additions & 1 deletion packages/data-context/schemas/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1181,7 +1181,6 @@ enum ErrorTypeEnum {
EXPERIMENTAL_JIT_COMPILE_REMOVED
EXPERIMENTAL_ORIGIN_DEPENDENCIES_E2E_ONLY
EXPERIMENTAL_PROMPT_COMMAND_E2E_ONLY
EXPERIMENTAL_RUN_ALL_SPECS_E2E_ONLY
EXPERIMENTAL_SESSION_AND_ORIGIN_REMOVED
EXPERIMENTAL_SINGLE_TAB_RUN_MODE
EXPERIMENTAL_SKIP_DOMAIN_INJECTION_REMOVED
Expand Down
1 change: 1 addition & 0 deletions packages/data-context/src/sources/HtmlDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export class HtmlDataSource {
<body>
<script>
window.__RUN_MODE_SPECS__ = ${JSON.stringify(this.ctx.project.specs)}
window.__RUN_ALL_SPECS__ = ${JSON.stringify(this.ctx.project.runAllSpecs || [])}
window.__CYPRESS_MODE__ = ${JSON.stringify(this.ctx.isRunMode && !process.env.CYPRESS_INTERNAL_SIMULATE_OPEN_MODE ? 'run' : 'open')};
window.__CYPRESS_CONFIG__ = ${JSON.stringify(serveConfig)};
window.__CYPRESS_TESTING_TYPE__ = '${this.ctx.coreData.currentTestingType}'
Expand Down
Loading