diff --git a/automatic_updates_extensions/tests/src/Kernel/AutomaticUpdatesExtensionsKernelTestBase.php b/automatic_updates_extensions/tests/src/Kernel/AutomaticUpdatesExtensionsKernelTestBase.php
index 7608924002b6c9a21dc1537ab0c64cde5c6688ab..fcc0b6d08f177d6b35ce6cb7328eb36082221219 100644
--- a/automatic_updates_extensions/tests/src/Kernel/AutomaticUpdatesExtensionsKernelTestBase.php
+++ b/automatic_updates_extensions/tests/src/Kernel/AutomaticUpdatesExtensionsKernelTestBase.php
@@ -9,9 +9,9 @@ use Drupal\automatic_updates_extensions\ExtensionUpdater;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\package_manager\UnusedConfigFactory;
 use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase;
-use Drupal\Tests\package_manager\Kernel\TestPathFactory;
 use Drupal\Tests\package_manager\Kernel\TestStageTrait;
 use Drupal\Tests\package_manager\Kernel\TestStageValidationException;
+use PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactory;
 
 /**
  * Base class for kernel tests of the Automatic Updates Extensions module.
@@ -42,14 +42,14 @@ abstract class AutomaticUpdatesExtensionsKernelTestBase extends AutomaticUpdates
   }
 
   /**
-   * Create Virtual Project.
+   * Create Test Project.
    *
    * @param string|null $source_dir
    *   Source directory.
    */
-  protected function createVirtualProject(?string $source_dir = NULL): void {
+  protected function createTestProject(?string $source_dir = NULL): void {
     $source_dir = $source_dir ?? __DIR__ . '/../../fixtures/fake-site';
-    parent::createVirtualProject($source_dir);
+    parent::createTestProject($source_dir);
   }
 
   /**
@@ -122,7 +122,7 @@ abstract class AutomaticUpdatesExtensionsKernelTestBase extends AutomaticUpdates
       $this->container->get('event_dispatcher'),
       $this->container->get('tempstore.shared'),
       $this->container->get('datetime.time'),
-      new TestPathFactory(),
+      new PathFactory(),
       $this->container->get('package_manager.failure_marker')
     );
   }
diff --git a/package_manager/src/ComposerUtility.php b/package_manager/src/ComposerUtility.php
index 8bc91b02ae0c7cff3d9f644bdc03129d52272f18..7fa924c39e57cb983c463db07600f8951348e0e0 100644
--- a/package_manager/src/ComposerUtility.php
+++ b/package_manager/src/ComposerUtility.php
@@ -90,15 +90,7 @@ class ComposerUtility {
    */
   public static function createForDirectory(string $dir): self {
     $io = new NullIO();
-
-    // Pre-load the contents of composer.json so that Factory::createComposer()
-    // won't try to call realpath(), which will fail if composer.json is in a
-    // virtual file system.
     $configuration = $dir . DIRECTORY_SEPARATOR . 'composer.json';
-    if (file_exists($configuration)) {
-      $configuration = file_get_contents($configuration);
-      $configuration = json_decode($configuration, TRUE, 512, JSON_THROW_ON_ERROR);
-    }
 
     // The Composer factory requires that either the HOME or COMPOSER_HOME
     // environment variables be set, so momentarily set the COMPOSER_HOME
diff --git a/package_manager/src/PathExcluder/PathExclusionsTrait.php b/package_manager/src/PathExcluder/PathExclusionsTrait.php
index bd9fb8928bb5446fa364f625342b343e737add5d..001b6cb978b5a24a8a3949c8e6ecff24d3b31401 100644
--- a/package_manager/src/PathExcluder/PathExclusionsTrait.php
+++ b/package_manager/src/PathExcluder/PathExclusionsTrait.php
@@ -65,7 +65,7 @@ trait PathExclusionsTrait {
     $project_root = $this->pathLocator->getProjectRoot();
 
     foreach ($paths as $path) {
-      if (str_starts_with($path, '/') || str_starts_with($path, 'vfs:')) {
+      if (str_starts_with($path, '/')) {
         if (!str_starts_with($path, $project_root)) {
           throw new \LogicException("$path is not inside the project root: $project_root.");
         }
diff --git a/package_manager/src/PathExcluder/SiteFilesExcluder.php b/package_manager/src/PathExcluder/SiteFilesExcluder.php
index 05d2d1be8889d354edb92ed5576d4083e2ea058f..41be60c32d684f2a151994958fe3ae409ba0561c 100644
--- a/package_manager/src/PathExcluder/SiteFilesExcluder.php
+++ b/package_manager/src/PathExcluder/SiteFilesExcluder.php
@@ -79,7 +79,7 @@ final class SiteFilesExcluder implements EventSubscriberInterface {
         $path = $wrapper->getDirectoryPath();
 
         if ($this->fileSystem->isAbsolutePath($path)) {
-          $this->excludeInProjectRoot($event, [$path]);
+          $this->excludeInProjectRoot($event, [realpath($path)]);
         }
         else {
           $this->excludeInWebRoot($event, [$path]);
diff --git a/package_manager/src/Validator/ComposerPatchesValidator.php b/package_manager/src/Validator/ComposerPatchesValidator.php
index 65ca7c4c8d08799b7d6757544fdc4af85d5bfd2b..7588095ffdbd9e03aa85fa4c200e511c50d2aea3 100644
--- a/package_manager/src/Validator/ComposerPatchesValidator.php
+++ b/package_manager/src/Validator/ComposerPatchesValidator.php
@@ -32,9 +32,7 @@ class ComposerPatchesValidator implements EventSubscriberInterface {
       if (empty($extra['composer-exit-on-patch-failure'])) {
         $event->addError([
           $this->t('The <code>cweagans/composer-patches</code> plugin is installed, but the <code>composer-exit-on-patch-failure</code> key is not set to <code>true</code> in the <code>extra</code> section of @file.', [
-            // If composer.json is in a virtual file system, Composer will not
-            // be able to resolve a real path for it.
-            '@file' => $composer->getConfig()->getConfigSource()->getName() ?: 'composer.json',
+            '@file' => $composer->getConfig()->getConfigSource()->getName(),
           ]),
         ]);
       }
diff --git a/package_manager/tests/modules/package_manager_bypass/src/PathLocator.php b/package_manager/tests/modules/package_manager_bypass/src/PathLocator.php
index cdcb32eca1a7895f0e0b3e3cde56572d2e43a7f1..4bbfee5b57de40b07a915d0c61d91b63b387f433 100644
--- a/package_manager/tests/modules/package_manager_bypass/src/PathLocator.php
+++ b/package_manager/tests/modules/package_manager_bypass/src/PathLocator.php
@@ -40,10 +40,7 @@ class PathLocator extends BasePathLocator {
     $project_root = $this->state->get(static::class . ' root');
     if ($project_root === NULL) {
       $project_root = $this->getVendorDirectory() . DIRECTORY_SEPARATOR . '..';
-      // @see https://github.com/bovigo/vfsStream/issues/207
-      $project_root = !str_starts_with($project_root, 'vfs://')
-        ? realpath($project_root)
-        : $project_root;
+      $project_root = realpath($project_root);
     }
     return $project_root;
   }
@@ -85,15 +82,14 @@ class PathLocator extends BasePathLocator {
    */
   public function setPaths(?string $project_root, ?string $vendor_dir, ?string $web_root, ?string $staging_root): void {
     foreach ([$project_root, $staging_root] as $path) {
-      // @todo Remove the VFS check in https://www.drupal.org/i/3328234.
-      if (!empty($path) && !str_starts_with($path, 'vfs://') && !str_starts_with($path, 'sites/') && !Path::isAbsolute($path)) {
+      if (!empty($path) && !Path::isAbsolute($path)) {
         throw new \InvalidArgumentException('project_root and staging_root need to be absolute paths.');
       }
     }
-    $this->state->set(static::class . ' root', is_null($project_root) ? NULL : Path::canonicalize($project_root));
-    $this->state->set(static::class . ' vendor', is_null($vendor_dir) ? NULL : Path::canonicalize($vendor_dir));
+    $this->state->set(static::class . ' root', is_null($project_root) ? NULL : realpath($project_root));
+    $this->state->set(static::class . ' vendor', is_null($vendor_dir) ? NULL : realpath($vendor_dir));
     $this->state->set(static::class . ' web', is_null($web_root) ? NULL : Path::canonicalize($web_root));
-    $this->state->set(static::class . ' stage', is_null($staging_root) ? NULL : Path::canonicalize($staging_root));
+    $this->state->set(static::class . ' stage', is_null($staging_root) ? NULL : realpath($staging_root));
   }
 
 }
diff --git a/package_manager/tests/src/Functional/FailureMarkerRequirementTest.php b/package_manager/tests/src/Functional/FailureMarkerRequirementTest.php
index 16b1ad29a7a0657cce26669f77245f93adf9c7b8..27062ecbd1cf6b92109c6b29dfbcb3722768fb1d 100644
--- a/package_manager/tests/src/Functional/FailureMarkerRequirementTest.php
+++ b/package_manager/tests/src/Functional/FailureMarkerRequirementTest.php
@@ -42,15 +42,16 @@ class FailureMarkerRequirementTest extends BrowserTestBase {
     ]);
     $this->drupalLogin($account);
 
+    $fake_project_root = $this->root . DIRECTORY_SEPARATOR . $this->publicFilesDirectory;
     $this->container->get('package_manager.path_locator')
-      ->setPaths($this->publicFilesDirectory, NULL, NULL, NULL);
+      ->setPaths($fake_project_root, NULL, NULL, NULL);
 
     $failure_marker = $this->container->get('package_manager.failure_marker');
     $message = $this->t('Package Manager is here to wreck your day.');
     $failure_marker->write($this->createMock(Stage::class), $message);
     $path = $failure_marker->getPath();
     $this->assertFileExists($path);
-    $this->assertStringStartsWith($this->publicFilesDirectory, $path);
+    $this->assertStringStartsWith($fake_project_root, $path);
 
     $this->drupalGet('/admin/reports/status');
     $assert_session = $this->assertSession();
diff --git a/package_manager/tests/src/Kernel/ComposerJsonExistsValidatorTest.php b/package_manager/tests/src/Kernel/ComposerJsonExistsValidatorTest.php
index 262713740b0e21093a5e317b644f5044ff54aee2..e5604330df416a3a8c2e199c0a868626d70b4ee7 100644
--- a/package_manager/tests/src/Kernel/ComposerJsonExistsValidatorTest.php
+++ b/package_manager/tests/src/Kernel/ComposerJsonExistsValidatorTest.php
@@ -22,7 +22,7 @@ class ComposerJsonExistsValidatorTest extends PackageManagerKernelTestBase {
     unlink($this->container->get('package_manager.path_locator')
       ->getProjectRoot() . '/composer.json');
     $result = ValidationResult::createError([
-      'No composer.json file can be found at vfs://root/active',
+      'No composer.json file can be found at <PROJECT_ROOT>',
     ]);
     foreach ([PreCreateEvent::class, StatusCheckEvent::class] as $event_class) {
       $this->assertEventPropagationStopped($event_class, [$this->container->get('package_manager.validator.composer_json_exists'), 'validateComposerJson']);
@@ -36,7 +36,7 @@ class ComposerJsonExistsValidatorTest extends PackageManagerKernelTestBase {
    */
   public function testComposerRequirementDuringPreApply(): void {
     $result = ValidationResult::createError([
-      'No composer.json file can be found at vfs://root/active',
+      'No composer.json file can be found at <PROJECT_ROOT>',
     ]);
     $this->addEventTestListener(function (): void {
       unlink($this->container->get('package_manager.path_locator')
diff --git a/package_manager/tests/src/Kernel/ComposerPatchesValidatorTest.php b/package_manager/tests/src/Kernel/ComposerPatchesValidatorTest.php
index 718563a3c8a88b718168466983e024abf8148184..54c307715d2c3bbd42f0b115c793c2eef6e3446f 100644
--- a/package_manager/tests/src/Kernel/ComposerPatchesValidatorTest.php
+++ b/package_manager/tests/src/Kernel/ComposerPatchesValidatorTest.php
@@ -31,8 +31,8 @@ class ComposerPatchesValidatorTest extends PackageManagerKernelTestBase {
     // factory as an array, Composer will assume that the configuration is
     // coming from a config.json file, even if one doesn't exist.
     $error = ValidationResult::createError([
-      t('The <code>cweagans/composer-patches</code> plugin is installed, but the <code>composer-exit-on-patch-failure</code> key is not set to <code>true</code> in the <code>extra</code> section of @dir/config.json.', [
-        '@dir' => $dir,
+      t('The <code>cweagans/composer-patches</code> plugin is installed, but the <code>composer-exit-on-patch-failure</code> key is not set to <code>true</code> in the <code>extra</code> section of @dir/composer.json.', [
+        '@dir' => realpath($dir),
       ]),
     ]);
     $this->assertStatusCheckResults([$error]);
@@ -55,7 +55,7 @@ class ComposerPatchesValidatorTest extends PackageManagerKernelTestBase {
     // factory as an array, Composer will assume that the configuration is
     // coming from a config.json file, even if one doesn't exist.
     $error = ValidationResult::createError([
-      "The <code>cweagans/composer-patches</code> plugin is installed, but the <code>composer-exit-on-patch-failure</code> key is not set to <code>true</code> in the <code>extra</code> section of $dir/config.json.",
+      "The <code>cweagans/composer-patches</code> plugin is installed, but the <code>composer-exit-on-patch-failure</code> key is not set to <code>true</code> in the <code>extra</code> section of $dir/composer.json.",
     ]);
     $this->assertResults([$error], PreApplyEvent::class);
   }
diff --git a/package_manager/tests/src/Kernel/ComposerUtilityTest.php b/package_manager/tests/src/Kernel/ComposerUtilityTest.php
index 2a9187f73e249b96e11b40eb918f6f5490add76f..a9da1a5132be3a97686d3f6225258a870b8b7967 100644
--- a/package_manager/tests/src/Kernel/ComposerUtilityTest.php
+++ b/package_manager/tests/src/Kernel/ComposerUtilityTest.php
@@ -4,12 +4,13 @@ declare(strict_types = 1);
 
 namespace Drupal\Tests\package_manager\Kernel;
 
+use Drupal\Component\FileSystem\FileSystem as DrupalFileSystem;
 use Drupal\fixture_manipulator\FixtureManipulator;
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\package_manager\ComposerUtility;
 use Drupal\Tests\package_manager\Traits\AssertPreconditionsTrait;
 use Drupal\Tests\package_manager\Traits\FixtureUtilityTrait;
-use org\bovigo\vfs\vfsStream;
+use Symfony\Component\Filesystem\Filesystem;
 
 /**
  * @coversDefaultClass \Drupal\package_manager\ComposerUtility
@@ -21,6 +22,13 @@ class ComposerUtilityTest extends KernelTestBase {
   use AssertPreconditionsTrait;
   use FixtureUtilityTrait;
 
+  /**
+   * The temporary root directory for testing.
+   *
+   * @var string
+   */
+  protected string $rootDir;
+
   /**
    * {@inheritdoc}
    */
@@ -32,9 +40,14 @@ class ComposerUtilityTest extends KernelTestBase {
   protected function setUp(): void {
     parent::setUp();
 
-    $fixture = vfsStream::newDirectory('fixture');
-    $this->vfsRoot->addChild($fixture);
-    static::copyFixtureFilesTo(__DIR__ . '/../../fixtures/fake_site', $fixture->url());
+    $this->rootDir = DrupalFileSystem::getOsTemporaryDirectory() . DIRECTORY_SEPARATOR . 'composer_utility_testing_root' . $this->databasePrefix;
+    $fs = new Filesystem();
+    if (is_dir($this->rootDir)) {
+      $fs->remove($this->rootDir);
+    }
+    $fs->mkdir($this->rootDir);
+    $fixture = $this->rootDir . DIRECTORY_SEPARATOR . 'fixture' . DIRECTORY_SEPARATOR;
+    static::copyFixtureFilesTo(__DIR__ . '/../../fixtures/fake_site', $fixture);
     $relative_projects_dir = '../../web/projects';
     (new FixtureManipulator())
       ->addPackage(
@@ -88,17 +101,17 @@ class ComposerUtilityTest extends KernelTestBase {
       // not match the project or package. Only the project key in this file
       // need to match.
       ->addProjectAtPath("web/projects/any_folder_name/any_sub_folder", 'nested_no_match_project', 'any_yml_file.info.yml')
-      ->commitChanges($fixture->url());
+      ->commitChanges($fixture);
   }
 
   /**
    * Tests that ComposerUtility::CreateForDirectory() validates the directory.
    */
   public function testCreateForDirectoryValidation(): void {
+    $dir = $this->rootDir;
     $this->expectException(\InvalidArgumentException::class);
-    $this->expectExceptionMessage('Composer could not find the config file: vfs://root/composer.json');
+    $this->expectExceptionMessage('Composer could not find the config file: ' . $dir . DIRECTORY_SEPARATOR . 'composer.json');
 
-    $dir = vfsStream::setup()->url();
     ComposerUtility::createForDirectory($dir);
   }
 
@@ -106,7 +119,7 @@ class ComposerUtilityTest extends KernelTestBase {
    * Tests that ComposerUtility disables automatic creation of .htaccess files.
    */
   public function testHtaccessProtectionDisabled(): void {
-    $dir = vfsStream::setup()->url();
+    $dir = $this->rootDir;
     file_put_contents($dir . '/composer.json', '{}');
 
     ComposerUtility::createForDirectory($dir);
@@ -124,7 +137,7 @@ class ComposerUtilityTest extends KernelTestBase {
    * @dataProvider providerGetProjectForPackage
    */
   public function testGetProjectForPackage(string $package, ?string $expected_project): void {
-    $dir = $this->vfsRoot->getChild('fixture')->url();
+    $dir = $this->rootDir . DIRECTORY_SEPARATOR . 'fixture';
     $this->assertSame($expected_project, ComposerUtility::createForDirectory($dir)->getProjectForPackage($package));
   }
 
@@ -174,7 +187,7 @@ class ComposerUtilityTest extends KernelTestBase {
    * @dataProvider providerGetPackageForProject
    */
   public function testGetPackageForProject(string $project, ?string $expected_package): void {
-    $dir = $this->vfsRoot->getChild('fixture')->url();
+    $dir = $this->rootDir . DIRECTORY_SEPARATOR . 'fixture';
     $this->assertSame($expected_package, ComposerUtility::createForDirectory($dir)->getPackageForProject($project));
   }
 
diff --git a/package_manager/tests/src/Kernel/DiskSpaceValidatorTest.php b/package_manager/tests/src/Kernel/DiskSpaceValidatorTest.php
index d7e2e5a1edd1b61ac004db85f91f4b42f3679888..d0a21a88690a8ee8cd3060f2dd3f5d072cd97175 100644
--- a/package_manager/tests/src/Kernel/DiskSpaceValidatorTest.php
+++ b/package_manager/tests/src/Kernel/DiskSpaceValidatorTest.php
@@ -23,9 +23,9 @@ class DiskSpaceValidatorTest extends PackageManagerKernelTestBase {
    *   The test cases.
    */
   public function providerDiskSpaceValidation(): array {
-    // These are defined by ::createVirtualProject().
-    $root = 'vfs://root/active';
-    $vendor = "$root/vendor";
+    // @see \Drupal\Tests\package_manager\Traits\ValidationTestTrait::resolvePlaceholdersInArrayValuesWithRealPaths()
+    $root = '<PROJECT_ROOT>';
+    $vendor = '<VENDOR_DIR>';
 
     $root_insufficient = "Drupal root filesystem \"$root\" has insufficient space. There must be at least 1024 megabytes free.";
     $vendor_insufficient = "Vendor filesystem \"$vendor\" has insufficient space. There must be at least 1024 megabytes free.";
@@ -146,6 +146,8 @@ class DiskSpaceValidatorTest extends PackageManagerKernelTestBase {
    * @dataProvider providerDiskSpaceValidation
    */
   public function testDiskSpaceValidation(bool $shared_disk, array $free_space, array $expected_results): void {
+    $free_space = array_flip($this->resolvePlaceholdersInArrayValuesWithRealPaths(array_flip($free_space)));
+
     /** @var \Drupal\Tests\package_manager\Kernel\TestDiskSpaceValidator $validator */
     $validator = $this->container->get('package_manager.validator.disk_space');
     $validator->sharedDisk = $shared_disk;
@@ -171,6 +173,8 @@ class DiskSpaceValidatorTest extends PackageManagerKernelTestBase {
    * @dataProvider providerDiskSpaceValidation
    */
   public function testDiskSpaceValidationDuringPreApply(bool $shared_disk, array $free_space, array $expected_results): void {
+    $free_space = array_flip($this->resolvePlaceholdersInArrayValuesWithRealPaths(array_flip($free_space)));
+
     $this->addEventTestListener(function () use ($shared_disk, $free_space): void {
       /** @var \Drupal\Tests\package_manager\Kernel\TestDiskSpaceValidator $validator */
       $validator = $this->container->get('package_manager.validator.disk_space');
diff --git a/package_manager/tests/src/Kernel/DuplicateInfoFileValidatorTest.php b/package_manager/tests/src/Kernel/DuplicateInfoFileValidatorTest.php
index 08b3db2805bb3d4acda738c25bdaee2b233e62ec..fc337a6cfcf2f79bb9017356690ca16cd67b9446 100644
--- a/package_manager/tests/src/Kernel/DuplicateInfoFileValidatorTest.php
+++ b/package_manager/tests/src/Kernel/DuplicateInfoFileValidatorTest.php
@@ -136,10 +136,10 @@ class DuplicateInfoFileValidatorTest extends PackageManagerKernelTestBase {
         ],
         [
           ValidationResult::createError([
-            'The stage directory has 2 instances of module1.info.yml as compared to 1 in the active directory. This likely indicates that a duplicate extension was installed.',
+            'The stage directory has 3 instances of module2.info.yml as compared to 1 in the active directory. This likely indicates that a duplicate extension was installed.',
           ]),
           ValidationResult::createError([
-            'The stage directory has 3 instances of module2.info.yml as compared to 1 in the active directory. This likely indicates that a duplicate extension was installed.',
+            'The stage directory has 2 instances of module1.info.yml as compared to 1 in the active directory. This likely indicates that a duplicate extension was installed.',
           ]),
         ],
       ],
@@ -154,10 +154,10 @@ class DuplicateInfoFileValidatorTest extends PackageManagerKernelTestBase {
         ],
         [
           ValidationResult::createError([
-            'The stage directory has 2 instances of module1.info.yml. This likely indicates that a duplicate extension was installed.',
+            'The stage directory has 3 instances of module2.info.yml. This likely indicates that a duplicate extension was installed.',
           ]),
           ValidationResult::createError([
-            'The stage directory has 3 instances of module2.info.yml. This likely indicates that a duplicate extension was installed.',
+            'The stage directory has 2 instances of module1.info.yml. This likely indicates that a duplicate extension was installed.',
           ]),
         ],
       ],
@@ -174,10 +174,10 @@ class DuplicateInfoFileValidatorTest extends PackageManagerKernelTestBase {
         ],
         [
           ValidationResult::createError([
-            'The stage directory has 2 instances of module1.info.yml as compared to 1 in the active directory. This likely indicates that a duplicate extension was installed.',
+            'The stage directory has 3 instances of module2.info.yml. This likely indicates that a duplicate extension was installed.',
           ]),
           ValidationResult::createError([
-            'The stage directory has 3 instances of module2.info.yml. This likely indicates that a duplicate extension was installed.',
+            'The stage directory has 2 instances of module1.info.yml as compared to 1 in the active directory. This likely indicates that a duplicate extension was installed.',
           ]),
         ],
       ],
diff --git a/package_manager/tests/src/Kernel/FixtureManipulatorTest.php b/package_manager/tests/src/Kernel/FixtureManipulatorTest.php
index 5fd2177b25e16ea600f6d0adad2477a03e64679f..d742bf8f6c3e3a6157190f6fc5782bc9e7e58335 100644
--- a/package_manager/tests/src/Kernel/FixtureManipulatorTest.php
+++ b/package_manager/tests/src/Kernel/FixtureManipulatorTest.php
@@ -8,6 +8,7 @@ use Drupal\fixture_manipulator\ActiveFixtureManipulator;
 use Drupal\fixture_manipulator\FixtureManipulator;
 use Drupal\fixture_manipulator\StageFixtureManipulator;
 use Drupal\package_manager_bypass\Beginner;
+use PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactory;
 use Symfony\Component\Filesystem\Filesystem;
 
 /**
@@ -18,7 +19,7 @@ use Symfony\Component\Filesystem\Filesystem;
 class FixtureManipulatorTest extends PackageManagerKernelTestBase {
 
   /**
-   * The root directory of the virtual project.
+   * The root directory of the test project.
    *
    * @var string
    */
@@ -375,15 +376,16 @@ class FixtureManipulatorTest extends PackageManagerKernelTestBase {
 
     $path_locator = $this->container->get('package_manager.path_locator');
     // We need to set the vendor directory's permissions first because, in the
-    // virtual project, it's located inside the project root.
+    // test project, it's located inside the project root.
     $this->assertTrue(chmod($path_locator->getVendorDirectory(), 0777));
     $this->assertTrue(chmod($path_locator->getProjectRoot(), 0777));
 
     // Simulate a stage beginning, which would commit the changes.
     // @see \Drupal\package_manager_bypass\Beginner::begin()
+    $path_factory = new PathFactory();
     $this->container->get('package_manager.beginner')->begin(
-      new TestPath($path_locator->getProjectRoot()),
-      new TestPath($path_locator->getStagingRoot()),
+      $path_factory->create($path_locator->getProjectRoot()),
+      $path_factory->create($path_locator->getStagingRoot()),
     );
   }
 
diff --git a/package_manager/tests/src/Kernel/LockFileValidatorTest.php b/package_manager/tests/src/Kernel/LockFileValidatorTest.php
index 6184957cc52e9e038a1dfa3ddec63947ef51627d..4eff1499f0d6dab07df5fe77146f5e7c9fbf85f5 100644
--- a/package_manager/tests/src/Kernel/LockFileValidatorTest.php
+++ b/package_manager/tests/src/Kernel/LockFileValidatorTest.php
@@ -19,7 +19,7 @@ use Drupal\package_manager_bypass\Stager;
 class LockFileValidatorTest extends PackageManagerKernelTestBase {
 
   /**
-   * The path of the active directory in the virtual file system.
+   * The path of the active directory in the test project.
    *
    * @var string
    */
diff --git a/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php b/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php
index c365efac7148106252d420e1343b4cbff3994cc6..63929a6ee8dbf124706423c2fb1f2f6c98611f23 100644
--- a/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php
+++ b/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php
@@ -4,6 +4,7 @@ declare(strict_types = 1);
 
 namespace Drupal\Tests\package_manager\Kernel;
 
+use Drupal\Component\FileSystem\FileSystem as DrupalFileSystem;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\Site\Settings;
 use Drupal\KernelTests\KernelTestBase;
@@ -23,12 +24,9 @@ use GuzzleHttp\Handler\MockHandler;
 use GuzzleHttp\HandlerStack;
 use GuzzleHttp\Psr7\Response;
 use GuzzleHttp\Psr7\Utils;
-use org\bovigo\vfs\vfsStream;
-use PhpTuf\ComposerStager\Domain\Value\Path\PathInterface;
-use PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface;
-use PhpTuf\ComposerStager\Infrastructure\Value\Path\AbstractPath;
+use PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactory;
 use Psr\Http\Message\RequestInterface;
-use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\Filesystem\Filesystem;
 
 /**
  * Base class for kernel tests of Package Manager's functionality.
@@ -54,7 +52,6 @@ abstract class PackageManagerKernelTestBase extends KernelTestBase {
    */
   private $client;
 
-
   /**
    * {@inheritdoc}
    */
@@ -81,7 +78,7 @@ abstract class PackageManagerKernelTestBase extends KernelTestBase {
     parent::setUp();
     $this->installConfig('package_manager');
 
-    $this->createVirtualProject();
+    $this->createTestProject();
 
     // The Update module's default configuration must be installed for our
     // fake release metadata to be fetched, and the System module's to ensure
@@ -105,19 +102,12 @@ abstract class PackageManagerKernelTestBase extends KernelTestBase {
       $container->set('http_client', $this->client);
     }
 
-    // Ensure that Composer Stager uses the test path factory, which is aware
-    // of the virtual file system.
-    $definition = new Definition(TestPathFactory::class);
-    $class = $definition->getClass();
-    $container->setDefinition($class, $definition->setPublic(FALSE));
-    $container->setAlias(PathFactoryInterface::class, $class);
-
-    // When a virtual project is used, the disk space validator is replaced with
+    // When the test project is used, the disk space validator is replaced with
     // a mock. When staged changes are applied, the container is rebuilt, which
     // destroys the mocked service and can cause unexpected side effects. The
     // 'persist' tag prevents the mock from being destroyed during a container
     // rebuild.
-    // @see ::createVirtualProject()
+    // @see ::createTestProject()
     $container->getDefinition('package_manager.validator.disk_space')
       ->addTag('persist');
 
@@ -146,7 +136,7 @@ abstract class PackageManagerKernelTestBase extends KernelTestBase {
       $this->container->get('event_dispatcher'),
       $this->container->get('tempstore.shared'),
       $this->container->get('datetime.time'),
-      new TestPathFactory(),
+      new PathFactory(),
       $this->container->get('package_manager.failure_marker')
     );
   }
@@ -219,32 +209,42 @@ abstract class PackageManagerKernelTestBase extends KernelTestBase {
   }
 
   /**
-   * Creates a test project in a virtual file system.
+   * Creates a test project.
    *
-   * This will create two directories at the root of the virtual file system:
+   * This will create a temporary uniques root directory and then creates two
+   * directories in it:
    * 'active', which is the active directory containing a fake Drupal code base,
    * 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.
    *
    * @param string|null $source_dir
    *   (optional) The path of a directory which should be copied into the
-   *   virtual file system and used as the active directory.
+   *   test project and used as the active directory.
    */
-  protected function createVirtualProject(?string $source_dir = NULL): void {
+  protected function createTestProject(?string $source_dir = NULL): void {
     $source_dir = $source_dir ?? __DIR__ . '/../../fixtures/fake_site';
+    $root = DrupalFileSystem::getOsTemporaryDirectory() . DIRECTORY_SEPARATOR . 'package_manager_testing_root' . $this->databasePrefix;
+    $fs = new Filesystem();
+    if (is_dir($root)) {
+      $fs->remove($root);
+    }
+    $fs->mkdir($root);
 
     // Create the active directory and copy its contents from a fixture.
-    $active_dir = vfsStream::newDirectory('active');
-    $this->vfsRoot->addChild($active_dir);
-    $active_dir = $active_dir->url();
-    // Move vfs://root/sites to vfs://root/active/sites.
-    $sites_in_vfs = vfsStream::url('root/sites');
-    rename($sites_in_vfs, $sites_in_vfs . '/active');
+    $active_dir = $root . DIRECTORY_SEPARATOR . 'active';
+    $this->assertTrue(mkdir($active_dir));
     static::copyFixtureFilesTo($source_dir, $active_dir);
 
-    // Override siteDirectory to point to root/active/... instead of root/... .
+    // Removing 'vfs://root/' from site path set in
+    // \Drupal\KernelTests\KernelTestBase::setUpFilesystem as we don't use vfs.
     $test_site_path = str_replace('vfs://root/', '', $this->siteDirectory);
-    $this->siteDirectory = vfsStream::url('root/active/' . $test_site_path);
+
+    // Copy directory structure from vfs site directory to our site directory.
+    (new Filesystem())->mirror($this->siteDirectory, $active_dir . DIRECTORY_SEPARATOR . $test_site_path);
+
+    // Override siteDirectory to point to root/active/... instead of root/... .
+    $this->siteDirectory = $active_dir . DIRECTORY_SEPARATOR . $test_site_path;
+
     // Override KernelTestBase::setUpFilesystem's Settings object.
     $settings = Settings::getInstance() ? Settings::getAll() : [];
     $settings['file_public_path'] = $this->siteDirectory . '/files';
@@ -252,24 +252,20 @@ abstract class PackageManagerKernelTestBase extends KernelTestBase {
     new Settings($settings);
 
     // Create a stage root directory alongside the active directory.
-    $stage_dir = vfsStream::newDirectory('stage');
-    $this->vfsRoot->addChild($stage_dir);
+    $staging_root = $root . DIRECTORY_SEPARATOR . 'stage';
+    $this->assertTrue(mkdir($staging_root));
 
-    // Ensure the path locator points to the virtual active directory. We assume
-    // that is its own web root and that the vendor directory is at its top
-    // level.
+    // Ensure the path locator points to the test project. We assume that is its
+    // own web root and the vendor directory is at its top level.
     /** @var \Drupal\package_manager_bypass\PathLocator $path_locator */
     $path_locator = $this->container->get('package_manager.path_locator');
-    $path_locator->setPaths($active_dir, $active_dir . '/vendor', '', $stage_dir->url());
+    $path_locator->setPaths($active_dir, $active_dir . '/vendor', '', $staging_root);
 
-    // Ensure the active directory will be copied into the virtual stage
+    // Ensure the active directory will be copied into the test project's stage
     // directory.
     Beginner::setFixturePath($active_dir);
 
-    // Since the path locator now points to a virtual file system, we need to
-    // replace the disk space validator with a test-only version that bypasses
-    // system calls, like disk_free_space() and stat(), which aren't supported
-    // by vfsStream. This validator will persist through container rebuilds.
+    // This validator will persist through container rebuilds.
     // @see ::register()
     $validator = new TestDiskSpaceValidator(
       $path_locator,
@@ -450,37 +446,6 @@ trait TestStageTrait {
 
 }
 
-/**
- * Defines a path value object that is aware of the virtual file system.
- */
-class TestPath extends AbstractPath {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function doResolve(string $basePath): string {
-    if (str_starts_with($this->path, vfsStream::SCHEME . '://')) {
-      return $this->path;
-    }
-    return implode(DIRECTORY_SEPARATOR, [$basePath, $this->path]);
-  }
-
-}
-
-/**
- * Defines a path factory that is aware of the virtual file system.
- */
-class TestPathFactory implements PathFactoryInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(string $path): PathInterface {
-    return new TestPath($path);
-  }
-
-}
-
 /**
  * Defines a stage specifically for testing purposes.
  */
diff --git a/package_manager/tests/src/Kernel/StageOwnershipTest.php b/package_manager/tests/src/Kernel/StageOwnershipTest.php
index a91066c9006581c73cd818c5de7f60cd0862ce50..83b3c50e408080bcebc2280e0837e616d805285d 100644
--- a/package_manager/tests/src/Kernel/StageOwnershipTest.php
+++ b/package_manager/tests/src/Kernel/StageOwnershipTest.php
@@ -262,6 +262,7 @@ class StageOwnershipTest extends PackageManagerKernelTestBase {
       $this->container->get('settings'),
       $logger_channel,
     ];
+    $this->assertSame(E_ALL, error_reporting());
     $this->container->set('file_system', new class (...$arguments) extends FileSystem {
 
       /**
@@ -275,6 +276,40 @@ class StageOwnershipTest extends PackageManagerKernelTestBase {
         // write-protected files.
       }
 
+      /**
+       * {@inheritdoc}
+       */
+      public function unlink($uri, $context = NULL) {
+        // PHP's unlink() will generate a warning upon failure, which PHPUnit's
+        // error handler will convert to an exception. This happens because all
+        // tests run with E_ALL. But production sites either have E_ALL disabled
+        // or they catch and log it. So: match that behavior in the test, but
+        // only for this single method, to still maximally catch errors.
+        // @see https://www.php.net/manual/en/function.unlink.php,
+        // @see \ERROR_REPORTING_HIDE
+        error_reporting(E_ALL & ~E_WARNING);
+        $result = parent::unlink($uri, $context);
+        error_reporting(E_ALL);
+        return $result;
+      }
+
+      /**
+       * {@inheritdoc}
+       */
+      public function rmdir($uri, $context = NULL) {
+        // PHP's rmdir() will generate a warning upon failure, which PHPUnit's
+        // error handler will convert to an exception. This happens because all
+        // tests run with E_ALL. But production sites either have E_ALL disabled
+        // or they catch and log it. So: match that behavior in the test, but
+        // only for this single method, to still maximally catch errors.
+        // @see https://php.net/manual/en/function.rmdir.php,
+        // @see \ERROR_REPORTING_HIDE
+        error_reporting(E_ALL & ~E_WARNING);
+        $result = parent::rmdir($uri, $context);
+        error_reporting(E_ALL);
+        return $result;
+      }
+
     });
 
     $stage = $this->createStage();
@@ -285,7 +320,7 @@ class StageOwnershipTest extends PackageManagerKernelTestBase {
     // Write-protect the stage directory, which should prevent files in it from
     // being deleted.
     $dir = $stage->getStageDirectory();
-    chmod($dir, 0400);
+    chmod($dir, 0500);
     $this->assertDirectoryIsNotWritable($dir);
 
     $logger = new TestLogger();
diff --git a/package_manager/tests/src/Kernel/StageTest.php b/package_manager/tests/src/Kernel/StageTest.php
index c8ba215b0b73ecfb9f05c897624c4a4f52208263..78a570c235f9219ac0267c292709d4ee160a3358 100644
--- a/package_manager/tests/src/Kernel/StageTest.php
+++ b/package_manager/tests/src/Kernel/StageTest.php
@@ -5,6 +5,7 @@ declare(strict_types = 1);
 namespace Drupal\Tests\package_manager\Kernel;
 
 use Drupal\Component\Datetime\Time;
+use Drupal\Component\FileSystem\FileSystem;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\Extension\ModuleUninstallValidatorException;
 use Drupal\Core\Logger\RfcLogLevel;
@@ -16,10 +17,10 @@ use Drupal\package_manager\Event\StageEvent;
 use Drupal\package_manager\Exception\ApplyFailedException;
 use Drupal\package_manager\Exception\StageException;
 use Drupal\package_manager\Stage;
+use Drupal\package_manager_bypass\Beginner;
 use Drupal\package_manager_bypass\Committer;
 use PhpTuf\ComposerStager\Domain\Exception\InvalidArgumentException;
 use PhpTuf\ComposerStager\Domain\Exception\PreconditionException;
-use Drupal\package_manager_bypass\Beginner;
 use PhpTuf\ComposerStager\Domain\Service\Precondition\PreconditionInterface;
 use Psr\Log\LogLevel;
 use ColinODell\PsrTestLogger\TestLogger;
@@ -57,8 +58,7 @@ class StageTest extends PackageManagerKernelTestBase {
     $validator = $this->container->get('package_manager.validator.file_system');
     $this->container->get('event_dispatcher')->removeSubscriber($validator);
 
-    // Don't mirror the active directory from the virtual project into the
-    // real file system.
+    // Don't mirror the active directory from the test project.
     Beginner::setFixturePath(NULL);
 
     /** @var \Drupal\package_manager_bypass\PathLocator $path_locator */
@@ -72,7 +72,11 @@ class StageTest extends PackageManagerKernelTestBase {
     // If the stage root directory is changed, the existing stage shouldn't be
     // affected...
     $active_dir = $path_locator->getProjectRoot();
-    $path_locator->setPaths($active_dir, "$active_dir/vendor", '', '/junk/drawer');
+    $new_staging_root = FileSystem::getOsTemporaryDirectory() . DIRECTORY_SEPARATOR . 'junk';
+    if (!is_dir($new_staging_root)) {
+      mkdir($new_staging_root);
+    }
+    $path_locator->setPaths($active_dir, "$active_dir/vendor", '', $new_staging_root);
     $this->assertSame($stage_dir, $stage->getStageDirectory());
     $stage->destroy();
     // ...but a new stage should be.
@@ -80,7 +84,7 @@ class StageTest extends PackageManagerKernelTestBase {
     $another_id = $stage->create();
     $this->assertNotSame($id, $another_id);
     $stage_dir = $stage->getStageDirectory();
-    $this->assertStringStartsWith('/junk/drawer/', $stage_dir);
+    $this->assertStringStartsWith(realpath($new_staging_root), $stage_dir);
     $this->assertStringEndsWith("/$another_id", $stage_dir);
   }
 
diff --git a/package_manager/tests/src/Kernel/WritableFileSystemValidatorTest.php b/package_manager/tests/src/Kernel/WritableFileSystemValidatorTest.php
index 0ef49d5df216cf0070ffa45ce7c260e061a1282a..b73a5227bbe907f3960630fd9a1a4088677e6ff2 100644
--- a/package_manager/tests/src/Kernel/WritableFileSystemValidatorTest.php
+++ b/package_manager/tests/src/Kernel/WritableFileSystemValidatorTest.php
@@ -30,12 +30,12 @@ class WritableFileSystemValidatorTest extends PackageManagerKernelTestBase {
    *   The test cases.
    */
   public function providerWritable(): array {
-    // The root and vendor paths are defined by ::createVirtualProject().
-    $root_error = t('The Drupal directory "vfs://root/active" is not writable.');
-    $vendor_error = t('The vendor directory "vfs://root/active/vendor" is not writable.');
+    // @see \Drupal\Tests\package_manager\Traits\ValidationTestTrait::resolvePlaceholdersInArrayValuesWithRealPaths()
+    $root_error = t('The Drupal directory "<PROJECT_ROOT>" is not writable.');
+    $vendor_error = t('The vendor directory "<VENDOR_DIR>" is not writable.');
     $summary = t('The file system is not writable.');
     $writable_permission = 0777;
-    $non_writable_permission = 0444;
+    $non_writable_permission = 0550;
 
     return [
       'root and vendor are writable' => [
@@ -83,7 +83,7 @@ class WritableFileSystemValidatorTest extends PackageManagerKernelTestBase {
     $path_locator = $this->container->get('package_manager.path_locator');
 
     // We need to set the vendor directory's permissions first because, in the
-    // virtual project, it's located inside the project root.
+    // test project, it's located inside the project root.
     $this->assertTrue(chmod($path_locator->getVendorDirectory(), $vendor_permissions));
     $this->assertTrue(chmod($path_locator->getProjectRoot(), $root_permissions));
 
@@ -109,12 +109,12 @@ class WritableFileSystemValidatorTest extends PackageManagerKernelTestBase {
         $path_locator = $this->container->get('package_manager.path_locator');
 
         // We need to set the vendor directory's permissions first because, in
-        // the virtual project, it's located inside the project root.
+        // the test project, it's located inside the project root.
         $this->assertTrue(chmod($path_locator->getVendorDirectory(), $vendor_permissions));
         $this->assertTrue(chmod($path_locator->getProjectRoot(), $root_permissions));
 
         // During pre-apply we don't care whether the staging root is writable.
-        $this->assertTrue(chmod($path_locator->getStagingRoot(), 0444));
+        $this->assertTrue(chmod($path_locator->getStagingRoot(), 0550));
       },
     );
 
@@ -129,7 +129,7 @@ class WritableFileSystemValidatorTest extends PackageManagerKernelTestBase {
    */
   public function providerStagingRootPermissions(): array {
     $writable_permission = 0777;
-    $non_writable_permission = 0444;
+    $non_writable_permission = 0550;
     $summary = t('The file system is not writable.');
     return [
       'writable stage root exists' => [
@@ -140,14 +140,14 @@ class WritableFileSystemValidatorTest extends PackageManagerKernelTestBase {
       'write-protected stage root exists' => [
         $non_writable_permission,
         [
-          ValidationResult::createError(['The stage root directory "vfs://root/stage" is not writable.'], $summary),
+          ValidationResult::createError(['The stage root directory "<STAGE_ROOT>" is not writable.'], $summary),
         ],
         FALSE,
       ],
       'stage root directory does not exist, parent directory not writable' => [
         $non_writable_permission,
         [
-          ValidationResult::createError(['The stage root directory will not able to be created at "vfs://root".'], $summary),
+          ValidationResult::createError(['The stage root directory will not able to be created at "<STAGE_ROOT_PARENT>".'], $summary),
         ],
         TRUE,
       ],
diff --git a/package_manager/tests/src/Traits/InfoYmlConverterTrait.php b/package_manager/tests/src/Traits/InfoYmlConverterTrait.php
deleted file mode 100644
index a49d9f1bb1907af24b8b5ff20cafa8d6cf3fd5c7..0000000000000000000000000000000000000000
--- a/package_manager/tests/src/Traits/InfoYmlConverterTrait.php
+++ /dev/null
@@ -1,52 +0,0 @@
-<?php
-
-declare(strict_types = 1);
-
-namespace Drupal\Tests\package_manager\Traits;
-
-use org\bovigo\vfs\vfsStream;
-use org\bovigo\vfs\vfsStreamDirectory;
-use org\bovigo\vfs\vfsStreamFile;
-use org\bovigo\vfs\visitor\vfsStreamAbstractVisitor;
-
-/**
- * Common methods to convert info.yml file that will pass core coding standards.
- *
- * @internal
- */
-trait InfoYmlConverterTrait {
-
-  /**
-   * Renames all files that end with .info.yml.hide.
-   */
-  protected function renameVfsInfoYmlFiles(): void {
-    // Strip the `.hide` suffix from all `.info.yml.hide` files. Drupal's coding
-    // standards don't allow info files to have the `project` key, but we need
-    // it to be present for testing.
-    vfsStream::inspect(new class () extends vfsStreamAbstractVisitor {
-
-      /**
-       * {@inheritdoc}
-       */
-      public function visitFile(vfsStreamFile $file) {
-        $name = $file->getName();
-
-        if (str_ends_with($name, '.info.yml.hide')) {
-          $new_name = basename($name, '.hide');
-          $file->rename($new_name);
-        }
-      }
-
-      /**
-       * {@inheritdoc}
-       */
-      public function visitDirectory(vfsStreamDirectory $dir) {
-        foreach ($dir->getChildren() as $child) {
-          $this->visit($child);
-        }
-      }
-
-    });
-  }
-
-}
diff --git a/package_manager/tests/src/Traits/ValidationTestTrait.php b/package_manager/tests/src/Traits/ValidationTestTrait.php
index 3a8de3401f4365fe56276565a5abaf43be64dba3..440b963f19dee42c5a08b40f0a24a4b1dc2a4081 100644
--- a/package_manager/tests/src/Traits/ValidationTestTrait.php
+++ b/package_manager/tests/src/Traits/ValidationTestTrait.php
@@ -5,6 +5,7 @@ declare(strict_types = 1);
 namespace Drupal\Tests\package_manager\Traits;
 
 use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\package_manager\PathLocator;
 use Drupal\package_manager\ValidationResult;
 use Drupal\Tests\UnitTestCase;
 
@@ -25,14 +26,49 @@ trait ValidationTestTrait {
    *   The expected validation results.
    * @param \Drupal\package_manager\ValidationResult[] $actual_results
    *   The actual validation results.
+   * @param \Drupal\package_manager\PathLocator|null $path_locator
+   *   (optional) The path locator (when this trait is used in unit tests).
    */
-  protected function assertValidationResultsEqual(array $expected_results, array $actual_results): void {
-    $expected_results = $this->getValidationResultsAsArray($expected_results);
+  protected function assertValidationResultsEqual(array $expected_results, array $actual_results, ?PathLocator $path_locator = NULL): void {
+    if ($path_locator) {
+      assert(is_a(get_called_class(), UnitTestCase::class, TRUE));
+    }
+    $expected_results = array_map(
+      function (array $result) use ($path_locator): array {
+        $result['messages'] = $this->resolvePlaceholdersInArrayValuesWithRealPaths($result['messages'], $path_locator);
+        return $result;
+      },
+      $this->getValidationResultsAsArray($expected_results)
+    );
     $actual_results = $this->getValidationResultsAsArray($actual_results);
 
     self::assertSame($expected_results, $actual_results);
   }
 
+  /**
+   * Resolves <PROJECT_ROOT>, <VENDOR_DIR>, <STAGE_ROOT>, <STAGE_ROOT_PARENT>.
+   *
+   * @param array $subject
+   *   An array with arbitrary keys, and values potentially containing the
+   *   placeholders <PROJECT_ROOT>, <VENDOR_DIR>, <STAGE_ROOT>, or
+   *   <STAGE_ROOT_PARENT>.
+   * @param \Drupal\package_manager\PathLocator|null $path_locator
+   *   (optional) The path locator (when this trait is used in unit tests).
+   *
+   * @return array
+   *   The same array, with unchanged keys, and with the placeholders resolved.
+   */
+  protected function resolvePlaceholdersInArrayValuesWithRealPaths(array $subject, ?PathLocator $path_locator = NULL): array {
+    if (!$path_locator) {
+      $path_locator = $this->container->get('package_manager.path_locator');
+    }
+    return str_replace(
+      ['<PROJECT_ROOT>', '<VENDOR_DIR>', '<STAGE_ROOT>', '<STAGE_ROOT_PARENT>'],
+      [$path_locator->getProjectRoot(), $path_locator->getVendorDirectory(), $path_locator->getStagingRoot(), dirname($path_locator->getStagingRoot())],
+      $subject
+    );
+  }
+
   /**
    * Gets an array representation of validation results for easy comparison.
    *
diff --git a/package_manager/tests/src/Unit/StageNotInActiveValidatorTest.php b/package_manager/tests/src/Unit/StageNotInActiveValidatorTest.php
index b05e42a35e0106350daf625ac0e8928d018dc7f1..a96e16986e5d0df96d609293b9131d56d469a8db 100644
--- a/package_manager/tests/src/Unit/StageNotInActiveValidatorTest.php
+++ b/package_manager/tests/src/Unit/StageNotInActiveValidatorTest.php
@@ -37,6 +37,7 @@ class StageNotInActiveValidatorTest extends UnitTestCase {
     $path_locator_prophecy = $this->prophesize(PathLocator::class);
     $path_locator_prophecy->getProjectRoot()->willReturn(Path::canonicalize($project_root));
     $path_locator_prophecy->getStagingRoot()->willReturn(Path::canonicalize($staging_root));
+    $path_locator_prophecy->getVendorDirectory()->willReturn('not used');
     $path_locator = $path_locator_prophecy->reveal();
     $stage = $this->prophesize(Stage::class)->reveal();
 
@@ -44,7 +45,7 @@ class StageNotInActiveValidatorTest extends UnitTestCase {
     $stage_not_in_active_validator->setStringTranslation($this->getStringTranslationStub());
     $event = new PreCreateEvent($stage, ['some/path']);
     $stage_not_in_active_validator->checkNotInActive($event);
-    $this->assertValidationResultsEqual($expected, $event->getResults());
+    $this->assertValidationResultsEqual($expected, $event->getResults(), $path_locator);
   }
 
   /**
@@ -69,47 +70,45 @@ class StageNotInActiveValidatorTest extends UnitTestCase {
         "/var/root",
         "/home/var/root",
       ],
-
-      // @todo Replace `vfs://` with `/var/ in https://www.drupal.org/i/3328234.
       'Stage with .. segments, outside active' => [
         [],
-        "vfs://root/active",
-        "vfs://root/active/../stage",
+        "/var/root/active",
+        "/var/root/active/../stage",
       ],
       'Stage without .. segments, outside active' => [
         [],
-        "vfs://root/active",
-        "vfs://root/stage",
+        "/var/root/active",
+        "/var/root/stage",
       ],
       'Stage with .. segments, inside active' => [
         [$expected_symlink_validation_error],
-        "vfs://root/active",
-        "vfs://root/active/../active/stage",
+        "/var/root/active",
+        "/var/root/active/../active/stage",
       ],
       'Stage without .. segments, inside active' => [
         [$expected_symlink_validation_error],
-        "vfs://root/active",
-        "vfs://root/active/stage",
+        "/var/root/active",
+        "/var/root/active/stage",
       ],
       'Stage with .. segments, outside active, active with .. segments' => [
         [],
-        "vfs://root/active",
-        "vfs://root/active/../stage",
+        "/var/root/active",
+        "/var/root/active/../stage",
       ],
       'Stage without .. segments, outside active, active with .. segments' => [
         [],
-        "vfs://root/random/../active",
-        "vfs://root/stage",
+        "/var/root/random/../active",
+        "/var/root/stage",
       ],
       'Stage with .. segments, inside active, active with .. segments' => [
         [$expected_symlink_validation_error],
-        "vfs://root/random/../active",
-        "vfs://root/active/../active/stage",
+        "/var/root/random/../active",
+        "/var/root/active/../active/stage",
       ],
       'Stage without .. segments, inside active, active with .. segments' => [
         [$expected_symlink_validation_error],
-        "vfs://root/random/../active",
-        "vfs://root/active/stage",
+        "/var/root/random/../active",
+        "/var/root/active/stage",
       ],
     ];
   }
diff --git a/tests/src/Kernel/StatusCheck/ScaffoldFilePermissionsValidatorTest.php b/tests/src/Kernel/StatusCheck/ScaffoldFilePermissionsValidatorTest.php
index d5d163cfb3b361aa1a04c3f9610ef473a6fd218b..9392224e59229039ec5f869dba25eb32d8372128 100644
--- a/tests/src/Kernel/StatusCheck/ScaffoldFilePermissionsValidatorTest.php
+++ b/tests/src/Kernel/StatusCheck/ScaffoldFilePermissionsValidatorTest.php
@@ -7,6 +7,7 @@ namespace Drupal\Tests\automatic_updates\Kernel\StatusCheck;
 use Drupal\fixture_manipulator\ActiveFixtureManipulator;
 use Drupal\fixture_manipulator\StageFixtureManipulator;
 use Drupal\package_manager\Exception\StageValidationException;
+use Drupal\package_manager\PathLocator;
 use Drupal\package_manager\ValidationResult;
 use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase;
 
@@ -23,7 +24,7 @@ class ScaffoldFilePermissionsValidatorTest extends AutomaticUpdatesKernelTestBas
   protected static $modules = ['automatic_updates'];
 
   /**
-   * The active directory of the virtual project.
+   * The active directory of the test project.
    *
    * @var string
    */
@@ -42,7 +43,7 @@ class ScaffoldFilePermissionsValidatorTest extends AutomaticUpdatesKernelTestBas
   /**
    * {@inheritdoc}
    */
-  protected function assertValidationResultsEqual(array $expected_results, array $actual_results): void {
+  protected function assertValidationResultsEqual(array $expected_results, array $actual_results, ?PathLocator $path_locator = NULL): void {
     $map = function (string $path): string {
       return $this->activeDir . '/' . $path;
     };
@@ -52,7 +53,7 @@ class ScaffoldFilePermissionsValidatorTest extends AutomaticUpdatesKernelTestBas
       $messages = array_map($map, $result->getMessages());
       $expected_results[$i] = ValidationResult::createError($messages, t('The following paths must be writable in order to update default site configuration files.'));
     }
-    parent::assertValidationResultsEqual($expected_results, $actual_results);
+    parent::assertValidationResultsEqual($expected_results, $actual_results, $path_locator);
   }
 
   /**
@@ -64,7 +65,7 @@ class ScaffoldFilePermissionsValidatorTest extends AutomaticUpdatesKernelTestBas
   private function writeProtect(array $paths): void {
     foreach ($paths as $path) {
       $path = $this->activeDir . '/' . $path;
-      chmod($path, 0400);
+      chmod($path, 0500);
       $this->assertFileIsNotWritable($path, "Failed to write-protect $path.");
     }
   }
diff --git a/tests/src/Kernel/StatusCheck/VersionPolicyValidatorTest.php b/tests/src/Kernel/StatusCheck/VersionPolicyValidatorTest.php
index ea7aa571d1885705ec7633832d9d976ad3c79fb1..2f82f8fced79b91f00d3d30a2a9ae5ca890cc4c8 100644
--- a/tests/src/Kernel/StatusCheck/VersionPolicyValidatorTest.php
+++ b/tests/src/Kernel/StatusCheck/VersionPolicyValidatorTest.php
@@ -398,7 +398,7 @@ class VersionPolicyValidatorTest extends AutomaticUpdatesKernelTestBase {
    *
    * @param \Closure $listener
    *   A pre-create event listener to run before all validators. This should put
-   *   the virtual project and/or updater into a state which will cause
+   *   the test project and/or updater into a state which will cause
    *   \Drupal\automatic_updates\Validator\VersionPolicyValidator::getTargetVersion()
    *   to throw an exception because the target version of Drupal core is not
    *   known.