vitest-plugin-vis 1.1.0 → 1.3.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/augment.d.ts +8 -0
- package/esm/client/expect/augment.d.ts.map +1 -0
- package/esm/client/expect/augment.js +1 -0
- package/esm/client/expect/extend.d.ts +1 -7
- package/esm/client/expect/extend.d.ts.map +1 -1
- package/esm/client/expect/extend.js +1 -0
- package/esm/client/expect/to_match_image_snapshot.d.ts.map +1 -1
- package/esm/client/expect/to_match_image_snapshot.js +3 -2
- package/esm/client/expect/to_match_image_snapshot.types.d.ts +5 -0
- package/esm/client/expect/to_match_image_snapshot.types.d.ts.map +1 -1
- package/esm/client/match_image_snapshot.d.ts +4 -1
- package/esm/client/match_image_snapshot.d.ts.map +1 -1
- package/esm/client/match_image_snapshot.js +43 -30
- package/esm/client/match_image_snapshot.logic.js +1 -1
- package/esm/client/page/augment.d.ts +6 -0
- package/esm/client/page/augment.d.ts.map +1 -0
- package/esm/client/page/augment.js +1 -0
- package/esm/client/page/extend.d.ts +1 -5
- package/esm/client/page/extend.d.ts.map +1 -1
- package/esm/client/page/extend.js +1 -0
- package/esm/client/selector.d.ts +0 -2
- package/esm/client/selector.d.ts.map +1 -1
- package/esm/client/selector.js +5 -12
- package/esm/commands.d.ts +2 -0
- package/esm/commands.d.ts.map +1 -1
- package/esm/commands.js +2 -0
- package/esm/config/vis.d.ts +1 -1
- package/esm/index.d.ts +2 -0
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +2 -0
- package/esm/presets/auto.d.ts +2 -1
- package/esm/presets/auto.d.ts.map +1 -1
- package/esm/presets/auto.js +2 -1
- package/esm/presets/manual.d.ts +2 -1
- package/esm/presets/manual.d.ts.map +1 -1
- package/esm/presets/manual.js +2 -1
- package/esm/server/commands/_assertions.d.ts +5 -0
- package/esm/server/commands/_assertions.d.ts.map +1 -0
- package/esm/server/commands/_assertions.js +11 -0
- package/esm/server/commands/extend.d.ts +1 -1
- package/esm/server/commands/has_image_snapshot.d.ts.map +1 -1
- package/esm/server/commands/has_image_snapshot.js +2 -3
- package/esm/server/commands/image_snapshot_next_index.d.ts.map +1 -1
- package/esm/server/commands/image_snapshot_next_index.js +2 -3
- package/esm/server/commands/match_image_snapshot.d.ts.map +1 -1
- package/esm/server/commands/match_image_snapshot.js +6 -5
- package/esm/server/commands/prepare_image_snapshot_comparison.d.ts.map +1 -1
- package/esm/server/commands/prepare_image_snapshot_comparison.js +2 -3
- package/esm/server/commands/setup_vis_suite.d.ts.map +1 -1
- package/esm/server/commands/setup_vis_suite.js +2 -0
- package/esm/server/commands/write_image_snapshot.d.ts +8 -2
- package/esm/server/commands/write_image_snapshot.d.ts.map +1 -1
- package/esm/server/commands/write_image_snapshot.js +6 -7
- package/esm/server/vis_context.d.ts.map +1 -1
- package/esm/server/vis_context.logic.d.ts +2 -2
- package/esm/server/vis_context.logic.d.ts.map +1 -1
- package/esm/server/vis_context.logic.js +3 -3
- package/esm/shared/compare_image.d.ts +6 -0
- package/esm/shared/compare_image.d.ts.map +1 -0
- package/esm/shared/compare_image.js +10 -0
- package/esm/testing/stubBrowserCommandContext.d.ts +3 -0
- package/esm/testing/stubBrowserCommandContext.d.ts.map +1 -0
- package/esm/testing/stubBrowserCommandContext.js +2 -0
- package/esm/testing.d.ts +1 -0
- package/esm/testing.d.ts.map +1 -1
- package/esm/testing.js +1 -0
- package/package.json +2 -1
- package/readme.md +38 -2
- package/src/client/expect/augment.ts +8 -0
- package/src/client/expect/extend.ts +1 -8
- package/src/client/expect/to_match_image_snapshot.ts +3 -2
- package/src/client/expect/to_match_image_snapshot.types.ts +7 -1
- package/src/client/match_image_snapshot.logic.ts +1 -1
- package/src/client/match_image_snapshot.ts +68 -36
- package/src/client/page/augment.ts +5 -0
- package/src/client/page/extend.ts +2 -5
- package/src/client/selector.ts +0 -13
- package/src/commands.ts +2 -0
- package/src/index.ts +3 -0
- package/src/presets/auto.ts +2 -1
- package/src/presets/manual.ts +2 -1
- package/src/server/commands/_assertions.ts +18 -0
- package/src/server/commands/has_image_snapshot.ts +2 -3
- package/src/server/commands/image_snapshot_next_index.ts +2 -3
- package/src/server/commands/match_image_snapshot.ts +13 -6
- package/src/server/commands/prepare_image_snapshot_comparison.ts +2 -3
- package/src/server/commands/setup_vis_suite.ts +3 -0
- package/src/server/commands/write_image_snapshot.ts +17 -11
- package/src/server/vis_context.logic.ts +6 -6
- package/src/shared/compare_image.ts +20 -0
- package/src/testing/stubBrowserCommandContext.ts +4 -0
- package/src/testing.ts +1 -0
- package/esm/client/compare_image.d.ts +0 -7
- package/esm/client/compare_image.d.ts.map +0 -1
- package/esm/client/compare_image.js +0 -13
- package/esm/server/commands/is_ci.d.ts +0 -2
- package/esm/server/commands/is_ci.d.ts.map +0 -1
- package/esm/server/commands/is_ci.js +0 -6
- package/esm/server/compare_image.d.ts +0 -8
- package/esm/server/compare_image.d.ts.map +0 -1
- package/esm/server/compare_image.js +0 -16
- package/src/client/compare_image.ts +0 -21
- package/src/server/commands/is_ci.ts +0 -7
- package/src/server/compare_image.ts +0 -34
|
@@ -99,9 +99,6 @@ async function setupState(suite, visOptions) {
|
|
|
99
99
|
await Promise.allSettled([ctx.rimraf(join(state.snapshotDiffDir)), ctx.rimraf(join(state.snapshotResultDir))]);
|
|
100
100
|
return state;
|
|
101
101
|
}
|
|
102
|
-
export function getSuiteId(state, testPath, options) {
|
|
103
|
-
return getSnapshotSubpath(relative(state.projectPath, testPath), options);
|
|
104
|
-
}
|
|
105
102
|
export function createSuite(state, testPath, options) {
|
|
106
103
|
const suiteId = getSuiteId(state, testPath, options);
|
|
107
104
|
return {
|
|
@@ -114,3 +111,6 @@ export function createSuite(state, testPath, options) {
|
|
|
114
111
|
},
|
|
115
112
|
};
|
|
116
113
|
}
|
|
114
|
+
export function getSuiteId(state, testPath, options) {
|
|
115
|
+
return getSnapshotSubpath(relative(state.projectPath, testPath), options);
|
|
116
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ImageSnapshotCompareOptions } from './types.ts';
|
|
2
|
+
export declare function compareImage(img1: Uint8ClampedArray | Buffer, img2: Uint8ClampedArray | Buffer, output: Uint8ClampedArray | Buffer, width: number, height: number, { failureThreshold, failureThresholdType, diffOptions }?: ImageSnapshotCompareOptions): {
|
|
3
|
+
pass: boolean;
|
|
4
|
+
diffAmount: number;
|
|
5
|
+
};
|
|
6
|
+
//# sourceMappingURL=compare_image.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare_image.d.ts","sourceRoot":"","sources":["../../src/shared/compare_image.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAA;AAE7D,wBAAgB,YAAY,CAC3B,IAAI,EAAE,iBAAiB,GAAG,MAAM,EAChC,IAAI,EAAE,iBAAiB,GAAG,MAAM,EAChC,MAAM,EAAE,iBAAiB,GAAG,MAAM,EAClC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,EAAE,gBAAoB,EAAE,oBAA8B,EAAE,WAAW,EAAE,GAAE,2BAAgC;;;EASvG"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import pixelmatch from 'pixelmatch';
|
|
2
|
+
import { convertThresholdUnit } from "./convert_threshold_unit.js";
|
|
3
|
+
export function compareImage(img1, img2, output, width, height, { failureThreshold = 0, failureThresholdType = 'pixel', diffOptions } = {}) {
|
|
4
|
+
const pixelDiff = pixelmatch(img1, img2, output, width, height, diffOptions);
|
|
5
|
+
const diffAmount = convertThresholdUnit({ failureThresholdType, width, height }, pixelDiff);
|
|
6
|
+
return {
|
|
7
|
+
pass: diffAmount <= failureThreshold,
|
|
8
|
+
diffAmount,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stubBrowserCommandContext.d.ts","sourceRoot":"","sources":["../../src/testing/stubBrowserCommandContext.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAExD,eAAO,MAAM,yBAAyB,2GAAwC,CAAA"}
|
package/esm/testing.d.ts
CHANGED
package/esm/testing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAA"}
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAA;AACtC,cAAc,wCAAwC,CAAA"}
|
package/esm/testing.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vitest-plugin-vis",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Vitest visual testing plugin",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"image-snapshot",
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
"@vitest/coverage-v8": "^2.1.8",
|
|
81
81
|
"@vitest/expect": "^2.1.8",
|
|
82
82
|
"cross-env": "^7.0.3",
|
|
83
|
+
"never-fail": "^1.1.4",
|
|
83
84
|
"npm-run-all2": "^7.0.2",
|
|
84
85
|
"playwright": "^1.49.0",
|
|
85
86
|
"react": "^19.0.0",
|
package/readme.md
CHANGED
|
@@ -103,7 +103,7 @@ vis.presets.auto()
|
|
|
103
103
|
vis.presets.manual()
|
|
104
104
|
|
|
105
105
|
|
|
106
|
-
// or capture image snapshot for all
|
|
106
|
+
// or capture image snapshot for all rendering tests
|
|
107
107
|
// for multiple themes (light and dark in this example)
|
|
108
108
|
vis.presets.theme({
|
|
109
109
|
light() { document.body.classList.remove('dark') },
|
|
@@ -111,11 +111,30 @@ vis.presets.theme({
|
|
|
111
111
|
})
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
### TypeScript Configuration
|
|
115
|
+
|
|
116
|
+
The main usage of this addon is to use the `toMatchImageSnapshot` matcher.
|
|
117
|
+
|
|
118
|
+
Since it is exposed under the `expect` object of `vitest`,
|
|
119
|
+
you typically do not need to import `storybook-addon-vis` directly.
|
|
120
|
+
|
|
121
|
+
Because of this, TypeScript may not recognize the matcher.
|
|
122
|
+
To address this, you can add the following to your `tsconfig.json`:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"compilerOptions": {
|
|
127
|
+
"types": ["vitest-plugin-vis"]
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
114
132
|
## Usage
|
|
115
133
|
|
|
116
134
|
### Auto Snapshot
|
|
117
135
|
|
|
118
|
-
By default, the plugin will use the `auto` preset,
|
|
136
|
+
By default, the plugin will use the `auto` preset,
|
|
137
|
+
which will take a snapshot at the end of each test.
|
|
119
138
|
|
|
120
139
|
You can control how the auto snapshot is taken using the `setAutoSnapshotOptions` function:
|
|
121
140
|
|
|
@@ -153,6 +172,23 @@ describe('nested scope', () => {
|
|
|
153
172
|
})
|
|
154
173
|
```
|
|
155
174
|
|
|
175
|
+
The options are similar to `expect(...).toMatchImageSnapshot(options)`:
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
// enable/disable auto snapshot
|
|
179
|
+
setAutoSnapshotOptions(true /* or false */)
|
|
180
|
+
|
|
181
|
+
// detailed options
|
|
182
|
+
setAutoSnapshotOptions({
|
|
183
|
+
enable: true,
|
|
184
|
+
customizeSnapshotId: (id, index) => `${id}-custom-${index}`,
|
|
185
|
+
diffOptions: { threshold: 0.01 } // pixelmatch options,
|
|
186
|
+
failureThreshold: 0.01,
|
|
187
|
+
failureThresholdType: 'percent',
|
|
188
|
+
timeout: 15000
|
|
189
|
+
})
|
|
190
|
+
```
|
|
191
|
+
|
|
156
192
|
### Manual Snapshot
|
|
157
193
|
|
|
158
194
|
You can also set the preset to `manual` and compare snapshots manually:
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ImageSnapshotMatcher } from './to_match_image_snapshot.types.ts'
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
namespace jest {
|
|
5
|
+
// biome-ignore lint/correctness/noUnusedVariables: augmentation must have matching type params.
|
|
6
|
+
interface Matchers<R, T> extends ImageSnapshotMatcher {}
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import { expect } from 'vitest'
|
|
2
2
|
import { toMatchImageSnapshot } from './to_match_image_snapshot.ts'
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
declare global {
|
|
6
|
-
namespace jest {
|
|
7
|
-
// biome-ignore lint/correctness/noUnusedVariables: augmentation must have matching type params.
|
|
8
|
-
interface Matchers<R, T> extends ImageSnapshotMatcher {}
|
|
9
|
-
}
|
|
10
|
-
}
|
|
3
|
+
import './augment.ts'
|
|
11
4
|
|
|
12
5
|
expect.extend({ toMatchImageSnapshot })
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { commands } from '@vitest/browser/context'
|
|
1
2
|
import type { AsyncExpectationResult } from '@vitest/expect'
|
|
2
3
|
import { ctx } from '../ctx.ts'
|
|
3
|
-
import {
|
|
4
|
+
import { imageSnapshotMatcher } from '../match_image_snapshot.ts'
|
|
4
5
|
import { success } from './expectation_result.ts'
|
|
5
6
|
import type { ToMatchImageSnapshotOptions } from './to_match_image_snapshot.types.ts'
|
|
6
7
|
|
|
@@ -24,5 +25,5 @@ export function toMatchImageSnapshot(
|
|
|
24
25
|
)
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
return
|
|
28
|
+
return imageSnapshotMatcher(commands)(test, subject, options).then(() => success)
|
|
28
29
|
}
|
|
@@ -11,4 +11,10 @@ export interface ImageSnapshotMatcher {
|
|
|
11
11
|
export interface ToMatchImageSnapshotOptions
|
|
12
12
|
extends ImageSnapshotTimeoutOptions,
|
|
13
13
|
ImageSnapshotIdOptions,
|
|
14
|
-
ImageSnapshotCompareOptions {
|
|
14
|
+
ImageSnapshotCompareOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Expect the matcher to fail.
|
|
17
|
+
* If it passes, it will throw an error with details.
|
|
18
|
+
*/
|
|
19
|
+
expectToFail?: boolean | undefined
|
|
20
|
+
}
|
|
@@ -4,7 +4,7 @@ export function prettifyOptions(options: ToMatchImageSnapshotOptions | undefined
|
|
|
4
4
|
if (!options) return 'none'
|
|
5
5
|
|
|
6
6
|
return [
|
|
7
|
-
`failureThreshold: ${options.failureThreshold ?? 0} ${options.failureThresholdType ?? '
|
|
7
|
+
`failureThreshold: ${options.failureThreshold ?? 0} ${options.failureThresholdType ?? 'pixels'}`,
|
|
8
8
|
options.diffOptions ? `diffOptions: ${JSON.stringify(options.diffOptions)}` : '',
|
|
9
9
|
options.timeout ? `timeout: ${options.timeout} ms` : '',
|
|
10
10
|
]
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { commands } from '@vitest/browser/context'
|
|
2
1
|
import dedent from 'dedent'
|
|
3
2
|
import { resolve } from 'pathe'
|
|
4
3
|
import type { Task } from 'vitest'
|
|
4
|
+
import type { ImageSnapshotNextIndexCommand } from '../commands.ts'
|
|
5
|
+
import type { PrepareImageSnapshotComparisonCommand } from '../server/commands/prepare_image_snapshot_comparison.ts'
|
|
6
|
+
import type { WriteImageSnapshotCommand } from '../server/commands/write_image_snapshot.ts'
|
|
5
7
|
import { isBase64String } from '../shared/base64.ts'
|
|
8
|
+
import { compareImage } from '../shared/compare_image.ts'
|
|
6
9
|
import { alignImagesToSameSize } from './align_images.ts'
|
|
7
|
-
import { compareImage } from './compare_image.ts'
|
|
8
10
|
import type { ToMatchImageSnapshotOptions } from './expect/to_match_image_snapshot.types.ts'
|
|
9
11
|
import { toDataURL, toImageData } from './image_data.ts'
|
|
10
12
|
import { prettifyOptions } from './match_image_snapshot.logic.ts'
|
|
@@ -12,45 +14,71 @@ import { convertElementToCssSelector } from './selector.ts'
|
|
|
12
14
|
import { toTaskId } from './task_id.ts'
|
|
13
15
|
import { server } from './vitest_browser_context_proxy.ts'
|
|
14
16
|
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
export function imageSnapshotMatcher(
|
|
18
|
+
commands: PrepareImageSnapshotComparisonCommand & WriteImageSnapshotCommand & ImageSnapshotNextIndexCommand,
|
|
19
|
+
) {
|
|
20
|
+
return async function matchImageSnapshot(test: Task, subject: any, options?: ToMatchImageSnapshotOptions) {
|
|
21
|
+
const taskId = toTaskId(test)
|
|
22
|
+
const info = await commands.prepareImageSnapshotComparison(
|
|
23
|
+
taskId,
|
|
24
|
+
parseImageSnapshotSubject(subject),
|
|
25
|
+
options?.customizeSnapshotId ? await parseImageSnapshotOptions(commands, taskId, options) : options,
|
|
26
|
+
)
|
|
22
27
|
|
|
23
|
-
|
|
28
|
+
if (!info) return
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
const baselineImage = await toImageData(info.baseline)
|
|
31
|
+
const resultImage = await toImageData(info.result)
|
|
32
|
+
const [baselineAlignedImage, resultAlignedImage] = alignImagesToSameSize(baselineImage, resultImage)
|
|
33
|
+
const { width, height } = baselineAlignedImage
|
|
34
|
+
const diffImage = new ImageData(width, height)
|
|
35
|
+
const { pass, diffAmount } = compareImage(
|
|
36
|
+
baselineAlignedImage.data,
|
|
37
|
+
resultAlignedImage.data,
|
|
38
|
+
diffImage.data,
|
|
39
|
+
width,
|
|
40
|
+
height,
|
|
41
|
+
options,
|
|
42
|
+
)
|
|
43
|
+
if (pass) {
|
|
44
|
+
if (options?.expectToFail) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
dedent`Snapshot \`${taskId}\` matched but expected to fail.
|
|
34
47
|
|
|
35
|
-
|
|
48
|
+
Options: ${prettifyOptions(options)}
|
|
49
|
+
DiffAmount: ${options.failureThresholdType === 'percent' ? `${diffAmount}%` : `${diffAmount} pixels`}
|
|
36
50
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
${
|
|
41
|
-
options?.failureThreshold
|
|
42
|
-
? options?.failureThresholdType === 'percent'
|
|
43
|
-
? `Expected image to match within ${options.failureThreshold}% but was differ by ${diffAmount}%.`
|
|
44
|
-
: `Expected image to match within ${options.failureThreshold} pixels but was differ by ${diffAmount} pixels.`
|
|
45
|
-
: `Expected image to match but was differ by ${options?.failureThresholdType === 'percent' ? `${diffAmount}%` : `${diffAmount} pixels`}.`
|
|
51
|
+
Expected: ${resolve(info.projectRoot, info.baselinePath)}
|
|
52
|
+
Actual: ${resolve(info.projectRoot, info.resultPath)}`,
|
|
53
|
+
)
|
|
46
54
|
}
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
if (server.config.snapshotOptions.updateSnapshot === 'all') {
|
|
58
|
+
await writeSnapshot(commands, info.baselinePath, resultImage)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
47
61
|
|
|
48
|
-
|
|
62
|
+
await writeSnapshot(commands, info.diffPath, diffImage)
|
|
49
63
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
64
|
+
throw new Error(
|
|
65
|
+
dedent`Snapshot \`${taskId}\` mismatched
|
|
66
|
+
|
|
67
|
+
${
|
|
68
|
+
options?.failureThreshold
|
|
69
|
+
? options?.failureThresholdType === 'percent'
|
|
70
|
+
? `Expected image to match within ${options.failureThreshold}% but was differ by ${diffAmount}%.`
|
|
71
|
+
: `Expected image to match within ${options.failureThreshold} pixels but was differ by ${diffAmount} pixels.`
|
|
72
|
+
: `Expected image to match but was differ by ${options?.failureThresholdType === 'percent' ? `${diffAmount}%` : `${diffAmount} pixels`}.`
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
Options: ${prettifyOptions(options)}
|
|
76
|
+
|
|
77
|
+
Expected: ${resolve(info.projectRoot, info.baselinePath)}
|
|
78
|
+
Actual: ${resolve(info.projectRoot, info.resultPath)}
|
|
79
|
+
Difference: ${resolve(info.projectRoot, info.diffPath)}`,
|
|
80
|
+
)
|
|
81
|
+
}
|
|
54
82
|
}
|
|
55
83
|
|
|
56
84
|
/**
|
|
@@ -65,12 +93,16 @@ export function parseImageSnapshotSubject(subject: any) {
|
|
|
65
93
|
)
|
|
66
94
|
}
|
|
67
95
|
|
|
68
|
-
async function writeSnapshot(path: string, image: ImageData) {
|
|
96
|
+
async function writeSnapshot(commands: WriteImageSnapshotCommand, path: string, image: ImageData) {
|
|
69
97
|
const content = (await toDataURL(image)).split(',')[1]!
|
|
70
98
|
return commands.writeImageSnapshot(path, content)
|
|
71
99
|
}
|
|
72
100
|
|
|
73
|
-
async function parseImageSnapshotOptions(
|
|
101
|
+
async function parseImageSnapshotOptions(
|
|
102
|
+
commands: ImageSnapshotNextIndexCommand,
|
|
103
|
+
taskId: string,
|
|
104
|
+
options: ToMatchImageSnapshotOptions,
|
|
105
|
+
) {
|
|
74
106
|
const index = await commands.imageSnapshotNextIndex(taskId)
|
|
75
107
|
const { customizeSnapshotId, ...rest } = options
|
|
76
108
|
const snapshotFileId = customizeSnapshotId!(taskId, index)
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { page } from '@vitest/browser/context'
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
declare module '@vitest/browser/context' {
|
|
5
|
-
interface BrowserPage extends HasImageSnapshotAction {}
|
|
6
|
-
}
|
|
2
|
+
import './augment.ts'
|
|
3
|
+
import { hasImageSnapshot } from './has_image_snapshot.ts'
|
|
7
4
|
|
|
8
5
|
page.extend({ hasImageSnapshot })
|
package/src/client/selector.ts
CHANGED
|
@@ -3,20 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @see https://github.com/vitest-dev/vitest/blob/main/packages/browser/src/client/utils.ts
|
|
5
5
|
*/
|
|
6
|
-
import type { Locator } from '@vitest/browser/context'
|
|
7
6
|
|
|
8
|
-
export function convertToSelector(elementOrLocator: Element | Locator): string {
|
|
9
|
-
if (!elementOrLocator) {
|
|
10
|
-
throw new Error('Expected element or locator to be defined.')
|
|
11
|
-
}
|
|
12
|
-
if (elementOrLocator instanceof Element) {
|
|
13
|
-
return convertElementToCssSelector(elementOrLocator)
|
|
14
|
-
}
|
|
15
|
-
if ('selector' in elementOrLocator) {
|
|
16
|
-
return (elementOrLocator as any).selector
|
|
17
|
-
}
|
|
18
|
-
throw new Error('Expected element or locator to be an instance of Element or Locator.')
|
|
19
|
-
}
|
|
20
7
|
export function convertElementToCssSelector(element: Element) {
|
|
21
8
|
if (!element || !(element instanceof Element)) {
|
|
22
9
|
throw new Error(`Expected DOM element to be an instance of Element, received ${typeof element}`)
|
package/src/commands.ts
CHANGED
|
@@ -2,3 +2,5 @@ export * from './server/commands/has_image_snapshot.ts'
|
|
|
2
2
|
export * from './server/commands/image_snapshot_next_index.ts'
|
|
3
3
|
export * from './server/commands/match_image_snapshot.ts'
|
|
4
4
|
export * from './server/commands/setup_vis_suite.ts'
|
|
5
|
+
export * from './server/commands/prepare_image_snapshot_comparison.ts'
|
|
6
|
+
export * from './server/commands/write_image_snapshot.ts'
|
package/src/index.ts
CHANGED
package/src/presets/auto.ts
CHANGED
package/src/presets/manual.ts
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { isAbsolute } from 'pathe'
|
|
2
|
+
import type { RequiredPick } from 'type-plus'
|
|
3
|
+
import type { BrowserCommandContext } from 'vitest/node'
|
|
4
|
+
|
|
5
|
+
export function assertTestPathDefined(
|
|
6
|
+
context: BrowserCommandContext,
|
|
7
|
+
commandName: string,
|
|
8
|
+
): asserts context is RequiredPick<BrowserCommandContext, 'testPath'> {
|
|
9
|
+
if (!context.testPath) {
|
|
10
|
+
throw new Error(`'commands.${commandName}' requires testPath to be defined`)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function assertIsRelativePath(relativeFilePath: string, propName: string) {
|
|
15
|
+
if (isAbsolute(relativeFilePath)) {
|
|
16
|
+
throw new Error(`'${propName}' must be a relative path`)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BrowserCommand } from 'vitest/node'
|
|
2
2
|
import { visContext } from '../vis_context.ts'
|
|
3
|
+
import { assertTestPathDefined } from './_assertions.ts'
|
|
3
4
|
|
|
4
5
|
export interface HasImageSnapshotCommand {
|
|
5
6
|
hasImageSnapshot(taskId: string, snapshotId?: string | undefined): Promise<boolean>
|
|
@@ -10,9 +11,7 @@ export const hasImageSnapshot: BrowserCommand<[taskId: string, snapshotId?: stri
|
|
|
10
11
|
taskId,
|
|
11
12
|
snapshotId,
|
|
12
13
|
) => {
|
|
13
|
-
|
|
14
|
-
throw new Error('Cannot take snapshot without testPath')
|
|
15
|
-
}
|
|
14
|
+
assertTestPathDefined(context, 'hasImageSnapshot')
|
|
16
15
|
|
|
17
16
|
return visContext.hasImageSnapshot(context.testPath, taskId, snapshotId)
|
|
18
17
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BrowserCommand } from 'vitest/node'
|
|
2
2
|
import { visContext } from '../vis_context.ts'
|
|
3
|
+
import { assertTestPathDefined } from './_assertions.ts'
|
|
3
4
|
|
|
4
5
|
export interface ImageSnapshotNextIndexCommand {
|
|
5
6
|
/**
|
|
@@ -9,9 +10,7 @@ export interface ImageSnapshotNextIndexCommand {
|
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export const imageSnapshotNextIndex: BrowserCommand<[taskId: string]> = async (context, taskId) => {
|
|
12
|
-
|
|
13
|
-
throw new Error('Cannot take snapshot without testPath')
|
|
14
|
-
}
|
|
13
|
+
assertTestPathDefined(context, 'imageSnapshotNextIndex')
|
|
15
14
|
|
|
16
15
|
return visContext.getTaskCount(context.testPath, taskId)
|
|
17
16
|
}
|
|
@@ -4,6 +4,7 @@ import { resolve } from 'pathe'
|
|
|
4
4
|
import { PNG } from 'pngjs'
|
|
5
5
|
import type { BrowserCommand } from 'vitest/node'
|
|
6
6
|
import { isBase64String } from '../../shared/base64.ts'
|
|
7
|
+
import { compareImage } from '../../shared/compare_image.ts'
|
|
7
8
|
import { getMaxSize } from '../../shared/get_max_size.ts'
|
|
8
9
|
import { isSameSize } from '../../shared/is_same_size.ts'
|
|
9
10
|
import type {
|
|
@@ -11,10 +12,10 @@ import type {
|
|
|
11
12
|
ImageSnapshotIdOptions,
|
|
12
13
|
ImageSnapshotTimeoutOptions,
|
|
13
14
|
} from '../../shared/types.ts'
|
|
14
|
-
import { compareImage } from '../compare_image.ts'
|
|
15
15
|
import { file } from '../file.ts'
|
|
16
16
|
import { takeSnapshot, takeSnapshotByBrowser, writeSnapshot, writeSnapshotBuffer } from '../snapshot.ts'
|
|
17
17
|
import { visContext } from '../vis_context.ts'
|
|
18
|
+
import { assertTestPathDefined } from './_assertions.ts'
|
|
18
19
|
|
|
19
20
|
export interface MatchImageSnapshotCommand {
|
|
20
21
|
matchImageSnapshot: (
|
|
@@ -37,9 +38,7 @@ export interface MatchImageSnapshotOptions
|
|
|
37
38
|
export const matchImageSnapshot: BrowserCommand<
|
|
38
39
|
[taskId: string, subject: string, options?: MatchImageSnapshotOptions | undefined]
|
|
39
40
|
> = async (context, taskId, subject, options) => {
|
|
40
|
-
|
|
41
|
-
throw new Error('Cannot match snapshot without testPath')
|
|
42
|
-
}
|
|
41
|
+
assertTestPathDefined(context, 'matchImageSnapshot')
|
|
43
42
|
|
|
44
43
|
// vitest:browser passes in `null` when not defined
|
|
45
44
|
if (!options) options = {}
|
|
@@ -60,8 +59,16 @@ export const matchImageSnapshot: BrowserCommand<
|
|
|
60
59
|
const baselineImage = PNG.sync.read(baselineBuffer, { skipRescale: true, checkCRC: false })
|
|
61
60
|
const resultImage = PNG.sync.read(resultBuffer, { skipRescale: true, checkCRC: false })
|
|
62
61
|
const [baselineAlignedImage, resultAlignedImage] = alignImageSizes(baselineImage, resultImage)
|
|
63
|
-
|
|
64
|
-
const
|
|
62
|
+
const { width, height } = baselineAlignedImage
|
|
63
|
+
const diffImage = new PNG({ width, height })
|
|
64
|
+
const { pass, diffAmount } = compareImage(
|
|
65
|
+
baselineAlignedImage.data,
|
|
66
|
+
resultAlignedImage.data,
|
|
67
|
+
diffImage.data,
|
|
68
|
+
width,
|
|
69
|
+
height,
|
|
70
|
+
options,
|
|
71
|
+
)
|
|
65
72
|
if (pass) {
|
|
66
73
|
if (sizeNotChanged(baselineImage, baselineAlignedImage) && sizeNotChanged(resultImage, resultAlignedImage)) {
|
|
67
74
|
return
|
|
@@ -4,6 +4,7 @@ import { isBase64String } from '../../shared/base64.ts'
|
|
|
4
4
|
import { file } from '../file.ts'
|
|
5
5
|
import { takeSnapshot, takeSnapshotByBrowser, writeSnapshot } from '../snapshot.ts'
|
|
6
6
|
import { visContext } from '../vis_context.ts'
|
|
7
|
+
import { assertTestPathDefined } from './_assertions.ts'
|
|
7
8
|
import type { MatchImageSnapshotOptions } from './match_image_snapshot.ts'
|
|
8
9
|
|
|
9
10
|
type ImageSnapshotComparisonInfo = {
|
|
@@ -26,9 +27,7 @@ export interface PrepareImageSnapshotComparisonCommand {
|
|
|
26
27
|
export const prepareImageSnapshotComparison: BrowserCommand<
|
|
27
28
|
[taskId: string, snapshotId: string, options?: MatchImageSnapshotOptions | undefined]
|
|
28
29
|
> = async (context, taskId, subject, options) => {
|
|
29
|
-
|
|
30
|
-
throw new Error('Cannot match snapshot without testPath')
|
|
31
|
-
}
|
|
30
|
+
assertTestPathDefined(context, 'prepareImageSnapshotComparison')
|
|
32
31
|
|
|
33
32
|
// vitest:browser passes in `null` when not defined
|
|
34
33
|
if (!options) options = {}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BrowserCommand } from 'vitest/node'
|
|
2
2
|
import { visContext } from '../vis_context.ts'
|
|
3
|
+
import { assertTestPathDefined } from './_assertions.ts'
|
|
3
4
|
|
|
4
5
|
export interface SetupVisSuiteCommand {
|
|
5
6
|
/**
|
|
@@ -13,5 +14,7 @@ export interface SetupVisSuiteCommand {
|
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export const setupVisSuite: BrowserCommand<[]> = async (context) => {
|
|
17
|
+
assertTestPathDefined(context, 'setupVisSuite')
|
|
18
|
+
|
|
16
19
|
await visContext.setupSuite(context)
|
|
17
20
|
}
|
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import { mkdirp } from 'mkdirp'
|
|
2
|
-
import { dirname } from 'pathe'
|
|
2
|
+
import { dirname, isAbsolute } from 'pathe'
|
|
3
3
|
import type { BrowserCommand } from 'vitest/node'
|
|
4
4
|
import { file } from '../file.ts'
|
|
5
|
+
import { assertIsRelativePath } from './_assertions.ts'
|
|
5
6
|
|
|
6
7
|
export interface WriteImageSnapshotCommand {
|
|
7
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Writes the image snapshot to the file system.
|
|
10
|
+
* @param relativeFilePath The file path to write the image snapshot to.
|
|
11
|
+
* It is relative to the project root.
|
|
12
|
+
* @param imageBase64 The base64 encoded image.
|
|
13
|
+
*/
|
|
14
|
+
writeImageSnapshot(relativeFilePath: string, imageBase64: string): Promise<void>
|
|
8
15
|
}
|
|
9
16
|
|
|
10
|
-
export const writeImageSnapshot: BrowserCommand<[
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
export const writeImageSnapshot: BrowserCommand<[relativeFilePath: string, imageBase64: string]> = async (
|
|
18
|
+
_,
|
|
19
|
+
relativeFilePath,
|
|
20
|
+
imageBase64,
|
|
14
21
|
) => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
await
|
|
19
|
-
await file.writeFile(filePath, base64, { encoding: 'base64' })
|
|
22
|
+
assertIsRelativePath(relativeFilePath, 'relativeFilePath')
|
|
23
|
+
|
|
24
|
+
await mkdirp(dirname(relativeFilePath))
|
|
25
|
+
await file.writeFile(relativeFilePath, imageBase64, { encoding: 'base64' })
|
|
20
26
|
}
|
|
@@ -17,7 +17,7 @@ export type PartialBrowserCommandContext = {
|
|
|
17
17
|
hookTimeout: number
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
testPath: string
|
|
20
|
+
testPath: string
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export function createVisContext() {
|
|
@@ -51,7 +51,7 @@ export function createVisContext() {
|
|
|
51
51
|
await globalStateReady
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
const { suiteId, suite } = createSuite(state, context.testPath
|
|
54
|
+
const { suiteId, suite } = createSuite(state, context.testPath, visOptions)
|
|
55
55
|
state.suites[suiteId] = suite
|
|
56
56
|
await Promise.allSettled([ctx.rimraf(suite.diffDir), ctx.rimraf(suite.resultDir)])
|
|
57
57
|
},
|
|
@@ -123,10 +123,6 @@ async function setupState(suite: PartialBrowserCommandContext, visOptions: VisOp
|
|
|
123
123
|
return state
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
export function getSuiteId(state: VisState, testPath: string, options: VisOptions) {
|
|
127
|
-
return getSnapshotSubpath(relative(state.projectPath, testPath), options)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
126
|
export function createSuite(state: VisState, testPath: string, options: VisOptions) {
|
|
131
127
|
const suiteId = getSuiteId(state, testPath, options)
|
|
132
128
|
return {
|
|
@@ -139,3 +135,7 @@ export function createSuite(state: VisState, testPath: string, options: VisOptio
|
|
|
139
135
|
},
|
|
140
136
|
}
|
|
141
137
|
}
|
|
138
|
+
|
|
139
|
+
export function getSuiteId(state: VisState, testPath: string, options: VisOptions) {
|
|
140
|
+
return getSnapshotSubpath(relative(state.projectPath, testPath), options)
|
|
141
|
+
}
|