diff --git a/automatic_updates_extensions/tests/src/Kernel/Validator/PackagesInstalledWithComposerValidatorTest.php b/automatic_updates_extensions/tests/src/Kernel/Validator/PackagesInstalledWithComposerValidatorTest.php
index ae19a6db45d4274426ff703c60fb29fd41da81cc..34598ecf423584ec187ad553d561743370bb3da5 100644
--- a/automatic_updates_extensions/tests/src/Kernel/Validator/PackagesInstalledWithComposerValidatorTest.php
+++ b/automatic_updates_extensions/tests/src/Kernel/Validator/PackagesInstalledWithComposerValidatorTest.php
@@ -36,7 +36,6 @@ class PackagesInstalledWithComposerValidatorTest extends AutomaticUpdatesExtensi
     // type validator.
     $this->disableValidators[] = 'automatic_updates_extensions.validator.packages_type';
     parent::setUp();
-    $this->createTestProject();
     $this->activeDir = $this->container->get('package_manager.path_locator')
       ->getProjectRoot();
   }
diff --git a/automatic_updates_extensions/tests/src/Kernel/Validator/UpdatePackagesTypeValidatorTest.php b/automatic_updates_extensions/tests/src/Kernel/Validator/UpdatePackagesTypeValidatorTest.php
index 95fc564bcb55e12db0bb38b446f3de7ca38ce322..1d99721d74124ad47e5c1390234479105dcfcce5 100644
--- a/automatic_updates_extensions/tests/src/Kernel/Validator/UpdatePackagesTypeValidatorTest.php
+++ b/automatic_updates_extensions/tests/src/Kernel/Validator/UpdatePackagesTypeValidatorTest.php
@@ -25,7 +25,6 @@ class UpdatePackagesTypeValidatorTest extends AutomaticUpdatesExtensionsKernelTe
     $this->disableValidators[] = 'automatic_updates_extensions.validator.target_release';
     $this->disableValidators[] = 'automatic_updates_extensions.validator.packages_installed_with_composer';
     parent::setUp();
-    $this->createTestProject();
   }
 
   /**
diff --git a/automatic_updates_extensions/tests/src/Kernel/Validator/UpdateReleaseValidatorTest.php b/automatic_updates_extensions/tests/src/Kernel/Validator/UpdateReleaseValidatorTest.php
index 35706c57eee13fe445e4d4882adf602f1c923946..4b0714b0bbe88b60d7ba6b9107b67d70048d696e 100644
--- a/automatic_updates_extensions/tests/src/Kernel/Validator/UpdateReleaseValidatorTest.php
+++ b/automatic_updates_extensions/tests/src/Kernel/Validator/UpdateReleaseValidatorTest.php
@@ -20,7 +20,6 @@ class UpdateReleaseValidatorTest extends AutomaticUpdatesExtensionsKernelTestBas
   protected function setUp(): void {
     $this->disableValidators[] = 'automatic_updates_extensions.validator.packages_installed_with_composer';
     parent::setUp();
-    $this->createTestProject();
   }
 
   /**
diff --git a/package_manager/package_manager.services.yml b/package_manager/package_manager.services.yml
index b3b7d6ef4de79630481dacea05c3248d6118160b..bef3aec5c9a3c52052c389e4b3164516e62fcff2 100644
--- a/package_manager/package_manager.services.yml
+++ b/package_manager/package_manager.services.yml
@@ -179,7 +179,6 @@ services:
     class: Drupal\package_manager\Validator\WritableFileSystemValidator
     arguments:
       - '@package_manager.path_locator'
-      - '%app.root%'
       - '@string_translation'
     tags:
       - { name: event_subscriber }
diff --git a/package_manager/src/Validator/WritableFileSystemValidator.php b/package_manager/src/Validator/WritableFileSystemValidator.php
index 24ccc241867fa46634e5ea9c089124b57907f208..4b60c0cb7b5a10de6570800dc4a1fe1e33f8c2e0 100644
--- a/package_manager/src/Validator/WritableFileSystemValidator.php
+++ b/package_manager/src/Validator/WritableFileSystemValidator.php
@@ -2,10 +2,10 @@
 
 namespace Drupal\package_manager\Validator;
 
+use Drupal\Core\StringTranslation\TranslationInterface;
 use Drupal\package_manager\Event\PreCreateEvent;
 use Drupal\package_manager\Event\PreOperationStageEvent;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Drupal\Core\StringTranslation\TranslationInterface;
 use Drupal\package_manager\PathLocator;
 
 /**
@@ -22,26 +22,16 @@ class WritableFileSystemValidator implements PreOperationStageValidatorInterface
    */
   protected $pathLocator;
 
-  /**
-   * The Drupal root.
-   *
-   * @var string
-   */
-  protected $appRoot;
-
   /**
    * Constructs a WritableFileSystemValidator object.
    *
    * @param \Drupal\package_manager\PathLocator $path_locator
    *   The path locator service.
-   * @param string $app_root
-   *   The Drupal root.
    * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
    *   The translation service.
    */
-  public function __construct(PathLocator $path_locator, string $app_root, TranslationInterface $translation) {
+  public function __construct(PathLocator $path_locator, TranslationInterface $translation) {
     $this->pathLocator = $path_locator;
-    $this->appRoot = $app_root;
     $this->setStringTranslation($translation);
   }
 
@@ -56,9 +46,14 @@ class WritableFileSystemValidator implements PreOperationStageValidatorInterface
   public function validateStagePreOperation(PreOperationStageEvent $event): void {
     $messages = [];
 
-    if (!is_writable($this->appRoot)) {
+    $drupal_root = $this->pathLocator->getProjectRoot();
+    $web_root = $this->pathLocator->getWebRoot();
+    if ($web_root) {
+      $drupal_root .= DIRECTORY_SEPARATOR . $web_root;
+    }
+    if (!is_writable($drupal_root)) {
       $messages[] = $this->t('The Drupal directory "@dir" is not writable.', [
-        '@dir' => $this->appRoot,
+        '@dir' => $drupal_root,
       ]);
     }
 
diff --git a/package_manager/tests/src/Kernel/ComposerExecutableValidatorTest.php b/package_manager/tests/src/Kernel/ComposerExecutableValidatorTest.php
index 1548f5da649316aa009ef2bea4f092d4f1a07896..a9d60ac959bb16883715b95b9ac8b725013003cf 100644
--- a/package_manager/tests/src/Kernel/ComposerExecutableValidatorTest.php
+++ b/package_manager/tests/src/Kernel/ComposerExecutableValidatorTest.php
@@ -31,9 +31,6 @@ class ComposerExecutableValidatorTest extends PackageManagerKernelTestBase {
   protected function setUp(): void {
     $this->composerRunner = $this->prophesize(ComposerRunnerInterface::class);
     parent::setUp();
-    // Use a virtual project so that the test isn't affected by symlinks or
-    // other unexpected things that might be present in the running code base.
-    $this->createTestProject();
   }
 
   /**
diff --git a/package_manager/tests/src/Kernel/ComposerSettingsValidatorTest.php b/package_manager/tests/src/Kernel/ComposerSettingsValidatorTest.php
index 071bbfc823e47eaed78a00299d8ac96629999125..3226b725832b1e15aed8ac1455430f6414ade641 100644
--- a/package_manager/tests/src/Kernel/ComposerSettingsValidatorTest.php
+++ b/package_manager/tests/src/Kernel/ComposerSettingsValidatorTest.php
@@ -59,7 +59,6 @@ class ComposerSettingsValidatorTest extends PackageManagerKernelTestBase {
    * @dataProvider providerSecureHttpValidation
    */
   public function testSecureHttpValidation(string $contents, array $expected_results): void {
-    $this->createTestProject();
     $active_dir = $this->container->get('package_manager.path_locator')
       ->getProjectRoot();
     file_put_contents("$active_dir/composer.json", $contents);
diff --git a/package_manager/tests/src/Kernel/DiskSpaceValidatorTest.php b/package_manager/tests/src/Kernel/DiskSpaceValidatorTest.php
index a972c7e8a417910106e55072b7842a82e0c06cf7..c18c5e440f100d3bff5967110994824797413add 100644
--- a/package_manager/tests/src/Kernel/DiskSpaceValidatorTest.php
+++ b/package_manager/tests/src/Kernel/DiskSpaceValidatorTest.php
@@ -20,7 +20,7 @@ class DiskSpaceValidatorTest extends PackageManagerKernelTestBase {
    *   Sets of arguments to pass to the test method.
    */
   public function providerDiskSpaceValidation(): array {
-    // These will be defined by ::createTestProject().
+    // These are defined by ::createVirtualProject().
     $root = 'vfs://root/active';
     $vendor = "$root/vendor";
 
@@ -143,8 +143,6 @@ class DiskSpaceValidatorTest extends PackageManagerKernelTestBase {
    * @dataProvider providerDiskSpaceValidation
    */
   public function testDiskSpaceValidation(bool $shared_disk, array $free_space, array $expected_results): void {
-    $this->createTestProject();
-
     /** @var \Drupal\Tests\package_manager\Kernel\TestDiskSpaceValidator $validator */
     $validator = $this->container->get('package_manager.validator.disk_space');
     $validator->sharedDisk = $shared_disk;
diff --git a/package_manager/tests/src/Kernel/LockFileValidatorTest.php b/package_manager/tests/src/Kernel/LockFileValidatorTest.php
index 0d3e5c6af6781108223ec7900eb2b84e61765747..803ca066ab8bbdfafd88b22311881b16876cf00d 100644
--- a/package_manager/tests/src/Kernel/LockFileValidatorTest.php
+++ b/package_manager/tests/src/Kernel/LockFileValidatorTest.php
@@ -28,7 +28,6 @@ class LockFileValidatorTest extends PackageManagerKernelTestBase {
    */
   protected function setUp(): void {
     parent::setUp();
-    $this->createTestProject();
     $this->activeDir = $this->container->get('package_manager.path_locator')
       ->getProjectRoot();
   }
diff --git a/package_manager/tests/src/Kernel/MultisiteValidatorTest.php b/package_manager/tests/src/Kernel/MultisiteValidatorTest.php
index b83fe43af3334b33b88be50839a6fb5efd01c740..a21f5c02295688f1426b29036e5a5f4a30cdc281 100644
--- a/package_manager/tests/src/Kernel/MultisiteValidatorTest.php
+++ b/package_manager/tests/src/Kernel/MultisiteValidatorTest.php
@@ -46,8 +46,6 @@ class MultisiteValidatorTest extends PackageManagerKernelTestBase {
    * @dataProvider providerMultisite
    */
   public function testMultisite(bool $is_multisite, array $expected_results = []): void {
-    $this->createTestProject();
-
     // If we should simulate a multisite, ensure there is a sites.php in the
     // test project.
     // @see \Drupal\package_manager\Validator\MultisiteValidator::isMultisite()
diff --git a/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php b/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php
index 617d068d1731cd811084204ab02cc002fec37f97..3d42bf13bb4832f8fb03ae04ba0b8d8f1740b8c8 100644
--- a/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php
+++ b/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php
@@ -53,15 +53,7 @@ abstract class PackageManagerKernelTestBase extends KernelTestBase {
    *
    * @var string[]
    */
-  protected $disableValidators = [
-    // Disable the filesystem permissions validator, since we cannot guarantee
-    // that the current code base will be writable in all testing situations.
-    // We test this validator functionally in Automatic Updates' build tests,
-    // since those do give us control over the filesystem permissions.
-    // @see \Drupal\Tests\automatic_updates\Build\CoreUpdateTest::assertReadOnlyFileSystemError()
-    // @see \Drupal\Tests\package_manager\Kernel\WritableFileSystemValidatorTest
-    'package_manager.validator.file_system',
-  ];
+  protected $disableValidators = [];
 
   /**
    * {@inheritdoc}
@@ -69,6 +61,8 @@ abstract class PackageManagerKernelTestBase extends KernelTestBase {
   protected function setUp(): void {
     parent::setUp();
     $this->installConfig('package_manager');
+
+    $this->createVirtualProject();
   }
 
   /**
@@ -89,7 +83,7 @@ abstract class PackageManagerKernelTestBase extends KernelTestBase {
     // is rebuilt, which destroys the mocked services and can cause unexpected
     // side effects. The 'persist' tag prevents the mocks from being destroyed
     // during a container rebuild.
-    // @see ::createTestProject()
+    // @see ::createVirtualProject()
     $persist = [
       'package_manager.path_locator',
       'package_manager.validator.disk_space',
@@ -181,7 +175,7 @@ abstract class PackageManagerKernelTestBase extends KernelTestBase {
    * and 'stage', which is the root directory used to stage changes. The path
    * locator service will also be mocked so that it points to the test project.
    */
-  protected function createTestProject(): void {
+  protected function createVirtualProject(): void {
     // Create the active directory and copy its contents from a fixture.
     $active_dir = vfsStream::newDirectory('active');
     $this->vfsRoot->addChild($active_dir);
diff --git a/package_manager/tests/src/Kernel/PathExcluder/GitExcluderTest.php b/package_manager/tests/src/Kernel/PathExcluder/GitExcluderTest.php
index a1817cc0d043c772854c42d85c7a37ac3b8e4956..24ca0cb409f4e6505f9fa7f04b1537eacee8ac10 100644
--- a/package_manager/tests/src/Kernel/PathExcluder/GitExcluderTest.php
+++ b/package_manager/tests/src/Kernel/PathExcluder/GitExcluderTest.php
@@ -26,7 +26,6 @@ class GitExcluderTest extends PackageManagerKernelTestBase {
    * Tests that unreadable directories are ignored by the event subscriber.
    */
   public function testUnreadableDirectoriesAreIgnored(): void {
-    $this->createTestProject();
     $active_dir = $this->container->get('package_manager.path_locator')
       ->getProjectRoot();
 
@@ -51,7 +50,6 @@ class GitExcluderTest extends PackageManagerKernelTestBase {
     // Ensure we have an up-to-date container.
     $this->container = $this->container->get('kernel')->getContainer();
 
-    $this->createTestProject();
     $active_dir = $this->container->get('package_manager.path_locator')
       ->getProjectRoot();
 
diff --git a/package_manager/tests/src/Kernel/PathExcluder/SiteConfigurationExcluderTest.php b/package_manager/tests/src/Kernel/PathExcluder/SiteConfigurationExcluderTest.php
index 95c4352e4c2667b2c4dd51bf541f38a32e8c1354..75f0b0cd3fc90bc278b23ff82e071acf4f7fc8cd 100644
--- a/package_manager/tests/src/Kernel/PathExcluder/SiteConfigurationExcluderTest.php
+++ b/package_manager/tests/src/Kernel/PathExcluder/SiteConfigurationExcluderTest.php
@@ -44,7 +44,6 @@ class SiteConfigurationExcluderTest extends PackageManagerKernelTestBase {
     // Ensure we have an up-to-date container.
     $this->container = $this->container->get('kernel')->getContainer();
 
-    $this->createTestProject();
     $active_dir = $this->container->get('package_manager.path_locator')
       ->getProjectRoot();
 
diff --git a/package_manager/tests/src/Kernel/PathExcluder/SiteFilesExcluderTest.php b/package_manager/tests/src/Kernel/PathExcluder/SiteFilesExcluderTest.php
index 524139764200d2bda5a835c5c64604261d570f36..3dd86484ae941af0d11d24f59a9f337827abab61 100644
--- a/package_manager/tests/src/Kernel/PathExcluder/SiteFilesExcluderTest.php
+++ b/package_manager/tests/src/Kernel/PathExcluder/SiteFilesExcluderTest.php
@@ -36,7 +36,6 @@ class SiteFilesExcluderTest extends PackageManagerKernelTestBase {
     // Ensure we have an up-to-date container.
     $this->container = $this->container->get('kernel')->getContainer();
 
-    $this->createTestProject();
     $active_dir = $this->container->get('package_manager.path_locator')
       ->getProjectRoot();
 
diff --git a/package_manager/tests/src/Kernel/PathExcluder/SqliteDatabaseExcluderTest.php b/package_manager/tests/src/Kernel/PathExcluder/SqliteDatabaseExcluderTest.php
index 96711346073a305b480929752c1184e70f2fcbff..dc8350b56a503401f6e82416de0f40f00241d5d3 100644
--- a/package_manager/tests/src/Kernel/PathExcluder/SqliteDatabaseExcluderTest.php
+++ b/package_manager/tests/src/Kernel/PathExcluder/SqliteDatabaseExcluderTest.php
@@ -61,7 +61,6 @@ class SqliteDatabaseExcluderTest extends PackageManagerKernelTestBase {
     // Ensure we have an up-to-date container.
     $this->container = $this->container->get('kernel')->getContainer();
 
-    $this->createTestProject();
     $active_dir = $this->container->get('package_manager.path_locator')
       ->getProjectRoot();
 
@@ -99,8 +98,6 @@ class SqliteDatabaseExcluderTest extends PackageManagerKernelTestBase {
    *   Sets of arguments to pass to the test method.
    */
   public function providerPathProcessing(): array {
-    $drupal_root = $this->getDrupalRoot();
-
     return [
       'relative path, in site directory' => [
         'sites/example.com/db.sqlite',
@@ -119,7 +116,7 @@ class SqliteDatabaseExcluderTest extends PackageManagerKernelTestBase {
         ],
       ],
       'absolute path, in site directory' => [
-        $drupal_root . '/sites/example.com/db.sqlite',
+        '/sites/example.com/db.sqlite',
         [
           'sites/example.com/db.sqlite',
           'sites/example.com/db.sqlite-shm',
@@ -127,7 +124,7 @@ class SqliteDatabaseExcluderTest extends PackageManagerKernelTestBase {
         ],
       ],
       'absolute path, at root' => [
-        $drupal_root . '/db.sqlite',
+        '/db.sqlite',
         [
           'db.sqlite',
           'db.sqlite-shm',
@@ -146,13 +143,19 @@ class SqliteDatabaseExcluderTest extends PackageManagerKernelTestBase {
    *
    * @param string $database_path
    *   The path of the SQLite database, as set in the database connection
-   *   options.
+   *   options. If it begins with a slash, it will be prefixed with the path of
+   *   the active directory.
    * @param string[] $expected_exclusions
    *   The database paths which should be flagged for exclusion.
    *
    * @dataProvider providerPathProcessing
    */
   public function testPathProcessing(string $database_path, array $expected_exclusions): void {
+    // If the database path should be treated as absolute, prefix it with the
+    // path of the active directory.
+    if (str_starts_with($database_path, '/')) {
+      $database_path = $this->container->get('package_manager.path_locator')->getProjectRoot() . $database_path;
+    }
     $this->mockDatabase([
       'database' => $database_path,
     ]);
diff --git a/package_manager/tests/src/Kernel/PathExcluder/TestSiteExcluderTest.php b/package_manager/tests/src/Kernel/PathExcluder/TestSiteExcluderTest.php
index 7b19c31732d35461ec7b651c2ea91ea7f6be70c3..43a7ad9f063c1b4d1023fca026180e54d81ceaec 100644
--- a/package_manager/tests/src/Kernel/PathExcluder/TestSiteExcluderTest.php
+++ b/package_manager/tests/src/Kernel/PathExcluder/TestSiteExcluderTest.php
@@ -32,7 +32,6 @@ class TestSiteExcluderTest extends PackageManagerKernelTestBase {
     // Ensure we have an up-to-date container.
     $this->container = $this->container->get('kernel')->getContainer();
 
-    $this->createTestProject();
     $active_dir = $this->container->get('package_manager.path_locator')
       ->getProjectRoot();
 
diff --git a/package_manager/tests/src/Kernel/PathExcluder/VendorHardeningExcluderTest.php b/package_manager/tests/src/Kernel/PathExcluder/VendorHardeningExcluderTest.php
index 8d74f74daa37cfdf208f313e32fd48fd4dd56bbe..0e28251ea7c02366e10ecf0f71c763a5d31135ed 100644
--- a/package_manager/tests/src/Kernel/PathExcluder/VendorHardeningExcluderTest.php
+++ b/package_manager/tests/src/Kernel/PathExcluder/VendorHardeningExcluderTest.php
@@ -32,7 +32,6 @@ class VendorHardeningExcluderTest extends PackageManagerKernelTestBase {
     // Ensure we have an up-to-date container.
     $this->container = $this->container->get('kernel')->getContainer();
 
-    $this->createTestProject();
     $active_dir = $this->container->get('package_manager.path_locator')
       ->getProjectRoot();
 
diff --git a/package_manager/tests/src/Kernel/PendingUpdatesValidatorTest.php b/package_manager/tests/src/Kernel/PendingUpdatesValidatorTest.php
index 80a3640bb23f60581869b5a2dfc07b9d540ccd50..35605045ea2b3b4b50816f875400338e6ec8c85d 100644
--- a/package_manager/tests/src/Kernel/PendingUpdatesValidatorTest.php
+++ b/package_manager/tests/src/Kernel/PendingUpdatesValidatorTest.php
@@ -17,16 +17,6 @@ class PendingUpdatesValidatorTest extends PackageManagerKernelTestBase {
    */
   protected static $modules = ['system'];
 
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-    // Use a virtual project so that the test isn't affected by symlinks or
-    // other unexpected things that might be present in the running code base.
-    $this->createTestProject();
-  }
-
   /**
    * Tests that no error is raised if there are no pending updates.
    */
diff --git a/package_manager/tests/src/Kernel/SettingsValidatorTest.php b/package_manager/tests/src/Kernel/SettingsValidatorTest.php
index 5a1633a88408228bec6af1f7f2808368bfb6bb3e..f319740eaf3f2ae3a39679841b645e8fdffca7b9 100644
--- a/package_manager/tests/src/Kernel/SettingsValidatorTest.php
+++ b/package_manager/tests/src/Kernel/SettingsValidatorTest.php
@@ -38,10 +38,6 @@ class SettingsValidatorTest extends PackageManagerKernelTestBase {
    * @dataProvider providerSettingsValidation
    */
   public function testSettingsValidation(bool $setting, array $expected_results): void {
-    // Use a virtual project so that the test isn't affected by symlinks or
-    // other unexpected things that might be present in the running code base.
-    $this->createTestProject();
-
     $this->setSetting('update_fetch_with_http_fallback', $setting);
 
     try {
diff --git a/package_manager/tests/src/Kernel/StageEventsTest.php b/package_manager/tests/src/Kernel/StageEventsTest.php
index 877dfa71ff133ec010f57f45764685b057f902ed..4ea2817c1f18ab553666ccf44a2bbc101d7ee90f 100644
--- a/package_manager/tests/src/Kernel/StageEventsTest.php
+++ b/package_manager/tests/src/Kernel/StageEventsTest.php
@@ -44,9 +44,6 @@ class StageEventsTest extends PackageManagerKernelTestBase implements EventSubsc
    */
   protected function setUp(): void {
     parent::setUp();
-    // Use a virtual project so that the test isn't affected by symlinks or
-    // other unexpected things that might be present in the running code base.
-    $this->createTestProject();
     $this->stage = $this->createStage();
   }
 
diff --git a/package_manager/tests/src/Kernel/StageOwnershipTest.php b/package_manager/tests/src/Kernel/StageOwnershipTest.php
index bcfe025be7bec82c2fb885133469ef56b8e92b38..21011a7578fe58bef8e4d54ee931dab2701f8d6a 100644
--- a/package_manager/tests/src/Kernel/StageOwnershipTest.php
+++ b/package_manager/tests/src/Kernel/StageOwnershipTest.php
@@ -39,9 +39,6 @@ class StageOwnershipTest extends PackageManagerKernelTestBase {
     $this->installSchema('user', ['users_data']);
     $this->installEntitySchema('user');
     $this->registerPostUpdateFunctions();
-    // Use a virtual project so that the test isn't affected by symlinks or
-    // other unexpected things that might be present in the running code base.
-    $this->createTestProject();
   }
 
   /**
@@ -242,7 +239,6 @@ class StageOwnershipTest extends PackageManagerKernelTestBase {
     ]);
     // Ensure we have an up-to-date container.
     $this->container = $this->container->get('kernel')->getContainer();
-    $this->createTestProject();
 
     $logger_channel = $this->container->get('logger.channel.file');
     $arguments = [
diff --git a/package_manager/tests/src/Kernel/StageTest.php b/package_manager/tests/src/Kernel/StageTest.php
index addfa40f70ae2bfbfe42315b3faba266e0b0cfa7..76c5d8a86c9314962968c9cf29355cc1fc9f9f11 100644
--- a/package_manager/tests/src/Kernel/StageTest.php
+++ b/package_manager/tests/src/Kernel/StageTest.php
@@ -67,6 +67,10 @@ class StageTest extends PackageManagerKernelTestBase {
     $site_id = $this->config('system.site')->get('uuid');
     $this->assertNotEmpty($site_id);
 
+    // Even though we're using a virtual project, we want to test what happens
+    // when we aren't.
+    static::$testStagingRoot = NULL;
+
     $stage = $this->createStage();
     $id = $stage->create();
     // If the file_temp_path setting is empty, the stage directory should be
diff --git a/package_manager/tests/src/Kernel/SymlinkValidatorTest.php b/package_manager/tests/src/Kernel/SymlinkValidatorTest.php
index ea93febb6e92f471a0db35f76093fd0835921355..5e462dd444207ee917327bd0e45dd8bb96305109 100644
--- a/package_manager/tests/src/Kernel/SymlinkValidatorTest.php
+++ b/package_manager/tests/src/Kernel/SymlinkValidatorTest.php
@@ -14,14 +14,6 @@ use Drupal\package_manager\Validator\SymlinkValidator;
  */
 class SymlinkValidatorTest extends PackageManagerKernelTestBase {
 
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-    $this->createTestProject();
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -66,7 +58,7 @@ class SymlinkValidatorTest extends PackageManagerKernelTestBase {
     $stage->create();
     // Simulate updating a package. This will copy the active directory into
     // the (virtual) staging area.
-    // @see ::createTestProject()
+    // @see ::createVirtualProject()
     // @see \Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager::copyFilesFromFixture()
     $stage->require(['composer/semver:^3']);
 
@@ -99,7 +91,7 @@ class SymlinkValidatorTest extends PackageManagerKernelTestBase {
     $stage->create();
     // Simulate updating a package. This will copy the active directory into
     // the (virtual) staging area.
-    // @see ::createTestProject()
+    // @see ::createVirtualProject()
     // @see \Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager::copyFilesFromFixture()
     $stage->require(['composer/semver:^3']);
 
diff --git a/package_manager/tests/src/Kernel/WritableFileSystemValidatorTest.php b/package_manager/tests/src/Kernel/WritableFileSystemValidatorTest.php
index addf0a2172e285992f392f837fad895d18db7da2..411c27b4c55cc79b456b551c05ecca82a9bdf5f8 100644
--- a/package_manager/tests/src/Kernel/WritableFileSystemValidatorTest.php
+++ b/package_manager/tests/src/Kernel/WritableFileSystemValidatorTest.php
@@ -3,9 +3,7 @@
 namespace Drupal\Tests\package_manager\Kernel;
 
 use Drupal\package_manager\Event\PreCreateEvent;
-use Drupal\package_manager\Validator\WritableFileSystemValidator;
 use Drupal\package_manager\ValidationResult;
-use Drupal\Core\DependencyInjection\ContainerBuilder;
 
 /**
  * Unit tests the file system permissions validator.
@@ -21,26 +19,6 @@ use Drupal\Core\DependencyInjection\ContainerBuilder;
  */
 class WritableFileSystemValidatorTest extends PackageManagerKernelTestBase {
 
-  /**
-   * {@inheritdoc}
-   */
-  protected $disableValidators = [
-    // The parent class disables the validator we're testing, so prevent that
-    // here with an empty array.
-  ];
-
-  /**
-   * {@inheritdoc}
-   */
-  public function register(ContainerBuilder $container) {
-    parent::register($container);
-
-    // Replace the file system permissions validator with our test-only
-    // implementation.
-    $container->getDefinition('package_manager.validator.file_system')
-      ->setClass(TestWritableFileSystemValidator::class);
-  }
-
   /**
    * Data provider for ::testWritable().
    *
@@ -48,7 +26,7 @@ class WritableFileSystemValidatorTest extends PackageManagerKernelTestBase {
    *   Sets of arguments to pass to the test method.
    */
   public function providerWritable(): array {
-    // The root and vendor paths are defined by ::createTestProject().
+    // The root and vendor paths are defined by ::createVirtualProject().
     $root_error = 'The Drupal directory "vfs://root/active" is not writable.';
     $vendor_error = 'The vendor directory "vfs://root/active/vendor" is not writable.';
     $summary = t('The file system is not writable.');
@@ -98,30 +76,14 @@ class WritableFileSystemValidatorTest extends PackageManagerKernelTestBase {
    * @dataProvider providerWritable
    */
   public function testWritable(int $root_permissions, int $vendor_permissions, array $expected_results): void {
-    $this->createTestProject();
-    // For reasons unclear, the built-in chmod() function doesn't seem to work
-    // when changing vendor permissions, so just call vfsStream's API directly.
-    $active_dir = $this->vfsRoot->getChild('active');
-    $active_dir->chmod($root_permissions);
-    $active_dir->getChild('vendor')->chmod($vendor_permissions);
+    $path_locator = $this->container->get('package_manager.path_locator');
 
-    /** @var \Drupal\Tests\package_manager\Kernel\TestWritableFileSystemValidator $validator */
-    $validator = $this->container->get('package_manager.validator.file_system');
-    $validator->appRoot = $active_dir->url();
+    // We need to set the vendor directory's permissions first because, in the
+    // virtual project, it's located inside the project root.
+    $this->assertTrue(chmod($path_locator->getVendorDirectory(), $vendor_permissions));
+    $this->assertTrue(chmod($path_locator->getProjectRoot(), $root_permissions));
 
     $this->assertResults($expected_results, PreCreateEvent::class);
   }
 
 }
-
-/**
- * A test version of the file system permissions validator.
- */
-class TestWritableFileSystemValidator extends WritableFileSystemValidator {
-
-  /**
-   * {@inheritdoc}
-   */
-  public $appRoot;
-
-}
diff --git a/tests/src/Kernel/CronUpdaterTest.php b/tests/src/Kernel/CronUpdaterTest.php
index f07e16086fc01e52a65c62b79511e0964972ae37..115f024f87bde49478fa0d39e17d9c1ca6cd2892 100644
--- a/tests/src/Kernel/CronUpdaterTest.php
+++ b/tests/src/Kernel/CronUpdaterTest.php
@@ -357,10 +357,6 @@ class CronUpdaterTest extends AutomaticUpdatesKernelTestBase {
    * Tests that user 1 is emailed when an unattended update succeeds.
    */
   public function testEmailOnSuccess(): void {
-    // Use a virtual project so that the test is unaffected by symlinks or other
-    // artifacts that might be in the running code base.
-    $this->createTestProject();
-
     $this->config('update.settings')
       ->set('notification.emails', [
         'emissary@deep.space',
diff --git a/tests/src/Kernel/ReadinessValidation/CronFrequencyValidatorTest.php b/tests/src/Kernel/ReadinessValidation/CronFrequencyValidatorTest.php
index 75fb7ee57f0b7e5d3fff01000f15c66d0c3dd36c..601da39fa48050cecc58fff25337e909c1604bca 100644
--- a/tests/src/Kernel/ReadinessValidation/CronFrequencyValidatorTest.php
+++ b/tests/src/Kernel/ReadinessValidation/CronFrequencyValidatorTest.php
@@ -26,10 +26,6 @@ class CronFrequencyValidatorTest extends AutomaticUpdatesKernelTestBase {
    */
   protected function setUp(): void {
     parent::setUp();
-    // Run with a virtual project so that the test isn't affected by any
-    // symbolic links or other artifacts that might be in the running code
-    // base.
-    $this->createTestProject();
     // In this test, we do not want to do an update. We're just testing that
     // cron is configured to run frequently enough to do automatic updates. So,
     // pretend we're already on the latest secure version of core.
diff --git a/tests/src/Kernel/ReadinessValidation/ReadinessValidationManagerTest.php b/tests/src/Kernel/ReadinessValidation/ReadinessValidationManagerTest.php
index d9fb9faf80f4a2d4c189bdb8a837a78477def8e2..7766e911d7a6603084e195913f056387673ca8f6 100644
--- a/tests/src/Kernel/ReadinessValidation/ReadinessValidationManagerTest.php
+++ b/tests/src/Kernel/ReadinessValidation/ReadinessValidationManagerTest.php
@@ -34,9 +34,6 @@ class ReadinessValidationManagerTest extends AutomaticUpdatesKernelTestBase {
     $this->installEntitySchema('user');
     $this->installSchema('user', ['users_data']);
     $this->createTestValidationResults();
-    // Use a virtual project so that the test isn't affected by symlinks or
-    // other unexpected things that might be present in the running code base.
-    $this->createTestProject();
   }
 
   /**
diff --git a/tests/src/Kernel/ReadinessValidation/ScaffoldFilePermissionsValidatorTest.php b/tests/src/Kernel/ReadinessValidation/ScaffoldFilePermissionsValidatorTest.php
index d77ce05b3a96c0a51bec502d3f1cb0db2580586f..8f4e0530c8b6f01e61c3a5be00c480e5430e32bf 100644
--- a/tests/src/Kernel/ReadinessValidation/ScaffoldFilePermissionsValidatorTest.php
+++ b/tests/src/Kernel/ReadinessValidation/ScaffoldFilePermissionsValidatorTest.php
@@ -31,7 +31,6 @@ class ScaffoldFilePermissionsValidatorTest extends AutomaticUpdatesKernelTestBas
   protected function setUp(): void {
     parent::setUp();
 
-    $this->createTestProject();
     $this->activeDir = $this->container->get('package_manager.path_locator')
       ->getProjectRoot();
   }
@@ -299,7 +298,7 @@ class ScaffoldFilePermissionsValidatorTest extends AutomaticUpdatesKernelTestBas
 
     // Simulate updating Drupal core. This will copy the active directory into
     // the (virtual) staging area.
-    // @see ::createTestProject()
+    // @see ::createVirtualProject()
     // @see \Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager::copyFilesFromFixture()
     $updater = $this->container->get('automatic_updates.updater');
     $updater->begin(['drupal' => '9.8.1']);
diff --git a/tests/src/Kernel/ReadinessValidation/StagedDatabaseUpdateValidatorTest.php b/tests/src/Kernel/ReadinessValidation/StagedDatabaseUpdateValidatorTest.php
index 086575a13c46b318558d124520f9002905bc8589..e06b324f3fb2c1ac50ba20ce427bf1e828085059 100644
--- a/tests/src/Kernel/ReadinessValidation/StagedDatabaseUpdateValidatorTest.php
+++ b/tests/src/Kernel/ReadinessValidation/StagedDatabaseUpdateValidatorTest.php
@@ -38,7 +38,6 @@ class StagedDatabaseUpdateValidatorTest extends AutomaticUpdatesKernelTestBase {
    */
   protected function setUp(): void {
     parent::setUp();
-    $this->createTestProject();
 
     $this->logger = new TestLogger();
     $this->container->get('logger.factory')
@@ -49,8 +48,8 @@ class StagedDatabaseUpdateValidatorTest extends AutomaticUpdatesKernelTestBase {
   /**
    * {@inheritdoc}
    */
-  protected function createTestProject(): void {
-    parent::createTestProject();
+  protected function createVirtualProject(): void {
+    parent::createVirtualProject();
 
     $drupal_root = $this->getDrupalRoot();
     $virtual_active_dir = $this->container->get('package_manager.path_locator')
diff --git a/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php b/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php
index 7268428389adc3d098fe0856c4327c43617269d6..924772eb17bdd6488da38ac99a8e0a224e34ea4f 100644
--- a/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php
+++ b/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php
@@ -32,7 +32,6 @@ class StagedProjectsValidatorTest extends AutomaticUpdatesKernelTestBase {
   protected function setUp(): void {
     parent::setUp();
 
-    $this->createTestProject();
     $this->activeDir = $this->container->get('package_manager.path_locator')
       ->getProjectRoot();
   }
diff --git a/tests/src/Kernel/ReadinessValidation/VersionPolicyValidatorTest.php b/tests/src/Kernel/ReadinessValidation/VersionPolicyValidatorTest.php
index 4363bfb47593bbbc21728a273dfe68398d865fd5..599f04e8af51a29ef6ee1fb918e8758e8b8c32c8 100644
--- a/tests/src/Kernel/ReadinessValidation/VersionPolicyValidatorTest.php
+++ b/tests/src/Kernel/ReadinessValidation/VersionPolicyValidatorTest.php
@@ -21,16 +21,6 @@ class VersionPolicyValidatorTest extends AutomaticUpdatesKernelTestBase {
    */
   protected static $modules = ['automatic_updates'];
 
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-    // Use a virtual project so that the test isn't affected by symlinks or
-    // other unexpected things that might be present in the running code base.
-    $this->createTestProject();
-  }
-
   /**
    * Data provider for ::testReadinessCheck().
    *
@@ -413,7 +403,6 @@ class VersionPolicyValidatorTest extends AutomaticUpdatesKernelTestBase {
    *   known.
    */
   private function assertTargetVersionNotDiscoverable(\Closure $listener): void {
-    $this->createTestProject();
     $this->container->get('event_dispatcher')
       ->addListener(PreCreateEvent::class, $listener, PHP_INT_MAX);