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
11 changes: 5 additions & 6 deletions clients/storybook/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 26 additions & 1 deletion clients/storybook/src/crawler.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,20 +96,45 @@ 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<Object>} stories - Array of story objects
* @param {Object} config - Configuration object
* @returns {Array<Object>} 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;
Expand Down
31 changes: 31 additions & 0 deletions clients/storybook/tests/crawler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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);
Expand Down
32 changes: 31 additions & 1 deletion clients/storybook/tests/sdk-integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
);
});
});

// ===========================================================================
Expand Down