diff --git a/core/lib/Drupal/Core/Entity/Controller/EntityController.php b/core/lib/Drupal/Core/Entity/Controller/EntityController.php
new file mode 100644
index 0000000000000000000000000000000000000000..0af9638f54c50f9811cf321a1942f66f129d9572
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Controller/EntityController.php
@@ -0,0 +1,143 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Controller\EntityController.
+ */
+
+namespace Drupal\Core\Entity\Controller;
+
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides generic entity title callbacks for use in routing.
+ *
+ * It provides:
+ * - A view title callback.
+ * - An edit title callback.
+ * - A delete title callback.
+ */
+class EntityController implements ContainerInjectionInterface {
+
+  use StringTranslationTrait;
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * Constructs a new EntityController.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+   *   The string translation.
+   */
+  public function __construct(EntityManagerInterface $entity_manager, TranslationInterface $string_translation) {
+    $this->entityManager = $entity_manager;
+    $this->stringTranslation = $string_translation;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity.manager'),
+      $container->get('string_translation')
+    );
+  }
+
+  /**
+   * Provides a generic title callback for a single entity.
+   *
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The route match.
+   * @param \Drupal\Core\Entity\EntityInterface $_entity
+   *   (optional) An entity, passed in directly from the request attributes.
+   *
+   * @return string
+   *   The title for the entity view page.
+   */
+  public function title(RouteMatchInterface $route_match, EntityInterface $_entity = NULL) {
+    if ($entity = $this->doGetEntity($route_match, $_entity)) {
+      return $entity->label();
+    }
+  }
+
+  /**
+   * Provides a generic edit title callback.
+   *
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The route match.
+   * @param \Drupal\Core\Entity\EntityInterface $_entity
+   *   (optional) An entity, passed in directly from the request attributes.
+   *
+   * @return string
+   *   The title for the entity edit page.
+   */
+  public function editTitle(RouteMatchInterface $route_match, EntityInterface $_entity = NULL) {
+    if ($entity = $this->doGetEntity($route_match, $_entity)) {
+      return $this->t('Edit %label', ['%label' => $entity->label()]);
+    }
+  }
+
+  /**
+   * Provides a generic delete title callback.
+   *
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The route match.
+   * @param \Drupal\Core\Entity\EntityInterface $_entity
+   *   (optional) An entity, passed in directly from the request attributes, and
+   *   set in \Drupal\Core\Entity\Enhancer\EntityRouteEnhancer.
+   *
+   * @return string
+   *   The title for the delete entity page.
+   */
+  public function deleteTitle(RouteMatchInterface $route_match, EntityInterface $_entity = NULL) {
+    if ($entity = $this->doGetEntity($route_match, $_entity)) {
+      return $this->t('Delete %label', ['%label' => $entity->label()]);
+    }
+  }
+
+  /**
+   * Determines the entity.
+   *
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The route match.
+   * @param \Drupal\Core\Entity\EntityInterface $_entity
+   *   (optional) The entity, set in
+   *   \Drupal\Core\Entity\Enhancer\EntityRouteEnhancer.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface|NULL
+   *   The entity, if it is passed in directly or if the first parameter of the
+   *   active route is an entity; otherwise, NULL.
+   */
+  protected function doGetEntity(RouteMatchInterface $route_match, EntityInterface $_entity = NULL) {
+    if ($_entity) {
+      $entity = $_entity;
+    }
+    else {
+      // Let's look up in the route object for the name of upcasted values.
+      foreach ($route_match->getParameters() as $parameter) {
+        if ($parameter instanceof EntityInterface) {
+          $entity = $parameter;
+          break;
+        }
+      }
+    }
+    if ($entity) {
+      return $this->entityManager->getTranslationFromContext($entity);
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Routing/AdminHtmlRouteProvider.php b/core/lib/Drupal/Core/Entity/Routing/AdminHtmlRouteProvider.php
new file mode 100644
index 0000000000000000000000000000000000000000..39c1c6ebccac220471d90173746e41fc06d9f131
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Routing/AdminHtmlRouteProvider.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider.
+ */
+
+namespace Drupal\Core\Entity\Routing;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+
+/**
+ * Provides HTML routes for entities with administrative edit/delete pages.
+ *
+ * Use this class if the edit and delete form routes should use the
+ * administrative theme.
+ *
+ * @see \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider.
+ *
+ * @internal
+ */
+class AdminHtmlRouteProvider extends DefaultHtmlRouteProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditFormRoute(EntityTypeInterface $entity_type) {
+    if ($route = parent::getEditFormRoute($entity_type)) {
+      $route->setOption('_admin_route', TRUE);
+      return $route;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDeleteFormRoute(EntityTypeInterface $entity_type) {
+    if ($route = parent::getDeleteFormRoute($entity_type)) {
+      $route->setOption('_admin_route', TRUE);
+      return $route;
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
new file mode 100644
index 0000000000000000000000000000000000000000..f0b5907de3f026f56f271a7ce8523679261487df
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
@@ -0,0 +1,135 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider.
+ */
+
+namespace Drupal\Core\Entity\Routing;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Provides HTML routes for entities.
+ *
+ * This class provides the following routes for entities, with title and access
+ * callbacks:
+ * - canonical
+ * - edit-form
+ * - delete-form
+ *
+ * @see \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider.
+ *
+ * @internal
+ */
+class DefaultHtmlRouteProvider implements EntityRouteProviderInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRoutes(EntityTypeInterface $entity_type) {
+    $collection = new RouteCollection();
+
+    $entity_type_id = $entity_type->id();
+
+    if ($edit_route = $this->getEditFormRoute($entity_type)) {
+      $collection->add("entity.{$entity_type_id}.edit_form", $edit_route);
+    }
+
+    if ($canonical_route = $this->getCanonicalRoute($entity_type)) {
+      $collection->add("entity.{$entity_type_id}.canonical", $canonical_route);
+    }
+
+    if ($delete_route = $this->getDeleteFormRoute($entity_type)) {
+      $collection->add("entity.{$entity_type_id}.delete_form", $delete_route);
+    }
+
+    return $collection;
+  }
+
+  /**
+   * Gets the canonical route.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   *
+   * @return \Symfony\Component\Routing\Route|null
+   *   The generated route, if available.
+   */
+  protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
+    if ($entity_type->hasLinkTemplate('canonical') && $entity_type->hasViewBuilderClass()) {
+      $entity_type_id = $entity_type->id();
+      $route = new Route($entity_type->getLinkTemplate('canonical'));
+      $route
+        ->addDefaults([
+          '_entity_view' => "{$entity_type_id}.full",
+          '_title_callback' => '\Drupal\Core\Entity\Controller\EntityController::title',
+        ])
+        ->setRequirement('_entity_access', "{$entity_type_id}.view")
+        ->setOption('parameters', [
+          $entity_type_id => ['type' => 'entity:' . $entity_type_id],
+        ]);
+      return $route;
+    }
+  }
+
+  /**
+   * Gets the edit-form route.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   *
+   * @return \Symfony\Component\Routing\Route|null
+   *   The generated route, if available.
+   */
+  protected function getEditFormRoute(EntityTypeInterface $entity_type) {
+    if ($entity_type->hasLinkTemplate('edit-form')) {
+      $entity_type_id = $entity_type->id();
+      $route = new Route($entity_type->getLinkTemplate('edit-form'));
+      // Use the edit form handler, if available, otherwise default.
+      $operation = 'default';
+      if ($entity_type->getFormClass('edit')) {
+        $operation = 'edit';
+      }
+      $route
+        ->setDefaults([
+          '_entity_form' => "{$entity_type_id}.{$operation}",
+          '_title_callback' => '\Drupal\Core\Entity\Controller\EntityController::editTitle'
+        ])
+        ->setRequirement('_entity_access', "{$entity_type_id}.update")
+        ->setOption('parameters', [
+          $entity_type_id => ['type' => 'entity:' . $entity_type_id],
+        ]);
+      return $route;
+    }
+  }
+
+  /**
+   * Gets the delete-form route.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   *
+   * @return \Symfony\Component\Routing\Route|null
+   *   The generated route, if available.
+   */
+  protected function getDeleteFormRoute(EntityTypeInterface $entity_type) {
+    if ($entity_type->hasLinkTemplate('delete-form')) {
+      $entity_type_id = $entity_type->id();
+      $route = new Route($entity_type->getLinkTemplate('delete-form'));
+      $route
+        ->addDefaults([
+          '_entity_form' => "{$entity_type_id}.delete",
+          '_title_callback' => '\Drupal\Core\Entity\Controller\EntityController::deleteTitle',
+        ])
+        ->setRequirement('_entity_access', "{$entity_type_id}.delete")
+        ->setOption('parameters', [
+          $entity_type_id => ['type' => 'entity:' . $entity_type_id],
+        ]);
+      return $route;
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/entity.api.php b/core/lib/Drupal/Core/Entity/entity.api.php
index 3a3d61c6b80d10ad87473e7fe10ccc21e957f7d8..8883f78d038ec21afb7ce5f8d08fdd9a9db4f3b6 100644
--- a/core/lib/Drupal/Core/Entity/entity.api.php
+++ b/core/lib/Drupal/Core/Entity/entity.api.php
@@ -324,7 +324,9 @@
  *   also need to add a corresponding route to your module's routing.yml file;
  *   see the entity.node.canonical route in node.routing.yml for an example, and see
  *   @ref sec_routes below for some notes.
- * - Define routes and links for the various URLs associated with the entity.
+ * - Optionally, instead of defining routes, routes can be auto generated by
+ *   providing a route handler. See @ref sec_routes. Otherwise, define routes
+ *   and links for the various URLs associated with the entity.
  *   These go into the 'links' annotation, with the link type as the key, and
  *   the path of this link template as the value. The corresponding route
  *   requires the following route name:
@@ -358,8 +360,10 @@
  *
  * @section sec_routes Entity routes
  * Entity routes, like other routes, are defined in *.routing.yml files; see
- * the @link menu Menu and routing @endlink topic for more information. Here
- * is a typical entry, for the block configure form:
+ * the @link routing Routing API @endlink topic for more information. Entities
+ * may alternatively use an auto route provider class; there is an example of
+ * this at the end of this section. If providing routes directly, here is a
+ * typical entry, for the block configure form:
  * @code
  * entity.block.edit_form:
  *   path: '/admin/structure/block/manage/{block}'
@@ -386,6 +390,19 @@
  *     "form" = {
  *       "default" = "Drupal\block\BlockForm",
  *   @endcode
+ * - Instead of putting the routes for your entity in a *.routing.yml file, you
+ *   can instead use a route provider class.
+ *   \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider provides canonical,
+ *   edit-form, and delete-form routes;
+ *   \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider provides the same
+ *   routes, set up to use the administrative theme for edit and delete pages.
+ *   You can also create your own class. To use a route provider class, add
+ *   lines like the following to your entity annotation:
+ *   @code
+ *   handlers = {
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *   @endcode
  *
  * @section bundle Defining a content entity bundle
  * For entity types that use bundles, such as Node (bundles are content types)
diff --git a/core/lib/Drupal/Core/EventSubscriber/EntityRouteProviderSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/EntityRouteProviderSubscriber.php
index d5c4def45017314c1cb9ce64313734755382bd1c..326c9984c4339dbde92993dc6cd80108adcb2317 100644
--- a/core/lib/Drupal/Core/EventSubscriber/EntityRouteProviderSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/EntityRouteProviderSubscriber.php
@@ -48,12 +48,14 @@ public function onDynamicRouteEvent(RouteBuildEvent $event) {
         foreach ($this->entityManager->getRouteProviders($entity_type->id()) as $route_provider) {
           // Allow to both return an array of routes or a route collection,
           // like route_callbacks in the routing.yml file.
+
           $routes = $route_provider->getRoutes($entity_type);
           if ($routes instanceof RouteCollection) {
-            $route_collection->addCollection($routes);
+            $routes = $routes->all();
           }
-          elseif (is_array($routes)) {
-            foreach ($routes as $route_name => $route) {
+          foreach ($routes as $route_name => $route) {
+            // Don't override existing routes.
+            if (!$route_collection->get($route_name)) {
               $route_collection->add($route_name, $route);
             }
           }
diff --git a/core/modules/aggregator/aggregator.routing.yml b/core/modules/aggregator/aggregator.routing.yml
index 2f2d5536c8f98ddeb8e45cf6a0997e0e00ead58d..941f6934350327697777ae9e59a37e7701babc53 100644
--- a/core/modules/aggregator/aggregator.routing.yml
+++ b/core/modules/aggregator/aggregator.routing.yml
@@ -49,34 +49,6 @@ aggregator.feed_add:
   options:
     _admin_route: TRUE
 
-entity.aggregator_feed.canonical:
-  path: '/aggregator/sources/{aggregator_feed}'
-  defaults:
-    _entity_view: 'aggregator_feed'
-    _title_callback: '\Drupal\aggregator\Controller\AggregatorController::feedTitle'
-  requirements:
-    _permission: 'access news feeds'
-
-entity.aggregator_feed.edit_form:
-  path: '/aggregator/sources/{aggregator_feed}/configure'
-  defaults:
-    _entity_form: 'aggregator_feed.default'
-    _title: 'Configure'
-  requirements:
-    _permission: 'administer news feeds'
-  options:
-    _admin_route: TRUE
-
-entity.aggregator_feed.delete_form:
-  path: '/aggregator/sources/{aggregator_feed}/delete'
-  defaults:
-    _entity_form: 'aggregator_feed.delete'
-    _title: 'Delete feed'
-  requirements:
-    _permission: 'administer news feeds'
-  options:
-    _admin_route: TRUE
-
 aggregator.page_last:
   path: '/aggregator'
   defaults:
diff --git a/core/modules/aggregator/src/Entity/Feed.php b/core/modules/aggregator/src/Entity/Feed.php
index 68494e1a771b289706a3217fed4c49a32b38f89b..f478d1cc3d902768dd80c6ed20756021aa1edfe2 100644
--- a/core/modules/aggregator/src/Entity/Feed.php
+++ b/core/modules/aggregator/src/Entity/Feed.php
@@ -29,7 +29,10 @@
  *       "default" = "Drupal\aggregator\FeedForm",
  *       "delete" = "Drupal\aggregator\Form\FeedDeleteForm",
  *       "delete_items" = "Drupal\aggregator\Form\FeedItemsDeleteForm",
- *     }
+ *     },
+ *     "route_provider" = {
+ *       "html" = "Drupal\aggregator\FeedHtmlRouteProvider",
+ *     },
  *   },
  *   links = {
  *     "canonical" = "/aggregator/sources/{aggregator_feed}",
diff --git a/core/modules/aggregator/src/FeedHtmlRouteProvider.php b/core/modules/aggregator/src/FeedHtmlRouteProvider.php
new file mode 100644
index 0000000000000000000000000000000000000000..ad168ee1a1f66dbe83de2215be27c7120f9fa889
--- /dev/null
+++ b/core/modules/aggregator/src/FeedHtmlRouteProvider.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\aggregator\FeedHtmlRouteProvider.
+ */
+
+namespace Drupal\aggregator;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider;
+
+/**
+ * Provides HTML routes for the feed entity type.
+ */
+class FeedHtmlRouteProvider extends AdminHtmlRouteProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
+    $route = parent::getCanonicalRoute($entity_type);
+    $route->setDefault('_title_controller', '\Drupal\aggregator\Controller\AggregatorController::feedTitle');
+
+    return $route;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditFormRoute(EntityTypeInterface $entity_type) {
+    $route = parent::getEditFormRoute($entity_type);
+
+    $route->setDefault('_title', 'Configure');
+
+    return $route;
+  }
+
+}
diff --git a/core/modules/aggregator/src/Tests/AddFeedTest.php b/core/modules/aggregator/src/Tests/AddFeedTest.php
index 94f315b6e12e1b59813bc34d4d77be8a75ad71db..56f5586349a847a879f3b5ff7aa903eff8cdbbf5 100644
--- a/core/modules/aggregator/src/Tests/AddFeedTest.php
+++ b/core/modules/aggregator/src/Tests/AddFeedTest.php
@@ -61,8 +61,8 @@ public function testFeedLabelEscaping() {
     $this->drupalGet('aggregator/sources/' . $feed->id());
     $this->assertResponse(200);
 
-    $result = $this->xpath('//h1');
-    $this->assertEqual((string) $result[0], 'Test feed title alert(123);');
+    $this->assertEscaped('Test feed title <script>alert(123);</script>');
+    $this->assertNoRaw('Test feed title <script>alert(123);</script>');
 
     // Ensure the feed icon title is escaped.
     $this->assertTrue(strpos(str_replace(["\n", "\r"], '', $this->getRawContent()), 'class="feed-icon">  Subscribe to Test feed title &lt;script&gt;alert(123);&lt;/script&gt; feed</a>') !== FALSE);
diff --git a/core/modules/comment/src/Tests/CommentNonNodeTest.php b/core/modules/comment/src/Tests/CommentNonNodeTest.php
index 98099fb85065f22ee5789927c163875704e13188..c7f3e71a5ab8e16e8b6ea55c856da38a80862f1a 100644
--- a/core/modules/comment/src/Tests/CommentNonNodeTest.php
+++ b/core/modules/comment/src/Tests/CommentNonNodeTest.php
@@ -435,7 +435,7 @@ function testCommentFunctionality() {
     $data = array('bundle' => 'entity_test', 'name' => $random_label);
     $new_entity = entity_create('entity_test', $data);
     $new_entity->save();
-    $this->drupalGet('entity_test/manage/' . $new_entity->id());
+    $this->drupalGet('entity_test/manage/' . $new_entity->id() . '/edit');
     $this->assertNoFieldChecked('edit-field-foobar-0-status-1');
     $this->assertFieldChecked('edit-field-foobar-0-status-2');
     $this->assertNoField('edit-field-foobar-0-status-0');
diff --git a/core/modules/content_translation/src/Tests/ContentTestTranslationUITest.php b/core/modules/content_translation/src/Tests/ContentTestTranslationUITest.php
index ce9237cc31f4d9c69d9399877c30628a4c7de062..3569a457d3c6f6c89cfdc91b3f07fb52b8cc2d00 100644
--- a/core/modules/content_translation/src/Tests/ContentTestTranslationUITest.php
+++ b/core/modules/content_translation/src/Tests/ContentTestTranslationUITest.php
@@ -26,19 +26,6 @@ class ContentTestTranslationUITest extends ContentTranslationUITestBase {
    */
   public static $modules = array('language', 'content_translation', 'entity_test');
 
-  /**
-   * {@inheritdoc}
-   */
-  protected $defaultCacheContexts = [
-    'languages:language_interface',
-    'session',
-    'theme',
-    'url.path',
-    'url.query_args',
-    'user.permissions',
-    'user.roles:authenticated',
-  ];
-
   /**
    * Overrides \Drupal\simpletest\WebTestBase::setUp().
    */
diff --git a/core/modules/field/src/Tests/EntityReference/EntityReferenceIntegrationTest.php b/core/modules/field/src/Tests/EntityReference/EntityReferenceIntegrationTest.php
index e73d4897313798d65d39e829229dc6fe708a4365..4d678f8a50428a1531135af3e2bb5c35cf1a0e09 100644
--- a/core/modules/field/src/Tests/EntityReference/EntityReferenceIntegrationTest.php
+++ b/core/modules/field/src/Tests/EntityReference/EntityReferenceIntegrationTest.php
@@ -87,7 +87,7 @@ public function testSupportedEntityTypesAndWidgets() {
       // Try to post the form again with no modification and check if the field
       // values remain the same.
       $entity = current(entity_load_multiple_by_properties($this->entityType, array('name' => $entity_name)));
-      $this->drupalGet($this->entityType . '/manage/' . $entity->id());
+      $this->drupalGet($this->entityType . '/manage/' . $entity->id() . '/edit');
       $this->assertFieldByName($this->fieldName . '[0][target_id]', $referenced_entities[0]->label() . ' (' . $referenced_entities[0]->id() . ')');
       $this->assertFieldByName($this->fieldName . '[1][target_id]', $referenced_entities[1]->label() . ' (' . $referenced_entities[1]->id() . ')');
 
@@ -113,7 +113,7 @@ public function testSupportedEntityTypesAndWidgets() {
       // Try to post the form again with no modification and check if the field
       // values remain the same.
       $entity = current(entity_load_multiple_by_properties($this->entityType, array('name' => $entity_name)));
-      $this->drupalGet($this->entityType . '/manage/' . $entity->id());
+      $this->drupalGet($this->entityType . '/manage/' . $entity->id() . '/edit');
       $this->assertFieldByName($this->fieldName . '[target_id]', $target_id . ' (' . $referenced_entities[1]->id() . ')');
 
       $this->drupalPostForm(NULL, array(), t('Save'));
@@ -132,7 +132,7 @@ public function testSupportedEntityTypesAndWidgets() {
           'type' => $widget_type,
         ))->save();
 
-        $this->drupalPostForm($this->entityType . '/manage/' . $entity->id(), array(), t('Save'));
+        $this->drupalPostForm($this->entityType . '/manage/' . $entity->id() . '/edit', array(), t('Save'));
         $this->assertFieldValues($entity_name, $referenced_entities);
       }
 
diff --git a/core/modules/field/src/Tests/FormTest.php b/core/modules/field/src/Tests/FormTest.php
index ed56dceb4c5f44b11bf026f0e8d4cf2d155582f0..dbdc71a4f7636c5f5a386b7d3580df5238a3d379 100644
--- a/core/modules/field/src/Tests/FormTest.php
+++ b/core/modules/field/src/Tests/FormTest.php
@@ -139,7 +139,7 @@ function testFieldFormSingle() {
     $this->assertEqual($entity->{$field_name}->value, $value, 'Field value was saved');
 
     // Display edit form.
-    $this->drupalGet('entity_test/manage/' . $id);
+    $this->drupalGet('entity_test/manage/' . $id . '/edit');
     $this->assertFieldByName("{$field_name}[0][value]", $value, 'Widget is displayed with the correct default value');
     $this->assertNoField("{$field_name}[1][value]", 'No extraneous widget is displayed');
 
@@ -159,7 +159,7 @@ function testFieldFormSingle() {
     $edit = array(
       "{$field_name}[0][value]" => $value
     );
-    $this->drupalPostForm('entity_test/manage/' . $id, $edit, t('Save'));
+    $this->drupalPostForm('entity_test/manage/' . $id . '/edit', $edit, t('Save'));
     $this->assertText(t('entity_test @id has been updated.', array('@id' => $id)), 'Entity was updated');
     $this->container->get('entity.manager')->getStorage('entity_test')->resetCache(array($id));
     $entity = entity_load('entity_test', $id);
@@ -231,7 +231,7 @@ function testFieldFormSingleRequired() {
     $edit = array(
       "{$field_name}[0][value]" => $value,
     );
-    $this->drupalPostForm('entity_test/manage/' . $id, $edit, t('Save'));
+    $this->drupalPostForm('entity_test/manage/' . $id . '/edit', $edit, t('Save'));
     $this->assertRaw(t('@name field is required.', array('@name' => $this->field['label'])), 'Required field with no value fails validation');
   }
 
@@ -491,7 +491,7 @@ function testFieldFormMultipleWidget() {
     $this->assertFieldValues($entity_init, $field_name, array(1, 2, 3));
 
     // Display the form, check that the values are correctly filled in.
-    $this->drupalGet('entity_test/manage/' . $id);
+    $this->drupalGet('entity_test/manage/' . $id . '/edit');
     $this->assertFieldByName($field_name, '1, 2, 3', 'Widget is displayed.');
 
     // Submit the form with more values than the field accepts.
@@ -574,7 +574,7 @@ function testFieldFormAccess() {
       "{$field_name}[0][value]" => 2,
       'revision' => TRUE,
     );
-    $this->drupalPostForm($entity_type . '/manage/' . $id, $edit, t('Save'));
+    $this->drupalPostForm($entity_type . '/manage/' . $id . '/edit', $edit, t('Save'));
 
     // Check that the new revision has the expected values.
     $this->container->get('entity.manager')->getStorage($entity_type)->resetCache(array($id));
@@ -630,7 +630,7 @@ function testHiddenField() {
       ->save();
 
     // Display edit form.
-    $this->drupalGet($entity_type . '/manage/' . $id);
+    $this->drupalGet($entity_type . '/manage/' . $id . '/edit');
     $this->assertFieldByName("{$field_name}[0][value]", 99, 'Widget is displayed with the correct default value');
 
     // Update the entity.
@@ -649,7 +649,7 @@ function testHiddenField() {
 
     // Create a new revision.
     $edit = array('revision' => TRUE);
-    $this->drupalPostForm($entity_type . '/manage/' . $id, $edit, t('Save'));
+    $this->drupalPostForm($entity_type . '/manage/' . $id . '/edit', $edit, t('Save'));
 
     // Check that the expected value has been carried over to the new revision.
     \Drupal::entityManager()->getStorage($entity_type)->resetCache(array($id));
diff --git a/core/modules/field/src/Tests/TranslationWebTest.php b/core/modules/field/src/Tests/TranslationWebTest.php
index 4aa8c4893071104116566af5c3e446d70e7637d0..8350285c152c564ae005e6a988f6c724418ce0e8 100644
--- a/core/modules/field/src/Tests/TranslationWebTest.php
+++ b/core/modules/field/src/Tests/TranslationWebTest.php
@@ -114,7 +114,7 @@ function testFieldFormTranslationRevisions() {
       "{$field_name}[0][value]" => $entity->{$field_name}->value,
       'revision' => TRUE,
     );
-    $this->drupalPostForm($this->entityTypeId . '/manage/' . $entity->id(), $edit, t('Save'));
+    $this->drupalPostForm($this->entityTypeId . '/manage/' . $entity->id() . '/edit', $edit, t('Save'));
 
     // Check translation revisions.
     $this->checkTranslationRevisions($entity->id(), $entity->getRevisionId(), $available_langcodes);
diff --git a/core/modules/link/src/Tests/LinkFieldTest.php b/core/modules/link/src/Tests/LinkFieldTest.php
index 4b61f730a021b2a43ce850b192da9b08eab3c484..494936a09c24159d132520c0d730eb580ec9fc6d 100644
--- a/core/modules/link/src/Tests/LinkFieldTest.php
+++ b/core/modules/link/src/Tests/LinkFieldTest.php
@@ -318,7 +318,7 @@ function testLinkTitle() {
     $edit = array(
       "{$field_name}[0][title]" => $title,
     );
-    $this->drupalPostForm("entity_test/manage/$id", $edit, t('Save'));
+    $this->drupalPostForm("entity_test/manage/$id/edit", $edit, t('Save'));
     $this->assertText(t('entity_test @id has been updated.', array('@id' => $id)));
 
     $this->renderTestEntity($id);
diff --git a/core/modules/options/src/Tests/OptionsSelectDynamicValuesTest.php b/core/modules/options/src/Tests/OptionsSelectDynamicValuesTest.php
index f6a661a70a39ab682c1728e33cddb0d0e65d357f..2b9c0bb531be3a9d9f27e8b2963d3fb2580081d4 100644
--- a/core/modules/options/src/Tests/OptionsSelectDynamicValuesTest.php
+++ b/core/modules/options/src/Tests/OptionsSelectDynamicValuesTest.php
@@ -25,7 +25,7 @@ function testSelectListDynamic() {
     $this->drupalLogin($web_user);
 
     // Display form.
-    $this->drupalGet('entity_test_rev/manage/' . $this->entity->id());
+    $this->drupalGet('entity_test_rev/manage/' . $this->entity->id() . '/edit');
     $options = $this->xpath('//select[@id="edit-test-options"]/option');
     $this->assertEqual(count($options), count($this->test) + 1);
     foreach ($options as $option) {
diff --git a/core/modules/options/src/Tests/OptionsWidgetsTest.php b/core/modules/options/src/Tests/OptionsWidgetsTest.php
index f1d2ca327c999a20e62e42e3dcb8d1941f6686f2..93293cac3873873367f2b610c3d066e2da52e048 100644
--- a/core/modules/options/src/Tests/OptionsWidgetsTest.php
+++ b/core/modules/options/src/Tests/OptionsWidgetsTest.php
@@ -108,7 +108,7 @@ function testRadioButtons() {
     $entity_init = clone $entity;
 
     // With no field data, no buttons are checked.
-    $this->drupalGet('entity_test/manage/' . $entity->id());
+    $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
     $this->assertNoFieldChecked('edit-card-1-0');
     $this->assertNoFieldChecked('edit-card-1-1');
     $this->assertNoFieldChecked('edit-card-1-2');
@@ -121,7 +121,7 @@ function testRadioButtons() {
     $this->assertFieldValues($entity_init, 'card_1', array(0));
 
     // Check that the selected button is checked.
-    $this->drupalGet('entity_test/manage/' . $entity->id());
+    $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
     $this->assertFieldChecked('edit-card-1-0');
     $this->assertNoFieldChecked('edit-card-1-1');
     $this->assertNoFieldChecked('edit-card-1-2');
@@ -136,7 +136,7 @@ function testRadioButtons() {
     $this->card1->save();
     $field->setRequired(TRUE);
     $field->save();
-    $this->drupalGet('entity_test/manage/' . $entity->id());
+    $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
     $this->assertFieldChecked('edit-card-1-99');
   }
 
@@ -165,7 +165,7 @@ function testCheckBoxes() {
     $entity_init = clone $entity;
 
     // Display form: with no field data, nothing is checked.
-    $this->drupalGet('entity_test/manage/' . $entity->id());
+    $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
     $this->assertNoFieldChecked('edit-card-2-0');
     $this->assertNoFieldChecked('edit-card-2-1');
     $this->assertNoFieldChecked('edit-card-2-2');
@@ -181,7 +181,7 @@ function testCheckBoxes() {
     $this->assertFieldValues($entity_init, 'card_2', array(0, 2));
 
     // Display form: check that the right options are selected.
-    $this->drupalGet('entity_test/manage/' . $entity->id());
+    $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
     $this->assertFieldChecked('edit-card-2-0');
     $this->assertNoFieldChecked('edit-card-2-1');
     $this->assertFieldChecked('edit-card-2-2');
@@ -196,7 +196,7 @@ function testCheckBoxes() {
     $this->assertFieldValues($entity_init, 'card_2', array(0));
 
     // Display form: check that the right options are selected.
-    $this->drupalGet('entity_test/manage/' . $entity->id());
+    $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
     $this->assertFieldChecked('edit-card-2-0');
     $this->assertNoFieldChecked('edit-card-2-1');
     $this->assertNoFieldChecked('edit-card-2-2');
@@ -225,7 +225,7 @@ function testCheckBoxes() {
     $this->card2->save();
     $field->setRequired(TRUE);
     $field->save();
-    $this->drupalGet('entity_test/manage/' . $entity->id());
+    $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
     $this->assertFieldChecked('edit-card-2-99');
   }
 
@@ -255,7 +255,7 @@ function testSelectListSingle() {
     $entity_init = clone $entity;
 
     // Display form.
-    $this->drupalGet('entity_test/manage/' . $entity->id());
+    $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
     // A required field without any value has a "none" option.
     $this->assertTrue($this->xpath('//select[@id=:id]//option[@value="_none" and text()=:label]', array(':id' => 'edit-card-1', ':label' => t('- Select a value -'))), 'A required select list has a "Select a value" choice.');
 
@@ -277,7 +277,7 @@ function testSelectListSingle() {
     $this->assertFieldValues($entity_init, 'card_1', array(0));
 
     // Display form: check that the right options are selected.
-    $this->drupalGet('entity_test/manage/' . $entity->id());
+    $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
     // A required field with a value has no 'none' option.
     $this->assertFalse($this->xpath('//select[@id=:id]//option[@value="_none"]', array(':id' => 'edit-card-1')), 'A required select list with an actual value has no "none" choice.');
     $this->assertOptionSelected('edit-card-1', 0);
@@ -289,12 +289,12 @@ function testSelectListSingle() {
     $field->save();
 
     // Display form.
-    $this->drupalGet('entity_test/manage/' . $entity->id());
+    $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
     // A non-required field has a 'none' option.
     $this->assertTrue($this->xpath('//select[@id=:id]//option[@value="_none" and text()=:label]', array(':id' => 'edit-card-1', ':label' => t('- None -'))), 'A non-required select list has a "None" choice.');
     // Submit form: Unselect the option.
     $edit = array('card_1' => '_none');
-    $this->drupalPostForm('entity_test/manage/' . $entity->id(), $edit, t('Save'));
+    $this->drupalPostForm('entity_test/manage/' . $entity->id() . '/edit', $edit, t('Save'));
     $this->assertFieldValues($entity_init, 'card_1', array());
 
     // Test optgroups.
@@ -304,7 +304,7 @@ function testSelectListSingle() {
     $this->card1->save();
 
     // Display form: with no field data, nothing is selected
-    $this->drupalGet('entity_test/manage/' . $entity->id());
+    $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
     $this->assertNoOptionSelected('edit-card-1', 0);
     $this->assertNoOptionSelected('edit-card-1', 1);
     $this->assertNoOptionSelected('edit-card-1', 2);
@@ -318,14 +318,14 @@ function testSelectListSingle() {
     $this->assertFieldValues($entity_init, 'card_1', array(0));
 
     // Display form: check that the right options are selected.
-    $this->drupalGet('entity_test/manage/' . $entity->id());
+    $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
     $this->assertOptionSelected('edit-card-1', 0);
     $this->assertNoOptionSelected('edit-card-1', 1);
     $this->assertNoOptionSelected('edit-card-1', 2);
 
     // Submit form: Unselect the option.
     $edit = array('card_1' => '_none');
-    $this->drupalPostForm('entity_test/manage/' . $entity->id(), $edit, t('Save'));
+    $this->drupalPostForm('entity_test/manage/' . $entity->id() . '/edit', $edit, t('Save'));
     $this->assertFieldValues($entity_init, 'card_1', array());
   }
 
@@ -354,7 +354,7 @@ function testSelectListMultiple() {
     $entity_init = clone $entity;
 
     // Display form: with no field data, nothing is selected.
-    $this->drupalGet('entity_test/manage/' . $entity->id());
+    $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
     $this->assertOptionSelected("edit-card-2", '_none');
     $this->assertNoOptionSelected('edit-card-2', 0);
     $this->assertNoOptionSelected('edit-card-2', 1);
@@ -367,7 +367,7 @@ function testSelectListMultiple() {
     $this->assertFieldValues($entity_init, 'card_2', array(0, 2));
 
     // Display form: check that the right options are selected.
-    $this->drupalGet('entity_test/manage/' . $entity->id());
+    $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
     $this->assertOptionSelected('edit-card-2', 0);
     $this->assertNoOptionSelected('edit-card-2', 1);
     $this->assertOptionSelected('edit-card-2', 2);
@@ -378,7 +378,7 @@ function testSelectListMultiple() {
     $this->assertFieldValues($entity_init, 'card_2', array(0));
 
     // Display form: check that the right options are selected.
-    $this->drupalGet('entity_test/manage/' . $entity->id());
+    $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
     $this->assertOptionSelected('edit-card-2', 0);
     $this->assertNoOptionSelected('edit-card-2', 1);
     $this->assertNoOptionSelected('edit-card-2', 2);
@@ -398,18 +398,18 @@ function testSelectListMultiple() {
     // Check that the 'none' option has no effect if actual options are selected
     // as well.
     $edit = array('card_2[]' => array('_none' => '_none', 0 => 0));
-    $this->drupalPostForm('entity_test/manage/' . $entity->id(), $edit, t('Save'));
+    $this->drupalPostForm('entity_test/manage/' . $entity->id() . '/edit', $edit, t('Save'));
     $this->assertFieldValues($entity_init, 'card_2', array(0));
 
     // Check that selecting the 'none' option empties the field.
     $edit = array('card_2[]' => array('_none' => '_none'));
-    $this->drupalPostForm('entity_test/manage/' . $entity->id(), $edit, t('Save'));
+    $this->drupalPostForm('entity_test/manage/' . $entity->id() . '/edit', $edit, t('Save'));
     $this->assertFieldValues($entity_init, 'card_2', array());
 
     // A required select list does not have an empty key.
     $field->setRequired(TRUE);
     $field->save();
-    $this->drupalGet('entity_test/manage/' . $entity->id());
+    $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
     $this->assertFalse($this->xpath('//select[@id=:id]//option[@value=""]', array(':id' => 'edit-card-2')), 'A required select list does not have an empty key.');
 
     // We do not have to test that a required select list with one option is
@@ -425,7 +425,7 @@ function testSelectListMultiple() {
     $field->save();
 
     // Display form: with no field data, nothing is selected.
-    $this->drupalGet('entity_test/manage/' . $entity->id());
+    $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
     $this->assertNoOptionSelected('edit-card-2', 0);
     $this->assertNoOptionSelected('edit-card-2', 1);
     $this->assertNoOptionSelected('edit-card-2', 2);
@@ -439,14 +439,14 @@ function testSelectListMultiple() {
     $this->assertFieldValues($entity_init, 'card_2', array(0));
 
     // Display form: check that the right options are selected.
-    $this->drupalGet('entity_test/manage/' . $entity->id());
+    $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
     $this->assertOptionSelected('edit-card-2', 0);
     $this->assertNoOptionSelected('edit-card-2', 1);
     $this->assertNoOptionSelected('edit-card-2', 2);
 
     // Submit form: Unselect the option.
     $edit = array('card_2[]' => array('_none' => '_none'));
-    $this->drupalPostForm('entity_test/manage/' . $entity->id(), $edit, t('Save'));
+    $this->drupalPostForm('entity_test/manage/' . $entity->id() . '/edit', $edit, t('Save'));
     $this->assertFieldValues($entity_init, 'card_2', array());
   }
 
@@ -476,7 +476,7 @@ function testEmptyValue() {
     $entity->save();
 
     // Display form: check that _none options are present and has label.
-    $this->drupalGet('entity_test/manage/' . $entity->id());
+    $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
     $this->assertTrue($this->xpath('//div[@id=:id]//input[@value=:value]', array(':id' => 'edit-card-1', ':value' => '_none')), 'A test radio button has a "None" choice.');
     $this->assertTrue($this->xpath('//div[@id=:id]//label[@for=:for and text()=:label]', array(':id' => 'edit-card-1', ':for' => 'edit-card-1-none', ':label' => 'N/A')), 'A test radio button has a "N/A" choice.');
 
@@ -488,7 +488,7 @@ function testEmptyValue() {
       ->save();
 
     // Display form: check that _none options are present and has label.
-    $this->drupalGet('entity_test/manage/' . $entity->id());
+    $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit');
     // A required field without any value has a "none" option.
     $this->assertTrue($this->xpath('//select[@id=:id]//option[@value="_none" and text()=:label]', array(':id' => 'edit-card-1', ':label' => t('- None -'))), 'A test select has a "None" choice.');
   }
diff --git a/core/modules/system/src/Tests/Entity/EntityFormTest.php b/core/modules/system/src/Tests/Entity/EntityFormTest.php
index ffa5cca84ac37da4e46e346f4ae723e753fc45dd..4b2647a5fa4d7a8bc3dbfb7691d078a6b42ba40a 100644
--- a/core/modules/system/src/Tests/Entity/EntityFormTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityFormTest.php
@@ -26,7 +26,7 @@ class EntityFormTest extends WebTestBase {
 
   protected function setUp() {
     parent::setUp();
-    $web_user = $this->drupalCreateUser(array('administer entity_test content'));
+    $web_user = $this->drupalCreateUser(array('administer entity_test content', 'view test entity'));
     $this->drupalLogin($web_user);
 
     // Add a language.
@@ -84,14 +84,14 @@ protected function doTestFormCRUD($entity_type) {
     $this->assertTrue($entity, format_string('%entity_type: Entity found in the database.', array('%entity_type' => $entity_type)));
 
     $edit['name[0][value]'] = $name2;
-    $this->drupalPostForm($entity_type . '/manage/' . $entity->id(), $edit, t('Save'));
+    $this->drupalPostForm($entity_type . '/manage/' . $entity->id() . '/edit', $edit, t('Save'));
     $entity = $this->loadEntityByName($entity_type, $name1);
     $this->assertFalse($entity, format_string('%entity_type: The entity has been modified.', array('%entity_type' => $entity_type)));
     $entity = $this->loadEntityByName($entity_type, $name2);
     $this->assertTrue($entity, format_string('%entity_type: Modified entity found in the database.', array('%entity_type' => $entity_type)));
     $this->assertNotEqual($entity->name->value, $name1, format_string('%entity_type: The entity name has been modified.', array('%entity_type' => $entity_type)));
 
-    $this->drupalGet($entity_type . '/manage/' . $entity->id());
+    $this->drupalGet($entity_type . '/manage/' . $entity->id() . '/edit');
     $this->clickLink(t('Delete'));
     $this->drupalPostForm(NULL, array(), t('Delete'));
     $entity = $this->loadEntityByName($entity_type, $name2);
@@ -125,12 +125,12 @@ protected function doTestMultilingualFormCRUD($entity_type_id) {
     $this->assertEqual($translated_entity->name->value, $name1_ro, format_string('%entity_type: The translation has been added.', array('%entity_type' => $entity_type_id)));
 
     $edit['name[0][value]'] = $name2_ro;
-    $this->drupalPostForm('ro/' . $entity_type_id . '/manage/' . $entity->id(), $edit, t('Save'));
+    $this->drupalPostForm('ro/' . $entity_type_id . '/manage/' . $entity->id() . '/edit', $edit, t('Save'));
     $translated_entity = $this->loadEntityByName($entity_type_id, $name1)->getTranslation('ro');
     $this->assertTrue($translated_entity, format_string('%entity_type: Modified translation found in the database.', array('%entity_type' => $entity_type_id)));
     $this->assertEqual($translated_entity->name->value, $name2_ro, format_string('%entity_type: The name of the translation has been modified.', array('%entity_type' => $entity_type_id)));
 
-    $this->drupalGet('ro/' . $entity_type_id . '/manage/' . $entity->id());
+    $this->drupalGet('ro/' . $entity_type_id . '/manage/' . $entity->id() . '/edit');
     $this->clickLink(t('Delete'));
     $this->drupalPostForm(NULL, array(), t('Delete Romanian translation'));
     $entity = $this->loadEntityByName($entity_type_id, $name1);
diff --git a/core/modules/system/src/Tests/Entity/EntityRevisionsTest.php b/core/modules/system/src/Tests/Entity/EntityRevisionsTest.php
index 53f5288e2847fd50e7554385ba435caa761ddd52..39f04c32d728e3d9cb14853e6551176c2b0cfc5c 100644
--- a/core/modules/system/src/Tests/Entity/EntityRevisionsTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityRevisionsTest.php
@@ -37,6 +37,7 @@ protected function setUp() {
     // Create and login user.
     $this->webUser = $this->drupalCreateUser(array(
       'administer entity_test content',
+      'view test entity',
     ));
     $this->drupalLogin($this->webUser);
   }
@@ -110,7 +111,7 @@ protected function runRevisionsTests($entity_type) {
 
     // Confirm the correct revision text appears in the edit form.
     $entity = entity_load($entity_type, $entity->id->value);
-    $this->drupalGet($entity_type . '/manage/' . $entity->id->value);
+    $this->drupalGet($entity_type . '/manage/' . $entity->id->value . '/edit');
     $this->assertFieldById('edit-name-0-value', $entity->name->value, format_string('%entity_type: Name matches in UI.', array('%entity_type' => $entity_type)));
     $this->assertFieldById('edit-field-test-text-0-value', $entity->field_test_text->value, format_string('%entity_type: Text matches in UI.', array('%entity_type' => $entity_type)));
   }
diff --git a/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php b/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php
index 1d6e4e82af49eaa2ad7718f874ab1c4db2119b3f..b44c7ebeb221889574173c5a4bf6651ed6c2b9fc 100644
--- a/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php
+++ b/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php
@@ -62,26 +62,6 @@ public function testAdd($entity_type_id) {
     return $form;
   }
 
-  /**
-   * Displays the 'Edit existing entity_test' form.
-   *
-   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
-   *   The route match object to get entity type from.
-   * @param string $entity_type_id
-   *   The entity type ID.
-   *
-   * @return array
-   *   The processed form for the edited entity.
-   *
-   * @see \Drupal\entity_test\Routing\EntityTestRoutes::routes()
-   */
-  public function testEdit(RouteMatchInterface $route_match, $entity_type_id) {
-    $entity = $route_match->getParameter($entity_type_id);
-    $form = $this->entityFormBuilder()->getForm($entity);
-    $form['#title'] = $entity->label();
-    return $form;
-  }
-
   /**
    * Returns an empty page.
    *
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
index 89b4c20405124e7638cabc54383456dcb1bd3c0c..1197c2cd34aa93ef80a09cee190acbb9c4dd8e6f 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
@@ -28,10 +28,14 @@
  *       "default" = "Drupal\entity_test\EntityTestForm",
  *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
  *     },
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
  *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
  *     "views_data" = "Drupal\entity_test\EntityTestViewsData"
  *   },
  *   base_table = "entity_test",
+ *   admin_permission = "administer entity_test content",
  *   persistent_cache = FALSE,
  *   list_cache_contexts = { "entity_test_view_grants" },
  *   entity_keys = {
@@ -43,7 +47,7 @@
  *   },
  *   links = {
  *     "canonical" = "/entity_test/{entity_test}",
- *     "edit-form" = "/entity_test/manage/{entity_test}",
+ *     "edit-form" = "/entity_test/manage/{entity_test}/edit",
  *     "delete-form" = "/entity_test/delete/entity_test/{entity_test}",
  *   },
  *   field_ui_base_route = "entity.entity_test.admin_form",
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestAdminRoutes.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestAdminRoutes.php
new file mode 100644
index 0000000000000000000000000000000000000000..2660412acb01e4a5ae7e41e36a70cca8ae813d08
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestAdminRoutes.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\entity_test\Entity\EntityTestAdminRoutes.
+ */
+
+namespace Drupal\entity_test\Entity;
+
+/**
+ * Defines a test entity type with administrative routes.
+ *
+ * @ContentEntityType(
+ *   id = "entity_test_admin_routes",
+ *   label = @Translation("Test entity - admin routes"),
+ *   handlers = {
+ *     "view_builder" = "Drupal\entity_test\EntityTestViewBuilder",
+ *     "access" = "Drupal\entity_test\EntityTestAccessControlHandler",
+ *     "form" = {
+ *       "default" = "Drupal\entity_test\EntityTestForm",
+ *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
+ *     },
+ *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
+ *     "views_data" = "Drupal\views\EntityViewsData",
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
+ *     },
+ *   },
+ *   base_table = "entity_test_admin_routes",
+ *   data_table = "entity_test_admin_routes_property_data",
+ *   admin_permission = "administer entity_test content",
+ *   translatable = TRUE,
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "uuid" = "uuid",
+ *     "bundle" = "type",
+ *     "label" = "name",
+ *     "langcode" = "langcode",
+ *   },
+ *   links = {
+ *     "canonical" = "/entity_test_admin_routes/manage/{entity_test_admin_routes}",
+ *     "edit-form" = "/entity_test_admin_routes/manage/{entity_test_admin_routes}/edit",
+ *     "delete-form" = "/entity_test/delete/entity_test_admin_routes/{entity_test_admin_routes}",
+ *   },
+ * )
+ */
+class EntityTestAdminRoutes extends EntityTest {
+
+}
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestBaseFieldDisplay.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestBaseFieldDisplay.php
index 915f057c864f55372aabe6168579ddefc89447f3..d2f83780a4a6a0be8432738db89f971a07ddf60f 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestBaseFieldDisplay.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestBaseFieldDisplay.php
@@ -22,9 +22,13 @@
  *     "form" = {
  *       "default" = "Drupal\entity_test\EntityTestForm"
  *     },
- *     "translation" = "Drupal\content_translation\ContentTranslationHandler"
+ *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
  *   },
  *   base_table = "entity_test_base_field_display",
+ *   admin_permission = "administer entity_test content",
  *   entity_keys = {
  *     "id" = "id",
  *     "label" = "name",
@@ -32,7 +36,9 @@
  *     "bundle" = "type"
  *   },
  *   links = {
+ *     "canonical" = "/entity_test_base_field_display/{entity_test_base_field_display}/edit",
  *     "edit-form" = "/entity_test_base_field_display/manage/{entity_test_base_field_display}",
+ *     "delete-form" = "/entity_test/delete/entity_test_base_field_display/{entity_test_base_field_display}/edit",
  *   },
  *   field_ui_base_route = "entity.entity_test_base_field_display.admin_form",
  * )
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php
index e821272150ffa9d6a36b4e0def726cdb738d665c..d1908d35be9b44008038c301e08e1ab7f5d4d885 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php
@@ -21,10 +21,14 @@
  *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
  *     },
  *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
- *     "views_data" = "Drupal\views\EntityViewsData"
+ *     "views_data" = "Drupal\views\EntityViewsData",
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
  *   },
  *   base_table = "entity_test_mul",
  *   data_table = "entity_test_mul_property_data",
+ *   admin_permission = "administer entity_test content",
  *   translatable = TRUE,
  *   entity_keys = {
  *     "id" = "id",
@@ -35,7 +39,7 @@
  *   },
  *   links = {
  *     "canonical" = "/entity_test_mul/manage/{entity_test_mul}",
- *     "edit-form" = "/entity_test_mul/manage/{entity_test_mul}",
+ *     "edit-form" = "/entity_test_mul/manage/{entity_test_mul}/edit",
  *     "delete-form" = "/entity_test/delete/entity_test_mul/{entity_test_mul}",
  *   },
  *   field_ui_base_route = "entity.entity_test_mul.admin_form",
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php
index 67811ac925463c17419153aca254839aae1a6484..055e2906c5a19be63fadc79c69ce1450927f25c7 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php
@@ -25,6 +25,9 @@
  *       "default" = "Drupal\entity_test\EntityTestForm",
  *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
  *     },
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
  *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
  *     "views_data" = "Drupal\views\EntityViewsData"
  *   },
@@ -40,7 +43,7 @@
  *   },
  *   links = {
  *     "canonical" = "/entity_test_mul_changed/manage/{entity_test_mul_changed}",
- *     "edit-form" = "/entity_test_mul_changed/manage/{entity_test_mul_changed}",
+ *     "edit-form" = "/entity_test_mul_changed/manage/{entity_test_mul_changed}/edit",
  *     "delete-form" = "/entity_test/delete/entity_test_mul_changed/{entity_test_mul_changed}",
  *   },
  *   field_ui_base_route = "entity.entity_test_mul_changed.admin_form",
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulLangcodeKey.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulLangcodeKey.php
index 3be6a903e9ee4e1d798769e543dfaba5097fd108..3f794e5b0b18ab1a768ee146db12abc8c0ed0036 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulLangcodeKey.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulLangcodeKey.php
@@ -23,10 +23,14 @@
  *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
  *     },
  *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
- *     "views_data" = "Drupal\views\EntityViewsData"
+ *     "views_data" = "Drupal\views\EntityViewsData",
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
  *   },
  *   base_table = "entity_test_mul_langcode_key",
  *   data_table = "entity_test_mul_langcode_key_field_data",
+ *   admin_permission = "administer entity_test content",
  *   translatable = TRUE,
  *   entity_keys = {
  *     "id" = "id",
@@ -38,7 +42,7 @@
  *   },
  *   links = {
  *     "canonical" = "/entity_test_mul_langcode_key/manage/{entity_test_mul_langcode_key}",
- *     "edit-form" = "/entity_test_mul_langcode_key/manage/{entity_test_mul_langcode_key}",
+ *     "edit-form" = "/entity_test_mul_langcode_key/manage/{entity_test_mul_langcode_key}/edit",
  *     "delete-form" = "/entity_test/delete/entity_test_mul_langcode_key/{entity_test_mul_langcode_key}",
  *   },
  *   field_ui_base_route = "entity.entity_test_mul_langcode_key.admin_form",
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
index a29dbbf424a584713eb1d843e6113e38a90768b1..1eb7715cf397fd42009610e8ac295e0b9a699c06 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
@@ -21,12 +21,16 @@
  *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
  *     },
  *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
- *     "views_data" = "Drupal\views\EntityViewsData"
+ *     "views_data" = "Drupal\views\EntityViewsData",
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
  *   },
  *   base_table = "entity_test_mulrev",
  *   data_table = "entity_test_mulrev_property_data",
  *   revision_table = "entity_test_mulrev_revision",
  *   revision_data_table = "entity_test_mulrev_property_revision",
+ *   admin_permission = "administer entity_test content",
  *   translatable = TRUE,
  *   entity_keys = {
  *     "id" = "id",
@@ -39,7 +43,7 @@
  *   links = {
  *     "canonical" = "/entity_test_mulrev/manage/{entity_test_mulrev}",
  *     "delete-form" = "/entity_test/delete/entity_test_mulrev/{entity_test_mulrev}",
- *     "edit-form" = "/entity_test_mulrev/manage/{entity_test_mulrev}",
+ *     "edit-form" = "/entity_test_mulrev/manage/{entity_test_mulrev}/edit",
  *     "revision" = "/entity_test_mulrev/{entity_test_mulrev}/revision/{entity_test_mulrev_revision}/view",
  *   }
  * )
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php
index 933e55adbc4b5cdd4cc00d1e92bc5f7d3c7c90d3..f2a9667d2e8f3697105e77dadbb7daf8e3933a8a 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php
@@ -23,6 +23,9 @@
  *       "default" = "Drupal\entity_test\EntityTestForm",
  *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
  *     },
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
  *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
  *     "views_data" = "Drupal\views\EntityViewsData"
  *   },
@@ -42,7 +45,7 @@
  *   links = {
  *     "canonical" = "/entity_test_mulrev_changed/manage/{entity_test_mulrev_changed}",
  *     "delete-form" = "/entity_test/delete/entity_test_mulrev_changed/{entity_test_mulrev_changed}",
- *     "edit-form" = "/entity_test_mulrev_changed/manage/{entity_test_mulrev_changed}",
+ *     "edit-form" = "/entity_test_mulrev_changed/manage/{entity_test_mulrev_changed}/edit",
  *     "revision" = "/entity_test_mulrev_changed/{entity_test_mulrev_changed}/revision/{entity_test_mulrev_changed_revision}/view",
  *   }
  * )
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php
index 7a4fefde876ce9b68c234cc034a971b874f5947a..047b85dc7fc50ba678dc5cbd853645434558d6c6 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php
@@ -18,16 +18,21 @@
  *   label = @Translation("Test entity - revisions"),
  *   handlers = {
  *     "access" = "Drupal\entity_test\EntityTestAccessControlHandler",
+ *     "view_builder" = "Drupal\entity_test\EntityTestViewBuilder",
  *     "form" = {
  *       "default" = "Drupal\entity_test\EntityTestForm",
  *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
  *     },
  *     "view_builder" = "Drupal\entity_test\EntityTestViewBuilder",
  *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
- *     "views_data" = "Drupal\views\EntityViewsData"
+ *     "views_data" = "Drupal\views\EntityViewsData",
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
  *   },
  *   base_table = "entity_test_rev",
  *   revision_table = "entity_test_rev_revision",
+ *   admin_permission = "administer entity_test content",
  *   entity_keys = {
  *     "id" = "id",
  *     "uuid" = "uuid",
@@ -39,7 +44,7 @@
  *   links = {
  *     "canonical" = "/entity_test_rev/manage/{entity_test_rev}",
  *     "delete-form" = "/entity_test/delete/entity_test_rev/{entity_test_rev}",
- *     "edit-form" = "/entity_test_rev/manage/{entity_test_rev}",
+ *     "edit-form" = "/entity_test_rev/manage/{entity_test_rev}/edit",
  *     "revision" = "/entity_test_rev/{entity_test_rev}/revision/{entity_test_rev_revision}/view",
  *   }
  * )
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestStringId.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestStringId.php
index d6b6c59ea129e29ec06acf09036de0bcac5caad3..1f53e044f40a09fdfa0c20871b96d1b06dab6c90 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestStringId.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestStringId.php
@@ -20,9 +20,13 @@
  *     "form" = {
  *       "default" = "Drupal\entity_test\EntityTestForm"
  *     },
- *     "translation" = "Drupal\content_translation\ContentTranslationHandler"
+ *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
  *   },
  *   base_table = "entity_test_string",
+ *   admin_permission = "administer entity_test content",
  *   entity_keys = {
  *     "id" = "id",
  *     "uuid" = "uuid",
diff --git a/core/modules/system/tests/modules/entity_test/src/Routing/EntityTestRoutes.php b/core/modules/system/tests/modules/entity_test/src/Routing/EntityTestRoutes.php
index 09d677f1864e34b6cff00fb06b75d849d8f8df8f..8992550f31693fc10edabe09e052b0c0bc8f48e3 100644
--- a/core/modules/system/tests/modules/entity_test/src/Routing/EntityTestRoutes.php
+++ b/core/modules/system/tests/modules/entity_test/src/Routing/EntityTestRoutes.php
@@ -33,30 +33,6 @@ public function routes() {
         array('_permission' => 'administer entity_test content')
       );
 
-      $routes["entity.$entity_type_id.canonical"] = new Route(
-        $entity_type_id . '/manage/{' . $entity_type_id . '}',
-        array('_controller' => '\Drupal\entity_test\Controller\EntityTestController::testEdit', 'entity_type_id' => $entity_type_id),
-        array('_permission' => 'administer entity_test content'),
-        array('parameters' => array(
-          $entity_type_id => array('type' => 'entity:' . $entity_type_id),
-        ))
-      );
-
-      $routes["entity.$entity_type_id.edit_form"] = new Route(
-        $entity_type_id . '/manage/{' . $entity_type_id . '}',
-        array('_controller' => '\Drupal\entity_test\Controller\EntityTestController::testEdit', 'entity_type_id' => $entity_type_id),
-        array('_permission' => 'administer entity_test content'),
-        array('parameters' => array(
-          $entity_type_id => array('type' => 'entity:' . $entity_type_id),
-        ))
-      );
-
-      $routes["entity.$entity_type_id.delete_form"] = new Route(
-        'entity_test/delete/' . $entity_type_id . '/{' . $entity_type_id . '}',
-        array('_entity_form' => $entity_type_id . '.delete'),
-        array('_permission' => 'administer entity_test content')
-      );
-
       $routes["entity.$entity_type_id.admin_form"] = new Route(
         "$entity_type_id/structure/{bundle}",
         array('_controller' => '\Drupal\entity_test\Controller\EntityTestController::testAdmin'),
diff --git a/core/modules/text/src/Tests/TextFieldTest.php b/core/modules/text/src/Tests/TextFieldTest.php
index a1c9ec32678f09bd2209deae0d0e78ff0bb24a43..8279a6ded7e414e66dcc034180814f5fabd4fc9e 100644
--- a/core/modules/text/src/Tests/TextFieldTest.php
+++ b/core/modules/text/src/Tests/TextFieldTest.php
@@ -223,7 +223,7 @@ function _testTextfieldWidgetsFormatted($field_type, $widget_type) {
 
     // Display edition form.
     // We should now have a 'text format' selector.
-    $this->drupalGet('entity_test/manage/' . $id);
+    $this->drupalGet('entity_test/manage/' . $id . '/edit');
     $this->assertFieldByName("{$field_name}[0][value]", NULL, 'Widget is displayed');
     $this->assertFieldByName("{$field_name}[0][format]", NULL, 'Format selector is displayed');
 
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/RouteProviderTest.php b/core/tests/Drupal/KernelTests/Core/Entity/RouteProviderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..3b7af4156c265aab1190596d7018c8dd7b3f74bf
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Entity/RouteProviderTest.php
@@ -0,0 +1,148 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\KernelTests\Core\Entity\RouteProviderTest.
+ */
+
+namespace Drupal\KernelTests\Core\Entity;
+
+use Drupal\entity_test\Entity\EntityTestAdminRoutes;
+use Drupal\entity_test\Entity\EntityTestMul;
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\user\Entity\Role;
+use Drupal\user\RoleInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+
+/**
+ * Tests route providers for entity types.
+ *
+ * @group Entity
+ */
+class RouteProviderTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['entity_test', 'user', 'system'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('user');
+    $this->installEntitySchema('entity_test_mul');
+    $this->installEntitySchema('entity_test_admin_routes');
+    $this->installSchema('system', 'router');
+
+    $router_builder = \Drupal::service('router.builder');
+    $router_builder->rebuild();
+
+    /** @var \Drupal\Core\Routing\RouteBuilderInterface $router_builder */
+    $router_builder = \Drupal::service('router.builder');
+    $router_builder->rebuild();
+
+    /** @var \Drupal\user\RoleInterface $role */
+    $role = Role::create([
+      'id' => RoleInterface::ANONYMOUS_ID
+    ]);
+    $role
+      ->grantPermission('administer entity_test content')
+      ->grantPermission('view test entity');
+    $role->save();
+  }
+
+  protected function httpKernelHandle($url) {
+    $request = Request::create($url);
+    /** @var \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel */
+    $http_kernel = \Drupal::service('http_kernel');
+    return $http_kernel->handle($request, HttpKernelInterface::SUB_REQUEST)->getContent();
+  }
+
+  /**
+   * @covers \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider::getRoutes
+   */
+  public function testHtmlRoutes() {
+    /** @var \Drupal\Core\Routing\RouteProviderInterface $route_provider */
+    $route_provider = \Drupal::service('router.route_provider');
+
+    $route = $route_provider->getRouteByName('entity.entity_test_mul.canonical');
+    $this->assertEquals('entity_test_mul.full', $route->getDefault('_entity_view'));
+    $this->assertEquals('\Drupal\Core\Entity\Controller\EntityController::title', $route->getDefault('_title_callback'));
+    $this->assertEquals('entity_test_mul.view', $route->getRequirement('_entity_access'));
+    $this->assertFalse($route->hasOption('_admin_route'));
+
+    $route = $route_provider->getRouteByName('entity.entity_test_mul.edit_form');
+    $this->assertEquals('entity_test_mul.default', $route->getDefault('_entity_form'));
+    $this->assertEquals('\Drupal\Core\Entity\Controller\EntityController::editTitle', $route->getDefault('_title_callback'));
+    $this->assertEquals('entity_test_mul.update', $route->getRequirement('_entity_access'));
+    $this->assertFalse($route->hasOption('_admin_route'));
+
+    $route = $route_provider->getRouteByName('entity.entity_test_mul.delete_form');
+    $this->assertEquals('entity_test_mul.delete', $route->getDefault('_entity_form'));
+    $this->assertEquals('\Drupal\Core\Entity\Controller\EntityController::deleteTitle', $route->getDefault('_title_callback'));
+    $this->assertEquals('entity_test_mul.delete', $route->getRequirement('_entity_access'));
+    $this->assertFalse($route->hasOption('_admin_route'));
+
+    $entity = EntityTestMul::create([
+      'name' => 'Test title',
+    ]);
+    $entity->save();
+
+    $this->setRawContent($this->httpKernelHandle($entity->url()));
+    $this->assertTitle('Test title | ');
+
+    $this->setRawContent($this->httpKernelHandle($entity->url('edit-form')));
+    $this->assertTitle('Edit Test title | ');
+
+    $this->setRawContent($this->httpKernelHandle($entity->url('delete-form')));
+    $this->assertTitle('Are you sure you want to delete the test entity - data table Test title? | ');
+  }
+
+  /**
+   * @covers \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider::getEditFormRoute
+   * @covers \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider::getDeleteFormRoute
+   */
+  public function testAdminHtmlRoutes() {
+    /** @var \Drupal\Core\Routing\RouteProviderInterface $route_provider */
+    $route_provider = \Drupal::service('router.route_provider');
+
+    $route = $route_provider->getRouteByName('entity.entity_test_admin_routes.canonical');
+    $this->assertEquals('entity_test_admin_routes.full', $route->getDefault('_entity_view'));
+    $this->assertEquals('\Drupal\Core\Entity\Controller\EntityController::title', $route->getDefault('_title_callback'));
+    $this->assertEquals('entity_test_admin_routes.view', $route->getRequirement('_entity_access'));
+    $this->assertFalse($route->hasOption('_admin_route'));
+
+    $route = $route_provider->getRouteByName('entity.entity_test_admin_routes.edit_form');
+    $this->assertEquals('entity_test_admin_routes.default', $route->getDefault('_entity_form'));
+    $this->assertEquals('\Drupal\Core\Entity\Controller\EntityController::editTitle', $route->getDefault('_title_callback'));
+    $this->assertEquals('entity_test_admin_routes.update', $route->getRequirement('_entity_access'));
+    $this->assertTrue($route->hasOption('_admin_route'));
+    $this->assertTrue($route->getOption('_admin_route'));
+
+    $route = $route_provider->getRouteByName('entity.entity_test_admin_routes.delete_form');
+    $this->assertEquals('entity_test_admin_routes.delete', $route->getDefault('_entity_form'));
+    $this->assertEquals('\Drupal\Core\Entity\Controller\EntityController::deleteTitle', $route->getDefault('_title_callback'));
+    $this->assertEquals('entity_test_admin_routes.delete', $route->getRequirement('_entity_access'));
+    $this->assertTrue($route->hasOption('_admin_route'));
+    $this->assertTrue($route->getOption('_admin_route'));
+
+    $entity = EntityTestAdminRoutes::create([
+      'name' => 'Test title',
+    ]);
+    $entity->save();
+
+    $this->setRawContent($this->httpKernelHandle($entity->url()));
+    $this->assertTitle('Test title | ');
+
+    $this->setRawContent($this->httpKernelHandle($entity->url('edit-form')));
+    $this->assertTitle('Edit Test title | ');
+
+    $this->setRawContent($this->httpKernelHandle($entity->url('delete-form')));
+    $this->assertTitle('Are you sure you want to delete the test entity - admin routes Test title? | ');
+  }
+
+}