vitest-plugin-vis 1.3.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/client/expect/to_match_image_snapshot.types.d.ts +4 -4
- package/esm/client/expect/to_match_image_snapshot.types.d.ts.map +1 -1
- package/esm/client/image_snapshot_matcher.d.ts +1 -1
- package/esm/client/image_snapshot_matcher.d.ts.map +1 -1
- package/esm/client/image_snapshot_matcher.js +1 -0
- package/esm/client/image_snapshot_matcher.logic.d.ts +1 -1
- package/esm/client/image_snapshot_matcher.logic.d.ts.map +1 -1
- package/esm/client/image_snapshot_matcher.logic.js +3 -2
- package/esm/client/should_take_snapshot.d.ts +1 -1
- package/esm/client/should_take_snapshot.d.ts.map +1 -1
- package/esm/client/snapshot_options.d.ts +4 -3
- package/esm/client/snapshot_options.d.ts.map +1 -1
- package/esm/client/snapshot_options.internal.d.ts +1 -1
- package/esm/client/snapshot_options.internal.d.ts.map +1 -1
- package/esm/config/types.d.ts +12 -3
- package/esm/config/types.d.ts.map +1 -1
- package/esm/config/vis.d.ts +2 -1
- package/esm/config/vis.d.ts.map +1 -1
- package/esm/server/commands/match_image_snapshot.d.ts +2 -2
- package/esm/server/commands/match_image_snapshot.d.ts.map +1 -1
- package/esm/server/commands/match_image_snapshot.js +1 -2
- package/esm/server/commands/prepare_image_snapshot_comparison.d.ts +2 -1
- package/esm/server/commands/prepare_image_snapshot_comparison.d.ts.map +1 -1
- package/esm/server/commands/prepare_image_snapshot_comparison.js +1 -2
- package/esm/server/snapshot_path.d.ts +2 -2
- package/esm/server/snapshot_path.d.ts.map +1 -1
- package/esm/server/vis_context.ctx.d.ts.map +1 -1
- package/esm/server/vis_context.ctx.js +0 -1
- package/esm/server/vis_context.d.ts +16 -2
- package/esm/server/vis_context.d.ts.map +1 -1
- package/esm/server/vis_context.logic.d.ts +18 -4
- package/esm/server/vis_context.logic.d.ts.map +1 -1
- package/esm/server/vis_context.logic.js +3 -1
- package/esm/setup/create_vis.js +1 -1
- package/esm/shared/compare_image.d.ts +2 -2
- package/esm/shared/compare_image.d.ts.map +1 -1
- package/esm/shared/compare_image.js +36 -4
- package/esm/shared/types.d.ts +20 -7
- package/esm/shared/types.d.ts.map +1 -1
- package/package.json +3 -1
- package/readme.md +12 -7
- package/src/client/expect/to_match_image_snapshot.types.ts +11 -11
- package/src/client/image_snapshot_matcher.logic.ts +4 -3
- package/src/client/image_snapshot_matcher.ts +5 -2
- package/src/client/should_take_snapshot.ts +1 -1
- package/src/client/snapshot_options.internal.ts +1 -1
- package/src/client/snapshot_options.ts +14 -10
- package/src/config/types.ts +51 -34
- package/src/config/vis.ts +2 -1
- package/src/server/commands/match_image_snapshot.ts +9 -11
- package/src/server/commands/prepare_image_snapshot_comparison.ts +10 -3
- package/src/server/snapshot_path.ts +2 -2
- package/src/server/vis_context.ctx.ts +0 -1
- package/src/server/vis_context.logic.ts +11 -6
- package/src/setup/create_vis.ts +1 -1
- package/src/shared/compare_image.ts +55 -6
- package/src/shared/types.ts +25 -6
package/readme.md
CHANGED
|
@@ -41,6 +41,7 @@ export default defineConfig({
|
|
|
41
41
|
|
|
42
42
|
This default configuration will:
|
|
43
43
|
|
|
44
|
+
- Use `pixelmatch` as the image comparison method.
|
|
44
45
|
- Use the `auto` preset, taking image snapshot at the end on each rendering test.
|
|
45
46
|
- Set config to compare image snapshot with a failure threshold of `0 pixels`.
|
|
46
47
|
- Local (non-CI) image snapshots are saved in the `<root>/__vis__/local` directory.
|
|
@@ -60,12 +61,15 @@ export default defineConfig({
|
|
|
60
61
|
vis({
|
|
61
62
|
preset: 'auto',
|
|
62
63
|
snapshotRootDir: '__vis__',
|
|
64
|
+
platform: '...', // {process.platform} or `local`
|
|
63
65
|
customizeSnapshotSubpath: (subpath) => trimCommonFolder(subpath),
|
|
64
66
|
customizeSnapshotId: (id, index) => `${id}-${index}`,
|
|
65
|
-
|
|
67
|
+
comparisonMethod: 'pixel',
|
|
68
|
+
// pixelmatch or ssim.js options, depending on `comparisonMethod`.
|
|
69
|
+
diffOptions: undefined,
|
|
66
70
|
failureThresholdType: 'pixel',
|
|
67
71
|
failureThreshold: 0,
|
|
68
|
-
timeout:
|
|
72
|
+
timeout: 30000
|
|
69
73
|
})
|
|
70
74
|
],
|
|
71
75
|
test:{
|
|
@@ -181,11 +185,13 @@ setAutoSnapshotOptions(true /* or false */)
|
|
|
181
185
|
// detailed options
|
|
182
186
|
setAutoSnapshotOptions({
|
|
183
187
|
enable: true,
|
|
188
|
+
comparisonMethod: 'pixel',
|
|
184
189
|
customizeSnapshotId: (id, index) => `${id}-custom-${index}`,
|
|
185
|
-
|
|
190
|
+
// pixelmatch or ssim.js options, depending on `comparisonMethod`.
|
|
191
|
+
diffOptions: { threshold: 0.01 },
|
|
186
192
|
failureThreshold: 0.01,
|
|
187
193
|
failureThresholdType: 'percent',
|
|
188
|
-
timeout:
|
|
194
|
+
timeout: 60000
|
|
189
195
|
})
|
|
190
196
|
```
|
|
191
197
|
|
|
@@ -238,7 +244,7 @@ it('manual snapshot with options', async () => {
|
|
|
238
244
|
diffOptions: {
|
|
239
245
|
threshold: 0.1
|
|
240
246
|
},
|
|
241
|
-
timeout:
|
|
247
|
+
timeout: 60000
|
|
242
248
|
})
|
|
243
249
|
})
|
|
244
250
|
```
|
|
@@ -282,6 +288,7 @@ Please follow its guide to set up your environment.
|
|
|
282
288
|
|
|
283
289
|
Bonus note, if you want to install [Firefox] on WSL,
|
|
284
290
|
you can follow these steps: [Install Firefox on Ubuntu 22.04](https://askubuntu.com/a/1444967).
|
|
291
|
+
Also, you may need to `sudo apt-get install xdg-utils` to fix [`xdg-settings: not found`](https://serverfault.com/questions/1091926/running-chrome-on-ubuntu-server-how-to-solve-xdg-settings-not-found-using).
|
|
285
292
|
|
|
286
293
|
## Running on CI
|
|
287
294
|
|
|
@@ -296,8 +303,6 @@ It is recommended to run your tests serially to avoid flakiness.
|
|
|
296
303
|
|
|
297
304
|
Some features in [`jest-image-snapshot`][jest-image-snapshot] are not implemented in [`vitest-plugin-vis`][vitest-plugin-vis] yet.
|
|
298
305
|
This is because through our own usage, we do not found a good use case for them.
|
|
299
|
-
For example, the `ssim` comparison method is not implemented in [`vitest-plugin-vis`][vitest-plugin-vis],
|
|
300
|
-
because we found that figuring out what is the delta using `ssim` is quite difficult.
|
|
301
306
|
|
|
302
307
|
If you have a good use case for these features, please open an issue or PR.
|
|
303
308
|
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
ComparisonMethod,
|
|
2
3
|
ImageSnapshotCompareOptions,
|
|
3
4
|
ImageSnapshotIdOptions,
|
|
4
5
|
ImageSnapshotTimeoutOptions,
|
|
5
6
|
} from '../../shared/types.ts'
|
|
6
7
|
|
|
7
8
|
export interface ImageSnapshotMatcher {
|
|
8
|
-
toMatchImageSnapshot(options?: ToMatchImageSnapshotOptions | undefined): Promise<void>
|
|
9
|
+
toMatchImageSnapshot<M extends ComparisonMethod>(options?: ToMatchImageSnapshotOptions<M> | undefined): Promise<void>
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
12
|
+
export type ToMatchImageSnapshotOptions<M extends ComparisonMethod = 'pixel'> = ImageSnapshotTimeoutOptions &
|
|
13
|
+
ImageSnapshotIdOptions &
|
|
14
|
+
ImageSnapshotCompareOptions<M> & {
|
|
15
|
+
/**
|
|
16
|
+
* Expect the matcher to fail.
|
|
17
|
+
* If it passes, it will throw an error with details.
|
|
18
|
+
*/
|
|
19
|
+
expectToFail?: boolean | undefined
|
|
20
|
+
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { ToMatchImageSnapshotOptions } from './expect/to_match_image_snapshot.types.ts'
|
|
2
2
|
|
|
3
|
-
export function prettifyOptions(options: ToMatchImageSnapshotOptions | undefined) {
|
|
3
|
+
export function prettifyOptions(options: ToMatchImageSnapshotOptions<any> | undefined) {
|
|
4
4
|
if (!options) return 'none'
|
|
5
5
|
|
|
6
6
|
return [
|
|
7
7
|
`failureThreshold: ${options.failureThreshold ?? 0} ${options.failureThresholdType ?? 'pixels'}`,
|
|
8
|
-
options.diffOptions ? `diffOptions: ${JSON.stringify(options.diffOptions)}` : '',
|
|
9
8
|
options.timeout ? `timeout: ${options.timeout} ms` : '',
|
|
9
|
+
`comparisonMethod: ${options.comparisonMethod ?? 'pixel'}`,
|
|
10
|
+
options.diffOptions ? `diffOptions: ${JSON.stringify(options.diffOptions)}` : '',
|
|
10
11
|
]
|
|
11
12
|
.filter(Boolean)
|
|
12
|
-
.join('
|
|
13
|
+
.join('\n ')
|
|
13
14
|
}
|
|
@@ -6,6 +6,7 @@ import type { PrepareImageSnapshotComparisonCommand } from '../server/commands/p
|
|
|
6
6
|
import type { WriteImageSnapshotCommand } from '../server/commands/write_image_snapshot.ts'
|
|
7
7
|
import { isBase64String } from '../shared/base64.ts'
|
|
8
8
|
import { compareImage } from '../shared/compare_image.ts'
|
|
9
|
+
import type { ComparisonMethod } from '../shared/types.ts'
|
|
9
10
|
import { alignImagesToSameSize } from './align_images.ts'
|
|
10
11
|
import type { ToMatchImageSnapshotOptions } from './expect/to_match_image_snapshot.types.ts'
|
|
11
12
|
import { toDataURL, toImageData } from './image_data.ts'
|
|
@@ -17,7 +18,7 @@ import { server } from './vitest_browser_context_proxy.ts'
|
|
|
17
18
|
export function imageSnapshotMatcher(
|
|
18
19
|
commands: PrepareImageSnapshotComparisonCommand & WriteImageSnapshotCommand & ImageSnapshotNextIndexCommand,
|
|
19
20
|
) {
|
|
20
|
-
return async function matchImageSnapshot(test: Task, subject: any, options?: ToMatchImageSnapshotOptions) {
|
|
21
|
+
return async function matchImageSnapshot(test: Task, subject: any, options?: ToMatchImageSnapshotOptions<any>) {
|
|
21
22
|
const taskId = toTaskId(test)
|
|
22
23
|
const info = await commands.prepareImageSnapshotComparison(
|
|
23
24
|
taskId,
|
|
@@ -27,6 +28,8 @@ export function imageSnapshotMatcher(
|
|
|
27
28
|
|
|
28
29
|
if (!info) return
|
|
29
30
|
|
|
31
|
+
options = { ...info, ...options } as any
|
|
32
|
+
|
|
30
33
|
const baselineImage = await toImageData(info.baseline)
|
|
31
34
|
const resultImage = await toImageData(info.result)
|
|
32
35
|
const [baselineAlignedImage, resultAlignedImage] = alignImagesToSameSize(baselineImage, resultImage)
|
|
@@ -101,7 +104,7 @@ async function writeSnapshot(commands: WriteImageSnapshotCommand, path: string,
|
|
|
101
104
|
async function parseImageSnapshotOptions(
|
|
102
105
|
commands: ImageSnapshotNextIndexCommand,
|
|
103
106
|
taskId: string,
|
|
104
|
-
options: ToMatchImageSnapshotOptions
|
|
107
|
+
options: ToMatchImageSnapshotOptions<any>,
|
|
105
108
|
) {
|
|
106
109
|
const index = await commands.imageSnapshotNextIndex(taskId)
|
|
107
110
|
const { customizeSnapshotId, ...rest } = options
|
|
@@ -9,7 +9,7 @@ import type { SnapshotMeta } from './snapshot_options.ts'
|
|
|
9
9
|
* no `snapshot` tag: false
|
|
10
10
|
* disabled by `!snapshot` tag: false
|
|
11
11
|
*/
|
|
12
|
-
export function shouldTakeSnapshot(meta?: SnapshotMeta | undefined) {
|
|
12
|
+
export function shouldTakeSnapshot(meta?: SnapshotMeta<any> | undefined) {
|
|
13
13
|
const m = meta ?? getAutoSnapshotOptions(ctx.getCurrentTest())
|
|
14
14
|
return document.body.childElementCount > 0 && m?.enable !== false
|
|
15
15
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NAME } from '../shared/constants.ts'
|
|
2
2
|
import type { MetaTask, SnapshotMeta } from './snapshot_options.ts'
|
|
3
3
|
|
|
4
|
-
export function getAutoSnapshotOptions(task: MetaTask): SnapshotMeta | undefined {
|
|
4
|
+
export function getAutoSnapshotOptions(task: MetaTask): SnapshotMeta<any> | undefined {
|
|
5
5
|
if (!task) return
|
|
6
6
|
|
|
7
7
|
const list: any[] = []
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { NAME } from '../shared/constants.ts'
|
|
2
|
+
import type { ComparisonMethod } from '../shared/types.ts'
|
|
2
3
|
import { ctx } from './ctx.ts'
|
|
3
4
|
import type { ToMatchImageSnapshotOptions } from './expect/to_match_image_snapshot.types.ts'
|
|
4
5
|
|
|
5
|
-
export type SnapshotMeta = ToMatchImageSnapshotOptions & { enable?: boolean | undefined }
|
|
6
|
+
export type SnapshotMeta<M extends ComparisonMethod> = ToMatchImageSnapshotOptions<M> & { enable?: boolean | undefined }
|
|
6
7
|
|
|
7
8
|
type Suite = { meta: Record<string, any>; suite?: Suite | undefined }
|
|
8
9
|
|
|
@@ -29,10 +30,13 @@ export type MetaTask =
|
|
|
29
30
|
* @param task Optional. Suite or task to set the options.
|
|
30
31
|
* If not provided, it will set the options for the current test.
|
|
31
32
|
*/
|
|
32
|
-
export function setAutoSnapshotOptions
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
export function setAutoSnapshotOptions<M extends ComparisonMethod>(
|
|
34
|
+
task: MetaTask,
|
|
35
|
+
meta: SnapshotMeta<M> | boolean,
|
|
36
|
+
): void
|
|
37
|
+
export function setAutoSnapshotOptions<M extends ComparisonMethod>(meta: SnapshotMeta<M> | boolean): void
|
|
38
|
+
export function setAutoSnapshotOptions<M extends ComparisonMethod>(
|
|
39
|
+
...args: [task: MetaTask, meta: SnapshotMeta<M> | boolean] | [meta: SnapshotMeta<M> | boolean]
|
|
36
40
|
): void {
|
|
37
41
|
const [task, meta] = parseArgs(args)
|
|
38
42
|
if (task)
|
|
@@ -42,12 +46,12 @@ export function setAutoSnapshotOptions(
|
|
|
42
46
|
}
|
|
43
47
|
}
|
|
44
48
|
|
|
45
|
-
function parseArgs(
|
|
46
|
-
args: [task: MetaTask, meta: SnapshotMeta | boolean] | [meta: boolean | SnapshotMeta],
|
|
47
|
-
): [MetaTask | undefined, SnapshotMeta] {
|
|
49
|
+
function parseArgs<M extends ComparisonMethod>(
|
|
50
|
+
args: [task: MetaTask, meta: SnapshotMeta<M> | boolean] | [meta: boolean | SnapshotMeta<M>],
|
|
51
|
+
): [MetaTask | undefined, SnapshotMeta<M>] {
|
|
48
52
|
return args.length === 1 ? [ctx.getCurrentTest(), parseMeta(args[0])] : [args[0], parseMeta(args[1])]
|
|
49
53
|
}
|
|
50
54
|
|
|
51
|
-
function parseMeta(meta: boolean | SnapshotMeta): SnapshotMeta {
|
|
52
|
-
return typeof meta === 'boolean' ? { enable: meta } : { enable: true, ...meta }
|
|
55
|
+
function parseMeta<M extends ComparisonMethod>(meta: boolean | SnapshotMeta<M>): SnapshotMeta<M> {
|
|
56
|
+
return typeof meta === 'boolean' ? ({ enable: meta } as any) : { enable: true, ...meta }
|
|
53
57
|
}
|
package/src/config/types.ts
CHANGED
|
@@ -1,35 +1,52 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ComparisonMethod,
|
|
3
|
+
ImageSnapshotCompareOptions,
|
|
4
|
+
ImageSnapshotIdOptions,
|
|
5
|
+
ImageSnapshotTimeoutOptions,
|
|
6
|
+
} from '../shared/types.ts'
|
|
2
7
|
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
8
|
+
export type { ComparisonMethod }
|
|
9
|
+
|
|
10
|
+
export type VisOptions<M extends ComparisonMethod = 'pixel'> = ImageSnapshotTimeoutOptions &
|
|
11
|
+
ImageSnapshotIdOptions &
|
|
12
|
+
ImageSnapshotCompareOptions<M> & {
|
|
13
|
+
/**
|
|
14
|
+
* Loads the `test.setupFiles` of the specified preset.
|
|
15
|
+
*/
|
|
16
|
+
preset?: 'manual' | 'auto' | 'none' | undefined
|
|
17
|
+
/**
|
|
18
|
+
* The name of the subdirectory that the baseline snapshots get saved in.
|
|
19
|
+
*
|
|
20
|
+
* <snapshotRootDir>/<platform>/<snapshotSubpath>/<snapshotId>.png
|
|
21
|
+
*
|
|
22
|
+
* Default: undefined
|
|
23
|
+
*/
|
|
24
|
+
platform?: string | undefined
|
|
25
|
+
/**
|
|
26
|
+
* The snapshot folder relative to the root of the project.
|
|
27
|
+
*
|
|
28
|
+
* Default: `__vis__`
|
|
29
|
+
*/
|
|
30
|
+
snapshotRootDir?: string | undefined
|
|
31
|
+
/**
|
|
32
|
+
* Customize the snapshot subpath.
|
|
33
|
+
*
|
|
34
|
+
* The snapshot subpath is used along with `snapshotRootDir` to determine the folder of the snapshots:
|
|
35
|
+
*
|
|
36
|
+
* - baseline: `<snapshotRootDir>/<platform>/<snapshotSubpath>/<snapshotId>.png`
|
|
37
|
+
* - result: `<snapshotRootDir>/__results__/<snapshotSubpath>/<snapshotId>.png`
|
|
38
|
+
* - diff: `<snapshotRootDir>/__diffs__/<snapshotSubpath>/<snapshotId>.png`
|
|
39
|
+
*
|
|
40
|
+
* Typically, you place your test files either in a dedicated `tests` folder or in the `src` folder along with your source code.
|
|
41
|
+
* By default, the snapshot subpath is the test file path with that folder removed to reduces nesting of the snapshot folders.
|
|
42
|
+
*
|
|
43
|
+
* If you place your test files in multiple folders,
|
|
44
|
+
* such as in both `tests` and `src` folders,
|
|
45
|
+
* and they might have files with the same name and create conflicting snapshots,
|
|
46
|
+
* you can set this to customize the snapshot subpath.
|
|
47
|
+
*
|
|
48
|
+
* @param subPath - The path of the test file to be used as the snapshot sub path.
|
|
49
|
+
* If you want to keep the full path, you can simply return it.
|
|
50
|
+
*/
|
|
51
|
+
customizeSnapshotSubpath?: (subPath: string) => string
|
|
52
|
+
}
|
package/src/config/vis.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Plugin } from 'vite'
|
|
|
2
2
|
import { commands } from '../server/commands/extend.ts'
|
|
3
3
|
import { visContext } from '../server/vis_context.ts'
|
|
4
4
|
import { NAME } from '../shared/constants.ts'
|
|
5
|
+
import type { ComparisonMethod } from '../shared/types.ts'
|
|
5
6
|
import type { VisOptions } from './types.ts'
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -10,7 +11,7 @@ import type { VisOptions } from './types.ts'
|
|
|
10
11
|
* If options are not provided, the plugin will use the default options,
|
|
11
12
|
* which enables the `auto` preset.
|
|
12
13
|
*/
|
|
13
|
-
export function vis(options: VisOptions = { preset: 'auto' }) {
|
|
14
|
+
export function vis<M extends ComparisonMethod = 'pixel'>(options: VisOptions<M> = { preset: 'auto' } as any) {
|
|
14
15
|
visContext.setOptions(options)
|
|
15
16
|
const preset = options?.preset
|
|
16
17
|
return {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import dedent from 'dedent'
|
|
2
|
-
import ci from 'is-ci'
|
|
3
2
|
import { resolve } from 'pathe'
|
|
4
3
|
import { PNG } from 'pngjs'
|
|
5
4
|
import type { BrowserCommand } from 'vitest/node'
|
|
@@ -25,15 +24,14 @@ export interface MatchImageSnapshotCommand {
|
|
|
25
24
|
) => Promise<void>
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
export
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
27
|
+
export type MatchImageSnapshotOptions = ImageSnapshotTimeoutOptions &
|
|
28
|
+
ImageSnapshotIdOptions &
|
|
29
|
+
ImageSnapshotCompareOptions<any> & {
|
|
30
|
+
/**
|
|
31
|
+
* The snapshot file id calculated on the client side.
|
|
32
|
+
*/
|
|
33
|
+
snapshotFileId?: string | undefined
|
|
34
|
+
}
|
|
37
35
|
|
|
38
36
|
export const matchImageSnapshot: BrowserCommand<
|
|
39
37
|
[taskId: string, subject: string, options?: MatchImageSnapshotOptions | undefined]
|
|
@@ -42,7 +40,7 @@ export const matchImageSnapshot: BrowserCommand<
|
|
|
42
40
|
|
|
43
41
|
// vitest:browser passes in `null` when not defined
|
|
44
42
|
if (!options) options = {}
|
|
45
|
-
options.timeout = options.timeout ??
|
|
43
|
+
options.timeout = options.timeout ?? 30000
|
|
46
44
|
|
|
47
45
|
const info = visContext.getSnapshotInfo(context.testPath, taskId, options)
|
|
48
46
|
const baselineBuffer = await file.tryReadFile(info.baselinePath)
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import ci from 'is-ci'
|
|
2
1
|
import type { BrowserCommand } from 'vitest/node'
|
|
3
2
|
import { isBase64String } from '../../shared/base64.ts'
|
|
3
|
+
import type {
|
|
4
|
+
FailureThresholdOptions,
|
|
5
|
+
ImageSnapshotTimeoutOptions,
|
|
6
|
+
PixelComparisonOptions,
|
|
7
|
+
SsimComparisonOptions,
|
|
8
|
+
} from '../../shared/types.ts'
|
|
4
9
|
import { file } from '../file.ts'
|
|
5
10
|
import { takeSnapshot, takeSnapshotByBrowser, writeSnapshot } from '../snapshot.ts'
|
|
6
11
|
import { visContext } from '../vis_context.ts'
|
|
@@ -14,7 +19,9 @@ type ImageSnapshotComparisonInfo = {
|
|
|
14
19
|
diffPath: string
|
|
15
20
|
baseline: string
|
|
16
21
|
result: string
|
|
17
|
-
}
|
|
22
|
+
} & ImageSnapshotTimeoutOptions &
|
|
23
|
+
FailureThresholdOptions &
|
|
24
|
+
(SsimComparisonOptions | PixelComparisonOptions)
|
|
18
25
|
|
|
19
26
|
export interface PrepareImageSnapshotComparisonCommand {
|
|
20
27
|
prepareImageSnapshotComparison: (
|
|
@@ -31,7 +38,7 @@ export const prepareImageSnapshotComparison: BrowserCommand<
|
|
|
31
38
|
|
|
32
39
|
// vitest:browser passes in `null` when not defined
|
|
33
40
|
if (!options) options = {}
|
|
34
|
-
options.timeout = options.timeout ??
|
|
41
|
+
options.timeout = options.timeout ?? 30000
|
|
35
42
|
|
|
36
43
|
const info = visContext.getSnapshotInfo(context.testPath, taskId, options)
|
|
37
44
|
const baselineBuffer = await file.tryReadFile(info.baselinePath)
|
|
@@ -2,11 +2,11 @@ import type { VisOptions } from '../config/types.ts'
|
|
|
2
2
|
import { SNAPSHOT_ROOT_DIR } from '../shared/constants.ts'
|
|
3
3
|
import { trimCommonFolder } from '../shared/trim_common_folder.ts'
|
|
4
4
|
|
|
5
|
-
export function resolveSnapshotRootDir(options: VisOptions | undefined) {
|
|
5
|
+
export function resolveSnapshotRootDir(options: Pick<VisOptions, 'snapshotRootDir'> | undefined) {
|
|
6
6
|
return options?.snapshotRootDir ?? SNAPSHOT_ROOT_DIR
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export function getSnapshotSubpath(suiteName: string, options: VisOptions) {
|
|
9
|
+
export function getSnapshotSubpath(suiteName: string, options: Pick<VisOptions, 'customizeSnapshotSubpath'>) {
|
|
10
10
|
const customizeSnapshotSubpath = options.customizeSnapshotSubpath ?? trimCommonFolder
|
|
11
11
|
return customizeSnapshotSubpath(suiteName)
|
|
12
12
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { join, relative } from 'pathe'
|
|
2
|
+
import { pick } from 'type-plus'
|
|
2
3
|
import type { VisOptions } from '../config/types.ts'
|
|
3
4
|
import { DIFF_DIR, RESULT_DIR } from '../shared/constants.ts'
|
|
4
5
|
import { file } from './file.ts'
|
|
@@ -21,12 +22,12 @@ export type PartialBrowserCommandContext = {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export function createVisContext() {
|
|
24
|
-
let visOptions: VisOptions = {}
|
|
25
|
+
let visOptions: VisOptions<any> = {}
|
|
25
26
|
let state: VisState
|
|
26
27
|
let globalStateReady: Promise<VisState>
|
|
27
28
|
|
|
28
29
|
const context = {
|
|
29
|
-
setOptions(options: VisOptions = {}) {
|
|
30
|
+
setOptions<M extends 'pixel' | 'ssim'>(options: VisOptions<M> = {} as any) {
|
|
30
31
|
visOptions = options
|
|
31
32
|
},
|
|
32
33
|
__test__getOptions() {
|
|
@@ -67,6 +68,7 @@ export function createVisContext() {
|
|
|
67
68
|
const diffPath = join(diffDir, snapshotFilename)
|
|
68
69
|
|
|
69
70
|
return {
|
|
71
|
+
...pick(visOptions, 'comparisonMethod', 'diffOptions', 'failureThreshold', 'failureThresholdType', 'timeout'),
|
|
70
72
|
baselinePath,
|
|
71
73
|
resultPath,
|
|
72
74
|
diffPath,
|
|
@@ -102,10 +104,13 @@ export function createVisContext() {
|
|
|
102
104
|
return context
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
async function setupState(
|
|
107
|
+
async function setupState(
|
|
108
|
+
suite: PartialBrowserCommandContext,
|
|
109
|
+
visOptions: Pick<VisOptions, 'snapshotRootDir' | 'platform'>,
|
|
110
|
+
) {
|
|
106
111
|
const snapshotRootDir = resolveSnapshotRootDir(visOptions)
|
|
107
112
|
const projectPath = suite.project.config.root
|
|
108
|
-
const platform = ctx.getSnapshotPlatform()
|
|
113
|
+
const platform = visOptions.platform ?? ctx.getSnapshotPlatform()
|
|
109
114
|
|
|
110
115
|
const state = {
|
|
111
116
|
projectPath,
|
|
@@ -123,7 +128,7 @@ async function setupState(suite: PartialBrowserCommandContext, visOptions: VisOp
|
|
|
123
128
|
return state
|
|
124
129
|
}
|
|
125
130
|
|
|
126
|
-
export function createSuite(state: VisState, testPath: string, options: VisOptions) {
|
|
131
|
+
export function createSuite(state: VisState, testPath: string, options: Pick<VisOptions, 'customizeSnapshotSubpath'>) {
|
|
127
132
|
const suiteId = getSuiteId(state, testPath, options)
|
|
128
133
|
return {
|
|
129
134
|
suiteId,
|
|
@@ -136,6 +141,6 @@ export function createSuite(state: VisState, testPath: string, options: VisOptio
|
|
|
136
141
|
}
|
|
137
142
|
}
|
|
138
143
|
|
|
139
|
-
export function getSuiteId(state: VisState, testPath: string, options: VisOptions) {
|
|
144
|
+
export function getSuiteId(state: VisState, testPath: string, options: Pick<VisOptions, 'customizeSnapshotSubpath'>) {
|
|
140
145
|
return getSnapshotSubpath(relative(state.projectPath, testPath), options)
|
|
141
146
|
}
|
package/src/setup/create_vis.ts
CHANGED
|
@@ -1,20 +1,69 @@
|
|
|
1
1
|
import pixelmatch from 'pixelmatch'
|
|
2
|
+
import { type Options, ssim } from 'ssim.js'
|
|
2
3
|
import { convertThresholdUnit } from './convert_threshold_unit.ts'
|
|
3
|
-
import type { ImageSnapshotCompareOptions } from './types.ts'
|
|
4
|
+
import type { ComparisonMethod, ImageSnapshotCompareOptions } from './types.ts'
|
|
4
5
|
|
|
5
|
-
export function compareImage(
|
|
6
|
+
export function compareImage<M extends ComparisonMethod>(
|
|
6
7
|
img1: Uint8ClampedArray | Buffer,
|
|
7
8
|
img2: Uint8ClampedArray | Buffer,
|
|
8
9
|
output: Uint8ClampedArray | Buffer,
|
|
9
10
|
width: number,
|
|
10
11
|
height: number,
|
|
11
|
-
|
|
12
|
+
options: ImageSnapshotCompareOptions<M> = {} as any,
|
|
12
13
|
) {
|
|
13
|
-
const pixelDiff =
|
|
14
|
-
|
|
14
|
+
const pixelDiff =
|
|
15
|
+
options.comparisonMethod === 'ssim'
|
|
16
|
+
? compareWithSsim(img1, img2, output, width, height, options.diffOptions)
|
|
17
|
+
: pixelmatch(img1, img2, output, width, height, options.diffOptions)
|
|
18
|
+
const diffAmount = convertThresholdUnit(
|
|
19
|
+
{ failureThresholdType: options.failureThresholdType ?? 'pixel', width, height },
|
|
20
|
+
pixelDiff,
|
|
21
|
+
)
|
|
15
22
|
|
|
16
23
|
return {
|
|
17
|
-
pass: diffAmount <= failureThreshold,
|
|
24
|
+
pass: diffAmount <= (options.failureThreshold ?? 0),
|
|
18
25
|
diffAmount,
|
|
19
26
|
}
|
|
20
27
|
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @author This code is based on `jest-image-snapshot` implementation.
|
|
31
|
+
*/
|
|
32
|
+
function compareWithSsim(
|
|
33
|
+
img1: Uint8ClampedArray | Buffer,
|
|
34
|
+
img2: Uint8ClampedArray | Buffer,
|
|
35
|
+
output: Uint8ClampedArray | Buffer,
|
|
36
|
+
width: number,
|
|
37
|
+
height: number,
|
|
38
|
+
diffOptions?: Partial<Options>,
|
|
39
|
+
) {
|
|
40
|
+
const newImage = { data: img1, width: width, height: height }
|
|
41
|
+
const baselineImage = { data: img2, width: width, height: height }
|
|
42
|
+
const { ssim_map, mssim } = ssim(newImage as any, baselineImage as any, {
|
|
43
|
+
ssim: 'bezkrovny',
|
|
44
|
+
...diffOptions,
|
|
45
|
+
})
|
|
46
|
+
// Converts the SSIM value to different pixels based on image width and height
|
|
47
|
+
// conforms to how pixelmatch works.
|
|
48
|
+
const diffPixels = Math.round((1 - mssim) * width * height)
|
|
49
|
+
const diffRgbaPixels = new DataView(output.buffer, output.byteOffset)
|
|
50
|
+
for (let ln = 0; ln !== height; ++ln) {
|
|
51
|
+
for (let pos = 0; pos !== width; ++pos) {
|
|
52
|
+
const rPos = ln * width + pos
|
|
53
|
+
// initial value is transparent. We'll add in the SSIM offset.
|
|
54
|
+
// red (ff) green (00) blue (00) alpha (00)
|
|
55
|
+
const diffValue =
|
|
56
|
+
0xff000000 +
|
|
57
|
+
Math.floor(
|
|
58
|
+
0xff *
|
|
59
|
+
(1 -
|
|
60
|
+
ssim_map.data[
|
|
61
|
+
ssim_map.width * Math.round((ssim_map.height * ln) / height) +
|
|
62
|
+
Math.round((ssim_map.width * pos) / width)
|
|
63
|
+
]!),
|
|
64
|
+
)
|
|
65
|
+
diffRgbaPixels.setUint32(rPos * 4, diffValue)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return diffPixels
|
|
69
|
+
}
|
package/src/shared/types.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { PixelmatchOptions } from 'pixelmatch'
|
|
2
|
+
import type { Options as SsimOptions } from 'ssim.js'
|
|
2
3
|
|
|
3
4
|
export interface ImageSnapshotTimeoutOptions {
|
|
4
5
|
/**
|
|
5
6
|
* Timeout for taking the snapshot.
|
|
6
7
|
*
|
|
7
|
-
* Default:
|
|
8
|
+
* Default: 30000 ms (30 seconds)
|
|
8
9
|
*/
|
|
9
10
|
timeout?: number | undefined
|
|
10
11
|
}
|
|
@@ -19,11 +20,9 @@ export interface ImageSnapshotIdOptions {
|
|
|
19
20
|
customizeSnapshotId?: (id: string, index: number) => string
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
*/
|
|
26
|
-
diffOptions?: PixelmatchOptions | undefined
|
|
23
|
+
export type ComparisonMethod = 'pixel' | 'ssim'
|
|
24
|
+
|
|
25
|
+
export type FailureThresholdOptions = {
|
|
27
26
|
/**
|
|
28
27
|
* Failure threshold should measure in `pixel` or `percent`.
|
|
29
28
|
*
|
|
@@ -37,3 +36,23 @@ export interface ImageSnapshotCompareOptions {
|
|
|
37
36
|
*/
|
|
38
37
|
failureThreshold?: number | undefined
|
|
39
38
|
}
|
|
39
|
+
|
|
40
|
+
export type SsimComparisonOptions<M = 'ssim'> = {
|
|
41
|
+
comparisonMethod: M
|
|
42
|
+
/**
|
|
43
|
+
* Custom options passed to 'ssim'
|
|
44
|
+
*/
|
|
45
|
+
diffOptions?: Partial<SsimOptions> | undefined
|
|
46
|
+
}
|
|
47
|
+
export type PixelComparisonOptions<M = 'pixel'> = {
|
|
48
|
+
comparisonMethod?: M | undefined
|
|
49
|
+
/**
|
|
50
|
+
* Custom options passed to 'pixelmatch'
|
|
51
|
+
*/
|
|
52
|
+
diffOptions?: PixelmatchOptions | undefined
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type ImageSnapshotCompareOptions<M extends ComparisonMethod = 'pixel'> = (M extends 'ssim'
|
|
56
|
+
? SsimComparisonOptions<M>
|
|
57
|
+
: PixelComparisonOptions<M>) &
|
|
58
|
+
FailureThresholdOptions
|