vitest-plugin-vis 1.0.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/ctx.d.ts +6 -0
- package/esm/client/ctx.d.ts.map +1 -0
- package/esm/client/ctx.js +7 -0
- package/esm/client/expect/expectation_result.d.ts +3 -0
- package/esm/client/expect/expectation_result.d.ts.map +1 -0
- package/esm/client/expect/expectation_result.js +5 -0
- package/esm/client/expect/extend.d.ts +8 -0
- package/esm/client/expect/extend.d.ts.map +1 -0
- package/esm/client/expect/extend.js +3 -0
- package/esm/client/expect/to_match_image_snapshot.d.ts +9 -0
- package/esm/client/expect/to_match_image_snapshot.d.ts.map +1 -0
- package/esm/client/expect/to_match_image_snapshot.js +32 -0
- package/esm/client/expect/to_match_image_snapshot.logic.d.ts +2 -0
- package/esm/client/expect/to_match_image_snapshot.logic.d.ts.map +1 -0
- package/esm/client/expect/to_match_image_snapshot.logic.js +11 -0
- package/esm/client/expect/to_match_image_snapshot.types.d.ts +7 -0
- package/esm/client/expect/to_match_image_snapshot.types.d.ts.map +1 -0
- package/esm/client/expect/to_match_image_snapshot.types.js +1 -0
- package/esm/client/page/extend.d.ts +6 -0
- package/esm/client/page/extend.d.ts.map +1 -0
- package/esm/client/page/extend.js +3 -0
- package/esm/client/page/has_image_snapshot.d.ts +10 -0
- package/esm/client/page/has_image_snapshot.d.ts.map +1 -0
- package/esm/client/page/has_image_snapshot.js +23 -0
- package/esm/client/selector.d.ts +9 -0
- package/esm/client/selector.d.ts.map +1 -0
- package/esm/client/selector.js +104 -0
- package/esm/client/should_take_snapshot.d.ts +10 -0
- package/esm/client/should_take_snapshot.d.ts.map +1 -0
- package/esm/client/should_take_snapshot.js +13 -0
- package/esm/client/snapshot_options.d.ts +34 -0
- package/esm/client/snapshot_options.d.ts.map +1 -0
- package/esm/client/snapshot_options.internal.d.ts +3 -0
- package/esm/client/snapshot_options.internal.d.ts.map +1 -0
- package/esm/client/snapshot_options.internal.js +20 -0
- package/esm/client/snapshot_options.js +16 -0
- package/esm/client/task_id.d.ts +7 -0
- package/esm/client/task_id.d.ts.map +1 -0
- package/esm/client/task_id.js +10 -0
- package/esm/client.d.ts +8 -0
- package/esm/client.d.ts.map +1 -0
- package/esm/client.js +5 -0
- package/esm/commands.d.ts +5 -0
- package/esm/commands.d.ts.map +1 -0
- package/esm/commands.js +4 -0
- package/esm/config/types.d.ts +35 -0
- package/esm/config/types.d.ts.map +1 -0
- package/esm/config/types.js +1 -0
- package/esm/config/vis.d.ts +25 -0
- package/esm/config/vis.d.ts.map +1 -0
- package/esm/config/vis.js +27 -0
- package/esm/config.d.ts +4 -0
- package/esm/config.d.ts.map +1 -0
- package/esm/config.js +2 -0
- package/esm/index.d.ts +2 -0
- package/esm/index.d.ts.map +1 -0
- package/esm/index.js +2 -0
- package/esm/presets/auto.d.ts +2 -0
- package/esm/presets/auto.d.ts.map +1 -0
- package/esm/presets/auto.js +3 -0
- package/esm/presets/manual.d.ts +2 -0
- package/esm/presets/manual.d.ts.map +1 -0
- package/esm/presets/manual.js +3 -0
- package/esm/server/browser_provider/browser_api.d.ts +3 -0
- package/esm/server/browser_provider/browser_api.d.ts.map +1 -0
- package/esm/server/browser_provider/browser_api.js +11 -0
- package/esm/server/browser_provider/playwright.d.ts +4 -0
- package/esm/server/browser_provider/playwright.d.ts.map +1 -0
- package/esm/server/browser_provider/playwright.js +13 -0
- package/esm/server/browser_provider/types.d.ts +6 -0
- package/esm/server/browser_provider/types.d.ts.map +1 -0
- package/esm/server/browser_provider/types.js +1 -0
- package/esm/server/browser_provider/webdriverio.d.ts +4 -0
- package/esm/server/browser_provider/webdriverio.d.ts.map +1 -0
- package/esm/server/browser_provider/webdriverio.js +10 -0
- package/esm/server/commands/extend.d.ts +15 -0
- package/esm/server/commands/extend.d.ts.map +1 -0
- package/esm/server/commands/extend.js +10 -0
- package/esm/server/commands/has_image_snapshot.d.ts +6 -0
- package/esm/server/commands/has_image_snapshot.d.ts.map +1 -0
- package/esm/server/commands/has_image_snapshot.js +7 -0
- package/esm/server/commands/image_snapshot_next_index.d.ts +9 -0
- package/esm/server/commands/image_snapshot_next_index.d.ts.map +1 -0
- package/esm/server/commands/image_snapshot_next_index.js +7 -0
- package/esm/server/commands/is_ci.d.ts +2 -0
- package/esm/server/commands/is_ci.d.ts.map +1 -0
- package/esm/server/commands/is_ci.js +6 -0
- package/esm/server/commands/match_image_snapshot.d.ts +17 -0
- package/esm/server/commands/match_image_snapshot.d.ts.map +1 -0
- package/esm/server/commands/match_image_snapshot.js +88 -0
- package/esm/server/commands/setup_vis_suite.d.ts +13 -0
- package/esm/server/commands/setup_vis_suite.d.ts.map +1 -0
- package/esm/server/commands/setup_vis_suite.js +4 -0
- package/esm/server/compare_image.d.ts +8 -0
- package/esm/server/compare_image.d.ts.map +1 -0
- package/esm/server/compare_image.js +16 -0
- package/esm/server/file.d.ts +6 -0
- package/esm/server/file.d.ts.map +1 -0
- package/esm/server/file.js +14 -0
- package/esm/server/snapshot_path.d.ts +4 -0
- package/esm/server/snapshot_path.d.ts.map +1 -0
- package/esm/server/snapshot_path.js +9 -0
- package/esm/server/vis_context.ctx.d.ts +29 -0
- package/esm/server/vis_context.ctx.d.ts.map +1 -0
- package/esm/server/vis_context.ctx.js +11 -0
- package/esm/server/vis_context.d.ts +33 -0
- package/esm/server/vis_context.d.ts.map +1 -0
- package/esm/server/vis_context.js +2 -0
- package/esm/server/vis_context.logic.d.ts +63 -0
- package/esm/server/vis_context.logic.d.ts.map +1 -0
- package/esm/server/vis_context.logic.js +116 -0
- package/esm/server/vis_context.types.d.ts +20 -0
- package/esm/server/vis_context.types.d.ts.map +1 -0
- package/esm/server/vis_context.types.js +1 -0
- package/esm/setup/create_vis.d.ts +39 -0
- package/esm/setup/create_vis.d.ts.map +1 -0
- package/esm/setup/create_vis.js +96 -0
- package/esm/setup/vis.d.ts +18 -0
- package/esm/setup/vis.d.ts.map +1 -0
- package/esm/setup/vis.js +6 -0
- package/esm/setup.d.ts +5 -0
- package/esm/setup.d.ts.map +1 -0
- package/esm/setup.js +7 -0
- package/esm/shared/base64.d.ts +2 -0
- package/esm/shared/base64.d.ts.map +1 -0
- package/esm/shared/base64.js +3 -0
- package/esm/shared/constants.d.ts +5 -0
- package/esm/shared/constants.d.ts.map +1 -0
- package/esm/shared/constants.js +4 -0
- package/esm/shared/convert_threshold_unit.d.ts +6 -0
- package/esm/shared/convert_threshold_unit.d.ts.map +1 -0
- package/esm/shared/convert_threshold_unit.js +10 -0
- package/esm/shared/get_max_size.d.ts +11 -0
- package/esm/shared/get_max_size.d.ts.map +1 -0
- package/esm/shared/get_max_size.js +5 -0
- package/esm/shared/is_same_size.d.ts +8 -0
- package/esm/shared/is_same_size.d.ts.map +1 -0
- package/esm/shared/is_same_size.js +3 -0
- package/esm/shared/trim_common_folder.d.ts +6 -0
- package/esm/shared/trim_common_folder.d.ts.map +1 -0
- package/esm/shared/trim_common_folder.js +10 -0
- package/esm/shared/types.d.ts +37 -0
- package/esm/shared/types.d.ts.map +1 -0
- package/esm/shared/types.js +1 -0
- package/esm/testing/constants.d.ts +9 -0
- package/esm/testing/constants.d.ts.map +1 -0
- package/esm/testing/constants.js +1933 -0
- package/esm/testing.d.ts +2 -0
- package/esm/testing.d.ts.map +1 -0
- package/esm/testing.js +1 -0
- package/package.json +115 -0
- package/readme.md +264 -0
- package/src/client/ctx.ts +8 -0
- package/src/client/expect/expectation_result.ts +7 -0
- package/src/client/expect/extend.ts +12 -0
- package/src/client/expect/to_match_image_snapshot.logic.ts +11 -0
- package/src/client/expect/to_match_image_snapshot.ts +43 -0
- package/src/client/expect/to_match_image_snapshot.types.ts +14 -0
- package/src/client/page/extend.ts +8 -0
- package/src/client/page/has_image_snapshot.ts +33 -0
- package/src/client/selector.ts +118 -0
- package/src/client/should_take_snapshot.ts +15 -0
- package/src/client/snapshot_options.internal.ts +22 -0
- package/src/client/snapshot_options.ts +53 -0
- package/src/client/task_id.ts +16 -0
- package/src/client.ts +7 -0
- package/src/commands.ts +4 -0
- package/src/config/readme.md +4 -0
- package/src/config/types.ts +35 -0
- package/src/config/vis.ts +30 -0
- package/src/config.ts +3 -0
- package/src/index.ts +2 -0
- package/src/presets/auto.ts +4 -0
- package/src/presets/manual.ts +4 -0
- package/src/server/browser_provider/browser_api.ts +13 -0
- package/src/server/browser_provider/playwright.ts +16 -0
- package/src/server/browser_provider/types.ts +11 -0
- package/src/server/browser_provider/webdriverio.ts +13 -0
- package/src/server/commands/extend.ts +19 -0
- package/src/server/commands/has_image_snapshot.ts +18 -0
- package/src/server/commands/image_snapshot_next_index.ts +17 -0
- package/src/server/commands/is_ci.ts +7 -0
- package/src/server/commands/match_image_snapshot.ts +139 -0
- package/src/server/commands/setup_vis_suite.ts +17 -0
- package/src/server/compare_image.ts +34 -0
- package/src/server/file.ts +15 -0
- package/src/server/readme.md +5 -0
- package/src/server/snapshot_path.ts +12 -0
- package/src/server/vis_context.ctx.ts +12 -0
- package/src/server/vis_context.logic.ts +141 -0
- package/src/server/vis_context.ts +3 -0
- package/src/server/vis_context.types.ts +25 -0
- package/src/setup/create_vis.ts +98 -0
- package/src/setup/vis.ts +7 -0
- package/src/setup.ts +9 -0
- package/src/shared/base64.ts +3 -0
- package/src/shared/constants.ts +4 -0
- package/src/shared/convert_threshold_unit.ts +13 -0
- package/src/shared/get_max_size.ts +5 -0
- package/src/shared/is_same_size.ts +3 -0
- package/src/shared/trim_common_folder.ts +10 -0
- package/src/shared/types.ts +39 -0
- package/src/testing/constants.ts +1936 -0
- package/src/testing.ts +1 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NAME } from '../shared/constants.ts'
|
|
2
|
+
import type { MetaTask, SnapshotMeta } from './snapshot_options.ts'
|
|
3
|
+
|
|
4
|
+
export function getAutoSnapshotOptions(task: MetaTask): SnapshotMeta | undefined {
|
|
5
|
+
if (!task) return
|
|
6
|
+
|
|
7
|
+
const list: any[] = []
|
|
8
|
+
let current = task
|
|
9
|
+
while (current?.suite) {
|
|
10
|
+
list.unshift(current.suite.meta)
|
|
11
|
+
current = current.suite
|
|
12
|
+
}
|
|
13
|
+
list.unshift(task.file?.meta)
|
|
14
|
+
list.push(task.meta)
|
|
15
|
+
return list.reduce((acc, cur) => {
|
|
16
|
+
if (cur?.[NAME]) {
|
|
17
|
+
const { [NAME]: meta } = cur
|
|
18
|
+
return Object.assign({}, acc, meta)
|
|
19
|
+
}
|
|
20
|
+
return acc
|
|
21
|
+
}, {})
|
|
22
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { NAME } from '../shared/constants.ts'
|
|
2
|
+
import { ctx } from './ctx.ts'
|
|
3
|
+
import type { ToMatchImageSnapshotOptions } from './expect/to_match_image_snapshot.types.ts'
|
|
4
|
+
|
|
5
|
+
export type SnapshotMeta = ToMatchImageSnapshotOptions & { enable?: boolean | undefined }
|
|
6
|
+
|
|
7
|
+
type Suite = { meta: Record<string, any>; suite?: Suite | undefined }
|
|
8
|
+
|
|
9
|
+
export type MetaTask =
|
|
10
|
+
| {
|
|
11
|
+
file?: { meta: Record<string, any> } | undefined
|
|
12
|
+
suite?: Suite | undefined
|
|
13
|
+
meta: Record<string, any>
|
|
14
|
+
}
|
|
15
|
+
| undefined
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Set the snapshot options for auto snapshot.
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* beforeAll((suite) => setAutoSnapshotOptions(suite, ...))
|
|
22
|
+
* beforeEach(({ task }) => setAutoSnapshotOptions(task, ...))
|
|
23
|
+
*
|
|
24
|
+
* it('...', ({ task }) => {
|
|
25
|
+
* setAutoSnapshotOptions(task, ...)
|
|
26
|
+
* })
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @param task Optional. Suite or task to set the options.
|
|
30
|
+
* If not provided, it will set the options for the current test.
|
|
31
|
+
*/
|
|
32
|
+
export function setAutoSnapshotOptions(task: MetaTask, meta: SnapshotMeta | boolean): void
|
|
33
|
+
export function setAutoSnapshotOptions(meta: SnapshotMeta | boolean): void
|
|
34
|
+
export function setAutoSnapshotOptions(
|
|
35
|
+
...args: [task: MetaTask, meta: SnapshotMeta | boolean] | [meta: SnapshotMeta | boolean]
|
|
36
|
+
): void {
|
|
37
|
+
const [task, meta] = parseArgs(args)
|
|
38
|
+
if (task)
|
|
39
|
+
task.meta[NAME] = {
|
|
40
|
+
...task.meta[NAME],
|
|
41
|
+
...meta,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseArgs(
|
|
46
|
+
args: [task: MetaTask, meta: SnapshotMeta | boolean] | [meta: boolean | SnapshotMeta],
|
|
47
|
+
): [MetaTask | undefined, SnapshotMeta] {
|
|
48
|
+
return args.length === 1 ? [ctx.getCurrentTest(), parseMeta(args[0])] : [args[0], parseMeta(args[1])]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function parseMeta(meta: boolean | SnapshotMeta): SnapshotMeta {
|
|
52
|
+
return typeof meta === 'boolean' ? { enable: meta } : { enable: true, ...meta }
|
|
53
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type Task = {
|
|
2
|
+
name: string
|
|
3
|
+
suite?: Task | undefined
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function toTaskId(task: Task) {
|
|
7
|
+
const l: any[] = []
|
|
8
|
+
let t = task
|
|
9
|
+
while (t?.suite) {
|
|
10
|
+
l.unshift(t.suite.name.replace(/[^a-z0-9]/gi, '-').toLowerCase())
|
|
11
|
+
t = t.suite
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
l.push(task.name.replace(/[^a-z0-9]/gi, '-').toLowerCase())
|
|
15
|
+
return l.join('/')
|
|
16
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './client/expect/expectation_result.ts'
|
|
2
|
+
export * from './client/expect/to_match_image_snapshot.logic.ts'
|
|
3
|
+
export type * from './client/expect/to_match_image_snapshot.types.ts'
|
|
4
|
+
export * from './client/should_take_snapshot.ts'
|
|
5
|
+
export * from './client/snapshot_options.ts'
|
|
6
|
+
export * from './client/task_id.ts'
|
|
7
|
+
export type * from './shared/types.ts'
|
package/src/commands.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ToMatchImageSnapshotOptions } from '../client/expect/to_match_image_snapshot.types.ts'
|
|
2
|
+
|
|
3
|
+
export interface VisOptions extends ToMatchImageSnapshotOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Loads the `test.setupFiles` of the specified preset.
|
|
6
|
+
*/
|
|
7
|
+
preset?: 'manual' | 'auto' | 'none' | undefined
|
|
8
|
+
/**
|
|
9
|
+
* The snapshot folder relative to the root of the project.
|
|
10
|
+
*
|
|
11
|
+
* Default: `__vis__`
|
|
12
|
+
*/
|
|
13
|
+
snapshotRootDir?: string | undefined
|
|
14
|
+
/**
|
|
15
|
+
* Customize the snapshot subpath.
|
|
16
|
+
*
|
|
17
|
+
* The snapshot subpath is used along with `snapshotRootDir` to determine the folder of the snapshots:
|
|
18
|
+
*
|
|
19
|
+
* - baseline: `<snapshotRootDir>/<platform>/<snapshotSubpath>/<snapshotId>.png`
|
|
20
|
+
* - result: `<snapshotRootDir>/__results__/<snapshotSubpath>/<snapshotId>.png`
|
|
21
|
+
* - diff: `<snapshotRootDir>/__diffs__/<snapshotSubpath>/<snapshotId>.png`
|
|
22
|
+
*
|
|
23
|
+
* Typically, you place your test files either in a dedicated `tests` folder or in the `src` folder along with your source code.
|
|
24
|
+
* By default, the snapshot subpath is the test file path with that folder removed to reduces nesting of the snapshot folders.
|
|
25
|
+
*
|
|
26
|
+
* If you place your test files in multiple folders,
|
|
27
|
+
* such as in both `tests` and `src` folders,
|
|
28
|
+
* and they might have files with the same name and create conflicting snapshots,
|
|
29
|
+
* you can set this to customize the snapshot subpath.
|
|
30
|
+
*
|
|
31
|
+
* @param subPath - The path of the test file to be used as the snapshot sub path.
|
|
32
|
+
* If you want to keep the full path, you can simply return it.
|
|
33
|
+
*/
|
|
34
|
+
customizeSnapshotSubpath?: (subPath: string) => string
|
|
35
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Plugin } from 'vite'
|
|
2
|
+
import { commands } from '../server/commands/extend.ts'
|
|
3
|
+
import { visContext } from '../server/vis_context.ts'
|
|
4
|
+
import { NAME } from '../shared/constants.ts'
|
|
5
|
+
import type { VisOptions } from './types.ts'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create a Vite plugin for visual testing.
|
|
9
|
+
*
|
|
10
|
+
* If options are not provided, the plugin will use the default options,
|
|
11
|
+
* which enables the `auto` preset.
|
|
12
|
+
*/
|
|
13
|
+
export function vis(options: VisOptions = { preset: 'auto' }) {
|
|
14
|
+
visContext.setOptions(options)
|
|
15
|
+
const preset = options?.preset
|
|
16
|
+
return {
|
|
17
|
+
name: NAME,
|
|
18
|
+
config() {
|
|
19
|
+
return {
|
|
20
|
+
test: {
|
|
21
|
+
browser: {
|
|
22
|
+
name: undefined as unknown as string,
|
|
23
|
+
commands,
|
|
24
|
+
},
|
|
25
|
+
setupFiles: preset && preset !== 'none' ? [`vitest-plugin-vis/presets/${preset}`] : undefined,
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
} satisfies Plugin
|
|
30
|
+
}
|
package/src/config.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BrowserCommandContext } from 'vitest/node'
|
|
2
|
+
import { playwright } from './playwright.ts'
|
|
3
|
+
import { webdriverio } from './webdriverio.ts'
|
|
4
|
+
|
|
5
|
+
export function browserApi(context: BrowserCommandContext) {
|
|
6
|
+
if (context.provider.name === 'playwright') {
|
|
7
|
+
return playwright(context)
|
|
8
|
+
}
|
|
9
|
+
if (context.provider.name === 'webdriverio') {
|
|
10
|
+
return webdriverio(context)
|
|
11
|
+
}
|
|
12
|
+
throw new Error(`Unsupported provider: ${context.provider.name}`)
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BrowserCommandContext } from 'vitest/node'
|
|
2
|
+
import type { BrowserApi } from './types.ts'
|
|
3
|
+
|
|
4
|
+
export function playwright(context: BrowserCommandContext): BrowserApi {
|
|
5
|
+
return {
|
|
6
|
+
async takeScreenshot(filePath, selector, options) {
|
|
7
|
+
// The `Locator` type from `vitest` has less props than the `Locator` in `playwright`
|
|
8
|
+
const subject = context.iframe.locator(selector ?? 'body')
|
|
9
|
+
const buffer = await subject.screenshot({
|
|
10
|
+
timeout: options?.timeout,
|
|
11
|
+
path: filePath,
|
|
12
|
+
})
|
|
13
|
+
return buffer.toString('base64')
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BrowserCommandContext } from 'vitest/node'
|
|
2
|
+
import type { BrowserApi } from './types.ts'
|
|
3
|
+
|
|
4
|
+
export function webdriverio(context: BrowserCommandContext): BrowserApi {
|
|
5
|
+
const page = (context.provider as any).browser!
|
|
6
|
+
return {
|
|
7
|
+
async takeScreenshot(filePath, selector) {
|
|
8
|
+
const element = await page.$(`${selector ?? 'body'}`)
|
|
9
|
+
const buffer = await element.saveScreenshot(filePath)
|
|
10
|
+
return buffer.toString('base64')
|
|
11
|
+
},
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type HasImageSnapshotCommand, hasImageSnapshot } from './has_image_snapshot.ts'
|
|
2
|
+
import { type ImageSnapshotNextIndexCommand, imageSnapshotNextIndex } from './image_snapshot_next_index.ts'
|
|
3
|
+
import { type MatchImageSnapshotCommand, matchImageSnapshot } from './match_image_snapshot.ts'
|
|
4
|
+
import { type SetupVisSuiteCommand, setupVisSuite } from './setup_vis_suite.ts'
|
|
5
|
+
|
|
6
|
+
declare module '@vitest/browser/context' {
|
|
7
|
+
interface BrowserCommands
|
|
8
|
+
extends SetupVisSuiteCommand,
|
|
9
|
+
ImageSnapshotNextIndexCommand,
|
|
10
|
+
HasImageSnapshotCommand,
|
|
11
|
+
MatchImageSnapshotCommand {}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const commands = {
|
|
15
|
+
setupVisSuite,
|
|
16
|
+
imageSnapshotNextIndex,
|
|
17
|
+
hasImageSnapshot,
|
|
18
|
+
matchImageSnapshot,
|
|
19
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { BrowserCommand } from 'vitest/node'
|
|
2
|
+
import { visContext } from '../vis_context.ts'
|
|
3
|
+
|
|
4
|
+
export interface HasImageSnapshotCommand {
|
|
5
|
+
hasImageSnapshot(taskId: string, snapshotId?: string | undefined): Promise<boolean>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const hasImageSnapshot: BrowserCommand<[taskId: string, snapshotId?: string | undefined]> = async (
|
|
9
|
+
context,
|
|
10
|
+
taskId,
|
|
11
|
+
snapshotId,
|
|
12
|
+
) => {
|
|
13
|
+
if (!context.testPath) {
|
|
14
|
+
throw new Error('Cannot take snapshot without testPath')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return visContext.hasImageSnapshot(context.testPath, taskId, snapshotId)
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { BrowserCommand } from 'vitest/node'
|
|
2
|
+
import { visContext } from '../vis_context.ts'
|
|
3
|
+
|
|
4
|
+
export interface ImageSnapshotNextIndexCommand {
|
|
5
|
+
/**
|
|
6
|
+
* Get the index of the snapshot image to be created.
|
|
7
|
+
*/
|
|
8
|
+
imageSnapshotNextIndex(taskId: string): Promise<number>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const imageSnapshotNextIndex: BrowserCommand<[taskId: string]> = async (context, taskId) => {
|
|
12
|
+
if (!context.testPath) {
|
|
13
|
+
throw new Error('Cannot take snapshot without testPath')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return visContext.getTaskCount(context.testPath, taskId)
|
|
17
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import dedent from 'dedent'
|
|
2
|
+
import ci from 'is-ci'
|
|
3
|
+
import { mkdirp } from 'mkdirp'
|
|
4
|
+
import { dirname, resolve } from 'pathe'
|
|
5
|
+
import { PNG } from 'pngjs'
|
|
6
|
+
import type { BrowserCommand, BrowserCommandContext } from 'vitest/node'
|
|
7
|
+
import { isBase64String } from '../../shared/base64.ts'
|
|
8
|
+
import { getMaxSize } from '../../shared/get_max_size.ts'
|
|
9
|
+
import { isSameSize } from '../../shared/is_same_size.ts'
|
|
10
|
+
import type {
|
|
11
|
+
ImageSnapshotCompareOptions,
|
|
12
|
+
ImageSnapshotIdOptions,
|
|
13
|
+
ImageSnapshotTimeoutOptions,
|
|
14
|
+
} from '../../shared/types.ts'
|
|
15
|
+
import { browserApi } from '../browser_provider/browser_api.ts'
|
|
16
|
+
import { compareImage } from '../compare_image.ts'
|
|
17
|
+
import { file } from '../file.ts'
|
|
18
|
+
import { visContext } from '../vis_context.ts'
|
|
19
|
+
|
|
20
|
+
export interface MatchImageSnapshotCommand {
|
|
21
|
+
matchImageSnapshot: (
|
|
22
|
+
taskId: string | undefined,
|
|
23
|
+
subject: string,
|
|
24
|
+
options?: MatchImageSnapshotOptions | undefined,
|
|
25
|
+
) => Promise<void>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface MatchImageSnapshotOptions
|
|
29
|
+
extends ImageSnapshotTimeoutOptions,
|
|
30
|
+
ImageSnapshotIdOptions,
|
|
31
|
+
ImageSnapshotCompareOptions {
|
|
32
|
+
/**
|
|
33
|
+
* The snapshot file id calculated on the client side.
|
|
34
|
+
*/
|
|
35
|
+
snapshotFileId?: string | undefined
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const matchImageSnapshot: BrowserCommand<
|
|
39
|
+
[taskId: string, subject: string, options?: MatchImageSnapshotOptions | undefined]
|
|
40
|
+
> = async (context, taskId, subject, options) => {
|
|
41
|
+
if (!context.testPath) {
|
|
42
|
+
throw new Error('Cannot match snapshot without testPath')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// vitest:browser passes in `null` when not defined
|
|
46
|
+
if (!options) options = {}
|
|
47
|
+
options.timeout = options.timeout ?? (ci ? 30000 : 5000)
|
|
48
|
+
|
|
49
|
+
const info = visContext.getSnapshotInfo(context.testPath, taskId, options)
|
|
50
|
+
const baselineBase64 = await file.tryReadFileBase64(info.baselinePath)
|
|
51
|
+
if (!baselineBase64) {
|
|
52
|
+
await takeSnapshot(context, subject, info.baselinePath, options)
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const resultBase64 = await takeSnapshot(context, subject, info.resultPath, options)
|
|
57
|
+
const baselineImage = PNG.sync.read(Buffer.from(baselineBase64, 'base64'))
|
|
58
|
+
const resultImage = PNG.sync.read(Buffer.from(resultBase64, 'base64'))
|
|
59
|
+
const [baselineAlignedImage, resultAlignedImage] = isSameSize(baselineImage, resultImage)
|
|
60
|
+
? [baselineImage, resultImage]
|
|
61
|
+
: alignImageSizes(baselineImage, resultImage)
|
|
62
|
+
|
|
63
|
+
const { pass, diffAmount, diffImage } = compareImage(baselineAlignedImage, resultAlignedImage, options)
|
|
64
|
+
if (pass) {
|
|
65
|
+
if (sizeNotChanged(baselineImage, baselineAlignedImage)) {
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
throw new Error(
|
|
69
|
+
dedent`Snapshot \`${taskId}\` mismatched
|
|
70
|
+
|
|
71
|
+
The image size changed form ${baselineImage.width}x${baselineImage.height} to ${resultImage.width}x${resultImage.height}
|
|
72
|
+
|
|
73
|
+
Expected: ${resolve(context.project.runner.root, info.baselinePath)}
|
|
74
|
+
Actual: ${resolve(context.project.runner.root, info.resultPath)}`,
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const diffBase64 = PNG.sync.write(diffImage).toString('base64')
|
|
79
|
+
await writeSnapshot(diffBase64, info.diffPath)
|
|
80
|
+
|
|
81
|
+
throw new Error(
|
|
82
|
+
dedent`Snapshot \`${taskId}\` mismatched
|
|
83
|
+
|
|
84
|
+
${
|
|
85
|
+
options?.failureThreshold
|
|
86
|
+
? options?.failureThresholdType === 'percent'
|
|
87
|
+
? `Expected image to match within ${options.failureThreshold}% but was differ by ${diffAmount}%.`
|
|
88
|
+
: `Expected image to match within ${options.failureThreshold} pixels but was differ by ${diffAmount} pixels.`
|
|
89
|
+
: `Expected image to match but was differ by ${options?.failureThresholdType === 'percent' ? `${diffAmount}%` : `${diffAmount} pixels`}.`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
Options: ${JSON.stringify(options)}
|
|
93
|
+
|
|
94
|
+
Expected: ${resolve(context.project.runner.root, info.baselinePath)}
|
|
95
|
+
Actual: ${resolve(context.project.runner.root, info.resultPath)}
|
|
96
|
+
Difference: ${resolve(context.project.runner.root, info.diffPath)}`,
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function takeSnapshot(
|
|
101
|
+
context: BrowserCommandContext,
|
|
102
|
+
subject: string,
|
|
103
|
+
filePath: string,
|
|
104
|
+
options: ImageSnapshotTimeoutOptions | undefined,
|
|
105
|
+
) {
|
|
106
|
+
if (isBase64String(subject)) {
|
|
107
|
+
return writeSnapshot(subject, filePath)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await mkdirp(dirname(filePath))
|
|
111
|
+
const browser = browserApi(context)
|
|
112
|
+
return browser.takeScreenshot(filePath, subject, {
|
|
113
|
+
timeout: options?.timeout,
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function writeSnapshot(subject: string, filePath: string) {
|
|
118
|
+
await mkdirp(dirname(filePath))
|
|
119
|
+
await file.writeFileBase64(filePath, subject)
|
|
120
|
+
return subject
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function alignImageSizes(baseline: PNG, result: PNG) {
|
|
124
|
+
const size = getMaxSize(baseline, result)
|
|
125
|
+
|
|
126
|
+
const baselineAligned = new PNG(size)
|
|
127
|
+
const resultAligned = new PNG(size)
|
|
128
|
+
|
|
129
|
+
baselineAligned.data.fill(0)
|
|
130
|
+
resultAligned.data.fill(0)
|
|
131
|
+
|
|
132
|
+
PNG.bitblt(baseline, baselineAligned, 0, 0, baseline.width, baseline.height)
|
|
133
|
+
PNG.bitblt(result, resultAligned, 0, 0, result.width, result.height)
|
|
134
|
+
|
|
135
|
+
return [baselineAligned, resultAligned] as const
|
|
136
|
+
}
|
|
137
|
+
function sizeNotChanged(baselineImage: PNG, baselineAlignedImage: PNG) {
|
|
138
|
+
return baselineImage === baselineAlignedImage
|
|
139
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { BrowserCommand } from 'vitest/node'
|
|
2
|
+
import { visContext } from '../vis_context.ts'
|
|
3
|
+
|
|
4
|
+
export interface SetupVisSuiteCommand {
|
|
5
|
+
/**
|
|
6
|
+
* Sets up the visual testing suite by cleaning up previous snapshot and diff output directories.
|
|
7
|
+
* When this command is run for the first time,
|
|
8
|
+
* it will remove the results and diffs directory of all suites.
|
|
9
|
+
*
|
|
10
|
+
* In subsequent runs, it will only remove the results and diffs directory of the current suite.
|
|
11
|
+
*/
|
|
12
|
+
setupVisSuite: () => Promise<void>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const setupVisSuite: BrowserCommand<[]> = async (context) => {
|
|
16
|
+
await visContext.setupSuite(context)
|
|
17
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import pixelmatch from 'pixelmatch'
|
|
2
|
+
import { PNG } from 'pngjs'
|
|
3
|
+
import { convertThresholdUnit } from '../shared/convert_threshold_unit.ts'
|
|
4
|
+
import type { ImageSnapshotCompareOptions } from '../shared/types.ts'
|
|
5
|
+
|
|
6
|
+
export function compareImage(
|
|
7
|
+
baselineImage: PNG,
|
|
8
|
+
resultImage: PNG,
|
|
9
|
+
{ failureThreshold = 0, failureThresholdType = 'pixel', diffOptions }: ImageSnapshotCompareOptions = {},
|
|
10
|
+
) {
|
|
11
|
+
const diffImage = new PNG({
|
|
12
|
+
width: baselineImage.width,
|
|
13
|
+
height: baselineImage.height,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const pixelDiff = pixelmatch(
|
|
17
|
+
resultImage.data,
|
|
18
|
+
baselineImage.data,
|
|
19
|
+
diffImage.data,
|
|
20
|
+
baselineImage.width,
|
|
21
|
+
baselineImage.height,
|
|
22
|
+
diffOptions,
|
|
23
|
+
)
|
|
24
|
+
const diffAmount = convertThresholdUnit(
|
|
25
|
+
{ failureThresholdType, width: baselineImage.width, height: baselineImage.height },
|
|
26
|
+
pixelDiff,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
pass: diffAmount <= failureThreshold,
|
|
31
|
+
diffAmount,
|
|
32
|
+
diffImage,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { readFile, stat, writeFile } from 'node:fs/promises'
|
|
2
|
+
|
|
3
|
+
export const file = {
|
|
4
|
+
async tryReadFileBase64(filePath: string): Promise<string | undefined> {
|
|
5
|
+
return readFile(filePath, { encoding: 'base64' }).catch(() => undefined)
|
|
6
|
+
},
|
|
7
|
+
writeFileBase64(filePath: string, data: string) {
|
|
8
|
+
return writeFile(filePath, data, { encoding: 'base64' })
|
|
9
|
+
},
|
|
10
|
+
existFile(filePath: string) {
|
|
11
|
+
return stat(filePath)
|
|
12
|
+
.then((s) => s.isFile())
|
|
13
|
+
.catch(() => false)
|
|
14
|
+
},
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { VisOptions } from '../config/types.ts'
|
|
2
|
+
import { SNAPSHOT_ROOT_DIR } from '../shared/constants.ts'
|
|
3
|
+
import { trimCommonFolder } from '../shared/trim_common_folder.ts'
|
|
4
|
+
|
|
5
|
+
export function resolveSnapshotRootDir(options: VisOptions | undefined) {
|
|
6
|
+
return options?.snapshotRootDir ?? SNAPSHOT_ROOT_DIR
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getSnapshotSubpath(suiteName: string, options: VisOptions) {
|
|
10
|
+
const customizeSnapshotSubpath = options.customizeSnapshotSubpath ?? trimCommonFolder
|
|
11
|
+
return customizeSnapshotSubpath(suiteName)
|
|
12
|
+
}
|