Skip to content

Bug: storybookTest vitest plugin sets root to app subdirectory but computes test.include paths relative to process.cwd(), causing "No test files found" in monorepos #34554

@yannbf

Description

@yannbf

Describe the bug

This issue was found while evaluating https://github.com/storybook-tmp/excalidraw

When storybookTest is used with a configDir that is not a direct child of the vitest working directory (i.e. any monorepo where the Storybook app lives in a subdirectory), vitest reports "No test files found" even though the include patterns shown in the output look correct.

Root cause

In vitest-plugin/index.ts, the plugin's config hook does two inconsistent things:

  1. Sets root to the parent of configDir:

    const commonConfig = { root: resolve(finalOptions.configDir, '..') }
    // → root = "monorepo/excalidraw-app/"
  2. Computes test.include paths using vitestRoot, which falls back to process.cwd() (the monorepo root):

    finalOptions.vitestRoot = testConfig?.dir || testConfig?.root || nonMutableInputConfig.root || process.cwd();
    //                                                                                              ↑ monorepo root
    includeStories = stories.map(story => relative(finalOptions.vitestRoot, story));
    // → ["excalidraw-app/stories/**/*.stories.@(js|jsx|mjs|ts|tsx)"]  (relative to monorepo root)

Vitest then resolves test.include patterns relative to root. Since root = excalidraw-app/ but the patterns are relative to the monorepo root, vitest looks for files at excalidraw-app/excalidraw-app/stories/**/* — a path that doesn't exist.

In a standard single-package project this never surfaces because resolve(configDir, "..") equals process.cwd(). The mismatch only appears when configDir is nested inside a subdirectory.

Reproduction

monorepo/
  vitest.config.mts       ← storybookTest({ configDir: "excalidraw-app/.storybook" })
  excalidraw-app/
    .storybook/
      main.ts
    stories/
      Button.stories.ts

vitest --project=storybook output:

No test files found.

storybook (chromium)

include: excalidraw-app/stories/**/*.stories.@(js|jsx|mjs|ts|tsx)
exclude: **/*.mdx

The include pattern looks right but resolves against the wrong root.

Why .. alone isn't the full fix

Changing the vitestRoot fallback to resolve(finalOptions.configDir, "..") fixes the simple case but breaks another common monorepo pattern: a dedicated Storybook package whose stories reference components from sibling packages:

monorepo/
  packages/
    storybook/
      .storybook/    ← configDir, root = packages/storybook/
      stories/       ← stories import from ../ui/
    ui/
      Button.tsx
      Button.stories.ts   ← outside root!

With root = packages/storybook/, those cross-package stories would be discovered via ../ui/... paths, but Vite's server.fs restricts serving files outside root, so they'd be found but fail to load.

Proper fix

Use absolute paths in includeStories instead of paths relative to vitestRoot:

// Before
includeStories = stories
  .map(story => join(finalOptions.configDir, storyPath))
  .map(story => relative(finalOptions.vitestRoot, story));  // ← root-dependent

// After
includeStories = stories
  .map(story => join(finalOptions.configDir, storyPath));  // ← absolute, root-agnostic

Absolute paths work with tinyglobby and vitest's include regardless of what root is set to. Combined with ensuring server.fs.allow covers the necessary directories, this would work for all monorepo layouts.

Current workaround

Add a post-plugin to the vitest config that resets root back to the monorepo root after storybookTest sets it:

// vitest.config.mts
plugins: [
  storybookTest({ configDir: path.join(dirname, "excalidraw-app", ".storybook") }),
  {
    name: "fix-storybook-root",
    enforce: "post" as const,
    config: () => ({ root: dirname }),
  },
],

This works because it makes root consistent with vitestRoot = process.cwd(), so the include paths resolve correctly. It does not handle the cross-package stories case.

Environment

  • @storybook/addon-vitest: any version with the current vitestRoot computation
  • vitest: 3.x
  • Affected layout: any monorepo where configDir is not a direct child of the vitest working directory

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions