diff --git a/package.json b/package.json index fa6a115a..b1386f0c 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "scripts": { "build": "pnpm -r build", "lint": "eslint '**/*.ts' --ignore-pattern '**/*.d.ts'", - "lint:fix": "pnpm lint --fix", + "fix": "pnpm lint --fix", "lint:ci": "pnpm lint --max-warnings 0", "format": "pnpm -r format", "format:ci": "pnpm -r format:ci", diff --git a/packages/cypress-plugin-visual-regression-diff/MIGRATION.md b/packages/cypress-plugin-visual-regression-diff/MIGRATION.md new file mode 100644 index 00000000..10ecfd9e --- /dev/null +++ b/packages/cypress-plugin-visual-regression-diff/MIGRATION.md @@ -0,0 +1,98 @@ +# Migration Guide + +## 4.0.x -> 4.1.x + +### Migrating to Cypress 16 (`Cypress.expose` API) + +Cypress 16 removes `Cypress.env()` in favor of the new `Cypress.expose()` API (introduced in Cypress 15.10.0). +This plugin supports both APIs automatically based on the detected Cypress version: + +- **Cypress ≥ 15.10** – reads plugin options via `Cypress.expose()` / `config.expose` +- **Cypress < 15.10** – reads plugin options via `Cypress.env()` / `config.env` (legacy) + +No code change is required inside your tests. You only need to update **how you supply plugin options**. + +### What changed + +| Location | Before (all Cypress versions) | After (Cypress ≥ 15.10) | +| ------------------- | ----------------------------- | ------------------------------------------- | +| CLI flag | `--env "key=value"` | `--expose "key=value"` | +| `cypress.config.ts` | `env: { key: value }` | `expose: { key: value }` | +| `cypress.env.json` | `{ "key": "value" }` | use `expose` in `cypress.config.ts` instead | + +### Plugin option names + +All option names stay the same — they are prefixed with `pluginVisualRegression`: + +| Option | Config key | +| ------------------------ | ---------------------------------------------- | +| `updateImages` | `pluginVisualRegressionUpdateImages` | +| `cleanupUnusedImages` | `pluginVisualRegressionCleanupUnusedImages` | +| `diffConfig` | `pluginVisualRegressionDiffConfig` | +| `forceDeviceScaleFactor` | `pluginVisualRegressionForceDeviceScaleFactor` | +| `maxDiffThreshold` | `pluginVisualRegressionMaxDiffThreshold` | + +### CLI + +```bash +# Before (deprecated in 15.10, removed in 16) +npx cypress run --env "pluginVisualRegressionUpdateImages=true" + +# After (Cypress ≥ 15.10) +npx cypress run --expose "pluginVisualRegressionUpdateImages=true" +``` + +### cypress.config.ts + +```ts +// Before +export default defineConfig({ + env: { + pluginVisualRegressionUpdateImages: true, + pluginVisualRegressionDiffConfig: { threshold: 0.01 }, + }, +}); + +// After (Cypress ≥ 15.10) +export default defineConfig({ + expose: { + pluginVisualRegressionUpdateImages: true, + pluginVisualRegressionDiffConfig: { threshold: 0.01 }, + }, +}); +``` + +### cypress.env.json + +`cypress.env.json` only works with `Cypress.env()` and is not supported by the `expose` API. +Migrate any plugin options from `cypress.env.json` into the `expose` block of `cypress.config.ts`. + +```jsonc +// cypress.env.json — REMOVE these plugin keys: +{ + // "pluginVisualRegressionUpdateImages": true, ← move to cypress.config.ts expose block +} +``` + +### Supporting both Cypress 15.x and 16.x simultaneously + +If you need your config to work on both Cypress 15.9 and below **and** 15.10+, you can supply the +option in both places — the plugin will prefer `expose` on newer Cypress and fall back to `env` on +older ones: + +```ts +export default defineConfig({ + expose: { + pluginVisualRegressionUpdateImages: true, + }, + env: { + pluginVisualRegressionUpdateImages: true, // fallback for Cypress < 15.10 + }, +}); +``` + +### References + +- [Cypress `Cypress.env()` migration guide](https://docs.cypress.io/app/references/migration-guide#Migrating-away-from-Cypressenv) +- [Cypress `expose` API docs](https://on.cypress.io/expose) +- [Issue #375](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/375) diff --git a/packages/cypress-plugin-visual-regression-diff/README.md b/packages/cypress-plugin-visual-regression-diff/README.md index 716e0f82..48b67faa 100644 --- a/packages/cypress-plugin-visual-regression-diff/README.md +++ b/packages/cypress-plugin-visual-regression-diff/README.md @@ -158,9 +158,12 @@ Still got troubles with installation? Have a look at [examples directory of this ## Automatic clean up of unused images It's useful to remove screenshots generated by the visual regression plugin that are not used by any test anymore. -Enable this feature via env variable and enjoy freed up storage space 🚀: +Enable this feature via expose variable and enjoy freed up storage space 🚀: ```bash +# Cypress 15.10+ +npx cypress run --expose "pluginVisualRegressionCleanupUnusedImages=true" +# Cypress <15.10 (deprecated in 15.10, removed in 16) npx cypress run --env "pluginVisualRegressionCleanupUnusedImages=true" ``` @@ -216,33 +219,40 @@ cy.matchImage({ }); ``` -- 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.: +- 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.: ```bash +# Cypress 15.10+ +npx cypress run --expose "pluginVisualRegressionUpdateImages=true" --expose "pluginVisualRegressionDiffConfig={\"threshold\":0.01}" +# Cypress <15.10 (deprecated in 15.10, removed in 16) npx cypress run --env "pluginVisualRegressionUpdateImages=true,pluginVisualRegressionDiffConfig={\"threshold\":0.01}" ``` ```ts -// cypress.config.ts +// cypress.config.ts (Cypress 15.10+) import { defineConfig } from 'cypress'; export default defineConfig({ - env: { + expose: { pluginVisualRegressionUpdateImages: true, pluginVisualRegressionDiffConfig: { threshold: 0.01 }, }, }); ``` -```json -// cypress.env.json (https://docs.cypress.io/guides/guides/environment-variables#Option-2-cypress-env-json) -{ - "pluginVisualRegressionUpdateImages": true, - "pluginVisualRegressionDiffConfig": { "threshold": 0.01 } -} +```ts +// cypress.config.ts (Cypress <15.10, deprecated in newer versions) +import { defineConfig } from 'cypress'; + +export default defineConfig({ + env: { + pluginVisualRegressionUpdateImages: true, + pluginVisualRegressionDiffConfig: { threshold: 0.01 }, + }, +}); ``` -For more ways of setting environment variables [take a look here](https://docs.cypress.io/guides/guides/environment-variables#Setting). +For more ways of setting expose variables [take a look here](https://on.cypress.io/expose). ## FAQ @@ -269,9 +279,12 @@ This is typically caused by device pixel ratio differences between headless and cy.matchImage({ forceDeviceScaleFactor: true }); ``` -Or set it globally via environment variable: +Or set it globally via expose variable: ```bash +# Cypress 15.10+ +npx cypress run --expose "pluginVisualRegressionForceDeviceScaleFactor=true" +# Cypress <15.10 (deprecated in 15.10, removed in 16) npx cypress run --env "pluginVisualRegressionForceDeviceScaleFactor=true" ``` @@ -340,9 +353,12 @@ Set `maxDiffThreshold` to `0`: cy.matchImage({ maxDiffThreshold: 0 }); ``` -Or globally via an environment variable: +Or globally via an expose variable: ```bash +# Cypress 15.10+ +npx cypress run --expose "pluginVisualRegressionMaxDiffThreshold=0" +# Cypress <15.10 (deprecated in 15.10, removed in 16) npx cypress run --env "pluginVisualRegressionMaxDiffThreshold=0" ``` diff --git a/packages/cypress-plugin-visual-regression-diff/src/commands.ts b/packages/cypress-plugin-visual-regression-diff/src/commands.ts index eb40896b..7080773c 100644 --- a/packages/cypress-plugin-visual-regression-diff/src/commands.ts +++ b/packages/cypress-plugin-visual-regression-diff/src/commands.ts @@ -1,4 +1,5 @@ import { FILE_SUFFIX, LINK_PREFIX, TASK } from './constants'; +import { supportsExpose } from './version.utils'; import type pixelmatch from 'pixelmatch'; import * as Base64 from '@frsource/base64'; import type { CompareImagesTaskReturn } from './types'; @@ -58,9 +59,14 @@ const constructCypressError = (log: Cypress.Log, err: Error) => { const capitalize = (text: string) => text.charAt(0).toUpperCase() + text.slice(1); -const getPluginEnv = (key: K) => - Cypress.env(`pluginVisualRegression${capitalize(key)}`) as - Cypress.MatchImageOptions[K] | undefined; +const getPluginEnv = (key: K) => { + const envKey = `pluginVisualRegression${capitalize(key)}`; + if (supportsExpose(Cypress.version)) { + return Cypress.expose(envKey) as Cypress.MatchImageOptions[K] | undefined; + } + + return Cypress.env(envKey) as Cypress.MatchImageOptions[K] | undefined; +}; const booleanOption = ( options: Cypress.MatchImageOptions, diff --git a/packages/cypress-plugin-visual-regression-diff/src/plugins.test.ts b/packages/cypress-plugin-visual-regression-diff/src/plugins.test.ts index 4e57c44a..b00600a9 100644 --- a/packages/cypress-plugin-visual-regression-diff/src/plugins.test.ts +++ b/packages/cypress-plugin-visual-regression-diff/src/plugins.test.ts @@ -11,9 +11,10 @@ vi.mock('./afterScreenshot.hook.ts', () => ({ })); describe('initPlugin', () => { - it('inits hooks', () => { + it('inits hooks (Cypress <15.10, env API)', () => { const onMock = vi.fn(); initPlugin(onMock, { + version: '13.17.0', env: { pluginVisualRegressionForceDeviceScaleFactor: false }, } as unknown as Cypress.PluginConfigOptions); @@ -22,4 +23,18 @@ describe('initPlugin', () => { expect(initTaskHook).toBeCalledTimes(1); expect(initAfterScreenshotHook).toBeCalledTimes(1); }); + + it('inits hooks (Cypress 15.10+, expose API)', () => { + const onMock = vi.fn(); + initPlugin(onMock, { + version: '15.10.0', + expose: { pluginVisualRegressionForceDeviceScaleFactor: false }, + env: {}, + } as unknown as Cypress.PluginConfigOptions); + + expect(onMock).toBeCalledWith('task', 'task'); + expect(onMock).toBeCalledWith('after:screenshot', 'after:screenshot'); + expect(initTaskHook).toBeCalledTimes(2); + expect(initAfterScreenshotHook).toBeCalledTimes(2); + }); }); diff --git a/packages/cypress-plugin-visual-regression-diff/src/plugins.ts b/packages/cypress-plugin-visual-regression-diff/src/plugins.ts index e9d76d19..a6737fb5 100644 --- a/packages/cypress-plugin-visual-regression-diff/src/plugins.ts +++ b/packages/cypress-plugin-visual-regression-diff/src/plugins.ts @@ -1,5 +1,6 @@ import { initTaskHook } from './task.hook'; import { initAfterScreenshotHook } from './afterScreenshot.hook'; +import { getPluginConfig } from './version.utils'; /* c8 ignore start */ const initForceDeviceScaleFactor = (on: Cypress.PluginEvents) => { @@ -24,7 +25,10 @@ export const initPlugin = ( config: Cypress.PluginConfigOptions, ) => { /* c8 ignore start */ - if (config.env['pluginVisualRegressionForceDeviceScaleFactor'] !== false) { + if ( + getPluginConfig(config, 'pluginVisualRegressionForceDeviceScaleFactor') !== + false + ) { initForceDeviceScaleFactor(on); } /* c8 ignore stop */ diff --git a/packages/cypress-plugin-visual-regression-diff/src/task.hook.test.ts b/packages/cypress-plugin-visual-regression-diff/src/task.hook.test.ts index f5a9723d..46842f83 100644 --- a/packages/cypress-plugin-visual-regression-diff/src/task.hook.test.ts +++ b/packages/cypress-plugin-visual-regression-diff/src/task.hook.test.ts @@ -138,6 +138,7 @@ describe('cleanupImagesTask', () => { cleanupImagesTask({ projectRoot, + version: '13.17.0', env: { pluginVisualRegressionCleanupUnusedImages: true }, testingType: 'e2e', } as unknown as Cypress.PluginConfigOptions); @@ -155,6 +156,7 @@ describe('cleanupImagesTask', () => { cleanupImagesTask({ projectRoot, + version: '13.17.0', env: { pluginVisualRegressionCleanupUnusedImages: true }, } as unknown as Cypress.PluginConfigOptions); @@ -172,6 +174,7 @@ describe('cleanupImagesTask', () => { cleanupImagesTask({ projectRoot, + version: '13.17.0', env: { pluginVisualRegressionCleanupUnusedImages: true }, testingType: 'component', } as unknown as Cypress.PluginConfigOptions); @@ -190,6 +193,7 @@ describe('cleanupImagesTask', () => { cleanupImagesTask({ projectRoot, + version: '13.17.0', env: { pluginVisualRegressionCleanupUnusedImages: true }, testingType: 'e2e', } as unknown as Cypress.PluginConfigOptions); @@ -206,6 +210,7 @@ describe('cleanupImagesTask', () => { cleanupImagesTask({ projectRoot, + version: '13.17.0', env: { pluginVisualRegressionCleanupUnusedImages: true }, testingType: 'e2e', } as unknown as Cypress.PluginConfigOptions); @@ -213,6 +218,44 @@ describe('cleanupImagesTask', () => { expect(existsSync(screenshotPath)).toBe(false); }); }); + + describe('with Cypress 15.10+ (expose API)', () => { + it('reads pluginVisualRegressionCleanupUnusedImages from config.expose', async () => { + const { path: projectRoot } = await dir(); + const screenshotPath = await writeTmpFixture( + path.join(projectRoot, 'some-file-2_#0.png'), + oldImgFixture, + ); + + cleanupImagesTask({ + projectRoot, + version: '15.10.0', + expose: { pluginVisualRegressionCleanupUnusedImages: true }, + env: {}, + testingType: 'e2e', + } as unknown as Cypress.PluginConfigOptions); + + expect(existsSync(screenshotPath)).toBe(false); + }); + + it('does not remove images when expose flag is not set', async () => { + const { path: projectRoot } = await dir(); + const screenshotPath = await writeTmpFixture( + path.join(projectRoot, 'some-file-2_#0.png'), + oldImgFixture, + ); + + cleanupImagesTask({ + projectRoot, + version: '15.10.0', + expose: {}, + env: {}, + testingType: 'e2e', + } as unknown as Cypress.PluginConfigOptions); + + expect(existsSync(screenshotPath)).toBe(true); + }); + }); }); }); diff --git a/packages/cypress-plugin-visual-regression-diff/src/task.hook.ts b/packages/cypress-plugin-visual-regression-diff/src/task.hook.ts index 6978d3a8..4d52bd1e 100644 --- a/packages/cypress-plugin-visual-regression-diff/src/task.hook.ts +++ b/packages/cypress-plugin-visual-regression-diff/src/task.hook.ts @@ -6,6 +6,7 @@ type PixelmatchOptions = NonNullable[5]>; import { moveFile } from 'move-file'; import path from 'path'; import { FILE_SUFFIX, TASK } from './constants'; +import { getPluginConfig } from './version.utils'; import { cleanupUnused, alignImagesToSameSize, @@ -47,7 +48,7 @@ export const getScreenshotPathInfoTask = (cfg: { }; export const cleanupImagesTask = (config: Cypress.PluginConfigOptions) => { - if (config.env['pluginVisualRegressionCleanupUnusedImages']) { + if (getPluginConfig(config, 'pluginVisualRegressionCleanupUnusedImages')) { cleanupUnused(config); } diff --git a/packages/cypress-plugin-visual-regression-diff/src/version.utils.ts b/packages/cypress-plugin-visual-regression-diff/src/version.utils.ts new file mode 100644 index 00000000..1129aa1a --- /dev/null +++ b/packages/cypress-plugin-visual-regression-diff/src/version.utils.ts @@ -0,0 +1,14 @@ +export const supportsExpose = (version: string): boolean => { + const [major, minor] = version.split('.').map(Number); + return major > 15 || (major === 15 && minor >= 10); +}; + +export const getPluginConfig = ( + config: Cypress.PluginConfigOptions, + key: string, +): unknown => { + if (supportsExpose(config.version ?? '')) { + return config.expose?.[key]; + } + return config.env?.[key]; +};