@uiverify/vitest 1.0.1 → 1.1.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/dist/browser-setup.js +2 -5
- package/dist/{chunk-ZCABESWT.js → chunk-YDH23IMH.js} +82 -1
- package/dist/index.js +1 -1
- package/dist/plugin.js +146 -4
- package/package.json +1 -1
- package/dist/chunk-X2KT34IP.js +0 -40
package/dist/browser-setup.js
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
autoSnapshot,
|
|
3
3
|
beginTest
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import {
|
|
6
|
-
UI_VERIFY_GLOBAL
|
|
7
|
-
} from "./chunk-X2KT34IP.js";
|
|
4
|
+
} from "./chunk-YDH23IMH.js";
|
|
8
5
|
|
|
9
6
|
// src/browser-setup.ts
|
|
10
7
|
import { afterEach, beforeEach, inject } from "vitest";
|
|
11
|
-
Reflect.set(globalThis,
|
|
8
|
+
Reflect.set(globalThis, "__UI_VERIFY__", true);
|
|
12
9
|
function globalAutoDisabled() {
|
|
13
10
|
try {
|
|
14
11
|
return Boolean(inject("__uiverify")?.disableAutoSnapshot);
|
|
@@ -23,7 +23,83 @@ function snapshotIds(task, name) {
|
|
|
23
23
|
return { id: name ? `${idBase}::${name}` : idBase, title };
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// package.json
|
|
27
|
+
var package_default = {
|
|
28
|
+
name: "@uiverify/vitest",
|
|
29
|
+
version: "1.1.0",
|
|
30
|
+
description: "Vitest browser-mode capture SDK for UI Verify (uiverify.ai): add uiverifyPlugin() to your Vitest config and each browser-mode test archives its final DOM (serialized DOM + resource bytes) to be replayed + visually diffed by UI Verify. Produces an archive the uiverify CLI uploads; talks to nothing at runtime.",
|
|
31
|
+
keywords: [
|
|
32
|
+
"visual-testing",
|
|
33
|
+
"visual-regression",
|
|
34
|
+
"vitest",
|
|
35
|
+
"vitest-browser",
|
|
36
|
+
"screenshot-testing",
|
|
37
|
+
"rrweb",
|
|
38
|
+
"uiverify"
|
|
39
|
+
],
|
|
40
|
+
homepage: "https://uiverify.ai",
|
|
41
|
+
bugs: "https://github.com/uiverify/uiverify/issues",
|
|
42
|
+
repository: {
|
|
43
|
+
type: "git",
|
|
44
|
+
url: "git+https://github.com/uiverify/uiverify.git",
|
|
45
|
+
directory: "packages/vitest"
|
|
46
|
+
},
|
|
47
|
+
license: "MIT",
|
|
48
|
+
author: "UI Verify",
|
|
49
|
+
engines: {
|
|
50
|
+
node: ">=24"
|
|
51
|
+
},
|
|
52
|
+
type: "module",
|
|
53
|
+
publishConfig: {
|
|
54
|
+
access: "public"
|
|
55
|
+
},
|
|
56
|
+
main: "./dist/index.js",
|
|
57
|
+
types: "./dist/index.d.ts",
|
|
58
|
+
exports: {
|
|
59
|
+
".": {
|
|
60
|
+
types: "./dist/index.d.ts",
|
|
61
|
+
default: "./dist/index.js"
|
|
62
|
+
},
|
|
63
|
+
"./plugin": {
|
|
64
|
+
types: "./dist/plugin.d.ts",
|
|
65
|
+
default: "./dist/plugin.js"
|
|
66
|
+
},
|
|
67
|
+
"./browser-setup": {
|
|
68
|
+
types: "./dist/browser-setup.d.ts",
|
|
69
|
+
default: "./dist/browser-setup.js"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
files: [
|
|
73
|
+
"dist",
|
|
74
|
+
"LICENSE"
|
|
75
|
+
],
|
|
76
|
+
scripts: {
|
|
77
|
+
build: "tsup",
|
|
78
|
+
prepublishOnly: "pnpm build",
|
|
79
|
+
typecheck: "tsc --noEmit",
|
|
80
|
+
test: "vitest run"
|
|
81
|
+
},
|
|
82
|
+
peerDependencies: {
|
|
83
|
+
"@vitest/browser": ">=3.0.0",
|
|
84
|
+
vitest: ">=3.0.0"
|
|
85
|
+
},
|
|
86
|
+
dependencies: {
|
|
87
|
+
"rrweb-snapshot": "^2.1.0"
|
|
88
|
+
},
|
|
89
|
+
devDependencies: {
|
|
90
|
+
"@rrweb/types": "^2.0.0",
|
|
91
|
+
"@types/node": "^24.0.0",
|
|
92
|
+
"@uiverify/archive-core": "workspace:*",
|
|
93
|
+
"@vitest/browser": "^4.1.8",
|
|
94
|
+
tsup: "^8.5.0",
|
|
95
|
+
typescript: "^5.7.0",
|
|
96
|
+
vite: "^6.0.0",
|
|
97
|
+
vitest: "^4.1.8"
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
26
101
|
// src/capture.ts
|
|
102
|
+
var PRODUCER = { name: package_default.name, version: package_default.version };
|
|
27
103
|
var MAX_RESOURCE_BYTES = 10 * 1024 * 1024;
|
|
28
104
|
function isArchivableContentType(contentType) {
|
|
29
105
|
if (!contentType) return false;
|
|
@@ -87,7 +163,12 @@ async function capture(task, name) {
|
|
|
87
163
|
...deviceScaleFactor ? { deviceScaleFactor } : {},
|
|
88
164
|
colorScheme,
|
|
89
165
|
dom,
|
|
90
|
-
resources
|
|
166
|
+
resources,
|
|
167
|
+
producer: PRODUCER,
|
|
168
|
+
// The test file (project-root-relative), so UI Verify can locate this snapshot in the module graph
|
|
169
|
+
// for skip-unchanged. `task.file.name` is already root-relative — the same value snapshot ids derive
|
|
170
|
+
// their first segment from — so it lines up with the graph names the reporter emits.
|
|
171
|
+
...task.file?.name ? { sourcePath: task.file.name } : {}
|
|
91
172
|
};
|
|
92
173
|
await commands.__uiverifyWriteSnapshot(archived);
|
|
93
174
|
return id;
|
package/dist/index.js
CHANGED
package/dist/plugin.js
CHANGED
|
@@ -1,7 +1,144 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// ../archive-core/src/snapshot-file.ts
|
|
2
|
+
import { createHash } from "crypto";
|
|
3
|
+
function snapshotFileName(id) {
|
|
4
|
+
const slug = id.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
5
|
+
const hash = createHash("sha1").update(id).digest("hex").slice(0, 10);
|
|
6
|
+
return `${slug || "snapshot"}-${hash}.json`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// ../archive-core/src/out-dir.ts
|
|
10
|
+
import path from "path";
|
|
11
|
+
function resolveOutDir() {
|
|
12
|
+
return process.env.UIVERIFY_ARCHIVE_DIR ?? path.resolve(process.cwd(), "uiverify-archive");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ../archive-core/src/ui-verify.ts
|
|
16
|
+
var UI_VERIFY_GLOBAL = "__UI_VERIFY__";
|
|
17
|
+
var UI_VERIFY_MARKER_SCRIPT = `globalThis.${UI_VERIFY_GLOBAL} = true;`;
|
|
18
|
+
|
|
19
|
+
// ../archive-core/src/write.ts
|
|
20
|
+
import fs from "fs";
|
|
21
|
+
import path2 from "path";
|
|
22
|
+
function writeSnapshot(outDir, snapshot) {
|
|
23
|
+
const dir = path2.join(outDir, "snapshots");
|
|
24
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
25
|
+
const file = path2.join(dir, snapshotFileName(snapshot.id));
|
|
26
|
+
fs.writeFileSync(file, JSON.stringify(snapshot));
|
|
27
|
+
return file;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ../archive-core/src/finalize.ts
|
|
31
|
+
import fs2 from "fs";
|
|
32
|
+
import path3 from "path";
|
|
33
|
+
|
|
34
|
+
// src/graph-reporter.ts
|
|
35
|
+
import fs3 from "fs";
|
|
36
|
+
import path5 from "path";
|
|
37
|
+
|
|
38
|
+
// src/preview-stats.ts
|
|
39
|
+
import path4 from "path";
|
|
40
|
+
function toRepoRel(viteRoot, raw) {
|
|
41
|
+
if (!raw) return null;
|
|
42
|
+
const p = raw.replace(/\?.*$/, "");
|
|
43
|
+
if (p.includes("\0") || p.startsWith("virtual:")) return null;
|
|
44
|
+
if (!path4.isAbsolute(p)) return null;
|
|
45
|
+
if (/(^|[/\\])node_modules[/\\]/.test(p)) return null;
|
|
46
|
+
return path4.relative(viteRoot, p).split(path4.sep).join("/");
|
|
47
|
+
}
|
|
48
|
+
function buildPreviewStats(input) {
|
|
49
|
+
const reasonsByName = /* @__PURE__ */ new Map();
|
|
50
|
+
const ensure = (name) => {
|
|
51
|
+
let set = reasonsByName.get(name);
|
|
52
|
+
if (!set) reasonsByName.set(name, set = /* @__PURE__ */ new Set());
|
|
53
|
+
return set;
|
|
54
|
+
};
|
|
55
|
+
for (const mod of input.modules) {
|
|
56
|
+
const name = toRepoRel(input.viteRoot, mod.file ?? mod.id);
|
|
57
|
+
if (name === null) continue;
|
|
58
|
+
const reasons = ensure(name);
|
|
59
|
+
for (const imp of mod.importers ?? []) {
|
|
60
|
+
const r = toRepoRel(input.viteRoot, imp.file ?? imp.id);
|
|
61
|
+
if (r !== null && r !== name) reasons.add(r);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const testNames = input.testFiles.map((f) => toRepoRel(input.viteRoot, f)).filter((n) => n !== null);
|
|
65
|
+
for (const sf of input.setupFiles) {
|
|
66
|
+
const name = toRepoRel(input.viteRoot, sf);
|
|
67
|
+
if (name === null) continue;
|
|
68
|
+
const reasons = ensure(name);
|
|
69
|
+
for (const t of testNames) if (t !== name) reasons.add(t);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
modules: [...reasonsByName].map(([name, reasons]) => ({
|
|
73
|
+
name,
|
|
74
|
+
reasons: [...reasons].map((moduleName) => ({ moduleName }))
|
|
75
|
+
}))
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/graph-reporter.ts
|
|
80
|
+
function collectModules(map, out) {
|
|
81
|
+
if (typeof map?.forEach !== "function") return;
|
|
82
|
+
map.forEach((mod) => out.push(mod));
|
|
83
|
+
}
|
|
84
|
+
function buildStatsFromRun(vitest, testModules) {
|
|
85
|
+
const projects = vitest?.projects ?? [];
|
|
86
|
+
if (projects.length === 0) return null;
|
|
87
|
+
const testFilesByProject = /* @__PURE__ */ new Map();
|
|
88
|
+
for (const tm of testModules ?? []) {
|
|
89
|
+
if (!tm.project || !tm.moduleId) continue;
|
|
90
|
+
const arr = testFilesByProject.get(tm.project) ?? [];
|
|
91
|
+
arr.push(tm.moduleId);
|
|
92
|
+
testFilesByProject.set(tm.project, arr);
|
|
93
|
+
}
|
|
94
|
+
const reasonsByName = /* @__PURE__ */ new Map();
|
|
95
|
+
let any = false;
|
|
96
|
+
for (const project of projects) {
|
|
97
|
+
const vite = project.browser?.vite;
|
|
98
|
+
const viteRoot = vite?.config?.root;
|
|
99
|
+
if (!vite || !viteRoot) continue;
|
|
100
|
+
const modules = [];
|
|
101
|
+
collectModules((vite.environments?.client?.moduleGraph ?? vite.moduleGraph)?.idToModuleMap, modules);
|
|
102
|
+
if (modules.length === 0) continue;
|
|
103
|
+
const testFiles = [.../* @__PURE__ */ new Set([...testFilesByProject.get(project) ?? [], ...project.testFilesList ?? []])];
|
|
104
|
+
const stats = buildPreviewStats({ viteRoot, modules, setupFiles: project.config?.setupFiles ?? [], testFiles });
|
|
105
|
+
for (const m of stats.modules) {
|
|
106
|
+
let set = reasonsByName.get(m.name);
|
|
107
|
+
if (!set) reasonsByName.set(m.name, set = /* @__PURE__ */ new Set());
|
|
108
|
+
for (const r of m.reasons) set.add(r.moduleName);
|
|
109
|
+
}
|
|
110
|
+
any = true;
|
|
111
|
+
}
|
|
112
|
+
if (!any) return null;
|
|
113
|
+
return {
|
|
114
|
+
modules: [...reasonsByName].map(([name, reasons]) => ({
|
|
115
|
+
name,
|
|
116
|
+
reasons: [...reasons].map((moduleName) => ({ moduleName }))
|
|
117
|
+
}))
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function emitModuleGraph(outDir, vitest, testModules) {
|
|
121
|
+
const stats = buildStatsFromRun(vitest, testModules);
|
|
122
|
+
if (!stats) return false;
|
|
123
|
+
fs3.mkdirSync(outDir, { recursive: true });
|
|
124
|
+
fs3.writeFileSync(path5.join(outDir, "preview-stats.json"), JSON.stringify(stats));
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
function graphReporter(outDir) {
|
|
128
|
+
let vitest;
|
|
129
|
+
return {
|
|
130
|
+
onInit(ctx) {
|
|
131
|
+
vitest = ctx;
|
|
132
|
+
},
|
|
133
|
+
onTestRunEnd(testModules) {
|
|
134
|
+
try {
|
|
135
|
+
emitModuleGraph(outDir, vitest, testModules);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.warn("[uiverify] could not emit the module graph for --only-changed:", err);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
5
142
|
|
|
6
143
|
// src/plugin.ts
|
|
7
144
|
var SETUP_MODULE = "@uiverify/vitest/browser-setup";
|
|
@@ -16,6 +153,11 @@ function uiverifyPlugin(options = {}) {
|
|
|
16
153
|
optimizeDeps: { exclude: ["@uiverify/vitest"] },
|
|
17
154
|
test: {
|
|
18
155
|
setupFiles: [SETUP_MODULE],
|
|
156
|
+
// Emit Vite's module graph as `preview-stats.json` so `uiverify upload --only-changed` can skip
|
|
157
|
+
// unchanged snapshots. `"default"` is kept so the plugin doesn't silence Vitest's own output. If a
|
|
158
|
+
// user's own `reporters` config ends up displacing this, the only effect is that no graph ships and
|
|
159
|
+
// the server safely renders everything — add `graphReporter()` back to keep the skip savings.
|
|
160
|
+
reporters: ["default", graphReporter(outDir)],
|
|
19
161
|
provide: { __uiverify: { disableAutoSnapshot: options.disableAutoSnapshot ?? false } },
|
|
20
162
|
browser: {
|
|
21
163
|
commands: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uiverify/vitest",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Vitest browser-mode capture SDK for UI Verify (uiverify.ai): add uiverifyPlugin() to your Vitest config and each browser-mode test archives its final DOM (serialized DOM + resource bytes) to be replayed + visually diffed by UI Verify. Produces an archive the uiverify CLI uploads; talks to nothing at runtime.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"visual-testing",
|
package/dist/chunk-X2KT34IP.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
// ../archive-core/src/out-dir.ts
|
|
2
|
-
import path from "path";
|
|
3
|
-
function resolveOutDir() {
|
|
4
|
-
return process.env.UIVERIFY_ARCHIVE_DIR ?? path.resolve(process.cwd(), "uiverify-archive");
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
// ../archive-core/src/ui-verify.ts
|
|
8
|
-
var UI_VERIFY_GLOBAL = "__UI_VERIFY__";
|
|
9
|
-
var UI_VERIFY_MARKER_SCRIPT = `globalThis.${UI_VERIFY_GLOBAL} = true;`;
|
|
10
|
-
|
|
11
|
-
// ../archive-core/src/write.ts
|
|
12
|
-
import fs from "fs";
|
|
13
|
-
import path2 from "path";
|
|
14
|
-
|
|
15
|
-
// ../archive-core/src/snapshot-file.ts
|
|
16
|
-
import { createHash } from "crypto";
|
|
17
|
-
function snapshotFileName(id) {
|
|
18
|
-
const slug = id.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
19
|
-
const hash = createHash("sha1").update(id).digest("hex").slice(0, 10);
|
|
20
|
-
return `${slug || "snapshot"}-${hash}.json`;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// ../archive-core/src/write.ts
|
|
24
|
-
function writeSnapshot(outDir, snapshot) {
|
|
25
|
-
const dir = path2.join(outDir, "snapshots");
|
|
26
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
27
|
-
const file = path2.join(dir, snapshotFileName(snapshot.id));
|
|
28
|
-
fs.writeFileSync(file, JSON.stringify(snapshot));
|
|
29
|
-
return file;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// ../archive-core/src/finalize.ts
|
|
33
|
-
import fs2 from "fs";
|
|
34
|
-
import path3 from "path";
|
|
35
|
-
|
|
36
|
-
export {
|
|
37
|
-
resolveOutDir,
|
|
38
|
-
UI_VERIFY_GLOBAL,
|
|
39
|
-
writeSnapshot
|
|
40
|
-
};
|