vitest-plugin-vis 2.4.4 → 3.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.
Files changed (264) hide show
  1. package/esm/client/actions/has_image_snapshot_action.d.ts +4 -0
  2. package/esm/client/actions/has_image_snapshot_action.d.ts.map +1 -0
  3. package/esm/client/actions/has_image_snapshot_action.js +6 -0
  4. package/esm/client/actions/load_image_snapshot_results_action.d.ts +3 -0
  5. package/esm/client/actions/load_image_snapshot_results_action.d.ts.map +1 -0
  6. package/esm/client/actions/load_image_snapshot_results_action.js +3 -0
  7. package/esm/client/actions/match_image_snapshot_action.d.ts +6 -0
  8. package/esm/client/actions/match_image_snapshot_action.d.ts.map +1 -0
  9. package/esm/client/actions/match_image_snapshot_action.js +20 -0
  10. package/esm/client/actions/match_page_image_snapshot_action.d.ts +6 -0
  11. package/esm/client/actions/match_page_image_snapshot_action.d.ts.map +1 -0
  12. package/esm/client/actions/match_page_image_snapshot_action.js +9 -0
  13. package/esm/client/auto_snapshot_options.d.ts +29 -0
  14. package/esm/client/auto_snapshot_options.d.ts.map +1 -0
  15. package/esm/client/auto_snapshot_options.js +49 -0
  16. package/esm/client/compare_image_snapshot.d.ts +6 -0
  17. package/esm/client/compare_image_snapshot.d.ts.map +1 -0
  18. package/esm/client/compare_image_snapshot.js +52 -0
  19. package/esm/client/{image_snapshot_matcher.logic.d.ts → compare_image_snapshot.logic.d.ts} +1 -1
  20. package/esm/client/compare_image_snapshot.logic.d.ts.map +1 -0
  21. package/esm/client/expect/to_match_image_snapshot.d.ts.map +1 -1
  22. package/esm/client/expect/to_match_image_snapshot.js +4 -3
  23. package/esm/client/image_resizer.d.ts.map +1 -1
  24. package/esm/client/image_snapshot_options.d.ts +2 -0
  25. package/esm/client/image_snapshot_options.d.ts.map +1 -0
  26. package/esm/client/image_snapshot_options.js +6 -0
  27. package/esm/client/page/augment.d.ts +2 -1
  28. package/esm/client/page/augment.d.ts.map +1 -1
  29. package/esm/client/page/extend.js +2 -1
  30. package/esm/client/page/has_image_snapshot.d.ts +6 -6
  31. package/esm/client/page/has_image_snapshot.d.ts.map +1 -1
  32. package/esm/client/page/has_image_snapshot.js +2 -11
  33. package/esm/client/page/to_match_image_snapshot.d.ts +8 -0
  34. package/esm/client/page/to_match_image_snapshot.d.ts.map +1 -0
  35. package/esm/client/page/to_match_image_snapshot.js +15 -0
  36. package/esm/client/setup/create_vis.d.ts +96 -0
  37. package/esm/client/setup/create_vis.d.ts.map +1 -0
  38. package/esm/{setup → client/setup}/create_vis.js +43 -31
  39. package/esm/client/setup/vis.ctx.d.ts +12 -0
  40. package/esm/client/setup/vis.ctx.d.ts.map +1 -0
  41. package/esm/client/setup/vis.ctx.js +32 -0
  42. package/esm/client/setup/vis.d.ts +5 -0
  43. package/esm/client/setup/vis.d.ts.map +1 -0
  44. package/esm/client/should_take_snapshot.d.ts +2 -9
  45. package/esm/client/should_take_snapshot.d.ts.map +1 -1
  46. package/esm/client/should_take_snapshot.js +1 -11
  47. package/esm/client/snapshot_meta.d.ts +6 -0
  48. package/esm/client/snapshot_meta.d.ts.map +1 -0
  49. package/esm/client/task_id.d.ts +0 -3
  50. package/esm/client/task_id.d.ts.map +1 -1
  51. package/esm/client/task_id.js +0 -3
  52. package/esm/client/vitest_suite_proxy.d.ts +2 -9
  53. package/esm/client/vitest_suite_proxy.d.ts.map +1 -1
  54. package/esm/client-api.d.ts +11 -0
  55. package/esm/client-api.d.ts.map +1 -0
  56. package/esm/client-api.js +8 -0
  57. package/esm/commands.d.ts +0 -1
  58. package/esm/commands.d.ts.map +1 -1
  59. package/esm/commands.js +0 -1
  60. package/esm/config/types.d.ts +22 -22
  61. package/esm/config/types.d.ts.map +1 -1
  62. package/esm/config/vis.d.ts +4 -3
  63. package/esm/config/vis.d.ts.map +1 -1
  64. package/esm/config/vis.js +3 -4
  65. package/esm/index.d.ts +1 -1
  66. package/esm/index.d.ts.map +1 -1
  67. package/esm/index.js +1 -1
  68. package/esm/presets/auto.d.ts +1 -2
  69. package/esm/presets/auto.d.ts.map +1 -1
  70. package/esm/presets/auto.js +8 -4
  71. package/esm/presets/enable.d.ts +1 -2
  72. package/esm/presets/enable.d.ts.map +1 -1
  73. package/esm/presets/enable.js +4 -4
  74. package/esm/presets/manual.d.ts +1 -2
  75. package/esm/presets/manual.d.ts.map +1 -1
  76. package/esm/presets/manual.js +3 -4
  77. package/esm/server/browser_provider/playwright.d.ts.map +1 -1
  78. package/esm/server/browser_provider/playwright.js +9 -3
  79. package/esm/server/browser_provider/types.d.ts +5 -1
  80. package/esm/server/browser_provider/types.d.ts.map +1 -1
  81. package/esm/server/browser_provider/webdriverio.d.ts.map +1 -1
  82. package/esm/server/browser_provider/webdriverio.js +7 -4
  83. package/esm/server/commands/augment.d.ts +11 -0
  84. package/esm/server/commands/augment.d.ts.map +1 -0
  85. package/esm/server/commands/augment.js +1 -0
  86. package/esm/server/commands/commands.d.ts +9 -0
  87. package/esm/server/commands/commands.d.ts.map +1 -0
  88. package/esm/server/commands/commands.js +14 -0
  89. package/esm/server/commands/has_image_snapshot.d.ts +2 -6
  90. package/esm/server/commands/has_image_snapshot.d.ts.map +1 -1
  91. package/esm/server/commands/has_image_snapshot.js +3 -3
  92. package/esm/server/commands/image_snapshot_next_index.d.ts +1 -1
  93. package/esm/server/commands/image_snapshot_next_index.d.ts.map +1 -1
  94. package/esm/server/commands/image_snapshot_next_index.js +2 -2
  95. package/esm/server/commands/load_image_snapshot_results.d.ts +21 -0
  96. package/esm/server/commands/load_image_snapshot_results.d.ts.map +1 -0
  97. package/esm/server/commands/load_image_snapshot_results.js +6 -0
  98. package/esm/server/commands/prepare_image_snapshot_comparison.d.ts +8 -10
  99. package/esm/server/commands/prepare_image_snapshot_comparison.d.ts.map +1 -1
  100. package/esm/server/commands/prepare_image_snapshot_comparison.js +11 -9
  101. package/esm/server/commands/prepare_page_image_snapshot_comparison.d.ts +35 -0
  102. package/esm/server/commands/prepare_page_image_snapshot_comparison.d.ts.map +1 -0
  103. package/esm/server/commands/prepare_page_image_snapshot_comparison.js +27 -0
  104. package/esm/server/commands/setup_vis_suite.d.ts +3 -3
  105. package/esm/server/commands/setup_vis_suite.d.ts.map +1 -1
  106. package/esm/server/commands/setup_vis_suite.js +2 -3
  107. package/esm/server/commands/types.d.ts +3 -0
  108. package/esm/server/commands/types.d.ts.map +1 -0
  109. package/esm/server/commands/types.js +1 -0
  110. package/esm/server/externals/file.d.ts +10 -0
  111. package/esm/server/externals/file.d.ts.map +1 -0
  112. package/esm/server/{file.js → externals/file.js} +12 -4
  113. package/esm/server/{commands/browser_command_context.d.ts → project.d.ts} +8 -3
  114. package/esm/server/project.d.ts.map +1 -0
  115. package/esm/server/{commands/browser_command_context.js → project.js} +2 -2
  116. package/esm/server/snapshot.d.ts +4 -5
  117. package/esm/server/snapshot.d.ts.map +1 -1
  118. package/esm/server/snapshot.js +15 -13
  119. package/esm/server/snapshot_path.d.ts +3 -3
  120. package/esm/server/snapshot_path.d.ts.map +1 -1
  121. package/esm/server/snapshot_path.js +12 -11
  122. package/esm/server/snapshot_writer.d.ts +13 -0
  123. package/esm/server/snapshot_writer.d.ts.map +1 -0
  124. package/esm/server/snapshot_writer.js +15 -0
  125. package/esm/server/suite.d.ts +53 -0
  126. package/esm/server/suite.d.ts.map +1 -0
  127. package/esm/server/suite.js +73 -0
  128. package/esm/server/vis_options.d.ts +18 -0
  129. package/esm/server/vis_options.d.ts.map +1 -0
  130. package/esm/server/vis_options.js +14 -0
  131. package/esm/server/vis_server_context.d.ts +50 -0
  132. package/esm/server/vis_server_context.d.ts.map +1 -0
  133. package/esm/server/{vis_context.ctx.d.ts → vis_server_context.deps.d.ts} +2 -2
  134. package/esm/server/vis_server_context.deps.d.ts.map +1 -0
  135. package/esm/server/{vis_context.ctx.js → vis_server_context.deps.js} +1 -1
  136. package/esm/server/vis_server_context.js +2 -0
  137. package/esm/server/{vis_context.logic.d.ts → vis_server_context.logic.d.ts} +12 -32
  138. package/esm/server/vis_server_context.logic.d.ts.map +1 -0
  139. package/esm/server/vis_server_context.logic.js +79 -0
  140. package/esm/server/{vis_context.types.d.ts → vis_server_context.types.d.ts} +6 -6
  141. package/esm/server/vis_server_context.types.d.ts.map +1 -0
  142. package/esm/server/vis_server_context.types.js +1 -0
  143. package/esm/server-utils.d.ts +3 -0
  144. package/esm/server-utils.d.ts.map +1 -0
  145. package/esm/server-utils.js +2 -0
  146. package/esm/setup.d.ts +2 -2
  147. package/esm/setup.d.ts.map +1 -1
  148. package/esm/setup.js +2 -2
  149. package/esm/shared/asserts.d.ts +2 -0
  150. package/esm/shared/asserts.d.ts.map +1 -0
  151. package/esm/shared/asserts.js +5 -0
  152. package/esm/shared/types.d.ts +22 -21
  153. package/esm/shared/types.d.ts.map +1 -1
  154. package/esm/testing/stubBrowserCommandContext.d.ts +1 -1
  155. package/esm/testing/stubBrowserCommandContext.d.ts.map +1 -1
  156. package/esm/testing/stubBrowserCommandContext.js +3 -13
  157. package/esm/testing/stubSuite.d.ts +10 -0
  158. package/esm/testing/stubSuite.d.ts.map +1 -0
  159. package/esm/testing/stubSuite.js +27 -0
  160. package/esm/testing/stubUserConfig.d.ts +7 -0
  161. package/esm/testing/stubUserConfig.d.ts.map +1 -0
  162. package/esm/testing/stubUserConfig.js +4 -0
  163. package/package.json +29 -28
  164. package/readme.md +409 -110
  165. package/src/client/actions/has_image_snapshot_action.ts +14 -0
  166. package/src/client/actions/load_image_snapshot_results_action.ts +5 -0
  167. package/src/client/actions/match_image_snapshot_action.ts +32 -0
  168. package/src/client/actions/match_page_image_snapshot_action.ts +20 -0
  169. package/src/client/auto_snapshot_options.ts +71 -0
  170. package/src/client/compare_image_snapshot.ts +84 -0
  171. package/src/client/expect/to_match_image_snapshot.ts +4 -3
  172. package/src/client/image_snapshot_options.ts +6 -0
  173. package/src/client/page/augment.ts +2 -1
  174. package/src/client/page/extend.ts +2 -1
  175. package/src/client/page/has_image_snapshot.ts +8 -21
  176. package/src/client/page/to_match_image_snapshot.ts +27 -0
  177. package/src/client/setup/create_vis.ts +227 -0
  178. package/src/client/setup/vis.ctx.ts +33 -0
  179. package/src/client/should_take_snapshot.ts +3 -13
  180. package/src/client/snapshot_meta.ts +7 -0
  181. package/src/client/task_id.ts +0 -3
  182. package/src/client/vitest_suite_proxy.ts +2 -5
  183. package/src/client-api.ts +11 -0
  184. package/src/commands.ts +0 -1
  185. package/src/config/types.ts +32 -27
  186. package/src/config/vis.ts +3 -5
  187. package/src/index.ts +1 -1
  188. package/src/presets/auto.ts +8 -4
  189. package/src/presets/enable.ts +4 -4
  190. package/src/presets/manual.ts +3 -4
  191. package/src/server/browser_provider/playwright.ts +9 -3
  192. package/src/server/browser_provider/types.ts +12 -1
  193. package/src/server/browser_provider/webdriverio.ts +7 -4
  194. package/src/server/commands/augment.ts +16 -0
  195. package/src/server/commands/commands.ts +15 -0
  196. package/src/server/commands/has_image_snapshot.ts +8 -6
  197. package/src/server/commands/image_snapshot_next_index.ts +5 -3
  198. package/src/server/commands/load_image_snapshot_results.ts +32 -0
  199. package/src/server/commands/prepare_image_snapshot_comparison.ts +20 -14
  200. package/src/server/commands/prepare_page_image_snapshot_comparison.ts +75 -0
  201. package/src/server/commands/setup_vis_suite.ts +17 -7
  202. package/src/server/commands/types.ts +9 -0
  203. package/src/server/externals/file.ts +21 -0
  204. package/src/server/{commands/browser_command_context.ts → project.ts} +2 -4
  205. package/src/server/snapshot.ts +23 -14
  206. package/src/server/snapshot_path.ts +15 -13
  207. package/src/server/snapshot_writer.ts +22 -0
  208. package/src/server/suite.ts +96 -0
  209. package/src/server/vis_options.ts +27 -0
  210. package/src/server/{vis_context.ctx.ts → vis_server_context.deps.ts} +1 -1
  211. package/src/server/vis_server_context.logic.ts +112 -0
  212. package/src/server/vis_server_context.ts +3 -0
  213. package/src/server/{vis_context.types.ts → vis_server_context.types.ts} +5 -5
  214. package/src/server-utils.ts +2 -0
  215. package/src/setup.ts +2 -2
  216. package/src/shared/asserts.ts +5 -0
  217. package/src/shared/types.ts +25 -23
  218. package/src/testing/stubBrowserCommandContext.ts +4 -14
  219. package/src/testing/stubSuite.ts +38 -0
  220. package/src/testing/stubUserConfig.ts +6 -0
  221. package/esm/client/image_snapshot_matcher.d.ts +0 -11
  222. package/esm/client/image_snapshot_matcher.d.ts.map +0 -1
  223. package/esm/client/image_snapshot_matcher.js +0 -80
  224. package/esm/client/image_snapshot_matcher.logic.d.ts.map +0 -1
  225. package/esm/client/snapshot_options.d.ts +0 -33
  226. package/esm/client/snapshot_options.d.ts.map +0 -1
  227. package/esm/client/snapshot_options.js +0 -37
  228. package/esm/client.d.ts +0 -8
  229. package/esm/client.d.ts.map +0 -1
  230. package/esm/client.js +0 -5
  231. package/esm/server/commands/browser_command_context.d.ts.map +0 -1
  232. package/esm/server/commands/extend.d.ts +0 -17
  233. package/esm/server/commands/extend.d.ts.map +0 -1
  234. package/esm/server/commands/extend.js +0 -12
  235. package/esm/server/commands/match_image_snapshot.d.ts +0 -18
  236. package/esm/server/commands/match_image_snapshot.d.ts.map +0 -1
  237. package/esm/server/commands/match_image_snapshot.js +0 -81
  238. package/esm/server/file.d.ts +0 -7
  239. package/esm/server/file.d.ts.map +0 -1
  240. package/esm/server/vis_context.ctx.d.ts.map +0 -1
  241. package/esm/server/vis_context.d.ts +0 -56
  242. package/esm/server/vis_context.d.ts.map +0 -1
  243. package/esm/server/vis_context.js +0 -2
  244. package/esm/server/vis_context.logic.d.ts.map +0 -1
  245. package/esm/server/vis_context.logic.js +0 -137
  246. package/esm/server/vis_context.types.d.ts.map +0 -1
  247. package/esm/setup/create_vis.d.ts +0 -53
  248. package/esm/setup/create_vis.d.ts.map +0 -1
  249. package/esm/setup/vis.d.ts +0 -5
  250. package/esm/setup/vis.d.ts.map +0 -1
  251. package/src/client/image_snapshot_matcher.ts +0 -121
  252. package/src/client/snapshot_options.ts +0 -82
  253. package/src/client.ts +0 -7
  254. package/src/server/commands/extend.ts +0 -25
  255. package/src/server/commands/match_image_snapshot.ts +0 -128
  256. package/src/server/file.ts +0 -13
  257. package/src/server/vis_context.logic.ts +0 -200
  258. package/src/server/vis_context.ts +0 -3
  259. package/src/setup/create_vis.ts +0 -154
  260. /package/esm/client/{image_snapshot_matcher.logic.js → compare_image_snapshot.logic.js} +0 -0
  261. /package/esm/{setup → client/setup}/vis.js +0 -0
  262. /package/esm/{server/vis_context.types.js → client/snapshot_meta.js} +0 -0
  263. /package/src/client/{image_snapshot_matcher.logic.ts → compare_image_snapshot.logic.ts} +0 -0
  264. /package/src/{setup → client/setup}/vis.ts +0 -0
@@ -1,27 +1,29 @@
1
1
  import ci from 'is-ci'
2
+ import { platform } from 'node:process'
2
3
  import type { VisOptions } from '../config/types.ts'
3
4
  import { SNAPSHOT_ROOT_DIR } from '../shared/constants.ts'
4
5
  import { trimCommonFolder } from '../shared/trim_common_folder.ts'
5
- import type { PartialBrowserCommandContext } from './vis_context.types.ts'
6
+ import type { PartialBrowserCommandContext } from './vis_server_context.types.ts'
6
7
 
7
- export function resolveSnapshotRootDir(suite: PartialBrowserCommandContext, options: VisOptions) {
8
- if (!options.snapshotRootDir) return getSnapshotRootDir(SNAPSHOT_ROOT_DIR, options.platform)
8
+ export function resolveSnapshotRootDir(browserCommandContext: PartialBrowserCommandContext, options: VisOptions) {
9
+ if (!options.snapshotRootDir) return getSnapshotRootDir(SNAPSHOT_ROOT_DIR)
9
10
  const snapshotRootDir = options.snapshotRootDir
10
- if (typeof snapshotRootDir === 'string') return getSnapshotRootDir(snapshotRootDir, options.platform!)
11
+ if (typeof snapshotRootDir === 'string') return getSnapshotRootDir(snapshotRootDir)
11
12
  return snapshotRootDir({
12
13
  ci,
13
- browserName: suite.provider.browserName,
14
- providerName: suite.provider.name,
15
- platform: process.platform,
16
- screenshotFailures: suite.provider.options?.screenshotFailures,
17
- screenshotDirectory: suite.provider.options?.screenshotDirectory,
14
+ browserName: browserCommandContext.provider.browserName,
15
+ providerName: browserCommandContext.provider.name,
16
+ platform,
17
+ screenshotFailures: browserCommandContext.provider.options?.screenshotFailures,
18
+ screenshotDirectory: browserCommandContext.provider.options?.screenshotDirectory,
18
19
  })
19
20
  }
20
21
 
21
- function getSnapshotRootDir(snapshotRootDir: string, platform: string | undefined = process.platform) {
22
+ function getSnapshotRootDir(snapshotRootDir: string) {
22
23
  return `${snapshotRootDir}/${ci ? platform : 'local'}`
23
24
  }
24
- export function getSnapshotSubpath(suiteName: string, options: Pick<VisOptions, 'customizeSnapshotSubpath'>) {
25
- const customizeSnapshotSubpath = options.customizeSnapshotSubpath ?? trimCommonFolder
26
- return customizeSnapshotSubpath(suiteName)
25
+
26
+ export function getSnapshotSubpath(suiteName: string, options: Pick<VisOptions, 'snapshotSubpath'>) {
27
+ const customizeSnapshotSubpath = options.snapshotSubpath ?? (({ subpath }) => trimCommonFolder(subpath))
28
+ return customizeSnapshotSubpath({ subpath: suiteName })
27
29
  }
@@ -0,0 +1,22 @@
1
+ import { dirname } from 'pathe'
2
+ import { file } from './externals/file.ts'
3
+
4
+ export interface SnapshotWriter {
5
+ writeBase64(filePath: string, data: string): Promise<void>
6
+ writeBuffer(filePath: string, data: Buffer): Promise<void>
7
+ }
8
+
9
+ export function createSnapshotWriter({ mkdirp, writeFile } = file): SnapshotWriter {
10
+ return {
11
+ async writeBase64(filePath: string, data: string) {
12
+ await mkdirp(dirname(filePath))
13
+ await writeFile(filePath, data, { encoding: 'base64' })
14
+ },
15
+ async writeBuffer(filePath, data) {
16
+ await mkdirp(dirname(filePath))
17
+ await writeFile(filePath, data)
18
+ },
19
+ }
20
+ }
21
+
22
+ export const snapshotWriter = createSnapshotWriter()
@@ -0,0 +1,96 @@
1
+ import { join, relative } from 'pathe'
2
+ import type { VisOptions } from '../config/types.ts'
3
+ import { BASELINE_DIR, DIFF_DIR, RESULT_DIR } from '../shared/constants.ts'
4
+ import { getProjectName, getProjectRoot } from './project.ts'
5
+ import { getSnapshotSubpath, resolveSnapshotRootDir } from './snapshot_path.ts'
6
+ import { getVisOption } from './vis_options.ts'
7
+ import { deps } from './vis_server_context.deps.ts'
8
+ import type { PartialBrowserCommandContext, VisSuite, VisSuites } from './vis_server_context.types.ts'
9
+
10
+ const suites: VisSuites = {}
11
+
12
+ /**
13
+ * Setup suite is called on each test file's beforeAll hook.
14
+ * Test files include vitest test files and storybook story files.
15
+ * It needs to make sure there is no race condition between the test files.
16
+ */
17
+ export async function setupSuite(browserContext: PartialBrowserCommandContext) {
18
+ const suiteId = getSuiteId(browserContext)
19
+ const visOptions = getVisOption(browserContext)
20
+
21
+ if (!suites[suiteId]) {
22
+ suites[suiteId] = createSuite(browserContext, visOptions)
23
+ }
24
+
25
+ const suite = await suites[suiteId]
26
+
27
+ const { taskSubpath, baselineDir, diffDir, resultDir, tasks } = createModule(
28
+ suite,
29
+ browserContext.testPath,
30
+ visOptions,
31
+ )
32
+ suite.modules[taskSubpath] = { baselineDir, diffDir, resultDir, tasks }
33
+
34
+ await Promise.allSettled([deps.rimraf(diffDir), deps.rimraf(resultDir)])
35
+ return {
36
+ comparisonMethod: visOptions.comparisonMethod,
37
+ diffOptions: visOptions.diffOptions,
38
+ failureThreshold: visOptions.failureThreshold,
39
+ failureThresholdType: visOptions.failureThresholdType,
40
+ snapshotKey: visOptions.snapshotKey,
41
+ subject: visOptions.subject,
42
+ timeout: visOptions.timeout,
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Suite ID also contains the project name to make it unique
48
+ * across different projects.
49
+ */
50
+ export function getSuiteId(context: {
51
+ project: { config: { root: string; name: string }; vite: { config: { test?: { name?: string | undefined } } } }
52
+ }) {
53
+ return `${getProjectName(context)}/${context.project.config.name}`
54
+ }
55
+
56
+ async function createSuite(
57
+ browserContext: PartialBrowserCommandContext,
58
+ visOptions: Pick<VisOptions, 'snapshotRootDir' | 'subject'>,
59
+ ) {
60
+ const snapshotRootDir = resolveSnapshotRootDir(browserContext, visOptions)
61
+ const projectRoot = getProjectRoot(browserContext)
62
+
63
+ const state = {
64
+ projectRoot,
65
+ testTimeout: browserContext.project.config.testTimeout,
66
+ hookTimeout: browserContext.project.config.hookTimeout,
67
+ snapshotRootDir,
68
+ snapshotBaselineDir: join(snapshotRootDir, BASELINE_DIR),
69
+ snapshotResultDir: join(snapshotRootDir, RESULT_DIR),
70
+ snapshotDiffDir: join(snapshotRootDir, DIFF_DIR),
71
+ snapshotRootPath: join(projectRoot, snapshotRootDir),
72
+ subject: visOptions.subject,
73
+ modules: {},
74
+ }
75
+ await Promise.allSettled([deps.rimraf(join(state.snapshotDiffDir)), deps.rimraf(join(state.snapshotResultDir))])
76
+ return state
77
+ }
78
+
79
+ export function createModule(state: VisSuite, testPath: string, options: Pick<VisOptions, 'snapshotSubpath'>) {
80
+ const taskSubpath = getTaskSubpath(state, testPath, options)
81
+ return {
82
+ taskSubpath,
83
+ baselineDir: join(state.snapshotBaselineDir, taskSubpath),
84
+ resultDir: join(state.snapshotResultDir, taskSubpath),
85
+ diffDir: join(state.snapshotDiffDir, taskSubpath),
86
+ tasks: {},
87
+ }
88
+ }
89
+
90
+ export function getTaskSubpath(state: VisSuite, testPath: string, options: Pick<VisOptions, 'snapshotSubpath'>) {
91
+ return getSnapshotSubpath(relative(state.projectRoot, testPath), options)
92
+ }
93
+
94
+ export function getSuite(context: PartialBrowserCommandContext) {
95
+ return suites[getSuiteId(context)]!
96
+ }
@@ -0,0 +1,27 @@
1
+ import type { VisOptions } from '../config/types.ts'
2
+ import { assertSnapshotKeyWithoutDash } from '../shared/asserts.ts'
3
+ import { getProjectName } from './project.ts'
4
+
5
+ const DEFAULT_PROJECT_NAME = '__default'
6
+
7
+ const visOptions: Record<string, VisOptions<any> | undefined> = {}
8
+
9
+ export function setVisOption(
10
+ userConfig: { test?: { name?: string | undefined } | undefined },
11
+ options: VisOptions<any> | undefined,
12
+ ) {
13
+ assertSnapshotKeyWithoutDash(options?.snapshotKey)
14
+
15
+ const name = userConfig.test?.name
16
+ const id = name ?? DEFAULT_PROJECT_NAME
17
+
18
+ visOptions[id] = options
19
+ }
20
+
21
+ export function getVisOption(context: {
22
+ project: { vite: { config: { test?: { name?: string | undefined } } } }
23
+ }) {
24
+ const id = getProjectName(context) ?? DEFAULT_PROJECT_NAME
25
+
26
+ return visOptions[id] ?? {}
27
+ }
@@ -2,7 +2,7 @@
2
2
  import ci from 'is-ci'
3
3
  import { rimraf } from 'rimraf'
4
4
 
5
- export const ctx = {
5
+ export const deps = {
6
6
  rimraf,
7
7
  getSnapshotPlatform() {
8
8
  return ci ? process.platform : 'local'
@@ -0,0 +1,112 @@
1
+ import { basename, join, resolve } from 'pathe'
2
+ import { pick } from 'type-plus'
3
+ import type { ImageSnapshotKeyOptions } from '../client-api.ts'
4
+ import type { ImageSnapshotResult } from './commands/load_image_snapshot_results.ts'
5
+ import { file } from './externals/file.ts'
6
+ import { getSuite, getTaskSubpath } from './suite.ts'
7
+ import { getVisOption } from './vis_options.ts'
8
+ import type { PartialBrowserCommandContext } from './vis_server_context.types.ts'
9
+
10
+ export function createVisServerContext() {
11
+ const context = {
12
+ async getSnapshotResults(browserContext: PartialBrowserCommandContext, taskId: string) {
13
+ const suiteInfo = await context.getSuiteInfo(browserContext, taskId)
14
+ const baselines = await file.glob(join(suiteInfo.projectRoot, suiteInfo.baselineDir, `${suiteInfo.taskId}-*.png`))
15
+ const results = await file.glob(join(suiteInfo.projectRoot, suiteInfo.resultDir, `${suiteInfo.taskId}-*.png`))
16
+ const diffs = await file.glob(join(suiteInfo.projectRoot, suiteInfo.diffDir, `${suiteInfo.taskId}-*.png`))
17
+
18
+ const r: ImageSnapshotResult[] = []
19
+ await Promise.all(
20
+ baselines.map(async (baselinePath) => {
21
+ const filename = basename(baselinePath)
22
+ const resultPath = results.find((r) => r.endsWith(filename))
23
+ const diffPath = diffs.find((d) => d.endsWith(filename))
24
+
25
+ const baselineBuffer = await file.tryReadFile(baselinePath)
26
+ const resultBuffer = await file.tryReadFile(resultPath)
27
+ const diffBuffer = await file.tryReadFile(diffPath)
28
+ r.push({
29
+ filename,
30
+ baseline: baselineBuffer?.toString('base64'),
31
+ result: resultBuffer?.toString('base64'),
32
+ diff: diffBuffer?.toString('base64'),
33
+ })
34
+ }),
35
+ )
36
+ return r
37
+ },
38
+ async getSnapshotInfo(
39
+ browserContext: PartialBrowserCommandContext,
40
+ taskId: string,
41
+ options?: ImageSnapshotKeyOptions,
42
+ ) {
43
+ const suiteInfo = await context.getSuiteInfo(browserContext, taskId)
44
+ const snapshotFilename = context.getSnapshotFilename(browserContext, suiteInfo, options?.snapshotKey)
45
+
46
+ const { baselineDir, resultDir, diffDir, task } = suiteInfo
47
+
48
+ task.count = task.count + 1
49
+ const baselinePath = join(baselineDir, snapshotFilename)
50
+ const resultPath = join(resultDir, snapshotFilename)
51
+ const diffPath = join(diffDir, snapshotFilename)
52
+
53
+ return {
54
+ ...pick(
55
+ getVisOption(browserContext),
56
+ 'comparisonMethod',
57
+ 'diffOptions',
58
+ 'failureThreshold',
59
+ 'failureThresholdType',
60
+ 'timeout',
61
+ ),
62
+ baselinePath,
63
+ resultPath,
64
+ diffPath,
65
+ }
66
+ },
67
+
68
+ async getTaskCount(browserContext: PartialBrowserCommandContext, taskId: string) {
69
+ return (await context.getSuiteInfo(browserContext, taskId)).task.count
70
+ },
71
+ async hasImageSnapshot(
72
+ browserContext: PartialBrowserCommandContext,
73
+ taskId: string,
74
+ snapshotKey: string | undefined,
75
+ ) {
76
+ const info = await context.getSuiteInfo(browserContext, taskId)
77
+
78
+ return file.existFile(
79
+ resolve(info.projectRoot, info.baselineDir, context.getSnapshotFilename(browserContext, info, snapshotKey)),
80
+ )
81
+ },
82
+ getSnapshotFilename(
83
+ browserContext: PartialBrowserCommandContext,
84
+ info: { taskId: string; task: { count: number } },
85
+ snapshotKey: string | undefined,
86
+ ) {
87
+ if (snapshotKey) return `${info.taskId}-${snapshotKey}.png`
88
+ const visOptions = getVisOption(browserContext)
89
+ if (typeof visOptions.snapshotKey === 'string') {
90
+ return `${info.taskId}-${visOptions.snapshotKey}.png`
91
+ }
92
+ return `${info.taskId}-${info.task.count}.png`
93
+ },
94
+ async getSuiteInfo(browserContext: PartialBrowserCommandContext, taskId: string) {
95
+ const projectState = await getSuite(browserContext)
96
+ const visOptions = getVisOption(browserContext)
97
+ const moduleId = getTaskSubpath(projectState, browserContext.testPath, visOptions)
98
+ const m = projectState.modules[moduleId]!
99
+ const task = (m.tasks[taskId] = m.tasks[taskId] ?? { count: 1 })
100
+ return {
101
+ projectRoot: projectState.projectRoot,
102
+ suiteId: moduleId,
103
+ taskId,
104
+ baselineDir: m.baselineDir,
105
+ resultDir: m.resultDir,
106
+ diffDir: m.diffDir,
107
+ task,
108
+ }
109
+ },
110
+ }
111
+ return context
112
+ }
@@ -0,0 +1,3 @@
1
+ import { createVisServerContext } from './vis_server_context.logic.ts'
2
+
3
+ export const visServerContext = createVisServerContext()
@@ -1,8 +1,8 @@
1
- export type VisState = {
2
- [projectPath: string]: Promise<VisProjectState>
1
+ export type VisSuites = {
2
+ [projectPath: string]: Promise<VisSuite>
3
3
  }
4
4
 
5
- export type VisProjectState = {
5
+ export type VisSuite = {
6
6
  projectRoot: string
7
7
  testTimeout: number
8
8
  hookTimeout: number
@@ -11,8 +11,8 @@ export type VisProjectState = {
11
11
  snapshotResultDir: string
12
12
  snapshotDiffDir: string
13
13
  snapshotRootPath: string
14
- subjectDataTestId: string | undefined
15
- suites: Record<
14
+ subject: string | undefined
15
+ modules: Record<
16
16
  string,
17
17
  {
18
18
  baselineDir: string
@@ -0,0 +1,2 @@
1
+ export * from './shared/constants.ts'
2
+ export * from './shared/trim_common_folder.ts'
package/src/setup.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  import './client/expect/extend.ts'
3
3
  import './client/page/extend.ts'
4
4
 
5
- export * from './setup/vis.ts'
5
+ export * from './client/setup/vis.ts'
6
6
 
7
7
  // This is exported for main/app boundary separation
8
8
  // They can co-exist in the same export file thanks to ESM.
9
- export * from './setup/create_vis.ts'
9
+ export * from './client/setup/create_vis.ts'
@@ -0,0 +1,5 @@
1
+ export function assertSnapshotKeyWithoutDash(snapshotKey: string | undefined) {
2
+ if (snapshotKey?.includes('-')) {
3
+ throw new Error('Snapshot key cannot contain dash')
4
+ }
5
+ }
@@ -1,6 +1,6 @@
1
1
  import type pixelMatch from 'pixelmatch'
2
- import type { Options as SsimOptions } from 'ssim.js'
3
- import type { getCurrentTest } from 'vitest/suite'
2
+ import type { Options as SsimDiffOptions } from 'ssim.js'
3
+ import type { SnapshotMeta } from '../client/snapshot_meta.ts'
4
4
  import type { NAME } from './constants.ts'
5
5
 
6
6
  export interface ImageSnapshotTimeoutOptions {
@@ -12,18 +12,11 @@ export interface ImageSnapshotTimeoutOptions {
12
12
  timeout?: number | undefined
13
13
  }
14
14
 
15
- export interface ImageSnapshotIdOptions {
15
+ export interface ImageSnapshotKeyOptions {
16
16
  /**
17
- * Customize the snapshot id. This is used as the filename of the snapshot: `${snapshotId}.png`
18
- *
19
- * @param id The id of the snapshot.
20
- * @param index The index of the snapshot.
17
+ * Customize the `snapshotKey` of the snapshot.
21
18
  */
22
- customizeSnapshotId?: (context: {
23
- id: string
24
- index: number
25
- isAutoSnapshot: boolean
26
- }) => string
19
+ snapshotKey?: string | undefined
27
20
  }
28
21
 
29
22
  export type ComparisonMethod = 'pixel' | 'ssim'
@@ -48,32 +41,37 @@ export type SsimComparisonOptions<M = 'ssim'> = {
48
41
  /**
49
42
  * Custom options passed to 'ssim'
50
43
  */
51
- diffOptions?: Partial<SsimOptions> | undefined
44
+ diffOptions?: Partial<SsimDiffOptions> | undefined
52
45
  }
46
+
47
+ export type { SsimDiffOptions }
48
+
53
49
  export type PixelComparisonOptions<M = 'pixel'> = {
54
50
  comparisonMethod?: M | undefined
55
51
  /**
56
52
  * Custom options passed to 'pixelmatch'
57
53
  */
58
- diffOptions?: Parameters<typeof pixelMatch>[5] | undefined
54
+ diffOptions?: PixelDiffOptions | undefined
59
55
  }
60
56
 
57
+ export type PixelDiffOptions = Parameters<typeof pixelMatch>[5]
58
+
61
59
  export type ImageSnapshotCompareOptions<M extends ComparisonMethod = 'pixel'> = (M extends 'ssim'
62
60
  ? SsimComparisonOptions<M>
63
61
  : PixelComparisonOptions<M>) &
64
62
  FailureThresholdOptions
65
63
 
66
- export type AutoSnapshotOptions = {
64
+ export type ImageSnapshotSubjectOptions = {
67
65
  /**
68
- * Specify the data-testid of the subject element. Default is `subject`.
66
+ * Specify the query of the subject element.
69
67
  *
70
- * If the test does not have an element with the specified data-testid,
68
+ * If the test does not have an element with the specified query,
71
69
  * the `body` element will be used.
72
70
  */
73
- subjectDataTestId?: string | undefined
71
+ subject?: string | undefined
74
72
  }
75
73
  export type ToMatchImageSnapshotOptions<M extends ComparisonMethod = 'pixel'> = ImageSnapshotTimeoutOptions &
76
- ImageSnapshotIdOptions &
74
+ ImageSnapshotKeyOptions &
77
75
  ImageSnapshotCompareOptions<M> & {
78
76
  /**
79
77
  * Expect the matcher to fail.
@@ -82,8 +80,12 @@ export type ToMatchImageSnapshotOptions<M extends ComparisonMethod = 'pixel'> =
82
80
  expectToFail?: boolean | undefined
83
81
  }
84
82
 
85
- export type SnapshotMeta<M extends ComparisonMethod> = ToMatchImageSnapshotOptions<M> &
86
- AutoSnapshotOptions & {
87
- enable?: boolean | undefined
88
- [key: string]: unknown
83
+ export interface PageImageSnapshotOptions {
84
+ fullPage?: boolean | undefined
85
+ }
86
+
87
+ export type SnapshotTestMeta = {
88
+ meta: {
89
+ [NAME]?: SnapshotMeta<ComparisonMethod>
89
90
  }
91
+ }
@@ -1,26 +1,16 @@
1
1
  import { stub } from 'type-plus'
2
2
  import type { BrowserCommandContext } from 'vitest/node'
3
- import type { PartialBrowserCommandContext } from '../server/vis_context.types.ts'
3
+ import type { PartialBrowserCommandContext } from '../server/vis_server_context.types.ts'
4
4
 
5
5
  export const stubBrowserCommandContext = stub.build<BrowserCommandContext>({})
6
6
 
7
7
  export function createStubPartialBrowserCommandContext({ root, testPath }: { root: string; testPath: string }) {
8
8
  return stub.build<PartialBrowserCommandContext>({
9
9
  project: {
10
- config: {
11
- root,
12
- },
13
- vite: {
14
- config: {
15
- test: {
16
- name: 'subject',
17
- },
18
- },
19
- },
20
- },
21
- provider: {
22
- options: {},
10
+ config: { root },
11
+ vite: { config: { test: { name: 'subject' } } },
23
12
  },
13
+ provider: { options: {} },
24
14
  testPath,
25
15
  })
26
16
  }
@@ -0,0 +1,38 @@
1
+ import { resolve } from 'pathe'
2
+ import { requiredDeep, type RecursivePartial } from 'type-plus'
3
+ import type { UserConfig } from 'vite'
4
+ import type { BrowserCommandContext } from 'vitest/node'
5
+ import { stubBrowserCommandContext } from './stubBrowserCommandContext.ts'
6
+ import { stubUserConfig } from './stubUserConfig.ts'
7
+
8
+ export function stubSuite(
9
+ partialUserConfig: RecursivePartial<UserConfig> = {},
10
+ partialBrowserCommandContext: RecursivePartial<BrowserCommandContext> = {},
11
+ ) {
12
+ const root = resolve(import.meta.dirname, '../..')
13
+ const userConfig = stubUserConfig({
14
+ root,
15
+ ...partialUserConfig,
16
+ })
17
+ const browserCommandContext = stubBrowserCommandContext(
18
+ requiredDeep<RecursivePartial<BrowserCommandContext>>(
19
+ {
20
+ project: {
21
+ config: { root },
22
+ vite: {
23
+ config: {
24
+ test: {
25
+ name: partialUserConfig.test?.name,
26
+ },
27
+ },
28
+ },
29
+ },
30
+ },
31
+ partialBrowserCommandContext,
32
+ ),
33
+ )
34
+ return {
35
+ userConfig,
36
+ browserCommandContext,
37
+ }
38
+ }
@@ -0,0 +1,6 @@
1
+ import { stub } from 'type-plus'
2
+ import type { UserConfig } from 'vite'
3
+
4
+ export const stubUserConfig = stub.build<UserConfig & { configFile: string }>((stub) => ({
5
+ ...stub,
6
+ }))
@@ -1,11 +0,0 @@
1
- import type { BrowserCommands } from '@vitest/browser/context';
2
- import type { ImageSnapshotNextIndexCommand } from '../commands.ts';
3
- import type { PrepareImageSnapshotComparisonCommand } from '../server/commands/prepare_image_snapshot_comparison.ts';
4
- import type { ToMatchImageSnapshotOptions } from '../shared/types.ts';
5
- import type { CurrentTest } from './vitest_suite_proxy.ts';
6
- export declare function imageSnapshotMatcher(commands: BrowserCommands & PrepareImageSnapshotComparisonCommand & ImageSnapshotNextIndexCommand): (test: CurrentTest & {}, subject: any, options?: ToMatchImageSnapshotOptions<any>) => Promise<void>;
7
- /**
8
- * @deprecated internalized. Use `matchImageSnapshot` directly instead.
9
- */
10
- export declare function parseImageSnapshotSubject(subject: any): any;
11
- //# sourceMappingURL=image_snapshot_matcher.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"image_snapshot_matcher.d.ts","sourceRoot":"","sources":["../../src/client/image_snapshot_matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAG9D,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,gBAAgB,CAAA;AACnE,OAAO,KAAK,EAAE,qCAAqC,EAAE,MAAM,yDAAyD,CAAA;AAGpH,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAA;AAOrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAE1D,wBAAgB,oBAAoB,CACnC,QAAQ,EAAE,eAAe,GAAG,qCAAqC,GAAG,6BAA6B,UAG1F,WAAW,GAAG,EAAE,WACb,GAAG,YACF,2BAA2B,CAAC,GAAG,CAAC,mBAqE3C;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,GAAG,OAOrD"}
@@ -1,80 +0,0 @@
1
- import dedent from 'dedent';
2
- import { resolve } from 'pathe';
3
- import { isBase64String } from "../shared/base64.js";
4
- import { compareImage } from "../shared/compare_image.js";
5
- import { alignImagesToSameSize } from "./align_images.js";
6
- import { toDataURL, toImageData } from "./image_data.js";
7
- import { prettifyOptions } from "./image_snapshot_matcher.logic.js";
8
- import { convertElementToCssSelector } from "./selector.js";
9
- import { toTaskId } from "./task_id.js";
10
- import { server } from "./vitest_browser_context_proxy.js";
11
- export function imageSnapshotMatcher(commands) {
12
- return async function matchImageSnapshot(test, subject, options) {
13
- const isAutoSnapshot = !!test.meta.vis?.isAutoSnapshot;
14
- const taskId = toTaskId(test);
15
- const info = await commands.prepareImageSnapshotComparison(taskId, parseImageSnapshotSubject(subject), isAutoSnapshot, options?.customizeSnapshotId
16
- ? await parseImageSnapshotOptions(commands, taskId, isAutoSnapshot, options)
17
- : options);
18
- if (!info)
19
- return;
20
- options = { ...info, ...options };
21
- const baselineImage = await toImageData(info.baseline);
22
- const resultImage = await toImageData(info.result);
23
- const [baselineAlignedImage, resultAlignedImage] = alignImagesToSameSize(baselineImage, resultImage);
24
- const { width, height } = baselineAlignedImage;
25
- const diffImage = new ImageData(width, height);
26
- const { pass, diffAmount } = compareImage(baselineAlignedImage.data, resultAlignedImage.data, diffImage.data, width, height, options);
27
- if (pass) {
28
- if (options?.expectToFail) {
29
- throw new Error(dedent `Snapshot \`${taskId}\` matched but expected to fail.
30
-
31
- Options: ${prettifyOptions(options)}
32
- Diff: ${options.failureThresholdType === 'percent' ? `${diffAmount}%` : `${diffAmount} pixels`}
33
-
34
- Expected: ${resolve(info.projectRoot, info.baselinePath)}
35
- Actual: ${resolve(info.projectRoot, info.resultPath)}`);
36
- }
37
- return;
38
- }
39
- if (server.config.snapshotOptions.updateSnapshot === 'all' && !options?.expectToFail) {
40
- await writeSnapshot(commands, resolve(info.projectRoot, info.baselinePath), resultImage);
41
- return;
42
- }
43
- await writeSnapshot(commands, resolve(info.projectRoot, info.diffPath), diffImage);
44
- throw new Error(dedent `Snapshot \`${taskId}\` mismatched
45
-
46
- ${options?.failureThreshold
47
- ? options?.failureThresholdType === 'percent'
48
- ? `Expected image to match within ${options.failureThreshold}% but was differ by ${diffAmount}%.`
49
- : `Expected image to match within ${options.failureThreshold} pixels but was differ by ${diffAmount} pixels.`
50
- : `Expected image to match but was differ by ${options?.failureThresholdType === 'percent' ? `${diffAmount}%` : `${diffAmount} pixels`}.`}
51
-
52
- Options: ${prettifyOptions(options)}
53
-
54
- Expected: ${resolve(info.projectRoot, info.baselinePath)}
55
- Actual: ${resolve(info.projectRoot, info.resultPath)}
56
- Difference: ${resolve(info.projectRoot, info.diffPath)}`);
57
- };
58
- }
59
- /**
60
- * @deprecated internalized. Use `matchImageSnapshot` directly instead.
61
- */
62
- export function parseImageSnapshotSubject(subject) {
63
- if (subject instanceof Element)
64
- return convertElementToCssSelector(subject);
65
- if (subject?.['selector'])
66
- return subject['selector'];
67
- if (isBase64String(subject))
68
- return subject;
69
- throw new Error(`'toMatchImageSnapshot()' expects the subject to be an element, locator, or image encoded in base64 string, but got: ${subject}`);
70
- }
71
- async function writeSnapshot(commands, path, image) {
72
- const content = (await toDataURL(image)).split(',')[1];
73
- return commands.writeFile(path, content, { encoding: 'base64' });
74
- }
75
- async function parseImageSnapshotOptions(commands, taskId, isAutoSnapshot, options) {
76
- const index = await commands.imageSnapshotNextIndex(taskId);
77
- const { customizeSnapshotId, ...rest } = options;
78
- const snapshotFileId = customizeSnapshotId({ id: taskId, index, isAutoSnapshot });
79
- return { ...rest, snapshotFileId };
80
- }
@@ -1 +0,0 @@
1
- {"version":3,"file":"image_snapshot_matcher.logic.d.ts","sourceRoot":"","sources":["../../src/client/image_snapshot_matcher.logic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAA;AAErE,wBAAgB,eAAe,CAAC,OAAO,EAAE,2BAA2B,CAAC,GAAG,CAAC,GAAG,SAAS,UAWpF"}