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
2 changes: 1 addition & 1 deletion .circleci/src/pipeline/@pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,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/angular_dep_resolution_windows" ]]; then
echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "feat/support_angular_21" ]]; 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
Expand Down
2 changes: 1 addition & 1 deletion .circleci/src/pipeline/workflows/@main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ linux-x64:
- 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/angular_dep_resolution_windows', << pipeline.git.branch >> ]
- equal: [ 'feat/support_angular_21', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
Expand Down
4 changes: 4 additions & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ _Released 12/2/2025 (PENDING)_

- Improved performance when viewing command snapshots in the Command Log. Element highlighting is now significantly faster, especially when highlighting multiple elements or complex pages. This is achieved by reducing redundant style calculations and batching DOM operations to minimize browser reflows. Addressed in [#32951](https://github.com/cypress-io/cypress/pull/32951).

**Features:**

- `Angular` version 21 is now supported within component testing. Addressed in [#33004](https://github.com/cypress-io/cypress/pull/33004).

## 15.7.0

_Released 11/19/2025_
Expand Down
2 changes: 1 addition & 1 deletion npm/cypress-schematic/src/ct.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const copyAngularMount = async (projectPath: string) => {

const cypressSchematicPackagePath = path.join(__dirname, '..')

const ANGULAR_PROJECTS: ProjectFixtureDir[] = ['angular-19', 'angular-20']
const ANGULAR_PROJECTS: ProjectFixtureDir[] = ['angular-20', 'angular-21']

const timeout = 1000 * 60 * 5

Expand Down
2 changes: 1 addition & 1 deletion npm/cypress-schematic/src/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const runCommandInProject = (command: string, projectPath: string) => {

const cypressSchematicPackagePath = path.join(__dirname, '..')

const ANGULAR_PROJECTS: ProjectFixtureDir[] = ['angular-19', 'angular-20']
const ANGULAR_PROJECTS: ProjectFixtureDir[] = ['angular-20', 'angular-21']

describe('ng add @cypress/schematic / only e2e', { timeout: 1000 * 60 * 5 }, function () {
for (const project of ANGULAR_PROJECTS) {
Expand Down
1 change: 1 addition & 0 deletions npm/webpack-dev-server/cypress/e2e/angular.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const WEBPACK_ANGULAR: ProjectFixtureDir[] = [
'angular-18',
'angular-19',
'angular-20',
'angular-21',
]

// Add to this list to focus on a particular permutation
Expand Down
29 changes: 10 additions & 19 deletions npm/webpack-dev-server/test/handlers/angularHandler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {
import { scaffoldMigrationProject } from '../test-helpers/scaffoldProject'

describe('angularHandler', { timeout: 60000 }, function () {
it('sources the config from angular-19', async () => {
const projectRoot = await scaffoldMigrationProject('angular-19')
it('sources the config from angular-20', async () => {
const projectRoot = await scaffoldMigrationProject('angular-20')

process.chdir(projectRoot)
const devServerConfig = {
Expand All @@ -33,39 +33,34 @@ describe('angularHandler', { timeout: 60000 }, function () {
expect(webpackConfig).toBeDefined()
expect((webpackConfig?.entry as any).main).toBeUndefined()
expect(sourceWebpackModulesResult.framework?.importPath).toContain(path.join('@angular-devkit', 'build-angular'))
expect(webpackConfig.stats).toEqual('errors-only')
const projectConfig = await getProjectConfig(projectRoot)

expect(projectConfig).toEqual({
root: '',
sourceRoot: 'src',
buildOptions: {
outputPath: 'dist/angular',
index: 'src/index.html',
main: 'src/main.ts',
polyfills: 'src/polyfills.ts',
browser: 'src/main.ts',
// because of the way the main fixtures are configured in the system-test projects, we need to run as a zone.js application
polyfills: [
'zone.js',
],
tsConfig: 'tsconfig.app.json',
inlineStyleLanguage: 'scss',
assets: ['src/favicon.ico', 'src/assets'],
styles: ['src/styles.scss'],
scripts: [],
buildOptimizer: false,
optimization: false,
vendorChunk: true,
extractLicenses: false,
sourceMap: true,
namedChunks: true,
},
})

await expectLoadsAngularJson(projectRoot)
await expectLoadsAngularCLiModules(projectRoot)
await expectGeneratesTsConfig(devServerConfig, projectConfig.buildOptions, true)
await expectGeneratesTsConfig(devServerConfig, projectConfig.buildOptions, false)
expectLoadsAngularBuildOptions(projectConfig.buildOptions)
})

it('sources the config from angular-20', async () => {
const projectRoot = await scaffoldMigrationProject('angular-20')
it('sources the config from angular-21', async () => {
const projectRoot = await scaffoldMigrationProject('angular-21')

process.chdir(projectRoot)
const devServerConfig = {
Expand All @@ -87,10 +82,6 @@ describe('angularHandler', { timeout: 60000 }, function () {
sourceRoot: 'src',
buildOptions: {
browser: 'src/main.ts',
// because of the way the main fixtures are configured in the system-test projects, we need to run as a zone.js application
polyfills: [
'zone.js',
],
tsConfig: 'tsconfig.app.json',
assets: ['src/favicon.ico', 'src/assets'],
styles: ['src/styles.scss'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { fixtureDirs } from '@tooling/system-tests'
type ProjectDirs = typeof fixtureDirs

const PROJECTS: {projectName: ProjectDirs[number], test: string}[] = [
{ projectName: 'angular-20', test: 'app.component' },
{ projectName: 'angular-21', test: 'app.component' },
{ projectName: 'react-vite-ts-configured', test: 'App.cy' },
{ projectName: 'react18', test: 'App.cy' },
{ projectName: 'next-14', test: 'index.cy' },
Expand Down
20 changes: 10 additions & 10 deletions packages/scaffold-config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ We will also attempt to scaffold a configuration file for projects using React a

### Supported Frameworks and Libraries

| Name | Version | Dev Server | Version | Library | Component Adaptor | Example Project |
| ---------------- | -----------| ---------- | ---------- | ------------------ | -------------------------- | ------------------------------------------------------------------- |
| React | - | Vite | 5, 6, 7 | React 18, 19 | `@cypress/react@latest` | [Link](../../system-tests/projects/react-vite-ts-configured) |
| React | - | Webpack | 5 | React 18, 19 | `@cypress/vue@latest` | [Link](../../system-tests/projects/react18) |
| Vue | - | Vite | 5, 6, 7 | Vue 3 | `@cypress/react@latest` | [Link](../../system-tests/projects/vue3-vite-ts-configured) |
| Vue | - | Webpack | 5 | Vue 3 | `@cypress/vue@latest` | [Link](../../system-tests/projects/vue3-webpack-ts-configured) |
| Angular | - | Webpack | 5 | Angular 17, 18, 19 | `@cypress/angular@latest` | [Link](../../system-tests/projects/angular-cli-configured) |
| Svelte | - | Vite | 5, 6, 7 | Svelte 5 | `@cypress/svelte@latest` | [Link](../../system-tests/projects/svelte-vite-configured) |
| Svelte | - | Webpack | 5 | Svelte 5 | `@cypress/svelte@latest` | [Link](../../system-tests/projects/svelte-webpack-configured) |
| Next.js | 14, 15, 16 | Webpack | 5 | React 18, 19 | `@cypress/react@latest` | [Link](../../system-tests/projects/nextjs-configured) |
| Name | Version | Dev Server | Version | Library | Component Adaptor | Example Project |
| ---------------- | -----------| ---------- | ---------- | ---------------------- | -------------------------- | ------------------------------------------------------------------- |
| React | - | Vite | 5, 6, 7 | React 18, 19 | `@cypress/react@latest` | [Link](../../system-tests/projects/react-vite-ts-configured) |
| React | - | Webpack | 5 | React 18, 19 | `@cypress/vue@latest` | [Link](../../system-tests/projects/react18) |
| Vue | - | Vite | 5, 6, 7 | Vue 3 | `@cypress/react@latest` | [Link](../../system-tests/projects/vue3-vite-ts-configured) |
| Vue | - | Webpack | 5 | Vue 3 | `@cypress/vue@latest` | [Link](../../system-tests/projects/vue3-webpack-ts-configured) |
| Angular | - | Webpack | 5 | Angular 18, 19, 20, 21 | `@cypress/angular@latest` | [Link](../../system-tests/projects/angular-cli-configured) |
| Svelte | - | Vite | 5, 6, 7 | Svelte 5 | `@cypress/svelte@latest` | [Link](../../system-tests/projects/svelte-vite-configured) |
| Svelte | - | Webpack | 5 | Svelte 5 | `@cypress/svelte@latest` | [Link](../../system-tests/projects/svelte-webpack-configured) |
| Next.js | 14, 15, 16 | Webpack | 5 | React 18, 19 | `@cypress/react@latest` | [Link](../../system-tests/projects/nextjs-configured) |

### Adding More Projects

Expand Down
10 changes: 5 additions & 5 deletions packages/scaffold-config/src/dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export const WIZARD_DEPENDENCY_ANGULAR_CLI = {
package: '@angular/cli',
installer: '@angular/cli',
description: 'CLI tool that you use to initialize, develop, scaffold, and maintain Angular applications.',
minVersion: '^18.0.0 || ^19.0.0 || ^20.0.0',
minVersion: '^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0',
} as const

export const WIZARD_DEPENDENCY_ANGULAR_DEVKIT_BUILD_ANGULAR = {
Expand All @@ -79,7 +79,7 @@ export const WIZARD_DEPENDENCY_ANGULAR_DEVKIT_BUILD_ANGULAR = {
package: '@angular-devkit/build-angular',
installer: '@angular-devkit/build-angular',
description: 'Angular Webpack build facade',
minVersion: '^18.0.0 || ^19.0.0 || ^20.0.0',
minVersion: '^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0',
} as const

export const WIZARD_DEPENDENCY_ANGULAR_CORE = {
Expand All @@ -88,7 +88,7 @@ export const WIZARD_DEPENDENCY_ANGULAR_CORE = {
package: '@angular/core',
installer: '@angular/core',
description: 'The core of the Angular framework',
minVersion: '^18.0.0 || ^19.0.0 || ^20.0.0',
minVersion: '^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0',
} as const

export const WIZARD_DEPENDENCY_ANGULAR_COMMON = {
Expand All @@ -97,7 +97,7 @@ export const WIZARD_DEPENDENCY_ANGULAR_COMMON = {
package: '@angular/common',
installer: '@angular/common',
description: 'Commonly needed Angular directives and services',
minVersion: '^18.0.0 || ^19.0.0 || ^20.0.0',
minVersion: '^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0',
} as const

export const WIZARD_DEPENDENCY_ANGULAR_PLATFORM_BROWSER_DYNAMIC = {
Expand All @@ -106,7 +106,7 @@ export const WIZARD_DEPENDENCY_ANGULAR_PLATFORM_BROWSER_DYNAMIC = {
package: '@angular/platform-browser-dynamic',
installer: '@angular/platform-browser-dynamic',
description: 'Library for using Angular in a web browser with JIT compilation',
minVersion: '^18.0.0 || ^19.0.0 || ^20.0.0',
minVersion: '^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0',
} as const

export const WIZARD_DEPENDENCY_SVELTE: Cypress.CypressComponentDependency = {
Expand Down
2 changes: 1 addition & 1 deletion packages/scaffold-config/test/detect.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ describe('detectFramework', () => {
})
})

;['18.2.0', '19.2.9'].forEach((v) => {
;['20.1.0', '21.0.0'].forEach((v) => {
it(`Angular CLI v${v}`, async () => {
const projectPath = await scaffoldMigrationProject('angular-cli-unconfigured')

Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import { Component } from '@angular/core'
import { Component, signal } from '@angular/core'
import { ChildProvidersService } from './child-providers.service'
import { take } from 'rxjs/operators'

@Component({
selector: 'app-another-child',
standalone: false,
template: `<button (click)="handleClick()">{{ message }}</button>`,
template: `<button (click)="handleClick()">{{ message() }}</button>`,
providers: [ChildProvidersService],
})
export class AnotherChildProvidersComponent {
message = 'default another child message'
message = signal('default another child message')

constructor (private readonly service: ChildProvidersService) {}

handleClick (): void {
this.service.getMessage().pipe(
take(1),
).subscribe((message) => this.message = message)
async handleClick (): Promise<void> {
const message = await this.service.getMessage()

this.message.set(message)
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import { Component } from '@angular/core'
import { Component, signal } from '@angular/core'
import { ChildProvidersService } from './child-providers.service'
import { take } from 'rxjs/operators'

@Component({
selector: 'app-child-providers',
standalone: false,
template: `<button (click)="handleClick()">{{ message }}</button>`,
template: `<button (click)="handleClick()">{{ message() }}</button>`,
})
export class ChildProvidersComponent {
message = 'default message'
message = signal('default message')

constructor (private readonly service: ChildProvidersService) {}

handleClick (): void {
this.service.getMessage().pipe(
take(1),
).subscribe((message) => this.message = message)
async handleClick (): Promise<void> {
const message = await this.service.getMessage()

this.message.set(message)
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import { Injectable } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'

@Injectable()
export class ChildProvidersService {
constructor (private readonly http: HttpClient) {}
async getMessage (): Promise<string> {
const response = await fetch('https://myfakeapiurl.com/api/message')
const data = await response.json()

getMessage (): Observable<string> {
return this.http.get<{ message: string }>('https://myfakeapiurl.com/api/message').pipe(
map((response) => response.message),
)
return data.message
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
import { Component } from '@angular/core'
import { CounterService } from './counter.service'
import { Component, model } from '@angular/core'

@Component({
selector: 'counter-component',
standalone: false,
template: `<button (click)="increment()">
Increment: {{ count$ | async }}
Increment: {{ count() }}
</button>`,
})
export class CounterComponent {
count$

constructor (private counterService: CounterService) {
this.count$ = this.counterService.count$
}
count = model<number>(0)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Incorrect use of model signal for internal state

The model() function is used for count, but model() is specifically for two-way binding with parent components (input/output), not for internal component state. For internal state management, signal() should be used instead. This will cause the component to not work correctly as model() expects to be bound to a parent component property.

Fix in Cursor Fix in Web


increment () {
this.counterService.increment()
this.count.set(this.count() + 1)
}
}

This file was deleted.

Loading
Loading