diff --git a/modules/project_browser_devel/src/Plugin/ProjectBrowserSource/RandomDataPlugin.php b/modules/project_browser_devel/src/Plugin/ProjectBrowserSource/RandomDataPlugin.php
index 9e28991b66f2ca0cd1bca2b0a23c2d816be791c4..7b16a795fb73c873f5aed5c56a05e4dc15b1a86f 100644
--- a/modules/project_browser_devel/src/Plugin/ProjectBrowserSource/RandomDataPlugin.php
+++ b/modules/project_browser_devel/src/Plugin/ProjectBrowserSource/RandomDataPlugin.php
@@ -22,6 +22,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  *   id = "random_data",
  *   label = @Translation("Random data"),
  *   description = @Translation("Gets random project and filters information"),
+ *   local_task = {}
  * )
  */
 class RandomDataPlugin extends ProjectBrowserSourceBase {
diff --git a/project_browser.links.task.yml b/project_browser.links.task.yml
index c808c538cdd0822012760e807c5eea3ec64952fa..320afe230e1fce6d3b036f03bd8f7400fa864ad1 100644
--- a/project_browser.links.task.yml
+++ b/project_browser.links.task.yml
@@ -1,5 +1,4 @@
 project_browser.browse:
   route_name: project_browser.browse
   base_route: system.modules_list
-  title: 'Browse'
-  weight: 5
+  deriver: 'Drupal\project_browser\Plugin\Derivative\LocalTaskDeriver'
diff --git a/project_browser.module b/project_browser.module
index 329bffad7f777351e4fce43a9772d5711300f7ab..a823a5531ffb805cf64b7cfa3fd5f1b66d4d1c67 100644
--- a/project_browser.module
+++ b/project_browser.module
@@ -52,6 +52,9 @@ function project_browser_project_browser_source_info_alter(array &$definitions):
       'id' => 'recipes',
       'label' => t('Recipes'),
       'description' => t('Recipes available in the local code base'),
+      'local_task' => [
+        'weight' => ($definitions['drupalorg_jsonapi']['local_task']['weight'] ?? 5) + 2,
+      ],
       'class' => Recipes::class,
     ];
   }
diff --git a/project_browser.routing.yml b/project_browser.routing.yml
index 3d0b4f96cb769c83cf23fb80af6945ff78e9363a..2067321095503a258f05d5bb0e9c7876f1e67baa 100644
--- a/project_browser.routing.yml
+++ b/project_browser.routing.yml
@@ -24,7 +24,6 @@ project_browser.browse:
     _controller: '\Drupal\project_browser\Controller\BrowserController::browse'
     _title: 'Browse projects'
     id: null
-    source: null
   requirements:
     _permission: 'administer modules'
 project_browser.settings:
diff --git a/src/Annotation/ProjectBrowserSource.php b/src/Annotation/ProjectBrowserSource.php
index e5852f6c3ac33fe678ef00fdfd57e97b92379609..aab59a0160a0c9ea9d0a87a7790d0f29f9bd3fe6 100644
--- a/src/Annotation/ProjectBrowserSource.php
+++ b/src/Annotation/ProjectBrowserSource.php
@@ -48,4 +48,13 @@ class ProjectBrowserSource extends Plugin {
    */
   public $description;
 
+  /**
+   * The local task definition at which this source should be exposed.
+   *
+   * If NULL, the source will never be exposed as a local task.
+   *
+   * @var array|null
+   */
+  public ?array $local_task = NULL;
+
 }
diff --git a/src/Controller/BrowserController.php b/src/Controller/BrowserController.php
index 73941c43b6644e8a0d6becc67f4f57ccbf11e058..f1a8ae13f921bc7a625378ebc2702db25f247e00 100644
--- a/src/Controller/BrowserController.php
+++ b/src/Controller/BrowserController.php
@@ -24,8 +24,8 @@ class BrowserController extends ControllerBase {
    * rendered. For example, 'https//drupal-site/admin/modules/browse/ctools'
    * will display the details for ctools.
    *
-   * @param string|null $source
-   *   If viewing a specific project, the ID of its source plugin.
+   * @param string $source
+   *   The ID of the source plugin to query for projects.
    * @param string|null $id
    *   If viewing a specific project, the project's local ID (as known to the
    *   source plugin).
@@ -33,7 +33,7 @@ class BrowserController extends ControllerBase {
    * @return array
    *   A render array.
    */
-  public function browse(?string $source, ?string $id): array {
+  public function browse(string $source, ?string $id): array {
     return [
       '#type' => 'project_browser',
       '#source' => $source,
diff --git a/src/Controller/InstallerController.php b/src/Controller/InstallerController.php
index 47f223f16cd963e670279c43e02d9db6d5dfc0b0..dcca7a89589173e9fcb5ab8034cb362ce112553d 100644
--- a/src/Controller/InstallerController.php
+++ b/src/Controller/InstallerController.php
@@ -201,10 +201,13 @@ class InstallerController extends ControllerBase {
   /**
    * Unlocks and destroys the stage.
    *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request.
+   *
    * @return \Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse
    *   Redirects to the main project browser page.
    */
-  public function unlock(): JsonResponse|RedirectResponse {
+  public function unlock(Request $request): JsonResponse|RedirectResponse {
     try {
       // It's possible the unlock url was provided before applying began, but
       // accessed after. This final check ensures a destroy is not attempted
@@ -231,7 +234,11 @@ class InstallerController extends ControllerBase {
     }
     $this->installState->deleteAll();
     $this->messenger()->addStatus($this->t('Operation complete, you can add a new project again.'));
-    return $this->redirect('project_browser.browse');
+
+    $redirect = Url::fromUserInput($this->getRedirectDestination()->get())
+      ->setAbsolute()
+      ->toString();
+    return new RedirectResponse($redirect);
   }
 
   /**
@@ -264,20 +271,28 @@ class InstallerController extends ControllerBase {
   /**
    * Begins requiring by creating a stage.
    *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request.
+   *
    * @return \Symfony\Component\HttpFoundation\JsonResponse
    *   Status message.
    */
-  public function begin(): JsonResponse {
+  public function begin(Request $request): JsonResponse {
     $stage_available = $this->installer->isAvailable();
     if (!$stage_available) {
+      $unlock_url = self::getUrlWithReplacedCsrfTokenPlaceholder(
+        Url::fromRoute('project_browser.install.unlock'),
+      );
+      $destination = Url::fromRoute('project_browser.browse', [
+        'source' => $request->query->get('source'),
+      ]);
+      $unlock_url .= '&destination=' . $destination->toString();
+
       $updated_time = $this->installState->getFirstUpdatedTime();
       if (!$this->installer->lockCameFromProjectBrowserInstaller()) {
         return $this->lockedResponse($this->t('The process for adding projects was locked by something else outside of Project Browser. Projects can be added again once the process is unlocked. Try again in a few minutes.'), '');
       }
       if (empty($updated_time)) {
-        $unlock_url = self::getUrlWithReplacedCsrfTokenPlaceholder(
-          Url::fromRoute('project_browser.install.unlock')
-        );
         $message = t('The process for adding projects is locked, but that lock has expired. Use [+ unlock link] to unlock the process and try to add the project again.');
         return $this->lockedResponse($message, $unlock_url);
       }
@@ -302,10 +317,6 @@ class InstallerController extends ControllerBase {
           $this->t('The process for adding the project was locked @hours hours, @minutes minutes ago. Use [+ unlock link] to unlock the process.',
             ['@hours' => $hours, '@minutes' => $minutes]);
       }
-
-      $unlock_url = self::getUrlWithReplacedCsrfTokenPlaceholder(
-        Url::fromRoute('project_browser.install.unlock')
-      );
       return $this->lockedResponse($message, $unlock_url);
     }
 
diff --git a/src/Element/ProjectBrowser.php b/src/Element/ProjectBrowser.php
index 06b284163eb468eac44aa323754834702ba10dc8..7308643c50e46d2246cb1633abdcbd01893df197 100644
--- a/src/Element/ProjectBrowser.php
+++ b/src/Element/ProjectBrowser.php
@@ -97,7 +97,7 @@ final class ProjectBrowser implements ElementInterface, ContainerFactoryPluginIn
    */
   public function attachProjectBrowserSettings(array $element): array {
     $element['#attached']['drupalSettings']['project_browser'] = $this->getDrupalSettings(
-      $element['#source'] ?? NULL,
+      $element['#source'],
       $element['#id'] ?? NULL
     );
     return $element;
@@ -106,8 +106,8 @@ final class ProjectBrowser implements ElementInterface, ContainerFactoryPluginIn
   /**
    * Gets the Drupal settings for the Project Browser.
    *
-   * @param string|null $source
-   *   If viewing a specific project, the ID of its source plugin.
+   * @param string $source
+   *   The ID of the source plugin to query for projects.
    * @param string|null $id
    *   If viewing a specific project, the project's local ID (as known to the
    *   source plugin).
@@ -115,13 +115,10 @@ final class ProjectBrowser implements ElementInterface, ContainerFactoryPluginIn
    * @return array
    *   An array of Drupal settings.
    */
-  private function getDrupalSettings(?string $source, ?string $id): array {
-    $current_sources = $this->enabledSourceHandler->getCurrentSources();
-    if ($source) {
-      $current_sources = [
-        $source => $current_sources[$source],
-      ];
-    }
+  private function getDrupalSettings(string $source, ?string $id): array {
+    $current_sources = [
+      $source => $this->enabledSourceHandler->getCurrentSources()[$source],
+    ];
 
     $package_manager = [
       'available' => (bool) $this->configFactory->get('project_browser.admin_settings')->get('allow_ui_install'),
@@ -129,7 +126,7 @@ final class ProjectBrowser implements ElementInterface, ContainerFactoryPluginIn
       'warnings' => [],
       'status_checked' => FALSE,
     ];
-    if (empty($source) || empty($id)) {
+    if (empty($id)) {
       if ($package_manager['available']) {
         $package_manager = array_merge($package_manager, $this->installReadiness->validatePackageManager());
         $package_manager['status_checked'] = TRUE;
@@ -151,7 +148,6 @@ final class ProjectBrowser implements ElementInterface, ContainerFactoryPluginIn
       'security_options' => SecurityStatus::asOptions(),
       'development_options' => DevelopmentStatus::asOptions(),
       'default_plugin_id' => reset($current_sources)->getPluginId(),
-      'current_sources_keys' => array_keys($current_sources),
       'package_manager' => $package_manager,
       'filters' => array_map(
         fn (ProjectBrowserSourceInterface $source) => $source->getFilterDefinitions(),
diff --git a/src/Form/SettingsForm.php b/src/Form/SettingsForm.php
index d2f8023b3d696bb2bd3479b07f0b842d45d51ecf..30a5a0e490998f7037fe5835a774435e1f5938b3 100644
--- a/src/Form/SettingsForm.php
+++ b/src/Form/SettingsForm.php
@@ -2,12 +2,14 @@
 
 namespace Drupal\project_browser\Form;
 
+use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Menu\LocalTaskManagerInterface;
 use Drupal\project_browser\Plugin\ProjectBrowserSourceManager;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -16,26 +18,13 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  */
 class SettingsForm extends ConfigFormBase {
 
-  /**
-   * Constructor for settings form.
-   *
-   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
-   *   The config factory interface.
-   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager
-   *   The typed config manager.
-   * @param \Drupal\project_browser\Plugin\ProjectBrowserSourceManager $manager
-   *   The module source manger.
-   * @param \Drupal\Core\Cache\CacheBackendInterface $cacheBin
-   *   The back end cache.
-   * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
-   *   The module handler.
-   */
   public function __construct(
     ConfigFactoryInterface $config_factory,
     TypedConfigManagerInterface $typed_config_manager,
     private readonly ProjectBrowserSourceManager $manager,
     private readonly CacheBackendInterface $cacheBin,
     private readonly ModuleHandlerInterface $moduleHandler,
+    private readonly LocalTaskManagerInterface&CachedDiscoveryInterface $localTaskManager,
   ) {
     parent::__construct($config_factory, $typed_config_manager);
   }
@@ -50,6 +39,7 @@ class SettingsForm extends ConfigFormBase {
       $container->get(ProjectBrowserSourceManager::class),
       $container->get('cache.project_browser'),
       $container->get(ModuleHandlerInterface::class),
+      $container->get(LocalTaskManagerInterface::class),
     );
   }
 
@@ -233,6 +223,7 @@ class SettingsForm extends ConfigFormBase {
       ->set('allow_ui_install', $form_state->getValue('allow_ui_install'))
       ->save();
     $this->cacheBin->deleteAll();
+    $this->localTaskManager->clearCachedDefinitions();
     parent::submitForm($form, $form_state);
   }
 
diff --git a/src/Plugin/Derivative/LocalTaskDeriver.php b/src/Plugin/Derivative/LocalTaskDeriver.php
new file mode 100644
index 0000000000000000000000000000000000000000..cb2d31d4a362f0eadd8378a07ee19d284d56b9a8
--- /dev/null
+++ b/src/Plugin/Derivative/LocalTaskDeriver.php
@@ -0,0 +1,61 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\project_browser\Plugin\Derivative;
+
+use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\project_browser\EnabledSourceHandler;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Exposes local tasks for all enabled source plugins.
+ */
+final class LocalTaskDeriver extends DeriverBase implements ContainerDeriverInterface {
+
+  use StringTranslationTrait;
+
+  public function __construct(
+    private readonly EnabledSourceHandler $enabledSources,
+  ) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, $base_plugin_id) {
+    return new static(
+      $container->get(EnabledSourceHandler::class),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_plugin_definition) {
+    $i = 5;
+    foreach ($this->enabledSources->getCurrentSources() as $source) {
+      $source_definition = $source->getPluginDefinition();
+
+      if (isset($source_definition['local_task'])) {
+        $local_task = $base_plugin_definition + $source_definition['local_task'];
+        // If no title was provided for the local task, fall back to the
+        // source's administrative label.
+        $local_task += [
+          'title' => $source_definition['label'],
+          'weight' => $i++,
+        ];
+        $source_id = $source->getPluginId();
+        $local_task['route_parameters'] = [
+          'source' => $source_id,
+          'id' => NULL,
+        ];
+        $derivative_id = str_replace($source::DERIVATIVE_SEPARATOR, '__', $source_id);
+        $this->derivatives[$derivative_id] = $local_task;
+      }
+    }
+    return parent::getDerivativeDefinitions($base_plugin_definition);
+  }
+
+}
diff --git a/src/Plugin/ProjectBrowserSource/DrupalDotOrgJsonApi.php b/src/Plugin/ProjectBrowserSource/DrupalDotOrgJsonApi.php
index b047e3b7805c2c6189308b6cca992035968d66a3..70e1496b7eacf94cd00ab3b2a91f9c0e9888b71d 100644
--- a/src/Plugin/ProjectBrowserSource/DrupalDotOrgJsonApi.php
+++ b/src/Plugin/ProjectBrowserSource/DrupalDotOrgJsonApi.php
@@ -30,6 +30,9 @@ use Symfony\Component\HttpFoundation\Response;
  *   id = "drupalorg_jsonapi",
  *   label = @Translation("Contrib modules"),
  *   description = @Translation("Modules on Drupal.org queried via the JSON:API endpoint"),
+ *   local_task = {
+ *     "title" = @Translation("Browse"),
+ *   }
  * )
  */
 class DrupalDotOrgJsonApi extends ProjectBrowserSourceBase {
diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js
index af3bd9d38857ecbfc6a3f18998f03c37eabf9225..873722c94ef0d7a31cbcd045e29748890ac6878c 100644
Binary files a/sveltejs/public/build/bundle.js and b/sveltejs/public/build/bundle.js differ
diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map
index 80bae75a083a63eb2e13fc5d7ec756e62fb77ebb..b8782dc1ba8789c34f7cb2ce08f3e933eadff1f4 100644
Binary files a/sveltejs/public/build/bundle.js.map and b/sveltejs/public/build/bundle.js.map differ
diff --git a/sveltejs/src/ProcessQueueButton.svelte b/sveltejs/src/ProcessQueueButton.svelte
index 44d4a5b9b15c8ec0599b6437514a3b3bfa989438..af379705aab234a7c9c5fda9f8b86ab6ca0d4cba 100644
--- a/sveltejs/src/ProcessQueueButton.svelte
+++ b/sveltejs/src/ProcessQueueButton.svelte
@@ -110,7 +110,7 @@
    *   Returns a promise that resolves once the download and activation process is complete.
    */
   async function doRequests(projectIds) {
-    const beginInstallUrl = `${BASE_URL}admin/modules/project_browser/install-begin`;
+    const beginInstallUrl = `${BASE_URL}admin/modules/project_browser/install-begin?source=${$activeTab}`;
     const beginInstallResponse = await fetch(beginInstallUrl);
     if (!beginInstallResponse.ok) {
       await handleError(beginInstallResponse);
diff --git a/sveltejs/src/ProjectBrowser.svelte b/sveltejs/src/ProjectBrowser.svelte
index 6d733765a3b3312467dc16192022517d701416d9..e5168987a3c748e00346e91d3c1a0121f9589ea0 100644
--- a/sveltejs/src/ProjectBrowser.svelte
+++ b/sveltejs/src/ProjectBrowser.svelte
@@ -4,7 +4,6 @@
   import ProjectGrid, { Search } from './ProjectGrid.svelte';
   import Pagination from './Pagination.svelte';
   import Project from './Project/Project.svelte';
-  import Tabs from './Tabs.svelte';
   import { numberFormatter } from './util';
   import ProcessQueueButton from './ProcessQueueButton.svelte';
   import {
@@ -12,7 +11,6 @@
     filters,
     rowsCount,
     moduleCategoryFilter,
-    isFirstLoad,
     page,
     sort,
     focusedElement,
@@ -32,7 +30,6 @@
     COVERED_ID,
     ALL_VALUES_ID,
     DEFAULT_SOURCE_ID,
-    CURRENT_SOURCES_KEYS,
     BASE_URL,
     FULL_MODULE_PATH,
     SORT_OPTIONS,
@@ -173,7 +170,6 @@
       $filters.securityCoverage = COVERED_ID;
       $filters.developmentStatus = ALL_VALUES_ID;
     }
-    isFirstLoad.set(false);
   }
 
   /**
@@ -184,33 +180,18 @@
     if (savedPageSize) {
       pageSize.set(Number(savedPageSize));
     }
-    const matches = window.location.pathname.match(
-      /\/admin\/modules\/browse\/(.+)/,
-    );
-    projectId = matches ? matches[1] : null;
-    // If current active plugin is disabled or if this is a plugin specific
-    // route, remove storage keys and reload page.
-    const settingsActiveTab = JSON.stringify(DEFAULT_SOURCE_ID);
-    if (
-      ($activeTab !== settingsActiveTab &&
-        CURRENT_SOURCES_KEYS.indexOf($activeTab) === -1) ||
-      (projectId && $isFirstLoad)
-    ) {
-      sessionStorage.removeItem('activeTab');
-      sessionStorage.removeItem('categoryFilter');
-      sessionStorage.removeItem('categoryCheckedTrack');
-      sessionStorage.removeItem('sortCriteria');
-      sessionStorage.removeItem('sourceFilters');
-      sessionStorage.removeItem('sort');
-      sessionStorage.setItem('activeTab', settingsActiveTab);
-      window.location.reload();
-    }
-    // Only filter by recommended on first page load or  if this is a
-    // plugin specific route.
-    if (projectId || $isFirstLoad) {
-      await filterRecommended();
+
+    $activeTab = DEFAULT_SOURCE_ID;
+    // The project ID, if there is one, will be the last thing in the URL
+    // path, and we can reasonably expect it to be different than the
+    // source plugin ID.
+    projectId = window.location.pathname.substring(1).split('/').pop();
+    if (projectId === $activeTab) {
+      projectId = null;
     }
 
+    await filterRecommended();
+
     await load($page);
     const focus = element ? document.getElementById(element) : false;
     if (focus) {
@@ -353,10 +334,6 @@
 <MediaQuery query="(min-width: 1200px)" let:matches>
   <ProjectGrid {toggleView} {loading} {rows} {pageIndex} {$pageSize} let:rows>
     <div slot="head">
-      <!--Show tabs only if there are 2 or more plugins enabled.-->
-      {#if dataArray.length >= 2 && !projectId}
-        <Tabs {dataArray} on:tabChange={toggleRows} />
-      {/if}
       <Search
         bind:this={searchComponent}
         on:search={onSearch}
diff --git a/sveltejs/src/Tabs.svelte b/sveltejs/src/Tabs.svelte
deleted file mode 100644
index ea2cbe34beee167e2bc806b030ad9995e070097b..0000000000000000000000000000000000000000
--- a/sveltejs/src/Tabs.svelte
+++ /dev/null
@@ -1,55 +0,0 @@
-<script>
-  import { createEventDispatcher } from 'svelte';
-  import { activeTab } from './stores';
-
-  const { Drupal } = window;
-  const dispatch = createEventDispatcher();
-
-  // eslint-disable-next-line import/no-mutable-exports,import/prefer-default-export
-  export let dataArray = [];
-  let tabButtons;
-</script>
-
-<nav class="tabs-wrapper tabs-wrapper--secondary is-horizontal">
-  <div
-    role="tablist"
-    id="plugin-tabs"
-    aria-label={Drupal.t('Plugin tabs')}
-    bind:this={tabButtons}
-    class="tabs tabs--secondary pb-tabs"
-  >
-    {#each dataArray.map( (item) => ({ ...item, isActive: item.pluginId === $activeTab }), ) as { pluginId, pluginLabel, totalResults, isActive }}
-      <span
-        class="tabs__tab pb-tabs__tab"
-        class:is-active={isActive === true}
-        class:pb-tabs__tab--active={isActive === true}
-      >
-        <button
-          type="button"
-          role="tab"
-          aria-selected={isActive ? 'true' : 'false'}
-          aria-controls={pluginId}
-          tabindex="0"
-          id={pluginId}
-          class="pb-tabs__link tabs__link"
-          class:is-active={isActive === true}
-          class:pb-tabs__link--active={isActive === true}
-          value={pluginId}
-          on:click={(event) => {
-            dispatch('tabChange', {
-              pluginId,
-              event,
-            });
-          }}
-        >
-          {pluginLabel}
-          <br />
-          {Drupal.formatPlural(totalResults, '1 result', '@count results')}
-          {#if isActive}
-            <span class="visually-hidden">({Drupal.t('active tab')})</span>
-          {/if}
-        </button>
-      </span>
-    {/each}
-  </div>
-</nav>
diff --git a/sveltejs/src/constants.js b/sveltejs/src/constants.js
index 3f5ede0781e97954a5f416d314973b0edf084f2b..1dd3633979c076d962fdb4563856eb8a1d8eab4e 100644
--- a/sveltejs/src/constants.js
+++ b/sveltejs/src/constants.js
@@ -12,8 +12,6 @@ export const ALL_VALUES_ID =
   drupalSettings.project_browser.special_ids.all_values;
 export const DEFAULT_SOURCE_ID =
   drupalSettings.project_browser.default_plugin_id;
-export const CURRENT_SOURCES_KEYS =
-  drupalSettings.project_browser.current_sources_keys;
 export const BASE_URL = `${window.location.protocol}//${window.location.host}${drupalSettings.path.baseUrl + drupalSettings.path.pathPrefix}`;
 export const FULL_MODULE_PATH = `${BASE_URL}${drupalSettings.project_browser.module_path}`;
 export const DARK_COLOR_SCHEME =
diff --git a/sveltejs/src/stores.js b/sveltejs/src/stores.js
index 11124f69e552e85857454e2b11ca82f0c1a87b7c..8ec141381f71a7f0d9806e96febe47ea4e786381 100644
--- a/sveltejs/src/stores.js
+++ b/sveltejs/src/stores.js
@@ -6,25 +6,20 @@ import {
 } from './constants';
 
 // Store the selected tab.
-const storedActiveTab = JSON.parse(sessionStorage.getItem('activeTab')) || DEFAULT_SOURCE_ID;
+const storedActiveTab = DEFAULT_SOURCE_ID;
 let activeFilters = {};
-if (sessionStorage.getItem('sourceFilters')){
-  activeFilters = JSON.parse(sessionStorage.getItem('sourceFilters'));
-}
-else if (storedActiveTab in FILTERS) {
+if (storedActiveTab in FILTERS) {
   activeFilters = FILTERS[storedActiveTab];
 }
 export const sourceFilters = writable(activeFilters);
-sourceFilters.subscribe((val) => sessionStorage.setItem('sourceFilters', JSON.stringify(val)));
 
 // Store for applied advanced filters.
-const storedFilters = JSON.parse(sessionStorage.getItem('advancedFilter')) || {
+const storedFilters = {
   developmentStatus: '',
   maintenanceStatus: '',
   securityCoverage: ''
 };
 export const filters = writable(storedFilters);
-filters.subscribe((val) => sessionStorage.setItem('advancedFilter', JSON.stringify(val)));
 
 export const rowsCount = writable(0);
 
@@ -35,9 +30,8 @@ export const filtersVocabularies = writable({
 });
 
 // Store for applied category filters.
-const storedModuleCategoryFilter = JSON.parse(sessionStorage.getItem('categoryFilter')) || [];
+const storedModuleCategoryFilter = [];
 export const moduleCategoryFilter = writable(storedModuleCategoryFilter);
-moduleCategoryFilter.subscribe((val) => sessionStorage.setItem('categoryFilter', JSON.stringify(val)));
 
 // Store for module category vocabularies.
 const storedModuleCategoryVocabularies = JSON.parse(localStorage.getItem('moduleCategoryVocabularies')) || {};
@@ -45,52 +39,42 @@ export const moduleCategoryVocabularies = writable(storedModuleCategoryVocabular
 moduleCategoryVocabularies.subscribe((val) => localStorage.setItem('moduleCategoryVocabularies', JSON.stringify(val)));
 
 // Store used to check if the page has loaded once already.
-const storedIsFirstLoad = JSON.parse(sessionStorage.getItem('isFirstLoad')) === false ? JSON.parse(sessionStorage.getItem('isFirstLoad')) : true;
+const storedIsFirstLoad = true;
 export const isFirstLoad = writable(storedIsFirstLoad);
-isFirstLoad.subscribe((val) => sessionStorage.setItem('isFirstLoad', JSON.stringify(val)));
 
 // Store the page the user is on.
-const storedPage = JSON.parse(sessionStorage.getItem('page')) || 0;
+const storedPage = 0;
 export const page = writable(storedPage);
-page.subscribe((val) => sessionStorage.setItem('page', JSON.stringify(val)));
 
 export const activeTab = writable(storedActiveTab);
-activeTab.subscribe((val) => sessionStorage.setItem('activeTab', JSON.stringify(val)));
 
 // Store the current sort selected.
-const storedSort = JSON.parse(sessionStorage.getItem('sort')) || SORT_OPTIONS[storedActiveTab][0].id;
+const storedSort = SORT_OPTIONS[storedActiveTab][0].id;
 export const sort = writable(storedSort);
-sort.subscribe((val) => sessionStorage.setItem('sort', JSON.stringify(val)));
 
 // Store tab-wise checked categories.
-const storedCategoryCheckedTrack = JSON.parse(sessionStorage.getItem('categoryCheckedTrack')) || {};
+const storedCategoryCheckedTrack = {};
 export const categoryCheckedTrack = writable(storedCategoryCheckedTrack);
-categoryCheckedTrack.subscribe((val) => sessionStorage.setItem('categoryCheckedTrack', JSON.stringify(val)));
 
 // Store the element that was last focused.
-const storedFocus = JSON.parse(sessionStorage.getItem('focusedElement')) || '';
+const storedFocus = '';
 export const focusedElement = writable(storedFocus);
-focusedElement.subscribe((val) => sessionStorage.setItem('focusedElement', JSON.stringify(val)));
 
 // Store the search string.
-const storedSearchString = JSON.parse(sessionStorage.getItem('searchString')) || '';
+const storedSearchString = '';
 export const searchString = writable(storedSearchString);
-searchString.subscribe((val) => sessionStorage.setItem('searchString', JSON.stringify(val)));
 
 // Store for sort criteria.
-const storedSortCriteria = JSON.parse(sessionStorage.getItem('sortCriteria')) || SORT_OPTIONS[storedActiveTab];
+const storedSortCriteria = SORT_OPTIONS[storedActiveTab];
 export const sortCriteria = writable(storedSortCriteria);
-sortCriteria.subscribe((val) => sessionStorage.setItem('sortCriteria', JSON.stringify(val)));
 
 // Store the selected toggle view.
-const storedPreferredView = JSON.parse(sessionStorage.getItem('preferredView')) || 'Grid';
+const storedPreferredView = 'Grid';
 export const preferredView = writable(storedPreferredView);
-preferredView.subscribe((val) => sessionStorage.setItem('preferredView', JSON.stringify(val)));
 
 // Store the selected page size.
-const storedPageSize = JSON.parse(sessionStorage.getItem('pageSize')) || 12;
+const storedPageSize = 12;
 export const pageSize = writable(storedPageSize);
-pageSize.subscribe((val) => sessionStorage.setItem('pageSize', JSON.stringify(val)));
 
 // Store the value of media queries.
 export const mediaQueryValues = writable(new Map());
@@ -98,11 +82,8 @@ export const mediaQueryValues = writable(new Map());
 export const updated = writable(0);
 
 // Store for the queue list.
-const storedQueueList = JSON.parse(sessionStorage.getItem('queueList')) || {};
+const storedQueueList = {};
 export const queueList = writable(storedQueueList);
-queueList.subscribe((val) =>
-  sessionStorage.setItem('queueList', JSON.stringify(val)),
-);
 
 export function addToQueue(tabId, project) {
   queueList.update((currentList) => {
diff --git a/tests/modules/project_browser_test/src/Plugin/ProjectBrowserSource/ProjectBrowserTestMock.php b/tests/modules/project_browser_test/src/Plugin/ProjectBrowserSource/ProjectBrowserTestMock.php
index 35321cf6c7b5c55371ea2324f5c424300fcf28d1..081570f20346b7f22d30b5b8a1de65cad6b722ef 100644
--- a/tests/modules/project_browser_test/src/Plugin/ProjectBrowserSource/ProjectBrowserTestMock.php
+++ b/tests/modules/project_browser_test/src/Plugin/ProjectBrowserSource/ProjectBrowserTestMock.php
@@ -29,6 +29,9 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  *   id = "project_browser_test_mock",
  *   label = @Translation("Project Browser Mock Plugin"),
  *   description = @Translation("Gets project and filters information from a database"),
+ *   local_task = {
+ *     "title" = @Translation("Browse"),
+ *   }
  * )
  */
 class ProjectBrowserTestMock extends ProjectBrowserSourceBase {
diff --git a/tests/src/Functional/InstallerControllerTest.php b/tests/src/Functional/InstallerControllerTest.php
index ccba786a502c38d9c0de8eb33df04e890cecf809..0779255457a3a65725ce5f2bfba5cccf7a9f9f66 100644
--- a/tests/src/Functional/InstallerControllerTest.php
+++ b/tests/src/Functional/InstallerControllerTest.php
@@ -439,10 +439,27 @@ class InstallerControllerTest extends BrowserTestBase {
     $this->doStart();
     $this->doRequire();
 
+    $request_options = [
+      'query' => ['source' => 'project_browser_test_mock'],
+    ];
+
+    $assert_unlock_response = function (string $response, string $expected_message): void {
+      $response = Json::decode($response);
+      $this->assertSame($expected_message, $response['message']);
+
+      if ($response['unlock_url']) {
+        $this->assertStringEndsWith('/admin/modules/project_browser/install/unlock', parse_url($response['unlock_url'], PHP_URL_PATH));
+        $query = parse_url($response['unlock_url'], PHP_URL_QUERY);
+        parse_str($query, $query);
+        $this->assertNotEmpty($query['token']);
+        $this->assertStringEndsWith('/admin/modules/browse/project_browser_test_mock', $query['destination']);
+      }
+    };
+
     // Check for mid install unlock offer message.
-    $this->drupalGet('admin/modules/project_browser/install-begin');
+    $response = $this->drupalGet('admin/modules/project_browser/install-begin', $request_options);
     $this->assertSession()->statusCodeEquals(418);
-    $this->assertMatchesRegularExpression('/{"message":"The process for adding the project that was locked less than 1 minutes ago might still be in progress. Consider waiting a few more minutes before using \[\+unlock link\].","unlock_url":".*admin..modules..project_browser..install..unlock\?token=[a-zA-Z0-9_-]*"}/', $this->getSession()->getPage()->getContent());
+    $assert_unlock_response($response, "The process for adding the project that was locked less than 1 minutes ago might still be in progress. Consider waiting a few more minutes before using [+unlock link].");
     $expected = [
       'project_browser_test_mock/awesome_module' => [
         'source' => 'project_browser_test_mock',
@@ -453,27 +470,27 @@ class InstallerControllerTest extends BrowserTestBase {
     $this->assertFalse($this->installer->isAvailable());
     $this->assertFalse($this->installer->isApplying());
     TestTime::setFakeTimeByOffset("+800 seconds");
-    $this->drupalGet('admin/modules/project_browser/install-begin');
+    $response = $this->drupalGet('admin/modules/project_browser/install-begin', $request_options);
     $this->assertSession()->statusCodeEquals(418);
     $this->assertFalse($this->installer->isAvailable());
     $this->assertFalse($this->installer->isApplying());
-    $this->assertMatchesRegularExpression('/{"message":"The process for adding the project was locked 13 minutes ago. Use \[\+ unlock link\] to unlock the process.","unlock_url":".*admin..modules..project_browser..install..unlock\?token=[a-zA-Z0-9_-]*"}/', $this->getSession()->getPage()->getContent());
+    $assert_unlock_response($response, "The process for adding the project was locked 13 minutes ago. Use [+ unlock link] to unlock the process.");
     $this->doApply();
     TestTime::setFakeTimeByOffset('+800 seconds');
-    $this->drupalGet('admin/modules/project_browser/install-begin');
+    $response = $this->drupalGet('admin/modules/project_browser/install-begin', $request_options);
     $this->assertSession()->statusCodeEquals(418);
     $this->assertFalse($this->installer->isAvailable());
     $this->assertTrue($this->installer->isApplying());
-    $this->assertMatchesRegularExpression('/{"message":"The process for adding the project was locked 13 minutes ago. It should not be unlocked while changes are being applied to the site.","unlock_url":""}/', $this->getSession()->getPage()->getContent());
+    $assert_unlock_response($response, "The process for adding the project was locked 13 minutes ago. It should not be unlocked while changes are being applied to the site.");
     TestTime::setFakeTimeByOffset("+55 minutes");
-    $this->drupalGet('admin/modules/project_browser/install-begin');
+    $response = $this->drupalGet('admin/modules/project_browser/install-begin', $request_options);
     $this->assertSession()->statusCodeEquals(418);
-    $this->assertMatchesRegularExpression('/{"message":"The process for adding the project was locked 55 minutes ago. It should not be unlocked while changes are being applied to the site.","unlock_url":""}/', $this->getSession()->getPage()->getContent());
+    $assert_unlock_response($response, "The process for adding the project was locked 55 minutes ago. It should not be unlocked while changes are being applied to the site.");
     // Unlocking the stage becomes possible after 1 hour regardless of source.
     TestTime::setFakeTimeByOffset("+75 minutes");
-    $this->drupalGet('admin/modules/project_browser/install-begin');
+    $response = $this->drupalGet('admin/modules/project_browser/install-begin', $request_options);
     $this->assertSession()->statusCodeEquals(418);
-    $this->assertMatchesRegularExpression('/{"message":"The process for adding the project was locked 1 hours, 15 minutes ago. Use \[\+ unlock link\] to unlock the process.","unlock_url":".*admin..modules..project_browser..install..unlock\?token=[a-zA-Z0-9_-]*"}/', $this->getSession()->getPage()->getContent());
+    $assert_unlock_response($response, "The process for adding the project was locked 1 hours, 15 minutes ago. Use [+ unlock link] to unlock the process.");
   }
 
   /**
@@ -487,15 +504,19 @@ class InstallerControllerTest extends BrowserTestBase {
     $this->doStart();
     // Try beginning another install while one is in progress, but not yet in
     // the applying stage.
-    $content = $this->drupalGet('admin/modules/project_browser/install-begin');
+    $content = $this->drupalGet('admin/modules/project_browser/install-begin', [
+      'query' => [
+        'source' => 'project_browser_test_mock',
+      ],
+    ]);
     $this->assertSession()->statusCodeEquals(418);
     $this->assertFalse($this->installer->isAvailable());
     $this->assertFalse($this->installer->isApplying());
     $json = Json::decode($content);
     $this->assertSame('The process for adding projects is locked, but that lock has expired. Use [+ unlock link] to unlock the process and try to add the project again.', $json['message']);
-    $path = explode('?', $json['unlock_url'])[0];
-    $token = explode('=', $json['unlock_url'])[1];
-    $unlock_content = $this->drupalGet($path, ['query' => ['token' => $token]]);
+    $unlock_url = parse_url($json['unlock_url']);
+    parse_str($unlock_url['query'], $unlock_url['query']);
+    $unlock_content = $this->drupalGet($unlock_url['path'], ['query' => $unlock_url['query']]);
     $this->assertSession()->statusCodeEquals(200);
     $this->assertTrue($this->installer->isAvailable());
     $this->assertStringContainsString('Operation complete, you can add a new project again.', $unlock_content);
@@ -511,15 +532,17 @@ class InstallerControllerTest extends BrowserTestBase {
   public function testCanBreakStageWithMissingProjectBrowserLock() {
     $this->doStart();
     $this->container->get(InstallState::class)->deleteAll();
-    $content = $this->drupalGet('admin/modules/project_browser/install-begin');
+    $content = $this->drupalGet('admin/modules/project_browser/install-begin', [
+      'query' => ['source' => 'project_browser_test_mock'],
+    ]);
     $this->assertSession()->statusCodeEquals(418);
     $this->assertFalse($this->installer->isAvailable());
     $this->assertFalse($this->installer->isApplying());
     $json = Json::decode($content);
     $this->assertSame('The process for adding projects is locked, but that lock has expired. Use [+ unlock link] to unlock the process and try to add the project again.', $json['message']);
-    $path = explode('?', $json['unlock_url'])[0];
-    $token = explode('=', $json['unlock_url'])[1];
-    $unlock_content = $this->drupalGet($path, ['query' => ['token' => $token]]);
+    $unlock_url = parse_url($json['unlock_url']);
+    parse_str($unlock_url['query'], $unlock_url['query']);
+    $unlock_content = $this->drupalGet($unlock_url['path'], ['query' => $unlock_url['query']]);
     $this->assertSession()->statusCodeEquals(200);
     $this->assertTrue($this->installer->isAvailable());
     $this->assertStringContainsString('Operation complete, you can add a new project again.', $unlock_content);
diff --git a/tests/src/Functional/ProjectBrowserMenuTabsTest.php b/tests/src/Functional/ProjectBrowserMenuTabsTest.php
index 76f48584143e6af9ee33acb460ee8fd9e968bac4..58336cea91ec84d54a2006f74e2e136177e3172d 100644
--- a/tests/src/Functional/ProjectBrowserMenuTabsTest.php
+++ b/tests/src/Functional/ProjectBrowserMenuTabsTest.php
@@ -41,7 +41,7 @@ class ProjectBrowserMenuTabsTest extends BrowserTestBase {
    */
   public function testBrowseMenuPosition(): void {
     $this->drupalPlaceBlock('local_tasks_block');
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     // Assert that the second tab in the nav bar is the Browse tab.
     // @todo Use elementTextEquals() once support for Drupal <10.3 is dropped.
     // @see https://www.drupal.org/project/drupal/issues/3424746
diff --git a/tests/src/FunctionalJavascript/ProjectBrowserExamplePluginTest.php b/tests/src/FunctionalJavascript/ProjectBrowserExamplePluginTest.php
index c33faf916295de86d3b038de91c40740f4e87463..8265c2911acb1f3bc0c990bd445a026870629feb 100644
--- a/tests/src/FunctionalJavascript/ProjectBrowserExamplePluginTest.php
+++ b/tests/src/FunctionalJavascript/ProjectBrowserExamplePluginTest.php
@@ -48,7 +48,7 @@ class ProjectBrowserExamplePluginTest extends WebDriverTestBase {
     $assert_session = $this->assertSession();
 
     $this->getSession()->resizeWindow(1280, 960);
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_source_example');
     $this->svelteInitHelper('css', '#project-browser .pb-project--grid');
     $this->assertEquals('Grid', $this->getElementText('#project-browser .pb-display__button[value="Grid"]'));
     $assert_session->waitForElementVisible('css', '#project-browser .pb-project');
diff --git a/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php b/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php
index cb0008fc9fffb84251cf10d14904373a046b5282..471a786d90cb69284f2b0156593f17ad8793ceda 100644
--- a/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php
+++ b/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php
@@ -71,7 +71,7 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
   public function testSingleModuleAddAndInstall(): void {
     $assert_session = $this->assertSession();
     $page = $this->getSession()->getPage();
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Cream cheese on a bagel');
     $cream_cheese_module_selector = '#project-browser .pb-layout__main ul > li:nth-child(1)';
     $download_button = $assert_session->waitForElementVisible('css', "$cream_cheese_module_selector button.project__action_button");
@@ -99,7 +99,7 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
   public function testInstallModuleAlreadyInFilesystem() {
     $assert_session = $this->assertSession();
     $page = $this->getSession()->getPage();
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Pinky and the Brain');
     $pinky_brain_selector = '#project-browser .pb-layout__main ul > li:nth-child(2)';
     $action_button = $assert_session->waitForElementVisible('css', "$pinky_brain_selector button.project__action_button");
@@ -133,7 +133,7 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
       ->set('enabled_sources', ['recipes'])
       ->save();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/recipes');
     $this->svelteInitHelper('css', '.pb-projects-list');
     $this->inputSearchField('image', TRUE);
     $assert_session->waitForElementVisible('css', ".search__search-submit")->click();
@@ -163,7 +163,7 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
     $assert_session = $this->assertSession();
     $page = $this->getSession()->getPage();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Pinky and the Brain');
 
     $cream_cheese_module_selector = '#project-browser .pb-layout__main ul > li:nth-child(1)';
@@ -176,7 +176,7 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
     $this->submitForm([], 'Save');
     $this->assertTrue($assert_session->waitForText('The configuration options have been saved.'));
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Cream cheese on a bagel');
     $action_button = $assert_session->waitForElementVisible('css', "$cream_cheese_module_selector button.project__action_button");
     $this->assertNotEmpty($action_button);
@@ -192,13 +192,12 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
     $assert_session = $this->assertSession();
     $page = $this->getSession()->getPage();
 
-    // Find a project we can install.
-    $project_id = $this->chooseProjectToInstall();
-
     // Start install begin.
-    $this->drupalGet('admin/modules/project_browser/install-begin');
+    $this->drupalGet('admin/modules/project_browser/install-begin', [
+      'query' => ['source' => 'project_browser_test_mock'],
+    ]);
     $this->installState->deleteAll();
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Cream cheese on a bagel');
     // Try beginning another install while one is in progress, but not yet in
     // the applying stage.
@@ -221,7 +220,6 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
     $installed_action = $assert_session->waitForElementVisible('css', "$cream_cheese_module_selector .project_status-indicator", 30000);
     $assert_session->waitForText('✓ Cream cheese on a bagel is Installed');
     $this->assertSame('✓ Cream cheese on a bagel is Installed', $installed_action->getText());
-
   }
 
   /**
@@ -239,8 +237,10 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
     $project_id = $this->chooseProjectToInstall(['cream_cheese']);
 
     // Start install begin.
-    $this->drupalGet('admin/modules/project_browser/install-begin');
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/project_browser/install-begin', [
+      'query' => ['source' => 'project_browser_test_mock'],
+    ]);
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Cream cheese on a bagel');
     // Try beginning another install while one is in progress, but not yet in
     // the applying stage.
@@ -272,7 +272,7 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
       ->set('project_browser_test.simulated_result_severity', SystemManager::REQUIREMENT_ERROR);
 
     $assert_session = $this->assertSession();
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $settings = $this->getDrupalSettings();
     $this->assertTrue($settings['project_browser']['package_manager']['status_checked']);
     $this->svelteInitHelper('text', 'Cream cheese on a bagel');
@@ -293,7 +293,7 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
 
     $assert_session = $this->assertSession();
     $page = $this->getSession()->getPage();
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Cream cheese on a bagel');
     $assert_session->statusMessageContains("Simulate a warning message for the project browser.", 'warning');
     $cream_cheese_module_selector = '#project-browser .pb-layout__main ul > li:nth-child(1)';
@@ -346,7 +346,7 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
   public function testMultipleModuleAddAndInstall(): void {
     $page = $this->getSession()->getPage();
     $assert_session = $this->assertSession();
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Cream cheese on a bagel');
     $this->svelteInitHelper('text', 'Kangaroo');
     $assert_session->buttonNotExists('Install selected projects');
@@ -398,7 +398,7 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
   public function testPluginSpecificQueue() {
     $assert_session = $this->assertSession();
     $this->container->get('module_installer')->install(['project_browser_devel'], TRUE);
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
 
     $assert_session->buttonNotExists('Install selected projects');
     $cream_cheese_module_selector = '#project-browser .pb-layout__main ul > li:nth-child(1)';
@@ -406,20 +406,13 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
     $select_button1->click();
     $this->assertNotEmpty($assert_session->waitForButton('Install selected projects'));
 
-    $this->pressWithWait('random_data');
+    $this->drupalGet('admin/modules/browse/random_data');
     $assert_session->buttonNotExists('Install selected projects');
     $random_data = '#project-browser .pb-layout__main ul > li:nth-child(2)';
     $select_button2 = $assert_session->waitForElementVisible('css', "$random_data button.project__action_button");
     $this->assertNotEmpty($select_button2);
     $select_button2->click();
     $this->assertNotEmpty($assert_session->waitForButton('Install selected projects'));
-
-    $this->pressWithWait('project_browser_test_mock');
-    $select_button1 = $assert_session->waitForElementVisible('css', "$cream_cheese_module_selector button.project__action_button");
-    $select_button1->click();
-    $assert_session->buttonNotExists('Install selected projects');
-    $this->pressWithWait('random_data');
-    $this->assertNotEmpty($assert_session->waitForButton('Install selected projects'));
   }
 
   /**
@@ -428,9 +421,11 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
   public function testUnlockLinkMarkup(): void {
     $assert_session = $this->assertSession();
     $page = $this->getSession()->getPage();
-    $this->drupalGet('admin/modules/project_browser/install-begin');
+    $this->drupalGet('admin/modules/project_browser/install-begin', [
+      'query' => ['source' => 'project_browser_test_mock'],
+    ]);
     $this->installState->deleteAll();
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Cream cheese on a bagel');
     $cream_cheese_module_selector = '#project-browser .pb-layout__main ul > li:nth-child(1)';
     $download_button = $assert_session->waitForElementVisible('css', "$cream_cheese_module_selector button.project__action_button");
@@ -439,10 +434,12 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
     $download_button->click();
     $this->assertNotEmpty($assert_session->waitForButton('Install selected projects'));
     $page->pressButton('Install selected projects');
-    $this->assertMatchesRegularExpression(
-      '/.*\/admin\/modules\/project_browser\/install\/unlock\?token=[\w\-]+&destination=.*\/admin\/modules\/browse$/',
-      urldecode($assert_session->waitForElementVisible('css', "#unlock-link")->getAttribute('href'))
-    );
+    $unlock_url = $assert_session->waitForElementVisible('css', "#unlock-link")->getAttribute('href');
+    $this->assertStringEndsWith('/admin/modules/project_browser/install/unlock', parse_url($unlock_url, PHP_URL_PATH));
+    $query = parse_url($unlock_url, PHP_URL_QUERY);
+    parse_str($query, $query);
+    $this->assertNotEmpty($query['token']);
+    $this->assertStringEndsWith('/admin/modules/browse/project_browser_test_mock', $query['destination']);
   }
 
   /**
@@ -450,7 +447,7 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
    */
   public function testSelectDeselectToggleInModal(): void {
     $assert_session = $this->assertSession();
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Cream cheese on a bagel');
     $assert_session->waitForButton('Helvetica')?->click();
     // Click select button in modal.
diff --git a/tests/src/FunctionalJavascript/ProjectBrowserPluginTest.php b/tests/src/FunctionalJavascript/ProjectBrowserPluginTest.php
index 43c6508da7f752ab2dba620308d7e8b9b7bbea68..013f583bf2075d048801f9591d2cfab114731a4a 100644
--- a/tests/src/FunctionalJavascript/ProjectBrowserPluginTest.php
+++ b/tests/src/FunctionalJavascript/ProjectBrowserPluginTest.php
@@ -56,7 +56,7 @@ class ProjectBrowserPluginTest extends WebDriverTestBase {
     $assert_session = $this->assertSession();
 
     $this->getSession()->resizeWindow(1280, 960);
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/random_data');
     $this->svelteInitHelper('css', '#project-browser .pb-project--grid');
     $this->assertEquals('Grid', $this->getElementText('#project-browser .pb-display__button[value="Grid"]'));
     $assert_session->waitForElementVisible('css', '#project-browser .pb-project');
@@ -70,7 +70,7 @@ class ProjectBrowserPluginTest extends WebDriverTestBase {
   public function testCategories(): void {
     $assert_session = $this->assertSession();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/random_data');
     $this->svelteInitHelper('css', '.pb-filter__checkbox');
     $assert_session->elementsCount('css', '.pb-filter__checkbox', 20);
   }
@@ -85,7 +85,7 @@ class ProjectBrowserPluginTest extends WebDriverTestBase {
     $page = $this->getSession()->getPage();
     $assert_session = $this->assertSession();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/random_data');
     // Immediately clear filters so there are enough visible to enable paging.
     $this->svelteInitHelper('test', 'Clear Filters');
     $this->svelteInitHelper('css', '.pager__item--next');
@@ -100,9 +100,7 @@ class ProjectBrowserPluginTest extends WebDriverTestBase {
    * Tests advanced filtering.
    */
   public function testAdvancedFiltering(): void {
-    $assert_session = $this->assertSession();
-
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/random_data');
     $this->svelteInitHelper('text', 'Results');
 
     $this->assertEquals('Show projects covered by a security policy', $this->getElementText(self::SECURITY_OPTION_SELECTOR . self::OPTION_CHECKED));
@@ -136,7 +134,7 @@ class ProjectBrowserPluginTest extends WebDriverTestBase {
   public function testBrokenImages(): void {
     $assert_session = $this->assertSession();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/random_data');
     $this->svelteInitHelper('css', 'img[src$="images/puzzle-piece-placeholder.svg"]');
 
     // RandomData always give an image URL. Sometimes it is a fake URL on
@@ -149,7 +147,7 @@ class ProjectBrowserPluginTest extends WebDriverTestBase {
    * Tests the not-compatible flag.
    */
   public function testNotCompatibleText(): void {
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/random_data');
     $this->svelteInitHelper('css', '.project_status-indicator');
     $this->assertEquals($this->getElementText('.project_status-indicator .visually-hidden') . ' Not compatible', $this->getElementText('.project_status-indicator'));
   }
@@ -161,7 +159,7 @@ class ProjectBrowserPluginTest extends WebDriverTestBase {
     $assert_session = $this->assertSession();
     $page = $this->getSession()->getPage();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/random_data');
     $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#project-browser .pb-project'));
     $this->assertTrue($assert_session->waitForText('Results'));
 
diff --git a/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php b/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php
index ed47bea148c27a440886b128088fbf8ebc487741..1cb47bf99673a57ac39866b25047bd5af987f026 100644
--- a/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php
+++ b/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php
@@ -68,7 +68,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     $page = $this->getSession()->getPage();
 
     $this->getSession()->resizeWindow(1250, 1000);
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('css', '.pb-project.pb-project--grid');
     $assert_session->waitForElementVisible('css', '#pb-project-browser .pb-display__button[value="Grid"]');
     $grid_text = $this->getElementText('#project-browser .pb-display__button[value="Grid"]');
@@ -97,7 +97,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
   public function testCategories(): void {
     $assert_session = $this->assertSession();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('css', '.pb-filter__checkbox');
     $assert_session->elementsCount('css', '.pb-filter__checkbox', 19);
   }
@@ -107,9 +107,8 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
    */
   public function testClickableCategory(): void {
     $assert_session = $this->assertSession();
-    $page = $this->getSession()->getPage();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Dancing Queen');
 
     // Click to open module page.
@@ -120,10 +119,9 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
    * Tests category filtering.
    */
   public function testCategoryFiltering(): void {
-    $page = $this->getSession()->getPage();
     $assert_session = $this->assertSession();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('css', '.pb-filter__multi-dropdown');
     // Initial results count on page load.
     $this->assertTrue($assert_session->waitForText('10 Results'));
@@ -194,9 +192,8 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
    * Tests the Target blank functionality.
    */
   public function testTargetBlank(): void {
-    $page = $this->getSession()->getPage();
     $assert_session = $this->assertSession();
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Helvetica');
     $assert_session->waitForButton('Helvetica')?->click();
   }
@@ -207,7 +204,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
   public function testReadonlyFields(): void {
     $page = $this->getSession()->getPage();
     $assert_session = $this->assertSession();
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Helvetica');
 
     $assert_session->waitForElementVisible('css', '.project__action_button');
@@ -235,7 +232,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     $page = $this->getSession()->getPage();
     $assert_session = $this->assertSession();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', '10 Results');
 
     $this->assertProjectsVisible([
@@ -323,7 +320,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     $page = $this->getSession()->getPage();
     $assert_session = $this->assertSession();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('css', '.pb-project.pb-project--list');
     $this->pressWithWait('Clear filters');
     $assert_session->waitForText('Modules per page');
@@ -339,9 +336,8 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
    */
   public function testAdvancedFiltering(): void {
     $page = $this->getSession()->getPage();
-    $assert_session = $this->assertSession();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Astronaut Simulator');
     $this->pressWithWait('Clear filters');
     $this->pressWithWait('Recommended filters');
@@ -450,7 +446,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
   public function testSortingCriteria(): void {
     $assert_session = $this->assertSession();
     // Clear filters.
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Clear Filters');
     $this->pressWithWait('Clear filters');
     $assert_session->elementsCount('css', '#pb-sort option', 4);
@@ -540,15 +536,11 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
    * Tests search with strings that need URI encoding.
    */
   public function testSearchForSpecialChar(): void {
-
     // Clear filters.
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', '10 Results');
     $this->pressWithWait('Clear filters', '25 Results');
 
-    // Tests for the presence of search bar placeholder text.
-    $search_field = $this->getSession()->getPage()->find('css', '#pb-text');
-
     // Fill in the search field.
     $this->inputSearchField('', TRUE);
     $this->inputSearchField('&', TRUE);
@@ -601,8 +593,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
    */
   public function testDetailPage(): void {
     $assert_session = $this->assertSession();
-    $page = $this->getSession()->getPage();
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Helvetica');
     $assert_session->waitForButton('Helvetica')?->click();
     // Check the detail modal displays.
@@ -618,7 +609,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
    */
   public function testReopenDetailModal(): void {
     $assert_session = $this->assertSession();
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Helvetica');
     $assert_session->waitForButton('Helvetica')?->click();
     // Check the detail modal displays.
@@ -641,9 +632,10 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
    * Tests that filtering, sorting, paging persists.
    */
   public function testPersistence(): void {
+    $this->markTestSkipped('Skipped because the persistence layer has been removed for now and needs to be rewritten.');
+
     $assert_session = $this->assertSession();
-    $page = $this->getSession()->getPage();
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Clear Filters');
     $this->pressWithWait('Clear filters');
 
@@ -726,7 +718,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
   public function testRecommendedFilter(): void {
     $assert_session = $this->assertSession();
     // Clear filters.
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Clear Filters');
     $this->pressWithWait('Clear filters', '25 Results');
     $this->pressWithWait('Recommended filters');
@@ -742,6 +734,8 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
    * Tests multiple source plugins at once.
    */
   public function testMultiplePlugins(): void {
+    $this->markTestSkipped('This test is skipped because it needs to be rewritten now that in-app tabbing and persistence is removed.');
+
     $page = $this->getSession()->getPage();
     $assert_session = $this->assertSession();
     // Enable module for extra source plugin.
@@ -870,7 +864,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     $this->getSession()->resizeWindow(1300, 1300);
 
     foreach ($viewSwitches as $selector) {
-      $this->drupalGet('admin/modules/browse');
+      $this->drupalGet('admin/modules/browse/project_browser_test_mock');
       $this->svelteInitHelper('css', $selector['selector']);
       $this->getSession()->getPage()->pressButton($selector['value']);
       $this->svelteInitHelper('text', 'Helvetica');
@@ -878,7 +872,6 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
       $this->svelteInitHelper('text', 'Close');
       $assert_session->waitForButton('Close')?->click();
       $this->assertSession()->elementExists('css', $selector['selector'] . '.pb-display__button--selected');
-
     }
   }
 
@@ -888,17 +881,18 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
   public function testTabledrag(): void {
     $page = $this->getSession()->getPage();
     $assert_session = $this->assertSession();
-    $this->container->get('module_installer')->install(['project_browser_devel'], TRUE);
-
-    $this->drupalGet('admin/modules/browse');
-    $this->svelteInitHelper('text', 'Random data');
-    // Count tabs.
-    $tab_count = $page->findAll('css', '.pb-tabs__link');
-    $this->assertCount(2, $tab_count);
+    $this->container->get('module_installer')->install([
+      'block',
+      'project_browser_devel',
+    ]);
+    $this->drupalPlaceBlock('local_tasks_block');
 
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
+    $local_tasks = $assert_session->elementExists('css', 'h2:contains("Primary tabs") + ul')
+      ->findAll('css', 'li a[href*="/admin/modules/browse/"]');
+    $this->assertCount(2, $local_tasks);
     // Verify that the mock plugin is first tab.
-    $first_tab = $page->find('css', '.pb-tabs__link:nth-child(1)');
-    $this->assertEquals('project_browser_test_mock', $first_tab->getValue());
+    $this->assertSame('Browse', $local_tasks[0]->getText());
 
     // Re-order plugins.
     $this->drupalGet('admin/config/development/project_browser');
@@ -909,10 +903,8 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     $this->submitForm([], 'Save');
 
     // Verify that Random data is first tab.
-    $this->drupalGet('admin/modules/browse');
-    $this->svelteInitHelper('text', 'Project Browser Mock Plugin');
-    $first_tab = $page->find('css', '.pb-tabs__link:nth-child(1)');
-    $this->assertEquals('random_data', $first_tab->getValue());
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
+    $this->assertSame('Random data', $local_tasks[0]->getText());
 
     // Disable the mock plugin.
     $this->drupalGet('admin/config/development/project_browser');
@@ -924,7 +916,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     $assert_session->pageTextContains('The configuration options have been saved.');
 
     // Verify that only Random data plugin is enabled.
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/random_data');
     $this->svelteInitHelper('css', '.pb-filter__checkbox');
     $assert_session->elementsCount('css', '.pb-filter__checkbox', 20);
 
@@ -934,7 +926,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     $this->assertTrue($assert_session->optionExists('edit-enabled-sources-random-data-status', 'disabled')->isSelected());
 
     // Verify that only the mock plugin is enabled.
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('css', '.pb-filter__checkbox');
     $assert_session->elementsCount('css', '.pb-filter__checkbox', 19);
   }
@@ -957,7 +949,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
 
     // Check visibility of categories in each view.
     foreach ($view_options as $selector) {
-      $this->drupalGet('admin/modules/browse');
+      $this->drupalGet('admin/modules/browse/project_browser_test_mock');
       $this->svelteInitHelper('css', $selector['selector']);
       $this->getSession()->getPage()->pressButton($selector['value']);
       $this->svelteInitHelper('text', 'Helvetica');
@@ -978,7 +970,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
   public function testPaginationWithFilters(): void {
     $assert_session = $this->assertSession();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->pressWithWait('Clear filters');
     $this->assertProjectsVisible([
       'Jazz',
@@ -1019,7 +1011,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     $this->config('project_browser.admin_settings')
       ->set('enabled_sources', ['drupal_core'])
       ->save(TRUE);
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/drupal_core');
     $this->svelteInitHelper('css', '.pb-project.pb-project--list');
 
     $this->inputSearchField('inline form errors', TRUE);
@@ -1059,7 +1051,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
    */
   public function testClearKeywordSearch() {
     $assert_session = $this->assertSession();
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('css', '.pb-search-results');
 
     // Get the original result count.
@@ -1086,7 +1078,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
   public function testSearchClearNoTabIndex(): void {
     $page = $this->getSession()->getPage();
     $this->assertSession();
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('css', '.pb-search-results');
 
     // Search and confirm clear button has no focus after tabbing.
@@ -1111,7 +1103,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
       ->set('enabled_sources', ['recipes'])
       ->save();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/recipes');
     $this->svelteInitHelper('css', '.pb-projects-list');
     $this->inputSearchField('image', TRUE);
     $assert_session->waitForElementVisible('css', ".search__search-submit")->click();
@@ -1136,7 +1128,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
   public function testActiveInstallVisibility(): void {
     $page = $this->getSession()->getPage();
     $assert_session = $this->assertSession();
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('css', '.pb-search-results');
 
     $assert_session->waitForElementVisible('css', '.pb-project');
@@ -1182,7 +1174,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
   public function testWrenchIcon(): void {
     $assert_session = $this->assertSession();
     $this->getSession()->resizeWindow(1460, 960);
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Helvetica');
     // This asserts that status icon is present on the cards.
     $this->assertNotNull($assert_session->waitForElementVisible('css', '.pb-project__maintenance-icon .pb-project__status-icon-btn'));
@@ -1206,7 +1198,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     $assert_session = $this->assertSession();
     $page = $this->getSession()->getPage();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
 
     // Ensure the project list is loaded.
     $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#project-browser .pb-project'));
diff --git a/tests/src/FunctionalJavascript/ProjectBrowserUiTestJsonApi.php b/tests/src/FunctionalJavascript/ProjectBrowserUiTestJsonApi.php
index 27d373604b3a58215dd9062e28fa3ab26aba7fda..5d65055a79e5dbd5ae95717ca7041d1198f5a892 100644
--- a/tests/src/FunctionalJavascript/ProjectBrowserUiTestJsonApi.php
+++ b/tests/src/FunctionalJavascript/ProjectBrowserUiTestJsonApi.php
@@ -59,7 +59,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
     $page = $this->getSession()->getPage();
 
     $this->getSession()->resizeWindow(1250, 1000);
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/drupalorg_jsonapi');
     $this->svelteInitHelper('css', '.pb-project.pb-project--grid');
     $assert_session->waitForElementVisible('css', '#project-browser .pb-display__button[value="Grid"]');
     $grid_text = $this->getElementText('#project-browser .pb-display__button[value="Grid"]');
@@ -86,7 +86,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
   public function testCategories(): void {
     $assert_session = $this->assertSession();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/drupalorg_jsonapi');
     $this->svelteInitHelper('css', '.pb-filter__multi-dropdown input[type="checkbox"]');
     $assert_session->elementsCount('css', '.pb-filter__multi-dropdown input[type="checkbox"]', 19);
   }
@@ -97,7 +97,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
   public function testClickableCategory(): void {
     $assert_session = $this->assertSession();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/drupalorg_jsonapi');
     $this->svelteInitHelper('text', 'Token');
     $assert_session->waitForButton('Token')->click();
 
@@ -109,7 +109,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
   public function testCategoryFiltering(): void {
     $assert_session = $this->assertSession();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/drupalorg_jsonapi');
     $this->svelteInitHelper('css', '.pb-filter__multi-dropdown');
     // Initial results count on page load.
     $this->assertTrue($assert_session->waitForText(' Results'));
@@ -162,7 +162,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
    */
   public function testTargetBlank(): void {
     $assert_session = $this->assertSession();
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/drupalorg_jsonapi');
     $this->svelteInitHelper('text', 'Token');
     $assert_session->waitForButton('Token')->click();
   }
@@ -174,7 +174,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
     $page = $this->getSession()->getPage();
     $assert_session = $this->assertSession();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/drupalorg_jsonapi');
     $this->svelteInitHelper('text', ' Results');
     $assert_session->pageTextNotContains(' 0 Results');
     $this->assertPagerItems(['1', '2', '3', '4', '5', '…', 'Next', 'Last']);
@@ -220,7 +220,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
   public function testAdvancedFiltering(): void {
     $assert_session = $this->assertSession();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/drupalorg_jsonapi');
     $this->svelteInitHelper('text', 'Token');
     $this->pressWithWait('Clear filters');
     $this->pressWithWait('Recommended filters');
@@ -262,7 +262,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
   public function testSortingCriteria(): void {
     $assert_session = $this->assertSession();
     // Clear filters.
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/drupalorg_jsonapi');
     $this->svelteInitHelper('text', 'Clear Filters');
     $this->pressWithWait('Clear filters');
 
@@ -285,7 +285,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
   public function testDetailPage(): void {
     $assert_session = $this->assertSession();
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/drupalorg_jsonapi');
     $this->svelteInitHelper('text', 'Token');
     $assert_session->waitForButton('Token')->click();
   }
@@ -303,7 +303,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
   public function testRecommendedFilter(): void {
     $assert_session = $this->assertSession();
     // Clear filters.
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/drupalorg_jsonapi');
     $this->svelteInitHelper('text', 'Clear Filters');
     $this->pressWithWait('Clear filters', 'Results');
     $this->pressWithWait('Recommended filters');
@@ -326,16 +326,13 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
       $this->markTestSkipped('This test requires Drupal 10.3 or later.');
     }
     $assert_session = $this->assertSession();
-    $page = $this->getSession()->getPage();
     // Enable module for extra source plugin.
     $this->container->get('module_installer')->install(['project_browser_devel']);
     $this->config('project_browser.admin_settings')
       ->set('enabled_sources', ['recipes', 'project_browser_test_mock'])
       ->save();
 
-    $this->drupalGet('admin/modules/browse');
-    $this->assertTrue($assert_session->waitForText('Recipes'));
-    $page->pressButton('Recipes');
+    $this->drupalGet('admin/modules/browse/recipes');
     // Recipes doesn't define any filters so no filters are displayed.
     $this->assertNull($assert_session->waitForElementVisible('css', '.search__form-filters-container'));
 
@@ -344,9 +341,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
     $filters_to_define = ['maintenanceStatus', 'securityCoverage'];
     \Drupal::state()->set('filters_to_define', $filters_to_define);
 
-    $this->drupalGet('admin/modules/browse');
-    $this->assertTrue($assert_session->waitForText('Project Browser Mock Plugin'));
-    $page->pressButton('Project Browser Mock Plugin');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     // Drupal.org test mock defines only two filters (actively maintained filter
     // and security coverage filter).
     $assert_session->waitForElementVisible('css', '.search__form-filters-container');
@@ -379,7 +374,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
     $this->getSession()->resizeWindow(1300, 1300);
 
     foreach ($viewSwitches as $selector) {
-      $this->drupalGet('admin/modules/browse');
+      $this->drupalGet('admin/modules/browse/drupalorg_jsonapi');
       $this->svelteInitHelper('css', $selector['selector']);
       $this->getSession()->getPage()->pressButton($selector['value']);
       $this->svelteInitHelper('text', 'Token');
@@ -396,17 +391,19 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
   public function testTabledrag(): void {
     $page = $this->getSession()->getPage();
     $assert_session = $this->assertSession();
-    $this->container->get('module_installer')->install(['project_browser_devel']);
-
-    $this->drupalGet('admin/modules/browse');
+    $this->container->get('module_installer')->install([
+      'block',
+      'project_browser_devel',
+    ]);
+    $this->drupalPlaceBlock('local_tasks_block');
+
+    $this->drupalGet('admin/modules/browse/drupalorg_jsonapi');
+    $local_tasks = $assert_session->elementExists('css', 'h2:contains("Primary tabs") + ul')
+      ->findAll('css', 'li a[href*="/admin/modules/browse/"]');
+    $this->assertCount(2, $local_tasks);
+    // Verify that the mocked source is first tab.
+    $this->assertSame('Browse', $local_tasks[0]->getText());
     $assert_session->waitForElementVisible('css', '.pb-display__button');
-    // Count tabs.
-    $tab_count = $page->findAll('css', '.pb-tabs__link');
-    $this->assertCount(2, $tab_count);
-
-    // Verify that the mock plugin is first tab.
-    $first_tab = $page->find('css', '.pb-tabs__link:nth-child(1)');
-    $this->assertEquals('drupalorg_jsonapi', $first_tab->getValue());
 
     // Re-order plugins.
     $this->drupalGet('admin/config/development/project_browser');
@@ -417,10 +414,10 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
     $this->submitForm([], 'Save');
 
     // Verify that Random data is first tab.
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/drupalorg_jsonapi');
     $assert_session->waitForElementVisible('css', '#project-browser .pb-project');
     $first_tab = $page->find('css', '.pb-tabs__link:nth-child(1)');
-    $this->assertEquals('random_data', $first_tab->getValue());
+    $this->assertSame('Random data', $local_tasks[0]->getText());
 
     // Disable the mock plugin.
     $this->drupalGet('admin/config/development/project_browser');
@@ -432,7 +429,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
     $assert_session->pageTextContains('The configuration options have been saved.');
 
     // Verify that only Random data plugin is enabled.
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/random_data');
     $this->svelteInitHelper('css', '.pb-filter__multi-dropdown input[type="checkbox"]');
     $assert_session->elementsCount('css', '.pb-filter__multi-dropdown input[type="checkbox"]', 20);
 
@@ -442,7 +439,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
     $this->assertTrue($assert_session->optionExists('edit-enabled-sources-random-data-status', 'disabled')->isSelected());
 
     // Verify that only the mock plugin is enabled.
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('css', '.pb-filter__multi-dropdown input[type="checkbox"]');
     $assert_session->elementsCount('css', '.pb-filter__multi-dropdown input[type="checkbox"]', 19);
   }
diff --git a/tests/src/FunctionalJavascript/TranslatedSvelteAppTest.php b/tests/src/FunctionalJavascript/TranslatedSvelteAppTest.php
index 3dbf7c367bc6787acc2de9e1c346152d933b9a6e..73f292b4c0bc89b603f5e904d471cf9405795311 100644
--- a/tests/src/FunctionalJavascript/TranslatedSvelteAppTest.php
+++ b/tests/src/FunctionalJavascript/TranslatedSvelteAppTest.php
@@ -70,7 +70,7 @@ class TranslatedSvelteAppTest extends WebDriverTestBase {
 
     $translate_to = 'Soorch Foor Moodools';
 
-    $this->drupalGet('admin/modules/browse');
+    $this->drupalGet('admin/modules/browse/drupalorg_jsonapi');
     $this->svelteInitHelper('text', 'Search');
     $this->assertFalse($this->assertSession()->waitForText($translate_to));
 
@@ -88,7 +88,7 @@ class TranslatedSvelteAppTest extends WebDriverTestBase {
     $this->submitForm(['string' => 'Search'], 'Filter');
     $edit = ['strings[' . $string->lid . '][translations][0]' => $translate_to];
     $this->submitForm($edit, 'Save translations');
-    $this->drupalGet("/$prefix/admin/modules/browse");
+    $this->drupalGet("/$prefix/admin/modules/browse/drupalorg_jsonapi");
     $this->svelteInitHelper('text', $translate_to);
   }
 
diff --git a/tests/src/Nightwatch/Tests/consistentPagination.js b/tests/src/Nightwatch/Tests/consistentPagination.js
index 532628b509a411797eb02ab56e5af1c9de605639..4874cac0bba05475154f1d4e87464018059f60f5 100644
--- a/tests/src/Nightwatch/Tests/consistentPagination.js
+++ b/tests/src/Nightwatch/Tests/consistentPagination.js
@@ -9,7 +9,7 @@ module.exports = {
   'Test pagination consistency across tabs': function (browser) {
     browser.drupalLoginAsAdmin(() => {
       browser
-        .drupalRelativeURL('/admin/modules/browse')
+        .drupalRelativeURL('/admin/modules/browse/project_browser_test_mock')
         .waitForElementVisible('h1', 100)
         .assert.textContains('h1', 'Browse projects')
         .assert.visible('select.pagination__num-projects')
@@ -17,7 +17,7 @@ module.exports = {
 
       browser
         .openNewWindow('tab')
-        .drupalRelativeURL('/admin/modules/browse')
+        .drupalRelativeURL('/admin/modules/browse/project_browser_test_mock')
         .waitForElementVisible('h1', 100)
         .assert.textContains('h1', 'Browse projects')
         .assert.visible('select.pagination__num-projects')
diff --git a/tests/src/Nightwatch/Tests/keyboardTest.js b/tests/src/Nightwatch/Tests/keyboardTest.js
index a6fcaeda277bfce364c7653e13ee7a7185ee8198..37795baafb446d7d38003922968af479e367aa10 100644
--- a/tests/src/Nightwatch/Tests/keyboardTest.js
+++ b/tests/src/Nightwatch/Tests/keyboardTest.js
@@ -64,7 +64,7 @@ module.exports = {
 
       // Open project browser.
       browser
-        .drupalRelativeURL('/admin/modules/browse')
+        .drupalRelativeURL('/admin/modules/browse/project_browser_test_mock')
         .waitForElementVisible('h1', delayInMilliseconds)
         .assert.textContains('h1', 'Browse projects')
         .waitForElementVisible(filterDropdownSelector);