Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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).
-
**Performance:**

- Limits the number of matched elements that are tested for visibility when added to a command log entry. Fixes a crash scenario related to rapid successive DOM additions in conjunction with a large number of elements returned from a query. Addressed in [#32937](https://github.com/cypress-io/cypress/pull/32937).
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