diff --git a/Applications/ApplicationsLib/CMakeLists.txt b/Applications/ApplicationsLib/CMakeLists.txt
index dd3feb419ba09ecd7b051efd7f62f0f16b8753e6..e446674da71a1d67ac88933d621a4b316da444bc 100644
--- a/Applications/ApplicationsLib/CMakeLists.txt
+++ b/Applications/ApplicationsLib/CMakeLists.txt
@@ -7,7 +7,8 @@ ogs_add_library(ApplicationsLib ${LIB_SOURCES})
 
 target_link_libraries(ApplicationsLib
                       PUBLIC BaseLib GeoLib NumLib Processes
-                      PRIVATE MathLib
+                      PRIVATE CMakeInfoLib
+                              MathLib
                               MeshLib
                               MeshGeoToolsLib
                               ParameterLib
diff --git a/Applications/ApplicationsLib/ProjectData.cpp b/Applications/ApplicationsLib/ProjectData.cpp
index 5e45b588975f7d28c04f9fd285bea47a214110ff..d55c082006dc94066e9f7db761e6d3dde7b74e2e 100644
--- a/Applications/ApplicationsLib/ProjectData.cpp
+++ b/Applications/ApplicationsLib/ProjectData.cpp
@@ -15,7 +15,6 @@
 
 #include <algorithm>
 #include <cctype>
-#include "BaseLib/Logging.h"
 #include <set>
 
 #ifdef OGS_USE_PYTHON
@@ -25,8 +24,10 @@
 #include "BaseLib/Algorithm.h"
 #include "BaseLib/ConfigTree.h"
 #include "BaseLib/FileTools.h"
+#include "BaseLib/Logging.h"
 #include "BaseLib/StringTools.h"
 #include "GeoLib/GEOObjects.h"
+#include "InfoLib/CMakeInfo.h"
 #include "MaterialLib/MPL/CreateMedium.h"
 #include "MathLib/Curve/CreatePiecewiseLinearCurve.h"
 #include "MeshGeoToolsLib/ConstructMeshesFromGeometries.h"
@@ -275,15 +276,21 @@ ProjectData::ProjectData(BaseLib::ConfigTree const& project_config,
 #ifdef OGS_USE_PYTHON
         namespace py = pybind11;
 
-        // Append project's directory to python's module search path.
-        py::module::import("sys").attr("path").attr("append")(
-            project_directory);
+        // Append to python's module search path
+        auto py_path = py::module::import("sys").attr("path");
+        py_path.attr("append")(project_directory);  // .prj directory
+        // virtualenv
+        py_path.attr("append")(
+            CMakeInfoLib::CMakeInfo::python_virtualenv_sitepackages);
 
         auto const script_path =
             BaseLib::copyPathToFileName(*python_script, project_directory);
 
         // Evaluate in scope of main module
         py::object scope = py::module::import("__main__").attr("__dict__");
+        // add (global) variables
+        auto globals = py::dict(scope);
+        globals["ogs_prj_directory"] = project_directory;
         py::eval_file(script_path, scope);
 #else
         OGS_FATAL("OpenGeoSys has not been built with Python support.");
diff --git a/Applications/Utils/Tests.cmake b/Applications/Utils/Tests.cmake
index e8622976917fdb378cc193e54e4642577d437153..fb9f483f20428e35c8394b669005297a76736bc2 100644
--- a/Applications/Utils/Tests.cmake
+++ b/Applications/Utils/Tests.cmake
@@ -2,6 +2,7 @@
 AddTest(
     NAME MapGeometryToMeshSurface_Ammer
     PATH MeshGeoToolsLib/Ammer
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/MeshGeoToolsLib/Ammer
     EXECUTABLE MapGeometryToMeshSurface
     EXECUTABLE_ARGS -m Ammer-Homogen100m-Final-TopSurface.vtu -i Ammer-Rivers.gml -o ${Data_BINARY_DIR}/MeshGeoToolsLib/Ammer/Ammer-Rivers-Mapped.gml
     TESTER diff
@@ -14,6 +15,7 @@ if(NOT "${HOSTNAME}" MATCHES "frontend.*")
     AddTest(
         NAME MapGeometryToMeshSurface_Bode
         PATH MeshGeoToolsLib/Bode
+        WORKING_DIRECTORY ${Data_SOURCE_DIR}/MeshGeoToolsLib/Bode
         EXECUTABLE MapGeometryToMeshSurface
         EXECUTABLE_ARGS -m BodeComplex.msh -i BodeEZG_Fliessgewaesser.gml -o ${Data_BINARY_DIR}/MeshGeoToolsLib/Bode/BodeEZG_Fliessgewaesser-Mapped.gml
         REQUIREMENTS NOT OGS_USE_MPI
@@ -25,6 +27,7 @@ endif()
 AddTest(
     NAME MapGeometryToMeshSurface_Naegelstedt
     PATH MeshGeoToolsLib/Naegelstedt
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/MeshGeoToolsLib/Naegelstedt
     EXECUTABLE MapGeometryToMeshSurface
     EXECUTABLE_ARGS -m SmallTest.vtu -i RiverNetwork.gml -o ${Data_BINARY_DIR}/MeshGeoToolsLib/Naegelstedt/RiverNetwork-Mapped.gml
     REQUIREMENTS NOT OGS_USE_MPI
@@ -35,6 +38,7 @@ AddTest(
 AddTest(
     NAME postLIE
     PATH LIE/PostProcessing
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/LIE/PostProcessing
     EXECUTABLE postLIE
     EXECUTABLE_ARGS -i single_joint_pcs_0.pvd -o ${Data_BINARY_DIR}/LIE/PostProcessing/post_single_joint_pcs_0.pvd
     REQUIREMENTS NOT OGS_USE_MPI AND OGS_BUILD_PROCESS_LIE
@@ -46,6 +50,7 @@ AddTest(
 AddTest(
     NAME identifySubdomains_2D_Create
     PATH MeshGeoToolsLib/IdentifySubdomains
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/MeshGeoToolsLib/IdentifySubdomains
     EXECUTABLE identifySubdomains
     EXECUTABLE_ARGS -m 2D_mesh.vtu -o ${Data_BINARY_DIR}/MeshGeoToolsLib/IdentifySubdomains/new_ -- 2D_mesh_top_boundary.vtu 2D_mesh_bottom_boundary.vtu
     REQUIREMENTS NOT OGS_USE_MPI
@@ -60,6 +65,7 @@ AddTest(
 AddTest(
     NAME identifySubdomains_2D_Check
     PATH MeshGeoToolsLib/IdentifySubdomains
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/MeshGeoToolsLib/IdentifySubdomains
     EXECUTABLE identifySubdomains
     EXECUTABLE_ARGS -m 2D_mesh.vtu -o ${Data_BINARY_DIR}/MeshGeoToolsLib/IdentifySubdomains/check_ -- 2D_mesh_top.vtu 2D_mesh_bottom.vtu
     REQUIREMENTS NOT OGS_USE_MPI
@@ -74,6 +80,7 @@ AddTest(
 AddTest(
     NAME identifySubdomains_riverTriangleMesh
     PATH MeshGeoToolsLib/IdentifySubdomains
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/MeshGeoToolsLib/IdentifySubdomains
     EXECUTABLE identifySubdomains
     EXECUTABLE_ARGS -m river_domain_triangle.vtu -o ${Data_BINARY_DIR}/MeshGeoToolsLib/IdentifySubdomains/triangle_ -- river_bc.vtu
     REQUIREMENTS NOT OGS_USE_MPI
@@ -87,6 +94,7 @@ AddTest(
 AddTest(
     NAME identifySubdomains_riverPrismMesh
     PATH MeshGeoToolsLib/IdentifySubdomains
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/MeshGeoToolsLib/IdentifySubdomains
     EXECUTABLE identifySubdomains
     EXECUTABLE_ARGS -s 1e-3 -m river_domain_prism.vtu -o ${Data_BINARY_DIR}/MeshGeoToolsLib/IdentifySubdomains/prism_ -- river_bc.vtu
     REQUIREMENTS NOT OGS_USE_MPI
@@ -102,6 +110,7 @@ AddTest(
 AddTest(
     NAME partmesh_2Dmesh_3partitions_ascii
     PATH NodePartitionedMesh/partmesh_2Dmesh_3partitions/ASCII
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/NodePartitionedMesh/partmesh_2Dmesh_3partitions/ASCII
     EXECUTABLE partmesh
     EXECUTABLE_ARGS -a -m -n 3 -i 2Dmesh.vtu -o ${Data_BINARY_DIR}/NodePartitionedMesh/partmesh_2Dmesh_3partitions/ASCII
     REQUIREMENTS NOT (OGS_USE_MPI OR APPLE)
@@ -116,6 +125,7 @@ AddTest(
 AddTest(
     NAME partmesh_2Dmesh_3partitions_binary
     PATH NodePartitionedMesh/partmesh_2Dmesh_3partitions/Binary
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/NodePartitionedMesh/partmesh_2Dmesh_3partitions/Binary
     EXECUTABLE partmesh
     EXECUTABLE_ARGS -m -n 3 -i 2Dmesh.vtu
                     -o ${Data_BINARY_DIR}/NodePartitionedMesh/partmesh_2Dmesh_3partitions/Binary --
@@ -193,6 +203,7 @@ AddTest(
 AddTest(
     NAME checkMesh_LIE_HM_TaskB
     PATH LIE/HydroMechanics
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/LIE/HydroMechanics
     EXECUTABLE checkMesh
     EXECUTABLE_ARGS -p -v TaskB_mesh.vtu
     REQUIREMENTS NOT OGS_USE_MPI
@@ -201,6 +212,7 @@ AddTest(
 AddTest(
     NAME mesh2raster_test
     PATH MeshGeoToolsLib/Hamburg
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/MeshGeoToolsLib/Hamburg
     EXECUTABLE Mesh2Raster
     EXECUTABLE_ARGS -i 00-surface.vtu -o ${Data_BINARY_DIR}/MeshGeoToolsLib/Hamburg/00-raster.asc -c 25
     REQUIREMENTS NOT OGS_USE_MPI
@@ -211,6 +223,7 @@ AddTest(
 MeshTest(
     NAME ExtractSurfaceLeft
     PATH MeshLib/
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/MeshLib
     EXECUTABLE ExtractSurface
     EXECUTABLE_ARGS -i cube_1x1x1_hex_1e3_layers_10.vtu -o ${Data_BINARY_DIR}/MeshLib/Left.vtu -x 1 -y 0 -z 0 -a 25
     REQUIREMENTS NOT OGS_USE_MPI
@@ -220,6 +233,7 @@ MeshTest(
 MeshTest(
     NAME ExtractSurfaceRight
     PATH MeshLib/
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/MeshLib
     EXECUTABLE ExtractSurface
     EXECUTABLE_ARGS -i cube_1x1x1_hex_1e3_layers_10.vtu -o ${Data_BINARY_DIR}/MeshLib/Right.vtu -x -1 -y 0 -z 0 -a 25
     REQUIREMENTS NOT OGS_USE_MPI
@@ -229,6 +243,7 @@ MeshTest(
 MeshTest(
     NAME ExtractSurfaceFront
     PATH MeshLib/
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/MeshLib
     EXECUTABLE ExtractSurface
     EXECUTABLE_ARGS -i cube_1x1x1_hex_1e3_layers_10.vtu -o ${Data_BINARY_DIR}/MeshLib/Front.vtu -x 0 -y 1 -z 0 -a 25
     REQUIREMENTS NOT OGS_USE_MPI
@@ -238,6 +253,7 @@ MeshTest(
 MeshTest(
     NAME ExtractSurfaceBack
     PATH MeshLib/
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/MeshLib
     EXECUTABLE ExtractSurface
     EXECUTABLE_ARGS -i cube_1x1x1_hex_1e3_layers_10.vtu -o ${Data_BINARY_DIR}/MeshLib/Back.vtu -x 0 -y -1 -z 0 -a 25
     REQUIREMENTS NOT OGS_USE_MPI
@@ -247,6 +263,7 @@ MeshTest(
 MeshTest(
     NAME GocadTSurface_Mesh_Test
     PATH MeshLib/
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/MeshLib
     EXECUTABLE GocadTSurfaceReader
     EXECUTABLE_ARGS -i Top-Lower-Shaly.ts -o ${Data_BINARY_DIR}/MeshLib -b
     REQUIREMENTS NOT OGS_USE_MPI
@@ -256,6 +273,7 @@ MeshTest(
 AddTest(
     NAME GocadTSurface_Array_Test
     PATH MeshLib/
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/MeshLib
     EXECUTABLE GocadTSurfaceReader
     EXECUTABLE_ARGS -i Top-Lower-Shaly.ts -o ${Data_BINARY_DIR}/MeshLib -b
     REQUIREMENTS NOT OGS_USE_MPI
@@ -268,6 +286,7 @@ AddTest(
 AddTest(
     NAME createIntermediateRasters_test
     PATH MeshGeoToolsLib/Hamburg
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/MeshGeoToolsLib/Hamburg
     EXECUTABLE createIntermediateRasters
     EXECUTABLE_ARGS --file1 layer04.asc --file2 layer17.asc -o ${Data_BINARY_DIR}/MeshGeoToolsLib/Hamburg/output.asc
     REQUIREMENTS NOT OGS_USE_MPI
@@ -278,6 +297,7 @@ AddTest(
 AddTest(
     NAME Vtu2Grid_Test
     PATH FileIO/
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/FileIO
     EXECUTABLE Vtu2Grid
     EXECUTABLE_ARGS -i AmmerSubsurfaceCoarse.vtu -o ${Data_BINARY_DIR}/FileIO/AmmerGridOutput.vtu -x 200 -y 200 -z 20
     REQUIREMENTS NOT OGS_USE_MPI
@@ -316,6 +336,7 @@ endforeach()
 AddTest(
     NAME partmesh_with_field_data
     PATH NodePartitionedMesh/partmesh
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/NodePartitionedMesh/partmesh
     EXECUTABLE partmesh
     EXECUTABLE_ARGS -n 2 -i cube_1x1x1_hex_8.vtu -o ${Data_BINARY_DIR}/NodePartitionedMesh/partmesh
     TESTER diff
@@ -335,6 +356,7 @@ if(OGS_USE_NETCDF)
     AddTest(
         NAME NetCDF_2D_Test
         PATH FileConverter/
+        WORKING_DIRECTORY ${Data_SOURCE_DIR}/FileConverter
         EXECUTABLE NetCdfConverter
         EXECUTABLE_ARGS -i sresa1b_ncar_ccsm3-example.nc -o ${Data_BINARY_DIR}/FileConverter/sresa1b_ncar_ccsm3-example.vtu -v pr -t 0 --dim1 2 --dim2 1 --timestep-first 0 --timestep-last 0 -e tri
         REQUIREMENTS NOT OGS_USE_MPI
@@ -346,6 +368,7 @@ if(OGS_USE_NETCDF)
     AddTest(
         NAME NetCDF_3D_Test
         PATH FileConverter/
+        WORKING_DIRECTORY ${Data_SOURCE_DIR}/FileConverter
         EXECUTABLE NetCdfConverter
         EXECUTABLE_ARGS -i slim_100897_198.nc -o ${Data_BINARY_DIR}/FileConverter/slim_100897_198.vtu -v NO -t 0 --dim1 3 --dim2 2 --dim3 1 --timestep-first 0 --timestep-last 0 -e hex
         REQUIREMENTS NOT OGS_USE_MPI
@@ -357,6 +380,7 @@ if(OGS_USE_NETCDF)
     AddTest(
         NAME NetCDF_Image_Test
         PATH FileConverter
+        WORKING_DIRECTORY ${Data_SOURCE_DIR}/FileConverter
         EXECUTABLE NetCdfConverter
         EXECUTABLE_ARGS -i sresa1b_ncar_ccsm3-example.nc -o ${Data_BINARY_DIR}/FileConverter/sresa1b_ncar_ccsm3-example.asc -v pr -t 0 --dim1 2 --dim2 1 --timestep-first 0 --timestep-last 0 --images
         REQUIREMENTS NOT OGS_USE_MPI
@@ -369,6 +393,7 @@ if(OGS_BUILD_GUI)
     AddTest(
         NAME RemoveGhostData_Test
         PATH MeshLib/
+        WORKING_DIRECTORY ${Data_SOURCE_DIR}/MeshLib
         EXECUTABLE RemoveGhostData
         EXECUTABLE_ARGS -i Mesh3D.pvtu -o ${Data_BINARY_DIR}/MeshLib/RemoveGhostDataOutput.vtu
         REQUIREMENTS NOT OGS_USE_MPI
@@ -380,6 +405,7 @@ if(OGS_BUILD_GUI)
     AddTest(
         NAME RemoveGhostData_EllipticSquareTest
         PATH EllipticPETSc/
+        WORKING_DIRECTORY ${Data_SOURCE_DIR}/PATH EllipticPETSc
         EXECUTABLE RemoveGhostData
         EXECUTABLE_ARGS -i square_1e1_neumann_pcs_0_ts_1_t_1_000000.pvtu -o ${Data_BINARY_DIR}/EllipticPETSc/square_1e1_neumann_pcs_0_ts_1_t_1_000000.vtu
         REQUIREMENTS NOT OGS_USE_MPI
@@ -392,6 +418,7 @@ endif()
 AddTest(
     NAME Raster2Mesh_Elevation_Test
     PATH FileConverter
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/FileConverter
     EXECUTABLE Raster2Mesh
     EXECUTABLE_ARGS -i RainEvent30.asc -o ${Data_BINARY_DIR}/FileConverter/RainEvent30-elevation.vtu -e tri -p elevation
     REQUIREMENTS NOT OGS_USE_MPI
@@ -402,6 +429,7 @@ AddTest(
 AddTest(
     NAME Raster2Mesh_Materials_Test
     PATH FileConverter
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/FileConverter
     EXECUTABLE Raster2Mesh
     EXECUTABLE_ARGS -i RainEvent30.asc -o ${Data_BINARY_DIR}/FileConverter/RainEvent30-materials.vtu -e quad -p materials
     REQUIREMENTS NOT OGS_USE_MPI
@@ -413,6 +441,7 @@ AddTest(
 AddTest(
     NAME Raster2Mesh_Scalars_Test
     PATH FileConverter
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/FileConverter
     EXECUTABLE Raster2Mesh
     EXECUTABLE_ARGS -i RainEvent30.asc -o ${Data_BINARY_DIR}/FileConverter/RainEvent30-scalars.vtu -e tri -p scalar -n ScalarValues
     REQUIREMENTS NOT OGS_USE_MPI
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5d74fc66b444486aa93603afa3149e8ff476af36..13b429b2fb7d337f88a1b6286630a3c112cc37ab 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -61,11 +61,11 @@ endif()
 option(OGS_USE_CVODE "Use the Sundials CVODE module?" OFF)
 
 # ---- CMake includes ----
+include(Versions)
 include(PreFind)
 include(SubmoduleSetup)
 include(ProcessesSetup)
 include(ProjectSetup)
-include(Versions)
 include(Functions)
 include(ConanSetup)
 include(CompilerSetup)
diff --git a/InfoLib/CMakeInfo.cpp.in b/InfoLib/CMakeInfo.cpp.in
index bc28b2a72ae07c26e7f2724a2579faa0f2e2397a..77d6c8260487a24b253c85499747950d4aa6f05a 100644
--- a/InfoLib/CMakeInfo.cpp.in
+++ b/InfoLib/CMakeInfo.cpp.in
@@ -17,6 +17,7 @@ namespace CMakeInfoLib
 
 namespace CMakeInfo
 {
+    const std::string python_virtualenv_sitepackages("@Python3_VIRTUALENV_SITEPACKAGES@");
     const std::string cmake_args("@CMAKE_ARGS_ESCAPED@");
 }
 }
diff --git a/InfoLib/CMakeInfo.h b/InfoLib/CMakeInfo.h
index 6933e19f770d241fb22867920f580718d29a0055..128a1e31c5ee6392d692ae7e393a8eda664ead4e 100644
--- a/InfoLib/CMakeInfo.h
+++ b/InfoLib/CMakeInfo.h
@@ -21,6 +21,7 @@ namespace CMakeInfoLib
 
 namespace CMakeInfo
 {
+    extern CMAKEINFOLIB_EXPORT const std::string python_virtualenv_sitepackages;
     extern CMAKEINFOLIB_EXPORT const std::string cmake_args;
 }  // namespace
 }  // namespace
diff --git a/ProcessLib/HeatTransportBHE/Tests.cmake b/ProcessLib/HeatTransportBHE/Tests.cmake
index 906eddd362dc083f8a59c73ee86b1fba46624048..c3d6a2fdeb5d97febe0c73ca4a74684a6477dfee 100644
--- a/ProcessLib/HeatTransportBHE/Tests.cmake
+++ b/ProcessLib/HeatTransportBHE/Tests.cmake
@@ -92,6 +92,7 @@ AddTest(
     WRAPPER time
     TESTER vtkdiff
     REQUIREMENTS OGS_USE_PYTHON AND NOT OGS_USE_MPI
+    PYTHON_PACKAGES TESPy=0.3.2
     DIFF_DATA
     3bhes_1U_pcs_0_ts_10_t_600.000000.vtu 3bhes_1U_pcs_0_ts_10_t_600.000000.vtu temperature_soil temperature_soil 1e-12 1e-13
     3bhes_1U_pcs_0_ts_10_t_600.000000.vtu 3bhes_1U_pcs_0_ts_10_t_600.000000.vtu temperature_BHE1 temperature_BHE1 1e-10 1e-13
diff --git a/ProcessLib/SmallDeformation/Tests.cmake b/ProcessLib/SmallDeformation/Tests.cmake
index a9d7d2fb17af5eb4c861e459f512f4d979fc1c96..9caff56ff079b24b5a5572e2bab776e650a50ec5 100644
--- a/ProcessLib/SmallDeformation/Tests.cmake
+++ b/ProcessLib/SmallDeformation/Tests.cmake
@@ -86,6 +86,7 @@ AddTest(
 AddTest(
     NAME Mechanics_DruckerPrager_mfront
     PATH Mechanics/Ehlers/MFront
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/Mechanics/Ehlers/MFront
     EXECUTABLE ogs
     EXECUTABLE_ARGS cube_1e0_dp.prj
     TESTER vtkdiff
@@ -103,6 +104,7 @@ AddTest(
 AddTest(
     NAME SmallDeformation_ring_plane_strain_axi_mfront
     PATH Mechanics/Linear/MFront/axisymm_ring
+    WORKING_DIRECTORY ${Data_SOURCE_DIR}/Mechanics/Linear/MFront/axisymm_ring
     EXECUTABLE ogs
     EXECUTABLE_ARGS ring_plane_strain.prj
     TESTER vtkdiff
diff --git a/Tests/Data/Parabolic/T/3D_3BHEs_array/README.md b/Tests/Data/Parabolic/T/3D_3BHEs_array/README.md
index 56c4278f3e74abca6f295e00d5603b19a850b2ff..92f79b19686a7aa8693be2154aa99e3ff992917d 100644
--- a/Tests/Data/Parabolic/T/3D_3BHEs_array/README.md
+++ b/Tests/Data/Parabolic/T/3D_3BHEs_array/README.md
@@ -1,11 +1,5 @@
 # How to run
 
- You need to build OGS with `OGS_USE_PYTHON=ON` and have the Python modules
- listed in `requirements.txt` installed, e.g.:
+ You need to have [poetry][1] installed and build OGS with `OGS_USE_PYTHON=ON`.
 
- - In this directory:
-   - `virtualenv .venv` Make sure you use the same Python interpreter which was
-     embedded into OGS (look for `Found PythonInterp: ..` in the CMake output)
-   - `source .venv/bin/activate`
-   - `pip install -r requirements.txt`
-   - `PYTHONPATH=.venv/lib/python3.7/site-packages ~/code/ogs6/build/bin/ogs -o ~/tmp 3bhes_1U.prj`
+ [1]: https://python-poetry.org
diff --git a/Tests/Data/Parabolic/T/3D_3BHEs_array/bcs_tespy.py b/Tests/Data/Parabolic/T/3D_3BHEs_array/bcs_tespy.py
index df8fe46060641801f29a3140b11fd020f494805d..9451cf8fc35af1328e7eade27bf77e18d33230e6 100644
--- a/Tests/Data/Parabolic/T/3D_3BHEs_array/bcs_tespy.py
+++ b/Tests/Data/Parabolic/T/3D_3BHEs_array/bcs_tespy.py
@@ -155,8 +155,7 @@ class BC(OpenGeoSys.BHENetwork):
 # initialize the tespy model of the bhe network
 # load path of network model:
 # loading the TESPy model
-project_dir = os.getcwd()
-print("Project dir is: ", project_dir)
+project_dir = os.chdir(ogs_prj_directory)
 nw = load_network('./pre/tespy_nw')
 # set if print the network iteration info
 nw.set_attr(iterinfo=False)
diff --git a/Tests/Data/Parabolic/T/3D_3BHEs_array/requirements.txt b/Tests/Data/Parabolic/T/3D_3BHEs_array/requirements.txt
deleted file mode 100644
index a786bbd3757877b110292eb41a871ac0b1050e55..0000000000000000000000000000000000000000
--- a/Tests/Data/Parabolic/T/3D_3BHEs_array/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-TESPy==0.3.2
diff --git a/ThirdParty/container-maker b/ThirdParty/container-maker
index 0a38ff038beca76808aa8fa16515231d6e8c7af0..dd01b38f400a7e3dbfa85a3d7eeeddaa9bdd8c8f 160000
--- a/ThirdParty/container-maker
+++ b/ThirdParty/container-maker
@@ -1 +1 @@
-Subproject commit 0a38ff038beca76808aa8fa16515231d6e8c7af0
+Subproject commit dd01b38f400a7e3dbfa85a3d7eeeddaa9bdd8c8f
diff --git a/scripts/ci/jobs/build-linux-conan.yml b/scripts/ci/jobs/build-linux-conan.yml
index 2371f5309fc0bda55c4176686f7d4ac67d44ba34..c07a3f61f1b46956c24bdd4d004525c04cfca15c 100644
--- a/scripts/ci/jobs/build-linux-conan.yml
+++ b/scripts/ci/jobs/build-linux-conan.yml
@@ -8,5 +8,5 @@ build linux conan:
     CMAKE_ARGS: >-
       -DBUILD_SHARED_LIBS=ON
       -DOGS_BUILD_UTILS=ON
-      -DOGS_USE_CONAN=ON
+      -DOGS_USE_CONAN=AUTO
       -DOGS_USE_MFRONT=ON
diff --git a/scripts/ci/jobs/build-win.yml b/scripts/ci/jobs/build-win.yml
index d485cf93709d2f475222e1f76fff63e20748eeef..93d8c4d0a391487d7ca5a154365fef5275ae76db 100644
--- a/scripts/ci/jobs/build-win.yml
+++ b/scripts/ci/jobs/build-win.yml
@@ -6,6 +6,7 @@ build win:
       -DOGS_BUILD_UTILS=ON
       -DOGS_CI_TESTRUNNER_REPEAT=1
       -DOGS_USE_PYTHON=ON
+      -DOGS_USE_CONAN=AUTO
 
 build win no python:
   extends: .template-build-win
diff --git a/scripts/cmake/ConanSetup.cmake b/scripts/cmake/ConanSetup.cmake
index 916322ab4c7108cb8dd380ad5c573a7a76bb3cf3..9b2cb255cc7f8a746427ca709b7c9cbbea66c245 100644
--- a/scripts/cmake/ConanSetup.cmake
+++ b/scripts/cmake/ConanSetup.cmake
@@ -1,7 +1,20 @@
 if(NOT OGS_USE_CONAN)
     return()
 endif()
-find_program(CONAN_CMD conan)
+string(TOLOWER ${OGS_USE_CONAN} OGS_USE_CONAN_lower)
+if(OGS_USE_CONAN_lower STREQUAL "auto" AND POETRY)
+    execute_process(COMMAND ${CMD_COMMAND} poetry add conan=${ogs.minimum_version.conan})
+    find_program(CONAN_CMD conan HINTS
+        ${PROJECT_BINARY_DIR}/.venv/bin
+        ${PROJECT_BINARY_DIR}/.venv/Scripts
+        REQUIRED NO_DEFAULT_PATH
+    )
+    find_program(CONAN_CMD conan HINTS ${PROJECT_BINARY_DIR}/.venv/bin
+        REQUIRED NO_DEFAULT_PATH
+    )
+else()
+    find_program(CONAN_CMD conan)
+endif()
 if(NOT CONAN_CMD)
     message(WARNING "conan executable not found. Consider installing Conan for "
         "automatic third-party library handling. https://www.opengeosys.org/doc"
diff --git a/scripts/cmake/PreFind.cmake b/scripts/cmake/PreFind.cmake
index 58aa681e37f81aa347802835eae9a7c4cb718807..783387ea358989c9d919d15163b08280e21f165d 100644
--- a/scripts/cmake/PreFind.cmake
+++ b/scripts/cmake/PreFind.cmake
@@ -1,6 +1,12 @@
+### Git detection ###
 find_package(Git REQUIRED)
 
-if(NOT IS_GIT_REPO)
+if(DEFINED ENV{OGS_VERSION})
+    set(OGS_VERSION $ENV{OGS_VERSION})
+    message(STATUS "OGS VERSION: ${OGS_VERSION} (set via environment)")
+endif()
+
+if(NOT IS_GIT_REPO AND NOT OGS_VERSION)
     execute_process(COMMAND ${GIT_EXECUTABLE} status
         WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
         RESULT_VARIABLE IS_GIT_REPO
@@ -8,8 +14,7 @@ if(NOT IS_GIT_REPO)
     if(IS_GIT_REPO GREATER 0)
         set(IS_GIT_REPO FALSE CACHE INTERNAL "")
         if(DEFINED OGS_VERSION)
-            message(WARNING "Using user-provided OGS_VERSION: ${OGS_VERSION}!")
-            message(WARNING "Submodule setup is skipped!")
+            message(WARNING "Using user-provided OGS_VERSION; Submodule setup is skipped!")
         else()
             message(FATAL_ERROR "No git repository found at ${PROJECT_SOURCE_DIR}! "
                 "Please use git to obtain the source code! See "
@@ -21,8 +26,101 @@ if(NOT IS_GIT_REPO)
     endif()
 endif()
 
+if(IS_GIT_REPO AND NOT OGS_VERSION)
+    # Get version info from Git, implementation based on
+    # https://github.com/tomtom-international/cpp-dependencies
+    execute_process(
+        COMMAND ${GIT_EXECUTABLE} describe --tags --long --dirty --always
+        WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+        RESULT_VARIABLE DESCRIBE_RESULT
+        OUTPUT_VARIABLE DESCRIBE_STDOUT
+    )
+    if(DESCRIBE_RESULT EQUAL 0)
+        string(STRIP "${DESCRIBE_STDOUT}" DESCRIBE_STDOUT)
+        message(STATUS "Git reported this project's version as '${DESCRIBE_STDOUT}'")
+        if(DESCRIBE_STDOUT MATCHES "^(.*)-(dirty)$")
+          set(DESCRIBE_DIRTY "${CMAKE_MATCH_2}")
+          set(DESCRIBE_STDOUT "${CMAKE_MATCH_1}")
+        endif()
+        if(DESCRIBE_STDOUT MATCHES "^([0-9a-f]+)$")
+          set(DESCRIBE_COMMIT_NAME "${CMAKE_MATCH_1}")
+          set(DESCRIBE_STDOUT "")
+        elseif(DESCRIBE_STDOUT MATCHES "^(.*)-g([0-9a-f]+)$")
+          set(DESCRIBE_COMMIT_NAME "g${CMAKE_MATCH_2}")
+          set(DESCRIBE_STDOUT "${CMAKE_MATCH_1}")
+        endif()
+        if(DESCRIBE_STDOUT MATCHES "^(.*)-([0-9]+)$")
+          set(DESCRIBE_COMMIT_COUNT "${CMAKE_MATCH_2}")
+          set(DESCRIBE_TAG "${CMAKE_MATCH_1}")
+          set(DESCRIBE_STDOUT "")
+        endif()
+
+        set(OGS_VERSION ${DESCRIBE_TAG})
+        if(DESCRIBE_COMMIT_COUNT GREATER 0)
+          set(OGS_VERSION "${OGS_VERSION}-${DESCRIBE_COMMIT_COUNT}-${DESCRIBE_COMMIT_NAME}")
+        endif()
+
+        if(DESCRIBE_DIRTY)
+          string(TIMESTAMP DESCRIBE_DIRTY_TIMESTAMP "%Y%m%d%H%M%S" UTC)
+          set(OGS_VERSION "${OGS_VERSION}.dirty.${DESCRIBE_DIRTY_TIMESTAMP}")
+        endif()
+        message(STATUS "OGS VERSION: ${OGS_VERSION}")
+    else()
+        message(WARNING "Git repository contains no tags! Please run: git fetch --tags")
+    endif()
+
+    # Get git commit
+    execute_process(
+        COMMAND ${GIT_EXECUTABLE} log -1 --format=%H
+        WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+        OUTPUT_VARIABLE GIT_SHA1
+        OUTPUT_STRIP_TRAILING_WHITESPACE
+    )
+
+    execute_process(
+        COMMAND ${GIT_EXECUTABLE} log -1 --format=%h
+        WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+        OUTPUT_VARIABLE GIT_SHA1_SHORT
+        OUTPUT_STRIP_TRAILING_WHITESPACE
+    )
+endif()
+
+if(MSVC)
+    set(CMD_COMMAND "cmd" "/c" CACHE INTERNAL "")
+endif()
+
+### Python setup ###
+find_program(POETRY poetry)
+if(POETRY)
+    configure_file(${PROJECT_SOURCE_DIR}/scripts/python/poetry.in.toml
+        ${PROJECT_BINARY_DIR}/poetry.toml COPYONLY)
+    if(NOT EXISTS ${PROJECT_BINARY_DIR}/pyproject.toml)
+        configure_file(${PROJECT_SOURCE_DIR}/scripts/python/pyproject.in.toml
+            ${PROJECT_BINARY_DIR}/pyproject.toml)
+    endif()
+    if(NOT EXISTS ${PROJECT_BINARY_DIR}/.venv)
+        execute_process(COMMAND ${CMD_COMMAND} poetry install)
+    endif()
+    set(Python3_ROOT_DIR ${PROJECT_BINARY_DIR}/.venv)
+    set(Python3_EXECUTABLE ${Python3_ROOT_DIR}/bin/python)
+    if(MSVC)
+        set(Python3_EXECUTABLE ${Python3_ROOT_DIR}/Scripts/python.exe)
+    endif()
+endif()
+
 if(OGS_USE_PYTHON)
-    find_package(Python3 COMPONENTS Interpreter Development REQUIRED)
+    find_package(Python3 ${ogs.minimum_version.python} COMPONENTS Interpreter Development REQUIRED)
 else()
-    find_package(Python3 COMPONENTS Interpreter)
+    find_package(Python3 ${ogs.minimum_version.python} COMPONENTS Interpreter)
+endif()
+if(POETRY)
+    if(MSVC)
+        file(TO_NATIVE_PATH "${Python3_ROOT_DIR}/Lib/site-packages"
+            Python3_VIRTUALENV_SITEPACKAGES)
+        string(REPLACE "\\" "\\\\" Python3_VIRTUALENV_SITEPACKAGES
+            ${Python3_VIRTUALENV_SITEPACKAGES})
+    else()
+        set(Python3_VIRTUALENV_SITEPACKAGES
+            ${Python3_ROOT_DIR}/lib/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages)
+    endif()
 endif()
diff --git a/scripts/cmake/ProjectSetup.cmake b/scripts/cmake/ProjectSetup.cmake
index 0432deda6ef1de290902d7ac41d76ba788f37dcb..4b5c27caecc36a9c1f314dda345bbe344255f6dd 100644
--- a/scripts/cmake/ProjectSetup.cmake
+++ b/scripts/cmake/ProjectSetup.cmake
@@ -27,69 +27,3 @@ if(APPLE)
 else()
     set(CMAKE_INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}")
 endif()
-
-if(NOT IS_GIT_REPO)
-    return()
-endif()
-
-if(DEFINED ENV{OGS_VERSION})
-    set(OGS_VERSION $ENV{OGS_VERSION})
-    message(STATUS "OGS VERSION: ${OGS_VERSION} (set via environment)")
-else()
-    # Get version info from Git, implementation based on
-    # https://github.com/tomtom-international/cpp-dependencies
-    execute_process(
-        COMMAND ${GIT_EXECUTABLE} describe --tags --long --dirty --always
-        WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
-        RESULT_VARIABLE DESCRIBE_RESULT
-        OUTPUT_VARIABLE DESCRIBE_STDOUT
-    )
-    if(DESCRIBE_RESULT EQUAL 0)
-        string(STRIP "${DESCRIBE_STDOUT}" DESCRIBE_STDOUT)
-        message(STATUS "Git reported this project's version as '${DESCRIBE_STDOUT}'")
-        if(DESCRIBE_STDOUT MATCHES "^(.*)-(dirty)$")
-          set(DESCRIBE_DIRTY "${CMAKE_MATCH_2}")
-          set(DESCRIBE_STDOUT "${CMAKE_MATCH_1}")
-        endif()
-        if(DESCRIBE_STDOUT MATCHES "^([0-9a-f]+)$")
-          set(DESCRIBE_COMMIT_NAME "${CMAKE_MATCH_1}")
-          set(DESCRIBE_STDOUT "")
-        elseif(DESCRIBE_STDOUT MATCHES "^(.*)-g([0-9a-f]+)$")
-          set(DESCRIBE_COMMIT_NAME "g${CMAKE_MATCH_2}")
-          set(DESCRIBE_STDOUT "${CMAKE_MATCH_1}")
-        endif()
-        if(DESCRIBE_STDOUT MATCHES "^(.*)-([0-9]+)$")
-          set(DESCRIBE_COMMIT_COUNT "${CMAKE_MATCH_2}")
-          set(DESCRIBE_TAG "${CMAKE_MATCH_1}")
-          set(DESCRIBE_STDOUT "")
-        endif()
-
-        set(OGS_VERSION ${DESCRIBE_TAG})
-        if(DESCRIBE_COMMIT_COUNT GREATER 0)
-          set(OGS_VERSION "${OGS_VERSION}-${DESCRIBE_COMMIT_COUNT}-${DESCRIBE_COMMIT_NAME}")
-        endif()
-
-        if(DESCRIBE_DIRTY)
-          string(TIMESTAMP DESCRIBE_DIRTY_TIMESTAMP "%Y%m%d%H%M%S" UTC)
-          set(OGS_VERSION "${OGS_VERSION}.dirty.${DESCRIBE_DIRTY_TIMESTAMP}")
-        endif()
-        message(STATUS "OGS VERSION: ${OGS_VERSION}")
-    else()
-        message(WARNING "Git repository contains no tags! Please run: git fetch --tags")
-    endif()
-endif()
-
-# Get git commit
-execute_process(
-    COMMAND ${GIT_EXECUTABLE} log -1 --format=%H
-    WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
-    OUTPUT_VARIABLE GIT_SHA1
-    OUTPUT_STRIP_TRAILING_WHITESPACE
-)
-
-execute_process(
-    COMMAND ${GIT_EXECUTABLE} log -1 --format=%h
-    WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
-    OUTPUT_VARIABLE GIT_SHA1_SHORT
-    OUTPUT_STRIP_TRAILING_WHITESPACE
-)
diff --git a/scripts/cmake/test/AddTest.cmake b/scripts/cmake/test/AddTest.cmake
index e47de17eca97b8f3ac63138abf2d159cb15d6381..fb18a77ae8509550c72c054ca1c1b7f43ef0d9e5 100644
--- a/scripts/cmake/test/AddTest.cmake
+++ b/scripts/cmake/test/AddTest.cmake
@@ -15,9 +15,11 @@
 #   REQUIREMENTS # optional simple boolean expression which has to be true to
 #                  enable the test, e.g.
 #                  OGS_USE_PETSC AND (OGS_USE_EIGEN OR OGS_USE_LIS)
+#   PYTHON_PACKAGES package_x=1.2.3 package_y=0.1.x # optional
 #   VIS <vtu output file(s)> # optional for documentation
 #   RUNTIME <in seconds> # optional for optimizing ctest duration
 #                          values should be taken from envinf job
+#   WORKING_DIRECTORY # optional, specify the working directory of the test
 #   DISABLED # optional, disables the test
 # )
 #
@@ -45,8 +47,8 @@ function (AddTest)
 
     # parse arguments
     set(options DISABLED)
-    set(oneValueArgs EXECUTABLE PATH NAME WRAPPER TESTER ABSTOL RELTOL RUNTIME DEPENDS)
-    set(multiValueArgs EXECUTABLE_ARGS DATA DIFF_DATA WRAPPER_ARGS REQUIREMENTS VIS)
+    set(oneValueArgs EXECUTABLE PATH NAME WRAPPER TESTER ABSTOL RELTOL RUNTIME DEPENDS WORKING_DIRECTORY)
+    set(multiValueArgs EXECUTABLE_ARGS DATA DIFF_DATA WRAPPER_ARGS REQUIREMENTS PYTHON_PACKAGES VIS)
     cmake_parse_arguments(AddTest "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
 
 
@@ -66,9 +68,13 @@ function (AddTest)
     if (NOT DEFINED AddTest_RUNTIME)
         set(AddTest_RUNTIME 1)
     endif()
+    if(NOT DEFINED AddTest_WORKING_DIRECTORY)
+        set(AddTest_WORKING_DIRECTORY ${AddTest_BINARY_PATH})
+    endif()
 
     if("${AddTest_EXECUTABLE}" STREQUAL "ogs")
-        set(AddTest_EXECUTABLE_ARGS -o ${AddTest_BINARY_PATH_NATIVE} ${AddTest_EXECUTABLE_ARGS})
+        set(AddTest_EXECUTABLE_ARGS -o ${AddTest_BINARY_PATH_NATIVE}
+            ${AddTest_SOURCE_PATH}/${AddTest_EXECUTABLE_ARGS})
     endif()
 
     if(${AddTest_RUNTIME} GREATER ${LARGE_RUNTIME})
@@ -251,24 +257,18 @@ Use six arguments version of AddTest with absolute and relative tolerances")
         COMMAND ${CMAKE_COMMAND}
         -DEXECUTABLE=${AddTest_EXECUTABLE_PARSED}
         "-DEXECUTABLE_ARGS=${AddTest_EXECUTABLE_ARGS}" # Quoted because passed as list
-        -DSOURCE_PATH=${AddTest_SOURCE_PATH}           # see https://stackoverflow.com/a/33248574/80480
+                                                       # see https://stackoverflow.com/a/33248574/80480
         -DBINARY_PATH=${AddTest_BINARY_PATH}
         -DWRAPPER_COMMAND=${WRAPPER_COMMAND}
         "-DWRAPPER_ARGS=${AddTest_WRAPPER_ARGS}"
         "-DFILES_TO_DELETE=${FILES_TO_DELETE}"
         -DPython3_EXECUTABLE=${Python3_EXECUTABLE}
+        -DWORKING_DIRECTORY=${AddTest_WORKING_DIRECTORY}
         -P ${PROJECT_SOURCE_DIR}/scripts/cmake/test/AddTestWrapper.cmake
     )
     if(DEFINED AddTest_DEPENDS)
         set_tests_properties(${TEST_NAME} PROPERTIES DEPENDS ${AddTest_DEPENDS})
     endif()
-    if(EXISTS ${AddTest_SOURCE_PATH}/requirements.txt)
-        set(PYTHONPATH "${AddTest_BINARY_PATH}/.venv/lib/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages")
-        if(WIN32)
-            set(PYTHONPATH "${AddTest_BINARY_PATH}/.venv/Lib/site-packages")
-        endif()
-        set_tests_properties(${TEST_NAME} PROPERTIES ENVIRONMENT "PYTHONPATH=${PYTHONPATH}")
-    endif()
     if(DEFINED MPI_PROCESSORS)
         set_tests_properties(${TEST_NAME} PROPERTIES PROCESSORS ${MPI_PROCESSORS})
     endif()
@@ -285,6 +285,20 @@ Use six arguments version of AddTest with absolute and relative tolerances")
         add_dependencies(ctest-large ${AddTest_EXECUTABLE})
     endif()
 
+    if(AddTest_PYTHON_PACKAGES)
+        if(POETRY)
+            execute_process(
+                COMMAND ${CMD_COMMAND} poetry add ${AddTest_PYTHON_PACKAGES}
+                WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
+            )
+        else()
+            message(STATUS "Warning: Benchmark ${AddTest_NAME} requires these "
+                "Python packages: ${AddTest_PYTHON_PACKAGES}!\n Make sure to "
+                "have them installed in your current Python environment OR "
+                "install the Poetry package manager for Python!")
+        endif()
+    endif()
+
     if(NOT AddTest_TESTER OR OGS_COVERAGE)
         return()
     endif()
diff --git a/scripts/cmake/test/AddTestWrapper.cmake b/scripts/cmake/test/AddTestWrapper.cmake
index 9152c9f0924e14ed0978a3e41ed72e78ffdb7bb4..c8d801d698c2ea770827073aa027dd341ba8c3a0 100644
--- a/scripts/cmake/test/AddTestWrapper.cmake
+++ b/scripts/cmake/test/AddTestWrapper.cmake
@@ -4,26 +4,13 @@ foreach(FILE ${FILES_TO_DELETE})
     file(REMOVE ${BINARY_PATH}/${FILE})
 endforeach()
 
-# Create Python virtual environment and install packages
-set(PIP .venv/bin/pip)
-if(WIN32)
-    set(PIP .venv/Scripts/pip.exe)
-endif()
-if(EXISTS ${SOURCE_PATH}/requirements.txt AND NOT EXISTS ${BINARY_PATH}/${PIP})
-    message(STATUS "Generating Python virtual environment...")
-    execute_process(
-        COMMAND virtualenv --python ${Python3_EXECUTABLE} .venv
-        WORKING_DIRECTORY ${BINARY_PATH})
-endif()
-if(EXISTS ${SOURCE_PATH}/requirements.txt)
-    execute_process(
-        COMMAND ${PIP} install -r ${SOURCE_PATH}/requirements.txt
-        WORKING_DIRECTORY ${BINARY_PATH})
-endif()
+string(REPLACE ";" " " CMD_STRING "cd ${WORKING_DIRECTORY} && ${WRAPPER_COMMAND} "
+"${WRAPPER_ARGS} ${EXECUTABLE} ${EXECUTABLE_ARGS}")
+message(STATUS "Test command cleaned:\n${CMD_STRING}")
 
 execute_process(
     COMMAND ${WRAPPER_COMMAND} ${WRAPPER_ARGS} ${EXECUTABLE} ${EXECUTABLE_ARGS}
-    WORKING_DIRECTORY ${SOURCE_PATH}
+    WORKING_DIRECTORY ${WORKING_DIRECTORY}
     RESULT_VARIABLE EXIT_CODE
     OUTPUT_VARIABLE OUTPUT
     ERROR_VARIABLE OUTPUT
diff --git a/scripts/cmake/test/MeshTest.cmake b/scripts/cmake/test/MeshTest.cmake
index e0a61410d203155013965f1dc9981a0adc16c827..aaa6fc21cabd17c443dfdd81fbef991aa16e1bb3 100644
--- a/scripts/cmake/test/MeshTest.cmake
+++ b/scripts/cmake/test/MeshTest.cmake
@@ -22,7 +22,7 @@ function (MeshTest)
     endif()
     # parse arguments
     set(options NONE)
-    set(oneValueArgs EXECUTABLE PATH NAME WRAPPER RUNTIME)
+    set(oneValueArgs EXECUTABLE PATH NAME WRAPPER RUNTIME WORKING_DIRECTORY)
     set(multiValueArgs EXECUTABLE_ARGS DATA DIFF_DATA WRAPPER_ARGS REQUIREMENTS)
     cmake_parse_arguments(MeshTest "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
 
@@ -40,6 +40,9 @@ function (MeshTest)
     if (NOT DEFINED MeshTest_RUNTIME)
         set(MeshTest_RUNTIME 1)
     endif()
+    if(NOT DEFINED MeshTest_WORKING_DIRECTORY)
+        set(MeshTest_WORKING_DIRECTORY ${MeshTest_BINARY_PATH})
+    endif()
 
     # --- Implement wrappers ---
     # check requirements, disable if not met
@@ -140,12 +143,13 @@ function (MeshTest)
         COMMAND ${CMAKE_COMMAND}
         -DEXECUTABLE=${MeshTest_EXECUTABLE_PARSED}
         "-DEXECUTABLE_ARGS=${MeshTest_EXECUTABLE_ARGS}" # Quoted because passed as list
-        -DSOURCE_PATH=${MeshTest_SOURCE_PATH}             # see https://stackoverflow.com/a/33248574/80480
+                                                        # see https://stackoverflow.com/a/33248574/80480
         -DBINARY_PATH=${MeshTest_BINARY_PATH}
         -DWRAPPER_COMMAND=${WRAPPER_COMMAND}
         "-DWRAPPER_ARGS=${MeshTest_WRAPPER_ARGS}"
         "-DFILES_TO_DELETE=${FILES_TO_DELETE}"
         -DSTDOUT_FILE_PATH=${MeshTest_STDOUT_FILE_PATH}
+        -DWORKING_DIRECTORY=${MeshTest_WORKING_DIRECTORY}
         -P ${PROJECT_SOURCE_DIR}/scripts/cmake/test/AddTestWrapper.cmake
     )
     set_tests_properties(${TEST_NAME} PROPERTIES COST ${MeshTest_RUNTIME})
diff --git a/scripts/python/poetry.in.toml b/scripts/python/poetry.in.toml
new file mode 100644
index 0000000000000000000000000000000000000000..ab1033bd37224ee84b5862fb25f094db73809b74
--- /dev/null
+++ b/scripts/python/poetry.in.toml
@@ -0,0 +1,2 @@
+[virtualenvs]
+in-project = true
diff --git a/scripts/python/pyproject.in.toml b/scripts/python/pyproject.in.toml
new file mode 100644
index 0000000000000000000000000000000000000000..0b6bb77dbe4159e826e1be0a682ec4ffd847188f
--- /dev/null
+++ b/scripts/python/pyproject.in.toml
@@ -0,0 +1,14 @@
+[tool.poetry]
+name = "ogs-build"
+version = "@OGS_VERSION@" # is written just once, maybe outdated
+description = "CMake auto-generated"
+authors = [""]
+
+[tool.poetry.dependencies]
+python = "^@ogs.minimum_version.python@, <3.9" # TODO: <3.9 is a tespy requirement
+
+[tool.poetry.dev-dependencies]
+
+[build-system]
+requires = ["poetry>=0.12"]
+build-backend = "poetry.masonry.api"
diff --git a/web/content/docs/devguide/advanced/python-env.md b/web/content/docs/devguide/advanced/python-env.md
new file mode 100644
index 0000000000000000000000000000000000000000..3442b24d7ee5d80ef9dfd93c9f1d8e57fbc83bd8
--- /dev/null
+++ b/web/content/docs/devguide/advanced/python-env.md
@@ -0,0 +1,80 @@
++++
+date = "2020-10-05T15:16:13+01:00"
+title = "Python environment"
+author = "Lars Bilke"
+weight = 1030
+
+[menu]
+  [menu.devguide]
+    parent = "advanced"
++++
+
+In OGS we make us of Python packages at different stages, e.g.:
+
+- [Conan]({{< ref "conan.md" >}}) at configure-time to install third-party dependencies
+- [ogs-container-maker](https://gitlab.opengeosys.org/ogs/container-maker) when the CI prepares its environment
+- [TESPy]({{< ref "3d_3bhes_array.md#tespy" >}}) for simulating thermal engineering plants in a benchmark
+- [pvpython](https://kitware.github.io/paraview-docs/latest/python/) for pre- and post-processing
+- ...
+
+Python packages are usually installed via `pip` and `virtualenv` provides an isolated environment to install these packages too.
+
+## Poetry
+
+We make use of [poetry](https://python-poetry.org) which is a wrapper for `pip` and `virtualenv`. We recommend to install `poetry` with your system package manager or:
+
+<div class='win'>
+
+```ps
+(Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python -
+```
+
+</div>
+
+<div class='linux'>
+
+```bash
+curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
+```
+
+</div>
+
+<div class='mac'>
+
+```bash
+curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
+```
+
+</div>
+
+When configuring with CMake `poetry` creates a new virtual environment in the `.venv`-directory inside your build directory. It will also install required Python packages into this environment. You can see the current environment definition in the file `pyproject.toml` inside your build-directory.
+
+To manually add Python packages run the following inside your build-directory:
+
+```bash
+poetry add python-package-name # e.g. poetry add numpy
+```
+
+To activate the environment run `poetry shell` (this opens a new shell) or for one-off commands run `poetry run command`, e.g. `poetry run pip list`.
+
+### Poetry & Conan
+
+You can also install Conan with Poetry (so you don't need to install it system-wide) with the CMake-option `OGS_USE_CONAN=auto`:
+
+```bash
+cmake ../ogs -DOGS_USE_CONAN=auto
+```
+
+### Poetry & Benchmarks
+
+You can use the argument `PYTHON_PACKAGES` on `AddTest()` to specifiy additional Python package dependencies.
+
+The following example would install the latest version of `numpy` and `pandas` version 0.1.2:
+
+```cmake
+AddTest(
+    ...
+    PYTHON_PACKAGES numpy pandas=0.1.2
+    ...
+)
+```
diff --git a/web/content/docs/devguide/getting-started/prerequisites.md b/web/content/docs/devguide/getting-started/prerequisites.md
index 8c9ad0f811894fa989a7c1f57ec0816e1cd995d9..8169c0b727a3cea91c6b026448ca23f163d8bef2 100644
--- a/web/content/docs/devguide/getting-started/prerequisites.md
+++ b/web/content/docs/devguide/getting-started/prerequisites.md
@@ -23,7 +23,7 @@ The minimum prerequisites to build OGS are:
 
 ### Note about skipping installation steps
 
-A fresh system with none of the prerequisites fulfilled is assumed. Skipping installation steps or using a non-supported version might result in unexpected problems. If possible, you may consider reinstalling or manual modifying the configuration of the already installed tool. 
+A fresh system with none of the prerequisites fulfilled is assumed. Skipping installation steps or using a non-supported version might result in unexpected problems. If possible, you may consider reinstalling or manual modifying the configuration of the already installed tool.
 
 </div>
 
@@ -275,3 +275,9 @@ Check on a newly opened command line if Conan was installed successfully:
 $ conan --version
 Conan version {{< dataFile "versions.minimum_version.conan" >}}
 ```
+
+<div class='note'>
+
+**Advanced usage:** You can also have Conan auto-installed when using the CMake-option `OGS_USE_CONAN=auto`. See the page on [Python environment]({{< ref "python-env.md" >}}) for details.
+
+</div>
diff --git a/web/data/versions.json b/web/data/versions.json
index 7d7298131316a1bb51aa9c2463066bf019ed9097..66796e47d7e95c9ca946840d3747d8a1f150a250 100644
--- a/web/data/versions.json
+++ b/web/data/versions.json
@@ -16,7 +16,8 @@
     "eigen": "3.3.4",
     "vtk": "8.1.2",
     "petsc": "3.11.2",
-    "qt": "5.12.4"
+    "qt": "5.12.4",
+    "python": "3.7"
   },
   "tested_version": {
     "vtk": "8.2.0",