diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,6 @@
-# Unreleased
+# v0.3.2.0
+
+* Added support for GHC 9.4
 
 # v0.3.1.0
 
diff --git a/exe/Preprocessor.hs b/exe/Preprocessor.hs
--- a/exe/Preprocessor.hs
+++ b/exe/Preprocessor.hs
@@ -20,11 +20,15 @@
 module Main where
 
 import qualified Data.Text.IO as Text
+import GHC.IO.Encoding (setLocaleEncoding, utf8)
 import System.Environment (getArgs)
 import Test.Tasty.AutoCollect (processFile)
 
 main :: IO ()
-main =
+main = do
+  -- just to be extra sure we don't run into encoding issues
+  setLocaleEncoding utf8
+
   getArgs >>= \case
     -- https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/phases.html#options-affecting-a-haskell-pre-processor
     [fp, input, output] -> Text.readFile input >>= processFile fp >>= Text.writeFile output
diff --git a/src/Test/Tasty/AutoCollect/ConvertTest.hs b/src/Test/Tasty/AutoCollect/ConvertTest.hs
--- a/src/Test/Tasty/AutoCollect/ConvertTest.hs
+++ b/src/Test/Tasty/AutoCollect/ConvertTest.hs
@@ -30,10 +30,10 @@
   setKeepRawTokenStream
     defaultPlugin
       { pluginRecompile = purePlugin
-      , parsedResultAction = \_ _ modl -> do
+      , parsedResultAction = \_ _ result -> do
           env <- getHscEnv
           names <- liftIO $ loadExternalNames env
-          pure $ transformTestModule names modl
+          pure $ withParsedResultModule result (transformTestModule names)
       }
 
 {- |
diff --git a/src/Test/Tasty/AutoCollect/GHC.hs b/src/Test/Tasty/AutoCollect/GHC.hs
--- a/src/Test/Tasty/AutoCollect/GHC.hs
+++ b/src/Test/Tasty/AutoCollect/GHC.hs
@@ -31,14 +31,11 @@
   mkRdrNameType,
   mkLRdrNameType,
   fromRdrName,
-  thNameToGhcNameIO,
 ) where
 
 import Data.Foldable (foldl')
-import Data.IORef (IORef)
 import Data.List (sortOn)
 import Data.Maybe (fromMaybe, listToMaybe, mapMaybe)
-import qualified Language.Haskell.TH as TH
 
 import Test.Tasty.AutoCollect.GHC.Shim
 
@@ -126,22 +123,3 @@
 
 fromRdrName :: LocatedN RdrName -> String
 fromRdrName = occNameString . rdrNameOcc . unLoc
-
--- https://gitlab.haskell.org/ghc/ghc/-/merge_requests/8492
-thNameToGhcNameIO :: HscEnv -> IORef NameCache -> TH.Name -> IO (Maybe Name)
-thNameToGhcNameIO hscEnv cache name =
-  fmap fst
-    . runCoreM
-      hscEnv{hsc_NC = cache}
-      (unused "cr_rule_base")
-      (strict '.')
-      (unused "cr_module")
-      (strict mempty)
-      (unused "cr_print_unqual")
-      (unused "cr_loc")
-    $ thNameToGhcName name
-  where
-    unused msg = error $ "unexpectedly used: " ++ msg
-
-    -- marks fields that are strict, so we can't use `unused`
-    strict = id
diff --git a/src/Test/Tasty/AutoCollect/GHC/Shim.hs b/src/Test/Tasty/AutoCollect/GHC/Shim.hs
--- a/src/Test/Tasty/AutoCollect/GHC/Shim.hs
+++ b/src/Test/Tasty/AutoCollect/GHC/Shim.hs
@@ -10,4 +10,6 @@
 import Test.Tasty.AutoCollect.GHC.Shim_9_0 as X
 #elif __GLASGOW_HASKELL__ == 902
 import Test.Tasty.AutoCollect.GHC.Shim_9_2 as X
+#elif __GLASGOW_HASKELL__ == 904
+import Test.Tasty.AutoCollect.GHC.Shim_9_4 as X
 #endif
diff --git a/src/Test/Tasty/AutoCollect/GHC/Shim_8_10.hs b/src/Test/Tasty/AutoCollect/GHC/Shim_8_10.hs
--- a/src/Test/Tasty/AutoCollect/GHC/Shim_8_10.hs
+++ b/src/Test/Tasty/AutoCollect/GHC/Shim_8_10.hs
@@ -11,6 +11,7 @@
 
   -- ** Plugin
   setKeepRawTokenStream,
+  withParsedResultModule,
 
   -- ** Annotations
   getExportComments,
@@ -48,6 +49,7 @@
   noAnn,
   hsTypeToHsSigType,
   hsTypeToHsSigWcType,
+  thNameToGhcNameIO,
 ) where
 
 -- Re-exports
@@ -59,9 +61,11 @@
 
 import ApiAnnotation (getAnnotationComments)
 import Data.Foldable (foldl')
+import Data.IORef (IORef)
 import Data.Maybe (mapMaybe)
 import qualified Data.Text as Text
 import qualified GHC.Hs.Utils as GHC (mkMatch)
+import qualified Language.Haskell.TH as TH
 import qualified OccName as NameSpace (tcName, varName)
 import qualified SrcLoc as GHC (srcSpanStart)
 
@@ -77,6 +81,9 @@
         pure $ df `gopt_set` Opt_KeepRawTokenStream
     }
 
+withParsedResultModule :: HsParsedModule -> (HsParsedModule -> HsParsedModule) -> HsParsedModule
+withParsedResultModule = flip ($)
+
 {----- Compat / Annotations -----}
 
 -- | Get the contents of all comments in the given hsmodExports list.
@@ -201,3 +208,22 @@
 
 hsTypeToHsSigWcType :: LHsType GhcPs -> LHsSigWcType GhcPs
 hsTypeToHsSigWcType = mkLHsSigWcType
+
+-- https://gitlab.haskell.org/ghc/ghc/-/merge_requests/8492
+thNameToGhcNameIO :: HscEnv -> IORef NameCache -> TH.Name -> IO (Maybe Name)
+thNameToGhcNameIO hscEnv cache name =
+  fmap fst
+    . runCoreM
+      hscEnv{hsc_NC = cache}
+      (unused "cr_rule_base")
+      (strict '.')
+      (unused "cr_module")
+      (strict mempty)
+      (unused "cr_print_unqual")
+      (unused "cr_loc")
+    $ thNameToGhcName name
+  where
+    unused msg = error $ "unexpectedly used: " ++ msg
+
+    -- marks fields that are strict, so we can't use `unused`
+    strict = id
diff --git a/src/Test/Tasty/AutoCollect/GHC/Shim_9_0.hs b/src/Test/Tasty/AutoCollect/GHC/Shim_9_0.hs
--- a/src/Test/Tasty/AutoCollect/GHC/Shim_9_0.hs
+++ b/src/Test/Tasty/AutoCollect/GHC/Shim_9_0.hs
@@ -10,6 +10,7 @@
 
   -- ** Plugin
   setKeepRawTokenStream,
+  withParsedResultModule,
 
   -- ** Annotations
   getExportComments,
@@ -44,6 +45,7 @@
   noAnn,
   hsTypeToHsSigType,
   hsTypeToHsSigWcType,
+  thNameToGhcNameIO,
 ) where
 
 -- Re-exports
@@ -53,11 +55,13 @@
 import GHC.Plugins as X hiding (getHscEnv, showPpr, srcSpanStart, varName)
 import GHC.Types.Name.Cache as X (NameCache)
 
+import Data.IORef (IORef)
 import qualified Data.Text as Text
 import qualified GHC.Hs.Utils as GHC (mkMatch)
 import GHC.Parser.Annotation (getAnnotationComments)
 import qualified GHC.Types.Name.Occurrence as NameSpace (tcName, varName)
 import qualified GHC.Types.SrcLoc as GHC (srcSpanStart)
+import qualified Language.Haskell.TH as TH
 
 import Test.Tasty.AutoCollect.GHC.Shim_Common
 import Test.Tasty.AutoCollect.Utils.Text
@@ -71,6 +75,9 @@
         pure $ df `gopt_set` Opt_KeepRawTokenStream
     }
 
+withParsedResultModule :: HsParsedModule -> (HsParsedModule -> HsParsedModule) -> HsParsedModule
+withParsedResultModule = flip ($)
+
 {----- Compat / Annotations -----}
 
 -- | Get the contents of all comments in the given hsmodExports list.
@@ -175,3 +182,22 @@
 
 hsTypeToHsSigWcType :: LHsType GhcPs -> LHsSigWcType GhcPs
 hsTypeToHsSigWcType = mkLHsSigWcType
+
+-- https://gitlab.haskell.org/ghc/ghc/-/merge_requests/8492
+thNameToGhcNameIO :: HscEnv -> IORef NameCache -> TH.Name -> IO (Maybe Name)
+thNameToGhcNameIO hscEnv cache name =
+  fmap fst
+    . runCoreM
+      hscEnv{hsc_NC = cache}
+      (unused "cr_rule_base")
+      (strict '.')
+      (unused "cr_module")
+      (strict mempty)
+      (unused "cr_print_unqual")
+      (unused "cr_loc")
+    $ thNameToGhcName name
+  where
+    unused msg = error $ "unexpectedly used: " ++ msg
+
+    -- marks fields that are strict, so we can't use `unused`
+    strict = id
diff --git a/src/Test/Tasty/AutoCollect/GHC/Shim_9_2.hs b/src/Test/Tasty/AutoCollect/GHC/Shim_9_2.hs
--- a/src/Test/Tasty/AutoCollect/GHC/Shim_9_2.hs
+++ b/src/Test/Tasty/AutoCollect/GHC/Shim_9_2.hs
@@ -11,6 +11,7 @@
 
   -- ** Plugin
   setKeepRawTokenStream,
+  withParsedResultModule,
 
   -- ** Annotations
   generatedSrcAnn,
@@ -35,6 +36,9 @@
   mkExplicitList,
   mkExplicitTuple,
   xAppTypeE,
+
+  -- * Backports
+  thNameToGhcNameIO,
 ) where
 
 -- Re-exports
@@ -43,9 +47,11 @@
 import GHC.Plugins as X hiding (AnnBind (..), AnnExpr' (..), getHscEnv, showPpr, srcSpanStart, varName)
 import GHC.Types.Name.Cache as X (NameCache)
 
+import Data.IORef (IORef)
 import qualified Data.Text as Text
 import qualified GHC.Types.Name.Occurrence as NameSpace (tcName, varName)
 import qualified GHC.Types.SrcLoc as GHC (srcSpanStart)
+import qualified Language.Haskell.TH as TH
 
 import Test.Tasty.AutoCollect.GHC.Shim_Common
 import Test.Tasty.AutoCollect.Utils.Text
@@ -62,6 +68,9 @@
             }
     }
 
+withParsedResultModule :: HsParsedModule -> (HsParsedModule -> HsParsedModule) -> HsParsedModule
+withParsedResultModule = flip ($)
+
 {----- Compat / Annotations -----}
 
 -- | Get the contents of all comments in the given hsmodExports list.
@@ -143,3 +152,24 @@
 
 xAppTypeE :: XAppTypeE GhcPs
 xAppTypeE = generatedSrcSpan
+
+{----- Backports -----}
+
+-- https://gitlab.haskell.org/ghc/ghc/-/merge_requests/8492
+thNameToGhcNameIO :: HscEnv -> IORef NameCache -> TH.Name -> IO (Maybe Name)
+thNameToGhcNameIO hscEnv cache name =
+  fmap fst
+    . runCoreM
+      hscEnv{hsc_NC = cache}
+      (unused "cr_rule_base")
+      (strict '.')
+      (unused "cr_module")
+      (strict mempty)
+      (unused "cr_print_unqual")
+      (unused "cr_loc")
+    $ thNameToGhcName name
+  where
+    unused msg = error $ "unexpectedly used: " ++ msg
+
+    -- marks fields that are strict, so we can't use `unused`
+    strict = id
diff --git a/src/Test/Tasty/AutoCollect/GHC/Shim_9_4.hs b/src/Test/Tasty/AutoCollect/GHC/Shim_9_4.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Tasty/AutoCollect/GHC/Shim_9_4.hs
@@ -0,0 +1,166 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Test.Tasty.AutoCollect.GHC.Shim_9_4 (
+  -- * Re-exports
+  module X,
+
+  -- * Compat
+
+  -- ** Plugin
+  setKeepRawTokenStream,
+  withParsedResultModule,
+
+  -- ** Annotations
+  generatedSrcAnn,
+  getExportComments,
+  toSrcAnnA,
+
+  -- ** SrcSpan
+  srcSpanStart,
+
+  -- ** OccName
+  mkOccNameVar,
+  mkOccNameTC,
+
+  -- ** Decl
+  parseDecl,
+
+  -- ** Type
+  parseSigWcType,
+  parseType,
+
+  -- ** Expr
+  mkExplicitList,
+  mkExplicitTuple,
+  xAppTypeE,
+
+  -- * Backports
+  thNameToGhcNameIO,
+) where
+
+-- Re-exports
+import GHC.Driver.Main as X (getHscEnv)
+import GHC.Hs as X hiding (comment, mkHsAppType, mkHsAppTypes)
+import GHC.Plugins as X hiding (
+  AnnBind (..),
+  AnnExpr' (..),
+  getHscEnv,
+  msg,
+  showPpr,
+  srcSpanStart,
+  thNameToGhcNameIO,
+  varName,
+ )
+import GHC.Types.Name.Cache as X (NameCache)
+
+import qualified Data.Text as Text
+import qualified GHC.Data.Strict as Strict
+import qualified GHC.Plugins as GHC (thNameToGhcNameIO)
+import qualified GHC.Types.Name.Occurrence as NameSpace (tcName, varName)
+import qualified GHC.Types.SrcLoc as GHC (srcSpanStart)
+import qualified Language.Haskell.TH as TH
+
+import Test.Tasty.AutoCollect.GHC.Shim_Common
+import Test.Tasty.AutoCollect.Utils.Text
+
+{----- Compat / Plugin -----}
+
+setKeepRawTokenStream :: Plugin -> Plugin
+setKeepRawTokenStream plugin =
+  plugin
+    { driverPlugin = \_ env ->
+        pure
+          env
+            { hsc_dflags = hsc_dflags env `gopt_set` Opt_KeepRawTokenStream
+            }
+    }
+
+withParsedResultModule :: ParsedResult -> (HsParsedModule -> HsParsedModule) -> ParsedResult
+withParsedResultModule result f = result{parsedResultModule = f $ parsedResultModule result}
+
+{----- Compat / Annotations -----}
+
+-- | Get the contents of all comments in the given hsmodExports list.
+getExportComments :: HsParsedModule -> LocatedL [LIE GhcPs] -> [RealLocated String]
+getExportComments _ = map fromLEpaComment . priorComments . epAnnComments . ann . getLoc
+  where
+    fromLEpaComment (L Anchor{anchor} EpaComment{ac_tok}) =
+      L anchor $ (Text.unpack . Text.strip . unwrap) ac_tok
+    unwrap = \case
+      EpaDocComment doc -> Text.pack $ renderHsDocString doc
+      EpaDocOptions s -> Text.pack s
+      EpaLineComment s -> withoutPrefix "--" $ Text.pack s
+      EpaBlockComment s -> withoutPrefix "{-" . withoutSuffix "-}" $ Text.pack s
+      EpaEofComment -> ""
+
+generatedSrcAnn :: SrcAnn ann
+generatedSrcAnn = SrcSpanAnn noAnn generatedSrcSpan
+
+toSrcAnnA :: RealSrcSpan -> SrcSpanAnnA
+toSrcAnnA rss = SrcSpanAnn noAnn (RealSrcSpan rss Strict.Nothing)
+
+{----- Compat / SrcSpan -----}
+
+srcSpanStart :: SrcSpan -> Either String RealSrcLoc
+srcSpanStart ss =
+  case GHC.srcSpanStart ss of
+    RealSrcLoc srcLoc _ -> Right srcLoc
+    UnhelpfulLoc s -> Left $ unpackFS s
+
+{----- Compat / OccName -----}
+
+mkOccNameVar :: String -> OccName
+mkOccNameVar = mkOccName NameSpace.varName
+
+mkOccNameTC :: String -> OccName
+mkOccNameTC = mkOccName NameSpace.tcName
+
+{----- Compat / Decl -----}
+
+parseDecl :: LHsDecl GhcPs -> Maybe ParsedDecl
+parseDecl (L _ decl) =
+  case decl of
+    SigD _ (TypeSig _ names ty) -> Just $ FuncSig names ty
+    ValD _ (FunBind _ name MG{mg_alts = L _ matches} _) ->
+      Just $ FuncDef name $ map (fmap parseFuncSingleDef) matches
+    _ -> Nothing
+  where
+    parseFuncSingleDef Match{m_pats, m_grhss = GRHSs _ bodys whereClause} =
+      FuncSingleDef
+        { funcDefArgs = m_pats
+        , funcDefGuards = map parseFuncGuardedBody bodys
+        , funcDefWhereClause = whereClause
+        }
+    parseFuncGuardedBody (L _ (GRHS _ guards body)) =
+      FuncGuardedBody guards body
+
+{----- Compat / Type -----}
+
+parseSigWcType :: LHsSigWcType GhcPs -> Maybe ParsedType
+parseSigWcType (HsWC _ (L _ (HsSig _ _ ltype))) = parseType ltype
+
+parseType :: LHsType GhcPs -> Maybe ParsedType
+parseType (L _ ty) =
+  case ty of
+    HsTyVar _ flag name -> Just $ TypeVar flag name
+    HsListTy _ t -> TypeList <$> parseType t
+    _ -> Nothing
+
+{----- Compat / Expr -----}
+
+mkExplicitList :: [LHsExpr GhcPs] -> HsExpr GhcPs
+mkExplicitList = ExplicitList noAnn
+
+mkExplicitTuple :: [HsTupArg GhcPs] -> Boxity -> HsExpr GhcPs
+mkExplicitTuple = ExplicitTuple noAnn
+
+xAppTypeE :: XAppTypeE GhcPs
+xAppTypeE = generatedSrcSpan
+
+{----- Backports -----}
+
+thNameToGhcNameIO :: HscEnv -> NameCache -> TH.Name -> IO (Maybe Name)
+thNameToGhcNameIO _ = GHC.thNameToGhcNameIO
diff --git a/src/Test/Tasty/AutoCollect/GHC/Shim_Common.hs b/src/Test/Tasty/AutoCollect/GHC/Shim_Common.hs
--- a/src/Test/Tasty/AutoCollect/GHC/Shim_Common.hs
+++ b/src/Test/Tasty/AutoCollect/GHC/Shim_Common.hs
@@ -19,6 +19,9 @@
 #elif __GLASGOW_HASKELL__ == 902
 import GHC.Types.Basic (PromotionFlag)
 import GHC.Types.Name.Reader (RdrName)
+#elif __GLASGOW_HASKELL__ == 904
+import GHC.Types.Basic (PromotionFlag)
+import GHC.Types.Name.Reader (RdrName)
 #endif
 
 #if __GLASGOW_HASKELL__ < 902
diff --git a/src/Test/Tasty/AutoCollect/Utils/Text.hs b/src/Test/Tasty/AutoCollect/Utils/Text.hs
--- a/src/Test/Tasty/AutoCollect/Utils/Text.hs
+++ b/src/Test/Tasty/AutoCollect/Utils/Text.hs
@@ -3,6 +3,7 @@
 module Test.Tasty.AutoCollect.Utils.Text (
   withoutPrefix,
   withoutSuffix,
+  breakOnEnd,
   listify,
   quoted,
 ) where
@@ -16,6 +17,12 @@
 
 withoutSuffix :: Text -> Text -> Text
 withoutSuffix post s = fromMaybe s $ Text.stripSuffix post s
+
+-- | Same as 'Text.breakOnEnd', but omits the delimiter
+breakOnEnd :: Text -> Text -> (Text, Text)
+breakOnEnd delim s =
+  let (a, b) = Text.breakOnEnd delim s
+   in (fromMaybe a (Text.stripSuffix delim a), b)
 
 -- | Convert a list @["a", "b"]@ to the text @"[\"a\", \"b\"]"@.
 listify :: [Text] -> Text
diff --git a/tasty-autocollect.cabal b/tasty-autocollect.cabal
--- a/tasty-autocollect.cabal
+++ b/tasty-autocollect.cabal
@@ -1,11 +1,11 @@
 cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.34.4.
+-- This file has been generated from package.yaml by hpack version 0.35.0.
 --
 -- see: https://github.com/sol/hpack
 
 name:           tasty-autocollect
-version:        0.3.1.0
+version:        0.3.2.0
 synopsis:       Autocollection of tasty tests.
 description:    Autocollection of tasty tests. See README.md for more details.
 category:       Testing
@@ -68,16 +68,20 @@
     , containers >=0.6.2.1 && <0.7
     , directory >=1.3.6.0 && <2
     , filepath >=1.4.2.1 && <2
-    , ghc >=8.10 && <9.3
+    , ghc >=8.10 && <9.5
     , tasty >=1.4.2.1 && <2
     , tasty-expected-failure >=0.11 && <1
-    , template-haskell >=2.16 && <2.19
+    , template-haskell >=2.16 && <2.20
     , text >=1.2.3.2 && <3
     , transformers >=0.5.6.2 && <1
+  default-language: Haskell2010
   if impl(ghc >= 8.0)
     ghc-options: -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wnoncanonical-monad-instances
   if impl(ghc < 8.8)
     ghc-options: -Wnoncanonical-monadfail-instances
+  if impl(ghc >= 9.4) && impl(ghc < 9.6)
+    other-modules:
+        Test.Tasty.AutoCollect.GHC.Shim_9_4
   if impl(ghc >= 9.2) && impl(ghc < 9.4)
     other-modules:
         Test.Tasty.AutoCollect.GHC.Shim_9_2
@@ -87,7 +91,6 @@
   if impl(ghc >= 8.10) && impl(ghc < 9.0)
     other-modules:
         Test.Tasty.AutoCollect.GHC.Shim_8_10
-  default-language: Haskell2010
 
 executable tasty-autocollect
   main-is: Preprocessor.hs
@@ -100,11 +103,11 @@
       base >=4.14 && <5
     , tasty-autocollect
     , text >=1.2.3.2 && <3
+  default-language: Haskell2010
   if impl(ghc >= 8.0)
     ghc-options: -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wnoncanonical-monad-instances
   if impl(ghc < 8.8)
     ghc-options: -Wnoncanonical-monadfail-instances
-  default-language: Haskell2010
 
 test-suite tasty-autocollect-tests
   type: exitcode-stdio-1.0
@@ -140,9 +143,9 @@
     , temporary
     , text
     , typed-process
+  default-language: Haskell2010
   if impl(ghc >= 8.0)
     ghc-options: -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wnoncanonical-monad-instances
   if impl(ghc < 8.8)
     ghc-options: -Wnoncanonical-monadfail-instances
-  default-language: Haskell2010
   build-tool-depends: tasty-autocollect:tasty-autocollect
diff --git a/test/Test/Tasty/AutoCollect/ConvertTestTest.hs b/test/Test/Tasty/AutoCollect/ConvertTestTest.hs
--- a/test/Test/Tasty/AutoCollect/ConvertTestTest.hs
+++ b/test/Test/Tasty/AutoCollect/ConvertTestTest.hs
@@ -83,11 +83,13 @@
     assertAnyFailure . runTestWith (modifyFile "Test.hs" (map removeExports)) $
       [ "test = testCase \"a test\" $ return ()"
       ]
-  getTestLines stderr @?~ containsStripped (startsWith "Module ‘Test’ does not export")
+  getTestLines stderr @?~ containsStripped (startsWith messagePreGHC94 `orP` startsWith messagePostGHC94)
   where
     removeExports s
       | "module " `Text.isPrefixOf` s = "module Test () where"
       | otherwise = s
+    messagePreGHC94 = "Module ‘Test’ does not export"
+    messagePostGHC94 = "NB: the module ‘Test’ does not export"
 
 test = testCase "test file can omit an explicit export list" $ do
   (stdout, _) <-
diff --git a/test/Test/Tasty/AutoCollect/GenerateMainTest.hs b/test/Test/Tasty/AutoCollect/GenerateMainTest.hs
--- a/test/Test/Tasty/AutoCollect/GenerateMainTest.hs
+++ b/test/Test/Tasty/AutoCollect/GenerateMainTest.hs
@@ -209,7 +209,7 @@
       , "ingredients = myIngredient"
       , "-}"
       ]
-  getTestLines stderr @?~ contains (eq "Ingredient needs to be fully qualified: myIngredient")
+  getTestLines stderr @?~ containsStripped (eq "Ingredient needs to be fully qualified: myIngredient")
 
 test = testCase "allows disabling default tasty ingredients" $ do
   (_, stderr) <-
diff --git a/test/TestUtils/Golden.hs b/test/TestUtils/Golden.hs
--- a/test/TestUtils/Golden.hs
+++ b/test/TestUtils/Golden.hs
@@ -1,4 +1,6 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE OverloadedStrings #-}
 
 module TestUtils.Golden (
   testGolden,
@@ -10,5 +12,19 @@
 import Test.Tasty (TestTree)
 import Test.Tasty.Golden
 
+#if __GLASGOW_HASKELL__ != 904
+import qualified Data.Text as Text
+#endif
+
 testGolden :: String -> FilePath -> IO Text -> TestTree
-testGolden name fp = goldenVsString name ("test/golden/" ++ fp) . fmap (TextL.encodeUtf8 . TextL.fromStrict)
+testGolden name fp = goldenVsString name ("test/golden/" ++ fp) . fmap (TextL.encodeUtf8 . TextL.fromStrict . normalizePgmError)
+
+-- GHC 9.4 added a prefix to pgmError messages. Normalize old versions to show new format.
+normalizePgmError :: Text -> Text
+#if __GLASGOW_HASKELL__ == 904
+normalizePgmError = id
+#else
+normalizePgmError t
+  | "tasty-autocollect failure" `Text.isInfixOf` t =  "\n<no location info>: error:\n    " <> t
+  | otherwise = t
+#endif
diff --git a/test/TestUtils/Integration.hs b/test/TestUtils/Integration.hs
--- a/test/TestUtils/Integration.hs
+++ b/test/TestUtils/Integration.hs
@@ -28,7 +28,6 @@
 
 import Control.Monad (forM_, void)
 import Data.Char (isDigit)
-import Data.Maybe (fromMaybe)
 import Data.Text (Text)
 import qualified Data.Text as Text
 import qualified Data.Text.IO as Text
@@ -37,8 +36,15 @@
 import System.Directory (createDirectoryIfMissing)
 import System.FilePath (takeDirectory, (</>))
 import System.IO.Temp (withSystemTempDirectory)
-import System.Process.Typed (ExitCode (..), proc, readProcess, setWorkingDir)
+import System.Process.Typed (
+  ExitCode (..),
+  proc,
+  readProcess,
+  setWorkingDir,
+ )
 
+import Test.Tasty.AutoCollect.Utils.Text (breakOnEnd)
+
 assertStatus :: (ExitCode -> Bool) -> IO (ExitCode, Text, Text) -> IO (Text, Text)
 assertStatus isExpected testResult = do
   (code, stdout, stderr) <- testResult
@@ -93,30 +99,33 @@
 runghc :: GHCProject -> IO (ExitCode, Text, Text)
 runghc GHCProject{..} =
   withSystemTempDirectory "tasty-autocollect-integration-test" $ \tmpdir -> do
+    -- create files
     forM_ files $ \(fp, contents) -> do
       let testFile = tmpdir </> fp
       createDirectoryIfMissing True (takeDirectory testFile)
       Text.writeFile testFile (Text.unlines contents)
 
+    -- run callback
     preRunCallback tmpdir
 
+    -- run ghc
     let ghcArgs =
           concat
             [ ["-hide-all-packages"]
             , ["-package " <> dep | dep <- dependencies]
+            , ["-package tasty-autocollect"]
             , extraGhcArgs
             ]
-
     (code, stdout, stderr) <-
       readProcess $
         setWorkingDir tmpdir . proc "runghc" . concat $
-          [ ["--"]
-          , map Text.unpack ghcArgs
+          [ "--" : map Text.unpack ghcArgs
           , "--" : entrypoint : map Text.unpack runArgs
           ]
 
-    let decode = TextL.toStrict . TextL.decodeUtf8
     return (code, decode stdout, decode stderr)
+  where
+    decode = TextL.toStrict . TextL.decodeUtf8
 
 {----- Helpers -----}
 
@@ -168,8 +177,3 @@
       , Text.all isDigit b =
           pre
       | otherwise = s
-
-    -- Text.breakOnEnd, but omits the delimiter
-    breakOnEnd delim s =
-      let (a, b) = Text.breakOnEnd delim s
-       in (fromMaybe a (Text.stripSuffix delim a), b)
diff --git a/test/golden/test_args.golden b/test/golden/test_args.golden
--- a/test/golden/test_args.golden
+++ b/test/golden/test_args.golden
@@ -1,4 +1,6 @@
 
+<no location info>: error:
+    
 ******************** tasty-autocollect failure ********************
 test should not be used with arguments (at line 5)
 
diff --git a/test/golden/test_batch_args.golden b/test/golden/test_batch_args.golden
--- a/test/golden/test_batch_args.golden
+++ b/test/golden/test_batch_args.golden
@@ -1,4 +1,6 @@
 
+<no location info>: error:
+    
 ******************** tasty-autocollect failure ********************
 test_batch should not be used with arguments (at line 5)
 
diff --git a/test/golden/test_batch_type.golden b/test/golden/test_batch_type.golden
--- a/test/golden/test_batch_type.golden
+++ b/test/golden/test_batch_type.golden
@@ -1,4 +1,6 @@
 
+<no location info>: error:
+    
 ******************** tasty-autocollect failure ********************
 Expected type: [TestTree]
 Got: TestTree
diff --git a/test/golden/test_prop_bad_arg.golden b/test/golden/test_prop_bad_arg.golden
--- a/test/golden/test_prop_bad_arg.golden
+++ b/test/golden/test_prop_bad_arg.golden
@@ -1,4 +1,6 @@
 
+<no location info>: error:
+    
 ******************** tasty-autocollect failure ********************
 test_prop expected a String for the name of the test.
 Got: 11
diff --git a/test/golden/test_prop_no_args.golden b/test/golden/test_prop_no_args.golden
--- a/test/golden/test_prop_no_args.golden
+++ b/test/golden/test_prop_no_args.golden
@@ -1,4 +1,6 @@
 
+<no location info>: error:
+    
 ******************** tasty-autocollect failure ********************
 test_prop requires at least the name of the test
 
diff --git a/test/golden/test_todo_args.golden b/test/golden/test_todo_args.golden
--- a/test/golden/test_todo_args.golden
+++ b/test/golden/test_todo_args.golden
@@ -1,4 +1,6 @@
 
+<no location info>: error:
+    
 ******************** tasty-autocollect failure ********************
 test_todo should not be used with arguments (at line 5)
 
diff --git a/test/golden/test_todo_type.golden b/test/golden/test_todo_type.golden
--- a/test/golden/test_todo_type.golden
+++ b/test/golden/test_todo_type.golden
@@ -1,4 +1,6 @@
 
+<no location info>: error:
+    
 ******************** tasty-autocollect failure ********************
 Expected type: String
 Got: Int
diff --git a/test/golden/test_type.golden b/test/golden/test_type.golden
--- a/test/golden/test_type.golden
+++ b/test/golden/test_type.golden
@@ -1,4 +1,6 @@
 
+<no location info>: error:
+    
 ******************** tasty-autocollect failure ********************
 Expected type: TestTree
 Got: Int
