Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -364,3 +364,46 @@ jobs:
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
NODE_VERSION: ${{ matrix.node-version }}
OS: ${{ matrix.os }}

# `@argos-ci/storybook` supports Storybook 8 through 11. The workspace is
# pinned to 10, so this job moves the package to the newest 11 pre-release and
# re-runs the Vitest suite against it, under its own build name. The Test
# Runner suite is left out for now: Storybook 11's config loader registers a
# Node module hook (`module.register()`), which Jest refuses, so
# `@storybook/test-runner` cannot load its config on 11 yet.
e2e-storybook-next:
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
node-version: [lts/*]
os: [ubuntu-latest]

runs-on: ${{ matrix.os }}

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup deps
uses: ./.github/actions/setup-deps
with:
node-version: ${{ matrix.node-version }}

- name: Upgrade to the Storybook 11 pre-release
run: >-
pnpm --filter=@argos-ci/storybook up
"storybook@>=11.0.0-0 <12.0.0-0"
"@storybook/react-vite@>=11.0.0-0 <12.0.0-0"
"@storybook/addon-docs@>=11.0.0-0 <12.0.0-0"
"@storybook/addon-links@>=11.0.0-0 <12.0.0-0"
"@storybook/addon-themes@>=11.0.0-0 <12.0.0-0"
"@storybook/addon-vitest@>=11.0.0-0 <12.0.0-0"
"storybook-addon-pseudo-states@>=11.0.0-0 <12.0.0-0"

- name: Run integration tests
run: pnpm exec -- turbo run e2e-next --filter=@argos-ci/storybook
env:
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
NODE_VERSION: ${{ matrix.node-version }}
OS: ${{ matrix.os }}
2 changes: 1 addition & 1 deletion packages/playwright/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"debug": "^4.4.3"
},
"devDependencies": {
"@playwright/test": "^1.62.1",
"@playwright/test": "^1.63.0",
"@types/debug": "^4.1.13",
"@types/node": "catalog:",
"vitest": "catalog:"
Expand Down
2 changes: 1 addition & 1 deletion packages/storybook/.storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const preview: Preview = {
},
},
viewport: {
viewports: INITIAL_VIEWPORTS,
options: INITIAL_VIEWPORTS,
},
},
};
Expand Down
4 changes: 4 additions & 0 deletions packages/storybook/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ export const Example: Story = {

> Using the [Storybook Test Runner](https://storybook.js.org/docs/writing-tests/integrations/test-runner) instead? Import `argosScreenshot` from `@argos-ci/storybook/test-runner` and call it from the `postVisit` hook. See the [Test Runner quickstart](https://argos-ci.com/docs/quickstart/storybook-quickstart/storybook-test-runner-quickstart).

## Compatibility

`@argos-ci/storybook` supports Storybook 8 through 11, including the 11 pre-releases. Screenshots can be taken with the [Vitest addon](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon) (Storybook 9 or later, Vitest 4 or 5) or with the [Storybook Test Runner](https://storybook.js.org/docs/writing-tests/integrations/test-runner) (Storybook 8 or later).

## Links

- [Official SDK Docs](https://argos-ci.com/docs/reference/storybook)
Expand Down
8 changes: 6 additions & 2 deletions packages/storybook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
"@argos-ci/util": "workspace:*",
"@argos-ci/vitest": "workspace:*"
},
"peerDependencies": {
"storybook": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0-0"
},
"devDependencies": {
"@argos-ci/cli": "workspace:*",
"@argos-ci/util": "workspace:*",
Expand All @@ -63,7 +66,7 @@
"@storybook/addon-themes": "^10.5.5",
"@storybook/addon-vitest": "^10.5.5",
"@storybook/react-vite": "^10.5.5",
"@storybook/test-runner": "^0.24.4",
"@storybook/test-runner": "^0.24.5",
"@types/node": "catalog:",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4",
Expand All @@ -72,7 +75,7 @@
"@vitest/coverage-v8": "^5.0.0",
"@vitest/ui": "^5.0.0",
"http-server": "^14.1.1",
"playwright": "^1.62.1",
"playwright": "^1.63.0",
"prop-types": "^15.8.1",
"react": "^19.2.8",
"react-dom": "^19.2.8",
Expand All @@ -98,6 +101,7 @@
"e2e-runner": "pnpm dlx concurrently -k -s first -n \"SB,TEST\" -c \"magenta,blue\" \"pnpm run serve-storybook\" \"pnpm run wait-storybook && pnpm run test-e2e-runner\"",
"e2e-vitest": "cross-env BUILD_NAME=\"argos-storybook-vitest-e2e-node-$NODE_VERSION-$OS\" UPLOAD_TO_ARGOS=true pnpm run test-e2e-vitest",
"e2e-compat": "cross-env BUILD_NAME=\"argos-storybook-vitest4-e2e-node-$NODE_VERSION-$OS\" UPLOAD_TO_ARGOS=true pnpm run test-e2e-vitest",
"e2e-next": "cross-env BUILD_NAME=\"argos-storybook-next-e2e-node-$NODE_VERSION-$OS\" UPLOAD_TO_ARGOS=true pnpm run test-e2e-vitest",
"e2e": "pnpm run e2e-runner && rm -rf ./screenshots && pnpm run e2e-vitest",
"check-types": "tsc",
"check-format": "prettier --check --ignore-unknown --ignore-path=./.gitignore --ignore-path=../../.gitignore --ignore-path=../../.prettierignore .",
Expand Down
99 changes: 99 additions & 0 deletions packages/storybook/src/utils/parameters.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { describe, expect, it } from "vitest";
import { getDefaultViewport, getViewport } from "./parameters";

const definitions = {
compact: {
name: "Compact",
styles: { width: "600px", height: "900px" },
},
widescreen: {
name: "Widescreen",
styles: { width: "1440px", height: "900px" },
},
};

describe("getViewport", () => {
it("resolves a viewport name from `viewport.options` (Storybook 9+)", () => {
expect(
getViewport({ viewport: { options: definitions } }, "compact"),
).toEqual({ width: 600, height: 900 });
});

it("resolves a viewport name from the legacy `viewport.viewports`", () => {
expect(
getViewport({ viewport: { viewports: definitions } }, "compact"),
).toEqual({ width: 600, height: 900 });
});

it("prefers `options` over `viewports` when both are defined", () => {
const parameters = {
viewport: {
options: definitions,
viewports: {
compact: { name: "Old", styles: { width: "1px", height: "1px" } },
},
},
};
expect(getViewport(parameters, "compact")).toEqual({
width: 600,
height: 900,
});
});

it("accepts the Storybook 9+ `{ value, isRotated }` global", () => {
const parameters = { viewport: { options: definitions } };
expect(getViewport(parameters, { value: "compact" })).toEqual({
width: 600,
height: 900,
});
expect(
getViewport(parameters, { value: "compact", isRotated: true }),
).toEqual({ width: 900, height: 600 });
});

it("uses a number as the viewport width", () => {
expect(getViewport({}, 800)).toEqual({ width: 800, height: 720 });
expect(getViewport({}, { value: 800 })).toEqual({
width: 800,
height: 720,
});
});

it("returns null for the responsive viewport, unknown names or no global", () => {
const parameters = { viewport: { options: definitions } };
expect(getViewport(parameters, { value: undefined })).toBeNull();
expect(getViewport(parameters, "unknown")).toBeNull();
expect(getViewport(parameters, undefined)).toBeNull();
expect(getViewport(parameters, null)).toBeNull();
expect(getViewport({}, "compact")).toBeNull();
});
});

describe("getDefaultViewport", () => {
it("reads the legacy `viewport.defaultViewport` parameter", () => {
expect(
getDefaultViewport({
viewport: { viewports: definitions, defaultViewport: "widescreen" },
}),
).toEqual({ width: 1440, height: 900 });
});

it("rotates the legacy default when `defaultOrientation` is landscape", () => {
expect(
getDefaultViewport({
viewport: {
options: definitions,
defaultViewport: "compact",
defaultOrientation: "landscape",
},
}),
).toEqual({ width: 900, height: 600 });
});

it("returns null without a legacy default", () => {
expect(
getDefaultViewport({ viewport: { options: definitions } }),
).toBeNull();
expect(getDefaultViewport({})).toBeNull();
});
});
80 changes: 65 additions & 15 deletions packages/storybook/src/utils/parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@ import type { ViewportSize } from "playwright";

export type StorybookGlobals = Record<string, any>;

/**
* Value of the Storybook `viewport` global.
*
* Storybook 9+ stores it as `{ value, isRotated }`. Argos modes and older
* Storybook versions use the bare viewport name; a number is used as a width.
*/
export type StorybookViewportGlobal =
| string
| number
| { value?: string | number | null; isRotated?: boolean }
| null
| undefined;

export type FitToContent = {
/**
* Padding around the content in pixels.
Expand Down Expand Up @@ -33,40 +46,77 @@ export interface ArgosStorybookParameters {

export type StoryParameters = Record<string, any>;

type ViewportDefinitions = Record<
string,
{ styles?: { width?: string; height?: string } | null } | undefined
>;

/**
* Get the viewports defined in the Storybook `viewport` parameter.
* Storybook 9+ lists them under `options`, older versions under `viewports`.
*/
function getViewportDefinitions(
parameters: StoryParameters,
): ViewportDefinitions | null {
const viewport = parameters?.viewport;
if (!viewport || typeof viewport !== "object") {
return null;
}
const definitions = viewport.options ?? viewport.viewports;
return definitions && typeof definitions === "object" ? definitions : null;
}

/**
* Get the default viewport size from the Storybook parameters.
*
* `viewport.defaultViewport` (and `defaultOrientation`) were replaced by the
* `viewport` global in Storybook 9 and removed in Storybook 10. The global is
* resolved with `getViewport`; this only covers the legacy parameter.
*/
export function getDefaultViewport(
parameters: StoryParameters,
): ViewportSize | null {
const defaultViewport = parameters?.viewport?.defaultViewport;
if (defaultViewport) {
return getViewport(parameters, defaultViewport);
return getViewport(parameters, {
value: defaultViewport,
isRotated: parameters.viewport.defaultOrientation === "landscape",
});
}
return null;
}

/**
* Get the viewport size from the Storybook parameters.
* Get the viewport size matching a `viewport` global.
*/
export function getViewport(
parameters: StoryParameters,
viewportName: string,
viewport: StorybookViewportGlobal,
): ViewportSize | null {
if (typeof viewportName === "number") {
return { width: viewportName, height: 720 };
const { value, isRotated } =
viewport && typeof viewport === "object"
? {
value: viewport.value ?? null,
isRotated: Boolean(viewport.isRotated),
}
: { value: viewport ?? null, isRotated: false };

if (typeof value === "number") {
return { width: value, height: 720 };
}
const viewports = parameters?.viewport?.viewports;
if (viewports && viewportName in viewports) {
if ("styles" in viewports[viewportName] && viewports[viewportName].styles) {
const width = parseInt(viewports[viewportName].styles.width, 10);
const height = parseInt(viewports[viewportName].styles.height, 10);
if (!isNaN(width) && !isNaN(height)) {
return { width, height };
}
}
if (!value) {
return null;
}
return null;
const styles = getViewportDefinitions(parameters)?.[value]?.styles;
if (!styles) {
return null;
}
const width = parseInt(String(styles.width), 10);
const height = parseInt(String(styles.height), 10);
if (isNaN(width) || isNaN(height)) {
return null;
}
return isRotated ? { width: height, height: width } : { width, height };
}

/**
Expand Down
9 changes: 5 additions & 4 deletions packages/storybook/src/utils/screenshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,10 +290,11 @@ async function runHooksAndScreenshot<Handler extends Page | Frame>(args: {

await runStory({ handler, globals, storyId: context.story.id });

// Get the viewport from globals set on the mode.
const viewportFromGlobals = globals.viewport
? getViewport(context.story.parameters, globals.viewport)
: null;
// Get the viewport from the globals (set by the Argos mode or the story).
const viewportFromGlobals = getViewport(
context.story.parameters,
globals.viewport,
);

const viewport =
viewportFromGlobals ??
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"@vitest/browser": "^5.0.0",
"@vitest/browser-playwright": "^5.0.0",
"@vitest/pretty-format": "^5.0.0",
"playwright": "^1.62.1",
"playwright": "^1.63.0",
"vitest": "catalog:"
},
"scripts": {
Expand Down
Loading
Loading