Skip to content

feat(testing): support cypress v16 - #36953

Open
leosvelperez wants to merge 27 commits into
masterfrom
nxc-4544
Open

feat(testing): support cypress v16#36953
leosvelperez wants to merge 27 commits into
masterfrom
nxc-4544

Conversation

@leosvelperez

@leosvelperez leosvelperez commented Sep 8, 2026

Copy link
Copy Markdown
Member

Current Behavior

@nx/cypress declares a peer range of >= 13 < 16, so installing Cypress 16 fails peer resolution and nx migrate never offers the bump. Generators install Cypress 15. For zoneless Angular projects they import the mount helper from cypress/angular-zoneless, an entry point Cypress 16 does not ship. Nothing rewrites the config options and commands Cypress 16 removed or renamed.

Expected Behavior

Cypress 16 installs with the plugin, generators scaffold projects on it, and nx migrate moves a workspace on Cypress 15 to 16. The migration updates the renamed config options, the command overwrites that became queries, and the zoneless Angular mount import. The changes that need judgment go to the AI migration instructions. Cypress 13 to 15 keep working.

Related Issue(s)

NXC-4544

Implementation Notes

  • Angular component testing on Cypress 16 mounts through a zoneless harness for every project. A template re-renders on a signal write, not a plain property mutation. Verified on Angular 20 to 22, zone and zoneless. Angular 20 runs with a Cypress "not officially supported" warning and no fallback.
  • The Cypress.Commands.overwrite() rewrite is hybrid. The rename to overwriteQuery is mechanical. The prompt handles a callback that chains .then() or calls cy.*, which must return a function.
  • keystrokeDelay defaults to 0, down from 10. The migration does not restore 10, to keep the upstream default.
  • Generators use the installed Cypress when it satisfies the declared range, latest and next included. Otherwise a range counts as the highest supported major it reaches, where a clean install lands. Its floor is the lowest bound. A Cypress 16 prerelease, declared or installed, selects the 16 harness.
  • The Angular 22.1 guard and the zoneless 15.8 floor reject an installed Cypress below them or a range capped below. The guard reads Angular the same way: an installed Angular that satisfies the range decides.
  • The config options migration follows defineConfig() and the e2e/component blocks through same-file variables. An unresolved config becomes a next step. Spreads are not followed.
  • The mount import and query overwrite migrations scan the whole workspace, shared libraries included. A file binding its own Cypress or require is skipped and reported. Ambient and type-only declarations are not bindings.
  • cypressProjectConfigs yields each @nx/cypress:cypress target's config.

Migration coverage

Cypress 16 change Treatment
Cypress 16 peer and generator versions Version admission. packageJsonUpdates gated on Cypress 15
experimentalMemoryManagement, experimentalFastVisibility, experimentalSourceRewriting, allowCypressEnv, execTimeout Source transform. update-cypress-16-config-options
Cookie and storage getters became queries Hybrid. update-cypress-16-query-command-overwrites renames overwrite to overwriteQuery, the prompt adapts callbacks
cypress/angular-zoneless removed, @cypress/angular-zoneless deprecated Source transform. update-angular-zoneless-mount-import rewrites imports and drops the dependency
@angular/platform-browser-dynamic replaced Generator stops installing it on Cypress 16. Uninstall left to the instructions because the Jest generator installs it too
Cypress.env(), env test overrides, cy.exec(), cy.end() Prompt-only. create-ai-instructions-for-cypress-16. Each call needs a decision a transform cannot make, such as sensitive vs public value or the task body
Cypress.config() viewport and blockHosts writes, CoffeeScript, autoSpyOutputs and autoDetectChanges Prompt-only, same entry. Replacements change test structure
Native browser network, keystrokeDelay, Node floor, Electron deprecation Prompt-only, same entry. Behavior changes to review, not code to rewrite
Angular 18 to 20 floor Nothing. Verified to run on the new harness with a warning, documented
Vite 8, Next.js 15.0.4 floors Nothing here. Bumps live in @nx/vite and @nx/next
Bundled Node bump, cypress info output Nothing. No Nx surface

@netlify

netlify Bot commented Sep 8, 2026

Copy link
Copy Markdown

Deploy Preview for nx-docs ready!

Name Link
🔨 Latest commit b83d996
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/6aa26ca68a8c5d0008dd51b7
😎 Deploy Preview https://deploy-preview-36953--nx-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Sep 8, 2026

Copy link
Copy Markdown

Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit b83d996
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/6aa26ca6f2b7f30008228e58
😎 Deploy Preview https://deploy-preview-36953--nx-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@leosvelperez leosvelperez self-assigned this Sep 8, 2026
@nx-cloud

nx-cloud Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit b83d996

Command Status Duration Result
nx affected --targets=lint,oxlint,test,build,e2... ✅ Succeeded 39m 45s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 4s View ↗
nx-cloud record -- pnpm nx-cloud conformance:check ✅ Succeeded 1m 3s View ↗
nx build workspace-plugin ✅ Succeeded <1s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded 21s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 3s View ↗

☁️ Nx Cloud last updated this comment at 2026-09-10 09:23:23 UTC

@socket-security

socket-security Bot commented Sep 8, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatednpm/​cypress@​14.3.0 ⏵ 16.0.0911008497 +1100

View full report

@leosvelperez
leosvelperez force-pushed the nxc-4544 branch 2 times, most recently from 92c7510 to 7acdee2 Compare September 9, 2026 09:29
nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

@nx-cloud nx-cloud Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nx Cloud has identified a flaky task in your failed CI:

🔂 Since the failure was identified as flaky, we triggered a CI rerun by adding an empty commit to this branch.

Nx Cloud View detailed reasoning in Nx Cloud ↗

🔔 Heads up, your workspace has pending recommendations ↗ to auto-apply fixes for similar failures.


🎓 Learn more about Self-Healing CI on nx.dev

Cypress 16 is the latest major. `@nx/cypress` now accepts it as a peer
(`>= 13 < 17`), and the generators install Cypress `^16.0.0`,
`@cypress/vite-dev-server` `^8.0.0`, `@cypress/webpack-dev-server` `^6.0.0`
and Vite `^8.0.0` (the floor of the Vite dev server) on new setups. A
workspace that already has Cypress 15 keeps receiving the 15-compatible
versions through the version map, as 13 and 14 do.

`nx migrate` moves Cypress 15 workspaces to 16 together with the dev
server packages. The repo itself runs Cypress 16: the root pin, the
hardcoded pnpm path in the graph e2e tsconfig, and the e2e fixtures that
read env values (`Cypress.env()` throws in 16, so they use `cy.env()`).
…s v16

Cypress 16 renamed `experimentalMemoryManagement` to `manageBrowserMemory`,
replaced `experimentalFastVisibility` with `visibilityStrategy`, and
removed `experimentalSourceRewriting`, `allowCypressEnv` and `execTimeout`.
It also turned `getCookie`, `getCookies`, `getAllCookies`,
`getAllLocalStorage` and `getAllSessionStorage` into queries, so an
existing `Cypress.Commands.overwrite()` of any of them fails at runtime.

Two migrations gated on Cypress 16 cover this. The first rewrites the
option keys in the Cypress config's top level and `e2e`/`component`
blocks, keeping the key's quoting. It reports as next steps what it
cannot decide: a non-literal `experimentalFastVisibility` value, a removed
`execTimeout` (the `cy.task()` replacement uses `taskTimeout`) and a
removed `experimentalSourceRewriting: true` (`removeSRIAttributes` is the
replacement for Subresource Integrity workarounds). The second renames the
overwrite calls to `overwriteQuery()` and hands the callback review to an
AI prompt, since a query callback must return a function rather than a
chainable.
…cypress v16

Cypress 16 dropped the `cypress/angular-zoneless` entry point; its
`cypress/angular` harness mounts with zoneless change detection, requires
Angular 21 or later, and bootstraps through `@angular/platform-browser`
instead of `@angular/platform-browser-dynamic`. The Angular component
testing generator now writes `cypress/angular` on Cypress 16 and keeps
`cypress/angular-zoneless` for zoneless projects on Cypress 15.8 to 15.x,
and the component test generator only adds
`@angular/platform-browser-dynamic` for Cypress versions below 16. A
migration gated on Cypress 16 rewrites existing `cypress/angular-zoneless`
module references in Cypress projects. The generator spec drops the Vite 7
pin it carried while Cypress lacked Vite 8 support.
Cypress 16 removed `Cypress.env()`, `cy.exec()` and `.end()`, dropped `env`
from test-configuration overrides, and stopped accepting `blockHosts` and
the viewport options through `Cypress.config()` during a test. The
replacement for each depends on where the value comes from (`cy.env()`
versus the new `expose` sources, a `cy.task()` in `setupNodeEvents`), so
a prompt migration gated on Cypress 16 walks an agent through them and
through the component testing changes (zoneless Angular harness, Vite 8,
Electron deprecation).
The supported range becomes `>= 13 < 17`. The environment variables
section explains how tests read `env` values on Cypress 16 (`cy.env()`,
since `Cypress.env()` was removed) and when `expose` applies. The
component testing guide notes the zoneless `cypress/angular` harness and
what that means for zone-based Angular applications.
…d range floor

`getDeclaredPackageVersion` coerces a range to its first version, so a range
spanning majors (`>=15.20.1 <17`) always reads as the lower major even when the
upper one is installed. `assertSupportedPackageVersion` already prefers the
installed version when it satisfies the declared range; extract that resolution
into `getResolvedPackageVersion` so version selection can use the same answer
as the floor check.
… declared range

`getInstalledCypressVersion(tree)` read only the declared range, so a range
spanning Cypress 15 and 16 selected the Cypress 15 dependency set and, for
zoneless Angular projects, the `cypress/angular-zoneless` import that Cypress
16 no longer ships. The floor check in the same generator run already resolved
the installed version first; use the shared devkit helper so both agree.
…rget

`cypressProjectConfigs` picked the first target using `@nx/cypress:cypress`,
so a project with an `e2e` and a `component-test` target on separate config
files only had the first one migrated. Enumerate every such target and yield
each distinct config path once.
…g options

The config options migration only dispatched `PropertyAssignment` nodes with
an identifier or string name, so `{ experimentalFastVisibility }` and
`['experimentalMemoryManagement']: true` kept the removed option with no
next step while the package update moved the workspace to Cypress 16.

Shorthand and statically computed keys are now handled: a renamed shorthand
becomes `manageBrowserMemory: experimentalMemoryManagement`, a removed
shorthand is deleted, and a shorthand `experimentalFastVisibility` or
`experimentalSourceRewriting` is reported since its value is unknown.

The `quoteLike` helper is dropped: an unquoted key is valid in every config
and `formatFiles` normalizes the style anyway, so the quoted-keys test could
not observe it. The test now states what it covers, that quoted legacy keys
are recognized.
The zoneless mount import and query command overwrite migrations visited
only the roots of projects owning a Cypress config, so a shared library
exporting the mount helper or registering an overwrite kept the removed
`cypress/angular-zoneless` import or the obsolete `overwrite()` call.

Both migrations now scan every non-ignored JavaScript and TypeScript file
in the workspace once, with the same string prefilter, matching the
workspace-wide scan of `rewrite-internal-subpath-imports`.
…nternal entry point

Lets plugin version gates check what is installed while keeping their own
declared-range fallback for the fresh-install path.
…2.1 component testing

The guard only rejected declared ranges wholly below 15.20.1, so a range
spanning the floor such as `>=15.0.0 <17` passed with Cypress 15.17.0
installed and the generator wrote a component testing setup that cannot
load Angular's Babel 8 dependencies.

The guard now checks the installed version when it satisfies the declared
range and keeps the range check for workspaces with nothing installed.
Asserts the `23.3.0-cypress-16` group applies to Cypress 15 only and lands
`^16.0.0` with the matching dev servers, and that each gated 23.3.0
migration requires the landing version, mirroring how nx evaluates
`requires`.
…ed major

With nothing installed, the resolved Cypress version was the floor of the
declared range, so `>=15.20.1 <17` selected the Cypress 15 tuple and the
zoneless Angular generator wrote the `cypress/angular-zoneless` import that
the Cypress 16 a clean install resolves no longer exports. The resolver now
follows the highest supported major the range reaches.

The zoneless floor check in the Angular generator shares the guard's
installed-vs-range decision: an installed version decides, otherwise only a
range capped below 15.8.0 fails, so `>=14 <15.10` no longer throws for a
floor the install never produces.
The config resolver followed an exported variable only when it held an
object literal, so `const config = defineConfig({...}); export default
config` resolved to nothing and the migration skipped the file without a
word. The resolver now unwraps `defineConfig()` and follows same-file
variables at any depth, which also reaches the two earlier migrations that
share it. The migration edits `e2e`/`component` blocks held in variables
and lists a config it cannot resolve as a next step when it mentions one of
the changed options.
…lling

The query-overwrite migration renamed every `Cypress.Commands.overwrite()`
spelled with dot access, so `Cypress.Commands['overwrite']('getCookie', ...)`
survived into Cypress 16 and a file with its own `Cypress` value was
rewritten as if it called the global. Both workspace-wide migrations now
skip a file that binds the identifier they rely on (`Cypress`, or `require`
for the CommonJS rewrite) as a runtime value, list it as a next step, and
the query migration recognizes the static bracket spellings. Ambient
declarations, `declare global { namespace Cypress { ... } }` included, and
type-only imports do not count as bindings.

The mount migration no longer returns the install callback: the migration
runner discards it and installs from the package.json diff.
leosvelperez and others added 10 commits September 10, 2026 10:33
…d version

`latest` and `next` never satisfy a semver range, so the resolver ignored
the install and the Angular 22.1 component-testing guard let an installed
Cypress 15.17 through. The tag resolved to that install, so the installed
version now decides for both; with nothing installed the resolver still
falls back to `latestKnownVersion`.
…ent once

A variable holding the block for both testing types resolved to the same
node twice, so its edits applied twice and corrupted the file.
…indings

`const Fake = class Cypress {}` and `function require() {}` expressions
bind the name for the migrations that skip shadowed files, same as the
declaration forms.
`getResolvedPackageVersion` normalized the declared range with
`semver.coerce`, which returns the first numeric tuple in the string. A
range that lists its upper bound first, such as `<16 >=15.8.0`, resolved
to `16.0.0` instead of `15.8.0`. The Cypress generators read that value
when nothing is installed, so a zoneless Angular project on such a range
got the Cypress 16 `cypress/angular` mount import and the Vite dev server
that peers on Cypress 16, while a clean install lands on Cypress 15.

The floor is `semver.minVersion` of the range. A range nothing satisfies
has no minimum and falls back to the coerced declaration as before.

A prerelease floor (`^16.0.0-beta.1`) is below `16.0.0`, so the Angular
generator's mount selection compares majors instead. An installed
`16.0.0-beta.1` hit the same comparison.
The heading read as if every supported Cypress version lost the API.
Cypress 13 to 15 keep it.
…nent testing guard

The Angular 22.1 guard read `@angular/core` from the declared range with
`semver.coerce`, which takes the first number in the string. A range that
lists its upper bound first, such as `<22.1 >=22.0`, read as 22.1 and the
guard required Cypress 15.20.1 for a workspace that can only install
Angular 22.0. An installed Angular that satisfied the range was ignored.

The guard resolves the version the same way the Cypress generators do:
the installed version when it satisfies the declared range, otherwise the
range's lowest bound.
…s trailing trivia

`removeProperty` in the Cypress 16 config options migration looked for the
separator only at `property.getEnd()`, which stops before trailing trivia.
A comment, a space or a line break between the value and the comma left
the comma behind, and the migrated config failed to parse with "Property
assignment expected". Scan forward across the trivia and delete through the
comma when it is the next token.
The branch also runs when an installed Cypress no longer satisfies a
repinned range, and an unsatisfiable range resolves to the coerced
declaration rather than a floor.
@leosvelperez
leosvelperez marked this pull request as ready for review September 10, 2026 09:24
@leosvelperez
leosvelperez requested a review from a team as a code owner September 10, 2026 09:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant