diff --git a/.gitignore b/.gitignore
index 3aeb7b4cfdd3c03331af284813bfa9ac1dd5ad91..e4510e26f8a8144c334a2482ce947d5619da9ec5 100755
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,6 @@ classes/
 *.iml
 /.idea/
 .vscode/settings.json
+selenium/.pytest_cache
+selenium/.venv
+selenium/tests/__pycache__
diff --git a/Dockerfile b/Dockerfile
index 48c2c4e70cca1c5b90523c6f5d84e02cf379e621..9c7a35ff737b36c51ac70f1be1835a267f76d7f4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,7 +4,7 @@
 
 
 # the first stage of our build will use a maven 3.6 parent image
-FROM maven:3.6-openjdk-11 AS MAVEN_BUILD
+FROM maven:3.8-openjdk-11 AS MAVEN_BUILD
 
 # We need VIPSCommon too, so the source code must be available in the parent folder
 COPY ./VIPSCommon ./VIPSCommon
diff --git a/pom.xml b/pom.xml
index 481a18f4c0a0909aac1f29afc41afb1d4138f062..c7d009dc54fe4258a51dbea6f6ceba2844483759 100755
--- a/pom.xml
+++ b/pom.xml
@@ -9,7 +9,6 @@
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
-        <geotools.version>20.2</geotools.version>
     </properties>
     <name>VIPSLogic</name>
     <url>http://maven.apache.org</url>
@@ -60,30 +59,15 @@
             <version>2.3.1</version>
             <scope>compile</scope>
         </dependency>
-        <dependency>
-            <groupId>edu.ucar</groupId>
-            <artifactId>cdm</artifactId>
-            <version>4.6.10</version>
-        </dependency>
         <dependency>
             <groupId>org.flywaydb</groupId>
             <artifactId>flyway-core</artifactId>
-            <version>8.3.0</version>
+            <version>10.4.1</version>
         </dependency>
         <dependency>
-            <groupId>org.hibernate</groupId>
-            <artifactId>hibernate-spatial</artifactId>
-            <version>5.6.3.Final</version>
-            <exclusions>
-                <exclusion>
-                    <artifactId>postgresql</artifactId>
-                    <groupId>postgresql</groupId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.dom4j</groupId>
-                    <artifactId>dom4j</artifactId>
-                </exclusion>
-            </exclusions>
+            <groupId>org.flywaydb</groupId>
+            <artifactId>flyway-database-postgresql</artifactId>
+            <version>10.4.1</version>
         </dependency>
         <dependency>
             <groupId>javax.servlet</groupId>
@@ -94,19 +78,19 @@
         <dependency>
             <groupId>org.jboss.resteasy</groupId>
             <artifactId>resteasy-client</artifactId>
-            <version>4.7.2.Final</version>
+            <version>4.7.9.Final</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.jboss.resteasy</groupId>
             <artifactId>resteasy-core</artifactId>
-            <version>4.7.2.Final</version>
+            <version>4.7.9.Final</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.jboss.resteasy</groupId>
             <artifactId>resteasy-jackson2-provider</artifactId>
-            <version>4.7.2.Final</version>
+            <version>4.7.9.Final</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -118,14 +102,14 @@
         <dependency>
             <groupId>org.apache.httpcomponents</groupId>
             <artifactId>httpclient</artifactId>
-            <version>4.5.13</version>
+            <version>4.5.14</version>
             <type>jar</type>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
-            <version>4.10</version>
+            <version>4.13.2</version>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -139,39 +123,32 @@
             <artifactId>JavaAPIforKml</artifactId>
             <version>2.2.1</version>
         </dependency>
-        <!-- https://mvnrepository.com/artifact/net.postgis/postgis-jdbc -->
-        <dependency>
-            <groupId>net.postgis</groupId>
-            <artifactId>postgis-jdbc</artifactId>
-            <version>2.5.1</version>
-        </dependency>
-
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-annotations</artifactId>
-            <version>2.13.1</version>
+            <version>2.16.1</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-core</artifactId>
-            <version>2.13.1</version>
+            <version>2.16.1</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
-            <version>2.13.1</version>
+            <version>2.16.1</version>
         </dependency>
         <dependency>
             <groupId>org.passay</groupId>
             <artifactId>passay</artifactId>
-            <version>1.6.1</version>
+            <version>1.6.4</version>
         </dependency>
         <dependency>
             <groupId>org.hibernate</groupId>
             <artifactId>hibernate-core</artifactId>
-            <version>5.6.3.Final</version>
+            <version>5.6.15.Final</version>
             <exclusions>
                 <exclusion>
                     <groupId>org.dom4j</groupId>
@@ -179,39 +156,49 @@
                 </exclusion>
             </exclusions>
         </dependency>
+        <dependency>
+            <groupId>org.hibernate</groupId>
+            <artifactId>hibernate-spatial</artifactId>
+            <version>5.6.15.Final</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>postgresql</artifactId>
+                    <groupId>postgresql</groupId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.dom4j</groupId>
+                    <artifactId>dom4j</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
         <dependency>
             <groupId>org.postgresql</groupId>
             <artifactId>postgresql</artifactId>
-            <version>42.3.1</version>
+            <version>42.7.1</version>
             <scope>provided</scope>
         </dependency>
-        <dependency>
-            <groupId>com.fasterxml.jackson.dataformat</groupId>
-            <artifactId>jackson-dataformat-csv</artifactId>
-            <version>2.13.1</version>
-            <type>jar</type>
-        </dependency>
         <dependency>
             <groupId>com.fasterxml.jackson.datatype</groupId>
             <artifactId>jackson-datatype-jsr310</artifactId>
-            <version>2.13.1</version>
+            <version>2.16.1</version>
             <type>jar</type>
         </dependency>
         <dependency>
             <groupId>org.locationtech.jts</groupId>
             <artifactId>jts-core</artifactId>
-            <version>1.18.2</version>
+            <version>1.19.0</version>
             <type>jar</type>
         </dependency>
         <dependency>
             <groupId>org.wololo</groupId>
             <artifactId>jts2geojson</artifactId>
-            <version>0.16.1</version>
+            <version>0.18.1</version>
         </dependency>
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-csv</artifactId>
-            <version>1.2</version>
+            <version>1.10.0</version>
             <type>jar</type>
         </dependency>
         <dependency>
@@ -236,75 +223,32 @@
         <dependency>
             <groupId>org.freemarker</groupId>
             <artifactId>freemarker</artifactId>
-            <version>2.3.31</version>
+            <version>2.3.32</version>
         </dependency>
         <dependency>
             <groupId>it.sauronsoftware.cron4j</groupId>
             <artifactId>cron4j</artifactId>
             <version>2.2.5</version>
         </dependency>
-        <!--dependency>
-            <groupId>org.openid4java</groupId>
-            <artifactId>openid4java</artifactId>
-            <version>0.9.8</version>
-        </dependency-->
-        <dependency>
-            <groupId>com.google.api-client</groupId>
-            <artifactId>google-api-client</artifactId>
-            <version>1.19.1</version>
-        </dependency>
-        <!--dependency>
-            <groupId>edu.vt.middleware</groupId>
-            <artifactId>vt-password</artifactId>
-            <version>3.1.2</version>
-        </dependency-->
         <dependency>
             <groupId>commons-fileupload</groupId>
             <artifactId>commons-fileupload</artifactId>
-            <version>1.4</version>
+            <version>1.5</version>
         </dependency>
         <dependency>
             <groupId>com.ibm.icu</groupId>
             <artifactId>icu4j</artifactId>
-            <version>70.1</version>
-        </dependency>
-        
-        <!--dependency>
-                <groupId>org.postgresql</groupId>
-                <artifactId>postgresql</artifactId>
-                <version>9.4-1211</version>
-                <scope>provided</scope>
-        </dependency-->
-        <dependency>
-            <groupId>org.openjdk.jol</groupId>
-            <artifactId>jol-core</artifactId>
-            <version>0.16</version>
-        </dependency>
-        <dependency>
-            <groupId>org.geotools</groupId>
-            <artifactId>gt-api</artifactId>
-            <version>20.5</version>
-        </dependency>
-        <dependency>
-            <groupId>org.geotools</groupId>
-            <artifactId>gt-epsg-hsql</artifactId>
-            <version>20.5</version>
+            <version>74.2</version>
         </dependency>
         <dependency>
             <groupId>javax.measure</groupId>
             <artifactId>unit-api</artifactId>
-            <version>1.0</version>
+            <version>2.2</version>
         </dependency>
         <dependency>
             <groupId>com.webcohesion.enunciate</groupId>
             <artifactId>enunciate-core-annotations</artifactId>
-            <version>2.13.3</version>
-        </dependency>
-
-        <dependency>
-            <groupId>com.webcohesion.enunciate</groupId>
-            <artifactId>enunciate-rt-util</artifactId>
-            <version>2.13.3</version>
+            <version>2.17.1</version>
         </dependency>
         <dependency>
             <groupId>com.bedatadriven</groupId>
@@ -314,26 +258,30 @@
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
-            <version>1.7.36</version>
+            <version>2.0.10</version>
             <scope>provided</scope>
         </dependency>
     </dependencies>
 
     <build>
         <plugins>
+            <plugin>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <version>3.6.1</version>
+            </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
-                <version>3.8.1</version>
+                <version>3.12.1</version>
                 <configuration>
-                    <source>11</source>
-                    <target>11</target>
+                    <source>17</source>
+                    <target>17</target>
                 </configuration>
             </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-war-plugin</artifactId>
-                <version>3.3.2</version>
+                <version>3.4.0</version>
                 <configuration>
                     <webResources>
                         <resource>
@@ -358,7 +306,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <version>2.22.2</version>
+                <version>3.2.3</version>
                 <configuration>
                     <argLine>-Xmx6048m</argLine>
                 </configuration>
@@ -366,7 +314,7 @@
             <plugin>
                 <groupId>com.webcohesion.enunciate</groupId>
                 <artifactId>enunciate-maven-plugin</artifactId>
-                <version>2.13.3</version>
+                <version>2.17.1</version>
                 <executions>
                     <execution>
                         <goals>
diff --git a/postman_tests/VIPSLogic tests.postman_collection.json b/postman_tests/VIPSLogic tests.postman_collection.json
index 50c25b51b3357d68033d5796327584748f35deeb..5cba588d2439ebebabbef738ee719b711ee7da38 100644
--- a/postman_tests/VIPSLogic tests.postman_collection.json	
+++ b/postman_tests/VIPSLogic tests.postman_collection.json	
@@ -56,6 +56,54 @@
 					},
 					"response": []
 				},
+				{
+					"name": "Get all observations of Delia radicum for NIBIO",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [],
+						"url": {
+							"raw": "{{vipslogic_url}}/rest/observation/list/filter/1?pestId=129&from=2010-01-01&to=2024-01-01",
+							"host": [
+								"{{vipslogic_url}}"
+							],
+							"path": [
+								"rest",
+								"observation",
+								"list",
+								"filter",
+								"1"
+							],
+							"query": [
+								{
+									"key": "pestId",
+									"value": "129"
+								},
+								{
+									"key": "from",
+									"value": "2010-01-01"
+								},
+								{
+									"key": "to",
+									"value": "2024-01-01"
+								}
+							]
+						}
+					},
+					"response": []
+				},
 				{
 					"name": "Get all observations from an organization as GeoJson",
 					"event": [
@@ -232,6 +280,44 @@
 					},
 					"response": []
 				},
+				{
+					"name": "Get model information",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "eeb6ee57-3540-40a4-9a07-0d9366d6b1e9"
+							}
+						],
+						"url": {
+							"raw": "{{vipslogic_url}}/rest/model/PSILARTEMP",
+							"host": [
+								"{{vipslogic_url}}"
+							],
+							"path": [
+								"rest",
+								"model",
+								"PSILARTEMP"
+							]
+						}
+					},
+					"response": []
+				},
 				{
 					"name": "List weather stations for one organization as KML",
 					"event": [
@@ -386,6 +472,170 @@
 					},
 					"response": []
 				},
+				{
+					"name": "List pests for crop",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "eeb6ee57-3540-40a4-9a07-0d9366d6b1e9"
+							}
+						],
+						"url": {
+							"raw": "{{vipslogic_url}}/rest/organism/croppest/15",
+							"host": [
+								"{{vipslogic_url}}"
+							],
+							"path": [
+								"rest",
+								"organism",
+								"croppest",
+								"15"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "List crop categories for an organization",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "eeb6ee57-3540-40a4-9a07-0d9366d6b1e9"
+							}
+						],
+						"url": {
+							"raw": "{{vipslogic_url}}/rest/organism/cropcategory/1",
+							"host": [
+								"{{vipslogic_url}}"
+							],
+							"path": [
+								"rest",
+								"organism",
+								"cropcategory",
+								"1"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "List organizations",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "eeb6ee57-3540-40a4-9a07-0d9366d6b1e9"
+							}
+						],
+						"url": {
+							"raw": "{{vipslogic_url}}/rest/organization",
+							"host": [
+								"{{vipslogic_url}}"
+							],
+							"path": [
+								"rest",
+								"organization"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "List all messages for an organization",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "eeb6ee57-3540-40a4-9a07-0d9366d6b1e9"
+							}
+						],
+						"url": {
+							"raw": "{{vipslogic_url}}/rest/message/list/1?publishedFrom=2022-01-01&publishedTo=2022-12-31",
+							"host": [
+								"{{vipslogic_url}}"
+							],
+							"path": [
+								"rest",
+								"message",
+								"list",
+								"1"
+							],
+							"query": [
+								{
+									"key": "publishedFrom",
+									"value": "2022-01-01"
+								},
+								{
+									"key": "publishedTo",
+									"value": "2022-12-31"
+								}
+							]
+						}
+					},
+					"response": []
+				},
 				{
 					"name": "Get all results for one forecast",
 					"event": [
@@ -578,6 +828,137 @@
 					},
 					"response": []
 				},
+				{
+					"name": "Get forecast results aggregate as KML",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"pm.test(\"Status code is 200 or 204\", function () {",
+									"    console.info(pm.response.code)",
+									"    pm.expect(pm.response.code == 200 || pm.response.code == 204).to.eql(true)",
+									"});",
+									""
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "eeb6ee57-3540-40a4-9a07-0d9366d6b1e9"
+							}
+						],
+						"url": {
+							"raw": "{{vipslogic_url}}/rest/forecastresults/aggregate/1",
+							"host": [
+								"{{vipslogic_url}}"
+							],
+							"path": [
+								"rest",
+								"forecastresults",
+								"aggregate",
+								"1"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Get the forecast results from a POI",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"pm.test(\"Status code is 200 or 204\", function () {",
+									"    console.info(pm.response.code)",
+									"    pm.expect(pm.response.code == 200 || pm.response.code == 204).to.eql(true)",
+									"});",
+									""
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "eeb6ee57-3540-40a4-9a07-0d9366d6b1e9"
+							}
+						],
+						"url": {
+							"raw": "{{vipslogic_url}}/rest/forecastresults/latest/poi/10",
+							"host": [
+								"{{vipslogic_url}}"
+							],
+							"path": [
+								"rest",
+								"forecastresults",
+								"latest",
+								"poi",
+								"10"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Get forecast configurations for one org",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "eeb6ee57-3540-40a4-9a07-0d9366d6b1e9"
+							}
+						],
+						"url": {
+							"raw": "{{vipslogic_url}}/rest/forecastconfigurationsincludeorgs/1?from=2021-01-01&to=2021-12-31",
+							"host": [
+								"{{vipslogic_url}}"
+							],
+							"path": [
+								"rest",
+								"forecastconfigurationsincludeorgs",
+								"1"
+							],
+							"query": [
+								{
+									"key": "from",
+									"value": "2021-01-01"
+								},
+								{
+									"key": "to",
+									"value": "2021-12-31"
+								}
+							]
+						}
+					},
+					"response": []
+				},
 				{
 					"name": "Get forecast configurations for given model and season",
 					"event": [
@@ -656,6 +1037,45 @@
 					},
 					"response": []
 				},
+				{
+					"name": "Get KML list of POIs for a given organization",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "eeb6ee57-3540-40a4-9a07-0d9366d6b1e9"
+							}
+						],
+						"url": {
+							"raw": "{{vipslogic_url}}/rest/pois/kml/1",
+							"host": [
+								"{{vipslogic_url}}"
+							],
+							"path": [
+								"rest",
+								"pois",
+								"kml",
+								"1"
+							]
+						}
+					},
+					"response": []
+				},
 				{
 					"name": "Evaluate password (requirements configured with Passay)",
 					"event": [
@@ -736,6 +1156,39 @@
 						}
 					},
 					"response": []
+				},
+				{
+					"name": "Get POI(s) by name",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [],
+						"url": {
+							"raw": "{{vipslogic_url}}/rest/poi/name/Sandefjord",
+							"host": [
+								"{{vipslogic_url}}"
+							],
+							"path": [
+								"rest",
+								"poi",
+								"name",
+								"Sandefjord"
+							]
+						}
+					},
+					"response": []
 				}
 			]
 		},
@@ -935,6 +1388,115 @@
 					"response": []
 				}
 			]
+		},
+		{
+			"name": "ModelFormService",
+			"item": [
+				{
+					"name": "Run SeptoriaHumidityModel",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [],
+						"url": {
+							"raw": "{{vipslogic_url}}/rest/modelform/SEPTORIAHU/runmodel/?organizationId_countryCode=1_NO&weatherStationId=91&dateSpraying1=&dateSpraying2=&dateGs31=2023-05-01&date3rdUpperLeafEmerging=2023-05-11&date2ndUpperLeafEmerging=2023-05-21&dateUpperLeafEmerging=2023-05-31&dateGs75=2023-06-30&thresholdRelativeHumidity=85&thresholdLeafWetness=30&thresholdPrecipitation=0.2&slidingHoursPast=36&slidingHoursAhead=36&thresholdHumidPeriodHours=20&sprayingProtectionDays=10&leafLifeTime=75",
+							"host": [
+								"{{vipslogic_url}}"
+							],
+							"path": [
+								"rest",
+								"modelform",
+								"SEPTORIAHU",
+								"runmodel",
+								""
+							],
+							"query": [
+								{
+									"key": "organizationId_countryCode",
+									"value": "1_NO"
+								},
+								{
+									"key": "weatherStationId",
+									"value": "91"
+								},
+								{
+									"key": "dateSpraying1",
+									"value": ""
+								},
+								{
+									"key": "dateSpraying2",
+									"value": ""
+								},
+								{
+									"key": "dateGs31",
+									"value": "2023-05-01"
+								},
+								{
+									"key": "date3rdUpperLeafEmerging",
+									"value": "2023-05-11"
+								},
+								{
+									"key": "date2ndUpperLeafEmerging",
+									"value": "2023-05-21"
+								},
+								{
+									"key": "dateUpperLeafEmerging",
+									"value": "2023-05-31"
+								},
+								{
+									"key": "dateGs75",
+									"value": "2023-06-30"
+								},
+								{
+									"key": "thresholdRelativeHumidity",
+									"value": "85"
+								},
+								{
+									"key": "thresholdLeafWetness",
+									"value": "30"
+								},
+								{
+									"key": "thresholdPrecipitation",
+									"value": "0.2"
+								},
+								{
+									"key": "slidingHoursPast",
+									"value": "36"
+								},
+								{
+									"key": "slidingHoursAhead",
+									"value": "36"
+								},
+								{
+									"key": "thresholdHumidPeriodHours",
+									"value": "20"
+								},
+								{
+									"key": "sprayingProtectionDays",
+									"value": "10"
+								},
+								{
+									"key": "leafLifeTime",
+									"value": "75"
+								}
+							]
+						}
+					},
+					"response": []
+				}
+			]
 		}
 	]
 }
\ No newline at end of file
diff --git a/selenium/Makefile b/selenium/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..265c51021c31907065b1ee0ba9aaae60774c454d
--- /dev/null
+++ b/selenium/Makefile
@@ -0,0 +1,12 @@
+venv: venv/touchfile
+
+venv/touchfile: requirements.txt
+	python3 -m venv .venv
+	. .venv/bin/activate; python3 -m pip install --upgrade -q pip; pip install -Ur requirements.txt
+	touch .venv/touchfile
+
+test: venv 
+	@echo 'Run selenium tests'
+	. .venv/bin/activate \
+	&& pytest
+
diff --git a/selenium/README.md b/selenium/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..e3a646c5a199283eefa529315b82f58733ae580e
--- /dev/null
+++ b/selenium/README.md
@@ -0,0 +1,7 @@
+# Selenium tests
+
+Ensure application is running locally. Tests expect login form to be available on http://vipslogic/index.html. Run tests like this:
+
+```
+$ make test
+```
diff --git a/selenium/requirements.txt b/selenium/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4b18905fbc889d7ed4c301541ec06b763ed0911d
--- /dev/null
+++ b/selenium/requirements.txt
@@ -0,0 +1,5 @@
+selenium==4.16.0
+pytest
+trio
+pytest-trio
+flake8
\ No newline at end of file
diff --git a/selenium/tests/test_selenium.py b/selenium/tests/test_selenium.py
new file mode 100644
index 0000000000000000000000000000000000000000..45d44fdffd11f0106ad1a5edec3744ae763560da
--- /dev/null
+++ b/selenium/tests/test_selenium.py
@@ -0,0 +1,111 @@
+from selenium import webdriver
+import pytest
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+from selenium.webdriver.support import expected_conditions as EC
+
+# https://github.com/SeleniumHQ/seleniumhq.github.io/blob/trunk/examples/python/README.md
+
+# BROWSER = 'chrome'
+BROWSER = "firefox"
+
+
+@pytest.fixture
+def browser():
+    if BROWSER == "chrome":
+        chrome_options = webdriver.ChromeOptions()
+        chrome_options.add_argument("--headless")
+        driver = webdriver.Chrome(options=chrome_options)
+    elif BROWSER == "firefox":
+        firefox_options = webdriver.FirefoxOptions()
+        firefox_options.headless = False
+        driver = webdriver.Firefox(options=firefox_options)
+    else:
+        raise Exception("Browser must be set to either chrome or firefox")
+
+    driver.get("http://vipslogic/index.html")
+
+    assert driver.title == "Please log in"
+    driver.implicitly_wait(0.5)
+    driver.find_element(by=By.NAME, value="username").send_keys("seleniumtest")
+    driver.find_element(by=By.NAME, value="password").send_keys("seleniumTEST")
+    driver.find_element(by=By.CSS_SELECTOR, value="button").click()
+    assert driver.title == "Welcome to VIPSLogic"
+    yield driver
+    driver.quit()
+
+
+def test_name_of_logged_in_user(browser):
+    link = browser.find_element(By.XPATH, '//a[text()="Selenium Testbruker"]')
+    assert link is not None
+
+
+def test_admin_organisms(browser):
+    browser.find_element(By.XPATH, '//a[text()="Admin"]').click()
+    link = WebDriverWait(browser, 15).until(
+        EC.element_to_be_clickable((By.XPATH, '//a[text()="Organisms"]'))
+    )
+    assert link.get_attribute("href") == "http://vipslogic/organism"
+    link.click()
+    assert browser.title == "Organisms"
+    link = WebDriverWait(browser, 15).until(
+        EC.element_to_be_clickable((By.XPATH, '//a[text()="List all pests"]'))
+    )
+    link.click()
+    assert browser.title == "All pests"
+
+
+def test_admin_scheduling(browser):
+    browser.find_element(By.XPATH, '//a[text()="Admin"]').click()
+    link = WebDriverWait(browser, 10).until(
+        EC.element_to_be_clickable((By.XPATH, '//a[text()="Scheduling"]'))
+    )
+    assert link.get_attribute("href") == "http://vipslogic/scheduling"
+    link.click()
+    assert browser.title == "Scheduling overview"
+
+
+def test_admin_organization_group(browser):
+    browser.find_element(By.XPATH, '//a[text()="Admin"]').click()
+    link = WebDriverWait(browser, 10).until(
+        EC.element_to_be_clickable((By.XPATH, '//a[text()="Organization groups"]'))
+    )
+    assert link.get_attribute("href") == "http://vipslogic/organizationgroup"
+    link.click()
+    assert browser.title == "Organization groups"
+
+
+def test_admin_apple_fruit_moth(browser):
+    browser.find_element(By.XPATH, '//a[text()="Admin"]').click()
+    link = WebDriverWait(browser, 10).until(
+        EC.element_to_be_clickable((By.XPATH, '//a[text()="Apple fruit moth"]'))
+    )
+    assert link.get_attribute("href") == "http://vipslogic/applefruitmoth"
+    link.click()
+    assert browser.title == "Rognebærmøllstasjoner"
+
+
+def test_admin_users(browser):
+    browser.find_element(By.XPATH, '//a[text()="Admin"]').click()
+    link = WebDriverWait(browser, 20).until(
+        EC.element_to_be_clickable((By.XPATH, '//a[text()="Users"]'))
+    )
+    assert link.get_attribute("href") == "http://vipslogic/user"
+    link.click()
+    WebDriverWait(browser, 20).until(
+        EC.visibility_of_element_located((By.XPATH, "//h1[text()='Users']"))
+    )
+    assert browser.title == "Users"
+
+
+def test_admin_forecasts(browser):
+    browser.find_element(By.XPATH, '//a[text()="Admin"]').click()
+    link = WebDriverWait(browser, 20).until(
+        EC.element_to_be_clickable((By.XPATH, '//a[text()="Forecasts"]'))
+    )
+    assert link.get_attribute("href") == "http://vipslogic/forecastConfiguration"
+    link.click()
+    WebDriverWait(browser, 20).until(
+        EC.visibility_of_element_located((By.XPATH, "//h1[text()='Forecasts']"))
+    )
+    assert browser.title == "Forecasts"
diff --git a/src/main/java/no/nibio/vips/logic/controller/servlet/LoginController.java b/src/main/java/no/nibio/vips/logic/controller/servlet/LoginController.java
index f1a606829d626c648e961c3e5bd5836148ff89dc..ba830e6cc3ad88ba3f49b845648b4cdbf4a91898 100755
--- a/src/main/java/no/nibio/vips/logic/controller/servlet/LoginController.java
+++ b/src/main/java/no/nibio/vips/logic/controller/servlet/LoginController.java
@@ -19,13 +19,6 @@
 package no.nibio.vips.logic.controller.servlet;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeRequestUrl;
-import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest;
-import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
-import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload;
-import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
-import com.google.api.client.http.javanet.NetHttpTransport;
-import com.google.api.client.json.jackson2.JacksonFactory;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.UnsupportedEncodingException;
@@ -88,70 +81,9 @@ public class LoginController extends HttpServlet {
         // We remove the session attribute, so it doesn't stick
         request.getSession().removeAttribute(LoginController.RETURN_UUID_PARAMETER_NAME);
         
-        // This means that an OpenId authentication has returned the user to this URL
-        // See code below
-        if(request.getServletPath().contains("oauth2callback"))
-        {
-            // Is it an authorization response?
-            if(request.getParameter("code") != null)
-            {
-                String authorizationCode = request.getParameter("code");
-                // Verify state
-                String storedState = (String) request.getSession().getAttribute("state");
-                String receivedState = request.getParameter("state");
-                if(receivedState == null || storedState == null || ! storedState.equals(receivedState))
-                {
-                    request.setAttribute("errorMessageKey", "invalidcredentials");
-                    request.getRequestDispatcher("/login.ftl").forward(request, response);
-                    return;
-                }
-                
-                // Use code, make request to Google for getting token with user information
-                GoogleTokenResponse tokenResponse = new GoogleAuthorizationCodeTokenRequest(
-                        new NetHttpTransport(), 
-                        new JacksonFactory(),
-                        System.getProperty("no.nibio.vips.logic.GOOGLE_OPENID_CLIENT_ID"),
-                        System.getProperty("no.nibio.vips.logic.GOOGLE_OPENID_CLIENT_SECRET"),
-                        authorizationCode,
-                        Globals.PROTOCOL + "://" + ServletUtil.getServerName(request) + "/oauth2callback" 
-                ).execute();
-                GoogleIdToken idToken = GoogleIdToken.parse(new JacksonFactory(), tokenResponse.getIdToken());
-                Payload payload = idToken.getPayload();
-                
-                // Try to find the user
-                VipsLogicUser user = userBean.getUser(payload.getSubject(), UserAuthenticationType.TYPE_OPENID_GOOGLE);
-                if(user != null)
-                {
-                    request.getSession().setAttribute("user", user);
-                    UUID uUUID = this.handleRememberUser(request, response, user, returnUUID);
-                    if(returnUUID)
-                    {
-                        nextPage += (nextPage.contains("?") ? "&": "?") + "returnUUID=" + uUUID.toString();
-                    }
-                    if(nextPage.indexOf(Globals.PROTOCOL) == 0)
-                    {
-                        System.out.println("nextPage=" + nextPage);
-                        response.sendRedirect(nextPage);
-                    }
-                    else
-                    {
-                        response.sendRedirect(new StringBuilder(Globals.PROTOCOL + "://").append(ServletUtil.getServerName(request)).append(nextPage).toString());
-                    }
-                }
-                else
-                {
-                    // This might be 
-                    // * a new user
-                    // * an existing user logging in with OpenId/Google for the first time.
-                    // Sending user to form asking this question
-                    request.setAttribute("userAuthenticationTypeId", UserAuthenticationType.TYPE_OPENID_GOOGLE);
-                    request.getSession().setAttribute("openId", payload.getSubject());
-                    request.getRequestDispatcher("/registerOpenIdForm.ftl").forward(request, response);
-                }
-            }
-        }
+
         // A log out request
-        else if(request.getServletPath().contains("logout"))
+        if(request.getServletPath().contains("logout"))
         {
             request.getSession().removeAttribute("user");
             Cookie rememberedUser = ServletUtil.getCookie(request, "rememberedUser");
@@ -230,39 +162,6 @@ public class LoginController extends HttpServlet {
 
                 }
             }
-            /*  Login with Google OpenConnect/OAuth2
-                For documentation about how this is done, see:
-                https://developers.google.com/accounts/docs/OAuth2WebServer
-                and https://developers.google.com/accounts/docs/OpenIDConnect 
-                ClientID, ClientSecret, callbacks etc. has been created by logging
-                in to https://console.developers.google.com as tor-einar.skog@nibio.no
-            */
-            else if(userAuthenticationTypeId.equals(UserAuthenticationType.TYPE_OPENID_GOOGLE))
-            {
-                    // configure the return_to URL where your application will receive
-                    // the authentication responses from the OpenID Connect provider
-                    String serverName = ServletUtil.getServerName(request);
-                    String callbackUrl = Globals.PROTOCOL + "://" + serverName + "/oauth2callback" ;
-                    // We store the information about the next page in a session
-                    // as Google does not accept to forward it
-                    request.getSession().setAttribute("nextPage", URLEncoder.encode(nextPage, "UTF-8"));
-                    request.getSession().setAttribute(LoginController.RETURN_UUID_PARAMETER_NAME, returnUUID);
-                    request.getSession().setAttribute("rememberUser", request.getParameter("rememberUser"));
-                    // Token to check for security (avoid man-in-the-middle)
-                    String state = new BigInteger(130, new SecureRandom()).toString(32);
-                    request.getSession().setAttribute("state", state);
-                    String url =
-                        new GoogleAuthorizationCodeRequestUrl(
-                                System.getProperty("no.nibio.vips.logic.GOOGLE_OPENID_CLIENT_ID"),
-                                callbackUrl,
-                                Arrays.asList(
-                                "https://www.googleapis.com/auth/userinfo.email")
-                        )
-                        .setState(state).build();
-                    
-                    // Redirect to Google for authentication
-                    response.sendRedirect(url);
-            }
             // Authentication method not recognized, redirect to standard form
             else
             {
diff --git a/src/main/java/no/nibio/vips/logic/modules/barkbeetle/BarkbeetleBean.java b/src/main/java/no/nibio/vips/logic/modules/barkbeetle/BarkbeetleBean.java
index 94616429a87b91531c992a68b174273bb7d03e12..06e7363b938c4d32745695a4e713c60ad3e33ce7 100644
--- a/src/main/java/no/nibio/vips/logic/modules/barkbeetle/BarkbeetleBean.java
+++ b/src/main/java/no/nibio/vips/logic/modules/barkbeetle/BarkbeetleBean.java
@@ -55,7 +55,6 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 
-import no.nibio.vips.logic.controller.session.SchedulingBean;
 import no.nibio.vips.logic.controller.session.SessionControllerGetter;
 import no.nibio.vips.logic.controller.session.UserBean;
 import no.nibio.vips.logic.entity.Organization;
diff --git a/src/main/java/no/nibio/vips/logic/service/LogicService.java b/src/main/java/no/nibio/vips/logic/service/LogicService.java
index f6802571fb0edd4b9471c3b55e211935922e8be1..e4d7b5d0f7fe06aed8b1b0e81de170acda3a840d 100755
--- a/src/main/java/no/nibio/vips/logic/service/LogicService.java
+++ b/src/main/java/no/nibio/vips/logic/service/LogicService.java
@@ -1259,8 +1259,8 @@ public class LogicService {
     
     /**
      * 
-     * @param cropOrganismId
-     * @return 
+     * @param cropOrganismId ID of the crop
+     * @return list of pests associated with the given crop
      */
     @GET
     @Path("organism/croppest/{cropOrganismId}")
@@ -1283,7 +1283,7 @@ public class LogicService {
     /**
      * 
      * @param organizationId
-     * @return 
+     * @return List of crop categories for a given organization
      */
     @GET
     @Path("organism/cropcategory/{organizationId}")
diff --git a/src/main/java/no/nibio/vips/logic/service/ModelFormService.java b/src/main/java/no/nibio/vips/logic/service/ModelFormService.java
index c4726bdb2604e0a585d443614029e6fe17c60307..c1c739baa829e0672830c473feb08357555712f5 100644
--- a/src/main/java/no/nibio/vips/logic/service/ModelFormService.java
+++ b/src/main/java/no/nibio/vips/logic/service/ModelFormService.java
@@ -195,7 +195,6 @@ public class ModelFormService {
             ModelConfiguration mConf = new SeptoriaHumidityModelPreprocessor().getModelConfiguration(fConf);
 
             Integer VIPSCoreUserId = organization.getDefaultVipsCoreUserId();
-            System.out.println("defaultVIPScoreUserId = " + VIPSCoreUserId);
 
             List<Result>results = forecastBean.runForecast(mConf, VIPSCoreUserId);
 
diff --git a/src/main/webapp/templates/login.ftl b/src/main/webapp/templates/login.ftl
index f25f1444044893dc65d987002b902411ee0b8347..e0a5e522c0483945bd4bed81dbde6167f169fdd4 100755
--- a/src/main/webapp/templates/login.ftl
+++ b/src/main/webapp/templates/login.ftl
@@ -49,14 +49,6 @@
 		<a href="/user?action=registerNewUserForm&userAuthenticationTypeId=1">${i18nBundle.registerNewUser}</a><br/>
 		<a href="/user?action=resetPasswordRequestForm">${i18nBundle.forgottenPassword}</a>
 	</form>
-	<form class="form-signin" action="/loginsubmit" method="POST">
-		<input type="hidden" name="userAuthenticationTypeId" value="3"/>
-		<input type="hidden" name="nextPage" value="${nextPage!"/"}"/>
-		<input type="hidden" name="returnUUID" value="<#if returnUUID?has_content>${returnUUID?c!""}</#if>"/>
-		<h2 class="form-signin-heading">${i18nBundle.or}</h2>
-		<input type="image" src="/images/btn_sign_in_with_google.png" alt="${i18nBundle.signInWith} Google"/><br/>
-		<input type="checkbox" name="rememberUser"<#if checkRemember?has_content && checkRemember> checked="checked"</#if>/> ${i18nBundle.rememberLogin}
-	</form>
 </div>
 </#macro>
 <@page_html/>