Storybook Angular integration:
npm install @analogjs/storybook-angular @angular/animations zone.js --save-dev
Update the .storybook/main.ts file to use the @analogjs/storybook-angular package:
import { StorybookConfig } from '@analogjs/storybook-angular';
const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-interactions',
],
framework: {
name: '@analogjs/storybook-angular',
options: {},
},
};
export default config;
Update the angular.json storybook targets to use the @analogjs/storybook-angular builders:
"storybook": {
"builder": "@analogjs/storybook-angular:start-storybook",
"options": {
"configDir": ".storybook",
"styles": ["src/styles.css"],
"experimentalZoneless": true
}
},
"build-storybook": {
"builder": "@analogjs/storybook-angular:build-storybook",
"options": {
"configDir": ".storybook",
"styles": ["src/styles.css"],
"experimentalZoneless": true
}
}
Run Storybook to verify everything works:
npm run storybook
Setting up Vitest for Interaction Testing
Install the Vitest addon and dependencies:
npm install @analogjs/vitest-angular @storybook/addon-vitest vitest @vitest/browser-playwright --save-dev
Add the addon to your .storybook/main.ts:
import { StorybookConfig } from '@analogjs/storybook-angular';
const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-vitest',
],
framework: {
name: '@analogjs/storybook-angular',
options: {},
},
};
export default config;
Create a .storybook/vitest.setup.ts file:
import '@angular/compiler';
import { setProjectAnnotations } from '@analogjs/storybook-angular/testing';
import { beforeAll } from 'vitest';
import * as projectAnnotations from './preview';
const project = setProjectAnnotations([projectAnnotations]);
beforeAll(project.beforeAll);
Update .storybook/tsconfig.json to include the setup file:
{
"extends": "../tsconfig.app.json",
"compilerOptions": {
"types": ["node"],
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true
},
"exclude": ["../src/test.ts", "../src/**/*.spec.ts"],
"include": ["../src/**/*.stories.*", "./preview.ts", "./vitest.setup.ts"],
"files": ["./typings.d.ts"]
}
Create a vitest.config.ts file in your project root:
/// <reference types="vitest/config" />
import { defineConfig } from 'vite';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
import { playwright } from '@vitest/browser-playwright';
const dirname =
typeof __dirname !== 'undefined'
? __dirname
: path.dirname(fileURLToPath(import.meta.url));
export default defineConfig({
test: {
projects: [
{
extends: true,
plugins: [
storybookTest({
configDir: path.join(dirname, '.storybook'),
}),
],
test: {
name: 'storybook',
browser: {
enabled: true,
headless: true,
provider: playwright(),
instances: [{ browser: 'chromium' }],
},
setupFiles: ['.storybook/vitest.setup.ts'],
},
},
],
},
});
Install Playwright browser binaries:
npx playwright install chromium
Add the test-storybook target to your angular.json:
"test-storybook": {
"builder": "@analogjs/vitest-angular:test",
"options": {
"configFile": "vitest.config.ts"
}
}
Add a test script to your package.json:
"scripts": {
"test-storybook": "ng run your-app:test-storybook"
}
Writing Interaction Tests
The Button component from Storybook's initial setup is a great example for interaction testing. Here's how to add a play function to test user interactions:
import type { Meta, StoryObj } from '@storybook/angular';
import { fn, expect, userEvent, within } from '@storybook/test';
import { Button } from './button.component';
const meta: Meta<Button> = {
title: 'Example/Button',
component: Button,
argTypes: {
backgroundColor: { control: 'color' },
},
args: { onClick: fn() },
};
export default meta;
type Story = StoryObj<Button>;
export const Primary: Story = {
args: {
primary: true,
label: 'Button',
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole('button', { name: /Button/i });
await userEvent.click(button);
await expect(args.onClick).toHaveBeenCalled();
},
};
export const Secondary: Story = {
args: {
label: 'Button',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole('button', { name: /Button/i });
await expect(button).toBeInTheDocument();
await expect(button).toHaveClass('storybook-button--secondary');
},
};
The play function allows you to:
- Query elements using
within(canvasElement)
- Simulate user interactions with
userEvent
- Make assertions with
expect
- And more ...
Running Tests
Run your interaction tests with:
npm run test-storybook
This runs all stories with play functions as tests in a real browser using Playwright, giving you confidence that your components work as expected.
You can also run tests directly in the Storybook UI. Start Storybook and use the "Run Tests" button in the sidebar, or navigate to a story to see interaction tests run automatically in the Interactions panel.
Conclusion
Using Storybook with Angular and Vite provides faster builds and a better developer experience. Adding Vitest for interaction testing lets you verify component behavior directly in your stories.
See the example repository with a full Storybook setup
and for more content!
SOCIAL SHARE CARD GENERATOR