diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e30836c72e59fdfabee40f8479a6a7c7238e4fbf..280341858f5ac286f395de17829b702da62555ec 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -56,6 +56,7 @@ stages:
 variables:
   # For parallel running, needs more CPU resources.
   KUBERNETES_CPU_REQUEST: 16
+  OPT_IN_TEST_PREVIOUS_MAJOR: '1'
   OPT_IN_TEST_NEXT_MAJOR: '1'
   # These two variables allow PHPUnit to run in a parallel job matrix.
   _PHPUNIT_CONCURRENT: '1'
@@ -271,3 +272,12 @@ phpunit:
 phpunit (next major):
   # Require us to be compatible with the next major version of core.
   allow_failure: false
+
+phpstan (previous major):
+  extends: phpstan
+  rules:
+    - !reference [ .opt-in-previous-major-rule ]
+    - !reference [ .skip-phpstan-rule ]
+    - !reference [ .phpstan-allow-failure-rule ]
+  needs:
+    - 'composer (previous major)'
diff --git a/composer.json b/composer.json
index be76fe7d9ddf3bdf8241dcb7c6fea52dfb689584..a56379196a760b82f78ea534eb276df4cac58ca9 100644
--- a/composer.json
+++ b/composer.json
@@ -14,7 +14,7 @@
     "require-dev": {
         "colinodell/psr-testlogger": "^1.2",
         "drupal/automatic_updates": "^3.1.3",
-        "drush/drush": "^13"
+        "drush/drush": "^12.5 || ^13"
     },
     "conflict": {
         "drupal/automatic_updates": "<3.0",
diff --git a/project_browser.info.yml b/project_browser.info.yml
index 57dc124901ddf5678bbdcd4b2b4255269a0b732b..61e6cca65e3c480f1cb2085ff2fbb17807956c37 100644
--- a/project_browser.info.yml
+++ b/project_browser.info.yml
@@ -1,5 +1,5 @@
 name: Project Browser
 type: module
 description: Provides a user interface for browsing available Drupal projects.
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^10.4 || ^11
 configure: project_browser.settings
diff --git a/src/Plugin/DrupalDotOrgSourceBase.php b/src/Plugin/DrupalDotOrgSourceBase.php
index b458539d1d8018493dae98cba9bbdfce3d8e905d..ffd4f4b81d88e6ebc6c117a346feea087b45fc50 100644
--- a/src/Plugin/DrupalDotOrgSourceBase.php
+++ b/src/Plugin/DrupalDotOrgSourceBase.php
@@ -34,35 +34,35 @@ abstract class DrupalDotOrgSourceBase extends ProjectBrowserSourceBase implement
    *
    * @const string
    */
-  public const string DRUPAL_ORG_ENDPOINT = 'https://www.drupal.org';
+  public const DRUPAL_ORG_ENDPOINT = 'https://www.drupal.org';
 
   /**
    * Endpoint to query data from.
    *
    * @const string
    */
-  public const string JSONAPI_ENDPOINT = self::DRUPAL_ORG_ENDPOINT . '/jsonapi';
+  public const JSONAPI_ENDPOINT = self::DRUPAL_ORG_ENDPOINT . '/jsonapi';
 
   /**
    * Endpoint to query modules.
    *
    * @const string
    */
-  protected const string JSONAPI_MODULES_ENDPOINT = self::JSONAPI_ENDPOINT . '/index/project_modules';
+  protected const JSONAPI_MODULES_ENDPOINT = self::JSONAPI_ENDPOINT . '/index/project_modules';
 
   /**
    * Value of the revoked status in the security coverage field.
    *
    * @const string
    */
-  protected const string REVOKED_STATUS = 'revoked';
+  protected const REVOKED_STATUS = 'revoked';
 
   /**
    * This is what Drupal.org understands as "Covered" modules.
    *
    * @var array
    */
-  private const array COVERED_VALUES = ['covered'];
+  private const COVERED_VALUES = ['covered'];
 
   public function __construct(
     array $configuration,
diff --git a/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php b/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php
index e142ab5dbcd7053f230d4dbbe74b224e3df17a76..8f5936c16effacca5b98de6adb1947141db5c228 100644
--- a/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php
+++ b/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php
@@ -6,7 +6,7 @@ namespace Drupal\Tests\project_browser\FunctionalJavascript;
 
 use Behat\Mink\Element\NodeElement;
 use Drupal\Core\Extension\ModuleInstallerInterface;
-use Drupal\Core\Recipe\Recipe;
+use Drupal\Core\Recipe\RecipeInputFormTrait;
 use Drupal\Core\State\StateInterface;
 use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
 use Drupal\project_browser\InstallState;
@@ -154,7 +154,7 @@ final class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
 
     // Apply a recipe that requires user input.
     // @todo Remove this check in https://www.drupal.org/i/3494848.
-    if (!property_exists(Recipe::class, 'input')) {
+    if (!trait_exists(RecipeInputFormTrait::class)) {
       $this->markTestSkipped('This test cannot continue because this version of Drupal does not support collecting recipe input.');
     }
     $this->inputSearchField('test', TRUE);