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:
-
Sets root to the parent of configDir:
const commonConfig = { root: resolve(finalOptions.configDir, '..') }
// → root = "monorepo/excalidraw-app/"
-
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
Describe the bug
This issue was found while evaluating https://github.com/storybook-tmp/excalidraw
When
storybookTestis used with aconfigDirthat 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'sconfighook does two inconsistent things:Sets
rootto the parent ofconfigDir:Computes
test.includepaths usingvitestRoot, which falls back toprocess.cwd()(the monorepo root):Vitest then resolves
test.includepatterns relative toroot. Sinceroot = excalidraw-app/but the patterns are relative to the monorepo root, vitest looks for files atexcalidraw-app/excalidraw-app/stories/**/*— a path that doesn't exist.In a standard single-package project this never surfaces because
resolve(configDir, "..")equalsprocess.cwd(). The mismatch only appears whenconfigDiris nested inside a subdirectory.Reproduction
vitest --project=storybookoutput:The include pattern looks right but resolves against the wrong root.
Why
..alone isn't the full fixChanging the
vitestRootfallback toresolve(finalOptions.configDir, "..")fixes the simple case but breaks another common monorepo pattern: a dedicated Storybook package whose stories reference components from sibling packages:With
root = packages/storybook/, those cross-package stories would be discovered via../ui/...paths, but Vite'sserver.fsrestricts serving files outsideroot, so they'd be found but fail to load.Proper fix
Use absolute paths in
includeStoriesinstead of paths relative tovitestRoot:Absolute paths work with tinyglobby and vitest's
includeregardless of whatrootis set to. Combined with ensuringserver.fs.allowcovers the necessary directories, this would work for all monorepo layouts.Current workaround
Add a post-plugin to the vitest config that resets
rootback to the monorepo root afterstorybookTestsets it:This works because it makes
rootconsistent withvitestRoot = 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 currentvitestRootcomputationvitest: 3.xconfigDiris not a direct child of the vitest working directory