@argos-ci/storybook 4.0.10 → 5.0.1

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.
@@ -90,54 +90,89 @@ async function storybookArgosScreenshot(handler, context, options) {
90
90
  const argosParameters = getArgosParameters(context.story.parameters);
91
91
  const modes = argosParameters?.modes;
92
92
  const allAttachments = [];
93
- if (modes) {
94
- for (const [name, globals] of Object.entries(modes)) {
95
- if (globals.disabled) {
96
- continue;
93
+ if (context.mode === "automatic") {
94
+ if (modes) {
95
+ for (const [name, globals] of Object.entries(modes)) {
96
+ if (globals.disabled) {
97
+ continue;
98
+ }
99
+ await handler.evaluate((name2) => {
100
+ globalThis.__ARGOS_CURRENT_MODE = name2;
101
+ }, name);
102
+ const attachments = await runHooksAndScreenshot({
103
+ handler,
104
+ context,
105
+ metadata,
106
+ options: argosOptions,
107
+ suffix: getModeSuffix(name),
108
+ globals: {
109
+ ...context.story.globals,
110
+ ...globals
111
+ }
112
+ });
113
+ allAttachments.push(...attachments);
114
+ await context.setViewportSize("default");
115
+ await handler.evaluate(() => {
116
+ delete globalThis.__ARGOS_CURRENT_MODE;
117
+ });
97
118
  }
119
+ } else {
98
120
  const attachments = await runHooksAndScreenshot({
99
121
  handler,
100
122
  context,
101
123
  metadata,
102
124
  options: argosOptions,
103
- suffix: ` mode-[${name}]`,
104
- globals: {
105
- ...context.story.globals,
106
- ...globals
107
- }
125
+ globals: context.story.globals ?? {}
108
126
  });
109
127
  allAttachments.push(...attachments);
110
- await context.setViewportSize("default");
111
- await setStorybookGlobals({ handler, globals: {} });
112
128
  }
113
- } else {
114
- const attachments = await runHooksAndScreenshot({
115
- handler,
116
- context,
117
- metadata,
118
- options: argosOptions,
119
- globals: context.story.globals ?? {}
129
+ } else if (context.mode === "manual") {
130
+ const currentMode = await handler.evaluate(() => {
131
+ return globalThis.__ARGOS_CURRENT_MODE || null;
120
132
  });
121
- allAttachments.push(...attachments);
133
+ if (modes && !currentMode) {
134
+ return;
135
+ }
136
+ await markStoryAsRendered(handler, context.story.id);
137
+ await argosPlaywrightScreenshot(
138
+ handler,
139
+ composeName(context.name, getModeSuffix(currentMode)),
140
+ options
141
+ );
122
142
  }
123
143
  await context.afterScreenshot?.({ handler, globals: {} });
124
144
  return allAttachments;
125
145
  }
126
- async function setStorybookGlobals(args) {
127
- const { handler, globals } = args;
128
- await handler.evaluate((globals2) => {
146
+ async function runStory(args) {
147
+ const { handler, globals, storyId } = args;
148
+ await handler.evaluate(async (globals2) => {
149
+ if ("__ARGOS_STORYBOOK_STORY" in globalThis) {
150
+ const storyFn = globalThis.__ARGOS_STORYBOOK_STORY;
151
+ if (!storyFn) {
152
+ throw new Error(
153
+ "@argos-ci/storybook: Unable to find `__ARGOS_STORYBOOK_STORY`."
154
+ );
155
+ }
156
+ const canvasElement = (() => {
157
+ const existing = document.getElementById("storybook-root");
158
+ existing?.remove();
159
+ const canvasElement2 = document.createElement("div");
160
+ canvasElement2.id = "storybook-root";
161
+ document.body.appendChild(canvasElement2);
162
+ return canvasElement2;
163
+ })();
164
+ await storyFn.run({ globals: globals2, canvasElement });
165
+ return;
166
+ }
129
167
  const channel = (() => {
130
168
  if ("__STORYBOOK_PREVIEW__" in globalThis) {
131
169
  return globalThis.__STORYBOOK_PREVIEW__.channel;
132
170
  }
133
- if ("__STORYBOOK_ADDONS_CHANNEL__" in globalThis) {
134
- return globalThis.__STORYBOOK_ADDONS_CHANNEL__;
135
- }
136
171
  return null;
137
172
  })();
138
173
  if (!channel) {
139
174
  throw new Error(
140
- "@argos-ci/storybook: Unable to find Storybook preview instance."
175
+ "@argos-ci/storybook: Unable to find `__STORYBOOK_PREVIEW__`."
141
176
  );
142
177
  }
143
178
  const initialGlobals = channel.last("globalsUpdated")?.[0].initialGlobals;
@@ -145,10 +180,24 @@ async function setStorybookGlobals(args) {
145
180
  globals: { ...initialGlobals, ...globals2 }
146
181
  });
147
182
  }, globals);
183
+ await markStoryAsRendered(handler, storyId);
184
+ }
185
+ async function markStoryAsRendered(handler, storyId) {
186
+ await handler.evaluate((storyId2) => {
187
+ if ("__ARGOS_STORYBOOK_STORY" in globalThis) {
188
+ const addons = globalThis.__STORYBOOK_ADDONS_PREVIEW;
189
+ if (!addons) {
190
+ throw new Error(
191
+ "@argos-ci/storybook: Unable to find `__STORYBOOK_ADDONS_PREVIEW`."
192
+ );
193
+ }
194
+ addons.getChannel().emit("storyRendered", storyId2);
195
+ }
196
+ }, storyId);
148
197
  }
149
198
  async function runHooksAndScreenshot(args) {
150
199
  const { handler, context, options, globals, metadata } = args;
151
- await setStorybookGlobals({ handler, globals });
200
+ await runStory({ handler, globals, storyId: context.story.id });
152
201
  const viewportFromGlobals = globals.viewport ? getViewport(context.story.parameters, globals.viewport) : null;
153
202
  const viewport = viewportFromGlobals ?? getDefaultViewport(context.story.parameters) ?? "default";
154
203
  await context.setViewportSize(viewport);
@@ -159,10 +208,16 @@ async function runHooksAndScreenshot(args) {
159
208
  await context.beforeScreenshot?.({ handler, globals });
160
209
  return argosPlaywrightScreenshot(
161
210
  handler,
162
- context.name + (args.suffix ?? ""),
211
+ composeName(context.name, args.suffix),
163
212
  options
164
213
  );
165
214
  }
215
+ function getModeSuffix(mode) {
216
+ return mode ? ` mode-[${mode}]` : "";
217
+ }
218
+ function composeName(name, suffix) {
219
+ return name + (suffix ?? "");
220
+ }
166
221
 
167
222
  // src/test-runner.ts
168
223
  var DEFAULT_PLAYWRIGHT_VIEWPORT_SIZE = { width: 1280, height: 720 };
@@ -172,6 +227,7 @@ async function argosScreenshot(page, context, options) {
172
227
  await storybookArgosScreenshot(
173
228
  page,
174
229
  {
230
+ mode: "automatic",
175
231
  name: storyContext.id,
176
232
  playwrightLibraries: ["@storybook/test-runner"],
177
233
  beforeScreenshot: async ({ handler }) => {
@@ -7,6 +7,11 @@ import { UploadParameters } from '@argos-ci/core';
7
7
  type StorybookGlobals = Record<string, any>;
8
8
 
9
9
  type StorybookScreenshotContext<Handler extends Page | Frame> = {
10
+ /**
11
+ * - "manual" means the `argosScreenshot` has been called in the story play function.
12
+ * - "automatic" means the screenshot is taken automatically after each test.
13
+ */
14
+ mode: "manual" | "automatic";
10
15
  name: string;
11
16
  playwrightLibraries: string[];
12
17
  test?: MetadataConfig["test"];
@@ -30,7 +35,7 @@ type ArgosScreenshotOptions = Omit<ArgosScreenshotOptions$1, "viewports">;
30
35
  type ArgosReporterConfig = UploadParameters;
31
36
 
32
37
  type ArgosScreenshotCommandArgs = [
33
- Pick<StorybookScreenshotContext<Frame>, "name" | "story" | "test">
38
+ Pick<StorybookScreenshotContext<Frame>, "name" | "story" | "test" | "mode">
34
39
  ];
35
40
  interface ArgosVitestPluginOptions extends ArgosReporterConfig, ArgosScreenshotOptions {
36
41
  /**
@@ -38,26 +43,6 @@ interface ArgosVitestPluginOptions extends ArgosReporterConfig, ArgosScreenshotO
38
43
  * @default true
39
44
  */
40
45
  uploadToArgos?: boolean;
41
- /**
42
- * Opportunity to apply globals or other context-specific settings.
43
- * This can be useful to emulate dark mode or other visual modes.
44
- * @example
45
- * ```typescript
46
- * applyGlobals: async ({ frame, globals }) => {
47
- * await frame.evaluate((globals) => {
48
- * if (globals.theme === "dark") {
49
- * document.documentElement.classList.add("dark");
50
- * } else {
51
- * document.documentElement.classList.remove("dark");
52
- * }
53
- * }, globals);
54
- * }
55
- * ```
56
- */
57
- applyGlobals?: (input: {
58
- handler: Frame;
59
- globals: StorybookGlobals;
60
- }) => Promise<void>;
61
46
  }
62
47
  /**
63
48
  * Create a command for taking Argos screenshots in Vitest.
@@ -87,54 +87,89 @@ async function storybookArgosScreenshot(handler, context, options) {
87
87
  const argosParameters = getArgosParameters(context.story.parameters);
88
88
  const modes = argosParameters?.modes;
89
89
  const allAttachments = [];
90
- if (modes) {
91
- for (const [name, globals] of Object.entries(modes)) {
92
- if (globals.disabled) {
93
- continue;
90
+ if (context.mode === "automatic") {
91
+ if (modes) {
92
+ for (const [name, globals] of Object.entries(modes)) {
93
+ if (globals.disabled) {
94
+ continue;
95
+ }
96
+ await handler.evaluate((name2) => {
97
+ globalThis.__ARGOS_CURRENT_MODE = name2;
98
+ }, name);
99
+ const attachments = await runHooksAndScreenshot({
100
+ handler,
101
+ context,
102
+ metadata,
103
+ options: argosOptions,
104
+ suffix: getModeSuffix(name),
105
+ globals: {
106
+ ...context.story.globals,
107
+ ...globals
108
+ }
109
+ });
110
+ allAttachments.push(...attachments);
111
+ await context.setViewportSize("default");
112
+ await handler.evaluate(() => {
113
+ delete globalThis.__ARGOS_CURRENT_MODE;
114
+ });
94
115
  }
116
+ } else {
95
117
  const attachments = await runHooksAndScreenshot({
96
118
  handler,
97
119
  context,
98
120
  metadata,
99
121
  options: argosOptions,
100
- suffix: ` mode-[${name}]`,
101
- globals: {
102
- ...context.story.globals,
103
- ...globals
104
- }
122
+ globals: context.story.globals ?? {}
105
123
  });
106
124
  allAttachments.push(...attachments);
107
- await context.setViewportSize("default");
108
- await setStorybookGlobals({ handler, globals: {} });
109
125
  }
110
- } else {
111
- const attachments = await runHooksAndScreenshot({
112
- handler,
113
- context,
114
- metadata,
115
- options: argosOptions,
116
- globals: context.story.globals ?? {}
126
+ } else if (context.mode === "manual") {
127
+ const currentMode = await handler.evaluate(() => {
128
+ return globalThis.__ARGOS_CURRENT_MODE || null;
117
129
  });
118
- allAttachments.push(...attachments);
130
+ if (modes && !currentMode) {
131
+ return;
132
+ }
133
+ await markStoryAsRendered(handler, context.story.id);
134
+ await argosPlaywrightScreenshot(
135
+ handler,
136
+ composeName(context.name, getModeSuffix(currentMode)),
137
+ options
138
+ );
119
139
  }
120
140
  await context.afterScreenshot?.({ handler, globals: {} });
121
141
  return allAttachments;
122
142
  }
123
- async function setStorybookGlobals(args) {
124
- const { handler, globals } = args;
125
- await handler.evaluate((globals2) => {
143
+ async function runStory(args) {
144
+ const { handler, globals, storyId } = args;
145
+ await handler.evaluate(async (globals2) => {
146
+ if ("__ARGOS_STORYBOOK_STORY" in globalThis) {
147
+ const storyFn = globalThis.__ARGOS_STORYBOOK_STORY;
148
+ if (!storyFn) {
149
+ throw new Error(
150
+ "@argos-ci/storybook: Unable to find `__ARGOS_STORYBOOK_STORY`."
151
+ );
152
+ }
153
+ const canvasElement = (() => {
154
+ const existing = document.getElementById("storybook-root");
155
+ existing?.remove();
156
+ const canvasElement2 = document.createElement("div");
157
+ canvasElement2.id = "storybook-root";
158
+ document.body.appendChild(canvasElement2);
159
+ return canvasElement2;
160
+ })();
161
+ await storyFn.run({ globals: globals2, canvasElement });
162
+ return;
163
+ }
126
164
  const channel = (() => {
127
165
  if ("__STORYBOOK_PREVIEW__" in globalThis) {
128
166
  return globalThis.__STORYBOOK_PREVIEW__.channel;
129
167
  }
130
- if ("__STORYBOOK_ADDONS_CHANNEL__" in globalThis) {
131
- return globalThis.__STORYBOOK_ADDONS_CHANNEL__;
132
- }
133
168
  return null;
134
169
  })();
135
170
  if (!channel) {
136
171
  throw new Error(
137
- "@argos-ci/storybook: Unable to find Storybook preview instance."
172
+ "@argos-ci/storybook: Unable to find `__STORYBOOK_PREVIEW__`."
138
173
  );
139
174
  }
140
175
  const initialGlobals = channel.last("globalsUpdated")?.[0].initialGlobals;
@@ -142,10 +177,24 @@ async function setStorybookGlobals(args) {
142
177
  globals: { ...initialGlobals, ...globals2 }
143
178
  });
144
179
  }, globals);
180
+ await markStoryAsRendered(handler, storyId);
181
+ }
182
+ async function markStoryAsRendered(handler, storyId) {
183
+ await handler.evaluate((storyId2) => {
184
+ if ("__ARGOS_STORYBOOK_STORY" in globalThis) {
185
+ const addons = globalThis.__STORYBOOK_ADDONS_PREVIEW;
186
+ if (!addons) {
187
+ throw new Error(
188
+ "@argos-ci/storybook: Unable to find `__STORYBOOK_ADDONS_PREVIEW`."
189
+ );
190
+ }
191
+ addons.getChannel().emit("storyRendered", storyId2);
192
+ }
193
+ }, storyId);
145
194
  }
146
195
  async function runHooksAndScreenshot(args) {
147
196
  const { handler, context, options, globals, metadata } = args;
148
- await setStorybookGlobals({ handler, globals });
197
+ await runStory({ handler, globals, storyId: context.story.id });
149
198
  const viewportFromGlobals = globals.viewport ? getViewport(context.story.parameters, globals.viewport) : null;
150
199
  const viewport = viewportFromGlobals ?? getDefaultViewport(context.story.parameters) ?? "default";
151
200
  await context.setViewportSize(viewport);
@@ -156,10 +205,16 @@ async function runHooksAndScreenshot(args) {
156
205
  await context.beforeScreenshot?.({ handler, globals });
157
206
  return argosPlaywrightScreenshot(
158
207
  handler,
159
- context.name + (args.suffix ?? ""),
208
+ composeName(context.name, args.suffix),
160
209
  options
161
210
  );
162
211
  }
212
+ function getModeSuffix(mode) {
213
+ return mode ? ` mode-[${mode}]` : "";
214
+ }
215
+ function composeName(name, suffix) {
216
+ return name + (suffix ?? "");
217
+ }
163
218
 
164
219
  // src/vitest-reporter.ts
165
220
  import { upload } from "@argos-ci/core";
@@ -184,7 +239,7 @@ var ArgosReporter = class {
184
239
  // src/vitest-plugin.ts
185
240
  import { resolve } from "path";
186
241
  var createArgosScreenshotCommand = (pluginOptions) => {
187
- const { applyGlobals, ...screenshotOptions } = pluginOptions ?? {};
242
+ const screenshotOptions = pluginOptions ?? {};
188
243
  return async (ctx, testContext) => {
189
244
  const frame = await ctx.frame();
190
245
  const fitToContent = getFitToContentFromParameters(
@@ -216,9 +271,7 @@ var createArgosScreenshotCommand = (pluginOptions) => {
216
271
  iframe.style.height = `${size2.height}px`;
217
272
  }
218
273
  }, size);
219
- },
220
- beforeScreenshot: applyGlobals,
221
- afterScreenshot: applyGlobals
274
+ }
222
275
  },
223
276
  applyFitToContent(screenshotOptions, fitToContent)
224
277
  );
@@ -1,15 +1,19 @@
1
- // src/vitest.ts
1
+ // src/vitest-setup-file.ts
2
2
  import { afterEach } from "vitest";
3
- import { server } from "@vitest/browser/context";
4
- function setupArgos() {
5
- afterEach(async (ctx) => {
3
+
4
+ // src/vitest.ts
5
+ function setupArgos(api) {
6
+ api.afterEach(async (ctx) => {
6
7
  const story = "story" in ctx ? ctx.story : null;
7
8
  if (!story) {
8
9
  throw new Error(
9
10
  `@argos-ci/storybook/vitest-plugin should be used with @storybook/addon-vitest/vitest-plugin`
10
11
  );
11
12
  }
13
+ const { server } = await import("@vitest/browser/context");
14
+ globalThis.__ARGOS_STORYBOOK_STORY = story;
12
15
  await server.commands.argosScreenshot({
16
+ mode: "automatic",
13
17
  name: story.id,
14
18
  story: {
15
19
  id: story.id,
@@ -31,4 +35,4 @@ function setupArgos() {
31
35
  }
32
36
 
33
37
  // src/vitest-setup-file.ts
34
- setupArgos();
38
+ setupArgos({ afterEach });
package/dist/vitest.d.ts CHANGED
@@ -1,9 +1,15 @@
1
+ import * as vitest from 'vitest';
1
2
  import { MetadataConfig, ArgosScreenshotOptions as ArgosScreenshotOptions$1, Attachment } from '@argos-ci/playwright';
2
3
  import { Page, Frame, ViewportSize } from 'playwright';
3
4
 
4
5
  type StorybookGlobals = Record<string, any>;
5
6
 
6
7
  type StorybookScreenshotContext<Handler extends Page | Frame> = {
8
+ /**
9
+ * - "manual" means the `argosScreenshot` has been called in the story play function.
10
+ * - "automatic" means the screenshot is taken automatically after each test.
11
+ */
12
+ mode: "manual" | "automatic";
7
13
  name: string;
8
14
  playwrightLibraries: string[];
9
15
  test?: MetadataConfig["test"];
@@ -25,7 +31,7 @@ type StorybookScreenshotContext<Handler extends Page | Frame> = {
25
31
  type ArgosScreenshotOptions = Omit<ArgosScreenshotOptions$1, "viewports">;
26
32
 
27
33
  type ArgosScreenshotCommandArgs = [
28
- Pick<StorybookScreenshotContext<Frame>, "name" | "story" | "test">
34
+ Pick<StorybookScreenshotContext<Frame>, "name" | "story" | "test" | "mode">
29
35
  ];
30
36
 
31
37
  declare module "@vitest/browser/context" {
@@ -36,7 +42,9 @@ declare module "@vitest/browser/context" {
36
42
  /**
37
43
  * Setup Argos hooks for Vitest.
38
44
  */
39
- declare function setupArgos(): void;
45
+ declare function setupArgos(api: {
46
+ afterEach: typeof vitest.afterEach;
47
+ }): void;
40
48
  /**
41
49
  * Take an Argos screenshot of a story in Vitest.
42
50
  */
package/dist/vitest.js CHANGED
@@ -1,15 +1,16 @@
1
1
  // src/vitest.ts
2
- import { afterEach } from "vitest";
3
- import { server } from "@vitest/browser/context";
4
- function setupArgos() {
5
- afterEach(async (ctx) => {
2
+ function setupArgos(api) {
3
+ api.afterEach(async (ctx) => {
6
4
  const story = "story" in ctx ? ctx.story : null;
7
5
  if (!story) {
8
6
  throw new Error(
9
7
  `@argos-ci/storybook/vitest-plugin should be used with @storybook/addon-vitest/vitest-plugin`
10
8
  );
11
9
  }
10
+ const { server } = await import("@vitest/browser/context");
11
+ globalThis.__ARGOS_STORYBOOK_STORY = story;
12
12
  await server.commands.argosScreenshot({
13
+ mode: "automatic",
13
14
  name: story.id,
14
15
  story: {
15
16
  id: story.id,
@@ -30,7 +31,13 @@ function setupArgos() {
30
31
  });
31
32
  }
32
33
  async function argosScreenshot(story, name) {
34
+ const isVitest = await checkIsVitestEnv();
35
+ if (!isVitest) {
36
+ return;
37
+ }
38
+ const { server } = await import("@vitest/browser/context");
33
39
  await server.commands.argosScreenshot({
40
+ mode: "manual",
34
41
  name: `${story.id}/${name}`,
35
42
  story: {
36
43
  id: story.id,
@@ -39,6 +46,14 @@ async function argosScreenshot(story, name) {
39
46
  }
40
47
  });
41
48
  }
49
+ async function checkIsVitestEnv() {
50
+ try {
51
+ await import("@vitest/browser/context");
52
+ return true;
53
+ } catch {
54
+ return false;
55
+ }
56
+ }
42
57
  export {
43
58
  argosScreenshot,
44
59
  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": "4.0.10",
4
+ "version": "5.0.1",
5
5
  "author": "Smooth Code",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -50,18 +50,18 @@
50
50
  "node": ">=20.0.0"
51
51
  },
52
52
  "dependencies": {
53
- "@argos-ci/playwright": "6.1.3",
53
+ "@argos-ci/playwright": "6.1.5",
54
54
  "@argos-ci/util": "3.1.0"
55
55
  },
56
56
  "devDependencies": {
57
- "@argos-ci/cli": "3.0.6",
58
- "@argos-ci/core": "4.1.2",
57
+ "@argos-ci/cli": "3.0.8",
58
+ "@argos-ci/core": "4.1.4",
59
59
  "@argos-ci/util": "workspace:*",
60
- "@storybook/addon-docs": "^9.1.0",
61
- "@storybook/addon-links": "^9.1.0",
62
- "@storybook/addon-themes": "^9.1.0",
63
- "@storybook/addon-vitest": "^9.1.0",
64
- "@storybook/react-vite": "^9.1.0",
60
+ "@storybook/addon-docs": "^9.1.5",
61
+ "@storybook/addon-links": "^9.1.5",
62
+ "@storybook/addon-themes": "^9.1.5",
63
+ "@storybook/addon-vitest": "^9.1.5",
64
+ "@storybook/react-vite": "^9.1.5",
65
65
  "@storybook/test-runner": "^0.23.0",
66
66
  "@types/react": "^19.1.9",
67
67
  "@types/react-dom": "^19.1.7",
@@ -73,7 +73,8 @@
73
73
  "prop-types": "^15.8.1",
74
74
  "react": "^19.1.1",
75
75
  "react-dom": "^19.1.1",
76
- "storybook": "^9.1.0",
76
+ "storybook": "^9.1.5",
77
+ "storybook-addon-pseudo-states": "^9.1.5",
77
78
  "vitest": "catalog:",
78
79
  "wait-on": "^8.0.4"
79
80
  },
@@ -97,5 +98,5 @@
97
98
  "check-format": "prettier --check --ignore-unknown --ignore-path=./.gitignore --ignore-path=../../.gitignore --ignore-path=../../.prettierignore .",
98
99
  "lint": "eslint ."
99
100
  },
100
- "gitHead": "c705285fa19c31ef0c071bc7bf5182f7a366e2c4"
101
+ "gitHead": "a1f55cdee00ce144eb5b0f7ef08e451867d0c129"
101
102
  }