happo / happo-plugin-storybook

A happo.io plugin for Storybook
MIT License
11 stars 5 forks source link

Question/Possible Feature Request: Screenshots of different variants/args? #81

Open BCerki opened 1 year ago

BCerki commented 1 year ago

Good afternoon! Thanks so much for creating this plugin—it's a very helpful tool.

We're building a component library with Storybook and using the plugin to take screenshots of all of our components for visual testing in CI. Instead of creating a story for each variant, we allow users to explore the different colours, sizes, arguments, etc. with the Storybook Controls add-on. Is there a way to configure the plugin so it takes multiple screenshots of the same story with different arguments?

In case it's helpful, here's an example of our Button component story. (Our component library is all about progressive enhancement, so our stories showcase that while other options are available via the Controls.)

I've looked into the variants add on, but it would require making an All Variants story and/or adding code to each story individually, which isn't quite what we're trying to do.

Please let me know if you'd like more details, or if I can further help.

Thanks so much!

trotzig commented 1 year ago

Hi @BCerki! Good question! The short answer is no, Happo takes only one screenshot per variant. There is the interactions add-on that allow you to interact with the UI as part of rendering a story, but that only runs once per story so it's not really an option here.

With the boring answer out of the way, let's see if we can figure out ideas on how to get this solved. My first thought was that we can somehow extract information from the Controls add-on and auto-generate variants from it. But I quickly realized this would lead to an exponential number of variants. For instance, the TextArea component has 8 props that can be configured. Auto-generating all possible combinations would result in ... a whole lot of variants. So we need to curate a set of variants to feed into Happo. The easiest way of doing that would be to simply add them as exported variants in the code, e.g.

// TextArea.stories.tsx

// The first two are the ones we normally have
export const Default = Template.bind({});
Default.args = args;

export const HTML = HTMLTemplate.bind({});
HTML.args = args;

// Then we specify special variants for Happo
export const Disabled = () => <Textarea {...args} disabled />;
export const SmallDisabled = () => <Textarea {...args} disabled size="small" />;

Of course this would also mean that the new Happo variants would show up in the regular Storybook UI, which might not be what we want. We could solve this by conditionally hiding Happo variants if they are not in a Happo environment. I'm not 100% sure how we do this since exports are static and can't be conditionally added. Maybe if we prefixed everything with "Happo" we could write a babel plugin that would strip out anything matching `/^Happo.*/?

// TextArea.stories.tsx
export const HappoDisabled = () => <Textarea {...args} disabled />;
export const HappoSmallDisabled = () => <Textarea {...args} disabled size="small" />;

The plugin could look something like this:

module.exports = () => ({
  name: 'babel-plugin-remove-happo-exports',
  visitor: {
    ExportNamedDeclaration: (path, { opts }) => {
      if (path.node.declaration.declarations[0].id.name.startsWith('Happo')) {
        path.remove();
      }   
    }
  }
});

I've tested this via a babel playground and it works, at least for a very simple case.

We can then adjust the babel plugin to not remove the exports if it's for a Happo run using an environment variable:

const { REMOVE_HAPPO_EXPORTS } = process.env;
module.exports = () => ({
  name: 'babel-plugin-remove-happo-exports',
  visitor: {
    ExportNamedDeclaration: (path, { opts }) => {
      if (REMOVE_HAPPO_EXPORTS !== 'false' && path.node.declaration.declarations[0].id.name.startsWith('Happo')) {
        path.remove();
      }   
    }
  }
});

And then when invoking the Happo run, we inject the environment variable:

REMOVE_HAPPO_EXPORTS=false yarn happo-ci-github-actions

Let me know what you think of this idea!

BCerki commented 1 year ago

Thanks for such a thoughtful response! These suggestions look great. I'll experiment with them and post an update later this week.

trotzig commented 1 year ago

Excellent! I forgot to mention I did some exploratory work myself on a fork of service-development-toolkit. The babel plugin works but it looks like Storybook chokes when the export is stripped out. The following error appears when we've stripped out some exports from Button.stories.tsx:

Unexpected error while loading ./Button.stories.tsx: ReferenceError: HappoDisabled is not defined

I believe this is coming from @storybook/source-loader. There's a separate process (outside of the webpack build) parsing the stories files for exports. We'll likely have to inject ourselves here too. Not sure how to do that though. 🤔

Here's a link to the commit on my fork where I was exploring this: https://github.com/trotzig/service-development-toolkit/commit/d8f86db7e353d5c43993b7f500dccca15c261d0c

BCerki commented 1 year ago

Just a short update for now: Storybook is considering a feature to hide stories that might do the job