├── .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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
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 | 
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 |
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 |
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 |
12 | We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
14 | properly without JavaScript enabled. Please enable it to
15 | continue.
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/examples/webpack/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
2 |
3 |
{{ msg }}
4 |
5 | For a guide and recipes on how to configure / customize this project,
6 | check out the
7 | vue-cli documentation .
10 |
11 |
Installed CLI Plugins
12 |
22 |
Essential Links
23 |
46 |
Ecosystem
47 |
78 |
79 |
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 |
7 | some title - screenshot diff
8 |
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 |
41 | some title - screenshot diff
42 |
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 |
75 | some title - screenshot diff
76 |
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 |
37 | ${title} - screenshot diff
38 |
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 |
--------------------------------------------------------------------------------