diff --git a/modules/commerce/tests/src/Functional/d7/MigrateUpgradeCommerce1Test.php b/modules/commerce/tests/src/Functional/d7/MigrateUpgradeCommerce1Test.php
new file mode 100644
index 0000000..cc6a8db
--- /dev/null
+++ b/modules/commerce/tests/src/Functional/d7/MigrateUpgradeCommerce1Test.php
@@ -0,0 +1,372 @@
+<?php
+
+namespace Drupal\Tests\commerce_migrate_commerce\Functional\d7;
+
+use Drupal\Core\Database\Database;
+use Drupal\migrate\Plugin\MigrateIdMapInterface;
+use Drupal\migrate_drupal\MigrationConfigurationTrait;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests Commerce 1 upgrade using the migrate UI.
+ *
+ * The test method is provided by the MigrateUpgradeTestBase class.
+ *
+ * @requires module migrate_plus
+ *
+ * @group commerce_migrate
+ * @group commerce_migrate_commerce_d7
+ */
+class MigrateUpgradeCommerce1Test extends BrowserTestBase {
+
+  use MigrationConfigurationTrait;
+
+  /**
+   * Use the Standard profile to test help implementations of many core modules.
+   *
+   * @var string
+   */
+  protected $profile = 'standard';
+
+  /**
+   * The source database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $sourceDatabase;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'block',
+    'block_content',
+    'comment',
+    'dblog',
+    'field',
+    'filter',
+    'node',
+    'path',
+    'search',
+    'shortcut',
+    'system',
+    'taxonomy',
+    'user',
+    'address',
+    'commerce',
+    'commerce_cart',
+    'commerce_log',
+    'commerce_order',
+    'commerce_payment',
+    'commerce_price',
+    'commerce_product',
+    'commerce_promotion',
+    'commerce_store',
+    'commerce_migrate',
+    'migrate',
+    'migrate_drupal',
+    'migrate_drupal_ui',
+    'address',
+    'datetime',
+    'entity_reference_revisions',
+    'file',
+    'image',
+    'link',
+    'options',
+    'telephone',
+    'text',
+    'entity',
+    'profile',
+    'inline_entity_form',
+    'state_machine',
+    'views',
+    'migrate_plus',
+    'commerce_migrate_ubercart',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->createMigrationConnection();
+    $this->sourceDatabase = Database::getConnection('default', 'migrate_drupal_ui');
+
+    // Log in as user 1. Migrations in the UI can only be performed as user 1.
+    $this->drupalLogin($this->rootUser);
+
+    $this->loadFixture(drupal_get_path('module', 'commerce_migrate_ubercart') . '/tests/fixtures/uc6.php');
+  }
+
+  /**
+   * Loads a database fixture into the source database connection.
+   *
+   * @param string $path
+   *   Path to the dump file.
+   */
+  protected function loadFixture($path) {
+    $default_db = Database::getConnection()->getKey();
+    Database::setActiveConnection($this->sourceDatabase->getKey());
+
+    if (substr($path, -3) == '.gz') {
+      $path = 'compress.zlib://' . $path;
+    }
+    require $path;
+
+    Database::setActiveConnection($default_db);
+  }
+
+  /**
+   * Changes the database connection to the prefixed one.
+   *
+   * @todo Remove when we don't use global. https://www.drupal.org/node/2552791
+   */
+  protected function createMigrationConnection() {
+    $connection_info = Database::getConnectionInfo('default')['default'];
+    if ($connection_info['driver'] === 'sqlite') {
+      // Create database file in the test site's public file directory so that
+      // \Drupal\simpletest\TestBase::restoreEnvironment() will delete this once
+      // the test is complete.
+      $file = $this->publicFilesDirectory . '/' . $this->testId . '-migrate.db.sqlite';
+      touch($file);
+      $connection_info['database'] = $file;
+      $connection_info['prefix'] = '';
+    }
+    else {
+      $prefix = is_array($connection_info['prefix']) ? $connection_info['prefix']['default'] : $connection_info['prefix'];
+      // Simpletest uses fixed length prefixes. Create a new prefix for the
+      // source database. Adding to the end of the prefix ensures that
+      // \Drupal\simpletest\TestBase::restoreEnvironment() will remove the
+      // additional tables.
+      $connection_info['prefix'] = $prefix . '0';
+    }
+
+    Database::addConnectionInfo('migrate_drupal_ui', 'default', $connection_info);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function tearDown() {
+    Database::removeConnection('migrate_drupal_ui');
+    parent::tearDown();
+  }
+
+  /**
+   * Executes all steps of migrations upgrade.
+   */
+  public function testMigrateUpgrade() {
+    $connection_options = $this->sourceDatabase->getConnectionOptions();
+    $this->drupalGet('/upgrade');
+    $this->assertSession()->responseContains('Upgrade a site by importing its files and the data from its database into a clean and empty new install of Drupal 8.');
+
+    $this->drupalPostForm(NULL, [], t('Continue'));
+    $this->assertText('Provide credentials for the database of the Drupal site you want to upgrade.');
+    $this->assertFieldByName('mysql[host]');
+
+    $driver = $connection_options['driver'];
+    $connection_options['prefix'] = $connection_options['prefix']['default'];
+
+    // Use the driver connection form to get the correct options out of the
+    // database settings. This supports all of the databases we test against.
+    $drivers = drupal_get_database_types();
+    $form = $drivers[$driver]->getFormOptions($connection_options);
+    $connection_options = array_intersect_key($connection_options, $form + $form['advanced_options']);
+    $version = $this->getLegacyDrupalVersion($this->sourceDatabase);
+    $edit = [
+      $driver => $connection_options,
+      'source_private_file_path' => $this->getSourceBasePath(),
+      'version' => $version,
+    ];
+    if ($version == 6) {
+      $edit['d6_source_base_path'] = $this->getSourceBasePath();
+    }
+    else {
+      $edit['source_base_path'] = $this->getSourceBasePath();
+    }
+    if (count($drivers) !== 1) {
+      $edit['driver'] = $driver;
+    }
+    $edits = $this->translatePostValues($edit);
+
+    // Start the upgrade process.
+    $this->drupalGet('/upgrade');
+    $this->assertSession()->responseContains('Upgrade a site by importing its files and the data from its database into a clean and empty new install of Drupal 8.');
+
+    $this->drupalPostForm(NULL, [], t('Continue'));
+    $this->assertSession()->pageTextContains('Provide credentials for the database of the Drupal site you want to upgrade.');
+    $this->assertSession()->fieldExists('mysql[host]');
+
+    $this->drupalPostForm(NULL, $edits, t('Review upgrade'));
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('Upgrade analysis report');
+
+    // Test the available migration paths.
+    $all_available = $this->getAvailablePaths();
+    $session = $this->assertSession();
+    foreach ($all_available as $available) {
+      $session->elementExists('xpath', "//span[contains(@class, 'checked') and text() = '$available']");
+      $session->elementNotExists('xpath', "//span[contains(@class, 'warning') and text() = '$available']");
+    }
+
+    // Test the missing migration paths.
+    $all_missing = $this->getMissingPaths();
+    foreach ($all_missing as $missing) {
+      $session->elementExists('xpath', "//span[contains(@class, 'warning') and text() = '$missing']");
+      $session->elementNotExists('xpath', "//span[contains(@class, 'checked') and text() = '$missing']");
+    }
+
+    $this->drupalPostForm(NULL, [], t('Perform upgrade'));
+    $this->assertText(t('Congratulations, you upgraded Drupal!'));
+
+    // Have to reset all the statics after migration to ensure entities are
+    // loadable.
+    $this->resetAll();
+
+    $expected_counts = $this->getEntityCounts();
+    foreach (array_keys(\Drupal::entityTypeManager()->getDefinitions()) as $entity_type) {
+      $real_count = \Drupal::entityQuery($entity_type)->count()->execute();
+      $expected_count = isset($expected_counts[$entity_type]) ? $expected_counts[$entity_type] : 0;
+      //$this->assertEqual($expected_count, $real_count, "Found $real_count $entity_type entities, expected $expected_count.");
+      var_dump("$entity_type . ' => ' . $real_count");
+    }
+
+    $plugin_manager = \Drupal::service('plugin.manager.migration');
+    /** @var \Drupal\migrate\Plugin\Migration[] $all_migrations */
+    $all_migrations = $plugin_manager->createInstancesByTag('Drupal ' . $version);
+    foreach ($all_migrations as $migration) {
+      $id_map = $migration->getIdMap();
+      foreach ($id_map as $source_id => $map) {
+        // Convert $source_id into a keyless array so that
+        // \Drupal\migrate\Plugin\migrate\id_map\Sql::getSourceHash() works as
+        // expected.
+        $source_id_values = array_values(unserialize($source_id));
+        $row = $id_map->getRowBySource($source_id_values);
+        $destination = serialize($id_map->currentDestination());
+        $message = "Migration of $source_id to $destination as part of the {$migration->id()} migration. The source row status is " . $row['source_row_status'];
+        // A completed migration should have maps with
+        // MigrateIdMapInterface::STATUS_IGNORED or
+        // MigrateIdMapInterface::STATUS_IMPORTED.
+        if ($row['source_row_status'] == MigrateIdMapInterface::STATUS_FAILED || $row['source_row_status'] == MigrateIdMapInterface::STATUS_NEEDS_UPDATE) {
+          $this->fail($message);
+        }
+        else {
+          $this->pass($message);
+        }
+      }
+    }
+    \Drupal::service('module_installer')->install(['forum']);
+    \Drupal::service('module_installer')->install(['book']);
+  }
+
+  /**
+   * Transforms a nested array into a flat array suitable for BrowserTestBase::drupalPostForm().
+   *
+   * @param array $values
+   *   A multi-dimensional form values array to convert.
+   *
+   * @return array
+   *   The flattened $edit array suitable for BrowserTestBase::drupalPostForm().
+   */
+  protected function translatePostValues(array $values) {
+    $edit = [];
+    // The easiest and most straightforward way to translate values suitable for
+    // BrowserTestBase::drupalPostForm() is to actually build the POST data string
+    // and convert the resulting key/value pairs back into a flat array.
+    $query = http_build_query($values);
+    foreach (explode('&', $query) as $item) {
+      list($key, $value) = explode('=', $item);
+      $edit[urldecode($key)] = urldecode($value);
+    }
+    return $edit;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getSourceBasePath() {
+    return __DIR__ . '/files';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEntityCounts() {
+    return [
+      'block' => '24',
+      'block_content' => '0',
+      'block_content_type' => '1',
+      'comment' => '0',
+      'comment_type' => '3',
+      'commerce_log' => '0',
+      'commerce_order_type' => '1',
+      'commerce_order' => '3',
+      'commerce_order_item' => '4',
+      'commerce_order_item_type' => '1',
+      'commerce_payment_method' => '0',
+      'commerce_payment' => '4',
+      'commerce_payment_gateway' => '2',
+      'commerce_currency' => '2',
+      'commerce_product_attribute' => '4',
+      'commerce_product' => '5',
+      'commerce_product_variation_type' => '1',
+      'commerce_product_variation' => '5',
+      'commerce_product_type' => '3',
+      'commerce_product_attribute_value' => '7',
+      'commerce_promotion_coupon' => '0',
+      'commerce_promotion' => '0',
+      'commerce_store' => '1',
+      'commerce_store_type' => '1',
+      'contact_form' => '2',
+      'contact_message' => '0',
+      'editor' => '2',
+      'field_config' => '37',
+      'field_storage_config' => '27',
+      'file' => '0',
+      'filter_format' => '5',
+      'image_style' => '3',
+      'migration_group' => '1',
+      'migration' => '0',
+      'node_type' => '3',
+      'node' => '1',
+      'profile_type' => '1',
+      'profile' => '3',
+      'rdf_mapping' => '5',
+      'search_page' => '2',
+      'shortcut' => '2',
+      'shortcut_set' => '1',
+      'action' => '29',
+      'menu' => '8',
+      'taxonomy_vocabulary' => '2',
+      'taxonomy_term' => '3',
+      'tour' => '1',
+      'user_role' => '5',
+      'user' => '6',
+      'menu_link_content' => '0',
+      'view' => '24',
+      'base_field_override' => '6',
+      'date_format' => '11',
+      'entity_view_display' => '27',
+      'entity_view_mode' => '16',
+      'entity_form_display' => '16',
+      'entity_form_mode' => '2',
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getAvailablePaths() {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getMissingPaths() {
+    return [];
+  }
+
+}
