diff --git a/core/scripts/setup-drupal-test.php b/core/scripts/setup-drupal-test.php
new file mode 100644
index 0000000000..44b0f9eaf1
--- /dev/null
+++ b/core/scripts/setup-drupal-test.php
@@ -0,0 +1,31 @@
+#!/usr/bin/env php
+<?php
+
+/**
+ * @file
+ * A command line application to install drupal for tests.
+ */
+
+use Drupal\Core\DrupalKernel;
+use Drupal\Core\Site\Settings;
+use Drupal\Setup\Commands\TestInstallationSetupApplication;
+use Symfony\Component\HttpFoundation\Request;
+
+if (PHP_SAPI !== 'cli') {
+  return;
+}
+
+// Bootstrap.
+$autoloader = require __DIR__ . '/../../autoload.php';
+
+$request = Request::createFromGlobals();
+$kernel = DrupalKernel::createFromRequest($request, $autoloader, 'testing');
+DrupalKernel::bootEnvironment($kernel->getAppRoot());
+
+Settings::initialize(dirname(dirname(__DIR__)),
+  DrupalKernel::findSitePath($request), $autoloader);
+
+require_once __DIR__ . '/../tests/bootstrap.php';
+
+(new TestInstallationSetupApplication())
+  ->run();
diff --git a/core/tests/Drupal/FunctionalTests/SetupDrupalTestScriptTest.php b/core/tests/Drupal/FunctionalTests/SetupDrupalTestScriptTest.php
new file mode 100644
index 0000000000..8a7c3267cb
--- /dev/null
+++ b/core/tests/Drupal/FunctionalTests/SetupDrupalTestScriptTest.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\FunctionalTests;
+
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\Process\PhpExecutableFinder;
+use Symfony\Component\Process\Process;
+
+/**
+ * Tests setup-drupal-test.php
+ *
+ * @group core
+ */
+class SetupDrupalTestScriptTest extends UnitTestCase {
+
+  public function testInstallScript() {
+    $php_executable_finder = new PhpExecutableFinder();
+    $php = $php_executable_finder->find();
+    
+    $db_url = getenv('SIMPLETEST_DB');
+    $base_url = getenv('SIMPLETEST_BASE_URL');
+    $process = new Process("$php {$this->root}/core/scripts/setup-drupal-test.php --db_url={$db_url} --base_url={$base_url}");
+    $process->run(function ($type, $data) {
+      // @todo How does one test an async API?
+      // This code might happen after the test function is done.
+    });
+  }
+
+}
diff --git a/core/tests/Drupal/Setup/Commands/TestInstallationSetupApplication.php b/core/tests/Drupal/Setup/Commands/TestInstallationSetupApplication.php
new file mode 100644
index 0000000000..eaef9c8745
--- /dev/null
+++ b/core/tests/Drupal/Setup/Commands/TestInstallationSetupApplication.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Drupal\Setup\Commands;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Input\InputInterface;
+
+/**
+ * Command 
+ * @internal
+ */
+class TestInstallationSetupApplication extends Application {
+
+  /**
+   * SetupDrupalApplication constructor.
+   */
+  public function __construct() {
+    parent::__construct('setup-drupal-test', '1.0.0');
+  }
+
+  /**
+   * {@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;
+  }
+}
diff --git a/core/tests/Drupal/Setup/Commands/TestInstallationSetupCommand.php b/core/tests/Drupal/Setup/Commands/TestInstallationSetupCommand.php
new file mode 100644
index 0000000000..bb40013222
--- /dev/null
+++ b/core/tests/Drupal/Setup/Commands/TestInstallationSetupCommand.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Drupal\Setup\Commands;
+
+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;
+
+/**
+ * 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, '', getenv('SIMPLETEST_DB'))
+      ->addOption('base_url', NULL, InputOption::VALUE_OPTIONAL, '', getenv('SIMPLETEST_BASE_URL'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function execute(InputInterface $input, OutputInterface $output) {
+    $test = new TestInstallationSetup();
+    $test->setup('testing', $input->getOption('setup_file'));
+
+    $db_url = $input->getOption('db_url');
+    $base_url = $input->getOption('base_url');
+    putenv("SIMPLETEST_DB=$db_url");
+    putenv("SIMPLETEST_BASE_URL=$base_url");
+
+    $output->writeln(drupal_generate_test_ua($test->getDatabasePrefix()));
+  }
+}
diff --git a/core/tests/Drupal/Setup/ExampleTestSetup.php b/core/tests/Drupal/Setup/ExampleTestSetup.php
new file mode 100644
index 0000000000..9ee6c2d15d
--- /dev/null
+++ b/core/tests/Drupal/Setup/ExampleTestSetup.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Drupal\Setup;
+
+use Drupal\node\Entity\Node;
+
+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..c0bde3851f
--- /dev/null
+++ b/core/tests/Drupal/Setup/TestInstallationSetup.php
@@ -0,0 +1,195 @@
+<?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'));
+    }
+    if (!is_subclass_of($classes[0], TestSetupInterface::class)) {
+      throw new \InvalidArgumentException(sprintf('You need to define a class implementing \Drupal\Setup\TestSetupInterface'));
+    }
+
+    require_once $setup_file;
+
+    /** @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..77b270fe23 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__);
