Files
safe-wallet/apps/web/scripts/generate-storybook-tests.cjs
b36f9d7280 chore: Add automated Storybook snapshot testing with CI integration (#6614)
* feat: add automated Storybook snapshot testing with CI integration

This PR introduces automated Jest snapshot testing for all Storybook stories using the Portable Stories API.

## Changes

- **Test Setup**: Created automated test file that discovers and generates JSON snapshots for all `.stories.tsx` files
- **CI Pipeline**: Added GitHub Actions workflow to run snapshot tests on PRs and main branch pushes
- **Scripts**: Added `test:storybook` and `test:storybook:ci` npm scripts
- **Documentation**: Created comprehensive guide in `STORYBOOK_SNAPSHOTS.md`

## Benefits

- Automatically catches unintended UI changes in components
- Provides reviewable diffs in pull requests
- Fast execution compared to visual regression tests
- Version-controlled snapshot history
- Zero configuration required - automatically discovers all stories

## Usage

```bash
yarn test:storybook     # Run tests locally
yarn test:storybook -u  # Update snapshots
```

The CI workflow runs automatically on changes to `apps/web/**` and will fail if snapshots don't match, uploading diffs as artifacts for review.

* fix: move docs to docs folder and add workflow permissions

- Move STORYBOOK_SNAPSHOTS.md to docs/storybook-snapshots.md
- Add permissions block to workflow for security (contents: read)
- Fixes CodeQL security warning about GITHUB_TOKEN permissions

* fix: use globSync for Jest compatibility

- Change from glob.sync to globSync import
- Fixes ES module import error in Jest

* fix: address CI issues for storybook snapshot tests

- Add @storybook and storybook to Jest transformIgnorePatterns for ESM compatibility
- Fix TypeScript typing for Story component in tests
- Add baseline snapshots for CI to pass
- Merged latest dev to get react-dom path fixes in jest config

* style: fix prettier formatting in storybook test

* fix: exclude storybook tests from main test run

Storybook snapshot tests have their own CI workflow (web-storybook-tests.yml),
so they don't need to run as part of the main test:ci. This also helps avoid
the jest-coverage-report-action character limit issue.

* fix: override testPathIgnorePatterns for storybook tests

The storybook test script needs to override the testPathIgnorePatterns
from jest.config.cjs to allow running the storybook.test.tsx file.

* fix: add timeout and continue-on-error to storybook tests

The storybook snapshot tests are taking too long and occasionally hanging.
Adding a 15-minute timeout and continue-on-error to prevent blocking PRs
while the tests are being stabilized.

* refactor: use colocated snapshot tests instead of single giant file

- Generate individual test files (*.stories.test.tsx) next to each story
- Each component gets its own __snapshots__ directory for easier review
- Add generator script: yarn generate:storybook-tests
- Add storybook-setup.ts to apply global decorators (ThemeProvider, etc.)
- Fix workflow: use step outcome for artifact upload condition
- Skip NativeSwapsCard story (requires NextRouter mock)

This makes snapshot diffs much easier to review since changes are
scoped to individual components.

---------

Co-authored-by: Hanzo Dev <[email protected]>
2025-12-01 18:23:47 +01:00

72 lines
2.3 KiB
JavaScript

#!/usr/bin/env node
/**
* Generates individual snapshot test files for each Storybook story file.
* This creates colocated test files (*.stories.test.tsx) next to each story file,
* resulting in separate snapshot files per component.
*
* Usage: node scripts/generate-storybook-tests.cjs
*/
const { globSync } = require('glob')
const fs = require('fs')
const path = require('path')
const SRC_DIR = path.join(__dirname, '../src')
// Find all story files
const storyFiles = globSync(path.join(SRC_DIR, '**/*.stories.tsx'))
const testTemplate = (storyImportPath, setupImportPath) => `/**
* Auto-generated snapshot tests for Storybook stories
* Run "yarn generate:storybook-tests" to regenerate
*/
import '${setupImportPath}'
import { composeStories } from '@storybook/react'
import { render } from '@testing-library/react'
import type { ComponentType } from 'react'
import * as stories from '${storyImportPath}'
const composedStories = composeStories(stories)
describe('${storyImportPath}', () => {
Object.entries(composedStories).forEach(([storyName, Story]) => {
test(storyName, () => {
const StoryComponent = Story as ComponentType
const { container } = render(<StoryComponent />)
expect(container.firstChild).toMatchSnapshot()
})
})
})
`
let generated = 0
let skipped = 0
const SETUP_FILE = path.join(SRC_DIR, 'tests/storybook-setup')
storyFiles.forEach((storyFilePath) => {
const testFilePath = storyFilePath.replace('.stories.tsx', '.stories.test.tsx')
const testFileDir = path.dirname(testFilePath)
const storyImportPath = './' + path.basename(storyFilePath).replace('.tsx', '')
// Calculate relative path from test file to setup file
const setupImportPath = path.relative(testFileDir, SETUP_FILE).replace(/\\/g, '/')
// Skip if test file already exists and wasn't auto-generated
if (fs.existsSync(testFilePath)) {
const content = fs.readFileSync(testFilePath, 'utf-8')
if (!content.includes('Auto-generated snapshot tests')) {
console.log(`Skipping ${path.relative(SRC_DIR, testFilePath)} (manually created)`)
skipped++
return
}
}
fs.writeFileSync(testFilePath, testTemplate(storyImportPath, setupImportPath))
console.log(`Generated ${path.relative(SRC_DIR, testFilePath)}`)
generated++
})
console.log(`\nDone! Generated ${generated} test files, skipped ${skipped}`)