Token导航 LogoToken导航TokenDH.com
研究检索只读github未标认证来源可访问clear审计通过

storybook-play-functions故事书播放功能

Agent Skill

storybook-play-functions 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

2,088

周安装

87

GitHub Stars

142

下载量

696
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

3

许可证

MIT

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:storybook-play-functions(故事书播放功能)
来源仓库:https://github.com/thebushidocollective/han
仓库路径:skills/storybook-play-functions
安装命令:
npx skills add https://github.com/thebushidocollective/han --skill storybook-play-functions
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/thebushidocollective/han --skill storybook-play-functions

简介

storybook-play-functions 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。

  • 适用于快速信息收集和筛选的场景,如研究、调研或内容创作。
  • 通过关键词匹配和来源分析,帮助 Agent 高效定位相关内容。
  • 安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。
  • 可结合来源仓库和原始 README 继续核验具体用法和实现细节。

SKILL.md

Storybook - Play Functions

Write automated interaction tests within stories using play functions to verify component behavior, simulate user actions, and test edge cases.

Key Concepts

Play Functions

Play functions run after a story renders, allowing you to simulate user interactions:

import { within, userEvent, expect } from '@storybook/test';
import type { Meta, StoryObj } from '@storybook/react';
import { LoginForm } from './LoginForm';

const meta = {
  component: LoginForm,
} satisfies Meta<typeof LoginForm>;

export default meta;
type Story = StoryObj<typeof meta>;

export const FilledForm: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    await userEvent.type(canvas.getByLabelText('Email'), 'user@example.com');
    await userEvent.type(canvas.getByLabelText('Password'), 'password123');
    await userEvent.click(canvas.getByRole('button', { name: /submit/i }));

    await expect(canvas.getByText('Welcome!')).toBeInTheDocument();
  },
};

Testing Library Integration

Storybook integrates with Testing Library for queries and interactions:

  • within(canvasElement) - Scopes queries to the story
  • userEvent - Simulates realistic user interactions
  • expect - Jest-compatible assertions
  • waitFor - Waits for async changes

Test Execution

Play functions execute:

  • When viewing a story in Storybook
  • During visual regression testing
  • In test runners for automated testing
  • On story hot-reload during development

Best Practices

1. Use Testing Library Queries

Use semantic queries to find elements:

export const SearchFlow: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    // Good - Semantic queries
    const searchInput = canvas.getByRole('searchbox');
    const submitButton = canvas.getByRole('button', { name: /search/i });
    const results = canvas.getByRole('list', { name: /results/i });

    await userEvent.type(searchInput, 'storybook');
    await userEvent.click(submitButton);

    await expect(results).toBeInTheDocument();
  },
};

2. Simulate Realistic User Behavior

Use userEvent for realistic interactions:

import { userEvent } from '@storybook/test';

export const FormInteraction: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    // Type naturally with delay
    await userEvent.type(canvas.getByLabelText('Name'), 'John Doe', {
      delay: 100,
    });

    // Tab between fields
    await userEvent.tab();

    // Select from dropdown
    await userEvent.selectOptions(
      canvas.getByLabelText('Country'),
      'United States'
    );

    // Click submit
    await userEvent.click(canvas.getByRole('button', { name: /submit/i }));
  },
};

3. Test Async Behavior

Use waitFor for async state changes:

import { waitFor } from '@storybook/test';

export const AsyncData: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    await userEvent.click(canvas.getByRole('button', { name: /load data/i }));

    // Wait for loading state
    await waitFor(() => {
      expect(canvas.getByText('Loading...')).toBeInTheDocument();
    });

    // Wait for data to appear
    await waitFor(
      () => {
        expect(canvas.getByRole('list')).toBeInTheDocument();
        expect(canvas.getAllByRole('listitem')).toHaveLength(5);
      },
      { timeout: 3000 }
    );
  },
};

4. Test Error States

Validate error handling and validation:

export const ValidationErrors: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    // Submit empty form
    await userEvent.click(canvas.getByRole('button', { name: /submit/i }));

    // Verify error messages
    await expect(canvas.getByText('Email is required')).toBeInTheDocument();
    await expect(canvas.getByText('Password is required')).toBeInTheDocument();

    // Fill only email
    await userEvent.type(canvas.getByLabelText('Email'), 'invalid-email');
    await userEvent.click(canvas.getByRole('button', { name: /submit/i }));

    // Verify email validation
    await expect(canvas.getByText('Email is invalid')).toBeInTheDocument();
  },
};

5. Compose Complex Scenarios

Break complex interactions into steps:

export const CheckoutFlow: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    // Step 1: Add items to cart
    await userEvent.click(canvas.getByRole('button', { name: /add to cart/i }));
    await expect(canvas.getByText('1 item in cart')).toBeInTheDocument();

    // Step 2: Proceed to checkout
    await userEvent.click(canvas.getByRole('button', { name: /checkout/i }));
    await expect(canvas.getByRole('heading', { name: /checkout/i })).toBeInTheDocument();

    // Step 3: Fill shipping info
    await userEvent.type(canvas.getByLabelText('Address'), '123 Main St');
    await userEvent.type(canvas.getByLabelText('City'), 'New York');
    await userEvent.selectOptions(canvas.getByLabelText('State'), 'NY');

    // Step 4: Submit order
    await userEvent.click(canvas.getByRole('button', { name: /place order/i }));
    await waitFor(() => {
      expect(canvas.getByText('Order confirmed!')).toBeInTheDocument();
    });
  },
};

Common Patterns

Modal Interactions

export const OpenModal: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    // Modal not visible initially
    expect(canvas.queryByRole('dialog')).not.toBeInTheDocument();

    // Click trigger
    await userEvent.click(canvas.getByRole('button', { name: /open/i }));

    // Modal appears
    const modal = canvas.getByRole('dialog');
    await expect(modal).toBeInTheDocument();

    // Close modal
    await userEvent.click(within(modal).getByRole('button', { name: /close/i }));

    // Modal disappears
    await waitFor(() => {
      expect(canvas.queryByRole('dialog')).not.toBeInTheDocument();
    });
  },
};

Keyboard Navigation

export const KeyboardNav: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    const firstItem = canvas.getAllByRole('menuitem')[0];
    firstItem.focus();

    // Navigate with arrow keys
    await userEvent.keyboard('{ArrowDown}');
    await expect(canvas.getAllByRole('menuitem')[1]).toHaveFocus();

    await userEvent.keyboard('{ArrowDown}');
    await expect(canvas.getAllByRole('menuitem')[2]).toHaveFocus();

    // Select with Enter
    await userEvent.keyboard('{Enter}');
    await expect(canvas.getByText('Item 3 selected')).toBeInTheDocument();

    // Close with Escape
    await userEvent.keyboard('{Escape}');
    await waitFor(() => {
      expect(canvas.queryByRole('menu')).not.toBeInTheDocument();
    });
  },
};

Multi-Step Forms

export const Wizard: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    // Step 1
    await userEvent.type(canvas.getByLabelText('First Name'), 'John');
    await userEvent.type(canvas.getByLabelText('Last Name'), 'Doe');
    await userEvent.click(canvas.getByRole('button', { name: /next/i }));

    // Step 2
    await expect(canvas.getByText('Step 2 of 3')).toBeInTheDocument();
    await userEvent.type(canvas.getByLabelText('Email'), 'john@example.com');
    await userEvent.click(canvas.getByRole('button', { name: /next/i }));

    // Step 3
    await expect(canvas.getByText('Step 3 of 3')).toBeInTheDocument();
    await userEvent.click(canvas.getByRole('checkbox', { name: /agree/i }));
    await userEvent.click(canvas.getByRole('button', { name: /submit/i }));

    // Success
    await waitFor(() => {
      expect(canvas.getByText('Registration complete!')).toBeInTheDocument();
    });
  },
};

Drag and Drop

export const DragDrop: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    const draggable = canvas.getByRole('button', { name: /drag me/i });
    const dropzone = canvas.getByRole('region', { name: /drop zone/i });

    // Perform drag and drop
    await userEvent.pointer([
      { keys: '[MouseLeft>]', target: draggable },
      { coords: { x: 100, y: 100 } },
      { target: dropzone },
      { keys: '[/MouseLeft]' },
    ]);

    await expect(canvas.getByText('Item dropped!')).toBeInTheDocument();
  },
};

File Upload

export const FileUpload: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    const file = new File(['content'], 'test.txt', { type: 'text/plain' });
    const input = canvas.getByLabelText('Upload file');

    await userEvent.upload(input, file);

    await expect(canvas.getByText('test.txt')).toBeInTheDocument();
    await expect(canvas.getByText('1 file selected')).toBeInTheDocument();
  },
};

Advanced Patterns

Reusable Play Functions

// helpers.ts
export async function login(canvas: ReturnType<typeof within>) {
  await userEvent.type(canvas.getByLabelText('Email'), 'user@example.com');
  await userEvent.type(canvas.getByLabelText('Password'), 'password123');
  await userEvent.click(canvas.getByRole('button', { name: /login/i }));
}

// Story.stories.tsx
export const AfterLogin: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    await login(canvas);

    // Test authenticated state
    await expect(canvas.getByText('Welcome, User!')).toBeInTheDocument();
  },
};

Step-Through Testing

import { step } from '@storybook/test';

export const MultiStep: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    await step('Fill in personal info', async () => {
      await userEvent.type(canvas.getByLabelText('Name'), 'John Doe');
      await userEvent.type(canvas.getByLabelText('Email'), 'john@example.com');
    });

    await step('Select preferences', async () => {
      await userEvent.click(canvas.getByLabelText('Subscribe to newsletter'));
      await userEvent.selectOptions(canvas.getByLabelText('Theme'), 'dark');
    });

    await step('Submit form', async () => {
      await userEvent.click(canvas.getByRole('button', { name: /submit/i }));
      await expect(canvas.getByText('Success!')).toBeInTheDocument();
    });
  },
};

Anti-Patterns

❌ Don't Use Direct DOM Manipulation

// Bad
export const Bad: Story = {
  play: async ({ canvasElement }) => {
    const input = canvasElement.querySelector('input');
    input.value = 'text';
    input.dispatchEvent(new Event('input'));
  },
};
// Good
export const Good: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    await userEvent.type(canvas.getByRole('textbox'), 'text');
  },
};

❌ Don't Forget Async/Await

// Bad - Missing await
export const Bad: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    userEvent.click(canvas.getByRole('button'));  // Won't work!
    expect(canvas.getByText('Clicked')).toBeInTheDocument();
  },
};
// Good
export const Good: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    await userEvent.click(canvas.getByRole('button'));
    await expect(canvas.getByText('Clicked')).toBeInTheDocument();
  },
};

❌ Don't Use Brittle Selectors

// Bad - Fragile selectors
export const Bad: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    await userEvent.click(canvas.getByText('Submit'));  // Breaks if text changes
  },
};
// Good - Semantic selectors
export const Good: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    await userEvent.click(canvas.getByRole('button', { name: /submit/i }));
  },
};

Related Skills

  • storybook-story-writing: Creating stories to test with play functions
  • storybook-args-controls: Using args to test different component states
  • storybook-configuration: Setting up test runner for automated testing

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

04

需要参考平台分布和安装热度时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Claude Code

31%
按下载量换算216

Antigravity

20.83%
按下载量换算145

Cursor

17.33%
按下载量换算121

Codex

13.76%
按下载量换算96

OpenCode

6.95%
按下载量换算48

Gemini CLI

3.38%
按下载量换算24

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

只读

该 Skill 主要提供规则、说明或参考内容,本身偏只读;真正读写文件、联网或执行命令仍取决于宿主 Agent 的任务。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

来源信息

继续浏览同类 Skills