diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Revision history for erebos-tester
 
+## 0.2.3 -- 2024-08-10
+
+* Added `network` member to the `node` object
+* Use colors by default only on terminal, add `--color`/`--no-color` options to select manually.
+* Accept module name declaration
+* Report multiple parsing errors in single pass
+
 ## 0.2.2 -- 2024-05-17
 
 * Fix unshare failing with newer compilers
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -160,7 +160,7 @@
 ```
 let str = "."
 let re1 = /./
-let re2 = "$str$re1" # match '.' followed by any character
+let re2 = /$str$re1/ # match '.' followed by any character
 ```
 
 #### boolean
@@ -173,19 +173,25 @@
 
 #### node
 
-Represents network node, created by `node` command and used by `spawn` or network configuration commands.
+Represents network node, created by `node` command or implicitly by `spawn`,
+and used by `spawn` or network configuration commands.
 
 Members:
 
-`ip`: string representation of node's IP address.
+`ip`
+: String representation of node's IP address.
 
+`network`
+: The network which the node belogs to.
+
 #### process
 
 Represents running process. Created by `spawn`, used by `send` and `expect` commands.
 
 Members:
 
-`node`: node on which the process is running
+`node`
+: Node on which the process is running.
 
 #### list
 
diff --git a/erebos-tester.cabal b/erebos-tester.cabal
--- a/erebos-tester.cabal
+++ b/erebos-tester.cabal
@@ -1,7 +1,7 @@
 cabal-version:       3.0
 
 name:                erebos-tester
-version:             0.2.2
+version:             0.2.3
 synopsis:            Test framework with virtual network using Linux namespaces
 description:
     This framework is intended mainly for networking libraries/applications and
@@ -87,6 +87,7 @@
                        Run
                        Run.Monad
                        Test
+                       Test.Builtins
                        Util
                        Version
                        Version.Git
diff --git a/src/Main.hs b/src/Main.hs
--- a/src/Main.hs
+++ b/src/Main.hs
@@ -14,6 +14,8 @@
 import System.FilePath
 import System.FilePath.Glob
 import System.IO
+import System.Posix.Terminal
+import System.Posix.Types
 
 import Config
 import Output
@@ -28,6 +30,7 @@
     { optTest :: TestOptions
     , optRepeat :: Int
     , optVerbose :: Bool
+    , optColor :: Maybe Bool
     , optShowHelp :: Bool
     , optShowVersion :: Bool
     }
@@ -37,6 +40,7 @@
     { optTest = defaultTestOptions
     , optRepeat = 1
     , optVerbose = False
+    , optColor = Nothing
     , optShowHelp = False
     , optShowVersion = False
     }
@@ -52,6 +56,12 @@
     , Option ['v'] ["verbose"]
         (NoArg (\opts -> opts { optVerbose = True }))
         "show output of processes and successful tests"
+    , Option [] [ "color" ]
+        (NoArg (\opts -> opts { optColor = Just True }))
+        "always use colors for output (default when stdout is tty)"
+    , Option [] [ "no-color" ]
+        (NoArg (\opts -> opts { optColor = Just False }))
+        "never use colors for output (default when stdout is not a tty)"
     , Option ['t'] ["timeout"]
         (ReqArg (\str -> to $ \opts -> case readMaybe str of
                                             Just timeout -> opts { optTimeout = timeout }
@@ -133,13 +143,16 @@
 
     when (null files) $ fail $ "No test files"
 
-    out <- startOutput $ optVerbose opts
+    useColor <- case optColor opts of
+        Just use -> return use
+        Nothing -> queryTerminal (Fd 1)
+    out <- startOutput (optVerbose opts) useColor
 
     tests <- forM files $ \(path, mbTestName) -> do
-        fileTests <- parseTestFile path
+        Module { .. } <- parseTestFile path
         return $ case mbTestName of
-            Nothing -> fileTests
-            Just name -> filter ((==name) . testName) fileTests
+            Nothing -> moduleTests
+            Just name -> filter ((==name) . testName) moduleTests
 
     ok <- allM (runTest out $ optTest opts) $
         concat $ replicate (optRepeat opts) $ concat tests
diff --git a/src/Network.hs b/src/Network.hs
--- a/src/Network.hs
+++ b/src/Network.hs
@@ -110,6 +110,7 @@
 
     recordMembers = map (first T.pack)
         [ ("ip", RecordSelector $ textIpAddress . nodeIp)
+        , ("network", RecordSelector $ nodeNetwork)
         ]
 
 
diff --git a/src/Output.hs b/src/Output.hs
--- a/src/Output.hs
+++ b/src/Output.hs
@@ -27,6 +27,7 @@
 
 data OutputConfig = OutputConfig
     { outVerbose :: Bool
+    , outUseColor :: Bool
     }
 
 data OutputState = OutputState
@@ -50,10 +51,10 @@
 instance MonadIO m => MonadOutput (ReaderT Output m) where
     getOutput = ask
 
-startOutput :: Bool -> IO Output
-startOutput verbose = Output
+startOutput :: Bool -> Bool -> IO Output
+startOutput outVerbose outUseColor = Output
     <$> newMVar OutputState { outPrint = TL.putStrLn, outHistory = emptyHistory }
-    <*> pure OutputConfig { outVerbose = verbose }
+    <*> pure OutputConfig { .. }
 
 outColor :: OutputType -> Text
 outColor OutputChildStdout = T.pack "0"
@@ -97,11 +98,15 @@
 outLine otype prompt line = ioWithOutput $ \out ->
     when (outVerbose (outConfig out) || printWhenQuiet otype) $ do
         withMVar (outState out) $ \st -> do
-            outPrint st $ TL.fromChunks
-                [ T.pack "\ESC[", outColor otype, T.pack "m"
-                , maybe "" (<> outSign otype <> outArr otype <> " ") prompt
-                , line
-                , T.pack "\ESC[0m"
+            outPrint st $ TL.fromChunks $ concat
+                [ if outUseColor (outConfig out)
+                    then [ T.pack "\ESC[", outColor otype, T.pack "m" ]
+                    else []
+                , [ maybe "" (<> outSign otype <> outArr otype <> " ") prompt ]
+                , [ line ]
+                , if outUseColor (outConfig out)
+                    then [ T.pack "\ESC[0m" ]
+                    else []
                 ]
 
 outPromptGetLine :: MonadOutput m => Text -> m (Maybe Text)
diff --git a/src/Parser.hs b/src/Parser.hs
--- a/src/Parser.hs
+++ b/src/Parser.hs
@@ -5,38 +5,69 @@
 ) where
 
 import Control.Monad.State
+import Control.Monad.Writer
 
-import qualified Data.Text.Lazy as TL
-import qualified Data.Text.Lazy.IO as TL
+import Data.Maybe
+import Data.Set qualified as S
+import Data.Text qualified as T
+import Data.Text.Lazy qualified as TL
+import Data.Text.Lazy.IO qualified as TL
 
 import Text.Megaparsec hiding (State)
+import Text.Megaparsec.Char
 
+import System.Directory
 import System.Exit
+import System.FilePath
 
 import Parser.Core
+import Parser.Expr
 import Parser.Statement
 import Test
+import Test.Builtins
 
-parseTestDefinition :: TestParser Test
-parseTestDefinition = label "test definition" $ toplevel $ do
+parseTestDefinition :: TestParser ()
+parseTestDefinition = label "test definition" $ toplevel ToplevelTest $ do
     block (\name steps -> return $ Test name $ concat steps) header testStep
     where header = do
               wsymbol "test"
               lexeme $ TL.toStrict <$> takeWhileP (Just "test name") (/=':')
 
-parseTestDefinitions :: TestParser [Test]
-parseTestDefinitions = do
-    tests <- many parseTestDefinition
+parseTestModule :: FilePath -> TestParser Module
+parseTestModule absPath = do
+    moduleName <- choice
+        [ label "module declaration" $ do
+            wsymbol "module"
+            off <- stateOffset <$> getParserState
+            x <- identifier
+            name <- (x:) <$> many (symbol "." >> identifier)
+            when (or (zipWith (/=) (reverse name) (reverse $ map T.pack $ splitDirectories $ dropExtension $ absPath))) $ do
+                registerParseError $ FancyError off $ S.singleton $ ErrorFail $ T.unpack $
+                    "module name does not match file path"
+            eol >> scn
+            return name
+        , do
+            return $ [ T.pack $ takeBaseName absPath ]
+        ]
+    (_, toplevels) <- listen $ many $ choice
+        [ parseTestDefinition
+        ]
+    let moduleTests = catMaybes $ map (\case ToplevelTest x -> Just x; {- _ -> Nothing -}) toplevels
     eof
-    return tests
+    return Module { .. }
 
-parseTestFile :: FilePath -> IO [Test]
+parseTestFile :: FilePath -> IO Module
 parseTestFile path = do
     content <- TL.readFile path
+    absPath <- makeAbsolute path
     let initState = TestParserState
-            { testVars = []
+            { testVars = concat
+                [ map (fmap someVarValueType) builtins
+                ]
             , testContext = SomeExpr RootNetwork
             }
-    case evalState (runParserT parseTestDefinitions path content) initState of
+        (res, _) = flip evalState initState $ runWriterT $ runParserT (parseTestModule absPath) path content
+
+    case res of
          Left err -> putStr (errorBundlePretty err) >> exitFailure
-         Right tests -> return tests
+         Right testModule -> return testModule
diff --git a/src/Parser/Core.hs b/src/Parser/Core.hs
--- a/src/Parser/Core.hs
+++ b/src/Parser/Core.hs
@@ -2,6 +2,7 @@
 
 import Control.Monad
 import Control.Monad.State
+import Control.Monad.Writer
 
 import Data.Text (Text)
 import qualified Data.Text.Lazy as TL
@@ -15,18 +16,18 @@
 import Network ()
 import Test
 
-type TestParser = ParsecT Void TestStream (State TestParserState)
+type TestParser = ParsecT Void TestStream (WriterT [ Toplevel ] (State TestParserState))
 
 type TestStream = TL.Text
 
+data Toplevel
+    = ToplevelTest Test
+
 data TestParserState = TestParserState
     { testVars :: [(VarName, SomeExprType)]
     , testContext :: SomeExpr
     }
 
-data SomeExpr = forall a. ExprType a => SomeExpr (Expr a)
-data SomeExprType = forall a. ExprType a => SomeExprType (Proxy a)
-
 someEmptyVar :: SomeExprType -> SomeVarValue
 someEmptyVar (SomeExprType (Proxy :: Proxy a)) = SomeVarValue $ emptyVarValue @a
 
@@ -68,8 +69,8 @@
     put s
     return x
 
-toplevel :: TestParser a -> TestParser a
-toplevel = L.nonIndented scn
+toplevel :: (a -> Toplevel) -> TestParser a -> TestParser ()
+toplevel f = tell . (: []) . f <=< L.nonIndented scn
 
 block :: (a -> [b] -> TestParser c) -> TestParser a -> TestParser b -> TestParser c
 block merge header item = L.indentBlock scn $ do
diff --git a/src/Parser/Expr.hs b/src/Parser/Expr.hs
--- a/src/Parser/Expr.hs
+++ b/src/Parser/Expr.hs
@@ -1,4 +1,6 @@
 module Parser.Expr (
+    identifier,
+
     varName,
     newVarName,
     addVarName,
@@ -46,7 +48,7 @@
 addVarName :: forall a. ExprType a => Int -> TypedVarName a -> TestParser ()
 addVarName off (TypedVarName name) = do
     gets (lookup name . testVars) >>= \case
-        Just _ -> parseError $ FancyError off $ S.singleton $ ErrorFail $ T.unpack $
+        Just _ -> registerParseError $ FancyError off $ S.singleton $ ErrorFail $ T.unpack $
             T.pack "variable '" <> textVarName name <> T.pack "' already exists"
         Nothing -> return ()
     modify $ \s -> s { testVars = (name, SomeExprType @a Proxy) : testVars s }
@@ -65,8 +67,10 @@
 stringExpansion tname conv = do
     off <- stateOffset <$> getParserState
     SomeExpr e <- someExpansion
-    let err = parseError $ FancyError off $ S.singleton $ ErrorFail $ T.unpack $ T.concat
-            [ tname, T.pack " expansion not defined for '", textExprType e, T.pack "'" ]
+    let err = do
+            registerParseError $ FancyError off $ S.singleton $ ErrorFail $ T.unpack $ T.concat
+                [ tname, T.pack " expansion not defined for '", textExprType e, T.pack "'" ]
+            return $ Pure emptyVarValue
 
     maybe err return $ listToMaybe $ catMaybes $ conv e
 
@@ -310,6 +314,8 @@
 typedExpr = do
     off <- stateOffset <$> getParserState
     SomeExpr e <- someExpr
-    let err = parseError $ FancyError off $ S.singleton $ ErrorFail $ T.unpack $ T.concat
-            [ T.pack "expected '", textExprType @a Proxy, T.pack "', expression has type '", textExprType e, T.pack "'" ]
+    let err = do
+            registerParseError $ FancyError off $ S.singleton $ ErrorFail $ T.unpack $ T.concat
+                [ T.pack "expected '", textExprType @a Proxy, T.pack "', expression has type '", textExprType e, T.pack "'" ]
+            return $ Pure emptyVarValue
     maybe err return $ cast e
diff --git a/src/Parser/Statement.hs b/src/Parser/Statement.hs
--- a/src/Parser/Statement.hs
+++ b/src/Parser/Statement.hs
@@ -75,6 +75,11 @@
         body <- testBlock indent
         return [For line tname (unpack <$> e) body]
 
+exprStatement :: TestParser [ TestStep ]
+exprStatement  = do
+    expr <- typedExpr
+    return [ ExprStatement expr ]
+
 class (Typeable a, Typeable (ParamRep a)) => ParamType a where
     type ParamRep a :: Type
     type ParamRep a = a
@@ -116,7 +121,13 @@
 
 instance (ParamType a, ParamType b) => ParamType (Either a b) where
     type ParamRep (Either a b) = Either (ParamRep a) (ParamRep b)
-    parseParam _ = try (Left <$> parseParam @a Proxy) <|> (Right <$> parseParam @b Proxy)
+    parseParam _ = try' (Left <$> parseParam @a Proxy) <|> (Right <$> parseParam @b Proxy)
+      where
+        try' act = try $ do
+            x <- act
+            (stateParseErrors <$> getParserState) >>= \case
+                [] -> return x
+                (_ : _) -> fail ""
     showParamType _ = showParamType @a Proxy ++ " or " ++ showParamType @b Proxy
     paramFromSomeExpr _ se = (Left <$> paramFromSomeExpr @a Proxy se) <|> (Right <$> paramFromSomeExpr @b Proxy se)
 
@@ -255,7 +266,7 @@
     notAllowed <- flip allM expected $ \case
         SomeExprType (Proxy :: Proxy a) | Just (Refl :: ctxe :~: a) <- eqT -> return False
         _ -> return True
-    when notAllowed $ parseError $ FancyError off $ S.singleton $ ErrorFail $ T.unpack $
+    when notAllowed $ registerParseError $ FancyError off $ S.singleton $ ErrorFail $ T.unpack $
         "expected " <> T.intercalate ", " (map (("'"<>) . (<>"'") . textSomeExprType) expected) <> ", expression has type '" <> textExprType @ctxe Proxy <> "'"
 
     symbol ":"
@@ -329,11 +340,6 @@
     <*> innerBlock
 
 
-testWait :: TestParser [TestStep]
-testWait = do
-    wsymbol "wait"
-    return [Wait]
-
 testBlock :: Pos -> TestParser [TestStep]
 testBlock indent = concat <$> go
   where
@@ -363,5 +369,5 @@
     , testDisconnectNodes
     , testDisconnectUpstream
     , testPacketLoss
-    , testWait
+    , exprStatement
     ]
diff --git a/src/Run.hs b/src/Run.hs
--- a/src/Run.hs
+++ b/src/Run.hs
@@ -32,6 +32,7 @@
 import Process
 import Run.Monad
 import Test
+import Test.Builtins
 
 runTest :: Output -> TestOptions -> Test -> IO Bool
 runTest out opts test = do
@@ -60,7 +61,7 @@
             }
         tstate = TestState
             { tsNetwork = error "network not initialized"
-            , tsVars = []
+            , tsVars = builtins
             , tsNodePacketLoss = M.empty
             , tsDisconnectedUp = S.empty
             , tsDisconnectedBridge = S.empty
@@ -120,6 +121,10 @@
         value <- eval expr
         forM_ value $ \i -> do
             withVar name i $ evalSteps inner
+
+    ExprStatement expr -> do
+        TestBlock steps <- eval expr
+        evalSteps steps
 
     Subnet name@(TypedVarName vname) parentExpr inner -> do
         parent <- eval parentExpr
diff --git a/src/Test.hs b/src/Test.hs
--- a/src/Test.hs
+++ b/src/Test.hs
@@ -1,12 +1,14 @@
 module Test (
+    Module(..),
     Test(..),
     TestStep(..),
+    TestBlock(..),
     SourceLine(..),
 
     MonadEval(..),
     VarName(..), TypedVarName(..), textVarName, unpackVarName,
-    ExprType(..),
-    SomeVarValue(..), fromSomeVarValue, textSomeVarValue,
+    ExprType(..), SomeExpr(..), SomeExprType(..), someExprType,
+    SomeVarValue(..), fromSomeVarValue, textSomeVarValue, someVarValueType,
     RecordSelector(..),
     ExprListUnpacker(..),
     ExprEnumerator(..),
@@ -30,13 +32,21 @@
 import {-# SOURCE #-} Process
 import Util
 
+data Module = Module
+    { moduleName :: [ Text ]
+    , moduleTests :: [ Test ]
+    }
+
 data Test = Test
     { testName :: Text
     , testSteps :: [TestStep]
     }
 
+newtype TestBlock = TestBlock [ TestStep ]
+
 data TestStep = forall a. ExprType a => Let SourceLine (TypedVarName a) (Expr a) [TestStep]
               | forall a. ExprType a => For SourceLine (TypedVarName a) (Expr [a]) [TestStep]
+              | ExprStatement (Expr TestBlock)
               | Subnet (TypedVarName Network) (Expr Network) [TestStep]
               | DeclNode (TypedVarName Node) (Expr Network) [TestStep]
               | Spawn (TypedVarName Process) (Either (Expr Network) (Expr Node)) [TestStep]
@@ -120,16 +130,34 @@
 
     exprListUnpacker _ = Just $ ExprListUnpacker id (const Proxy)
 
-data SomeVarValue = forall a. ExprType a => SomeVarValue a
+instance ExprType TestBlock where
+    textExprType _ = "test block"
+    textExprValue _ = "<test block>"
+    emptyVarValue = TestBlock []
 
-data RecordSelector a = forall b. ExprType b => RecordSelector (a -> b)
 
+data SomeExpr = forall a. ExprType a => SomeExpr (Expr a)
+
+data SomeExprType = forall a. ExprType a => SomeExprType (Proxy a)
+
+someExprType :: SomeExpr -> SomeExprType
+someExprType (SomeExpr (_ :: Expr a)) = SomeExprType (Proxy @a)
+
+
+data SomeVarValue = forall a. ExprType a => SomeVarValue a
+
 fromSomeVarValue :: forall a m. (ExprType a, MonadFail m) => VarName -> SomeVarValue -> m a
 fromSomeVarValue name (SomeVarValue value) = maybe (fail err) return $ cast value
   where err = T.unpack $ T.concat [ T.pack "expected ", textExprType @a Proxy, T.pack ", but variable '", textVarName name, T.pack "' has type ", textExprType (Just value) ]
 
 textSomeVarValue :: SomeVarValue -> Text
 textSomeVarValue (SomeVarValue value) = textExprValue value
+
+someVarValueType :: SomeVarValue -> SomeExprType
+someVarValueType (SomeVarValue (_ :: a)) = SomeExprType (Proxy @a)
+
+
+data RecordSelector a = forall b. ExprType b => RecordSelector (a -> b)
 
 data ExprListUnpacker a = forall e. ExprType e => ExprListUnpacker (a -> [e]) (Proxy a -> Proxy e)
 
diff --git a/src/Test/Builtins.hs b/src/Test/Builtins.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Builtins.hs
@@ -0,0 +1,13 @@
+module Test.Builtins (
+    builtins,
+) where
+
+import Test
+
+builtins :: [ ( VarName, SomeVarValue ) ]
+builtins =
+    [ ( VarName "wait", SomeVarValue builtinWait )
+    ]
+
+builtinWait :: TestBlock
+builtinWait = TestBlock [ Wait ]
