diff --git a/Applications/Utils/Tests.cmake b/Applications/Utils/Tests.cmake
index c2763c777f46c882c3680093921c93d4fd0c5773..a41af36e14bb7497c6cecd6ab704f6c773335d7c 100644
--- a/Applications/Utils/Tests.cmake
+++ b/Applications/Utils/Tests.cmake
@@ -10,19 +10,16 @@ AddTest(
     DIFF_DATA Ammer-Rivers-Mapped.gml
 )
 
-# Disable test on eve frontends
-if("${HOSTNAME}" MATCHES "envinf1")
-    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 -a -o ${Data_BINARY_DIR}/MeshGeoToolsLib/Bode/BodeEZG_Fliessgewaesser-Mapped.gml
-        REQUIREMENTS NOT OGS_USE_MPI
-        TESTER diff
-        DIFF_DATA BodeEZG_Fliessgewaesser-Mapped.gml
-    )
-endif()
+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 -a -o ${Data_BINARY_DIR}/MeshGeoToolsLib/Bode/BodeEZG_Fliessgewaesser-Mapped.gml
+    REQUIREMENTS NOT OGS_USE_MPI
+    TESTER gmldiff
+    DIFF_DATA BodeEZG_Fliessgewaesser-Mapped.gml 1e-10 1e-10
+)
 
 AddTest(
     NAME MapGeometryToMeshSurface_Naegelstedt
diff --git a/scripts/cmake/test/AddTest.cmake b/scripts/cmake/test/AddTest.cmake
index e6b5715319da861032e61b7852a41afbe026410e..08831a63f9363264b64b49f4a23914f7ad86cdd5 100644
--- a/scripts/cmake/test/AddTest.cmake
+++ b/scripts/cmake/test/AddTest.cmake
@@ -11,7 +11,7 @@
 #   EXECUTABLE_ARGS <arguments>
 #   WRAPPER <time|memcheck|callgrind|mpirun> # optional
 #   WRAPPER_ARGS <arguments> # optional
-#   TESTER <diff|vtkdiff|memcheck> # optional
+#   TESTER <diff|vtkdiff|gmldiff|memcheck> # optional
 #   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)
@@ -40,6 +40,12 @@
 #         Searches for all matching files in the working directory (PATH).
 #         Matched files are then compared against files with the same name in
 #         the benchmark output directory.
+#
+#   gmldiff-tester
+#     - DIFF_DATA
+#         <gml file> <absolute tolerance> <relative tolerance>
+#         Can be given multiple times; the point coordinates in the gml files are
+#         compared using the given absolute and relative tolerances.
 
 function (AddTest)
 
@@ -142,6 +148,9 @@ function (AddTest)
     if(AddTest_TESTER STREQUAL "xdmfdiff" AND NOT TARGET xdmfdiff)
         return()
     endif()
+    if(AddTest_TESTER STREQUAL "gmldiff" AND NOT ${Python3_Interpreter_FOUND})
+        return()
+    endif()
     if(AddTest_TESTER STREQUAL "memcheck" AND NOT GREP_TOOL_PATH)
         return()
     endif()
@@ -236,7 +245,25 @@ Use six arguments version of AddTest with absolute and relative tolerances")
         else ()
             message(FATAL_ERROR "For vtkdiff tester the number of diff data arguments must be a multiple of six.")
         endif()
-    elseif(tester STREQUAL "memcheck")
+    elseif(AddTest_TESTER STREQUAL "gmldiff")
+        list(LENGTH AddTest_DIFF_DATA DiffDataLength)
+        math(EXPR DiffDataLastIndex "${DiffDataLength}-1")
+        foreach(DiffDataIndex RANGE 0 ${DiffDataLastIndex} 3)
+            list(GET AddTest_DIFF_DATA "${DiffDataIndex}" GML_FILE)
+            math(EXPR DiffDataAuxIndex "${DiffDataIndex}+1")
+            list(GET AddTest_DIFF_DATA "${DiffDataAuxIndex}" ABS_TOL)
+            math(EXPR DiffDataAuxIndex "${DiffDataIndex}+2")
+            list(GET AddTest_DIFF_DATA "${DiffDataAuxIndex}" REL_TOL)
+
+            get_filename_component(FILE_EXPECTED ${GML_FILE} NAME)
+            list(APPEND TESTER_COMMAND
+                "${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/scripts/test/gmldiff.py \
+                --abs ${ABS_TOL} --rel ${REL_TOL} \
+                ${TESTER_ARGS} \
+                ${AddTest_SOURCE_PATH}/${FILE_EXPECTED} \
+                ${AddTest_BINARY_PATH}/${GML_FILE}")
+        endforeach()
+    elseif(AddTest_TESTER STREQUAL "memcheck")
         set(TESTER_COMMAND "! ${GREP_TOOL_PATH} definitely ${AddTest_SOURCE_PATH}/${AddTest_NAME}_memcheck.log")
     endif()
 
diff --git a/scripts/test/gmldiff.py b/scripts/test/gmldiff.py
new file mode 100644
index 0000000000000000000000000000000000000000..94108301404781e97c82703e1edb2d92a566a236
--- /dev/null
+++ b/scripts/test/gmldiff.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org)
+#            Distributed under a Modified BSD License.
+#              See accompanying file LICENSE.txt or
+#              http://www.opengeosys.org/project/license
+
+from xml.dom import minidom
+import argparse
+import math
+
+parser = argparse.ArgumentParser(description="Diff OpenGeoSys GML files.")
+parser.add_argument(
+    "gmls", metavar="gml", type=str, nargs=2, help="Two gml files to compare"
+)
+parser.add_argument("--abs", type=float, default=1e-16)
+parser.add_argument("--rel", type=float, default=1e-16)
+
+args = parser.parse_args()
+
+docA = minidom.parse(args.gmls[0])
+docB = minidom.parse(args.gmls[1])
+
+name = docA.getElementsByTagName("name")[0]
+print(
+    f"Comparing gml with name '{name.firstChild.data}', abs={args.abs}, rel={args.rel}"
+)
+
+pointsA = docA.getElementsByTagName("point")
+pointsB = docB.getElementsByTagName("point")
+
+if len(pointsA) != len(pointsB):
+    print("Mismatch of number of points!")
+    exit(1)
+
+for pointA, pointB in zip(pointsA, pointsB):
+    if int(pointA.getAttribute("id")) != int(pointB.getAttribute("id")):
+        print("Points do not have the same order!")
+        exit(1)
+
+    for dim in ["x", "y", "z"]:
+        a = float(pointA.getAttribute(dim))
+        b = float(pointB.getAttribute(dim))
+        if not math.isclose(
+            a,
+            b,
+            rel_tol=args.rel,
+            abs_tol=args.abs,
+        ):
+            print(
+                f"Point with id={pointA.getAttribute('id')} differ: abs={abs(a - b)}, rel={abs(a - b) / b}"
+            )
+            exit(1)
+
+polysA = docA.getElementsByTagName("polyline")
+polysB = docB.getElementsByTagName("polyline")
+
+if len(polysA) != len(polysB):
+    print("Mismatch of number of polylines!")
+    exit(1)
+
+for polyA, polyB in zip(polysA, polysB):
+    if int(polyA.getAttribute("id")) != int(polyB.getAttribute("id")):
+        print("Polylines do not have the same order!")
+        exit(1)
+
+    pntsA = polyA.getElementsByTagName("pnt")
+    pntsB = polyA.getElementsByTagName("pnt")
+    for pntA, pntB in zip(pntsA, pntsB):
+        if int(pntA.childNodes[0].nodeValue) != int(pntB.childNodes[0].nodeValue):
+            print(f"Polyline with id={polyA.getAttribute('id')} differ!")
+            exit(1)
+
+exit(0)