From fa88c095e3d2f1a0071fef7806edc72e2d82980e Mon Sep 17 00:00:00 2001 From: Robert DeLuca Date: Thu, 12 Feb 2026 17:27:54 -0600 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=90=9B=20Fix=20Storybook=20skip=20fil?= =?UTF-8?q?tering=20via=20vizzly-skip=20tag=20(#215)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - add support for tag-based skip filtering via vizzly-skip during story discovery - keep parameters.vizzly.skip support for backwards compatibility - add debug logs to show why a story is filtered out - add crawler tests for tag-only and mixed tag+parameter skip cases - update Storybook README and example story to recommend tags: ['vizzly-skip'] ## Test plan - [x] npm test -- tests/crawler.test.js (from clients/storybook) - [ ] Verify against a real Storybook 7+ build with tagged stories --- clients/storybook/README.md | 11 +++---- .../story-with-vizzly-config.stories.js | 8 ++--- clients/storybook/src/crawler.js | 27 +++++++++++++++- clients/storybook/tests/crawler.test.js | 31 +++++++++++++++++++ 4 files changed, 64 insertions(+), 13 deletions(-) diff --git a/clients/storybook/README.md b/clients/storybook/README.md index b7e3ce58..e06621d8 100644 --- a/clients/storybook/README.md +++ b/clients/storybook/README.md @@ -94,7 +94,8 @@ Run `vizzly init` to generate a config file with sensible defaults. ### Per-Story Configuration -You can configure specific stories by adding a `vizzly` parameter in your story files: +You can configure specific stories by adding tags and `vizzly` parameters in your story files. +Use `tags: ['vizzly-skip']` as the primary way to skip screenshot capture: ```javascript // Button.stories.js @@ -114,14 +115,12 @@ export let Primary = { export let Disabled = { args: { label: 'Disabled', disabled: true }, - parameters: { - vizzly: { - skip: true, // Don't screenshot this story - }, - }, + tags: ['vizzly-skip'], // Don't screenshot this story }; ``` +`parameters.vizzly.skip` is still supported for backwards compatibility. + ## Configuration Priority Configuration is merged in this order (later overrides earlier): diff --git a/clients/storybook/examples/story-with-vizzly-config.stories.js b/clients/storybook/examples/story-with-vizzly-config.stories.js index c1a616b0..de7d5508 100644 --- a/clients/storybook/examples/story-with-vizzly-config.stories.js +++ b/clients/storybook/examples/story-with-vizzly-config.stories.js @@ -46,12 +46,8 @@ export let Deprecated = { args: { label: 'Old Button', }, - parameters: { - vizzly: { - // Don't capture screenshots for this story - skip: true, - }, - }, + // Don't capture screenshots for this story + tags: ['vizzly-skip'], }; // Story with full page screenshot diff --git a/clients/storybook/src/crawler.js b/clients/storybook/src/crawler.js index 353264e4..746c065b 100644 --- a/clients/storybook/src/crawler.js +++ b/clients/storybook/src/crawler.js @@ -96,6 +96,15 @@ export function extractStoryConfig(story) { return story.parameters?.vizzly || null; } +/** + * Check whether a story is tagged to skip Vizzly capture + * @param {Object} story - Story object with tags + * @returns {boolean} + */ +function hasVizzlySkipTag(story) { + return Array.isArray(story.tags) && story.tags.includes('vizzly-skip'); +} + /** * Filter stories based on include/exclude patterns and skip config * @param {Array} stories - Array of story objects @@ -103,13 +112,29 @@ export function extractStoryConfig(story) { * @returns {Array} Filtered stories */ export function filterStories(stories, config) { + let verbose = process.env.VIZZLY_LOG_LEVEL === 'debug'; + // First filter by include/exclude patterns let filtered = filterByPattern(stories, config.include, config.exclude); // Then filter out stories marked to skip filtered = filtered.filter(story => { + if (hasVizzlySkipTag(story)) { + if (verbose) { + console.error(` [filter] Skipping ${story.id} (tag: vizzly-skip)`); + } + return false; + } + let storyConfig = extractStoryConfig(story); - return !storyConfig?.skip; + if (storyConfig?.skip) { + if (verbose) { + console.error(` [filter] Skipping ${story.id} (parameters.vizzly.skip)`); + } + return false; + } + + return true; }); return filtered; diff --git a/clients/storybook/tests/crawler.test.js b/clients/storybook/tests/crawler.test.js index d037b05f..b373fde0 100644 --- a/clients/storybook/tests/crawler.test.js +++ b/clients/storybook/tests/crawler.test.js @@ -134,6 +134,11 @@ describe('filterStories', () => { title: 'Card', parameters: { vizzly: { skip: true } }, }, + { + id: 'button--tag-skipped', + title: 'Button', + tags: ['vizzly-skip'], + }, ]; it('should filter by include pattern', () => { @@ -163,6 +168,32 @@ describe('filterStories', () => { ); }); + it('should skip stories tagged with vizzly-skip', () => { + let config = {}; + let filtered = filterStories(stories, config); + + assert.equal( + filtered.find(s => s.id === 'button--tag-skipped'), + undefined + ); + }); + + it('should skip stories when both tag and parameters are present', () => { + let config = {}; + let storiesWithBoth = [ + ...stories, + { + id: 'card--both-skipped', + title: 'Card', + tags: ['vizzly-skip'], + parameters: { vizzly: { skip: true } }, + }, + ]; + let filtered = filterStories(storiesWithBoth, config); + + assert.equal(filtered.find(s => s.id === 'card--both-skipped'), undefined); + }); + it('should apply both include and skip filters', () => { let config = { include: 'card*' }; let filtered = filterStories(stories, config); From f45403ec2bc364bd0b9e6feed4a6386ac54d11fb Mon Sep 17 00:00:00 2001 From: Robert DeLuca Date: Thu, 12 Feb 2026 17:54:44 -0600 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9C=85=20Add=20skipped=20sentinel=20stor?= =?UTF-8?q?y=20to=20Storybook=20E2E=20fixtures=20(#215)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - add a large sentinel story named WE SHOULD NEVER SEE THIS IN VIZZLY - tag the sentinel story with vizzly-skip in the example Storybook fixture - add an E2E discovery assertion that the sentinel exists in index.json with the tag - assert the sentinel is excluded from discoverStories results ## Test plan - [x] node --test --test-reporter=spec tests/crawler.test.js (from clients/storybook) - [ ] VIZZLY_E2E=1 npm test -- tests/sdk-integration.test.js (requires full Storybook E2E deps) --- .../src/components/Button.stories.js | 9 ++++++ .../storybook/tests/sdk-integration.test.js | 32 ++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/clients/storybook/example-storybook/src/components/Button.stories.js b/clients/storybook/example-storybook/src/components/Button.stories.js index 0decf0ab..76b3ff2a 100644 --- a/clients/storybook/example-storybook/src/components/Button.stories.js +++ b/clients/storybook/example-storybook/src/components/Button.stories.js @@ -33,3 +33,12 @@ export let Danger = { variant: 'danger', }, }; + +export let WeShouldNeverSeeThisInVizzly = { + name: 'WE SHOULD NEVER SEE THIS IN VIZZLY', + tags: ['vizzly-skip'], + args: { + label: 'WE SHOULD NEVER SEE THIS IN VIZZLY', + variant: 'danger', + }, +}; diff --git a/clients/storybook/tests/sdk-integration.test.js b/clients/storybook/tests/sdk-integration.test.js index f4130dce..adf4ca78 100644 --- a/clients/storybook/tests/sdk-integration.test.js +++ b/clients/storybook/tests/sdk-integration.test.js @@ -19,12 +19,14 @@ import { join, resolve } from 'node:path'; import { after, before, describe, it } from 'node:test'; import { closeBrowser, launchBrowser, navigateToUrl } from '../src/browser.js'; -import { discoverStories, generateStoryUrl } from '../src/crawler.js'; +import { discoverStories, generateStoryUrl, readIndexJson } from '../src/crawler.js'; import { getBeforeScreenshotHook, getStoryConfig } from '../src/hooks.js'; import { captureAndSendScreenshot } from '../src/screenshot.js'; import { startStaticServer, stopStaticServer } from '../src/server.js'; import { setViewport } from '../src/utils/viewport.js'; +let skippedStoryName = 'WE SHOULD NEVER SEE THIS IN VIZZLY'; + /** * Prepare a story page for screenshot (test helper) * @param {Object} browser - Browser instance @@ -217,6 +219,34 @@ describe('Storybook E2E with example-storybook', { skip: !runE2E }, () => { ); } }); + + it('skips vizzly-skip tagged stories from discovery', async () => { + let indexData = await readIndexJson(storybookBuildPath); + let entries = Object.values(indexData.entries || indexData.stories || {}); + let skippedEntry = entries.find( + entry => + entry.type === 'story' && + entry.name === skippedStoryName && + entry.tags?.includes('vizzly-skip') + ); + + assert.ok( + skippedEntry, + `${skippedStoryName} should exist in storybook index with vizzly-skip tag` + ); + + let config = { + storybookPath: storybookBuildPath, + include: null, + exclude: null, + }; + let stories = await discoverStories(storybookBuildPath, config); + + assert.equal( + stories.find(story => story.name === skippedStoryName), + undefined + ); + }); }); // ===========================================================================