diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,20 @@
 # Changelog
 
+## [0.28.0.0] - 2026-08-08
+
+### Added
+
+* `scenarioDirOfDirs`, for scenarios that consist of more than one file.  It
+  defines a test for each subdirectory of the given directory, and hands that
+  subdirectory to the test definition.
+
+### Changed
+
+* `scenarioDir` and `scenarioDirRecur` now define a single failing test when
+  they find no files, instead of defining no tests at all.  An empty scenario
+  directory usually means the scenario files were omitted by accident, for
+  example because they were not packaged in `extra-source-files`.
+
 ## [0.27.2.0] - 2026-07-16
 
 ### Changed
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/src/Test/Syd.hs b/src/Test/Syd.hs
--- a/src/Test/Syd.hs
+++ b/src/Test/Syd.hs
@@ -87,6 +87,7 @@
     -- ** Scenario tests
     scenarioDir,
     scenarioDirRecur,
+    scenarioDirOfDirs,
 
     -- ** Expectations
     shouldBe,
diff --git a/src/Test/Syd/Def/Scenario.hs b/src/Test/Syd/Def/Scenario.hs
--- a/src/Test/Syd/Def/Scenario.hs
+++ b/src/Test/Syd/Def/Scenario.hs
@@ -1,4 +1,4 @@
-module Test.Syd.Def.Scenario (scenarioDir, scenarioDirRecur) where
+module Test.Syd.Def.Scenario (scenarioDir, scenarioDirRecur, scenarioDirOfDirs) where
 
 import Control.Monad
 import Control.Monad.IO.Class
@@ -8,9 +8,17 @@
 import qualified System.FilePath as FP
 import Test.Syd.Def.Specify
 import Test.Syd.Def.TestDefM
+import Test.Syd.Expectation
 
 -- | Define a test for each file in the given directory.
 --
+-- Subdirectories are ignored, use 'scenarioDirRecur' to descend into them or
+-- 'scenarioDirOfDirs' to treat each of them as a scenario.
+--
+-- If the directory is empty or absent, this defines a single failing test
+-- instead, because that usually means the scenario files were omitted by
+-- accident.
+--
 -- Example:
 --
 -- >   scenarioDir "test_resources/even" $ \fp ->
@@ -19,10 +27,14 @@
 -- >       n <- readIO s
 -- >       (n :: Int) `shouldSatisfy` even
 scenarioDir :: FilePath -> (FilePath -> TestDefM outers inner ()) -> TestDefM outers inner ()
-scenarioDir = scenarioDirHelper listDirRel
+scenarioDir = scenarioDirHelper "files" (fmap (map fromRelFile . snd) . listDirRel)
 
 -- | Define a test for each file in the given directory, recursively.
 --
+-- If the directory contains no files, or is absent, this defines a single
+-- failing test instead, because that usually means the scenario files were
+-- omitted by accident.
+--
 -- Example:
 --
 -- >   scenarioDirRecur "test_resources/odd" $ \fp ->
@@ -31,17 +43,46 @@
 -- >       n <- readIO s
 -- >       (n :: Int) `shouldSatisfy` odd
 scenarioDirRecur :: FilePath -> (FilePath -> TestDefM outers inner ()) -> TestDefM outers inner ()
-scenarioDirRecur = scenarioDirHelper listDirRecurRel
+scenarioDirRecur = scenarioDirHelper "files" (fmap (map fromRelFile . snd) . listDirRecurRel)
 
+-- | Define a test for each subdirectory of the given directory.
+--
+-- Use this when a single scenario consists of more than one file.  Files in
+-- the given directory itself are ignored, and so is any nesting below the
+-- subdirectories: each subdirectory is one scenario, whatever it contains.
+--
+-- If there are no subdirectories, or the directory is absent, this defines a
+-- single failing test instead, because that usually means the scenarios were
+-- omitted by accident.
+--
+-- Example:
+--
+-- >   scenarioDirOfDirs "test_resources/same" $ \fp ->
+-- >     it "contains two files with the same contents" $ do
+-- >       a <- readFile (fp </> "a")
+-- >       b <- readFile (fp </> "b")
+-- >       a `shouldBe` b
+scenarioDirOfDirs :: FilePath -> (FilePath -> TestDefM outers inner ()) -> TestDefM outers inner ()
+scenarioDirOfDirs =
+  scenarioDirHelper
+    "directories"
+    (fmap (map (FP.dropTrailingPathSeparator . fromRelDir) . fst) . listDirRel)
+
 scenarioDirHelper ::
-  (Path Abs Dir -> IO ([Path Rel Dir], [Path Rel File])) ->
+  -- | What the lister looks for, for the description of the failing test that
+  -- an empty scenario directory produces.
+  String ->
+  -- | The scenarios, relative to the given directory
+  (Path Abs Dir -> IO [FilePath]) ->
   FilePath ->
   (FilePath -> TestDefM outers inner ()) ->
   TestDefM outers inner ()
-scenarioDirHelper lister dp func =
+scenarioDirHelper noun lister dp func =
   describe dp $ do
     ad <- liftIO $ resolveDir' dp
-    fs <- liftIO $ fmap (fromMaybe []) $ forgivingAbsence $ snd <$> lister ad
-    forM_ fs $ \rf -> do
-      let fp = dp FP.</> fromRelFile rf
-      describe (fromRelFile rf) $ func fp
+    ss <- liftIO $ fmap (fromMaybe []) $ forgivingAbsence $ lister ad
+    if null ss
+      then it (unwords ["has scenario", noun]) $ \_ ->
+        (expectationFailure $ unwords ["No scenario", noun, "found in", dp] :: IO ())
+      else forM_ ss $ \s ->
+        describe s $ func (dp FP.</> s)
diff --git a/sydtest.cabal b/sydtest.cabal
--- a/sydtest.cabal
+++ b/sydtest.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           sydtest
-version:        0.27.2.0
+version:        0.28.0.0
 synopsis:       A modern testing framework for Haskell with good defaults and advanced testing features.
 description:    A modern testing framework for Haskell with good defaults and advanced testing features. Sydtest aims to make the common easy and the hard possible. See https://github.com/NorfairKing/sydtest#readme for more information.
 category:       Testing
@@ -93,7 +93,7 @@
     , safe-coloured-text
     , stm
     , svg-builder
-    , sydtest-mutation-runtime
+    , sydtest-mutation-runtime >=0.1
     , text
     , transformers
     , vector
