diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 42fb02cc12b39aba9f95c2f4188ade4351d462a6..8e4218c12ab0e8c3b9f93eb148e2f48a65ca1afa 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,5 +1,6 @@
 stages:
   - build
+  - test
 
 variables:
   BUILD_ENV_DOCKER_IMAGE: docker-registry.ebrains.eu/tc/ebrains-spack-build-env/okd:okd_23.06
@@ -59,7 +60,7 @@ deploy-int-release-dev-cscs:
     RELEASE_NAME: EBRAINS-test
   resource_group: shared-NFS-mount-dev-cscs
   rules:
-    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PROJECT_NAMESPACE =~ /technical-coordination/
+    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PROJECT_NAMESPACE =~ /technical-coordination/ && $CI_PIPELINE_SOURCE != "schedule"
 
 # Deploy the production release of tools (manual pipeline)
 # deploy on the production environment of the okd prod cluster at CSCS
@@ -113,11 +114,8 @@ deploy-exp-release-dev-cscs:
     SPACK_ENV: experimental
     RELEASE_NAME: EBRAINS-experimental
   resource_group: shared-NFS-mount-dev-cscs
-  only:
-    refs:
-      - schedules
-    variables:
-      - $DEPLOYMENT == "dev"
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "schedule"  && $DEPLOYMENT == "dev"
 
 # Deploy the experimental release of tools (sheduled pipeline)
 # once a week from latest working version of integration release 
@@ -138,11 +136,8 @@ deploy-exp-release-prod-cscs:
   tags:             # this is just to ensure that the two jobs will run on different runners
     - read-write    # to avoid issues with common environment variables
     - shell-runner
-  only:
-    refs:
-      - schedules
-    variables:
-      - $DEPLOYMENT == "prod"
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "schedule"  && $DEPLOYMENT == "prod"
 
 # Deploy the experimental release of tools (sheduled pipeline)
 # once a week from latest working version of integration release 
@@ -163,11 +158,8 @@ deploy-exp-release-prod-jsc:
   tags:             # this is just to ensure that the two jobs will run on different runners
     - read-only     # to avoid issues with common environment variables
     - shell-runner
-  only:
-    refs:
-      - schedules
-    variables:
-      - $DEPLOYMENT == "prod"
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "schedule"  && $DEPLOYMENT == "prod"
 
 build-spack-env-on-runner:
   stage: build
@@ -180,6 +172,10 @@ build-spack-env-on-runner:
   script:
     # run installation script
     - . install_spack_env.sh $CI_PROJECT_DIR $SPACK_VERSION $CI_PROJECT_DIR $SPACK_DEV_ENV $SPACK_PATH_GITLAB
+    # re-activate envionment and run tests
+    - spack env activate $SPACK_DEV_ENV
+    # TODO: run all tests when test dependency issue is fixed
+    # - spack test run -x wf-brainscales2-demos wf-multi-area-model
   after_script:
     - mkdir -p $CI_PROJECT_DIR/spack_logs/installed $CI_PROJECT_DIR/spack_logs/not_installed
       # for succesfully installed packages: keep the spack logs for any package modified during this CI job
@@ -187,14 +183,15 @@ build-spack-env-on-runner:
     - if cd $PKG_DIR; then find . \( -name ".spack" -o -name ".build" -o -name ".spack_test_results" \) -exec cp -r --parents "{}" $CI_PROJECT_DIR/spack_logs/installed \;; fi
       # for not succesfully installed packages: also keep the spack logs for any packages that failed
     - if cd /tmp/$(whoami)/spack-stage/; then find . -maxdepth 2 -name "*.txt" -exec cp --parents "{}" $CI_PROJECT_DIR/spack_logs/not_installed \;; fi
+    # - if [ -d /tmp/spack_tests ]; then mv /tmp/spack_tests $CI_PROJECT_DIR; fi
   artifacts:
     paths:
       - spack_logs
+      # - spack_tests
     when: always
   timeout: 2 days
-  except:
-    variables:
-      - $CI_PIPELINE_SOURCE == "schedule"
+  rules:
+    - if: $CI_PIPELINE_SOURCE != "schedule" && $CI_PIPELINE_SOURCE != "merge_request_event"
 
 sync-gitlab-spack-instance:
   stage: build
@@ -203,7 +200,7 @@ sync-gitlab-spack-instance:
     - read-write
   image: $GITLAB_BUILD_ENV_DOCKER_IMAGE
   variables:
-    SPACK_NFS_ENV: ebrains-runner-build
+    SPACK_NFS_ENV: $CI_COMMIT_BRANCH
     SPACK_REPO_PATH: $SPACK_PATH_GITLAB/ebrains-spack-builds
   script:
     # get latest state of EBRAINS repo
@@ -223,5 +220,31 @@ sync-gitlab-spack-instance:
       - spack_logs
     when: always
   rules:
-    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PROJECT_NAMESPACE =~ /technical-coordination/
+    - if: ($CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_BRANCH == "experimental_rel" || $CI_COMMIT_BRANCH =~ /^ebrains/) && $CI_PROJECT_NAMESPACE =~ /technical-coordination/ && $CI_PIPELINE_SOURCE != "schedule"
       when: manual
+
+test-gitlab-spack-instance:
+  stage: test
+  tags:
+    - docker-runner
+    - read-write
+  image: $GITLAB_BUILD_ENV_DOCKER_IMAGE
+  variables:
+    SPACK_NFS_ENV: $CI_COMMIT_BRANCH
+    SPACK_REPO_PATH: $SPACK_PATH_GITLAB/ebrains-spack-builds
+    SPACK_USER_CACHE_PATH: $SPACK_PATH_GITLAB/spack/.spack
+    SPACK_USER_CONFIG_PATH: $SPACK_PATH_GITLAB/spack/.spack
+  script:
+    - source $SPACK_PATH_GITLAB/spack/share/spack/setup-env.sh
+    - spack env activate $SPACK_NFS_ENV
+    # TODO: run all tests when test dependency issue is fixed
+    - spack test run -x wf-brainscales2-demos wf-multi-area-model
+  after_script:
+    - if [ -d /tmp/spack_tests ]; then mv /tmp/spack_tests $CI_PROJECT_DIR; fi
+  artifacts:
+    paths:
+      - spack_tests
+    when: always
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "schedule" && $TEST_DEPLOYMENT == "true"
+
diff --git a/packages/biobb-gromacs/package.py b/packages/biobb-gromacs/package.py
index f6003f6d2aa2762afd62ad2d9bbc6090ac334a13..72d3ce3fc0c6e4fb79f473664ecf02f66e1eb444 100644
--- a/packages/biobb-gromacs/package.py
+++ b/packages/biobb-gromacs/package.py
@@ -23,7 +23,7 @@ class BiobbGromacs(PythonPackage):
 
     # Patching to enable python 3.10 (not official, might not be stable)
     def patch(self):
-        filter_file("    python_requires='>=3.7,<3.10',", "    python_requires='>=3.7,<3.11',", "setup.py")
+        filter_file("    python_requires='>=3.7,<=3.10',", "    python_requires='>=3.7,<3.11',", "setup.py")
 
     # Test
     @run_after('install')
diff --git a/packages/biobb-model/package.py b/packages/biobb-model/package.py
index 8e0147d86b7777e9bef34d5f99a1709b28ac49b3..024c3d3a95bd9076ac42970a155139229ecdb464 100644
--- a/packages/biobb-model/package.py
+++ b/packages/biobb-model/package.py
@@ -23,7 +23,7 @@ class BiobbModel(PythonPackage):
 
     # Patching to enable python 3.10 (not official, might not be stable)
     def patch(self):
-        filter_file("    python_requires='>=3.7,<3.10',", "    python_requires='>=3.7,<3.11',", "setup.py")
+        filter_file("    python_requires='>=3.7,<=3.10',", "    python_requires='>=3.7,<3.11',", "setup.py")
         filter_file(
             "'Programming Language :: Python :: 3.9'",
             "'Programming Language :: Python :: 3.9',\r\n        "
diff --git a/packages/biobb-structure-utils/package.py b/packages/biobb-structure-utils/package.py
index ab132e9721965d0645e535387ff80782dc19f2e3..0c394b3630f421c9d5c2abb52d131624a1873d9d 100644
--- a/packages/biobb-structure-utils/package.py
+++ b/packages/biobb-structure-utils/package.py
@@ -23,7 +23,7 @@ class BiobbStructureUtils(PythonPackage):
 
     # Patching to enable python 3.10 (not official, might not be stable)
     def patch(self):
-        filter_file("    python_requires='>=3.7,<3.10',", "    python_requires='>=3.7,<3.11',", "setup.py")
+        filter_file("    python_requires='>=3.7,<=3.10',", "    python_requires='>=3.7,<3.11',", "setup.py")
         filter_file(
             "'Programming Language :: Python :: 3.9'",
             "'Programming Language :: Python :: 3.9',\r\n        "
diff --git a/packages/hxtorch/package.py b/packages/hxtorch/package.py
index 28aec2442e5cd0415be40a212bd0c247b2de7af6..c4991029b69d1d1262b06201a0d7c91c66988e18 100644
--- a/packages/hxtorch/package.py
+++ b/packages/hxtorch/package.py
@@ -153,6 +153,9 @@ class Hxtorch(WafPackage):
     def setup_run_environment(self, env):
         self._setup_common_env(env)
 
+    def setup_dependent_run_environment(self, env, dependent_spec):
+        self._setup_common_env(env)
+
     # override configure step as we perform a project setup first
     def configure(self, spec, prefix):
         """Setup and configure the project."""
diff --git a/packages/nest/nest-simulator-3.6-p1-CxxRealPath.patch b/packages/nest/nest-simulator-3.6-p1-CxxRealPath.patch
new file mode 100644
index 0000000000000000000000000000000000000000..db06851a1d56456c7d62e55cae24cf6fab54df19
--- /dev/null
+++ b/packages/nest/nest-simulator-3.6-p1-CxxRealPath.patch
@@ -0,0 +1,13 @@
+diff --git a/bin/nest-config.in b/bin/nest-config.in
+index a54ae0352..43bff5286 100755
+--- a/bin/nest-config.in
++++ b/bin/nest-config.in
+@@ -71,7 +71,7 @@
+         echo "-L$prefix/@CMAKE_INSTALL_LIBDIR@/nest @MODULE_LINK_LIBS@"
+         ;;
+     --compiler)
+-        echo "@CMAKE_CXX_COMPILER@"
++        echo "@SPACK_CXX_COMPILER@"
+         ;;
+     --compiler-name)
+         echo "@CMAKE_CXX_COMPILER_ID@"
diff --git a/packages/nest/package.py b/packages/nest/package.py
index 6cf3ccb381289c5f27356e74a1ff95cf52893701..24d7ca2de8bb866159298b0ab29219d939cb5a98 100644
--- a/packages/nest/package.py
+++ b/packages/nest/package.py
@@ -29,6 +29,7 @@ class Nest(CMakePackage):
 
     version('master', branch='master')
     version('3.6',    sha256='68d6b11791e1284dc94fef35d84c08dd7a11322c0f1e1fc9b39c5e6882284922')
+    patch('nest-simulator-3.6-p1-CxxRealPath.patch', when='@3.6')
     version('3.5',    sha256='3cdf5720854a4d8a7d359f9de9d2fb3619a0be2e36932028d6940360741547bd')
     version('3.4',    sha256='c56699111f899045ba48e55e87d14eca8763b48ebbb3648beee701a36aa3af20')
     version('3.3',    sha256='179462b966cc61f5785d2fee770bc36f86745598ace9cd97dd620622b62043ed')
@@ -159,7 +160,10 @@ class Nest(CMakePackage):
         make("install")
 
     def cmake_args(self):
-        args = ["-DCMAKE_INSTALL_LIBDIR=lib"]
+        args = [
+            "-DCMAKE_INSTALL_LIBDIR=lib",
+            "-DSPACK_CXX_COMPILER=" + self.compiler.cxx
+            ]
 
         for flag in "boost mpi openmp optimize".split():
             if '+' + flag in self.spec:
diff --git a/packages/py-astropy/package.py b/packages/py-astropy/package.py
new file mode 100644
index 0000000000000000000000000000000000000000..47bd618496931d77ed46838265318fe113be113b
--- /dev/null
+++ b/packages/py-astropy/package.py
@@ -0,0 +1,103 @@
+# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
+# Spack Project Developers. See the top-level COPYRIGHT file for details.
+#
+# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+
+import os
+
+from spack.package import *
+
+
+class PyAstropy(PythonPackage):
+    """The Astropy Project is a community effort to develop a single core
+    package for Astronomy in Python and foster interoperability between
+    Python astronomy packages."""
+
+    homepage = "https://astropy.org/"
+    pypi = "astropy/astropy-4.0.1.post1.tar.gz"
+
+    version("5.2.2", sha256="e6a9e34716bda5945788353c63f0644721ee7e5447d16b1cdcb58c48a96b0d9c")
+    version("5.1", sha256="1db1b2c7eddfc773ca66fa33bd07b25d5b9c3b5eee2b934e0ca277fa5b1b7b7e")
+    version(
+        "4.0.1.post1", sha256="5c304a6c1845ca426e7bc319412b0363fccb4928cb4ba59298acd1918eec44b5"
+    )
+    version("3.2.1", sha256="706c0457789c78285e5464a5a336f5f0b058d646d60f4e5f5ba1f7d5bf424b28")
+    version("2.0.14", sha256="618807068609a4d8aeb403a07624e9984f566adc0dc0f5d6b477c3658f31aeb6")
+    version("1.1.2", sha256="6f0d84cd7dfb304bb437dda666406a1d42208c16204043bc920308ff8ffdfad1")
+    version("1.1.post1", sha256="64427ec132620aeb038e4d8df94d6c30df4cc8b1c42a6d8c5b09907a31566a21")
+
+    variant("extras", default=False, description="Enable extra functionality")
+
+    # Required dependencies
+    depends_on("python@3.8:", when="@5.1:", type=("build", "run"))
+    depends_on("python@3.6:", when="@4.0:", type=("build", "run"))
+    depends_on("python@3.5:", when="@3.0:", type=("build", "run"))
+    depends_on("python@2.7:2.8,3.4:", when="@2.0:", type=("build", "run"))
+    depends_on("python@2.7:2.8,3.3:", when="@1.2:", type=("build", "run"))
+    depends_on("python@2.6:", type=("build", "run"))
+    depends_on("py-setuptools", type="build")
+    depends_on("py-cython@0.29.13:", type="build")
+    depends_on("py-numpy@1.18:", when="@5.1:", type=("build", "run"))
+    depends_on("py-numpy@1.16:", when="@4.0:", type=("build", "run"))
+    depends_on("py-numpy@1.13:", when="@3.1:", type=("build", "run"))
+    depends_on("py-numpy@1.10:", when="@3.0:", type=("build", "run"))
+    depends_on("py-numpy@1.9:", when="@2.0:", type=("build", "run"))
+    depends_on("py-numpy@1.7:", when="@1.2:", type=("build", "run"))
+    depends_on("py-numpy", type=("build", "run"))
+    depends_on("py-packaging@19.0:", when="@5.1:", type=("build", "run"))
+    depends_on("py-pyyaml@3.13:", when="@5.1:", type=("build", "run"))
+    depends_on("py-pyerfa@2.0:", when="@5.1:", type=("build", "run"))
+    depends_on("py-setuptools-scm@6.2:", when="@5.1:", type="build")
+    depends_on("py-cython@0.29.30:", when="@5.1:", type="build")
+    depends_on("py-extension-helpers", when="@5.1:", type="build")
+    depends_on("pkgconfig", type="build")
+
+    # Optional dependencies
+    depends_on("py-scipy@0.18:", when="+extras", type=("build", "run"))
+    depends_on("py-h5py", when="+extras", type=("build", "run"))
+    depends_on("py-beautifulsoup4", when="+extras", type=("build", "run"))
+    depends_on("py-html5lib", when="+extras", type=("build", "run"))
+    depends_on("py-bleach", when="+extras", type=("build", "run"))
+    depends_on("py-pyyaml", when="+extras", type=("build", "run"))
+    depends_on("py-pandas", when="+extras", type=("build", "run"))
+    depends_on("py-bintrees", when="+extras", type=("build", "run"))
+    depends_on("py-sortedcontainers", when="+extras", type=("build", "run"))
+    depends_on("py-pytz", when="+extras", type=("build", "run"))
+    depends_on("py-jplephem", when="+extras", type=("build", "run"))
+    depends_on("py-matplotlib@2.0:", when="+extras", type=("build", "run"))
+    depends_on("py-scikit-image", when="+extras", type=("build", "run"))
+    depends_on("py-mpmath", when="+extras", type=("build", "run"))
+    depends_on("py-asdf@2.3:", when="+extras", type=("build", "run"))
+    depends_on("py-bottleneck", when="+extras", type=("build", "run"))
+    depends_on("py-pytest", when="+extras", type=("build", "run"))
+
+    # System dependencies
+    depends_on("erfa")
+    depends_on("wcslib")
+    depends_on("cfitsio@:3")
+    depends_on("expat")
+
+    def patch(self):
+        # forces the rebuild of files with cython
+        # avoids issues with PyCode_New() in newer
+        # versions of python in the distributed
+        # cython-ized files
+        if os.path.exists("astropy/cython_version.py"):
+            os.remove("astropy/cython_version.py")
+
+    def install_options(self, spec, prefix):
+        args = [
+            "--use-system-libraries",
+            "--use-system-erfa",
+            "--use-system-wcslib",
+            "--use-system-cfitsio",
+            "--use-system-expat",
+        ]
+
+        return args
+
+    @run_after("install")
+    @on_package_attributes(run_tests=True)
+    def install_test(self):
+        with working_dir("spack-test", create=True):
+            python("-c", "import astropy; astropy.test()")
diff --git a/packages/py-nestml/package.py b/packages/py-nestml/package.py
index 7e3329d55ce4e0f73848a1b3d955cb1381d3f16a..30fc96c9e220407c577f98bdbd288ce8ce30bd2e 100644
--- a/packages/py-nestml/package.py
+++ b/packages/py-nestml/package.py
@@ -18,18 +18,19 @@ class PyNestml(PythonPackage):
 
     maintainers = ['clinssen', 'pnbabu', 'jougs']
 
+    version('6.0.0', sha256='224993f175b9599a3662e65afdaf57e355246c0eab5f88b795fdb089ea28b39e', expand=False)
     version('5.3.0', sha256='8de543d7d3a166cd4d6a0d536a2d4e769513bbf8d7aeaf64458e9d9c21fe546e', expand=False)
     version('5.2.0', sha256='acb703bf9c7f70304bd5d547dccd6a6a219f8acb298a6412df779b808241eb14', expand=False)
 
     depends_on('python@3.8:', type=('build', 'run'))
     depends_on('py-pip', type='build')
-    depends_on('py-numpy', type=('build', 'run'))
+    depends_on('py-numpy@1.8.2:', type=('build', 'run'))
     depends_on('py-scipy', type=('build', 'run'))
-    depends_on('py-sympy', type=('build', 'run'))
-    depends_on('py-antlr4-python3-runtime', type=('build', 'run'))
+    depends_on('py-sympy@1.1.1:1.10.1', type=('build', 'run'))
+    depends_on('py-antlr4-python3-runtime@4.10:', type=('build', 'run'))
     depends_on('py-setuptools', type=('build', 'run'))
-    depends_on('py-jinja2', type=('build', 'run'))
+    depends_on('py-jinja2@2.10:', type=('build', 'run'))
     depends_on('py-astropy', type=('build', 'run'))
-    depends_on('py-odetoolbox', type=('build', 'run'))
+    depends_on('py-odetoolbox@2.4:', type=('build', 'run'))
     depends_on('nest', type=('build', 'run'))
     depends_on('py-pytest', type='test')
diff --git a/packages/py-odetoolbox/package.py b/packages/py-odetoolbox/package.py
index 0f8265d12e9b20c65d3842d23aac1689bde53976..97eca37480c8db504901b0b1e56fbcc32121c52f 100644
--- a/packages/py-odetoolbox/package.py
+++ b/packages/py-odetoolbox/package.py
@@ -12,15 +12,15 @@ class PyOdetoolbox(PythonPackage):
 
     maintainers = ['clinssen']
 
+    version('2.5.4', sha256='eadb8f42a553cb34180a11d96cbac49ec4d0f07caf0358e066ab63415746e009', expand=False)
     version('2.5', sha256='947bbb289830dde60066106f5f628de2598a71129eafff8ec86317a0b5e8960e', expand=False)
 
     depends_on('python@3.8:', type=('build', 'run'))
     depends_on('py-pip', type='build')
-    depends_on('py-sympy@:1.4,1.7,1.8,1.9,1.11:', type=('build', 'run'))
+    depends_on('py-sympy@:1.4,1.7,1.8,1.9,1.10.1:', type=('build', 'run'))
     depends_on('py-scipy', type=('build', 'run'))
     depends_on('py-numpy@1.8.2:', type=('build', 'run'))
     depends_on('py-cython', type=('build', 'run'))
-    depends_on('py-graphviz', type=('build', 'run'))
     depends_on('py-matplotlib', type=('build', 'run'))
     depends_on('py-setuptools', type=('build', 'run'))
     depends_on('py-pytest', type='test')
diff --git a/packages/py-pynn/package.py b/packages/py-pynn/package.py
index 0366f2aab09328f9368096a7f60884fd5e4afbc0..402d05099e70dc5476fec69de07c1d96a497446f 100644
--- a/packages/py-pynn/package.py
+++ b/packages/py-pynn/package.py
@@ -31,34 +31,44 @@ class PyPynn(PythonPackage):
 
     depends_on('py-setuptools',         type=('build'))
 
-    depends_on('py-jinja2@2.7:',        type=('build', 'run'))
-    depends_on('py-docutils@0.10:',     type=('build', 'run'))
+    depends_on('py-jinja2@2.7:',        type=('run', 'test'))
+    depends_on('py-docutils@0.10:',     type=('run', 'test'))
 
-    depends_on('py-numpy@1.8.2:',       type=('build', 'run'), when="@0.9.5")
-    depends_on('py-numpy@1.13.0:',      type=('build', 'run'), when="@0.9.6")
-    depends_on('py-numpy@1.16.1:',      type=('build', 'run'), when="@0.10.0")
-    depends_on('py-numpy@1.18.5:',      type=('build', 'run'), when="@0.10.1:")
+    depends_on('py-numpy@1.8.2:',       type=('run', 'test'), when="@0.9.5")
+    depends_on('py-numpy@1.13.0:',      type=('run', 'test'), when="@0.9.6")
+    depends_on('py-numpy@1.16.1:',      type=('run', 'test'), when="@0.10.0")
+    depends_on('py-numpy@1.18.5:',      type=('run', 'test'), when="@0.10.1:")
 
-    depends_on('py-mpi4py', type=('build', 'run'), when='+mpi')
-    depends_on('py-quantities@0.12.1:', type=('build', 'run'), when="@0.9.5:")
+    depends_on('py-mpi4py',             type=('run', 'test'), when='+mpi')
+    depends_on('py-quantities@0.12.1:', type=('run', 'test'), when="@0.9.5:")
 
-    depends_on('py-lazyarray@0.3.2:',   type=('build', 'run'), when="@0.9.5")
-    depends_on('py-lazyarray@0.3.4:',   type=('build', 'run'), when="@0.9.6")
-    depends_on('py-lazyarray@0.5.0:',   type=('build', 'run'), when="@0.10.0")
-    depends_on('py-lazyarray@0.5.2:',   type=('build', 'run'), when="@0.10.1:")
+    depends_on('py-lazyarray@0.3.2:',   type=('run', 'test'), when="@0.9.5")
+    depends_on('py-lazyarray@0.3.4:',   type=('run', 'test'), when="@0.9.6")
+    depends_on('py-lazyarray@0.5.0:',   type=('run', 'test'), when="@0.10.0")
+    depends_on('py-lazyarray@0.5.2:',   type=('run', 'test'), when="@0.10.1:")
 
-    depends_on('py-neo@0.5.2:',         type=('build', 'run'), when="@0.9.5")
-    depends_on('py-neo@0.8.0',          type=('build', 'run'), when="@0.9.6")
-    depends_on('py-neo@0.10.0:',        type=('build', 'run'), when="@0.10.0")
-    depends_on('py-neo@0.11.0:',        type=('build', 'run'), when="@0.10.1:")
+    depends_on('py-neo@0.5.2:',         type=('run', 'test'), when="@0.9.5")
+    depends_on('py-neo@0.8.0',          type=('run', 'test'), when="@0.9.6")
+    depends_on('py-neo@0.10.0:',        type=('run', 'test'), when="@0.10.0")
+    depends_on('py-neo@0.11.0:',        type=('run', 'test'), when="@0.10.1:")
 
-    depends_on('neuron@8.1:',           type=('build','run'), when="@0.10.1:")
-    depends_on('nest@3.3:',             type=('build','run'), when="@0.10.1:")
-    depends_on('py-brian2',             type=('build','run'))
+    depends_on('neuron@8.1:+python',    type=('run', 'test'), when="@0.10.1:")
+    depends_on('nest@3.3:+python',      type=('run', 'test'), when="@0.10.1:")
+    depends_on('py-brian2',             type=('run', 'test'))
 
-    depends_on('py-mock@1.0:', type='test')
+    depends_on('py-mock@1.0:',          type='test')
+    depends_on('py-matplotlib',         type='test')
+    depends_on("py-pytest",             type='test', when="@0.11.0:")
 
     patch('pynn-0.9.6-python3.patch', when='@0.9.6 ^python@3:')
 
     # neuroml and nineml are optional dependencies. Leave out of import_modules to avoid errors in tests
     skip_modules = ['pyNN.neuroml', 'pyNN.nineml', 'pyNN.hardware']
+
+    @run_after('install')
+    @on_package_attributes(run_tests=True)
+    def install_test(self):
+        # run tests here:
+        pytest = which('pytest')
+        # TODO: fix neuron tests, see !328
+        pytest('-k', 'not [neuron]')
diff --git a/packages/py-sympy/package.py b/packages/py-sympy/package.py
new file mode 100644
index 0000000000000000000000000000000000000000..cba3ed0197e40c8292204d4da5bfa4b17e4bcc2a
--- /dev/null
+++ b/packages/py-sympy/package.py
@@ -0,0 +1,39 @@
+# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
+# Spack Project Developers. See the top-level COPYRIGHT file for details.
+#
+# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+
+from spack.package import *
+
+
+class PySympy(PythonPackage):
+    """SymPy is a Python library for symbolic mathematics."""
+
+    pypi = "sympy/sympy-0.7.6.tar.gz"
+
+    version("1.11.1", sha256="e32380dce63cb7c0108ed525570092fd45168bdae2faa17e528221ef72e88658")
+    version("1.10.1", sha256="5939eeffdf9e152172601463626c022a2c27e75cf6278de8d401d50c9d58787b")
+    version("1.9", sha256="c7a880e229df96759f955d4f3970d4cabce79f60f5b18830c08b90ce77cd5fdc")
+    version("1.8", sha256="1ca588a9f6ce6a323c5592f9635159c2093572826668a1022c75c75bdf0297cb")
+    version("1.7.1", sha256="a3de9261e97535b83bb8607b0da2c7d03126650fafea2b2789657b229c246b2e")
+    version("1.7", sha256="9104004669cda847f38cfd8cd16dd174952c537349dbae740fea5331d2b3a51b")
+    version("1.6.2", sha256="1cfadcc80506e4b793f5b088558ca1fcbeaec24cd6fc86f1fdccaa3ee1d48708")
+    version("1.6.1", sha256="7386dba4f7e162e90766b5ea7cab5938c2fe3c620b310518c8ff504b283cb15b")
+    version("1.6", sha256="9769e3d2952e211b1245f1d0dfdbfbdde1f7779a3953832b7dd2b88a21ca6cc6")
+    version("1.5.1", sha256="d77901d748287d15281f5ffe5b0fef62dd38f357c2b827c44ff07f35695f4e7e")
+    version("1.5", sha256="31567dc010bff0967ef7a87210acf3f938c6ab24481581fc143536fb103e9ce8")
+    version("1.4", sha256="71a11e5686ae7ab6cb8feb5bd2651ef4482f8fd43a7c27e645a165e4353b23e1")
+    version("1.3", sha256="e1319b556207a3758a0efebae14e5e52c648fc1db8975953b05fff12b6871b54")
+    version("1.1.1", sha256="ac5b57691bc43919dcc21167660a57cc51797c28a4301a6144eff07b751216a4")
+    version("1.0", sha256="3eacd210d839e4db911d216a9258a3ac6f936992f66db211e22767983297ffae")
+    version("0.7.6", sha256="dfa3927e9befdfa7da7a18783ccbc2fe489ce4c46aa335a879e49e48fc03d7a7")
+
+    depends_on("python@2.7:2.8,3.4:", when="@:1.4", type=("build", "run"))
+    depends_on("python@2.7:2.8,3.5:", when="@1.5", type=("build", "run"))
+    depends_on("python@3.5:", when="@1.6", type=("build", "run"))
+    depends_on("python@3.6:", when="@1.7:", type=("build", "run"))
+    depends_on("python@3.8:", when="@1.11.1:", type=("build", "run"))
+
+    # pip silently replaces distutils with setuptools
+    depends_on("py-setuptools", type="build")
+    depends_on("py-mpmath@0.19:", when="@1.0:", type=("build", "run"))
diff --git a/packages/pynn-brainscales/package.py b/packages/pynn-brainscales/package.py
index 60afda339e5708b68e663da167c6854e9834254b..7ce106c83d3eb05445c416900ac915b01b2aa6a8 100644
--- a/packages/pynn-brainscales/package.py
+++ b/packages/pynn-brainscales/package.py
@@ -130,6 +130,9 @@ class PynnBrainscales(WafPackage):
     def setup_run_environment(self, env):
         self._setup_common_env(env)
 
+    def setup_dependent_run_environment(self, env, dependent_spec):
+        self._setup_common_env(env)
+
     # override configure step as we perform a project setup first
     def configure(self, spec, prefix):
         """Setup and configure the project."""
diff --git a/packages/wf-brainscales2-demos/package.py b/packages/wf-brainscales2-demos/package.py
index a879c609f6ceebf48dcb4aebdc46f455501ef28d..5acf551cb328cf87a9d2942de1baa612fd044c4d 100644
--- a/packages/wf-brainscales2-demos/package.py
+++ b/packages/wf-brainscales2-demos/package.py
@@ -37,16 +37,13 @@ class WfBrainscales2Demos(Package):
     depends_on('py-pandas@1.4.2:', type=("run", "test"))
 
     def install(self, spec, prefix):
-        # sanity_check_prefix requires something in the install directory
-        mkdirp(prefix + "/.spack_test_results")
+        install_tree(".", join_path(prefix, "notebooks"))
 
-    def _run_notebooks(self):
-        # TODO: remove debug output
-        cmd_env = which("env")
-        cmd_env()
+    def _run_notebooks(self, output_dir):
+        mkdirp(output_dir)
         # execute notebook and save
         jupyter = Executable("jupyter")
-        for fn in glob("ts*.ipynb") + glob("tp*.ipynb"):
+        for fn in glob(join_path(prefix, "notebooks", "ts*.ipynb")) + glob(join_path(prefix, "notebooks", "tp*.ipynb")):
             jupyter("nbconvert",
                 "--ExecutePreprocessor.kernel_name=python3",
                 "--execute",
@@ -55,7 +52,7 @@ class WfBrainscales2Demos(Package):
                 "notebook",
                 fn,
                 "--output",
-                prefix + "/.spack_test_results/" + fn)
+                join_path(output_dir, os.path.basename(fn)))
 
     def _set_collab_things(self):
         # enable "EBRAINS lab" mode
@@ -69,8 +66,8 @@ class WfBrainscales2Demos(Package):
     def installcheck(self):
         self._set_collab_things()
         # TODO (ECM): Provide a selection of notebooks that perform local-only tests.
-        #self._run_notebooks()
+        #self._run_notebooks(join_path(self.prefix, ".spack_test_results"))
 
     def test_notebooks(self):
         self._set_collab_things()
-        self._run_notebooks()
+        self._run_notebooks(join_path(self.test_suite.stage, self.spec.format("out-{name}-{version}-{hash:7}")))
diff --git a/packages/wf-multi-area-model/package.py b/packages/wf-multi-area-model/package.py
index 01ffda36e18216b0037e7d4fc043830fb8cbd2b7..3b6c6c24cb686d641ec81c0b14c91bad8c72e8f5 100644
--- a/packages/wf-multi-area-model/package.py
+++ b/packages/wf-multi-area-model/package.py
@@ -17,23 +17,37 @@ class WfMultiAreaModel(Package):
     version("master",  branch="master")
     version("ebrains",  branch="lab.ebrains.eu")
 
-    depends_on("py-nested-dict")
-    depends_on("py-dicthash")
-    depends_on("py-matplotlib")
-    depends_on("py-numpy")
-    depends_on("py-scipy")
-    depends_on("py-future")
-    depends_on("nest")
-    depends_on("r-aod")
-    depends_on("py-notebook", type="test")
+    depends_on("py-nested-dict", type=("run", "test"))
+    depends_on("py-dicthash", type=("run", "test"))
+    depends_on("py-matplotlib", type=("run", "test"))
+    depends_on("py-numpy", type=("run", "test"))
+    depends_on("py-scipy", type=("run", "test"))
+    depends_on("py-future", type=("run", "test"))
+    depends_on("nest", type=("run", "test"))
+    depends_on("r-aod", type=("run", "test"))
+    depends_on("py-notebook", type=("run", "test"))
 
     def install(self, spec, prefix):
-        # sanity_check_prefix requires something in the install directory
-        mkdirp(prefix + "/.spack_test_results")
+        install_tree(".", join_path(prefix, "notebooks"))
+
+    def _run_notebooks(self, output_dir):
+        mkdirp(output_dir)
+        # execute notebook and save
+        jupyter = Executable("jupyter")
+        jupyter("nbconvert",
+            "--ExecutePreprocessor.kernel_name=python3",
+            "--execute",
+            "--allow-errors",
+            "--to",
+            "notebook",
+            join_path(self.prefix, "notebooks", "multi-area-model.ipynb"),
+            "--output",
+            join_path(output_dir, "multi-area-model.ipynb"))
 
     @run_after("install")
     @on_package_attributes(run_tests=True)
     def installcheck(self):
-        # execute notebook and save
-        jupyter = Executable("jupyter")
-        jupyter("nbconvert", "--ExecutePreprocessor.kernel_name=python3", "--execute", "--allow-errors", "--to", "notebook", "multi-area-model.ipynb", "--output", prefix+"/.spack_test_results/multi-area-model.ipynb")
+        self._run_notebooks(join_path(self.prefix, ".spack_test_results"))
+
+    def test_notebook(self):
+        self._run_notebooks(join_path(self.test_suite.stage, self.spec.format("out-{name}-{version}-{hash:7}")))
diff --git a/site-config/ebrainslab/config.yaml b/site-config/ebrainslab/config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..8e58bc0377128adb64c3c25f790da18e19cd3316
--- /dev/null
+++ b/site-config/ebrainslab/config.yaml
@@ -0,0 +1,2 @@
+config:
+  test_stage: /tmp/spack_tests
diff --git a/spack.yaml b/spack.yaml
index 94e3dbd67e5ca3c6dd3720347b7e1b58fe53961c..f599072f2a3657293e578eb19d66e7e6028dbae3 100644
--- a/spack.yaml
+++ b/spack.yaml
@@ -33,7 +33,7 @@ spack:
     - py-lfpykit@0.5.1
     - py-libsonata@0.1.23
     - py-neo@0.12.0
-    - py-nestml@5.3.0
+    - py-nestml@6.0.0
     - py-netpyne@1.0.5
     - py-neurom@3.2.2
     - py-neuror@1.6.4