@argos-ci/storybook 5.2.15 → 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.
@@ -1,5 +1,5 @@
1
1
  const argosScreenshot = async (...args) => {
2
- const { argosScreenshot } = await import("./test-runner.js");
2
+ const { argosScreenshot } = await import("./test-runner.mjs");
3
3
  return argosScreenshot(...args);
4
4
  };
5
5
  exports.argosScreenshot = argosScreenshot;
@@ -1,36 +1,39 @@
1
- import { TestContext } from '@storybook/test-runner';
2
- import { ArgosScreenshotOptions as ArgosScreenshotOptions$1 } from '@argos-ci/playwright';
3
- import { Page } from 'playwright';
1
+ import { TestContext } from "@storybook/test-runner";
2
+ import { ArgosScreenshotOptions as ArgosScreenshotOptions$1 } from "@argos-ci/playwright";
3
+ import { Page } from "playwright";
4
4
 
5
+ //#region src/utils/parameters.d.ts
5
6
  type StorybookGlobals = Record<string, any>;
6
7
  type FitToContent = {
7
- /**
8
- * Padding around the content in pixels.
9
- * @default 16
10
- */
11
- padding: number;
12
- /**
13
- * Zoom level for the content.
14
- * @default 2
15
- */
16
- zoom: number;
8
+ /**
9
+ * Padding around the content in pixels.
10
+ * @default 16
11
+ */
12
+ padding: number;
13
+ /**
14
+ * Zoom level for the content.
15
+ * @default 2
16
+ */
17
+ zoom: number;
17
18
  };
18
19
  /**
19
20
  * Argos parameters in Storybook.
20
21
  */
21
22
  interface ArgosStorybookParameters {
22
- /**
23
- * Modes for the story.
24
- */
25
- modes?: Record<string, StorybookGlobals>;
26
- /**
27
- * Fit to content option for the story.
28
- */
29
- fitToContent?: boolean | Partial<FitToContent>;
23
+ /**
24
+ * Modes for the story.
25
+ */
26
+ modes?: Record<string, StorybookGlobals>;
27
+ /**
28
+ * Fit to content option for the story.
29
+ */
30
+ fitToContent?: boolean | Partial<FitToContent>;
30
31
  }
31
-
32
+ //#endregion
33
+ //#region src/utils/screenshot.d.ts
32
34
  type ArgosScreenshotOptions = Omit<ArgosScreenshotOptions$1, "viewports">;
33
-
35
+ //#endregion
36
+ //#region src/test-runner.d.ts
34
37
  /**
35
38
  * Stabilize the UI and takes a screenshot of the application under test.
36
39
  *
@@ -41,14 +44,17 @@ declare function argosScreenshot(
41
44
  /**
42
45
  * Playwright `page` object.
43
46
  */
44
- page: Page,
47
+
48
+ page: Page,
45
49
  /**
46
50
  * Context of the test.
47
51
  */
48
- context: TestContext,
52
+
53
+ context: TestContext,
49
54
  /**
50
55
  * Options for the screenshot.
51
56
  */
52
- options?: ArgosScreenshotOptions): Promise<void>;
53
57
 
54
- export { type ArgosScreenshotOptions, type ArgosStorybookParameters, argosScreenshot };
58
+ options?: ArgosScreenshotOptions): Promise<void>;
59
+ //#endregion
60
+ export { type ArgosScreenshotOptions, type ArgosStorybookParameters, argosScreenshot };
@@ -0,0 +1,311 @@
1
+ import { createRequire } from "node:module";
2
+ import { getStoryContext, waitForPageReady } from "@storybook/test-runner";
3
+ import { DO_NOT_USE_setMetadataConfig, argosScreenshot as argosScreenshot$1 } from "@argos-ci/playwright";
4
+ import { readVersionFromPackage } from "@argos-ci/util";
5
+ //#region src/utils/metadata.ts
6
+ const require = createRequire(import.meta.url);
7
+ /**
8
+ * Get the version of the Argos Playwright SDK.
9
+ */
10
+ async function getArgosStorybookVersion() {
11
+ return readVersionFromPackage(require.resolve("@argos-ci/storybook/package.json"));
12
+ }
13
+ //#endregion
14
+ //#region src/utils/storyMetadata.ts
15
+ function toArray(v) {
16
+ return Array.isArray(v) ? v : v ? [v] : [];
17
+ }
18
+ function mergeTags(...tags) {
19
+ const merged = tags.flatMap((tags) => toArray(tags));
20
+ return Array.from(new Set(merged)).filter((tag) => typeof tag === "string" && tag !== "");
21
+ }
22
+ /**
23
+ * Return whether a story context has a play function.
24
+ * Handles both the `play` field (plain story function) and the legacy
25
+ * `playFunction` field present in older Storybook test-runner contexts.
26
+ */
27
+ function hasPlay(storyContext) {
28
+ if (!storyContext || typeof storyContext !== "object") return false;
29
+ return "play" in storyContext && typeof storyContext.play === "function" || "playFunction" in storyContext && typeof storyContext.playFunction === "function";
30
+ }
31
+ /**
32
+ * Build the story metadata object that is attached to a screenshot.
33
+ *
34
+ * @param story - The story shape (id, tags, play flag).
35
+ * @param storyMode - The Storybook mode name (e.g. "dark", "mobile") as defined in `parameters.argos.modes`
36
+ */
37
+ function getStoryMetadata(story, storyMode) {
38
+ return {
39
+ id: story.id,
40
+ tags: story.tags ?? [],
41
+ mode: storyMode ?? void 0,
42
+ play: Boolean(story.play)
43
+ };
44
+ }
45
+ //#endregion
46
+ //#region src/utils/parameters.ts
47
+ /**
48
+ * Get the default viewport size from the Storybook parameters.
49
+ */
50
+ function getDefaultViewport(parameters) {
51
+ const defaultViewport = parameters?.viewport?.defaultViewport;
52
+ if (defaultViewport) return getViewport(parameters, defaultViewport);
53
+ return null;
54
+ }
55
+ /**
56
+ * Get the viewport size from the Storybook parameters.
57
+ */
58
+ function getViewport(parameters, viewportName) {
59
+ if (typeof viewportName === "number") return {
60
+ width: viewportName,
61
+ height: 720
62
+ };
63
+ const viewports = parameters?.viewport?.viewports;
64
+ if (viewports && viewportName in viewports) {
65
+ if ("styles" in viewports[viewportName] && viewports[viewportName].styles) {
66
+ const width = parseInt(viewports[viewportName].styles.width, 10);
67
+ const height = parseInt(viewports[viewportName].styles.height, 10);
68
+ if (!isNaN(width) && !isNaN(height)) return {
69
+ width,
70
+ height
71
+ };
72
+ }
73
+ }
74
+ return null;
75
+ }
76
+ /**
77
+ * Get the Argos parameters from the Storybook context.
78
+ */
79
+ function getArgosParameters(parameters) {
80
+ if ("argos" in parameters && parameters.argos && typeof parameters.argos === "object") return parameters.argos;
81
+ if ("chromatic" in parameters && parameters.chromatic && typeof parameters.chromatic === "object") return parameters.chromatic;
82
+ return null;
83
+ }
84
+ const DEFAULT_FIT_TO_CONTENT = {
85
+ padding: 16,
86
+ zoom: 2
87
+ };
88
+ function getFitToContentFromParameters(parameters) {
89
+ const argosParameters = getArgosParameters(parameters);
90
+ if (argosParameters && "fitToContent" in argosParameters) {
91
+ if (typeof argosParameters.fitToContent === "boolean") return argosParameters.fitToContent ? DEFAULT_FIT_TO_CONTENT : null;
92
+ if (typeof argosParameters.fitToContent === "object" && argosParameters.fitToContent) return {
93
+ ...DEFAULT_FIT_TO_CONTENT,
94
+ ...argosParameters.fitToContent
95
+ };
96
+ }
97
+ return DEFAULT_FIT_TO_CONTENT;
98
+ }
99
+ //#endregion
100
+ //#region src/utils/screenshot.ts
101
+ /**
102
+ * Take a screenshot in the context of a Storybook story.
103
+ */
104
+ async function storybookArgosScreenshot(handler, context, options) {
105
+ const argosOptions = {
106
+ ...options,
107
+ stabilize: options?.stabilize ?? {
108
+ waitForAriaBusy: false,
109
+ ...typeof options?.stabilize === "object" ? options.stabilize : {}
110
+ }
111
+ };
112
+ const version = await getArgosStorybookVersion();
113
+ const storyUrl = `http://localhost:6006/?path=/story/${context.story.id}`;
114
+ const metadata = {
115
+ sdk: {
116
+ name: "@argos-ci/storybook",
117
+ version
118
+ },
119
+ playwrightLibraries: context.playwrightLibraries,
120
+ url: storyUrl,
121
+ test: context.test,
122
+ story: getStoryMetadata(context.story)
123
+ };
124
+ const modes = getArgosParameters(context.story.parameters)?.modes;
125
+ const allAttachments = [];
126
+ if (context.mode === "automatic") if (modes) for (const [name, globals] of Object.entries(modes)) {
127
+ if (globals.disabled) continue;
128
+ await handler.evaluate((name) => {
129
+ globalThis.__ARGOS_CURRENT_MODE = name;
130
+ }, name);
131
+ const attachments = await runHooksAndScreenshot({
132
+ handler,
133
+ context,
134
+ metadata,
135
+ options: argosOptions,
136
+ suffix: getModeSuffix(name),
137
+ mode: name,
138
+ globals: {
139
+ ...context.story.globals,
140
+ ...globals
141
+ }
142
+ });
143
+ allAttachments.push(...attachments);
144
+ await context.setViewportSize("initial");
145
+ await handler.evaluate(() => {
146
+ delete globalThis.__ARGOS_CURRENT_MODE;
147
+ });
148
+ }
149
+ else {
150
+ const attachments = await runHooksAndScreenshot({
151
+ handler,
152
+ context,
153
+ metadata,
154
+ options: argosOptions,
155
+ mode: null,
156
+ globals: context.story.globals ?? {}
157
+ });
158
+ await context.setViewportSize("initial");
159
+ allAttachments.push(...attachments);
160
+ }
161
+ else if (context.mode === "manual") {
162
+ const currentMode = await handler.evaluate(() => {
163
+ return globalThis.__ARGOS_CURRENT_MODE || null;
164
+ });
165
+ if (modes && !currentMode) return;
166
+ await markStoryAsRendered(handler, context.story.id);
167
+ DO_NOT_USE_setMetadataConfig({
168
+ ...metadata,
169
+ story: getStoryMetadata(context.story, currentMode)
170
+ });
171
+ await argosScreenshot$1(handler, composeName(context.name, getModeSuffix(currentMode)), options);
172
+ }
173
+ await context.afterScreenshot?.({
174
+ handler,
175
+ globals: {}
176
+ });
177
+ return allAttachments;
178
+ }
179
+ /**
180
+ * Run the story with the given globals.
181
+ */
182
+ async function runStory(args) {
183
+ const { handler, globals, storyId } = args;
184
+ await handler.evaluate(async (globals) => {
185
+ if ("__ARGOS_STORYBOOK_STORY" in globalThis) {
186
+ const storyFn = globalThis.__ARGOS_STORYBOOK_STORY;
187
+ if (!storyFn) throw new Error("@argos-ci/storybook: Unable to find `__ARGOS_STORYBOOK_STORY`.");
188
+ const canvasElement = (() => {
189
+ document.getElementById("storybook-root")?.remove();
190
+ const canvasElement = document.createElement("div");
191
+ canvasElement.id = "storybook-root";
192
+ document.body.appendChild(canvasElement);
193
+ return canvasElement;
194
+ })();
195
+ await storyFn.run({
196
+ globals,
197
+ canvasElement
198
+ });
199
+ return;
200
+ }
201
+ const channel = (() => {
202
+ if ("__STORYBOOK_PREVIEW__" in globalThis) return globalThis.__STORYBOOK_PREVIEW__.channel;
203
+ return null;
204
+ })();
205
+ if (!channel) throw new Error("@argos-ci/storybook: Unable to find `__STORYBOOK_PREVIEW__`.");
206
+ const initialGlobals = channel.last("globalsUpdated")?.[0].initialGlobals;
207
+ channel.emit("updateGlobals", { globals: {
208
+ ...initialGlobals,
209
+ ...globals
210
+ } });
211
+ }, globals);
212
+ await markStoryAsRendered(handler, storyId);
213
+ }
214
+ /**
215
+ * Notify Storybook that the story has been rendered.
216
+ * It will run all the necessary hooks (like pseudo-states).
217
+ */
218
+ async function markStoryAsRendered(handler, storyId) {
219
+ await handler.evaluate((storyId) => {
220
+ if ("__ARGOS_STORYBOOK_STORY" in globalThis) {
221
+ const addons = globalThis.__STORYBOOK_ADDONS_PREVIEW;
222
+ if (!addons) throw new Error("@argos-ci/storybook: Unable to find `__STORYBOOK_ADDONS_PREVIEW`.");
223
+ addons.getChannel().emit("storyRendered", storyId);
224
+ }
225
+ }, storyId);
226
+ }
227
+ /**
228
+ * Wait for the page to be ready and take a screenshot.
229
+ */
230
+ async function runHooksAndScreenshot(args) {
231
+ const { handler, context, options, globals, metadata, mode } = args;
232
+ await runStory({
233
+ handler,
234
+ globals,
235
+ storyId: context.story.id
236
+ });
237
+ const viewport = (globals.viewport ? getViewport(context.story.parameters, globals.viewport) : null) ?? getDefaultViewport(context.story.parameters) ?? "default";
238
+ await context.setViewportSize(viewport);
239
+ DO_NOT_USE_setMetadataConfig({
240
+ ...metadata,
241
+ story: getStoryMetadata(context.story, mode),
242
+ viewport: viewport && viewport !== "default" ? viewport : void 0
243
+ });
244
+ await context.beforeScreenshot?.({
245
+ handler,
246
+ globals
247
+ });
248
+ return argosScreenshot$1(handler, composeName(context.name, args.suffix), options);
249
+ }
250
+ function getModeSuffix(mode) {
251
+ return mode ? ` mode-[${mode}]` : "";
252
+ }
253
+ function composeName(name, suffix) {
254
+ return name + (suffix ?? "");
255
+ }
256
+ //#endregion
257
+ //#region src/test-runner.ts
258
+ const DEFAULT_PLAYWRIGHT_VIEWPORT_SIZE = {
259
+ width: 1280,
260
+ height: 720
261
+ };
262
+ /**
263
+ * Stabilize the UI and takes a screenshot of the application under test.
264
+ *
265
+ * @example argosScreenshot(page, context, options)
266
+ * @see https://argos-ci.com/docs/playwright#api-overview
267
+ */
268
+ async function argosScreenshot(page, context, options) {
269
+ const storyContext = await getStoryContext(page, context);
270
+ const fitToContent = getFitToContentFromParameters(storyContext.parameters);
271
+ await storybookArgosScreenshot(page, {
272
+ mode: "automatic",
273
+ name: storyContext.id,
274
+ playwrightLibraries: ["@storybook/test-runner"],
275
+ beforeScreenshot: async ({ handler }) => {
276
+ await waitForPageReady(handler);
277
+ },
278
+ story: {
279
+ id: storyContext.id,
280
+ tags: mergeTags(storyContext.tags, storyContext.parameters.tags),
281
+ play: hasPlay(storyContext),
282
+ parameters: storyContext.parameters,
283
+ globals: null
284
+ },
285
+ setViewportSize: async (size) => {
286
+ const actualSize = await page.viewportSize();
287
+ const absoluteSize = size === "default" || size === "initial" ? DEFAULT_PLAYWRIGHT_VIEWPORT_SIZE : size;
288
+ if (!actualSize || actualSize.height !== absoluteSize.height || actualSize.width !== absoluteSize.width) {
289
+ await page.setViewportSize(absoluteSize);
290
+ await page.waitForFunction(({ width, height }) => window.innerWidth === width && window.innerHeight === height, {
291
+ width: absoluteSize.width,
292
+ height: absoluteSize.height
293
+ });
294
+ }
295
+ }
296
+ }, applyFitToContent(options, fitToContent));
297
+ }
298
+ /**
299
+ * Apply fitToContent options to the screenshot options.
300
+ */
301
+ function applyFitToContent(options, fitToContent) {
302
+ if (!fitToContent) return options;
303
+ const { padding, zoom } = fitToContent;
304
+ return {
305
+ ...options,
306
+ element: "#storybook-root",
307
+ argosCSS: `#storybook-root { padding: ${padding}px; width: fit-content; height: fit-content; zoom: ${zoom}; }` + (options?.argosCSS ?? "")
308
+ };
309
+ }
310
+ //#endregion
311
+ export { argosScreenshot };
@@ -0,0 +1,56 @@
1
+ import { ArgosScreenshotOptions as ArgosScreenshotOptions$1, MetadataConfig } from "@argos-ci/playwright";
2
+ import { UploadParameters } from "@argos-ci/core";
3
+ import { Plugin } from "vitest/config";
4
+ import { BrowserCommand } from "vitest/node";
5
+ import { Frame, Page, ViewportSize } from "playwright";
6
+ //#region src/utils/parameters.d.ts
7
+ type StorybookGlobals = Record<string, any>;
8
+ //#endregion
9
+ //#region src/utils/screenshot.d.ts
10
+ type StorybookScreenshotContext<Handler extends Page | Frame> = {
11
+ /**
12
+ * - "manual" means the `argosScreenshot` has been called in the story play function.
13
+ * - "automatic" means the screenshot is taken automatically after each test.
14
+ */
15
+ mode: "manual" | "automatic";
16
+ name: string;
17
+ playwrightLibraries: string[];
18
+ test?: MetadataConfig["test"];
19
+ setViewportSize: (size: ViewportSize | "default" | "initial") => Promise<void>;
20
+ beforeScreenshot?: (input: {
21
+ handler: Handler;
22
+ globals: StorybookGlobals;
23
+ }) => Promise<void>;
24
+ afterScreenshot?: (input: {
25
+ handler: Handler;
26
+ globals: StorybookGlobals;
27
+ }) => Promise<void>;
28
+ story: {
29
+ id: string;
30
+ tags?: string[];
31
+ play?: boolean;
32
+ parameters: Record<string, any>;
33
+ globals: StorybookGlobals | null;
34
+ };
35
+ };
36
+ type ArgosScreenshotOptions = Omit<ArgosScreenshotOptions$1, "viewports">;
37
+ //#endregion
38
+ //#region src/vitest-reporter.d.ts
39
+ type ArgosReporterConfig = UploadParameters;
40
+ //#endregion
41
+ //#region src/vitest-plugin.d.ts
42
+ type ArgosScreenshotCommandArgs = [Pick<StorybookScreenshotContext<Frame>, "name" | "story" | "test" | "mode">];
43
+ interface ArgosVitestPluginOptions extends ArgosReporterConfig, ArgosScreenshotOptions {
44
+ /**
45
+ * Upload the report to Argos.
46
+ * @default true
47
+ */
48
+ uploadToArgos?: boolean;
49
+ }
50
+ /**
51
+ * Create a command for taking Argos screenshots in Vitest.
52
+ */
53
+ declare const createArgosScreenshotCommand: (pluginOptions?: ArgosVitestPluginOptions) => BrowserCommand<ArgosScreenshotCommandArgs>;
54
+ declare function argosVitestPlugin(options?: ArgosVitestPluginOptions): Plugin;
55
+ //#endregion
56
+ export { ArgosScreenshotCommandArgs, type ArgosScreenshotOptions, ArgosVitestPluginOptions, argosVitestPlugin, createArgosScreenshotCommand };