Skip to content

Commit 928072c

Browse files
FRSgitclaude
andcommitted
feat: migrate plugin options from Cypress.env() to Cypress.expose() for Cypress 16 compatibility
Cypress 16 removes Cypress.env() in favor of the synchronous Cypress.expose() API (available since 15.10.0). The plugin now detects the Cypress version at runtime and uses Cypress.expose()/config.expose for >=15.10.0 and falls back to the legacy Cypress.env()/config.env for older versions. Closes #375 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent a647cf1 commit 928072c

8 files changed

Lines changed: 211 additions & 19 deletions

File tree

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Migration Guide
2+
3+
## Migrating to Cypress 16 (`Cypress.expose` API)
4+
5+
Cypress 16 removes `Cypress.env()` in favor of the new `Cypress.expose()` API (introduced in Cypress 15.10.0).
6+
This plugin supports both APIs automatically based on the detected Cypress version:
7+
8+
- **Cypress ≥ 15.10** – reads plugin options via `Cypress.expose()` / `config.expose`
9+
- **Cypress < 15.10** – reads plugin options via `Cypress.env()` / `config.env` (legacy)
10+
11+
No code change is required inside your tests. You only need to update **how you supply plugin options**.
12+
13+
### What changed
14+
15+
| Location | Before (all Cypress versions) | After (Cypress ≥ 15.10) |
16+
|---|---|---|
17+
| CLI flag | `--env "key=value"` | `--expose "key=value"` |
18+
| `cypress.config.ts` | `env: { key: value }` | `expose: { key: value }` |
19+
| `cypress.env.json` | `{ "key": "value" }` | use `expose` in `cypress.config.ts` instead |
20+
21+
### Plugin option names
22+
23+
All option names stay the same — they are prefixed with `pluginVisualRegression`:
24+
25+
| Option | Config key |
26+
|---|---|
27+
| `updateImages` | `pluginVisualRegressionUpdateImages` |
28+
| `cleanupUnusedImages` | `pluginVisualRegressionCleanupUnusedImages` |
29+
| `diffConfig` | `pluginVisualRegressionDiffConfig` |
30+
| `forceDeviceScaleFactor` | `pluginVisualRegressionForceDeviceScaleFactor` |
31+
| `maxDiffThreshold` | `pluginVisualRegressionMaxDiffThreshold` |
32+
33+
### CLI
34+
35+
```bash
36+
# Before (deprecated in 15.10, removed in 16)
37+
npx cypress run --env "pluginVisualRegressionUpdateImages=true"
38+
39+
# After (Cypress ≥ 15.10)
40+
npx cypress run --expose "pluginVisualRegressionUpdateImages=true"
41+
```
42+
43+
### cypress.config.ts
44+
45+
```ts
46+
// Before
47+
export default defineConfig({
48+
env: {
49+
pluginVisualRegressionUpdateImages: true,
50+
pluginVisualRegressionDiffConfig: { threshold: 0.01 },
51+
},
52+
});
53+
54+
// After (Cypress ≥ 15.10)
55+
export default defineConfig({
56+
expose: {
57+
pluginVisualRegressionUpdateImages: true,
58+
pluginVisualRegressionDiffConfig: { threshold: 0.01 },
59+
},
60+
});
61+
```
62+
63+
### cypress.env.json
64+
65+
`cypress.env.json` only works with `Cypress.env()` and is not supported by the `expose` API.
66+
Migrate any plugin options from `cypress.env.json` into the `expose` block of `cypress.config.ts`.
67+
68+
```jsonc
69+
// cypress.env.json — REMOVE these plugin keys:
70+
{
71+
// "pluginVisualRegressionUpdateImages": true, ← move to cypress.config.ts expose block
72+
}
73+
```
74+
75+
### Supporting both Cypress 15.x and 16.x simultaneously
76+
77+
If you need your config to work on both Cypress 15.9 and below **and** 15.10+, you can supply the
78+
option in both places — the plugin will prefer `expose` on newer Cypress and fall back to `env` on
79+
older ones:
80+
81+
```ts
82+
export default defineConfig({
83+
expose: {
84+
pluginVisualRegressionUpdateImages: true,
85+
},
86+
env: {
87+
pluginVisualRegressionUpdateImages: true, // fallback for Cypress < 15.10
88+
},
89+
});
90+
```
91+
92+
### References
93+
94+
- [Cypress `Cypress.env()` migration guide](https://docs.cypress.io/app/references/migration-guide#Migrating-away-from-Cypressenv)
95+
- [Cypress `expose` API docs](https://on.cypress.io/expose)
96+
- [Issue #375](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/375)

packages/cypress-plugin-visual-regression-diff/README.md

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,12 @@ Still got troubles with installation? Have a look at [examples directory of this
158158
## Automatic clean up of unused images
159159

160160
It's useful to remove screenshots generated by the visual regression plugin that are not used by any test anymore.
161-
Enable this feature via env variable and enjoy freed up storage space 🚀:
161+
Enable this feature via expose variable and enjoy freed up storage space 🚀:
162162

163163
```bash
164+
# Cypress 15.10+
165+
npx cypress run --expose "pluginVisualRegressionCleanupUnusedImages=true"
166+
# Cypress <15.10 (deprecated in 15.10, removed in 16)
164167
npx cypress run --env "pluginVisualRegressionCleanupUnusedImages=true"
165168
```
166169

@@ -216,33 +219,40 @@ cy.matchImage({
216219
});
217220
```
218221

219-
- via [global env configuration](https://docs.cypress.io/guides/guides/environment-variables#Setting). Environment variable names are the same as keys of the configuration object above, but with added `pluginVisualRegression` prefix, e.g.:
222+
- via [global expose configuration](https://on.cypress.io/expose) (Cypress 15.10+). Configuration key names are the same as the keys of the configuration object above, but with added `pluginVisualRegression` prefix, e.g.:
220223

221224
```bash
225+
# Cypress 15.10+
226+
npx cypress run --expose "pluginVisualRegressionUpdateImages=true" --expose "pluginVisualRegressionDiffConfig={\"threshold\":0.01}"
227+
# Cypress <15.10 (deprecated in 15.10, removed in 16)
222228
npx cypress run --env "pluginVisualRegressionUpdateImages=true,pluginVisualRegressionDiffConfig={\"threshold\":0.01}"
223229
```
224230

225231
```ts
226-
// cypress.config.ts
232+
// cypress.config.ts (Cypress 15.10+)
227233
import { defineConfig } from 'cypress';
228234

229235
export default defineConfig({
230-
env: {
236+
expose: {
231237
pluginVisualRegressionUpdateImages: true,
232238
pluginVisualRegressionDiffConfig: { threshold: 0.01 },
233239
},
234240
});
235241
```
236242

237-
```json
238-
// cypress.env.json (https://docs.cypress.io/guides/guides/environment-variables#Option-2-cypress-env-json)
239-
{
240-
"pluginVisualRegressionUpdateImages": true,
241-
"pluginVisualRegressionDiffConfig": { "threshold": 0.01 }
242-
}
243+
```ts
244+
// cypress.config.ts (Cypress <15.10, deprecated)
245+
import { defineConfig } from 'cypress';
246+
247+
export default defineConfig({
248+
env: {
249+
pluginVisualRegressionUpdateImages: true,
250+
pluginVisualRegressionDiffConfig: { threshold: 0.01 },
251+
},
252+
});
243253
```
244254

245-
For more ways of setting environment variables [take a look here](https://docs.cypress.io/guides/guides/environment-variables#Setting).
255+
For more ways of setting expose variables [take a look here](https://on.cypress.io/expose).
246256

247257
## FAQ
248258

@@ -269,9 +279,12 @@ This is typically caused by device pixel ratio differences between headless and
269279
cy.matchImage({ forceDeviceScaleFactor: true });
270280
```
271281

272-
Or set it globally via environment variable:
282+
Or set it globally via expose variable:
273283

274284
```bash
285+
# Cypress 15.10+
286+
npx cypress run --expose "pluginVisualRegressionForceDeviceScaleFactor=true"
287+
# Cypress <15.10 (deprecated in 15.10, removed in 16)
275288
npx cypress run --env "pluginVisualRegressionForceDeviceScaleFactor=true"
276289
```
277290

@@ -340,9 +353,12 @@ Set `maxDiffThreshold` to `0`:
340353
cy.matchImage({ maxDiffThreshold: 0 });
341354
```
342355

343-
Or globally via an environment variable:
356+
Or globally via an expose variable:
344357

345358
```bash
359+
# Cypress 15.10+
360+
npx cypress run --expose "pluginVisualRegressionMaxDiffThreshold=0"
361+
# Cypress <15.10 (deprecated in 15.10, removed in 16)
346362
npx cypress run --env "pluginVisualRegressionMaxDiffThreshold=0"
347363
```
348364

packages/cypress-plugin-visual-regression-diff/src/commands.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { FILE_SUFFIX, LINK_PREFIX, TASK } from './constants';
2+
import { supportsExpose } from './version.utils';
23
import type pixelmatch from 'pixelmatch';
34
import * as Base64 from '@frsource/base64';
45
import type { CompareImagesTaskReturn } from './types';
@@ -58,9 +59,14 @@ const constructCypressError = (log: Cypress.Log, err: Error) => {
5859
const capitalize = (text: string) =>
5960
text.charAt(0).toUpperCase() + text.slice(1);
6061

61-
const getPluginEnv = <K extends keyof Cypress.MatchImageOptions>(key: K) =>
62-
Cypress.env(`pluginVisualRegression${capitalize(key)}`) as
63-
Cypress.MatchImageOptions[K] | undefined;
62+
const getPluginEnv = <K extends keyof Cypress.MatchImageOptions>(key: K) => {
63+
const envKey = `pluginVisualRegression${capitalize(key)}`;
64+
if (supportsExpose(Cypress.version)) {
65+
return Cypress.expose(envKey) as Cypress.MatchImageOptions[K] | undefined;
66+
}
67+
// eslint-disable-next-line @typescript-eslint/no-deprecated
68+
return Cypress.env(envKey) as Cypress.MatchImageOptions[K] | undefined;
69+
};
6470

6571
const booleanOption = <K extends keyof Cypress.MatchImageOptions, Return>(
6672
options: Cypress.MatchImageOptions,

packages/cypress-plugin-visual-regression-diff/src/plugins.test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ vi.mock('./afterScreenshot.hook.ts', () => ({
1111
}));
1212

1313
describe('initPlugin', () => {
14-
it('inits hooks', () => {
14+
it('inits hooks (Cypress <15.10, env API)', () => {
1515
const onMock = vi.fn();
1616
initPlugin(onMock, {
17+
version: '13.17.0',
1718
env: { pluginVisualRegressionForceDeviceScaleFactor: false },
1819
} as unknown as Cypress.PluginConfigOptions);
1920

@@ -22,4 +23,18 @@ describe('initPlugin', () => {
2223
expect(initTaskHook).toBeCalledTimes(1);
2324
expect(initAfterScreenshotHook).toBeCalledTimes(1);
2425
});
26+
27+
it('inits hooks (Cypress 15.10+, expose API)', () => {
28+
const onMock = vi.fn();
29+
initPlugin(onMock, {
30+
version: '15.10.0',
31+
expose: { pluginVisualRegressionForceDeviceScaleFactor: false },
32+
env: {},
33+
} as unknown as Cypress.PluginConfigOptions);
34+
35+
expect(onMock).toBeCalledWith('task', 'task');
36+
expect(onMock).toBeCalledWith('after:screenshot', 'after:screenshot');
37+
expect(initTaskHook).toBeCalledTimes(2);
38+
expect(initAfterScreenshotHook).toBeCalledTimes(2);
39+
});
2540
});

packages/cypress-plugin-visual-regression-diff/src/plugins.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { initTaskHook } from './task.hook';
22
import { initAfterScreenshotHook } from './afterScreenshot.hook';
3+
import { getPluginConfig } from './version.utils';
34

45
/* c8 ignore start */
56
const initForceDeviceScaleFactor = (on: Cypress.PluginEvents) => {
@@ -24,7 +25,7 @@ export const initPlugin = (
2425
config: Cypress.PluginConfigOptions,
2526
) => {
2627
/* c8 ignore start */
27-
if (config.env['pluginVisualRegressionForceDeviceScaleFactor'] !== false) {
28+
if (getPluginConfig(config, 'pluginVisualRegressionForceDeviceScaleFactor') !== false) {
2829
initForceDeviceScaleFactor(on);
2930
}
3031
/* c8 ignore stop */

packages/cypress-plugin-visual-regression-diff/src/task.hook.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ describe('cleanupImagesTask', () => {
138138

139139
cleanupImagesTask({
140140
projectRoot,
141+
version: '13.17.0',
141142
env: { pluginVisualRegressionCleanupUnusedImages: true },
142143
testingType: 'e2e',
143144
} as unknown as Cypress.PluginConfigOptions);
@@ -155,6 +156,7 @@ describe('cleanupImagesTask', () => {
155156

156157
cleanupImagesTask({
157158
projectRoot,
159+
version: '13.17.0',
158160
env: { pluginVisualRegressionCleanupUnusedImages: true },
159161
} as unknown as Cypress.PluginConfigOptions);
160162

@@ -172,6 +174,7 @@ describe('cleanupImagesTask', () => {
172174

173175
cleanupImagesTask({
174176
projectRoot,
177+
version: '13.17.0',
175178
env: { pluginVisualRegressionCleanupUnusedImages: true },
176179
testingType: 'component',
177180
} as unknown as Cypress.PluginConfigOptions);
@@ -190,6 +193,7 @@ describe('cleanupImagesTask', () => {
190193

191194
cleanupImagesTask({
192195
projectRoot,
196+
version: '13.17.0',
193197
env: { pluginVisualRegressionCleanupUnusedImages: true },
194198
testingType: 'e2e',
195199
} as unknown as Cypress.PluginConfigOptions);
@@ -206,13 +210,52 @@ describe('cleanupImagesTask', () => {
206210

207211
cleanupImagesTask({
208212
projectRoot,
213+
version: '13.17.0',
209214
env: { pluginVisualRegressionCleanupUnusedImages: true },
210215
testingType: 'e2e',
211216
} as unknown as Cypress.PluginConfigOptions);
212217

213218
expect(existsSync(screenshotPath)).toBe(false);
214219
});
215220
});
221+
222+
describe('with Cypress 15.10+ (expose API)', () => {
223+
it('reads pluginVisualRegressionCleanupUnusedImages from config.expose', async () => {
224+
const { path: projectRoot } = await dir();
225+
const screenshotPath = await writeTmpFixture(
226+
path.join(projectRoot, 'some-file-2_#0.png'),
227+
oldImgFixture,
228+
);
229+
230+
cleanupImagesTask({
231+
projectRoot,
232+
version: '15.10.0',
233+
expose: { pluginVisualRegressionCleanupUnusedImages: true },
234+
env: {},
235+
testingType: 'e2e',
236+
} as unknown as Cypress.PluginConfigOptions);
237+
238+
expect(existsSync(screenshotPath)).toBe(false);
239+
});
240+
241+
it('does not remove images when expose flag is not set', async () => {
242+
const { path: projectRoot } = await dir();
243+
const screenshotPath = await writeTmpFixture(
244+
path.join(projectRoot, 'some-file-2_#0.png'),
245+
oldImgFixture,
246+
);
247+
248+
cleanupImagesTask({
249+
projectRoot,
250+
version: '15.10.0',
251+
expose: {},
252+
env: {},
253+
testingType: 'e2e',
254+
} as unknown as Cypress.PluginConfigOptions);
255+
256+
expect(existsSync(screenshotPath)).toBe(true);
257+
});
258+
});
216259
});
217260
});
218261

packages/cypress-plugin-visual-regression-diff/src/task.hook.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ type PixelmatchOptions = NonNullable<Parameters<typeof pixelmatch>[5]>;
66
import { moveFile } from 'move-file';
77
import path from 'path';
88
import { FILE_SUFFIX, TASK } from './constants';
9+
import { getPluginConfig } from './version.utils';
910
import {
1011
cleanupUnused,
1112
alignImagesToSameSize,
@@ -47,7 +48,7 @@ export const getScreenshotPathInfoTask = (cfg: {
4748
};
4849

4950
export const cleanupImagesTask = (config: Cypress.PluginConfigOptions) => {
50-
if (config.env['pluginVisualRegressionCleanupUnusedImages']) {
51+
if (getPluginConfig(config, 'pluginVisualRegressionCleanupUnusedImages')) {
5152
cleanupUnused(config);
5253
}
5354

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export const supportsExpose = (version: string): boolean => {
2+
const [major, minor] = version.split('.').map(Number);
3+
return major > 15 || (major === 15 && minor >= 10);
4+
};
5+
6+
export const getPluginConfig = (
7+
config: Cypress.PluginConfigOptions,
8+
key: string,
9+
): unknown => {
10+
if (supportsExpose(config.version ?? '')) {
11+
return config.expose?.[key];
12+
}
13+
return config.env[key];
14+
};

0 commit comments

Comments
 (0)