storybookjs / storybook

Storybook is the industry standard workshop for building, documenting, and testing UI components in isolation
https://storybook.js.org
MIT License
83.93k stars 9.21k forks source link

Addon-docs: Support CSF3 play function #15511

Open shilman opened 3 years ago

shilman commented 3 years ago

As a user, I'd like to see stories that use play functions to achieve interactions the same on the Docs page as how it appears on the Storybook Canvas.

Storybook CSF3 adds a play function for scripted interaction with components after a story has rendered. This currently does not work in the docs tab. We need to make it work:

There are a few different issues to deal with around dueling play functions and dueling focus.

Dueling play functions

Two play functions can't run at the same time: their events will clobber one another, especially if they require focus, e.g. keyboard events.

One way to deal with this is to run play functions sequentially. This can be problematic for long-running play functions.

Another way to deal with it is to not run play functions on the Docs page at all, and have UI by which the user can run a play function.

Dueling focus

A related issue is dueling Focus. Even if a play function finishes instantaneously, if story A shows element A focused, and story B shows element B focused, then at most one of those stories can show correctly on the Docs page.

The sequential approach above won't fix this. Only the play function UI, would work for this.

Proposal

Since the play function UI works for all cases, that's a relatively simple solution.

Alternative

A more complex solution would be to make it configurable with a parameter, e.g.

parameters: {
  docs: {
    playFunction: true,
  }
}

And then sequentially play all stories with playFunction true. This way the CSF author has control and can set playFunction to false for long-running stories.

┆Issue is synchronized with this Asana task by Unito

yannbf commented 3 years ago

Question: How would the following code work in a docs page?

const HelloStory = {
  play: async () => {
    userEvent.type(screen.getByRole('textbox'), 'Hello, World!')
  }
}

const GoodbyeStory = {
  play: async () => {
    userEvent.type(screen.getByRole('textbox'), 'Goodbye, World!')
  }
}

I think getByRole might return an error like: TestingLibraryElementError: Found multiple elements with the role "textbox" because the stories are inline, thus the selectors are duplicated.

ndelangen commented 3 years ago

@yannbf If the expectation is role='textbox' occurs only once on a screen, then there's no way to work if it appears multiple time.

I only see 2 ways of dealing with this:

donaldpipowitch commented 3 years ago

modify screen to select within the "targetContainer"

Can't we do something similar to the renderResult you get back when Testing Lib takes care of the rendering? It creates some preconfigured queries which are limited to the "targetContainer". Something like that could be passed to the play function?

// how it can be done inside a unit test
import { render } from '@testing-library/react';
const result = render(<GoodbyeStory />);

// in your story (`result` being something similar to what we would get from `render`)
const GoodbyeStory = {
  play: async ({ result }) => {
    userEvent.type(result.getByRole('textbox'), 'Goodbye, World!')
  }
}

Or if you want less coupling to Testing Lib this would be an alternative with a bit more typing:

import { within } from '@testing-library/react';

const GoodbyeStory = {
  play: async ({ container }) => {
    const queries = within(container); // container is just the DOM element with our rendered story
    userEvent.type(queries.getByRole('textbox'), 'Goodbye, World!')
  }
}
ndelangen commented 3 years ago
const queries = within(container)

❤️

ndelangen commented 3 years ago

@ghengeveld We talked about this a lot last week, should this ticket be assigned to you or us both perhaps?

ndelangen commented 2 years ago

@ghengeveld Want to talk about this later this week?

bodograumann commented 2 years ago

Pitty that this is not in 6.4 already, but given that chromatic does not look at the docs page it’s not that bad.

Having play UI on each story with a play function in the docs page sounds great functionally, but is probably not easy to make look good, especially when the story is not wrapped in a Canvas.

Good job with the docs promoting const canvas = within(canvasElement) as standard practice. :-)

Due to the way the interactive step-by-step debugger works, each play function instruction already has to be written in a way that is independant from the current state of focus. Is this something you want to tackle there as well?

stale[bot] commented 2 years ago

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

bodograumann commented 2 years ago

I feel like issue, where someone is assigned, shouldn't be closed automatically.

bodograumann commented 2 years ago

Is this included in version 6.5?

shilman commented 2 years ago

@bodograumann nope, unfortunately not. 7.0 🤞

cazdev commented 1 year ago

Is this included in 7.0?

yannbf commented 1 year ago

Is this included in 7.0?

Great question! Yes, the play function works in MDX, however does not run by defualt. You can try it out with the Story block by passing the autoplay property to it, and then it will:

import { Meta, Story } from '@storybook/blocks'
import * as ButtonStories from './Button.stories'

# Button

<Meta of={ButtonStories} />
<Story of={ButtonStories.Primary} autoplay />
ignaciolarranaga commented 1 year ago

Is there a way to run it on the default template @yannbf ?

yannbf commented 1 year ago

Is there a way to run it on the default template @yannbf ?

Good question! I don't think so? @JReinhold @tmeasday could answer this one.

I was going to suggest to write a custom default autodocs template but you'd end up using the <Stories /> block and not the <Story /> one. The Stories block does not (currently) have an autoplay prop that would be passed down to all stories.

JReinhold commented 1 year ago

Is there a way to run it on the default template @yannbf ?

There is! All props on doc blocks can also be set via parameters on stories/component/project. So you can set parameters.docs.story.autoplay = true, documented here: https://storybook.js.org/docs/react/api/doc-block-story#autoplay

jonniebigodes commented 1 year ago

@ignaciolarranaga we have documentation on this. If you check this, you'll see all the available props and what is available with that Doc Block. Heads up, @JReinhold, check your link. You referenced something unrelated to the conversation. Might I suggest that you remove it?

Leland commented 10 months ago

After searching far and wide and finally finding this thread, it's worth pointing out that previous versions' documentation does not indicate that play functions will not be ran on DocsPages: https://storybook.js.org/docs/6.5/react/writing-stories/play-function#writing-stories-with-the-play-function

The inclusion of MDX files on this v6.5 documentation even may make it seem that it's allowed, as does this generic language:

Play functions are small snippets of code executed after the story renders

yannbf commented 9 months ago

cc @jonniebigodes ☝️

donaldpipowitch commented 3 weeks ago

Is it possible to run play functions in the docs page with some workaround today? I tried setting parameters.docs.story.autoplay = true in my preview.tsx, but it doesn't seem to have an effect.

JReinhold commented 2 weeks ago

Is it possible to run play functions in the docs page with some workaround today? I tried setting parameters.docs.story.autoplay = true in my preview.tsx, but it doesn't seem to have an effect.

@donaldpipowitch that should be the current workaround, so if that doesn't work, that's a bug. Can you open a separate bug report with a minimal reproduction?