diff --git a/.gitignore b/.gitignore
index 953682c1b73708d0a4a17503fd0cb2b5c30e8cce..b60d09df8b79d309890161b60d209ee3421343f4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,4 @@ npm-debug.log
 /.project
 
 composer.lock
+local.log
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f2d9f6dd5691388e2c041fa95ce08db7d4184614..acd4464664bb33d5c2e463f62483db54697fb71b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -37,74 +37,6 @@ stages:
 build:
   services:
 
-# Prepares the secret files that we cannot or don't want to share with public
-prepare_secrets_master:
-  stage: prepare
-  image: alpine:latest
-  script: 
-    - cp $ENVFILE .env
-    - cp $SUMAS config/sumas.json
-    - cp $SUMASEN config/sumasEn.json
-    - cp $BLACKLISTURL config/blacklistUrl.txt
-    - cp $BLACKLISTDOMAINS config/blacklistDomains.txt
-    - cp $ADBLACKLISTURL config/adBlacklistUrl.txt
-    - cp $ADBLACKLISTDOMAINS config/adBlacklistDomains.txt
-    - cp $SPAM config/spam.txt
-    - cp $USERSSEEDER database/seeds/UsersSeeder.php
-    - cp database/useragents.sqlite.example database/useragents.sqlite
-    - sed -i 's/^APP_ENV=.*/APP_ENV=production/g' .env
-    - sed -i 's/^REDIS_PASSWORD=.*/REDIS_PASSWORD=null/g' .env
-  artifacts:
-    paths:
-      - .env
-      - config/sumas.json
-      - config/sumasEn.json
-      - config/blacklistUrl.txt
-      - config/blacklistDomains.txt
-      - config/adBlacklistUrl.txt
-      - config/adBlacklistDomains.txt
-      - config/spam.txt
-      - database/seeds/UsersSeeder.php
-      - database/useragents.sqlite
-  only:
-    refs:
-      - master    
-
-prepare_secrets_development:
-  stage: prepare
-  image: alpine:latest
-  script: 
-    - cp $ENVFILE .env
-    - cp $SUMAS config/sumas.json
-    - cp $SUMASEN config/sumasEn.json
-    - cp $BLACKLISTURL config/blacklistUrl.txt
-    - cp $BLACKLISTDOMAINS config/blacklistDomains.txt
-    - cp $ADBLACKLISTURL config/adBlacklistUrl.txt
-    - cp $ADBLACKLISTDOMAINS config/adBlacklistDomains.txt
-    - cp $SPAM config/spam.txt
-    - cp $USERSSEEDER database/seeds/UsersSeeder.php
-    - cp database/useragents.sqlite.example database/useragents.sqlite
-    - sed -i 's/^APP_ENV=.*/APP_ENV=development/g' .env
-    - sed -i 's/^REDIS_PASSWORD=.*/REDIS_PASSWORD=null/g' .env
-  artifacts:
-    paths:
-      - .env
-      - config/sumas.json
-      - config/sumasEn.json
-      - config/blacklistUrl.txt
-      - config/blacklistDomains.txt
-      - config/adBlacklistUrl.txt
-      - config/adBlacklistDomains.txt
-      - config/spam.txt
-      - database/seeds/UsersSeeder.php
-      - database/useragents.sqlite
-  only:
-    - branches
-    - tags
-  except:
-    refs:
-      - master
-
 prepare_node:
   stage: prepare
   image: node:10
@@ -211,9 +143,13 @@ integrationtest:
   script:
     # Install Dev Dependencies
     - composer install
+    - cp .env.example .env
+    - echo "WEBDRIVER_USER=\"$WEBDRIVER_KEY\"" >> .env
+    - echo "WEBDRIVER_URL=\"$WEBDRIVER_URL\"" >> .env
+    - echo "WEBDRIVER_KEY=\"$WEBDRIVER_USER\"" >> .env
+    - php artisan key:generate
     - URL=$(cat environment_url.txt | tr -d '\n')
     - sed -i "s#^APP_URL=.*#APP_URL=$URL#g" .env
     - sed -i "s#^BRANCH_NAME=.*#BRANCH_NAME=$CI_COMMIT_REF_NAME#g" .env
     - sed -i "s#^COMMIT_NAME=.*#COMMIT_NAME=$CI_COMMIT_REF_SLUG#g" .env
-    - ./vendor/phpunit/phpunit/phpunit
-
+    - php artisan dusk
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 7bfbc1866a1f3b39b5a057fbaac0c42e7fa298c9..a74f916b948735b21b1bc4ae838d4f2a515dae5d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -69,7 +69,11 @@ COPY --chown=root:nginx . /html
 WORKDIR /html
 EXPOSE 80
 
-CMD chown -R root:nginx storage/logs/metager bootstrap/cache && \
+CMD cp /root/.env .env && \
+    sed -i 's/^REDIS_PASSWORD=.*/REDIS_PASSWORD=null/g' .env && \
+    if [ "$GITLAB_ENVIRONMENT_NAME" = "production" ]; then sed -i 's/^APP_ENV=.*/APP_ENV=production/g' .env; else sed -i 's/^APP_ENV=.*/APP_ENV=development/g' .env; fi && \
+    cp database/useragents.sqlite.example database/useragents.sqlite && \
+    chown -R root:nginx storage/logs/metager bootstrap/cache && \
     chmod -R g+w storage/logs/metager bootstrap/cache && \
     crond -L /dev/stdout && \
     php-fpm7
diff --git a/app/Browserstack.php b/app/Browserstack.php
deleted file mode 100644
index 6c9423a2864e385591b14a55ced59b28bef62220..0000000000000000000000000000000000000000
--- a/app/Browserstack.php
+++ /dev/null
@@ -1,84 +0,0 @@
-<?php
-
-namespace App;
-
-use BrowserStack\Local;
-use Facebook\WebDriver\Remote\RemoteWebDriver;
-
-class Browserstack
-{
-    private $webdriver, $bs_local = null;
-    private $LOCALCAPABILITIES = array();
-    private $CAPABILITIES = array();
-
-    public function __construct()
-    {
-        $this->setCapabilities();
-        $caps = null;
-        if ($this->isLocal()) {
-            $caps = $this->LOCALCAPABILITIES;
-            $this->bs_local = new Local();
-            $bs_local_args = array("key" => env("WEBDRIVER_KEY", ""));
-            $this->bs_local->start($bs_local_args);
-        } else {
-            $caps = $this->CAPABILITIES;
-        }
-        $this->webdriver = RemoteWebDriver::create(
-            getenv("WEBDRIVER_URL"),
-            $caps
-        );
-    }
-
-    private function setCapabilities()
-    {
-        $this->LOCALCAPABILITIES = array(
-            "os" => "Windows",
-            "os_version" => "10",
-            "browser" => "Firefox",
-            "browser_version" => "79.0 beta",
-            "resolution" => "1920x1080",
-            "project" => env("PROJECT_NAME", "Not Set"),
-            "build" => env("BRANCH_NAME", "Not Set"),
-            "name" => env("COMMIT_NAME", "Not Set"),
-            "browserstack.local" => "true",
-            "browserstack.console" => "verbose",
-            "browserstack.networkLogs" => "true",
-            "browserstack.timezone" => "Europe/Berlin",
-            "browserstack.selenium_version" => "3.5.2",
-        );
-        $this->CAPABILITIES = array(
-            "os" => "Windows",
-            "os_version" => "10",
-            "browser" => "Firefox",
-            "browser_version" => "79.0 beta",
-            "resolution" => "1920x1080",
-            "project" => env("PROJECT_NAME", "Not Set"),
-            "build" => env("BRANCH_NAME", "Not Set"),
-            "name" => env("COMMIT_NAME", "Not Set"),
-            "browserstack.local" => "false",
-            "browserstack.console" => "verbose",
-            "browserstack.networkLogs" => "true",
-            "browserstack.timezone" => "Europe/Berlin",
-            "browserstack.selenium_version" => "3.5.2",
-        );
-    }
-
-    public function getWebdriver()
-    {
-        return $this->webdriver;
-    }
-
-    public function shutdown()
-    {
-        $this->webdriver->quit();
-        if ($this->bs_local != null) {
-            $this->bs_local->stop();
-        }
-    }
-
-    private function isLocal()
-    {
-        return env("APP_URL", "") === "http://nginx";
-    }
-
-}
diff --git a/app/Console/Commands/LoadSpam.php b/app/Console/Commands/LoadSpam.php
new file mode 100644
index 0000000000000000000000000000000000000000..fbe636da3d7434ba6866fd18530ea44ccff6ba6b
--- /dev/null
+++ b/app/Console/Commands/LoadSpam.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Carbon;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Redis;
+
+class LoadSpam extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'spam:load';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Loads a list of current Spams into redis';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        $filePath = \storage_path('logs/metager/ban.txt');
+        $bans = [];
+        if (\file_exists($filePath)) {
+            $bans = json_decode(file_get_contents($filePath), true);
+        }
+
+        $bansToLoad = [];
+
+        foreach ($bans as $ban) {
+            $bannedUntil = Carbon::createFromFormat("Y-m-d H:i:s", $ban["banned-until"]);
+            if ($bannedUntil->isAfter(Carbon::now())) {
+                $bansToLoad[] = $ban["regexp"];
+            }
+        }
+
+        Redis::pipeline(function ($redis) use ($bansToLoad) {
+            $redis->del("spam");
+            foreach ($bansToLoad as $ban) {
+                $redis->rpush("spam", $ban);
+            }
+        });
+    }
+}
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index 51233aaaee9bbf7bc976113e4e7bdbe32ca99a1b..65b76241cde59edb99cd5b29642e2bf72ca5eefc 100644
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -28,7 +28,7 @@ class Kernel extends ConsoleKernel
         $schedule->command('requests:gather')->everyFifteenMinutes();
         $schedule->command('requests:useragents')->everyFiveMinutes();
         $schedule->command('logs:gather')->everyMinute();
-
+        $schedule->command('spam:load')->everyFiveMinutes();
         $schedule->call(function () {
             DB::table('monthlyrequests')->truncate();
             DB::disconnect('mysql');
diff --git a/app/Http/Controllers/AdminSpamController.php b/app/Http/Controllers/AdminSpamController.php
new file mode 100644
index 0000000000000000000000000000000000000000..e4a514ce347b6dadf4141353316a7b315c187b37
--- /dev/null
+++ b/app/Http/Controllers/AdminSpamController.php
@@ -0,0 +1,145 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Carbon;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Redis;
+
+class AdminSpamController extends Controller
+{
+    public function index()
+    {
+        $queries = $this->getQueries();
+
+        $currentBans = $this->getBans();
+        $loadedBans = Redis::lrange("spam", 0, -1);
+
+        return view("admin.spam")
+            ->with('title', "Spam Konfiguration - MetaGer")
+            ->with('queries', $queries)
+            ->with('bans', $currentBans)
+            ->with('loadedBans', $loadedBans);
+    }
+
+    public function ban(Request $request)
+    {
+        $banTime = $request->input('ban-time');
+        $banRegexp = $request->input('regexp');
+
+        $file = storage_path('logs/metager/ban.txt');
+
+        $bans = [];
+        if (file_exists($file)) {
+            $bans = json_decode(file_get_contents($file), true);
+        }
+
+        $bans[] = ["banned-until" => Carbon::now()->add($banTime)->format("Y-m-d H:i:s"), "regexp" => $banRegexp];
+
+        \file_put_contents($file, json_encode($bans));
+
+        return redirect(url('admin/spam'));
+    }
+
+    public function jsonQueries()
+    {
+        $queries = $this->getQueries();
+        return response()->json($queries);
+    }
+
+    public function queryregexp(Request $request)
+    {
+        $data = json_decode($request->getContent(), true);
+        $queries = $data["queries"];
+        $regexps = [$data["regexp"]];
+
+        $bans = $this->getBans();
+        foreach ($bans as $ban) {
+            $regexps[] = $ban["regexp"];
+        }
+
+        $resultData = [];
+
+        foreach ($queries as $query) {
+            $matches = false;
+            foreach ($regexps as $regexp) {
+                try {
+                    if (preg_match($regexp, $query)) {
+                        $matches = true;
+                    }
+                } catch (\Exception $e) {
+                    // Exceptions are expected when no valid regexp is given
+                }
+            }
+            $resultData[] = [
+                "query" => $query,
+                "matches" => $matches,
+            ];
+        }
+
+        return response()->json($resultData);
+
+    }
+
+    private function getQueries()
+    {
+        $minuteToFetch = Carbon::now()->subMinutes(2);
+        $logFile = storage_path("logs/metager/" . $minuteToFetch->format("Y/m/d") . ".log");
+
+        $result = shell_exec("cat $logFile | grep " . $minuteToFetch->format("H:i:"));
+        $result = explode(PHP_EOL, $result);
+
+        $queries = array();
+
+        foreach ($result as $line) {
+            if ($query = \preg_match("/.*eingabe=(.*)$/", $line, $matches)) {
+                $queries[] = $matches[1];
+            }
+        }
+        return $queries;
+    }
+
+    public function getBans()
+    {
+        $file = \storage_path('logs/metager/ban.txt');
+        $bans = [];
+
+        if (file_exists($file)) {
+            $tmpBans = json_decode(file_get_contents($file), true);
+
+            foreach ($tmpBans as $ban) {
+                #dd($ban["banned-until"]);
+                $bannedUntil = Carbon::createFromFormat('Y-m-d H:i:s', $ban["banned-until"]);
+                if ($bannedUntil->isAfter(Carbon::now())) {
+                    $bans[] = $ban;
+                }
+            }
+        }
+
+        file_put_contents($file, json_encode($bans));
+
+        return $bans;
+    }
+
+    public function deleteRegexp(Request $request)
+    {
+        $file = \storage_path('logs/metager/ban.txt');
+        $bans = [];
+
+        if (file_exists($file)) {
+            $bans = json_decode(file_get_contents($file), true);
+        }
+
+        $regexpToDelete = $request->input('regexp');
+        $newBans = [];
+
+        foreach ($bans as $ban) {
+            if ($ban["regexp"] !== $regexpToDelete) {
+                $newBans[] = $ban;
+            }
+        }
+
+        file_put_contents($file, json_encode($newBans));
+        return redirect(url('admin/spam'));
+    }
+}
diff --git a/app/Http/Controllers/HumanVerification.php b/app/Http/Controllers/HumanVerification.php
index fcf6e183d9d38bce7bc72426605c1efd39090bf2..741e55fef5a42d6e28090e6660d2f9e1488b560d 100644
--- a/app/Http/Controllers/HumanVerification.php
+++ b/app/Http/Controllers/HumanVerification.php
@@ -7,6 +7,7 @@ use Carbon;
 use Illuminate\Hashing\BcryptHasher as Hasher;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Redis;
 use Input;
 
 class HumanVerification extends Controller
@@ -39,6 +40,7 @@ class HumanVerification extends Controller
             $key = strtolower($key);
 
             if (!$hasher->check($key, $lockedKey)) {
+                sleep(\random_int(1, 8));
                 $captcha = Captcha::create("default", true);
                 $user["lockedKey"] = $captcha["key"];
                 HumanVerification::saveUser($user);
@@ -65,6 +67,7 @@ class HumanVerification extends Controller
                 }
             }
         }
+        sleep(\random_int(1, 8));
         $captcha = Captcha::create("default", true);
         $user["lockedKey"] = $captcha["key"];
         HumanVerification::saveUser($user);
@@ -105,7 +108,7 @@ class HumanVerification extends Controller
     private static function saveUser($user)
     {
         $userList = Cache::get(HumanVerification::PREFIX . "." . $user["id"], []);
-        
+
         if ($user["whitelist"]) {
             $user["expiration"] = now()->addWeeks(2);
         } else {
@@ -196,21 +199,20 @@ class HumanVerification extends Controller
 
     public static function couldBeSpammer($ip)
     {
-        $possibleSpammer = false;
-
         # Check for recent Spams
         $eingabe = \Request::input('eingabe');
-        if (\preg_match("/^susimail\s+-site:[^\s]+\s-site:/si", $eingabe)) {
-            return true;
-        } else if (\preg_match("/^\s*site:\"linkedin\.com[^\"]*\"\s+/si", $eingabe)) {
-            return true;
+        $spams = Redis::lrange("spam", 0, -1);
+        foreach ($spams as $spam) {
+            if (\preg_match($spam, $eingabe)) {
+                return true;
+            }
         }
 
-        return $possibleSpammer;
-
+        return false;
     }
 
-    public function botOverview(Request $request){
+    public function botOverview(Request $request)
+    {
         $id = "";
         $uid = "";
         $ip = $request->ip();
@@ -232,7 +234,8 @@ class HumanVerification extends Controller
             ->with('user', $user);
     }
 
-    public function botOverviewChange(Request $request) {
+    public function botOverviewChange(Request $request)
+    {
         $id = "";
         $uid = "";
         $ip = $request->ip();
@@ -247,11 +250,11 @@ class HumanVerification extends Controller
         $userList = Cache::get(HumanVerification::PREFIX . "." . $id);
         $user = $userList[$uid];
 
-        if($request->filled("locked")){
+        if ($request->filled("locked")) {
             $user["locked"] = boolval($request->input('locked'));
-        }elseif($request->filled("whitelist")) {
+        } elseif ($request->filled("whitelist")) {
             $user["whitelist"] = boolval($request->input('whitelist'));
-        }elseif($request->filled("unusedResultPages")) {
+        } elseif ($request->filled("unusedResultPages")) {
             $user["unusedResultPages"] = intval($request->input('unusedResultPages'));
         }
 
diff --git a/app/Http/Controllers/MetaGerSearch.php b/app/Http/Controllers/MetaGerSearch.php
index 4d594cb5b85cdfd1dc1735fdb0320fb200b551d5..55589b09cd032fcb5454049a6deb038bf6ff6d43 100644
--- a/app/Http/Controllers/MetaGerSearch.php
+++ b/app/Http/Controllers/MetaGerSearch.php
@@ -7,6 +7,7 @@ use App\MetaGer;
 use Cache;
 use Illuminate\Http\Request;
 use LaravelLocalization;
+use Log;
 use View;
 
 class MetaGerSearch extends Controller
@@ -63,7 +64,7 @@ class MetaGerSearch extends Controller
 
         # Search query can be empty after parsing the formdata
         # we will cancel the search in that case and show an error to the user
-        if(empty($metager->getQ())){
+        if (empty($metager->getQ())) {
             return $metager->createView();
         }
 
@@ -109,7 +110,11 @@ class MetaGerSearch extends Controller
             }
         }
 
-        Cache::put("loader_" . $metager->getSearchUid(), $metager->getEngines(), 60 * 60);
+        try {
+            Cache::put("loader_" . $metager->getSearchUid(), $metager->getEngines(), 60 * 60);
+        } catch (\Exception $e) {
+            Log::error($e->getMessage());
+        }
         if (!empty($timings)) {
             $timings["Filled resultloader Cache"] = microtime(true) - $time;
         }
@@ -117,7 +122,11 @@ class MetaGerSearch extends Controller
         # Die Ausgabe erstellen:
         $resultpage = $metager->createView();
         if ($spamEntry !== null) {
-            Cache::put('spam.' . $metager->getFokus() . "." . md5($spamEntry), $resultpage->render(), 604800);
+            try {
+                Cache::put('spam.' . $metager->getFokus() . "." . md5($spamEntry), $resultpage->render(), 604800);
+            } catch (\Exception $e) {
+                Log::error($e->getMessage());
+            }
         }
 
         if (!empty($timings)) {
@@ -133,7 +142,7 @@ class MetaGerSearch extends Controller
         $counter->incBy(sizeof($metager->getResults()));
         $counter = $registry->getOrRegisterCounter('metager', 'query_counter', 'counts total number of search queries', []);
         $counter->inc();
-        
+
         return $resultpage;
     }
 
@@ -225,7 +234,7 @@ class MetaGerSearch extends Controller
 
         $result["finished"] = $finished;
 
-        if($newResults > 0){
+        if ($newResults > 0) {
             $registry = \Prometheus\CollectorRegistry::getDefault();
             $counter = $registry->getOrRegisterCounter('metager', 'result_counter', 'counts total number of returned results', []);
             $counter->incBy($newResults);
@@ -290,7 +299,7 @@ class MetaGerSearch extends Controller
     {
         $search = $request->input('search', '');
         $quotes = $request->input('quotes', 'on');
-        if(empty($search)){
+        if (empty($search)) {
             abort(404);
         }
 
diff --git a/app/Http/Middleware/HumanVerification.php b/app/Http/Middleware/HumanVerification.php
index 1ce6b38a2a8ab00f0f7d0db9e3fdbca1352f81d2..5599671c6102f335ee32d1c2e3b9f8b8eefa2453 100644
--- a/app/Http/Middleware/HumanVerification.php
+++ b/app/Http/Middleware/HumanVerification.php
@@ -7,6 +7,7 @@ use Captcha;
 use Closure;
 use Cookie;
 use Illuminate\Http\Response;
+use Log;
 use URL;
 
 class HumanVerification
@@ -66,7 +67,6 @@ class HumanVerification
             } else {
                 $user = $users[$uid];
             }
-
             # Lock out everyone in a Bot network
             # Find out how many requests this IP has made
             $sum = 0;
@@ -80,7 +80,7 @@ class HumanVerification
                     }
                 }
             }
-            
+
             # A lot of automated requests are from websites that redirect users to our result page.
             # We will detect those requests and put a captcha
             $referer = URL::previous();
@@ -98,9 +98,10 @@ class HumanVerification
             if ((!$alone && $sum >= 50 && !$user["whitelist"]) || $refererLock) {
                 $user["locked"] = true;
             }
-            
+
             # If the user is locked we will force a Captcha validation
             if ($user["locked"]) {
+                sleep(\random_int(1, 8));
                 $captcha = Captcha::create("default", true);
                 $user["lockedKey"] = $captcha["key"];
                 \App\PrometheusExporter::CaptchaShown();
@@ -127,16 +128,23 @@ class HumanVerification
                 if ($user["unusedResultPages"] === 50 || ($user["unusedResultPages"] > 50 && $user["unusedResultPages"] % 25 === 0)) {
                     $user["locked"] = true;
                 }
-
             }
+            \App\PrometheusExporter::HumanVerificationSuccessfull();
+        } catch (\Exception $e) {
+            Log::error($e->getMessage());
+            \App\PrometheusExporter::HumanVerificationError();
         } finally {
-            if ($update) {
+            if ($update && $user != null) {
                 if ($user["whitelist"]) {
                     $user["expiration"] = now()->addWeeks(2);
                 } else {
                     $user["expiration"] = now()->addHours(72);
                 }
-                $this->setUser($prefix, $user);
+                try {
+                    $this->setUser($prefix, $user);
+                } catch (\Exception $e) {
+                    Log::error($e->getMessage());
+                }
             }
         }
 
@@ -147,7 +155,6 @@ class HumanVerification
 
     public function setUser($prefix, $user)
     {
-        // Lock must be acquired within 2 seconds
         $userList = Cache::get($prefix . "." . $user["id"], []);
         $userList[$user["uid"]] = $user;
         Cache::put($prefix . "." . $user["id"], $userList, 2 * 7 * 24 * 60 * 60);
diff --git a/app/Models/Quicktips/Quicktips.php b/app/Models/Quicktips/Quicktips.php
index ba558afcf9dd0f498c2ae3bf15dec2686f2784f2..1210568fe193be1f0f8295598e6249d8627d0fba 100644
--- a/app/Models/Quicktips/Quicktips.php
+++ b/app/Models/Quicktips/Quicktips.php
@@ -32,12 +32,23 @@ class Quicktips
         $url = $this->quicktipUrl . "?search=" . $this->normalize_search($search) . "&locale=" . $locale . "&quotes=" . $quotes;
         $this->hash = md5($url);
 
-        if (!Cache::has($this->hash)) {
+        $results = null;
+
+        try {
+            if (!Cache::has($this->hash)) {
+                $results = file_get_contents($url);
+                Cache::put($this->hash, $results, Quicktips::CACHE_DURATION);
+            } else {
+                $results = Cache::get($this->hash);
+            }
+        } catch (\Exception $e) {
+            Log::error($e->getMessage());
+        }
+
+        if ($results === null) {
             $results = file_get_contents($url);
-            Cache::put($this->hash, $results, Quicktips::CACHE_DURATION);
-        } else {
-            $results = Cache::get($this->hash);
         }
+
         $this->results = $this->loadResults($results);
     }
 
diff --git a/app/Models/Searchengine.php b/app/Models/Searchengine.php
index eba7c83bd86e4a2104b17fd5a165de4c43fc3570..9cafcf0c61aa5b5d1542b6054ae25ec13127b327 100644
--- a/app/Models/Searchengine.php
+++ b/app/Models/Searchengine.php
@@ -5,6 +5,7 @@ namespace App\Models;
 use App\MetaGer;
 use Cache;
 use Illuminate\Support\Facades\Redis;
+use Log;
 
 abstract class Searchengine
 {
@@ -94,7 +95,7 @@ abstract class Searchengine
             $tmpPara = true;
             $engineParameterKey = $filter->sumas->{$name}->{"get-parameter"};
             $engineParameterValue = $filter->sumas->{$name}->values->{$inputParameter};
-            if(stripos($engineParameterValue, "dyn-") === 0){
+            if (stripos($engineParameterValue, "dyn-") === 0) {
                 $functionname = substr($engineParameterValue, stripos($engineParameterValue, "dyn-") + 4);
                 $engineParameterValue = \App\DynamicEngineParameters::$functionname();
             }
@@ -207,7 +208,11 @@ abstract class Searchengine
         }
 
         if ($body !== null) {
-            Cache::put($this->hash, $body, $this->cacheDuration * 60);
+            try {
+                Cache::put($this->hash, $body, $this->cacheDuration * 60);
+            } catch (\Exception $e) {
+                Log::error($e->getMessage());
+            }
             $this->loadResults($body);
             $this->getNext($metager, $body);
             $this->markNew();
diff --git a/app/PrometheusExporter.php b/app/PrometheusExporter.php
index 1332e1cf2f5176fbcd91e6b8c704ac7cc692fa9c..dd70db4e0a2c3a240aab2675bd5f718486faed46 100644
--- a/app/PrometheusExporter.php
+++ b/app/PrometheusExporter.php
@@ -2,24 +2,41 @@
 
 namespace App;
 
-class PrometheusExporter {
+class PrometheusExporter
+{
 
-    public static function CaptchaShown() {
+    public static function CaptchaShown()
+    {
         $registry = \Prometheus\CollectorRegistry::getDefault();
         $counter = $registry->getOrRegisterCounter('metager', 'captcha_shown', 'counts how often the captcha was shown', []);
         $counter->inc();
     }
 
-    public static function CaptchaCorrect() {
+    public static function CaptchaCorrect()
+    {
         $registry = \Prometheus\CollectorRegistry::getDefault();
         $counter = $registry->getOrRegisterCounter('metager', 'captcha_correct', 'counts how often the captcha was solved correctly', []);
         $counter->inc();
     }
 
-    public static function CaptchaAnswered() {
+    public static function CaptchaAnswered()
+    {
         $registry = \Prometheus\CollectorRegistry::getDefault();
         $counter = $registry->getOrRegisterCounter('metager', 'captcha_answered', 'counts how often the captcha was answered', []);
         $counter->inc();
     }
 
-}
\ No newline at end of file
+    public static function HumanVerificationSuccessfull()
+    {
+        $registry = \Prometheus\CollectorRegistry::getDefault();
+        $counter = $registry->getOrRegisterCounter('metager', 'humanverification_success', 'counts how often humanverification middleware was successfull', []);
+        $counter->inc();
+    }
+
+    public static function HumanVerificationError()
+    {
+        $registry = \Prometheus\CollectorRegistry::getDefault();
+        $counter = $registry->getOrRegisterCounter('metager', 'humanverification_error', 'counts how often humanverification middleware had an error', []);
+        $counter->inc();
+    }
+}
diff --git a/app/Traits/SupportsBrowserStack.php b/app/Traits/SupportsBrowserStack.php
new file mode 100644
index 0000000000000000000000000000000000000000..33a3187ce91e98577685437797a6568366438a07
--- /dev/null
+++ b/app/Traits/SupportsBrowserStack.php
@@ -0,0 +1,42 @@
+<?php
+namespace App\Traits;
+
+use BrowserStack\Local;
+use Facebook\WebDriver\Remote\RemoteWebDriver;
+
+/**
+ * Run BrowserStack from your tests.
+ */
+trait SupportsBrowserStack
+{
+    protected static $bs_local;
+    /**
+     * Create the BrowserStack WebDriver instance.
+     */
+    public function createBrowserStackDriver(array $config = null): RemoteWebDriver
+    {
+        if ($config["capabilities"]["browserstack.local"] === "true") {
+            $this->bs_local = new Local();
+            $bs_local_args = [
+                "key" => $config["key"],
+            ];
+            $this->bs_local->start($bs_local_args);
+        }
+
+        return RemoteWebDriver::create(
+            "https://$config[username]:$config[key]@hub-cloud.browserstack.com/wd/hub",
+            $config["capabilities"]
+        );
+    }
+
+    /**
+     * @afterClass
+     */
+    public static function shutdown()
+    {
+        if (static::$bs_local && static::$bs_local->isRunning()) {
+            static::$bs_local->stop();
+        }
+    }
+
+}
diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml
index b790fdcba3e3bb955c5c11bbdb36f56de234240a..c01a6f1142d516d7048360218bfe80887ce5c063 100644
--- a/chart/templates/deployment.yaml
+++ b/chart/templates/deployment.yaml
@@ -47,6 +47,21 @@ spec:
       - name: mglogs-persistent-storage
         persistentVolumeClaim:
           claimName: mg-logs
+      - name: env-files
+        secret:
+          secretName: metager-env
+      - name: sumas
+        secret:
+          secretName: metager-sumas
+      - name: sumas-en
+        secret:
+          secretName: metager-sumas-en
+      - name: blacklist
+        secret:
+          secretName: metager-blacklist
+      - name: blacklist-ad
+        secret:
+          secretName: metager-ad-blacklist
       containers:
       # Main PHP-FPM Container
       - name: {{ .Chart.Name }}-phpfpm
@@ -81,6 +96,42 @@ spec:
         - name: mglogs-persistent-storage
           mountPath: /html/storage/logs/metager
           readOnly: false
+        - name: env-files
+          mountPath: /root/.env
+          subPath: .env
+          readOnly: true
+        - name: env-files
+          mountPath: /html/database/seeds/UsersSeeder.php
+          subPath: UsersSeeder.php
+          readOnly: true
+        - name: env-files
+          mountPath: /html/config/spam.txt
+          subPath: spam.txt
+          readOnly: true
+        - name: sumas
+          mountPath: /html/config/sumas.json
+          subPath: sumas.json
+          readOnly: true
+        - name: sumas-en
+          mountPath: /html/config/sumasEn.json
+          subPath: sumasEn.json
+          readOnly: true
+        - name: blacklist
+          mountPath: /html/config/blacklistUrl.txt
+          subPath: blacklistUrl.txt
+          readOnly: true
+        - name: blacklist
+          mountPath: /html/config/blacklistDomains.txt
+          subPath: blacklistDomains.txt
+          readOnly: true
+        - name: blacklist-ad
+          mountPath: /html/config/adBlacklistUrl.txt
+          subPath: adBlacklistUrl.txt
+          readOnly: true
+        - name: blacklist-ad
+          mountPath: /html/config/adBlacklistDomains.txt
+          subPath: adBlacklistDomains.txt
+          readOnly: true
         resources:
           requests:
             cpu: 500m
diff --git a/composer.json b/composer.json
index afaeda31728ab51fa3aa9fba6dd49b78fe3fd400..9355e6fc34eaa29601f2daeac6f4da7c30793eea 100644
--- a/composer.json
+++ b/composer.json
@@ -23,12 +23,13 @@
     },
     "require-dev": {
         "beyondcode/laravel-dump-server": "^1.0",
-        "php-webdriver/webdriver": "^1.6",
         "browserstack/browserstack-local": "^1.1",
         "filp/whoops": "^2.0",
         "fzaninotto/faker": "^1.4",
+        "laravel/dusk": "^5.0",
         "mockery/mockery": "^1.0",
         "nunomaduro/collision": "^3.0",
+        "php-webdriver/webdriver": "^1.6",
         "phpunit/phpunit": "^7.5"
     },
     "config": {
diff --git a/contributor license agreement.md b/contributor license agreement.md
new file mode 100644
index 0000000000000000000000000000000000000000..7e7cbcd9f1a2d15b38a46aa381c9246cb9891f68
--- /dev/null
+++ b/contributor license agreement.md	
@@ -0,0 +1,79 @@
+
+# Contributor Agreement
+### Entity Contributor Non-Exclusive License Agreement (including the Traditional Patent License OPTION)
+
+Thank you for your interest in contributing to SUMA-EV – Verein für freien Wissenszugang's MetaGer ("We" or "Us").
+
+The purpose of this contributor agreement ("Agreement") is to clarify and document the rights granted by contributors to Us.
+
+## 1. Definitions
+
+"You" means the individual Copyright owner who Submits a Contribution to Us.
+
+"Legal Entity" means an entity that is not a natural person.
+
+"Affiliate" means any other Legal Entity that controls, is controlled by, or under common control with that Legal Entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such Legal Entity, whether by contract or otherwise, (ii) ownership of fifty percent (50%) or more of the outstanding shares or securities that vote to elect the management or other persons who direct such Legal Entity or (iii) beneficial ownership of such entity.
+
+"Contribution" means any original work of authorship, including any original modifications or additions to an existing work of authorship, Submitted by You to Us, in which You own the Copyright.
+
+"Copyright" means all rights protecting works of authorship, including copyright, moral and neighboring rights, as appropriate, for the full term of their existence.
+
+"Material" means the software or documentation made available by Us to third parties. When this Agreement covers more than one software project, the Material means the software or documentation to which the Contribution was Submitted. After You Submit the Contribution, it may be included in the Material.
+
+"Submit" means any act by which a Contribution is transferred to Us by You by means of tangible or intangible media, including but not limited to electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, Us, but excluding any transfer that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
+
+"Documentation" means any non-software portion of a Contribution.
+## 2. License grant
+### 2.1 Copyright license to Us
+
+Subject to the terms and conditions of this Agreement, You hereby grant to Us a worldwide, royalty-free, NON-exclusive, perpetual and irrevocable (except as stated in Section 8.2) license, with the right to transfer an unlimited number of non-exclusive licenses or to grant sublicenses to third parties, under the Copyright covering the Contribution to use the Contribution by all means, including, but not limited to:
+
+-     publish the Contribution,
+-     modify the Contribution,
+-     prepare derivative works based upon or containing the Contribution and/or to combine the Contribution with other Materials,
+-     reproduce the Contribution in original or modified form,
+-     distribute, to make the Contribution available to the public, display and publicly perform the Contribution in original or modified form.
+
+## 2.2 Moral rights
+
+Moral Rights remain unaffected to the extent they are recognized and not waivable by applicable law. Notwithstanding, You may add your name to the attribution mechanism customary used in the Materials you Contribute to, such as the header of the source code files of Your Contribution, and We will respect this attribution when using Your Contribution.
+## 3. Patents
+### 3.1 Patent license
+
+Subject to the terms and conditions of this Agreement You hereby grant to Us and to recipients of Materials distributed by Us a worldwide, royalty-free, non-exclusive, perpetual and irrevocable (except as stated in Section 3.2) patent license, with the right to transfer an unlimited number of non-exclusive licenses or to grant sublicenses to third parties, to make, have made, use, sell, offer for sale, import and otherwise transfer the Contribution and the Contribution in combination with any Material (and portions of such combination). This license applies to all patents owned or controlled by You, whether already acquired or hereafter acquired, that would be infringed by making, having made, using, selling, offering for sale, importing or otherwise transferring of Your Contribution(s) alone or by combination of Your Contribution(s) with any Material.
+3.2 Revocation of patent license
+
+You reserve the right to revoke the patent license stated in section 3.1 if We make any infringement claim that is targeted at your Contribution and not asserted for a Defensive Purpose. An assertion of claims of the Patents shall be considered for a "Defensive Purpose" if the claims are asserted against an entity that has filed, maintained, threatened, or voluntarily participated in a patent infringement lawsuit against Us or any of Our licensees.
+## 4. License obligations by Us
+
+We agree to (sub)license the Contribution or any Materials containing, based on or derived from your Contribution under the terms of any licenses the Free Software Foundation classifies as Free Software License and which are approved by the Open Source Initiative as Open Source licenses.
+
+We agree to license patents owned or controlled by You only to the extent necessary to (sub)license Your Contribution(s) and the combination of Your Contribution(s) with the Material under the terms of any licenses the Free Software Foundation classifies as Free Software licenses and which are approved by the Open Source Initiative as Open Source licenses.
+## 5. Disclaimer
+
+THE CONTRIBUTION IS PROVIDED "AS IS". MORE PARTICULARLY, ALL EXPRESS OR IMPLIED WARRANTIES INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTY OF SATISFACTORY QUALITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE EXPRESSLY DISCLAIMED BY YOU TO US AND BY US TO YOU. TO THE EXTENT THAT ANY SUCH WARRANTIES CANNOT BE DISCLAIMED, SUCH WARRANTY IS LIMITED IN DURATION AND EXTENT TO THE MINIMUM PERIOD AND EXTENT PERMITTED BY LAW.
+
+## 6. Consequential damage waiver
+
+TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL YOU OR WE BE LIABLE FOR ANY LOSS OF PROFITS, LOSS OF ANTICIPATED SAVINGS, LOSS OF DATA, INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL AND EXEMPLARY DAMAGES ARISING OUT OF THIS AGREEMENT REGARDLESS OF THE LEGAL OR EQUITABLE THEORY (CONTRACT, TORT OR OTHERWISE) UPON WHICH THE CLAIM IS BASED.
+## 7. Approximation of disclaimer and damage waiver
+
+IF THE DISCLAIMER AND DAMAGE WAIVER MENTIONED IN SECTION 5. AND SECTION 6. CANNOT BE GIVEN LEGAL EFFECT UNDER APPLICABLE LOCAL LAW, REVIEWING COURTS SHALL APPLY LOCAL LAW THAT MOST CLOSELY APPROXIMATES AN ABSOLUTE WAIVER OF ALL CIVIL OR CONTRACTUAL LIABILITY IN CONNECTION WITH THE CONTRIBUTION.
+## 8. Term
+
+- 8.1 This Agreement shall come into effect upon commiting your contribution to our software repository.
+
+- 8.2 This Agreement shall apply for the term of the copyright and patents licensed here. However, You shall have the right to terminate the Agreement if We do not fulfill the obligations as set forth in Section 4. Such termination must be made in writing.
+
+- 8.3 In the event of a termination of this Agreement Sections 5, 6, 7, 8 and 9 shall survive such termination and shall remain in full force thereafter. For the avoidance of doubt, Free and Open Source Software (sub)licenses that have already been granted for Contributions at the date of the termination shall remain in full force after the termination of this Agreement.
+## 9 Miscellaneous
+
+- 9.1 This Agreement and all disputes, claims, actions, suits or other proceedings arising out of this agreement or relating in any way to it shall be governed by the laws of Germany excluding its private international law provisions.
+
+- 9.2 This Agreement sets out the entire agreement between You and Us for Your Contributions to Us and overrides all other agreements or understandings.
+
+- 9.3 In case of Your death, this agreement shall continue with Your heirs. In case of more than one heir, all heirs must exercise their rights through a commonly authorized person.
+
+- 9.4 If any provision of this Agreement is found void and unenforceable, such provision will be replaced to the extent possible with a provision that comes closest to the meaning of the original provision and that is enforceable. The terms and conditions set forth in this Agreement shall apply notwithstanding any failure of essential purpose of this Agreement or any limited remedy to the maximum extent possible under law.
+
+- 9.5 You agree to notify Us of any facts or circumstances of which you become aware that would make this Agreement inaccurate in any respect.
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index aac33500354b36cc53afa114fe5089287df3fe67..86f67380c09364af43322a4efa01b536faf09119 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -75,4 +75,4 @@ services:
     working_dir: /html
     volumes:
       - .:/html
-    command: "./vendor/phpunit/phpunit/phpunit"
+    command: "php artisan dusk"
diff --git a/local.log b/local.log
deleted file mode 100644
index 5a4e92611a6e4aedccb0fde0950962fae5cd1d21..0000000000000000000000000000000000000000
--- a/local.log
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-/bin/sh: 1: ps: not found
-/bin/sh: 1: ps: not found
-Thu Jul 09 2020 11:17:18 GMT+0000 (UTC) -- [INFO] Started the BrowserStack Binary server on 45691, PID: 22
-Thu Jul 09 2020 11:17:19 GMT+0000 (UTC) -- [SUCCESS] You can now access your local server(s) in our remote browser
-
-Thu Jul 09 2020 11:17:19 GMT+0000 (UTC) -- Press Ctrl-C to exit
-
-
diff --git a/resources/lang/de/impressum.php b/resources/lang/de/impressum.php
index c5a9b7ae1857b1070a10aa5d2bf5fe2d47743c63..f41ffa6f7ade39a6df31b748d3d29a55c729bfc1 100644
--- a/resources/lang/de/impressum.php
+++ b/resources/lang/de/impressum.php
@@ -15,7 +15,7 @@ Deutschland/Germany',
 Tel.: ++49-(0)511-34000070
 EMail: <a href="mailto:office@suma-ev.de">office@suma-ev.de</a><a href="/kontakt/"> - Public-PGP-Key</a>
 <a href="/kontakt/">Verschlüsselndes Kontaktformular</a>',
-    'info.4' => 'Vorstand: Dominik Hebeler, Carsten Riel, <a href="https://www.ostfalia.de/cms/de/pws/jensen/index.html">Prof.Dr. Nils Jensen</a>',
+    'info.4' => 'Vorstand: Dominik Hebeler, Carsten Riel',
     'info.6' => 'Jugendschutzbeauftragte: Manuela Branz <a href="mailto:jugendschutz@metager.de">jugendschutz@metager.de</a>',
     'info.8' => '"SUMA-EV - Verein für freien Wissenszugang" ist ein gemeinnütziger
 Verein, eingetragen in das Vereinsregister beim Amtsgericht Hannover
diff --git a/resources/lang/en/impressum.php b/resources/lang/en/impressum.php
index ba88f17dd67bb27fdb40f07c25d8a140e6e53126..31c36493521a5fedf42082b37bf0c915abd6b0f5 100644
--- a/resources/lang/en/impressum.php
+++ b/resources/lang/en/impressum.php
@@ -14,7 +14,7 @@ Deutschland/Germany',
 Tel.: ++49-(0)511-34000070
 EMail: <a href="mailto:office@suma-ev.de">office@suma-ev.de</a><a href="/kontakt/"> - Public-PGP-Key</a>
 <a href="/kontakt/">encrypted contact form</a>',
-    'info.4' => 'Board: Dominik Hebeler, Carsten Riel, <a href="https://www.ostfalia.de/cms/de/pws/jensen/index.html">Prof.Dr. Nils Jensen</a>',
+    'info.4' => 'Board: Dominik Hebeler, Carsten Riel',
     'info.6' => 'Youth Protection Commissioner: Manuela Branz <a href="mailto:jugendschutz@metager.de">jugendschutz@metager.de</a>',
     'info.8' => '"SUMA-EV - Verein für freien Wissenszugang" is a charitable association, registered in the register of associations at the Amtsgericht Hannover
 under VR200033.
diff --git a/resources/lang/es/impressum.php b/resources/lang/es/impressum.php
index 2d7e5a036c5d425beef9d6b7fe2b508d9af09723..9035a55ac5fed77351a0e2ab908e14a8a082243d 100644
--- a/resources/lang/es/impressum.php
+++ b/resources/lang/es/impressum.php
@@ -6,7 +6,7 @@ return [
     "info.1" => "Articulo de Wikipedia de <a href=\"http://de.wikipedia.org/wiki/Suma_e.V.\" target=\"_blank\" rel=\"noopener\">SUMA-EV</a>",
     "info.2" => " SUMA-EV Röselerstr. 3 D-30159 Hannover Deutschland/Germany",
     "info.3" => "Contacto: Tel.: ++49-(0)511-34000070 EMail: <a href=\"mailto:office@suma-ev.de\">office@suma-ev.de</a><a href=\"/kontakt/\"> - Public-PGP-Key</a> <a href=\"/kontakt/\">Formulario encriptado</a>",
-    "info.4" => "Junta directiva: Dominik Hebeler, Carsten Riel, <a href=\"https://www2.ostfalia.de/cms/de/pws/jensenn/index.html\">Prof.Dr. Nils Jensen</a>",
+    "info.4" => "Junta directiva: Dominik Hebeler, Carsten Riel",
     "info.6" => "Encargado de protección de menores: Manuela Branz <a href=\"mailto:jugendschutz@metager.de\">jugendschutz@metager.de</a>",
     "info.8" => "\"SUMA-EV - Verein für freien Wissenszugang\" es una asociación sin fines de lucro, registrado en el registro de asociaciones del Amtsgericht Hannover bajo numero VR200033. Número de identificación a efectos del IVA:  DE 300 464 091 La \"Gottfried Wilhelm Leibniz Universität Hannover\" es una entidad del derecho publico.",
     "info.9" => "Exención de responsabilidad",
diff --git a/resources/views/admin/spam.blade.php b/resources/views/admin/spam.blade.php
new file mode 100644
index 0000000000000000000000000000000000000000..ffcdfe2e98fc859a3ae351397c9d8bb76a21fd1e
--- /dev/null
+++ b/resources/views/admin/spam.blade.php
@@ -0,0 +1,198 @@
+@extends('layouts.subPages')
+
+@section('title', $title )
+
+@section('content')
+<style>
+    #head {
+        display: flex;
+        align-items: center;
+        margin-bottom: 16px;
+    }
+    #head > button {
+        margin-left: 16px;
+    }
+    #head > h1 {
+        margin: 0;
+    }
+    #queries {
+        display: flex;
+        flex-wrap: wrap;
+        justify-content: space-between;
+    }
+
+    .matches {
+        background-color: #c9f4c9;
+    }
+    #block-requests {
+        margin-bottom: 16px;
+    }
+    #regexp {
+        margin-bottom: 8px;
+    }
+    #ban-time {
+        margin-bottom: 8px;
+    }
+</style>
+<div id="block-requests">
+    <form method="post">
+        <input class="form-control" type="text" name="regexp" id="regexp" placeholder="Type in regexp to match queries...">
+        <select name="ban-time" id="ban-time" class="form-control">
+            <option value="1 day">Einen Tag</option>
+            <option value="1 week">Eine Woche</option>
+            <option value="2 weeks">Zwei Wochen</option>
+            <option value="1 month" selected>Einen Monat</option>
+        </select>
+        <button type="submit" class="btn btn-default btn-sm">Sperren</button>
+    </form>
+</div>
+<div id="head">
+    <h1>Letzte Suchanfragen</h1>
+    <button type="button" class="btn btn-success btn-sm">Aktualisierung stoppen (60)</button>
+</div>
+<input class="form-control" type="text" name="" id="check-against" placeholder="Match against...">
+<div id="queries">
+    @foreach($queries as $query)
+    <div class="query card">{{$query}}</div>
+    @endforeach
+</div>
+<div id="bans">
+    <h1>Current Bans</h1>
+    <table class="table table-striped">
+        <thead>
+            <tr>
+                <td>Regexp</td>
+                <td>Banned until</td>
+                <td>Actions</td>
+            </tr>
+        </thead>
+        <tbody>
+            @foreach($bans as $ban)
+            <tr>
+                <td>{{ $ban["regexp"] }}</td>
+                <td>{{ Carbon::createFromFormat("Y-m-d H:i:s", $ban["banned-until"])->format("d.m.Y H:i:s")}} ({{ Carbon::createFromFormat("Y-m-d H:i:s", $ban["banned-until"])->diffInDays(Carbon::now()) }} Days)</td>
+                <td>
+                    <form action="{{ url("admin/spam/deleteRegexp") }}" method="post">
+                        <input type="hidden" name="regexp" value="{{ $ban["regexp"] }}">
+                        <button type="submit">&#128465;</button>
+                    </form>
+                </td>
+            </tr>
+            @endforeach
+        </tbody>
+    </table>
+</div>
+<div id="loadedbans">
+    <h1>Loaded Bans</h1>
+    <table class="table table-striped">
+        <thead>
+            <tr>
+                <td>Regexp</td>
+            </tr>
+        </thead>
+        <tbody>
+            @foreach($loadedBans as $ban)
+            <tr>
+                <td>{{ $ban }}</td>
+            </tr>
+            @endforeach
+        </tbody>
+    </table>
+</div>
+<script>
+    var lastUpdate = Date.now();
+    var updating = true;
+    var buttonText = "Aktualisierung stoppen";
+    var interval = setInterval(updateQueries, 1000);
+    $("#regexp").on("input", checkRegexp);
+    $("#check-against").on("input", checkRegexp);
+    $(document).ready(function(){
+        checkRegexp();
+    });
+
+
+    $("#head > button").click(function() {
+        if(!updating) {
+            $("#head > button").removeClass("btn-danger");
+            $("#head > button").addClass("btn-success");
+            buttonText = "Aktualisierung stoppen";
+            interval = setInterval(updateQueries, 1000);
+        }
+        var updateAt = lastUpdate + 60000;
+        var updateIn = Math.round((updateAt - Date.now()) / 1000);
+        $("#head > button").html(buttonText + " (" + updateIn + ")");
+        updating = !updating;
+    });
+
+    function updateQueries() {
+        var updateAt = lastUpdate + 60000;
+        var updateIn = Math.round((updateAt - Date.now()) / 1000);
+
+        if(!updating){
+            $("#head > button").removeClass("btn-success");
+            $("#head > button").addClass("btn-danger");
+            buttonText = "Aktualisierung starten";
+            clearInterval(interval);
+        }
+
+        $("#head > button").html(buttonText + " (" + updateIn + ")");
+        if(updateAt > Date.now()){
+            return;
+        }
+        fetch("{{ url('admin/spam/jsonQueries') }}")
+            .then(response => response.json())
+            .then(data => {
+                $("#queries").html("");
+                $(data).each(function(index, el){
+                    $("#queries").append("<div class=\"query card\">" + el + "</div>");
+                });
+                lastUpdate = Date.now();
+                checkRegexp();
+            });
+
+    }
+
+
+    function checkRegexp() {
+        var val = $("#regexp").val();
+        var queries = [];
+
+
+        $("#queries > .query").each(function(index, el){
+            queries.push($(el).html());
+        });
+        queries.push($("#check-against").val());
+
+        var url = "{{ url('admin/spam/queryregexp') }}";
+        var options = {
+            method: 'POST',
+            body: JSON.stringify({
+                "queries": queries,
+                "regexp": val
+            }),
+            headers: {
+                'Content-Type': 'application/json'
+            }
+        };
+
+        fetch(url, options)
+            .then(response => response.json())
+            .then(data => {
+                $("#queries > .query").each(function(index, el){
+                    if(data[index]["matches"]){
+                        $(el).addClass("matches");
+                    }else{
+                        $(el).removeClass("matches");
+                    }
+                });
+                if(data[data.length-1]["matches"]){
+                    $("#check-against").addClass("matches");
+                }else{
+                    $("#check-against").removeClass("matches");
+                }
+            });
+    }
+
+
+</script>
+@endsection
diff --git a/resources/views/datenschutz/english.blade.php b/resources/views/datenschutz/english.blade.php
index e70d7394933d0ea8ed3ad2a427ba8cbd96945ceb..c253a5b12394b49af7bb38ff4939a5fd8398884c 100644
--- a/resources/views/datenschutz/english.blade.php
+++ b/resources/views/datenschutz/english.blade.php
@@ -270,8 +270,7 @@
     </div>
     <div class="section">
         <h1>Hosting</h1>
-        The websites under the domain "suma-ev.de" are hosted and administered by Intares GmbH. The remaining services
-        are administrated by us, the SUMA-EV, and operated on hired hardware at Hetzner Online GmbH.
+        Our services are administrated by us, the SUMA-EV, and operated on rented hardware at Hetzner Online GmbH.
     </div>
     <div class="section">
         <h1>Legal basis for processing</h1>
@@ -345,4 +344,4 @@
         Like our offers, this privacy policy is subject to constant change. You should therefore read it again
         regularly.
         <br />This version of our Privacy Policy is dated 2018-05-24.
-    </div>
\ No newline at end of file
+    </div>
diff --git a/resources/views/datenschutz/german.blade.php b/resources/views/datenschutz/german.blade.php
index 104b834885f848122ba780ef3bfb727dc58b7364..572b56b5e12ecd3af2baaba0d703ca77ed791da6 100644
--- a/resources/views/datenschutz/german.blade.php
+++ b/resources/views/datenschutz/german.blade.php
@@ -286,8 +286,7 @@
     </div>
     <div class="section">
         <h1>Hosting</h1>
-        Die Webseiten unter der Domain „suma-ev.de“ werden bei der Intares GmbH gehostet und administriert. Die übrigen
-        Dienste werden von uns, dem SUMA-EV, administriert und auf angemieteter Hardware bei der Hetzner Online GmbH
+        Unsere Dienste werden von uns, dem SUMA-EV, administriert und auf angemieteter Hardware bei der Hetzner Online GmbH
         betrieben.
     </div>
     <div class="section">
@@ -366,4 +365,4 @@
         Wie unsere Angebote ist auch diese Datenschutzerklärung einem ständigen Wandel unterworfen. Sie sollten sie
         daher regelmäßig erneut lesen.
         <br />Die vorliegende Version unserer Datenschutzerklärung trägt folgendes Datum: 2018-05-24
-    </div>
\ No newline at end of file
+    </div>
diff --git a/resources/views/spende/spende.blade.php b/resources/views/spende/spende.blade.php
index f4edc0297895f981a6add37038e1807011f30467..c2686d1b9ac3df3c76959e161ad693f715db40f4 100644
--- a/resources/views/spende/spende.blade.php
+++ b/resources/views/spende/spende.blade.php
@@ -61,6 +61,7 @@
 					<div class="center-wrapper">
 						@if (LaravelLocalization::getCurrentLocale() == "de")
 						<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
+							<input type="hidden" name="lc" value="{{ Request::getPreferredLanguage([]) }}">
 							<input type="hidden" name="cmd" value="_s-xclick" />
 							<input type="hidden" name="hosted_button_id" value="5JPHYQT88JSRQ" />
 							<input type="image" src="{{ action('Pictureproxy@get', ['url' => 'https://www.paypalobjects.com/de_DE/DE/i/btn/btn_donateCC_LG.gif']) }}" border="0" name="submit" title="PayPal - The safer, easier way to pay online!" alt="Donate with PayPal button" />
@@ -68,6 +69,7 @@
 						</form>
 						@else
 						<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
+							<input type="hidden" name="lc" value="{{ Request::getPreferredLanguage([]) }}">
 							<input type="hidden" name="cmd" value="_s-xclick" />
 							<input type="hidden" name="hosted_button_id" value="LXWAVD6P3ZSWG" />
 							<input type="image" src="{{ action('Pictureproxy@get', ['url' => 'https://www.paypalobjects.com/en_US/DK/i/btn/btn_donateCC_LG.gif']) }}" border="0" name="submit" title="PayPal - The safer, easier way to pay online!" alt="Donate with PayPal button" />
diff --git a/routes/web.php b/routes/web.php
index e3c254a46f6b9ca85e1cfd781bba20d0dc4d936c..caf716a5e8aad65efd1db41fcf78f767980d6a89 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -182,6 +182,13 @@ Route::group(
             });
             Route::get('bot', 'HumanVerification@botOverview');
             Route::post('bot', 'HumanVerification@botOverviewChange');
+            Route::group(['prefix' => 'spam'], function () {
+                Route::get('/', 'AdminSpamController@index');
+                Route::post('/', 'AdminSpamController@ban');
+                Route::get('jsonQueries', 'AdminSpamController@jsonQueries');
+                Route::post('queryregexp', 'AdminSpamController@queryregexp');
+                Route::post('deleteRegexp', 'AdminSpamController@deleteRegexp');
+            });
         });
 
         Route::get('settings', function () {
diff --git a/tests/Browser/Pages/About.php b/tests/Browser/Pages/About.php
new file mode 100644
index 0000000000000000000000000000000000000000..046879d9a9acd59938cab1800d31e6f33c2d96ba
--- /dev/null
+++ b/tests/Browser/Pages/About.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Tests\Browser\Pages;
+
+use Laravel\Dusk\Browser;
+
+class About extends Page
+{
+    /**
+     * Get the URL for the page.
+     *
+     * @return string
+     */
+    public function url()
+    {
+        return '/about';
+    }
+
+    /**
+     * Assert that the browser is on the page.
+     *
+     * @param  Browser  $browser
+     * @return void
+     */
+    public function assert(Browser $browser)
+    {
+        $browser->assertPathIs($this->url())
+            ->waitForText("Was MetaGer auszeichnet")
+            ->assertTitle("Ãœber Uns - MetaGer")
+            ->switchLanguage("English")
+            ->waitForText("MetaGer - Characteristic qualities")
+            ->assertTitle("About Us - MetaGer")
+            ->switchLanguage("Español")
+            ->waitForText("Was MetaGer auszeichnet")
+            ->assertTitle("Sobre nosotros - MetaGer")
+            ->switchLanguage("Deutsch");
+
+    }
+
+    /**
+     * Get the element shortcuts for the page.
+     *
+     * @return array
+     */
+    public function elements()
+    {
+        return [
+            '@element' => '#selector',
+        ];
+    }
+}
diff --git a/tests/Browser/Pages/App.php b/tests/Browser/Pages/App.php
new file mode 100644
index 0000000000000000000000000000000000000000..c28c9a7039fed75cb5955bc0af2f1457df1e7a05
--- /dev/null
+++ b/tests/Browser/Pages/App.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Tests\Browser\Pages;
+
+use Laravel\Dusk\Browser;
+
+class App extends Page
+{
+    /**
+     * Get the URL for the page.
+     *
+     * @return string
+     */
+    public function url()
+    {
+        return '/app';
+    }
+
+    /**
+     * Assert that the browser is on the page.
+     *
+     * @param  Browser  $browser
+     * @return void
+     */
+    public function assert(Browser $browser)
+    {
+        $browser->assertPathIs($this->url())
+            ->waitForText("Diese App bringt die volle Power unserer Suchmaschine auf ihr Smartphone.")
+            ->assertTitle("Apps - MetaGer")
+            ->switchLanguage("English")
+            ->waitForText("This App brings the full Metager power to your smartphone.")
+            ->assertTitle("Apps - MetaGer")
+            ->switchLanguage("Español")
+            ->waitForText("Diese App bringt die volle Power unserer Suchmaschine auf ihr Smartphone.")
+            ->assertTitle("Aplicaciones - MetaGer")
+            ->switchLanguage("Deutsch");
+
+    }
+
+    /**
+     * Get the element shortcuts for the page.
+     *
+     * @return array
+     */
+    public function elements()
+    {
+        return [
+            '@element' => '#selector',
+        ];
+    }
+}
diff --git a/tests/Browser/Pages/Datenschutz.php b/tests/Browser/Pages/Datenschutz.php
new file mode 100644
index 0000000000000000000000000000000000000000..00ad16475a4e5aa4fda9aa2c074cf93b80bd35bd
--- /dev/null
+++ b/tests/Browser/Pages/Datenschutz.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Tests\Browser\Pages;
+
+use Laravel\Dusk\Browser;
+
+class Datenschutz extends Page
+{
+    /**
+     * Get the URL for the page.
+     *
+     * @return string
+     */
+    public function url()
+    {
+        return '/datenschutz';
+    }
+
+    /**
+     * Assert that the browser is on the page.
+     *
+     * @param  Browser  $browser
+     * @return void
+     */
+    public function assert(Browser $browser)
+    {
+        $browser->assertPathIs($this->url())
+            ->waitForText("Datenschutz bei MetaGer/SUMA-EV")
+            ->assertTitle("Datenschutz und Privatsphäre - MetaGer")
+            ->switchLanguage("English")
+            ->waitForText("Data protection at MetaGer/SUMA-EV")
+            ->assertTitle("Privacy - MetaGer")
+            ->switchLanguage("Español")
+            ->waitForText("Data protection at MetaGer/SUMA-EV")
+            ->assertTitle("Protección de datos y privacidad - MetaGer")
+            ->switchLanguage("Deutsch");
+    }
+
+    /**
+     * Get the element shortcuts for the page.
+     *
+     * @return array
+     */
+    public function elements()
+    {
+        return [
+            '@element' => '#selector',
+        ];
+    }
+}
diff --git a/tests/Browser/Pages/Hilfe.php b/tests/Browser/Pages/Hilfe.php
new file mode 100644
index 0000000000000000000000000000000000000000..343c6433f813ba47ceb1d81d1118cb68bcddea2e
--- /dev/null
+++ b/tests/Browser/Pages/Hilfe.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Tests\Browser\Pages;
+
+use Laravel\Dusk\Browser;
+
+class Hilfe extends Page
+{
+    /**
+     * Get the URL for the page.
+     *
+     * @return string
+     */
+    public function url()
+    {
+        return '/hilfe';
+    }
+
+    /**
+     * Assert that the browser is on the page.
+     *
+     * @param  Browser  $browser
+     * @return void
+     */
+    public function assert(Browser $browser)
+    {
+        $browser->assertPathIs($this->url())
+            ->waitForText("MetaGer - Hilfe")
+            ->assertTitle("Hilfe - MetaGer")
+            ->switchLanguage("English")
+            ->waitForText("MetaGer Help")
+            ->assertTitle("Help - MetaGer")
+            ->switchLanguage("Español")
+            ->waitForText("MetaGer - Hilfe")
+            ->assertTitle("Ayuda - MetaGer")
+            ->switchLanguage("Deutsch");
+
+    }
+
+    /**
+     * Get the element shortcuts for the page.
+     *
+     * @return array
+     */
+    public function elements()
+    {
+        return [
+            '@element' => '#selector',
+        ];
+    }
+}
diff --git a/tests/Browser/Pages/HomePage.php b/tests/Browser/Pages/HomePage.php
new file mode 100644
index 0000000000000000000000000000000000000000..3ceecbff78ddc96186640287b178bc26aa3ac79d
--- /dev/null
+++ b/tests/Browser/Pages/HomePage.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Tests\Browser\Pages;
+
+use Laravel\Dusk\Browser;
+
+class HomePage extends Page
+{
+    /**
+     * Get the URL for the page.
+     *
+     * @return string
+     */
+    public function url()
+    {
+        return '/';
+    }
+
+    /**
+     * Assert that the browser is on the page.
+     *
+     * @param  \Laravel\Dusk\Browser  $browser
+     * @return void
+     */
+    public function assert(Browser $browser)
+    {
+        # German
+        $browser->assertPathIs($this->url())
+            ->waitForText("Garantierte Privatsphäre", 1)
+            ->assertTitle('MetaGer - Mehr als eine Suchmaschine')
+            ->assertSee("Vielfältig & Frei")
+            ->assertSee("100% Ökostrom")
+            ->assertSee("Gemeinnütziger Verein")
+            ->switchLanguage("English")
+            ->waitForText("Guaranteed Privacy", 1)
+            ->assertTitle('MetaGer: Privacy Protected Search & Find')
+            ->assertSee("Diverse & free")
+            ->assertSee("100 % renewable energy")
+            ->assertSee("Nonprofit organization")
+            ->switchLanguage("Español")
+            ->waitForText("Privacidad garantizada", 1)
+            ->assertTitle('MetaGer: Buscar & encontrar seguro, proteger la privacidad')
+            ->assertSee("Diversa y libre")
+            ->assertSee("Energía 100% renovable")
+            ->assertSee("Organización sin ánimo de lucro")
+            ->switchLanguage("Deutsch");
+    }
+
+    /**
+     * Get the element shortcuts for the page.
+     *
+     * @return array
+     */
+    public function elements()
+    {
+        return [
+            '@sidebarToggle' => 'label.sidebar-opener[for=sidebarToggle]',
+        ];
+    }
+}
diff --git a/tests/Browser/Pages/Impress.php b/tests/Browser/Pages/Impress.php
new file mode 100644
index 0000000000000000000000000000000000000000..a54c0a0f6c82a80c7d38d2743997acf82649ee44
--- /dev/null
+++ b/tests/Browser/Pages/Impress.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Tests\Browser\Pages;
+
+use Laravel\Dusk\Browser;
+
+class Impress extends Page
+{
+    /**
+     * Get the URL for the page.
+     *
+     * @return string
+     */
+    public function url()
+    {
+        return '/impressum';
+    }
+
+    /**
+     * Assert that the browser is on the page.
+     *
+     * @param  Browser  $browser
+     * @return void
+     */
+    public function assert(Browser $browser)
+    {
+        $browser->assertPathIs($this->url())
+            ->waitForText("Haftungshinweis:")
+            ->assertTitle("Impressum - MetaGer")
+            ->switchLanguage("English")
+            ->waitForText("Liability Note:")
+            ->assertTitle("Site Notice - MetaGer")
+            ->switchLanguage("Español")
+            ->waitForText("Exención de responsabilidad")
+            ->assertTitle("Aviso legal - MetaGer")
+            ->switchLanguage("Deutsch");
+
+    }
+
+    /**
+     * Get the element shortcuts for the page.
+     *
+     * @return array
+     */
+    public function elements()
+    {
+        return [
+            '@element' => '#selector',
+        ];
+    }
+}
diff --git a/tests/Browser/Pages/Kontakt.php b/tests/Browser/Pages/Kontakt.php
new file mode 100644
index 0000000000000000000000000000000000000000..02ed0ab07dee8066219f22296aa1f81d891963c9
--- /dev/null
+++ b/tests/Browser/Pages/Kontakt.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Tests\Browser\Pages;
+
+use Laravel\Dusk\Browser;
+
+class Kontakt extends Page
+{
+    /**
+     * Get the URL for the page.
+     *
+     * @return string
+     */
+    public function url()
+    {
+        return '/kontakt';
+    }
+
+    /**
+     * Assert that the browser is on the page.
+     *
+     * @param  Browser  $browser
+     * @return void
+     */
+    public function assert(Browser $browser)
+    {
+        $browser->assertPathIs($this->url())
+            ->waitForText("Sicheres Kontaktformular")
+            ->assertTitle("Kontakt - MetaGer")
+            ->switchLanguage("English")
+            ->waitForText("Secure Contact Form")
+            ->assertTitle("Contact - MetaGer")
+            ->switchLanguage("Español")
+            ->waitForText("Formulario de contacto seguro")
+            ->assertTitle("Contacto - MetaGer")
+            ->switchLanguage("Deutsch");
+
+    }
+
+    /**
+     * Get the element shortcuts for the page.
+     *
+     * @return array
+     */
+    public function elements()
+    {
+        return [
+            '@element' => '#selector',
+        ];
+    }
+}
diff --git a/tests/Browser/Pages/Page.php b/tests/Browser/Pages/Page.php
new file mode 100644
index 0000000000000000000000000000000000000000..d569e1c3e58db3d83025164058a89b986d30d6d6
--- /dev/null
+++ b/tests/Browser/Pages/Page.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Tests\Browser\Pages;
+
+use Laravel\Dusk\Browser;
+use Laravel\Dusk\Page as BasePage;
+
+abstract class Page extends BasePage
+{
+    /**
+     * Get the global element shortcuts for the site.
+     *
+     * @return array
+     */
+    public static function siteElements()
+    {
+        return [
+            '@sidebarToggle' => 'label.sidebar-opener[for=sidebarToggle]',
+            '@languageDropdown' => '#navigationSprache',
+        ];
+    }
+
+    public function switchLanguage(Browser $browser, $language)
+    {
+        $browser->waitFor("@sidebarToggle")
+            ->click("@sidebarToggle")
+            ->click("@languageDropdown")
+            ->clickLink($language);
+    }
+}
diff --git a/tests/Browser/Pages/Plugin.php b/tests/Browser/Pages/Plugin.php
new file mode 100644
index 0000000000000000000000000000000000000000..e47cea91d3efb62ec460b346cb9ad3c45e9862a8
--- /dev/null
+++ b/tests/Browser/Pages/Plugin.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Tests\Browser\Pages;
+
+use Laravel\Dusk\Browser;
+
+class Plugin extends Page
+{
+    /**
+     * Get the URL for the page.
+     *
+     * @return string
+     */
+    public function url()
+    {
+        return '/plugin';
+    }
+
+    /**
+     * Assert that the browser is on the page.
+     *
+     * @param  Browser  $browser
+     * @return void
+     */
+    public function assert(Browser $browser)
+    {
+        $browser->assertPathIs($this->url())
+            ->waitForText("MetaGer zum Firefox hinzufügen")
+            ->assertTitle("Plugin - MetaGer")
+            ->switchLanguage("English")
+            ->waitForText("Add MetaGer to your Firefox")
+            ->assertTitle("Plugin - MetaGer")
+            ->switchLanguage("Español")
+            ->waitForText("Añadir MetaGer a Firefox")
+            ->assertTitle("Plugin - MetaGer")
+            ->switchLanguage("Deutsch");
+    }
+
+    /**
+     * Get the element shortcuts for the page.
+     *
+     * @return array
+     */
+    public function elements()
+    {
+        return [
+            '@element' => '#selector',
+        ];
+    }
+}
diff --git a/tests/Browser/Pages/SitesearchWidget.php b/tests/Browser/Pages/SitesearchWidget.php
new file mode 100644
index 0000000000000000000000000000000000000000..5ed902bc197670b18fcac7eb73288a4247a8cadf
--- /dev/null
+++ b/tests/Browser/Pages/SitesearchWidget.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Tests\Browser\Pages;
+
+use Laravel\Dusk\Browser;
+
+class SitesearchWidget extends Page
+{
+    /**
+     * Get the URL for the page.
+     *
+     * @return string
+     */
+    public function url()
+    {
+        return '/sitesearch/';
+    }
+
+    /**
+     * Assert that the browser is on the page.
+     *
+     * @param  Browser  $browser
+     * @return void
+     */
+    public function assert(Browser $browser)
+    {
+        $browser->assertPathIs($this->url())
+            ->waitForText("Hier finden Sie ein Metager-Widget für Ihre Webseite.")
+            ->assertTitle("Sitesearch-Widget - MetaGer")
+            ->type("site", "https://metager.de")
+            ->press("Generieren")
+            ->waitForLocation("/sitesearch")
+            ->waitForText("Hier finden Sie ein Metager-Widget für Ihre Webseite.")
+            ->assertTitle("Sitesearch-Widget - MetaGer")
+            ->visit($this->url())
+            ->switchLanguage("English")
+            ->waitForText("Here you find a Metager-Widget for your website.")
+            ->assertTitle("Sitesearch-Widget - MetaGer")
+            ->type("site", "https://metager.de")
+            ->press("Generate");
+        $location = "/en/sitesearch";
+        if (env("APP_ENV", "") === "production") {
+            $location = "/sitesearch";
+        }
+        $browser->waitForLocation($location)
+            ->waitForText("Here you find a Metager-Widget for your website.")
+            ->assertTitle("Sitesearch-Widget - MetaGer")
+            ->visit($this->url())
+            ->switchLanguage("Español")
+            ->waitForText("Aquí encuentra el Metger-Widget para su sitio web")
+            ->assertTitle("Widget para búsquedas dentro de tu página - MetaGer")
+            ->type("site", "https://metager.de")
+            ->press("Generar");
+        $location = "/es/sitesearch";
+        if (env("APP_ENV", "") === "production") {
+            $location = "/sitesearch";
+        }
+        $browser->waitForLocation($location)
+            ->waitForText("Aquí encuentra el Metger-Widget para su sitio web")
+            ->assertTitle("Widget para búsquedas dentro de tu página - MetaGer")
+            ->visit($this->url())
+            ->switchLanguage("Deutsch");
+    }
+
+    /**
+     * Get the element shortcuts for the page.
+     *
+     * @return array
+     */
+    public function elements()
+    {
+        return [
+            '@element' => '#selector',
+        ];
+    }
+}
diff --git a/tests/Browser/Pages/Spende.php b/tests/Browser/Pages/Spende.php
new file mode 100644
index 0000000000000000000000000000000000000000..ef37b7191451fcd8eb2b034985c81e5ce8ff681b
--- /dev/null
+++ b/tests/Browser/Pages/Spende.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Tests\Browser\Pages;
+
+use Laravel\Dusk\Browser;
+
+class Spende extends Page
+{
+    /**
+     * Get the URL for the page.
+     *
+     * @return string
+     */
+    public function url()
+    {
+        return '/spende';
+    }
+
+    /**
+     * Assert that the browser is on the page.
+     *
+     * @param  Browser  $browser
+     * @return void
+     */
+    public function assert(Browser $browser)
+    {
+        $browser->assertPathIs($this->url())
+            ->waitForText("Ihre Spende für SUMA-EV und MetaGer")
+            ->assertTitle("Spenden - MetaGer")
+            ->switchLanguage("English")
+            ->waitForText("Your Donation for MetaGer to SUMA-EV")
+            ->assertTitle("Donation - MetaGer")
+            ->switchLanguage("Español")
+            ->waitForText("Su donación para SUME-EV y MetaGer")
+            ->assertTitle("Donaciones - MetaGer")
+            ->switchLanguage("Deutsch");
+    }
+
+    /**
+     * Get the element shortcuts for the page.
+     *
+     * @return array
+     */
+    public function elements()
+    {
+        return [
+            '@element' => '#selector',
+        ];
+    }
+}
diff --git a/tests/Browser/Pages/Team.php b/tests/Browser/Pages/Team.php
new file mode 100644
index 0000000000000000000000000000000000000000..8077b875bdfa4135875044eec344df756d04e0ea
--- /dev/null
+++ b/tests/Browser/Pages/Team.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Tests\Browser\Pages;
+
+use Laravel\Dusk\Browser;
+
+class Team extends Page
+{
+    /**
+     * Get the URL for the page.
+     *
+     * @return string
+     */
+    public function url()
+    {
+        return '/team';
+    }
+
+    /**
+     * Assert that the browser is on the page.
+     *
+     * @param  Browser  $browser
+     * @return void
+     */
+    public function assert(Browser $browser)
+    {
+        $browser->assertPathIs($this->url())
+            ->waitForText("geschäftsführender Vorstand")
+            ->assertTitle("Team - MetaGer")
+            ->switchLanguage("English")
+            ->waitForText("CEO")
+            ->assertTitle("Team - MetaGer")
+            ->switchLanguage("Español")
+            ->waitForText("Director ejecutivo [CEO]")
+            ->assertTitle("Nuestra gente - MetaGer")
+            ->switchLanguage("Deutsch");
+
+    }
+
+    /**
+     * Get the element shortcuts for the page.
+     *
+     * @return array
+     */
+    public function elements()
+    {
+        return [
+            '@element' => '#selector',
+        ];
+    }
+}
diff --git a/tests/Browser/Pages/WebsearchWidget.php b/tests/Browser/Pages/WebsearchWidget.php
new file mode 100644
index 0000000000000000000000000000000000000000..d6c78492e021220aead9a7b7d80cceca96004ce7
--- /dev/null
+++ b/tests/Browser/Pages/WebsearchWidget.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Tests\Browser\Pages;
+
+use Laravel\Dusk\Browser;
+
+class WebsearchWidget extends Page
+{
+    /**
+     * Get the URL for the page.
+     *
+     * @return string
+     */
+    public function url()
+    {
+        return "/websearch/";
+    }
+
+    /**
+     * Assert that the browser is on the page.
+     *
+     * @param  Browser  $browser
+     * @return void
+     */
+    public function assert(Browser $browser)
+    {
+        $browser->assertPathIs($this->url())
+            ->waitForText("Hier finden Sie ein Metager-Widget für Ihre Webseite.")
+            ->assertTitle("Websuche-Widget - MetaGer")
+            ->switchLanguage("English")
+            ->waitForText("Here you find a Metager-Widget for your website.")
+            ->assertTitle("Websearch-Widget - MetaGer")
+            ->switchLanguage("Español")
+            ->waitForText("Aquí encuentra el MetaGer-widget para su sitio web")
+            ->assertTitle("Widget para buscar la web - MetaGer")
+            ->switchLanguage("Deutsch");
+
+    }
+
+    /**
+     * Get the element shortcuts for the page.
+     *
+     * @return array
+     */
+    public function elements()
+    {
+        return [
+            '@element' => '#selector',
+        ];
+    }
+}
diff --git a/tests/Browser/Pages/Widget.php b/tests/Browser/Pages/Widget.php
new file mode 100644
index 0000000000000000000000000000000000000000..4a3d927a0f53b3c1288eb37c2a64d58e75ce197f
--- /dev/null
+++ b/tests/Browser/Pages/Widget.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Tests\Browser\Pages;
+
+use Laravel\Dusk\Browser;
+
+class Widget extends Page
+{
+    /**
+     * Get the URL for the page.
+     *
+     * @return string
+     */
+    public function url()
+    {
+        return '/widget';
+    }
+
+    /**
+     * Assert that the browser is on the page.
+     *
+     * @param  Browser  $browser
+     * @return void
+     */
+    public function assert(Browser $browser)
+    {
+        $browser->assertPathIs($this->url())
+            ->waitForText("MetaGer zum Einbau in Ihre Webseite. Wählen Sie dafür aus, wo gesucht werden soll:")
+            ->assertTitle("MetaGer Widget")
+            ->switchLanguage("English")
+            ->waitForText("MetaGer for usage on your website. Please choose the scope of your widget:")
+            ->assertTitle("MetaGer Widget")
+            ->switchLanguage("Español")
+            ->waitForText("MetaGer: un motor de búsqueda for suyo sitio web. Selecciona por favor:")
+            ->assertTitle("MetaGer Widget")
+            ->switchLanguage("Deutsch");
+
+    }
+
+    /**
+     * Get the element shortcuts for the page.
+     *
+     * @return array
+     */
+    public function elements()
+    {
+        return [
+            '@element' => '#selector',
+        ];
+    }
+}
diff --git a/tests/Browser/StaticPagesTest.php b/tests/Browser/StaticPagesTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d1a710f0b3a2371819c51db52dd91e4c4a92e899
--- /dev/null
+++ b/tests/Browser/StaticPagesTest.php
@@ -0,0 +1,191 @@
+<?php
+
+namespace Tests\Browser;
+
+use Laravel\Dusk\Browser;
+use Tests\Browser\Pages\About;
+use Tests\Browser\Pages\App;
+use Tests\Browser\Pages\Datenschutz;
+use Tests\Browser\Pages\Hilfe;
+use Tests\Browser\Pages\HomePage;
+use Tests\Browser\Pages\Impress;
+use Tests\Browser\Pages\Kontakt;
+use Tests\Browser\Pages\Plugin;
+use Tests\Browser\Pages\SitesearchWidget;
+use Tests\Browser\Pages\Spende;
+use Tests\Browser\Pages\Team;
+use Tests\Browser\Pages\WebsearchWidget;
+use Tests\Browser\Pages\Widget;
+use Tests\DuskTestCase;
+
+class StaticPagesTest extends DuskTestCase
+{
+    private $bs = null;
+    /**
+     * Tests for each static page on MetaGers website whether it can be reached by navigation
+     *
+     * @return void
+     */
+    public function testStartpage()
+    {
+        $this->browse(function (Browser $browser) {
+            $browser->visit(new HomePage);
+        });
+    }
+
+    public function testDatenschutz()
+    {
+        $this->browse(function (Browser $browser) {
+            $browser->visit("/")
+                ->waitFor("@sidebarToggle")
+                ->click("@sidebarToggle")
+                ->clickLink("Datenschutz")
+                ->waitForLocation("/datenschutz")
+                ->on(new Datenschutz);
+        });
+    }
+
+    public function testHilfe()
+    {
+        $this->browse(function (Browser $browser) {
+            $browser->visit("/")
+                ->waitFor("@sidebarToggle")
+                ->click("@sidebarToggle")
+                ->clickLink("Hilfe")
+                ->waitForLocation("/hilfe")
+                ->on(new Hilfe);
+        });
+    }
+
+    public function testSpenden()
+    {
+        $this->browse(function (Browser $browser) {
+            $browser->visit("/")
+                ->waitFor("@sidebarToggle")
+                ->click("@sidebarToggle")
+                ->clickLink("Spenden")
+                ->waitForLocation("/spende")
+                ->on(new Spende);
+        });
+    }
+
+    public function testApp()
+    {
+        $this->browse(function (Browser $browser) {
+            $browser->visit("/")
+                ->waitFor("@sidebarToggle")
+                ->click("@sidebarToggle")
+                ->clickLink("MetaGer App")
+                ->waitForLocation("/app")
+                ->on(new App);
+        });
+    }
+
+    public function testKontakt()
+    {
+        $this->browse(function (Browser $browser) {
+            $browser->visit("/")
+                ->waitFor("@sidebarToggle")
+                ->click("@sidebarToggle")
+                ->click("label#navigationKontakt")
+                ->clickLink("Kontakt")
+                ->waitForLocation("/kontakt")
+                ->on(new Kontakt);
+        });
+    }
+
+    public function testTeam()
+    {
+        $this->browse(function (Browser $browser) {
+            $browser->visit("/")
+                ->waitFor("@sidebarToggle")
+                ->click("@sidebarToggle")
+                ->click("label#navigationKontakt")
+                ->clickLink("Team")
+                ->waitForLocation("/team")
+                ->on(new Team);
+        });
+    }
+
+    public function testAbout()
+    {
+        $this->browse(function (Browser $browser) {
+            $browser->visit("/")
+                ->waitFor("@sidebarToggle")
+                ->click("@sidebarToggle")
+                ->click("label#navigationKontakt")
+                ->clickLink("Ãœber uns")
+                ->waitForLocation("/about")
+                ->on(new About);
+        });
+    }
+
+    public function testImpress()
+    {
+        $this->browse(function (Browser $browser) {
+            $browser->visit("/")
+                ->waitFor("@sidebarToggle")
+                ->click("@sidebarToggle")
+                ->click("label#navigationKontakt")
+                ->clickLink("Impressum")
+                ->waitForLocation("/impressum")
+                ->on(new Impress);
+        });
+    }
+
+    public function testPlugin()
+    {
+        $this->browse(function (Browser $browser) {
+            $browser->visit("/")
+                ->waitFor("@sidebarToggle")
+                ->click("@sidebarToggle")
+                ->click("label[for=servicesToggle]")
+                ->clickLink("MetaGer Plugin")
+                ->waitForLocation("/plugin")
+                ->on(new Plugin);
+        });
+    }
+
+    public function testWidget()
+    {
+        $this->browse(function (Browser $browser) {
+            $browser->visit("/")
+                ->waitFor("@sidebarToggle")
+                ->click("@sidebarToggle")
+                ->click("label[for=servicesToggle]")
+                ->clickLink("Widget")
+                ->waitForLocation("/widget")
+                ->on(new Widget);
+        });
+    }
+
+    public function testWebsearchWidget()
+    {
+        $this->browse(function (Browser $browser) {
+            $browser->visit("/")
+                ->waitFor("@sidebarToggle")
+                ->click("@sidebarToggle")
+                ->click("label[for=servicesToggle]")
+                ->clickLink("Widget")
+                ->waitForLocation("/widget")
+                ->clickLink("Suche im Web")
+                ->waitForLocation("\/websearch\/")
+                ->on(new WebsearchWidget);
+        });
+    }
+
+    public function testSitesearchWidget()
+    {
+        $this->browse(function (Browser $browser) {
+            $browser->visit("/")
+                ->waitFor("@sidebarToggle")
+                ->click("@sidebarToggle")
+                ->click("label[for=servicesToggle]")
+                ->clickLink("Widget")
+                ->waitForLocation("/widget")
+                ->clickLink("Suche nur auf einer Domain")
+                ->waitForLocation("/sitesearch/")
+                ->on(new SitesearchWidget);
+        });
+    }
+}
diff --git a/tests/DuskTestCase.php b/tests/DuskTestCase.php
new file mode 100644
index 0000000000000000000000000000000000000000..e22536cbfd9e21df44a3184d6f8550d0db90bbde
--- /dev/null
+++ b/tests/DuskTestCase.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Tests;
+
+use Facebook\WebDriver\Remote\RemoteWebDriver;
+use Laravel\Dusk\TestCase as BaseTestCase;
+use \App\Traits\SupportsBrowserStack;
+
+abstract class DuskTestCase extends BaseTestCase
+{
+    use CreatesApplication;
+    use SupportsBrowserStack;
+
+    /**
+     * Prepare for Dusk test execution.
+     *
+     * @beforeClass
+     * @return void
+     */
+    public static function prepare()
+    {
+    }
+
+    /**
+     * Create the RemoteWebDriver instance.
+     *
+     * @return \Facebook\WebDriver\Remote\RemoteWebDriver
+     */
+    protected function driver()
+    {
+        $capabilities = [
+            "os" => "Windows",
+            "os_version" => "10",
+            "browser" => "Firefox",
+            "browser_version" => "79.0 beta",
+            "resolution" => "1920x1080",
+            "project" => env("PROJECT_NAME", "Not Set"),
+            "build" => env("BRANCH_NAME", "Not Set"),
+            "name" => env("COMMIT_NAME", "Not Set"),
+            "browserstack.local" => "true",
+            "browserstack.console" => "verbose",
+            "browserstack.networkLogs" => "true",
+            "browserstack.timezone" => "Europe/Berlin",
+            "browserstack.selenium_version" => "3.5.2",
+        ];
+        if (env("APP_URL", "") !== "http://nginx") {
+            # Not local Testing
+            $capabilities["browserstack.local"] = "false";
+        }
+        return $this->createBrowserStackDriver([
+            "username" => env("WEBDRIVER_USER", ""),
+            "key" => env("WEBDRIVER_KEY", ""),
+            "capabilities" => $capabilities,
+        ]);
+    }
+}