From 2af7c07f7581de9e1f94dbeaf2e2623cf1832e6d Mon Sep 17 00:00:00 2001
From: Michael Lee <lee.5151@osu.edu>
Date: Thu, 10 Feb 2022 15:14:56 -0500
Subject: [PATCH] Upgrading drupal/captcha (1.1.0 => 1.2.0)

---
 composer.lock                                 |  14 +-
 vendor/composer/autoload_classmap.php         |   2 -
 vendor/composer/autoload_static.php           |   2 -
 vendor/composer/installed.json                |  16 +-
 vendor/composer/installed.php                 |  10 +-
 web/modules/captcha/README.md                 |   6 +
 web/modules/captcha/captcha.inc               |  14 +
 web/modules/captcha/captcha.info.yml          |   6 +-
 web/modules/captcha/captcha.module            | 170 ++++++++----
 .../config/schema/captcha.settings.yml        |   3 +
 .../image_captcha/image_captcha.info.yml      |   8 +-
 .../image_captcha/image_captcha.install       |   4 +-
 .../image_captcha/image_captcha.libraries.yml |   3 +
 .../image_captcha.links.menu.yml              |   6 +
 .../image_captcha.links.task.yml              |   5 +
 .../image_captcha/image_captcha.module        |  56 ++++
 .../image_captcha/image_captcha.routing.yml   |   2 +-
 .../image_captcha/image_captcha_refresh.css   |  38 +++
 .../image_captcha/js/image_captcha_refresh.js |   3 +
 .../CaptchaFontPreviewController.php          |  59 ++++
 .../CaptchaImageGeneratorController.php       |  26 +-
 .../src/Form/ImageCaptchaSettingsForm.php     |  72 ++---
 .../src/Response/CaptchaImageResponse.php     |  31 ++-
 .../CaptchaFontPreviewStreamedResponse.php}   |  53 ++--
 .../captcha/migrations/d7_captcha_points.yml  |  18 ++
 .../migrations/d7_captcha_settings.yml        |  36 +++
 .../captcha_long_form_id_test.info.yml        |   6 +-
 .../captcha_test/captcha_test.info.yml        |  11 +
 .../modules/captcha_test/captcha_test.module  |  45 +++
 web/modules/captcha/src/Element/Captcha.php   |   2 +-
 .../captcha/src/Form/CaptchaSettingsForm.php  |  13 +-
 .../migrate/process/CaptchaTypeFormatter.php  |  35 +++
 .../Plugin/migrate/source/CaptchaPoints.php   |  68 +++++
 .../captcha/tests/fixtures/drupal7.php        | 256 ++++++++++++++++++
 .../tests/src/Functional/CaptchaAdminTest.php | 133 +++++----
 .../tests/src/Functional/CaptchaCacheTest.php |  74 ++++-
 .../tests/src/Functional/CaptchaCronTest.php  |   2 +-
 .../src/Functional/CaptchaPersistenceTest.php |  17 +-
 .../CaptchaSessionReuseAttackTestCase.php     |  35 +--
 .../tests/src/Functional/CaptchaTest.php      |  72 ++++-
 .../src/Functional/CaptchaWebTestBase.php     |  14 +-
 .../Migrate/d7/MigrateCaptchaPointsTest.php   |  94 +++++++
 .../MigrateCaptchaSimpleConfigurationTest.php |  69 +++++
 43 files changed, 1321 insertions(+), 288 deletions(-)
 create mode 100644 web/modules/captcha/image_captcha/image_captcha.links.menu.yml
 create mode 100644 web/modules/captcha/image_captcha/image_captcha.links.task.yml
 create mode 100644 web/modules/captcha/image_captcha/image_captcha_refresh.css
 create mode 100644 web/modules/captcha/image_captcha/src/Controller/CaptchaFontPreviewController.php
 rename web/modules/captcha/image_captcha/src/{Controller/CaptchaFontPreview.php => StreamedResponse/CaptchaFontPreviewStreamedResponse.php} (60%)
 create mode 100644 web/modules/captcha/migrations/d7_captcha_points.yml
 create mode 100644 web/modules/captcha/migrations/d7_captcha_settings.yml
 create mode 100644 web/modules/captcha/modules/captcha_test/captcha_test.info.yml
 create mode 100644 web/modules/captcha/modules/captcha_test/captcha_test.module
 create mode 100644 web/modules/captcha/src/Plugin/migrate/process/CaptchaTypeFormatter.php
 create mode 100644 web/modules/captcha/src/Plugin/migrate/source/CaptchaPoints.php
 create mode 100644 web/modules/captcha/tests/fixtures/drupal7.php
 create mode 100644 web/modules/captcha/tests/src/Kernel/Migrate/d7/MigrateCaptchaPointsTest.php
 create mode 100644 web/modules/captcha/tests/src/Kernel/Migrate/d7/MigrateCaptchaSimpleConfigurationTest.php

diff --git a/composer.lock b/composer.lock
index dd6dcc6451..d9e5d40ed0 100644
--- a/composer.lock
+++ b/composer.lock
@@ -2280,17 +2280,17 @@
         },
         {
             "name": "drupal/captcha",
-            "version": "1.1.0",
+            "version": "1.2.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/captcha.git",
-                "reference": "8.x-1.1"
+                "reference": "8.x-1.2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/captcha-8.x-1.1.zip",
-                "reference": "8.x-1.1",
-                "shasum": "2eaf6f00ea256652c09f785b26dc933cc5b3dd26"
+                "url": "https://ftp.drupal.org/files/projects/captcha-8.x-1.2.zip",
+                "reference": "8.x-1.2",
+                "shasum": "e35a2ce42b652f833d140f7571d1eef0e06b0edc"
             },
             "require": {
                 "drupal/core": "^8.8 || ^9"
@@ -2298,8 +2298,8 @@
             "type": "drupal-module",
             "extra": {
                 "drupal": {
-                    "version": "8.x-1.1",
-                    "datestamp": "1591160977",
+                    "version": "8.x-1.2",
+                    "datestamp": "1619673374",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
index 8bdfb0c588..95ebedfe9f 100644
--- a/vendor/composer/autoload_classmap.php
+++ b/vendor/composer/autoload_classmap.php
@@ -2278,8 +2278,6 @@
     'Drupal\\Core\\Language\\LanguageManager' => $baseDir . '/web/core/lib/Drupal/Core/Language/LanguageManager.php',
     'Drupal\\Core\\Language\\LanguageManagerInterface' => $baseDir . '/web/core/lib/Drupal/Core/Language/LanguageManagerInterface.php',
     'Drupal\\Core\\Layout\\Annotation\\Layout' => $baseDir . '/web/core/lib/Drupal/Core/Layout/Annotation/Layout.php',
-    'Drupal\\Core\\Layout\\Icon\\IconBuilderInterface' => $baseDir . '/web/core/lib/Drupal/Core/Layout/Icon/IconBuilderInterface.php',
-    'Drupal\\Core\\Layout\\Icon\\SvgIconBuilder' => $baseDir . '/web/core/lib/Drupal/Core/Layout/Icon/SvgIconBuilder.php',
     'Drupal\\Core\\Layout\\LayoutDefault' => $baseDir . '/web/core/lib/Drupal/Core/Layout/LayoutDefault.php',
     'Drupal\\Core\\Layout\\LayoutDefinition' => $baseDir . '/web/core/lib/Drupal/Core/Layout/LayoutDefinition.php',
     'Drupal\\Core\\Layout\\LayoutInterface' => $baseDir . '/web/core/lib/Drupal/Core/Layout/LayoutInterface.php',
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
index 3ff8bd2803..13f2b32f3a 100644
--- a/vendor/composer/autoload_static.php
+++ b/vendor/composer/autoload_static.php
@@ -3064,8 +3064,6 @@ class ComposerStaticInit5c689ffcd54b9e495ed983fdce09b530
         'Drupal\\Core\\Language\\LanguageManager' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Language/LanguageManager.php',
         'Drupal\\Core\\Language\\LanguageManagerInterface' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Language/LanguageManagerInterface.php',
         'Drupal\\Core\\Layout\\Annotation\\Layout' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Layout/Annotation/Layout.php',
-        'Drupal\\Core\\Layout\\Icon\\IconBuilderInterface' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Layout/Icon/IconBuilderInterface.php',
-        'Drupal\\Core\\Layout\\Icon\\SvgIconBuilder' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Layout/Icon/SvgIconBuilder.php',
         'Drupal\\Core\\Layout\\LayoutDefault' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Layout/LayoutDefault.php',
         'Drupal\\Core\\Layout\\LayoutDefinition' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Layout/LayoutDefinition.php',
         'Drupal\\Core\\Layout\\LayoutInterface' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Layout/LayoutInterface.php',
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 24aec2af9b..359dd1abc7 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -2350,18 +2350,18 @@
         },
         {
             "name": "drupal/captcha",
-            "version": "1.1.0",
-            "version_normalized": "1.1.0.0",
+            "version": "1.2.0",
+            "version_normalized": "1.2.0.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/captcha.git",
-                "reference": "8.x-1.1"
+                "reference": "8.x-1.2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/captcha-8.x-1.1.zip",
-                "reference": "8.x-1.1",
-                "shasum": "2eaf6f00ea256652c09f785b26dc933cc5b3dd26"
+                "url": "https://ftp.drupal.org/files/projects/captcha-8.x-1.2.zip",
+                "reference": "8.x-1.2",
+                "shasum": "e35a2ce42b652f833d140f7571d1eef0e06b0edc"
             },
             "require": {
                 "drupal/core": "^8.8 || ^9"
@@ -2369,8 +2369,8 @@
             "type": "drupal-module",
             "extra": {
                 "drupal": {
-                    "version": "8.x-1.1",
-                    "datestamp": "1591160977",
+                    "version": "8.x-1.2",
+                    "datestamp": "1619673374",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
index 3b1cc54827..168c2ae453 100644
--- a/vendor/composer/installed.php
+++ b/vendor/composer/installed.php
@@ -5,7 +5,7 @@
         'type' => 'project',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
-        'reference' => '017d554d05ee95263629d62bfe1ad0b3dcb57e47',
+        'reference' => '9338a6f25615021ab7915cd9aa7304c374d1a068',
         'name' => 'osu-asc-webservices/d8-upstream',
         'dev' => true,
     ),
@@ -434,12 +434,12 @@
             'dev_requirement' => false,
         ),
         'drupal/captcha' => array(
-            'pretty_version' => '1.1.0',
-            'version' => '1.1.0.0',
+            'pretty_version' => '1.2.0',
+            'version' => '1.2.0.0',
             'type' => 'drupal-module',
             'install_path' => __DIR__ . '/../../web/modules/captcha',
             'aliases' => array(),
-            'reference' => '8.x-1.1',
+            'reference' => '8.x-1.2',
             'dev_requirement' => false,
         ),
         'drupal/ckeditor' => array(
@@ -2101,7 +2101,7 @@
             'type' => 'project',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
-            'reference' => '017d554d05ee95263629d62bfe1ad0b3dcb57e47',
+            'reference' => '9338a6f25615021ab7915cd9aa7304c374d1a068',
             'dev_requirement' => false,
         ),
         'pantheon-systems/quicksilver-pushback' => array(
diff --git a/web/modules/captcha/README.md b/web/modules/captcha/README.md
index 61f7cf8301..771e737a4b 100755
--- a/web/modules/captcha/README.md
+++ b/web/modules/captcha/README.md
@@ -83,3 +83,9 @@ MAINTAINERS
 Supporting organizations:
 
  * Chuva Inc. - https://www.drupal.org/chuva-inc
+
+DEVELOPMENT
+-------------
+  You can disable captcha in your local or test environment by adding the
+  following line to settings.php:
+  $settings['disable_captcha'] = TRUE;
diff --git a/web/modules/captcha/captcha.inc b/web/modules/captcha/captcha.inc
index b1fa2379aa..f390faadd1 100755
--- a/web/modules/captcha/captcha.inc
+++ b/web/modules/captcha/captcha.inc
@@ -178,6 +178,20 @@ function _captcha_get_description() {
   return Xss::filter($description);
 }
 
+/**
+ * Gets the error message for when a user enters an incorrect CAPTCHA answer.
+ *
+ * @return string
+ *   Error message.
+ */
+function _captcha_get_error_message() {
+  $error_message = \Drupal::config('captcha.settings')->get('wrong_captcha_response_message');
+  if ($error_message) {
+    return Xss::filter($error_message);
+  }
+  return t('The answer you entered for the CAPTCHA was not correct.');
+}
+
 /**
  * Parse or interpret the given captcha_type.
  *
diff --git a/web/modules/captcha/captcha.info.yml b/web/modules/captcha/captcha.info.yml
index 5fd7cf593a..d197d084a6 100644
--- a/web/modules/captcha/captcha.info.yml
+++ b/web/modules/captcha/captcha.info.yml
@@ -8,7 +8,7 @@ configure: captcha_settings
 dependencies:
   - drupal:node
 
-# Information added by Drupal.org packaging script on 2020-06-03
-version: '8.x-1.1'
+# Information added by Drupal.org packaging script on 2021-04-29
+version: '8.x-1.2'
 project: 'captcha'
-datestamp: 1591160979
+datestamp: 1619673377
diff --git a/web/modules/captcha/captcha.module b/web/modules/captcha/captcha.module
index 8174a92362..0b298252c8 100755
--- a/web/modules/captcha/captcha.module
+++ b/web/modules/captcha/captcha.module
@@ -19,6 +19,7 @@
 use Drupal\Core\Render\Markup;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Url;
+use Drupal\Core\Site\Settings;
 
 /**
  * Constants for CAPTCHA persistence.
@@ -288,6 +289,22 @@ function captcha_form_alter(array &$form, FormStateInterface $form_state, $form_
       '#markup' => '<div class="messages messages--warning">' . t('Most CAPTCHA methods will disable the caching of pages that contain a CAPTCHA element. Check the different implementations to know more about how it affects caching.') . '</div>',
     ];
   }
+
+  // Disable captcha if override is set.
+  if (Settings::get('disable_captcha', FALSE) === TRUE) {
+    $override_notice = [
+      '#type' => 'html_tag',
+      '#tag' => 'strong',
+      '#value' => t('Captcha is currently disabled via settings.php.'),
+    ];
+    if (isset($form['elements']['captcha'])) {
+      $form['elements']['captcha'] = $override_notice;
+    }
+    if (isset($form['captcha'])) {
+      $form['captcha'] = $override_notice;
+    }
+  }
+
 }
 
 /**
@@ -465,71 +482,34 @@ function captcha_validate($element, FormStateInterface &$form_state) {
   // TODO: is this correct in all cases: see comments in previous revisions?
   $csid = $captcha_info['captcha_sid'];
 
-  $solution = \Drupal::database()
-    ->select('captcha_sessions', 'cs')
-    ->fields('cs', ['solution'])
-    ->condition('csid', $csid)
-    ->execute()
-    ->fetchField();
-
   // Bypass captcha validation if access attribute value is false.
   if (empty($captcha_info['access'])) {
     return FALSE;
   }
 
-  // @todo: what is the result when there is no entry for
-  // the captcha_session? in D6 it was FALSE, what in D7?
-  if ($solution === FALSE) {
-    // Unknown challenge_id.
-    // TODO: this probably never happens anymore now that there is detection
-    // for CAPTCHA session reuse attacks in _captcha_get_posted_captcha_info().
-    $form_state->setErrorByName('captcha', t('CAPTCHA validation error: unknown CAPTCHA session ID. Contact the site administrator if this problem persists.'));
-    \Drupal::logger('CAPTCHA')->error(
-      'CAPTCHA validation error: unknown CAPTCHA session ID (%csid).',
-      ['%csid' => var_export($csid, TRUE)]);
-  }
-  else {
-    // Get CAPTCHA validate function or fall back on strict equality.
+  // If the form is cacheable where all solution validation is handed off or if
+  // we found a session with a solution then continue with validation.
+  $is_cacheable = (bool) $form_state->getValue('captcha_cacheable', FALSE);
+
+  if ($is_cacheable) {
+    // Completely ignore the captcha_sessions table,
+    // since the captcha_sid can get reused by the cache.
+    $solution = FALSE;
     $captcha_validate = $element['#captcha_validate'];
     if (!function_exists($captcha_validate)) {
-      $captcha_validate = 'captcha_validate_strict_equality';
+      // Cacheable CAPTCHAs must provide their own validation function.
+      $form_state->setErrorByName('captcha', t('CAPTCHA configuration error: Contact the site administrator.'));
+      \Drupal::logger('CAPTCHA')->error(
+        'CAPTCHA configuration error: cacheable CAPTCHA type %challenge did not provide a validation function.',
+        ['%challenge' => $captcha_info['captcha_type']]);
     }
     // Check the response with the CAPTCHA validation function.
     // Apart from the traditional expected $solution and received $response,
     // we also provide the CAPTCHA $element and $form_state
     // arrays for more advanced use cases.
-    if ($captcha_validate($solution, $captcha_response, $element, $form_state)) {
-
-      // Get the CAPTCHA persistence setting.
-      $captcha_persistence = \Drupal::config('captcha.settings')
-        ->get('persistence');
-
-      if (in_array($captcha_persistence,
-        [
-          CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL,
-          CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_TYPE,
-        ])) {
-        // Only save the success in $_SESSION if it is actually needed for
-        // further validation in _captcha_required_for_user(). Setting
-        // this kills the page cache so let's not be cavalier about it.
-        $_SESSION['captcha_success_form_ids'][$form_id] = $form_id;
-      }
-
-      // Record success.
-      \Drupal::database()->update('captcha_sessions')
-        ->condition('csid', $csid)
-        ->fields(['status' => CAPTCHA_STATUS_SOLVED])
-        ->expression('attempts', 'attempts + 1')
-        ->execute();
-    }
-    else {
+    if (!$captcha_validate($solution, $captcha_response, $element, $form_state)) {
       // Wrong answer.
-      \Drupal::database()->update('captcha_sessions')
-        ->condition('csid', $csid)
-        ->expression('attempts', 'attempts + 1')
-        ->execute();
-
-      $form_state->setErrorByName('captcha_response', t('The answer you entered for the CAPTCHA was not correct.'));
+      $form_state->setErrorByName('captcha_response', _captcha_get_error_message());
       // Update wrong response counter.
       if (\Drupal::config('captcha.settings')->get('enable_stats', FALSE)) {
         Drupal::state()->set('captcha.wrong_response_counter', Drupal::state()
@@ -540,17 +520,97 @@ function captcha_validate($element, FormStateInterface &$form_state) {
         ->get('log_wrong_responses', FALSE)
       ) {
         \Drupal::logger('CAPTCHA')->notice(
-          '%form_id post blocked by CAPTCHA module: challenge %challenge (by module %module), user answered "@response", but the solution was "@solution".',
+          '%form_id post blocked by CAPTCHA module: challenge %challenge (by module %module).',
           [
             '%form_id' => $form_id,
-            '@response' => $captcha_response,
-            '@solution' => $solution,
             '%challenge' => $captcha_info['captcha_type'],
             '%module' => $captcha_info['module'],
           ]);
       }
     }
   }
+  else {
+    $solution = \Drupal::database()
+      ->select('captcha_sessions', 'cs')
+      ->fields('cs', ['solution'])
+      ->condition('csid', $csid)
+      ->execute()
+      ->fetchField();
+
+    if ($solution !== FALSE) {
+      // Get CAPTCHA validate function or fall back on strict equality.
+      $captcha_validate = $element['#captcha_validate'];
+      if (!function_exists($captcha_validate)) {
+        $captcha_validate = 'captcha_validate_strict_equality';
+      }
+      // Check the response with the CAPTCHA validation function.
+      // Apart from the traditional expected $solution and received $response,
+      // we also provide the CAPTCHA $element and $form_state
+      // arrays for more advanced use cases.
+      if ($captcha_validate($solution, $captcha_response, $element, $form_state)) {
+
+        // Get the CAPTCHA persistence setting.
+        $captcha_persistence = \Drupal::config('captcha.settings')
+          ->get('persistence');
+
+        if (in_array($captcha_persistence,
+          [
+            CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL,
+            CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_TYPE,
+          ])) {
+          // Only save the success in $_SESSION if it is actually needed for
+          // further validation in _captcha_required_for_user(). Setting
+          // this kills the page cache so let's not be cavalier about it.
+          $_SESSION['captcha_success_form_ids'][$form_id] = $form_id;
+        }
+
+        // Record success.
+        \Drupal::database()->update('captcha_sessions')
+          ->condition('csid', $csid)
+          ->fields(['status' => CAPTCHA_STATUS_SOLVED])
+          ->expression('attempts', 'attempts + 1')
+          ->execute();
+      }
+      else {
+        // Wrong answer.
+        \Drupal::database()->update('captcha_sessions')
+          ->condition('csid', $csid)
+          ->expression('attempts', 'attempts + 1')
+          ->execute();
+
+        $form_state->setErrorByName('captcha_response', _captcha_get_error_message());
+        // Update wrong response counter.
+        if (\Drupal::config('captcha.settings')->get('enable_stats', FALSE)) {
+          Drupal::state()->set('captcha.wrong_response_counter', Drupal::state()
+            ->get('captcha.wrong_response_counter', 0) + 1);
+        }
+
+        if (\Drupal::config('captcha.settings')
+          ->get('log_wrong_responses', FALSE)
+        ) {
+          \Drupal::logger('CAPTCHA')->notice(
+            '%form_id post blocked by CAPTCHA module: challenge %challenge (by module %module), user answered "@response", but the solution was "@solution".',
+            [
+              '%form_id' => $form_id,
+              '@response' => $captcha_response,
+              '@solution' => $solution,
+              '%challenge' => $captcha_info['captcha_type'],
+              '%module' => $captcha_info['module'],
+            ]);
+        }
+      }
+    }
+    else {
+      // If the session is gone and we can't confirm a solution error.
+      // Note: _captcha_get_posted_captcha_info() validates and triggers session
+      // rebuilds for re-use attacks during element processing so this should be
+      // rare if it ever happens.
+      $form_state->setErrorByName('captcha', t('CAPTCHA validation error: unknown CAPTCHA session ID. Contact the site administrator if this problem persists.'));
+      \Drupal::logger('CAPTCHA')->error(
+        'CAPTCHA validation error: unknown CAPTCHA session ID (%csid).',
+        ['%csid' => var_export($csid, TRUE)]);
+    }
+  }
 }
 
 /**
diff --git a/web/modules/captcha/config/schema/captcha.settings.yml b/web/modules/captcha/config/schema/captcha.settings.yml
index b91cb03a54..483148a24f 100755
--- a/web/modules/captcha/config/schema/captcha.settings.yml
+++ b/web/modules/captcha/config/schema/captcha.settings.yml
@@ -23,6 +23,9 @@ captcha.settings:
     add_captcha_description:
       type: boolean
       label: 'Add a description to the CAPTCHA'
+    wrong_captcha_response_message:
+      type: label
+      label: 'The error message when a user has entered an incorrect CAPTCHA answer.'
     default_validation:
       type: integer
       label: 'Default CAPTCHA validation'
diff --git a/web/modules/captcha/image_captcha/image_captcha.info.yml b/web/modules/captcha/image_captcha/image_captcha.info.yml
index a1d5195a7f..859edd8942 100644
--- a/web/modules/captcha/image_captcha/image_captcha.info.yml
+++ b/web/modules/captcha/image_captcha/image_captcha.info.yml
@@ -4,10 +4,10 @@ description: Provides an image based CAPTCHA.
 package: Spam control
 core_version_requirement: ^8.8 || ^9
 dependencies:
-  - captcha
+  - captcha:captcha
 configure: admin/config/people/captcha/image_captcha
 
-# Information added by Drupal.org packaging script on 2020-06-03
-version: '8.x-1.1'
+# Information added by Drupal.org packaging script on 2021-04-29
+version: '8.x-1.2'
 project: 'captcha'
-datestamp: 1591160979
+datestamp: 1619673377
diff --git a/web/modules/captcha/image_captcha/image_captcha.install b/web/modules/captcha/image_captcha/image_captcha.install
index 003f224697..913fbef329 100755
--- a/web/modules/captcha/image_captcha/image_captcha.install
+++ b/web/modules/captcha/image_captcha/image_captcha.install
@@ -38,7 +38,7 @@ function image_captcha_install() {
   $config = \Drupal::configFactory()->getEditable('image_captcha.settings');
 
   $config->set('image_captcha_fonts', [
-    drupal_get_path('module', 'image_captcha') . '/fonts/Tesox/tesox.ttf',
-    drupal_get_path('module', 'image_captcha') . '/fonts/Tuffy/Tuffy.ttf',
+    hash('sha256', drupal_get_path('module', 'image_captcha') . '/fonts/Tesox/tesox.ttf'),
+    hash('sha256', drupal_get_path('module', 'image_captcha') . '/fonts/Tuffy/Tuffy.ttf'),
   ])->save(TRUE);
 }
diff --git a/web/modules/captcha/image_captcha/image_captcha.libraries.yml b/web/modules/captcha/image_captcha/image_captcha.libraries.yml
index 4f37b86f0d..b9b8de2bc8 100755
--- a/web/modules/captcha/image_captcha/image_captcha.libraries.yml
+++ b/web/modules/captcha/image_captcha/image_captcha.libraries.yml
@@ -15,3 +15,6 @@ image-captcha-refresh:
   version: 1.0
   js:
     js/image_captcha_refresh.js: {}
+  css:
+    theme:
+      image_captcha_refresh.css: {}
diff --git a/web/modules/captcha/image_captcha/image_captcha.links.menu.yml b/web/modules/captcha/image_captcha/image_captcha.links.menu.yml
new file mode 100644
index 0000000000..b2f3731dcc
--- /dev/null
+++ b/web/modules/captcha/image_captcha/image_captcha.links.menu.yml
@@ -0,0 +1,6 @@
+image_captcha.settings:
+  title: 'Image Captcha'
+  route_name: image_captcha.settings
+  description: 'Configure Image Captcha Settings.'
+  parent: captcha.settings
+  weight: -1
diff --git a/web/modules/captcha/image_captcha/image_captcha.links.task.yml b/web/modules/captcha/image_captcha/image_captcha.links.task.yml
new file mode 100644
index 0000000000..0e18abcbf4
--- /dev/null
+++ b/web/modules/captcha/image_captcha/image_captcha.links.task.yml
@@ -0,0 +1,5 @@
+image_captcha.settings:
+  title: 'Image Captcha'
+  route_name: image_captcha.settings
+  base_route: captcha_settings
+  weight: -1
diff --git a/web/modules/captcha/image_captcha/image_captcha.module b/web/modules/captcha/image_captcha/image_captcha.module
index 8832ac80b6..7ad3c1ad26 100755
--- a/web/modules/captcha/image_captcha/image_captcha.module
+++ b/web/modules/captcha/image_captcha/image_captcha.module
@@ -8,6 +8,7 @@
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Url;
 use Drupal\Core\Link;
+use Drupal\Core\DrupalKernel;
 
 define('IMAGE_CAPTCHA_ALLOWED_CHARACTERS', 'aAbBCdEeFfGHhijKLMmNPQRrSTtWXYZ23456789');
 
@@ -47,6 +48,61 @@ function _image_captcha_get_enabled_fonts() {
   }
 }
 
+/**
+ * Helper function to get font(s).
+ *
+ * @return string|array
+ *   URI of file hash or List of font paths.
+ */
+function _image_captcha_get_font_uri($token = NULL) {
+  $fonts = [
+    'BUILTIN' => 'BUILTIN',
+  ];
+  $available_fonts = _image_captcha_get_available_fonts_from_directories();
+  foreach ($available_fonts as $file_token => $font_info) {
+    $fonts[$file_token] = $font_info['uri'];
+  }
+  return ((!empty($token) && !empty($fonts[$token])) ? $fonts[$token] : $fonts);
+}
+
+/**
+ * Helper function to get fonts from the given directories.
+ *
+ * @param array|null $directories
+ *   (Optional) an array of directories
+ *   to recursively search through, if not given, the default
+ *   directories will be used.
+ *
+ * @return array
+ *   Fonts file objects (with fields 'name',
+ *   'basename' and 'filename'), keyed on the sha256 hash of the font
+ *   path (to have an easy token that can be used in an url
+ *   without en/decoding issues).
+ */
+function _image_captcha_get_available_fonts_from_directories($directories = NULL) {
+  // If no fonts directories are given: use the default.
+  if ($directories === NULL) {
+    $request = \Drupal::service('request_stack')->getCurrentRequest();
+    $directories = [
+      drupal_get_path('module', 'image_captcha') . '/fonts',
+      'sites/all/libraries/fonts',
+      DrupalKernel::findSitePath($request) . '/libraries/fonts',
+    ];
+  }
+  // Collect the font information.
+  $fonts = [];
+  foreach ($directories as $directory) {
+    if (\Drupal::service('file_system')->prepareDirectory($directory)) {
+      $files = \Drupal::service('file_system')->scanDirectory($directory, '/\.[tT][tT][fF]$/');
+      foreach ($files as $filename => $font) {
+        $fonts[hash('sha256', $filename)] = (array) $font;
+      }
+    }
+  }
+
+  return $fonts;
+}
+
 /**
  * Helper function for checking if the specified fonts are available.
  *
diff --git a/web/modules/captcha/image_captcha/image_captcha.routing.yml b/web/modules/captcha/image_captcha/image_captcha.routing.yml
index bd6c3ad866..e3d92a502e 100755
--- a/web/modules/captcha/image_captcha/image_captcha.routing.yml
+++ b/web/modules/captcha/image_captcha/image_captcha.routing.yml
@@ -8,7 +8,7 @@ image_captcha.settings:
 image_captcha.font_preview:
   path: '/admin/config/people/captcha/image_captcha/font_preview/{token}'
   defaults:
-    _controller: '\Drupal\image_captcha\Controller\CaptchaFontPreview::content'
+    _controller: '\Drupal\image_captcha\Controller\CaptchaFontPreviewController::getFont'
   requirements:
     _permission: 'administer CAPTCHA settings'
 
diff --git a/web/modules/captcha/image_captcha/image_captcha_refresh.css b/web/modules/captcha/image_captcha/image_captcha_refresh.css
new file mode 100644
index 0000000000..325b1f3f79
--- /dev/null
+++ b/web/modules/captcha/image_captcha/image_captcha_refresh.css
@@ -0,0 +1,38 @@
+.image_captcha_refresh_loader {
+  border: 5px solid #f3f3f3;
+  border-radius: 50%;
+  border-top: 5px solid #000;
+  width: 30px;
+  height: 30px;
+  -webkit-animation: image_captcha_refresh_spin 1s linear infinite;
+  animation: image_captcha_refresh_spin 1s linear infinite;
+  position: absolute;
+  left: 0;
+  right: 0;
+  margin: auto;
+  top: 0;
+  bottom: 0;
+}
+
+/* Safari */
+@-webkit-keyframes image_captcha_refresh_spin {
+  0% {
+    -webkit-transform: rotate(0deg);
+}
+  100% {
+    -webkit-transform: rotate(360deg);
+}
+}
+
+@keyframes image_captcha_refresh_spin {
+  0% {
+    transform: rotate(0deg);
+}
+  100% {
+    transform: rotate(360deg);
+}
+}
+
+.captcha {
+  position: relative;
+}
diff --git a/web/modules/captcha/image_captcha/js/image_captcha_refresh.js b/web/modules/captcha/image_captcha/js/image_captcha_refresh.js
index 457c58e7d1..807fa510cc 100644
--- a/web/modules/captcha/image_captcha/js/image_captcha_refresh.js
+++ b/web/modules/captcha/image_captcha/js/image_captcha_refresh.js
@@ -24,6 +24,8 @@
         var date = new Date();
         var baseUrl = document.location.origin;
         var url = baseUrl + '/' + $(this).attr('href') + '?' + date.getTime();
+        // Adding loader.
+        $('.captcha').append('<div class="image_captcha_refresh_loader"></div>');
         $.get(
           url,
           {},
@@ -32,6 +34,7 @@
               $('.captcha', $form).find('img').attr('src', response.data.url);
               $('input[name=captcha_sid]', $form).val(response.data.sid);
               $('input[name=captcha_token]', $form).val(response.data.token);
+              $('.captcha .image_captcha_refresh_loader').remove();
             }
             else {
               alert(response.message);
diff --git a/web/modules/captcha/image_captcha/src/Controller/CaptchaFontPreviewController.php b/web/modules/captcha/image_captcha/src/Controller/CaptchaFontPreviewController.php
new file mode 100644
index 0000000000..ed405fcff9
--- /dev/null
+++ b/web/modules/captcha/image_captcha/src/Controller/CaptchaFontPreviewController.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Drupal\image_captcha\Controller;
+
+use Drupal\Core\Config\ImmutableConfig;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\image_captcha\StreamedResponse\CaptchaFontPreviewStreamedResponse;
+use Drupal\Core\PageCache\ResponsePolicy\KillSwitch;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Controller which generates the image from defined settings.
+ */
+class CaptchaFontPreviewController implements ContainerInjectionInterface {
+
+  /**
+   * Image Captcha config storage.
+   *
+   * @var \Drupal\Core\Config\ImmutableConfig
+   */
+  protected $config;
+
+  /**
+   * Kill Switch for page caching.
+   *
+   * @var \Drupal\Core\PageCache\ResponsePolicy\KillSwitch
+   */
+  protected $killSwitch;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(ImmutableConfig $config, KillSwitch $kill_switch) {
+    $this->config = $config;
+    $this->killSwitch = $kill_switch;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config.factory')->get('image_captcha.settings'),
+      $container->get('page_cache_kill_switch')
+    );
+  }
+
+  /**
+   * Main method that throw ImageResponse object to generate image.
+   *
+   * @return \Drupal\image_captcha\StreamedResponse\CaptchaFontPreviewStreamedResponse
+   *   Make a CaptchaImageResponse with the correct configuration and return it.
+   */
+  public function getFont($token) {
+    $this->killSwitch->trigger();
+    return new CaptchaFontPreviewStreamedResponse($this->config, $token);
+  }
+
+}
diff --git a/web/modules/captcha/image_captcha/src/Controller/CaptchaImageGeneratorController.php b/web/modules/captcha/image_captcha/src/Controller/CaptchaImageGeneratorController.php
index 49bef40e60..241f2f4442 100644
--- a/web/modules/captcha/image_captcha/src/Controller/CaptchaImageGeneratorController.php
+++ b/web/modules/captcha/image_captcha/src/Controller/CaptchaImageGeneratorController.php
@@ -3,6 +3,8 @@
 namespace Drupal\image_captcha\Controller;
 
 use Drupal\Core\Config\Config;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\File\FileSystemInterface;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Psr\Log\LoggerInterface;
 use Drupal\Core\PageCache\ResponsePolicy\KillSwitch;
@@ -14,6 +16,13 @@
  */
 class CaptchaImageGeneratorController implements ContainerInjectionInterface {
 
+  /**
+   * Connection container.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $connection;
+
   /**
    * Image Captcha config storage.
    *
@@ -21,6 +30,13 @@ class CaptchaImageGeneratorController implements ContainerInjectionInterface {
    */
   protected $config;
 
+  /**
+   * File System Service.
+   *
+   * @var \Drupal\Core\File\FileSystemInterface
+   */
+  protected $fileSystem;
+
   /**
    * Watchdog logger channel for captcha.
    *
@@ -38,10 +54,12 @@ class CaptchaImageGeneratorController implements ContainerInjectionInterface {
   /**
    * {@inheritdoc}
    */
-  public function __construct(Config $config, LoggerInterface $logger, KillSwitch $kill_switch) {
+  public function __construct(Config $config, LoggerInterface $logger, KillSwitch $kill_switch, Connection $connection, FileSystemInterface $file_system) {
     $this->config = $config;
     $this->logger = $logger;
     $this->killSwitch = $kill_switch;
+    $this->connection = $connection;
+    $this->fileSystem = $file_system;
   }
 
   /**
@@ -51,7 +69,9 @@ public static function create(ContainerInterface $container) {
     return new static(
       $container->get('config.factory')->get('image_captcha.settings'),
       $container->get('logger.factory')->get('captcha'),
-      $container->get('page_cache_kill_switch')
+      $container->get('page_cache_kill_switch'),
+      $container->get('database'),
+      $container->get('file_system')
     );
   }
 
@@ -63,7 +83,7 @@ public static function create(ContainerInterface $container) {
    */
   public function image() {
     $this->killSwitch->trigger();
-    return new CaptchaImageResponse($this->config, $this->logger);
+    return new CaptchaImageResponse($this->config, $this->logger, $this->connection, $this->fileSystem);
   }
 
 }
diff --git a/web/modules/captcha/image_captcha/src/Form/ImageCaptchaSettingsForm.php b/web/modules/captcha/image_captcha/src/Form/ImageCaptchaSettingsForm.php
index 130ecf2aea..d56f0ce7cc 100755
--- a/web/modules/captcha/image_captcha/src/Form/ImageCaptchaSettingsForm.php
+++ b/web/modules/captcha/image_captcha/src/Form/ImageCaptchaSettingsForm.php
@@ -79,7 +79,7 @@ protected function getEditableConfigNames() {
   public function buildForm(array $form, FormStateInterface $form_state) {
     $config = $this->config('image_captcha.settings');
     // Add CSS and JS for theming and added usability on admin form.
-    $form['#attached']['library'][] = 'captcha_image/base';
+    $form['#attached']['library'][] = 'image_captcha/base';
 
     // First some error checking.
     $setup_status = _image_captcha_check_setup(FALSE);
@@ -274,6 +274,10 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
       }
 
       $readable_fonts = [];
+      $available_fonts = _image_captcha_get_font_uri();
+      foreach ($fonts as $token) {
+        $fonts[$token] = $available_fonts[$token];
+      }
       list($readable_fonts, $problem_fonts) = _image_captcha_check_fonts($fonts);
       if (count($problem_fonts) > 0) {
         $form_state->setErrorByName('image_captcha_fonts', $this->t('The following fonts are not readable: %fonts.', ['%fonts' => implode(', ', $problem_fonts)]));
@@ -295,13 +299,21 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
+    $imageSettings = $form_state->cleanValues()->getValues();
     if (!isset($form['image_captcha_font_settings']['no_ttf_support'])) {
       // Filter the image_captcha fonts array to pick out the selected ones.
-      $fonts = array_filter($form_state->getValue('image_captcha_fonts'));
-      $this->config('image_captcha.settings')
-        ->set('image_captcha_fonts', $fonts)
-        ->save();
+      $image_captcha_fonts = $form_state->getValue('image_captcha_fonts');
+      $imageSettings['image_captcha_fonts'] = array_filter($imageSettings['image_captcha_fonts']);
+    }
+    $config = $this->config('image_captcha.settings');
+    // Exclude few fields from config.
+    $exclude = ['image', 'captcha_sid', 'captcha_token', 'captcha_response'];
+    foreach ($imageSettings as $configName => $configValue) {
+      if (!in_array($configName, $exclude)) {
+        $config->set($configName, $configValue);
+      }
     }
+    $config->save();
 
     parent::SubmitForm($form, $form_state);
   }
@@ -339,7 +351,7 @@ protected function settingsDotSection() {
       $available_fonts = [];
 
       // List of folders to search through for TrueType fonts.
-      $fonts = $this->getAvailableFontsFromDirectories();
+      $fonts = _image_captcha_get_available_fonts_from_directories();
       // Cache the list of previewable fonts. All the previews are done
       // in separate requests, and we don't want to rescan the filesystem
       // every time, so we cache the result.
@@ -358,20 +370,16 @@ protected function settingsDotSection() {
           'title' => $title,
           'alt' => $title,
         ];
-        $available_fonts[$font['uri']] = '<img' . new Attribute($attributes) . ' />';
+        $available_fonts[$token] = '<img' . new Attribute($attributes) . ' />';
       }
 
       // Append the PHP built-in font at the end.
       $title = $this->t('Preview of built-in font');
-      $attributes = [
-        'src' => Url::fromRoute('image_captcha.font_preview', ['token' => 'BUILTIN'])
+      $available_fonts['BUILTIN'] = $this->t('PHP built-in font: <img src="@font_preview_url" alt="@title" title="@title"', [
+        '@font_preview_url' => Url::fromRoute('image_captcha.font_preview', ['token' => 'BUILTIN'])
           ->toString(),
-        'alt' => $title,
-        'title' => $title,
-      ];
-      $available_fonts['BUILTIN'] = (string) $this->t('PHP built-in font: font_preview', [
-        'font_preview' => '<img' . new Attribute($attributes) . ' />',
-      ]);
+        '@title' => $title,
+      ])->__toString();
 
       $default_fonts = _image_captcha_get_enabled_fonts();
       $conf_path = DrupalKernel::findSitePath($this->getRequest());
@@ -426,38 +434,4 @@ protected function settingsDotSection() {
     return $form;
   }
 
-  /**
-   * Helper function to get fonts from the given directories.
-   *
-   * @param array|null $directories
-   *   (Optional) an array of directories
-   *   to recursively search through, if not given, the default
-   *   directories will be used.
-   *
-   * @return array
-   *   Fonts file objects (with fields 'name',
-   *   'basename' and 'filename'), keyed on the sha256 hash of the font
-   *   path (to have an easy token that can be used in an url
-   *   without en/decoding issues).
-   */
-  protected function getAvailableFontsFromDirectories($directories = NULL) {
-    // If no fonts directories are given: use the default.
-    if ($directories === NULL) {
-      $directories = [
-        drupal_get_path('module', 'image_captcha') . '/fonts',
-        'sites/all/libraries/fonts',
-        DrupalKernel::findSitePath($this->getRequest()) . '/libraries/fonts',
-      ];
-    }
-    // Collect the font information.
-    $fonts = [];
-    foreach ($directories as $directory) {
-      foreach ($this->fileSystem->scanDirectory($directory, '/\.[tT][tT][fF]$/') as $filename => $font) {
-        $fonts[hash('sha256', $filename)] = $font;
-      }
-    }
-
-    return $fonts;
-  }
-
 }
diff --git a/web/modules/captcha/image_captcha/src/Response/CaptchaImageResponse.php b/web/modules/captcha/image_captcha/src/Response/CaptchaImageResponse.php
index 6f44106982..8668954dc0 100755
--- a/web/modules/captcha/image_captcha/src/Response/CaptchaImageResponse.php
+++ b/web/modules/captcha/image_captcha/src/Response/CaptchaImageResponse.php
@@ -3,6 +3,8 @@
 namespace Drupal\image_captcha\Response;
 
 use Drupal\Core\Config\Config;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\File\FileSystemInterface;
 use Psr\Log\LoggerInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
@@ -16,6 +18,13 @@ class CaptchaImageResponse extends Response {
 
   const LOG_LEVEL = 'ERROR';
 
+  /**
+   * Database connection configuration container.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $connection;
+
   /**
    * Image Captcha config storage.
    *
@@ -23,6 +32,13 @@ class CaptchaImageResponse extends Response {
    */
   protected $config;
 
+  /**
+   * File System container.
+   *
+   * @var \Drupal\Core\File\FileSystemInterface
+   */
+  protected $fileSystem;
+
   /**
    * Watchdog logger channel for captcha.
    *
@@ -40,11 +56,13 @@ class CaptchaImageResponse extends Response {
   /**
    * {@inheritdoc}
    */
-  public function __construct(Config $config, LoggerInterface $logger, $callback = NULL, $status = 200, $headers = []) {
+  public function __construct(Config $config, LoggerInterface $logger, Connection $connection, FileSystemInterface $fileSystem, $callback = NULL, $status = 200, $headers = []) {
     parent::__construct(NULL, $status, $headers);
 
     $this->config = $config;
     $this->logger = $logger;
+    $this->connection = $connection;
+    $this->fileSystem = $fileSystem;
   }
 
   /**
@@ -53,7 +71,7 @@ public function __construct(Config $config, LoggerInterface $logger, $callback =
   public function prepare(Request $request) {
     $session_id = $request->get('session_id');
 
-    $code = \Drupal::database()
+    $code = $this->connection
       ->select('captcha_sessions', 'cs')
       ->fields('cs', ['solution'])
       ->condition('csid', $session_id)
@@ -371,11 +389,12 @@ protected function printString(&$image, $width, $height, array $fonts, $font_siz
 
       // Pick a random font from the list.
       $font = $fonts[array_rand($fonts)];
+      $font = _image_captcha_get_font_uri($font);
 
       // Get character dimensions for TrueType fonts.
       if ($font != 'BUILTIN') {
         putenv('GDFONTPATH=' . realpath('.'));
-        $bbox = imagettfbbox($font_size, 0, \Drupal::service('file_system')->realpath($font), $character);
+        $bbox = imagettfbbox($font_size, 0, $this->fileSystem->realpath($font), $character);
         // In very rare cases with some versions of the GD library, the x-value
         // of the left side of the bounding box as returned by the first call of
         // imagettfbbox is corrupt (value -2147483648 = 0x80000000).
@@ -383,7 +402,7 @@ protected function printString(&$image, $width, $height, array $fonts, $font_siz
         // can be used as workaround.
         // This issue is discussed at http://drupal.org/node/349218.
         if ($bbox[2] < 0) {
-          $bbox = imagettfbbox($font_size, 0, \Drupal::service('file_system')->realpath($font), $character);
+          $bbox = imagettfbbox($font_size, 0, $this->fileSystem->realpath($font), $character);
         }
       }
       else {
@@ -402,7 +421,7 @@ protected function printString(&$image, $width, $height, array $fonts, $font_siz
       }
 
       // Random (but small) rotation of the character.
-      // TODO: add a setting for this?
+      // @todo add a setting for this?
       $angle = mt_rand(-10, 10);
 
       // Determine print position: at what coordinate should the character be
@@ -439,7 +458,7 @@ protected function printString(&$image, $width, $height, array $fonts, $font_siz
         imagestring($image, 5, $pos_x, $pos_y, $character, $color);
       }
       else {
-        imagettftext($image, $font_size, $angle, $pos_x, $pos_y, $color, \Drupal::service('file_system')->realpath($font), $character);
+        imagettftext($image, $font_size, $angle, $pos_x, $pos_y, $color, $this->fileSystem->realpath($font), $character);
       }
     }
 
diff --git a/web/modules/captcha/image_captcha/src/Controller/CaptchaFontPreview.php b/web/modules/captcha/image_captcha/src/StreamedResponse/CaptchaFontPreviewStreamedResponse.php
similarity index 60%
rename from web/modules/captcha/image_captcha/src/Controller/CaptchaFontPreview.php
rename to web/modules/captcha/image_captcha/src/StreamedResponse/CaptchaFontPreviewStreamedResponse.php
index c843153f31..a5dbac3bdd 100755
--- a/web/modules/captcha/image_captcha/src/Controller/CaptchaFontPreview.php
+++ b/web/modules/captcha/image_captcha/src/StreamedResponse/CaptchaFontPreviewStreamedResponse.php
@@ -1,38 +1,58 @@
 <?php
 
-namespace Drupal\image_captcha\Controller;
+namespace Drupal\image_captcha\StreamedResponse;
 
-use Symfony\Component\HttpFoundation\Request;
+use Drupal\Core\Config\ImmutableConfig;
 use Symfony\Component\HttpFoundation\StreamedResponse;
 
 /**
  * A Controller to preview the captcha font on the settings page.
  */
-class CaptchaFontPreview extends StreamedResponse {
+class CaptchaFontPreviewStreamedResponse extends StreamedResponse {
+
+  /**
+   * Config service.
+   *
+   * @var \Drupal\Core\Config\ImmutableConfig
+   */
+  protected $config;
+
+  /**
+   * Token font selector.
+   *
+   * @string
+   */
+  protected $token;
 
   /**
    * {@inheritdoc}
    */
-  public function content(Request $request) {
-    $token = $request->get('token');
+  public function __construct(ImmutableConfig $config, $token, $callback = NULL, $status = 200, $headers = []) {
+    parent::__construct(NULL, $status, $headers);
+
+    $this->config = $config;
+    $this->token = $token;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function sendContent() {
     // Get the font from the given font token.
-    if ($token == 'BUILTIN') {
+    if ($this->token == 'BUILTIN') {
       $font = 'BUILTIN';
     }
     else {
       // Get the mapping of font tokens to font file objects.
-      $fonts = \Drupal::config('image_captcha.settings')
-        ->get('image_captcha_fonts_preview_map_cache');
-      if (!isset($fonts[$token])) {
-        echo 'bad token';
-        exit();
+      $fonts = $this->config->get('image_captcha_fonts_preview_map_cache');
+      if (!isset($fonts[$this->token])) {
+        return 'bad token';
       }
       // Get the font path.
-      $font = $fonts[$token]['uri'];
+      $font = $fonts[$this->token]['uri'];
       // Some sanity checks if the given font is valid.
       if (!is_file($font) || !is_readable($font)) {
-        echo 'bad font';
-        exit();
+        return 'bad font';
       }
     }
 
@@ -45,7 +65,7 @@ public function content(Request $request) {
     // Allocate image resource.
     $image = imagecreatetruecolor($width, $height);
     if (!$image) {
-      exit();
+      return NULL;
     }
     // White background and black foreground.
     $background_color = imagecolorallocate($image, 255, 255, 255);
@@ -65,9 +85,6 @@ public function content(Request $request) {
     imagepng($image);
     // Release image memory.
     imagedestroy($image);
-
-    // Close connection.
-    exit();
   }
 
 }
diff --git a/web/modules/captcha/migrations/d7_captcha_points.yml b/web/modules/captcha/migrations/d7_captcha_points.yml
new file mode 100644
index 0000000000..74d1cffdcb
--- /dev/null
+++ b/web/modules/captcha/migrations/d7_captcha_points.yml
@@ -0,0 +1,18 @@
+id: d7_captcha_points
+label: 'Captcha Points Table'
+migration_tags:
+  - Drupal 7
+  - Configuration
+source:
+  plugin: d7_captcha_points
+process:
+  formId: form_id
+  label: form_id
+  captchaType:
+    plugin: captcha_type_formatter
+    source: captcha_type
+  status:
+    plugin: default_value
+    default_value: TRUE
+destination:
+  plugin: entity:captcha_point
\ No newline at end of file
diff --git a/web/modules/captcha/migrations/d7_captcha_settings.yml b/web/modules/captcha/migrations/d7_captcha_settings.yml
new file mode 100644
index 0000000000..5e37885928
--- /dev/null
+++ b/web/modules/captcha/migrations/d7_captcha_settings.yml
@@ -0,0 +1,36 @@
+id: d7_captcha_settings
+label: 'Captcha Settings'
+migration_tags:
+  - Drupal 7
+  - Configuration
+source:
+  plugin: variable
+  variables:
+    - captcha_add_captcha_description
+    - captcha_administration_mode
+    - captcha_allow_on_admin_pages
+    - captcha_default_challenge
+    - captcha_default_challenge_on_nonlisted_forms
+    - captcha_default_validation
+    - captcha_description
+    - captcha_enable_stats
+    - captcha_error_message
+    - captcha_log_wrong_responses
+    - captcha_persistence
+    - captcha_placement_map_cache
+  source_module:
+    - captcha
+process:
+  enabled_default: captcha_default_challenge_on_nonlisted_forms
+  default_challenge: captcha_default_challenge
+  description: captcha_description
+  administration_mode: captcha_administration_mode
+  allow_on_admin_pages: captcha_allow_on_admin_pages
+  add_captcha_description: captcha_add_captcha_description
+  default_validation: captcha_default_validation
+  persistence: captcha_persistence
+  enable_stats: captcha_enable_stats
+  log_wrong_responses: captcha_log_wrong_responses
+destination:
+  plugin: config
+  config_name: captcha.settings
diff --git a/web/modules/captcha/modules/captcha_long_form_id_test/captcha_long_form_id_test.info.yml b/web/modules/captcha/modules/captcha_long_form_id_test/captcha_long_form_id_test.info.yml
index cf5b8d5668..6d6bce9f86 100644
--- a/web/modules/captcha/modules/captcha_long_form_id_test/captcha_long_form_id_test.info.yml
+++ b/web/modules/captcha/modules/captcha_long_form_id_test/captcha_long_form_id_test.info.yml
@@ -5,7 +5,7 @@ package: Testing
 core_version_requirement: ^8.8 || ^9
 hidden: true
 
-# Information added by Drupal.org packaging script on 2020-06-03
-version: '8.x-1.1'
+# Information added by Drupal.org packaging script on 2021-04-29
+version: '8.x-1.2'
 project: 'captcha'
-datestamp: 1591160979
+datestamp: 1619673377
diff --git a/web/modules/captcha/modules/captcha_test/captcha_test.info.yml b/web/modules/captcha/modules/captcha_test/captcha_test.info.yml
new file mode 100644
index 0000000000..df735cfe5e
--- /dev/null
+++ b/web/modules/captcha/modules/captcha_test/captcha_test.info.yml
@@ -0,0 +1,11 @@
+name: 'Captcha Test'
+type: module
+description: 'Provides captcha types for tests.'
+package: Testing
+core_version_requirement: ^8.8 || ^9
+hidden: true
+
+# Information added by Drupal.org packaging script on 2021-04-29
+version: '8.x-1.2'
+project: 'captcha'
+datestamp: 1619673377
diff --git a/web/modules/captcha/modules/captcha_test/captcha_test.module b/web/modules/captcha/modules/captcha_test/captcha_test.module
new file mode 100644
index 0000000000..97d976847e
--- /dev/null
+++ b/web/modules/captcha/modules/captcha_test/captcha_test.module
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Contains hook implementations for the Captcha Test module.
+ */
+
+/**
+ * Implements hook_captcha().
+ */
+function captcha_test_captcha($op, $captcha_type = '') {
+  switch ($op) {
+    case 'list':
+      return [];
+
+    case 'generate':
+      if ($captcha_type === 'TestCacheable') {
+        // A cacheable Captcha type.
+        $result = [
+          'cacheable' => TRUE,
+          // Cacheable captcha types need to provide a custom validation
+          // callback that doesn't care about the solution, because a form can
+          // be shown containing a cached CSID that has since been deleted
+          // from the {captcha_sessions} table.
+          'captcha_validate' => 'captcha_test_captcha_captcha_validation',
+          'solution' => 'Test 123',
+          'form' => [],
+        ];
+        $result['form']['captcha_response'] = [
+          '#type' => 'textfield',
+          '#title' => t('Test one two three'),
+          '#required' => TRUE,
+        ];
+
+        return $result;
+      }
+  }
+}
+
+/**
+ * Validation callback.
+ */
+function captcha_test_captcha_captcha_validation() {
+  return TRUE;
+}
diff --git a/web/modules/captcha/src/Element/Captcha.php b/web/modules/captcha/src/Element/Captcha.php
index 51c5dcd769..9fd2c04b01 100644
--- a/web/modules/captcha/src/Element/Captcha.php
+++ b/web/modules/captcha/src/Element/Captcha.php
@@ -101,7 +101,7 @@ public static function processCaptchaElement(&$element, FormStateInterface $form
     // Get the form ID of the form we are currently processing (which is not
     // necessary the same form that is submitted (if any).
     $this_form_id = isset($complete_form['form_id']['#value']) ?
-      preg_replace("/[^a-z0-9_]/", "", (string) $complete_form['form_id']['#value'])
+      preg_replace("/[^a-z0-9_-]/", "", (string) $complete_form['form_id']['#value'])
       : NULL;
 
     // Get the CAPTCHA session ID.
diff --git a/web/modules/captcha/src/Form/CaptchaSettingsForm.php b/web/modules/captcha/src/Form/CaptchaSettingsForm.php
index 36072f148e..32db4377a0 100755
--- a/web/modules/captcha/src/Form/CaptchaSettingsForm.php
+++ b/web/modules/captcha/src/Form/CaptchaSettingsForm.php
@@ -164,6 +164,16 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       ],
     ];
 
+    // Field for the wrong captcha response error message.
+    $form['wrong_captcha_response_message'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Wrong CAPTCHA response error message'),
+      '#description' => $this->t('Configurable error message that the user gets when it enters an incorrect CAPTCHA answer.'),
+      '#default_value' => _captcha_get_error_message(),
+      '#maxlength' => 256,
+      '#required' => TRUE,
+    ];
+
     // Option for case sensitive/insensitive validation of the responses.
     $form['default_validation'] = [
       '#type' => 'radios',
@@ -241,6 +251,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     // Save (or reset) the CAPTCHA descriptions.
     $config->set('description', $form_state->getValue('description'));
 
+    $config->set('wrong_captcha_response_message', $form_state->getValue('wrong_captcha_response_message'));
     $config->set('default_validation', $form_state->getValue('default_validation'));
     $config->set('persistence', $form_state->getValue('persistence'));
     $config->set('enable_stats', $form_state->getValue('enable_stats'));
@@ -256,7 +267,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
    *
    * @param array $form
    *   Form structured array.
-   * @param Drupal\Core\Form\FormStateInterface $form_state
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
    *   Form state structured array.
    */
   public function clearCaptchaPlacementCacheSubmit(array $form, FormStateInterface $form_state) {
diff --git a/web/modules/captcha/src/Plugin/migrate/process/CaptchaTypeFormatter.php b/web/modules/captcha/src/Plugin/migrate/process/CaptchaTypeFormatter.php
new file mode 100644
index 0000000000..d83eb5c811
--- /dev/null
+++ b/web/modules/captcha/src/Plugin/migrate/process/CaptchaTypeFormatter.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\captcha\Plugin\migrate\process;
+
+use Drupal\migrate\MigrateExecutableInterface;
+use Drupal\migrate\ProcessPluginBase;
+use Drupal\migrate\Row;
+
+/**
+ * Perform captcha type transformation.
+ *
+ * @MigrateProcessPlugin(
+ *   id = "captcha_type_formatter"
+ * )
+ *
+ * To do custom value transformations use the following:
+ *
+ * @code
+ * field_text:
+ *   plugin: captcha_type_formatter
+ *   source: text
+ * @endcode
+ */
+class CaptchaTypeFormatter extends ProcessPluginBase {
+
+  /**
+   * Transforms the d7 separate captcha type and module into one row.
+   */
+  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
+    $module = $row->getSourceProperty('module') ?? 'captcha';
+    $type = $row->getSourceProperty('captcha_type');
+    return $module . '/' . $type;
+  }
+
+}
diff --git a/web/modules/captcha/src/Plugin/migrate/source/CaptchaPoints.php b/web/modules/captcha/src/Plugin/migrate/source/CaptchaPoints.php
new file mode 100644
index 0000000000..18df2195fb
--- /dev/null
+++ b/web/modules/captcha/src/Plugin/migrate/source/CaptchaPoints.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\captcha\Plugin\migrate\source;
+
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\State\StateInterface;
+use Drupal\migrate\Plugin\MigrationInterface;
+use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Drupal 7 captcha point source from database.
+ *
+ * @MigrateSource(
+ *   id = "d7_captcha_points",
+ *   source_module = "captcha"
+ * )
+ */
+class CaptchaPoints extends DrupalSqlBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityTypeManagerInterface $entity_type_manager) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_type_manager);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $migration,
+      $container->get('state'),
+      $container->get('entity_type.manager'),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function query() {
+    return $this->select('captcha_points', 'c')->fields('c');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields() {
+    return [
+      'form_id' => $this->t('The name of the form'),
+      'module' => $this->t('The captcha point providing module.'),
+      'captcha_type' => $this->t('The captcha type.'),
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIds() {
+    $ids['form_id']['type'] = 'string';
+    return $ids;
+  }
+
+}
diff --git a/web/modules/captcha/tests/fixtures/drupal7.php b/web/modules/captcha/tests/fixtures/drupal7.php
new file mode 100644
index 0000000000..8a78dcf1c5
--- /dev/null
+++ b/web/modules/captcha/tests/fixtures/drupal7.php
@@ -0,0 +1,256 @@
+<?php
+
+/**
+ * @file
+ * A database agnostic dump for testing purposes.
+ */
+
+use Drupal\Core\Database\Database;
+
+$connection = Database::getConnection();
+
+$connection->insert('variable')
+  ->fields([
+    'name',
+    'value',
+  ])
+  ->values([
+    'name' => 'captcha_add_captcha_description',
+    'value' => 'i:1;',
+  ])
+  ->values([
+    'name' => 'captcha_administration_mode',
+    'value' => 'i:1;',
+  ])
+  ->values([
+    'name' => 'captcha_allow_on_admin_pages',
+    'value' => 'i:0;',
+  ])
+  ->values([
+    'name' => 'captcha_default_challenge',
+    'value' => 's:12:"captcha/Math";',
+  ])
+  ->values([
+    'name' => 'captcha_default_challenge_on_nonlisted_forms',
+    'value' => 'i:1;',
+  ])
+  ->values([
+    'name' => 'captcha_default_validation',
+    'value' => 's:1:"1";',
+  ])
+  ->values([
+    'name' => 'captcha_description',
+    'value' => 's:110:"This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.";',
+  ])
+  ->values([
+    'name' => 'captcha_enable_stats',
+    'value' => 'i:1;',
+  ])
+  ->values([
+    'name' => 'captcha_error_message',
+    'value' => 's:55:"The answer you entered for the CAPTCHA was not correct.";',
+  ])
+  ->values([
+    'name' => 'captcha_log_wrong_responses',
+    'value' => 'i:1;',
+  ])
+  ->values([
+    'name' => 'captcha_persistence',
+    'value' => 's:1:"1";',
+  ])
+  ->execute();
+
+$connection->insert('system')
+  ->fields([
+    'filename',
+    'name',
+    'type',
+    'owner',
+    'status',
+    'bootstrap',
+    'schema_version',
+    'weight',
+    'info',
+  ])
+  ->values([
+    'filename' => 'sites/all/modules/captcha/captcha.module',
+    'name' => 'captcha',
+    'type' => 'module',
+    'owner' => '',
+    'status' => '1',
+    'bootstrap' => '0',
+    'schema_version' => '7001',
+    'weight' => '0',
+    'info' => 'a:13:{s:4:\"name\";s:7:\"CAPTCHA\";s:11:\"description\";s:61:\"Base CAPTCHA module for adding challenges to arbitrary forms.\";s:7:\"package\";s:12:\"Spam control\";s:4:\"core\";s:3:\"7.x\";s:9:\"configure\";s:27:\"admin/config/people/captcha\";s:5:\"files\";a:5:{i:0;s:14:\"captcha.module\";i:1;s:11:\"captcha.inc\";i:2;s:17:\"captcha.admin.inc\";i:3;s:15:\"captcha.install\";i:4;s:12:\"captcha.test\";}s:7:\"version\";s:7:\"7.x-1.7\";s:7:\"project\";s:7:\"captcha\";s:9:\"datestamp\";s:10:\"1582293280\";s:5:\"mtime\";i:1582293280;s:12:\"dependencies\";a:0:{}s:3:\"php\";s:5:\"5.2.4\";s:9:\"bootstrap\";i:0;}',
+  ])
+  ->execute();
+
+// Create the Captcha Points D7 Table.
+$connection->schema()->createTable('captcha_points', [
+  'fields' => [
+    'form_id' => [
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => 128,
+      'default' => '',
+    ],
+    'module' => [
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => 64,
+      'default' => '',
+    ],
+    'captcha_type' => [
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => 64,
+      'default' => '',
+    ],
+  ],
+  'primary key' => [
+    'form_id',
+  ],
+  'mysql_character_set' => 'utf8',
+]);
+
+$connection->insert('captcha_points')
+  ->fields([
+    'form_id',
+    'module',
+    'captcha_type',
+  ])
+  ->values([
+    'form_id' => 'comment_node_article_form',
+    'module' => 'captcha',
+    'captcha_type' => 'Math',
+  ])
+  ->values([
+    'form_id' => 'user_pass',
+    'module' => 'captcha',
+    'captcha_type' => 'Math',
+  ])
+  ->execute();
+
+// Create the Captcha Points D7 Table.
+$connection->schema()->createTable('captcha_sessions', [
+  'fields' => [
+    'csid' => [
+      'type' => 'serial',
+      'not null' => TRUE,
+      'size' => 'normal',
+    ],
+    'token' => [
+      'type' => 'varchar',
+      'not null' => FALSE,
+      'length' => 64,
+    ],
+    'uid' => [
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => 0,
+    ],
+    'sid' => [
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => 128,
+      'default' => '',
+    ],
+    'ip_address' => [
+      'type' => 'varchar',
+      'not null' => FALSE,
+      'length' => 128,
+    ],
+    'timestamp' => [
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => 0,
+    ],
+    'form_id' => [
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => 128,
+      'default' => '',
+    ],
+    'solution' => [
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => 128,
+      'default' => '',
+    ],
+    'status' => [
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => 0,
+    ],
+    'attempts' => [
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => 0,
+    ],
+  ],
+  'primary key' => [
+    'csid',
+  ],
+  'indexes' => [
+    'csid_ip' => [
+      'csid',
+      'ip_address',
+    ],
+  ],
+  'mysql_character_set' => 'utf8',
+]);
+
+$connection->insert('captcha_sessions')
+  ->fields([
+    'csid',
+    'token',
+    'uid',
+    'sid',
+    'ip_address',
+    'timestamp',
+    'form_id',
+    'solution',
+    'status',
+    'attempts',
+  ])
+  ->values([
+    'csid' => 1,
+    'token' => '69e2767a2c651a887764bb60ea04cd0a',
+    'uid' => 0,
+    'sid' => 'svBxnT_AK4YFTbiUdCN3g9lCEqhC66NEbxasNNvGRug',
+    'ip_address' => '172.18.0.1',
+    'timestamp' => 1617948210,
+    'form_id' => 'user_login_block',
+    'solution' => '11',
+    'status' => 0,
+    'attempts' => 0,
+  ])
+  ->values([
+    'csid' => 2,
+    'token' => '69e2767a2c651a887764bb60ea04cd0b',
+    'uid' => 0,
+    'sid' => 'avBxnT_AK4YFTbiUdCN3g9lCEqhC66NEbxasNNvGRug',
+    'ip_address' => '172.18.0.1',
+    'timestamp' => 1617948230,
+    'form_id' => 'user_login_block',
+    'solution' => '20',
+    'status' => 0,
+    'attempts' => 0,
+  ])
+  ->values([
+    'csid' => 3,
+    'token' => '69e2767a2c651a887764bb60ea04cd0c',
+    'uid' => 0,
+    'sid' => 'bvBxnT_AK4YFTbiUdCN3g9lCEqhC66NEbxasNNvGRug',
+    'ip_address' => '172.18.0.1',
+    'timestamp' => 1617948240,
+    'form_id' => 'user_login_block',
+    'solution' => '25',
+    'status' => 0,
+    'attempts' => 0,
+  ])
+  ->execute();
diff --git a/web/modules/captcha/tests/src/Functional/CaptchaAdminTest.php b/web/modules/captcha/tests/src/Functional/CaptchaAdminTest.php
index 8f519d2ff5..3d9afe9ee5 100755
--- a/web/modules/captcha/tests/src/Functional/CaptchaAdminTest.php
+++ b/web/modules/captcha/tests/src/Functional/CaptchaAdminTest.php
@@ -4,6 +4,7 @@
 
 use Drupal\captcha\Entity\CaptchaPoint;
 use Drupal\Core\Url;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
 
 /**
  * Tests CAPTCHA admin settings.
@@ -12,17 +13,19 @@
  */
 class CaptchaAdminTest extends CaptchaWebTestBase {
 
+  use StringTranslationTrait;
+
   /**
    * Test access to the admin pages.
    */
   public function testAdminAccess() {
     $this->drupalLogin($this->normalUser);
     $this->drupalGet(self::CAPTCHA_ADMIN_PATH);
-    $this->assertText(t('Access denied'), 'Normal users should not be able to access the CAPTCHA admin pages', 'CAPTCHA');
+    $this->assertSession()->pageTextContains($this->t('Access denied'), 'Normal users should not be able to access the CAPTCHA admin pages', 'CAPTCHA');
 
     $this->drupalLogin($this->adminUser);
     $this->drupalGet(self::CAPTCHA_ADMIN_PATH);
-    $this->assertNoText(t('Access denied'), 'Admin users should be able to access the CAPTCHA admin pages', 'CAPTCHA');
+    $this->assertSession()->pageTextNotContains($this->t('Access denied'), 'Admin users should be able to access the CAPTCHA admin pages', 'CAPTCHA');
   }
 
   /**
@@ -34,10 +37,10 @@ public function testCaptchaPointSettingGetterAndSetter() {
     /* @var CaptchaPoint $result */
     $result = captcha_get_form_id_setting($comment_form_id);
     $this->assertNotNull($result, 'CAPTCHA exists', 'CAPTCHA');
-    $this->assertEqual($result->getCaptchaType(), 'test', 'CAPTCHA type: default', 'CAPTCHA');
+    $this->assertEquals($result->getCaptchaType(), 'test', 'CAPTCHA type: default');
     $result = captcha_get_form_id_setting($comment_form_id, TRUE);
     $this->assertNotNull($result, 'CAPTCHA exists', 'CAPTCHA');
-    $this->assertEqual($result, 'test', 'Setting and symbolic getting CAPTCHA point: "test"', 'CAPTCHA');
+    $this->assertEquals($result, 'test', 'Setting and symbolic getting CAPTCHA point: "test"');
 
     // Set to 'default'.
     captcha_set_form_id_setting($comment_form_id, 'default');
@@ -46,24 +49,24 @@ public function testCaptchaPointSettingGetterAndSetter() {
       ->save();
     $result = captcha_get_form_id_setting($comment_form_id);
     $this->assertNotNull($result, 'CAPTCHA exists', 'CAPTCHA');
-    $this->assertEqual($result->getCaptchaType(), 'foo/bar', 'Setting and getting CAPTCHA point: default', 'CAPTCHA');
+    $this->assertEquals($result->getCaptchaType(), 'foo/bar', 'Setting and getting CAPTCHA point: default');
     $result = captcha_get_form_id_setting($comment_form_id, TRUE);
-    $this->assertNotNull($result, 'Setting and symbolic getting CAPTCHA point: "default"', 'CAPTCHA');
-    $this->assertEqual($result, 'foo/bar', 'Setting and symbolic getting CAPTCHA point: default', 'CAPTCHA');
+    $this->assertNotNull($result, 'Setting and symbolic getting CAPTCHA point: "default"');
+    $this->assertEquals($result, 'foo/bar', 'Setting and symbolic getting CAPTCHA point: default');
 
     // Set to 'baz/boo'.
     captcha_set_form_id_setting($comment_form_id, 'baz/boo');
     $result = captcha_get_form_id_setting($comment_form_id);
     $this->assertNotNull($result, 'CAPTCHA exists', 'CAPTCHA');
-    $this->assertEqual($result->getCaptchaType(), 'baz/boo', 'Setting and getting CAPTCHA point: baz/boo', 'CAPTCHA');
+    $this->assertEquals($result->getCaptchaType(), 'baz/boo', 'Setting and getting CAPTCHA point: baz/boo');
     $result = captcha_get_form_id_setting($comment_form_id, TRUE);
-    $this->assertEqual($result, 'baz/boo', 'Setting and symbolic getting CAPTCHA point: "baz/boo"', 'CAPTCHA');
+    $this->assertEquals($result, 'baz/boo', 'Setting and symbolic getting CAPTCHA point: "baz/boo"');
 
     // Set to NULL (which should delete the CAPTCHA point setting entry).
     captcha_set_form_id_setting($comment_form_id, NULL);
     $result = captcha_get_form_id_setting($comment_form_id);
     $this->assertNotNull($result, 'CAPTCHA exists', 'CAPTCHA');
-    $this->assertEqual($result->getCaptchaType(), 'foo/bar', 'Setting and getting CAPTCHA point: NULL', 'CAPTCHA');
+    $this->assertEquals($result->getCaptchaType(), 'foo/bar', 'Setting and getting CAPTCHA point: NULL');
     $result = captcha_get_form_id_setting($comment_form_id, TRUE);
     $this->assertNotNull($result, 'CAPTCHA exists', 'CAPTCHA');
 
@@ -75,9 +78,9 @@ public function testCaptchaPointSettingGetterAndSetter() {
     $this->assertNotNull($result, 'Setting and getting CAPTCHA point: baba/fofo', 'CAPTCHA');
     // $this->assertEqual($result->module, 'baba', 'Setting and getting
     // CAPTCHA point: baba/fofo', 'CAPTCHA');.
-    $this->assertEqual($result->getCaptchaType(), 'baba/fofo', 'Setting and getting CAPTCHA point: baba/fofo', 'CAPTCHA');
+    $this->assertEquals($result->getCaptchaType(), 'baba/fofo', 'Setting and getting CAPTCHA point: baba/fofo');
     $result = captcha_get_form_id_setting($comment_form_id, TRUE);
-    $this->assertEqual($result, 'baba/fofo', 'Setting and symbolic getting CAPTCHA point: "baba/fofo"', 'CAPTCHA');
+    $this->assertEquals($result, 'baba/fofo', 'Setting and symbolic getting CAPTCHA point: "baba/fofo"');
   }
 
   /**
@@ -91,13 +94,12 @@ public function testCaptchaPointSettingGetterAndSetter() {
    */
   protected function assertCaptchaSetting($form_id, $challenge_type) {
     $result = captcha_get_form_id_setting(self::COMMENT_FORM_ID, TRUE);
-    $this->assertEqual($result, $challenge_type,
-      t('Check CAPTCHA setting for form: expected: @expected, received: @received.',
+    $this->assertEquals($result, $challenge_type,
+      $this->t('Check CAPTCHA setting for form: expected: @expected, received: @received.',
         [
           '@expected' => var_export($challenge_type, TRUE),
           '@received' => var_export($result, TRUE),
-        ]),
-      'CAPTCHA');
+        ]));
   }
 
   /**
@@ -110,8 +112,9 @@ public function testCaptchaAdminLinks() {
     $edit = [
       'administration_mode' => TRUE,
     ];
-
-    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH, $edit, t('Save configuration'));
+    
+    $this->drupalGet(self::CAPTCHA_ADMIN_PATH);
+    $this->submitForm($edit, $this->t('Save configuration'));
 
     // Create a node with comments enabled.
     $node = $this->drupalCreateNode();
@@ -120,7 +123,7 @@ public function testCaptchaAdminLinks() {
     $this->drupalGet('node/' . $node->id());
 
     // Click the add new comment link.
-    $this->clickLink(t('Add new comment'));
+    $this->clickLink($this->t('Add new comment'));
     $add_comment_url = $this->getUrl();
 
     // Remove fragment part from comment URL to avoid
@@ -128,56 +131,59 @@ public function testCaptchaAdminLinks() {
     $add_comment_url = strtok($add_comment_url, "#");
 
     // Click the CAPTCHA admin link to enable a challenge.
-    $this->clickLink(t('Place a CAPTCHA here for untrusted users.'));
+    $this->clickLink($this->t('Place a CAPTCHA here for untrusted users.'));
 
     // Enable Math CAPTCHA.
     $edit = ['captchaType' => 'captcha/Math'];
-    $this->drupalPostForm($this->getUrl(), $edit, t('Save'));
+    $this->drupalGet($this->getUrl());
+    $this->submitForm($edit, $this->t('Save'));
+
     // Check if returned to original comment form.
-    $this->assertUrl($add_comment_url, [],
+    $this->assertSession()->addressEquals($add_comment_url, [],
       'After setting CAPTCHA with CAPTCHA admin links: should return to original form.', 'CAPTCHA');
 
     // Check if CAPTCHA was successfully enabled
     // (on CAPTCHA admin links fieldset).
-    $this->assertText(t('CAPTCHA: challenge "@type" enabled', ['@type' => $edit['captchaType']]),
+    $this->assertSession()->pageTextContains($this->t('CAPTCHA: challenge "@type" enabled', ['@type' => $edit['captchaType']]),
       'Enable a challenge through the CAPTCHA admin links', 'CAPTCHA');
 
     // Check if CAPTCHA was successfully enabled (through API).
     $this->assertCaptchaSetting(self::COMMENT_FORM_ID, 'captcha/Math');
 
     // Edit challenge type through CAPTCHA admin links.
-    $this->clickLink(t('change'));
+    $this->clickLink($this->t('change'));
 
     // Enable Math CAPTCHA.
     $edit = ['captchaType' => 'default'];
-    $this->drupalPostForm($this->getUrl(), $edit, t('Save'));
+    $this->drupalGet($this->getUrl());
+    $this->submitForm($edit, 'Save');
 
     // Check if returned to original comment form.
-    $this->assertEqual($add_comment_url, $this->getUrl(),
-      'After editing challenge type CAPTCHA admin links: should return to original form.', 'CAPTCHA');
+    $this->assertEquals($add_comment_url, $this->getUrl(),
+      'After editing challenge type CAPTCHA admin links: should return to original form.');
 
     // Check if CAPTCHA was successfully changed
     // (on CAPTCHA admin links fieldset).
     // This is actually the same as the previous setting because
     // the captcha/Math is the default for the default challenge.
     // TODO Make sure the edit is a real change.
-    $this->assertText(t('CAPTCHA: challenge "@type" enabled', ['@type' => $edit['captchaType']]),
+    $this->assertSession()->pageTextContains($this->t('CAPTCHA: challenge "@type" enabled', ['@type' => $edit['captchaType']]),
       'Enable a challenge through the CAPTCHA admin links', 'CAPTCHA');
     // Check if CAPTCHA was successfully edited (through API).
     $this->assertCaptchaSetting(self::COMMENT_FORM_ID, 'default');
 
     // Disable challenge through CAPTCHA admin links.
     $this->drupalGet(Url::fromRoute('entity.captcha_point.disable', ['captcha_point' => self::COMMENT_FORM_ID]));
-    $this->drupalPostForm(NULL, [], t('Disable'));
+    $this->submitForm([], $this->t('Disable'));
 
     // Check if returned to captcha point list.
     global $base_url;
-    $this->assertEqual($base_url . '/admin/config/people/captcha/captcha-points', $this->getUrl(),
-      'After disabling challenge in CAPTCHA admin: should return to captcha point list.', 'CAPTCHA');
+    $this->assertEquals($base_url . '/admin/config/people/captcha/captcha-points', $this->getUrl(),
+      'After disabling challenge in CAPTCHA admin: should return to captcha point list.');
 
     // Check if CAPTCHA was successfully disabled
     // (on CAPTCHA admin links fieldset).
-    $this->assertRaw(t('Captcha point %form_id has been disabled.', ['%form_id' => self::COMMENT_FORM_ID]),
+    $this->assertSession()->responseContains($this->t('Captcha point %form_id has been disabled.', ['%form_id' => self::COMMENT_FORM_ID]),
       'Disable challenge through the CAPTCHA admin links', 'CAPTCHA');
   }
 
@@ -196,7 +202,7 @@ public function testUntrustedUserPosting() {
 
     // Go to node page and click the "add comment" link.
     $this->drupalGet('node/' . $node->id());
-    $this->clickLink(t('Add new comment'));
+    $this->clickLink($this->t('Add new comment'));
     $add_comment_url = $this->getUrl();
 
     // Check if CAPTCHA is visible on form.
@@ -204,8 +210,9 @@ public function testUntrustedUserPosting() {
     // Try to post a comment with wrong answer.
     $edit = $this->getCommentFormValues();
     $edit['captcha_response'] = 'xx';
-    $this->drupalPostForm($add_comment_url, $edit, t('Preview'));
-    $this->assertText(self::CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE,
+    $this->drupalGet($add_comment_url);
+    $this->submitForm($edit, $this->t('Preview'));
+    $this->assertSession()->pageTextContains(self::CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE,
       'wrong CAPTCHA should block form submission.', 'CAPTCHA');
   }
 
@@ -220,12 +227,13 @@ public function testXssOnCaptchaDescription() {
     $this->drupalLogin($this->adminUser);
     $xss = '<script type="text/javascript">alert("xss")</script>';
     $edit = ['description' => $xss];
-    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH, $edit, 'Save configuration');
+    $this->drupalGet(self::CAPTCHA_ADMIN_PATH);
+    $this->submitForm($edit, $this->t('Save configuration'));
 
     // Visit user register form and check if JavaScript snippet is there.
     $this->drupalLogout();
     $this->drupalGet('user/register');
-    $this->assertNoRaw($xss, 'JavaScript should not be allowed in CAPTCHA description.', 'CAPTCHA');
+    $this->assertSession()->responseNotContains($xss, 'JavaScript should not be allowed in CAPTCHA description.', 'CAPTCHA');
   }
 
   /**
@@ -242,7 +250,9 @@ public function testCaptchaPlacementCacheClearing() {
     $this->assertNotNull($placement_map, 'CAPTCHA placement cache should be set.');
     // Clear the cache.
     $this->drupalLogin($this->adminUser);
-    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH, [], t('Clear the CAPTCHA placement cache'));
+    $this->drupalGet(self::CAPTCHA_ADMIN_PATH);
+    $this->submitForm([], $this->t('Clear the CAPTCHA placement cache'));
+
     // Check that the placement cache is unset.
     $placement_map = $this->container->get('cache.default')
       ->get('captcha_placement_map_cache');
@@ -286,23 +296,26 @@ public function testCaptchaPointAdministration() {
       'formId' => $captcha_point_form_id,
       'captchaType' => $captcha_point_module . '/' . $captcha_point_type,
     ];
-    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH . '/captcha-points/add', $form_values, t('Save'));
-    $this->assertText(t('Form ID field is required.'));
+    $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha-points/add');
+    $this->submitForm($form_values, 'Save');
+    $this->assertSession()->pageTextContains($this->t('Form ID field is required.'));
 
     // Set CAPTCHA point through admin/user/captcha/captcha/captcha_point.
     $form_values['label'] = $label;
-    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH . '/captcha-points/add', $form_values, t('Save'));
-    $this->assertRaw(t('Captcha Point for %label form was created.', ['%label' => $captcha_point_form_id]));
+    $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha-points/add');
+    $this->submitForm($form_values, $this->t('Save'));
+    $this->assertSession()->responseContains($this->t('Captcha Point for %label form was created.', ['%label' => $captcha_point_form_id]));
 
     // Check in database.
     /* @var CaptchaPoint result */
     $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
-    $this->assertEqual($result->captchaType, $captcha_point_module . '/' . $captcha_point_type,
+    $this->assertEquals($result->captchaType, $captcha_point_module . '/' . $captcha_point_type,
       'Enabled CAPTCHA point should have module and type set');
 
     // Disable CAPTCHA point again.
-    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH . '/captcha-points/' . $captcha_point_form_id . '/disable', [], t('Disable'));
-    $this->assertRaw(t('Captcha point %label has been disabled.', ['%label' => $label]), 'Disabling of CAPTCHA point');
+    $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha-points/' . $captcha_point_form_id . '/disable');
+    $this->submitForm([], $this->t('Disable'));
+    $this->assertSession()->responseContains($this->t('Captcha point %label has been disabled.', ['%label' => $label]), 'Disabling of CAPTCHA point');
 
     // Check in database.
     $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
@@ -313,17 +326,19 @@ public function testCaptchaPointAdministration() {
     $form_values = [
       'captchaType' => $captcha_point_module . '/' . $captcha_point_type,
     ];
-    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH . '/captcha-points/' . $captcha_point_form_id, $form_values, t('Save'));
-    $this->assertRaw(t('Captcha Point for %form_id form was updated.', ['%form_id' => $captcha_point_form_id]), 'Saving of CAPTCHA point settings');
+    $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha-points/' . $captcha_point_form_id);
+    $this->submitForm($form_values, $this->t('Save'));
+    $this->assertSession()->responseContains($this->t('Captcha Point for %form_id form was updated.', ['%form_id' => $captcha_point_form_id]), 'Saving of CAPTCHA point settings');
 
     // Check in database.
     $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
-    $this->assertEqual($result->captchaType, $captcha_point_module . '/' . $captcha_point_type,
+    $this->assertEquals($result->captchaType, $captcha_point_module . '/' . $captcha_point_type,
       'Enabled CAPTCHA point should have module and type set');
 
     // Delete CAPTCHA point.
-    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH . '/captcha-points/' . $captcha_point_form_id . '/delete', [], t('Delete'));
-    $this->assertRaw(t('Captcha point %label has been deleted.', ['%label' => $label]),
+    $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha-points/' . $captcha_point_form_id . '/delete');
+    $this->submitForm([], $this->t('Delete'));
+    $this->assertSession()->responseContains($this->t('Captcha point %label has been deleted.', ['%label' => $label]),
       'Deleting of CAPTCHA point');
 
     $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
@@ -347,8 +362,9 @@ public function testCaptchaPointAdministrationByNonAdmin() {
       'formId' => $captcha_point_form_id,
       'captchaType' => $captcha_point_module . '/' . $captcha_point_type,
     ];
-    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH . '/captcha-points/add', $form_values, 'Save');
-    $this->assertRaw(t('Captcha Point for %form_id form was created.', ['%form_id' => $captcha_point_form_id]));
+    $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha-points/add');
+    $this->submitForm($form_values, $this->t('Save'));
+    $this->assertSession()->responseContains($this->t('Captcha Point for %form_id form was created.', ['%form_id' => $captcha_point_form_id]));
 
     // Switch from admin to non-admin.
     $this->drupalLogin($this->normalUser);
@@ -356,17 +372,17 @@ public function testCaptchaPointAdministrationByNonAdmin() {
     // Try to set CAPTCHA point
     // through admin/user/captcha/captcha/captcha_point.
     $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha-points');
-    $this->assertText(t('You are not authorized to access this page.'),
+    $this->assertSession()->pageTextContains($this->t('You are not authorized to access this page.'),
       'Non admin should not be able to set a CAPTCHA point');
 
     // Try to disable the CAPTCHA point.
     $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha-points/' . $captcha_point_form_id . '/disable');
-    $this->assertText(t('You are not authorized to access this page.'),
+    $this->assertSession()->pageTextContains($this->t('You are not authorized to access this page.'),
       'Non admin should not be able to disable a CAPTCHA point');
 
     // Try to delete the CAPTCHA point.
     $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha-points/' . $captcha_point_form_id . '/delete');
-    $this->assertText(t('You are not authorized to access this page.'),
+    $this->assertSession()->pageTextContains($this->t('You are not authorized to access this page.'),
       'Non admin should not be able to delete a CAPTCHA point');
 
     // Switch from nonadmin to admin again.
@@ -374,11 +390,12 @@ public function testCaptchaPointAdministrationByNonAdmin() {
 
     // Check if original CAPTCHA point still exists in database.
     $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
-    $this->assertEqual($result->captchaType, $captcha_point_module . '/' . $captcha_point_type, 'Enabled CAPTCHA point should have module and type set');
+    $this->assertEquals($result->captchaType, $captcha_point_module . '/' . $captcha_point_type, 'Enabled CAPTCHA point should have module and type set');
 
     // Delete captcha point.
-    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH . '/captcha-points/' . $captcha_point_form_id . '/delete', [], 'Delete');
-    $this->assertRaw(t('Captcha point %label has been deleted.', ['%label' => $label]), 'Disabling of CAPTCHA point');
+    $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha-points/' . $captcha_point_form_id . '/delete');
+    $this->submitForm([], 'Delete');
+    $this->assertSession()->responseContains($this->t('Captcha point %label has been deleted.', ['%label' => $label]), 'Disabling of CAPTCHA point');
   }
 
 }
diff --git a/web/modules/captcha/tests/src/Functional/CaptchaCacheTest.php b/web/modules/captcha/tests/src/Functional/CaptchaCacheTest.php
index 391856aac7..5e46398074 100644
--- a/web/modules/captcha/tests/src/Functional/CaptchaCacheTest.php
+++ b/web/modules/captcha/tests/src/Functional/CaptchaCacheTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\captcha\Functional;
 
+use Drupal\Core\Database\Database;
+
 /**
  * Tests CAPTCHA caching on various pages.
  *
@@ -14,12 +16,16 @@ class CaptchaCacheTest extends CaptchaWebTestBase {
    *
    * @var array
    */
-  public static $modules = ['block', 'image_captcha'];
+  protected static $modules = [
+    'block',
+    'image_captcha',
+    'captcha_test',
+  ];
 
   /**
    * {@inheritdoc}
    */
-  public function setUp() {
+  public function setUp(): void {
     parent::setUp();
 
     $this->drupalPlaceBlock('user_login_block', ['id' => 'login']);
@@ -32,41 +38,85 @@ public function testCacheTags() {
     global $base_path;
     // Check caching without captcha as anonymous user.
     $this->drupalGet('');
-    $this->assertEqual($this->drupalGetHeader('x-drupal-cache'), 'MISS');
+    $this->assertEquals($this->getSession()->getResponseHeader('x-drupal-cache'), 'MISS');
     $this->drupalGet('');
-    $this->assertEqual($this->drupalGetHeader('x-drupal-cache'), 'HIT');
+    $this->assertEquals($this->getSession()->getResponseHeader('x-drupal-cache'), 'HIT');
 
     // Enable captcha on login block and test caching.
     captcha_set_form_id_setting('user_login_form', 'captcha/Math');
     $this->drupalGet('');
     $sid = $this->getCaptchaSidFromForm();
-    $this->assertNull($this->drupalGetHeader('x-drupal-cache'), 'Cache is disabled');
+    $this->assertNull($this->getSession()->getResponseHeader('x-drupal-cache'), 'Cache is disabled');
     $this->drupalGet('');
-    $this->assertNotEqual($sid, $this->getCaptchaSidFromForm());
+    $this->assertNotEquals($sid, $this->getCaptchaSidFromForm());
 
     // Switch challenge to captcha/Test, check the captcha isn't cached.
     captcha_set_form_id_setting('user_login_form', 'captcha/Test');
     $this->drupalGet('');
     $sid = $this->getCaptchaSidFromForm();
-    $this->assertNull($this->drupalGetHeader('x-drupal-cache'), 'Cache is disabled');
+    $this->assertNull($this->getSession()->getResponseHeader('x-drupal-cache'), 'Cache is disabled');
     $this->drupalGet('');
-    $this->assertNotEqual($sid, $this->getCaptchaSidFromForm());
+    $this->assertNotEquals($sid, $this->getCaptchaSidFromForm());
 
     // Switch challenge to image_captcha/Image, check the captcha isn't cached.
     captcha_set_form_id_setting('user_login_form', 'image_captcha/Image');
     $this->drupalGet('');
     $image_path = $this->getSession()->getPage()->find('css', '.captcha img')->getAttribute('src');
-    $this->assertNull($this->drupalGetHeader('x-drupal-cache'), 'Cache disabled');
+    $this->assertNull($this->getSession()->getResponseHeader('x-drupal-cache'), 'Cache disabled');
     // Check that we get a new image when vising the page again.
     $this->drupalGet('');
-    $this->assertNotEqual($image_path, $this->getSession()->getPage()->find('css', '.captcha img')->getAttribute('src'));
+    $this->assertNotEquals($image_path, $this->getSession()->getPage()->find('css', '.captcha img')->getAttribute('src'));
     // Check image caching, remove the base path since drupalGet() expects the
     // internal path.
     $this->drupalGet(substr($image_path, strlen($base_path)));
-    $this->assertResponse(200);
+    $this->assertSession()->statusCodeEquals(200);
     // Request image twice to make sure no errors happen (due to page caching).
     $this->drupalGet(substr($image_path, strlen($base_path)));
-    $this->assertResponse(200);
+    $this->assertSession()->statusCodeEquals(200);
+  }
+
+  /**
+   * Tests a cacheable captcha type.
+   */
+  public function testCacheableCaptcha() {
+    $web_assert = $this->assertSession();
+
+    // Enable captcha on login block with a cacheable captcha.
+    captcha_set_form_id_setting('user_login_form', 'captcha_test/TestCacheable');
+
+    // Warm up the caches.
+    $this->drupalGet('');
+
+    // Let's check if the page is cached.
+    $this->drupalGet('');
+    static::assertSame('HIT', $this->drupalGetHeader('X-Drupal-Cache'), 'Cache enabled');
+
+    $edit = [
+      'name' => $this->normalUser->getDisplayName(),
+      'pass' => $this->normalUser->pass_raw,
+      'captcha_response' => 'Test 123',
+    ];
+    $this->submitForm($edit, 'Log in');
+    $web_assert->addressEquals('user/' . $this->normalUser->id());
+
+    // Simulate a cron run that deletes the {captcha_session} data.
+    $connection = Database::getConnection();
+    $connection->delete('captcha_sessions')->execute();
+
+    // Log out and reload the form. Because the captcha is cacheable, the form
+    // is retrieved from the render cache, and contains the same CSID as
+    // previously.
+    $this->drupalLogout();
+    $this->drupalGet('');
+    static::assertSame('HIT', $this->drupalGetHeader('X-Drupal-Cache'), 'Cache enabled');
+
+    $edit = [
+      'name' => $this->normalUser->getDisplayName(),
+      'pass' => $this->normalUser->pass_raw,
+      'captcha_response' => 'Test 123',
+    ];
+    $this->submitForm($edit, 'Log in');
+    $web_assert->addressEquals('user/' . $this->normalUser->id());
   }
 
 }
diff --git a/web/modules/captcha/tests/src/Functional/CaptchaCronTest.php b/web/modules/captcha/tests/src/Functional/CaptchaCronTest.php
index f908e2378d..44f58ccd35 100644
--- a/web/modules/captcha/tests/src/Functional/CaptchaCronTest.php
+++ b/web/modules/captcha/tests/src/Functional/CaptchaCronTest.php
@@ -17,7 +17,7 @@ class CaptchaCronTest extends BrowserTestBase {
    *
    * @var array
    */
-  public static $modules = ['captcha'];
+  protected static $modules = ['captcha'];
 
   /**
    * {@inheritdoc}
diff --git a/web/modules/captcha/tests/src/Functional/CaptchaPersistenceTest.php b/web/modules/captcha/tests/src/Functional/CaptchaPersistenceTest.php
index 9755da9001..725795f88f 100755
--- a/web/modules/captcha/tests/src/Functional/CaptchaPersistenceTest.php
+++ b/web/modules/captcha/tests/src/Functional/CaptchaPersistenceTest.php
@@ -23,7 +23,8 @@ private function setUpPersistence($persistence) {
     $this->drupalLogin($this->adminUser);
     // Set persistence.
     $edit = ['persistence' => $persistence];
-    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH, $edit, 'Save configuration');
+    $this->drupalGet(self::CAPTCHA_ADMIN_PATH);
+    $this->submitForm($edit, 'Save configuration');
     // Log admin out.
     $this->drupalLogout();
 
@@ -48,7 +49,7 @@ private function setUpPersistence($persistence) {
    */
   protected function assertPreservedCsid($captcha_sid_initial) {
     $captcha_sid = $this->getCaptchaSidFromForm();
-    $this->assertEqual($captcha_sid_initial, $captcha_sid,
+    $this->assertEquals($captcha_sid_initial, $captcha_sid,
       "CAPTCHA session ID should be preserved (expected: $captcha_sid_initial, found: $captcha_sid).");
   }
 
@@ -60,7 +61,7 @@ protected function assertPreservedCsid($captcha_sid_initial) {
    */
   protected function assertDifferentCsid($captcha_sid_initial) {
     $captcha_sid = $this->getCaptchaSidFromForm();
-    $this->assertNotEqual($captcha_sid_initial, $captcha_sid, "CAPTCHA session ID should be different.");
+    $this->assertNotEquals($captcha_sid_initial, $captcha_sid, "CAPTCHA session ID should be different.");
   }
 
   /**
@@ -82,7 +83,7 @@ public function testPersistenceAlways() {
       'pass' => 'bazlaz',
       'captcha_response' => 'Test 123',
     ];
-    $this->drupalPostForm(NULL, $edit, $this->t('Log in'), [], self::LOGIN_HTML_FORM_ID);
+    $this->submitForm($edit, $this->t('Log in'), self::LOGIN_HTML_FORM_ID);
     // Check that there was no error message for the CAPTCHA.
     $this->assertCaptchaResponseAccepted();
 
@@ -92,7 +93,7 @@ public function testPersistenceAlways() {
     $this->assertPreservedCsid($captcha_sid_initial);
 
     // Post from again.
-    $this->drupalPostForm(NULL, $edit, $this->t('Log in'), [], self::LOGIN_HTML_FORM_ID);
+    $this->submitForm($edit, $this->t('Log in'), self::LOGIN_HTML_FORM_ID);
     // Check that there was no error message for the CAPTCHA.
     $this->assertCaptchaResponseAccepted();
     $this->assertPreservedCsid($captcha_sid_initial);
@@ -116,7 +117,7 @@ public function testPersistencePerFormInstance() {
       'pass' => 'bazlaz',
       'captcha_response' => 'Test 123',
     ];
-    $this->drupalPostForm(NULL, $edit, $this->t('Log in'), [], self::LOGIN_HTML_FORM_ID);
+    $this->submitForm($edit, $this->t('Log in'), self::LOGIN_HTML_FORM_ID);
     // Check that there was no error message for the CAPTCHA.
     $this->assertCaptchaResponseAccepted();
     // There shouldn't be a CAPTCHA on the new form.
@@ -153,7 +154,7 @@ public function testPersistencePerFormType() {
       'pass' => 'bazlaz',
       'captcha_response' => 'Test 123',
     ];
-    $this->drupalPostForm(NULL, $edit, $this->t('Log in'), [], self::LOGIN_HTML_FORM_ID);
+    $this->submitForm($edit, $this->t('Log in'), self::LOGIN_HTML_FORM_ID);
     // Check that there was no error message for the CAPTCHA.
     $this->assertCaptchaResponseAccepted();
     // There shouldn't be a CAPTCHA on the new form.
@@ -195,7 +196,7 @@ public function testPersistenceOnlyOnce() {
       'pass' => 'bazlaz',
       'captcha_response' => 'Test 123',
     ];
-    $this->drupalPostForm(NULL, $edit, $this->t('Log in'), [], self::LOGIN_HTML_FORM_ID);
+    $this->submitForm($edit, $this->t('Log in'), self::LOGIN_HTML_FORM_ID);
     // Check that there was no error message for the CAPTCHA.
     $this->assertCaptchaResponseAccepted();
     // There shouldn't be a CAPTCHA on the new form.
diff --git a/web/modules/captcha/tests/src/Functional/CaptchaSessionReuseAttackTestCase.php b/web/modules/captcha/tests/src/Functional/CaptchaSessionReuseAttackTestCase.php
index b165f386f0..83c7d4ffe0 100755
--- a/web/modules/captcha/tests/src/Functional/CaptchaSessionReuseAttackTestCase.php
+++ b/web/modules/captcha/tests/src/Functional/CaptchaSessionReuseAttackTestCase.php
@@ -14,7 +14,7 @@ class CaptchaSessionReuseAttackTestCase extends CaptchaWebTestBase {
    */
   protected function assertCaptchaSessionIdReuseAttackDetection() {
     // There should be an error message about wrong response.
-    $this->assertText(self::CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE,
+    $this->assertSession()->pageTextContains(self::CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE,
       'CAPTCHA response should flagged as wrong.',
       'CAPTCHA'
     );
@@ -47,7 +47,7 @@ public function testCaptchaSessionReuseAttackDetectionOnCommentPreview() {
     // Post the form with the solution.
     $edit = $this->getCommentFormValues();
     $edit['captcha_response'] = $solution;
-    $this->drupalPostForm(NULL, $edit, t('Preview'));
+    $this->submitForm($edit, 'Preview');
     // Answer should be accepted and further CAPTCHA omitted.
     $this->assertCaptchaResponseAccepted();
     $this->assertCaptchaPresence(FALSE);
@@ -57,10 +57,10 @@ public function testCaptchaSessionReuseAttackDetectionOnCommentPreview() {
 
     // Post a new comment, reusing the previous CAPTCHA session.
     $edit = $this->getCommentFormValues();
-    $this->assertSession()->hiddenFieldExists("captcha_sid")->setValue((string) $captcha_sid);
-    $this->assertSession()->hiddenFieldExists("captcha_token")->setValue((string) $captcha_token);
+    $this->assertSession()->hiddenFieldExists("captcha_sid")->setValue((string)$captcha_sid);
+    $this->assertSession()->hiddenFieldExists("captcha_token")->setValue((string)$captcha_token);
     $edit['captcha_response'] = $solution;
-    $this->drupalPostForm(NULL, $edit, t('Preview'));
+    $this->submitForm($edit, 'Preview');
     // CAPTCHA session reuse attack should be detected.
     $this->assertCaptchaSessionIdReuseAttackDetection();
     // There should be a CAPTCHA.
@@ -93,7 +93,7 @@ public function testCaptchaSessionReuseAttackDetectionOnNodeForm() {
     $edit = $this->getNodeFormValues();
     $edit['captcha_response'] = $solution;
     // Preview the node.
-    $this->drupalPostForm(NULL, $edit, t('Preview'));
+    $this->submitForm($edit, 'Preview');
     // Answer should be accepted.
     $this->assertCaptchaResponseAccepted();
     // Check that there is no CAPTCHA after preview.
@@ -105,10 +105,10 @@ public function testCaptchaSessionReuseAttackDetectionOnNodeForm() {
 
     // Post a new node, reusing the previous CAPTCHA session.
     $edit = $this->getNodeFormValues();
-    $this->assertSession()->hiddenFieldExists("captcha_sid")->setValue((string) $captcha_sid);
-    $this->assertSession()->hiddenFieldExists("captcha_token")->setValue((string) $captcha_token);
+    $this->assertSession()->hiddenFieldExists("captcha_sid")->setValue((string)$captcha_sid);
+    $this->assertSession()->hiddenFieldExists("captcha_token")->setValue((string)$captcha_token);
     $edit['captcha_response'] = $solution;
-    $this->drupalPostForm(NULL, $edit, t('Preview'));
+    $this->submitForm($edit, 'Preview');
     // CAPTCHA session reuse attack should be detected.
     $this->assertCaptchaSessionIdReuseAttackDetection();
     // There should be a CAPTCHA.
@@ -140,12 +140,12 @@ public function testCaptchaSessionReuseAttackDetectionOnLoginForm() {
       'pass' => $this->normalUser->pass_raw,
       'captcha_response' => $solution,
     ];
-    $this->drupalPostForm(NULL, $edit, t('Log in'), [], self::LOGIN_HTML_FORM_ID);
+    $this->submitForm($edit, 'Log in', self::LOGIN_HTML_FORM_ID);
     $this->assertCaptchaResponseAccepted();
     $this->assertCaptchaPresence(FALSE);
     // If a "log out" link appears on the page, it is almost certainly because
     // the login was successful.
-    $this->assertText($this->normalUser->getDisplayName());
+    $this->assertSession()->pageTextContains($this->normalUser->getDisplayName());
 
     // Log out again.
     $this->drupalLogout();
@@ -154,10 +154,10 @@ public function testCaptchaSessionReuseAttackDetectionOnLoginForm() {
     $this->drupalGet('<front>');
 
     // Try to log in again, reusing the previous CAPTCHA session.
-    $this->assertSession()->hiddenFieldExists("captcha_sid")->setValue((string) $captcha_sid);
-    $this->assertSession()->hiddenFieldExists("captcha_token")->setValue((string) $captcha_token);
-    $this->assert('pass', json_encode($edit));
-    $this->drupalPostForm(NULL, $edit, t('Log in'));
+    $this->assertSession()->hiddenFieldExists("captcha_sid")->setValue((string)$captcha_sid);
+    $this->assertSession()->hiddenFieldExists("captcha_token")->setValue((string)$captcha_token);
+    $this->assertNotEmpty(json_encode($edit));
+    $this->submitForm($edit, 'Log in');
     // CAPTCHA session reuse attack should be detected.
     $this->assertCaptchaSessionIdReuseAttackDetection();
     // There should be a CAPTCHA.
@@ -182,11 +182,12 @@ public function testMultipleCaptchaProtectedFormsOnOnePage() {
     $edit = $this->getCommentFormValues();
     $comment_subject = $edit['subject[0][value]'];
     $edit['captcha_response'] = 'Test 123';
-    $this->drupalPostForm('comment/reply/node/' . $node->id() . '/comment', $edit, t('Preview'));
+    $this->drupalGet('comment/reply/node/' . $node->id() . '/comment');
+    $this->submitForm($edit, 'Preview');
     // Post should be accepted: no warnings,
     // no CAPTCHA reuse detection (which could be used by user log in block).
     $this->assertCaptchaResponseAccepted();
-    $this->assertText($comment_subject);
+    $this->assertSession()->pageTextContains($comment_subject);
   }
 
 }
diff --git a/web/modules/captcha/tests/src/Functional/CaptchaTest.php b/web/modules/captcha/tests/src/Functional/CaptchaTest.php
index d28c54431d..5463998664 100755
--- a/web/modules/captcha/tests/src/Functional/CaptchaTest.php
+++ b/web/modules/captcha/tests/src/Functional/CaptchaTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\captcha\Functional;
 
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
 /**
  * Tests CAPTCHA main test case sensitivity.
  *
@@ -9,12 +11,14 @@
  */
 class CaptchaTest extends CaptchaWebTestBase {
 
+  use StringTranslationTrait;
+
   /**
    * Modules to enable.
    *
    * @var array
    */
-  public static $modules = ['block', 'captcha_long_form_id_test'];
+  protected static $modules = ['block', 'captcha_long_form_id_test'];
 
   /**
    * Testing the protection of the user log in form.
@@ -44,15 +48,50 @@ public function testCaptchaOnLoginForm() {
       'pass' => $user->pass_raw,
       'captcha_response' => '?',
     ];
-    $this->drupalPostForm(NULL, $edit, t('Log in'), [], self::LOGIN_HTML_FORM_ID);
+    $this->submitForm($edit, $this->t('Log in'), self::LOGIN_HTML_FORM_ID);
     // Check for error message.
-    $this->assertText(self::CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE, 'CAPTCHA should block user login form', 'CAPTCHA');
+    $this->assertSession()->pageTextContains(self::CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE, 'CAPTCHA should block user login form', 'CAPTCHA');
 
     // And make sure that user is not logged in:
     // check for name and password fields on ?q=user.
     $this->drupalGet('user');
-    $this->assertField('name', t('Username field found.'), 'CAPTCHA');
-    $this->assertField('pass', t('Password field found.'), 'CAPTCHA');
+    $this->assertSession()->fieldExists('name');
+    $this->assertSession()->fieldExists('pass');
+  }
+
+  /**
+   * Testing the response error menssage.
+   */
+  public function testCaptchaResponseErrorMenssage() {
+    // Customize the response error message.
+    $this->drupalLogin($this->adminUser);
+    $customized_menssage = 'The answer you entered is wrong.';
+    $edit = [
+      'wrong_captcha_response_message' => $customized_menssage,
+    ];
+    $this->drupalGet("admin/config/people/captcha");
+    $this->submitForm($edit, $this->t('Save configuration'));
+
+    // Set a CAPTCHA on login form.
+    /* @var \Drupal\captcha\Entity\CaptchaPoint $captcha_point */
+    $captcha_point = \Drupal::entityTypeManager()
+      ->getStorage('captcha_point')
+      ->load('user_login_form');
+    $captcha_point->setCaptchaType('captcha/Math');
+    $captcha_point->enable()->save();
+
+    // Check if the menssage is default.
+    $this->drupalLogout();
+    $this->drupalGet('user');
+    // Try to log in, which should fail.
+    $edit = [
+      'name' => $this->adminUser->getDisplayName(),
+      'pass' => $this->adminUser->pass_raw,
+      'captcha_response' => '?',
+    ];
+    $this->submitForm($edit, $this->t('Log in'), self::LOGIN_HTML_FORM_ID);
+    $this->assertSession()->pageTextContains($customized_menssage, 'CAPTCHA should block user login form', 'CAPTCHA');
+
   }
 
   /**
@@ -83,23 +122,24 @@ protected function assertCommentPosting($captcha_response, $should_pass, $messag
     $comment_subject = $edit['subject[0][value]'];
     $comment_body = $edit['comment_body[0][value]'];
     $edit['captcha_response'] = $captcha_response;
-    $this->drupalPostForm('comment/reply/node/' . $node->id() . '/comment', $edit, t('Save'), [], 'comment-form');
+    $this->drupalGet('comment/reply/node/' . $node->id() . '/comment');
+    $this->submitForm($edit, $this->t('Save'), 'comment-form');
 
     if ($should_pass) {
       // There should be no error message.
       $this->assertCaptchaResponseAccepted();
       // Get node page and check that comment shows up.
       $this->drupalGet('node/' . $node->id());
-      $this->assertText($comment_subject, $message . ' Comment should show up on node page.', 'CAPTCHA');
-      $this->assertText($comment_body, $message . ' Comment should show up on node page.', 'CAPTCHA');
+      $this->assertSession()->pageTextContains($comment_subject, $message . ' Comment should show up on node page.', 'CAPTCHA');
+      $this->assertSession()->pageTextContains($comment_body, $message . ' Comment should show up on node page.', 'CAPTCHA');
     }
     else {
       // Check for error message.
-      $this->assertText(self::CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE, $message . ' Comment submission should be blocked.', 'CAPTCHA');
+      $this->assertSession()->pageTextContains(self::CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE, $message . ' Comment submission should be blocked.', 'CAPTCHA');
       // Get node page and check that comment is not present.
       $this->drupalGet('node/' . $node->id());
-      $this->assertNoText($comment_subject, $message . ' Comment should not show up on node page.', 'CAPTCHA');
-      $this->assertNoText($comment_body, $message . ' Comment should not show up on node page.', 'CAPTCHA');
+      $this->assertSession()->pageTextNotContains($comment_subject, $message . ' Comment should not show up on node page.', 'CAPTCHA');
+      $this->assertSession()->pageTextNotContains($comment_body, $message . ' Comment should not show up on node page.', 'CAPTCHA');
     }
   }
 
@@ -153,7 +193,8 @@ public function testCaptchaDescriptionAfterCommentPreview() {
     // Preview comment with correct CAPTCHA answer.
     $edit = $this->getCommentFormValues();
     $edit['captcha_response'] = 'Test 123';
-    $this->drupalPostForm('comment/reply/node/' . $node->id() . '/comment', $edit, t('Preview'));
+    $this->drupalGet('comment/reply/node/' . $node->id() . '/comment');
+    $this->submitForm($edit, $this->t('Preview'));
 
     // Check that there is no CAPTCHA after preview.
     $this->assertCaptchaPresence(FALSE);
@@ -180,7 +221,7 @@ public function testCaptchaSessionReuseOnNodeForms() {
     $edit = $this->getNodeFormValues();
     $edit['captcha_response'] = 'Test 123';
     $this->drupalGet('node/add/page');
-    $this->drupalPostForm(NULL, $edit, t('Preview'));
+    $this->submitForm($edit, $this->t('Preview'));
 
     $this->assertCaptchaPresence(FALSE);
   }
@@ -229,8 +270,9 @@ public function testLongFormId() {
     ];
 
     // Create intentionally long id Captcha Point.
-    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH . '/captcha-points/add', $form_values, t('Save'));
-    $this->assertRaw(t('Captcha Point for %label form was created.', ['%label' => $formId]));
+    $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha-points/add');
+    $this->submitForm($form_values, $this->t('Save'));
+    $this->assertSession()->responseContains($this->t('Captcha Point for %label form was created.', ['%label' => $formId]));
 
     // We need to log out to test the captcha.
     $this->drupalLogout();
diff --git a/web/modules/captcha/tests/src/Functional/CaptchaWebTestBase.php b/web/modules/captcha/tests/src/Functional/CaptchaWebTestBase.php
index 705684fd73..f4598ef8e0 100755
--- a/web/modules/captcha/tests/src/Functional/CaptchaWebTestBase.php
+++ b/web/modules/captcha/tests/src/Functional/CaptchaWebTestBase.php
@@ -44,7 +44,7 @@ abstract class CaptchaWebTestBase extends BrowserTestBase {
    *
    * @var array
    */
-  public static $modules = ['captcha', 'comment'];
+  protected static $modules = ['captcha', 'comment'];
 
   /**
    * {@inheritdoc}
@@ -120,12 +120,12 @@ public function setUp() {
    */
   protected function assertCaptchaResponseAccepted() {
     // There should be no error message about unknown CAPTCHA session ID.
-    $this->assertNoText(self::CAPTCHA_UNKNOWN_CSID_ERROR_MESSAGE,
+    $this->assertSession()->pageTextNotContains(self::CAPTCHA_UNKNOWN_CSID_ERROR_MESSAGE,
       'CAPTCHA response should be accepted (known CSID).',
       'CAPTCHA'
     );
     // There should be no error message about wrong response.
-    $this->assertNoText(self::CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE,
+    $this->assertSession()->pageTextNotContains(self::CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE,
       'CAPTCHA response should be accepted (correct response).',
       'CAPTCHA'
     );
@@ -139,12 +139,12 @@ protected function assertCaptchaResponseAccepted() {
    */
   protected function assertCaptchaPresence($presence) {
     if ($presence) {
-      $this->assertText(_captcha_get_description(),
+      $this->assertSession()->pageTextContains(_captcha_get_description(),
         'There should be a CAPTCHA on the form.', 'CAPTCHA'
       );
     }
     else {
-      $this->assertNoText(_captcha_get_description(),
+      $this->assertSession()->pageTextNotContains(_captcha_get_description(),
         'There should be no CAPTCHA on the form.', 'CAPTCHA'
       );
     }
@@ -236,9 +236,9 @@ protected function getMathCaptchaSolutionFromForm($form_html_id = NULL) {
     else {
       $elements = $this->xpath('//form[@id="' . $form_html_id . '"]//div[contains(@class, "form-item-captcha-response")]/span[@class="field-prefix"]');
     }
-    $this->assert('pass', json_encode($elements));
+    $this->assertTrue('pass', json_encode($elements));
     $challenge = (string) $elements[0];
-    $this->assert('pass', $challenge);
+    $this->assertTrue('pass', $challenge);
     // Extract terms and operator from challenge.
     $matches = [];
     preg_match('/\\s*(\\d+)\\s*(-|\\+)\\s*(\\d+)\\s*=\\s*/', $challenge, $matches);
diff --git a/web/modules/captcha/tests/src/Kernel/Migrate/d7/MigrateCaptchaPointsTest.php b/web/modules/captcha/tests/src/Kernel/Migrate/d7/MigrateCaptchaPointsTest.php
new file mode 100644
index 0000000000..b2148e4179
--- /dev/null
+++ b/web/modules/captcha/tests/src/Kernel/Migrate/d7/MigrateCaptchaPointsTest.php
@@ -0,0 +1,94 @@
+<?php
+
+namespace Drupal\Tests\captcha\Kernel\Migrate\d7;
+
+use Drupal\captcha\CaptchaPointInterface;
+use Drupal\captcha\Entity\CaptchaPoint;
+use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
+
+/**
+ * Migrates various configuration objects owned by the captcha module.
+ *
+ * @group captcha
+ */
+class MigrateCaptchaPointsTest extends MigrateDrupal7TestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['captcha'];
+
+  /**
+   * The expected captcha points from the Captcha fixture.
+   *
+   * @var array[]
+   */
+  protected $captchaPoints = [
+    [
+      'form_id' => 'comment_node_article_form',
+      'captcha_type' => 'captcha/Math',
+      'status' => TRUE,
+    ],
+    [
+      'form_id' => 'user_pass',
+      'captcha_type' => 'captcha/Math',
+      'status' => TRUE,
+    ],
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $this->loadFixture(implode(DIRECTORY_SEPARATOR, [
+      DRUPAL_ROOT,
+      drupal_get_path('module', 'captcha'),
+      'tests',
+      'fixtures',
+      'drupal7.php',
+    ]));
+
+    $this->installEntitySchema('captcha_point');
+    $this->installSchema('captcha', ['captcha_sessions']);
+    $this->installConfig('captcha');
+
+    $migrations = [
+      'd7_captcha_points',
+    ];
+    $this->executeMigrations($migrations);
+
+    $this->captchaStorage = $this->container->get('entity_type.manager')
+      ->getStorage('captcha_point');
+  }
+
+  /**
+   * Tests a single captcha point type.
+   *
+   * @param string $form_id
+   *   The captcha point form id.
+   * @param string $captcha_type
+   *   The expected captcha type for the config.
+   * @param bool $status
+   *   The expected status for a captcha type.
+   */
+  protected function assertEntity(string $form_id, string $captcha_type, $status) {
+    /** @var \Drupal\captcha\CaptchaPointInterface $entity */
+    $entity = CaptchaPoint::load($form_id);
+    $this->assertInstanceOf(CaptchaPointInterface::class, $entity);
+    $this->assertSame($form_id, $entity->getFormId());
+    $this->assertSame($captcha_type, $entity->getCaptchaType());
+    $this->assertSame($status, $entity->status());
+  }
+
+  /**
+   * Tests that all expected configuration gets migrated.
+   */
+  public function testCaptchaPointsMigration() {
+    // Test captcha points.
+    foreach ($this->captchaPoints as $captcha_point) {
+      $this->assertEntity($captcha_point['form_id'], $captcha_point['captcha_type'], $captcha_point['status']);
+    }
+  }
+
+}
diff --git a/web/modules/captcha/tests/src/Kernel/Migrate/d7/MigrateCaptchaSimpleConfigurationTest.php b/web/modules/captcha/tests/src/Kernel/Migrate/d7/MigrateCaptchaSimpleConfigurationTest.php
new file mode 100644
index 0000000000..82fa0447e0
--- /dev/null
+++ b/web/modules/captcha/tests/src/Kernel/Migrate/d7/MigrateCaptchaSimpleConfigurationTest.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Drupal\Tests\captcha\Kernel\Migrate\d7;
+
+use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
+
+/**
+ * Migrates various configuration objects owned by the captcha module.
+ *
+ * @group captcha
+ */
+class MigrateCaptchaSimpleConfigurationTest extends MigrateDrupal7TestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['captcha'];
+
+  /**
+   * The expected configuration from the Captcha fixture.
+   *
+   * @var array[]
+   */
+  protected $expectedConfig = [
+    'captcha.settings' => [
+      'enabled_default' => 1,
+      'default_challenge' => 'captcha/Math',
+      'description' => 'This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.',
+      'administration_mode' => TRUE,
+      'allow_on_admin_pages' => FALSE,
+      'add_captcha_description' => TRUE,
+      'default_validation' => 1,
+      'persistence' => 1,
+      'enable_stats' => TRUE,
+      'log_wrong_responses' => TRUE,
+    ],
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $this->loadFixture(implode(DIRECTORY_SEPARATOR, [
+      DRUPAL_ROOT,
+      drupal_get_path('module', 'captcha'),
+      'tests',
+      'fixtures',
+      'drupal7.php',
+    ]));
+
+    $migrations = [
+      'd7_captcha_settings',
+    ];
+    $this->executeMigrations($migrations);
+  }
+
+  /**
+   * Tests that all expected configuration gets migrated.
+   */
+  public function testConfigurationMigration() {
+    // Test Config.
+    foreach ($this->expectedConfig as $config_id => $values) {
+      $actual = \Drupal::config($config_id)->get();
+      $this->assertSame($values, $actual);
+    }
+  }
+
+}
-- 
GitLab