@argos-ci/storybook 5.2.14 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/test-runner.cjs +1 -1
- package/dist/{test-runner.d.ts → test-runner.d.mts} +33 -27
- package/dist/test-runner.mjs +311 -0
- package/dist/vitest-plugin.d.mts +56 -0
- package/dist/vitest-plugin.mjs +372 -0
- package/dist/vitest-setup-file.mjs +57 -0
- package/dist/vitest.d.mts +62 -0
- package/dist/vitest.mjs +82 -0
- package/package.json +31 -31
- package/dist/test-runner.js +0 -271
- package/dist/vitest-plugin.d.ts +0 -53
- package/dist/vitest-plugin.js +0 -383
- package/dist/vitest-setup-file.js +0 -38
- package/dist/vitest.d.ts +0 -57
- package/dist/vitest.js +0 -60
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { DO_NOT_USE_setMetadataConfig, argosScreenshot } from "@argos-ci/playwright";
|
|
3
|
+
import { readVersionFromPackage } from "@argos-ci/util";
|
|
4
|
+
import { upload } from "@argos-ci/core";
|
|
5
|
+
import { dirname, resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
//#region src/utils/metadata.ts
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
/**
|
|
10
|
+
* Get the version of the Argos Playwright SDK.
|
|
11
|
+
*/
|
|
12
|
+
async function getArgosStorybookVersion() {
|
|
13
|
+
return readVersionFromPackage(require.resolve("@argos-ci/storybook/package.json"));
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/utils/storyMetadata.ts
|
|
17
|
+
/**
|
|
18
|
+
* Build the story metadata object that is attached to a screenshot.
|
|
19
|
+
*
|
|
20
|
+
* @param story - The story shape (id, tags, play flag).
|
|
21
|
+
* @param storyMode - The Storybook mode name (e.g. "dark", "mobile") as defined in `parameters.argos.modes`
|
|
22
|
+
*/
|
|
23
|
+
function getStoryMetadata(story, storyMode) {
|
|
24
|
+
return {
|
|
25
|
+
id: story.id,
|
|
26
|
+
tags: story.tags ?? [],
|
|
27
|
+
mode: storyMode ?? void 0,
|
|
28
|
+
play: Boolean(story.play)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/utils/parameters.ts
|
|
33
|
+
/**
|
|
34
|
+
* Get the default viewport size from the Storybook parameters.
|
|
35
|
+
*/
|
|
36
|
+
function getDefaultViewport(parameters) {
|
|
37
|
+
const defaultViewport = parameters?.viewport?.defaultViewport;
|
|
38
|
+
if (defaultViewport) return getViewport(parameters, defaultViewport);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get the viewport size from the Storybook parameters.
|
|
43
|
+
*/
|
|
44
|
+
function getViewport(parameters, viewportName) {
|
|
45
|
+
if (typeof viewportName === "number") return {
|
|
46
|
+
width: viewportName,
|
|
47
|
+
height: 720
|
|
48
|
+
};
|
|
49
|
+
const viewports = parameters?.viewport?.viewports;
|
|
50
|
+
if (viewports && viewportName in viewports) {
|
|
51
|
+
if ("styles" in viewports[viewportName] && viewports[viewportName].styles) {
|
|
52
|
+
const width = parseInt(viewports[viewportName].styles.width, 10);
|
|
53
|
+
const height = parseInt(viewports[viewportName].styles.height, 10);
|
|
54
|
+
if (!isNaN(width) && !isNaN(height)) return {
|
|
55
|
+
width,
|
|
56
|
+
height
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get the Argos parameters from the Storybook context.
|
|
64
|
+
*/
|
|
65
|
+
function getArgosParameters(parameters) {
|
|
66
|
+
if ("argos" in parameters && parameters.argos && typeof parameters.argos === "object") return parameters.argos;
|
|
67
|
+
if ("chromatic" in parameters && parameters.chromatic && typeof parameters.chromatic === "object") return parameters.chromatic;
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const DEFAULT_FIT_TO_CONTENT = {
|
|
71
|
+
padding: 16,
|
|
72
|
+
zoom: 2
|
|
73
|
+
};
|
|
74
|
+
function getFitToContentFromParameters(parameters) {
|
|
75
|
+
const argosParameters = getArgosParameters(parameters);
|
|
76
|
+
if (argosParameters && "fitToContent" in argosParameters) {
|
|
77
|
+
if (typeof argosParameters.fitToContent === "boolean") return argosParameters.fitToContent ? DEFAULT_FIT_TO_CONTENT : null;
|
|
78
|
+
if (typeof argosParameters.fitToContent === "object" && argosParameters.fitToContent) return {
|
|
79
|
+
...DEFAULT_FIT_TO_CONTENT,
|
|
80
|
+
...argosParameters.fitToContent
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return DEFAULT_FIT_TO_CONTENT;
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/utils/screenshot.ts
|
|
87
|
+
/**
|
|
88
|
+
* Take a screenshot in the context of a Storybook story.
|
|
89
|
+
*/
|
|
90
|
+
async function storybookArgosScreenshot(handler, context, options) {
|
|
91
|
+
const argosOptions = {
|
|
92
|
+
...options,
|
|
93
|
+
stabilize: options?.stabilize ?? {
|
|
94
|
+
waitForAriaBusy: false,
|
|
95
|
+
...typeof options?.stabilize === "object" ? options.stabilize : {}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const version = await getArgosStorybookVersion();
|
|
99
|
+
const storyUrl = `http://localhost:6006/?path=/story/${context.story.id}`;
|
|
100
|
+
const metadata = {
|
|
101
|
+
sdk: {
|
|
102
|
+
name: "@argos-ci/storybook",
|
|
103
|
+
version
|
|
104
|
+
},
|
|
105
|
+
playwrightLibraries: context.playwrightLibraries,
|
|
106
|
+
url: storyUrl,
|
|
107
|
+
test: context.test,
|
|
108
|
+
story: getStoryMetadata(context.story)
|
|
109
|
+
};
|
|
110
|
+
const modes = getArgosParameters(context.story.parameters)?.modes;
|
|
111
|
+
const allAttachments = [];
|
|
112
|
+
if (context.mode === "automatic") if (modes) for (const [name, globals] of Object.entries(modes)) {
|
|
113
|
+
if (globals.disabled) continue;
|
|
114
|
+
await handler.evaluate((name) => {
|
|
115
|
+
globalThis.__ARGOS_CURRENT_MODE = name;
|
|
116
|
+
}, name);
|
|
117
|
+
const attachments = await runHooksAndScreenshot({
|
|
118
|
+
handler,
|
|
119
|
+
context,
|
|
120
|
+
metadata,
|
|
121
|
+
options: argosOptions,
|
|
122
|
+
suffix: getModeSuffix(name),
|
|
123
|
+
mode: name,
|
|
124
|
+
globals: {
|
|
125
|
+
...context.story.globals,
|
|
126
|
+
...globals
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
allAttachments.push(...attachments);
|
|
130
|
+
await context.setViewportSize("initial");
|
|
131
|
+
await handler.evaluate(() => {
|
|
132
|
+
delete globalThis.__ARGOS_CURRENT_MODE;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
const attachments = await runHooksAndScreenshot({
|
|
137
|
+
handler,
|
|
138
|
+
context,
|
|
139
|
+
metadata,
|
|
140
|
+
options: argosOptions,
|
|
141
|
+
mode: null,
|
|
142
|
+
globals: context.story.globals ?? {}
|
|
143
|
+
});
|
|
144
|
+
await context.setViewportSize("initial");
|
|
145
|
+
allAttachments.push(...attachments);
|
|
146
|
+
}
|
|
147
|
+
else if (context.mode === "manual") {
|
|
148
|
+
const currentMode = await handler.evaluate(() => {
|
|
149
|
+
return globalThis.__ARGOS_CURRENT_MODE || null;
|
|
150
|
+
});
|
|
151
|
+
if (modes && !currentMode) return;
|
|
152
|
+
await markStoryAsRendered(handler, context.story.id);
|
|
153
|
+
DO_NOT_USE_setMetadataConfig({
|
|
154
|
+
...metadata,
|
|
155
|
+
story: getStoryMetadata(context.story, currentMode)
|
|
156
|
+
});
|
|
157
|
+
await argosScreenshot(handler, composeName(context.name, getModeSuffix(currentMode)), options);
|
|
158
|
+
}
|
|
159
|
+
await context.afterScreenshot?.({
|
|
160
|
+
handler,
|
|
161
|
+
globals: {}
|
|
162
|
+
});
|
|
163
|
+
return allAttachments;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Run the story with the given globals.
|
|
167
|
+
*/
|
|
168
|
+
async function runStory(args) {
|
|
169
|
+
const { handler, globals, storyId } = args;
|
|
170
|
+
await handler.evaluate(async (globals) => {
|
|
171
|
+
if ("__ARGOS_STORYBOOK_STORY" in globalThis) {
|
|
172
|
+
const storyFn = globalThis.__ARGOS_STORYBOOK_STORY;
|
|
173
|
+
if (!storyFn) throw new Error("@argos-ci/storybook: Unable to find `__ARGOS_STORYBOOK_STORY`.");
|
|
174
|
+
const canvasElement = (() => {
|
|
175
|
+
document.getElementById("storybook-root")?.remove();
|
|
176
|
+
const canvasElement = document.createElement("div");
|
|
177
|
+
canvasElement.id = "storybook-root";
|
|
178
|
+
document.body.appendChild(canvasElement);
|
|
179
|
+
return canvasElement;
|
|
180
|
+
})();
|
|
181
|
+
await storyFn.run({
|
|
182
|
+
globals,
|
|
183
|
+
canvasElement
|
|
184
|
+
});
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const channel = (() => {
|
|
188
|
+
if ("__STORYBOOK_PREVIEW__" in globalThis) return globalThis.__STORYBOOK_PREVIEW__.channel;
|
|
189
|
+
return null;
|
|
190
|
+
})();
|
|
191
|
+
if (!channel) throw new Error("@argos-ci/storybook: Unable to find `__STORYBOOK_PREVIEW__`.");
|
|
192
|
+
const initialGlobals = channel.last("globalsUpdated")?.[0].initialGlobals;
|
|
193
|
+
channel.emit("updateGlobals", { globals: {
|
|
194
|
+
...initialGlobals,
|
|
195
|
+
...globals
|
|
196
|
+
} });
|
|
197
|
+
}, globals);
|
|
198
|
+
await markStoryAsRendered(handler, storyId);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Notify Storybook that the story has been rendered.
|
|
202
|
+
* It will run all the necessary hooks (like pseudo-states).
|
|
203
|
+
*/
|
|
204
|
+
async function markStoryAsRendered(handler, storyId) {
|
|
205
|
+
await handler.evaluate((storyId) => {
|
|
206
|
+
if ("__ARGOS_STORYBOOK_STORY" in globalThis) {
|
|
207
|
+
const addons = globalThis.__STORYBOOK_ADDONS_PREVIEW;
|
|
208
|
+
if (!addons) throw new Error("@argos-ci/storybook: Unable to find `__STORYBOOK_ADDONS_PREVIEW`.");
|
|
209
|
+
addons.getChannel().emit("storyRendered", storyId);
|
|
210
|
+
}
|
|
211
|
+
}, storyId);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Wait for the page to be ready and take a screenshot.
|
|
215
|
+
*/
|
|
216
|
+
async function runHooksAndScreenshot(args) {
|
|
217
|
+
const { handler, context, options, globals, metadata, mode } = args;
|
|
218
|
+
await runStory({
|
|
219
|
+
handler,
|
|
220
|
+
globals,
|
|
221
|
+
storyId: context.story.id
|
|
222
|
+
});
|
|
223
|
+
const viewport = (globals.viewport ? getViewport(context.story.parameters, globals.viewport) : null) ?? getDefaultViewport(context.story.parameters) ?? "default";
|
|
224
|
+
await context.setViewportSize(viewport);
|
|
225
|
+
DO_NOT_USE_setMetadataConfig({
|
|
226
|
+
...metadata,
|
|
227
|
+
story: getStoryMetadata(context.story, mode),
|
|
228
|
+
viewport: viewport && viewport !== "default" ? viewport : void 0
|
|
229
|
+
});
|
|
230
|
+
await context.beforeScreenshot?.({
|
|
231
|
+
handler,
|
|
232
|
+
globals
|
|
233
|
+
});
|
|
234
|
+
return argosScreenshot(handler, composeName(context.name, args.suffix), options);
|
|
235
|
+
}
|
|
236
|
+
function getModeSuffix(mode) {
|
|
237
|
+
return mode ? ` mode-[${mode}]` : "";
|
|
238
|
+
}
|
|
239
|
+
function composeName(name, suffix) {
|
|
240
|
+
return name + (suffix ?? "");
|
|
241
|
+
}
|
|
242
|
+
//#endregion
|
|
243
|
+
//#region src/vitest-reporter.ts
|
|
244
|
+
var ArgosReporter = class {
|
|
245
|
+
vitest;
|
|
246
|
+
config;
|
|
247
|
+
constructor(config) {
|
|
248
|
+
this.config = config;
|
|
249
|
+
}
|
|
250
|
+
onInit(vitest) {
|
|
251
|
+
this.vitest = vitest;
|
|
252
|
+
}
|
|
253
|
+
async onFinished() {
|
|
254
|
+
await this.onTestRunEnd();
|
|
255
|
+
}
|
|
256
|
+
async onTestRunEnd() {
|
|
257
|
+
if (this.vitest.config.watch) return;
|
|
258
|
+
const res = await upload(this.config);
|
|
259
|
+
console.log(`✅ Argos build created: ${res.build.url}`);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/vitest-plugin.ts
|
|
264
|
+
/**
|
|
265
|
+
* Create a command for taking Argos screenshots in Vitest.
|
|
266
|
+
*/
|
|
267
|
+
const createArgosScreenshotCommand = (pluginOptions) => {
|
|
268
|
+
const screenshotOptions = pluginOptions ?? {};
|
|
269
|
+
return async (ctx, testContext) => {
|
|
270
|
+
const frame = await ctx.frame();
|
|
271
|
+
const fitToContent = getFitToContentFromParameters(testContext.story.parameters);
|
|
272
|
+
const after = await before(ctx);
|
|
273
|
+
const attachments = await storybookArgosScreenshot(frame, {
|
|
274
|
+
...testContext,
|
|
275
|
+
playwrightLibraries: ["@storybook/addon-vitest"],
|
|
276
|
+
setViewportSize: async (size) => {
|
|
277
|
+
await ctx.page.evaluate(({ size, fullPage }) => {
|
|
278
|
+
const iframe = document.querySelector("iframe[data-vitest=\"true\"]");
|
|
279
|
+
if (!(iframe instanceof HTMLIFrameElement)) throw new Error("Vitest iframe not found");
|
|
280
|
+
if (!iframe.contentDocument) throw new Error("Vitest iframe contentDocument not found");
|
|
281
|
+
if (size === "initial") {
|
|
282
|
+
if (iframe.dataset.initialWidth && iframe.dataset.initialHeight) {
|
|
283
|
+
iframe.style.width = iframe.dataset.initialWidth;
|
|
284
|
+
iframe.style.height = iframe.dataset.initialHeight;
|
|
285
|
+
}
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (!iframe.dataset.initialWidth && !iframe.dataset.initialHeight) {
|
|
289
|
+
iframe.dataset.initialWidth = iframe.style.width;
|
|
290
|
+
iframe.dataset.initialHeight = iframe.style.height;
|
|
291
|
+
}
|
|
292
|
+
if (size !== "default") iframe.style.width = `${size.width}px`;
|
|
293
|
+
if (fullPage) {
|
|
294
|
+
if (!iframe.contentWindow) throw new Error(`Can't access iframe window`);
|
|
295
|
+
const viewportHeight = size === "default" ? iframe.contentWindow.innerHeight : size.height;
|
|
296
|
+
iframe.style.height = "auto";
|
|
297
|
+
iframe.style.height = viewportHeight < iframe.contentDocument.body.offsetHeight ? `${iframe.contentDocument.body.offsetHeight}px` : "100%";
|
|
298
|
+
} else if (size !== "default") {
|
|
299
|
+
iframe.style.height = "auto";
|
|
300
|
+
iframe.style.height = `${size.height}px`;
|
|
301
|
+
}
|
|
302
|
+
}, {
|
|
303
|
+
size,
|
|
304
|
+
fullPage: screenshotOptions.fullPage ?? !fitToContent
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}, applyFitToContent(screenshotOptions, fitToContent));
|
|
308
|
+
await after();
|
|
309
|
+
return attachments;
|
|
310
|
+
};
|
|
311
|
+
};
|
|
312
|
+
/**
|
|
313
|
+
* Run before taking the screenshots.
|
|
314
|
+
* Remove the scale from vitest "vitest-tester" div
|
|
315
|
+
* to avoid ending up with small screenshots.
|
|
316
|
+
* @returns A function to restore the scale after the test.
|
|
317
|
+
*/
|
|
318
|
+
async function before(ctx) {
|
|
319
|
+
await ctx.page.evaluate(() => {
|
|
320
|
+
const tester = document.getElementById("vitest-tester");
|
|
321
|
+
if (!(tester instanceof HTMLElement)) return;
|
|
322
|
+
if (!tester.getAttribute("data-scale")) throw new Error("Vitest iframe data-scale attribute not found");
|
|
323
|
+
tester.dataset.bckTransform = tester.style.transform;
|
|
324
|
+
tester.style.transform = `scale(1)`;
|
|
325
|
+
});
|
|
326
|
+
return async () => {
|
|
327
|
+
await ctx.page.evaluate(() => {
|
|
328
|
+
const tester = document.getElementById("vitest-tester");
|
|
329
|
+
if (!(tester instanceof HTMLElement)) return;
|
|
330
|
+
tester.style.transform = tester.dataset.bckTransform ?? "";
|
|
331
|
+
});
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Apply fitToContent options to the screenshot options.
|
|
336
|
+
*/
|
|
337
|
+
function applyFitToContent(options, fitToContent) {
|
|
338
|
+
if (!fitToContent) return options;
|
|
339
|
+
const { padding, zoom } = fitToContent;
|
|
340
|
+
return {
|
|
341
|
+
...options,
|
|
342
|
+
element: "body",
|
|
343
|
+
argosCSS: `body { padding: ${padding}px; width: fit-content; height: fit-content; min-height: initial; zoom: ${zoom}; }` + (options?.argosCSS ?? "")
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
const cwd = process.cwd();
|
|
347
|
+
function argosVitestPlugin(options) {
|
|
348
|
+
const { root: unresolvedRoot = "./screenshots", uploadToArgos, ...otherOptions } = options ?? {};
|
|
349
|
+
const root = resolve(cwd, unresolvedRoot);
|
|
350
|
+
const setupFile = resolve(dirname(fileURLToPath(import.meta.url)), "./vitest-setup-file.mjs");
|
|
351
|
+
return {
|
|
352
|
+
name: "@argos-ci/storybook/vitest-plugin",
|
|
353
|
+
configureVitest({ vitest, project }) {
|
|
354
|
+
project.config.setupFiles.push(setupFile);
|
|
355
|
+
if (uploadToArgos) vitest.config.reporters.push(new ArgosReporter({
|
|
356
|
+
...otherOptions,
|
|
357
|
+
root
|
|
358
|
+
}));
|
|
359
|
+
},
|
|
360
|
+
config() {
|
|
361
|
+
return {
|
|
362
|
+
optimizeDeps: { include: ["@argos-ci/storybook/internal/vitest-setup-file"] },
|
|
363
|
+
test: { browser: { commands: { argosScreenshot: createArgosScreenshotCommand({
|
|
364
|
+
...otherOptions,
|
|
365
|
+
root
|
|
366
|
+
}) } } }
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
//#endregion
|
|
372
|
+
export { argosVitestPlugin, createArgosScreenshotCommand };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { afterEach } from "vitest";
|
|
2
|
+
//#region src/utils/storyMetadata.ts
|
|
3
|
+
function toArray(v) {
|
|
4
|
+
return Array.isArray(v) ? v : v ? [v] : [];
|
|
5
|
+
}
|
|
6
|
+
function mergeTags(...tags) {
|
|
7
|
+
const merged = tags.flatMap((tags) => toArray(tags));
|
|
8
|
+
return Array.from(new Set(merged)).filter((tag) => typeof tag === "string" && tag !== "");
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Return whether a story context has a play function.
|
|
12
|
+
* Handles both the `play` field (plain story function) and the legacy
|
|
13
|
+
* `playFunction` field present in older Storybook test-runner contexts.
|
|
14
|
+
*/
|
|
15
|
+
function hasPlay(storyContext) {
|
|
16
|
+
if (!storyContext || typeof storyContext !== "object") return false;
|
|
17
|
+
return "play" in storyContext && typeof storyContext.play === "function" || "playFunction" in storyContext && typeof storyContext.playFunction === "function";
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/vitest.ts
|
|
21
|
+
/**
|
|
22
|
+
* Setup Argos hooks for Vitest.
|
|
23
|
+
*/
|
|
24
|
+
function setupArgos(api) {
|
|
25
|
+
api.afterEach(async (ctx) => {
|
|
26
|
+
const story = "story" in ctx ? ctx.story : null;
|
|
27
|
+
if (!story) throw new Error(`@argos-ci/storybook/vitest-plugin should be used with @storybook/addon-vitest/vitest-plugin`);
|
|
28
|
+
const { server } = await import("vitest/browser");
|
|
29
|
+
globalThis.__ARGOS_STORYBOOK_STORY = story;
|
|
30
|
+
await server.commands.argosScreenshot({
|
|
31
|
+
mode: "automatic",
|
|
32
|
+
name: story.id,
|
|
33
|
+
story: {
|
|
34
|
+
id: story.id,
|
|
35
|
+
tags: mergeTags(story.tags, story.parameters.tags),
|
|
36
|
+
play: hasPlay(story),
|
|
37
|
+
parameters: story.parameters,
|
|
38
|
+
globals: story.globals
|
|
39
|
+
},
|
|
40
|
+
test: {
|
|
41
|
+
id: ctx.task.id,
|
|
42
|
+
title: ctx.task.name,
|
|
43
|
+
titlePath: [ctx.task.file.name, ctx.task.name],
|
|
44
|
+
location: {
|
|
45
|
+
line: ctx.task.location?.line ?? 1,
|
|
46
|
+
column: ctx.task.location?.column ?? 1,
|
|
47
|
+
file: ctx.task.file.filepath
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/vitest-setup-file.ts
|
|
55
|
+
setupArgos({ afterEach });
|
|
56
|
+
//#endregion
|
|
57
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as vitest from "vitest";
|
|
2
|
+
import { ArgosAttachment, ArgosScreenshotOptions as ArgosScreenshotOptions$1, MetadataConfig } from "@argos-ci/playwright";
|
|
3
|
+
import { Frame, Page, ViewportSize } from "playwright";
|
|
4
|
+
//#region src/utils/parameters.d.ts
|
|
5
|
+
type StorybookGlobals = Record<string, any>;
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/utils/screenshot.d.ts
|
|
8
|
+
type StorybookScreenshotContext<Handler extends Page | Frame> = {
|
|
9
|
+
/**
|
|
10
|
+
* - "manual" means the `argosScreenshot` has been called in the story play function.
|
|
11
|
+
* - "automatic" means the screenshot is taken automatically after each test.
|
|
12
|
+
*/
|
|
13
|
+
mode: "manual" | "automatic";
|
|
14
|
+
name: string;
|
|
15
|
+
playwrightLibraries: string[];
|
|
16
|
+
test?: MetadataConfig["test"];
|
|
17
|
+
setViewportSize: (size: ViewportSize | "default" | "initial") => Promise<void>;
|
|
18
|
+
beforeScreenshot?: (input: {
|
|
19
|
+
handler: Handler;
|
|
20
|
+
globals: StorybookGlobals;
|
|
21
|
+
}) => Promise<void>;
|
|
22
|
+
afterScreenshot?: (input: {
|
|
23
|
+
handler: Handler;
|
|
24
|
+
globals: StorybookGlobals;
|
|
25
|
+
}) => Promise<void>;
|
|
26
|
+
story: {
|
|
27
|
+
id: string;
|
|
28
|
+
tags?: string[];
|
|
29
|
+
play?: boolean;
|
|
30
|
+
parameters: Record<string, any>;
|
|
31
|
+
globals: StorybookGlobals | null;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
type ArgosScreenshotOptions = Omit<ArgosScreenshotOptions$1, "viewports">;
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/vitest-plugin.d.ts
|
|
37
|
+
type ArgosScreenshotCommandArgs = [Pick<StorybookScreenshotContext<Frame>, "name" | "story" | "test" | "mode">];
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/vitest.d.ts
|
|
40
|
+
declare module "vitest/browser" {
|
|
41
|
+
interface BrowserCommands {
|
|
42
|
+
argosScreenshot: (...args: ArgosScreenshotCommandArgs) => Promise<ArgosAttachment[]>;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Setup Argos hooks for Vitest.
|
|
47
|
+
*/
|
|
48
|
+
declare function setupArgos(api: {
|
|
49
|
+
afterEach: typeof vitest.afterEach;
|
|
50
|
+
}): void;
|
|
51
|
+
/**
|
|
52
|
+
* Take an Argos screenshot of a story in Vitest.
|
|
53
|
+
*/
|
|
54
|
+
declare function argosScreenshot(story: {
|
|
55
|
+
parameters: Record<string, any>;
|
|
56
|
+
globals: StorybookGlobals | null;
|
|
57
|
+
id: string;
|
|
58
|
+
tags?: string[];
|
|
59
|
+
play?: unknown;
|
|
60
|
+
}, name: string): Promise<void>;
|
|
61
|
+
//#endregion
|
|
62
|
+
export { type ArgosScreenshotOptions, argosScreenshot, setupArgos };
|
package/dist/vitest.mjs
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
//#region src/utils/storyMetadata.ts
|
|
2
|
+
function toArray(v) {
|
|
3
|
+
return Array.isArray(v) ? v : v ? [v] : [];
|
|
4
|
+
}
|
|
5
|
+
function mergeTags(...tags) {
|
|
6
|
+
const merged = tags.flatMap((tags) => toArray(tags));
|
|
7
|
+
return Array.from(new Set(merged)).filter((tag) => typeof tag === "string" && tag !== "");
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Return whether a story context has a play function.
|
|
11
|
+
* Handles both the `play` field (plain story function) and the legacy
|
|
12
|
+
* `playFunction` field present in older Storybook test-runner contexts.
|
|
13
|
+
*/
|
|
14
|
+
function hasPlay(storyContext) {
|
|
15
|
+
if (!storyContext || typeof storyContext !== "object") return false;
|
|
16
|
+
return "play" in storyContext && typeof storyContext.play === "function" || "playFunction" in storyContext && typeof storyContext.playFunction === "function";
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/vitest.ts
|
|
20
|
+
/**
|
|
21
|
+
* Setup Argos hooks for Vitest.
|
|
22
|
+
*/
|
|
23
|
+
function setupArgos(api) {
|
|
24
|
+
api.afterEach(async (ctx) => {
|
|
25
|
+
const story = "story" in ctx ? ctx.story : null;
|
|
26
|
+
if (!story) throw new Error(`@argos-ci/storybook/vitest-plugin should be used with @storybook/addon-vitest/vitest-plugin`);
|
|
27
|
+
const { server } = await import("vitest/browser");
|
|
28
|
+
globalThis.__ARGOS_STORYBOOK_STORY = story;
|
|
29
|
+
await server.commands.argosScreenshot({
|
|
30
|
+
mode: "automatic",
|
|
31
|
+
name: story.id,
|
|
32
|
+
story: {
|
|
33
|
+
id: story.id,
|
|
34
|
+
tags: mergeTags(story.tags, story.parameters.tags),
|
|
35
|
+
play: hasPlay(story),
|
|
36
|
+
parameters: story.parameters,
|
|
37
|
+
globals: story.globals
|
|
38
|
+
},
|
|
39
|
+
test: {
|
|
40
|
+
id: ctx.task.id,
|
|
41
|
+
title: ctx.task.name,
|
|
42
|
+
titlePath: [ctx.task.file.name, ctx.task.name],
|
|
43
|
+
location: {
|
|
44
|
+
line: ctx.task.location?.line ?? 1,
|
|
45
|
+
column: ctx.task.location?.column ?? 1,
|
|
46
|
+
file: ctx.task.file.filepath
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Take an Argos screenshot of a story in Vitest.
|
|
54
|
+
*/
|
|
55
|
+
async function argosScreenshot(story, name) {
|
|
56
|
+
if (!await checkIsVitestEnv()) return;
|
|
57
|
+
const { server } = await import("vitest/browser");
|
|
58
|
+
await server.commands.argosScreenshot({
|
|
59
|
+
mode: "manual",
|
|
60
|
+
name: `${story.id}/${name}`,
|
|
61
|
+
story: {
|
|
62
|
+
id: story.id,
|
|
63
|
+
tags: mergeTags(story.tags, story.parameters.tags),
|
|
64
|
+
play: hasPlay(story),
|
|
65
|
+
parameters: story.parameters,
|
|
66
|
+
globals: story.globals
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if we are running in a Vitest environment.
|
|
72
|
+
*/
|
|
73
|
+
async function checkIsVitestEnv() {
|
|
74
|
+
try {
|
|
75
|
+
await import("vitest");
|
|
76
|
+
return true;
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
export { argosScreenshot, setupArgos };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@argos-ci/storybook",
|
|
3
3
|
"description": "Visual testing for Storybook test runner.",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.3.0",
|
|
5
5
|
"author": "Smooth Code",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -27,59 +27,59 @@
|
|
|
27
27
|
"type": "module",
|
|
28
28
|
"exports": {
|
|
29
29
|
"./test-runner": {
|
|
30
|
-
"types": "./dist/test-runner.d.
|
|
31
|
-
"import": "./dist/test-runner.
|
|
30
|
+
"types": "./dist/test-runner.d.mts",
|
|
31
|
+
"import": "./dist/test-runner.mjs",
|
|
32
32
|
"require": "./dist/test-runner.cjs",
|
|
33
33
|
"default": "./dist/test-runner.cjs"
|
|
34
34
|
},
|
|
35
35
|
"./vitest": {
|
|
36
|
-
"types": "./dist/vitest.d.
|
|
37
|
-
"import": "./dist/vitest.
|
|
38
|
-
"default": "./dist/vitest.
|
|
36
|
+
"types": "./dist/vitest.d.mts",
|
|
37
|
+
"import": "./dist/vitest.mjs",
|
|
38
|
+
"default": "./dist/vitest.mjs"
|
|
39
39
|
},
|
|
40
40
|
"./vitest-plugin": {
|
|
41
|
-
"types": "./dist/vitest-plugin.d.
|
|
42
|
-
"import": "./dist/vitest-plugin.
|
|
43
|
-
"default": "./dist/vitest-plugin.
|
|
41
|
+
"types": "./dist/vitest-plugin.d.mts",
|
|
42
|
+
"import": "./dist/vitest-plugin.mjs",
|
|
43
|
+
"default": "./dist/vitest-plugin.mjs"
|
|
44
44
|
},
|
|
45
|
-
"./internal/vitest-setup-file": "./dist/vitest-setup-file.
|
|
45
|
+
"./internal/vitest-setup-file": "./dist/vitest-setup-file.mjs",
|
|
46
46
|
"./package.json": "./package.json"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
49
|
"node": ">=20.0.0"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@argos-ci/playwright": "6.
|
|
53
|
-
"@argos-ci/util": "3.
|
|
52
|
+
"@argos-ci/playwright": "6.6.0",
|
|
53
|
+
"@argos-ci/util": "3.4.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@argos-ci/cli": "4.1.
|
|
57
|
-
"@argos-ci/core": "5.1.
|
|
56
|
+
"@argos-ci/cli": "4.1.3",
|
|
57
|
+
"@argos-ci/core": "5.1.3",
|
|
58
58
|
"@argos-ci/util": "workspace:*",
|
|
59
|
-
"@storybook/addon-docs": "^10.
|
|
60
|
-
"@storybook/addon-links": "^10.
|
|
61
|
-
"@storybook/addon-themes": "^10.
|
|
62
|
-
"@storybook/addon-vitest": "^10.
|
|
63
|
-
"@storybook/react-vite": "^10.
|
|
64
|
-
"@storybook/test-runner": "^0.24.
|
|
65
|
-
"@types/react": "^19.2.
|
|
59
|
+
"@storybook/addon-docs": "^10.3.3",
|
|
60
|
+
"@storybook/addon-links": "^10.3.3",
|
|
61
|
+
"@storybook/addon-themes": "^10.3.3",
|
|
62
|
+
"@storybook/addon-vitest": "^10.3.3",
|
|
63
|
+
"@storybook/react-vite": "^10.3.3",
|
|
64
|
+
"@storybook/test-runner": "^0.24.3",
|
|
65
|
+
"@types/react": "^19.2.14",
|
|
66
66
|
"@types/react-dom": "^19.2.3",
|
|
67
|
-
"@vitest/browser": "^4.
|
|
68
|
-
"@vitest/browser-playwright": "^4.
|
|
69
|
-
"@vitest/coverage-v8": "^4.
|
|
70
|
-
"@vitest/ui": "^4.
|
|
67
|
+
"@vitest/browser": "^4.1.2",
|
|
68
|
+
"@vitest/browser-playwright": "^4.1.2",
|
|
69
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
70
|
+
"@vitest/ui": "^4.1.2",
|
|
71
71
|
"http-server": "^14.1.1",
|
|
72
|
-
"playwright": "^1.58.
|
|
72
|
+
"playwright": "^1.58.2",
|
|
73
73
|
"prop-types": "^15.8.1",
|
|
74
74
|
"react": "^19.2.4",
|
|
75
75
|
"react-dom": "^19.2.4",
|
|
76
|
-
"storybook": "^10.
|
|
77
|
-
"storybook-addon-pseudo-states": "^10.
|
|
76
|
+
"storybook": "^10.3.3",
|
|
77
|
+
"storybook-addon-pseudo-states": "^10.3.3",
|
|
78
78
|
"vitest": "catalog:",
|
|
79
|
-
"wait-on": "^9.0.
|
|
79
|
+
"wait-on": "^9.0.4"
|
|
80
80
|
},
|
|
81
81
|
"scripts": {
|
|
82
|
-
"build": "
|
|
82
|
+
"build": "tsdown && cp ./src/test-runner.cjs ./dist",
|
|
83
83
|
"storybook": "storybook dev -p 6006",
|
|
84
84
|
"build-storybook": "storybook build",
|
|
85
85
|
"test-storybook": "cross-env NODE_NO_WARNINGS=1 NODE_OPTIONS=--experimental-vm-modules test-storybook",
|
|
@@ -98,5 +98,5 @@
|
|
|
98
98
|
"check-format": "prettier --check --ignore-unknown --ignore-path=./.gitignore --ignore-path=../../.gitignore --ignore-path=../../.prettierignore .",
|
|
99
99
|
"lint": "eslint ."
|
|
100
100
|
},
|
|
101
|
-
"gitHead": "
|
|
101
|
+
"gitHead": "b49d186d5eb6e1338982a4cd5c3cb29e6cb0b7af"
|
|
102
102
|
}
|