├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .nvmrc ├── .release-please-manifest.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets ├── frsource-visual-testing-example.gif ├── frsource-visual-testing-example.mp4 ├── license-1869795-2874802.pdf └── logo.svg ├── eslint.config.mjs ├── examples ├── next │ ├── .gitignore │ ├── README.md │ ├── components │ │ ├── about-component.cy.tsx │ │ └── about-component.tsx │ ├── cypress.config.ts │ ├── cypress │ │ ├── e2e │ │ │ └── app.cy.ts │ │ ├── fixtures │ │ │ └── example.json │ │ ├── support │ │ │ ├── commands.ts │ │ │ ├── component-index.html │ │ │ ├── component.ts │ │ │ └── e2e.ts │ │ └── tsconfig.json │ ├── package.json │ ├── pages │ │ ├── _app.tsx │ │ ├── about.tsx │ │ └── index.tsx │ ├── public │ │ ├── favicon.ico │ │ └── vercel.svg │ ├── styles │ │ ├── Home.module.css │ │ └── globals.css │ └── tsconfig.json └── webpack │ ├── .browserslistrc │ ├── .gitignore │ ├── README.md │ ├── cypress.config.ts │ ├── cypress │ ├── component │ │ ├── HelloWorld.cy.ts │ │ └── __image_snapshots__ │ │ │ ├── HelloWorld.cy.js playground #0.actual.png │ │ │ ├── HelloWorld.cy.js playground #0.diff.png │ │ │ ├── HelloWorld.cy.js playground #0.png │ │ │ └── HelloWorld.cy.js playground #1.png │ ├── e2e │ │ ├── __image_snapshots__ │ │ │ └── My First Test Visits the app root url #0.png │ │ └── spec.cy.ts │ ├── screenshots │ │ └── HelloWorld.cy.ts │ │ │ └── HelloWorld.cy.js -- playground (failed).png │ └── support │ │ ├── commands.ts │ │ ├── component-index.html │ │ ├── component.ts │ │ └── e2e.ts │ ├── package.json │ ├── public │ ├── favicon.ico │ └── index.html │ ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ └── HelloWorld.vue │ └── main.js │ └── tsconfig.json ├── package.json ├── packages └── cypress-plugin-visual-regression-diff │ ├── .prettierignore │ ├── CHANGELOG.md │ ├── __tests__ │ ├── fixtures │ │ ├── prepare-screenshot-for-cleanup.spec.cy.js │ │ ├── screenshot.actual.png │ │ ├── screenshot.diff.png │ │ └── screenshot.png │ └── mocks │ │ └── cypress.mock.ts │ ├── package.json │ ├── prettier.config.mjs │ ├── src │ ├── __snapshots__ │ │ ├── support.test.ts.snap │ │ └── task.hook.test.ts.snap │ ├── afterScreenshot.hook.test.ts │ ├── afterScreenshot.hook.ts │ ├── commands.ts │ ├── constants.ts │ ├── image.utils.ts │ ├── plugins.test.ts │ ├── plugins.ts │ ├── screenshotPath.utils.ts │ ├── support.test.ts │ ├── support.ts │ ├── task.hook.test.ts │ ├── task.hook.ts │ └── types.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vitest.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── release-please-config.json └── renovate.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{js,json,yml}] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | cache: 12 | name: CI cache 13 | runs-on: ubuntu-latest 14 | outputs: 15 | pnpm-cache: ${{ steps.vars.outputs.pnpm-cache }} 16 | key: ${{ steps.vars.outputs.key }} 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: pnpm/action-setup@v3 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version-file: '.nvmrc' 23 | - id: vars 24 | run: | 25 | echo "pnpm-cache=$(pnpm store path --silent)" >> $GITHUB_OUTPUT 26 | echo "key=${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}" >> $GITHUB_OUTPUT 27 | - name: Cache NPM and Cypress 📦 28 | uses: actions/cache@v4 29 | id: cache 30 | with: 31 | path: | 32 | ${{ steps.vars.outputs.pnpm-cache }} 33 | ~/.cache/Cypress 34 | key: ${{ steps.vars.outputs.key }} 35 | restore-keys: | 36 | ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} 37 | ${{ runner.os }}-pnpm- 38 | - run: pnpm install --frozen-lockfile --prefer-offline 39 | env: 40 | HUSKY: '0' # By default do not run HUSKY install 41 | 42 | lint: 43 | name: lint 44 | runs-on: ubuntu-latest 45 | needs: cache 46 | steps: 47 | - uses: actions/checkout@v4 48 | - uses: pnpm/action-setup@v3 49 | - uses: actions/setup-node@v4 50 | with: 51 | node-version-file: '.nvmrc' 52 | - name: Cache NPM and Cypress 📦 53 | uses: actions/cache@v4 54 | id: cache 55 | with: 56 | path: | 57 | ${{ needs.cache.outputs.pnpm-cache }} 58 | ~/.cache/Cypress 59 | key: ${{ needs.cache.outputs.key }} 60 | restore-keys: | 61 | ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} 62 | ${{ runner.os }}-pnpm- 63 | - run: pnpm install --frozen-lockfile --prefer-offline 64 | env: 65 | HUSKY: '0' # By default do not run HUSKY install 66 | - run: pnpm lint:ci 67 | - run: pnpm format:ci 68 | 69 | test: 70 | name: test 71 | runs-on: ubuntu-latest 72 | needs: cache 73 | steps: 74 | - uses: actions/checkout@v4 75 | - uses: pnpm/action-setup@v3 76 | - uses: actions/setup-node@v4 77 | with: 78 | node-version-file: '.nvmrc' 79 | - name: Cache NPM and Cypress 📦 80 | uses: actions/cache@v4 81 | id: cache 82 | with: 83 | path: | 84 | ${{ needs.cache.outputs.pnpm-cache }} 85 | ~/.cache/Cypress 86 | key: ${{ needs.cache.outputs.key }} 87 | restore-keys: | 88 | ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} 89 | ${{ runner.os }}-pnpm- 90 | - run: pnpm install --frozen-lockfile --prefer-offline 91 | env: 92 | HUSKY: '0' # By default do not run HUSKY install 93 | - run: pnpm --filter cypress-plugin-visual-regression-diff -r build 94 | - name: Test e2e 95 | run: pnpm test:e2e:ci 96 | - name: Test component-testing 97 | run: pnpm test:ct:ci 98 | - uses: actions/upload-artifact@v4 99 | with: 100 | name: test 101 | path: | 102 | examples/next/**/*.png 103 | examples/next/**/*.jpg 104 | examples/next/**/*.jpeg 105 | 106 | test-integration-coverage: 107 | name: test-integration-coverage 108 | runs-on: ubuntu-latest 109 | needs: cache 110 | steps: 111 | - uses: actions/checkout@v4 112 | - uses: pnpm/action-setup@v3 113 | - uses: actions/setup-node@v4 114 | with: 115 | node-version-file: '.nvmrc' 116 | - name: Cache NPM and Cypress 📦 117 | uses: actions/cache@v4 118 | id: cache 119 | with: 120 | path: | 121 | ${{ needs.cache.outputs.pnpm-cache }} 122 | ~/.cache/Cypress 123 | key: ${{ needs.cache.outputs.key }} 124 | restore-keys: | 125 | ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} 126 | ${{ runner.os }}-pnpm- 127 | - run: pnpm install --frozen-lockfile --prefer-offline 128 | env: 129 | HUSKY: '0' # By default do not run HUSKY install 130 | - name: Test integration (with coverage) and upload to CodeClimate 131 | if: ${{ github.ref == 'refs/heads/main' }} 132 | uses: paambaati/codeclimate-action@v4.0.0 133 | env: 134 | CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} 135 | with: 136 | debug: true 137 | coverageCommand: pnpm test:integration:ci 138 | coverageLocations: | 139 | ${{github.workspace}}/packages/*/coverage/lcov.info:lcov 140 | - name: Test integration (with coverage) 141 | if: ${{ github.ref != 'refs/heads/main' }} 142 | run: pnpm test:integration:ci 143 | 144 | prepare-release: 145 | name: detect changes, bump package versions, generate changelog and commit it to main branch 146 | runs-on: ubuntu-latest 147 | needs: [cache, lint, test, test-integration-coverage] 148 | if: ${{ github.actor != 'dependabot[bot]' && github.ref == 'refs/heads/main' && github.event_name == 'push' }} 149 | permissions: 150 | contents: write 151 | pull-requests: write 152 | outputs: 153 | releases_created: ${{ steps.release.outputs.releases_created }} 154 | steps: 155 | - uses: google-github-actions/release-please-action@v3.7.13 156 | id: release 157 | with: 158 | command: manifest 159 | default-branch: main 160 | monorepo-tags: true 161 | 162 | build-and-release: 163 | name: build and release 164 | runs-on: ubuntu-latest 165 | needs: [cache, prepare-release] 166 | if: needs.prepare-release.outputs.releases_created 167 | steps: 168 | - uses: actions/checkout@v4 169 | - uses: pnpm/action-setup@v3 170 | - uses: actions/setup-node@v4 171 | with: 172 | node-version-file: '.nvmrc' 173 | registry-url: 'https://registry.npmjs.org' 174 | - name: Cache NPM and Cypress 📦 175 | uses: actions/cache@v4 176 | id: cache 177 | with: 178 | path: | 179 | ${{ needs.cache.outputs.pnpm-cache }} 180 | ~/.cache/Cypress 181 | key: ${{ needs.cache.outputs.key }} 182 | restore-keys: | 183 | ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} 184 | ${{ runner.os }}-pnpm- 185 | - run: pnpm install --frozen-lockfile --prefer-offline 186 | env: 187 | HUSKY: '0' # By default do not run HUSKY install 188 | - name: Build packages to get cross-references working 🔧 189 | run: pnpm build 190 | - name: Release package 191 | run: pnpm publish -r 192 | env: 193 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 194 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 195 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 196 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 197 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Yarn 10 | .yarn/* 11 | */.yarn 12 | !.yarn/patches 13 | !.yarn/plugins 14 | !.yarn/releases 15 | !.yarn/sdks 16 | !.yarn/versions 17 | .pnp.* 18 | 19 | # Diagnostic reports (https://nodejs.org/api/report.html) 20 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 21 | 22 | # Runtime data 23 | pids 24 | *.pid 25 | *.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | lib-cov 30 | 31 | # Coverage directory used by tools like istanbul 32 | coverage 33 | *.lcov 34 | 35 | # nyc test coverage 36 | .nyc_output 37 | 38 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 39 | .grunt 40 | 41 | # Bower dependency directory (https://bower.io/) 42 | bower_components 43 | 44 | # node-waf configuration 45 | .lock-wscript 46 | 47 | # Compiled binary addons (https://nodejs.org/api/addons.html) 48 | build/Release 49 | 50 | # Dependency directories 51 | node_modules/ 52 | jspm_packages/ 53 | 54 | # TypeScript v1 declaration files 55 | typings/ 56 | 57 | # TypeScript cache 58 | *.tsbuildinfo 59 | 60 | # Optional npm cache directory 61 | .npm 62 | 63 | # Optional eslint cache 64 | .eslintcache 65 | 66 | # Microbundle cache 67 | .rpt2_cache/ 68 | .rts2_cache_cjs/ 69 | .rts2_cache_es/ 70 | .rts2_cache_umd/ 71 | 72 | # Optional REPL history 73 | .node_repl_history 74 | 75 | # Output of 'npm pack' 76 | *.tgz 77 | 78 | # Yarn Integrity file 79 | .yarn-integrity 80 | 81 | # dotenv environment variables file 82 | .env 83 | .env.test 84 | 85 | # parcel-bundler cache (https://parceljs.org/) 86 | .cache 87 | 88 | # Next.js build output 89 | .next 90 | 91 | # Nuxt.js build / generate output 92 | .nuxt 93 | dist 94 | 95 | # Gatsby files 96 | .cache/ 97 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 98 | # https://nextjs.org/blog/next-9-1#public-directory-support 99 | # public 100 | 101 | # vuepress build output 102 | .vuepress/dist 103 | 104 | # Serverless directories 105 | .serverless/ 106 | 107 | # FuseBox cache 108 | .fusebox/ 109 | 110 | # DynamoDB Local files 111 | .dynamodb/ 112 | 113 | # TernJS port file 114 | .tern-port 115 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20.17.0 2 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages/cypress-plugin-visual-regression-diff": "3.3.10" 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [3.3.9](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.3.8...v3.3.9) (2023-06-12) 2 | 3 | ## [3.3.8](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.3.7...v3.3.8) (2023-06-05) 4 | 5 | ## [3.3.7](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.3.6...v3.3.7) (2023-06-05) 6 | 7 | ## [3.3.6](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.3.5...v3.3.6) (2023-06-05) 8 | 9 | ## [3.3.5](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.3.4...v3.3.5) (2023-05-29) 10 | 11 | ## [3.3.4](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.3.3...v3.3.4) (2023-05-29) 12 | 13 | ## [3.3.3](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.3.2...v3.3.3) (2023-05-29) 14 | 15 | ## [3.3.2](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.3.1...v3.3.2) (2023-05-29) 16 | 17 | ## [3.3.1](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.3.0...v3.3.1) (2023-05-21) 18 | 19 | 20 | ### Bug Fixes 21 | 22 | * **deps:** update all non-major dependency bump ([#228](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/228)) ([845590e](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/845590e25edc8c0372fcf25333454ec03e5f167b)) 23 | 24 | # [3.3.0](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.2.15...v3.3.0) (2023-05-21) 25 | 26 | 27 | ### Bug Fixes 28 | 29 | * **deps:** update dependency pngjs to v7 ([#215](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/215)) ([af71297](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/af71297f3d2ab180798bb64a46145919a6924c74)) 30 | 31 | 32 | ### Features 33 | 34 | * createMissingImages option ([#222](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/222)) ([2aef358](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/2aef35840e00299783ddede6f240c6005ac5bfcb)), closes [#204](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/204) 35 | * separate versioning for images ([#221](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/221)) ([b2a7434](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/b2a74340fc2616aa16d54a4dfbd43ddbfdd24eb1)), closes [#197](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/197) 36 | 37 | ## [3.2.15](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.2.14...v3.2.15) (2023-05-21) 38 | 39 | ## [3.2.14](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.2.13...v3.2.14) (2023-03-26) 40 | 41 | ## [3.2.13](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.2.12...v3.2.13) (2023-03-26) 42 | 43 | ## [3.2.12](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.2.11...v3.2.12) (2023-03-26) 44 | 45 | 46 | ### Bug Fixes 47 | 48 | * import meta-png cjs dependency ([#209](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/209)) ([41aeee5](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/41aeee52c362e4a1817a9e364963c4aff1407d0a)), closes [#207](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/207) 49 | 50 | ## [3.2.11](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.2.10...v3.2.11) (2023-03-26) 51 | 52 | 53 | ### Bug Fixes 54 | 55 | * treat maxDiffThreshold 0 as valid value ([f4d3ec9](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/f4d3ec946547d648d1ec8ea9ccf9369540255adf)) 56 | 57 | ## [3.2.10](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.2.9...v3.2.10) (2023-03-26) 58 | 59 | 60 | ### Bug Fixes 61 | 62 | * add missing forceDeviceScaleFactor option ([ba7d2f1](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/ba7d2f15b57390bb1ef69de6f7ed979438155444)) 63 | 64 | ## [3.2.9](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.2.8...v3.2.9) (2023-03-26) 65 | 66 | ## [3.2.8](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.2.7...v3.2.8) (2022-12-18) 67 | 68 | ## [3.2.7](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.2.6...v3.2.7) (2022-12-14) 69 | 70 | ## [3.2.6](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.2.5...v3.2.6) (2022-12-11) 71 | 72 | ## [3.2.5](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.2.4...v3.2.5) (2022-12-09) 73 | 74 | ## [3.2.4](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.2.3...v3.2.4) (2022-12-04) 75 | 76 | ## [3.2.3](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.2.2...v3.2.3) (2022-11-30) 77 | 78 | 79 | ### Bug Fixes 80 | 81 | * **deps:** update all non-major dependencies ([#191](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/191)) ([26e6d8b](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/26e6d8bce436243020dbc645e32d70fbdaca993b)) 82 | 83 | ## [3.2.2](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.2.1...v3.2.2) (2022-11-29) 84 | 85 | 86 | ### Bug Fixes 87 | 88 | * return launchOptions so they are applied ([#186](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/186)) ([b1b9056](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/b1b905613c899af6d14af920e34290533c26c545)) 89 | 90 | ## [3.2.1](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.2.0...v3.2.1) (2022-11-29) 91 | 92 | 93 | ### Bug Fixes 94 | 95 | * update images via GUI in Cypress 11 ([#193](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/193)) ([bdebca2](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/bdebca28aea1dea63473243679a5a71e6b21f165)), closes [#187](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/187) 96 | 97 | # [3.2.0](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.1.7...v3.2.0) (2022-11-29) 98 | 99 | 100 | ### Features 101 | 102 | * support Cypress 11 ([#192](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/192)) ([7bd1a24](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/7bd1a24bcc5f38531d91845c141dd9a5713dec7e)), closes [#187](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/187) 103 | 104 | ## [3.1.7](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.1.6...v3.1.7) (2022-11-23) 105 | 106 | ## [3.1.6](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.1.5...v3.1.6) (2022-11-19) 107 | 108 | ## [3.1.5](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.1.4...v3.1.5) (2022-11-12) 109 | 110 | ## [3.1.4](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.1.3...v3.1.4) (2022-11-10) 111 | 112 | ## [3.1.3](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.1.2...v3.1.3) (2022-11-09) 113 | 114 | ## [3.1.2](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.1.1...v3.1.2) (2022-11-07) 115 | 116 | 117 | ### Bug Fixes 118 | 119 | * **deps:** update dependency sharp to v0.31.2 ([#174](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/174)) ([c2bce9d](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/c2bce9dda3a70635270375f99c17d458f4cf39b8)) 120 | 121 | ## [3.1.1](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.1.0...v3.1.1) (2022-11-04) 122 | 123 | 124 | ### Bug Fixes 125 | 126 | * new image prefix starts at -1 ([#172](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/172)) ([8279208](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/8279208c2ec6dbbbf7dd846463f684f19dfe0df6)) 127 | 128 | # [3.1.0](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.0.4...v3.1.0) (2022-11-03) 129 | 130 | 131 | ### Features 132 | 133 | * support Cypress retries functionality ([#171](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/171)) ([7d7d010](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/7d7d010938ee124e694e8cf0270aa99c89db59df)), closes [#168](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/168) 134 | 135 | ## [3.0.4](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.0.3...v3.0.4) (2022-11-03) 136 | 137 | 138 | ### Bug Fixes 139 | 140 | * typings for older typescript ([#170](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/170)) ([96499ec](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/96499ecc2959bab8c39b599ba7eb87fbd79ceec3)), closes [#167](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/167) 141 | 142 | ## [3.0.3](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.0.2...v3.0.3) (2022-11-01) 143 | 144 | 145 | ### Bug Fixes 146 | 147 | * ts declaration generation ([1a1e0cc](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/1a1e0ccd4c442d3e4d45f4d899139a08963e0c85)) 148 | 149 | ## [3.0.2](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.0.1...v3.0.2) (2022-10-27) 150 | 151 | 152 | ### Bug Fixes 153 | 154 | * **deps:** update all non-major dependencies ([#165](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/165)) ([602640f](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/602640fcca6e8173930efa116244258549aa5264)) 155 | 156 | ## [3.0.1](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v3.0.0...v3.0.1) (2022-10-26) 157 | 158 | 159 | ### Bug Fixes 160 | 161 | * reset name cache after tests run ([bfbf138](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/bfbf138fa52de06072db32a0181821b56ca5230f)) 162 | 163 | # [3.0.0](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.3.12...v3.0.0) (2022-10-26) 164 | 165 | 166 | ### Bug Fixes 167 | 168 | * security vulnerability ([d6f849c](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/d6f849cb017e452d9f121866a6429d4bee4b5f18)) 169 | 170 | 171 | ### Features 172 | 173 | * add matchAgainstPath option ([#146](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/146)) ([7a5e3a8](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/7a5e3a8ec5aa766e38ee372e11a6d1c155105126)), closes [#88](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/88) 174 | * auto clean unused files ([#124](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/124)) ([38679a7](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/38679a730edc4083b4bc751b19bc161bbb72d159)), closes [#118](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/118) 175 | * introduce imagesPath option ([#152](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/152)) ([961e137](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/961e137099ba22aa9f0b6d36e6e73d495196a764)), closes [#147](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/147) 176 | 177 | 178 | ### BREAKING CHANGES 179 | 180 | * deprecate imagesDir option in favor of imagesPath - see docs for additional information 181 | * To use autocleanup feature you need to update all of the screenshots, best do it by running your test suite with cypress env 'pluginVisualRegressionUpdateImages' set to true. 182 | * matchImage returns object containing comparison details from now on (previously was returning subject element from a chain) 183 | 184 | ## [2.3.12](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.3.11...v2.3.12) (2022-10-22) 185 | 186 | ## [2.3.11](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.3.10...v2.3.11) (2022-10-19) 187 | 188 | ## [2.3.10](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.3.9...v2.3.10) (2022-10-15) 189 | 190 | ## [2.3.9](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.3.8...v2.3.9) (2022-10-14) 191 | 192 | ## [2.3.8](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.3.7...v2.3.8) (2022-10-12) 193 | 194 | ## [2.3.7](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.3.6...v2.3.7) (2022-10-12) 195 | 196 | ## [2.3.6](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.3.5...v2.3.6) (2022-10-12) 197 | 198 | 199 | ### Bug Fixes 200 | 201 | * **deps:** update dependency @frsource/base64 to v1.0.3 ([#144](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/144)) ([09ecbd8](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/09ecbd81c9978161de2a782cf3bc735ce8d6ca3f)) 202 | 203 | ## [2.3.5](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.3.4...v2.3.5) (2022-10-10) 204 | 205 | ## [2.3.4](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.3.3...v2.3.4) (2022-10-10) 206 | 207 | ## [2.3.3](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.3.2...v2.3.3) (2022-10-10) 208 | 209 | ## [2.3.2](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.3.1...v2.3.2) (2022-10-09) 210 | 211 | ## [2.3.1](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.3.0...v2.3.1) (2022-10-06) 212 | 213 | 214 | ### Bug Fixes 215 | 216 | * security vulnerabilities ([d0bda44](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/d0bda44d3055cd578381406a06607bfba48ff447)) 217 | 218 | # [2.3.0](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.2.12...v2.3.0) (2022-10-06) 219 | 220 | 221 | ### Features 222 | 223 | * show comparison for successful tests ([#137](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/137)) ([c09bab3](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/c09bab3ef805de24fc7cbcc8c738137c35e3fe18)), closes [#104](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/104) 224 | 225 | ## [2.2.12](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.2.11...v2.2.12) (2022-10-06) 226 | 227 | 228 | ### Bug Fixes 229 | 230 | * **deps:** update dependency sharp to v0.31.1 ([#132](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/132)) ([15f0f5d](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/15f0f5d2824cba32d4611289442abd637d8438f5)) 231 | 232 | ## [2.2.11](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.2.10...v2.2.11) (2022-09-28) 233 | 234 | 235 | ### Bug Fixes 236 | 237 | * **deps:** update dependency vue to v3.2.40 ([#131](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/131)) ([537fd16](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/537fd16c4507c394998c0c7f0da7cff18e2d35c5)) 238 | 239 | ## [2.2.10](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.2.9...v2.2.10) (2022-09-28) 240 | 241 | ## [2.2.9](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.2.8...v2.2.9) (2022-09-27) 242 | 243 | ## [2.2.8](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.2.7...v2.2.8) (2022-09-26) 244 | 245 | ## [2.2.7](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.2.6...v2.2.7) (2022-09-26) 246 | 247 | ## [2.2.6](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.2.5...v2.2.6) (2022-09-25) 248 | 249 | 250 | ### Bug Fixes 251 | 252 | * remove alias leftovers from dist bundles ([407ce79](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/407ce79c6a6e00b509fd504f4cf615b3e3c504c3)) 253 | 254 | ## [2.2.5](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.2.4...v2.2.5) (2022-09-24) 255 | 256 | ## [2.2.4](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.2.3...v2.2.4) (2022-09-24) 257 | 258 | ## [2.2.3](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.2.2...v2.2.3) (2022-09-24) 259 | 260 | ## [2.2.2](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.2.1...v2.2.2) (2022-09-24) 261 | 262 | ## [2.2.1](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.2.0...v2.2.1) (2022-09-24) 263 | 264 | # [2.2.0](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.1.0...v2.2.0) (2022-09-23) 265 | 266 | 267 | ### Features 268 | 269 | * migrate to @frsource/base64 package ([e4f3a14](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/e4f3a14575648b76d4f41eeb5984b853b968c974)) 270 | 271 | # [2.1.0](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.0.3...v2.1.0) (2022-09-23) 272 | 273 | 274 | ### Features 275 | 276 | * provide modern exports ([5c911a1](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/5c911a113624cea79e8b09eba2e643954a04a057)) 277 | 278 | ## [2.0.3](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.0.2...v2.0.3) (2022-09-23) 279 | 280 | ## [2.0.2](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.0.1...v2.0.2) (2022-09-22) 281 | 282 | ## [2.0.1](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v2.0.0...v2.0.1) (2022-09-17) 283 | 284 | # [2.0.0](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.21...v2.0.0) (2022-09-15) 285 | 286 | 287 | ### Features 288 | 289 | * img diff when resolution differs ([#108](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/108)) ([c8a5044](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/c8a504480d09f6ffd34321163bf14b1a2f0e7bb0)), closes [#94](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/94) 290 | 291 | 292 | ### BREAKING CHANGES 293 | 294 | * different resolution doesn't fail test immediately - img diff is being done 295 | 296 | ## [1.9.21](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.20...v1.9.21) (2022-09-14) 297 | 298 | 299 | ### Bug Fixes 300 | 301 | * btoa utf8 encoding/decoding error ([#114](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/114)) ([0137014](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/01370148094f3152a374f4e612e75ef5fd2bc3d8)) 302 | 303 | ## [1.9.20](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.19...v1.9.20) (2022-09-13) 304 | 305 | ## [1.9.19](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.18...v1.9.19) (2022-09-12) 306 | 307 | ## [1.9.18](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.17...v1.9.18) (2022-09-09) 308 | 309 | ## [1.9.17](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.16...v1.9.17) (2022-09-08) 310 | 311 | 312 | ### Bug Fixes 313 | 314 | * **deps:** update dependency vue to v3.2.39 ([#110](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/110)) ([8a7f055](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/8a7f0555b1d664b83c7de64d93796408646704eb)) 315 | 316 | ## [1.9.16](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.15...v1.9.16) (2022-09-05) 317 | 318 | ## [1.9.15](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.14...v1.9.15) (2022-09-03) 319 | 320 | ## [1.9.14](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.13...v1.9.14) (2022-09-02) 321 | 322 | 323 | ### Bug Fixes 324 | 325 | * image diff calculation ([529cb22](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/529cb22a22200af234bdbc1399b6f97880001d12)), closes [#107](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/107) 326 | 327 | ## [1.9.13](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.12...v1.9.13) (2022-08-31) 328 | 329 | 330 | ### Bug Fixes 331 | 332 | * **deps:** update dependency vue to v3.2.38 ([#101](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/101)) ([e2d3c82](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/e2d3c823822ecb6738202599500435cf59f2f6d1)) 333 | 334 | ## [1.9.12](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.11...v1.9.12) (2022-08-30) 335 | 336 | ## [1.9.11](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.10...v1.9.11) (2022-08-30) 337 | 338 | ## [1.9.10](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.9...v1.9.10) (2022-08-27) 339 | 340 | ## [1.9.9](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.8...v1.9.9) (2022-08-26) 341 | 342 | ## [1.9.8](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.7...v1.9.8) (2022-08-25) 343 | 344 | ## [1.9.7](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.6...v1.9.7) (2022-08-23) 345 | 346 | ## [1.9.6](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.5...v1.9.6) (2022-08-22) 347 | 348 | ## [1.9.5](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.4...v1.9.5) (2022-08-22) 349 | 350 | ## [1.9.4](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.3...v1.9.4) (2022-08-21) 351 | 352 | ## [1.9.3](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.2...v1.9.3) (2022-08-17) 353 | 354 | ## [1.9.2](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.1...v1.9.2) (2022-08-09) 355 | 356 | ## [1.9.1](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.9.0...v1.9.1) (2022-08-09) 357 | 358 | # [1.9.0](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.8.10...v1.9.0) (2022-08-09) 359 | 360 | 361 | ### Features 362 | 363 | * add title option to matchImage command ([#81](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/81)) ([4d03866](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/4d03866f7f171473b16b4a7c869fbca02d5f46d1)) 364 | 365 | ## [1.8.10](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.8.9...v1.8.10) (2022-08-02) 366 | 367 | ## [1.8.9](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.8.8...v1.8.9) (2022-08-01) 368 | 369 | ## [1.8.8](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.8.7...v1.8.8) (2022-07-19) 370 | 371 | ## [1.8.7](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.8.6...v1.8.7) (2022-07-17) 372 | 373 | ## [1.8.6](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.8.5...v1.8.6) (2022-07-12) 374 | 375 | 376 | ### Bug Fixes 377 | 378 | * **deps:** pin dependency vue to 3.2.37 ([#68](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/68)) ([d09a762](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/d09a762bbcf0f6e9bb886f80e4d01724bf0e3367)) 379 | 380 | ## [1.8.5](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.8.4...v1.8.5) (2022-07-07) 381 | 382 | ## [1.8.4](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.8.3...v1.8.4) (2022-07-04) 383 | 384 | ## [1.8.3](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.8.2...v1.8.3) (2022-06-29) 385 | 386 | ## [1.8.2](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.8.1...v1.8.2) (2022-06-27) 387 | 388 | ## [1.8.1](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.8.0...v1.8.1) (2022-06-27) 389 | 390 | 391 | ### Bug Fixes 392 | 393 | * remove automated screenshots update ([acb3ef0](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/acb3ef08fb8ec5129bee9883431361dd804d23f3)) 394 | 395 | # [1.8.0](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.7.8...v1.8.0) (2022-06-27) 396 | 397 | 398 | ### Bug Fixes 399 | 400 | * **deps:** update dependency move-file to v3 ([#62](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/62)) ([4f6eaf6](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/4f6eaf64a0f3db6e54190ef7532059a451ad384f)) 401 | 402 | 403 | ### Features 404 | 405 | * make library cypress 10 compatible ([b26beb3](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/b26beb391cf440d2d4b01261271b7acffa6f600e)) 406 | * make plugin Cypress 10 compatible ([a03a17d](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/a03a17d7295dd811969c10ad562dda26795fd8f2)) 407 | 408 | ## [1.7.8](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.7.7...v1.7.8) (2022-06-24) 409 | 410 | ## [1.7.7](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.7.6...v1.7.7) (2022-06-24) 411 | 412 | ## [1.7.6](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.7.5...v1.7.6) (2022-06-24) 413 | 414 | 415 | ### Bug Fixes 416 | 417 | * **deps:** update dependency pixelmatch to v5.3.0 ([#55](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/55)) ([ca5d278](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/ca5d2784a5fffb60bebe7643f8beced6ad9979bd)) 418 | 419 | ## [1.7.5](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.7.4...v1.7.5) (2022-06-24) 420 | 421 | ## [1.7.4](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.7.3...v1.7.4) (2022-06-24) 422 | 423 | ## [1.7.3](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.7.2...v1.7.3) (2022-06-23) 424 | 425 | ## [1.7.2](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.7.1...v1.7.2) (2022-06-23) 426 | 427 | ## [1.7.1](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.7.0...v1.7.1) (2022-03-15) 428 | 429 | 430 | ### Bug Fixes 431 | 432 | * sanitize screenshot filenames ([fc57380](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/fc57380d40e72eec51d5fdf2615226a358efa070)) 433 | 434 | # [1.7.0](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.6.0...v1.7.0) (2022-03-01) 435 | 436 | 437 | ### Features 438 | 439 | * don't override screenshots if not needed ([9066017](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/90660179d05f4de1c803888fb66f8e1c240f7c37)) 440 | 441 | # [1.6.0](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.5.0...v1.6.0) (2022-02-25) 442 | 443 | 444 | ### Features 445 | 446 | * show scrollbar for overflowing images ([de994b9](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/de994b98ad3dea233aee70b0142992a309476e38)) 447 | 448 | # [1.5.0](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.4.0...v1.5.0) (2022-02-24) 449 | 450 | 451 | ### Features 452 | 453 | * add forceDeviceFactor option ([8d69632](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/8d6963222f924d73fc0aed08adecdb361104c2dc)) 454 | 455 | # [1.4.0](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.3.1...v1.4.0) (2022-02-21) 456 | 457 | 458 | ### Features 459 | 460 | * add possibility to change images dirname ([b831e94](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/b831e94677df906f0cbd889f7ce0994e1e8a7783)) 461 | 462 | ## [1.3.1](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.3.0...v1.3.1) (2021-11-23) 463 | 464 | 465 | ### Bug Fixes 466 | 467 | * create missing dirs when renaming screenshot files ([38e5ff5](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/38e5ff5d5f7c2a8d9b971deb13af821773815f66)) 468 | 469 | # [1.3.0](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.2.0...v1.3.0) (2021-11-09) 470 | 471 | 472 | ### Bug Fixes 473 | 474 | * text overflowing when image is small ([3b04f8e](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/3b04f8e1782754c4c48e946ebdb2f43ccfec9461)) 475 | 476 | 477 | ### Features 478 | 479 | * externalize important APIs ([9f94086](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/9f9408657e7970bdad5dfc7a599943a34a779ab7)) 480 | 481 | # [1.2.0](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.1.0...v1.2.0) (2021-10-26) 482 | 483 | 484 | ### Features 485 | 486 | * stop logging all of the tasks ([573e728](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/573e7282799c802b0f6e9ecbe66501d043745ac3)) 487 | 488 | # [1.1.0](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.0.1...v1.1.0) (2021-10-25) 489 | 490 | 491 | ### Features 492 | 493 | * add queue flushing in after block ([70f828f](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/70f828ff68c4de276dd10c64ab61fece573d305f)) 494 | 495 | ## [1.0.1](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/v1.0.0...v1.0.1) (2021-10-25) 496 | 497 | 498 | ### Bug Fixes 499 | 500 | * proper readme info ([dd87e19](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/dd87e19429fe232bd9027737ff7e218c52d8eb06)) 501 | 502 | # 1.0.0 (2021-10-25) 503 | 504 | 505 | ### Features 506 | 507 | * add typings ([0a0e8e6](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/0a0e8e63ba1df0f95cf81ba6b0b34a095a0b69be)) 508 | * first implementation ([388cccf](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/388cccf5f033010e4de9f88294f5fca30c6d0cd1)) 509 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # @frsource/cypress-plugin-visual-regression-diff Contributing Guide 2 | 3 | Hey! It’s really exciting for us to see your interest in contributing to this library. Before taking off with your work, please take a moment to read through these guidelines: 4 | 5 | - [Code of Conduct](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/blob/master/CODE_OF_CONDUCT.md) 6 | - [Questions?](#questions) 7 | - [Reporting an issue or a feature request](#reporing-an-issue-or-a-feature-request) 8 | - [Pull Request Guidelines](#pull-request-guidelines) 9 | - [Development Setup](#development-setup) 10 | 11 | ## Questions? 12 | 13 | Don’t hesitate to ask a question directly on the [discussions board](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/discussions)! 14 | 15 | ## Reporting an issue or a feature request 16 | 17 | - Please always use GitHub Issues tracker with [appropriate template](https://github.com/login?return_to=https%3A%2F%2Fgithub.com%2FFRSOURCE%2Fcypress-plugin-visual-regression-diff%2Fissues%2Fnew%2Fchoose) to create a new issue or suggestion 18 | 19 | ## Pull Request Guidelines 20 | 21 | - Check if there isn’t a similar PR already in the [GitHub Pull requests](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/pulls) - maybe somebody already has covered this topic? 22 | 23 | - Checkout the master branch and (after you do your work) file a PR against it 24 | 25 | - Read through the [development setup](#development-setup) to learn how to work with this project. Always make sure that `pnpm lint`, `pnpm build` and `pnpm test` pass 26 | 27 | - Please use [conventional commits v1.0.0 style guide](https://www.conventionalcommits.org/en/v1.0.0/) for commits and PR names 28 | 29 | - We have no preference about number of commits on the PR - they will be all squashed by GitHub while merging 30 | 31 | - When creating a new feature/plugin/integration: 32 | 33 | - Make sure the feature is covered by tests 34 | - Provide a meaningful description. In most cases it would make sens to first open a issue with a suggestion, discuss about it and have it approved before working on it 35 | 36 | - When fixing bug: 37 | - Try to cover the scenario with tests if possible 38 | - If an issue for this bug already exists, please reference it via (`Refs: #XYZ` - where `XYZ` is an issue number) at the very bottom of your commit message and PR description as proposed by [conventional commits v1.0.0 style guide](https://www.conventionalcommits.org/en/v1.0.0/#commit-message-with-multi-paragraph-body-and-multiple-footers) 39 | - If there is no issue connected with the bug, please provide a detailed description of the problem in the PR. Live demo preferred ([look for the codeine example project in the bug issue template](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/blob/master/.github/ISSUE_TEMPLATE/bug_report.md)) 40 | 41 | ## Development Setup 42 | 43 | 44 | 45 | You will need [Node.js](https://nodejs.org/en/) **version 16+** and [pnpm](https://pnpm.io/installation). 46 | 47 | 48 | 49 | After cloning the repository, run: 50 | 51 | ```bash 52 | pnpm i # installs the project dependencies 53 | ``` 54 | 55 | ### Committing Changes 56 | 57 | Commit messages should follow the [conventional commits v1.0.0](https://www.conventionalcommits.org/en/v1.0.0/) so that changelogs can be automatically generated. Commit messages will be automatically validated upon commit. 58 | 59 | ### These npm scripts are available in the repo 60 | 61 | When fired in the project root they will run corresponding actions in every nested package at once. 62 | 63 | ```bash 64 | # build the project for NPM and example usage 65 | $ pnpm build 66 | 67 | # run tests once 68 | $ pnpm test 69 | 70 | # open cypress component runner from example directory 71 | # requires plugin to be built first via `pnpm build` 72 | $ pnpm test:ct 73 | 74 | # open cypress e2e runner from example directory 75 | # requires plugin to be built first via `pnpm build` 76 | $ pnpm test:e2e 77 | 78 | # run integration tests once and collect coverage 79 | $ pnpm test:integration:coverage 80 | 81 | # run integration tests in watch mode 82 | $ pnpm test:integration:watch 83 | 84 | # lint & try to autofix linting errors 85 | $ pnpm fix:lint && pnpm format 86 | 87 | # lint files 88 | $ pnpm lint && pnpm format:ci 89 | ``` 90 | 91 | There are some other scripts available in the `scripts` section of the `package.json` file. 92 | 93 | ## Credits 94 | 95 | Many thanks to all the people who have already contributed to @frsource/cypress-plugin-visual-regression-diff! ❤️ 96 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 FRSOURCE - Let's shape your web 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | NPM version badge 4 | 5 | 6 | NPM total downloads badge 7 | 8 | 9 | CodeClimate maintainability badge 10 | 11 | 12 | 13 | 14 | 15 | semantic-relase badge 16 | 17 | 18 | license MIT badge 19 | 20 |

21 | 22 |

23 | Cypress Plugin Visual Regression Diff logo 24 |

25 | 26 |

Plugin for Cypress - Visual Regression Diff

27 |

Perform visual regression test with a nice GUI as help. 💅 Only for Cypress! Both e2e and component-testing compatible 💪

28 | 29 |

30 | Getting Started 31 | · 32 | Usage 33 | · 34 | FAQ 35 | · 36 | File an Issue 37 | · 38 | Have a question or an idea? 39 |
40 |

41 | 42 |

43 |
44 | Plugin for visual regression testing that provides smooth experience: 45 |
Specify threshold below which the test will fail. 46 |
Quickly preview old & new screenshot directly in the Cypress UI. 47 |
Find visual changes using images diff. 48 |
Published as treeshakeable bundles, separate for JS ES5 or modern bundlers thanks to microbundle. 49 |
Working with every bundler (tested on webpack, vite, rollup), 50 |
Provides proper typings as is written completely in typescript.
51 |
52 |
53 |

54 | 55 | ![frsource-visual-testing-example](https://user-images.githubusercontent.com/10456649/191988386-2be2ea14-7b7a-4048-a14e-0cad8d21e214.gif) 56 | 57 | ## Getting started 58 | 59 | ### Installation 60 | 61 | You can install this library using your favorite package manager: 62 | 63 | ```bash 64 | # npm 65 | npm install --save-dev @frsource/cypress-plugin-visual-regression-diff 66 | 67 | # yarn 68 | yarn add -D @frsource/cypress-plugin-visual-regression-diff 69 | 70 | # pnpm 71 | pnpm add -D @frsource/cypress-plugin-visual-regression-diff 72 | ``` 73 | 74 | Next, you need to import the library: 75 | 76 | - first, in your support file (located by default in `cypress/support/index.js`): 77 | 78 | ```ts 79 | // typescript / ES6 80 | import "@frsource/cypress-plugin-visual-regression-diff"; 81 | 82 | // javascript 83 | require("@frsource/cypress-plugin-visual-regression-diff"); 84 | ``` 85 | 86 | - secondly: 87 | - (for Cypress 10.0+) in `cypress.config.js` (or `cypress.config.ts`): 88 | 89 | ```ts 90 | // typescript / ES6 91 | import { defineConfig } from "cypress"; 92 | import { initPlugin } from "@frsource/cypress-plugin-visual-regression-diff/plugins"; 93 | 94 | export default defineConfig({ 95 | // initPlugin must be called in the section where it is used: e2e or component 96 | e2e: { 97 | setupNodeEvents(on, config) { 98 | initPlugin(on, config); 99 | }, 100 | }, 101 | component: { 102 | setupNodeEvents(on, config) { 103 | initPlugin(on, config); 104 | }, 105 | }, 106 | }); 107 | ``` 108 | 109 | - (for Cypress <10.0) in your plugins file (located by default in `cypress/plugins/index.js`): 110 | 111 | ```ts 112 | // typescript / ES6 113 | import { initPlugin } from "@frsource/cypress-plugin-visual-regression-diff/plugins"; 114 | 115 | export default function ( 116 | on: Cypress.PluginEvents, 117 | config: Cypress.PluginConfigOptions 118 | ) { 119 | initPlugin(on, config); 120 | 121 | return config; 122 | } 123 | 124 | // javascript 125 | const { 126 | initPlugin, 127 | } = require("@frsource/cypress-plugin-visual-regression-diff/plugins"); 128 | 129 | module.exports = function (on, config) { 130 | initPlugin(on, config); 131 | 132 | return config; 133 | }; 134 | ``` 135 | 136 | That's it - now let's see how to use the library in [usage section](#usage). 137 | 138 | ## Usage 139 | 140 | Once installed, the library might be used by writing in your test: 141 | 142 | ```ts 143 | cy.get(".an-element-of-your-choice").matchImage(); 144 | ``` 145 | 146 | Or, if you would like to make a screenshot of whole document: 147 | 148 | ```ts 149 | cy.matchImage(); 150 | ``` 151 | 152 | `matchImage` command will do a screenshot and compare it with image from a previous run. In case of regression the test will fail and you'll get a "See comparison" button to see what's a root of a problem. 153 | 154 | ## Example 155 | 156 | Still got troubles with installation? Have a look at [examples directory of this repo](./examples) to see how this plugin can be used in e2e or component-testing Cypress environment within your project. 157 | 158 | ## Automatic clean up of unused images 159 | 160 | 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 🚀: 162 | 163 | ```bash 164 | npx cypress run --env "pluginVisualRegressionCleanupUnusedImages=true" 165 | ``` 166 | 167 | ## Configuration 168 | 169 | Configure the plugin: 170 | 171 | - by passing in configuration as an argument to `matchImage` command: 172 | 173 | ```ts 174 | cy.matchImage({ 175 | // screenshot configuration, passed directly to the the Cypress screenshot method: https://docs.cypress.io/api/cypress-api/screenshot-api#Arguments 176 | // default: { } 177 | screenshotConfig: { 178 | blackout: ['.element-to-be-blackouted'] 179 | }, 180 | // pixelmatch options, see: https://www.npmjs.com/package/pixelmatch#pixelmatchimg1-img2-output-width-height-options 181 | // default: { includeAA: true } 182 | diffConfig: { 183 | threshold: 0.01, 184 | }, 185 | // whether to create missing baseline images automatically 186 | // default: true 187 | createMissingImages: false, 188 | // whether to update images automatically, without making a diff - useful for CI 189 | // default: false 190 | updateImages: true, 191 | // directory path in which screenshot images will be stored 192 | // relative path are resolved against project root 193 | // absolute paths (both on unix and windows OS) supported 194 | // path separators will be normalised by the plugin depending on OS, you should always use / as path separator, e.g.: C:/my-directory/nested for windows-like drive notation 195 | // There are one special variable available to be used in the path: 196 | // - {spec_path} - relative path leading from project root to the current spec file directory (e.g. `/src/components/my-tested-component`) 197 | // default: '{spec_path}/__image_snapshots__' 198 | imagesPath: 'this-might-be-your-custom/maybe-nested-directory', 199 | // maximum threshold above which the test should fail 200 | // default: 0.01 201 | maxDiffThreshold: 0.1, 202 | // forces scale factor to be set as value "1" 203 | // helps with screenshots being scaled 2x on high-density screens like Mac Retina 204 | // default: true 205 | forceDeviceScaleFactor: false, 206 | // title used for naming the image file 207 | // default: Cypress.currentTest.titlePath (your test title) 208 | title: `${Cypress.currentTest.titlePath.join(' ')} (${Cypress.browser.displayName})`, 209 | // pass a path to custom image that should be used for comparison 210 | // instead of checking against the image from previous run 211 | // default: undefined 212 | matchAgainstPath: '/path/to/reference-image.png' 213 | }) 214 | ``` 215 | 216 | - 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.: 217 | 218 | ```bash 219 | npx cypress run --env "pluginVisualRegressionUpdateImages=true,pluginVisualRegressionDiffConfig={\"threshold\":0.01}" 220 | ``` 221 | 222 | ```ts 223 | // cypress.config.ts 224 | import { defineConfig } from "cypress"; 225 | 226 | export default defineConfig({ 227 | env: { 228 | pluginVisualRegressionUpdateImages: true, 229 | pluginVisualRegressionDiffConfig: { threshold: 0.01 }, 230 | }, 231 | }); 232 | ``` 233 | 234 | ```json 235 | // cypress.env.json (https://docs.cypress.io/guides/guides/environment-variables#Option-2-cypress-env-json) 236 | { 237 | "pluginVisualRegressionUpdateImages": true, 238 | "pluginVisualRegressionDiffConfig": { "threshold": 0.01 } 239 | } 240 | ``` 241 | 242 | For more ways of setting environment variables [take a look here](https://docs.cypress.io/guides/guides/environment-variables#Setting). 243 | 244 | ## FAQ 245 | 246 |
Why screenshots doesn't conform to the `viewport` set in my Cypress configuration? 247 | 248 | Screenshots in Cypress do not scale to the viewport size by default. You can change this behavior: 249 | 250 | - globally, by changing default screenshot configuration: Cypress.Screenshot.defaults({ capture: 'viewport' }); 251 | - locally, by passing screenshot configuration directly to the .matchImage command: cy.matchImage({ screenshotConfig: { capture: 'viewport' } }); 252 | 253 |
254 | 255 |
I've upgraded version of this plugin and all on my baseline images has been automatically updated. Why? 256 | 257 | Sometimes we need to do a breaking change in image comparison or image generation algorithms. To provide you with the easiest upgrade path - the plugin updates your baseline images automatically. Just commit them to your repository after the plugin upgrade and you are good to go! 258 | 259 |
260 | 261 | ## Questions 262 | 263 | Don’t hesitate to ask a question directly on the [discussions board](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/discussions)! 264 | 265 | ## Changelog 266 | 267 | Changes for every release are documented in the [release notes](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/releases) and [CHANGELOG files of every package](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/tree/main/packages). 268 | 269 | ## License 270 | 271 | [MIT](https://opensource.org/licenses/MIT) 272 | 273 | Copyright (c) 2021-present, Jakub FRS Freisler, [FRSOURCE](https://www.frsource.org/) 274 | 275 |

276 | 277 | FRSOURCE logo 278 | 279 |

280 | -------------------------------------------------------------------------------- /assets/frsource-visual-testing-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRSOURCE/cypress-plugin-visual-regression-diff/765151bbddbfcd87864040aac5bfc6e7e68b639a/assets/frsource-visual-testing-example.gif -------------------------------------------------------------------------------- /assets/frsource-visual-testing-example.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRSOURCE/cypress-plugin-visual-regression-diff/765151bbddbfcd87864040aac5bfc6e7e68b639a/assets/frsource-visual-testing-example.mp4 -------------------------------------------------------------------------------- /assets/license-1869795-2874802.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRSOURCE/cypress-plugin-visual-regression-diff/765151bbddbfcd87864040aac5bfc6e7e68b639a/assets/license-1869795-2874802.pdf -------------------------------------------------------------------------------- /assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { typescript, javascript } from '@frsource/eslint-config'; 2 | import globals from 'globals'; 3 | import cypress from 'eslint-plugin-cypress/flat' 4 | 5 | /** @type {import("eslint").Linter.Config[]} */ 6 | export default [ 7 | ...javascript, 8 | ...typescript, 9 | { ignores: ['**/dist', '**/coverage', '**/node_modules'] }, 10 | { rules: { '@typescript-eslint/no-invalid-void-type': 'off' } }, 11 | { 12 | plugins: { cypress }, 13 | files: ['examples/*/cypress/**', 'packages/*/src/**'], 14 | languageOptions: { 15 | globals: { 16 | ...globals.es2021, 17 | ...globals.node, 18 | ...cypress.globals, 19 | }, 20 | }, 21 | }, 22 | ]; 23 | -------------------------------------------------------------------------------- /examples/next/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # we do not want to commit local screenshot files 12 | # as they might be different on different OSes 13 | **/__image_snapshots_local__ 14 | 15 | # next.js 16 | /.next/ 17 | /out/ 18 | 19 | # production 20 | /build 21 | 22 | # misc 23 | .DS_Store 24 | *.pem 25 | 26 | # debug 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | next-env.d.ts 40 | 41 | # cypress 42 | /cypress/screenshots 43 | /cypress/videos 44 | -------------------------------------------------------------------------------- /examples/next/README.md: -------------------------------------------------------------------------------- 1 | # Next.js + Cypress + @frsource/cypress-plugin-visual-regression-diff 2 | 3 | This example shows how to configure @frsource/cypress-plugin-visual-regression-diff to work with Cypress & Next.js. 4 | 5 | ## Project setup 6 | 7 | ```bash 8 | pnpm install 9 | ``` 10 | 11 | ### Run end-to-end tests 12 | 13 | > Important - remember to run `pnpm && pnpm build` command in this repo's root directory before starting e2e tests. 14 | 15 | ```bash 16 | pnpm e2e 17 | ``` 18 | 19 | ### Run component tests 20 | 21 | > Important - remember to run `pnpm && pnpm build` command in this repo's root directory before starting e2e tests. 22 | 23 | ```bash 24 | pnpm component 25 | ``` 26 | 27 | ### Compiles and hot-reloads for development 28 | 29 | ```bash 30 | pnpm dev 31 | ``` 32 | 33 | ### Compiles and minifies for production 34 | 35 | ```bash 36 | pnpm build 37 | ``` 38 | 39 | ## Credits 40 | 41 | Created using [Next.js Cypress template](https://nextjs.org/docs/pages/building-your-application/optimizing/testing#cypress). 42 | -------------------------------------------------------------------------------- /examples/next/components/about-component.cy.tsx: -------------------------------------------------------------------------------- 1 | import AboutComponent from './about-component' 2 | // Disable ESLint to prevent failing linting inside the Next.js repo. 3 | // If you're using ESLint on your project, we recommend installing the ESLint Cypress plugin instead: 4 | // https://github.com/cypress-io/eslint-plugin-cypress 5 | 6 | describe('', () => { 7 | it('should render and display expected content', () => { 8 | cy.mount().then(() => { 9 | cy.matchImage() 10 | cy.get('h1').matchImage() 11 | }) 12 | }) 13 | }) 14 | 15 | // Prevent TypeScript from reading file as legacy script 16 | export {} 17 | -------------------------------------------------------------------------------- /examples/next/components/about-component.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import React from 'react' 3 | import styles from '../styles/Home.module.css' 4 | 5 | export default function AboutComponent() { 6 | return ( 7 | <> 8 |

About Page

9 |

10 | ← Go Back 11 |

12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /examples/next/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | import { initPlugin } from "@frsource/cypress-plugin-visual-regression-diff/plugins"; 3 | 4 | export default defineConfig({ 5 | e2e: { 6 | setupNodeEvents(on, config) { 7 | initPlugin(on, config); 8 | }, 9 | baseUrl: "http://localhost:3000", 10 | }, 11 | component: { 12 | setupNodeEvents(on, config) { 13 | initPlugin(on, config); 14 | }, 15 | devServer: { 16 | framework: "next", 17 | bundler: "webpack", 18 | }, 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /examples/next/cypress/e2e/app.cy.ts: -------------------------------------------------------------------------------- 1 | describe("Navigation", () => { 2 | it("should navigate to the about page", () => { 3 | cy.visit("http://localhost:3000/"); 4 | 5 | cy.get('a[href*="about"]').click(); 6 | 7 | cy.url().should("include", "/about"); 8 | 9 | cy.matchImage().then(({ imgNewPath }) => { 10 | // match against image from custom path 11 | cy.matchImage({ matchAgainstPath: imgNewPath }); 12 | }); 13 | }); 14 | }); 15 | 16 | export {}; 17 | -------------------------------------------------------------------------------- /examples/next/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /examples/next/cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************** 3 | // This example commands.ts shows you how to 4 | // create various custom commands and overwrite 5 | // existing commands. 6 | // 7 | // For more comprehensive examples of custom 8 | // commands please read more here: 9 | // https://on.cypress.io/custom-commands 10 | // *********************************************** 11 | // 12 | // 13 | // -- This is a parent command -- 14 | // Cypress.Commands.add('login', (email, password) => { ... }) 15 | // 16 | // 17 | // -- This is a child command -- 18 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 19 | // 20 | // 21 | // -- This is a dual command -- 22 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 23 | // 24 | // 25 | // -- This will overwrite an existing command -- 26 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 27 | // 28 | // declare global { 29 | // namespace Cypress { 30 | // interface Chainable { 31 | // login(email: string, password: string): Chainable 32 | // drag(subject: string, options?: Partial): Chainable 33 | // dismiss(subject: string, options?: Partial): Chainable 34 | // visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable 35 | // } 36 | // } 37 | // } 38 | 39 | import "@frsource/cypress-plugin-visual-regression-diff/dist/support"; 40 | 41 | // Prevent TypeScript from reading file as legacy script 42 | export {}; 43 | -------------------------------------------------------------------------------- /examples/next/cypress/support/component-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Components App 8 | 9 |
10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/next/cypress/support/component.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/component.ts is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import "./commands"; 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | 22 | import { mount } from "cypress/react18"; 23 | 24 | // Augment the Cypress namespace to include type definitions for 25 | // your custom command. 26 | // Alternatively, can be defined in cypress/support/component.d.ts 27 | // with a at the top of your spec. 28 | declare global { 29 | // eslint-disable-next-line @typescript-eslint/no-namespace 30 | namespace Cypress { 31 | interface Chainable { 32 | mount: typeof mount; 33 | } 34 | } 35 | } 36 | 37 | Cypress.Commands.add("mount", mount); 38 | 39 | // Example use: 40 | // cy.mount() 41 | -------------------------------------------------------------------------------- /examples/next/cypress/support/e2e.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/e2e.ts is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /examples/next/cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["es5", "dom"], 5 | "types": ["cypress", "node"] 6 | }, 7 | "include": ["**/*.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /examples/next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "next dev", 5 | "build": "next build", 6 | "start": "next start", 7 | "cypress": "cypress open --env \"pluginVisualRegressionUpdateImages=true,pluginVisualRegressionImagesPath={spec_path}/__image_snapshots_local__\"", 8 | "cypress:ci": "cypress run --env \"pluginVisualRegressionUpdateImages=true\"", 9 | "test:e2e": "start-server-and-test dev http://localhost:3000 \"pnpm cypress --e2e\"", 10 | "test:e2e:ci": "start-server-and-test dev http://localhost:3000 \"pnpm cypress:ci --e2e\"", 11 | "test:ct": "start-server-and-test dev http://localhost:3000 \"pnpm cypress --component\"", 12 | "test:ct:ci": "start-server-and-test dev http://localhost:3000 \"pnpm cypress:ci --component\"" 13 | }, 14 | "dependencies": { 15 | "next": "latest", 16 | "react": "18.2.0", 17 | "react-dom": "18.2.0" 18 | }, 19 | "devDependencies": { 20 | "@frsource/cypress-plugin-visual-regression-diff": "workspace:*", 21 | "@types/node": "18.0.6", 22 | "@types/react": "18.0.15", 23 | "@types/react-dom": "18.0.6", 24 | "cypress": "12.12.0", 25 | "start-server-and-test": "1.15.2", 26 | "typescript": "4.7.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/next/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import type { AppProps } from 'next/app' 2 | import '../styles/globals.css' 3 | 4 | function MyApp({ Component, pageProps }: AppProps) { 5 | return 6 | } 7 | 8 | export default MyApp 9 | -------------------------------------------------------------------------------- /examples/next/pages/about.tsx: -------------------------------------------------------------------------------- 1 | import AboutComponent from '../components/about-component' 2 | import styles from '../styles/Home.module.css' 3 | 4 | export default function About() { 5 | return ( 6 |
7 |
8 | 9 |
10 |
11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /examples/next/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import Image from 'next/image' 3 | import Link from 'next/link' 4 | import styles from '../styles/Home.module.css' 5 | 6 | export default function Home() { 7 | return ( 8 |
9 | 10 | Create Next App 11 | 12 | 13 | 14 | 15 |
16 |

17 | Welcome to Next.js! 18 |

19 | 20 |

21 | Get started by editing{' '} 22 | pages/index.js 23 |

24 | 25 | 61 |
62 | 63 | 75 |
76 | ) 77 | } 78 | -------------------------------------------------------------------------------- /examples/next/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRSOURCE/cypress-plugin-visual-regression-diff/765151bbddbfcd87864040aac5bfc6e7e68b639a/examples/next/public/favicon.ico -------------------------------------------------------------------------------- /examples/next/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /examples/next/styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: 100vh; 3 | padding: 0 0.5rem; 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: center; 7 | align-items: center; 8 | height: 100vh; 9 | } 10 | 11 | .main { 12 | padding: 5rem 0; 13 | flex: 1; 14 | display: flex; 15 | flex-direction: column; 16 | justify-content: center; 17 | align-items: center; 18 | } 19 | 20 | .footer { 21 | width: 100%; 22 | height: 100px; 23 | border-top: 1px solid #eaeaea; 24 | display: flex; 25 | justify-content: center; 26 | align-items: center; 27 | } 28 | 29 | .footer a { 30 | display: flex; 31 | justify-content: center; 32 | align-items: center; 33 | flex-grow: 1; 34 | } 35 | 36 | .title a { 37 | color: #0070f3; 38 | text-decoration: none; 39 | } 40 | 41 | .title a:hover, 42 | .title a:focus, 43 | .title a:active { 44 | text-decoration: underline; 45 | } 46 | 47 | .title { 48 | margin: 0; 49 | line-height: 1.15; 50 | font-size: 4rem; 51 | } 52 | 53 | .title, 54 | .description { 55 | text-align: center; 56 | } 57 | 58 | .description { 59 | line-height: 1.5; 60 | font-size: 1.5rem; 61 | } 62 | 63 | .code { 64 | background: #fafafa; 65 | border-radius: 5px; 66 | padding: 0.75rem; 67 | font-size: 1.1rem; 68 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 69 | Bitstream Vera Sans Mono, Courier New, monospace; 70 | } 71 | 72 | .grid { 73 | display: flex; 74 | align-items: center; 75 | justify-content: center; 76 | flex-wrap: wrap; 77 | max-width: 800px; 78 | margin-top: 3rem; 79 | } 80 | 81 | .card { 82 | margin: 1rem; 83 | padding: 1.5rem; 84 | text-align: left; 85 | color: inherit; 86 | text-decoration: none; 87 | border: 1px solid #eaeaea; 88 | border-radius: 10px; 89 | transition: color 0.15s ease, border-color 0.15s ease; 90 | width: 45%; 91 | } 92 | 93 | .card:hover, 94 | .card:focus, 95 | .card:active { 96 | color: #0070f3; 97 | border-color: #0070f3; 98 | } 99 | 100 | .card h2 { 101 | margin: 0 0 1rem 0; 102 | font-size: 1.5rem; 103 | } 104 | 105 | .card p { 106 | margin: 0; 107 | font-size: 1.25rem; 108 | line-height: 1.5; 109 | } 110 | 111 | .logo { 112 | height: 1em; 113 | margin-left: 0.5rem; 114 | } 115 | 116 | @media (max-width: 600px) { 117 | .grid { 118 | width: 100%; 119 | flex-direction: column; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /examples/next/styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | -------------------------------------------------------------------------------- /examples/next/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": false, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/webpack/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | not ie 11 5 | -------------------------------------------------------------------------------- /examples/webpack/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | /tests/e2e/videos/ 6 | /tests/e2e/screenshots/ 7 | 8 | # we do not want to commit local screenshot files 9 | # as they might be different on different OSes 10 | **/__image_snapshots_local__ 11 | 12 | 13 | # local env files 14 | .env.local 15 | .env.*.local 16 | 17 | # Log files 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | pnpm-debug.log* 22 | 23 | # Editor directories and files 24 | .idea 25 | .vscode 26 | *.suo 27 | *.ntvs* 28 | *.njsproj 29 | *.sln 30 | *.sw? 31 | -------------------------------------------------------------------------------- /examples/webpack/README.md: -------------------------------------------------------------------------------- 1 | # Example for @frsource/cypress-plugin-visual-regression-diff 2 | 3 | ## Project setup 4 | 5 | ```bash 6 | pnpm install 7 | ``` 8 | 9 | ### Run your end-to-end tests 10 | 11 | > Important - remember to run `pnpm && pnpm build` command in this repo's root directory before starting e2e tests. 12 | 13 | ```bash 14 | pnpm test:e2e 15 | ``` 16 | 17 | ### Run component tests 18 | 19 | > Important - remember to run `pnpm && pnpm build` command in this repo's root directory before starting e2e tests. 20 | 21 | ```bash 22 | pnpm test:ct 23 | ``` 24 | 25 | ### Compiles and hot-reloads for development 26 | 27 | ```bash 28 | pnpm serve 29 | ``` 30 | 31 | ### Compiles and minifies for production 32 | 33 | ```bash 34 | pnpm build 35 | ``` 36 | 37 | ### Customize configuration 38 | 39 | See [Configuration Reference](https://cli.vuejs.org/config/). 40 | -------------------------------------------------------------------------------- /examples/webpack/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | import { initPlugin } from "@frsource/cypress-plugin-visual-regression-diff/plugins"; 3 | 4 | module.exports = defineConfig({ 5 | video: false, 6 | e2e: { 7 | setupNodeEvents(on, config) { 8 | initPlugin(on, config); 9 | }, 10 | specPattern: "cypress/e2e/**/*.cy.{js,jsx,ts,tsx}", 11 | }, 12 | 13 | component: { 14 | setupNodeEvents(on, config) { 15 | initPlugin(on, config); 16 | }, 17 | devServer: { 18 | framework: "vue-cli", 19 | bundler: "webpack", 20 | }, 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /examples/webpack/cypress/component/HelloWorld.cy.ts: -------------------------------------------------------------------------------- 1 | import { mount } from "cypress/vue"; 2 | import HelloWorld from "../../src/components/HelloWorld.vue"; 3 | 4 | const msg = "Some random test message"; 5 | 6 | describe("HelloWorld.cy.js", () => { 7 | it("playground", () => { 8 | mount(HelloWorld, { 9 | propsData: { msg }, 10 | }).then(() => { 11 | cy.contains("h1", msg); 12 | cy.matchImage(); 13 | cy.get('[data-testid="description"]').matchImage(); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /examples/webpack/cypress/component/__image_snapshots__/HelloWorld.cy.js playground #0.actual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRSOURCE/cypress-plugin-visual-regression-diff/765151bbddbfcd87864040aac5bfc6e7e68b639a/examples/webpack/cypress/component/__image_snapshots__/HelloWorld.cy.js playground #0.actual.png -------------------------------------------------------------------------------- /examples/webpack/cypress/component/__image_snapshots__/HelloWorld.cy.js playground #0.diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRSOURCE/cypress-plugin-visual-regression-diff/765151bbddbfcd87864040aac5bfc6e7e68b639a/examples/webpack/cypress/component/__image_snapshots__/HelloWorld.cy.js playground #0.diff.png -------------------------------------------------------------------------------- /examples/webpack/cypress/component/__image_snapshots__/HelloWorld.cy.js playground #0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRSOURCE/cypress-plugin-visual-regression-diff/765151bbddbfcd87864040aac5bfc6e7e68b639a/examples/webpack/cypress/component/__image_snapshots__/HelloWorld.cy.js playground #0.png -------------------------------------------------------------------------------- /examples/webpack/cypress/component/__image_snapshots__/HelloWorld.cy.js playground #1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRSOURCE/cypress-plugin-visual-regression-diff/765151bbddbfcd87864040aac5bfc6e7e68b639a/examples/webpack/cypress/component/__image_snapshots__/HelloWorld.cy.js playground #1.png -------------------------------------------------------------------------------- /examples/webpack/cypress/e2e/__image_snapshots__/My First Test Visits the app root url #0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRSOURCE/cypress-plugin-visual-regression-diff/765151bbddbfcd87864040aac5bfc6e7e68b639a/examples/webpack/cypress/e2e/__image_snapshots__/My First Test Visits the app root url #0.png -------------------------------------------------------------------------------- /examples/webpack/cypress/e2e/spec.cy.ts: -------------------------------------------------------------------------------- 1 | describe("My First Test", () => { 2 | it("Visits the app root url", () => { 3 | cy.visit("/"); 4 | cy.contains("h1", "Welcome to Your Vue.js App"); 5 | cy.matchImage().then(({ imgNewPath }) => { 6 | // match against image from custom path 7 | cy.matchImage({ matchAgainstPath: imgNewPath }); 8 | }); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /examples/webpack/cypress/screenshots/HelloWorld.cy.ts/HelloWorld.cy.js -- playground (failed).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRSOURCE/cypress-plugin-visual-regression-diff/765151bbddbfcd87864040aac5bfc6e7e68b639a/examples/webpack/cypress/screenshots/HelloWorld.cy.ts/HelloWorld.cy.js -- playground (failed).png -------------------------------------------------------------------------------- /examples/webpack/cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | 27 | import "@frsource/cypress-plugin-visual-regression-diff/dist/support"; 28 | -------------------------------------------------------------------------------- /examples/webpack/cypress/support/component-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Components App 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/webpack/cypress/support/component.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/component.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import "./commands.ts"; 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /examples/webpack/cypress/support/e2e.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/component.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import "./commands"; 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /examples/webpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "test:open": "vue-cli-service test:e2e --env \"pluginVisualRegressionImagesPath={spec_path}/__image_snapshots_local__\"", 9 | "test:run": "vue-cli-service test:e2e", 10 | "test:ct": "pnpm test:open --component", 11 | "test:ct:ci": "pnpm test:run --component --headless", 12 | "test:e2e": "pnpm test:open --e2e", 13 | "test:e2e:ci": "pnpm test:run --e2e --headless" 14 | }, 15 | "dependencies": { 16 | "vue": "3.2.45" 17 | }, 18 | "devDependencies": { 19 | "@frsource/cypress-plugin-visual-regression-diff": "workspace:*", 20 | "@vue/cli-plugin-e2e-cypress": "5.0.8", 21 | "@vue/cli-service": "5.0.8", 22 | "cypress": "12.12.0", 23 | "typescript": "5.0.4" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/webpack/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRSOURCE/cypress-plugin-visual-regression-diff/765151bbddbfcd87864040aac5bfc6e7e68b639a/examples/webpack/public/favicon.ico -------------------------------------------------------------------------------- /examples/webpack/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/webpack/src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 16 | 17 | 27 | -------------------------------------------------------------------------------- /examples/webpack/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRSOURCE/cypress-plugin-visual-regression-diff/765151bbddbfcd87864040aac5bfc6e7e68b639a/examples/webpack/src/assets/logo.png -------------------------------------------------------------------------------- /examples/webpack/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 80 | 81 | 89 | 90 | 91 | 107 | -------------------------------------------------------------------------------- /examples/webpack/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import App from "./App.vue"; 3 | 4 | createApp(App).mount("#app"); 5 | -------------------------------------------------------------------------------- /examples/webpack/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "baseUrl": "./", 6 | "moduleResolution": "node", 7 | "paths": { 8 | "@/*": ["src/*"] 9 | }, 10 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "author": "Jakub Freisler ", 5 | "scripts": { 6 | "build": "pnpm -r build", 7 | "lint": "eslint '**/*.ts' --ignore-pattern '**/*.d.ts'", 8 | "lint:fix": "pnpm lint --fix", 9 | "lint:ci": "pnpm lint --max-warnings 0", 10 | "format": "pnpm -r format", 11 | "format:ci": "pnpm -r format:ci", 12 | "test": "pnpm -r test:ct:ci && pnpm -r test:e2e:ci && pnpm -r test:integration:ci", 13 | "test:integration": "pnpm -r test:integration", 14 | "test:integration:watch": "pnpm -r test:integration:watch", 15 | "test:integration:coverage": "pnpm -r test:integration:coverage", 16 | "test:integration:ci": "pnpm -r test:integration:ci", 17 | "test:ct": "pnpm -r test:ct", 18 | "test:ct:ci": "pnpm -r test:ct:ci", 19 | "test:e2e": "pnpm -r test:e2e", 20 | "test:e2e:ci": "pnpm -r test:e2e:ci" 21 | }, 22 | "engines": { 23 | "node": ">=10" 24 | }, 25 | "packageManager": "pnpm@8.6.12", 26 | "peerDependencies": { 27 | "cypress": ">=4.5.0" 28 | }, 29 | "devDependencies": { 30 | "@frsource/eslint-config": "1.15.0", 31 | "eslint": "9.11.0", 32 | "eslint-plugin-cypress": "^3.5.0", 33 | "globals": "15.9.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/node_modules 3 | **/coverage 4 | coverage 5 | **/dist 6 | pnpm-lock.yaml 7 | CHANGELOG.md 8 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [3.3.10](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/compare/@frsource/cypress-plugin-visual-regression-diff-v3.3.9...@frsource/cypress-plugin-visual-regression-diff-v3.3.10) (2023-06-17) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * **deps:** update dependency @frsource/base64 to v1.0.17 ([#244](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/issues/244)) ([d55c677](https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff/commit/d55c67734d8526172bc1231128eb9faa8e39d51b)) 9 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/__tests__/fixtures/prepare-screenshot-for-cleanup.spec.cy.js: -------------------------------------------------------------------------------- 1 | describe('Cleanup test', () => { 2 | it('Create screenshot to be removed', () => { 3 | cy.visit('/'); 4 | cy.get('[data-testid="description"]').matchImage(); 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/__tests__/fixtures/screenshot.actual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRSOURCE/cypress-plugin-visual-regression-diff/765151bbddbfcd87864040aac5bfc6e7e68b639a/packages/cypress-plugin-visual-regression-diff/__tests__/fixtures/screenshot.actual.png -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/__tests__/fixtures/screenshot.diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRSOURCE/cypress-plugin-visual-regression-diff/765151bbddbfcd87864040aac5bfc6e7e68b639a/packages/cypress-plugin-visual-regression-diff/__tests__/fixtures/screenshot.diff.png -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/__tests__/fixtures/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRSOURCE/cypress-plugin-visual-regression-diff/765151bbddbfcd87864040aac5bfc6e7e68b639a/packages/cypress-plugin-visual-regression-diff/__tests__/fixtures/screenshot.png -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/__tests__/mocks/cypress.mock.ts: -------------------------------------------------------------------------------- 1 | import { vi } from 'vitest'; 2 | import { promises as fs } from 'fs'; 3 | 4 | export const Cypress = { 5 | Promise, 6 | }; 7 | vi.stubGlobal('Cypress', Cypress); 8 | 9 | export const cy = { 10 | readFile: vi.fn(fs.readFile), 11 | }; 12 | vi.stubGlobal('cy', cy); 13 | 14 | export const before = vi.fn(); 15 | vi.stubGlobal('before', before); 16 | 17 | export const after = vi.fn(); 18 | vi.stubGlobal('after', after); 19 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@frsource/cypress-plugin-visual-regression-diff", 3 | "description": "Perform visual regression test with a nice GUI as help. 💅 Only for Cypress!", 4 | "version": "3.3.10", 5 | "author": "Jakub Freisler ", 6 | "homepage": "https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff", 7 | "repository": "https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff.git", 8 | "sideEffects": [ 9 | "./dist/**" 10 | ], 11 | "main": "dist/support.js", 12 | "types": "dist/support.d.ts", 13 | "exports": { 14 | ".": { 15 | "types": "./dist/support.d.ts", 16 | "import": "./dist/support.mjs", 17 | "default": "./dist/support.js" 18 | }, 19 | "./support": { 20 | "types": "./dist/support.d.ts", 21 | "import": "./dist/support.mjs", 22 | "default": "./dist/support.js" 23 | }, 24 | "./plugins": { 25 | "types": "./dist/plugins.d.ts", 26 | "import": "./dist/plugins.mjs", 27 | "default": "./dist/plugins.js" 28 | }, 29 | "./constants": { 30 | "types": "./dist/constants.d.ts", 31 | "import": "./dist/constants.mjs", 32 | "default": "./dist/constants.js" 33 | }, 34 | "./*": "./*.js" 35 | }, 36 | "typesVersions": { 37 | "*": { 38 | "support": [ 39 | "dist/support.d.ts" 40 | ], 41 | "plugins": [ 42 | "dist/plugins.d.ts" 43 | ], 44 | "constants": [ 45 | "dist/constants.d.ts" 46 | ] 47 | } 48 | }, 49 | "license": "MIT", 50 | "scripts": { 51 | "build": "del-cli dist && microbundle src/{support,plugins,constants}.ts --target node --tsconfig tsconfig.build.json -f cjs,modern && cpy 'dist/src/*' dist && del-cli dist/src \"dist/*.{hook,utils}.d.ts\"", 52 | "format": "prettier --write .", 53 | "format:ci": "prettier --check .", 54 | "test:integration": "vitest run", 55 | "test:integration:watch": "vitest", 56 | "test:integration:coverage": "vitest run --coverage", 57 | "test:integration:ci": "CI=true vitest run --coverage" 58 | }, 59 | "engines": { 60 | "node": ">=10" 61 | }, 62 | "packageManager": "pnpm@8.6.12", 63 | "peerDependencies": { 64 | "cypress": ">=4.5.0" 65 | }, 66 | "devDependencies": { 67 | "@frsource/prettier-config": "^1.11.0", 68 | "@types/glob": "8.1.0", 69 | "@types/pixelmatch": "5.2.6", 70 | "@types/pngjs": "6.0.5", 71 | "@types/sharp": "0.32.0", 72 | "@types/tmp": "0.2.6", 73 | "@vitest/coverage-c8": "0.33.0", 74 | "cpy-cli": "4.2.0", 75 | "cypress": "12.14.0", 76 | "del-cli": "5.0.1", 77 | "microbundle": "0.15.1", 78 | "prettier": "3.3.3", 79 | "sanitize-filename": "1.6.3", 80 | "tmp-promise": "3.0.3", 81 | "typescript": "5.1.6", 82 | "vite-tsconfig-paths": "4.2.1", 83 | "vitest": "0.33.0" 84 | }, 85 | "keywords": [ 86 | "Cypress", 87 | "Cypress plugin", 88 | "visual regression testing", 89 | "visual diff", 90 | "image diff", 91 | "visual comparison", 92 | "image comparison", 93 | "Cypress visual regression", 94 | "regression testing", 95 | "visual snapshot", 96 | "image snapshot", 97 | "Cypress image snapshot" 98 | ], 99 | "dependencies": { 100 | "@frsource/base64": "1.0.79", 101 | "glob": "8.1.0", 102 | "meta-png": "1.0.6", 103 | "move-file": "2.1.0", 104 | "pixelmatch": "5.3.0", 105 | "pngjs": "7.0.0", 106 | "sharp": "0.32.6" 107 | }, 108 | "publishConfig": { 109 | "access": "public" 110 | }, 111 | "files": [ 112 | "dist", 113 | "package.json", 114 | "README.md", 115 | "LICENSE" 116 | ] 117 | } 118 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/prettier.config.mjs: -------------------------------------------------------------------------------- 1 | export { default } from '@frsource/prettier-config'; 2 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/src/__snapshots__/support.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1 2 | 3 | exports[`generateOverlayTemplate > generates proper template 1`] = ` 4 | "
5 |
6 | 13 |
14 |
15 |
16 |
21 |

New screenshot (hover mouse away too see the old one):

22 | 23 |
24 |

Old screenshot (hover over to see the new one):

25 | 26 |
27 |
28 |
29 |

Diff between new and old screenshot

30 | 31 |
32 |
33 |
34 |
" 35 | `; 36 | 37 | exports[`generateOverlayTemplate > generates proper template 2`] = ` 38 | "
39 |
40 | 47 |
48 |
49 |
50 |
55 |

New screenshot (hover mouse away too see the old one):

56 | 57 |
58 |

Old screenshot (hover over to see the new one):

59 | 60 |
61 |
62 |
63 |

Diff between new and old screenshot

64 | 65 |
66 |
67 |
68 |
" 69 | `; 70 | 71 | exports[`generateOverlayTemplate > generates proper template 3`] = ` 72 | "
73 |
74 | 81 |
82 |
83 |
84 |
89 |

New screenshot (hover mouse away too see the old one):

90 | 91 |
92 |

Old screenshot (hover over to see the new one):

93 | 94 |
95 |
96 |
97 |

Diff between new and old screenshot

98 | 99 |
100 |
101 |
102 |
" 103 | `; 104 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/src/afterScreenshot.hook.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect, describe } from 'vitest'; 2 | import path from 'path'; 3 | import { promises as fs, existsSync } from 'fs'; 4 | import { 5 | initAfterScreenshotHook, 6 | parseAbsolutePath, 7 | } from './afterScreenshot.hook'; 8 | import { dir, file, setGracefulCleanup } from 'tmp-promise'; 9 | import { IMAGE_SNAPSHOT_PREFIX, PATH_VARIABLES } from './constants'; 10 | 11 | setGracefulCleanup(); 12 | 13 | describe('initAfterScreenshotHook', () => { 14 | it('move file and remove old directories', async () => { 15 | const { path: screenshotsFolder } = await dir(); 16 | const imagesFolder = path.join(screenshotsFolder, IMAGE_SNAPSHOT_PREFIX); 17 | await fs.mkdir(imagesFolder); 18 | const { path: imgPath } = await file(); 19 | const projectRoot = path.dirname(imgPath); 20 | 21 | await initAfterScreenshotHook({ 22 | screenshotsFolder, 23 | projectRoot, 24 | } as Cypress.PluginConfigOptions)({ 25 | name: IMAGE_SNAPSHOT_PREFIX + path.sep + 'some_name', 26 | path: imgPath, 27 | } as Cypress.ScreenshotDetails); 28 | 29 | const expectedNewPath = path.join(projectRoot, 'some_name'); 30 | expect(existsSync(imagesFolder)).toBe(false); 31 | expect(existsSync(imgPath)).toBe(false); 32 | expect(existsSync(expectedNewPath)).toBe(true); 33 | 34 | await fs.unlink(expectedNewPath); 35 | }); 36 | }); 37 | 38 | describe('parseAbsolutePath', () => { 39 | const projectRoot = '/its/project/root'; 40 | 41 | it('resolves relative paths against project root', () => { 42 | expect( 43 | parseAbsolutePath({ screenshotPath: 'some/path.png', projectRoot }), 44 | ).toBe('/its/project/root/some/path.png'); 45 | }); 46 | 47 | it('builds proper win paths when found', () => { 48 | expect( 49 | parseAbsolutePath({ 50 | screenshotPath: `${PATH_VARIABLES.winSystemRootPath}/D/some/path.png`, 51 | projectRoot, 52 | }), 53 | ) 54 | // that's expected output accorind to https://stackoverflow.com/a/64135721/8805801 55 | .toBe('D:\\/some/path.png'); 56 | }); 57 | 58 | it('resolves relative paths against project root', () => { 59 | expect( 60 | parseAbsolutePath({ 61 | screenshotPath: `${PATH_VARIABLES.unixSystemRootPath}/some/path.png`, 62 | projectRoot, 63 | }), 64 | ).toBe('/some/path.png'); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/src/afterScreenshot.hook.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { promises as fs } from 'fs'; 3 | import moveFile from 'move-file'; 4 | import { IMAGE_SNAPSHOT_PREFIX, PATH_VARIABLES } from './constants'; 5 | 6 | type NotFalsy = T extends false | null | undefined ? never : T; 7 | 8 | const MIMIC_ROOT_WIN_REGEX = new RegExp( 9 | `^${PATH_VARIABLES.winSystemRootPath}\\${path.sep}([A-Z])\\${path.sep}`, 10 | ); 11 | const MIMIC_ROOT_UNIX_REGEX = new RegExp( 12 | `^${PATH_VARIABLES.unixSystemRootPath}\\${path.sep}`, 13 | ); 14 | 15 | const getConfigVariableOrThrow = ( 16 | config: Cypress.PluginConfigOptions, 17 | name: K, 18 | ) => { 19 | if (config[name]) { 20 | return config[name] as NotFalsy; 21 | } 22 | 23 | /* c8 ignore start */ 24 | throw `[@frsource/cypress-plugin-visual-regression-diff] CypressConfig.${name} cannot be missing or \`false\`!`; 25 | }; 26 | /* c8 ignore stop */ 27 | 28 | export const parseAbsolutePath = ({ 29 | screenshotPath, 30 | projectRoot, 31 | }: { 32 | screenshotPath: string; 33 | projectRoot: string; 34 | }) => { 35 | let newAbsolutePath: string; 36 | const matchedMimicingWinRoot = screenshotPath.match(MIMIC_ROOT_WIN_REGEX); 37 | const matchedMimicingUnixRoot = screenshotPath.match(MIMIC_ROOT_UNIX_REGEX); 38 | if (matchedMimicingWinRoot && matchedMimicingWinRoot[1]) { 39 | const driveLetter = matchedMimicingWinRoot[1]; 40 | newAbsolutePath = path.join( 41 | `${driveLetter}:\\`, 42 | screenshotPath.substring(matchedMimicingWinRoot[0].length), 43 | ); 44 | } else if (matchedMimicingUnixRoot) { 45 | newAbsolutePath = 46 | path.sep + screenshotPath.substring(matchedMimicingUnixRoot[0].length); 47 | } else { 48 | newAbsolutePath = path.join(projectRoot, screenshotPath); 49 | } 50 | return path.normalize(newAbsolutePath); 51 | }; 52 | 53 | export const initAfterScreenshotHook = 54 | (config: Cypress.PluginConfigOptions) => 55 | ( 56 | details: Cypress.ScreenshotDetails, 57 | ): 58 | | void 59 | | Cypress.AfterScreenshotReturnObject 60 | | Promise => { 61 | // it's not a screenshot generated by FRSOURCE Cypress Plugin Visual Regression Diff 62 | /* c8 ignore start */ 63 | if (details.name?.indexOf(IMAGE_SNAPSHOT_PREFIX) !== 0) return; 64 | /* c8 ignore stop */ 65 | const screenshotsFolder = getConfigVariableOrThrow( 66 | config, 67 | 'screenshotsFolder', 68 | ); 69 | const screenshotPath = details.name.substring( 70 | IMAGE_SNAPSHOT_PREFIX.length + path.sep.length, 71 | ); 72 | const newAbsolutePath = parseAbsolutePath({ 73 | screenshotPath, 74 | projectRoot: config.projectRoot, 75 | }); 76 | 77 | return (async () => { 78 | await moveFile(details.path, newAbsolutePath); 79 | await fs.rm(path.join(screenshotsFolder, IMAGE_SNAPSHOT_PREFIX), { 80 | recursive: true, 81 | force: true, 82 | }); 83 | 84 | return { path: newAbsolutePath }; 85 | })(); 86 | }; 87 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/src/commands.ts: -------------------------------------------------------------------------------- 1 | import { FILE_SUFFIX, LINK_PREFIX, TASK } from './constants'; 2 | import type pixelmatch from 'pixelmatch'; 3 | import * as Base64 from '@frsource/base64'; 4 | import type { CompareImagesTaskReturn } from './types'; 5 | 6 | declare global { 7 | // eslint-disable-next-line @typescript-eslint/no-namespace 8 | namespace Cypress { 9 | type MatchImageOptions = { 10 | screenshotConfig?: Partial; 11 | diffConfig?: Parameters[5]; 12 | createMissingImages?: boolean; 13 | updateImages?: boolean; 14 | /** 15 | * @deprecated since version 3.0, use imagesPath instead 16 | */ 17 | imagesDir?: string; 18 | imagesPath?: string; 19 | maxDiffThreshold?: number; 20 | forceDeviceScaleFactor?: boolean; 21 | title?: string; 22 | matchAgainstPath?: string; 23 | // IDEA: to be implemented if support for files NOT from filesystem needed 24 | // matchAgainst?: string | Buffer; 25 | }; 26 | 27 | type MatchImageReturn = { 28 | diffValue: number | undefined; 29 | imgNewPath: string; 30 | imgPath: string; 31 | imgDiffPath: string; 32 | imgNewBase64: string | undefined; 33 | imgBase64: string | undefined; 34 | imgDiffBase64: string | undefined; 35 | imgNew: InstanceType | undefined; 36 | img: InstanceType | undefined; 37 | imgDiff: InstanceType | undefined; 38 | }; 39 | 40 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 41 | interface Chainable { 42 | /** 43 | * Command to create and compare image snapshots. 44 | * @memberof Cypress.Chainable 45 | * @example cy.get('.my-element').matchImage(); 46 | */ 47 | matchImage( 48 | options?: Cypress.MatchImageOptions, 49 | ): Chainable; 50 | } 51 | } 52 | } 53 | 54 | const constructCypressError = (log: Cypress.Log, err: Error) => { 55 | // only way to throw & log the message properly in Cypress 56 | // https://github.com/cypress-io/cypress/blob/5f94cad3cb4126e0567290b957050c33e3a78e3c/packages/driver/src/cypress/error_utils.ts#L214-L216 57 | (err as unknown as { onFail: (e: Error) => void }).onFail = (err: Error) => 58 | log.error(err); 59 | return err; 60 | }; 61 | 62 | const capitalize = (text: string) => 63 | text.charAt(0).toUpperCase() + text.slice(1); 64 | 65 | const getPluginEnv = (key: K) => 66 | Cypress.env(`pluginVisualRegression${capitalize(key)}`) as 67 | | Cypress.MatchImageOptions[K] 68 | | undefined; 69 | 70 | const booleanOption = ( 71 | options: Cypress.MatchImageOptions, 72 | key: K, 73 | truthyValue: Return, 74 | falsyValue: Return, 75 | ) => 76 | options[key] === false || getPluginEnv(key) === false 77 | ? truthyValue 78 | : falsyValue; 79 | 80 | const optionWithDefaults = ( 81 | options: Cypress.MatchImageOptions, 82 | key: K, 83 | defaultValue: NonNullable, 84 | ) => options[key] ?? getPluginEnv(key) ?? defaultValue; 85 | 86 | const getImagesDir = (options: Cypress.MatchImageOptions) => { 87 | const imagesDir = options.imagesDir || getPluginEnv('imagesDir'); 88 | 89 | // TODO: remove in 4.0.0 90 | if (imagesDir) { 91 | // eslint-disable-next-line no-console 92 | console.warn( 93 | '@frsource/cypress-plugin-visual-regression-diff] `imagesDir` option is deprecated, use `imagesPath` instead (https://github.com/FRSOURCE/cypress-plugin-visual-regression-diff#configuration)', 94 | ); 95 | } 96 | 97 | return imagesDir; 98 | }; 99 | 100 | const getImagesPath = (options: Cypress.MatchImageOptions) => { 101 | const imagesDir = getImagesDir(options); 102 | 103 | return ( 104 | (imagesDir && `{spec_path}/${imagesDir}`) || 105 | optionWithDefaults(options, 'imagesPath', '{spec_path}/__image_snapshots__') 106 | ); 107 | }; 108 | 109 | export const getConfig = (options: Cypress.MatchImageOptions) => ({ 110 | scaleFactor: booleanOption( 111 | options, 112 | 'forceDeviceScaleFactor', 113 | 1, 114 | 1 / window.devicePixelRatio, 115 | ), 116 | createMissingImages: optionWithDefaults(options, 'createMissingImages', true), 117 | updateImages: optionWithDefaults(options, 'updateImages', false), 118 | imagesPath: getImagesPath(options), 119 | maxDiffThreshold: optionWithDefaults(options, 'maxDiffThreshold', 0.01), 120 | diffConfig: optionWithDefaults(options, 'diffConfig', {}), 121 | screenshotConfig: optionWithDefaults(options, 'screenshotConfig', {}), 122 | matchAgainstPath: options.matchAgainstPath || undefined, 123 | }); 124 | 125 | Cypress.Commands.add( 126 | 'matchImage', 127 | { prevSubject: 'optional' }, 128 | (subject, options = {}) => { 129 | const $el = subject as JQuery | undefined; 130 | let title: string; 131 | 132 | const { 133 | scaleFactor, 134 | createMissingImages, 135 | updateImages, 136 | imagesPath, 137 | maxDiffThreshold, 138 | diffConfig, 139 | screenshotConfig, 140 | matchAgainstPath, 141 | } = getConfig(options); 142 | 143 | const currentRetryNumber = ( 144 | cy as unknown as { 145 | state: (s: string) => { currentRetry: () => number }; 146 | } 147 | ) 148 | .state('test') 149 | .currentRetry(); 150 | 151 | return cy 152 | .then(() => 153 | cy.task<{ screenshotPath: string; title: string }>( 154 | TASK.getScreenshotPathInfo, 155 | { 156 | titleFromOptions: 157 | options.title || Cypress.currentTest.titlePath.join(' '), 158 | imagesPath, 159 | specPath: Cypress.spec.relative, 160 | currentRetryNumber, 161 | }, 162 | { log: false }, 163 | ), 164 | ) 165 | .then(({ screenshotPath, title: titleFromTask }) => { 166 | title = titleFromTask; 167 | let imgPath: string; 168 | return ($el ? cy.wrap($el) : cy) 169 | .screenshot(screenshotPath, { 170 | ...screenshotConfig, 171 | onAfterScreenshot(el, props) { 172 | imgPath = props.path; 173 | screenshotConfig.onAfterScreenshot?.(el, props); 174 | }, 175 | log: false, 176 | }) 177 | .then(() => imgPath); 178 | }) 179 | .then((imgPath) => 180 | cy 181 | .task( 182 | TASK.compareImages, 183 | { 184 | scaleFactor, 185 | imgNew: imgPath, 186 | imgOld: 187 | matchAgainstPath || imgPath.replace(FILE_SUFFIX.actual, ''), 188 | createMissingImages, 189 | updateImages, 190 | maxDiffThreshold, 191 | diffConfig, 192 | }, 193 | { log: false }, 194 | ) 195 | .then((res) => ({ 196 | res, 197 | imgPath, 198 | })), 199 | ) 200 | .then(({ res, imgPath }) => { 201 | const log = Cypress.log({ 202 | name: 'log', 203 | displayName: 'Match image', 204 | $el, 205 | }); 206 | 207 | if (!res) { 208 | log.set('message', 'Unexpected error!'); 209 | throw constructCypressError(log, new Error('Unexpected error!')); 210 | } 211 | 212 | log.set( 213 | 'message', 214 | `${res.message}${ 215 | res.imgDiffBase64 && res.imgNewBase64 && res.imgOldBase64 216 | ? `\n[See comparison](${LINK_PREFIX}${Base64.encode( 217 | encodeURIComponent( 218 | JSON.stringify({ 219 | title, 220 | imgPath, 221 | imgDiffBase64: res.imgDiffBase64, 222 | imgNewBase64: res.imgNewBase64, 223 | imgOldBase64: res.imgOldBase64, 224 | error: res.error, 225 | }), 226 | ), 227 | )})` 228 | : '' 229 | }`, 230 | ); 231 | 232 | if (res.error) { 233 | log.set('consoleProps', () => res); 234 | throw constructCypressError(log, new Error(res.message)); 235 | } 236 | 237 | return { 238 | diffValue: res.imgDiff, 239 | imgNewPath: imgPath, 240 | imgPath: imgPath.replace(FILE_SUFFIX.actual, ''), 241 | imgDiffPath: imgPath.replace(FILE_SUFFIX.actual, FILE_SUFFIX.diff), 242 | imgNewBase64: res.imgNewBase64, 243 | imgBase64: res.imgOldBase64, 244 | imgDiffBase64: res.imgDiffBase64, 245 | imgNew: 246 | typeof res.imgNewBase64 === 'string' 247 | ? Cypress.Buffer.from(res.imgNewBase64, 'base64') 248 | : undefined, 249 | img: 250 | typeof res.imgOldBase64 === 'string' 251 | ? Cypress.Buffer.from(res.imgOldBase64, 'base64') 252 | : undefined, 253 | imgDiff: 254 | typeof res.imgDiffBase64 === 'string' 255 | ? Cypress.Buffer.from(res.imgDiffBase64, 'base64') 256 | : undefined, 257 | }; 258 | }); 259 | }, 260 | ); 261 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/src/constants.ts: -------------------------------------------------------------------------------- 1 | const PLUGIN_NAME = 'cp-visual-regression-diff'; 2 | export const LINK_PREFIX = `#${PLUGIN_NAME}-`; 3 | export const OVERLAY_CLASS = `${PLUGIN_NAME}-overlay`; 4 | export const IMAGE_SNAPSHOT_PREFIX = `__${PLUGIN_NAME}_snapshots__`; 5 | 6 | export enum FILE_SUFFIX { 7 | diff = '.diff', 8 | actual = '.actual', 9 | } 10 | 11 | export const TASK = { 12 | getScreenshotPathInfo: `${PLUGIN_NAME}-getScreenshotPathInfo`, 13 | compareImages: `${PLUGIN_NAME}-compareImages`, 14 | approveImage: `${PLUGIN_NAME}-approveImage`, 15 | cleanupImages: `${PLUGIN_NAME}-cleanupImages`, 16 | doesFileExist: `${PLUGIN_NAME}-doesFileExist`, 17 | /* c8 ignore next */ 18 | }; 19 | 20 | export const PATH_VARIABLES = { 21 | specPath: '{spec_path}', 22 | unixSystemRootPath: '{unix_system_root_path}', 23 | winSystemRootPath: '{win_system_root_path}', 24 | } as const; 25 | 26 | export const WINDOWS_LIKE_DRIVE_REGEX = /^[A-Z]:$/; 27 | 28 | export const METADATA_KEY = 'FRSOURCE_CPVRD_V'; 29 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/src/image.utils.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | import { PNG, PNGWithMetadata } from 'pngjs'; 4 | import sharp from 'sharp'; 5 | import metaPngPkg from 'meta-png'; 6 | const { addMetadata, getMetadata } = metaPngPkg; 7 | import glob from 'glob'; 8 | import { wasScreenshotUsed } from './screenshotPath.utils'; 9 | import { METADATA_KEY } from './constants'; 10 | 11 | // version of images generated by this plugin 12 | // bump only when there is a breaking change to: 13 | // image comparison or image generation algorithms 14 | const DIFF_IMAGES_VERSION = '1'; 15 | 16 | export const addPNGMetadata = (png: Buffer) => 17 | addMetadata(png, METADATA_KEY, DIFF_IMAGES_VERSION /* c8 ignore next */); 18 | export const getPNGMetadata = (png: Buffer) => 19 | getMetadata(png, METADATA_KEY /* c8 ignore next */); 20 | export const isImageCurrentVersion = (png: Buffer) => 21 | getPNGMetadata(png) === DIFF_IMAGES_VERSION; 22 | export const isImageGeneratedByPlugin = (png: Buffer) => 23 | !!getPNGMetadata(png /* c8 ignore next */); 24 | 25 | export const writePNG = (name: string, png: PNG | Buffer) => 26 | fs.writeFileSync( 27 | name, 28 | addPNGMetadata(png instanceof PNG ? PNG.sync.write(png) : png), 29 | ); 30 | 31 | const inArea = (x: number, y: number, height: number, width: number) => 32 | y > height || x > width; 33 | 34 | export const fillSizeDifference = ( 35 | image: PNG, 36 | width: number, 37 | height: number, 38 | ) => { 39 | for (let y = 0; y < image.height; y++) { 40 | for (let x = 0; x < image.width; x++) { 41 | if (inArea(x, y, height, width)) { 42 | const idx = (image.width * y + x) << 2; 43 | image.data[idx] = 0; 44 | image.data[idx + 1] = 0; 45 | image.data[idx + 2] = 0; 46 | image.data[idx + 3] = 64; 47 | } 48 | } 49 | } 50 | return image; 51 | /* c8 ignore next */ 52 | }; 53 | 54 | export const createImageResizer = 55 | (width: number, height: number) => (source: PNG) => { 56 | const resized = new PNG({ width, height, fill: true }); 57 | PNG.bitblt(source, resized, 0, 0, source.width, source.height, 0, 0); 58 | return resized; 59 | /* c8 ignore next */ 60 | }; 61 | 62 | export const scaleImageAndWrite = async ({ 63 | scaleFactor, 64 | path, 65 | }: { 66 | scaleFactor: number; 67 | path: string; 68 | }) => { 69 | const imgBuffer = fs.readFileSync(path); 70 | if (scaleFactor === 1) return imgBuffer; 71 | 72 | const rawImgNew = PNG.sync.read(imgBuffer); 73 | const newImageWidth = Math.ceil(rawImgNew.width * scaleFactor); 74 | const newImageHeight = Math.ceil(rawImgNew.height * scaleFactor); 75 | await sharp(imgBuffer).resize(newImageWidth, newImageHeight).toFile(path); 76 | 77 | return fs.readFileSync(path); 78 | }; 79 | 80 | export const alignImagesToSameSize = ( 81 | firstImage: PNGWithMetadata, 82 | secondImage: PNGWithMetadata, 83 | ) => { 84 | const firstImageWidth = firstImage.width; 85 | const firstImageHeight = firstImage.height; 86 | const secondImageWidth = secondImage.width; 87 | const secondImageHeight = secondImage.height; 88 | 89 | const resizeToSameSize = createImageResizer( 90 | Math.max(firstImageWidth, secondImageWidth), 91 | Math.max(firstImageHeight, secondImageHeight), 92 | ); 93 | 94 | const resizedFirst = resizeToSameSize(firstImage); 95 | const resizedSecond = resizeToSameSize(secondImage); 96 | 97 | return [ 98 | fillSizeDifference(resizedFirst, firstImageWidth, firstImageHeight), 99 | fillSizeDifference(resizedSecond, secondImageWidth, secondImageHeight), 100 | ]; 101 | }; 102 | 103 | export const cleanupUnused = (rootPath: string) => { 104 | glob 105 | .sync('**/*.png', { 106 | cwd: rootPath, 107 | ignore: 'node_modules/**/*', 108 | }) 109 | .forEach((pngPath) => { 110 | const absolutePath = path.join(rootPath, pngPath); 111 | if ( 112 | !wasScreenshotUsed(pngPath) && 113 | isImageGeneratedByPlugin(fs.readFileSync(absolutePath)) 114 | ) { 115 | fs.unlinkSync(absolutePath); 116 | } 117 | }); 118 | }; 119 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/src/plugins.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect, describe, vi } from 'vitest'; 2 | import { initTaskHook } from './task.hook'; 3 | import { initAfterScreenshotHook } from './afterScreenshot.hook'; 4 | import { initPlugin } from './plugins'; 5 | 6 | vi.mock('./task.hook.ts', () => ({ 7 | initTaskHook: vi.fn().mockReturnValue('task'), 8 | })); 9 | vi.mock('./afterScreenshot.hook.ts', () => ({ 10 | initAfterScreenshotHook: vi.fn().mockReturnValue('after:screenshot'), 11 | })); 12 | 13 | describe('initPlugin', () => { 14 | it('inits hooks', () => { 15 | const onMock = vi.fn(); 16 | initPlugin(onMock, { 17 | env: { pluginVisualRegressionForceDeviceScaleFactor: false }, 18 | } as unknown as Cypress.PluginConfigOptions); 19 | 20 | expect(onMock).toBeCalledWith('task', 'task'); 21 | expect(onMock).toBeCalledWith('after:screenshot', 'after:screenshot'); 22 | expect(initTaskHook).toBeCalledTimes(1); 23 | expect(initAfterScreenshotHook).toBeCalledTimes(1); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/src/plugins.ts: -------------------------------------------------------------------------------- 1 | import { initTaskHook } from './task.hook'; 2 | import { initAfterScreenshotHook } from './afterScreenshot.hook'; 3 | 4 | /* c8 ignore start */ 5 | const initForceDeviceScaleFactor = (on: Cypress.PluginEvents) => { 6 | // based on https://github.com/cypress-io/cypress/issues/2102#issuecomment-521299946 7 | on('before:browser:launch', (browser, launchOptions) => { 8 | if (browser.name === 'chrome' || browser.name === 'chromium') { 9 | launchOptions.args.push('--force-device-scale-factor=1'); 10 | launchOptions.args.push('--high-dpi-support=1'); 11 | return launchOptions; 12 | } else if (browser.name === 'electron' && browser.isHeaded) { 13 | // eslint-disable-next-line no-console 14 | console.log( 15 | "There isn't currently a way of setting the device scale factor in Cypress when running headed electron so we disable the image regression commands.", 16 | ); 17 | } 18 | }); 19 | }; 20 | /* c8 ignore stop */ 21 | 22 | export const initPlugin = ( 23 | on: Cypress.PluginEvents, 24 | config: Cypress.PluginConfigOptions, 25 | ) => { 26 | /* c8 ignore start */ 27 | if (config.env['pluginVisualRegressionForceDeviceScaleFactor'] !== false) { 28 | initForceDeviceScaleFactor(on); 29 | } 30 | /* c8 ignore stop */ 31 | on('task', initTaskHook(config)); 32 | on('after:screenshot', initAfterScreenshotHook(config)); 33 | }; 34 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/src/screenshotPath.utils.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { 3 | FILE_SUFFIX, 4 | IMAGE_SNAPSHOT_PREFIX, 5 | PATH_VARIABLES, 6 | WINDOWS_LIKE_DRIVE_REGEX, 7 | } from './constants'; 8 | import sanitize from 'sanitize-filename'; 9 | 10 | const nameCacheCounter: Record = {}; 11 | const lastRetryNameCacheCounter: Record = {}; 12 | let lastRetryNumber = 0; 13 | 14 | const resetMap = (map: Record) => { 15 | // eslint-disable-next-line @typescript-eslint/no-dynamic-delete 16 | for (const key in map) delete map[key]; 17 | }; 18 | 19 | const parsePathPartVariables = ( 20 | specPath: string, 21 | pathPart: string, 22 | i: number, 23 | ) => { 24 | if (pathPart === PATH_VARIABLES.specPath) { 25 | return path.dirname(specPath); 26 | } else if (i === 0 && !pathPart) { 27 | // when unix-like absolute path 28 | return PATH_VARIABLES.unixSystemRootPath; 29 | } else if (i === 0 && WINDOWS_LIKE_DRIVE_REGEX.test(pathPart)) { 30 | // when win-like absolute path 31 | return path.join(PATH_VARIABLES.winSystemRootPath, pathPart[0]); 32 | } 33 | 34 | return pathPart; 35 | }; 36 | 37 | export const generateScreenshotPath = ({ 38 | titleFromOptions, 39 | imagesPath, 40 | specPath, 41 | currentRetryNumber, 42 | }: { 43 | titleFromOptions: string; 44 | imagesPath: string; 45 | specPath: string; 46 | currentRetryNumber: number; 47 | }) => { 48 | const screenshotPath = path.join( 49 | ...imagesPath.split('/').map(parsePathPartVariables.bind(void 0, specPath)), 50 | sanitize(titleFromOptions), 51 | ); 52 | 53 | if (typeof nameCacheCounter[screenshotPath] === 'undefined') { 54 | nameCacheCounter[screenshotPath] = -1; 55 | } 56 | 57 | // it's a retry of last test, so let's reset the counter to value before last retry 58 | if (currentRetryNumber > lastRetryNumber) { 59 | // +1 because we index screenshots starting at 0 60 | for (const screenshotPath in lastRetryNameCacheCounter) 61 | nameCacheCounter[screenshotPath] -= 62 | lastRetryNameCacheCounter[screenshotPath] + 1; 63 | } 64 | 65 | resetMap(lastRetryNameCacheCounter); 66 | 67 | lastRetryNumber = currentRetryNumber; 68 | lastRetryNameCacheCounter[screenshotPath] = ++nameCacheCounter[ 69 | screenshotPath 70 | ]; 71 | 72 | return path.join( 73 | IMAGE_SNAPSHOT_PREFIX, 74 | `${screenshotPath} #${nameCacheCounter[screenshotPath]}${FILE_SUFFIX.actual}.png`, 75 | ); 76 | }; 77 | 78 | const screenshotPathRegex = new RegExp( 79 | `^([\\s\\S]+?) #([0-9]+)(?:(?:\\${FILE_SUFFIX.diff})|(?:\\${FILE_SUFFIX.actual}))?\\.(?:png|PNG)$`, 80 | ); 81 | export const wasScreenshotUsed = (imagePath: string) => { 82 | const matched = imagePath.match(screenshotPathRegex); 83 | /* c8 ignore next */ if (!matched) return false; 84 | const [, screenshotPath, numString] = matched; 85 | const num = parseInt(numString); 86 | /* c8 ignore next */ if (!screenshotPath || isNaN(num)) return false; 87 | return ( 88 | screenshotPath in nameCacheCounter && 89 | num <= nameCacheCounter[screenshotPath] 90 | ); 91 | }; 92 | 93 | export const resetScreenshotNameCache = () => { 94 | lastRetryNumber = 0; 95 | resetMap(nameCacheCounter); 96 | resetMap(lastRetryNameCacheCounter); 97 | }; 98 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/src/support.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect, describe, vi } from 'vitest'; 2 | import { setGracefulCleanup } from 'tmp-promise'; 3 | import '@mocks/cypress.mock'; 4 | import { generateOverlayTemplate } from './support'; 5 | 6 | setGracefulCleanup(); 7 | 8 | vi.mock('./commands.ts', () => ({})); 9 | 10 | describe('generateOverlayTemplate', () => { 11 | it('generates proper template', () => { 12 | expect( 13 | generateOverlayTemplate({ 14 | title: 'some title', 15 | imgNewBase64: 'img-new-base64', 16 | imgOldBase64: 'img-old-base64', 17 | imgDiffBase64: 'img-diff-base64', 18 | wasImageNotUpdatedYet: true, 19 | error: true, 20 | }), 21 | ).toMatchSnapshot(); 22 | 23 | expect( 24 | generateOverlayTemplate({ 25 | title: 'some title', 26 | imgNewBase64: 'img-new-base64', 27 | imgOldBase64: 'img-old-base64', 28 | imgDiffBase64: 'img-diff-base64', 29 | wasImageNotUpdatedYet: false, 30 | error: true, 31 | }), 32 | ).toMatchSnapshot(); 33 | 34 | expect( 35 | generateOverlayTemplate({ 36 | title: 'some title', 37 | imgNewBase64: 'img-new-base64', 38 | imgOldBase64: 'img-old-base64', 39 | imgDiffBase64: 'img-diff-base64', 40 | wasImageNotUpdatedYet: false, 41 | error: false, 42 | }), 43 | ).toMatchSnapshot(); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/src/support.ts: -------------------------------------------------------------------------------- 1 | import * as Base64 from '@frsource/base64'; 2 | import './commands'; 3 | import { LINK_PREFIX, OVERLAY_CLASS, TASK } from './constants'; 4 | 5 | /* c8 ignore start */ 6 | function queueClear() { 7 | (cy as unknown as { queue: { reset: () => void } }).queue.reset?.(); 8 | (cy as unknown as { queue: { clear: () => void } }).queue.clear(); 9 | (cy as unknown as { state: (k: string, value: unknown) => void }).state( 10 | 'index', 11 | 0, 12 | ); 13 | } 14 | 15 | function queueRun() { 16 | // needed to run a task outside of the test processing flow 17 | (cy as unknown as { queue: { run: () => void } }).queue.run(); 18 | } 19 | /* c8 ignore stop */ 20 | export const generateOverlayTemplate = ({ 21 | title, 22 | imgNewBase64, 23 | imgOldBase64, 24 | imgDiffBase64, 25 | wasImageNotUpdatedYet, 26 | error, 27 | }: { 28 | title: string; 29 | imgNewBase64: string; 30 | imgOldBase64: string; 31 | imgDiffBase64: string; 32 | wasImageNotUpdatedYet: boolean; 33 | error: boolean; 34 | }) => `
35 |
36 | 49 |
50 |
51 |
52 |
57 |

New screenshot (hover mouse away too see the old one):

58 | 59 |
60 |

Old screenshot (hover over to see the new one):

61 | 62 |
63 |
64 |
65 |

Diff between new and old screenshot

66 | 67 |
68 |
69 |
70 |
`; 71 | 72 | /* c8 ignore start */ 73 | before(() => { 74 | if (!top) return null; 75 | Cypress.$(`.${OVERLAY_CLASS}`, top.document.body).remove(); 76 | }); 77 | 78 | after(() => { 79 | if (!top) return null; 80 | 81 | cy.task(TASK.cleanupImages, { log: false }); 82 | 83 | Cypress.$(top.document.body).on( 84 | 'click', 85 | `a[href^="${LINK_PREFIX}"]`, 86 | function (e) { 87 | e.preventDefault(); 88 | if (!top) return false; 89 | 90 | const { 91 | title, 92 | imgPath, 93 | imgDiffBase64, 94 | imgNewBase64, 95 | imgOldBase64, 96 | error, 97 | } = JSON.parse( 98 | decodeURIComponent( 99 | Base64.decode( 100 | e.currentTarget.getAttribute('href').substring(LINK_PREFIX.length), 101 | ), 102 | ), 103 | ); 104 | queueClear(); 105 | 106 | cy.task( 107 | TASK.doesFileExist, 108 | { path: imgPath }, 109 | { log: false }, 110 | ).then((wasImageNotUpdatedYet) => { 111 | if (!top) return false; 112 | 113 | Cypress.$( 114 | generateOverlayTemplate({ 115 | title, 116 | imgNewBase64, 117 | imgOldBase64, 118 | imgDiffBase64, 119 | error, 120 | wasImageNotUpdatedYet, 121 | }), 122 | ).appendTo(top.document.body); 123 | 124 | const wrapper = Cypress.$(`.${OVERLAY_CLASS}`, top.document.body); 125 | wrapper.on('click', 'button[data-type="close"]', function () { 126 | wrapper.remove(); 127 | }); 128 | 129 | wrapper.on('submit', 'form', function (e) { 130 | queueClear(); 131 | e.preventDefault(); 132 | 133 | cy.task(TASK.approveImage, { img: imgPath }).then(() => 134 | wrapper.remove(), 135 | ); 136 | 137 | queueRun(); 138 | }); 139 | }); 140 | 141 | queueRun(); 142 | 143 | return false; 144 | }, 145 | ); 146 | }); 147 | /* c8 ignore stop */ 148 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/src/task.hook.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect, describe, beforeEach, afterEach } from 'vitest'; 2 | import path from 'path'; 3 | import { promises as fs, existsSync } from 'fs'; 4 | import { dir, file, setGracefulCleanup, withFile } from 'tmp-promise'; 5 | import { 6 | approveImageTask, 7 | compareImagesTask, 8 | doesFileExistTask, 9 | getScreenshotPathInfoTask, 10 | CompareImagesCfg, 11 | cleanupImagesTask, 12 | } from './task.hook'; 13 | import { generateScreenshotPath } from './screenshotPath.utils'; 14 | import { IMAGE_SNAPSHOT_PREFIX } from './constants'; 15 | 16 | setGracefulCleanup(); 17 | 18 | const fixturesPath = path.resolve(__dirname, '..', '__tests__', 'fixtures'); 19 | const oldImgFixture = 'screenshot.png'; 20 | const newImgFixture = 'screenshot.actual.png'; 21 | const newFileContent = 'new file content'; 22 | 23 | const generateConfig = async (cfg: Partial) => ({ 24 | updateImages: false, 25 | createMissingImages: true, 26 | scaleFactor: 1, 27 | title: 'some title', 28 | imgNew: await writeTmpFixture((await file()).path, newImgFixture), 29 | imgOld: await writeTmpFixture((await file()).path, oldImgFixture), 30 | maxDiffThreshold: 0.5, 31 | diffConfig: {}, 32 | ...cfg, 33 | }); 34 | const writeTmpFixture = async (pathToWriteTo: string, fixtureName: string) => { 35 | await fs.mkdir(path.dirname(pathToWriteTo), { recursive: true }); 36 | await fs.writeFile( 37 | pathToWriteTo, 38 | await fs.readFile(path.join(fixturesPath, fixtureName)), 39 | ); 40 | return pathToWriteTo; 41 | }; 42 | 43 | describe('getScreenshotPathInfoTask', () => { 44 | const specPath = 'some/nested/spec-path/spec.ts'; 45 | 46 | it('returns sanitized path and title', () => { 47 | expect( 48 | getScreenshotPathInfoTask({ 49 | titleFromOptions: 'some-title-withśpęćiał人物', 50 | imagesPath: 'nested/images/dir', 51 | specPath, 52 | currentRetryNumber: 0, 53 | }), 54 | ).toEqual({ 55 | screenshotPath: 56 | '__cp-visual-regression-diff_snapshots__/nested/images/dir/some-title-withśpęćiał人物 #0.actual.png', 57 | title: 'some-title-withśpęćiał人物 #0.actual', 58 | }); 59 | }); 60 | 61 | it('supports {spec_path} variable', () => { 62 | expect( 63 | getScreenshotPathInfoTask({ 64 | titleFromOptions: 'some-title', 65 | imagesPath: '{spec_path}/images/dir', 66 | specPath, 67 | currentRetryNumber: 0, 68 | }), 69 | ).toEqual({ 70 | screenshotPath: 71 | '__cp-visual-regression-diff_snapshots__/some/nested/spec-path/images/dir/some-title #0.actual.png', 72 | title: 'some-title #0.actual', 73 | }); 74 | }); 75 | 76 | it('supports OS-specific absolute paths', () => { 77 | expect( 78 | getScreenshotPathInfoTask({ 79 | titleFromOptions: 'some-title', 80 | imagesPath: '/images/dir', 81 | specPath, 82 | currentRetryNumber: 0, 83 | }), 84 | ).toEqual({ 85 | screenshotPath: 86 | '__cp-visual-regression-diff_snapshots__/{unix_system_root_path}/images/dir/some-title #0.actual.png', 87 | title: 'some-title #0.actual', 88 | }); 89 | 90 | expect( 91 | getScreenshotPathInfoTask({ 92 | titleFromOptions: 'some-title', 93 | imagesPath: 'C:/images/dir', 94 | specPath, 95 | currentRetryNumber: 0, 96 | }), 97 | ).toEqual({ 98 | screenshotPath: 99 | '__cp-visual-regression-diff_snapshots__/{win_system_root_path}/C/images/dir/some-title #0.actual.png', 100 | title: 'some-title #0.actual', 101 | }); 102 | }); 103 | }); 104 | 105 | describe('cleanupImagesTask', () => { 106 | describe('when env is set', () => { 107 | const generateUsedScreenshotPath = async (projectRoot: string) => { 108 | const screenshotPathWithPrefix = generateScreenshotPath({ 109 | titleFromOptions: 'some-file', 110 | imagesPath: 'images', 111 | specPath: 'some/spec/path', 112 | currentRetryNumber: 0, 113 | }); 114 | return path.join( 115 | projectRoot, 116 | screenshotPathWithPrefix.substring( 117 | IMAGE_SNAPSHOT_PREFIX.length + path.sep.length, 118 | ), 119 | ); 120 | }; 121 | 122 | it('does not remove used screenshot', async () => { 123 | const { path: projectRoot } = await dir(); 124 | const screenshotPath = await writeTmpFixture( 125 | await generateUsedScreenshotPath(projectRoot), 126 | oldImgFixture, 127 | ); 128 | 129 | cleanupImagesTask({ 130 | projectRoot, 131 | env: { pluginVisualRegressionCleanupUnusedImages: true }, 132 | } as unknown as Cypress.PluginConfigOptions); 133 | 134 | expect(existsSync(screenshotPath)).toBe(true); 135 | }); 136 | 137 | it('removes unused screenshot', async () => { 138 | const { path: projectRoot } = await dir(); 139 | const screenshotPath = await writeTmpFixture( 140 | path.join(projectRoot, 'some-file-2 #0.png'), 141 | oldImgFixture, 142 | ); 143 | 144 | cleanupImagesTask({ 145 | projectRoot, 146 | env: { pluginVisualRegressionCleanupUnusedImages: true }, 147 | } as unknown as Cypress.PluginConfigOptions); 148 | 149 | expect(existsSync(screenshotPath)).toBe(false); 150 | }); 151 | }); 152 | }); 153 | 154 | describe('approveImageTask', () => { 155 | let newImgPath: string; 156 | let oldImgPath: string; 157 | let diffImgPath: string; 158 | 159 | beforeEach(() => 160 | withFile(async ({ path }) => { 161 | oldImgPath = path; 162 | newImgPath = `${oldImgPath}.actual`; 163 | await fs.writeFile(newImgPath, newFileContent); 164 | diffImgPath = `${oldImgPath}.diff`; 165 | await fs.writeFile(diffImgPath, ''); 166 | }), 167 | ); 168 | afterEach(async () => { 169 | if (existsSync(diffImgPath)) await fs.unlink(diffImgPath); 170 | if (existsSync(newImgPath)) await fs.unlink(newImgPath); 171 | }); 172 | 173 | it('removes diff image and replaces old with new', async () => { 174 | approveImageTask({ img: newImgPath }); 175 | 176 | expect((await fs.readFile(oldImgPath)).toString()).toBe(newFileContent); 177 | expect(existsSync(newImgPath)).toBe(false); 178 | expect(existsSync(diffImgPath)).toBe(false); 179 | }); 180 | }); 181 | 182 | describe('compareImagesTask', () => { 183 | describe('when images should be updated', () => { 184 | describe('when old screenshot exists', () => { 185 | it('resolves with a success message', async () => 186 | expect( 187 | compareImagesTask(await generateConfig({ updateImages: true })), 188 | ).resolves.toEqual({ 189 | error: false, 190 | message: 191 | 'Image diff factor (0%) is within boundaries of maximum threshold option 0.5.', 192 | imgDiff: 0, 193 | imgDiffBase64: '', 194 | imgNewBase64: '', 195 | imgOldBase64: '', 196 | maxDiffThreshold: 0.5, 197 | })); 198 | }); 199 | }); 200 | 201 | describe("when images shouldn't be updated", () => { 202 | describe("when old screenshot doesn't exist", () => { 203 | it('resolves with a success message', async () => { 204 | const cfg = await generateConfig({ updateImages: false }); 205 | await fs.unlink(cfg.imgOld); 206 | 207 | await expect(compareImagesTask(cfg)).resolves.toEqual({ 208 | error: false, 209 | message: 210 | 'Image diff factor (0%) is within boundaries of maximum threshold option 0.5.', 211 | imgDiff: 0, 212 | imgDiffBase64: '', 213 | imgNewBase64: '', 214 | imgOldBase64: '', 215 | maxDiffThreshold: 0.5, 216 | }); 217 | }); 218 | 219 | describe('when createMissingImages=false', () => { 220 | it('rejects with error message', async () => { 221 | const cfg = await generateConfig({ 222 | updateImages: false, 223 | createMissingImages: false, 224 | }); 225 | await fs.unlink(cfg.imgOld); 226 | 227 | await expect(compareImagesTask(cfg)).resolves.toEqual({ 228 | error: true, 229 | message: `Baseline image is missing at path: "${cfg.imgOld}". Provide a baseline image or enable "createMissingImages" option in plugin configuration.`, 230 | imgDiff: 0, 231 | imgDiffBase64: '', 232 | imgNewBase64: '', 233 | imgOldBase64: '', 234 | maxDiffThreshold: 0.5, 235 | }); 236 | }); 237 | }); 238 | }); 239 | 240 | describe('when old screenshot exists', () => { 241 | describe('when new image has different resolution', () => { 242 | it('resolves with an error message', async () => { 243 | const cfg = await generateConfig({ updateImages: false }); 244 | 245 | await expect(compareImagesTask(cfg)).resolves.toMatchSnapshot(); 246 | }); 247 | }); 248 | 249 | describe('when new image is exactly the same as old one', () => { 250 | it('resolves with a success message', async () => { 251 | const cfg = await generateConfig({ updateImages: false }); 252 | await writeTmpFixture(cfg.imgNew, oldImgFixture); 253 | 254 | await expect(compareImagesTask(cfg)).resolves.toMatchSnapshot(); 255 | }); 256 | }); 257 | }); 258 | }); 259 | }); 260 | 261 | describe('doesFileExistsTask', () => { 262 | it('checks whether file exists', () => { 263 | expect(doesFileExistTask({ path: 'some/random/path' })).toBe(false); 264 | expect( 265 | doesFileExistTask({ path: path.join(fixturesPath, oldImgFixture) }), 266 | ).toBe(true); 267 | }); 268 | }); 269 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/src/task.hook.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { PNG } from 'pngjs'; 3 | import pixelmatch, { PixelmatchOptions } from 'pixelmatch'; 4 | import moveFile from 'move-file'; 5 | import path from 'path'; 6 | import { FILE_SUFFIX, TASK } from './constants'; 7 | import { 8 | cleanupUnused, 9 | alignImagesToSameSize, 10 | scaleImageAndWrite, 11 | isImageCurrentVersion, 12 | writePNG, 13 | } from './image.utils'; 14 | import { 15 | generateScreenshotPath, 16 | resetScreenshotNameCache, 17 | } from './screenshotPath.utils'; 18 | import type { CompareImagesTaskReturn } from './types'; 19 | 20 | export type CompareImagesCfg = { 21 | scaleFactor: number; 22 | title: string; 23 | imgNew: string; 24 | imgOld: string; 25 | createMissingImages: boolean; 26 | updateImages: boolean; 27 | maxDiffThreshold: number; 28 | diffConfig: PixelmatchOptions; 29 | }; 30 | 31 | const round = (n: number) => Math.ceil(n * 1000) / 1000; 32 | 33 | const unlinkSyncSafe = (path: string) => 34 | fs.existsSync(path) && fs.unlinkSync(path); 35 | const moveSyncSafe = (pathFrom: string, pathTo: string) => 36 | fs.existsSync(pathFrom) && moveFile.sync(pathFrom, pathTo); 37 | 38 | export const getScreenshotPathInfoTask = (cfg: { 39 | titleFromOptions: string; 40 | imagesPath: string; 41 | specPath: string; 42 | currentRetryNumber: number; 43 | }) => { 44 | const screenshotPath = generateScreenshotPath(cfg); 45 | 46 | return { screenshotPath, title: path.basename(screenshotPath, '.png') }; 47 | }; 48 | 49 | export const cleanupImagesTask = (config: Cypress.PluginConfigOptions) => { 50 | if (config.env['pluginVisualRegressionCleanupUnusedImages']) { 51 | cleanupUnused(config.projectRoot); 52 | } 53 | 54 | resetScreenshotNameCache(); 55 | 56 | return null; 57 | }; 58 | 59 | export const approveImageTask = ({ img }: { img: string }) => { 60 | const oldImg = img.replace(FILE_SUFFIX.actual, ''); 61 | unlinkSyncSafe(oldImg); 62 | 63 | const diffImg = img.replace(FILE_SUFFIX.actual, FILE_SUFFIX.diff); 64 | unlinkSyncSafe(diffImg); 65 | 66 | moveSyncSafe(img, oldImg); 67 | 68 | return null; 69 | }; 70 | 71 | export const compareImagesTask = async ( 72 | cfg: CompareImagesCfg, 73 | ): Promise => { 74 | const messages = [] as string[]; 75 | const rawImgNewBuffer = await scaleImageAndWrite({ 76 | scaleFactor: cfg.scaleFactor, 77 | path: cfg.imgNew, 78 | }); 79 | let imgDiff: number | undefined; 80 | let imgNewBase64: string, imgOldBase64: string, imgDiffBase64: string; 81 | let error = false; 82 | 83 | if (fs.existsSync(cfg.imgOld) && !cfg.updateImages) { 84 | const rawImgNew = PNG.sync.read(rawImgNewBuffer); 85 | const rawImgOldBuffer = fs.readFileSync(cfg.imgOld); 86 | const rawImgOld = PNG.sync.read(rawImgOldBuffer); 87 | const isImgSizeDifferent = 88 | rawImgNew.height !== rawImgOld.height || 89 | rawImgNew.width !== rawImgOld.width; 90 | 91 | const [imgNew, imgOld] = isImgSizeDifferent 92 | ? alignImagesToSameSize(rawImgNew, rawImgOld) 93 | : [rawImgNew, rawImgOld]; 94 | 95 | const { width, height } = imgNew; 96 | const diff = new PNG({ width, height }); 97 | const diffConfig = Object.assign({ includeAA: true }, cfg.diffConfig); 98 | 99 | const diffPixels = pixelmatch( 100 | imgNew.data, 101 | imgOld.data, 102 | diff.data, 103 | width, 104 | height, 105 | diffConfig, 106 | ); 107 | imgDiff = diffPixels / (width * height); 108 | 109 | if (isImgSizeDifferent) { 110 | messages.push( 111 | `Warning: Images size mismatch - new screenshot is ${rawImgNew.width}px by ${rawImgNew.height}px while old one is ${rawImgOld.width}px by ${rawImgOld.height} (width x height).`, 112 | ); 113 | } 114 | 115 | if (imgDiff > cfg.maxDiffThreshold) { 116 | messages.unshift( 117 | `Image diff factor (${round( 118 | imgDiff, 119 | )}%) is bigger than maximum threshold option ${cfg.maxDiffThreshold}.`, 120 | ); 121 | error = true; 122 | } 123 | 124 | const diffBuffer = PNG.sync.write(diff); 125 | imgNewBase64 = PNG.sync.write(imgNew).toString('base64'); 126 | imgDiffBase64 = diffBuffer.toString('base64'); 127 | imgOldBase64 = PNG.sync.write(imgOld).toString('base64'); 128 | 129 | if (error) { 130 | writePNG( 131 | cfg.imgNew.replace(FILE_SUFFIX.actual, FILE_SUFFIX.diff), 132 | diffBuffer, 133 | ); 134 | } else { 135 | if (rawImgOld && !isImageCurrentVersion(rawImgOldBuffer)) { 136 | writePNG(cfg.imgNew, rawImgNewBuffer); 137 | moveFile.sync(cfg.imgNew, cfg.imgOld); 138 | } else { 139 | // don't overwrite file if it's the same (imgDiff < cfg.maxDiffThreshold && !isImgSizeDifferent) 140 | fs.unlinkSync(cfg.imgNew); 141 | } 142 | } 143 | } else { 144 | // there is no "old screenshot" or screenshots should be immediately updated 145 | imgDiff = 0; 146 | imgNewBase64 = ''; 147 | imgDiffBase64 = ''; 148 | imgOldBase64 = ''; 149 | if (cfg.createMissingImages) { 150 | writePNG(cfg.imgNew, rawImgNewBuffer); 151 | moveFile.sync(cfg.imgNew, cfg.imgOld); 152 | } else { 153 | error = true; 154 | messages.unshift( 155 | `Baseline image is missing at path: "${cfg.imgOld}". Provide a baseline image or enable "createMissingImages" option in plugin configuration.`, 156 | ); 157 | } 158 | } 159 | 160 | if (typeof imgDiff !== 'undefined') { 161 | if (!error) { 162 | messages.unshift( 163 | `Image diff factor (${round( 164 | imgDiff, 165 | )}%) is within boundaries of maximum threshold option ${ 166 | cfg.maxDiffThreshold 167 | }.`, 168 | ); 169 | } 170 | 171 | return { 172 | error, 173 | message: messages.join('\n'), 174 | imgDiff, 175 | imgNewBase64, 176 | imgDiffBase64, 177 | imgOldBase64, 178 | maxDiffThreshold: cfg.maxDiffThreshold, 179 | }; 180 | } 181 | 182 | /* c8 ignore next */ 183 | return null; 184 | }; 185 | 186 | export const doesFileExistTask = ({ path }: { path: string }) => 187 | fs.existsSync(path); 188 | 189 | /* c8 ignore start */ 190 | export const initTaskHook = (config: Cypress.PluginConfigOptions) => ({ 191 | [TASK.getScreenshotPathInfo]: getScreenshotPathInfoTask, 192 | [TASK.cleanupImages]: cleanupImagesTask.bind(undefined, config), 193 | [TASK.doesFileExist]: doesFileExistTask, 194 | [TASK.approveImage]: approveImageTask, 195 | [TASK.compareImages]: compareImagesTask, 196 | }); 197 | /* c8 ignore stop */ 198 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/src/types.ts: -------------------------------------------------------------------------------- 1 | export type CompareImagesTaskReturn = null | { 2 | error?: boolean; 3 | message?: string; 4 | imgDiff?: number; 5 | imgNewBase64?: string; 6 | imgDiffBase64?: string; 7 | imgOldBase64?: string; 8 | maxDiffThreshold?: number; 9 | }; 10 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src/*.ts"], 4 | "exclude": ["src/*.test.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["es6", "dom"], 5 | "types": ["cypress"], 6 | "strict": true, 7 | "moduleResolution": "node", 8 | "esModuleInterop": true, 9 | "baseUrl": ".", 10 | "paths": { 11 | "@fixtures/*": ["__tests__/partials/*"], 12 | "@mocks/*": ["__tests__/mocks/*"] 13 | }, 14 | "resolveJsonModule": true 15 | }, 16 | "include": ["src/*.ts", "vitest.config.ts"] 17 | } 18 | -------------------------------------------------------------------------------- /packages/cypress-plugin-visual-regression-diff/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, configDefaults } from 'vitest/config'; 2 | import tsconfigPaths from 'vite-tsconfig-paths'; 3 | import path from 'path'; 4 | 5 | const exludeFiles = [...configDefaults.exclude, 'example', '.yarn', '*.js']; 6 | const testsGlob = 'src/**.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'; 7 | 8 | const isCI = !!process.env.CI; 9 | 10 | export default defineConfig({ 11 | plugins: [tsconfigPaths()], 12 | test: { 13 | exclude: exludeFiles, 14 | include: [testsGlob], 15 | coverage: { 16 | provider: 'c8', 17 | reporter: isCI 18 | ? ['text', ['lcovonly', { projectRoot: '../..' }]] 19 | : ['text', 'lcov'], 20 | lines: 90, 21 | functions: 90, 22 | branches: 90, 23 | statements: 90, 24 | }, 25 | alias: { 26 | '@fixtures/*': path.resolve(__dirname, '__tests__', 'partials'), 27 | '@mocks/*': path.resolve(__dirname, '__tests__', 'mocks'), 28 | }, 29 | }, 30 | }); 31 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | - 'examples/*' 4 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", 3 | "sequential-calls": true, 4 | "plugins": ["node-workspace"], 5 | "packages": { 6 | "packages/cypress-plugin-visual-regression-diff": { 7 | "path": "packages/cypress-plugin-visual-regression-diff", 8 | "component": "@frsource/cypress-plugin-visual-regression-diff" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["github>FRSOURCE/renovate-config"] 4 | } 5 | --------------------------------------------------------------------------------