diff --git a/packages/hxtorch/package.py b/packages/hxtorch/package.py
index 7be0e3abc399f9a06a7186b39debacd14aa9538f..137eebc6bc4e77d519e199eb6d51cf66d250f958 100644
--- a/packages/hxtorch/package.py
+++ b/packages/hxtorch/package.py
@@ -11,24 +11,25 @@ class Hxtorch(WafPackage):
     """hxtorch --- a PyTorch-based toplevel for the BrainScaleS-2 neuromorphic hardware systems"""
 
     homepage = "https://github.com/electronicvisions/hxtorch"
-    # This repo provides a waf binary used for the build below
+    # This repo provides a custom waf binary used for the build below
     git      = "https://github.com/electronicvisions/pynn-brainscales.git"
 
     version('7.0-a1', branch='waf')
 
-    # PPU compiler dependencies
-    depends_on('oppulance@7.0-a1')
+    # compiler for the BrainScaleS-2 embedded processor ("PPU"); needed for
+    # building/linking, at runtime and for testing
+    depends_on('oppulance@7.0-a1', type=('build', 'link', 'run', 'test'))
 
     # host software dependencies
-    depends_on('bitsery', type=('build', 'link', 'run'))
+    depends_on('bitsery', type=('build', 'link', 'run', 'test'))
     depends_on('binutils+gold+ld+plugins', type=('build', 'link', 'run')) # specialize
-    depends_on('boost@1.69.0: +graph+icu+mpi+numpy+coroutine+context+filesystem+python+serialization+system+thread+program_options cxxstd=17', type=('build', 'link', 'run')) # specialize boost (non-clingo, type=('build', 'link', 'run'))
-    depends_on('cereal', type=('build', 'link', 'run'))
+    depends_on('boost@1.69.0: +graph+icu+mpi+numpy+coroutine+context+filesystem+python+serialization+system+thread+program_options cxxstd=17', type=('build', 'link', 'run', 'test'))
+    depends_on('cereal', type=('build', 'link', 'run', 'test'))
     depends_on('cppcheck', type=('build', 'link', 'run'))
     depends_on('genpybind@ebrains', type=('build', 'link', 'run'))
     depends_on('gflags', type=('build', 'link', 'run'))
     depends_on('googletest@1.11.0:+gmock', type=('build', 'link', 'run')) # variadic templates needed
-    depends_on('inja', type=('build', 'link', 'run')) # template engine for PPU source jit generation
+    depends_on('inja', type=('build', 'link', 'run', 'test')) # template engine for PPU source jit generation
     depends_on('intel-tbb', type=('build', 'link', 'run'))  # ppu gdbserver
     depends_on('libelf', type=('build', 'link', 'run'))
     depends_on('liblockfile', type=('build', 'link', 'run'))
@@ -36,7 +37,7 @@ class Hxtorch(WafPackage):
     depends_on('log4cxx@0.12.1:', type=('build', 'link', 'run'))
     depends_on('pkgconfig', type=('build', 'link', 'run'))
     depends_on('psmisc', type=('run', 'test'))
-    depends_on('python@3.7.0:', type=('build', 'link', 'run')) # BrainScaleS(-2, type=('build', 'link', 'run')) only supports Python >= 3.7
+    depends_on('python@3.7.0:', type=('build', 'link', 'run')) # BrainScaleS-2 only supports Python >= 3.7
     depends_on('py-h5py', type=('build', 'link', 'run')) # PyNN tests need it
     depends_on('py-jax@0.3.25:', type=('build', 'link', 'run'))
     depends_on('py-matplotlib', type=('build', 'link', 'run'))
@@ -48,7 +49,7 @@ class Hxtorch(WafPackage):
     depends_on('py-pycodestyle', type=('build', 'link', 'run'))
     depends_on('py-pyelftools', type=('build', 'link', 'run'))
     depends_on('py-pylint', type=('build', 'link', 'run'))
-    depends_on('py-torch@1.9.1:', type=('build', 'link', 'run'))
+    depends_on('py-torch@1.9.1:', type=('build', 'link', 'run', 'test'))
     depends_on('py-torchvision', type=('run')) # for demos
     depends_on('py-tree-math', type=('build', 'link', 'run'))
     depends_on('py-pyyaml', type=('build', 'link', 'run'))
@@ -128,11 +129,28 @@ class Hxtorch(WafPackage):
         env.set('WAF_CONFIGURE_LD_LIBRARY_PATH', ':'.join(library))
         env.prepend_path('PATH', ':'.join(path))
 
-    def setup_dependent_build_environment(self, env, dependent_spec):
+    def _setup_common_env(self, env):
+        # TODO: use standard install layout for Python modules and extensions'
+        # shared objects => remove then
         env.prepend_path('PYTHONPATH', self.prefix.lib)
 
+        # grenade needs to find some libraries for the JIT-compilation of
+        # programs for BrainScaleS-2's embedded processor.
+        ppu_include_dirs = []
+        ppu_dep_names = ['bitsery', 'boost', 'cereal']
+        for ppu_dep_name in ppu_dep_names:
+            dep = self.spec[ppu_dep_name]
+            dep_include_dirs = set(dep.headers.directories)
+            print('ppu includes (', dep.name, '):', dep_include_dirs, "\n")
+            ppu_include_dirs.extend(list(dep_include_dirs))
+        env.set('C_INCLUDE_PATH', ':'.join(ppu_include_dirs))
+        env.set('CPLUS_INCLUDE_PATH', ':'.join(ppu_include_dirs))
+
+    def setup_dependent_build_environment(self, env, dependent_spec):
+        self._setup_common_env(env)
+
     def setup_run_environment(self, env):
-        env.prepend_path('PYTHONPATH', self.prefix.lib)
+        self._setup_common_env(env)
 
     # override configure step as we perform a project setup first
     def configure(self, spec, prefix):
diff --git a/packages/pynn-brainscales/package.py b/packages/pynn-brainscales/package.py
index 5c7d33f7887cb816567dc619e7a2ac10eacb14d1..2b05efb396b58fa2aeeb10f3dd2a0554f113d915 100644
--- a/packages/pynn-brainscales/package.py
+++ b/packages/pynn-brainscales/package.py
@@ -11,23 +11,25 @@ class PynnBrainscales(WafPackage):
     """PyNN toplevel for the BrainScaleS-2 neuromorphic hardware systems"""
 
     homepage = "https://github.com/electronicvisions/pynn-brainscales"
+    # This repo provides a custom waf binary used for the build below
     git      = "https://github.com/electronicvisions/pynn-brainscales.git"
 
     version('7.0-a1', branch='waf')
 
-    # PPU compiler dependencies
-    depends_on('oppulance@7.0-a1')
+    # compiler for the BrainScaleS-2 embedded processor ("PPU"); needed for
+    # building/linking, at runtime and for testing
+    depends_on('oppulance@7.0-a1', type=('build', 'link', 'run', 'test'))
 
     # host software dependencies
-    depends_on('bitsery', type=('build', 'link', 'run'))
+    depends_on('bitsery', type=('build', 'link', 'run', 'test'))
     depends_on('binutils+gold+ld+plugins', type=('build', 'link', 'run')) # specialize
-    depends_on('boost@1.69.0: +graph+icu+mpi+numpy+coroutine+context+filesystem+python+serialization+system+thread+program_options cxxstd=17', type=('build', 'link', 'run')) # specialize boost (non-clingo, type=('build', 'link', 'run'))
-    depends_on('cereal', type=('build', 'link', 'run'))
+    depends_on('boost@1.69.0: +graph+icu+mpi+numpy+coroutine+context+filesystem+python+serialization+system+thread+program_options cxxstd=17', type=('build', 'link', 'run', 'test'))
+    depends_on('cereal', type=('build', 'link', 'run', 'test'))
     depends_on('cppcheck', type=('build', 'link', 'run'))
     depends_on('genpybind@ebrains', type=('build', 'link', 'run'))
     depends_on('gflags', type=('build', 'link', 'run'))
     depends_on('googletest@1.11.0:+gmock', type=('build', 'link', 'run')) # variadic templates needed
-    depends_on('inja', type=('build', 'link', 'run')) # template engine for PPU source jit generation
+    depends_on('inja', type=('build', 'link', 'run', 'test')) # template engine for PPU source jit generation
     depends_on('intel-tbb', type=('build', 'link', 'run'))  # ppu gdbserver
     depends_on('libelf', type=('build', 'link', 'run'))
     depends_on('liblockfile', type=('build', 'link', 'run'))
@@ -35,7 +37,7 @@ class PynnBrainscales(WafPackage):
     depends_on('log4cxx@0.12.1:', type=('build', 'link', 'run'))
     depends_on('pkgconfig', type=('build', 'link', 'run'))
     depends_on('psmisc', type=('run', 'test'))
-    depends_on('python@3.7.0:', type=('build', 'link', 'run')) # BrainScaleS(-2, type=('build', 'link', 'run')) only supports Python >= 3.7
+    depends_on('python@3.7.0:', type=('build', 'link', 'run')) # BrainScaleS-2 only supports Python >= 3.7
     depends_on('py-deap@1.3.1:', type=('build', 'link', 'run'))
     depends_on('py-h5py', type=('build', 'link', 'run')) # PyNN tests need it
     depends_on('py-matplotlib', type=('build', 'link', 'run'))
@@ -104,11 +106,28 @@ class PynnBrainscales(WafPackage):
         env.set('WAF_CONFIGURE_LD_LIBRARY_PATH', ':'.join(library))
         env.prepend_path('PATH', ':'.join(path))
 
-    def setup_dependent_build_environment(self, env, dependent_spec):
+    def _setup_common_env(self, env):
+        # TODO: use standard install layout for Python modules and extensions'
+        # shared objects => remove then
         env.prepend_path('PYTHONPATH', self.prefix.lib)
 
+        # grenade needs to find some libraries for the JIT-compilation of
+        # programs for BrainScaleS-2's embedded processor.
+        ppu_include_dirs = []
+        ppu_dep_names = ['bitsery', 'boost', 'cereal']
+        for ppu_dep_name in ppu_dep_names:
+            dep = self.spec[ppu_dep_name]
+            dep_include_dirs = set(dep.headers.directories)
+            print('ppu includes (', dep.name, '):', dep_include_dirs, "\n")
+            ppu_include_dirs.extend(list(dep_include_dirs))
+        env.set('C_INCLUDE_PATH', ':'.join(ppu_include_dirs))
+        env.set('CPLUS_INCLUDE_PATH', ':'.join(ppu_include_dirs))
+
+    def setup_dependent_build_environment(self, env, dependent_spec):
+        self._setup_common_env(env)
+
     def setup_run_environment(self, env):
-        env.prepend_path('PYTHONPATH', self.prefix.lib)
+        self._setup_common_env(env)
 
     # override configure step as we perform a project setup first
     def configure(self, spec, prefix):
diff --git a/packages/wf-brainscales2-demos/package.py b/packages/wf-brainscales2-demos/package.py
index 74a3fcf7b34e5a708317fb452e8d4075cbb7710e..69020d3252322c01fad43578fa4c4245f4eb6a9f 100644
--- a/packages/wf-brainscales2-demos/package.py
+++ b/packages/wf-brainscales2-demos/package.py
@@ -4,7 +4,9 @@
 # SPDX-License-Identifier: (Apache-2.0 OR MIT)
 
 
+from glob import glob
 from spack import *
+import os
 
 
 class WfBrainscales2Demos(Package):
@@ -38,19 +40,36 @@ class WfBrainscales2Demos(Package):
         # sanity_check_prefix requires something in the install directory
         mkdirp(prefix + "/.spack_test_results")
 
-    @run_after("install")
-    @on_package_attributes(run_tests=True)
-    def installcheck(self):
+    def _run_notebooks(self):
+        # TODO: remove debug output
         cmd_env = which("env")
         cmd_env()
         # execute notebook and save
         jupyter = Executable("jupyter")
-        jupyter("nbconvert",
-            "--ExecutePreprocessor.kernel_name=python3",
-            "--execute",
-            "--allow-errors",
-            "--to",
-            "notebook",
-            "ts_05-yin_yang.ipynb",
-            "--output",
-            prefix+"/.spack_test_results/ts_05-yin_yang.ipynb")
+        for fn in glob("ts*.ipynb") + glob("tp*.ipynb"):
+            jupyter("nbconvert",
+                "--ExecutePreprocessor.kernel_name=python3",
+                "--execute",
+                "--allow-errors",
+                "--to",
+                "notebook",
+                fn,
+                "--output",
+                prefix + "/.spack_test_results/" + fn)
+
+    def _set_collab_things(self):
+        # enable "EBRAINS lab" mode
+        os.environ["LAB_IMAGE_NAME"] = "EBRAINS"
+        # select "EBRAINS experimental" upstream experiment service
+        os.environ["LAB_KERNEL_NAME"] = "EBRAINS-experimental"
+        os.environ["JUPYTERHUB_USER"] = "spack-test-wf-brainscales2-demos"
+
+    @run_after("install")
+    @on_package_attributes(run_tests=True)
+    def installcheck(self):
+        self._set_collab_things()
+        self._run_notebooks()
+
+    def test_notebooks(self):
+        self._set_collab_things()
+        self._run_notebooks()