storybook-addon-vis 4.3.1 → 4.3.3

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.
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "storybook-addon-vis",
3
+ "version": "4.3.2",
4
+ "description": "Review failed image snapshots from storybook-addon-vis cheaply: read the message, then the review image.",
5
+ "author": {
6
+ "name": "Homa Wong",
7
+ "email": "homawong@gmail.com",
8
+ "url": "https://github.com/unional"
9
+ },
10
+ "homepage": "https://github.com/repobuddy/visual-testing/tree/main/packages/storybook-addon-vis",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/repobuddy/visual-testing",
14
+ "directory": "packages/storybook-addon-vis"
15
+ },
16
+ "license": "MIT",
17
+ "keywords": ["image-snapshot", "storybook-addons", "vitest", "visual-test", "testing", "test", "browser-mode"]
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "storybook-addon-vis",
3
+ "version": "4.3.2",
4
+ "description": "Review failed image snapshots from storybook-addon-vis cheaply: read the message, then the review image.",
5
+ "author": {
6
+ "name": "Homa Wong",
7
+ "email": "homawong@gmail.com",
8
+ "url": "https://github.com/unional"
9
+ },
10
+ "homepage": "https://github.com/repobuddy/visual-testing/tree/main/packages/storybook-addon-vis",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/repobuddy/visual-testing",
14
+ "directory": "packages/storybook-addon-vis"
15
+ },
16
+ "license": "MIT",
17
+ "keywords": ["image-snapshot", "storybook-addons", "vitest", "visual-test", "testing", "test", "browser-mode"]
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "storybook-addon-vis",
3
+ "version": "4.3.2",
4
+ "description": "Review failed image snapshots from storybook-addon-vis cheaply: read the message, then the review image.",
5
+ "author": {
6
+ "name": "Homa Wong",
7
+ "email": "homawong@gmail.com",
8
+ "url": "https://github.com/unional"
9
+ },
10
+ "homepage": "https://github.com/repobuddy/visual-testing/tree/main/packages/storybook-addon-vis",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/repobuddy/visual-testing",
14
+ "directory": "packages/storybook-addon-vis"
15
+ },
16
+ "license": "MIT",
17
+ "keywords": ["image-snapshot", "storybook-addons", "vitest", "visual-test", "testing", "test", "browser-mode"]
18
+ }
@@ -5,7 +5,7 @@ import { globSync } from "glob";
5
5
  import ci from "is-ci";
6
6
  import memoize from "memoize";
7
7
  import { basename, resolve } from "pathe";
8
- import { BASELINE_DIR, DIFF_DIR, SNAPSHOT_ROOT_DIR, getSnapshotRootDir, resolveSnapshotSubpathWithinLimits, trimCommonFolder } from "vitest-plugin-vis/server-utils";
8
+ import { BASELINE_DIR, DIFF_DIR, REVIEW_IMAGE_SUFFIX, SNAPSHOT_ROOT_DIR, getSnapshotRootDir, resolveSnapshotSubpathWithinLimits, trimCommonFolder } from "vitest-plugin-vis/server-utils";
9
9
 
10
10
  //#region src/server/create_storybook_vis_server.ts
11
11
  function createStorybookVisServer(options) {
@@ -33,8 +33,9 @@ function createStorybookVisServer(options) {
33
33
  const baselineGlob = resolve(snapshotRootDir, BASELINE_DIR, snapshotSubpath, taskGlob);
34
34
  const diffGlob = resolve(snapshotRootDir, DIFF_DIR, snapshotSubpath, taskGlob);
35
35
  const taskIdRegex = /* @__PURE__ */ new RegExp(`${taskId}-[^-]*.png$`);
36
- const baselineFiles = globSync(baselineGlob).filter((filePath) => taskIdRegex.test(filePath));
37
- const diffFiles = globSync(diffGlob).filter((filePath) => taskIdRegex.test(filePath));
36
+ const isSnapshotFile = (filePath) => taskIdRegex.test(filePath) && !filePath.endsWith(REVIEW_IMAGE_SUFFIX);
37
+ const baselineFiles = globSync(baselineGlob).filter(isSnapshotFile);
38
+ const diffFiles = globSync(diffGlob).filter(isSnapshotFile);
38
39
  return [...baselineFiles.map((filePath) => mapFileToResult(filePath, snapshotRootDir, "baseline")), ...diffFiles.map((filePath) => mapFileToResult(filePath, snapshotRootDir, "diff"))];
39
40
  });
40
41
  }
@@ -1 +1 @@
1
- {"version":3,"file":"preset.mjs","names":[],"sources":["../../src/server/create_storybook_vis_server.ts","../../src/shared/events.ts","../../src/exports/preset.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { platform } from 'node:process'\nimport { globSync } from 'glob'\nimport ci from 'is-ci'\nimport memoize from 'memoize'\nimport { basename, resolve } from 'pathe'\nimport {\n\tBASELINE_DIR,\n\tDIFF_DIR,\n\tgetSnapshotRootDir,\n\tresolveSnapshotSubpathWithinLimits,\n\tSNAPSHOT_ROOT_DIR,\n\ttrimCommonFolder,\n} from 'vitest-plugin-vis/server-utils'\nimport type { StorybookVisOptions } from './types.ts'\n\nexport function createStorybookVisServer(options: StorybookVisOptions) {\n\tconst visSuites = options.visProjects ?? [{}]\n\treturn {\n\t\toptions,\n\t\tasync getImageSnapshotResults(name: string, importPath: string) {\n\t\t\treturn visSuites\n\t\t\t\t.map((suite) => {\n\t\t\t\t\tconst snapshotRootDir =\n\t\t\t\t\t\ttypeof suite.snapshotRootDir === 'string'\n\t\t\t\t\t\t\t? suite.snapshotRootDir\n\t\t\t\t\t\t\t: typeof suite.snapshotRootDir === 'function'\n\t\t\t\t\t\t\t\t? memoize(suite.snapshotRootDir)({\n\t\t\t\t\t\t\t\t\t\tci,\n\t\t\t\t\t\t\t\t\t\tplatform,\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t: memoize(getSnapshotRootDir)(SNAPSHOT_ROOT_DIR)\n\n\t\t\t\t\tconst snapshotSubpathFn =\n\t\t\t\t\t\ttypeof suite.snapshotSubpath === 'function'\n\t\t\t\t\t\t\t? memoize(suite.snapshotSubpath)\n\t\t\t\t\t\t\t: memoize(({ subpath }: { subpath: string }) =>\n\t\t\t\t\t\t\t\t\ttrimCommonFolder(subpath.startsWith('./') ? subpath.slice(2) : subpath),\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\tconst snapshotSubpathRaw = snapshotSubpathFn({ subpath: importPath })\n\t\t\t\t\tconst snapshotBaselineRootAbs = resolve(snapshotRootDir, BASELINE_DIR)\n\t\t\t\t\tconst snapshotSubpath = resolveSnapshotSubpathWithinLimits({\n\t\t\t\t\t\tsnapshotBaselineRootAbs,\n\t\t\t\t\t\trawSnapshotSubpath: snapshotSubpathRaw,\n\t\t\t\t\t\tshortenLongSnapshotPaths: suite.shortenLongSnapshotPaths === true,\n\t\t\t\t\t})\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsnapshotRootDir,\n\t\t\t\t\t\tsnapshotSubpath,\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.flatMap(({ snapshotRootDir, snapshotSubpath }) => {\n\t\t\t\t\tconst taskId = name.replace(/[^a-z0-9]/gi, '-').toLowerCase()\n\t\t\t\t\tconst taskGlob = `${taskId}-*.png`\n\t\t\t\t\tconst baselineGlob = resolve(snapshotRootDir, BASELINE_DIR, snapshotSubpath, taskGlob)\n\t\t\t\t\tconst diffGlob = resolve(snapshotRootDir, DIFF_DIR, snapshotSubpath, taskGlob)\n\n\t\t\t\t\tconst taskIdRegex = new RegExp(`${taskId}-[^-]*.png$`)\n\t\t\t\t\tconst baselineFiles = globSync(baselineGlob).filter((filePath) => taskIdRegex.test(filePath))\n\t\t\t\t\tconst diffFiles = globSync(diffGlob).filter((filePath) => taskIdRegex.test(filePath))\n\t\t\t\t\treturn [\n\t\t\t\t\t\t...baselineFiles.map((filePath) => mapFileToResult(filePath, snapshotRootDir, 'baseline')),\n\t\t\t\t\t\t...diffFiles.map((filePath) => mapFileToResult(filePath, snapshotRootDir, 'diff')),\n\t\t\t\t\t]\n\t\t\t\t})\n\t\t},\n\t}\n}\n\nfunction mapFileToResult(filePath: string, snapshotRootDir: string, type: 'baseline' | 'diff') {\n\treturn {\n\t\tfilePath,\n\t\tfileName: basename(filePath),\n\t\tsnapshotRootDir,\n\t\ttype,\n\t\tbase64: readFileSync(filePath, 'base64'),\n\t}\n}\n","export type VisEvent = ImageSnapshotResultsRequest | ImageSnapshotResultsResponse\n\nexport const IMAGE_SNAPSHOT_RESULTS_REQUEST = 'IMAGE_SNAPSHOT_RESULTS_REQUEST'\nexport const IMAGE_SNAPSHOT_RESULTS_RESPONSE = 'IMAGE_SNAPSHOT_RESULTS_RESPONSE'\n\nexport function requestImageSnapshotResults({ name, importPath }: { name: string; importPath: string }) {\n\treturn {\n\t\ttype: IMAGE_SNAPSHOT_RESULTS_REQUEST,\n\t\tname,\n\t\timportPath,\n\t}\n}\n\nexport type ImageSnapshotResultsRequest = {\n\ttype: typeof IMAGE_SNAPSHOT_RESULTS_REQUEST\n\tname: string\n\timportPath: string\n}\n\nexport type ImageSnapshotResultsResponse = {\n\ttype: typeof IMAGE_SNAPSHOT_RESULTS_RESPONSE\n\tname: string\n\timportPath: string\n\tresults: ImageSnapshotResults[]\n}\n\nexport type ImageSnapshotResults = {\n\tfilePath: string\n\tfileName: string\n\tsnapshotRootDir: string\n\ttype: 'baseline' | 'diff' | 'result'\n\tbase64: string\n}\n","import type { Channel } from 'storybook/internal/channels'\nimport { createStorybookVisServer } from '../server/create_storybook_vis_server.ts'\nimport type { StorybookVisOptions } from '../server/types.ts'\nimport { NAME } from '../shared/constants.ts'\nimport { IMAGE_SNAPSHOT_RESULTS_REQUEST, IMAGE_SNAPSHOT_RESULTS_RESPONSE, type VisEvent } from '../shared/events.ts'\n\nexport const experimental_serverChannel = async (channel: Channel, options: StorybookVisOptions) => {\n\tconst server = createStorybookVisServer(options)\n\n\tchannel.on(NAME, async (event: VisEvent) => {\n\t\tif (event.type === IMAGE_SNAPSHOT_RESULTS_REQUEST) {\n\t\t\tconst results = await server.getImageSnapshotResults(event.name, event.importPath)\n\t\t\tchannel.emit(NAME, {\n\t\t\t\ttype: IMAGE_SNAPSHOT_RESULTS_RESPONSE,\n\t\t\t\tname: event.name,\n\t\t\t\timportPath: event.importPath,\n\t\t\t\tresults,\n\t\t\t})\n\t\t}\n\t})\n\n\treturn channel\n}\n"],"mappings":";;;;;;;;;;AAgBA,SAAgB,yBAAyB,SAA8B;CACtE,MAAM,YAAY,QAAQ,eAAe,CAAC,EAAE,CAAC;AAC7C,QAAO;EACN;EACA,MAAM,wBAAwB,MAAc,YAAoB;AAC/D,UAAO,UACL,KAAK,UAAU;IACf,MAAM,kBACL,OAAO,MAAM,oBAAoB,WAC9B,MAAM,kBACN,OAAO,MAAM,oBAAoB,aAChC,QAAQ,MAAM,gBAAgB,CAAC;KAC/B;KACA;KACA,CAAC,GACD,QAAQ,mBAAmB,CAAC,kBAAkB;IAQnD,MAAM,sBALL,OAAO,MAAM,oBAAoB,aAC9B,QAAQ,MAAM,gBAAgB,GAC9B,SAAS,EAAE,cACX,iBAAiB,QAAQ,WAAW,KAAK,GAAG,QAAQ,MAAM,EAAE,GAAG,QAAQ,CACvE,EACyC,EAAE,SAAS,YAAY,CAAC;AAOrE,WAAO;KACN;KACA,iBAPuB,mCAAmC;MAC1D,yBAF+B,QAAQ,iBAAiB,aAAa;MAGrE,oBAAoB;MACpB,0BAA0B,MAAM,6BAA6B;MAC7D,CAAC;KAID;KACA,CACD,SAAS,EAAE,iBAAiB,sBAAsB;IAClD,MAAM,SAAS,KAAK,QAAQ,eAAe,IAAI,CAAC,aAAa;IAC7D,MAAM,WAAW,GAAG,OAAO;IAC3B,MAAM,eAAe,QAAQ,iBAAiB,cAAc,iBAAiB,SAAS;IACtF,MAAM,WAAW,QAAQ,iBAAiB,UAAU,iBAAiB,SAAS;IAE9E,MAAM,8BAAc,IAAI,OAAO,GAAG,OAAO,aAAa;IACtD,MAAM,gBAAgB,SAAS,aAAa,CAAC,QAAQ,aAAa,YAAY,KAAK,SAAS,CAAC;IAC7F,MAAM,YAAY,SAAS,SAAS,CAAC,QAAQ,aAAa,YAAY,KAAK,SAAS,CAAC;AACrF,WAAO,CACN,GAAG,cAAc,KAAK,aAAa,gBAAgB,UAAU,iBAAiB,WAAW,CAAC,EAC1F,GAAG,UAAU,KAAK,aAAa,gBAAgB,UAAU,iBAAiB,OAAO,CAAC,CAClF;KACA;;EAEJ;;AAGF,SAAS,gBAAgB,UAAkB,iBAAyB,MAA2B;AAC9F,QAAO;EACN;EACA,UAAU,SAAS,SAAS;EAC5B;EACA;EACA,QAAQ,aAAa,UAAU,SAAS;EACxC;;;;;AC1EF,MAAa,iCAAiC;AAC9C,MAAa,kCAAkC;;;;ACG/C,MAAa,6BAA6B,OAAO,SAAkB,YAAiC;CACnG,MAAM,SAAS,yBAAyB,QAAQ;AAEhD,SAAQ,GAAG,MAAM,OAAO,UAAoB;AAC3C,MAAI,MAAM,SAAS,gCAAgC;GAClD,MAAM,UAAU,MAAM,OAAO,wBAAwB,MAAM,MAAM,MAAM,WAAW;AAClF,WAAQ,KAAK,MAAM;IAClB,MAAM;IACN,MAAM,MAAM;IACZ,YAAY,MAAM;IAClB;IACA,CAAC;;GAEF;AAEF,QAAO"}
1
+ {"version":3,"file":"preset.mjs","names":[],"sources":["../../src/server/create_storybook_vis_server.ts","../../src/shared/events.ts","../../src/exports/preset.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { platform } from 'node:process'\nimport { globSync } from 'glob'\nimport ci from 'is-ci'\nimport memoize from 'memoize'\nimport { basename, resolve } from 'pathe'\nimport {\n\tBASELINE_DIR,\n\tDIFF_DIR,\n\tgetSnapshotRootDir,\n\tREVIEW_IMAGE_SUFFIX,\n\tresolveSnapshotSubpathWithinLimits,\n\tSNAPSHOT_ROOT_DIR,\n\ttrimCommonFolder,\n} from 'vitest-plugin-vis/server-utils'\nimport type { StorybookVisOptions } from './types.ts'\n\nexport function createStorybookVisServer(options: StorybookVisOptions) {\n\tconst visSuites = options.visProjects ?? [{}]\n\treturn {\n\t\toptions,\n\t\tasync getImageSnapshotResults(name: string, importPath: string) {\n\t\t\treturn visSuites\n\t\t\t\t.map((suite) => {\n\t\t\t\t\tconst snapshotRootDir =\n\t\t\t\t\t\ttypeof suite.snapshotRootDir === 'string'\n\t\t\t\t\t\t\t? suite.snapshotRootDir\n\t\t\t\t\t\t\t: typeof suite.snapshotRootDir === 'function'\n\t\t\t\t\t\t\t\t? memoize(suite.snapshotRootDir)({\n\t\t\t\t\t\t\t\t\t\tci,\n\t\t\t\t\t\t\t\t\t\tplatform,\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t: memoize(getSnapshotRootDir)(SNAPSHOT_ROOT_DIR)\n\n\t\t\t\t\tconst snapshotSubpathFn =\n\t\t\t\t\t\ttypeof suite.snapshotSubpath === 'function'\n\t\t\t\t\t\t\t? memoize(suite.snapshotSubpath)\n\t\t\t\t\t\t\t: memoize(({ subpath }: { subpath: string }) =>\n\t\t\t\t\t\t\t\t\ttrimCommonFolder(subpath.startsWith('./') ? subpath.slice(2) : subpath),\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\tconst snapshotSubpathRaw = snapshotSubpathFn({ subpath: importPath })\n\t\t\t\t\tconst snapshotBaselineRootAbs = resolve(snapshotRootDir, BASELINE_DIR)\n\t\t\t\t\tconst snapshotSubpath = resolveSnapshotSubpathWithinLimits({\n\t\t\t\t\t\tsnapshotBaselineRootAbs,\n\t\t\t\t\t\trawSnapshotSubpath: snapshotSubpathRaw,\n\t\t\t\t\t\tshortenLongSnapshotPaths: suite.shortenLongSnapshotPaths === true,\n\t\t\t\t\t})\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsnapshotRootDir,\n\t\t\t\t\t\tsnapshotSubpath,\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.flatMap(({ snapshotRootDir, snapshotSubpath }) => {\n\t\t\t\t\tconst taskId = name.replace(/[^a-z0-9]/gi, '-').toLowerCase()\n\t\t\t\t\tconst taskGlob = `${taskId}-*.png`\n\t\t\t\t\tconst baselineGlob = resolve(snapshotRootDir, BASELINE_DIR, snapshotSubpath, taskGlob)\n\t\t\t\t\tconst diffGlob = resolve(snapshotRootDir, DIFF_DIR, snapshotSubpath, taskGlob)\n\n\t\t\t\t\tconst taskIdRegex = new RegExp(`${taskId}-[^-]*.png$`)\n\t\t\t\t\tconst isSnapshotFile = (filePath: string) =>\n\t\t\t\t\t\ttaskIdRegex.test(filePath) && !filePath.endsWith(REVIEW_IMAGE_SUFFIX)\n\t\t\t\t\tconst baselineFiles = globSync(baselineGlob).filter(isSnapshotFile)\n\t\t\t\t\tconst diffFiles = globSync(diffGlob).filter(isSnapshotFile)\n\t\t\t\t\treturn [\n\t\t\t\t\t\t...baselineFiles.map((filePath) => mapFileToResult(filePath, snapshotRootDir, 'baseline')),\n\t\t\t\t\t\t...diffFiles.map((filePath) => mapFileToResult(filePath, snapshotRootDir, 'diff')),\n\t\t\t\t\t]\n\t\t\t\t})\n\t\t},\n\t}\n}\n\nfunction mapFileToResult(filePath: string, snapshotRootDir: string, type: 'baseline' | 'diff') {\n\treturn {\n\t\tfilePath,\n\t\tfileName: basename(filePath),\n\t\tsnapshotRootDir,\n\t\ttype,\n\t\tbase64: readFileSync(filePath, 'base64'),\n\t}\n}\n","export type VisEvent = ImageSnapshotResultsRequest | ImageSnapshotResultsResponse\n\nexport const IMAGE_SNAPSHOT_RESULTS_REQUEST = 'IMAGE_SNAPSHOT_RESULTS_REQUEST'\nexport const IMAGE_SNAPSHOT_RESULTS_RESPONSE = 'IMAGE_SNAPSHOT_RESULTS_RESPONSE'\n\nexport function requestImageSnapshotResults({ name, importPath }: { name: string; importPath: string }) {\n\treturn {\n\t\ttype: IMAGE_SNAPSHOT_RESULTS_REQUEST,\n\t\tname,\n\t\timportPath,\n\t}\n}\n\nexport type ImageSnapshotResultsRequest = {\n\ttype: typeof IMAGE_SNAPSHOT_RESULTS_REQUEST\n\tname: string\n\timportPath: string\n}\n\nexport type ImageSnapshotResultsResponse = {\n\ttype: typeof IMAGE_SNAPSHOT_RESULTS_RESPONSE\n\tname: string\n\timportPath: string\n\tresults: ImageSnapshotResults[]\n}\n\nexport type ImageSnapshotResults = {\n\tfilePath: string\n\tfileName: string\n\tsnapshotRootDir: string\n\ttype: 'baseline' | 'diff' | 'result'\n\tbase64: string\n}\n","import type { Channel } from 'storybook/internal/channels'\nimport { createStorybookVisServer } from '../server/create_storybook_vis_server.ts'\nimport type { StorybookVisOptions } from '../server/types.ts'\nimport { NAME } from '../shared/constants.ts'\nimport { IMAGE_SNAPSHOT_RESULTS_REQUEST, IMAGE_SNAPSHOT_RESULTS_RESPONSE, type VisEvent } from '../shared/events.ts'\n\nexport const experimental_serverChannel = async (channel: Channel, options: StorybookVisOptions) => {\n\tconst server = createStorybookVisServer(options)\n\n\tchannel.on(NAME, async (event: VisEvent) => {\n\t\tif (event.type === IMAGE_SNAPSHOT_RESULTS_REQUEST) {\n\t\t\tconst results = await server.getImageSnapshotResults(event.name, event.importPath)\n\t\t\tchannel.emit(NAME, {\n\t\t\t\ttype: IMAGE_SNAPSHOT_RESULTS_RESPONSE,\n\t\t\t\tname: event.name,\n\t\t\t\timportPath: event.importPath,\n\t\t\t\tresults,\n\t\t\t})\n\t\t}\n\t})\n\n\treturn channel\n}\n"],"mappings":";;;;;;;;;;AAiBA,SAAgB,yBAAyB,SAA8B;CACtE,MAAM,YAAY,QAAQ,eAAe,CAAC,EAAE,CAAC;AAC7C,QAAO;EACN;EACA,MAAM,wBAAwB,MAAc,YAAoB;AAC/D,UAAO,UACL,KAAK,UAAU;IACf,MAAM,kBACL,OAAO,MAAM,oBAAoB,WAC9B,MAAM,kBACN,OAAO,MAAM,oBAAoB,aAChC,QAAQ,MAAM,gBAAgB,CAAC;KAC/B;KACA;KACA,CAAC,GACD,QAAQ,mBAAmB,CAAC,kBAAkB;IAQnD,MAAM,sBALL,OAAO,MAAM,oBAAoB,aAC9B,QAAQ,MAAM,gBAAgB,GAC9B,SAAS,EAAE,cACX,iBAAiB,QAAQ,WAAW,KAAK,GAAG,QAAQ,MAAM,EAAE,GAAG,QAAQ,CACvE,EACyC,EAAE,SAAS,YAAY,CAAC;AAOrE,WAAO;KACN;KACA,iBAPuB,mCAAmC;MAC1D,yBAF+B,QAAQ,iBAAiB,aAAa;MAGrE,oBAAoB;MACpB,0BAA0B,MAAM,6BAA6B;MAC7D,CAAC;KAID;KACA,CACD,SAAS,EAAE,iBAAiB,sBAAsB;IAClD,MAAM,SAAS,KAAK,QAAQ,eAAe,IAAI,CAAC,aAAa;IAC7D,MAAM,WAAW,GAAG,OAAO;IAC3B,MAAM,eAAe,QAAQ,iBAAiB,cAAc,iBAAiB,SAAS;IACtF,MAAM,WAAW,QAAQ,iBAAiB,UAAU,iBAAiB,SAAS;IAE9E,MAAM,8BAAc,IAAI,OAAO,GAAG,OAAO,aAAa;IACtD,MAAM,kBAAkB,aACvB,YAAY,KAAK,SAAS,IAAI,CAAC,SAAS,SAAS,oBAAoB;IACtE,MAAM,gBAAgB,SAAS,aAAa,CAAC,OAAO,eAAe;IACnE,MAAM,YAAY,SAAS,SAAS,CAAC,OAAO,eAAe;AAC3D,WAAO,CACN,GAAG,cAAc,KAAK,aAAa,gBAAgB,UAAU,iBAAiB,WAAW,CAAC,EAC1F,GAAG,UAAU,KAAK,aAAa,gBAAgB,UAAU,iBAAiB,OAAO,CAAC,CAClF;KACA;;EAEJ;;AAGF,SAAS,gBAAgB,UAAkB,iBAAyB,MAA2B;AAC9F,QAAO;EACN;EACA,UAAU,SAAS,SAAS;EAC5B;EACA;EACA,QAAQ,aAAa,UAAU,SAAS;EACxC;;;;;AC7EF,MAAa,iCAAiC;AAC9C,MAAa,kCAAkC;;;;ACG/C,MAAa,6BAA6B,OAAO,SAAkB,YAAiC;CACnG,MAAM,SAAS,yBAAyB,QAAQ;AAEhD,SAAQ,GAAG,MAAM,OAAO,UAAoB;AAC3C,MAAI,MAAM,SAAS,gCAAgC;GAClD,MAAM,UAAU,MAAM,OAAO,wBAAwB,MAAM,MAAM,MAAM,WAAW;AAClF,WAAQ,KAAK,MAAM;IAClB,MAAM;IACN,MAAM,MAAM;IACZ,YAAY,MAAM;IAClB;IACA,CAAC;;GAEF;AAEF,QAAO"}
package/dist/index.d.mts CHANGED
@@ -2,6 +2,7 @@ import "./global_matcher_augment-B1plNA_9.mjs";
2
2
  import { t as visAnnotations } from "./vis_annotation-N0xbuXis.mjs";
3
3
  import * as storybook_internal_csf0 from "storybook/internal/csf";
4
4
  import { ComparisonMethod, FailureThresholdOptions, ImageSnapshotCompareOptions, ImageSnapshotKeyOptions, ImageSnapshotKeyOptions as ImageSnapshotKeyOptions$1, ImageSnapshotSubjectOptions, ImageSnapshotTimeoutOptions, PixelComparisonOptions, SsimComparisonOptions, ToMatchImageSnapshotOptions } from "vitest-plugin-vis/client-api";
5
+ import * as vitest_plugin_vis0 from "vitest-plugin-vis";
5
6
  import { SetupVisOptions } from "vitest-plugin-vis";
6
7
  import "vitest";
7
8
  import { BrowserPage, CDPSession } from "vitest/browser";
@@ -23,7 +24,7 @@ declare function hasImageSnapshot(options?: ImageSnapshotKeyOptions$1 | undefine
23
24
  * Define snapshot parameter for auto snapshot.
24
25
  */
25
26
  declare function defineAutoSnapshotParam<M extends ComparisonMethod>(snapshot: ToMatchImageSnapshotOptions<M> & ImageSnapshotSubjectOptions): {
26
- snapshot: ImageSnapshotTimeoutOptions & ImageSnapshotKeyOptions & (M extends "ssim" ? SsimComparisonOptions<M> : PixelComparisonOptions<M>) & FailureThresholdOptions & {
27
+ snapshot: ImageSnapshotTimeoutOptions & ImageSnapshotKeyOptions & (M extends "ssim" ? SsimComparisonOptions<M> : PixelComparisonOptions<M>) & FailureThresholdOptions & vitest_plugin_vis0.ReviewImageOptions & {
27
28
  expectToFail?: boolean | undefined;
28
29
  createMissingBaseline?: boolean | undefined;
29
30
  } & ImageSnapshotSubjectOptions;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/client/vitest_proxy.ts","../src/client/has_image_snapshot.ts","../src/client/storybook/param.ts","../src/client/storybook/tags.ts","../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;cAyGa,MAAI;cAEJ,WAhCI;;;;;;iBCpED,gBAAA,WAA2B,gDAAmC;;;;;;iBCqB9D,kCAAkC,4BACvC,4BAA4B,KAAK;;;;;;;;iBC7B5B,iBAAA;;;cCqBoB,kBJoFpC,GIlFyB;;AJkFzB,CAAA,CAAA,EAAa,GIlFiE,uBAAA,CAAA,YJkFhD,CIlFU,uBAAA,CAAsC,UAAA,CJkFhD"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/client/vitest_proxy.ts","../src/client/has_image_snapshot.ts","../src/client/storybook/param.ts","../src/client/storybook/tags.ts","../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;cAyGa,MAAI;cAEJ,WAhCI;;;;;;iBCpED,gBAAA,WAA2B,gDAAmC;;;;;;iBCqB9D,kCAAkC,4BACvC,4BAA4B,KAAK;;;;;;;;iBC7B5B,iBAAA;;;cCqBoB,qBAEX;;OAAqD,uBAAA,CAAA,aAAtC,uBAAA,CAAsC,UAAA"}
package/llms.txt ADDED
@@ -0,0 +1,48 @@
1
+ # storybook-addon-vis
2
+
3
+ > storybook-addon-vis: Storybook Vitest visual testing addon. Image snapshot testing for Vitest browser mode, with a Storybook integration.
4
+
5
+ This file is generated by `packages/storybook-addon-vis/scripts/generate-llms-txt.mjs` from the packages' `exports` maps, their source, and the documentation pages. Do not edit it by hand; run `pnpm docs:llms` instead.
6
+
7
+ If an image snapshot test fails, read [Reviewing Failures with Agents](https://repobuddy.github.io/visual-testing/?path=/docs/reviewing-failures-with-agents--docs) before opening any image. Without network access, read it from `node_modules/storybook-addon-vis/src/reviewing_failures_with_agents.mdx`.
8
+
9
+ Image snapshots are stored in the repository under `__vis__/`. Within it, `__baselines__` holds the expected images, `__results__` holds the images from the latest run, and `__diffs__` holds the diff images of failed comparisons. Next to each diff image, `<name>.review.png` is the review image of the changed regions and `<name>.regions.json` lists those regions.
10
+
11
+ Import from the entry points listed below, not from package internals. Documentation pages list the names they cover; names without a page have only their type declarations.
12
+
13
+ ## Start here
14
+
15
+ - [storybook-addon-vis: Reviewing Failures with Agents](https://repobuddy.github.io/visual-testing/?path=/docs/reviewing-failures-with-agents--docs): When an image snapshot fails, a coding agent often opens the baseline and the result and compares them.
16
+
17
+ ## Packages
18
+
19
+ - [storybook-addon-vis](https://www.npmjs.com/package/storybook-addon-vis): Storybook Vitest visual testing addon. Peer dependencies: @storybook/addon-vitest ^10.1.10, @vitest/browser ^4, storybook ^10.1.10, vitest ^4.
20
+
21
+ ## Documentation
22
+
23
+ - [storybook-addon-vis: Overview](https://repobuddy.github.io/visual-testing/?path=/docs/overview--docs): storybook-addon-vis allows you to perform self contained Visual Testing for Storybook. Documents: cdp, defineAutoSnapshotParam, hasImageSnapshot, page, visAnnotations, storybookVis, vis.
24
+ - [storybook-addon-vis: Releases/Migrating to 1.0](https://repobuddy.github.io/visual-testing/?path=/docs/releases-migrating-to-1-0--docs): storybook-addon-vis 1.0 has made some major improvements over the previous version.
25
+ - [storybook-addon-vis: Releases/Migrating to 3.0](https://repobuddy.github.io/visual-testing/?path=/docs/releases-migrating-to-3-0--docs): storybook-addon-vis 3.0.0 supports Storybook 10.
26
+ - [storybook-addon-vis: node](https://repobuddy.github.io/visual-testing/?path=/docs/node--docs): `storybook-addon-vis/node` provides functionality to configure the Storybook Visual Testing addon in the `.storybook/main.ts` file.
27
+ - [storybook-addon-vis: node/defineStorybookVis](https://repobuddy.github.io/visual-testing/?path=/docs/node-definestorybookvis--docs): A helper function to configure the Storybook Visual Testing addon in `.storybook/main.ts`. Documents: defineStorybookVis.
28
+ - [storybook-addon-vis: node/defineStorybookVisOptions](https://repobuddy.github.io/visual-testing/?path=/docs/node-definestorybookvisoptions--docs): A helper function to configure options for Storybook Visual Testing addon. Documents: defineStorybookVisOptions.
29
+ - [vitest-plugin-vis: auto snapshots/setAutoSnapshotOptions](https://github.com/repobuddy/visual-testing/blob/main/packages/vitest-plugin-vis/src/auto_snapshots/auto_snapshot_options.mdx): A function to configure visual snapshot testing options for the current test or test suite. Documents: setAutoSnapshotOptions.
30
+ - [vitest-plugin-vis: config/trimCommonFolder](https://github.com/repobuddy/visual-testing/blob/main/packages/vitest-plugin-vis/src/config/trim_common_folder.mdx): The `trimCommonFolder` function is a utility that removes common folder prefixes from file paths to generate cleaner snapshot sub-paths. Documents: trimCommonFolder.
31
+
32
+ ## Entry points
33
+
34
+ - [`storybook-addon-vis`](https://github.com/repobuddy/visual-testing/blob/main/packages/storybook-addon-vis/src/index.ts): default export; functions: defineAutoSnapshotParam, hasImageSnapshot, isSnapshotEnabled, setAutoSnapshotOptions; types: Awaitable, ComparisonMethod, FailureThresholdOptions, ImageSnapshotCompareOptions, ImageSnapshotComparisonInfo, ImageSnapshotKeyOptions, ImageSnapshotSubjectOptions, ImageSnapshotTimeoutOptions, MatchImageSnapshotOptions, MetaTask, PageImageSnapshotOptions, PixelComparisonOptions, PixelDiffOptions, ReviewImageOptions, SetupVisOptions, SnapshotTestMeta, SsimComparisonOptions, SsimDiffOptions, ToMatchImageSnapshotOptions, ToMatchPageImageSnapshotOptions; values: cdp, page, visAnnotations (28 exports, 6 with a documentation page).
35
+ - [`storybook-addon-vis/manager`](https://github.com/repobuddy/visual-testing/blob/main/packages/storybook-addon-vis/src/exports/manager.tsx): no named exports; it is imported for its side effects.
36
+ - [`storybook-addon-vis/matcher`](https://github.com/repobuddy/visual-testing/blob/main/packages/storybook-addon-vis/src/exports/matcher.ts): no named exports; it is imported for its side effects.
37
+ - [`storybook-addon-vis/node`](https://github.com/repobuddy/visual-testing/blob/main/packages/storybook-addon-vis/src/exports/node.ts): functions: defineStorybookVis, defineStorybookVisOptions (2 exports, 2 with a documentation page).
38
+ - [`storybook-addon-vis/preset`](https://github.com/repobuddy/visual-testing/blob/main/packages/storybook-addon-vis/src/exports/preset.ts): value: experimental_serverChannel (1 export, 0 with a documentation page).
39
+ - [`storybook-addon-vis/vitest-plugin`](https://github.com/repobuddy/visual-testing/blob/main/packages/storybook-addon-vis/src/exports/vitest-plugin.ts): functions: storybookVis, trimCommonFolder (2 exports, 2 with a documentation page).
40
+ - [`storybook-addon-vis/vitest-setup`](https://github.com/repobuddy/visual-testing/blob/main/packages/storybook-addon-vis/src/exports/vitest-setup.ts): function: setAutoSnapshotOptions; values: vis, visAnnotations (3 exports, 3 with a documentation page).
41
+
42
+ ## Optional
43
+
44
+ - [storybook-addon-vis: Changelog](https://repobuddy.github.io/visual-testing/?path=/docs/changelog--docs): Release history of storybook-addon-vis.
45
+ - [Generator](https://github.com/repobuddy/visual-testing/blob/main/packages/storybook-addon-vis/scripts/generate-llms-txt.mjs): Source of this file. Run it with `--gaps` to list the undocumented exports.
46
+
47
+ Documentation coverage: 11 of 34 distinct exported declarations are named on a documentation page; 6 carry a doc comment.
48
+ 2 of the pages above are not published on the documentation site; they link to their source on GitHub.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "storybook-addon-vis",
3
- "version": "4.3.1",
3
+ "version": "4.3.3",
4
4
  "description": "Storybook Vitest visual testing addon",
5
5
  "keywords": [
6
6
  "image-snapshot",
@@ -52,6 +52,12 @@
52
52
  "dist",
53
53
  "src",
54
54
  "*.js",
55
+ "llms.txt",
56
+ "plugin.json",
57
+ ".claude-plugin",
58
+ ".cursor-plugin",
59
+ ".codex-plugin",
60
+ "skills",
55
61
  "*.d.ts",
56
62
  "!**/*.{spec,test,unit,accept,integrate,system,perf,stress,stories,study}.*"
57
63
  ],
@@ -61,11 +67,11 @@
61
67
  "is-ci": "^4.1.0",
62
68
  "memoize": "^11.0.0",
63
69
  "pathe": "^2.0.3",
64
- "type-plus": "8.0.0-beta.12",
65
- "vitest-plugin-vis": "^5.1.7"
70
+ "type-plus": "8.0.1",
71
+ "vitest-plugin-vis": "^5.2.0"
66
72
  },
67
73
  "devDependencies": {
68
- "@repobuddy/storybook": "^2.33.1",
74
+ "@repobuddy/storybook": "^2.33.4",
69
75
  "@storybook-community/storybook-dark-mode": "^7.1.3",
70
76
  "@storybook/addon-docs": "^10.5.10",
71
77
  "@storybook/addon-vitest": "^10.5.10",
@@ -81,7 +87,7 @@
81
87
  "@vitest/browser-webdriverio": "^4.1.11",
82
88
  "@vitest/coverage-istanbul": "^4.1.11",
83
89
  "@vitest/expect": "^4.1.11",
84
- "boxen": "^8.0.1",
90
+ "boxen": "^9.0.0",
85
91
  "cross-env": "^10.1.0",
86
92
  "dedent": "^1.7.2",
87
93
  "npm-run-all2": "^9.0.3",
package/plugin.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
+ "name": "storybook-addon-vis",
4
+ "version": "4.3.2",
5
+ "description": "Review failed image snapshots from storybook-addon-vis cheaply: read the message, then the review image.",
6
+ "author": {
7
+ "name": "Homa Wong",
8
+ "email": "homawong@gmail.com",
9
+ "url": "https://github.com/unional"
10
+ },
11
+ "homepage": "https://github.com/repobuddy/visual-testing/tree/main/packages/storybook-addon-vis",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/repobuddy/visual-testing",
15
+ "directory": "packages/storybook-addon-vis"
16
+ },
17
+ "license": "MIT",
18
+ "keywords": ["image-snapshot", "storybook-addons", "vitest", "visual-test", "testing", "test", "browser-mode"],
19
+ "extensions": {
20
+ "org.cyberuni.universal-plugin": {
21
+ "vendors": ["claude-code", "cursor", "codex", "copilot-cli"]
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,18 @@
1
+ # review-image-snapshot-failures
2
+
3
+ Reviews a failed image snapshot through its review image instead of the full images, which costs far fewer tokens.
4
+
5
+ ## When to use
6
+
7
+ An agent loads it when a visual test fails with a `Snapshot ... mismatched` error from `toMatchImageSnapshot`, or when it is asked to run visual tests or update image snapshots. It is not a slash command (`user-invocable: false`).
8
+
9
+ ## What it does
10
+
11
+ 1. Reads the error message: pixel count, size change, changed regions, image paths.
12
+ 2. Opens the review image, not the full Expected, Actual, or Difference images.
13
+ 3. Opens a full image only when the message says the review image was skipped.
14
+ 4. Updates a baseline (`-u`) only for an intended change, and reports rendering drift instead.
15
+
16
+ ## Install
17
+
18
+ It ships as a plugin in both the `vitest-plugin-vis` and `storybook-addon-vis` npm packages (`plugin.json` and `skills/` in the package), identical in each. The measured token costs are on the "Reviewing Failures with Agents" page.
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: review-image-snapshot-failures
3
+ user-invocable: false
4
+ description: Reviews a failed image snapshot through its review image instead of the full images, at a fraction of the tokens. Applies when a visual test fails with a `Snapshot ... mismatched` error from toMatchImageSnapshot, or when asked to run visual tests or update image snapshots, even if the request never mentions images.
5
+ ---
6
+
7
+ # Review image snapshot failures
8
+
9
+ Use the review image of a failed image snapshot before opening any full image.
10
+
11
+ ## Run the tests
12
+
13
+ Run the project's own test command (for example `vitest run`). Do not pass `-u` to make a failure go away. Updating baselines is step 5 below, and only for an intended change.
14
+
15
+ Snapshots live under `__vis__/`: `__baselines__` is expected, `__results__` is the latest run, `__diffs__` holds the diff of a failed comparison. Next to each diff, `<name>.review.png` is the review image and `<name>.regions.json` lists the changed regions.
16
+
17
+ ## When a snapshot fails
18
+
19
+ 1. **Read the error message first.** It gives the pixel count, any size change, the changed regions (`Changed:`), and the image paths. It is text and costs little.
20
+ 2. **Open the review image** (`Review:`), not the full images. For each region it shows Expected, Actual, and the changed pixels in red, side by side. Open the full Expected, Actual, or Difference image only if the review image leaves the question open.
21
+ 3. **If the message says the review image was skipped, read why.**
22
+ - The image size changed: open only the Actual image. A size change means the layout changed.
23
+ - Too many regions, or they cover most of the image: open the Difference image. Red pixels changed; yellow pixels are anti-aliasing and do not count. Open Expected and Actual only if the Difference image does not show what changed.
24
+ 4. **If there is no `Review:` line**, the review image is turned off (`reviewImage: false`). The `Changed:` line still gives each region. Crop Expected and Actual to those regions with any image tool available, and open the crops.
25
+ 5. **Decide what the change is.**
26
+ - Intended by the task: update the baseline (run the tests with `-u`).
27
+ - Same content, only the edges of text or shapes differ: rendering drift (fonts, operating system, browser). Report it; do not update the baseline.
28
+ - Anything else is a regression. Fix it or report it.
29
+
30
+ Full explanation and measured token costs: the "Reviewing Failures with Agents" page, shipped as `src/reviewing_failures_with_agents.mdx` in `storybook-addon-vis`.
31
+
32
+ ## Validate
33
+
34
+ - The error message was read before any image was opened.
35
+ - No full Expected, Actual, or Difference image was opened while a `Review:` image existed and answered the question.
36
+ - `-u` was not run unless the change is what the task intended.
37
+ - A change that only differs at the edges of text or shapes was reported as rendering drift, and its baseline is unchanged (`git diff` shows no file under `__baselines__`).
@@ -0,0 +1,206 @@
1
+ import { Meta } from "@storybook/addon-docs/blocks"
2
+
3
+ <Meta title="Reviewing Failures with Agents" />
4
+
5
+ # Reviewing Failures with Agents
6
+
7
+ When an image snapshot fails, a coding agent often opens the baseline and the result and compares them.
8
+ Images are the most expensive input an agent reads.
9
+ This page shows what each view costs and the order in which an agent should look.
10
+
11
+ In short: read the error message, then open the review image instead of the full images.
12
+ Open the Actual or Difference image only when the message says the review image was skipped.
13
+
14
+ ## What a failure gives you
15
+
16
+ A failed `toMatchImageSnapshot` throws a message like this:
17
+
18
+ ```
19
+ Snapshot `button/primary` mismatched
20
+
21
+ Expected image to match but was differ by 133 pixels.
22
+
23
+ Options: failureThreshold: 0 pixels
24
+ comparisonMethod: pixel
25
+
26
+ Expected: ./__vis__/linux/__baselines__/button.stories.tsx/primary.png
27
+ Actual: ./__vis__/linux/__results__/button.stories.tsx/primary.png
28
+ Difference: ./__vis__/linux/__diffs__/button.stories.tsx/primary.png
29
+ Changed: 1 region: 106x37 at (0,0)
30
+ Review: ./__vis__/linux/__diffs__/button.stories.tsx/primary.review.png (baseline, result, diff of each region)
31
+ ```
32
+
33
+ When the two images have different sizes, the message also says
34
+ `The image size changed from 1280x720 to 333x720.`
35
+
36
+ The message is text and costs little.
37
+ It tells the agent how much changed, whether the layout changed size, where each changed region is, and where the images are.
38
+
39
+ The `Changed:` line lists each changed region as `<width>x<height> at (<x>,<y>)`.
40
+ The plugin finds the changed pixels, grows them by 16 px, and turns each connected area into one region.
41
+ Anti-aliasing differences are not counted.
42
+
43
+ The review image places, for each region, the Expected crop, the Actual crop, and a diff crop next to each other.
44
+ The diff crop shows the baseline in gray at full contrast with the changed pixels in red,
45
+ so small text in the changed area stays readable.
46
+
47
+ The review image is skipped when cropping does not help:
48
+ the image size changed, there are more than 5 regions, or the regions cover more than half of the image.
49
+ The `Review:` line then says so, and why:
50
+
51
+ ```
52
+ Review: skipped, the image size changed; open Actual or Difference instead
53
+ ```
54
+
55
+ The regions are also written next to the diff, as `primary.regions.json`:
56
+ `{ "width": 1280, "height": 720, "regions": [{ "x": 0, "y": 0, "width": 106, "height": 37 }] }`.
57
+ Tools and UIs read this file to zoom to the changes; an agent gets the same data from the `Changed:` line.
58
+
59
+ The Difference image is made by [pixelmatch](https://github.com/mapbox/pixelmatch) with its default colors:
60
+
61
+ - red pixels are pixels that changed.
62
+ - yellow pixels are anti-aliasing differences. They do not count toward the failure.
63
+ - gray is the baseline, faded, for context.
64
+
65
+ The faded baseline makes small text hard to read in the Difference image.
66
+ It shows where the change is, not always what it is.
67
+
68
+ ## What each view costs
69
+
70
+ Claude reads an image in 28 × 28 pixel patches, and each patch is one token:
71
+ `⌈width / 28⌉ × ⌈height / 28⌉`, after the image is scaled down to fit the model's limit
72
+ (2576 px on the long edge and 4784 tokens on current models).
73
+ See [Vision](https://platform.claude.com/docs/en/build-with-claude/vision) in the Claude docs.
74
+ Other models count image tokens differently, but most charge more for larger images.
75
+
76
+ For the 1280 × 720 page above, where a 106 × 37 area changed:
77
+
78
+ <table>
79
+ <thead>
80
+ <tr><th>What the agent opens</th><th>Image tokens</th></tr>
81
+ </thead>
82
+ <tbody>
83
+ <tr><td>Expected and Actual</td><td>2,392</td></tr>
84
+ <tr><td>Difference only</td><td>1,196</td></tr>
85
+ <tr><td>Review image (106 × 119)</td><td>20</td></tr>
86
+ </tbody>
87
+ </table>
88
+
89
+ The Difference image is the same size as each snapshot, so it always costs half of the pair.
90
+ The review image holds only the changed region, three times, so its cost follows the size of the change, not of the page.
91
+
92
+ Across 109 failing comparisons from this repository (see [How this was measured](#how-this-was-measured)):
93
+
94
+ <table>
95
+ <thead>
96
+ <tr>
97
+ <th>What the agent opens</th>
98
+ <th>Full pages (66)</th>
99
+ <th>Components (39)</th>
100
+ <th>Size changed (4)</th>
101
+ </tr>
102
+ </thead>
103
+ <tbody>
104
+ <tr><td>Expected and Actual</td><td>79,396</td><td>28,902</td><td>6,032</td></tr>
105
+ <tr><td>Difference only</td><td>39,698 (2.0× less)</td><td>14,451 (2.0× less)</td><td>4,784 (1.3× less)</td></tr>
106
+ <tr><td>Review image, or the fallback below when it is skipped</td><td>667 (119× less)</td><td>350 (83× less)</td><td>3,016 (2.0× less)</td></tr>
107
+ </tbody>
108
+ </table>
109
+
110
+ Three things stand out:
111
+
112
+ - The plugin wrote a review image for 101 of the 109 comparisons: all 66 full pages and 35 of the 39 components.
113
+ The largest one costs 20 tokens.
114
+ - It skipped 4 components because the change covers 84% of an 86 × 36 image.
115
+ The agent opens the Difference image instead, which costs 8 tokens each.
116
+ - It skipped all 4 size changes. The Difference image is padded to the larger image and most of it shows the padding,
117
+ so the agent opens only the Actual image.
118
+
119
+ ## What the agent should do
120
+
121
+ **1. Read the error message first.**
122
+ It gives the pixel count, any size change, the changed regions, and the image paths.
123
+
124
+ **2. Open the review image.**
125
+ For each region, it shows Expected, Actual, and the changed pixels in red, side by side.
126
+ Do not open the full Expected, Actual, or Difference images unless the review image leaves the question open.
127
+
128
+ **3. If the message says the review image was skipped, read why.**
129
+
130
+ - The image size changed: open only the Actual image.
131
+ A size change means the layout changed, and the Actual image shows the new layout.
132
+ - Too many regions, or the regions cover most of the image: open the Difference image.
133
+ Note where the red pixels are, then open Expected and Actual only if the Difference image does not show what changed.
134
+
135
+ **4. If there is no `Review:` line, the review image is turned off (`reviewImage: false`).**
136
+ The `Changed:` line still gives each region.
137
+ Crop Expected and Actual to those regions with any image tool the environment has, and open the crops,
138
+ for example `magick primary.png -crop 106x37+0+0 +repage primary.crop.png`.
139
+
140
+ **5. Decide what the change is.**
141
+ Update the baseline (run the tests with `-u`) only when the change is what the task intended.
142
+ If the content is the same and only the edges of text or shapes differ, it is rendering drift
143
+ (fonts, operating system, browser). Report it instead of updating the baseline.
144
+ Anything else is a regression to fix or report.
145
+
146
+ ## Add it to your agent instructions
147
+
148
+ Paste this into your `AGENTS.md`, `CLAUDE.md`, or similar file:
149
+
150
+ ```md
151
+ ## Image snapshot failures
152
+
153
+ When a `toMatchImageSnapshot` assertion fails:
154
+
155
+ 1. Read the error message first. It gives the pixel difference, any size change,
156
+ the changed regions (`Changed:`), and the image paths.
157
+ 2. Open the review image (`Review:`), not the full images.
158
+ For each region it shows Expected, Actual, and the changed pixels in red.
159
+ 3. If the message says the review image was skipped:
160
+ - the image size changed: open only the Actual image.
161
+ - otherwise: open the Difference image. Red pixels changed.
162
+ Yellow pixels are anti-aliasing and do not count.
163
+ 4. Update the baseline (`-u`) only when the change is what the task intended.
164
+ Report anything else.
165
+ ```
166
+
167
+ ## Turning the review image off
168
+
169
+ The review image is on by default.
170
+ Set `reviewImage: false` in [`vis()`](https://github.com/repobuddy/visual-testing/blob/main/packages/vitest-plugin-vis/src/config/vis.mdx)
171
+ or in a `toMatchImageSnapshot()` call to not write it.
172
+ The regions file and the `Changed:` line are still written.
173
+
174
+ ## How this was measured
175
+
176
+ The image pairs come from this repository's committed baselines:
177
+
178
+ - 116 tests that have both a `linux` and a `win32` baseline.
179
+ The Linux image is the baseline and the Windows image is the result.
180
+ 107 of them differ, mostly in how text is rendered.
181
+ - 2 baselines that a commit changed in place, where the image size changed.
182
+
183
+ The groups in the table are: size changed, when the two images differ in size;
184
+ full pages, 1280 px wide; and components, everything else.
185
+
186
+ For each pair, the script pads both images to the same size, as this plugin does,
187
+ and runs the plugin's own code to compare them with default options,
188
+ find the changed regions, decide whether to skip the review image, and build it.
189
+ It then computes the token cost of each view with the formula above.
190
+ The 9 identical pairs are left out.
191
+
192
+ To reproduce, run from the repository root:
193
+
194
+ ```sh
195
+ node scripts/measure_diff_tokens.mjs . <out-dir>
196
+ ```
197
+
198
+ It writes `results.json` and every image it measured, with the review images and region files, to `<out-dir>`.
199
+ See [`scripts/measure_diff_tokens.mjs`](https://github.com/repobuddy/visual-testing/blob/main/scripts/measure_diff_tokens.mjs).
200
+
201
+ Limits of this measurement:
202
+
203
+ - The token counts are computed from Anthropic's published formula, not read from API usage.
204
+ - It measures cost, not whether the agent reaches the right decision from each view.
205
+ - Most of the changes are small text-rendering differences, and every comparison has one changed region.
206
+ A change that spreads across a page gives a larger review image, or skips it.
@@ -8,6 +8,7 @@ import {
8
8
  BASELINE_DIR,
9
9
  DIFF_DIR,
10
10
  getSnapshotRootDir,
11
+ REVIEW_IMAGE_SUFFIX,
11
12
  resolveSnapshotSubpathWithinLimits,
12
13
  SNAPSHOT_ROOT_DIR,
13
14
  trimCommonFolder,
@@ -56,8 +57,10 @@ export function createStorybookVisServer(options: StorybookVisOptions) {
56
57
  const diffGlob = resolve(snapshotRootDir, DIFF_DIR, snapshotSubpath, taskGlob)
57
58
 
58
59
  const taskIdRegex = new RegExp(`${taskId}-[^-]*.png$`)
59
- const baselineFiles = globSync(baselineGlob).filter((filePath) => taskIdRegex.test(filePath))
60
- const diffFiles = globSync(diffGlob).filter((filePath) => taskIdRegex.test(filePath))
60
+ const isSnapshotFile = (filePath: string) =>
61
+ taskIdRegex.test(filePath) && !filePath.endsWith(REVIEW_IMAGE_SUFFIX)
62
+ const baselineFiles = globSync(baselineGlob).filter(isSnapshotFile)
63
+ const diffFiles = globSync(diffGlob).filter(isSnapshotFile)
61
64
  return [
62
65
  ...baselineFiles.map((filePath) => mapFileToResult(filePath, snapshotRootDir, 'baseline')),
63
66
  ...diffFiles.map((filePath) => mapFileToResult(filePath, snapshotRootDir, 'diff')),