diff --git a/automatic_updates_extensions/src/ExtensionUpdater.php b/automatic_updates_extensions/src/ExtensionUpdater.php
index 6e532e75d7a230beefd9649c29fc8f07efd75f27..7ee83c8b340218fc194231daf5ff5eb0e5da2bdc 100644
--- a/automatic_updates_extensions/src/ExtensionUpdater.php
+++ b/automatic_updates_extensions/src/ExtensionUpdater.php
@@ -51,7 +51,7 @@ class ExtensionUpdater extends Stage {
     $this->tempStore->set(static::TEMPSTORE_METADATA_KEY, [
       'packages' => $package_versions,
     ]);
-    return parent::create();
+    return $this->create();
   }
 
   /**
diff --git a/automatic_updates_extensions/tests/src/Kernel/Validator/PackagesInstalledWithComposerValidatorTest.php b/automatic_updates_extensions/tests/src/Kernel/Validator/PackagesInstalledWithComposerValidatorTest.php
index 34598ecf423584ec187ad553d561743370bb3da5..c40ef7a63a1c4022db402f82c30df90244601931 100644
--- a/automatic_updates_extensions/tests/src/Kernel/Validator/PackagesInstalledWithComposerValidatorTest.php
+++ b/automatic_updates_extensions/tests/src/Kernel/Validator/PackagesInstalledWithComposerValidatorTest.php
@@ -16,57 +16,60 @@ use Drupal\Tests\automatic_updates_extensions\Kernel\AutomaticUpdatesExtensionsK
  */
 class PackagesInstalledWithComposerValidatorTest extends AutomaticUpdatesExtensionsKernelTestBase {
 
-  /**
-   * The active directory in the virtual file system.
-   *
-   * @var string
-   */
-  private $activeDir;
-
   /**
    * {@inheritdoc}
    */
   protected function setUp(): void {
-    // In this test, we don't focus on validating that the updated projects are
-    // secure and supported. Therefore, we need to disable the update release
-    // validator that validates updated projects are secure and supported.
+    // In this test, we don't care whether the updated projects are secure and
+    // supported.
     $this->disableValidators[] = 'automatic_updates_extensions.validator.target_release';
-    // In this test, we don't focus on validating that the updated projects are
-    // only themes or modules. Therefore, we need to disable the update packages
-    // type validator.
+    // We also don't care if the updated projects are themes and modules only.
     $this->disableValidators[] = 'automatic_updates_extensions.validator.packages_type';
     parent::setUp();
-    $this->activeDir = $this->container->get('package_manager.path_locator')
+
+    $active_dir = $this->container->get('package_manager.path_locator')
       ->getProjectRoot();
+
+    $installed = __DIR__ . '/../../../fixtures/packages_installed_with_composer_validator/active.installed.json';
+    $this->assertFileIsReadable($installed);
+    copy($installed, $active_dir . '/vendor/composer/installed.json');
   }
 
   /**
    * Data provider for testPreCreateException().
    *
-   * @return array
+   * @return array[]
    *   Test cases for testPreCreateException().
    */
   public function providerPreCreateException(): array {
+    $summary = t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:');
+
     return [
-      'module not installed via composer' => [
+      'module not installed via Composer' => [
         [
           'new_module' => '9.8.0',
         ],
-        [ValidationResult::createError(['new_module'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))],
+        [
+          ValidationResult::createError(['new_module'], $summary),
+        ],
       ],
-      'theme not installed via composer' => [
+      'theme not installed via Composer' => [
         [
           'new_theme' => '9.8.0',
         ],
-        [ValidationResult::createError(['new_theme'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))],
+        [
+          ValidationResult::createError(['new_theme'], $summary),
+        ],
       ],
-      'profile not installed via composer' => [
+      'profile not installed via Composer' => [
         [
           'new_profile' => '9.8.0',
         ],
-        [ValidationResult::createError(['new_profile'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))],
+        [
+          ValidationResult::createError(['new_profile'], $summary),
+        ],
       ],
-      'module_theme_profile_dependency_not_installed_via_composer' => [
+      'module, theme, profile, and library not installed via Composer' => [
         [
           'new_module' => '9.8.0',
           'new_theme' => '9.8.0',
@@ -74,12 +77,10 @@ class PackagesInstalledWithComposerValidatorTest extends AutomaticUpdatesExtensi
           'new_dependency' => '9.8.0',
         ],
         [
-          ValidationResult::createError(
-            ['new_module', 'new_theme', 'new_profile', 'new_dependency'],
-            t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:')),
+          ValidationResult::createError(['new_module', 'new_theme', 'new_profile', 'new_dependency'], $summary),
         ],
       ],
-      'module_theme_profile_installed_via_composer' => [
+      'module, theme, and profile installed via Composer' => [
         [
           'existing_module' => '9.8.1',
           'existing_theme' => '9.8.1',
@@ -87,18 +88,20 @@ class PackagesInstalledWithComposerValidatorTest extends AutomaticUpdatesExtensi
         ],
         [],
       ],
-      'existing module installed and new module not installed via composer' => [
+      'existing module installed and new module not installed via Composer' => [
         [
           'existing_module' => '9.8.1',
           'new_module' => '9.8.0',
         ],
-        [ValidationResult::createError(['new_module'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))],
+        [
+          ValidationResult::createError(['new_module'], $summary),
+        ],
       ],
     ];
   }
 
   /**
-   * Tests the packages installed with composer during pre-create.
+   * Tests the packages installed with Composer during pre-create.
    *
    * @param array $projects
    *   The projects to install.
@@ -108,11 +111,6 @@ class PackagesInstalledWithComposerValidatorTest extends AutomaticUpdatesExtensi
    * @dataProvider providerPreCreateException
    */
   public function testPreCreateException(array $projects, array $expected_results): void {
-    // Path of `active.installed.json` file. It will be used as the virtual
-    // project's active `vendor/composer/installed.json` file.
-    $active_installed = __DIR__ . '/../../../fixtures/packages_installed_with_composer_validator/active.installed.json';
-    $this->assertFileIsReadable($active_installed);
-    copy($active_installed, "$this->activeDir/vendor/composer/installed.json");
     $this->assertUpdateResults($projects, $expected_results, PreCreateEvent::class);
   }
 
@@ -123,32 +121,38 @@ class PackagesInstalledWithComposerValidatorTest extends AutomaticUpdatesExtensi
    *   Test cases for testPreApplyException().
    */
   public function providerPreApplyException(): array {
+    $summary = t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:');
     $fixtures_folder = __DIR__ . '/../../../fixtures/packages_installed_with_composer_validator';
+
     return [
-      'module not installed via composer' => [
+      'module not installed via Composer' => [
         "$fixtures_folder/module_not_installed.staged.installed.json",
-        [ValidationResult::createError(['new_module'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))],
+        [
+          ValidationResult::createError(['new_module'], $summary),
+        ],
       ],
-      'theme not installed via composer' => [
+      'theme not installed via Composer' => [
         "$fixtures_folder/theme_not_installed.staged.installed.json",
-        [ValidationResult::createError(['new_theme'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))],
+        [
+          ValidationResult::createError(['new_theme'], $summary),
+        ],
       ],
-      'profile not installed via composer' => [
+      'profile not installed via Composer' => [
         "$fixtures_folder/profile_not_installed.staged.installed.json",
-        [ValidationResult::createError(['new_profile'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))],
+        [
+          ValidationResult::createError(['new_profile'], $summary),
+        ],
       ],
-      // Dependency drupal/new_dependency of type 'drupal-library' will not show
-      // up in the error because it is not one of the covered types
-      // ('drupal-module', 'drupal-theme' or 'drupal-profile'). Module
-      // new_module1 will also not show up as it's name doesn't start with
-      // 'drupal/'.
+      // The `drupal/new_dependency` package won't show up in the error because
+      // its type is `drupal-library`, and the validator only considers the
+      // `drupal-module`, `drupal-theme`, and `drupal-profile` package types.
+      // The `not-drupal/new_module1` package won't show up either, even though
+      // its type is `drupal-module`, because it doesn't start with `drupal/`.
       // @see \Drupal\automatic_updates_extensions\Validator\PackagesInstalledWithComposerValidator
-      'module_theme_profile_dependency_not_installed_via_composer' => [
+      'module, theme, and profile not installed via Composer' => [
         "$fixtures_folder/module_theme_profile_dependency_not_installed.staged.installed.json",
         [
-          ValidationResult::createError(
-            ['new_module', 'new_theme', 'new_profile'],
-            t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:')),
+          ValidationResult::createError(['new_module', 'new_theme', 'new_profile'], $summary),
         ],
       ],
     ];
@@ -166,18 +170,14 @@ class PackagesInstalledWithComposerValidatorTest extends AutomaticUpdatesExtensi
    * @dataProvider providerPreApplyException
    */
   public function testPreApplyException(string $staged_installed, array $expected_results): void {
-    // Path of `active.installed.json` file. It will be used as the virtual
-    // project's active `vendor/composer/installed.json` file.
-    $active_installed = __DIR__ . '/../../../fixtures/packages_installed_with_composer_validator/active.installed.json';
-    $this->assertFileIsReadable($active_installed);
     $this->assertFileIsReadable($staged_installed);
-    copy($active_installed, "$this->activeDir/vendor/composer/installed.json");
+
     $listener = function (PreApplyEvent $event) use ($staged_installed): void {
       $stage_dir = $event->getStage()->getStageDirectory();
       copy($staged_installed, $stage_dir . "/vendor/composer/installed.json");
     };
     $this->container->get('event_dispatcher')->addListener(PreApplyEvent::class, $listener, 1000);
-    $this->assertUpdateResults([], $expected_results, PreApplyEvent::class);
+    $this->assertUpdateResults(['my_module' => '9.8.1'], $expected_results, PreApplyEvent::class);
   }
 
 }
diff --git a/package_manager/tests/modules/package_manager_bypass/src/Beginner.php b/package_manager/tests/modules/package_manager_bypass/src/Beginner.php
index 6509c0fcf1873ab5857ceb92eea2c1ad7c77f308..c746f1322ee0fa4585ee5dfec0bf12653fa76248 100644
--- a/package_manager/tests/modules/package_manager_bypass/src/Beginner.php
+++ b/package_manager/tests/modules/package_manager_bypass/src/Beginner.php
@@ -11,13 +11,14 @@ use PhpTuf\ComposerStager\Domain\Value\PathList\PathListInterface;
 /**
  * Defines an update beginner which doesn't do anything.
  */
-class Beginner extends InvocationRecorderBase implements BeginnerInterface {
+class Beginner extends BypassedStagerServiceBase implements BeginnerInterface {
 
   /**
    * {@inheritdoc}
    */
   public function begin(PathInterface $activeDir, PathInterface $stagingDir, ?PathListInterface $exclusions = NULL, ?ProcessOutputCallbackInterface $callback = NULL, ?int $timeout = ProcessRunnerInterface::DEFAULT_TIMEOUT): void {
     $this->saveInvocationArguments($activeDir, $stagingDir, $exclusions, $timeout);
+    $this->copyFixtureFilesTo($stagingDir);
   }
 
 }
diff --git a/package_manager/tests/modules/package_manager_bypass/src/BypassedStagerServiceBase.php b/package_manager/tests/modules/package_manager_bypass/src/BypassedStagerServiceBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..53015287233e66a1abb5b07709535ca683c3e098
--- /dev/null
+++ b/package_manager/tests/modules/package_manager_bypass/src/BypassedStagerServiceBase.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace Drupal\package_manager_bypass;
+
+use Drupal\Core\State\StateInterface;
+use PhpTuf\ComposerStager\Domain\Value\Path\PathInterface;
+use Symfony\Component\Filesystem\Filesystem;
+
+/**
+ * Records information about method invocations.
+ *
+ * This can be used by functional tests to ensure that the bypassed Composer
+ * Stager services were called as expected. Kernel and unit tests should use
+ * regular mocks instead.
+ */
+abstract class BypassedStagerServiceBase {
+
+  /**
+   * The state service.
+   *
+   * @var \Drupal\Core\State\StateInterface
+   */
+  protected $state;
+
+  /**
+   * The Symfony file system service.
+   *
+   * @var \Symfony\Component\Filesystem\Filesystem
+   */
+  protected $fileSystem;
+
+  /**
+   * Constructs an InvocationRecorderBase object.
+   *
+   * @param \Drupal\Core\State\StateInterface $state
+   *   The state service.
+   * @param \Symfony\Component\Filesystem\Filesystem $file_system
+   *   The Symfony file system service.
+   */
+  public function __construct(StateInterface $state, Filesystem $file_system) {
+    $this->state = $state;
+    $this->fileSystem = $file_system;
+  }
+
+  /**
+   * Sets a path to be mirrored into a destination by the main class method.
+   *
+   * @param string|null $path
+   *   A path to mirror into a destination directory when the main class method
+   *   is called, or NULL to disable.
+   *
+   * @see ::copyFixtureFilesTo()
+   */
+  public static function setFixturePath(?string $path): void {
+    \Drupal::state()->set(static::class . ' fixture', $path);
+  }
+
+  /**
+   * If a fixture path has been set, mirrors it to the given path.
+   *
+   * @param \PhpTuf\ComposerStager\Domain\Value\Path\PathInterface $destination
+   *   The path to which the fixture files should be mirrored.
+   */
+  protected function copyFixtureFilesTo(PathInterface $destination): void {
+    $fixturePath = $this->state->get(static::class . ' fixture');
+
+    if ($fixturePath && is_dir($fixturePath)) {
+      $this->fileSystem->mirror($fixturePath, $destination->resolve(), NULL, [
+        'override' => TRUE,
+        'delete' => TRUE,
+      ]);
+    }
+  }
+
+  /**
+   * Returns the arguments from every invocation of the main class method.
+   *
+   * @return array[]
+   *   The arguments from every invocation of the main class method.
+   */
+  public function getInvocationArguments(): array {
+    return $this->state->get(static::class . ' arguments', []);
+  }
+
+  /**
+   * Records the arguments from an invocation of the main class method.
+   *
+   * @param mixed ...$arguments
+   *   The arguments that the main class method was called with.
+   */
+  protected function saveInvocationArguments(...$arguments): void {
+    $invocations = $this->getInvocationArguments();
+    $invocations[] = $arguments;
+    $this->state->set(static::class . ' arguments', $invocations);
+  }
+
+}
diff --git a/package_manager/tests/modules/package_manager_bypass/src/Committer.php b/package_manager/tests/modules/package_manager_bypass/src/Committer.php
index ea528824a9c4537e4966e664d072c53175572eb3..eb8f50ce640df9b36cd3dcb633251d2175f5f8d5 100644
--- a/package_manager/tests/modules/package_manager_bypass/src/Committer.php
+++ b/package_manager/tests/modules/package_manager_bypass/src/Committer.php
@@ -11,13 +11,24 @@ use PhpTuf\ComposerStager\Domain\Value\PathList\PathListInterface;
 /**
  * Defines an update committer which doesn't do any actual committing.
  */
-class Committer extends InvocationRecorderBase implements CommitterInterface {
+class Committer extends BypassedStagerServiceBase implements CommitterInterface {
 
   /**
    * {@inheritdoc}
    */
   public function commit(PathInterface $stagingDir, PathInterface $activeDir, ?PathListInterface $exclusions = NULL, ?ProcessOutputCallbackInterface $callback = NULL, ?int $timeout = ProcessRunnerInterface::DEFAULT_TIMEOUT): void {
     $this->saveInvocationArguments($stagingDir, $activeDir, $exclusions, $timeout);
+    $this->copyFixtureFilesTo($activeDir);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function setFixturePath(?string $path): void {
+    // We haven't yet encountered a situation where we need the committer to
+    // copy fixture files to the active directory, but when we do, go ahead and
+    // remove this entire method.
+    throw new \BadMethodCallException('This is not implemented yet.');
   }
 
 }
diff --git a/package_manager/tests/modules/package_manager_bypass/src/InvocationRecorderBase.php b/package_manager/tests/modules/package_manager_bypass/src/InvocationRecorderBase.php
deleted file mode 100644
index edc1075a9aaf37fbf3f34835a1ea8a58df2d9bc6..0000000000000000000000000000000000000000
--- a/package_manager/tests/modules/package_manager_bypass/src/InvocationRecorderBase.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-
-namespace Drupal\package_manager_bypass;
-
-/**
- * Records information about method invocations.
- *
- * This can be used by functional tests to ensure that the bypassed Composer
- * Stager services were called as expected. Kernel and unit tests should use
- * regular mocks instead.
- */
-abstract class InvocationRecorderBase {
-
-  /**
-   * Returns the arguments from every invocation of the main class method.
-   *
-   * @return array[]
-   *   The arguments from every invocation of the main class method.
-   */
-  public function getInvocationArguments(): array {
-    return \Drupal::state()->get(static::class, []);
-  }
-
-  /**
-   * Records the arguments from an invocation of the main class method.
-   *
-   * @param mixed ...$arguments
-   *   The arguments that the main class method was called with.
-   */
-  protected function saveInvocationArguments(...$arguments): void {
-    $invocations = $this->getInvocationArguments();
-    $invocations[] = $arguments;
-    \Drupal::state()->set(static::class, $invocations);
-  }
-
-}
diff --git a/package_manager/tests/modules/package_manager_bypass/src/PackageManagerBypassServiceProvider.php b/package_manager/tests/modules/package_manager_bypass/src/PackageManagerBypassServiceProvider.php
index d62221fd1fb361da803384a83475340215e32072..51a0d5d66e73ec0b5ea80dc7b8079fb837596814 100644
--- a/package_manager/tests/modules/package_manager_bypass/src/PackageManagerBypassServiceProvider.php
+++ b/package_manager/tests/modules/package_manager_bypass/src/PackageManagerBypassServiceProvider.php
@@ -5,6 +5,7 @@ namespace Drupal\package_manager_bypass;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\DependencyInjection\ServiceProviderBase;
 use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\Filesystem\Filesystem;
 
 /**
  * Defines services to bypass Package Manager's core functionality.
@@ -17,21 +18,18 @@ class PackageManagerBypassServiceProvider extends ServiceProviderBase {
   public function alter(ContainerBuilder $container) {
     parent::alter($container);
 
-    $container->getDefinition('package_manager.beginner')
-      ->setClass(Beginner::class)
-      ->setArguments([]);
-    $container->getDefinition('package_manager.stager')
-      ->setClass(Stager::class)
-      ->setArguments([]);
-
-    $container->register('package_manager_bypass.committer')
-      ->setClass(Committer::class)
-      ->setPublic(FALSE)
-      ->setDecoratedService('package_manager.committer')
-      ->setArguments([
-        new Reference('package_manager_bypass.committer.inner'),
-      ])
-      ->setProperty('_serviceId', 'package_manager.committer');
+    $services = [
+      'package_manager.beginner' => Beginner::class,
+      'package_manager.stager' => Stager::class,
+      'package_manager.committer' => Committer::class,
+    ];
+    $arguments = [
+      new Reference('state'),
+      new Reference(Filesystem::class),
+    ];
+    foreach ($services as $id => $class) {
+      $container->getDefinition($id)->setClass($class)->setArguments($arguments);
+    }
   }
 
 }
diff --git a/package_manager/tests/modules/package_manager_bypass/src/Stager.php b/package_manager/tests/modules/package_manager_bypass/src/Stager.php
index 38a67fb3e7e6e21591691040393831efdbe75084..b6adfaf835b6bd714b702f47316795c35f83652a 100644
--- a/package_manager/tests/modules/package_manager_bypass/src/Stager.php
+++ b/package_manager/tests/modules/package_manager_bypass/src/Stager.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\package_manager_bypass;
 
+use Composer\Json\JsonFile;
 use PhpTuf\ComposerStager\Domain\Core\Stager\StagerInterface;
 use PhpTuf\ComposerStager\Domain\Service\ProcessOutputCallback\ProcessOutputCallbackInterface;
 use PhpTuf\ComposerStager\Domain\Service\ProcessRunner\ProcessRunnerInterface;
@@ -10,13 +11,36 @@ use PhpTuf\ComposerStager\Domain\Value\Path\PathInterface;
 /**
  * Defines an update stager which doesn't actually do anything.
  */
-class Stager extends InvocationRecorderBase implements StagerInterface {
+class Stager extends BypassedStagerServiceBase implements StagerInterface {
 
   /**
    * {@inheritdoc}
    */
   public function stage(array $composerCommand, PathInterface $activeDir, PathInterface $stagingDir, ?ProcessOutputCallbackInterface $callback = NULL, ?int $timeout = ProcessRunnerInterface::DEFAULT_TIMEOUT): void {
     $this->saveInvocationArguments($composerCommand, $stagingDir);
+    $this->copyFixtureFilesTo($stagingDir);
+
+    // If desired, simulate a change to the lock file (e.g., as a result of
+    // running `composer update`).
+    $lockFile = new JsonFile($stagingDir->resolve() . '/composer.lock');
+    $changeLockFile = $this->state->get(static::class . ' lock', TRUE);
+
+    if ($changeLockFile && $lockFile->exists()) {
+      $data = $lockFile->read();
+      $data['_time'] = microtime();
+      $lockFile->write($data);
+    }
+  }
+
+  /**
+   * Sets whether or not ::stage() should simulate a change in the lock file.
+   *
+   * @param bool $value
+   *   (optional) Whether or not to simulate a change in the lock file when
+   *   ::stage() is called. Defaults to TRUE.
+   */
+  public static function setLockFileShouldChange(bool $value = TRUE): void {
+    \Drupal::state()->set(static::class . ' lock', $value);
   }
 
 }
diff --git a/package_manager/tests/modules/package_manager_test_fixture/package_manager_test_fixture.info.yml b/package_manager/tests/modules/package_manager_test_fixture/package_manager_test_fixture.info.yml
deleted file mode 100644
index aaea8bb04c56bfe89613cf558d1b26ca64c361ee..0000000000000000000000000000000000000000
--- a/package_manager/tests/modules/package_manager_test_fixture/package_manager_test_fixture.info.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-name: 'Package Manager Test Fixture'
-description: 'Provides a mechanism for functional tests to stage fixture files.'
-type: module
-package: Testing
-dependencies:
-  - automatic_updates:package_manager
diff --git a/package_manager/tests/modules/package_manager_test_fixture/package_manager_test_fixture.services.yml b/package_manager/tests/modules/package_manager_test_fixture/package_manager_test_fixture.services.yml
deleted file mode 100644
index 394a1bb9d254e50d3fe97bae6904e100503dd2ac..0000000000000000000000000000000000000000
--- a/package_manager/tests/modules/package_manager_test_fixture/package_manager_test_fixture.services.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-services:
-  package_manager_test_fixture.stager:
-    class: Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager
-    arguments:
-      - '@state'
-      - '@Symfony\Component\Filesystem\Filesystem'
-    tags:
-      - { name: event_subscriber }
diff --git a/package_manager/tests/modules/package_manager_test_fixture/src/EventSubscriber/FixtureStager.php b/package_manager/tests/modules/package_manager_test_fixture/src/EventSubscriber/FixtureStager.php
deleted file mode 100644
index fd5ecb712492fea746fbf5d1c204e279d9b1038e..0000000000000000000000000000000000000000
--- a/package_manager/tests/modules/package_manager_test_fixture/src/EventSubscriber/FixtureStager.php
+++ /dev/null
@@ -1,106 +0,0 @@
-<?php
-
-namespace Drupal\package_manager_test_fixture\EventSubscriber;
-
-use Drupal\Core\State\StateInterface;
-use Drupal\package_manager\Event\PostRequireEvent;
-use Symfony\Component\EventDispatcher\EventSubscriberInterface;
-use Symfony\Component\Filesystem\Filesystem;
-
-/**
- * Defines an event subscriber which copies certain files into the staging area.
- *
- * This is most useful in conjunction with package_manager_bypass, which quietly
- * turns all Composer Stager operations into no-ops. In such cases, no staging
- * area will be physically created, but if a test needs to simulate certain
- * conditions in a staging area without actually staging the active code base,
- * this event subscriber is the way to do it.
- */
-class FixtureStager implements EventSubscriberInterface {
-
-  /**
-   * The state service.
-   *
-   * @var \Drupal\Core\State\StateInterface
-   */
-  protected $state;
-
-  /**
-   * The Symfony file system service.
-   *
-   * @var \Symfony\Component\Filesystem\Filesystem
-   */
-  protected $fileSystem;
-
-  /**
-   * Constructs a FixtureStager.
-   *
-   * @param \Drupal\Core\State\StateInterface $state
-   *   The state service.
-   * @param \Symfony\Component\Filesystem\Filesystem $file_system
-   *   The Symfony file system service.
-   */
-  public function __construct(StateInterface $state, Filesystem $file_system) {
-    $this->state = $state;
-    $this->fileSystem = $file_system;
-  }
-
-  /**
-   * Copies files from a fixture into the staging area.
-   *
-   * Tests which use this functionality are responsible for cleaning up the
-   * staging area.
-   *
-   * @param \Drupal\package_manager\Event\PostRequireEvent $event
-   *   The event object.
-   *
-   * @see \Drupal\Tests\automatic_updates\Functional\AutomaticUpdatesFunctionalTestBase::tearDown()
-   */
-  public function copyFilesFromFixture(PostRequireEvent $event): void {
-    [$fixturePath, $changeLock] = $this->state->get(static::class);
-
-    if ($fixturePath && is_dir($fixturePath)) {
-      $destination = $event->getStage()->getStageDirectory();
-
-      $this->fileSystem->mirror($fixturePath, $destination, NULL, [
-        'override' => TRUE,
-        'delete' => TRUE,
-      ]);
-
-      // Modify the lock file in the staging area, to simulate that a package
-      // was added, updated, or removed. Otherwise, tests must remember to
-      // disable the lock file validator.
-      // @see \Drupal\package_manager\Validator\LockFileValidator
-      $lock = $destination . '/composer.lock';
-      if ($changeLock && file_exists($lock)) {
-        $data = file_get_contents($lock);
-        $data = json_decode($data);
-        $data->_time = microtime();
-        file_put_contents($lock, json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
-      }
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function getSubscribedEvents() {
-    return [
-      PostRequireEvent::class => 'copyFilesFromFixture',
-    ];
-  }
-
-  /**
-   * Sets the path of the fixture to copy into the staging area.
-   *
-   * @param string $path
-   *   The path of the fixture to copy into the staging area.
-   * @param bool $change_lock
-   *   (optional) Whether to change the lock file, in order to simulate the
-   *   addition, updating, or removal of a package. Defaults to TRUE.
-   */
-  public static function setFixturePath(string $path, bool $change_lock = TRUE): void {
-    \Drupal::state()->set(static::class, [$path, $change_lock]);
-  }
-
-}
diff --git a/package_manager/tests/src/Kernel/LockFileValidatorTest.php b/package_manager/tests/src/Kernel/LockFileValidatorTest.php
index 803ca066ab8bbdfafd88b22311881b16876cf00d..02905e82758cb1a7591fb845dffc48ecb0aa8835 100644
--- a/package_manager/tests/src/Kernel/LockFileValidatorTest.php
+++ b/package_manager/tests/src/Kernel/LockFileValidatorTest.php
@@ -7,7 +7,7 @@ use Drupal\package_manager\Event\PreCreateEvent;
 use Drupal\package_manager\Event\PreRequireEvent;
 use Drupal\package_manager\Validator\LockFileValidator;
 use Drupal\package_manager\ValidationResult;
-use Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager;
+use Drupal\package_manager_bypass\Stager;
 
 /**
  * @coversDefaultClass \Drupal\package_manager\Validator\LockFileValidator
@@ -123,10 +123,8 @@ class LockFileValidatorTest extends PackageManagerKernelTestBase {
    * Tests validation when the staged and active lock files are identical.
    */
   public function testApplyWithNoChange(): void {
-    // Ensure the lock file is not changed when the active directory is copied
-    // into the virtual staging area.
-    // @see \Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager
-    FixtureStager::setFixturePath($this->activeDir, FALSE);
+    // Leave the staged lock file alone.
+    Stager::setLockFileShouldChange(FALSE);
 
     $result = ValidationResult::createError([
       'There are no pending Composer operations.',
diff --git a/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php b/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php
index 3d42bf13bb4832f8fb03ae04ba0b8d8f1740b8c8..281ce40da27cb21b1ded340a9784132ee4ddab0c 100644
--- a/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php
+++ b/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php
@@ -10,7 +10,7 @@ use Drupal\package_manager\Exception\StageException;
 use Drupal\package_manager\Exception\StageValidationException;
 use Drupal\package_manager\PathLocator;
 use Drupal\package_manager\Stage;
-use Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager;
+use Drupal\package_manager_bypass\Beginner;
 use Drupal\Tests\package_manager\Traits\ValidationTestTrait;
 use org\bovigo\vfs\vfsStream;
 use org\bovigo\vfs\vfsStreamDirectory;
@@ -34,7 +34,6 @@ abstract class PackageManagerKernelTestBase extends KernelTestBase {
   protected static $modules = [
     'package_manager',
     'package_manager_bypass',
-    'package_manager_test_fixture',
   ];
 
   /**
@@ -213,9 +212,8 @@ abstract class PackageManagerKernelTestBase extends KernelTestBase {
     $active_dir = $active_dir->url();
     $path_locator = $this->mockPathLocator($active_dir);
 
-    // Ensure that the active directory is copied into the virtual staging area,
-    // even if Package Manager's operations are bypassed.
-    FixtureStager::setFixturePath($active_dir);
+    // Ensure the active directory will be copied into the virtual staging area.
+    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
@@ -289,11 +287,23 @@ trait TestStageTrait {
    */
   public function __construct(...$arguments) {
     parent::__construct(...$arguments);
-    $mirror = new \ReflectionClass(Stage::class);
-    $this->tempStore->set($mirror->getConstant('TEMPSTORE_STAGING_ROOT_KEY'), PackageManagerKernelTestBase::$testStagingRoot);
     $this->pathFactory = new TestPathFactory();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function create(?int $timeout = 300): string {
+    // Ensure that tests which need to successively create multiple stages are
+    // always using the same staging root, since the stored value may be deleted
+    // if the stage encounters an error during pre-create.
+    // @see \Drupal\package_manager\Stage::markAsAvailable()
+    $constant = new \ReflectionClassConstant(Stage::class, 'TEMPSTORE_STAGING_ROOT_KEY');
+    $this->tempStore->set($constant->getValue(), PackageManagerKernelTestBase::$testStagingRoot);
+
+    return parent::create($timeout);
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/package_manager/tests/src/Kernel/PathExcluder/GitExcluderTest.php b/package_manager/tests/src/Kernel/PathExcluder/GitExcluderTest.php
index 24ca0cb409f4e6505f9fa7f04b1537eacee8ac10..36007e400db4b7749cce6b785ce5f8ec9d122bf3 100644
--- a/package_manager/tests/src/Kernel/PathExcluder/GitExcluderTest.php
+++ b/package_manager/tests/src/Kernel/PathExcluder/GitExcluderTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\package_manager\Kernel\PathExcluder;
 
+use Drupal\package_manager_bypass\Beginner;
 use Drupal\Tests\package_manager\Kernel\PackageManagerKernelTestBase;
 
 /**
@@ -37,6 +38,11 @@ class GitExcluderTest extends PackageManagerKernelTestBase {
     mkdir($unreadable_dir, 0000);
     $this->assertDirectoryIsNotReadable($unreadable_dir);
 
+    // Don't mirror the active directory into the virtual staging area, since
+    // the active directory contains an unreadable directory which will cause
+    // an exception.
+    Beginner::setFixturePath(NULL);
+
     $this->createStage()->create();
   }
 
diff --git a/package_manager/tests/src/Kernel/StageOwnershipTest.php b/package_manager/tests/src/Kernel/StageOwnershipTest.php
index 21011a7578fe58bef8e4d54ee931dab2701f8d6a..ccb4073793dcaa3e8eef3472b4ca3bcca127aea0 100644
--- a/package_manager/tests/src/Kernel/StageOwnershipTest.php
+++ b/package_manager/tests/src/Kernel/StageOwnershipTest.php
@@ -8,6 +8,7 @@ use Drupal\package_manager\Event\PostDestroyEvent;
 use Drupal\package_manager\Event\PreCreateEvent;
 use Drupal\package_manager\Exception\StageException;
 use Drupal\package_manager\Exception\StageOwnershipException;
+use Drupal\package_manager_bypass\Stager;
 use Drupal\package_manager_test_validation\EventSubscriber\TestSubscriber;
 use Drupal\Tests\user\Traits\UserCreationTrait;
 use Psr\Log\Test\TestLogger;
@@ -180,6 +181,12 @@ class StageOwnershipTest extends PackageManagerKernelTestBase {
       'postApply' => [],
       'destroy' => [],
     ];
+    // Since we deliberately don't call create() on the stages we create as
+    // we loop through the life cycle methods, ensure that the active directory
+    // is mirrored into the staging area when a package is required.
+    $active_dir = $this->container->get('package_manager.path_locator')
+      ->getProjectRoot();
+    Stager::setFixturePath($active_dir);
     foreach ($callbacks as $method => $arguments) {
       // Create a new stage instance for each method.
       $this->createStage()->claim($stage_id)->$method(...$arguments);
diff --git a/package_manager/tests/src/Kernel/StageTest.php b/package_manager/tests/src/Kernel/StageTest.php
index 76c5d8a86c9314962968c9cf29355cc1fc9f9f11..ac3e5c104d623437353ab34cbe184838415e991a 100644
--- a/package_manager/tests/src/Kernel/StageTest.php
+++ b/package_manager/tests/src/Kernel/StageTest.php
@@ -11,6 +11,7 @@ use Drupal\package_manager\Event\PostApplyEvent;
 use Drupal\package_manager\Event\PreApplyEvent;
 use Drupal\package_manager\Event\StageEvent;
 use Drupal\package_manager\Exception\StageException;
+use Drupal\package_manager_bypass\Beginner;
 
 /**
  * @coversDefaultClass \Drupal\package_manager\Stage
@@ -30,10 +31,6 @@ class StageTest extends PackageManagerKernelTestBase {
    * {@inheritdoc}
    */
   protected function setUp(): void {
-    // Disable the symlink validator, since this test doesn't use a virtual
-    // project, but the running code base may have symlinks that don't affect
-    // the test.
-    $this->disableValidators[] = 'package_manager.validator.symlink';
     parent::setUp();
 
     $this->installConfig('system');
@@ -70,6 +67,9 @@ class StageTest extends PackageManagerKernelTestBase {
     // Even though we're using a virtual project, we want to test what happens
     // when we aren't.
     static::$testStagingRoot = NULL;
+    // Don't mirror the active directory from the virtual project into the
+    // real file system.
+    Beginner::setFixturePath(NULL);
 
     $stage = $this->createStage();
     $id = $stage->create();
@@ -191,6 +191,7 @@ class StageTest extends PackageManagerKernelTestBase {
 
     $stage = $this->createStage();
     $stage->create();
+    $stage->require(['ext-json:*']);
     if ($expect_exception) {
       $this->expectException(StageException::class);
       $this->expectExceptionMessage('Cannot destroy the staging area while it is being applied to the active directory.');
@@ -230,6 +231,7 @@ class StageTest extends PackageManagerKernelTestBase {
 
     $stage = $this->createStage();
     $stage->create();
+    $stage->require(['ext-json:*']);
     $stage->apply();
   }
 
@@ -239,6 +241,7 @@ class StageTest extends PackageManagerKernelTestBase {
   public function testTimeouts(): void {
     $stage = $this->createStage();
     $stage->create(420);
+    $stage->require(['ext-json:*']);
     $stage->apply();
 
     $timeouts = [
diff --git a/package_manager/tests/src/Kernel/SymlinkValidatorTest.php b/package_manager/tests/src/Kernel/SymlinkValidatorTest.php
index 5e462dd444207ee917327bd0e45dd8bb96305109..14b7f0592d5798ebfb7facf8d43f16fce2aa17b5 100644
--- a/package_manager/tests/src/Kernel/SymlinkValidatorTest.php
+++ b/package_manager/tests/src/Kernel/SymlinkValidatorTest.php
@@ -56,10 +56,6 @@ class SymlinkValidatorTest extends PackageManagerKernelTestBase {
 
     $stage = $this->createStage();
     $stage->create();
-    // Simulate updating a package. This will copy the active directory into
-    // the (virtual) staging area.
-    // @see ::createVirtualProject()
-    // @see \Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager::copyFilesFromFixture()
     $stage->require(['composer/semver:^3']);
 
     // @see \Drupal\Tests\package_manager\Kernel\TestSymlinkValidator::isLink()
@@ -89,10 +85,6 @@ class SymlinkValidatorTest extends PackageManagerKernelTestBase {
 
     $stage = $this->createStage();
     $stage->create();
-    // Simulate updating a package. This will copy the active directory into
-    // the (virtual) staging area.
-    // @see ::createVirtualProject()
-    // @see \Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager::copyFilesFromFixture()
     $stage->require(['composer/semver:^3']);
 
     $active_dir = $this->container->get('package_manager.path_locator')
diff --git a/package_manager/tests/src/Traits/PackageManagerBypassTestTrait.php b/package_manager/tests/src/Traits/PackageManagerBypassTestTrait.php
index 3d80b39d74211adfa1726c2d53f460d3f52fd430..0c1f93fde723c9c379ab1d831c1ac2a90be2264f 100644
--- a/package_manager/tests/src/Traits/PackageManagerBypassTestTrait.php
+++ b/package_manager/tests/src/Traits/PackageManagerBypassTestTrait.php
@@ -14,18 +14,18 @@ trait PackageManagerBypassTestTrait {
    *   The expected number of times an update was staged.
    */
   private function assertUpdateStagedTimes(int $attempted_times): void {
-    /** @var \Drupal\package_manager_bypass\InvocationRecorderBase $beginner */
+    /** @var \Drupal\package_manager_bypass\BypassedStagerServiceBase $beginner */
     $beginner = $this->container->get('package_manager.beginner');
     $this->assertCount($attempted_times, $beginner->getInvocationArguments());
 
-    /** @var \Drupal\package_manager_bypass\InvocationRecorderBase $stager */
+    /** @var \Drupal\package_manager_bypass\BypassedStagerServiceBase $stager */
     $stager = $this->container->get('package_manager.stager');
     // If an update was attempted, then there will be two calls to the stager:
     // one to change the constraints in composer.json, and another to actually
     // update the installed dependencies.
     $this->assertCount($attempted_times * 2, $stager->getInvocationArguments());
 
-    /** @var \Drupal\package_manager_bypass\InvocationRecorderBase $committer */
+    /** @var \Drupal\package_manager_bypass\BypassedStagerServiceBase $committer */
     $committer = $this->container->get('package_manager.committer');
     $this->assertEmpty($committer->getInvocationArguments());
   }
diff --git a/tests/src/Functional/AvailableUpdatesReportTest.php b/tests/src/Functional/AvailableUpdatesReportTest.php
index 2052512d4bc88e687afaac86667f8fb62c12a270..327a3fe351f9857a0895ba46b7a1113719b604ff 100644
--- a/tests/src/Functional/AvailableUpdatesReportTest.php
+++ b/tests/src/Functional/AvailableUpdatesReportTest.php
@@ -23,7 +23,6 @@ class AvailableUpdatesReportTest extends AutomaticUpdatesFunctionalTestBase {
     'block',
     'automatic_updates',
     'automatic_updates_test',
-    'package_manager_test_fixture',
   ];
 
   /**
diff --git a/tests/src/Functional/ReadinessValidationTest.php b/tests/src/Functional/ReadinessValidationTest.php
index 942d28f22be0b87c488879446a026900df4b5cd1..ef351dee02bf33165fc486cd1d36aa0a4e8f430f 100644
--- a/tests/src/Functional/ReadinessValidationTest.php
+++ b/tests/src/Functional/ReadinessValidationTest.php
@@ -8,7 +8,7 @@ use Drupal\automatic_updates_test\Datetime\TestTime;
 use Drupal\automatic_updates_test\EventSubscriber\TestSubscriber1;
 use Drupal\automatic_updates_test2\EventSubscriber\TestSubscriber2;
 use Drupal\Core\Url;
-use Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager;
+use Drupal\package_manager_bypass\Stager;
 use Drupal\system\SystemManager;
 use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait;
 use Drupal\Tests\Traits\Core\CronRunTrait;
@@ -399,7 +399,6 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase {
     $this->container->get('module_installer')->install([
       'automatic_updates',
       'automatic_updates_test',
-      'package_manager_test_fixture',
     ]);
     // Because all actual staging operations are bypassed by
     // package_manager_bypass (enabled by the parent class), disable these
@@ -423,7 +422,7 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase {
     // readiness check (without storing the results), and the checker is no
     // longer raising an error.
     $this->drupalGet('/admin/modules/automatic-update');
-    FixtureStager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1');
+    Stager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1');
     $assert_session->buttonExists('Update');
     // Ensure that the previous results are still displayed on another admin
     // page, to confirm that the updater form is not discarding the previous
diff --git a/tests/src/Functional/UpdateLockTest.php b/tests/src/Functional/UpdateLockTest.php
index df5d5ad8ed952468362bd8ffc271df41acb2ea1e..3b0df121b482407415616967164432dd8600b477 100644
--- a/tests/src/Functional/UpdateLockTest.php
+++ b/tests/src/Functional/UpdateLockTest.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\Tests\automatic_updates\Functional;
 
-use Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager;
+use Drupal\package_manager_bypass\Stager;
 
 /**
  * Tests that only one Automatic Update operation can be performed at a time.
@@ -22,7 +22,6 @@ class UpdateLockTest extends AutomaticUpdatesFunctionalTestBase {
   protected static $modules = [
     'automatic_updates',
     'automatic_updates_test',
-    'package_manager_test_fixture',
   ];
 
   /**
@@ -53,7 +52,7 @@ class UpdateLockTest extends AutomaticUpdatesFunctionalTestBase {
     // We should be able to get partway through an update without issue.
     $this->drupalLogin($user_1);
     $this->drupalGet('/admin/modules/automatic-update');
-    FixtureStager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1');
+    Stager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1');
     $page->pressButton('Update');
     $this->checkForMetaRefresh();
     $this->assertUpdateReady('9.8.1');
diff --git a/tests/src/Functional/UpdaterFormTest.php b/tests/src/Functional/UpdaterFormTest.php
index 3cb2c6f8aff3ecab29eaf58a8fd3f090cabcd6b6..809beccce8ed9349fe40385c807f71eb38252d40 100644
--- a/tests/src/Functional/UpdaterFormTest.php
+++ b/tests/src/Functional/UpdaterFormTest.php
@@ -9,7 +9,7 @@ use Drupal\package_manager\Event\PreApplyEvent;
 use Drupal\package_manager\Event\PreCreateEvent;
 use Drupal\package_manager\ValidationResult;
 use Drupal\automatic_updates_test\EventSubscriber\TestSubscriber1;
-use Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager;
+use Drupal\package_manager_bypass\Stager;
 use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait;
 use Drupal\Tests\package_manager\Traits\PackageManagerBypassTestTrait;
 
@@ -35,7 +35,6 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
     'block',
     'automatic_updates',
     'automatic_updates_test',
-    'package_manager_test_fixture',
   ];
 
   /**
@@ -336,7 +335,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
     $this->checkForUpdates();
 
     $this->drupalGet('/admin/modules/automatic-update');
-    FixtureStager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1');
+    Stager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1');
     $page->pressButton('Update to 9.8.1');
     $this->checkForMetaRefresh();
     $this->assertUpdateStagedTimes(1);
@@ -471,7 +470,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
 
     $page = $this->getSession()->getPage();
     $this->drupalGet('/admin/modules/automatic-update');
-    FixtureStager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1');
+    Stager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1');
     // The warning should be visible.
     $assert_session = $this->assertSession();
     $assert_session->pageTextContains(reset($messages));
@@ -572,7 +571,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
     $cached_message = $this->setAndAssertCachedMessage();
 
     $this->drupalGet($update_form_url);
-    FixtureStager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1');
+    Stager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1');
     $assert_session->pageTextNotContains($cached_message);
     $page->pressButton('Update to 9.8.1');
     $this->checkForMetaRefresh();
@@ -601,7 +600,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
 
     $page = $this->getSession()->getPage();
     $this->drupalGet('/admin/modules/automatic-update');
-    FixtureStager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1');
+    Stager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1');
     $page->pressButton('Update to 9.8.1');
     $this->checkForMetaRefresh();
     $this->assertUpdateStagedTimes(1);
diff --git a/tests/src/Kernel/CronUpdaterTest.php b/tests/src/Kernel/CronUpdaterTest.php
index 115f024f87bde49478fa0d39e17d9c1ca6cd2892..7133ec51d72d863560802ea7a6e4ed3f0026829e 100644
--- a/tests/src/Kernel/CronUpdaterTest.php
+++ b/tests/src/Kernel/CronUpdaterTest.php
@@ -54,15 +54,6 @@ class CronUpdaterTest extends AutomaticUpdatesKernelTestBase {
    * {@inheritdoc}
    */
   protected function setUp(): void {
-    // Because package_manager_bypass is enabled, a staging directory will not
-    // actually exist. Therefore, we need to disable these validators because
-    // they attempt to compare the active and stage directories.
-    $this->disableValidators[] = 'automatic_updates.validator.staged_database_updates';
-    $this->disableValidators[] = 'automatic_updates.staged_projects_validator';
-    $this->disableValidators[] = 'automatic_updates.validator.scaffold_file_permissions';
-    // Since staging operations are bypassed, ignore any symbolic links in the
-    // running code base.
-    $this->disableValidators[] = 'package_manager.validator.symlink';
     parent::setUp();
 
     $this->logger = new TestLogger();
diff --git a/tests/src/Kernel/ReadinessValidation/ScaffoldFilePermissionsValidatorTest.php b/tests/src/Kernel/ReadinessValidation/ScaffoldFilePermissionsValidatorTest.php
index 8f4e0530c8b6f01e61c3a5be00c480e5430e32bf..396fb0cd5e82510bc227771f543f79d2d424e7b4 100644
--- a/tests/src/Kernel/ReadinessValidation/ScaffoldFilePermissionsValidatorTest.php
+++ b/tests/src/Kernel/ReadinessValidation/ScaffoldFilePermissionsValidatorTest.php
@@ -296,10 +296,6 @@ class ScaffoldFilePermissionsValidatorTest extends AutomaticUpdatesKernelTestBas
     touch($this->activeDir . '/sites/default/deleted.txt');
     touch($this->activeDir . '/foo.txt');
 
-    // Simulate updating Drupal core. This will copy the active directory into
-    // the (virtual) staging area.
-    // @see ::createVirtualProject()
-    // @see \Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager::copyFilesFromFixture()
     $updater = $this->container->get('automatic_updates.updater');
     $updater->begin(['drupal' => '9.8.1']);
     $updater->stage();