diff --git a/core/scripts/setup-drupal-test.php b/core/scripts/setup-drupal-test.php
new file mode 100644
index 0000000000..94e6b9c2d3
--- /dev/null
+++ b/core/scripts/setup-drupal-test.php
@@ -0,0 +1,21 @@
+#!/usr/bin/env php
+<?php
+
+/**
+ * @file
+ * A command line application to install drupal for tests.
+ */
+
+use Drupal\Setup\Commands\TestInstallationSetupApplication;
+
+if (PHP_SAPI !== 'cli') {
+  return;
+}
+
+// Bootstrap.
+$autoloader = require __DIR__ . '/../../autoload.php';
+require_once __DIR__ . '/../tests/bootstrap.php';
+
+$app = new TestInstallationSetupApplication();
+$app->setAutoloader($autoloader);
+$app->run();
diff --git a/core/tests/Drupal/KernelTests/Setup/Commands/SetupDrupalTestScript.php b/core/tests/Drupal/KernelTests/Setup/Commands/SetupDrupalTestScript.php
new file mode 100644
index 0000000000..e7b26d784f
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Setup/Commands/SetupDrupalTestScript.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Test\Setup\Commands;
+
+use Drupal\Setup\TestSetupInterface;
+
+/**
+ * Tests setup-drupal-test.php.
+ *
+ * @group core
+ */
+class SetupDrupalTestScript implements TestSetupInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setup() {
+    \Drupal::service('module_installer')->install(['test_page_test']);
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Setup/Commands/SetupDrupalTestScriptTest.php b/core/tests/Drupal/KernelTests/Setup/Commands/SetupDrupalTestScriptTest.php
new file mode 100644
index 0000000000..668439cf17
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Setup/Commands/SetupDrupalTestScriptTest.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Drupal\KernelTests\Setup\Commands;
+
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\Setup\Commands\TestInstallationSetupApplication;
+use Symfony\Component\Console\Tester\ApplicationTester;
+
+/**
+ * Tests setup-drupal-test.php.
+ *
+ * @group Commands
+ * @group core
+ *
+ * @todo Move this to the \Drupal\KernelTests\Setup\Commands\ namespace after
+ *   https://www.drupal.org/project/drupal/issues/2878269
+ */
+class SetupDrupalTestScriptTest extends KernelTestBase {
+
+  /**
+   * @coversNothing
+   */
+  public function testInstallScript() {
+    $autoloader = dirname(dirname(dirname(__DIR__))) . '/bootstrap.php';
+    $app = new TestInstallationSetupApplication($autoloader);
+    $app->setAutoExit(FALSE);
+
+    $app_tester = new ApplicationTester($app);
+    $app_tester->run(
+      ['command' => 'setup-drupal-test'],
+      [
+        'interactive' => FALSE,
+        'setup_file' => __DIR__ . '/SetupDrupalTestScript.php',
+      ]
+    );
+
+    $this->assertNotRegExp('/PHPUnit_Framework_Error_Warning/', $app_tester->getDisplay());
+    $this->assertNotRegExp('/AlreadyInstalledException/', $app_tester->getDisplay());
+    $this->assertRegExp('/simpletest/', $app_tester->getDisplay());
+    $this->assertEqual(0, $app_tester->getStatusCode());
+  }
+
+}
diff --git a/core/tests/Drupal/Setup/Commands/TestInstallationSetupApplication.php b/core/tests/Drupal/Setup/Commands/TestInstallationSetupApplication.php
new file mode 100644
index 0000000000..94b42c6ec9
--- /dev/null
+++ b/core/tests/Drupal/Setup/Commands/TestInstallationSetupApplication.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Drupal\Setup\Commands;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Input\InputInterface;
+
+/**
+ * Application wrapper for TestInstallationSetupCommand.
+ *
+ * @internal
+ */
+class TestInstallationSetupApplication extends Application {
+
+  protected $autoloader;
+
+  /**
+   * SetupDrupalApplication constructor.
+   */
+  public function __construct() {
+    parent::__construct('setup-drupal-test', '0.0.1');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getCommandName(InputInterface $input) {
+    return 'setup-drupal-test';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDefaultCommands() {
+    // Even though this is a single command, keep the HelpCommand (--help).
+    $default_commands = parent::getDefaultCommands();
+    $default_commands[] = new TestInstallationSetupCommand();
+    return $default_commands;
+  }
+
+  public function setAutoloader($autoloader) {
+    $this->autoloader = $autoloader;
+    return $this;
+  }
+
+  public function getAutoloader() {
+    return $this->autoloader;
+  }
+
+}
diff --git a/core/tests/Drupal/Setup/Commands/TestInstallationSetupCommand.php b/core/tests/Drupal/Setup/Commands/TestInstallationSetupCommand.php
new file mode 100644
index 0000000000..49fdafa1e2
--- /dev/null
+++ b/core/tests/Drupal/Setup/Commands/TestInstallationSetupCommand.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Drupal\Setup\Commands;
+
+use Drupal\Core\DrupalKernel;
+use Drupal\Core\Site\Settings;
+use Drupal\Setup\TestInstallationSetup;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Symfony console command to setup Drupal.
+ *
+ * @internal
+ */
+class TestInstallationSetupCommand extends Command {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function configure() {
+    $this->setName('setup-drupal-test')
+      ->addOption('setup_file', NULL, InputOption::VALUE_OPTIONAL)
+      ->addOption('db_url', NULL, InputOption::VALUE_OPTIONAL, 'URL for database or SIMPLETEST_DB', getenv('SIMPLETEST_DB'))
+      ->addOption('base_url', NULL, InputOption::VALUE_OPTIONAL, 'Base URL for site under test or SIMPLETEST_BASE_URL', getenv('SIMPLETEST_BASE_URL'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function execute(InputInterface $input, OutputInterface $output) {
+    $db_url = $input->getOption('db_url');
+    $base_url = $input->getOption('base_url');
+    putenv("SIMPLETEST_DB=$db_url");
+    putenv("SIMPLETEST_BASE_URL=$base_url");
+
+    $this->bootstrapDrupal($this->getApplication()->getAutoloader());
+
+    // Manage site fixture.
+    $test = new TestInstallationSetup();
+    $test->setup('testing', $input->getOption('setup_file'));
+
+    $output->writeln(drupal_generate_test_ua($test->getDatabasePrefix()));
+  }
+
+  protected function bootstrapDrupal($autoloader) {
+    $request = Request::createFromGlobals();
+    $kernel = DrupalKernel::createFromRequest($request, $autoloader, $this->getApplication()->getName());
+    DrupalKernel::bootEnvironment($kernel->getAppRoot());
+
+    Settings::initialize(
+      dirname(dirname(dirname(dirname(__DIR__)))),
+      DrupalKernel::findSitePath($request),
+      $autoloader
+    );
+  }
+
+}
diff --git a/core/tests/Drupal/Setup/ExampleTestSetup.php b/core/tests/Drupal/Setup/ExampleTestSetup.php
new file mode 100644
index 0000000000..ed94c18778
--- /dev/null
+++ b/core/tests/Drupal/Setup/ExampleTestSetup.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Drupal\Setup;
+
+use Drupal\node\Entity\Node;
+
+/**
+ * Example test setup file.
+ */
+class ExampleTestSetup implements TestSetupInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setup() {
+    \Drupal::service('module_installer')->install(['node']);
+
+    Node::create(['type' => 'page', 'title' => 'Test tile'])
+      ->save();
+  }
+
+}
diff --git a/core/tests/Drupal/Setup/TestInstallationSetup.php b/core/tests/Drupal/Setup/TestInstallationSetup.php
new file mode 100644
index 0000000000..714b2815e4
--- /dev/null
+++ b/core/tests/Drupal/Setup/TestInstallationSetup.php
@@ -0,0 +1,196 @@
+<?php
+
+namespace Drupal\Setup;
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Test\FunctionalTestSetupTrait;
+use Drupal\Core\Test\TestSetupTrait;
+use Drupal\Tests\RandomGeneratorTrait;
+use Drupal\Tests\SessionTestTrait;
+
+/**
+ * Provides a class used by setup-drupal-test.php to install Drupal for tests.
+ *
+ * @internal
+ */
+class TestInstallationSetup {
+
+  use FunctionalTestSetupTrait;
+  use RandomGeneratorTrait;
+  use SessionTestTrait;
+  use TestSetupTrait;
+
+  /**
+   * The install profile to use.
+   *
+   * @var string
+   */
+  protected $profile;
+
+  /**
+   * Time limit in seconds for the test.
+   *
+   * @var int
+   */
+  protected $timeLimit = 500;
+
+  /**
+   * The database prefix of this test run.
+   *
+   * @var string
+   */
+  protected $databasePrefix;
+
+  /**
+   * Creates a test drupal installation.
+   *
+   * @param string $profile
+   *   (optional) The installation profile to use.
+   * @param string $setup_file
+   *   (optional) Setup file. A PHP file to setup configuration used by the
+   *   test.
+   */
+  public function setup($profile = 'testing', $setup_file = NULL) {
+    $this->profile = $profile;
+    $this->setupBaseUrl();
+    $this->prepareEnvironment();
+    $this->installDrupal();
+
+    if ($setup_file) {
+      $this->executeSetupFile($setup_file);
+    }
+  }
+
+  /**
+   * Gets the database prefix.
+   *
+   * @return string
+   */
+  public function getDatabasePrefix() {
+    return $this->databasePrefix;
+  }
+
+  /**
+   * Installs Drupal into the Simpletest site.
+   */
+  protected function installDrupal() {
+    $this->initUserSession();
+    $this->prepareSettings();
+    $this->doInstall();
+    $this->initSettings();
+    $container = $this->initKernel(\Drupal::request());
+    $this->initConfig($container);
+    $this->installModulesFromClassProperty($container);
+    $this->rebuildAll();
+  }
+
+  /**
+   * Uses the setup file to configure Drupal.
+   *
+   * @param string $setup_file
+   *   The setup file.
+   */
+  protected function executeSetupFile($setup_file) {
+    $classes = static::fileGetPhpClasses($setup_file);
+
+    if (empty($classes)) {
+      throw new \InvalidArgumentException(sprintf('You need to define a class implementing \Drupal\Setup\TestSetupInterface'));
+    }
+    if (count($classes) > 1) {
+      throw new \InvalidArgumentException(sprintf('You need to define a single class implementing \Drupal\Setup\TestSetupInterface'));
+    }
+
+    require_once $setup_file;
+
+    if (!is_subclass_of($classes[0], TestSetupInterface::class)) {
+      throw new \InvalidArgumentException(sprintf('You need to define a class implementing \Drupal\Setup\TestSetupInterface'));
+    }
+
+    /** @var \Drupal\Setup\TestSetupInterface $instance */
+    $instance = new $classes[0];
+    $instance->setup();
+  }
+
+  /**
+   * Gets the PHP classes contained in a php file.
+   *
+   * @param string $filepath
+   *   The file path.
+   *
+   * @return string[]
+   *   An array of PHP classes.
+   */
+  protected static function fileGetPhpClasses($filepath) {
+    $php_code = file_get_contents($filepath);
+    $classes = static::extractClassesFromPhp($php_code);
+    return $classes;
+  }
+
+  /**
+   * @param string $php_code
+   *   PHP code to parse.
+   *
+   * @return string[]
+   *   An array of PHP classes.
+   */
+  protected static function extractClassesFromPhp($php_code) {
+    $classes = array();
+    $tokens = token_get_all($php_code);
+    $count = count($tokens);
+    for ($i = 2; $i < $count; $i++) {
+      if ($tokens[$i - 2][0] == T_CLASS
+        && $tokens[$i - 1][0] == T_WHITESPACE
+        && $tokens[$i][0] == T_STRING
+      ) {
+
+        $class_name = $tokens[$i][1];
+        $classes[] = $class_name;
+      }
+    }
+    return $classes;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function installParameters() {
+    $connection_info = Database::getConnectionInfo();
+    $driver = $connection_info['default']['driver'];
+    $connection_info['default']['prefix'] = $connection_info['default']['prefix']['default'];
+    unset($connection_info['default']['driver']);
+    unset($connection_info['default']['namespace']);
+    unset($connection_info['default']['pdo']);
+    unset($connection_info['default']['init_commands']);
+    $parameters = [
+      'interactive' => FALSE,
+      'parameters' => [
+        'profile' => $this->profile,
+        'langcode' => 'en',
+      ],
+      'forms' => [
+        'install_settings_form' => [
+          'driver' => $driver,
+          $driver => $connection_info['default'],
+        ],
+        'install_configure_form' => [
+          'site_name' => 'Drupal',
+          'site_mail' => 'simpletest@example.com',
+          'account' => [
+            'name' => $this->rootUser->name,
+            'mail' => $this->rootUser->getEmail(),
+            'pass' => [
+              'pass1' => $this->rootUser->pass_raw,
+              'pass2' => $this->rootUser->pass_raw,
+            ],
+          ],
+          // form_type_checkboxes_value() requires NULL instead of FALSE values
+          // for programmatic form submissions to disable a checkbox.
+          'enable_update_status_module' => NULL,
+          'enable_update_status_emails' => NULL,
+        ],
+      ],
+    ];
+    return $parameters;
+  }
+
+}
diff --git a/core/tests/Drupal/Setup/TestSetupInterface.php b/core/tests/Drupal/Setup/TestSetupInterface.php
new file mode 100644
index 0000000000..8bf2c2e96a
--- /dev/null
+++ b/core/tests/Drupal/Setup/TestSetupInterface.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\Setup;
+
+/**
+ * Allows you to setup an environment used for javascript tests.
+ */
+interface TestSetupInterface {
+
+  /**
+   * Run code to setup the test.
+   * 
+   * You have access to any API provided by any installed module. To install
+   * modules use
+   * @code
+   * \Drupal::service('module_installer')->install(['my_module'])
+   * @endcode
+   *
+   * Check out 'core/tests/Drupal/Setup/ExampleTestSetup.php' for an example.
+   */
+  public function setup();
+
+}
diff --git a/core/tests/bootstrap.php b/core/tests/bootstrap.php
index f78b69ff04..644d6abfb1 100644
--- a/core/tests/bootstrap.php
+++ b/core/tests/bootstrap.php
@@ -127,6 +127,7 @@ function drupal_phpunit_populate_class_loader() {
 
   // Start with classes in known locations.
   $loader->add('Drupal\\Tests', __DIR__);
+  $loader->add('Drupal\\Setup', __DIR__);
   $loader->add('Drupal\\KernelTests', __DIR__);
   $loader->add('Drupal\\FunctionalTests', __DIR__);
   $loader->add('Drupal\\FunctionalJavascriptTests', __DIR__);
@@ -147,7 +148,7 @@ function drupal_phpunit_populate_class_loader() {
 };
 
 // Do class loader population.
-drupal_phpunit_populate_class_loader();
+$this_is_the_drupal_test_bootstrap_autoloader_so_do_not_be_confused = drupal_phpunit_populate_class_loader();
 
 // Set sane locale settings, to ensure consistent string, dates, times and
 // numbers handling.
@@ -166,3 +167,5 @@ function drupal_phpunit_populate_class_loader() {
 // make PHP 5 and 7 handle assertion failures the same way, but this call does
 // not turn runtime assertions on if they weren't on already.
 Handle::register();
+
+return $this_is_the_drupal_test_bootstrap_autoloader_so_do_not_be_confused;
