Merge "Cleaning up previous content as soon as new content is available"
diff --git a/Documentation/dev-e2e-tests.txt b/Documentation/dev-e2e-tests.txt
index 9a62f01..7ebdbfd 100644
--- a/Documentation/dev-e2e-tests.txt
+++ b/Documentation/dev-e2e-tests.txt
@@ -1,29 +1,38 @@
 :linkattrs:
-= Gerrit Code Review - End to end load tests
+= Gerrit Code Review - End to end tests
 
-This document provides a description of a Gerrit load test scenario implemented using the
-link:https://gatling.io/[Gatling,role=external,window=_blank] framework.
+This document provides descriptions of Gerrit end-to-end (`e2e`) test scenarios implemented using
+the link:https://gatling.io/[Gatling,role=external,window=_blank] framework.
 
 Similar scenarios have been successfully used to compare performance of different Gerrit versions
-or study the Gerrit response under different load profiles.
+or study the Gerrit response under different load profiles. Although mostly for load, scenarios can
+either be for link:https://gatling.io/load-testing-continuous-integration/[load or functional,role=external,window=_blank]
+(e2e) testing purposes. Functional scenarios may then reuse this framework and Gatling's usability
+features such as its protocols (more below) and
+link:https://en.wikipedia.org/wiki/Domain-specific_language[DSL,role=external,window=_blank].
+
+That cross test-scope reusability applies to both Gerrit core scenarios and non-core ones, such as
+for Gerrit plugins or other potential extensions. End-to-end testing may then include scopes like
+feature integration, deployment, smoke (and load) testing. These load and functional test scopes
+should remain orthogonal to the unit and component (aka Gerrit `IT`-suffixed or `acceptance`) ones.
+The term `acceptance` though may still be coined by organizations to target e2e functional testing.
 
 == What is Gatling?
 
-Gatling is a load testing tool which provides out of the box support for the HTTP protocol.
+Gatling is mostly a load testing tool which provides out of the box support for the HTTP protocol.
 Documentation on how to write an HTTP load test can be found
 link:https://gatling.io/docs/current/http/http_protocol/[here,role=external,window=_blank].
-
-However, in the scenario we are proposing, we are leveraging the
-link:https://github.com/GerritForge/gatling-git[Gatling Git extension,role=external,window=_blank]
-to run tests at Git protocol level.
+However, in the scenarios that were initially proposed, the
+link:https://github.com/GerritForge/gatling-git[Gatling Git extension,role=external,window=_blank] was
+leveraged to run tests at the Git protocol level.
 
 Gatling is written in Scala, but the abstraction provided by the Gatling DSL makes the scenarios
 implementation easy even without any Scala knowledge. The
 link:https://gitenterprise.me/2019/12/20/stress-your-gerrit-with-gatling/[Stress your Gerrit with Gatling,role=external,window=_blank]
 blog post has more introductory information.
 
-Examples of scenarios can be found in the `e2e-tests` directory. The files in that directory
-should be formatted using the mainstream
+Examples of scenarios can be found in the `e2e-tests` directory. The files in that directory should
+be formatted using the mainstream
 link:https://plugins.jetbrains.com/plugin/1347-scala[Scala plugin for IntelliJ,role=external,window=_blank].
 The latter is not mandatory but preferred for `sbt` and Scala IDE purposes in this project.
 
@@ -86,10 +95,11 @@
 === Input file
 
 The `CloneUsingBothProtocols` scenario is fed with the data coming from the
-`src/test/resources/data/CloneUsingBothProtocols.json` file. Such a file contains the commands and
-repository used during the load test. That file currently looks like below. This scenario serves
-as a simple example with no actual load in it. It can be used to test or validate the local setup.
-More complex scenarios can be further developed, under the `com.google.gerrit.scenarios` package.
+`src/test/resources/data/com/google/gerrit/scenarios/CloneUsingBothProtocols.json` file. Such a
+file contains the commands and repository used during the e2e test. That file currently looks like
+below. This scenario serves as a simple example with no actual load in it. It can be used to test
+or validate the local setup. More complex scenarios can be further developed, under the
+`com.google.gerrit.scenarios` package.
 
 ----
 [
@@ -111,9 +121,19 @@
 * `pull`
 * `push`
 
+=== Project and HTTP credentials
+
 The example above assumes that the `loadtest-repo` project exists in the Gerrit under test. The
-`HTTP Credentials` or password obtained from test user's `Settings` (in Gerrit) may be required, in
-`src/test/resources/application.conf`, depending on the above commands used.
+`CloneUsingBothProtocols` scenario already includes creating that project and deleting it once done
+with it. That scenario class can be used as an example of how a scenario can compose itself
+alongside other scenarios (here, `CreateProject` and `DeleteProject`).
+
+The `HTTP Credentials` or password obtained from test user's `Settings` (in Gerrit) may be
+required, in `src/test/resources/application.conf`, depending on the above commands used. That
+file's `http` section shows which shell environment variables can be used to set those credentials.
+
+Executing the `CloneUsingBothProtocols` scenario, as is, does require setting the http credentials.
+That is because of the aforementioned create/delete project (http) scenarios composed within it.
 
 == How to run tests
 
@@ -142,6 +162,27 @@
 docker run -it e2e-tests -s com.google.gerrit.scenarios.CloneUsingBothProtocols
 ----
 
+=== How to run non-core scenarios
+
+Locally adding non-core scenarios, for example from Gerrit plugins, is as simple as copying such
+files in. Copying is necessary over linking, unless running using Docker (above) is not required.
+Docker does not support links for files it has to copy over through the Dockerfile (here, the
+scenario files). Here is how to proceed for adding such external (e.g., plugin) scenario files in:
+
+----
+pushd e2e-tests/src/test/scala
+cp -r (or, ln -s) scalaPackageStructure .
+popd
+
+pushd e2e-tests/src/test/resources/data
+cp -r (or, ln -s) jsonFilesPackageStructure .
+popd
+----
+
+The destination folders above readily git-ignore every non-core scenario file added under them. If
+running using Docker, `e2e-tests/Dockerfile` may require another `COPY` line for the hereby added
+scenarios. Aforementioned `sbt` or `docker` commands can then be used to run the added tests.
+
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-readme.txt b/Documentation/dev-readme.txt
index 5974aca..2748413 100644
--- a/Documentation/dev-readme.txt
+++ b/Documentation/dev-readme.txt
@@ -65,8 +65,8 @@
 [[e2e]]
 === End-to-end tests
 
-<<dev-e2e-tests#,This document>> describes how load test scenarios are
-implemented using link:https://gatling.io/[`Gatling`].
+<<dev-e2e-tests#,This document>> describes how `e2e` (load or functional) test
+scenarios are implemented using link:https://gatling.io/[`Gatling`,role=external,window=_blank].
 
 
 == Local server
diff --git a/Documentation/dev-release-jgit.txt b/Documentation/dev-release-jgit.txt
deleted file mode 100644
index 7fbbb95..0000000
--- a/Documentation/dev-release-jgit.txt
+++ /dev/null
@@ -1,53 +0,0 @@
-:linkattrs:
-= Making a Snapshot Release of JGit
-
-This step is only necessary if we need to create an unofficial JGit
-snapshot release and publish it to the
-link:https://developers.google.com/storage/[Google Cloud Storage,role=external,window=_blank].
-
-[[prepare-environment]]
-== Prepare the Maven Environment
-
-First, make sure you have done the necessary
-link:dev-release-deploy-config.html#deploy-configuration-settings-xml[
-configuration in Maven `settings.xml`].
-
-To apply the necessary settings in JGit's `pom.xml`, follow the instructions
-in link:dev-release-deploy-config.html#deploy-configuration-subprojects[
-Configuration for Subprojects in `pom.xml`], or apply the provided diff by
-executing the following command in the JGit workspace:
-
-----
-  git apply /path/to/gerrit/tools/jgit-snapshot-deploy-pom.diff
-----
-
-[[prepare-release]]
-== Prepare the Release
-
-Since JGit has its own release process we do not push any release tags. Instead
-we will use the output of `git describe` as the version of the current JGit
-snapshot.
-
-In the JGit workspace, execute the following command:
-
-----
-  ./tools/version.sh --release $(git describe)
-----
-
-[[publish-release]]
-== Publish the Release
-
-To deploy the new snapshot, execute the following command in the JGit
-workspace:
-
-----
-  mvn deploy
-----
-
-
-GERRIT
-------
-Part of link:index.html[Gerrit Code Review]
-
-SEARCHBOX
----------
diff --git a/WORKSPACE b/WORKSPACE
index 015675a..6a822d7 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -46,11 +46,13 @@
 # otherwise refer to RBE docs.
 rbe_autoconfig(name = "rbe_default")
 
+# TODO(davido): Switch to upstream again, when this PR is merged:
+# https://github.com/bazelbuild/rules_closure/pull/478
 http_archive(
     name = "io_bazel_rules_closure",
-    sha256 = "03c3b16f205085817fd89cfdcb2220a0138647ee7992be9cef291b069dd90301",
-    strip_prefix = "rules_closure-196a45f0ede2faec11dcc6c60fbc5e7471f4bd58",
-    urls = ["https://github.com/bazelbuild/rules_closure/archive/196a45f0ede2faec11dcc6c60fbc5e7471f4bd58.tar.gz"],
+    sha256 = "b9c2bc6ba377aa497eb7c31681d34404febf9d4e3c9c7d98ce0d78238a0af20f",
+    strip_prefix = "rules_closure-0.31",
+    urls = ["https://github.com/davido/rules_closure/archive/V0.31.tar.gz"],
 )
 
 http_archive(
@@ -149,24 +151,24 @@
     sha1 = "83cd2cd674a217ade95a4bb83a8a14f351f48bd0",
 )
 
-GUICE_VERS = "4.2.2"
+GUICE_VERS = "4.2.3"
 
 maven_jar(
     name = "guice-library",
     artifact = "com.google.inject:guice:" + GUICE_VERS,
-    sha1 = "6dacbe18e5eaa7f6c9c36db33b42e7985e94ce77",
+    sha1 = "2ea992d6d7bdcac7a43111a95d182a4c42eb5ff7",
 )
 
 maven_jar(
     name = "guice-assistedinject",
     artifact = "com.google.inject.extensions:guice-assistedinject:" + GUICE_VERS,
-    sha1 = "c33fb10080d58446f752b4fcfff8a5fabb80a449",
+    sha1 = "acbfddc556ee9496293ed1df250cc378f331d854",
 )
 
 maven_jar(
     name = "guice-servlet",
     artifact = "com.google.inject.extensions:guice-servlet:" + GUICE_VERS,
-    sha1 = "0d0054bdd812224078357a9b11409e43d182a046",
+    sha1 = "8d6e7e35eac4fb5e7df19c55b3bc23fa51b10a11",
 )
 
 maven_jar(
diff --git a/e2e-tests/README.md b/e2e-tests/README.md
deleted file mode 100644
index 534fde5..0000000
--- a/e2e-tests/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# How to build the Docker image
-
-```$shell
-docker build . -t e2e-tests
-```
-
-# How to run a test
-
-```$shell
-docker run -it e2e-tests -s com.google.gerrit.scenarios.ReplayRecordsFromFeederScenario
-```
diff --git a/e2e-tests/src/test/resources/data/.gitignore b/e2e-tests/src/test/resources/data/.gitignore
new file mode 100644
index 0000000..7354459
--- /dev/null
+++ b/e2e-tests/src/test/resources/data/.gitignore
@@ -0,0 +1,4 @@
+*
+!*/
+!/com/google/gerrit/scenarios/*
+!/.gitignore
diff --git a/e2e-tests/src/test/resources/data/CloneUsingBothProtocols.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CloneUsingBothProtocols.json
similarity index 100%
rename from e2e-tests/src/test/resources/data/CloneUsingBothProtocols.json
rename to e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CloneUsingBothProtocols.json
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateProject.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateProject.json
new file mode 100644
index 0000000..2e54de5
--- /dev/null
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateProject.json
@@ -0,0 +1,5 @@
+[
+  {
+    "url": "http://localhost:8080/a/projects/loadtest-repo"
+  }
+]
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/DeleteProject.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/DeleteProject.json
new file mode 100644
index 0000000..9312fb4
--- /dev/null
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/DeleteProject.json
@@ -0,0 +1,5 @@
+[
+  {
+    "url": "http://localhost:8080/a/projects/loadtest-repo/delete-project~delete"
+  }
+]
diff --git a/e2e-tests/src/test/resources/data/ReplayRecordsFromFeeder.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.json
similarity index 100%
rename from e2e-tests/src/test/resources/data/ReplayRecordsFromFeeder.json
rename to e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.json
diff --git a/e2e-tests/src/test/scala/.gitignore b/e2e-tests/src/test/scala/.gitignore
new file mode 100644
index 0000000..7354459
--- /dev/null
+++ b/e2e-tests/src/test/scala/.gitignore
@@ -0,0 +1,4 @@
+*
+!*/
+!/com/google/gerrit/scenarios/*
+!/.gitignore
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala
index c5a7cba..19fbf1b 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala
@@ -15,18 +15,32 @@
 package com.google.gerrit.scenarios
 
 import io.gatling.core.Predef._
+import io.gatling.core.feeder.FileBasedFeederBuilder
 import io.gatling.core.structure.ScenarioBuilder
 
 import scala.concurrent.duration._
 
 class CloneUsingBothProtocols extends GitSimulation {
+  private val data: FileBasedFeederBuilder[Any]#F = jsonFile(resource).queue
 
   private val test: ScenarioBuilder = scenario(name)
       .feed(data)
-      .exec(request)
+      .exec(gitRequest)
+
+  private val createProject = new CreateProject
+  private val deleteProject = new DeleteProject
 
   setUp(
+    createProject.test.inject(
+      atOnceUsers(1)
+    ),
     test.inject(
+      nothingFor(1 second),
       constantUsersPerSec(1) during (2 seconds)
-    )).protocols(protocol)
+    ),
+    deleteProject.test.inject(
+      nothingFor(3 second),
+      atOnceUsers(1)
+    ),
+  ).protocols(gitProtocol, httpProtocol)
 }
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateProject.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateProject.scala
new file mode 100644
index 0000000..58c8994
--- /dev/null
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateProject.scala
@@ -0,0 +1,32 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.scenarios
+
+import io.gatling.core.Predef._
+import io.gatling.core.feeder.FileBasedFeederBuilder
+import io.gatling.core.structure.ScenarioBuilder
+
+class CreateProject extends GerritSimulation {
+  private val data: FileBasedFeederBuilder[Any]#F = jsonFile(resource).queue
+
+  val test: ScenarioBuilder = scenario(name)
+      .feed(data)
+      .exec(httpRequest)
+
+  setUp(
+    test.inject(
+      atOnceUsers(1)
+    )).protocols(httpProtocol)
+}
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteProject.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteProject.scala
new file mode 100644
index 0000000..4b723cb
--- /dev/null
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteProject.scala
@@ -0,0 +1,32 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.scenarios
+
+import io.gatling.core.Predef._
+import io.gatling.core.feeder.FileBasedFeederBuilder
+import io.gatling.core.structure.ScenarioBuilder
+
+class DeleteProject extends GerritSimulation {
+  private val data: FileBasedFeederBuilder[Any]#F = jsonFile(resource).queue
+
+  val test: ScenarioBuilder = scenario(name)
+      .feed(data)
+      .exec(httpRequest)
+
+  setUp(
+    test.inject(
+      atOnceUsers(1)
+    )).protocols(httpProtocol)
+}
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala
new file mode 100644
index 0000000..b628bc7
--- /dev/null
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala
@@ -0,0 +1,34 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.scenarios
+
+import com.github.barbasa.gatling.git.GatlingGitConfiguration
+import io.gatling.core.Predef._
+import io.gatling.http.Predef.http
+import io.gatling.http.protocol.HttpProtocolBuilder
+import io.gatling.http.request.builder.HttpRequestBuilder
+
+class GerritSimulation extends Simulation {
+  implicit val conf: GatlingGitConfiguration = GatlingGitConfiguration()
+
+  private val path: String = this.getClass.getPackage.getName.replaceAllLiterally(".", "/")
+  protected val name: String = this.getClass.getSimpleName
+  protected val resource: String = s"data/$path/$name.json"
+
+  protected val httpRequest: HttpRequestBuilder = http(name).post("${url}")
+  protected val httpProtocol: HttpProtocolBuilder = http.basicAuth(
+    conf.httpConfiguration.userName,
+    conf.httpConfiguration.password)
+}
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GitSimulation.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GitSimulation.scala
index 4d5130f..e2f13a4 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GitSimulation.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GitSimulation.scala
@@ -16,23 +16,18 @@
 
 import java.io.{File, IOException}
 
+import com.github.barbasa.gatling.git.GitRequestSession
 import com.github.barbasa.gatling.git.protocol.GitProtocol
 import com.github.barbasa.gatling.git.request.builder.GitRequestBuilder
-import com.github.barbasa.gatling.git.{GatlingGitConfiguration, GitRequestSession}
 import io.gatling.core.Predef._
-import io.gatling.core.feeder.FileBasedFeederBuilder
 import org.apache.commons.io.FileUtils
 import org.eclipse.jgit.hooks.CommitMsgHook
 
-class GitSimulation extends Simulation {
-
-  implicit val conf: GatlingGitConfiguration = GatlingGitConfiguration()
+class GitSimulation extends GerritSimulation {
   implicit val postMessageHook: Option[String] = Some(s"hooks/${CommitMsgHook.NAME}")
 
-  protected val name: String = this.getClass.getSimpleName
-  protected val data: FileBasedFeederBuilder[Any]#F = jsonFile(s"data/$name.json").circular
-  protected val request = new GitRequestBuilder(GitRequestSession("${cmd}", "${url}"))
-  protected val protocol: GitProtocol = GitProtocol()
+  protected val gitRequest = new GitRequestBuilder(GitRequestSession("${cmd}", "${url}"))
+  protected val gitProtocol: GitProtocol = GitProtocol()
 
   after {
     Thread.sleep(5000)
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala
index 82342be..32df1b5 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala
@@ -15,16 +15,18 @@
 package com.google.gerrit.scenarios
 
 import io.gatling.core.Predef._
+import io.gatling.core.feeder.FileBasedFeederBuilder
 import io.gatling.core.structure.ScenarioBuilder
 
 import scala.concurrent.duration._
 
 class ReplayRecordsFromFeeder extends GitSimulation {
+  private val data: FileBasedFeederBuilder[Any]#F = jsonFile(resource).circular
 
   private val test: ScenarioBuilder = scenario(name)
       .repeat(10000) {
         feed(data)
-            .exec(request)
+            .exec(gitRequest)
       }
 
   setUp(
@@ -34,6 +36,6 @@
       rampUsers(10) during (5 seconds),
       constantUsersPerSec(20) during (15 seconds),
       constantUsersPerSec(20) during (15 seconds) randomized
-    )).protocols(protocol)
+    )).protocols(gitProtocol)
       .maxDuration(60 seconds)
 }
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 60127b9..7a84501 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -40,6 +40,7 @@
 import com.google.common.jimfs.Jimfs;
 import com.google.common.primitives.Chars;
 import com.google.gerrit.acceptance.AcceptanceTestRequestScope.Context;
+import com.google.gerrit.acceptance.PushOneCommit.Result;
 import com.google.gerrit.acceptance.testsuite.account.TestSshKeys;
 import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
 import com.google.gerrit.common.Nullable;
@@ -61,6 +62,7 @@
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.api.changes.ChangeApi;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.api.changes.RevisionApi;
 import com.google.gerrit.extensions.api.changes.SubmittedTogetherInfo;
@@ -836,6 +838,10 @@
     return gApi.changes().id(id).info();
   }
 
+  protected ChangeApi change(Result r) throws RestApiException {
+    return gApi.changes().id(r.getChange().getId().get());
+  }
+
   protected Optional<EditInfo> getEdit(String id) throws RestApiException {
     return gApi.changes().id(id).edit().get();
   }
diff --git a/java/com/google/gerrit/entities/AttentionStatus.java b/java/com/google/gerrit/entities/AttentionSetUpdate.java
similarity index 77%
rename from java/com/google/gerrit/entities/AttentionStatus.java
rename to java/com/google/gerrit/entities/AttentionSetUpdate.java
index c488ccd..45588722 100644
--- a/java/com/google/gerrit/entities/AttentionStatus.java
+++ b/java/com/google/gerrit/entities/AttentionSetUpdate.java
@@ -21,14 +21,14 @@
 /**
  * A single update to the attention set. To reconstruct the attention set these instances are parsed
  * in reverse chronological order. Since each update contains all required information and
- * invalidates all previous state (hence the name -Status rather than -Update), only the most recent
- * record is relevant for each user.
+ * invalidates all previous state, only the most recent record is relevant for each user.
  *
- * <p>See <a href="https://www.gerritcodereview.com/design-docs/attention-set.html">here</a> for
- * details.
+ * <p>See {@link com.google.gerrit.extensions.api.changes.AddToAttentionSetInput} and {@link
+ * com.google.gerrit.extensions.api.changes.RemoveFromAttentionSetInput} for the representation in
+ * the API.
  */
 @AutoValue
-public abstract class AttentionStatus {
+public abstract class AttentionSetUpdate {
 
   /** Users can be added to or removed from the attention set. */
   public enum Operation {
@@ -56,17 +56,17 @@
    * Create an instance from data read from NoteDB. This includes the timestamp taken from the
    * commit.
    */
-  public static AttentionStatus createFromRead(
+  public static AttentionSetUpdate createFromRead(
       Instant timestamp, Account.Id account, Operation operation, String reason) {
-    return new AutoValue_AttentionStatus(timestamp, account, operation, reason);
+    return new AutoValue_AttentionSetUpdate(timestamp, account, operation, reason);
   }
 
   /**
    * Create an instance to be written to NoteDB. This has no timestamp because the timestamp of the
    * commit will be used.
    */
-  public static AttentionStatus createForWrite(
+  public static AttentionSetUpdate createForWrite(
       Account.Id account, Operation operation, String reason) {
-    return new AutoValue_AttentionStatus(null, account, operation, reason);
+    return new AutoValue_AttentionSetUpdate(null, account, operation, reason);
   }
 }
diff --git a/java/com/google/gerrit/extensions/api/changes/AddToAttentionSetInput.java b/java/com/google/gerrit/extensions/api/changes/AddToAttentionSetInput.java
new file mode 100644
index 0000000..39efc64
--- /dev/null
+++ b/java/com/google/gerrit/extensions/api/changes/AddToAttentionSetInput.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.changes;
+
+/**
+ * Input at API level to add a user to the attention set.
+ *
+ * @see RemoveFromAttentionSetInput
+ * @see com.google.gerrit.extensions.common.AttentionSetEntry
+ */
+public class AddToAttentionSetInput {
+  public String user;
+  public String reason;
+
+  public AddToAttentionSetInput(String user, String reason) {
+    this.user = user;
+    this.reason = reason;
+  }
+
+  public AddToAttentionSetInput() {}
+}
diff --git a/java/com/google/gerrit/extensions/api/changes/AttentionSetApi.java b/java/com/google/gerrit/extensions/api/changes/AttentionSetApi.java
new file mode 100644
index 0000000..5086cd8
--- /dev/null
+++ b/java/com/google/gerrit/extensions/api/changes/AttentionSetApi.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.changes;
+
+import com.google.gerrit.extensions.restapi.NotImplementedException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+
+/** API for managing the attention set of a change. */
+public interface AttentionSetApi {
+
+  void remove(RemoveFromAttentionSetInput input) throws RestApiException;
+
+  /**
+   * A default implementation which allows source compatibility when adding new methods to the
+   * interface.
+   */
+  class NotImplemented implements AttentionSetApi {
+    @Override
+    public void remove(RemoveFromAttentionSetInput input) throws RestApiException {
+      throw new NotImplementedException();
+    }
+  }
+}
diff --git a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
index 96455a6..284d8f6 100644
--- a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -312,6 +312,16 @@
    */
   Set<String> getHashtags() throws RestApiException;
 
+  /**
+   * Manage the attention set.
+   *
+   * @param id The account identifier.
+   */
+  AttentionSetApi attention(String id) throws RestApiException;
+
+  /** Adds a user to the attention set. */
+  AccountInfo addToAttentionSet(AddToAttentionSetInput input) throws RestApiException;
+
   /** Set the assignee of a change. */
   AccountInfo setAssignee(AssigneeInput input) throws RestApiException;
 
@@ -581,6 +591,16 @@
     }
 
     @Override
+    public AttentionSetApi attention(String id) throws RestApiException {
+      throw new NotImplementedException();
+    }
+
+    @Override
+    public AccountInfo addToAttentionSet(AddToAttentionSetInput input) throws RestApiException {
+      throw new NotImplementedException();
+    }
+
+    @Override
     public AccountInfo setAssignee(AssigneeInput input) throws RestApiException {
       throw new NotImplementedException();
     }
diff --git a/java/com/google/gerrit/extensions/api/changes/RemoveFromAttentionSetInput.java b/java/com/google/gerrit/extensions/api/changes/RemoveFromAttentionSetInput.java
new file mode 100644
index 0000000..9212788
--- /dev/null
+++ b/java/com/google/gerrit/extensions/api/changes/RemoveFromAttentionSetInput.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.changes;
+
+import com.google.gerrit.extensions.restapi.DefaultInput;
+
+/**
+ * Input at API level to remove a user from the attention set.
+ *
+ * @see AddToAttentionSetInput
+ * @see com.google.gerrit.extensions.common.AttentionSetEntry
+ */
+public class RemoveFromAttentionSetInput {
+  @DefaultInput public String reason;
+
+  public RemoveFromAttentionSetInput(String reason) {
+    this.reason = reason;
+  }
+
+  public RemoveFromAttentionSetInput() {}
+}
diff --git a/java/com/google/gerrit/extensions/api/changes/RevisionApi.java b/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
index 6c6389e5..7ae570f 100644
--- a/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
@@ -16,6 +16,7 @@
 
 import com.google.common.collect.ListMultimap;
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.client.ArchiveFormat;
 import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.extensions.common.ActionInfo;
 import com.google.gerrit.extensions.common.ApprovalInfo;
@@ -158,6 +159,15 @@
   /** Returns votes on the revision. */
   ListMultimap<String, ApprovalInfo> votes() throws RestApiException;
 
+  /**
+   * Retrieves the revision as an archive.
+   *
+   * @param format the format of the archive
+   * @return the archive as {@link BinaryResult}
+   * @throws RestApiException
+   */
+  BinaryResult getArchive(ArchiveFormat format) throws RestApiException;
+
   abstract class MergeListRequest {
     private boolean addLinks;
     private int uninterestingParent = 1;
@@ -392,5 +402,10 @@
     public String etag() throws RestApiException {
       throw new NotImplementedException();
     }
+
+    @Override
+    public BinaryResult getArchive(ArchiveFormat format) throws RestApiException {
+      throw new NotImplementedException();
+    }
   }
 }
diff --git a/java/com/google/gerrit/extensions/client/ArchiveFormat.java b/java/com/google/gerrit/extensions/client/ArchiveFormat.java
new file mode 100644
index 0000000..4ec59cb
--- /dev/null
+++ b/java/com/google/gerrit/extensions/client/ArchiveFormat.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.client;
+
+/**
+ * The {@link com.google.gerrit.server.restapi.change.GetArchive} REST endpoint allows to download
+ * revisions as archive. This enum defines the supported archive formats.
+ */
+public enum ArchiveFormat {
+  TGZ,
+  TAR,
+  TBZ2,
+  TXZ,
+  ZIP;
+}
diff --git a/java/com/google/gerrit/extensions/common/AttentionSetEntry.java b/java/com/google/gerrit/extensions/common/AttentionSetEntry.java
new file mode 100644
index 0000000..356b38a
--- /dev/null
+++ b/java/com/google/gerrit/extensions/common/AttentionSetEntry.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.common;
+
+import java.sql.Timestamp;
+
+/**
+ * Represents a single user included in the attention set. Used in the API. See {@link
+ * com.google.gerrit.entities.AttentionSetUpdate} for the internal representation.
+ *
+ * <p>See <a href="https://www.gerritcodereview.com/design-docs/attention-set.html">here</a> for
+ * background.
+ */
+public class AttentionSetEntry {
+  /** The user included in the attention set. */
+  public AccountInfo accountInfo;
+  /** The timestamp of the last update. */
+  public Timestamp lastUpdate;
+  /** The human readable reason why the user was added. */
+  public String reason;
+
+  public AttentionSetEntry(AccountInfo accountInfo, Timestamp lastUpdate, String reason) {
+    this.accountInfo = accountInfo;
+    this.lastUpdate = lastUpdate;
+    this.reason = reason;
+  }
+}
diff --git a/java/com/google/gerrit/extensions/common/ChangeInfo.java b/java/com/google/gerrit/extensions/common/ChangeInfo.java
index d4a8477..dce6fd1 100644
--- a/java/com/google/gerrit/extensions/common/ChangeInfo.java
+++ b/java/com/google/gerrit/extensions/common/ChangeInfo.java
@@ -22,13 +22,28 @@
 import java.util.List;
 import java.util.Map;
 
+/**
+ * Representation of a change used in the API. Internally {@link
+ * com.google.gerrit.server.query.change.ChangeData} and {@link com.google.gerrit.entities.Change}
+ * are used.
+ *
+ * <p>Many fields are actually nullable.
+ */
 public class ChangeInfo {
   // ActionJson#copy(List, ChangeInfo) must be adapted if new fields are added that are not
   // protected by any ListChangesOption.
+
   public String id;
   public String project;
   public String branch;
   public String topic;
+  /**
+   * The <a href="https://www.gerritcodereview.com/design-docs/attention-set.html">attention set</a>
+   * for this change. Keyed by account ID. We don't use {@link
+   * com.google.gerrit.entities.Account.Id} to avoid a circular dependency.
+   */
+  public Map<Integer, AttentionSetEntry> attentionSet;
+
   public AccountInfo assignee;
   public Collection<String> hashtags;
   public String changeId;
diff --git a/java/com/google/gerrit/server/ChangeMessagesUtil.java b/java/com/google/gerrit/server/ChangeMessagesUtil.java
index 5f00b69..dd48b93 100644
--- a/java/com/google/gerrit/server/ChangeMessagesUtil.java
+++ b/java/com/google/gerrit/server/ChangeMessagesUtil.java
@@ -49,6 +49,8 @@
   public static final String TAG_RESTORE = AUTOGENERATED_TAG_PREFIX + "gerrit:restore";
   public static final String TAG_REVERT = AUTOGENERATED_TAG_PREFIX + "gerrit:revert";
   public static final String TAG_SET_ASSIGNEE = AUTOGENERATED_TAG_PREFIX + "gerrit:setAssignee";
+  public static final String TAG_UPDATE_ATTENTION_SET =
+      AUTOGENERATED_TAG_PREFIX + "gerrit:updateAttentionSet";
   public static final String TAG_SET_DESCRIPTION =
       AUTOGENERATED_TAG_PREFIX + "gerrit:setPsDescription";
   public static final String TAG_SET_HASHTAGS = AUTOGENERATED_TAG_PREFIX + "gerrit:setHashtag";
diff --git a/java/com/google/gerrit/server/api/changes/AttentionSetApiImpl.java b/java/com/google/gerrit/server/api/changes/AttentionSetApiImpl.java
new file mode 100644
index 0000000..8dc44b7
--- /dev/null
+++ b/java/com/google/gerrit/server/api/changes/AttentionSetApiImpl.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.api.changes;
+
+import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
+
+import com.google.gerrit.extensions.api.changes.AttentionSetApi;
+import com.google.gerrit.extensions.api.changes.RemoveFromAttentionSetInput;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.change.AttentionSetEntryResource;
+import com.google.gerrit.server.restapi.change.RemoveFromAttentionSet;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+public class AttentionSetApiImpl implements AttentionSetApi {
+  interface Factory {
+    AttentionSetApiImpl create(AttentionSetEntryResource attentionSetEntryResource);
+  }
+
+  private final RemoveFromAttentionSet removeFromAttentionSet;
+  private final AttentionSetEntryResource attentionSetEntryResource;
+
+  @Inject
+  AttentionSetApiImpl(
+      RemoveFromAttentionSet removeFromAttentionSet,
+      @Assisted AttentionSetEntryResource attentionSetEntryResource) {
+    this.removeFromAttentionSet = removeFromAttentionSet;
+    this.attentionSetEntryResource = attentionSetEntryResource;
+  }
+
+  @Override
+  public void remove(RemoveFromAttentionSetInput input) throws RestApiException {
+    try {
+      removeFromAttentionSet.apply(attentionSetEntryResource, input);
+    } catch (Exception e) {
+      throw asRestApiException("Cannot remove from attention set", e);
+    }
+  }
+}
diff --git a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index 0d640d9..5122f8a 100644
--- a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -23,7 +23,9 @@
 import com.google.gerrit.extensions.api.changes.AbandonInput;
 import com.google.gerrit.extensions.api.changes.AddReviewerInput;
 import com.google.gerrit.extensions.api.changes.AddReviewerResult;
+import com.google.gerrit.extensions.api.changes.AddToAttentionSetInput;
 import com.google.gerrit.extensions.api.changes.AssigneeInput;
+import com.google.gerrit.extensions.api.changes.AttentionSetApi;
 import com.google.gerrit.extensions.api.changes.ChangeApi;
 import com.google.gerrit.extensions.api.changes.ChangeEditApi;
 import com.google.gerrit.extensions.api.changes.ChangeMessageApi;
@@ -66,6 +68,8 @@
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.change.WorkInProgressOp;
 import com.google.gerrit.server.restapi.change.Abandon;
+import com.google.gerrit.server.restapi.change.AddToAttentionSet;
+import com.google.gerrit.server.restapi.change.AttentionSet;
 import com.google.gerrit.server.restapi.change.ChangeIncludedIn;
 import com.google.gerrit.server.restapi.change.ChangeMessages;
 import com.google.gerrit.server.restapi.change.Check;
@@ -147,6 +151,9 @@
   private final Provider<GetChange> getChangeProvider;
   private final PostHashtags postHashtags;
   private final GetHashtags getHashtags;
+  private final AttentionSet attentionSet;
+  private final AttentionSetApiImpl.Factory attentionSetApi;
+  private final AddToAttentionSet addToAttentionSet;
   private final PutAssignee putAssignee;
   private final GetAssignee getAssignee;
   private final GetPastAssignees getPastAssignees;
@@ -197,6 +204,9 @@
       Provider<GetChange> getChangeProvider,
       PostHashtags postHashtags,
       GetHashtags getHashtags,
+      AttentionSet attentionSet,
+      AttentionSetApiImpl.Factory attentionSetApi,
+      AddToAttentionSet addToAttentionSet,
       PutAssignee putAssignee,
       GetAssignee getAssignee,
       GetPastAssignees getPastAssignees,
@@ -245,6 +255,9 @@
     this.getChangeProvider = getChangeProvider;
     this.postHashtags = postHashtags;
     this.getHashtags = getHashtags;
+    this.attentionSet = attentionSet;
+    this.attentionSetApi = attentionSetApi;
+    this.addToAttentionSet = addToAttentionSet;
     this.putAssignee = putAssignee;
     this.getAssignee = getAssignee;
     this.getPastAssignees = getPastAssignees;
@@ -530,6 +543,24 @@
   }
 
   @Override
+  public AccountInfo addToAttentionSet(AddToAttentionSetInput input) throws RestApiException {
+    try {
+      return addToAttentionSet.apply(change, input).value();
+    } catch (Exception e) {
+      throw asRestApiException("Cannot add to attention set", e);
+    }
+  }
+
+  @Override
+  public AttentionSetApi attention(String id) throws RestApiException {
+    try {
+      return attentionSetApi.create(attentionSet.parse(change, IdString.fromDecoded(id)));
+    } catch (Exception e) {
+      throw asRestApiException("Cannot parse account", e);
+    }
+  }
+
+  @Override
   public AccountInfo setAssignee(AssigneeInput input) throws RestApiException {
     try {
       return putAssignee.apply(change, input).value();
diff --git a/java/com/google/gerrit/server/api/changes/Module.java b/java/com/google/gerrit/server/api/changes/Module.java
index 0edd58a..f54d1fe 100644
--- a/java/com/google/gerrit/server/api/changes/Module.java
+++ b/java/com/google/gerrit/server/api/changes/Module.java
@@ -32,5 +32,6 @@
     factory(RevisionReviewerApiImpl.Factory.class);
     factory(ChangeEditApiImpl.Factory.class);
     factory(ChangeMessageApiImpl.Factory.class);
+    factory(AttentionSetApiImpl.Factory.class);
   }
 }
diff --git a/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java b/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
index 48a8689..b515dfe 100644
--- a/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
@@ -37,6 +37,7 @@
 import com.google.gerrit.extensions.api.changes.RevisionReviewerApi;
 import com.google.gerrit.extensions.api.changes.RobotCommentApi;
 import com.google.gerrit.extensions.api.changes.SubmitInput;
+import com.google.gerrit.extensions.client.ArchiveFormat;
 import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.extensions.common.ActionInfo;
 import com.google.gerrit.extensions.common.ApprovalInfo;
@@ -70,6 +71,7 @@
 import com.google.gerrit.server.restapi.change.DraftComments;
 import com.google.gerrit.server.restapi.change.Files;
 import com.google.gerrit.server.restapi.change.Fixes;
+import com.google.gerrit.server.restapi.change.GetArchive;
 import com.google.gerrit.server.restapi.change.GetCommit;
 import com.google.gerrit.server.restapi.change.GetDescription;
 import com.google.gerrit.server.restapi.change.GetFixPreview;
@@ -96,6 +98,7 @@
 import com.google.inject.assistedinject.Assisted;
 import java.util.EnumSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import org.eclipse.jgit.lib.Repository;
@@ -146,6 +149,7 @@
   private final GetRelated getRelated;
   private final PutDescription putDescription;
   private final GetDescription getDescription;
+  private final Provider<GetArchive> getArchiveProvider;
   private final ApprovalsUtil approvalsUtil;
   private final AccountLoader.Factory accountLoaderFactory;
 
@@ -190,6 +194,7 @@
       GetRelated getRelated,
       PutDescription putDescription,
       GetDescription getDescription,
+      Provider<GetArchive> getArchiveProvider,
       ApprovalsUtil approvalsUtil,
       AccountLoader.Factory accountLoaderFactory,
       @Assisted RevisionResource r) {
@@ -232,6 +237,7 @@
     this.getRelated = getRelated;
     this.putDescription = putDescription;
     this.getDescription = getDescription;
+    this.getArchiveProvider = getArchiveProvider;
     this.approvalsUtil = approvalsUtil;
     this.accountLoaderFactory = accountLoaderFactory;
     this.revision = r;
@@ -649,4 +655,15 @@
   public String etag() throws RestApiException {
     return revisionActions.getETag(revision);
   }
+
+  @Override
+  public BinaryResult getArchive(ArchiveFormat format) throws RestApiException {
+    GetArchive getArchive = getArchiveProvider.get();
+    getArchive.setFormat(format != null ? format.name().toLowerCase(Locale.US) : null);
+    try {
+      return getArchive.apply(revision).value();
+    } catch (Exception e) {
+      throw asRestApiException("Cannot get archive", e);
+    }
+  }
 }
diff --git a/java/com/google/gerrit/server/change/ActionJson.java b/java/com/google/gerrit/server/change/ActionJson.java
index e87cf70..6f28dad 100644
--- a/java/com/google/gerrit/server/change/ActionJson.java
+++ b/java/com/google/gerrit/server/change/ActionJson.java
@@ -89,14 +89,12 @@
     return Lists.newArrayList(visitorSet);
   }
 
-  public ChangeInfo addChangeActions(ChangeInfo to, ChangeNotes notes) {
+  void addChangeActions(ChangeInfo to, ChangeNotes notes) {
     List<ActionVisitor> visitors = visitors();
     to.actions = toActionMap(notes, visitors, copy(visitors, to));
-    return to;
   }
 
-  public RevisionInfo addRevisionActions(
-      @Nullable ChangeInfo changeInfo, RevisionInfo to, RevisionResource rsrc) {
+  void addRevisionActions(@Nullable ChangeInfo changeInfo, RevisionInfo to, RevisionResource rsrc) {
     List<ActionVisitor> visitors = visitors();
     if (!visitors.isEmpty()) {
       if (changeInfo != null) {
@@ -106,7 +104,6 @@
       }
     }
     to.actions = toActionMap(rsrc, visitors, changeInfo, copy(visitors, to));
-    return to;
   }
 
   private ChangeInfo copy(List<ActionVisitor> visitors, ChangeInfo changeInfo) {
@@ -119,6 +116,8 @@
     copy.project = changeInfo.project;
     copy.branch = changeInfo.branch;
     copy.topic = changeInfo.topic;
+    copy.attentionSet =
+        changeInfo.attentionSet == null ? null : ImmutableMap.copyOf(changeInfo.attentionSet);
     copy.assignee = changeInfo.assignee;
     copy.hashtags = changeInfo.hashtags;
     copy.changeId = changeInfo.changeId;
diff --git a/java/com/google/gerrit/server/change/AddToAttentionSetOp.java b/java/com/google/gerrit/server/change/AddToAttentionSetOp.java
new file mode 100644
index 0000000..262fdc2
--- /dev/null
+++ b/java/com/google/gerrit/server/change/AddToAttentionSetOp.java
@@ -0,0 +1,85 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import static com.google.common.collect.ImmutableMap.toImmutableMap;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AttentionSetUpdate;
+import com.google.gerrit.entities.AttentionSetUpdate.Operation;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.ChangeMessagesUtil;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.update.BatchUpdateOp;
+import com.google.gerrit.server.update.ChangeContext;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import java.util.Map;
+import java.util.function.Function;
+
+/** Add a specified user to the attention set. */
+public class AddToAttentionSetOp implements BatchUpdateOp {
+
+  public interface Factory {
+    AddToAttentionSetOp create(Account.Id attentionUserId, String reason);
+  }
+
+  private final ChangeData.Factory changeDataFactory;
+  private final ChangeMessagesUtil cmUtil;
+  private final Account.Id attentionUserId;
+  private final String reason;
+
+  @Inject
+  AddToAttentionSetOp(
+      ChangeData.Factory changeDataFactory,
+      ChangeMessagesUtil cmUtil,
+      @Assisted Account.Id attentionUserId,
+      @Assisted String reason) {
+    this.changeDataFactory = changeDataFactory;
+    this.cmUtil = cmUtil;
+    this.attentionUserId = requireNonNull(attentionUserId, "user");
+    this.reason = requireNonNull(reason, "reason");
+  }
+
+  @Override
+  public boolean updateChange(ChangeContext ctx) throws RestApiException {
+    ChangeData changeData = changeDataFactory.create(ctx.getNotes());
+    Map<Account.Id, AttentionSetUpdate> attentionMap =
+        changeData.attentionSet().stream()
+            .collect(toImmutableMap(AttentionSetUpdate::account, Function.identity()));
+    AttentionSetUpdate existingEntry = attentionMap.get(attentionUserId);
+    if (existingEntry != null && existingEntry.operation() == Operation.ADD) {
+      return false;
+    }
+
+    ChangeUpdate update = ctx.getUpdate(ctx.getChange().currentPatchSetId());
+    update.setAttentionSetUpdates(
+        ImmutableList.of(
+            AttentionSetUpdate.createForWrite(
+                attentionUserId, AttentionSetUpdate.Operation.ADD, reason)));
+    addMessage(ctx, update);
+    return true;
+  }
+
+  private void addMessage(ChangeContext ctx, ChangeUpdate update) {
+    String message = "Added to attention set: " + attentionUserId;
+    cmUtil.addChangeMessage(
+        update,
+        ChangeMessagesUtil.newMessage(ctx, message, ChangeMessagesUtil.TAG_UPDATE_ATTENTION_SET));
+  }
+}
diff --git a/java/com/google/gerrit/server/change/ArchiveFormat.java b/java/com/google/gerrit/server/change/ArchiveFormatInternal.java
similarity index 95%
rename from java/com/google/gerrit/server/change/ArchiveFormat.java
rename to java/com/google/gerrit/server/change/ArchiveFormatInternal.java
index d895a66..f6e9ff9 100644
--- a/java/com/google/gerrit/server/change/ArchiveFormat.java
+++ b/java/com/google/gerrit/server/change/ArchiveFormatInternal.java
@@ -28,7 +28,7 @@
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectLoader;
 
-public enum ArchiveFormat {
+public enum ArchiveFormatInternal {
   TGZ("application/x-gzip", new TgzFormat()),
   TAR("application/x-tar", new TarFormat()),
   TBZ2("application/x-bzip2", new Tbz2Format()),
@@ -40,7 +40,7 @@
 
   private final String mimeType;
 
-  ArchiveFormat(String mimeType, ArchiveCommand.Format<?> format) {
+  ArchiveFormatInternal(String mimeType, ArchiveCommand.Format<?> format) {
     this.format = format;
     this.mimeType = mimeType;
     ArchiveCommand.registerFormat(name(), format);
diff --git a/java/com/google/gerrit/server/change/AttentionSetEntryResource.java b/java/com/google/gerrit/server/change/AttentionSetEntryResource.java
new file mode 100644
index 0000000..6c6c765
--- /dev/null
+++ b/java/com/google/gerrit/server/change/AttentionSetEntryResource.java
@@ -0,0 +1,46 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.extensions.restapi.RestResource;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.inject.TypeLiteral;
+
+/** REST resource that represents an entry in the attention set of a change. */
+public class AttentionSetEntryResource implements RestResource {
+  public static final TypeLiteral<RestView<AttentionSetEntryResource>> ATTENTION_SET_ENTRY_KIND =
+      new TypeLiteral<RestView<AttentionSetEntryResource>>() {};
+
+  public interface Factory {
+    AttentionSetEntryResource create(ChangeResource change, Account.Id id);
+  }
+
+  private final ChangeResource changeResource;
+  private final Account.Id accountId;
+
+  public AttentionSetEntryResource(ChangeResource changeResource, Account.Id accountId) {
+    this.changeResource = changeResource;
+    this.accountId = accountId;
+  }
+
+  public ChangeResource getChangeResource() {
+    return changeResource;
+  }
+
+  public Account.Id getAccountId() {
+    return accountId;
+  }
+}
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index b704205..e4148a5 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.common.collect.ImmutableMap.toImmutableMap;
 import static com.google.gerrit.extensions.client.ListChangesOption.ALL_COMMITS;
 import static com.google.gerrit.extensions.client.ListChangesOption.ALL_REVISIONS;
 import static com.google.gerrit.extensions.client.ListChangesOption.CHANGE_ACTIONS;
@@ -49,6 +50,7 @@
 import com.google.gerrit.common.data.SubmitRequirement;
 import com.google.gerrit.common.data.SubmitTypeRecord;
 import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AttentionSetUpdate;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.ChangeMessage;
 import com.google.gerrit.entities.PatchSet;
@@ -60,6 +62,7 @@
 import com.google.gerrit.extensions.client.ReviewerState;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.common.ApprovalInfo;
+import com.google.gerrit.extensions.common.AttentionSetEntry;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.ChangeMessageInfo;
 import com.google.gerrit.extensions.common.LabelInfo;
@@ -102,6 +105,7 @@
 import com.google.inject.Singleton;
 import com.google.inject.assistedinject.Assisted;
 import java.io.IOException;
+import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -504,6 +508,20 @@
     out.project = in.getProject().get();
     out.branch = in.getDest().shortName();
     out.topic = in.getTopic();
+    if (!cd.attentionSet().isEmpty()) {
+      out.attentionSet =
+          cd.attentionSet().stream()
+              // This filtering should match GetAttentionSet.
+              .filter(a -> a.operation() == AttentionSetUpdate.Operation.ADD)
+              .collect(
+                  toImmutableMap(
+                      a -> a.account().get(),
+                      a ->
+                          new AttentionSetEntry(
+                              accountLoader.get(a.account()),
+                              Timestamp.from(a.timestamp()),
+                              a.reason())));
+    }
     out.assignee = in.getAssignee() != null ? accountLoader.get(in.getAssignee()) : null;
     out.hashtags = cd.hashtags();
     out.changeId = in.getKey().get();
diff --git a/java/com/google/gerrit/server/change/RemoveFromAttentionSetOp.java b/java/com/google/gerrit/server/change/RemoveFromAttentionSetOp.java
new file mode 100644
index 0000000..7118089
--- /dev/null
+++ b/java/com/google/gerrit/server/change/RemoveFromAttentionSetOp.java
@@ -0,0 +1,84 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import static com.google.common.collect.ImmutableMap.toImmutableMap;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AttentionSetUpdate;
+import com.google.gerrit.entities.AttentionSetUpdate.Operation;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.ChangeMessagesUtil;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.update.BatchUpdateOp;
+import com.google.gerrit.server.update.ChangeContext;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import java.util.Map;
+import java.util.function.Function;
+
+/** Remove a specified user from the attention set. */
+public class RemoveFromAttentionSetOp implements BatchUpdateOp {
+
+  public interface Factory {
+    RemoveFromAttentionSetOp create(Account.Id attentionUserId, String reason);
+  }
+
+  private final ChangeData.Factory changeDataFactory;
+  private final ChangeMessagesUtil cmUtil;
+  private final Account.Id attentionUserId;
+  private final String reason;
+
+  @Inject
+  RemoveFromAttentionSetOp(
+      ChangeData.Factory changeDataFactory,
+      ChangeMessagesUtil cmUtil,
+      @Assisted Account.Id attentionUserId,
+      @Assisted String reason) {
+    this.changeDataFactory = changeDataFactory;
+    this.cmUtil = cmUtil;
+    this.attentionUserId = requireNonNull(attentionUserId, "user");
+    this.reason = requireNonNull(reason, "reason");
+  }
+
+  @Override
+  public boolean updateChange(ChangeContext ctx) throws RestApiException {
+    ChangeData changeData = changeDataFactory.create(ctx.getNotes());
+    Map<Account.Id, AttentionSetUpdate> attentionMap =
+        changeData.attentionSet().stream()
+            .collect(toImmutableMap(AttentionSetUpdate::account, Function.identity()));
+    AttentionSetUpdate existingEntry = attentionMap.get(attentionUserId);
+    if (existingEntry == null || existingEntry.operation() == Operation.REMOVE) {
+      return false;
+    }
+
+    ChangeUpdate update = ctx.getUpdate(ctx.getChange().currentPatchSetId());
+    update.setAttentionSetUpdates(
+        ImmutableList.of(
+            AttentionSetUpdate.createForWrite(attentionUserId, Operation.REMOVE, reason)));
+    addMessage(ctx, update);
+    return true;
+  }
+
+  private void addMessage(ChangeContext ctx, ChangeUpdate update) {
+    String message = "Removed from attention set: " + attentionUserId;
+    cmUtil.addChangeMessage(
+        update,
+        ChangeMessagesUtil.newMessage(ctx, message, ChangeMessagesUtil.TAG_UPDATE_ATTENTION_SET));
+  }
+}
diff --git a/java/com/google/gerrit/server/config/DownloadConfig.java b/java/com/google/gerrit/server/config/DownloadConfig.java
index 6dea07d..58ce098 100644
--- a/java/com/google/gerrit/server/config/DownloadConfig.java
+++ b/java/com/google/gerrit/server/config/DownloadConfig.java
@@ -17,7 +17,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.entities.CoreDownloadSchemes;
 import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DownloadCommand;
-import com.google.gerrit.server.change.ArchiveFormat;
+import com.google.gerrit.server.change.ArchiveFormatInternal;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.lang.reflect.Field;
@@ -37,7 +37,7 @@
 public class DownloadConfig {
   private final ImmutableSet<String> downloadSchemes;
   private final ImmutableSet<DownloadCommand> downloadCommands;
-  private final ImmutableSet<ArchiveFormat> archiveFormats;
+  private final ImmutableSet<ArchiveFormatInternal> archiveFormats;
 
   @Inject
   DownloadConfig(@GerritServerConfig Config cfg) {
@@ -69,13 +69,13 @@
 
     String v = cfg.getString("download", null, "archive");
     if (v == null) {
-      archiveFormats = ImmutableSet.copyOf(EnumSet.allOf(ArchiveFormat.class));
+      archiveFormats = ImmutableSet.copyOf(EnumSet.allOf(ArchiveFormatInternal.class));
     } else if (v.isEmpty() || "off".equalsIgnoreCase(v)) {
       archiveFormats = ImmutableSet.of();
     } else {
       archiveFormats =
           ImmutableSet.copyOf(
-              ConfigUtil.getEnumList(cfg, "download", null, "archive", ArchiveFormat.TGZ));
+              ConfigUtil.getEnumList(cfg, "download", null, "archive", ArchiveFormatInternal.TGZ));
     }
   }
 
@@ -110,7 +110,7 @@
   }
 
   /** Archive formats for downloading. */
-  public ImmutableSet<ArchiveFormat> getArchiveFormats() {
+  public ImmutableSet<ArchiveFormatInternal> getArchiveFormats() {
     return archiveFormats;
   }
 }
diff --git a/java/com/google/gerrit/server/git/validators/CommitValidators.java b/java/com/google/gerrit/server/git/validators/CommitValidators.java
index 6c4aacc..1e97a44 100644
--- a/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -403,6 +403,17 @@
     @Override
     public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent)
         throws CommitValidationException {
+      // TODO(zieren): Refactor interface to signal the intent of the event instead of hard-coding
+      // it here. Due to interface limitations, this method is called from both receive commits
+      // and from main Gerrit (e.g. when publishing a change edit). This is why we need to gate the
+      // early return on REFS_CHANGES (though pushes to refs/changes are not possible).
+      String refName = receiveEvent.command.getRefName();
+      if (!refName.startsWith("refs/for/") && !refName.startsWith(RefNames.REFS_CHANGES)) {
+        // This is a direct push bypassing review. We don't need to enforce any file-count limits
+        // here.
+        return Collections.emptyList();
+      }
+
       PatchListKey patchListKey =
           PatchListKey.againstBase(
               receiveEvent.commit.getId(), receiveEvent.commit.getParentCount());
diff --git a/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java b/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
index 287f3e7..86b6ed7 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
@@ -16,7 +16,7 @@
 
 import com.google.auto.value.AutoValue;
 import com.google.gerrit.entities.Account;
-import com.google.gerrit.entities.AttentionStatus;
+import com.google.gerrit.entities.AttentionSetUpdate;
 import com.google.gerrit.json.OutputFormat;
 import com.google.gerrit.server.config.GerritServerId;
 import com.google.gson.Gson;
@@ -171,11 +171,11 @@
   private static class AttentionStatusInNoteDb {
 
     final String personIdent;
-    final AttentionStatus.Operation operation;
+    final AttentionSetUpdate.Operation operation;
     final String reason;
 
     AttentionStatusInNoteDb(
-        String personIndent, AttentionStatus.Operation operation, String reason) {
+        String personIndent, AttentionSetUpdate.Operation operation, String reason) {
       this.personIdent = personIndent;
       this.operation = operation;
       this.reason = reason;
@@ -183,7 +183,7 @@
   }
 
   /** The returned {@link Optional} holds the parsed entity or is empty if parsing failed. */
-  static Optional<AttentionStatus> attentionStatusFromJson(
+  static Optional<AttentionSetUpdate> attentionStatusFromJson(
       Instant timestamp, String attentionString) {
     AttentionStatusInNoteDb inNoteDb =
         gson.fromJson(attentionString, AttentionStatusInNoteDb.class);
@@ -193,18 +193,20 @@
     }
     Optional<Account.Id> account = NoteDbUtil.parseIdent(personIdent);
     return account.map(
-        id -> AttentionStatus.createFromRead(timestamp, id, inNoteDb.operation, inNoteDb.reason));
+        id ->
+            AttentionSetUpdate.createFromRead(timestamp, id, inNoteDb.operation, inNoteDb.reason));
   }
 
-  String attentionStatusToJson(AttentionStatus attentionStatus) {
+  String attentionSetUpdateToJson(AttentionSetUpdate attentionSetUpdate) {
     PersonIdent personIdent =
         new PersonIdent(
-            getUsername(attentionStatus.account()), getEmailAddress(attentionStatus.account()));
+            getUsername(attentionSetUpdate.account()),
+            getEmailAddress(attentionSetUpdate.account()));
     StringBuilder stringBuilder = new StringBuilder();
     appendIdentString(stringBuilder, personIdent.getName(), personIdent.getEmailAddress());
     return gson.toJson(
         new AttentionStatusInNoteDb(
-            stringBuilder.toString(), attentionStatus.operation(), attentionStatus.reason()));
+            stringBuilder.toString(), attentionSetUpdate.operation(), attentionSetUpdate.reason()));
   }
 
   static void appendIdentString(StringBuilder stringBuilder, String name, String emailAddress) {
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotes.java b/java/com/google/gerrit/server/notedb/ChangeNotes.java
index 2de2195..84bd29b 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -40,7 +40,7 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.entities.Account;
-import com.google.gerrit.entities.AttentionStatus;
+import com.google.gerrit.entities.AttentionSetUpdate;
 import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.ChangeMessage;
@@ -376,8 +376,9 @@
     return state.reviewerUpdates();
   }
 
-  public ImmutableList<AttentionStatus> getAttentionUpdates() {
-    return state.attentionUpdates();
+  /** Returns the most recent update (i.e. status) per user. */
+  public ImmutableList<AttentionSetUpdate> getAttentionSet() {
+    return state.attentionSet();
   }
 
   /**
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index ed6039f..6b6a7ca 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -59,7 +59,7 @@
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.entities.Account;
-import com.google.gerrit.entities.AttentionStatus;
+import com.google.gerrit.entities.AttentionSetUpdate;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.ChangeMessage;
 import com.google.gerrit.entities.Comment;
@@ -118,7 +118,7 @@
   private final List<Account.Id> allPastReviewers;
   private final List<ReviewerStatusUpdate> reviewerUpdates;
   /** Holds only the most recent update per user. Older updates are discarded. */
-  private final Map<Account.Id, AttentionStatus> latestAttentionStatus;
+  private final Map<Account.Id, AttentionSetUpdate> latestAttentionStatus;
 
   private final List<AssigneeStatusUpdate> assigneeUpdates;
   private final List<SubmitRecord> submitRecords;
@@ -367,7 +367,7 @@
     }
 
     parseHashtags(commit);
-    parseAttentionUpdates(commit);
+    parseAttentionSetUpdates(commit);
     parseAssigneeUpdates(ts, commit);
 
     if (submissionId == null) {
@@ -578,11 +578,11 @@
     }
   }
 
-  private void parseAttentionUpdates(ChangeNotesCommit commit) throws ConfigInvalidException {
+  private void parseAttentionSetUpdates(ChangeNotesCommit commit) throws ConfigInvalidException {
     List<String> attentionStrings = commit.getFooterLineValues(FOOTER_ATTENTION);
     for (String attentionString : attentionStrings) {
 
-      Optional<AttentionStatus> attentionStatus =
+      Optional<AttentionSetUpdate> attentionStatus =
           ChangeNoteUtil.attentionStatusFromJson(
               Instant.ofEpochSecond(commit.getCommitTime()), attentionString);
       if (!attentionStatus.isPresent()) {
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesState.java b/java/com/google/gerrit/server/notedb/ChangeNotesState.java
index 67faa33..9cd4af3 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesState.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesState.java
@@ -35,7 +35,7 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.entities.Account;
-import com.google.gerrit.entities.AttentionStatus;
+import com.google.gerrit.entities.AttentionSetUpdate;
 import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.ChangeMessage;
@@ -56,7 +56,7 @@
 import com.google.gerrit.server.ReviewerStatusUpdate;
 import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto;
 import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.AssigneeStatusUpdateProto;
-import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.AttentionStatusProto;
+import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.AttentionSetUpdateProto;
 import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ChangeColumnsProto;
 import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerByEmailSetEntryProto;
 import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerSetEntryProto;
@@ -119,7 +119,7 @@
       ReviewerByEmailSet pendingReviewersByEmail,
       List<Account.Id> allPastReviewers,
       List<ReviewerStatusUpdate> reviewerUpdates,
-      List<AttentionStatus> attentionStatusUpdates,
+      List<AttentionSetUpdate> attentionSetUpdates,
       List<AssigneeStatusUpdate> assigneeUpdates,
       List<SubmitRecord> submitRecords,
       List<ChangeMessage> changeMessages,
@@ -170,7 +170,7 @@
         .pendingReviewersByEmail(pendingReviewersByEmail)
         .allPastReviewers(allPastReviewers)
         .reviewerUpdates(reviewerUpdates)
-        .attentionUpdates(attentionStatusUpdates)
+        .attentionSet(attentionSetUpdates)
         .assigneeUpdates(assigneeUpdates)
         .submitRecords(submitRecords)
         .changeMessages(changeMessages)
@@ -305,7 +305,8 @@
 
   abstract ImmutableList<ReviewerStatusUpdate> reviewerUpdates();
 
-  abstract ImmutableList<AttentionStatus> attentionUpdates();
+  /** Returns the most recent update (i.e. current status status) per user. */
+  abstract ImmutableList<AttentionSetUpdate> attentionSet();
 
   abstract ImmutableList<AssigneeStatusUpdate> assigneeUpdates();
 
@@ -384,7 +385,7 @@
           .pendingReviewersByEmail(ReviewerByEmailSet.empty())
           .allPastReviewers(ImmutableList.of())
           .reviewerUpdates(ImmutableList.of())
-          .attentionUpdates(ImmutableList.of())
+          .attentionSet(ImmutableList.of())
           .assigneeUpdates(ImmutableList.of())
           .submitRecords(ImmutableList.of())
           .changeMessages(ImmutableList.of())
@@ -418,7 +419,7 @@
 
     abstract Builder reviewerUpdates(List<ReviewerStatusUpdate> reviewerUpdates);
 
-    abstract Builder attentionUpdates(List<AttentionStatus> attentionUpdates);
+    abstract Builder attentionSet(List<AttentionSetUpdate> attentionSetUpdates);
 
     abstract Builder assigneeUpdates(List<AssigneeStatusUpdate> assigneeUpdates);
 
@@ -487,7 +488,7 @@
 
       object.allPastReviewers().forEach(a -> b.addPastReviewer(a.get()));
       object.reviewerUpdates().forEach(u -> b.addReviewerUpdate(toReviewerStatusUpdateProto(u)));
-      object.attentionUpdates().forEach(u -> b.addAttentionStatus(toAttentionStatusProto(u)));
+      object.attentionSet().forEach(u -> b.addAttentionSetUpdate(toAttentionSetUpdateProto(u)));
       object.assigneeUpdates().forEach(u -> b.addAssigneeUpdate(toAssigneeStatusUpdateProto(u)));
       object
           .submitRecords()
@@ -571,12 +572,13 @@
           .build();
     }
 
-    private static AttentionStatusProto toAttentionStatusProto(AttentionStatus attentionStatus) {
-      return AttentionStatusProto.newBuilder()
-          .setTimestampMillis(attentionStatus.timestamp().toEpochMilli())
-          .setAccount(attentionStatus.account().get())
-          .setOperation(attentionStatus.operation().name())
-          .setReason(attentionStatus.reason())
+    private static AttentionSetUpdateProto toAttentionSetUpdateProto(
+        AttentionSetUpdate attentionSetUpdate) {
+      return AttentionSetUpdateProto.newBuilder()
+          .setTimestampMillis(attentionSetUpdate.timestamp().toEpochMilli())
+          .setAccount(attentionSetUpdate.account().get())
+          .setOperation(attentionSetUpdate.operation().name())
+          .setReason(attentionSetUpdate.reason())
           .build();
     }
 
@@ -620,7 +622,7 @@
               .allPastReviewers(
                   proto.getPastReviewerList().stream().map(Account::id).collect(toImmutableList()))
               .reviewerUpdates(toReviewerStatusUpdateList(proto.getReviewerUpdateList()))
-              .attentionUpdates(toAttentionUpdateList(proto.getAttentionStatusList()))
+              .attentionSet(toAttentionSetUpdateList(proto.getAttentionSetUpdateList()))
               .assigneeUpdates(toAssigneeStatusUpdateList(proto.getAssigneeUpdateList()))
               .submitRecords(
                   proto.getSubmitRecordList().stream()
@@ -719,15 +721,15 @@
       return b.build();
     }
 
-    private static ImmutableList<AttentionStatus> toAttentionUpdateList(
-        List<AttentionStatusProto> protos) {
-      ImmutableList.Builder<AttentionStatus> b = ImmutableList.builder();
-      for (AttentionStatusProto proto : protos) {
+    private static ImmutableList<AttentionSetUpdate> toAttentionSetUpdateList(
+        List<AttentionSetUpdateProto> protos) {
+      ImmutableList.Builder<AttentionSetUpdate> b = ImmutableList.builder();
+      for (AttentionSetUpdateProto proto : protos) {
         b.add(
-            AttentionStatus.createFromRead(
+            AttentionSetUpdate.createFromRead(
                 Instant.ofEpochMilli(proto.getTimestampMillis()),
                 Account.id(proto.getAccount()),
-                AttentionStatus.Operation.valueOf(proto.getOperation()),
+                AttentionSetUpdate.Operation.valueOf(proto.getOperation()),
                 proto.getReason()));
       }
       return b.build();
diff --git a/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index 4492050..0de090f 100644
--- a/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -54,7 +54,7 @@
 import com.google.common.collect.TreeBasedTable;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.entities.Account;
-import com.google.gerrit.entities.AttentionStatus;
+import com.google.gerrit.entities.AttentionSetUpdate;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.Comment;
 import com.google.gerrit.entities.Project;
@@ -128,7 +128,7 @@
   private String submissionId;
   private String topic;
   private String commit;
-  private List<AttentionStatus> attentionUpdates;
+  private List<AttentionSetUpdate> attentionSetUpdates;
   private Optional<Account.Id> assignee;
   private Set<String> hashtags;
   private String changeMessage;
@@ -369,15 +369,15 @@
    * All updates must have a timestamp of null since we use the commit's timestamp. There also must
    * not be multiple updates for a single user.
    */
-  void setAttentionUpdates(List<AttentionStatus> attentionUpdates) {
+  public void setAttentionSetUpdates(List<AttentionSetUpdate> attentionSetUpdates) {
     checkArgument(
-        attentionUpdates.stream().noneMatch(x -> x.timestamp() != null),
+        attentionSetUpdates.stream().noneMatch(a -> a.timestamp() != null),
         "must not specify timestamp for write");
     checkArgument(
-        attentionUpdates.stream().map(AttentionStatus::account).distinct().count()
-            == attentionUpdates.size(),
+        attentionSetUpdates.stream().map(AttentionSetUpdate::account).distinct().count()
+            == attentionSetUpdates.size(),
         "must not specify multiple updates for single user");
-    this.attentionUpdates = attentionUpdates;
+    this.attentionSetUpdates = attentionSetUpdates;
   }
 
   public void setAssignee(Account.Id assignee) {
@@ -588,9 +588,9 @@
       addFooter(msg, FOOTER_COMMIT, commit);
     }
 
-    if (attentionUpdates != null) {
-      for (AttentionStatus attentionUpdate : attentionUpdates) {
-        addFooter(msg, FOOTER_ATTENTION, noteUtil.attentionStatusToJson(attentionUpdate));
+    if (attentionSetUpdates != null) {
+      for (AttentionSetUpdate attentionSetUpdate : attentionSetUpdates) {
+        addFooter(msg, FOOTER_ATTENTION, noteUtil.attentionSetUpdateToJson(attentionSetUpdate));
       }
     }
 
@@ -730,7 +730,7 @@
         && status == null
         && submissionId == null
         && submitRecords == null
-        && attentionUpdates == null
+        && attentionSetUpdates == null
         && assignee == null
         && hashtags == null
         && topic == null
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index 563ebb7..3dce678 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -37,6 +37,7 @@
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.common.data.SubmitTypeRecord;
 import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AttentionSetUpdate;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.ChangeMessage;
 import com.google.gerrit.entities.Comment;
@@ -297,6 +298,7 @@
   private List<ReviewerStatusUpdate> reviewerUpdates;
   private PersonIdent author;
   private PersonIdent committer;
+  private ImmutableList<AttentionSetUpdate> attentionSet;
   private int parentCount;
   private Integer unresolvedCommentCount;
   private Integer totalCommentCount;
@@ -598,6 +600,17 @@
     return true;
   }
 
+  /** Returns the most recent update (i.e. status) per user. */
+  public ImmutableList<AttentionSetUpdate> attentionSet() {
+    if (attentionSet == null) {
+      if (!lazyLoad) {
+        return ImmutableList.of();
+      }
+      attentionSet = notes().getAttentionSet();
+    }
+    return attentionSet;
+  }
+
   /** @return patches for the change, in patch set ID order. */
   public Collection<PatchSet> patchSets() {
     if (patchSets == null) {
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 43d7fc9..edd5411 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.query.change;
 
-import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.gerrit.entities.Change.CHANGE_ID_PATTERN;
 import static com.google.gerrit.server.account.AccountResolver.isSelf;
 import static com.google.gerrit.server.query.change.ChangeData.asChanges;
@@ -24,6 +23,7 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Enums;
 import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.flogger.FluentLogger;
 import com.google.common.primitives.Ints;
@@ -985,18 +985,23 @@
     if (isSelf(who)) {
       return isVisible();
     }
+    Set<Account.Id> accounts = null;
     try {
-      return Predicate.or(
-          parseAccount(who).stream()
-              .map(a -> visibleto(args.userFactory.create(a)))
-              .collect(toImmutableList()));
+      accounts = parseAccount(who);
     } catch (QueryParseException e) {
       if (e instanceof QueryRequiresAuthException) {
         throw e;
       }
-      // Otherwise continue: if it's not an account, maybe it's a group?
+    }
+    if (accounts != null) {
+      if (accounts.size() == 1) {
+        return visibleto(args.userFactory.create(Iterables.getOnlyElement(accounts)));
+      } else if (accounts.size() > 1) {
+        throw error(String.format("\"%s\" resolves to multiple accounts", who));
+      }
     }
 
+    // If its not an account, maybe its a group?
     Collection<GroupReference> suggestions = args.groupBackend.suggest(who, null);
     if (!suggestions.isEmpty()) {
       HashSet<AccountGroup.UUID> ids = new HashSet<>();
diff --git a/java/com/google/gerrit/server/restapi/change/AddToAttentionSet.java b/java/com/google/gerrit/server/restapi/change/AddToAttentionSet.java
new file mode 100644
index 0000000..5176fe9
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/change/AddToAttentionSet.java
@@ -0,0 +1,93 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.restapi.change;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.extensions.api.changes.AddToAttentionSetInput;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestCollectionModifyView;
+import com.google.gerrit.server.account.AccountLoader;
+import com.google.gerrit.server.account.AccountResolver;
+import com.google.gerrit.server.change.AddToAttentionSetOp;
+import com.google.gerrit.server.change.AttentionSetEntryResource;
+import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.permissions.ChangePermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.update.BatchUpdate;
+import com.google.gerrit.server.util.time.TimeUtil;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/** Adds a single user to the attention set. */
+@Singleton
+public class AddToAttentionSet
+    implements RestCollectionModifyView<
+        ChangeResource, AttentionSetEntryResource, AddToAttentionSetInput> {
+  private final BatchUpdate.Factory updateFactory;
+  private final AccountResolver accountResolver;
+  private final AddToAttentionSetOp.Factory opFactory;
+  private final AccountLoader.Factory accountLoaderFactory;
+  private final PermissionBackend permissionBackend;
+
+  @Inject
+  AddToAttentionSet(
+      BatchUpdate.Factory updateFactory,
+      AccountResolver accountResolver,
+      AddToAttentionSetOp.Factory opFactory,
+      AccountLoader.Factory accountLoaderFactory,
+      PermissionBackend permissionBackend) {
+    this.updateFactory = updateFactory;
+    this.accountResolver = accountResolver;
+    this.opFactory = opFactory;
+    this.accountLoaderFactory = accountLoaderFactory;
+    this.permissionBackend = permissionBackend;
+  }
+
+  @Override
+  public Response<AccountInfo> apply(ChangeResource changeResource, AddToAttentionSetInput input)
+      throws Exception {
+    input.user = Strings.nullToEmpty(input.user).trim();
+    if (input.user.isEmpty()) {
+      throw new BadRequestException("missing field: user");
+    }
+    input.reason = Strings.nullToEmpty(input.reason).trim();
+    if (input.reason.isEmpty()) {
+      throw new BadRequestException("missing field: reason");
+    }
+
+    Account.Id attentionUserId = accountResolver.resolve(input.user).asUnique().account().id();
+    try {
+      permissionBackend
+          .absentUser(attentionUserId)
+          .change(changeResource.getNotes())
+          .check(ChangePermission.READ);
+    } catch (AuthException e) {
+      throw new AuthException("read not permitted for " + attentionUserId, e);
+    }
+
+    try (BatchUpdate bu =
+        updateFactory.create(
+            changeResource.getChange().getProject(), changeResource.getUser(), TimeUtil.nowTs())) {
+      AddToAttentionSetOp op = opFactory.create(attentionUserId, input.reason);
+      bu.addOp(changeResource.getId(), op);
+      bu.execute();
+      return Response.ok(accountLoaderFactory.create(true).fillOne(attentionUserId));
+    }
+  }
+}
diff --git a/java/com/google/gerrit/server/restapi/change/AllowedFormats.java b/java/com/google/gerrit/server/restapi/change/AllowedFormats.java
index 2e313a1..ebec3295 100644
--- a/java/com/google/gerrit/server/restapi/change/AllowedFormats.java
+++ b/java/com/google/gerrit/server/restapi/change/AllowedFormats.java
@@ -18,7 +18,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
-import com.google.gerrit.server.change.ArchiveFormat;
+import com.google.gerrit.server.change.ArchiveFormatInternal;
 import com.google.gerrit.server.config.DownloadConfig;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -28,13 +28,13 @@
 
 @Singleton
 public class AllowedFormats {
-  final ImmutableMap<String, ArchiveFormat> extensions;
-  final ImmutableSet<ArchiveFormat> allowed;
+  final ImmutableMap<String, ArchiveFormatInternal> extensions;
+  final ImmutableSet<ArchiveFormatInternal> allowed;
 
   @Inject
   AllowedFormats(DownloadConfig cfg) {
-    Map<String, ArchiveFormat> exts = new HashMap<>();
-    for (ArchiveFormat format : cfg.getArchiveFormats()) {
+    Map<String, ArchiveFormatInternal> exts = new HashMap<>();
+    for (ArchiveFormatInternal format : cfg.getArchiveFormats()) {
       for (String ext : format.getSuffixes()) {
         exts.put(ext, format);
       }
@@ -46,14 +46,14 @@
     // valid JAR file, whose code would have access to cookies on the domain.
     allowed =
         Sets.immutableEnumSet(
-            Iterables.filter(cfg.getArchiveFormats(), f -> f != ArchiveFormat.ZIP));
+            Iterables.filter(cfg.getArchiveFormats(), f -> f != ArchiveFormatInternal.ZIP));
   }
 
-  public Set<ArchiveFormat> getAllowed() {
+  public Set<ArchiveFormatInternal> getAllowed() {
     return allowed;
   }
 
-  public ImmutableMap<String, ArchiveFormat> getExtensions() {
+  public ImmutableMap<String, ArchiveFormatInternal> getExtensions() {
     return extensions;
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/change/AttentionSet.java b/java/com/google/gerrit/server/restapi/change/AttentionSet.java
new file mode 100644
index 0000000..45d78dc
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/change/AttentionSet.java
@@ -0,0 +1,69 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.restapi.change;
+
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.server.account.AccountResolver;
+import com.google.gerrit.server.account.AccountResolver.UnresolvableAccountException;
+import com.google.gerrit.server.change.AttentionSetEntryResource;
+import com.google.gerrit.server.change.ChangeResource;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+
+@Singleton
+public class AttentionSet implements ChildCollection<ChangeResource, AttentionSetEntryResource> {
+  private final DynamicMap<RestView<AttentionSetEntryResource>> views;
+  private final AccountResolver accountResolver;
+  private final GetAttentionSet getAttentionSet;
+
+  @Inject
+  AttentionSet(
+      DynamicMap<RestView<AttentionSetEntryResource>> views,
+      GetAttentionSet getAttentionSet,
+      AccountResolver accountResolver) {
+    this.views = views;
+    this.accountResolver = accountResolver;
+    this.getAttentionSet = getAttentionSet;
+  }
+
+  @Override
+  public DynamicMap<RestView<AttentionSetEntryResource>> views() {
+    return views;
+  }
+
+  @Override
+  public RestView<ChangeResource> list() throws ResourceNotFoundException {
+    return getAttentionSet;
+  }
+
+  @Override
+  public AttentionSetEntryResource parse(ChangeResource changeResource, IdString idString)
+      throws ResourceNotFoundException, AuthException, IOException, ConfigInvalidException {
+    try {
+      Account.Id accountId = accountResolver.resolve(idString.get()).asUnique().account().id();
+      return new AttentionSetEntryResource(changeResource, accountId);
+    } catch (UnresolvableAccountException e) {
+      throw new ResourceNotFoundException(idString, e);
+    }
+  }
+}
diff --git a/java/com/google/gerrit/server/restapi/change/GetArchive.java b/java/com/google/gerrit/server/restapi/change/GetArchive.java
index 4ebcbdd..7ab1432 100644
--- a/java/com/google/gerrit/server/restapi/change/GetArchive.java
+++ b/java/com/google/gerrit/server/restapi/change/GetArchive.java
@@ -17,12 +17,13 @@
 import static com.google.gerrit.git.ObjectIds.abbreviateName;
 
 import com.google.common.base.Strings;
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.change.ArchiveFormat;
+import com.google.gerrit.server.change.ArchiveFormatInternal;
 import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
@@ -38,9 +39,12 @@
 public class GetArchive implements RestReadView<RevisionResource> {
   private final GitRepositoryManager repoManager;
   private final AllowedFormats allowedFormats;
+  @Nullable private String format;
 
   @Option(name = "--format")
-  private String format;
+  public void setFormat(String format) {
+    this.format = format;
+  }
 
   @Inject
   GetArchive(GitRepositoryManager repoManager, AllowedFormats allowedFormats) {
@@ -54,17 +58,17 @@
     if (Strings.isNullOrEmpty(format)) {
       throw new BadRequestException("format is not specified");
     }
-    final ArchiveFormat f = allowedFormats.extensions.get("." + format);
+    ArchiveFormatInternal f = allowedFormats.extensions.get("." + format);
     if (f == null) {
       throw new BadRequestException("unknown archive format");
     }
-    if (f == ArchiveFormat.ZIP) {
+    if (f == ArchiveFormatInternal.ZIP) {
       throw new MethodNotAllowedException("zip format is disabled");
     }
     boolean close = true;
-    final Repository repo = repoManager.openRepository(rsrc.getProject());
+    Repository repo = repoManager.openRepository(rsrc.getProject());
     try {
-      final RevCommit commit;
+      RevCommit commit;
       String name;
       try (RevWalk rw = new RevWalk(repo)) {
         commit = rw.parseCommit(rsrc.getPatchSet().commitId());
@@ -103,7 +107,7 @@
     }
   }
 
-  private static String name(ArchiveFormat format, RevWalk rw, RevCommit commit)
+  private static String name(ArchiveFormatInternal format, RevWalk rw, RevCommit commit)
       throws IOException {
     return String.format(
         "%s%s", abbreviateName(commit, rw.getObjectReader()), format.getDefaultSuffix());
diff --git a/java/com/google/gerrit/server/restapi/change/GetAttentionSet.java b/java/com/google/gerrit/server/restapi/change/GetAttentionSet.java
new file mode 100644
index 0000000..5d6d03d
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/change/GetAttentionSet.java
@@ -0,0 +1,59 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.restapi.change;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.AttentionSetUpdate.Operation;
+import com.google.gerrit.extensions.common.AttentionSetEntry;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.account.AccountLoader;
+import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.sql.Timestamp;
+import java.util.List;
+
+/** Reads the list of users currently in the attention set. */
+@Singleton
+public class GetAttentionSet implements RestReadView<ChangeResource> {
+
+  private final AccountLoader.Factory accountLoaderFactory;
+
+  @Inject
+  GetAttentionSet(AccountLoader.Factory accountLoaderFactory) {
+    this.accountLoaderFactory = accountLoaderFactory;
+  }
+
+  @Override
+  public Response<List<AttentionSetEntry>> apply(ChangeResource changeResource)
+      throws PermissionBackendException {
+    AccountLoader accountLoader = accountLoaderFactory.create(true);
+    ImmutableList<AttentionSetEntry> response =
+        changeResource.getNotes().getAttentionSet().stream()
+            // This filtering should match ChangeJson.
+            .filter(a -> a.operation() == Operation.ADD)
+            .map(
+                a ->
+                    new AttentionSetEntry(
+                        accountLoader.get(a.account()), Timestamp.from(a.timestamp()), a.reason()))
+            .collect(toImmutableList());
+    accountLoader.fill();
+    return Response.ok(response);
+  }
+}
diff --git a/java/com/google/gerrit/server/restapi/change/Module.java b/java/com/google/gerrit/server/restapi/change/Module.java
index 453b4df..387d0a8 100644
--- a/java/com/google/gerrit/server/restapi/change/Module.java
+++ b/java/com/google/gerrit/server/restapi/change/Module.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.restapi.change;
 
+import static com.google.gerrit.server.change.AttentionSetEntryResource.ATTENTION_SET_ENTRY_KIND;
 import static com.google.gerrit.server.change.ChangeEditResource.CHANGE_EDIT_KIND;
 import static com.google.gerrit.server.change.ChangeMessageResource.CHANGE_MESSAGE_KIND;
 import static com.google.gerrit.server.change.ChangeResource.CHANGE_KIND;
@@ -30,6 +31,7 @@
 import com.google.gerrit.extensions.restapi.RestApiModule;
 import com.google.gerrit.server.account.AccountLoader;
 import com.google.gerrit.server.change.AddReviewersOp;
+import com.google.gerrit.server.change.AddToAttentionSetOp;
 import com.google.gerrit.server.change.ChangeInserter;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.change.DeleteChangeOp;
@@ -38,6 +40,7 @@
 import com.google.gerrit.server.change.EmailReviewComments;
 import com.google.gerrit.server.change.PatchSetInserter;
 import com.google.gerrit.server.change.RebaseChangeOp;
+import com.google.gerrit.server.change.RemoveFromAttentionSetOp;
 import com.google.gerrit.server.change.ReviewerResource;
 import com.google.gerrit.server.change.SetAssigneeOp;
 import com.google.gerrit.server.change.SetCherryPickOp;
@@ -72,6 +75,7 @@
     DynamicMap.mapOf(binder(), CHANGE_EDIT_KIND);
     DynamicMap.mapOf(binder(), VOTE_KIND);
     DynamicMap.mapOf(binder(), CHANGE_MESSAGE_KIND);
+    DynamicMap.mapOf(binder(), ATTENTION_SET_ENTRY_KIND);
 
     postOnCollection(CHANGE_KIND).to(CreateChange.class);
     get(CHANGE_KIND).to(GetChange.class);
@@ -79,6 +83,10 @@
     get(CHANGE_KIND, "detail").to(GetDetail.class);
     get(CHANGE_KIND, "topic").to(GetTopic.class);
     get(CHANGE_KIND, "in").to(ChangeIncludedIn.class);
+    child(CHANGE_KIND, "attention").to(AttentionSet.class);
+    delete(ATTENTION_SET_ENTRY_KIND).to(RemoveFromAttentionSet.class);
+    post(ATTENTION_SET_ENTRY_KIND, "delete").to(RemoveFromAttentionSet.class);
+    postOnCollection(ATTENTION_SET_ENTRY_KIND).to(AddToAttentionSet.class);
     get(CHANGE_KIND, "assignee").to(GetAssignee.class);
     get(CHANGE_KIND, "past_assignees").to(GetPastAssignees.class);
     put(CHANGE_KIND, "assignee").to(PutAssignee.class);
@@ -207,5 +215,7 @@
     factory(SetPrivateOp.Factory.class);
     factory(WorkInProgressOp.Factory.class);
     factory(SetTopicOp.Factory.class);
+    factory(AddToAttentionSetOp.Factory.class);
+    factory(RemoveFromAttentionSetOp.Factory.class);
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/change/PreviewSubmit.java b/java/com/google/gerrit/server/restapi/change/PreviewSubmit.java
index e6a60d5..ed6c0a5 100644
--- a/java/com/google/gerrit/server/restapi/change/PreviewSubmit.java
+++ b/java/com/google/gerrit/server/restapi/change/PreviewSubmit.java
@@ -29,7 +29,7 @@
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.change.ArchiveFormat;
+import com.google.gerrit.server.change.ArchiveFormatInternal;
 import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.ioutil.LimitedByteArrayOutputStream;
@@ -87,12 +87,12 @@
     if (Strings.isNullOrEmpty(format)) {
       throw new BadRequestException("format is not specified");
     }
-    ArchiveFormat f = allowedFormats.extensions.get("." + format);
+    ArchiveFormatInternal f = allowedFormats.extensions.get("." + format);
     if (f == null && format.equals("tgz")) {
       // Always allow tgz, even when the allowedFormats doesn't contain it.
       // Then we allow at least one format even if the list of allowed
       // formats is empty.
-      f = ArchiveFormat.TGZ;
+      f = ArchiveFormatInternal.TGZ;
     }
     if (f == null) {
       throw new BadRequestException("unknown archive format");
@@ -109,7 +109,7 @@
     return Response.ok(getBundles(rsrc, f));
   }
 
-  private BinaryResult getBundles(RevisionResource rsrc, ArchiveFormat f)
+  private BinaryResult getBundles(RevisionResource rsrc, ArchiveFormatInternal f)
       throws RestApiException, UpdateException, IOException, ConfigInvalidException,
           PermissionBackendException {
     IdentifiedUser caller = rsrc.getUser().asIdentifiedUser();
@@ -138,10 +138,11 @@
   private static class SubmitPreviewResult extends BinaryResult {
 
     private final MergeOp mergeOp;
-    private final ArchiveFormat archiveFormat;
+    private final ArchiveFormatInternal archiveFormat;
     private final int maxBundleSize;
 
-    private SubmitPreviewResult(MergeOp mergeOp, ArchiveFormat archiveFormat, int maxBundleSize) {
+    private SubmitPreviewResult(
+        MergeOp mergeOp, ArchiveFormatInternal archiveFormat, int maxBundleSize) {
       this.mergeOp = mergeOp;
       this.archiveFormat = archiveFormat;
       this.maxBundleSize = maxBundleSize;
diff --git a/java/com/google/gerrit/server/restapi/change/RemoveFromAttentionSet.java b/java/com/google/gerrit/server/restapi/change/RemoveFromAttentionSet.java
new file mode 100644
index 0000000..ccf375a
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/change/RemoveFromAttentionSet.java
@@ -0,0 +1,70 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.restapi.change;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.api.changes.RemoveFromAttentionSetInput;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.change.AttentionSetEntryResource;
+import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.RemoveFromAttentionSetOp;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.update.BatchUpdate;
+import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
+import com.google.inject.Inject;
+import java.io.IOException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+
+/** Removes a single user from the attention set. */
+public class RemoveFromAttentionSet
+    implements RestModifyView<AttentionSetEntryResource, RemoveFromAttentionSetInput> {
+  private final BatchUpdate.Factory updateFactory;
+  private final RemoveFromAttentionSetOp.Factory opFactory;
+
+  @Inject
+  RemoveFromAttentionSet(
+      BatchUpdate.Factory updateFactory, RemoveFromAttentionSetOp.Factory opFactory) {
+    this.updateFactory = updateFactory;
+    this.opFactory = opFactory;
+  }
+
+  @Override
+  public Response<Object> apply(
+      AttentionSetEntryResource attentionResource, RemoveFromAttentionSetInput input)
+      throws RestApiException, PermissionBackendException, IOException, ConfigInvalidException,
+          UpdateException {
+    if (input == null) {
+      throw new BadRequestException("input may not be null");
+    }
+    input.reason = Strings.nullToEmpty(input.reason).trim();
+    if (input.reason.isEmpty()) {
+      throw new BadRequestException("missing field: reason");
+    }
+    ChangeResource changeResource = attentionResource.getChangeResource();
+    try (BatchUpdate bu =
+        updateFactory.create(
+            changeResource.getProject(), changeResource.getUser(), TimeUtil.nowTs())) {
+      RemoveFromAttentionSetOp op =
+          opFactory.create(attentionResource.getAccountId(), input.reason);
+      bu.addOp(changeResource.getId(), op);
+      bu.execute();
+    }
+    return Response.none();
+  }
+}
diff --git a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
index 4ddc3e8..c83bf42 100644
--- a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
+++ b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
@@ -43,7 +43,7 @@
 import com.google.gerrit.server.account.AccountVisibilityProvider;
 import com.google.gerrit.server.account.Realm;
 import com.google.gerrit.server.avatar.AvatarProvider;
-import com.google.gerrit.server.change.ArchiveFormat;
+import com.google.gerrit.server.change.ArchiveFormatInternal;
 import com.google.gerrit.server.change.MergeabilityComputationBehavior;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.AllUsersName;
@@ -254,7 +254,9 @@
           }
         });
     info.archives =
-        archiveFormats.getAllowed().stream().map(ArchiveFormat::getShortName).collect(toList());
+        archiveFormats.getAllowed().stream()
+            .map(ArchiveFormatInternal::getShortName)
+            .collect(toList());
     return info;
   }
 
diff --git a/java/com/google/gerrit/server/restapi/project/CreateBranch.java b/java/com/google/gerrit/server/restapi/project/CreateBranch.java
index c15fdeb..b901057 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateBranch.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateBranch.java
@@ -17,7 +17,6 @@
 import static com.google.gerrit.entities.RefNames.isConfigRef;
 
 import com.google.common.base.Strings;
-import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.extensions.api.projects.BranchInfo;
@@ -46,7 +45,6 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.io.IOException;
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.RefUpdate;
@@ -58,8 +56,6 @@
 @Singleton
 public class CreateBranch
     implements RestCollectionCreateView<ProjectResource, BranchResource, BranchInput> {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
   private final Provider<IdentifiedUser> identifiedUser;
   private final PermissionBackend permissionBackend;
   private final GitRepositoryManager repoManager;
@@ -114,7 +110,7 @@
               + "\"");
     }
 
-    final BranchNameKey name = BranchNameKey.create(rsrc.getNameKey(), ref);
+    BranchNameKey name = BranchNameKey.create(rsrc.getNameKey(), ref);
     try (Repository repo = repoManager.openRepository(rsrc.getNameKey())) {
       ObjectId revid = RefUtil.parseBaseRevision(repo, rsrc.getNameKey(), input.revision);
       RevWalk rw = RefUtil.verifyConnected(repo, revid);
@@ -122,80 +118,71 @@
 
       if (ref.startsWith(Constants.R_HEADS)) {
         // Ensure that what we start the branch from is a commit. If we
-        // were given a tag, deference to the commit instead.
+        // were given a tag, dereference to the commit instead.
         //
-        try {
-          object = rw.parseCommit(object);
-        } catch (IncorrectObjectTypeException notCommit) {
-          throw new BadRequestException("\"" + input.revision + "\" not a commit", notCommit);
-        }
+        object = rw.parseCommit(object);
       }
 
       createRefControl.checkCreateRef(identifiedUser, repo, name, object);
 
-      try {
-        final RefUpdate u = repo.updateRef(ref);
-        u.setExpectedOldObjectId(ObjectId.zeroId());
-        u.setNewObjectId(object.copy());
-        u.setRefLogIdent(identifiedUser.get().newRefLogIdent());
-        u.setRefLogMessage("created via REST from " + input.revision, false);
-        refCreationValidator.validateRefOperation(rsrc.getName(), identifiedUser.get(), u);
-        final RefUpdate.Result result = u.update(rw);
-        switch (result) {
-          case FAST_FORWARD:
-          case NEW:
-          case NO_CHANGE:
-            referenceUpdated.fire(
-                name.project(), u, ReceiveCommand.Type.CREATE, identifiedUser.get().state());
-            break;
-          case LOCK_FAILURE:
-            if (repo.getRefDatabase().exactRef(ref) != null) {
-              throw new ResourceConflictException("branch \"" + ref + "\" already exists");
+      RefUpdate u = repo.updateRef(ref);
+      u.setExpectedOldObjectId(ObjectId.zeroId());
+      u.setNewObjectId(object.copy());
+      u.setRefLogIdent(identifiedUser.get().newRefLogIdent());
+      u.setRefLogMessage("created via REST from " + input.revision, false);
+      refCreationValidator.validateRefOperation(rsrc.getName(), identifiedUser.get(), u);
+      RefUpdate.Result result = u.update(rw);
+      switch (result) {
+        case FAST_FORWARD:
+        case NEW:
+        case NO_CHANGE:
+          referenceUpdated.fire(
+              name.project(), u, ReceiveCommand.Type.CREATE, identifiedUser.get().state());
+          break;
+        case LOCK_FAILURE:
+          if (repo.getRefDatabase().exactRef(ref) != null) {
+            throw new ResourceConflictException("branch \"" + ref + "\" already exists");
+          }
+          String refPrefix = RefUtil.getRefPrefix(ref);
+          while (!Constants.R_HEADS.equals(refPrefix)) {
+            if (repo.getRefDatabase().exactRef(refPrefix) != null) {
+              throw new ResourceConflictException(
+                  "Cannot create branch \""
+                      + ref
+                      + "\" since it conflicts with branch \""
+                      + refPrefix
+                      + "\".");
             }
-            String refPrefix = RefUtil.getRefPrefix(ref);
-            while (!Constants.R_HEADS.equals(refPrefix)) {
-              if (repo.getRefDatabase().exactRef(refPrefix) != null) {
-                throw new ResourceConflictException(
-                    "Cannot create branch \""
-                        + ref
-                        + "\" since it conflicts with branch \""
-                        + refPrefix
-                        + "\".");
-              }
-              refPrefix = RefUtil.getRefPrefix(refPrefix);
-            }
-            throw new LockFailureException(String.format("Failed to create %s", ref), u);
-          case FORCED:
-          case IO_FAILURE:
-          case NOT_ATTEMPTED:
-          case REJECTED:
-          case REJECTED_CURRENT_BRANCH:
-          case RENAMED:
-          case REJECTED_MISSING_OBJECT:
-          case REJECTED_OTHER_REASON:
-          default:
-            throw new IOException(String.format("Failed to create %s: %s", ref, result.name()));
-        }
-
-        BranchInfo info = new BranchInfo();
-        info.ref = ref;
-        info.revision = revid.getName();
-
-        if (isConfigRef(name.branch())) {
-          // Never allow to delete the meta config branch.
-          info.canDelete = null;
-        } else {
-          info.canDelete =
-              permissionBackend.currentUser().ref(name).testOrFalse(RefPermission.DELETE)
-                      && rsrc.getProjectState().statePermitsWrite()
-                  ? true
-                  : null;
-        }
-        return Response.created(info);
-      } catch (IOException err) {
-        logger.atSevere().withCause(err).log("Cannot create branch \"%s\"", name);
-        throw err;
+            refPrefix = RefUtil.getRefPrefix(refPrefix);
+          }
+          throw new LockFailureException(String.format("Failed to create %s", ref), u);
+        case FORCED:
+        case IO_FAILURE:
+        case NOT_ATTEMPTED:
+        case REJECTED:
+        case REJECTED_CURRENT_BRANCH:
+        case RENAMED:
+        case REJECTED_MISSING_OBJECT:
+        case REJECTED_OTHER_REASON:
+        default:
+          throw new IOException(String.format("Failed to create %s: %s", ref, result.name()));
       }
+
+      BranchInfo info = new BranchInfo();
+      info.ref = ref;
+      info.revision = revid.getName();
+
+      if (isConfigRef(name.branch())) {
+        // Never allow to delete the meta config branch.
+        info.canDelete = null;
+      } else {
+        info.canDelete =
+            permissionBackend.currentUser().ref(name).testOrFalse(RefPermission.DELETE)
+                    && rsrc.getProjectState().statePermitsWrite()
+                ? true
+                : null;
+      }
+      return Response.created(info);
     } catch (RefUtil.InvalidRevisionException e) {
       throw new BadRequestException("invalid revision \"" + input.revision + "\"", e);
     }
diff --git a/java/com/google/gerrit/sshd/commands/UploadArchive.java b/java/com/google/gerrit/sshd/commands/UploadArchive.java
index 8543a1c..67dc5a5 100644
--- a/java/com/google/gerrit/sshd/commands/UploadArchive.java
+++ b/java/com/google/gerrit/sshd/commands/UploadArchive.java
@@ -20,7 +20,7 @@
 import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.server.change.ArchiveFormat;
+import com.google.gerrit.server.change.ArchiveFormatInternal;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.permissions.ProjectPermission;
@@ -174,7 +174,7 @@
       // Parse Git arguments
       readArguments();
 
-      ArchiveFormat f = allowedFormats.getExtensions().get("." + options.format);
+      ArchiveFormatInternal f = allowedFormats.getExtensions().get("." + options.format);
       if (f == null) {
         throw new Failure(3, "fatal: upload-archive not permitted for format " + options.format);
       }
@@ -222,8 +222,8 @@
     }
   }
 
-  private Map<String, Object> getFormatOptions(ArchiveFormat f) {
-    if (f == ArchiveFormat.ZIP) {
+  private Map<String, Object> getFormatOptions(ArchiveFormatInternal f) {
+    if (f == ArchiveFormatInternal.ZIP) {
       int value =
           Arrays.asList(
                   options.level0,
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 9399c3b..50aaa27 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -3210,7 +3210,8 @@
     mergeInput.source = "dev";
     MergePatchSetInput in = new MergePatchSetInput();
     in.merge = mergeInput;
-    in.subject = "update change by merge ps2";
+    String subject = "update change by merge ps2";
+    in.subject = subject;
 
     TestWorkInProgressStateChangedListener wipStateChangedListener =
         new TestWorkInProgressStateChangedListener();
@@ -3234,6 +3235,16 @@
     List<ChangeMessageInfo> messages = gApi.changes().id(changeId).messages();
     assertThat(messages).hasSize(2);
     assertThat(Iterables.getLast(messages).message).isEqualTo("Uploaded patch set 2.");
+
+    assertThat(changeInfo.revisions.get(changeInfo.currentRevision).commit.message)
+        .contains(subject);
+
+    // No subject: reuse message from previous patchset.
+    in.subject = null;
+    gApi.changes().id(changeId).createMergePatchSet(in);
+    changeInfo = gApi.changes().id(changeId).get(ALL_REVISIONS, CURRENT_COMMIT, CURRENT_REVISION);
+    assertThat(changeInfo.revisions.get(changeInfo.currentRevision).commit.message)
+        .contains(subject);
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/acceptance/api/project/CheckProjectIT.java b/javatests/com/google/gerrit/acceptance/api/project/CheckProjectIT.java
index 27dd16a..4163e17 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/CheckProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/CheckProjectIT.java
@@ -53,14 +53,14 @@
     PushOneCommit.Result r = createChange("refs/for/master");
     String branch = r.getChange().change().getDest().branch();
 
-    ChangeInfo info = gApi.changes().id(r.getChange().getId().get()).info();
+    ChangeInfo info = change(r).info();
     assertThat(info.status).isEqualTo(ChangeStatus.NEW);
 
     CheckProjectResultInfo checkResult =
         gApi.projects().name(project.get()).check(checkProjectInputForAutoCloseableCheck(branch));
     assertThat(checkResult.autoCloseableChangesCheckResult.autoCloseableChanges).isEmpty();
 
-    info = gApi.changes().id(r.getChange().getId().get()).info();
+    info = change(r).info();
     assertThat(info.status).isEqualTo(ChangeStatus.NEW);
   }
 
@@ -121,7 +121,7 @@
     RevCommit amendedCommit = serverSideTestRepo.amend(r.getCommit()).create();
     serverSideTestRepo.branch(branch).update(amendedCommit);
 
-    ChangeInfo info = gApi.changes().id(r.getChange().getId().get()).info();
+    ChangeInfo info = change(r).info();
     assertThat(info.status).isEqualTo(ChangeStatus.NEW);
 
     CheckProjectResultInfo checkResult =
@@ -132,7 +132,7 @@
                 .collect(toSet()))
         .containsExactly(r.getChange().getId().get());
 
-    info = gApi.changes().id(r.getChange().getId().get()).info();
+    info = change(r).info();
     assertThat(info.status).isEqualTo(ChangeStatus.NEW);
   }
 
@@ -144,7 +144,7 @@
     RevCommit amendedCommit = serverSideTestRepo.amend(r.getCommit()).create();
     serverSideTestRepo.branch(branch).update(amendedCommit);
 
-    ChangeInfo info = gApi.changes().id(r.getChange().getId().get()).info();
+    ChangeInfo info = change(r).info();
     assertThat(info.status).isEqualTo(ChangeStatus.NEW);
 
     CheckProjectInput input = checkProjectInputForAutoCloseableCheck(branch);
@@ -156,7 +156,7 @@
                 .collect(toSet()))
         .containsExactly(r.getChange().getId().get());
 
-    info = gApi.changes().id(r.getChange().getId().get()).info();
+    info = change(r).info();
     assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
   }
 
@@ -170,7 +170,7 @@
 
     serverSideTestRepo.commit(amendedCommit);
 
-    ChangeInfo info = gApi.changes().id(r.getChange().getId().get()).info();
+    ChangeInfo info = change(r).info();
     assertThat(info.status).isEqualTo(ChangeStatus.NEW);
 
     CheckProjectInput input = checkProjectInputForAutoCloseableCheck(branch);
@@ -179,7 +179,7 @@
     CheckProjectResultInfo checkResult = gApi.projects().name(project.get()).check(input);
     assertThat(checkResult.autoCloseableChangesCheckResult.autoCloseableChanges).isEmpty();
 
-    info = gApi.changes().id(r.getChange().getId().get()).info();
+    info = change(r).info();
     assertThat(info.status).isEqualTo(ChangeStatus.NEW);
 
     input.autoCloseableChangesCheck.maxCommits = 2;
@@ -190,7 +190,7 @@
                 .collect(toSet()))
         .containsExactly(r.getChange().getId().get());
 
-    info = gApi.changes().id(r.getChange().getId().get()).info();
+    info = change(r).info();
     assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
   }
 
@@ -204,7 +204,7 @@
 
     serverSideTestRepo.commit(amendedCommit);
 
-    ChangeInfo info = gApi.changes().id(r.getChange().getId().get()).info();
+    ChangeInfo info = change(r).info();
     assertThat(info.status).isEqualTo(ChangeStatus.NEW);
 
     CheckProjectInput input = checkProjectInputForAutoCloseableCheck(branch);
@@ -213,7 +213,7 @@
     CheckProjectResultInfo checkResult = gApi.projects().name(project.get()).check(input);
     assertThat(checkResult.autoCloseableChangesCheckResult.autoCloseableChanges).isEmpty();
 
-    info = gApi.changes().id(r.getChange().getId().get()).info();
+    info = change(r).info();
     assertThat(info.status).isEqualTo(ChangeStatus.NEW);
 
     input.autoCloseableChangesCheck.skipCommits = 1;
@@ -224,7 +224,7 @@
                 .collect(toSet()))
         .containsExactly(r.getChange().getId().get());
 
-    info = gApi.changes().id(r.getChange().getId().get()).info();
+    info = change(r).info();
     assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
   }
 
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 70fcfc4..2c2abfe 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -303,13 +303,7 @@
     PushOneCommit.Result r = createChange();
     requestScopeOperations.setApiUser(user.id());
     AuthException thrown =
-        assertThrows(
-            AuthException.class,
-            () ->
-                gApi.changes()
-                    .id(r.getChange().getId().get())
-                    .current()
-                    .review(ReviewInput.approve()));
+        assertThrows(AuthException.class, () -> change(r).current().review(ReviewInput.approve()));
     assertThat(thrown).hasMessageThat().contains("is restricted");
   }
 
@@ -560,7 +554,7 @@
     PushOneCommit.Result r = push.to("refs/for/master%topic=someTopic");
 
     // Verify before the cherry-pick that the change has exactly 1 message.
-    ChangeApi changeApi = gApi.changes().id(r.getChange().getId().get());
+    ChangeApi changeApi = change(r);
     assertThat(changeApi.get().messages).hasSize(1);
 
     // Cherry-pick the change to the other branch, that should fail with a conflict.
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index f190d59..d1e8cc5 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -437,7 +437,7 @@
     r.assertErrorStatus("change " + url + " closed");
 
     // Check change message that was added on auto-close
-    ChangeInfo change = gApi.changes().id(r.getChange().getId().get()).get();
+    ChangeInfo change = change(r).get();
     assertThat(Iterables.getLast(change.messages).message)
         .isEqualTo("Change has been successfully pushed.");
   }
@@ -477,7 +477,7 @@
     r.assertErrorStatus("change " + url + " closed");
 
     // Check that new commit was added as patch set
-    ChangeInfo change = gApi.changes().id(r.getChange().getId().get()).get();
+    ChangeInfo change = change(r).get();
     assertThat(change.revisions).hasSize(2);
     assertThat(change.currentRevision).isEqualTo(c.name());
   }
diff --git a/javatests/com/google/gerrit/acceptance/git/RefOperationValidationIT.java b/javatests/com/google/gerrit/acceptance/git/RefOperationValidationIT.java
index 876e342..d7952e4 100644
--- a/javatests/com/google/gerrit/acceptance/git/RefOperationValidationIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/RefOperationValidationIT.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.acceptance.ExtensionRegistry;
 import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
 import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.config.GerritConfig;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.extensions.api.projects.BranchInput;
@@ -206,4 +207,22 @@
       r4.assertErrorStatus(UPDATE_NONFASTFORWARD.name());
     }
   }
+
+  @Test
+  @GerritConfig(name = "change.maxFiles", value = "0")
+  public void dontEnforceFileCountForDirectPushes() throws Exception {
+    PushOneCommit push =
+        pushFactory.create(admin.newIdent(), testRepo, "change", "c.txt", "content");
+    PushOneCommit.Result result = push.to("refs/heads/master");
+    result.assertOkStatus();
+  }
+
+  @Test
+  @GerritConfig(name = "change.maxFiles", value = "0")
+  public void enforceFileCountLimitOnPushesForReview() throws Exception {
+    PushOneCommit push =
+        pushFactory.create(admin.newIdent(), testRepo, "change", "c.txt", "content");
+    PushOneCommit.Result result = push.to("refs/for/master");
+    result.assertErrorStatus("Exceeding maximum number of files per change");
+  }
 }
diff --git a/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java b/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
index 83bc3eb..574e919 100644
--- a/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
@@ -63,6 +63,8 @@
           RestCall.get("/changes/%s/comments"),
           RestCall.get("/changes/%s/robotcomments"),
           RestCall.get("/changes/%s/drafts"),
+          RestCall.get("/changes/%s/attention"),
+          RestCall.post("/changes/%s/attention"),
           RestCall.get("/changes/%s/assignee"),
           RestCall.get("/changes/%s/past_assignees"),
           RestCall.put("/changes/%s/assignee"),
@@ -267,6 +269,11 @@
           // Delete content of a file in an existing change edit.
           RestCall.delete("/changes/%s/edit/%s"));
 
+  private static final ImmutableList<RestCall> ATTENTION_SET_ENDPOINTS =
+      ImmutableList.of(
+          RestCall.post("/changes/%s/attention/%s/delete"),
+          RestCall.delete("/changes/%s/attention/%s"));
+
   private static final String FILENAME = "test.txt";
 
   @Test
@@ -477,6 +484,14 @@
     RestApiCallHelper.execute(adminRestSession, CHANGE_EDIT_ENDPOINTS, changeId, FILENAME);
   }
 
+  @Test
+  public void attentionSetEndpoints() throws Exception {
+    String changeId = createChange().getChangeId();
+    gApi.changes().id(changeId).edit().create();
+    RestApiCallHelper.execute(
+        adminRestSession, ATTENTION_SET_ENDPOINTS, changeId, user.id().toString());
+  }
+
   private static Comment.Range createRange(
       int startLine, int startCharacter, int endLine, int endCharacter) {
     Comment.Range range = new Comment.Range();
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AssigneeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/AssigneeIT.java
index 420ddda..2d47dd8 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AssigneeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AssigneeIT.java
@@ -188,11 +188,11 @@
   }
 
   private AccountInfo getAssignee(PushOneCommit.Result r) throws Exception {
-    return gApi.changes().id(r.getChange().getId().get()).getAssignee();
+    return change(r).getAssignee();
   }
 
   private List<AccountInfo> getPastAssignees(PushOneCommit.Result r) throws Exception {
-    return gApi.changes().id(r.getChange().getId().get()).getPastAssignees();
+    return change(r).getPastAssignees();
   }
 
   private Iterable<AccountInfo> getReviewers(PushOneCommit.Result r, ReviewerState state)
@@ -203,10 +203,10 @@
   private AccountInfo setAssignee(PushOneCommit.Result r, String identifieer) throws Exception {
     AssigneeInput input = new AssigneeInput();
     input.assignee = identifieer;
-    return gApi.changes().id(r.getChange().getId().get()).setAssignee(input);
+    return change(r).setAssignee(input);
   }
 
   private AccountInfo deleteAssignee(PushOneCommit.Result r) throws Exception {
-    return gApi.changes().id(r.getChange().getId().get()).deleteAssignee();
+    return change(r).deleteAssignee();
   }
 }
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java b/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
new file mode 100644
index 0000000..caa8832
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
@@ -0,0 +1,136 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.change;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.UseClockStep;
+import com.google.gerrit.entities.AttentionSetUpdate;
+import com.google.gerrit.extensions.api.changes.AddToAttentionSetInput;
+import com.google.gerrit.extensions.api.changes.RemoveFromAttentionSetInput;
+import com.google.gerrit.server.util.time.TimeUtil;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.concurrent.TimeUnit;
+import java.util.function.LongSupplier;
+import org.junit.Before;
+import org.junit.Test;
+
+@NoHttpd
+@UseClockStep(clockStepUnit = TimeUnit.MINUTES)
+public class AttentionSetIT extends AbstractDaemonTest {
+  /** Simulates a fake clock. Uses second granularity. */
+  private static class FakeClock implements LongSupplier {
+    Instant now = Instant.now();
+
+    @Override
+    public long getAsLong() {
+      return TimeUnit.SECONDS.toMillis(now.getEpochSecond());
+    }
+
+    Instant now() {
+      return Instant.ofEpochSecond(now.getEpochSecond());
+    }
+
+    void advance(Duration duration) {
+      now = now.plus(duration);
+    }
+  }
+
+  private FakeClock fakeClock = new FakeClock();
+
+  @Before
+  public void setUp() {
+    TimeUtil.setCurrentMillisSupplier(fakeClock);
+  }
+
+  @Test
+  public void emptyAttentionSet() throws Exception {
+    PushOneCommit.Result r = createChange();
+    assertThat(r.getChange().attentionSet()).isEmpty();
+  }
+
+  @Test
+  public void addUser() throws Exception {
+    PushOneCommit.Result r = createChange();
+    int accountId =
+        change(r).addToAttentionSet(new AddToAttentionSetInput(user.email(), "first"))._accountId;
+    assertThat(accountId).isEqualTo(user.id().get());
+    AttentionSetUpdate expectedAttentionSetUpdate =
+        AttentionSetUpdate.createFromRead(
+            fakeClock.now(), user.id(), AttentionSetUpdate.Operation.ADD, "first");
+    assertThat(r.getChange().attentionSet()).containsExactly(expectedAttentionSetUpdate);
+
+    // Second add is ignored.
+    accountId =
+        change(r).addToAttentionSet(new AddToAttentionSetInput(user.email(), "second"))._accountId;
+    assertThat(accountId).isEqualTo(user.id().get());
+    assertThat(r.getChange().attentionSet()).containsExactly(expectedAttentionSetUpdate);
+  }
+
+  @Test
+  public void addMultipleUsers() throws Exception {
+    PushOneCommit.Result r = createChange();
+    Instant timestamp1 = fakeClock.now();
+    int accountId1 =
+        change(r).addToAttentionSet(new AddToAttentionSetInput(user.email(), "user"))._accountId;
+    assertThat(accountId1).isEqualTo(user.id().get());
+    fakeClock.advance(Duration.ofSeconds(42));
+    Instant timestamp2 = fakeClock.now();
+    int accountId2 =
+        change(r)
+            .addToAttentionSet(new AddToAttentionSetInput(admin.id().toString(), "admin"))
+            ._accountId;
+    assertThat(accountId2).isEqualTo(admin.id().get());
+
+    AttentionSetUpdate expectedAttentionSetUpdate1 =
+        AttentionSetUpdate.createFromRead(
+            timestamp1, user.id(), AttentionSetUpdate.Operation.ADD, "user");
+    AttentionSetUpdate expectedAttentionSetUpdate2 =
+        AttentionSetUpdate.createFromRead(
+            timestamp2, admin.id(), AttentionSetUpdate.Operation.ADD, "admin");
+    assertThat(r.getChange().attentionSet())
+        .containsExactly(expectedAttentionSetUpdate1, expectedAttentionSetUpdate2);
+  }
+
+  @Test
+  public void removeUser() throws Exception {
+    PushOneCommit.Result r = createChange();
+    change(r).addToAttentionSet(new AddToAttentionSetInput(user.email(), "added"));
+    fakeClock.advance(Duration.ofSeconds(42));
+    change(r).attention(user.id().toString()).remove(new RemoveFromAttentionSetInput("removed"));
+    AttentionSetUpdate expectedAttentionSetUpdate =
+        AttentionSetUpdate.createFromRead(
+            fakeClock.now(), user.id(), AttentionSetUpdate.Operation.REMOVE, "removed");
+    assertThat(r.getChange().attentionSet()).containsExactly(expectedAttentionSetUpdate);
+
+    // Second removal is ignored.
+    fakeClock.advance(Duration.ofSeconds(42));
+    change(r)
+        .attention(user.id().toString())
+        .remove(new RemoveFromAttentionSetInput("removed again"));
+    assertThat(r.getChange().attentionSet()).containsExactly(expectedAttentionSetUpdate);
+  }
+
+  @Test
+  public void removeUnrelatedUser() throws Exception {
+    PushOneCommit.Result r = createChange();
+    change(r).attention(user.id().toString()).remove(new RemoveFromAttentionSetInput("foo"));
+    assertThat(r.getChange().attentionSet()).isEmpty();
+  }
+}
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/GetArchiveIT.java b/javatests/com/google/gerrit/acceptance/rest/change/GetArchiveIT.java
new file mode 100644
index 0000000..15e6360
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/rest/change/GetArchiveIT.java
@@ -0,0 +1,154 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.change;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.extensions.client.ArchiveFormat;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.BinaryResult;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.git.ObjectIds;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.HashMap;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Before;
+import org.junit.Test;
+
+public class GetArchiveIT extends AbstractDaemonTest {
+  private static final String DIRECTORY_NAME = "foo";
+  private static final String FILE_NAME = DIRECTORY_NAME + "/bar.txt";
+  private static final String FILE_CONTENT = "some content";
+
+  private String changeId;
+  private RevCommit commit;
+
+  @Before
+  public void setUp() throws Exception {
+    PushOneCommit push =
+        pushFactory.create(admin.newIdent(), testRepo, "My Change", FILE_NAME, FILE_CONTENT);
+    PushOneCommit.Result result = push.to("refs/for/master");
+    result.assertOkStatus();
+
+    changeId = result.getChangeId();
+    commit = result.getCommit();
+  }
+
+  @Test
+  public void formatNotSpecified() throws Exception {
+    BadRequestException ex =
+        assertThrows(
+            BadRequestException.class,
+            () -> gApi.changes().id(changeId).current().getArchive(null));
+    assertThat(ex).hasMessageThat().isEqualTo("format is not specified");
+  }
+
+  @Test
+  public void unknownFormat() throws Exception {
+    // Test this by a REST call, since the Java API doesn't allow to specify an unknown format.
+    RestResponse res =
+        adminRestSession.get(
+            String.format(
+                "/changes/%s/revisions/current/archive?format=%s", changeId, "unknownFormat"));
+    res.assertBadRequest();
+    assertThat(res.getEntityContent()).isEqualTo("unknown archive format");
+  }
+
+  @Test
+  public void zipFormatIsDisabled() throws Exception {
+    MethodNotAllowedException ex =
+        assertThrows(
+            MethodNotAllowedException.class,
+            () -> gApi.changes().id(changeId).current().getArchive(ArchiveFormat.ZIP));
+    assertThat(ex).hasMessageThat().isEqualTo("zip format is disabled");
+  }
+
+  @Test
+  public void getTarArchive() throws Exception {
+    BinaryResult res = gApi.changes().id(changeId).current().getArchive(ArchiveFormat.TAR);
+    assertThat(res.getAttachmentName())
+        .isEqualTo(commit.abbreviate(ObjectIds.ABBREV_STR_LEN).name() + ".tar");
+    assertThat(res.getContentType()).isEqualTo("application/x-tar");
+    assertThat(res.canGzip()).isFalse();
+
+    byte[] archiveBytes = getBinaryContent(res);
+    try (ByteArrayInputStream in = new ByteArrayInputStream(archiveBytes)) {
+      HashMap<String, String> archiveEntries = getTarContent(in);
+      assertThat(archiveEntries)
+          .containsExactly(DIRECTORY_NAME + "/", null, FILE_NAME, FILE_CONTENT);
+    }
+  }
+
+  @Test
+  public void getTgzArchive() throws Exception {
+    BinaryResult res = gApi.changes().id(changeId).current().getArchive(ArchiveFormat.TGZ);
+    assertThat(res.getAttachmentName())
+        .isEqualTo(commit.abbreviate(ObjectIds.ABBREV_STR_LEN).name() + ".tar.gz");
+    assertThat(res.getContentType()).isEqualTo("application/x-gzip");
+    assertThat(res.canGzip()).isFalse();
+
+    byte[] archiveBytes = getBinaryContent(res);
+    try (ByteArrayInputStream in = new ByteArrayInputStream(archiveBytes);
+        GzipCompressorInputStream gzipIn = new GzipCompressorInputStream(in)) {
+      HashMap<String, String> archiveEntries = getTarContent(gzipIn);
+      assertThat(archiveEntries)
+          .containsExactly(DIRECTORY_NAME + "/", null, FILE_NAME, FILE_CONTENT);
+    }
+  }
+
+  private HashMap<String, String> getTarContent(InputStream in) throws Exception {
+    HashMap<String, String> archiveEntries = new HashMap<>();
+    int bufferSize = 100;
+    try (TarArchiveInputStream tarIn = new TarArchiveInputStream(in)) {
+      TarArchiveEntry entry;
+      while ((entry = tarIn.getNextTarEntry()) != null) {
+        if (entry.isDirectory()) {
+          archiveEntries.put(entry.getName(), null);
+        } else {
+          byte data[] = new byte[bufferSize];
+          try (ByteArrayOutputStream out = new ByteArrayOutputStream();
+              BufferedOutputStream bufferedOut = new BufferedOutputStream(out, bufferSize)) {
+            int count;
+            while ((count = tarIn.read(data, 0, bufferSize)) != -1) {
+              bufferedOut.write(data, 0, count);
+            }
+            bufferedOut.flush();
+            archiveEntries.put(entry.getName(), out.toString());
+          }
+        }
+      }
+    }
+    return archiveEntries;
+  }
+
+  private byte[] getBinaryContent(BinaryResult res) throws Exception {
+    try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+      res.writeTo(out);
+      return out.toByteArray();
+    } finally {
+      res.close();
+    }
+  }
+}
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/HashtagsIT.java b/javatests/com/google/gerrit/acceptance/rest/change/HashtagsIT.java
index c57a035..0099fe6 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/HashtagsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/HashtagsIT.java
@@ -226,7 +226,7 @@
     HashtagsInput input = new HashtagsInput();
     input.add = Sets.newHashSet("tag3", "tag4");
     input.remove = Sets.newHashSet("tag1");
-    gApi.changes().id(r.getChange().getId().get()).setHashtags(input);
+    change(r).setHashtags(input);
     assertThatGet(r).containsExactly("tag2", "tag3", "tag4");
     assertMessage(r, "Hashtags added: tag3, tag4\nHashtag removed: tag1");
 
@@ -235,7 +235,7 @@
     input = new HashtagsInput();
     input.add = Sets.newHashSet("tag3", "tag4");
     input.remove = Sets.newHashSet("tag3");
-    gApi.changes().id(r.getChange().getId().get()).setHashtags(input);
+    change(r).setHashtags(input);
     assertThatGet(r).containsExactly("tag1", "tag2", "tag4");
     assertMessage(r, "Hashtag removed: tag3");
   }
@@ -271,19 +271,19 @@
   }
 
   private IterableSubject assertThatGet(PushOneCommit.Result r) throws Exception {
-    return assertThat(gApi.changes().id(r.getChange().getId().get()).getHashtags());
+    return assertThat(change(r).getHashtags());
   }
 
   private void addHashtags(PushOneCommit.Result r, String... toAdd) throws Exception {
     HashtagsInput input = new HashtagsInput();
     input.add = Sets.newHashSet(toAdd);
-    gApi.changes().id(r.getChange().getId().get()).setHashtags(input);
+    change(r).setHashtags(input);
   }
 
   private void removeHashtags(PushOneCommit.Result r, String... toRemove) throws Exception {
     HashtagsInput input = new HashtagsInput();
     input.remove = Sets.newHashSet(toRemove);
-    gApi.changes().id(r.getChange().getId().get()).setHashtags(input);
+    change(r).setHashtags(input);
   }
 
   private void assertMessage(PushOneCommit.Result r, String expectedMessage) throws Exception {
@@ -299,8 +299,7 @@
   }
 
   private ChangeMessageInfo getLastMessage(PushOneCommit.Result r) throws Exception {
-    ChangeMessageInfo lastMessage =
-        Iterables.getLast(gApi.changes().id(r.getChange().getId().get()).get().messages, null);
+    ChangeMessageInfo lastMessage = Iterables.getLast(change(r).get().messages, null);
     assertWithMessage(lastMessage.message).that(lastMessage).isNotNull();
     return lastMessage;
   }
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java b/javatests/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
index 85d383e..b01a07b 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
@@ -23,7 +23,9 @@
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
 import static com.google.gerrit.testing.GerritJUnit.assertThrows;
 
+import com.google.common.collect.ImmutableList;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
 import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
@@ -38,8 +40,20 @@
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.events.RefReceivedEvent;
+import com.google.gerrit.server.git.validators.RefOperationValidationListener;
+import com.google.gerrit.server.git.validators.ValidationMessage;
+import com.google.gerrit.server.util.MagicBranch;
+import com.google.gerrit.server.validators.ValidationException;
 import com.google.inject.Inject;
+import java.io.IOException;
+import java.util.List;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.junit.Before;
 import org.junit.Test;
@@ -47,6 +61,7 @@
 public class CreateBranchIT extends AbstractDaemonTest {
   @Inject private ProjectOperations projectOperations;
   @Inject private RequestScopeOperations requestScopeOperations;
+  @Inject private ExtensionRegistry extensionRegistry;
 
   private BranchNameKey testBranch;
 
@@ -82,7 +97,63 @@
   @Test
   public void branchAlreadyExists_Conflict() throws Exception {
     assertCreateSucceeds(testBranch);
-    assertCreateFails(testBranch, ResourceConflictException.class);
+    assertCreateFails(
+        testBranch,
+        ResourceConflictException.class,
+        "branch \"" + testBranch.branch() + "\" already exists");
+  }
+
+  @Test
+  public void createBranch_LockFailure() throws Exception {
+    // check that the branch doesn't exist yet
+    assertThrows(ResourceNotFoundException.class, () -> branch(testBranch).get());
+
+    // Register a validation listener that creates the branch to simulate a concurrent request that
+    // creates the same branch.
+    try (ExtensionRegistry.Registration registration =
+        extensionRegistry
+            .newRegistration()
+            .add(
+                new RefOperationValidationListener() {
+                  @Override
+                  public List<ValidationMessage> onRefOperation(RefReceivedEvent refEvent)
+                      throws ValidationException {
+                    try (Repository repo = repoManager.openRepository(project)) {
+                      RefUpdate u = repo.updateRef(testBranch.branch());
+                      u.setExpectedOldObjectId(ObjectId.zeroId());
+                      u.setNewObjectId(repo.exactRef("refs/heads/master").getObjectId());
+                      RefUpdate.Result result = u.update();
+                      if (result != RefUpdate.Result.NEW) {
+                        throw new ValidationException(
+                            "Concurrent creation of branch failed: " + result);
+                      }
+                      return ImmutableList.of();
+                    } catch (IOException e) {
+                      throw new ValidationException("Concurrent creation of branch failed.", e);
+                    }
+                  }
+                })) {
+      // Creating the branch is expected to fail, since it is created by the validation listener
+      // right before the ref update to create the new branch is done.
+      assertCreateFails(
+          testBranch,
+          ResourceConflictException.class,
+          "branch \"" + testBranch.branch() + "\" already exists");
+    }
+  }
+
+  @Test
+  public void conflictingBranchAlreadyExists_Conflict() throws Exception {
+    assertCreateSucceeds(testBranch);
+    BranchNameKey testBranch2 = BranchNameKey.create(project, testBranch.branch() + "/foo/bar");
+    assertCreateFails(
+        testBranch2,
+        ResourceConflictException.class,
+        "Cannot create branch \""
+            + testBranch2.branch()
+            + "\" since it conflicts with branch \""
+            + testBranch.branch()
+            + "\"");
   }
 
   @Test
@@ -119,6 +190,23 @@
   }
 
   @Test
+  public void createMetaConfigBranch() throws Exception {
+    // Since the refs/meta/config branch exists by default, we must delete it before we can test
+    // creating it. Since deleting the refs/meta/config branch is not allowed through the API, we
+    // delete it directly in the remote repository.
+    try (TestRepository<Repository> repo =
+        new TestRepository<>(repoManager.openRepository(project))) {
+      repo.delete(RefNames.REFS_CONFIG);
+    }
+
+    // Create refs/meta/config branch.
+    BranchInfo created =
+        branch(BranchNameKey.create(project, RefNames.REFS_CONFIG)).create(new BranchInput()).get();
+    assertThat(created.ref).isEqualTo(RefNames.REFS_CONFIG);
+    assertThat(created.canDelete).isNull();
+  }
+
+  @Test
   public void createUserBranch_Conflict() throws Exception {
     projectOperations
         .project(allUsers)
@@ -258,6 +346,54 @@
         "invalid revision \"invalid\trevision\"");
   }
 
+  @Test
+  public void cannotCreateBranchInMagicBranchNamespace() throws Exception {
+    assertCreateFails(
+        BranchNameKey.create(project, MagicBranch.NEW_CHANGE + "foo"),
+        BadRequestException.class,
+        "not allowed to create branches under \"" + MagicBranch.NEW_CHANGE + "\"");
+  }
+
+  @Test
+  public void cannotCreateBranchWithInvalidName() throws Exception {
+    assertCreateFails(
+        BranchNameKey.create(project, RefNames.REFS_HEADS),
+        BadRequestException.class,
+        "invalid branch name \"" + RefNames.REFS_HEADS + "\"");
+  }
+
+  @Test
+  public void createBranchLeadingSlashesAreRemoved() throws Exception {
+    BranchNameKey expectedNameKey = BranchNameKey.create(project, "test");
+
+    // check that the branch doesn't exist yet
+    assertThrows(
+        ResourceNotFoundException.class,
+        () -> gApi.projects().name(project.get()).branch(expectedNameKey.branch()).get());
+
+    // create the branch, but include leading slashes in the branch name,
+    // when creating the branch ensure that the branch name in the URL matches the branch name in
+    // the input (if there is a mismatch the creation request is rejected)
+    BranchInput branchInput = new BranchInput();
+    branchInput.ref = "////" + expectedNameKey.shortName();
+    gApi.projects().name(project.get()).branch(branchInput.ref).create(branchInput);
+
+    // verify that the branch was created without the leading slashes in the name
+    assertThat(gApi.projects().name(project.get()).branch(expectedNameKey.branch()).get().ref)
+        .isEqualTo(expectedNameKey.branch());
+  }
+
+  @Test
+  public void branchNameInInputMustMatchBranchNameInUrl() throws Exception {
+    BranchInput branchInput = new BranchInput();
+    branchInput.ref = "foo";
+    BadRequestException ex =
+        assertThrows(
+            BadRequestException.class,
+            () -> gApi.projects().name(project.get()).branch("bar").create(branchInput));
+    assertThat(ex).hasMessageThat().isEqualTo("ref must match URL");
+  }
+
   private void blockCreateReference() throws Exception {
     projectOperations
         .project(project)
@@ -302,9 +438,4 @@
       assertThat(thrown).hasMessageThat().contains(errMsg);
     }
   }
-
-  private void assertCreateFails(BranchNameKey branch, Class<? extends RestApiException> errType)
-      throws Exception {
-    assertCreateFails(branch, errType, null);
-  }
 }
diff --git a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
index b60cce5..566308d 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -48,6 +48,7 @@
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.server.CommentsUtil;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.notedb.ChangeNoteUtil;
@@ -85,6 +86,7 @@
   @Inject private Provider<ChangesCollection> changes;
   @Inject private Provider<PostReview> postReview;
   @Inject private RequestScopeOperations requestScopeOperations;
+  @Inject private CommentsUtil commentsUtil;
 
   private final Integer[] lines = {0, 1};
 
@@ -446,6 +448,82 @@
   }
 
   @Test
+  public void putDraft_idMismatch() throws Exception {
+    String file = "file";
+    PushOneCommit.Result r = createChange();
+    String changeId = r.getChangeId();
+    String revId = r.getCommit().getName();
+    DraftInput comment = newDraft(file, Side.REVISION, 0, "foo");
+    CommentInfo commentInfo = addDraft(changeId, revId, comment);
+    DraftInput draftInput = newDraft(file, Side.REVISION, 0, "bar");
+    draftInput.id = "anything_but_" + commentInfo.id;
+    BadRequestException e =
+        assertThrows(
+            BadRequestException.class,
+            () -> updateDraft(changeId, revId, draftInput, commentInfo.id));
+    assertThat(e).hasMessageThat().contains("id must match URL");
+  }
+
+  @Test
+  public void putDraft_negativeLine() throws Exception {
+    String file = "file";
+    PushOneCommit.Result r = createChange();
+    String changeId = r.getChangeId();
+    String revId = r.getCommit().getName();
+    DraftInput comment = newDraft(file, Side.REVISION, -666, "foo");
+    BadRequestException e =
+        assertThrows(BadRequestException.class, () -> addDraft(changeId, revId, comment));
+    assertThat(e).hasMessageThat().contains("line must be >= 0");
+  }
+
+  @Test
+  public void putDraft_invalidRange() throws Exception {
+    String file = "file";
+    PushOneCommit.Result r = createChange();
+    String changeId = r.getChangeId();
+    String revId = r.getCommit().getName();
+    DraftInput draftInput = newDraft(file, Side.REVISION, createLineRange(2, 3), "bar");
+    draftInput.line = 666;
+    BadRequestException e =
+        assertThrows(BadRequestException.class, () -> addDraft(changeId, revId, draftInput));
+    assertThat(e)
+        .hasMessageThat()
+        .contains("range endLine must be on the same line as the comment");
+  }
+
+  @Test
+  public void putDraft_updatePath() throws Exception {
+    PushOneCommit.Result r = createChange();
+    String changeId = r.getChangeId();
+    String revId = r.getCommit().getName();
+    DraftInput comment = newDraft("file_foo", Side.REVISION, 0, "foo");
+    CommentInfo commentInfo = addDraft(changeId, revId, comment);
+    assertThat(getDraftComments(changeId, revId).keySet()).containsExactly("file_foo");
+    DraftInput draftInput = newDraft("file_bar", Side.REVISION, 0, "bar");
+    updateDraft(changeId, revId, draftInput, commentInfo.id);
+    assertThat(getDraftComments(changeId, revId).keySet()).containsExactly("file_bar");
+  }
+
+  @Test
+  public void putDraft_updateInReplyToAndTag() throws Exception {
+    PushOneCommit.Result r = createChange();
+    String changeId = r.getChangeId();
+    String revId = r.getCommit().getName();
+    DraftInput draftInput1 = newDraft(FILE_NAME, Side.REVISION, 0, "foo");
+    CommentInfo commentInfo = addDraft(changeId, revId, draftInput1);
+    DraftInput draftInput2 = newDraft(FILE_NAME, Side.REVISION, 0, "bar");
+    String inReplyTo = "in_reply_to";
+    String tag = "täg";
+    draftInput2.inReplyTo = inReplyTo;
+    draftInput2.tag = tag;
+    updateDraft(changeId, revId, draftInput2, commentInfo.id);
+    com.google.gerrit.entities.Comment comment =
+        Iterables.getOnlyElement(commentsUtil.draftByChange(r.getChange().notes()));
+    assertThat(comment.parentUuid).isEqualTo(inReplyTo);
+    assertThat(comment.tag).isEqualTo(tag);
+  }
+
+  @Test
   public void listDrafts() throws Exception {
     String file = "file";
     PushOneCommit.Result r = createChange();
@@ -677,15 +755,15 @@
     addDraft(
         r1.getChangeId(),
         r1.getCommit().getName(),
-        newDraft(FILE_NAME, Side.REVISION, createLineRange(1, 4, 10), "Is it that bad?"));
+        newDraft(FILE_NAME, Side.REVISION, createLineRange(4, 10), "Is it that bad?"));
     addDraft(
         r1.getChangeId(),
         r1.getCommit().getName(),
-        newDraft(FILE_NAME, Side.PARENT, createLineRange(1, 0, 7), "what happened to this?"));
+        newDraft(FILE_NAME, Side.PARENT, createLineRange(0, 7), "what happened to this?"));
     addDraft(
         r2.getChangeId(),
         r2.getCommit().getName(),
-        newDraft(FILE_NAME, Side.REVISION, createLineRange(1, 4, 15), "better now"));
+        newDraft(FILE_NAME, Side.REVISION, createLineRange(4, 15), "better now"));
     addDraft(
         r2.getChangeId(),
         r2.getCommit().getName(),
@@ -905,7 +983,7 @@
   @Test
   public void deleteCommentCannotBeAppliedByUser() throws Exception {
     PushOneCommit.Result result = createChange();
-    CommentInput targetComment = addComment(result.getChangeId(), "My password: abc123");
+    CommentInput targetComment = addComment(result.getChangeId());
 
     Map<String, List<CommentInfo>> commentsMap =
         getPublishedComments(result.getChangeId(), result.getCommit().name());
@@ -1083,9 +1161,9 @@
         .collect(toList());
   }
 
-  private CommentInput addComment(String changeId, String message) throws Exception {
+  private CommentInput addComment(String changeId) throws Exception {
     ReviewInput input = new ReviewInput();
-    CommentInput comment = newComment(FILE_NAME, Side.REVISION, 0, message, false);
+    CommentInput comment = newComment(FILE_NAME, Side.REVISION, 0, "a message", false);
     input.comments = ImmutableMap.of(comment.path, Lists.newArrayList(comment));
     gApi.changes().id(changeId).current().review(input);
     return comment;
@@ -1240,7 +1318,7 @@
   private static CommentInput newCommentOnParent(
       String path, int parent, int line, String message) {
     CommentInput c = new CommentInput();
-    return populate(c, path, Side.PARENT, Integer.valueOf(parent), line, message, false);
+    return populate(c, path, Side.PARENT, parent, line, message, false);
   }
 
   private DraftInput newDraft(String path, Side side, int line, String message) {
@@ -1255,7 +1333,7 @@
 
   private DraftInput newDraftOnParent(String path, int parent, int line, String message) {
     DraftInput d = new DraftInput();
-    return populate(d, path, Side.PARENT, Integer.valueOf(parent), line, message, false);
+    return populate(d, path, Side.PARENT, parent, line, message, false);
   }
 
   private static <C extends Comment> C populate(
@@ -1284,11 +1362,11 @@
     return populate(c, path, side, parent, line, null, message, unresolved);
   }
 
-  private static Comment.Range createLineRange(int line, int startChar, int endChar) {
+  private static Comment.Range createLineRange(int startChar, int endChar) {
     Comment.Range range = new Comment.Range();
-    range.startLine = line;
+    range.startLine = 1;
     range.startCharacter = startChar;
-    range.endLine = line;
+    range.endLine = 1;
     range.endCharacter = endChar;
     return range;
   }
diff --git a/javatests/com/google/gerrit/server/change/ArchiveFormatInternalTest.java b/javatests/com/google/gerrit/server/change/ArchiveFormatInternalTest.java
new file mode 100644
index 0000000..003225c
--- /dev/null
+++ b/javatests/com/google/gerrit/server/change/ArchiveFormatInternalTest.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.stream.Collectors.toList;
+
+import com.google.gerrit.extensions.client.ArchiveFormat;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+
+public class ArchiveFormatInternalTest {
+  @Test
+  public void internalAndExternalArchiveFormatEnumsMatch() throws Exception {
+    assertThat(getEnumNames(ArchiveFormatInternal.class))
+        .containsExactlyElementsIn(getEnumNames(ArchiveFormat.class));
+  }
+
+  private static List<String> getEnumNames(Class<? extends Enum<?>> e) {
+    return Arrays.stream(e.getEnumConstants()).map(Enum::name).collect(toList());
+  }
+}
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
index 74fee65..0674dc0 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
@@ -28,7 +28,7 @@
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.common.data.SubmitRequirement;
 import com.google.gerrit.entities.Account;
-import com.google.gerrit.entities.AttentionStatus;
+import com.google.gerrit.entities.AttentionSetUpdate;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.ChangeMessage;
 import com.google.gerrit.entities.Comment;
@@ -45,7 +45,7 @@
 import com.google.gerrit.server.ReviewerStatusUpdate;
 import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto;
 import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.AssigneeStatusUpdateProto;
-import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.AttentionStatusProto;
+import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.AttentionSetUpdateProto;
 import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ChangeColumnsProto;
 import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerByEmailSetEntryProto;
 import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerSetEntryProto;
@@ -600,31 +600,31 @@
   }
 
   @Test
-  public void serializeAttentionUpdates() throws Exception {
+  public void serializeAttentionSetUpdates() throws Exception {
     assertRoundTrip(
         newBuilder()
-            .attentionUpdates(
+            .attentionSet(
                 ImmutableList.of(
-                    AttentionStatus.createFromRead(
+                    AttentionSetUpdate.createFromRead(
                         Instant.EPOCH.plusSeconds(23),
                         Account.id(1000),
-                        AttentionStatus.Operation.ADD,
+                        AttentionSetUpdate.Operation.ADD,
                         "reason 1"),
-                    AttentionStatus.createFromRead(
+                    AttentionSetUpdate.createFromRead(
                         Instant.EPOCH.plusSeconds(42),
                         Account.id(2000),
-                        AttentionStatus.Operation.REMOVE,
+                        AttentionSetUpdate.Operation.REMOVE,
                         "reason 2")))
             .build(),
         newProtoBuilder()
-            .addAttentionStatus(
-                AttentionStatusProto.newBuilder()
+            .addAttentionSetUpdate(
+                AttentionSetUpdateProto.newBuilder()
                     .setTimestampMillis(23_000) // epoch millis
                     .setAccount(1000)
                     .setOperation("ADD")
                     .setReason("reason 1"))
-            .addAttentionStatus(
-                AttentionStatusProto.newBuilder()
+            .addAttentionSetUpdate(
+                AttentionSetUpdateProto.newBuilder()
                     .setTimestampMillis(42_000) // epoch millis
                     .setAccount(2000)
                     .setOperation("REMOVE")
@@ -789,8 +789,8 @@
                     "reviewerUpdates",
                     new TypeLiteral<ImmutableList<ReviewerStatusUpdate>>() {}.getType())
                 .put(
-                    "attentionUpdates",
-                    new TypeLiteral<ImmutableList<AttentionStatus>>() {}.getType())
+                    "attentionSet",
+                    new TypeLiteral<ImmutableList<AttentionSetUpdate>>() {}.getType())
                 .put(
                     "assigneeUpdates",
                     new TypeLiteral<ImmutableList<AssigneeStatusUpdate>>() {}.getType())
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
index 4e068ba..f5a6dc3 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -38,8 +38,8 @@
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.entities.Account;
-import com.google.gerrit.entities.AttentionStatus;
-import com.google.gerrit.entities.AttentionStatus.Operation;
+import com.google.gerrit.entities.AttentionSetUpdate;
+import com.google.gerrit.entities.AttentionSetUpdate.Operation;
 import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.ChangeMessage;
@@ -694,51 +694,51 @@
   public void defaultAttentionSetIsEmpty() throws Exception {
     Change c = newChange();
     ChangeNotes notes = newNotes(c);
-    assertThat(notes.getAttentionUpdates()).isEmpty();
+    assertThat(notes.getAttentionSet()).isEmpty();
   }
 
   @Test
   public void addAttentionStatus() throws Exception {
     Change c = newChange();
     ChangeUpdate update = newUpdate(c, changeOwner);
-    AttentionStatus attentionStatus =
-        AttentionStatus.createForWrite(changeOwner.getAccountId(), Operation.ADD, "test");
-    update.setAttentionUpdates(ImmutableList.of(attentionStatus));
+    AttentionSetUpdate attentionSetUpdate =
+        AttentionSetUpdate.createForWrite(changeOwner.getAccountId(), Operation.ADD, "test");
+    update.setAttentionSetUpdates(ImmutableList.of(attentionSetUpdate));
     update.commit();
 
     ChangeNotes notes = newNotes(c);
-    assertThat(notes.getAttentionUpdates()).containsExactly(addTimestamp(attentionStatus, c));
+    assertThat(notes.getAttentionSet()).containsExactly(addTimestamp(attentionSetUpdate, c));
   }
 
   @Test
   public void filterLatestAttentionStatus() throws Exception {
     Change c = newChange();
     ChangeUpdate update = newUpdate(c, changeOwner);
-    AttentionStatus attentionStatus =
-        AttentionStatus.createForWrite(changeOwner.getAccountId(), Operation.ADD, "test");
-    update.setAttentionUpdates(ImmutableList.of(attentionStatus));
+    AttentionSetUpdate attentionSetUpdate =
+        AttentionSetUpdate.createForWrite(changeOwner.getAccountId(), Operation.ADD, "test");
+    update.setAttentionSetUpdates(ImmutableList.of(attentionSetUpdate));
     update.commit();
     update = newUpdate(c, changeOwner);
-    attentionStatus =
-        AttentionStatus.createForWrite(changeOwner.getAccountId(), Operation.REMOVE, "test");
-    update.setAttentionUpdates(ImmutableList.of(attentionStatus));
+    attentionSetUpdate =
+        AttentionSetUpdate.createForWrite(changeOwner.getAccountId(), Operation.REMOVE, "test");
+    update.setAttentionSetUpdates(ImmutableList.of(attentionSetUpdate));
     update.commit();
 
     ChangeNotes notes = newNotes(c);
-    assertThat(notes.getAttentionUpdates()).containsExactly(addTimestamp(attentionStatus, c));
+    assertThat(notes.getAttentionSet()).containsExactly(addTimestamp(attentionSetUpdate, c));
   }
 
   @Test
   public void addAttentionStatus_rejectTimestamp() throws Exception {
     Change c = newChange();
     ChangeUpdate update = newUpdate(c, changeOwner);
-    AttentionStatus attentionStatus =
-        AttentionStatus.createFromRead(
+    AttentionSetUpdate attentionSetUpdate =
+        AttentionSetUpdate.createFromRead(
             Instant.now(), changeOwner.getAccountId(), Operation.ADD, "test");
     IllegalArgumentException thrown =
         assertThrows(
             IllegalArgumentException.class,
-            () -> update.setAttentionUpdates(ImmutableList.of(attentionStatus)));
+            () -> update.setAttentionSetUpdates(ImmutableList.of(attentionSetUpdate)));
     assertThat(thrown).hasMessageThat().contains("must not specify timestamp for write");
   }
 
@@ -746,14 +746,16 @@
   public void addAttentionStatus_rejectMultiplePerUser() throws Exception {
     Change c = newChange();
     ChangeUpdate update = newUpdate(c, changeOwner);
-    AttentionStatus attentionStatus0 =
-        AttentionStatus.createForWrite(changeOwner.getAccountId(), Operation.ADD, "test 0");
-    AttentionStatus attentionStatus1 =
-        AttentionStatus.createForWrite(changeOwner.getAccountId(), Operation.ADD, "test 1");
+    AttentionSetUpdate attentionSetUpdate0 =
+        AttentionSetUpdate.createForWrite(changeOwner.getAccountId(), Operation.ADD, "test 0");
+    AttentionSetUpdate attentionSetUpdate1 =
+        AttentionSetUpdate.createForWrite(changeOwner.getAccountId(), Operation.ADD, "test 1");
     IllegalArgumentException thrown =
         assertThrows(
             IllegalArgumentException.class,
-            () -> update.setAttentionUpdates(ImmutableList.of(attentionStatus0, attentionStatus1)));
+            () ->
+                update.setAttentionSetUpdates(
+                    ImmutableList.of(attentionSetUpdate0, attentionSetUpdate1)));
     assertThat(thrown)
         .hasMessageThat()
         .contains("must not specify multiple updates for single user");
@@ -763,17 +765,18 @@
   public void addAttentionStatusForMultipleUsers() throws Exception {
     Change c = newChange();
     ChangeUpdate update = newUpdate(c, changeOwner);
-    AttentionStatus attentionStatus0 =
-        AttentionStatus.createForWrite(changeOwner.getAccountId(), Operation.ADD, "test");
-    AttentionStatus attentionStatus1 =
-        AttentionStatus.createForWrite(otherUser.getAccountId(), Operation.ADD, "test");
+    AttentionSetUpdate attentionSetUpdate0 =
+        AttentionSetUpdate.createForWrite(changeOwner.getAccountId(), Operation.ADD, "test");
+    AttentionSetUpdate attentionSetUpdate1 =
+        AttentionSetUpdate.createForWrite(otherUser.getAccountId(), Operation.ADD, "test");
 
-    update.setAttentionUpdates(ImmutableList.of(attentionStatus0, attentionStatus1));
+    update.setAttentionSetUpdates(ImmutableList.of(attentionSetUpdate0, attentionSetUpdate1));
     update.commit();
 
     ChangeNotes notes = newNotes(c);
-    assertThat(notes.getAttentionUpdates())
-        .containsExactly(addTimestamp(attentionStatus0, c), addTimestamp(attentionStatus1, c));
+    assertThat(notes.getAttentionSet())
+        .containsExactly(
+            addTimestamp(attentionSetUpdate0, c), addTimestamp(attentionSetUpdate1, c));
   }
 
   @Test
@@ -3230,12 +3233,12 @@
     return tr.parseBody(commit);
   }
 
-  private AttentionStatus addTimestamp(AttentionStatus attentionStatus, Change c) {
+  private AttentionSetUpdate addTimestamp(AttentionSetUpdate attentionSetUpdate, Change c) {
     Timestamp timestamp = newNotes(c).getChange().getLastUpdatedOn();
-    return AttentionStatus.createFromRead(
+    return AttentionSetUpdate.createFromRead(
         timestamp.toInstant(),
-        attentionStatus.account(),
-        attentionStatus.operation(),
-        attentionStatus.reason());
+        attentionSetUpdate.account(),
+        attentionSetUpdate.operation(),
+        attentionSetUpdate.reason());
   }
 }
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index fb09c9f..040e2eb 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -1784,16 +1784,43 @@
     assertQuery(q + " visibleto:self", change2, change1);
 
     // Second user cannot see first user's private change
-    Account.Id user2 = createAccount("anotheruser");
+    Account.Id user2 = createAccount("user2");
     assertQuery(q + " visibleto:" + user2.get(), change1);
-    assertQuery(q + " visibleto:anotheruser", change1);
+    assertQuery(q + " visibleto:user2", change1);
 
     String g1 = createGroup("group1", "Administrators");
-    gApi.groups().id(g1).addMembers("anotheruser");
+    gApi.groups().id(g1).addMembers("user2");
     assertQuery(q + " visibleto:" + g1, change1);
 
     requestContext.setContext(newRequestContext(user2));
     assertQuery("is:visible", change1);
+
+    Account.Id user3 = createAccount("user3");
+
+    // Explicitly authenticate user2 and user3 so that display name gets set
+    AuthRequest authRequest = AuthRequest.forUser("user2");
+    authRequest.setDisplayName("Another User");
+    authRequest.setEmailAddress("user2@example.com");
+    accountManager.authenticate(authRequest);
+    authRequest = AuthRequest.forUser("user3");
+    authRequest.setDisplayName("Another User");
+    authRequest.setEmailAddress("user3@example.com");
+    accountManager.authenticate(authRequest);
+
+    // Switch to user3
+    requestContext.setContext(newRequestContext(user3));
+    Change change3 = insert(repo, newChange(repo), user3);
+    Change change4 = insert(repo, newChangePrivate(repo), user3);
+
+    // User3 can see both their changes and the first user's change
+    assertQuery(q + " visibleto:" + user3.get(), change4, change3, change1);
+
+    // User2 cannot see user3's private change
+    assertQuery(q + " visibleto:" + user2.get(), change3, change1);
+
+    // Query as user3 by display name matching user2 and user3; bad request
+    assertFailingQuery(
+        q + " visibleto:\"Another User\"", "\"Another User\" resolves to multiple accounts");
   }
 
   @Test
diff --git a/plugins/download-commands b/plugins/download-commands
index 5c50f9b..3f5a024 160000
--- a/plugins/download-commands
+++ b/plugins/download-commands
@@ -1 +1 @@
-Subproject commit 5c50f9b17e616fd84d2c561822161fff46bbf902
+Subproject commit 3f5a024fd46f30f4646bfceb285763e44fda15a7
diff --git a/polygerrit-ui/README.md b/polygerrit-ui/README.md
index c5dd657..07eba02 100644
--- a/polygerrit-ui/README.md
+++ b/polygerrit-ui/README.md
@@ -64,7 +64,7 @@
 bazel fetch @ui_dev_npm//:node_modules
 
 # Install packages from tools/node_tools/packages.json
-bazel fetch @ui_dev_npm//:node_modules
+bazel fetch @tools_npm//:node_modules
 ```
 
 More information for installing and using nodejs rules can be found here https://bazelbuild.github.io/rules_nodejs/install.html
@@ -201,7 +201,7 @@
 * To run the linter on all of your local changes:
 
 ```sh
-git diff --name-only master | xargs node_modules/eslint/bin/eslint.js --ext .html,.js
+git diff --name-only HEAD | xargs node_modules/eslint/bin/eslint.js --ext .html,.js
 ```
 
 We also use the `polylint` tool to lint use of Polymer. To install polylint,
@@ -274,4 +274,4 @@
 
 If you are willing to join the queue and help the community review changes,
 you can create an issue through Monorail and request to join the queue!
-We will review your request and start from there.
\ No newline at end of file
+We will review your request and start from there.
diff --git a/polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior_test.html b/polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior_test.html
index fa5409b..4168801 100644
--- a/polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior_test.html
@@ -23,12 +23,7 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../test/test-pre-setup.js"></script>
-<script type="module" src="../../test/common-test-setup.js"></script>
-<script type="module" src="./async-foreach-behavior.js"></script>
-
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import './async-foreach-behavior.js';
 suite('async-foreach-behavior tests', () => {
diff --git a/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html b/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html
index 4f6f1f5..62b497f 100644
--- a/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html
@@ -23,17 +23,12 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../test/test-pre-setup.js"></script>
-<script type="module" src="../../test/common-test-setup.js"></script>
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import './base-url-behavior.js';
 /** @type {string} */
 window.CANONICAL_PATH = '/r';
 </script>
-<script type="module" src="./base-url-behavior.js"></script>
-
 <test-fixture id="basic">
   <template>
     <test-element></test-element>
@@ -49,7 +44,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import './base-url-behavior.js';
 import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
diff --git a/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior_test.html b/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior_test.html
index 4b72748..36eec89 100644
--- a/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior_test.html
@@ -17,21 +17,10 @@
 <!-- Polymer included for the html import polyfill. -->
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../test/test-pre-setup.js"></script>
-<script type="module" src="../../test/common-test-setup.js"></script>
 <title>docs-url-behavior</title>
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
-<script type="module" src="./docs-url-behavior.js"></script>
-
-<script type="module">
-import '../../test/test-pre-setup.js';
-import '../../test/common-test-setup.js';
-import './docs-url-behavior.js';
-void(0);
-</script>
-
 <test-fixture id="basic">
   <template>
     <docs-url-behavior-element></docs-url-behavior-element>
@@ -39,7 +28,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import './docs-url-behavior.js';
 import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
diff --git a/polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior_test.html b/polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior_test.html
index 09a7cc6..0432e20 100644
--- a/polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior_test.html
@@ -23,10 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../test/test-pre-setup.js"></script>
-<script type="module" src="../../test/common-test-setup.js"></script>
-<script type="module" src="./dom-util-behavior.js"></script>
-
 <test-fixture id="nested-structure">
   <template>
     <test-element></test-element>
@@ -41,7 +37,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import './dom-util-behavior.js';
 import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
diff --git a/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior_test.html b/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior_test.html
index e989a74..4f80b2b 100644
--- a/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior_test.html
@@ -23,10 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../test/test-pre-setup.js"></script>
-<script type="module" src="../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-access-behavior.js"></script>
-
 <test-fixture id="basic">
   <template>
     <test-element></test-element>
@@ -34,7 +30,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import './gr-access-behavior.js';
 import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
diff --git a/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html b/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html
index c2659bb..06fe722 100644
--- a/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html
@@ -23,10 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../test/test-pre-setup.js"></script>
-<script type="module" src="../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-admin-nav-behavior.js"></script>
-
 <test-fixture id="basic">
   <template>
     <test-element></test-element>
@@ -34,7 +30,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import './gr-admin-nav-behavior.js';
 import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
diff --git a/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html b/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html
index c314c18..2889371 100644
--- a/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html
@@ -23,10 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../test/test-pre-setup.js"></script>
-<script type="module" src="../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-change-table-behavior.js"></script>
-
 <test-fixture id="basic">
   <template>
     <test-element></test-element>
@@ -42,7 +38,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import './gr-change-table-behavior.js';
 import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
diff --git a/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior.js b/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior.js
index 1ba8fd9..2e7f5d4 100644
--- a/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior.js
+++ b/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior.js
@@ -25,12 +25,8 @@
   Gerrit.DisplayNameBehavior = {
     // TODO(dmfilippov) replace DisplayNameBehavior with GrDisplayNameUtils
 
-    /**
-     * enableEmail when true enables to fallback to using email if
-     * the account name is not avilable.
-     */
-    getUserName(config, account, enableEmail) {
-      return GrDisplayNameUtils.getUserName(config, account, enableEmail);
+    getUserName(config, account) {
+      return GrDisplayNameUtils.getUserName(config, account);
     },
 
     getGroupDisplayName(group) {
diff --git a/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior_test.html b/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior_test.html
index 863f708..e4fc3f2 100644
--- a/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior_test.html
@@ -23,10 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../test/test-pre-setup.js"></script>
-<script type="module" src="../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-display-name-behavior.js"></script>
-
 <test-fixture id="basic">
   <template>
     <test-element-anon></test-element-anon>
@@ -34,7 +30,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import './gr-display-name-behavior.js';
 import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
@@ -65,26 +60,26 @@
     const account = {
       name: 'test-name',
     };
-    assert.deepEqual(element.getUserName(config, account, true), 'test-name');
+    assert.equal(element.getUserName(config, account), 'test-name');
   });
 
   test('getUserName username only', () => {
     const account = {
       username: 'test-user',
     };
-    assert.deepEqual(element.getUserName(config, account, true), 'test-user');
+    assert.equal(element.getUserName(config, account), 'test-user');
   });
 
   test('getUserName email only', () => {
     const account = {
       email: 'test-user@test-url.com',
     };
-    assert.deepEqual(element.getUserName(config, account, true),
+    assert.equal(element.getUserName(config, account),
         'test-user@test-url.com');
   });
 
   test('getUserName returns not Anonymous Coward as the anon name', () => {
-    assert.deepEqual(element.getUserName(config, null, true), 'Anonymous');
+    assert.equal(element.getUserName(config, null), 'Anonymous');
   });
 
   test('getUserName for the config returning the anon name', () => {
@@ -93,7 +88,7 @@
         anonymous_coward_name: 'Test Anon',
       },
     };
-    assert.deepEqual(element.getUserName(config, null, true), 'Test Anon');
+    assert.equal(element.getUserName(config, null), 'Test Anon');
   });
 
   test('getGroupDisplayName', () => {
diff --git a/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior_test.html b/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior_test.html
index 37577df..c918d5e 100644
--- a/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior_test.html
@@ -23,10 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../test/test-pre-setup.js"></script>
-<script type="module" src="../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-list-view-behavior.js"></script>
-
 <test-fixture id="basic">
   <template>
     <test-element></test-element>
@@ -34,7 +30,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import './gr-list-view-behavior.js';
 import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
diff --git a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
index 8e05228..e0bbf17 100644
--- a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
@@ -17,16 +17,11 @@
 <!-- Polymer included for the html import polyfill. -->
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../test/test-pre-setup.js"></script>
-<script type="module" src="../../test/common-test-setup.js"></script>
 <title>gr-patch-set-behavior</title>
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
-<script type="module" src="./gr-patch-set-behavior.js"></script>
-
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import './gr-patch-set-behavior.js';
 suite('gr-patch-set-behavior tests', () => {
diff --git a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
index 8395ce9..f644469 100644
--- a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
@@ -17,16 +17,11 @@
 <!-- Polymer included for the html import polyfill. -->
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../test/test-pre-setup.js"></script>
-<script type="module" src="../../test/common-test-setup.js"></script>
 <title>gr-path-list-behavior</title>
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
-<script type="module" src="./gr-path-list-behavior.js"></script>
-
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import './gr-path-list-behavior.js';
 suite('gr-path-list-behavior tests', () => {
diff --git a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html
index 7387a17..efc8e9b 100644
--- a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html
@@ -22,16 +22,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../test/test-pre-setup.js"></script>
-<script type="module" src="../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-tooltip-behavior.js"></script>
-
-<script type="module">
-import '../../test/test-pre-setup.js';
-import '../../test/common-test-setup.js';
-import './gr-tooltip-behavior.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -40,7 +30,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import './gr-tooltip-behavior.js';
 import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
diff --git a/polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior_test.html b/polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior_test.html
index 94d6c12..6968bbf 100644
--- a/polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior_test.html
@@ -22,16 +22,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../test/test-pre-setup.js"></script>
-<script type="module" src="../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-url-encoding-behavior.js"></script>
-
-<script type="module">
-import '../../test/test-pre-setup.js';
-import '../../test/common-test-setup.js';
-import './gr-url-encoding-behavior.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -40,7 +30,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import './gr-url-encoding-behavior.js';
 import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
diff --git a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
index f7231fb..ac979b2 100644
--- a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
@@ -23,10 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../test/test-pre-setup.js"></script>
-<script type="module" src="../../test/common-test-setup.js"></script>
-<script type="module" src="./keyboard-shortcut-behavior.js"></script>
-
 <test-fixture id="basic">
   <template>
     <test-element></test-element>
@@ -42,7 +38,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import './keyboard-shortcut-behavior.js';
 import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
diff --git a/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html b/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html
index af078e5..aa24ffc 100644
--- a/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html
@@ -23,10 +23,7 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../test/test-pre-setup.js"></script>
-<script type="module" src="../../test/common-test-setup.js"></script>
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import '../base-url-behavior/base-url-behavior.js';
 import './rest-client-behavior.js';
@@ -34,9 +31,6 @@
 window.CANONICAL_PATH = '/r';
 </script>
 
-<script type="module" src="../base-url-behavior/base-url-behavior.js"></script>
-<script type="module" src="./rest-client-behavior.js"></script>
-
 <test-fixture id="basic">
   <template>
     <test-element></test-element>
@@ -52,7 +46,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import '../base-url-behavior/base-url-behavior.js';
 import './rest-client-behavior.js';
diff --git a/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior_test.html b/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior_test.html
index fc1224f..791dc4d 100644
--- a/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior_test.html
@@ -22,16 +22,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../test/test-pre-setup.js"></script>
-<script type="module" src="../../test/common-test-setup.js"></script>
-<script type="module" src="./safe-types-behavior.js"></script>
-
-<script type="module">
-import '../../test/test-pre-setup.js';
-import '../../test/common-test-setup.js';
-import './safe-types-behavior.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -40,7 +30,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import './safe-types-behavior.js';
 import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
diff --git a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
index 4754c4a..459949e 100644
--- a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
@@ -24,16 +24,6 @@
 <script src="/node_modules/page/page.js"></script>
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-access-section.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-access-section.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -42,7 +32,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-access-section.js';
 suite('gr-access-section tests', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html
index 36c2081..7a32737 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html
@@ -25,18 +25,6 @@
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
 
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-
-<script type="module" src="./gr-admin-group-list.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-admin-group-list.js';
-void(0);
-</script>
-
 <test-fixture id="basic">
   <template>
     <gr-admin-group-list></gr-admin-group-list>
@@ -44,7 +32,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-admin-group-list.js';
 let counter = 0;
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html
index 584eb88..7f989e7 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-admin-view.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-admin-view.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-admin-view.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog_test.html
index e948a31..daf37bf 100644
--- a/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-confirm-delete-item-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-confirm-delete-item-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-confirm-delete-item-dialog.js';
 suite('gr-confirm-delete-item-dialog tests', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.html
index 20226dd..4ce1855 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-create-change-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-create-change-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-create-change-dialog.js';
 suite('gr-create-change-dialog tests', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.html
index e9585d3..14644a4 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.html
@@ -24,16 +24,6 @@
 <script src="/node_modules/page/page.js"></script>
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-create-group-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-create-group-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -42,7 +32,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-create-group-dialog.js';
 suite('gr-create-group-dialog tests', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.html
index 28cf2e8..394b1d8 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-create-pointer-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-create-pointer-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-create-pointer-dialog.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_test.html
index 09bb63e..a14a9c9 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-create-repo-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-create-repo-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-create-repo-dialog.js';
 suite('gr-create-repo-dialog tests', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
index d62874d..c7a41bf 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-group-audit-log.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-group-audit-log.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-group-audit-log.js';
 suite('gr-group-audit-log tests', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html
index 9380b86..e7c4afd 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-group-members.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-group-members.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-group-members.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html b/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html
index 9f278c4..6461833 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-group.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-group.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-group.js';
 suite('gr-group tests', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
index 6f05029..82d89e1 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
@@ -24,16 +24,6 @@
 <script src="/node_modules/page/page.js"></script>
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-permission.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-permission.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -42,7 +32,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-permission.js';
 suite('gr-permission tests', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html
index f66346e..ecb2ec3 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-plugin-config-array-editor.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-plugin-config-array-editor.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-plugin-config-array-editor.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html
index f89eb8e..da626e9 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html
@@ -24,16 +24,6 @@
 <script src="/node_modules/page/page.js"></script>
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-plugin-list.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-plugin-list.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -42,7 +32,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-plugin-list.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
index 4a482db..89f4df7 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
@@ -24,16 +24,6 @@
 <script src="/node_modules/page/page.js"></script>
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-repo-access.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-repo-access.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -42,7 +32,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-repo-access.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command_test.html b/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command_test.html
index a3f507b..c260613 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-repo-command.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-repo-command.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-repo-command.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.html b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.html
index c2f71e7..ce2493a 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.html
@@ -24,16 +24,6 @@
 <script src="/node_modules/page/page.js"></script>
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-repo-commands.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-repo-commands.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -42,7 +32,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-repo-commands.js';
 suite('gr-repo-commands tests', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html
index 1d6f05e..14e67e9 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-repo-dashboards.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-repo-dashboards.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-repo-dashboards.js';
 suite('gr-repo-dashboards tests', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html
index 13510d8..27a3498 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html
@@ -24,16 +24,6 @@
 <script src="/node_modules/page/page.js"></script>
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-repo-detail-list.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-repo-detail-list.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -42,7 +32,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-repo-detail-list.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.html b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.html
index fbd1099..b5991b2 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.html
@@ -24,16 +24,6 @@
 <script src="/node_modules/page/page.js"></script>
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-repo-list.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-repo-list.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -42,7 +32,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-repo-list.js';
 let counter;
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html
index f70d3ea..c2abef2 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-repo-plugin-config.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-repo-plugin-config.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-repo-plugin-config.js';
 suite('gr-repo-plugin-config tests', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html
index b9d5d56..1dccb7a 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-repo.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-repo.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-repo.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html
index 42e5f32..052eb96 100644
--- a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html
@@ -24,16 +24,6 @@
 <script src="/node_modules/page/page.js"></script>
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-rule-editor.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-rule-editor.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -42,7 +32,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-rule-editor.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
index 9bfec19..d26fc17 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
@@ -23,19 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../../scripts/util.js"></script>
-
-<script type="module" src="./gr-change-list-item.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../../scripts/util.js';
-import './gr-change-list-item.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -44,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../../scripts/util.js';
 import './gr-change-list-item.js';
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.html
index 468cd41..44ebff7 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.html
@@ -24,16 +24,6 @@
 <script src="/node_modules/page/page.js"></script>
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-change-list-view.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-change-list-view.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -42,7 +32,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-change-list-view.js';
 const CHANGE_ID = 'IcA3dAB3edAB9f60B8dcdA6ef71A75980e4B7127';
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
index 6329666..d077f6b 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
@@ -23,19 +23,8 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
 <script src="/node_modules/page/page.js"></script>
 
-<script type="module" src="./gr-change-list.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-change-list.js';
-void(0);
-</script>
-
 <test-fixture id="basic">
   <template>
     <gr-change-list></gr-change-list>
@@ -49,7 +38,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-change-list.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.html b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.html
index 3cbab30..213ac07 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.html
@@ -23,19 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../../scripts/util.js"></script>
-
-<script type="module" src="./gr-create-change-help.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../../scripts/util.js';
-import './gr-create-change-help.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -44,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../../scripts/util.js';
 import './gr-create-change-help.js';
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html
index f992b0b..620e6fe 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-create-commands-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-create-commands-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-create-commands-dialog.js';
 suite('gr-create-commands-dialog tests', () => {
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
index c879923..8a0cfa0 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-dashboard-view.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-dashboard-view.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-dashboard-view.js';
 suite('gr-dashboard-view tests', () => {
diff --git a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header_test.html b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header_test.html
index 02fb715..a075870 100644
--- a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-repo-header.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-repo-header.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-repo-header.js';
 suite('gr-repo-header tests', () => {
diff --git a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.html b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.html
index 169c028..aa32a94 100644
--- a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-user-header.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-user-header.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-user-header.js';
 suite('gr-user-header tests', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index 51888d6..a3db19f 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -496,9 +496,7 @@
   }
 
   _getRebaseAction(revisionActions) {
-    return this._getRevisionAction(revisionActions, 'rebase',
-        {rebaseOnCurrent: null}
-    );
+    return this._getRevisionAction(revisionActions, 'rebase', null);
   }
 
   _getRevisionAction(revisionActions, actionName, emptyActionValue) {
@@ -523,7 +521,7 @@
         .then(revisionActions => {
           if (!revisionActions) { return; }
 
-          this.revisionActions = this._updateRebaseAction(revisionActions);
+          this.revisionActions = revisionActions;
           this._sendShowRevisionActions({
             change: this.change,
             revisionActions,
@@ -548,18 +546,6 @@
     );
   }
 
-  _updateRebaseAction(revisionActions) {
-    if (revisionActions && revisionActions.rebase) {
-      revisionActions.rebase.rebaseOnCurrent =
-          !!revisionActions.rebase.enabled;
-      this._parentIsCurrent = !revisionActions.rebase.enabled;
-      revisionActions.rebase.enabled = true;
-    } else {
-      this._parentIsCurrent = true;
-    }
-    return revisionActions;
-  }
-
   _changeChanged() {
     this.reload();
   }
@@ -969,7 +955,8 @@
   }
 
   showRevertDialog() {
-    const query = 'submissionid:' + this.change.submission_id;
+    // The search is still broken if there is a " in the topic.
+    const query = `submissionid: "${this.change.submission_id}"`;
     /* A chromium plugin expects that the modifyRevertMsg hook will only
     be called after the revert button is pressed, hence we populate the
     revert dialog after revert button is pressed. */
@@ -1120,8 +1107,9 @@
   }
 
   _calculateDisabled(action, hasKnownChainState) {
-    if (action.__key === 'rebase' && hasKnownChainState === false) {
-      return true;
+    if (action.__key === 'rebase') {
+      // Rebase button is only disabled when change has no parent(s).
+      return hasKnownChainState === false;
     }
     return !action.enabled;
   }
@@ -1601,6 +1589,13 @@
     });
   }
 
+  _computeRebaseOnCurrent(revisionRebaseAction) {
+    if (revisionRebaseAction) {
+      return !!revisionRebaseAction.enabled;
+    }
+    return null;
+  }
+
   /**
    * Occasionally, a change created by a change action is not yet knwon to the
    * API for a brief time. Wait for the given change number to be recognized.
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_html.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_html.js
index 7aa5ecd..b66beed 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_html.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_html.js
@@ -101,7 +101,7 @@
         </gr-dropdown>
     </div>
     <gr-overlay id="overlay" with-backdrop="">
-      <gr-confirm-rebase-dialog id="confirmRebase" class="confirmDialog" change-number="[[change._number]]" on-confirm="_handleRebaseConfirm" on-cancel="_handleConfirmDialogCancel" branch="[[change.branch]]" has-parent="[[hasParent]]" rebase-on-current="[[_revisionRebaseAction.rebaseOnCurrent]]" hidden=""></gr-confirm-rebase-dialog>
+      <gr-confirm-rebase-dialog id="confirmRebase" class="confirmDialog" change-number="[[change._number]]" on-confirm="_handleRebaseConfirm" on-cancel="_handleConfirmDialogCancel" branch="[[change.branch]]" has-parent="[[hasParent]]" rebase-on-current="[[_computeRebaseOnCurrent(_revisionRebaseAction)]]" hidden=""></gr-confirm-rebase-dialog>
       <gr-confirm-cherrypick-dialog id="confirmCherrypick" class="confirmDialog" change-status="[[changeStatus]]" commit-message="[[commitMessage]]" commit-num="[[commitNum]]" on-confirm="_handleCherrypickConfirm" on-cancel="_handleConfirmDialogCancel" project="[[change.project]]" hidden=""></gr-confirm-cherrypick-dialog>
       <gr-confirm-cherrypick-conflict-dialog id="confirmCherrypickConflict" class="confirmDialog" on-confirm="_handleCherrypickConflictConfirm" on-cancel="_handleConfirmDialogCancel" hidden=""></gr-confirm-cherrypick-conflict-dialog>
       <gr-confirm-move-dialog id="confirmMove" class="confirmDialog" on-confirm="_handleMoveConfirm" on-cancel="_handleConfirmDialogCancel" project="[[change.project]]" hidden=""></gr-confirm-move-dialog>
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index 0d78fb4..f8f991f 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -23,19 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../../scripts/util.js"></script>
-
-<script type="module" src="./gr-change-actions.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../../scripts/util.js';
-import './gr-change-actions.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -44,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../../scripts/util.js';
 import './gr-change-actions.js';
@@ -393,7 +379,7 @@
 
       action.enabled = false;
       assert.equal(
-          element._calculateDisabled(action, hasKnownChainState), true);
+          element._calculateDisabled(action, hasKnownChainState), false);
     });
 
     test('rebase change', done => {
@@ -416,7 +402,6 @@
         };
         assert.isTrue(fetchChangesStub.called);
         element._handleRebaseConfirm({detail: {base: '1234'}});
-        rebaseAction.rebaseOnCurrent = true;
         assert.deepEqual(fireActionStub.lastCall.args,
             ['/rebase', rebaseAction, true, {base: '1234'}]);
         done();
@@ -921,12 +906,13 @@
       });
 
       suite('revert change submitted together', () => {
+        let getChangesStub;
         setup(() => {
           element.change = {
-            submission_id: '199',
+            submission_id: '199 0',
             current_revision: '2000',
           };
-          sandbox.stub(element.$.restAPI, 'getChanges')
+          getChangesStub = sandbox.stub(element.$.restAPI, 'getChanges')
               .returns(Promise.resolve([
                 {change_id: '12345678901234', topic: 'T', subject: 'random'},
                 {change_id: '23456', topic: 'T', subject: 'a'.repeat(100)},
@@ -938,6 +924,7 @@
               .querySelector('gr-button[data-action-key="revert"]');
           MockInteractions.tap(revertButton);
           flush(() => {
+            assert.equal(getChangesStub.args[0][1], 'submissionid: "199 0"');
             const confirmRevertDialog = element.$.confirmRevertDialog;
             const revertSingleChangeLabel = confirmRevertDialog
                 .shadowRoot.querySelector('.revertSingleChange');
@@ -947,7 +934,7 @@
                 'Revert single change');
             assert(revertSubmissionLabel.innerText.trim() ===
                 'Revert entire submission (2 Changes)');
-            let expectedMsg = 'Revert submission 199' + '\n\n' +
+            let expectedMsg = 'Revert submission 199 0' + '\n\n' +
               'Reason for revert: <INSERT REASONING HERE>' + '\n' +
               'Reverted Changes:' + '\n' +
               '1234567890:random' + '\n' +
@@ -994,7 +981,7 @@
           flush(() => {
             const radioInputs = confirmRevertDialog.shadowRoot
                 .querySelectorAll('input[name="revertOptions"]');
-            const revertSubmissionMsg = 'Revert submission 199' + '\n\n' +
+            const revertSubmissionMsg = 'Revert submission 199 0' + '\n\n' +
             'Reason for revert: <INSERT REASONING HERE>' + '\n' +
             'Reverted Changes:' + '\n' +
             '1234567890:random' + '\n' +
@@ -1922,57 +1909,23 @@
       assert.strictEqual(element.$.confirmRebase.rebaseOnCurrent, null);
     });
 
-    test('_updateRebaseAction sets _parentIsCurrent on no rebase', () => {
-      const currentRevisionActions = {
-        cherrypick: {
-          enabled: true,
-          label: 'Cherry Pick',
-          method: 'POST',
-          title: 'cherrypick',
-        },
+    test('_computeRebaseOnCurrent', () => {
+      const rebaseAction = {
+        enabled: true,
+        label: 'Rebase',
+        method: 'POST',
+        title: 'Rebase onto tip of branch or parent change',
       };
-      element._parentIsCurrent = undefined;
-      element._updateRebaseAction(currentRevisionActions);
-      assert.isTrue(element._parentIsCurrent);
-    });
 
-    test('_updateRebaseAction', () => {
-      const currentRevisionActions = {
-        cherrypick: {
-          enabled: true,
-          label: 'Cherry Pick',
-          method: 'POST',
-          title: 'cherrypick',
-        },
-        rebase: {
-          enabled: true,
-          label: 'Rebase',
-          method: 'POST',
-          title: 'Rebase onto tip of branch or parent change',
-        },
-      };
-      element._parentIsCurrent = undefined;
-
-      // Rebase enabled should always end up true.
       // When rebase is enabled initially, rebaseOnCurrent should be set to
       // true.
-      assert.equal(element._updateRebaseAction(currentRevisionActions),
-          currentRevisionActions);
+      assert.isTrue(element._computeRebaseOnCurrent(rebaseAction));
 
-      assert.isTrue(currentRevisionActions.rebase.enabled);
-      assert.isTrue(currentRevisionActions.rebase.rebaseOnCurrent);
-      assert.isFalse(element._parentIsCurrent);
-
-      delete currentRevisionActions.rebase.enabled;
+      delete rebaseAction.enabled;
 
       // When rebase is not enabled initially, rebaseOnCurrent should be set to
       // false.
-      assert.equal(element._updateRebaseAction(currentRevisionActions),
-          currentRevisionActions);
-
-      assert.isTrue(currentRevisionActions.rebase.enabled);
-      assert.isFalse(currentRevisionActions.rebase.rebaseOnCurrent);
-      assert.isTrue(element._parentIsCurrent);
+      assert.isFalse(element._computeRebaseOnCurrent(rebaseAction));
     });
   });
 });
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html
index 3ee7219..160b912 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html
@@ -23,18 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../plugins/gr-plugin-host/gr-plugin-host.js"></script>
-<script type="module" src="./gr-change-metadata.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../plugins/gr-plugin-host/gr-plugin-host.js';
-import './gr-change-metadata.js';
-void(0);
-</script>
 
 <test-fixture id="element">
   <template>
@@ -49,7 +37,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../plugins/gr-plugin-host/gr-plugin-host.js';
 import './gr-change-metadata.js';
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
index 5fe53f8d..773e6ec 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
@@ -23,18 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../core/gr-router/gr-router.js"></script>
-<script type="module" src="./gr-change-metadata.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../core/gr-router/gr-router.js';
-import './gr-change-metadata.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -43,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../core/gr-router/gr-router.js';
 import './gr-change-metadata.js';
diff --git a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html
index 2883f20..3a4ef25 100644
--- a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-change-requirements.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-change-requirements.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-change-requirements.js';
 suite('gr-change-metadata tests', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 05afa35..d62ea04 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -292,7 +292,6 @@
       _loading: Boolean,
       /** @type {?} */
       _projectConfig: Object,
-      _rebaseOnCurrent: Boolean,
       _replyButtonLabel: {
         type: String,
         value: 'Reply',
@@ -1837,8 +1836,9 @@
    * @param {!Object} change
    */
   _computeCopyTextForTitle(change) {
-    return `${change._number}: ${change.subject}` +
-     ` | https://${location.host}${this._computeChangeUrl(change)}`;
+    return `${change._number}: ${change.subject} | ` +
+     `${location.protocol}//${location.host}` +
+       `${this._computeChangeUrl(change)}`;
   }
 
   _toggleCommitCollapsed() {
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index 4dc0e9b3..24e2fbd 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -23,21 +23,8 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
 <script src="/node_modules/page/page.js"></script>
 
-<script type="module" src="../../edit/gr-edit-constants.js"></script>
-<script type="module" src="./gr-change-view.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../edit/gr-edit-constants.js';
-import './gr-change-view.js';
-void(0);
-</script>
-
 <test-fixture id="basic">
   <template>
     <gr-change-view></gr-change-view>
@@ -51,7 +38,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../edit/gr-edit-constants.js';
 import './gr-change-view.js';
@@ -1204,7 +1190,7 @@
         .returns('/change/123');
     assert.equal(
         element._computeCopyTextForTitle(change),
-        '123: test subject | https://localhost:8081/change/123'
+        `123: test subject | http://${location.host}/change/123`
     );
   });
 
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html
index 5e8c3ab..c064679 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-comment-list.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-comment-list.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-comment-list.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.html b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.html
index f2fdbe0..74d4cca 100644
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.html
+++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.html
@@ -23,18 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../core/gr-router/gr-router.js"></script>
-<script type="module" src="./gr-commit-info.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../core/gr-router/gr-router.js';
-import './gr-commit-info.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -43,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../core/gr-router/gr-router.js';
 import './gr-commit-info.js';
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.html
index 522b290..c9bea1c 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-confirm-abandon-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-confirm-abandon-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-confirm-abandon-dialog.js';
 suite('gr-confirm-abandon-dialog tests', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html
index 58b1182..d78be11 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-confirm-cherrypick-conflict-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-confirm-cherrypick-conflict-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-confirm-cherrypick-conflict-dialog.js';
 suite('gr-confirm-cherrypick-conflict-dialog tests', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html
index fa9531f..780b2ee 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-confirm-cherrypick-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-confirm-cherrypick-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-confirm-cherrypick-dialog.js';
 suite('gr-confirm-cherrypick-dialog tests', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.html
index 27c9934..25b110ad 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-confirm-move-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-confirm-move-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-confirm-move-dialog.js';
 suite('gr-confirm-move-dialog tests', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
index d715fbe3..5cf455f 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-confirm-rebase-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-confirm-rebase-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-confirm-rebase-dialog.js';
 suite('gr-confirm-rebase-dialog tests', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html
index 1f8837b..fd984ec 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-confirm-revert-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-confirm-revert-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-confirm-revert-dialog.js';
 suite('gr-confirm-revert-dialog tests', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog_test.html
index d84aa4d..cc35d72 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-confirm-revert-submission-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-confirm-revert-submission-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -42,7 +32,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-confirm-revert-submission-dialog.js';
 suite('gr-confirm-revert-submission-dialog tests', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.html
index 5acbbde..dfe1e6e 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.html
@@ -23,19 +23,8 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
 <script src="/node_modules/page/page.js"></script>
 
-<script type="module" src="./gr-confirm-submit-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-confirm-submit-dialog.js';
-void(0);
-</script>
-
 <test-fixture id="basic">
   <template>
     <gr-confirm-submit-dialog></gr-confirm-submit-dialog>
@@ -43,7 +32,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-confirm-submit-dialog.js';
 suite('gr-file-list-header tests', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
index 8a55c21..c2f4963 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-download-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-download-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -47,7 +37,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-download-dialog.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html
index 32ecb14..745d25e 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html
@@ -23,19 +23,8 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
 <script src="/node_modules/page/page.js"></script>
 
-<script type="module" src="./gr-file-list-header.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-file-list-header.js';
-void(0);
-</script>
-
 <test-fixture id="basic">
   <template>
     <gr-file-list-header></gr-file-list-header>
@@ -49,7 +38,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-file-list-header.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index 4a5f324..6b8e971 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -474,11 +474,23 @@
    */
   _computeCommentsString(changeComments, patchRange, path) {
     const unresolvedCount =
-        changeComments.computeUnresolvedNum(patchRange.basePatchNum, path) +
-        changeComments.computeUnresolvedNum(patchRange.patchNum, path);
+        changeComments.computeUnresolvedNum({
+          patchNum: patchRange.basePatchNum,
+          path,
+        }) +
+        changeComments.computeUnresolvedNum({
+          patchNum: patchRange.patchNum,
+          path,
+        });
     const commentCount =
-        changeComments.computeCommentCount(patchRange.basePatchNum, path) +
-        changeComments.computeCommentCount(patchRange.patchNum, path);
+        changeComments.computeCommentCount({
+          patchNum: patchRange.basePatchNum,
+          path,
+        }) +
+        changeComments.computeCommentCount({
+          patchNum: patchRange.patchNum,
+          path,
+        });
     const commentString = GrCountStringFormatter.computePluralString(
         commentCount, 'comment');
     const unresolvedString = GrCountStringFormatter.computeString(
@@ -501,8 +513,14 @@
    */
   _computeDraftsString(changeComments, patchRange, path) {
     const draftCount =
-        changeComments.computeDraftCount(patchRange.basePatchNum, path) +
-        changeComments.computeDraftCount(patchRange.patchNum, path);
+        changeComments.computeDraftCount({
+          patchNum: patchRange.basePatchNum,
+          path,
+        }) +
+        changeComments.computeDraftCount({
+          patchNum: patchRange.patchNum,
+          path,
+        });
     return GrCountStringFormatter.computePluralString(draftCount, 'draft');
   }
 
@@ -516,8 +534,14 @@
    */
   _computeDraftsStringMobile(changeComments, patchRange, path) {
     const draftCount =
-        changeComments.computeDraftCount(patchRange.basePatchNum, path) +
-        changeComments.computeDraftCount(patchRange.patchNum, path);
+        changeComments.computeDraftCount({
+          patchNum: patchRange.basePatchNum,
+          path,
+        }) +
+        changeComments.computeDraftCount({
+          patchNum: patchRange.patchNum,
+          path,
+        });
     return GrCountStringFormatter.computeShortString(draftCount, 'd');
   }
 
@@ -531,8 +555,14 @@
    */
   _computeCommentsStringMobile(changeComments, patchRange, path) {
     const commentCount =
-        changeComments.computeCommentCount(patchRange.basePatchNum, path) +
-        changeComments.computeCommentCount(patchRange.patchNum, path);
+        changeComments.computeCommentCount({
+          patchNum: patchRange.basePatchNum,
+          path,
+        }) +
+        changeComments.computeCommentCount({
+          patchNum: patchRange.patchNum,
+          path,
+        });
     return GrCountStringFormatter.computeShortString(commentCount, 'c');
   }
 
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index f3412a7..96db120 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -23,26 +23,8 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
 <script src="/components/web-component-tester/data/a11ySuite.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
 <script src="/node_modules/page/page.js"></script>
-<script type="module" src="../../diff/gr-comment-api/gr-comment-api.js"></script>
-<script type="module" src="../../../scripts/util.js"></script>
-
-<script type="module" src="../../shared/gr-rest-api-interface/mock-diff-response_test.js"></script>
-<script type="module" src="./gr-file-list.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../diff/gr-comment-api/gr-comment-api.js';
-import '../../../scripts/util.js';
-import '../../shared/gr-rest-api-interface/mock-diff-response_test.js';
-import './gr-file-list.js';
-import '../../diff/gr-comment-api/gr-comment-api-mock_test.js';
-void(0);
-</script>
 
 <dom-module id="comment-api-mock">
   <template>
@@ -51,8 +33,7 @@
         on-reload-drafts="_reloadDraftsWithCallback"></gr-file-list>
     <gr-comment-api id="commentAPI"></gr-comment-api>
   </template>
-  <script type="module" src="../../diff/gr-comment-api/gr-comment-api-mock_test.js"></script>
-</dom-module>
+  </dom-module>
 
 <test-fixture id="basic">
   <template>
@@ -61,7 +42,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../diff/gr-comment-api/gr-comment-api.js';
 import '../../../scripts/util.js';
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html
index deb00bd..c87c54f 100644
--- a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-included-in-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-included-in-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-included-in-dialog.js';
 suite('gr-included-in-dialog', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.html b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.html
index 18ba2a5..374472f 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.html
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.html
@@ -24,17 +24,6 @@
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
 
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-label-score-row.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-label-score-row.js';
-void(0);
-</script>
-
 <test-fixture id="basic">
   <template>
     <gr-label-score-row></gr-label-score-row>
@@ -42,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-label-score-row.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html
index b6075ee..a01454c0 100644
--- a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html
+++ b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-label-scores.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-label-scores.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-label-scores.js';
 suite('gr-label-scores tests', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
index 04f12c2f..8864056 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-message.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-message.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-message.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
index 961abb9..a5cc939 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
@@ -23,20 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../diff/gr-comment-api/gr-comment-api.js"></script>
-
-<script type="module" src="./gr-messages-list.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../diff/gr-comment-api/gr-comment-api.js';
-import './gr-messages-list.js';
-import '../../diff/gr-comment-api/gr-comment-api-mock_test.js';
-void(0);
-</script>
 
 <dom-module id="comment-api-mock">
   <template>
@@ -45,8 +31,7 @@
         change-comments="[[_changeComments]]"></gr-messages-list>
     <gr-comment-api id="commentAPI"></gr-comment-api>
   </template>
-  <script type="module" src="../../diff/gr-comment-api/gr-comment-api-mock_test.js"></script>
-</dom-module>
+  </dom-module>
 
 <test-fixture id="basic">
   <template>
@@ -57,7 +42,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../diff/gr-comment-api/gr-comment-api.js';
 import './gr-messages-list.js';
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
index 43037af..94e0fe5 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-related-changes-list.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-related-changes-list.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-related-changes-list.js';
 suite('gr-related-changes-list tests', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html
index 7e651c5..0c9108c 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html
@@ -24,19 +24,6 @@
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
 
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../plugins/gr-plugin-host/gr-plugin-host.js"></script>
-<script type="module" src="./gr-reply-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../plugins/gr-plugin-host/gr-plugin-host.js';
-import './gr-reply-dialog.js';
-void(0);
-</script>
-
 <test-fixture id="basic">
   <template>
     <gr-reply-dialog></gr-reply-dialog>
@@ -50,7 +37,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../plugins/gr-plugin-host/gr-plugin-host.js';
 import './gr-reply-dialog.js';
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
index 5257ef46..f7361597 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-reply-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-reply-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import {IronOverlayManager} from '@polymer/iron-overlay-behavior/iron-overlay-manager.js';
 import '../../../test/common-test-setup.js';
 import './gr-reply-dialog.js';
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js
index a74ec5f..57337fb 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js
@@ -167,7 +167,7 @@
     return NaN;
   }
 
-  _computeReviewerTooltip(reviewer, change) {
+  _computeVoteableText(reviewer, change) {
     if (!change || !change.labels) { return ''; }
     const maxScores = [];
     const maxPermitted = this._getMaxPermittedScores(change);
@@ -181,11 +181,7 @@
         maxScores.push(`${label}`);
       }
     }
-    if (maxScores.length) {
-      return 'Votable: ' + maxScores.join(', ');
-    } else {
-      return '';
-    }
+    return maxScores.join(', ');
   }
 
   _reviewersChanged(changeRecord, owner) {
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_html.js b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_html.js
index bf7db12..c5df61d 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_html.js
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_html.js
@@ -44,7 +44,7 @@
     </style>
     <div class="container">
       <template is="dom-repeat" items="[[_displayedReviewers]]" as="reviewer">
-        <gr-account-chip class="reviewer" account="[[reviewer]]" on-remove="_handleRemove" additional-text="[[_computeReviewerTooltip(reviewer, change)]]" removable="[[_computeCanRemoveReviewer(reviewer, mutable)]]">
+        <gr-account-chip class="reviewer" account="[[reviewer]]" on-remove="_handleRemove" voteable-text="[[_computeVoteableText(reviewer, change)]]" removable="[[_computeCanRemoveReviewer(reviewer, mutable)]]">
         </gr-account-chip>
       </template>
       <gr-button class="hiddenReviewers" link="" hidden\$="[[!_hiddenReviewerCount]]" on-click="_handleViewAll">and [[_hiddenReviewerCount]] more</gr-button>
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
index 627fa10..ab60078 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-reviewer-list.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-reviewer-list.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-reviewer-list.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
@@ -329,13 +318,13 @@
       },
     };
     assert.strictEqual(
-        element._computeReviewerTooltip({_account_id: 1}, change),
-        'Votable: Bar');
+        element._computeVoteableText({_account_id: 1}, change),
+        'Bar');
     assert.strictEqual(
-        element._computeReviewerTooltip({_account_id: 7}, change),
-        'Votable: Foo: +2, Bar, FooBar');
+        element._computeVoteableText({_account_id: 7}, change),
+        'Foo: +2, Bar, FooBar');
     assert.strictEqual(
-        element._computeReviewerTooltip({_account_id: 2}, change),
+        element._computeVoteableText({_account_id: 2}, change),
         '');
   });
 
@@ -347,7 +336,7 @@
       },
     };
     assert.strictEqual(
-        element._computeReviewerTooltip({_account_id: 1}, change), '');
+        element._computeVoteableText({_account_id: 1}, change), '');
   });
 });
 </script>
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
index a2c3620..cb6ba28 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-thread-list.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-thread-list.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-thread-list.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_html.js b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_html.js
index ccf22e6..3f4fc42 100644
--- a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_html.js
+++ b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_html.js
@@ -26,7 +26,7 @@
         width: 100%;
       }
       ol {
-        margin-left: var(--spacing-l);
+        margin-left: var(--spacing-xl);
         list-style: decimal;
       }
       p {
diff --git a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html
index 2aa71c6..bea8113 100644
--- a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-upload-help-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-upload-help-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-upload-help-dialog.js';
 suite('gr-upload-help-dialog tests', () => {
diff --git a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js
index 6d9f9d7..444986b 100644
--- a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js
+++ b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js
@@ -124,7 +124,7 @@
   }
 
   _accountName(account) {
-    return this.getUserName(this.config, account, true);
+    return this.getUserName(this.config, account);
   }
 }
 
diff --git a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.html b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.html
index fa0c7a7..d9d932a 100644
--- a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.html
+++ b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-account-dropdown.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-account-dropdown.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-account-dropdown.js';
 suite('gr-account-dropdown tests', () => {
diff --git a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.html b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.html
index 296c6f0..2e6cff0 100644
--- a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.html
+++ b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-error-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-error-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-error-dialog.js';
 suite('gr-error-dialog tests', () => {
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
index a4a9a7d..1d3b1fe 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
@@ -23,9 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-error-manager.js"></script>
-
 <script type="module">
 import '../../../test/common-test-setup.js';
 import './gr-error-manager.js';
diff --git a/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display_test.html b/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display_test.html
index bb449c3..1bf0b14 100644
--- a/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display_test.html
+++ b/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display_test.html
@@ -22,16 +22,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-key-binding-display.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-key-binding-display.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -40,7 +30,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-key-binding-display.js';
 suite('gr-key-binding-display tests', () => {
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.html b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.html
index eedd166..06946c5 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.html
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.html
@@ -22,16 +22,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-keyboard-shortcuts-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-keyboard-shortcuts-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -40,7 +30,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-keyboard-shortcuts-dialog.js';
 suite('gr-keyboard-shortcuts-dialog tests', () => {
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
index 817643b..c641575 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-main-header.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-main-header.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-main-header.js';
 suite('gr-main-header tests', () => {
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation_test.html b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation_test.html
index 8f3c623..33f6215 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation_test.html
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation_test.html
@@ -23,11 +23,7 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 suite('gr-navigation tests', () => {
   test('invalid patch ranges throw exceptions', () => {
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
index 55f8abd..ae5cb67 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
@@ -284,23 +284,17 @@
    */
   appStarted() {
     this.timeEnd(TIMING.APP_STARTED);
-    this.pageLoaded();
+    this._reportNavResTimes();
   },
 
   /**
-   * Page load time and other metrics, should be reported at any time
-   * after navigation.
+   * Browser's navigation and resource timings
    */
-  pageLoaded() {
-    if (this.performanceTiming.loadEventEnd === 0) {
-      console.error('pageLoaded should be called after window.onload');
-      this.async(this.pageLoaded, 100);
-    } else {
-      const perfEvents = Object.keys(this.performanceTiming.toJSON());
-      perfEvents.forEach(
-          eventName => this._reportPerformanceTiming(eventName)
-      );
-    }
+  _reportNavResTimes() {
+    const perfEvents = Object.keys(this.performanceTiming.toJSON());
+    perfEvents.forEach(
+        eventName => this._reportPerformanceTiming(eventName)
+    );
   },
 
   _reportPerformanceTiming(eventName, eventDetails) {
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
index ad9903e..ea6e8cc 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-reporting.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-reporting.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-reporting.js';
 suite('gr-reporting tests', () => {
@@ -79,6 +68,12 @@
         element.reporter.calledWithMatch(
             'timing-report', 'UI Latency', 'App Started', 42
         ));
+    assert.isTrue(
+        element.reporter.calledWithExactly(
+            'timing-report', 'UI Latency', 'NavResTime - loadEventEnd',
+            fakePerformance.loadEventEnd - fakePerformance.navigationStart,
+            undefined, true)
+    );
   });
 
   test('WebComponentsReady', () => {
@@ -89,16 +84,6 @@
     ));
   });
 
-  test('pageLoaded', () => {
-    element.pageLoaded();
-    assert.isTrue(
-        element.reporter.calledWithExactly(
-            'timing-report', 'UI Latency', 'NavResTime - loadEventEnd',
-            fakePerformance.loadEventEnd - fakePerformance.navigationStart,
-            undefined, true)
-    );
-  });
-
   test('beforeLocationChanged', () => {
     element._baselines['garbage'] = 'monster';
     sandbox.stub(element, 'time');
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
index 6ea07a5..5e98011 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-router.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-router.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-router.js';
 suite('gr-router tests', () => {
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
index 1bd0fca..c115946 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
@@ -23,15 +23,9 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
 <script src="/node_modules/page/page.js"></script>
 
-<script type="module" src="./gr-search-bar.js"></script>
-<script type="module" src="../../../scripts/util.js"></script>
-
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-search-bar.js';
 import '../../../scripts/util.js';
@@ -45,7 +39,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-search-bar.js';
 import '../../../scripts/util.js';
@@ -242,4 +235,4 @@
     });
   });
 });
-</script>
\ No newline at end of file
+</script>
diff --git a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js
index a93c139..b27adf7 100644
--- a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js
+++ b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js
@@ -161,7 +161,7 @@
 
   _mapAccountsHelper(accounts, predicate) {
     return accounts.map(account => {
-      const userName = this.getUserName(this._serverConfig, account, false);
+      const userName = this.getUserName(this._serverConfig, account);
       return {
         label: account.name || '',
         text: account.email ?
diff --git a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search_test.html b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search_test.html
index a0ba203..7cc75bb 100644
--- a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search_test.html
+++ b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-smart-search.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-smart-search.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-smart-search.js';
 suite('gr-smart-search tests', () => {
diff --git a/polygerrit-ui/app/elements/custom-dark-theme_test.html b/polygerrit-ui/app/elements/custom-dark-theme_test.html
index 308e2ee..24d9cdc 100644
--- a/polygerrit-ui/app/elements/custom-dark-theme_test.html
+++ b/polygerrit-ui/app/elements/custom-dark-theme_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../test/test-pre-setup.js"></script>
-<script type="module" src="../test/common-test-setup.js"></script>
-<script type="module" src="./gr-app.js"></script>
-
-<script type="module">
-import '../test/test-pre-setup.js';
-import '../test/common-test-setup.js';
-import './gr-app.js';
-void(0);
-</script>
 
 <test-fixture id="element">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../test/test-pre-setup.js';
 import '../test/common-test-setup.js';
 import './gr-app.js';
 suite('gr-app custom dark theme tests', () => {
diff --git a/polygerrit-ui/app/elements/custom-light-theme_test.html b/polygerrit-ui/app/elements/custom-light-theme_test.html
index 66567be..eb442a6 100644
--- a/polygerrit-ui/app/elements/custom-light-theme_test.html
+++ b/polygerrit-ui/app/elements/custom-light-theme_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../test/test-pre-setup.js"></script>
-<script type="module" src="../test/common-test-setup.js"></script>
-<script type="module" src="./gr-app.js"></script>
-
-<script type="module">
-import '../test/test-pre-setup.js';
-import '../test/common-test-setup.js';
-import './gr-app.js';
-void(0);
-</script>
 
 <test-fixture id="element">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../test/test-pre-setup.js';
 import '../test/common-test-setup.js';
 import './gr-app.js';
 suite('gr-app custom light theme tests', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.html b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.html
index 386f829..2ad35b3 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.html
@@ -17,19 +17,12 @@
 -->
 <meta name='viewport' content='width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes'>
 <title>gr-apply-fix-dialog</title>
-<script type="module" src="../../../test/common-test-setup.js"></script>
 <script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-
-<script type="module" src="./gr-apply-fix-dialog.js"></script>
-
 <script type="module">
 import '../../../test/common-test-setup.js';
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-apply-fix-dialog.js';
 void (0);
@@ -43,7 +36,6 @@
 
 <script type="module">
 import '../../../test/common-test-setup.js';
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-apply-fix-dialog.js';
 suite('gr-apply-fix-dialog tests', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js
index 95de0d1..ac43679 100644
--- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js
+++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js
@@ -36,440 +36,509 @@
  * @param {!Object} drafts
  * @param {number} changeNum
  */
-function ChangeComments(comments, robotComments, drafts, changeNum) {
-  this._comments = comments;
-  this._robotComments = robotComments;
-  this._drafts = drafts;
-  this._changeNum = changeNum;
-}
-
-ChangeComments.prototype = {
-  get comments() {
-    return this._comments;
-  },
-  get drafts() {
-    return this._drafts;
-  },
-  get robotComments() {
-    return this._robotComments;
-  },
-};
-
-ChangeComments.prototype._patchNumEquals =
+class ChangeComments {
+  constructor(comments, robotComments, drafts, changeNum) {
+    // TODO(taoalpha): replace these with exported methods from patchset behavior
+    this._patchNumEquals =
     Gerrit.PatchSetBehavior.patchNumEquals;
-ChangeComments.prototype._isMergeParent =
+    this._isMergeParent =
     Gerrit.PatchSetBehavior.isMergeParent;
-ChangeComments.prototype._getParentIndex =
+    this._getParentIndex =
     Gerrit.PatchSetBehavior.getParentIndex;
 
-/**
- * Get an object mapping file paths to a boolean representing whether that
- * path contains diff comments in the given patch set (including drafts and
- * robot comments).
- *
- * Paths with comments are mapped to true, whereas paths without comments
- * are not mapped.
- *
- * @param {Gerrit.PatchRange=} opt_patchRange The patch-range object containing
- *     patchNum and basePatchNum properties to represent the range.
- * @return {!Object}
- */
-ChangeComments.prototype.getPaths = function(opt_patchRange) {
-  const responses = [this.comments, this.drafts, this.robotComments];
-  const commentMap = {};
-  for (const response of responses) {
-    for (const path in response) {
-      if (response.hasOwnProperty(path) &&
+    this._comments = comments;
+    this._robotComments = robotComments;
+    this._drafts = drafts;
+    this._changeNum = changeNum;
+  }
+
+  get comments() {
+    return this._comments;
+  }
+
+  get drafts() {
+    return this._drafts;
+  }
+
+  get robotComments() {
+    return this._robotComments;
+  }
+
+  /**
+   * Get an object mapping file paths to a boolean representing whether that
+   * path contains diff comments in the given patch set (including drafts and
+   * robot comments).
+   *
+   * Paths with comments are mapped to true, whereas paths without comments
+   * are not mapped.
+   *
+   * @param {Gerrit.PatchRange=} opt_patchRange The patch-range object containing
+   *     patchNum and basePatchNum properties to represent the range.
+   * @return {!Object}
+   */
+  getPaths(opt_patchRange) {
+    const responses = [this.comments, this.drafts, this.robotComments];
+    const commentMap = {};
+    for (const response of responses) {
+      for (const path in response) {
+        if (response.hasOwnProperty(path) &&
           response[path].some(c => {
             // If don't care about patch range, we know that the path exists.
             if (!opt_patchRange) { return true; }
             return this._isInPatchRange(c, opt_patchRange);
           })) {
-        commentMap[path] = true;
+          commentMap[path] = true;
+        }
       }
     }
+    return commentMap;
   }
-  return commentMap;
-};
 
-/**
- * Gets all the comments and robot comments for the given change.
- *
- * @param {number=} opt_patchNum
- * @return {!Object}
- */
-ChangeComments.prototype.getAllPublishedComments = function(opt_patchNum) {
-  return this.getAllComments(false, opt_patchNum);
-};
+  /**
+   * Gets all the comments and robot comments for the given change.
+   *
+   * @param {number=} opt_patchNum
+   * @return {!Object}
+   */
+  getAllPublishedComments(opt_patchNum) {
+    return this.getAllComments(false, opt_patchNum);
+  }
 
-/**
- * Gets all the comments for a particular thread group. Used for refreshing
- * comments after the thread group has already been built.
- *
- * @param {string} rootId
- * @return {!Array} an array of comments
- */
-ChangeComments.prototype.getCommentsForThread = function(rootId) {
-  const allThreads = this.getAllThreadsForChange();
-  const threadMatch = allThreads.find(t => t.rootId === rootId);
+  /**
+   * Gets all the comments for a particular thread group. Used for refreshing
+   * comments after the thread group has already been built.
+   *
+   * @param {string} rootId
+   * @return {!Array} an array of comments
+   */
+  getCommentsForThread(rootId) {
+    const allThreads = this.getAllThreadsForChange();
+    const threadMatch = allThreads.find(t => t.rootId === rootId);
 
-  // In the event that a single draft comment was removed by the thread-list
-  // and the diff view is updating comments, there will no longer be a thread
-  // found.  In this case, return null.
-  return threadMatch ? threadMatch.comments : null;
-};
+    // In the event that a single draft comment was removed by the thread-list
+    // and the diff view is updating comments, there will no longer be a thread
+    // found.  In this case, return null.
+    return threadMatch ? threadMatch.comments : null;
+  }
 
-/**
- * Filters an array of comments by line and side
- *
- * @param {!Array} comments
- * @param {boolean} parentOnly whether the only comments returned should have
- *   the side attribute set to PARENT
- * @param {string} commentSide whether the comment was left on the left or the
- *   right side regardless or unified or side-by-side
- * @param {number=} opt_line line number, can be undefined if file comment
- * @return {!Array} an array of comments
- */
-ChangeComments.prototype._filterCommentsBySideAndLine = function(comments,
-    parentOnly, commentSide, opt_line) {
-  return comments.filter(c => {
+  /**
+   * Filters an array of comments by line and side
+   *
+   * @param {!Array} comments
+   * @param {boolean} parentOnly whether the only comments returned should have
+   *   the side attribute set to PARENT
+   * @param {string} commentSide whether the comment was left on the left or the
+   *   right side regardless or unified or side-by-side
+   * @param {number=} opt_line line number, can be undefined if file comment
+   * @return {!Array} an array of comments
+   */
+  _filterCommentsBySideAndLine(comments,
+      parentOnly, commentSide, opt_line) {
+    return comments.filter(c => {
     // if parentOnly, only match comments with PARENT for the side.
-    let sideMatch = parentOnly ? c.side === PARENT : c.side !== PARENT;
-    if (parentOnly) {
-      sideMatch = sideMatch && c.side === PARENT;
-    }
-    return sideMatch && c.line === opt_line;
-  }).map(c => {
-    c.__commentSide = commentSide;
-    return c;
-  });
-};
+      let sideMatch = parentOnly ? c.side === PARENT : c.side !== PARENT;
+      if (parentOnly) {
+        sideMatch = sideMatch && c.side === PARENT;
+      }
+      return sideMatch && c.line === opt_line;
+    }).map(c => {
+      c.__commentSide = commentSide;
+      return c;
+    });
+  }
 
-/**
- * Gets all the comments and robot comments for the given change.
- *
- * @param {boolean=} opt_includeDrafts
- * @param {number=} opt_patchNum
- * @return {!Object}
- */
-ChangeComments.prototype.getAllComments = function(opt_includeDrafts,
-    opt_patchNum) {
-  const paths = this.getPaths();
-  const publishedComments = {};
-  for (const path of Object.keys(paths)) {
-    let commentsToAdd = this.getAllCommentsForPath(path, opt_patchNum);
+  /**
+   * Gets all the comments and robot comments for the given change.
+   *
+   * @param {boolean=} opt_includeDrafts
+   * @param {number=} opt_patchNum
+   * @return {!Object}
+   */
+  getAllComments(opt_includeDrafts,
+      opt_patchNum) {
+    const paths = this.getPaths();
+    const publishedComments = {};
+    for (const path of Object.keys(paths)) {
+      let commentsToAdd = this.getAllCommentsForPath(path, opt_patchNum);
+      if (opt_includeDrafts) {
+        const drafts = this.getAllDraftsForPath(path, opt_patchNum)
+            .map(d => Object.assign({__draft: true}, d));
+        commentsToAdd = commentsToAdd.concat(drafts);
+      }
+      publishedComments[path] = commentsToAdd;
+    }
+    return publishedComments;
+  }
+
+  /**
+   * Gets all the comments and robot comments for the given change.
+   *
+   * @param {number=} opt_patchNum
+   * @return {!Object}
+   */
+  getAllDrafts(opt_patchNum) {
+    const paths = this.getPaths();
+    const drafts = {};
+    for (const path of Object.keys(paths)) {
+      drafts[path] = this.getAllDraftsForPath(path, opt_patchNum);
+    }
+    return drafts;
+  }
+
+  /**
+   * Get the comments (robot comments) for a path and optional patch num.
+   *
+   * @param {!string} path
+   * @param {number=} opt_patchNum
+   * @param {boolean=} opt_includeDrafts
+   * @return {!Array}
+   */
+  getAllCommentsForPath(path,
+      opt_patchNum, opt_includeDrafts) {
+    const comments = this._comments[path] || [];
+    const robotComments = this._robotComments[path] || [];
+    let allComments = comments.concat(robotComments);
     if (opt_includeDrafts) {
-      const drafts = this.getAllDraftsForPath(path, opt_patchNum)
+      const drafts = this.getAllDraftsForPath(path)
           .map(d => Object.assign({__draft: true}, d));
-      commentsToAdd = commentsToAdd.concat(drafts);
+      allComments = allComments.concat(drafts);
     }
-    publishedComments[path] = commentsToAdd;
-  }
-  return publishedComments;
-};
-
-/**
- * Gets all the comments and robot comments for the given change.
- *
- * @param {number=} opt_patchNum
- * @return {!Object}
- */
-ChangeComments.prototype.getAllDrafts = function(opt_patchNum) {
-  const paths = this.getPaths();
-  const drafts = {};
-  for (const path of Object.keys(paths)) {
-    drafts[path] = this.getAllDraftsForPath(path, opt_patchNum);
-  }
-  return drafts;
-};
-
-/**
- * Get the comments (robot comments) for a path and optional patch num.
- *
- * @param {!string} path
- * @param {number=} opt_patchNum
- * @param {boolean=} opt_includeDrafts
- * @return {!Array}
- */
-ChangeComments.prototype.getAllCommentsForPath = function(path,
-    opt_patchNum, opt_includeDrafts) {
-  const comments = this._comments[path] || [];
-  const robotComments = this._robotComments[path] || [];
-  let allComments = comments.concat(robotComments);
-  if (opt_includeDrafts) {
-    const drafts = this.getAllDraftsForPath(path)
-        .map(d => Object.assign({__draft: true}, d));
-    allComments = allComments.concat(drafts);
-  }
-  if (!opt_patchNum) { return allComments; }
-  return (allComments || []).filter(c =>
-    this._patchNumEquals(c.patch_set, opt_patchNum)
-  );
-};
-
-/**
- * Get the drafts for a path and optional patch num.
- *
- * @param {!string} path
- * @param {number=} opt_patchNum
- * @return {!Array}
- */
-ChangeComments.prototype.getAllDraftsForPath = function(path,
-    opt_patchNum) {
-  const comments = this._drafts[path] || [];
-  if (!opt_patchNum) { return comments; }
-  return (comments || []).filter(c =>
-    this._patchNumEquals(c.patch_set, opt_patchNum)
-  );
-};
-
-/**
- * Get the comments (with drafts and robot comments) for a path and
- * patch-range. Returns an object with left and right properties mapping to
- * arrays of comments in on either side of the patch range for that path.
- *
- * @param {!string} path
- * @param {!Gerrit.PatchRange} patchRange The patch-range object containing patchNum
- *     and basePatchNum properties to represent the range.
- * @param {Object=} opt_projectConfig Optional project config object to
- *     include in the meta sub-object.
- * @return {!Gerrit.CommentsBySide}
- */
-ChangeComments.prototype.getCommentsBySideForPath = function(path,
-    patchRange, opt_projectConfig) {
-  let comments = [];
-  let drafts = [];
-  let robotComments = [];
-  if (this.comments && this.comments[path]) {
-    comments = this.comments[path];
-  }
-  if (this.drafts && this.drafts[path]) {
-    drafts = this.drafts[path];
-  }
-  if (this.robotComments && this.robotComments[path]) {
-    robotComments = this.robotComments[path];
+    if (!opt_patchNum) { return allComments; }
+    return (allComments || []).filter(c =>
+      this._patchNumEquals(c.patch_set, opt_patchNum)
+    );
   }
 
-  drafts.forEach(d => { d.__draft = true; });
+  /**
+   * Get the comments (robot comments) for a file.
+   *
+   * // TODO(taoalpha): maybe merge in *ForPath
+   *
+   * @param {!{path: string, oldPath?: string, patchNum?: number}} file
+   * @param {boolean=} opt_includeDrafts
+   * @return {!Array}
+   */
+  getAllCommentsForFile(file, opt_includeDrafts) {
+    let allComments = this.getAllCommentsForPath(
+        file.path, file.patchNum, opt_includeDrafts
+    );
 
-  const all = comments.concat(drafts).concat(robotComments);
-
-  const baseComments = all.filter(c =>
-    this._isInBaseOfPatchRange(c, patchRange));
-  const revisionComments = all.filter(c =>
-    this._isInRevisionOfPatchRange(c, patchRange));
-
-  return {
-    meta: {
-      changeNum: this._changeNum,
-      path,
-      patchRange,
-      projectConfig: opt_projectConfig,
-    },
-    left: baseComments,
-    right: revisionComments,
-  };
-};
-
-/**
- * @param {!Object} comments Object keyed by file, with a value of an array
- *   of comments left on that file.
- * @return {!Array} A flattened list of all comments, where each comment
- *   also includes the file that it was left on, which was the key of the
- *   originall object.
- */
-ChangeComments.prototype._commentObjToArrayWithFile = function(comments) {
-  let commentArr = [];
-  for (const file of Object.keys(comments)) {
-    const commentsForFile = [];
-    for (const comment of comments[file]) {
-      commentsForFile.push(Object.assign({__path: file}, comment));
+    if (file.oldPath) {
+      allComments = allComments.concat(
+          this.getAllCommentsForPath(
+              file.oldPath, file.patchNum, opt_includeDrafts
+          )
+      );
     }
-    commentArr = commentArr.concat(commentsForFile);
-  }
-  return commentArr;
-};
 
-ChangeComments.prototype._commentObjToArray = function(comments) {
-  let commentArr = [];
-  for (const file of Object.keys(comments)) {
-    commentArr = commentArr.concat(comments[file]);
-  }
-  return commentArr;
-};
-
-/**
- * Computes a string counting the number of commens in a given file and path.
- *
- * @param {number} patchNum
- * @param {string=} opt_path
- * @return {number}
- */
-ChangeComments.prototype.computeCommentCount = function(patchNum, opt_path) {
-  if (opt_path) {
-    return this.getAllCommentsForPath(opt_path, patchNum).length;
-  }
-  const allComments = this.getAllPublishedComments(patchNum);
-  return this._commentObjToArray(allComments).length;
-};
-
-/**
- * Computes a string counting the number of draft comments in the entire
- * change, optionally filtered by path and/or patchNum.
- *
- * @param {number=} opt_patchNum
- * @param {string=} opt_path
- * @return {number}
- */
-ChangeComments.prototype.computeDraftCount = function(opt_patchNum,
-    opt_path) {
-  if (opt_path) {
-    return this.getAllDraftsForPath(opt_path, opt_patchNum).length;
-  }
-  const allDrafts = this.getAllDrafts(opt_patchNum);
-  return this._commentObjToArray(allDrafts).length;
-};
-
-/**
- * Computes a number of unresolved comment threads in a given file and path.
- *
- * @param {number} patchNum
- * @param {string=} opt_path
- * @return {number}
- */
-ChangeComments.prototype.computeUnresolvedNum = function(patchNum,
-    opt_path) {
-  let comments = [];
-  let drafts = [];
-
-  if (opt_path) {
-    comments = this.getAllCommentsForPath(opt_path, patchNum);
-    drafts = this.getAllDraftsForPath(opt_path, patchNum);
-  } else {
-    comments = this._commentObjToArray(
-        this.getAllPublishedComments(patchNum));
+    return allComments;
   }
 
-  comments = comments.concat(drafts);
+  /**
+   * Get the drafts for a path and optional patch num.
+   *
+   * @param {!string} path
+   * @param {number=} opt_patchNum
+   * @return {!Array}
+   */
+  getAllDraftsForPath(path,
+      opt_patchNum) {
+    const comments = this._drafts[path] || [];
+    if (!opt_patchNum) { return comments; }
+    return (comments || []).filter(c =>
+      this._patchNumEquals(c.patch_set, opt_patchNum)
+    );
+  }
 
-  const threads = this.getCommentThreads(this._sortComments(comments));
+  /**
+   * Get the drafts for a file.
+   *
+   * // TODO(taoalpha): maybe merge in *ForPath
+   *
+   * @param {!{path: string, oldPath?: string, patchNum?: number}} file
+   * @return {!Array}
+   */
+  getAllDraftsForFile(file) {
+    let allDrafts = this.getAllDraftsForPath(file.path, file.patchNum);
+    if (file.oldPath) {
+      allDrafts = allDrafts.concat(
+          this.getAllDraftsForPath(file.oldPath, file.patchNum)
+      );
+    }
+    return allDrafts;
+  }
 
-  const unresolvedThreads = threads
-      .filter(thread =>
-        thread.comments.length &&
+  /**
+   * Get the comments (with drafts and robot comments) for a path and
+   * patch-range. Returns an object with left and right properties mapping to
+   * arrays of comments in on either side of the patch range for that path.
+   *
+   * @param {!string} path
+   * @param {!Gerrit.PatchRange} patchRange The patch-range object containing patchNum
+   *     and basePatchNum properties to represent the range.
+   * @param {Object=} opt_projectConfig Optional project config object to
+   *     include in the meta sub-object.
+   * @return {!Gerrit.CommentsBySide}
+   */
+  getCommentsBySideForPath(path,
+      patchRange, opt_projectConfig) {
+    let comments = [];
+    let drafts = [];
+    let robotComments = [];
+    if (this.comments && this.comments[path]) {
+      comments = this.comments[path];
+    }
+    if (this.drafts && this.drafts[path]) {
+      drafts = this.drafts[path];
+    }
+    if (this.robotComments && this.robotComments[path]) {
+      robotComments = this.robotComments[path];
+    }
+
+    drafts.forEach(d => { d.__draft = true; });
+
+    const all = comments.concat(drafts).concat(robotComments);
+
+    const baseComments = all.filter(c =>
+      this._isInBaseOfPatchRange(c, patchRange));
+    const revisionComments = all.filter(c =>
+      this._isInRevisionOfPatchRange(c, patchRange));
+
+    return {
+      meta: {
+        changeNum: this._changeNum,
+        path,
+        patchRange,
+        projectConfig: opt_projectConfig,
+      },
+      left: baseComments,
+      right: revisionComments,
+    };
+  }
+
+  /**
+   * Get the comments (with drafts and robot comments) for a file and
+   * patch-range. Returns an object with left and right properties mapping to
+   * arrays of comments in on either side of the patch range for that path.
+   *
+   * // TODO(taoalpha): maybe merge *ForPath so find all comments in one pass
+   *
+   * @param {!{path: string, oldPath?: string, patchNum?: number}} file
+   * @param {!Gerrit.PatchRange} patchRange The patch-range object containing patchNum
+   *     and basePatchNum properties to represent the range.
+   * @param {Object=} opt_projectConfig Optional project config object to
+   *     include in the meta sub-object.
+   * @return {!Gerrit.CommentsBySide}
+   */
+  getCommentsBySideForFile(file, patchRange, opt_projectConfig) {
+    const comments = this.getCommentsBySideForPath(
+        file.path, patchRange, opt_projectConfig
+    );
+    if (file.oldPath) {
+      const commentsForOldPath = this.getCommentsBySideForPath(
+          file.oldPath, patchRange, opt_projectConfig
+      );
+      // merge in the left and right
+      comments.left = comments.left.concat(commentsForOldPath.left);
+      comments.right = comments.right.concat(commentsForOldPath.right);
+    }
+  }
+
+  /**
+   * @param {!Object} comments Object keyed by file, with a value of an array
+   *   of comments left on that file.
+   * @return {!Array} A flattened list of all comments, where each comment
+   *   also includes the file that it was left on, which was the key of the
+   *   originall object.
+   */
+  _commentObjToArrayWithFile(comments) {
+    let commentArr = [];
+    for (const file of Object.keys(comments)) {
+      const commentsForFile = [];
+      for (const comment of comments[file]) {
+        commentsForFile.push(Object.assign({__path: file}, comment));
+      }
+      commentArr = commentArr.concat(commentsForFile);
+    }
+    return commentArr;
+  }
+
+  _commentObjToArray(comments) {
+    let commentArr = [];
+    for (const file of Object.keys(comments)) {
+      commentArr = commentArr.concat(comments[file]);
+    }
+    return commentArr;
+  }
+
+  /**
+   * Computes a string counting the number of commens in a given file.
+   *
+   * @param {{path: string, oldPath?: string, patchNum?: number}} file
+   * @return {number}
+   */
+  computeCommentCount(file) {
+    if (file.path) {
+      return this.getAllCommentsForFile(file).length;
+    }
+    const allComments = this.getAllPublishedComments(file.patchNum);
+    return this._commentObjToArray(allComments).length;
+  }
+
+  /**
+   * Computes a string counting the number of draft comments in the entire
+   * change, optionally filtered by path and/or patchNum.
+   *
+   * @param {?{path: string, oldPath?: string, patchNum?: number}} file
+   * @return {number}
+   */
+  computeDraftCount(file) {
+    if (file && file.path) {
+      return this.getAllDraftsForFile(file).length;
+    }
+    const allDrafts = this.getAllDrafts(file && file.patchNum);
+    return this._commentObjToArray(allDrafts).length;
+  }
+
+  /**
+   * Computes a number of unresolved comment threads in a given file and path.
+   *
+   * @param {{path: string, oldPath?: string, patchNum?: number}} file
+   * @return {number}
+   */
+  computeUnresolvedNum(file) {
+    let comments = [];
+    let drafts = [];
+
+    if (file.path) {
+      comments = this.getAllCommentsForFile(file);
+      drafts = this.getAllDraftsForFile(file);
+    } else {
+      comments = this._commentObjToArray(
+          this.getAllPublishedComments(file.patchNum));
+    }
+
+    comments = comments.concat(drafts);
+
+    const threads = this.getCommentThreads(this._sortComments(comments));
+
+    const unresolvedThreads = threads
+        .filter(thread =>
+          thread.comments.length &&
         thread.comments[thread.comments.length - 1].unresolved);
 
-  return unresolvedThreads.length;
-};
+    return unresolvedThreads.length;
+  }
 
-ChangeComments.prototype.getAllThreadsForChange = function() {
-  const comments = this._commentObjToArrayWithFile(this.getAllComments(true));
-  const sortedComments = this._sortComments(comments);
-  return this.getCommentThreads(sortedComments);
-};
+  getAllThreadsForChange() {
+    const comments = this._commentObjToArrayWithFile(this.getAllComments(true));
+    const sortedComments = this._sortComments(comments);
+    return this.getCommentThreads(sortedComments);
+  }
 
-ChangeComments.prototype._sortComments = function(comments) {
-  return comments.slice(0)
-      .sort(
-          (c1, c2) => util.parseDate(c1.updated) - util.parseDate(c2.updated)
-      );
-};
+  _sortComments(comments) {
+    return comments.slice(0)
+        .sort(
+            (c1, c2) => util.parseDate(c1.updated) - util.parseDate(c2.updated)
+        );
+  }
 
-/**
- * Computes all of the comments in thread format.
- *
- * @param {!Array} comments sorted by updated timestamp.
- * @return {!Array}
- */
-ChangeComments.prototype.getCommentThreads = function(comments) {
-  const threads = [];
-  const idThreadMap = {};
-  for (const comment of comments) {
+  /**
+   * Computes all of the comments in thread format.
+   *
+   * @param {!Array} comments sorted by updated timestamp.
+   * @return {!Array}
+   */
+  getCommentThreads(comments) {
+    const threads = [];
+    const idThreadMap = {};
+    for (const comment of comments) {
     // If the comment is in reply to another comment, find that comment's
     // thread and append to it.
-    if (comment.in_reply_to) {
-      const thread = idThreadMap[comment.in_reply_to];
-      if (thread) {
-        thread.comments.push(comment);
-        idThreadMap[comment.id] = thread;
-        continue;
+      if (comment.in_reply_to) {
+        const thread = idThreadMap[comment.in_reply_to];
+        if (thread) {
+          thread.comments.push(comment);
+          idThreadMap[comment.id] = thread;
+          continue;
+        }
       }
-    }
 
-    // Otherwise, this comment starts its own thread.
-    const newThread = {
-      comments: [comment],
-      patchNum: comment.patch_set,
-      path: comment.__path,
-      line: comment.line,
-      rootId: comment.id,
-    };
-    if (comment.side) {
-      newThread.commentSide = comment.side;
+      // Otherwise, this comment starts its own thread.
+      const newThread = {
+        comments: [comment],
+        patchNum: comment.patch_set,
+        path: comment.__path,
+        line: comment.line,
+        rootId: comment.id,
+      };
+      if (comment.side) {
+        newThread.commentSide = comment.side;
+      }
+      threads.push(newThread);
+      idThreadMap[comment.id] = newThread;
     }
-    threads.push(newThread);
-    idThreadMap[comment.id] = newThread;
+    return threads;
   }
-  return threads;
-};
 
-/**
- * Whether the given comment should be included in the base side of the
- * given patch range.
- *
- * @param {!Object} comment
- * @param {!Gerrit.PatchRange} range
- * @return {boolean}
- */
-ChangeComments.prototype._isInBaseOfPatchRange = function(comment, range) {
+  /**
+   * Whether the given comment should be included in the base side of the
+   * given patch range.
+   *
+   * @param {!Object} comment
+   * @param {!Gerrit.PatchRange} range
+   * @return {boolean}
+   */
+  _isInBaseOfPatchRange(comment, range) {
   // If the base of the patch range is a parent of a merge, and the comment
   // appears on a specific parent then only show the comment if the parent
   // index of the comment matches that of the range.
-  if (comment.parent && comment.side === PARENT) {
-    return this._isMergeParent(range.basePatchNum) &&
+    if (comment.parent && comment.side === PARENT) {
+      return this._isMergeParent(range.basePatchNum) &&
         comment.parent === this._getParentIndex(range.basePatchNum);
-  }
+    }
 
-  // If the base of the range is the parent of the patch:
-  if (range.basePatchNum === PARENT &&
+    // If the base of the range is the parent of the patch:
+    if (range.basePatchNum === PARENT &&
       comment.side === PARENT &&
       this._patchNumEquals(comment.patch_set, range.patchNum)) {
-    return true;
-  }
-  // If the base of the range is not the parent of the patch:
-  if (range.basePatchNum !== PARENT &&
+      return true;
+    }
+    // If the base of the range is not the parent of the patch:
+    if (range.basePatchNum !== PARENT &&
       comment.side !== PARENT &&
       this._patchNumEquals(comment.patch_set, range.basePatchNum)) {
-    return true;
+      return true;
+    }
+    return false;
   }
-  return false;
-};
 
-/**
- * Whether the given comment should be included in the revision side of the
- * given patch range.
- *
- * @param {!Object} comment
- * @param {!Gerrit.PatchRange} range
- * @return {boolean}
- */
-ChangeComments.prototype._isInRevisionOfPatchRange = function(comment,
-    range) {
-  return comment.side !== PARENT &&
+  /**
+   * Whether the given comment should be included in the revision side of the
+   * given patch range.
+   *
+   * @param {!Object} comment
+   * @param {!Gerrit.PatchRange} range
+   * @return {boolean}
+   */
+  _isInRevisionOfPatchRange(comment,
+      range) {
+    return comment.side !== PARENT &&
       this._patchNumEquals(comment.patch_set, range.patchNum);
-};
+  }
 
-/**
- * Whether the given comment should be included in the given patch range.
- *
- * @param {!Object} comment
- * @param {!Gerrit.PatchRange} range
- * @return {boolean|undefined}
- */
-ChangeComments.prototype._isInPatchRange = function(comment, range) {
-  return this._isInBaseOfPatchRange(comment, range) ||
+  /**
+   * Whether the given comment should be included in the given patch range.
+   *
+   * @param {!Object} comment
+   * @param {!Gerrit.PatchRange} range
+   * @return {boolean|undefined}
+   */
+  _isInPatchRange(comment, range) {
+    return this._isInBaseOfPatchRange(comment, range) ||
       this._isInRevisionOfPatchRange(comment, range);
-};
+  }
+}
 
 /**
  * @appliesMixin Gerrit.PatchSetMixin
diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html
index e39319a..e1539ea 100644
--- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html
@@ -23,17 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-
-<script type="module" src="./gr-comment-api.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-comment-api.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -42,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-comment-api.js';
 suite('gr-comment-api tests', () => {
@@ -395,11 +383,20 @@
 
       test('computeUnresolvedNum', () => {
         assert.equal(element._changeComments
-            .computeUnresolvedNum(2, 'file/one'), 0);
+            .computeUnresolvedNum({
+              patchNum: 2,
+              path: 'file/one',
+            }), 0);
         assert.equal(element._changeComments
-            .computeUnresolvedNum(1, 'file/one'), 0);
+            .computeUnresolvedNum({
+              patchNum: 1,
+              path: 'file/one',
+            }), 0);
         assert.equal(element._changeComments
-            .computeUnresolvedNum(2, 'file/three'), 1);
+            .computeUnresolvedNum({
+              patchNum: 2,
+              path: 'file/three',
+            }), 1);
       });
 
       test('computeUnresolvedNum w/ non-linear thread', () => {
@@ -437,20 +434,38 @@
 
       test('computeCommentCount', () => {
         assert.equal(element._changeComments
-            .computeCommentCount(2, 'file/one'), 4);
+            .computeCommentCount({
+              patchNum: 2,
+              path: 'file/one',
+            }), 4);
         assert.equal(element._changeComments
-            .computeCommentCount(1, 'file/one'), 0);
+            .computeCommentCount({
+              patchNum: 1,
+              path: 'file/one',
+            }), 0);
         assert.equal(element._changeComments
-            .computeCommentCount(2, 'file/three'), 1);
+            .computeCommentCount({
+              patchNum: 2,
+              path: 'file/three',
+            }), 1);
       });
 
       test('computeDraftCount', () => {
         assert.equal(element._changeComments
-            .computeDraftCount(2, 'file/one'), 2);
+            .computeDraftCount({
+              patchNum: 2,
+              path: 'file/one',
+            }), 2);
         assert.equal(element._changeComments
-            .computeDraftCount(1, 'file/one'), 0);
+            .computeDraftCount({
+              patchNum: 1,
+              path: 'file/one',
+            }), 0);
         assert.equal(element._changeComments
-            .computeDraftCount(2, 'file/three'), 0);
+            .computeDraftCount({
+              patchNum: 2,
+              path: 'file/three',
+            }), 0);
         assert.equal(element._changeComments
             .computeDraftCount(), 3);
       });
diff --git a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html
index 69948c3..cec75fd 100644
--- a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html
@@ -23,19 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../gr-diff/gr-diff-line.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-
-<script type="module" src="./gr-coverage-layer.js"></script>
-
-<script type="module">
-import '../gr-diff/gr-diff-line.js';
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-coverage-layer.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -45,7 +32,6 @@
 
 <script type="module">
 import '../gr-diff/gr-diff-line.js';
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-coverage-layer.js';
 suite('gr-coverage-layer', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.html
index 07edc7d..d9f8443 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.html
@@ -23,31 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../../scripts/util.js"></script>
-<script type="module" src="../gr-diff/gr-diff-line.js"></script>
-<script type="module" src="../gr-diff/gr-diff-group.js"></script>
-<script type="module" src="../gr-diff-highlight/gr-annotation.js"></script>
-<script type="module" src="./gr-diff-builder.js"></script>
-
-<script type="module" src="../../shared/gr-rest-api-interface/gr-rest-api-interface.js"></script>
-<script type="module" src="../../shared/gr-rest-api-interface/mock-diff-response_test.js"></script>
-<script type="module" src="./gr-diff-builder-element.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../../scripts/util.js';
-import '../gr-diff/gr-diff-line.js';
-import '../gr-diff/gr-diff-group.js';
-import '../gr-diff-highlight/gr-annotation.js';
-import './gr-diff-builder.js';
-import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
-import '../../shared/gr-rest-api-interface/mock-diff-response_test.js';
-import './gr-diff-builder-element.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template is="dom-template">
@@ -72,7 +47,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../../scripts/util.js';
 import '../gr-diff/gr-diff-line.js';
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.html
index 0ce9a42..88187fd 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.html
@@ -23,29 +23,8 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../../scripts/util.js"></script>
-<script type="module" src="../gr-diff/gr-diff-line.js"></script>
-<script type="module" src="../gr-diff/gr-diff-group.js"></script>
-<script type="module" src="../gr-diff-highlight/gr-annotation.js"></script>
-<script type="module" src="./gr-diff-builder.js"></script>
-<script type="module" src="./gr-diff-builder-unified.js"></script>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../../scripts/util.js';
-import '../gr-diff/gr-diff-line.js';
-import '../gr-diff/gr-diff-group.js';
-import '../gr-diff-highlight/gr-annotation.js';
-import './gr-diff-builder.js';
-import './gr-diff-builder-unified.js';
-void(0);
-</script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../../scripts/util.js';
 import '../gr-diff/gr-diff-line.js';
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
index 40507db..6d9c828 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
@@ -23,25 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../../scripts/util.js"></script>
-
-<script type="module" src="../gr-diff/gr-diff.js"></script>
-<script type="module" src="./gr-diff-cursor.js"></script>
-<script type="module" src="../../shared/gr-rest-api-interface/gr-rest-api-interface.js"></script>
-<script type="module" src="../../shared/gr-rest-api-interface/mock-diff-response_test.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../../scripts/util.js';
-import '../gr-diff/gr-diff.js';
-import './gr-diff-cursor.js';
-import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
-import '../../shared/gr-rest-api-interface/mock-diff-response_test.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -59,7 +40,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../../scripts/util.js';
 import '../gr-diff/gr-diff.js';
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html
index 6db0836..357c831 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html
@@ -23,17 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-annotation.js"></script>
-
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-annotation.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -42,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-annotation.js';
 import {sanitizeDOMValue, setSanitizeDOMValue} from '@polymer/polymer/lib/utils/settings.js';
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
index ca1e2e2..c4f7152 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-diff-highlight.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-diff-highlight.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -66,8 +56,7 @@
           </tr>
         </tbody>
 
-
-        <tbody class="section both">
+<tbody class="section both">
           <tr class="diff-row side-by-side" left-type="both" right-type="both">
             <td class="left lineNum" data-value="138"></td>
             <td class="content both"><div class="contentText">[14] Nam cum ad me in Cumanum salutandi causa uterque venisset,</div></td>
@@ -153,7 +142,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-diff-highlight.js';
 suite('gr-diff-highlight', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
index d2097d3..bb4ff3f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
@@ -23,17 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-
-<script type="module" src="./gr-diff-host.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-diff-host.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -42,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-diff-host.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.html b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.html
index 921bc74..5ffae8c 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.html
@@ -23,20 +23,7 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
 <script src="/node_modules/page/page.js"></script>
-<script type="module" src="../../../scripts/util.js"></script>
-
-<script type="module" src="./gr-diff-mode-selector.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../../scripts/util.js';
-import './gr-diff-mode-selector.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -45,7 +32,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../../scripts/util.js';
 import './gr-diff-mode-selector.js';
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html
index e62abe5..63209c1 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-diff-processor.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-diff-processor.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-diff-processor.js';
 suite('gr-diff-processor tests', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html
index ac3a87f..e9405cb 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-diff-selection.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-diff-selection.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -113,7 +103,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-diff-selection.js';
 suite('gr-diff-selection', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index cf29a03..94bc5b6 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -937,9 +937,9 @@
   }
 
   _computeCommentString(changeComments, patchNum, path, changeFileInfo) {
-    const unresolvedCount = changeComments.computeUnresolvedNum(patchNum,
-        path);
-    const commentCount = changeComments.computeCommentCount(patchNum, path);
+    const unresolvedCount = changeComments.computeUnresolvedNum({patchNum,
+      path});
+    const commentCount = changeComments.computeCommentCount({patchNum, path});
     const commentString = GrCountStringFormatter.computePluralString(
         commentCount, 'comment');
     const unresolvedString = GrCountStringFormatter.computeString(
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
index 35aa664..5dccd5e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -23,20 +23,7 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
 <script src="/node_modules/page/page.js"></script>
-<script type="module" src="../../../scripts/util.js"></script>
-
-<script type="module" src="./gr-diff-view.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../../scripts/util.js';
-import './gr-diff-view.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -51,7 +38,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../../scripts/util.js';
 import './gr-diff-view.js';
@@ -487,14 +473,14 @@
             sandbox.stub(comments, 'computeCommentCount');
         const unresolvedCountStub =
             sandbox.stub(comments, 'computeUnresolvedNum');
-        commentCountStub.withArgs(1, path).returns(0);
-        commentCountStub.withArgs(2, path).returns(1);
-        commentCountStub.withArgs(3, path).returns(2);
-        commentCountStub.withArgs(4, path).returns(0);
-        unresolvedCountStub.withArgs(1, path).returns(1);
-        unresolvedCountStub.withArgs(2, path).returns(0);
-        unresolvedCountStub.withArgs(3, path).returns(2);
-        unresolvedCountStub.withArgs(4, path).returns(0);
+        commentCountStub.withArgs({patchNum: 1, path}).returns(0);
+        commentCountStub.withArgs({patchNum: 2, path}).returns(1);
+        commentCountStub.withArgs({patchNum: 3, path}).returns(2);
+        commentCountStub.withArgs({patchNum: 4, path}).returns(0);
+        unresolvedCountStub.withArgs({patchNum: 1, path}).returns(1);
+        unresolvedCountStub.withArgs({patchNum: 2, path}).returns(0);
+        unresolvedCountStub.withArgs({patchNum: 3, path}).returns(2);
+        unresolvedCountStub.withArgs({patchNum: 4, path}).returns(0);
 
         assert.equal(element._computeCommentString(comments, 1, path, {}),
             '1 unresolved');
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group_test.html
index c461e93..cd43803 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group_test.html
@@ -22,13 +22,7 @@
 <script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-diff-line.js"></script>
-<script type="module" src="./gr-diff-group.js"></script>
-
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-diff-line.js';
 import './gr-diff-group.js';
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
index 0397767..08576a2 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
@@ -23,24 +23,7 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
 <script src="/components/web-component-tester/data/a11ySuite.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../../scripts/util.js"></script>
-
-<script type="module" src="../../shared/gr-rest-api-interface/gr-rest-api-interface.js"></script>
-<script type="module" src="../../shared/gr-rest-api-interface/mock-diff-response_test.js"></script>
-<script type="module" src="./gr-diff.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../../scripts/util.js';
-import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
-import '../../shared/gr-rest-api-interface/mock-diff-response_test.js';
-import './gr-diff.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -49,7 +32,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../../scripts/util.js';
 import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
index f2b0599..e9a64c4 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
@@ -242,11 +242,11 @@
   _computePatchSetCommentsString(changeComments, patchNum) {
     if (!changeComments) { return; }
 
-    const commentCount = changeComments.computeCommentCount(patchNum);
+    const commentCount = changeComments.computeCommentCount({patchNum});
     const commentString = GrCountStringFormatter.computePluralString(
         commentCount, 'comment');
 
-    const unresolvedCount = changeComments.computeUnresolvedNum(patchNum);
+    const unresolvedCount = changeComments.computeUnresolvedNum({patchNum});
     const unresolvedString = GrCountStringFormatter.computeString(
         unresolvedCount, 'unresolved');
 
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
index 3c07750..797e279 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
@@ -23,35 +23,15 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
 <script src="/node_modules/page/page.js"></script>
 
-<script type="module" src="../gr-comment-api/gr-comment-api.js"></script>
-<script type="module" src="../../shared/gr-rest-api-interface/mock-diff-response_test.js"></script>
-<script type="module" src="../../shared/revision-info/revision-info.js"></script>
-
-<script type="module" src="./gr-patch-range-select.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../gr-comment-api/gr-comment-api.js';
-import '../../shared/gr-rest-api-interface/mock-diff-response_test.js';
-import '../../shared/revision-info/revision-info.js';
-import './gr-patch-range-select.js';
-import '../gr-comment-api/gr-comment-api-mock_test.js';
-void(0);
-</script>
-
 <dom-module id="comment-api-mock">
   <template>
     <gr-patch-range-select id="patchRange" auto
         change-comments="[[_changeComments]]"></gr-patch-range-select>
     <gr-comment-api id="commentAPI"></gr-comment-api>
   </template>
-  <script type="module" src="../gr-comment-api/gr-comment-api-mock_test.js"></script>
-</dom-module>
+  </dom-module>
 
 <test-fixture id="basic">
   <template>
@@ -60,7 +40,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../gr-comment-api/gr-comment-api.js';
 import '../../shared/gr-rest-api-interface/mock-diff-response_test.js';
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html
index d2d97de..5414ba2 100644
--- a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html
@@ -23,19 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../gr-diff/gr-diff-line.js"></script>
-
-<script type="module" src="./gr-ranged-comment-layer.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../gr-diff/gr-diff-line.js';
-import './gr-ranged-comment-layer.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -44,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../gr-diff/gr-diff-line.js';
 import './gr-ranged-comment-layer.js';
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
index c0c711a..bc8484d 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-selection-action-box.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-selection-action-box.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -44,7 +34,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-selection-action-box.js';
 suite('gr-selection-action-box', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
index aa49f71..2385067 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
@@ -23,18 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../shared/gr-rest-api-interface/mock-diff-response_test.js"></script>
-<script type="module" src="./gr-syntax-layer.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../shared/gr-rest-api-interface/mock-diff-response_test.js';
-import './gr-syntax-layer.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -43,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../shared/gr-rest-api-interface/mock-diff-response_test.js';
 import './gr-syntax-layer.js';
diff --git a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.html b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.html
index 9c3a08d..1a238e8 100644
--- a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.html
+++ b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.html
@@ -24,16 +24,6 @@
 <script src="/node_modules/page/page.js"></script>
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-documentation-search.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-documentation-search.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -42,7 +32,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-documentation-search.js';
 let counter;
diff --git a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.html b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.html
index 043f656..30cfe39 100644
--- a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.html
@@ -22,17 +22,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-
-<script type="module" src="./gr-default-editor.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-default-editor.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +30,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-default-editor.js';
 suite('gr-default-editor tests', () => {
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html
index 034a7a7..dc019c3 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html
@@ -22,17 +22,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-
-<script type="module" src="./gr-edit-controls.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-edit-controls.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +30,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-edit-controls.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html
index d694226..46a336f 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html
@@ -22,19 +22,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-
-<script type="module" src="../gr-edit-constants.js"></script>
-<script type="module" src="./gr-edit-file-controls.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../gr-edit-constants.js';
-import './gr-edit-file-controls.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -43,7 +30,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../gr-edit-constants.js';
 import './gr-edit-file-controls.js';
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
index 8c0d491..4659065 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
@@ -22,17 +22,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-
-<script type="module" src="./gr-editor-view.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-editor-view.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +30,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-editor-view.js';
 suite('gr-editor-view tests', () => {
diff --git a/polygerrit-ui/app/elements/gr-app-element.js b/polygerrit-ui/app/elements/gr-app-element.js
index 785e8f9..70806e2 100644
--- a/polygerrit-ui/app/elements/gr-app-element.js
+++ b/polygerrit-ui/app/elements/gr-app-element.js
@@ -497,6 +497,12 @@
   }
 
   _showKeyboardShortcuts(e) {
+    // same shortcut should close the dialog if pressed again
+    // when dialog is open
+    if (this.$.keyboardShortcuts.opened) {
+      this.$.keyboardShortcuts.close();
+      return;
+    }
     if (this.shouldSuppressKeyboardShortcut(e)) { return; }
     this.$.keyboardShortcuts.open();
   }
diff --git a/polygerrit-ui/app/elements/gr-app_test.html b/polygerrit-ui/app/elements/gr-app_test.html
index 447aae4..71ebaa7 100644
--- a/polygerrit-ui/app/elements/gr-app_test.html
+++ b/polygerrit-ui/app/elements/gr-app_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../test/test-pre-setup.js"></script>
-<script type="module" src="../test/common-test-setup.js"></script>
-<script type="module" src="./gr-app.js"></script>
-
-<script type="module">
-import '../test/test-pre-setup.js';
-import '../test/common-test-setup.js';
-import './gr-app.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../test/test-pre-setup.js';
 import '../test/common-test-setup.js';
 import './gr-app.js';
 suite('gr-app tests', () => {
diff --git a/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html b/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html
index 21e46b8..de1fc86 100644
--- a/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html
@@ -23,21 +23,8 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../shared/gr-js-api-interface/gr-js-api-interface.js"></script>
-<script type="module" src="./gr-admin-api.js"></script>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../shared/gr-js-api-interface/gr-js-api-interface.js';
-import './gr-admin-api.js';
-void(0);
-</script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../shared/gr-js-api-interface/gr-js-api-interface.js';
 import './gr-admin-api.js';
diff --git a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.html b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.html
index cfb51f0..fd2fd5b 100644
--- a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.html
@@ -23,20 +23,9 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-attribute-helper.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-attribute-helper.js';
-void(0);
-</script>
 
 <dom-element id="some-element">
   <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-attribute-helper.js';
 import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
@@ -60,7 +49,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-attribute-helper.js';
 suite('gr-attribute-helper tests', () => {
diff --git a/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.html b/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.html
index 8e23b0d..70d7a7d 100644
--- a/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.html
@@ -23,18 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-dom-hooks.js"></script>
-<script type="module" src="../../shared/gr-js-api-interface/gr-js-api-interface.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-dom-hooks.js';
-import '../../shared/gr-js-api-interface/gr-js-api-interface.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -43,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-dom-hooks.js';
 import '../../shared/gr-js-api-interface/gr-js-api-interface.js';
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html
index fcac174..d167446 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html
@@ -23,18 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-endpoint-decorator.js"></script>
-<script type="module" src="../gr-endpoint-param/gr-endpoint-param.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-endpoint-decorator.js';
-import '../gr-endpoint-param/gr-endpoint-param.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -53,7 +41,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-endpoint-decorator.js';
 import '../gr-endpoint-param/gr-endpoint-param.js';
diff --git a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html
index 8eebe33..bd61a96 100644
--- a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html
@@ -23,20 +23,9 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-event-helper.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-event-helper.js';
-void(0);
-</script>
 
 <dom-element id="some-element">
   <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-event-helper.js';
 import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
@@ -65,7 +54,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-event-helper.js';
 import {addListener} from '@polymer/polymer/lib/utils/gestures.js';
diff --git a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.html b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.html
index 7b76f63..a6c8111 100644
--- a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.html
@@ -23,10 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-external-style.js"></script>
-
 <test-fixture id="basic">
   <template>
     <gr-external-style name="foo"></gr-external-style>
@@ -34,7 +30,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-external-style.js';
 suite('gr-external-style integration tests', () => {
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
index 894c8b4..008b274 100644
--- a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-plugin-host.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-plugin-host.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-plugin-host.js';
 suite('gr-plugin-host tests', () => {
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.html b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.html
index 00bbd52..2e65365 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-plugin-popup.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-plugin-popup.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-plugin-popup.js';
 suite('gr-plugin-popup tests', () => {
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.html b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.html
index aeef29a..661058e 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.html
@@ -23,18 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-popup-interface.js"></script>
-<script type="module" src="../../shared/gr-js-api-interface/gr-js-api-interface.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-popup-interface.js';
-import '../../shared/gr-js-api-interface/gr-js-api-interface.js';
-void(0);
-</script>
 
 <test-fixture id="container">
   <template>
@@ -47,7 +35,6 @@
     <div id="barfoo">some test module</div>
   </template>
   <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-popup-interface.js';
 import '../../shared/gr-js-api-interface/gr-js-api-interface.js';
@@ -57,7 +44,6 @@
 </dom-module>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-popup-interface.js';
 import '../../shared/gr-js-api-interface/gr-js-api-interface.js';
diff --git a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html
index c177715..0eed6d9 100644
--- a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html
@@ -23,18 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../gr-endpoint-decorator/gr-endpoint-decorator.js"></script>
-<script type="module" src="./gr-repo-api.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../gr-endpoint-decorator/gr-endpoint-decorator.js';
-import './gr-repo-api.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -44,7 +32,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../gr-endpoint-decorator/gr-endpoint-decorator.js';
 import './gr-repo-api.js';
diff --git a/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html b/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html
index b0dbc3c..e4c643a 100644
--- a/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html
@@ -23,18 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../gr-endpoint-decorator/gr-endpoint-decorator.js"></script>
-<script type="module" src="./gr-settings-api.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../gr-endpoint-decorator/gr-endpoint-decorator.js';
-import './gr-settings-api.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -46,7 +34,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../gr-endpoint-decorator/gr-endpoint-decorator.js';
 import './gr-settings-api.js';
diff --git a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html
index a69db8d..4901a37 100644
--- a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html
@@ -23,25 +23,12 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../shared/gr-js-api-interface/gr-js-api-interface.js"></script>
-<script type="module" src="./gr-styles-api.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../shared/gr-js-api-interface/gr-js-api-interface.js';
-import './gr-styles-api.js';
-void(0);
-</script>
 
 <dom-module id="gr-style-test-element">
   <template>
     <div id="wrapper"></div>
   </template>
   <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../shared/gr-js-api-interface/gr-js-api-interface.js';
 import './gr-styles-api.js';
@@ -51,7 +38,6 @@
 </dom-module>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../shared/gr-js-api-interface/gr-js-api-interface.js';
 import './gr-styles-api.js';
diff --git a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html
index 6428f90..83ea1bd 100644
--- a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html
@@ -23,18 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../gr-endpoint-decorator/gr-endpoint-decorator.js"></script>
-<script type="module" src="./gr-theme-api.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../gr-endpoint-decorator/gr-endpoint-decorator.js';
-import './gr-theme-api.js';
-void(0);
-</script>
 
 <test-fixture id="header-title">
   <template>
@@ -45,7 +33,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../gr-endpoint-decorator/gr-endpoint-decorator.js';
 import './gr-theme-api.js';
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js
index c425318..0c4b706 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js
@@ -63,11 +63,12 @@
         type: Boolean,
         notify: true,
         computed: '_computeHasUnsavedChanges(_hasNameChange, ' +
-          '_hasUsernameChange, _hasStatusChange)',
+          '_hasUsernameChange, _hasStatusChange, _hasDisplayNameChange)',
       },
 
       _hasNameChange: Boolean,
       _hasUsernameChange: Boolean,
+      _hasDisplayNameChange: Boolean,
       _hasStatusChange: Boolean,
       _loading: {
         type: Boolean,
@@ -95,6 +96,7 @@
     return [
       '_nameChanged(_account.name)',
       '_statusChanged(_account.status)',
+      '_displayNameChanged(_account.display_name)',
     ];
   }
 
@@ -110,6 +112,7 @@
     promises.push(this.$.restAPI.getAccount().then(account => {
       this._hasNameChange = false;
       this._hasUsernameChange = false;
+      this._hasDisplayNameChange = false;
       this._hasStatusChange = false;
       // Provide predefined value for username to trigger computation of
       // username mutability.
@@ -136,10 +139,12 @@
     // Set only the fields that have changed.
     // Must be done in sequence to avoid race conditions (@see Issue 5721)
     return this._maybeSetName()
-        .then(this._maybeSetUsername.bind(this))
-        .then(this._maybeSetStatus.bind(this))
+        .then(() => this._maybeSetUsername())
+        .then(() => this._maybeSetDisplayName())
+        .then(() => this._maybeSetStatus())
         .then(() => {
           this._hasNameChange = false;
+          this._hasDisplayNameChange = false;
           this._hasStatusChange = false;
           this._saving = false;
           this.fire('account-detail-update');
@@ -158,14 +163,22 @@
       Promise.resolve();
   }
 
+  _maybeSetDisplayName() {
+    return this._hasDisplayNameChange ?
+      this.$.restAPI.setAccountDisplayName(this._account.display_name) :
+      Promise.resolve();
+  }
+
   _maybeSetStatus() {
     return this._hasStatusChange ?
       this.$.restAPI.setAccountStatus(this._account.status) :
       Promise.resolve();
   }
 
-  _computeHasUnsavedChanges(nameChanged, usernameChanged, statusChanged) {
-    return nameChanged || usernameChanged || statusChanged;
+  _computeHasUnsavedChanges(nameChanged, usernameChanged, statusChanged,
+      displayNameChanged) {
+    return nameChanged || usernameChanged || statusChanged
+        || displayNameChanged;
   }
 
   _computeUsernameMutable(config, username) {
@@ -191,6 +204,11 @@
     this._hasStatusChange = true;
   }
 
+  _displayNameChanged() {
+    if (this._loading) { return; }
+    this._hasDisplayNameChange = true;
+  }
+
   _usernameChanged() {
     if (this._loading || !this._account) { return; }
     this._hasUsernameChange =
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_html.js b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_html.js
index 6e37c25..0f5ddc8 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_html.js
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_html.js
@@ -79,6 +79,14 @@
         </span>
       </section>
       <section>
+        <span class="title">Display name (defaults to "Full name")</span>
+        <span class="value">
+          <iron-input on-keydown="_handleKeydown" bind-value="{{_account.display_name}}">
+            <input is="iron-input" id="displayNameInput" disabled="[[_saving]]" on-keydown="_handleKeydown" bind-value="{{_account.display_name}}">
+          </iron-input>
+        </span>
+      </section>
+      <section>
         <span class="title">Status (e.g. "Vacation")</span>
         <span class="value">
           <iron-input on-keydown="_handleKeydown" bind-value="{{_account.status}}">
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html
index 640ae37..14bc5de 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-account-info.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-account-info.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-account-info.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.html b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.html
index 4aa917b..0a008a4 100644
--- a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-agreements-list.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-agreements-list.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-agreements-list.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html
index 1d60384..287ac3b 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-change-table-editor.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-change-table-editor.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-change-table-editor.js';
 suite('gr-change-table-editor tests', () => {
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html
index 50f82e2..a1874db 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-cla-view.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-cla-view.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-cla-view.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html
index b73b14f..47e295c 100644
--- a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-edit-preferences.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-edit-preferences.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-edit-preferences.js';
 suite('gr-edit-preferences tests', () => {
diff --git a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_test.html b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_test.html
index 196f8a9..dbdd2d6 100644
--- a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-email-editor.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-email-editor.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-email-editor.js';
 suite('gr-email-editor tests', () => {
diff --git a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.html b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.html
index 5c95222..c9daa89 100644
--- a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-gpg-editor.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-gpg-editor.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-gpg-editor.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.html b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.html
index 205b413..52a3edc 100644
--- a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-group-list.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-group-list.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-group-list.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
index 57c8622..b31fa50 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-http-password.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-http-password.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-http-password.js';
 suite('gr-http-password tests', () => {
diff --git a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.html b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.html
index acf4507..5c40b47 100644
--- a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-identities.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-identities.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-identities.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html
index 930255c..b3e9a15 100644
--- a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-menu-editor.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-menu-editor.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-menu-editor.js';
 import {flush as flush$0} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html
index 9aaaaa7..501dae5 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-registration-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-registration-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -47,7 +37,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-registration-dialog.js';
 suite('gr-registration-dialog tests', () => {
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
index a014c96..b6e59ae 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-settings-view.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-settings-view.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -47,7 +37,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-settings-view.js';
 import {flush, dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.html b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.html
index 4312d9a..ed1a7ca 100644
--- a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-ssh-editor.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-ssh-editor.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-ssh-editor.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html
index a02afcb..2bf0ca7 100644
--- a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-watched-projects-editor.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-watched-projects-editor.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-watched-projects-editor.js';
 suite('gr-watched-projects-editor tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js
index 4ac540d..22fd1c20 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js
@@ -56,7 +56,7 @@
   static get properties() {
     return {
       account: Object,
-      additionalText: String,
+      voteableText: String,
       disabled: {
         type: Boolean,
         value: false,
diff --git a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip_html.js b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip_html.js
index 7f219e5..14bbd57 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip_html.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip_html.js
@@ -81,7 +81,7 @@
       }
     </style>
     <div class\$="container [[_getBackgroundClass(transparentBackground)]]">
-      <gr-account-link account="[[account]]" additional-text="[[additionalText]]">
+      <gr-account-link account="[[account]]" voteable-text="[[voteableText]]">
       </gr-account-link>
       <gr-button id="remove" link="" hidden\$="[[!removable]]" hidden="" tabindex="-1" aria-label="Remove" class\$="remove [[_getBackgroundClass(transparentBackground)]]" on-click="_handleRemoveTap">
         <iron-icon icon="gr-icons:close"></iron-icon>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html
index 7ef07d5..6e8b493 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html
@@ -23,19 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../../scripts/util.js"></script>
-
-<script type="module" src="./gr-account-entry.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../../scripts/util.js';
-import './gr-account-entry.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -44,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../../scripts/util.js';
 import './gr-account-entry.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
index ba65e03..d279563 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
@@ -14,13 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import '../../../behaviors/gr-display-name-behavior/gr-display-name-behavior.js';
-
-import '../../../behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js';
 import '../../../scripts/bundled-polymer.js';
+
+import '@polymer/iron-icon/iron-icon.js';
+import '../../../behaviors/gr-display-name-behavior/gr-display-name-behavior.js';
+import '../../../behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js';
 import '../../../styles/shared-styles.js';
 import '../gr-avatar/gr-avatar.js';
-import '../gr-limited-text/gr-limited-text.js';
+import '../gr-hovercard-account/gr-hovercard-account.js';
 import '../gr-rest-api-interface/gr-rest-api-interface.js';
 import '../../../scripts/util.js';
 import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
@@ -31,12 +32,10 @@
 
 /**
  * @appliesMixin Gerrit.DisplayNameMixin
- * @appliesMixin Gerrit.TooltipMixin
  * @extends Polymer.Element
  */
 class GrAccountLabel extends mixinBehaviors( [
   Gerrit.DisplayNameBehavior,
-  Gerrit.TooltipBehavior,
 ], GestureEventListeners(
     LegacyElementMixin(
         PolymerElement))) {
@@ -46,25 +45,11 @@
 
   static get properties() {
     return {
-    /**
-     * @type {{ name: string, status: string }}
-     */
+      /**
+       * @type {{ name: string, status: string }}
+       */
       account: Object,
-      avatarImageSize: {
-        type: Number,
-        value: 32,
-      },
-      title: {
-        type: String,
-        reflectToAttribute: true,
-        computed: '_computeAccountTitle(account, additionalText)',
-      },
-      additionalText: String,
-      hasTooltip: {
-        type: Boolean,
-        reflectToAttribute: true,
-        computed: '_computeHasTooltip(account)',
-      },
+      voteableText: String,
       hideAvatar: {
         type: Boolean,
         value: false,
@@ -79,68 +64,12 @@
   /** @override */
   ready() {
     super.ready();
-    if (!this.additionalText) { this.additionalText = ''; }
     this.$.restAPI.getConfig()
         .then(config => { this._serverConfig = config; });
   }
 
   _computeName(account, config) {
-    return this.getUserName(config, account, false);
-  }
-
-  _computeStatusTextLength(account, config) {
-    // 35 as the max length of the name + status
-    return Math.max(10, 35 - this._computeName(account, config).length);
-  }
-
-  _computeAccountTitle(account, tooltip) {
-    // Polymer 2: check for undefined
-    if ([
-      account,
-      tooltip,
-    ].some(arg => arg === undefined)) {
-      return undefined;
-    }
-
-    if (!account) { return; }
-    let result = '';
-    if (this._computeName(account, this._serverConfig)) {
-      result += this._computeName(account, this._serverConfig);
-    }
-    if (account.email) {
-      result += ` <${account.email}>`;
-    }
-    if (this.additionalText) {
-      result += ` ${this.additionalText}`;
-    }
-
-    // Show status in the label tooltip instead of
-    // in a separate tooltip on status
-    if (account.status) {
-      result += ` (${account.status})`;
-    }
-
-    return result;
-  }
-
-  _computeShowEmailClass(account) {
-    if (!account || account.name || !account.email) { return ''; }
-    return 'showEmail';
-  }
-
-  _computeEmailStr(account) {
-    if (!account || !account.email) {
-      return '';
-    }
-    if (account.name) {
-      return '(' + account.email + ')';
-    }
-    return account.email;
-  }
-
-  _computeHasTooltip(account) {
-    // If an account has loaded to fire this method, then set to true.
-    return !!account;
+    return this.getUserName(config, account);
   }
 }
 
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_html.js b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_html.js
index e9d0e5d..a7d01ae 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_html.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_html.js
@@ -35,27 +35,24 @@
       .text:hover {
         @apply --gr-account-label-text-hover-style;
       }
-      .email,
-      .showEmail .name {
-        display: none;
-      }
-      .showEmail .email {
-        display: inline-block;
+      iron-icon {
+        width: 14px;
+        height: 14px;
+        vertical-align: top;
+        position: relative;
+        top: 2px;
       }
     </style>
     <span>
+      <gr-hovercard-account account="[[account]]" voteable-text="[[voteableText]]"></gr-hovercard-account>
       <template is="dom-if" if="[[!hideAvatar]]">
-        <gr-avatar account="[[account]]" image-size="[[avatarImageSize]]"></gr-avatar>
+        <gr-avatar account="[[account]]" image-size="32"></gr-avatar>
       </template>
-      <span class\$="text [[_computeShowEmailClass(account)]]">
+      <span class="text">
         <span class="name">
           [[_computeName(account, _serverConfig)]]</span>
-        <span class="email">
-          [[_computeEmailStr(account)]]
-        </span>
         <template is="dom-if" if="[[account.status]]">
-          (<gr-limited-text disable-tooltip="true" limit="[[_computeStatusTextLength(account, _serverConfig)]]" text="[[account.status]]">
-          </gr-limited-text>)
+          <iron-icon icon="gr-icons:calendar"></iron-icon>
         </template>
       </span>
     </span>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
index db742e6..ffe9647 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
@@ -23,19 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../../scripts/util.js"></script>
-
-<script type="module" src="./gr-account-label.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../../scripts/util.js';
-import './gr-account-label.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -44,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../../scripts/util.js';
 import './gr-account-label.js';
@@ -70,54 +56,6 @@
     });
   });
 
-  test('missing email', () => {
-    assert.equal('', element._computeEmailStr({name: 'foo'}));
-  });
-
-  test('computed fields', () => {
-    assert.equal(
-        element._computeAccountTitle({
-          name: 'Andrew Bonventre',
-          email: 'andybons+gerrit@gmail.com',
-        }, /* additionalText= */ ''),
-        'Andrew Bonventre <andybons+gerrit@gmail.com>');
-
-    assert.equal(
-        element._computeAccountTitle({
-          name: 'Andrew Bonventre',
-        }, /* additionalText= */ ''),
-        'Andrew Bonventre');
-
-    assert.equal(
-        element._computeAccountTitle({
-          email: 'andybons+gerrit@gmail.com',
-        }, /* additionalText= */ ''),
-        'Anonymous <andybons+gerrit@gmail.com>');
-
-    assert.equal(element._computeShowEmailClass(
-        {
-          name: 'Andrew Bonventre',
-          email: 'andybons+gerrit@gmail.com',
-        }, /* additionalText= */ ''), '');
-
-    assert.equal(element._computeShowEmailClass(
-        {
-          email: 'andybons+gerrit@gmail.com',
-        }, /* additionalText= */ ''), 'showEmail');
-
-    assert.equal(element._computeShowEmailClass(
-        {name: 'Andrew Bonventre'},
-        /* additionalText= */ ''
-    ),
-    '');
-
-    assert.equal(element._computeShowEmailClass(undefined), '');
-
-    assert.equal(
-        element._computeEmailStr({name: 'test', email: 'test'}), '(test)');
-    assert.equal(element._computeEmailStr({email: 'test'}, ''), 'test');
-  });
-
   suite('_computeName', () => {
     test('not showing anonymous', () => {
       const account = {name: 'Wyatt'};
@@ -152,45 +90,5 @@
           'TestAnon');
     });
   });
-
-  suite('status in tooltip', () => {
-    setup(() => {
-      element = fixture('basic');
-      element.account = {
-        name: 'test',
-        email: 'test@google.com',
-        status: 'OOO until Aug 10th',
-      };
-      element._config = {
-        user: {
-          anonymous_coward_name: 'Anonymous Coward',
-        },
-      };
-    });
-
-    test('tooltip should contain status text', () => {
-      assert.deepEqual(element.title,
-          'test <test@google.com> (OOO until Aug 10th)');
-    });
-
-    test('status text should not have tooltip', () => {
-      flushAsynchronousOperations();
-      assert.deepEqual(element.shadowRoot
-          .querySelector('gr-limited-text').title, '');
-    });
-
-    test('status text should honor the name length and total length', () => {
-      assert.deepEqual(
-          element._computeStatusTextLength(element.account, element._config),
-          31
-      );
-      assert.deepEqual(
-          element._computeStatusTextLength({
-            name: 'a very long long long long name',
-          }, element._config),
-          10
-      );
-    });
-  });
 });
 </script>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.js b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.js
index 4a38427..e0d5583 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.js
@@ -41,12 +41,8 @@
 
   static get properties() {
     return {
-      additionalText: String,
+      voteableText: String,
       account: Object,
-      avatarImageSize: {
-        type: Number,
-        value: 32,
-      },
     };
   }
 
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_html.js b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_html.js
index 4ea343e..4f1ea44 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_html.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_html.js
@@ -33,7 +33,7 @@
     </style>
     <span>
       <a href\$="[[_computeOwnerLink(account)]]" tabindex="-1">
-        <gr-account-label account="[[account]]" additional-text="[[additionalText]]" avatar-image-size="[[avatarImageSize]]"></gr-account-label>
+        <gr-account-label account="[[account]]" voteable-text="[[voteableText]]"></gr-account-label>
       </a>
     </span>
 `;
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html
index ff89a78..dc9cbda 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-account-link.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-account-link.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-account-link.js';
 suite('gr-account-link tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html
index 1fc78ea..799670e 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-account-list.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-account-list.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-account-list.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.html b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.html
index d291fc7..582dcf9 100644
--- a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.html
@@ -23,12 +23,7 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-alert.js"></script>
-
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-alert.js';
 suite('gr-alert tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html
index a18fcac..4f3d806 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-autocomplete-dropdown.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-autocomplete-dropdown.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-autocomplete-dropdown.js';
 suite('gr-autocomplete-dropdown', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
index 8a8f2f5..68de4fc 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-autocomplete.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-autocomplete.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-autocomplete.js';
 import {dom, flush as flush$0} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html
index 2cec20e..f64f163 100644
--- a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-avatar.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-avatar.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-avatar.js';
 suite('gr-avatar tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.js b/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
index cde56df..41d958fd 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
@@ -26,6 +26,7 @@
 import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
 import {PolymerElement} from '@polymer/polymer/polymer-element.js';
 import {htmlTemplate} from './gr-button_html.js';
+import '../../../scripts/util.js';
 
 /**
  * @appliesMixin Gerrit.KeyboardShortcutMixin
@@ -105,19 +106,8 @@
       return;
     }
 
-    let el = this.root;
-    let path = '';
-    while (el = el.parentNode || el.host) {
-      if (el.tagName && el.tagName.startsWith('GR-APP')) {
-        break;
-      }
-      if (el.tagName) {
-        const idString = el.id ? '#' + el.id : '';
-        path = el.tagName + idString + ' ' + path;
-      }
-    }
     this.$.reporting.reportInteraction('button-click',
-        {path: path.trim().toLowerCase()});
+        {path: util.getEventPath(e)});
   }
 
   _disabledChanged(disabled) {
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html b/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html
index 42f9a5a..9454d15 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-button.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-button.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -40,6 +30,14 @@
   </template>
 </test-fixture>
 
+<test-fixture id="nested">
+  <template>
+    <div id="test">
+      <gr-button class="testBtn"></gr-button>
+    </div>
+  </template>
+</test-fixture>
+
 <test-fixture id="tabindex">
   <template>
     <gr-button tabindex="3"></gr-button>
@@ -47,7 +45,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-button.js';
 import {addListener} from '@polymer/polymer/lib/utils/gestures.js';
@@ -190,5 +187,36 @@
       });
     }
   });
+
+  suite('reporting', () => {
+    const reportStub = sinon.stub();
+    setup(() => {
+      stub('gr-reporting', {
+        reportInteraction: (...args) => {
+          reportStub(...args);
+        },
+      });
+      reportStub.reset();
+    });
+
+    test('report event after click', () => {
+      MockInteractions.click(element);
+      assert.isTrue(reportStub.calledOnce);
+      assert.equal(reportStub.lastCall.args[0], 'button-click');
+      assert.deepEqual(reportStub.lastCall.args[1], {
+        path: 'html>body>test-fixture#basic>gr-button',
+      });
+    });
+
+    test('report event after click on nested', () => {
+      element = fixture('nested');
+      MockInteractions.click(element.querySelector('gr-button'));
+      assert.isTrue(reportStub.calledOnce);
+      assert.equal(reportStub.lastCall.args[0], 'button-click');
+      assert.deepEqual(reportStub.lastCall.args[1], {
+        path: 'html>body>test-fixture#nested>div#test>gr-button.testBtn',
+      });
+    });
+  });
 });
 </script>
diff --git a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html
index aa138d3..66bbd36 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-change-star.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-change-star.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-change-star.js';
 suite('gr-change-star tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.html b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.html
index 819411f..48c1495 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-change-status.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-change-status.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-change-status.js';
 const WIP_TOOLTIP = 'This change isn\'t ready to be reviewed or submitted. ' +
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html
index bb71869..895866b 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html
@@ -23,19 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../../scripts/util.js"></script>
-
-<script type="module" src="./gr-comment-thread.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../../scripts/util.js';
-import './gr-comment-thread.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -50,7 +37,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../../scripts/util.js';
 import './gr-comment-thread.js';
@@ -781,4 +767,4 @@
     assert.notOk(element.hasAttribute('range'));
   });
 });
-</script>
\ No newline at end of file
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html
index 96d497e..756e6fd 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html
@@ -23,20 +23,7 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
 <script src="/node_modules/page/page.js"></script>
-<script type="module" src="../../../scripts/util.js"></script>
-
-<script type="module" src="./gr-comment.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../../scripts/util.js';
-import './gr-comment.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -51,7 +38,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../../scripts/util.js';
 import './gr-comment.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html
index a39e4c7..84cb166 100644
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-copy-clipboard.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-copy-clipboard.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-copy-clipboard.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-count-string-formatter/gr-count-string-formatter_test.html b/polygerrit-ui/app/elements/shared/gr-count-string-formatter/gr-count-string-formatter_test.html
index 9981d45..ead0191 100644
--- a/polygerrit-ui/app/elements/shared/gr-count-string-formatter/gr-count-string-formatter_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-count-string-formatter/gr-count-string-formatter_test.html
@@ -23,12 +23,7 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-count-string-formatter.js"></script>
-
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-count-string-formatter.js';
 suite('gr-count-string-formatter tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager_test.html b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager_test.html
index 5264464..2f56ea6 100644
--- a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-cursor-manager.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-cursor-manager.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -47,7 +37,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-cursor-manager.js';
 suite('gr-cursor-manager tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
index 65f2248..867f5d38 100644
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
@@ -23,19 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../../scripts/util.js"></script>
-
-<script type="module" src="./gr-date-formatter.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../../scripts/util.js';
-import './gr-date-formatter.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -44,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../../scripts/util.js';
 import './gr-date-formatter.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.html b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.html
index 87e31a0..ded607f 100644
--- a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-dialog.js';
 suite('gr-dialog tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html
index 0387b89..5607a3d 100644
--- a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-diff-preferences.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-diff-preferences.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-diff-preferences.js';
 suite('gr-diff-preferences tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html
index a39a433..f63ac0c 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-download-commands.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-download-commands.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-download-commands.js';
 suite('gr-download-commands', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.html b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.html
index b3fda65..7d39f9e 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-dropdown-list.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-dropdown-list.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-dropdown-list.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html
index dcbab4d..a05634bce 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-dropdown.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-dropdown.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-dropdown.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html
index 8f3b4d3..8b9f39d 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html
@@ -23,8 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
 <!-- Can't use absolute path below for mock-interaction.js.
 Web component tester(wct) has a built-in http server and it serves "/components" directory (which is
 actually /node_modules directory). Also, wct patches some files to load modules from /components.
@@ -33,15 +31,6 @@
 -->
 <script src="../../../node_modules/iron-test-helpers/mock-interactions.js"></script>
 
-<script type="module" src="./gr-editable-content.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-editable-content.js';
-void(0);
-</script>
-
 <test-fixture id="basic">
   <template>
     <gr-editable-content></gr-editable-content>
@@ -49,7 +38,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-editable-content.js';
 suite('gr-editable-content tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.html b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.html
index 29e6619..ef392df 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.html
@@ -23,8 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
 <!-- Can't use absolute path below for mock-interaction.js.
 Web component tester(wct) has a built-in http server and it serves "/components" directory (which is
 actually /node_modules directory). Also, wct patches some files to load modules from /components.
@@ -33,15 +31,6 @@
 -->
 <script src="../../../node_modules/iron-test-helpers/mock-interactions.js"></script>
 
-<script type="module" src="./gr-editable-label.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-editable-label.js';
-void(0);
-</script>
-
 <test-fixture id="basic">
   <template>
     <gr-editable-label
@@ -66,7 +55,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-editable-label.js';
 import {flush as flush$0} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-event-interface/gr-event-interface_test.html b/polygerrit-ui/app/elements/shared/gr-event-interface/gr-event-interface_test.html
index a58df2e..7201ba4 100644
--- a/polygerrit-ui/app/elements/shared/gr-event-interface/gr-event-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-event-interface/gr-event-interface_test.html
@@ -21,16 +21,6 @@
 
 <script src="../../../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../gr-js-api-interface/gr-js-api-interface.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../gr-js-api-interface/gr-js-api-interface.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -39,7 +29,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../gr-js-api-interface/gr-js-api-interface.js';
 suite('gr-event-interface tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel_test.html b/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel_test.html
index e2f77c5..cc2fb70 100644
--- a/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel_test.html
@@ -23,9 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-fixed-panel.js"></script>
 <style>
   /* Prevent horizontal scrolling on page.
    New version of web-component-tester creates body with margins */
@@ -35,13 +32,6 @@
   }
 </style>
 
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-fixed-panel.js';
-void(0);
-</script>
-
 <test-fixture id="basic">
   <template>
     <gr-fixed-panel>
@@ -51,7 +41,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-fixed-panel.js';
 suite('gr-fixed-panel', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_html.js b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_html.js
index a30b65a..2b50565 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_html.js
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_html.js
@@ -48,7 +48,7 @@
       }
       code {
         display: block;
-        white-space: pre;
+        white-space: pre-wrap;
         color: var(--deemphasized-text-color);
       }
       li {
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.html b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.html
index 56bd44ab..c48ec8a 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-formatted-text.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-formatted-text.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-formatted-text.js';
 suite('gr-formatted-text tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account.js b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account.js
new file mode 100644
index 0000000..0bc9cb7
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account.js
@@ -0,0 +1,50 @@
+/**
+ * @license
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import '../../../scripts/bundled-polymer.js';
+
+import '@polymer/iron-icon/iron-icon.js';
+import '../../../styles/shared-styles.js';
+import '../gr-avatar/gr-avatar.js';
+import '../gr-button/gr-button.js';
+import {hovercardBehaviorMixin} from '../gr-hovercard/gr-hovercard-behavior.js';
+import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
+import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
+import {PolymerElement} from '@polymer/polymer/polymer-element.js';
+import {htmlTemplate} from './gr-hovercard-account_html.js';
+
+/** @extends Polymer.Element */
+class GrHovercardAccount extends GestureEventListeners(
+    hovercardBehaviorMixin(LegacyElementMixin(
+        PolymerElement))) {
+  static get template() { return htmlTemplate; }
+
+  static get is() { return 'gr-hovercard-account'; }
+
+  static get properties() {
+    return {
+      account: Object,
+      voteableText: String,
+      attention: {
+        type: Boolean,
+        value: false,
+        reflectToAttribute: true,
+      },
+    };
+  }
+}
+
+customElements.define(GrHovercardAccount.is, GrHovercardAccount);
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_html.js b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_html.js
new file mode 100644
index 0000000..0763420
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_html.js
@@ -0,0 +1,96 @@
+/**
+ * @license
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import '../gr-hovercard/gr-hovercard-shared-style.js';
+import {html} from '@polymer/polymer/lib/utils/html-tag.js';
+
+export const htmlTemplate = html`
+    <style include="gr-hovercard-shared-style">
+      .top,
+      .attention,
+      .status,
+      .voteable {
+        padding: var(--spacing-s) var(--spacing-l);
+      }
+      .top {
+        display: flex;
+        padding-top: var(--spacing-xl);
+        min-width: 300px;
+      }
+      gr-avatar {
+        height: 48px;
+        width: 48px;
+        margin-right: var(--spacing-l);
+      }
+      .title,
+      .email {
+        color: var(--deemphasized-text-color);
+      }
+      .status iron-icon {
+        width: 14px;
+        height: 14px;
+        vertical-align: top;
+        position: relative;
+        top: 2px;
+      }
+      .action {
+        border-top: 1px solid var(--border-color);
+        padding: var(--spacing-s) var(--spacing-l);
+        --gr-button: {
+          padding: var(--spacing-s) 0;
+        };
+      }
+      :host(:not([attention])) .attention {
+        display: none;
+      }
+      .attention {
+        background-color: var(--emphasis-color);
+      }
+      .attention iron-icon {
+        vertical-align: top;
+      }
+    </style>
+    <div id="container" role="tooltip" tabindex="-1">
+      <div class="top">
+        <div class="avatar">
+          <gr-avatar account="[[account]]" image-size="56"></gr-avatar>
+        </div>
+        <div class="account">
+          <h3 class="name">[[account.name]]</h3>
+          <div class="email">[[account.email]]</div>
+        </div>
+      </div>
+      <template is="dom-if" if="[[account.status]]">
+        <div class="status">
+          <span class="title">
+            <iron-icon icon="gr-icons:calendar"></iron-icon>
+            Status:
+          </span>
+          <span class="value">[[account.status]]</span>
+        </div>
+      </template>
+      <template is="dom-if" if="[[voteableText]]">
+        <div class="voteable">
+          <span class="title">Voteable:</span>
+          <span class="value">[[voteableText]]</span>
+        </div>
+      </template>
+      <div class="attention">
+        <iron-icon icon="gr-icons:attention"></iron-icon>
+        <span>It is this user's turn to take action.</span>
+      </div>
+    </div>
+`;
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_test.html b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_test.html
new file mode 100644
index 0000000..7a5f4c6
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_test.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2020 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport"
+      content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-hovercard-account</title>
+
+<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+
+<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/components/wct-browser-legacy/browser.js"></script>
+<script type="module" src="../../../test/test-pre-setup.js"></script>
+<script type="module" src="../../../test/common-test-setup.js"></script>
+<script src="../../../node_modules/iron-test-helpers/mock-interactions.js" type="module"></script>
+
+<script type="module" src="./gr-hovercard-account.js"></script>
+
+<script type="module">
+  import '../../../test/test-pre-setup.js';
+  import '../../../test/common-test-setup.js';
+  import './gr-hovercard-account.js';
+
+  void (0);
+</script>
+
+<test-fixture id="basic">
+  <template>
+    <gr-hovercard-account class="hovered"></gr-hovercard-account>
+  </template>
+</test-fixture>
+
+
+<script type="module">
+  import '../../../test/test-pre-setup.js';
+  import '../../../test/common-test-setup.js';
+  import './gr-hovercard-account.js';
+
+  suite('gr-hovercard-account tests', () => {
+    let element;
+    const ACCOUNT = {
+      email: 'kermit@gmail.com',
+      username: 'kermit',
+      name: 'Kermit The Frog',
+      _account_id: '31415926535',
+    };
+
+    setup(() => {
+      element = fixture('basic');
+      element.account = Object.assign({}, ACCOUNT);
+    });
+
+    test('account name is shown', () => {
+      assert.equal(element.shadowRoot.querySelector('.name').innerText,
+          'Kermit The Frog');
+    });
+
+    test('account status is not shown if the property is not set', () => {
+      assert.isNull(element.shadowRoot.querySelector('.status'));
+    });
+
+    test('account status is displayed', () => {
+      element.account = Object.assign({status: 'OOO'}, ACCOUNT);
+      flushAsynchronousOperations();
+      assert.equal(element.shadowRoot.querySelector('.status .value').innerText,
+          'OOO');
+    });
+
+    test('voteable div is not shown if the property is not set', () => {
+      assert.isNull(element.shadowRoot.querySelector('.voteable'));
+    });
+
+    test('voteable div is displayed', () => {
+      element.voteableText = 'CodeReview: +2';
+      flushAsynchronousOperations();
+      assert.equal(element.shadowRoot.querySelector('.voteable .value').innerText,
+          element.voteableText);
+    });
+  });
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.js b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.js
new file mode 100644
index 0000000..a77f5f7
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.js
@@ -0,0 +1,354 @@
+/**
+ * @license
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import '../../../scripts/bundled-polymer.js';
+
+import '../../../styles/shared-styles.js';
+import '../../../scripts/rootElement.js';
+import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
+
+const HOVER_CLASS = 'hovered';
+
+/**
+ * When the hovercard is positioned diagonally (bottom-left, bottom-right,
+ * top-left, or top-right), we add additional (invisible) padding so that the
+ * area that a user can hover over to access the hovercard is larger.
+ */
+const DIAGONAL_OVERFLOW = 15;
+
+/**
+ * The mixin for gr-hovercard-behavior.
+ *
+ * @example
+ *
+ * // LegacyElementMixin is still needed to support the old lifecycles
+ * // TODO: Replace old life cycles with new ones.
+ *
+ * class YourComponent extends hovercardBehaviorMixin(
+ *  LegacyElementMixin(PolymerElement)
+ * ) {
+ *   static get is() { return ''; }
+ *   static get template() { return html``; }
+ * }
+ *
+ * customElements.define(GrHovercard.is, GrHovercard);
+ *
+ * @see gr-hovercard.js
+ *
+ * // following annotations are required for polylint
+ * @polymer
+ * @mixinFunction
+ */
+export const hovercardBehaviorMixin = superClass => class extends superClass {
+  static get properties() {
+    return {
+      /**
+       * @type {?}
+       */
+      _target: Object,
+
+      /**
+       * Determines whether or not the hovercard is visible.
+       *
+       * @type {boolean}
+       */
+      _isShowing: {
+        type: Boolean,
+        value: false,
+      },
+      /**
+       * The `id` of the element that the hovercard is anchored to.
+       *
+       * @type {string}
+       */
+      for: {
+        type: String,
+        observer: '_forChanged',
+      },
+
+      /**
+       * The spacing between the top of the hovercard and the element it is
+       * anchored to.
+       *
+       * @type {number}
+       */
+      offset: {
+        type: Number,
+        value: 14,
+      },
+
+      /**
+       * Positions the hovercard to the top, right, bottom, left, bottom-left,
+       * bottom-right, top-left, or top-right of its content.
+       *
+       * @type {string}
+       */
+      position: {
+        type: String,
+        value: 'right',
+      },
+
+      container: Object,
+      /**
+       * ID for the container element.
+       *
+       * @type {string}
+       */
+      containerId: {
+        type: String,
+        value: 'gr-hovercard-container',
+      },
+    };
+  }
+
+  /** @override */
+  attached() {
+    super.attached();
+    if (!this._target) { this._target = this.target; }
+    this.listen(this._target, 'mouseenter', 'show');
+    this.listen(this._target, 'focus', 'show');
+    this.listen(this._target, 'mouseleave', 'hide');
+    this.listen(this._target, 'blur', 'hide');
+    this.listen(this._target, 'click', 'hide');
+  }
+
+  /** @override */
+  created() {
+    super.created();
+    this.addEventListener('mouseleave',
+        e => this.hide(e));
+  }
+
+  /** @override */
+  ready() {
+    super.ready();
+    // First, check to see if the container has already been created.
+    this.container = Gerrit.getRootElement()
+        .querySelector('#' + this.containerId);
+
+    if (this.container) { return; }
+
+    // If it does not exist, create and initialize the hovercard container.
+    this.container = document.createElement('div');
+    this.container.setAttribute('id', this.containerId);
+    Gerrit.getRootElement().appendChild(this.container);
+  }
+
+  removeListeners() {
+    this.unlisten(this._target, 'mouseenter', 'show');
+    this.unlisten(this._target, 'focus', 'show');
+    this.unlisten(this._target, 'mouseleave', 'hide');
+    this.unlisten(this._target, 'blur', 'hide');
+    this.unlisten(this._target, 'click', 'hide');
+  }
+
+  /**
+   * Returns the target element that the hovercard is anchored to (the `id` of
+   * the `for` property).
+   *
+   * @type {HTMLElement}
+   */
+  get target() {
+    const parentNode = dom(this).parentNode;
+    // If the parentNode is a document fragment, then we need to use the host.
+    const ownerRoot = dom(this).getOwnerRoot();
+    let target;
+    if (this.for) {
+      target = dom(ownerRoot).querySelector('#' + this.for);
+    } else {
+      target = parentNode.nodeType == Node.DOCUMENT_FRAGMENT_NODE ?
+        ownerRoot.host :
+        parentNode;
+    }
+    return target;
+  }
+
+  /**
+   * Hides/closes the hovercard. This occurs when the user triggers the
+   * `mouseleave` event on the hovercard's `target` element (as long as the
+   * user is not hovering over the hovercard).
+   *
+   * @param {Event} e DOM Event (e.g. `mouseleave` event)
+   */
+  hide(e) {
+    const targetRect = this._target.getBoundingClientRect();
+    const x = e.clientX;
+    const y = e.clientY;
+    if (x > targetRect.left && x < targetRect.right && y > targetRect.top &&
+        y < targetRect.bottom) {
+      // Sometimes the hovercard itself obscures the mouse pointer, and
+      // that generates a mouseleave event. We don't want to hide the hovercard
+      // in that situation.
+      return;
+    }
+
+    // If the hovercard is already hidden or the user is now hovering over the
+    //  hovercard or the user is returning from the hovercard but now hovering
+    //  over the target (to stop an annoying flicker effect), just return.
+    if (!this._isShowing || e.toElement === this ||
+        (e.fromElement === this && e.toElement === this._target)) {
+      return;
+    }
+
+    // Mark that the hovercard is not visible and do not allow focusing
+    this._isShowing = false;
+
+    // Clear styles in preparation for the next time we need to show the card
+    this.classList.remove(HOVER_CLASS);
+
+    // Reset and remove the hovercard from the DOM
+    this.style.cssText = '';
+    this.$.container.setAttribute('tabindex', -1);
+
+    // Remove the hovercard from the container, given that it is still a child
+    // of the container.
+    if (this.container.contains(this)) {
+      this.container.removeChild(this);
+    }
+  }
+
+  /**
+   * Shows/opens the hovercard. This occurs when the user triggers the
+   * `mousenter` event on the hovercard's `target` element.
+   *
+   * @param {Event} e DOM Event (e.g., `mouseenter` event)
+   */
+  show(e) {
+    if (this._isShowing) {
+      return;
+    }
+
+    // Mark that the hovercard is now visible
+    this._isShowing = true;
+    this.setAttribute('tabindex', 0);
+
+    // Add it to the DOM and calculate its position
+    this.container.appendChild(this);
+    this.updatePosition();
+
+    // Trigger the transition
+    this.classList.add(HOVER_CLASS);
+  }
+
+  /**
+   * Updates the hovercard's position based on the `position` attribute
+   * and the current position of the `target` element.
+   *
+   * The hovercard is supposed to stay open if the user hovers over it.
+   * To keep it open when the user moves away from the target, the bounding
+   * rects of the target and hovercard must touch or overlap.
+   *
+   * NOTE: You do not need to directly call this method unless you need to
+   * update the position of the tooltip while it is already visible (the
+   * target element has moved and the tooltip is still open).
+   */
+  updatePosition() {
+    if (!this._target) { return; }
+
+    // Calculate the necessary measurements and positions
+    const parentRect = document.documentElement.getBoundingClientRect();
+    const targetRect = this._target.getBoundingClientRect();
+    const thisRect = this.getBoundingClientRect();
+
+    const targetLeft = targetRect.left - parentRect.left;
+    const targetTop = targetRect.top - parentRect.top;
+
+    let hovercardLeft;
+    let hovercardTop;
+    const diagonalPadding = this.offset + DIAGONAL_OVERFLOW;
+    let cssText = '';
+
+    // Find the top and left position values based on the position attribute
+    // of the hovercard.
+    switch (this.position) {
+      case 'top':
+        hovercardLeft = targetLeft + (targetRect.width - thisRect.width) / 2;
+        hovercardTop = targetTop - thisRect.height - this.offset;
+        cssText += `padding-bottom:${this.offset
+        }px; margin-bottom:-${this.offset}px;`;
+        break;
+      case 'bottom':
+        hovercardLeft = targetLeft + (targetRect.width - thisRect.width) / 2;
+        hovercardTop = targetTop + targetRect.height + this.offset;
+        cssText +=
+            `padding-top:${this.offset}px; margin-top:-${this.offset}px;`;
+        break;
+      case 'left':
+        hovercardLeft = targetLeft - thisRect.width - this.offset;
+        hovercardTop = targetTop + (targetRect.height - thisRect.height) / 2;
+        cssText +=
+            `padding-right:${this.offset}px; margin-right:-${this.offset}px;`;
+        break;
+      case 'right':
+        hovercardLeft = targetRect.right + this.offset;
+        hovercardTop = targetTop + (targetRect.height - thisRect.height) / 2;
+        cssText +=
+            `padding-left:${this.offset}px; margin-left:-${this.offset}px;`;
+        break;
+      case 'bottom-right':
+        hovercardLeft = targetRect.left + targetRect.width + this.offset;
+        hovercardTop = targetRect.top + targetRect.height + this.offset;
+        cssText += `padding-top:${diagonalPadding}px;`;
+        cssText += `padding-left:${diagonalPadding}px;`;
+        cssText += `margin-left:-${diagonalPadding}px;`;
+        cssText += `margin-top:-${diagonalPadding}px;`;
+        break;
+      case 'bottom-left':
+        hovercardLeft = targetRect.left - thisRect.width - this.offset;
+        hovercardTop = targetRect.top + targetRect.height + this.offset;
+        cssText += `padding-top:${diagonalPadding}px;`;
+        cssText += `padding-right:${diagonalPadding}px;`;
+        cssText += `margin-right:-${diagonalPadding}px;`;
+        cssText += `margin-top:-${diagonalPadding}px;`;
+        break;
+      case 'top-left':
+        hovercardLeft = targetRect.left - thisRect.width - this.offset;
+        hovercardTop = targetRect.top - thisRect.height - this.offset;
+        cssText += `padding-bottom:${diagonalPadding}px;`;
+        cssText += `padding-right:${diagonalPadding}px;`;
+        cssText += `margin-bottom:-${diagonalPadding}px;`;
+        cssText += `margin-right:-${diagonalPadding}px;`;
+        break;
+      case 'top-right':
+        hovercardLeft = targetRect.left + targetRect.width + this.offset;
+        hovercardTop = targetRect.top - thisRect.height - this.offset;
+        cssText += `padding-bottom:${diagonalPadding}px;`;
+        cssText += `padding-left:${diagonalPadding}px;`;
+        cssText += `margin-bottom:-${diagonalPadding}px;`;
+        cssText += `margin-left:-${diagonalPadding}px;`;
+        break;
+    }
+
+    // Prevent hovercard from appearing outside the viewport.
+    // TODO(kaspern): fix hovercard appearing outside viewport on bottom and
+    // right.
+    if (hovercardLeft < 0) { hovercardLeft = 0; }
+    if (hovercardTop < 0) { hovercardTop = 0; }
+    // Set the hovercard's position
+    cssText += `left:${hovercardLeft}px; top:${hovercardTop}px;`;
+    this.style.cssText = cssText;
+  }
+
+  /**
+   * Responds to a change in the `for` value and gets the updated `target`
+   * element for the hovercard.
+   *
+   * @private
+   */
+  _forChanged() {
+    this._target = this.target;
+  }
+};
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-shared-style.js b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-shared-style.js
new file mode 100644
index 0000000..a392691
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-shared-style.js
@@ -0,0 +1,46 @@
+/**
+ * @license
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** The shared styles for all hover cards. */
+const GrHoverCardSharedStyle = document.createElement('dom-module');
+GrHoverCardSharedStyle.innerHTML =
+  `<template>
+    <style include="shared-styles">
+      :host {
+        box-sizing: border-box;
+        opacity: 0;
+        position: absolute;
+        transition: opacity 200ms;
+        visibility: hidden;
+        z-index: 200;
+      }
+      :host(.hovered) {
+        visibility: visible;
+        opacity: 1;
+      }
+      /* You have to use a <div class="container"> in your hovercard in order
+         to pick up this consistent styling. */
+      #container {
+        background: var(--dialog-background-color);
+        border: 1px solid var(--border-color);
+        border-radius: var(--border-radius);
+        box-shadow: var(--elevation-level-5);
+      }
+    </style>
+  </template>`;
+
+GrHoverCardSharedStyle.register('gr-hovercard-shared-style');
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.js b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.js
index ce2303f..3f936dd 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.js
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.js
@@ -18,327 +18,20 @@
 
 import '../../../styles/shared-styles.js';
 import '../../../scripts/rootElement.js';
-import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
 import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
-import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
 import {PolymerElement} from '@polymer/polymer/polymer-element.js';
 import {htmlTemplate} from './gr-hovercard_html.js';
-
-const HOVER_CLASS = 'hovered';
-
-/**
- * When the hovercard is positioned diagonally (bottom-left, bottom-right,
- * top-left, or top-right), we add additional (invisible) padding so that the
- * area that a user can hover over to access the hovercard is larger.
- */
-const DIAGONAL_OVERFLOW = 15;
+import {hovercardBehaviorMixin} from './gr-hovercard-behavior.js';
+import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
+import './gr-hovercard-shared-style.js';
 
 /** @extends Polymer.Element */
 class GrHovercard extends GestureEventListeners(
-    LegacyElementMixin(
-        PolymerElement)) {
+    hovercardBehaviorMixin(LegacyElementMixin(PolymerElement))
+) {
   static get template() { return htmlTemplate; }
 
   static get is() { return 'gr-hovercard'; }
-
-  static get properties() {
-    return {
-    /**
-     * @type {?}
-     */
-      _target: Object,
-
-      /**
-       * Determines whether or not the hovercard is visible.
-       *
-       * @type {boolean}
-       */
-      _isShowing: {
-        type: Boolean,
-        value: false,
-      },
-      /**
-       * The `id` of the element that the hovercard is anchored to.
-       *
-       * @type {string}
-       */
-      for: {
-        type: String,
-        observer: '_forChanged',
-      },
-
-      /**
-       * The spacing between the top of the hovercard and the element it is
-       * anchored to.
-       *
-       * @type {number}
-       */
-      offset: {
-        type: Number,
-        value: 14,
-      },
-
-      /**
-       * Positions the hovercard to the top, right, bottom, left, bottom-left,
-       * bottom-right, top-left, or top-right of its content.
-       *
-       * @type {string}
-       */
-      position: {
-        type: String,
-        value: 'bottom',
-      },
-
-      container: Object,
-      /**
-       * ID for the container element.
-       *
-       * @type {string}
-       */
-      containerId: {
-        type: String,
-        value: 'gr-hovercard-container',
-      },
-    };
-  }
-
-  /** @override */
-  attached() {
-    super.attached();
-    if (!this._target) { this._target = this.target; }
-    this.listen(this._target, 'mouseenter', 'show');
-    this.listen(this._target, 'focus', 'show');
-    this.listen(this._target, 'mouseleave', 'hide');
-    this.listen(this._target, 'blur', 'hide');
-    this.listen(this._target, 'click', 'hide');
-  }
-
-  /** @override */
-  created() {
-    super.created();
-    this.addEventListener('mouseleave',
-        e => this.hide(e));
-  }
-
-  /** @override */
-  ready() {
-    super.ready();
-    // First, check to see if the container has already been created.
-    this.container = Gerrit.getRootElement()
-        .querySelector('#' + this.containerId);
-
-    if (this.container) { return; }
-
-    // If it does not exist, create and initialize the hovercard container.
-    this.container = document.createElement('div');
-    this.container.setAttribute('id', this.containerId);
-    Gerrit.getRootElement().appendChild(this.container);
-  }
-
-  removeListeners() {
-    this.unlisten(this._target, 'mouseenter', 'show');
-    this.unlisten(this._target, 'focus', 'show');
-    this.unlisten(this._target, 'mouseleave', 'hide');
-    this.unlisten(this._target, 'blur', 'hide');
-    this.unlisten(this._target, 'click', 'hide');
-  }
-
-  /**
-   * Returns the target element that the hovercard is anchored to (the `id` of
-   * the `for` property).
-   *
-   * @type {HTMLElement}
-   */
-  get target() {
-    const parentNode = dom(this).parentNode;
-    // If the parentNode is a document fragment, then we need to use the host.
-    const ownerRoot = dom(this).getOwnerRoot();
-    let target;
-    if (this.for) {
-      target = dom(ownerRoot).querySelector('#' + this.for);
-    } else {
-      target = parentNode.nodeType == Node.DOCUMENT_FRAGMENT_NODE ?
-        ownerRoot.host :
-        parentNode;
-    }
-    return target;
-  }
-
-  /**
-   * Hides/closes the hovercard. This occurs when the user triggers the
-   * `mouseleave` event on the hovercard's `target` element (as long as the
-   * user is not hovering over the hovercard).
-   *
-   * @param {Event} e DOM Event (e.g. `mouseleave` event)
-   */
-  hide(e) {
-    const targetRect = this._target.getBoundingClientRect();
-    const x = e.clientX;
-    const y = e.clientY;
-    if (x > targetRect.left && x < targetRect.right && y > targetRect.top &&
-        y < targetRect.bottom) {
-      // Sometimes the hovercard itself obscures the mouse pointer, and
-      // that generates a mouseleave event. We don't want to hide the hovercard
-      // in that situation.
-      return;
-    }
-
-    // If the hovercard is already hidden or the user is now hovering over the
-    //  hovercard or the user is returning from the hovercard but now hovering
-    //  over the target (to stop an annoying flicker effect), just return.
-    if (!this._isShowing || e.toElement === this ||
-        (e.fromElement === this && e.toElement === this._target)) {
-      return;
-    }
-
-    // Mark that the hovercard is not visible and do not allow focusing
-    this._isShowing = false;
-
-    // Clear styles in preparation for the next time we need to show the card
-    this.classList.remove(HOVER_CLASS);
-
-    // Reset and remove the hovercard from the DOM
-    this.style.cssText = '';
-    this.$.hovercard.setAttribute('tabindex', -1);
-
-    // Remove the hovercard from the container, given that it is still a child
-    // of the container.
-    if (this.container.contains(this)) {
-      this.container.removeChild(this);
-    }
-  }
-
-  /**
-   * Shows/opens the hovercard. This occurs when the user triggers the
-   * `mousenter` event on the hovercard's `target` element.
-   *
-   * @param {Event} e DOM Event (e.g., `mouseenter` event)
-   */
-  show(e) {
-    if (this._isShowing) {
-      return;
-    }
-
-    // Mark that the hovercard is now visible
-    this._isShowing = true;
-    this.setAttribute('tabindex', 0);
-
-    // Add it to the DOM and calculate its position
-    this.container.appendChild(this);
-    this.updatePosition();
-
-    // Trigger the transition
-    this.classList.add(HOVER_CLASS);
-  }
-
-  /**
-   * Updates the hovercard's position based on the `position` attribute
-   * and the current position of the `target` element.
-   *
-   * The hovercard is supposed to stay open if the user hovers over it.
-   * To keep it open when the user moves away from the target, the bounding
-   * rects of the target and hovercard must touch or overlap.
-   *
-   * NOTE: You do not need to directly call this method unless you need to
-   * update the position of the tooltip while it is already visible (the
-   * target element has moved and the tooltip is still open).
-   */
-  updatePosition() {
-    if (!this._target) { return; }
-
-    // Calculate the necessary measurements and positions
-    const parentRect = document.documentElement.getBoundingClientRect();
-    const targetRect = this._target.getBoundingClientRect();
-    const thisRect = this.getBoundingClientRect();
-
-    const targetLeft = targetRect.left - parentRect.left;
-    const targetTop = targetRect.top - parentRect.top;
-
-    let hovercardLeft;
-    let hovercardTop;
-    const diagonalPadding = this.offset + DIAGONAL_OVERFLOW;
-    let cssText = '';
-
-    // Find the top and left position values based on the position attribute
-    // of the hovercard.
-    switch (this.position) {
-      case 'top':
-        hovercardLeft = targetLeft + (targetRect.width - thisRect.width) / 2;
-        hovercardTop = targetTop - thisRect.height - this.offset;
-        cssText += `padding-bottom:${this.offset
-        }px; margin-bottom:-${this.offset}px;`;
-        break;
-      case 'bottom':
-        hovercardLeft = targetLeft + (targetRect.width - thisRect.width) / 2;
-        hovercardTop = targetTop + targetRect.height + this.offset;
-        cssText +=
-            `padding-top:${this.offset}px; margin-top:-${this.offset}px;`;
-        break;
-      case 'left':
-        hovercardLeft = targetLeft - thisRect.width - this.offset;
-        hovercardTop = targetTop + (targetRect.height - thisRect.height) / 2;
-        cssText +=
-            `padding-right:${this.offset}px; margin-right:-${this.offset}px;`;
-        break;
-      case 'right':
-        hovercardLeft = targetRect.right + this.offset;
-        hovercardTop = targetTop + (targetRect.height - thisRect.height) / 2;
-        cssText +=
-            `padding-left:${this.offset}px; margin-left:-${this.offset}px;`;
-        break;
-      case 'bottom-right':
-        hovercardLeft = targetRect.left + targetRect.width + this.offset;
-        hovercardTop = targetRect.top + targetRect.height + this.offset;
-        cssText += `padding-top:${diagonalPadding}px;`;
-        cssText += `padding-left:${diagonalPadding}px;`;
-        cssText += `margin-left:-${diagonalPadding}px;`;
-        cssText += `margin-top:-${diagonalPadding}px;`;
-        break;
-      case 'bottom-left':
-        hovercardLeft = targetRect.left - thisRect.width - this.offset;
-        hovercardTop = targetRect.top + targetRect.height + this.offset;
-        cssText += `padding-top:${diagonalPadding}px;`;
-        cssText += `padding-right:${diagonalPadding}px;`;
-        cssText += `margin-right:-${diagonalPadding}px;`;
-        cssText += `margin-top:-${diagonalPadding}px;`;
-        break;
-      case 'top-left':
-        hovercardLeft = targetRect.left - thisRect.width - this.offset;
-        hovercardTop = targetRect.top - thisRect.height - this.offset;
-        cssText += `padding-bottom:${diagonalPadding}px;`;
-        cssText += `padding-right:${diagonalPadding}px;`;
-        cssText += `margin-bottom:-${diagonalPadding}px;`;
-        cssText += `margin-right:-${diagonalPadding}px;`;
-        break;
-      case 'top-right':
-        hovercardLeft = targetRect.left + targetRect.width + this.offset;
-        hovercardTop = targetRect.top - thisRect.height - this.offset;
-        cssText += `padding-bottom:${diagonalPadding}px;`;
-        cssText += `padding-left:${diagonalPadding}px;`;
-        cssText += `margin-bottom:-${diagonalPadding}px;`;
-        cssText += `margin-left:-${diagonalPadding}px;`;
-        break;
-    }
-
-    // Prevent hovercard from appearing outside the viewport.
-    // TODO(kaspern): fix hovercard appearing outside viewport on bottom and
-    // right.
-    if (hovercardLeft < 0) { hovercardLeft = 0; }
-    if (hovercardTop < 0) { hovercardTop = 0; }
-    // Set the hovercard's position
-    cssText += `left:${hovercardLeft}px; top:${hovercardTop}px;`;
-    this.style.cssText = cssText;
-  }
-
-  /**
-   * Responds to a change in the `for` value and gets the updated `target`
-   * element for the hovercard.
-   *
-   * @private
-   */
-  _forChanged() {
-    this._target = this.target;
-  }
 }
 
 customElements.define(GrHovercard.is, GrHovercard);
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_html.js b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_html.js
index 2969bdb..69fd4c5 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_html.js
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_html.js
@@ -17,26 +17,12 @@
 import {html} from '@polymer/polymer/lib/utils/html-tag.js';
 
 export const htmlTemplate = html`
-    <style include="shared-styles">
-      :host {
-        box-sizing: border-box;
-        opacity: 0;
-        position: absolute;
-        transition: opacity 200ms;
-        visibility: hidden;
-        z-index: 100;
-      }
-      :host(.hovered) {
-        visibility: visible;
-        opacity: 1;
-      }
-      #hovercard {
-        background: var(--dialog-background-color);
-        box-shadow: var(--elevation-level-2);
+    <style include="gr-hovercard-shared-style">
+      #container {
         padding: var(--spacing-l);
       }
     </style>
-    <div id="hovercard" role="tooltip" tabindex="-1">
+    <div id="container" role="tooltip" tabindex="-1">
       <slot></slot>
     </div>
 `;
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html
index ed087ca..1ffed0a 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html
@@ -23,8 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
 <!-- Can't use absolute path below for mock-interaction.js.
 Web component tester(wct) has a built-in http server and it serves "/components" directory (which is
 actually /node_modules directory). Also, wct patches some files to load modules from /components.
@@ -33,15 +31,6 @@
 -->
 <script src="../../../node_modules/iron-test-helpers/mock-interactions.js"></script>
 
-<script type="module" src="./gr-hovercard.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-hovercard.js';
-void(0);
-</script>
-
 <button id="foo">Hello</button>
 <test-fixture id="basic">
   <template>
@@ -50,7 +39,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-hovercard.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.js b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.js
index 5d7da6c..f7b8b63 100644
--- a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.js
+++ b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.js
@@ -59,6 +59,8 @@
       <g id="hourglass"><path d="M6 2v6h.01L6 8.01 10 12l-4 4 .01.01H6V22h12v-5.99h-.01L18 16l-4-4 4-3.99-.01-.01H18V2H6z"></path><path d="M0 0h24v24H0V0z" fill="none"></path></g>
       <!-- This SVG is a copy from material.io https://material.io/icons/#mode_comment-->
       <g id="comment"><path d="M21.99 4c0-1.1-.89-2-1.99-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14l4 4-.01-18z"></path><path d="M0 0h24v24H0z" fill="none"></path></g>
+      <!-- This SVG is a copy from material.io https://material.io/icons/#calendar_today-->
+      <g id="calendar"><path d="M20 3h-1V1h-2v2H7V1H5v2H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 18H4V8h16v13z"></path><path d="M0 0h24v24H0z" fill="none"></path></g>
       <!-- This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js -->
       <g id="error"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"></path></g>
       <!-- This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js -->
@@ -94,51 +96,10 @@
       <g id="review"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"></path></g>
       <!-- This is a custom PolyGerrit SVG -->
       <g id="zeroState"><path d="M22 9V7h-2V5c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-2h2v-2h-2v-2h2v-2h-2V9h2zm-4 10H4V5h14v14zM6 13h5v4H6zm6-6h4v3h-4zM6 7h5v5H6zm6 4h4v6h-4z"></path></g>
+      <!-- This SVG is an adaptation of material.io https://material.io/icons/#label_important-->
+      <g id="attention"><path d="M5.5 19 l9 0 c.67 0 1.27 -.33 1.63 -.84 L20.5 12 l-4.37 -6.16 c-.36 -.51 -.96 -.84 -1.63 -.84 l-9 0 L9 12 z"></path></g>
     </defs>
   </svg>
 </iron-iconset-svg>`;
 
 document.head.appendChild($_documentContainer.content);
-
-/* This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js */
-/* This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js */
-/* This SVG is a copy from material.io https://material.io/icons/#unfold_more */
-/* This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js */
-/* This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js */
-/* This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js */
-/* This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js */
-/* This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js */
-/* This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js */
-/* This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js */
-/* This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js */
-/* This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js */
-/* This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js */
-/* This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/editor-icons.html */
-/* This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/editor-icons.html */
-/* This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js */
-/* This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js */
-/* This SVG is a copy from material.io https://material.io/icons/#ic_hourglass_full*/
-/* This SVG is a copy from material.io https://material.io/icons/#mode_comment*/
-/* This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js */
-/* This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js */
-/* This is a custom PolyGerrit SVG */
-/* This is a custom PolyGerrit SVG */
-/* This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js */
-/* This is a custom PolyGerrit SVG */
-/* This is a custom PolyGerrit SVG */
-/* This is a custom PolyGerrit SVG */
-/* This is a custom PolyGerrit SVG */
-/* This is a custom PolyGerrit SVG */
-/* This is a custom PolyGerrit SVG */
-/* This is a custom PolyGerrit SVG */
-/* This is a custom PolyGerrit SVG */
-/* This is a custom PolyGerrit SVG */
-/* This is a custom PolyGerrit SVG */
-/* This is a custom PolyGerrit SVG */
-/* This is a custom PolyGerrit SVG */
-/*
-  FIXME(polymer-modulizer): the above comments were extracted
-  from HTML and may be out of place here. Review them and
-  then delete this comment!
-*/
-
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html
index c044327..c4c9043 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html
@@ -23,19 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../diff/gr-diff-highlight/gr-annotation.js"></script>
-
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-js-api-interface.js"></script>
-
-<script type="module">
-import '../../diff/gr-diff-highlight/gr-annotation.js';
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-js-api-interface.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -45,7 +32,6 @@
 
 <script type="module">
 import '../../diff/gr-diff-highlight/gr-annotation.js';
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-js-api-interface.js';
 suite('gr-annotation-actions-context tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html
index 061f22c..13aa6f4 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html
@@ -23,10 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../change/gr-change-actions/gr-change-actions.js"></script>
-
 <test-fixture id="basic">
   <template>
     <span hidden id="annotation-span">
@@ -39,7 +35,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../change/gr-change-actions/gr-change-actions.js';
 suite('gr-annotation-actions-js-api tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.html
index 154d287..9586ccb 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.html
@@ -23,19 +23,8 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-js-api-interface.js"></script>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-js-api-interface.js';
-void(0);
-</script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-js-api-interface.js';
 const PRELOADED_PROTOCOL = 'preloaded:';
@@ -92,4 +81,4 @@
     });
   });
 });
-</script>
\ No newline at end of file
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
index 1425f71..b5cbd97 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
@@ -23,20 +23,10 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
 <!--
 This must refer to the element this interface is wrapping around. Otherwise
 breaking changes to gr-change-actions won’t be noticed.
 -->
-<script type="module" src="../../change/gr-change-actions/gr-change-actions.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../change/gr-change-actions/gr-change-actions.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -45,7 +35,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../change/gr-change-actions/gr-change-actions.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html
index 3a1c51c..edc31b8 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html
@@ -23,20 +23,10 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
 <!--
 This must refer to the element this interface is wrapping around. Otherwise
 breaking changes to gr-reply-dialog won’t be noticed.
 -->
-<script type="module" src="../../change/gr-reply-dialog/gr-reply-dialog.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../change/gr-reply-dialog/gr-reply-dialog.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -45,7 +35,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../change/gr-reply-dialog/gr-reply-dialog.js';
 suite('gr-change-reply-js-api tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit_test.html
index 57f0646..d289368 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-js-api-interface.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-js-api-interface.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-js-api-interface.js';
 suite('gr-gerrit tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
index 1fff4a9..e626e33 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-js-api-interface.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-js-api-interface.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-js-api-interface.js';
 suite('gr-js-api-interface tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html
index 3ba2acc..48b2438 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-js-api-interface.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-js-api-interface.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-js-api-interface.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.html
index 94bb771..39c3385 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.html
@@ -23,19 +23,8 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-js-api-interface.js"></script>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-js-api-interface.js';
-void(0);
-</script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-js-api-interface.js';
 suite('gr-plugin-endpoints tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.html
index 46914dc..4440fc8 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-js-api-interface.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-js-api-interface.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-js-api-interface.js';
 suite('gr-plugin-loader tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html
index 64c31d7..8963821 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html
@@ -23,12 +23,7 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-js-api-interface.js"></script>
-
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-js-api-interface.js';
 suite('gr-plugin-rest-api tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.html b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.html
index 44627ab..f7e9a70 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.html
@@ -22,16 +22,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-label-info.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-label-info.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -40,7 +30,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-label-info.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html
index cd58932..7e83aaf 100644
--- a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html
@@ -22,16 +22,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-labeled-autocomplete.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-labeled-autocomplete.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -40,7 +30,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-labeled-autocomplete.js';
 suite('gr-labeled-autocomplete tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.html b/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.html
index 1f726b3..89672a1 100644
--- a/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-lib-loader.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-lib-loader.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-lib-loader.js';
 suite('gr-lib-loader tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html
index 8240cbf..1c6358c 100644
--- a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html
@@ -23,17 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-
-<script type="module" src="./gr-limited-text.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-limited-text.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -42,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-limited-text.js';
 suite('gr-limited-text tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.html b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.html
index af8d217..0ba6194 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.html
@@ -23,8 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
 <!-- Can't use absolute path below for mock-interaction.js.
 Web component tester(wct) has a built-in http server and it serves "/components" directory (which is
 actually /node_modules directory). Also, wct patches some files to load modules from /components.
@@ -33,15 +31,6 @@
 -->
 <script src="../../../node_modules/iron-test-helpers/mock-interactions.js"></script>
 
-<script type="module" src="./gr-linked-chip.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-linked-chip.js';
-void(0);
-</script>
-
 <test-fixture id="basic">
   <template>
     <gr-linked-chip></gr-linked-chip>
@@ -49,7 +38,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-linked-chip.js';
 suite('gr-linked-chip tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
index b16acf4..381b332 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
@@ -23,19 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../../scripts/util.js"></script>
-
-<script type="module" src="./gr-linked-text.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../../scripts/util.js';
-import './gr-linked-text.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -46,7 +33,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../../scripts/util.js';
 import './gr-linked-text.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html
index cd45650..accf73e 100644
--- a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html
@@ -24,17 +24,6 @@
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
 
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-list-view.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-list-view.js';
-void(0);
-</script>
-
 <test-fixture id="basic">
   <template>
     <gr-list-view></gr-list-view>
@@ -42,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-list-view.js';
 suite('gr-list-view tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay_test.html b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay_test.html
index 72f01fb..52bba7a 100644
--- a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay_test.html
@@ -25,18 +25,6 @@
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
 
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-
-<script type="module" src="./gr-overlay.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-overlay.js';
-void(0);
-</script>
-
 <test-fixture id="basic">
   <template>
     <gr-overlay>
@@ -46,7 +34,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-overlay.js';
 suite('gr-overlay tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav_test.html b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav_test.html
index 4f47b95..c1f7ace 100644
--- a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav_test.html
@@ -25,18 +25,6 @@
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
 
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-
-<script type="module" src="./gr-page-nav.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-page-nav.js';
-void(0);
-</script>
-
 <test-fixture id="basic">
   <template>
     <gr-page-nav>
@@ -48,7 +36,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-page-nav.js';
 suite('gr-page-nav tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html
index 191b5d5..8e50b09 100644
--- a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html
@@ -22,16 +22,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-repo-branch-picker.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-repo-branch-picker.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -40,7 +30,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-repo-branch-picker.js';
 suite('gr-repo-branch-picker tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html
index 01baa43..ab79f64 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html
@@ -23,14 +23,7 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../../behaviors/base-url-behavior/base-url-behavior.js"></script>
-
-<script type="module" src="./gr-auth.js"></script>
-
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../../behaviors/base-url-behavior/base-url-behavior.js';
 import './gr-auth.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator_test.html
index 3eae300..482dc6a 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator_test.html
@@ -23,13 +23,7 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-
-<script type="module" src="./gr-etag-decorator.js"></script>
-
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-etag-decorator.js';
 suite('gr-etag-decorator', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index 3e78bd3..849be00 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -772,6 +772,23 @@
   }
 
   /**
+   * @param {string} displayName
+   * @param {function(?Response, string=)=} opt_errFn
+   */
+  setAccountDisplayName(displayName, opt_errFn) {
+    const req = {
+      method: 'PUT',
+      url: '/accounts/self/displayname',
+      body: {display_name: displayName},
+      errFn: opt_errFn,
+      parseResponse: true,
+      reportUrlAsIs: true,
+    };
+    return this._restApiHelper.send(req)
+        .then(newName => this._updateCachedAccount({displayName: newName}));
+  }
+
+  /**
    * @param {string} status
    * @param {function(?Response, string=)=} opt_errFn
    */
@@ -1201,15 +1218,7 @@
       patchNum,
       reportEndpointAsIs: true,
     };
-    return this._getChangeURLAndFetch(req).then(revisionActions => {
-      // The rebase button on change screen is always enabled.
-      if (revisionActions.rebase) {
-        revisionActions.rebase.rebaseOnCurrent =
-            !!revisionActions.rebase.enabled;
-        revisionActions.rebase.enabled = true;
-      }
-      return revisionActions;
-    });
+    return this._getChangeURLAndFetch(req);
   }
 
   /**
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index 13ed562..814a474 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -23,19 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../../scripts/util.js"></script>
-
-<script type="module" src="./gr-rest-api-interface.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import '../../../scripts/util.js';
-import './gr-rest-api-interface.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -44,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../../scripts/util.js';
 import './gr-rest-api-interface.js';
@@ -332,36 +318,6 @@
         ]);
   });
 
-  suite('rebase action', () => {
-    let resolve_fetchJSON;
-    setup(() => {
-      sandbox.stub(element._restApiHelper, 'fetchJSON').returns(
-          new Promise(resolve => {
-            resolve_fetchJSON = resolve;
-          }));
-    });
-
-    test('no rebase on current', done => {
-      element.getChangeRevisionActions('42', '1337').then(
-          response => {
-            assert.isTrue(response.rebase.enabled);
-            assert.isFalse(response.rebase.rebaseOnCurrent);
-            done();
-          });
-      resolve_fetchJSON({rebase: {}});
-    });
-
-    test('rebase on current', done => {
-      element.getChangeRevisionActions('42', '1337').then(
-          response => {
-            assert.isTrue(response.rebase.enabled);
-            assert.isTrue(response.rebase.rebaseOnCurrent);
-            done();
-          });
-      resolve_fetchJSON({rebase: {enabled: true}});
-    });
-  });
-
   test('server error', done => {
     const getResponseObjectStub = sandbox.stub(element, 'getResponseObject');
     window.fetch.returns(Promise.resolve({ok: false}));
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html
index 7f86953..5ca70a3 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html
@@ -23,23 +23,8 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../../test/common-test-setup.js"></script>
-<script type="module" src="../../../../scripts/util.js"></script>
-<script type="module" src="../gr-auth.js"></script>
-<script type="module" src="./gr-rest-api-helper.js"></script>
 
 <script type="module">
-import '../../../../test/test-pre-setup.js';
-import '../../../../test/common-test-setup.js';
-import '../../../../scripts/util.js';
-import '../gr-auth.js';
-import './gr-rest-api-helper.js';
-void(0);
-</script>
-
-<script type="module">
-import '../../../../test/test-pre-setup.js';
 import '../../../../test/common-test-setup.js';
 import '../../../../scripts/util.js';
 import '../gr-auth.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html
index 6dcdc48..4e17e13 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html
@@ -23,13 +23,7 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="../../../scripts/util.js"></script>
-<script type="module" src="./gr-reviewer-updates-parser.js"></script>
-
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import '../../../scripts/util.js';
 import './gr-reviewer-updates-parser.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-select/gr-select_test.html b/polygerrit-ui/app/elements/shared/gr-select/gr-select_test.html
index a4d28a3..b18eb41 100644
--- a/polygerrit-ui/app/elements/shared/gr-select/gr-select_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-select/gr-select_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-select.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-select.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -56,7 +46,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-select.js';
 suite('gr-select tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_test.html b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_test.html
index 4e5be4d..cc779c7 100644
--- a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_test.html
@@ -23,16 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-shell-command.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-shell-command.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-shell-command.js';
 suite('gr-shell-command tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html
index 06e5915..5642e15 100644
--- a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html
@@ -22,16 +22,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-storage.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-storage.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -40,7 +30,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-storage.js';
 suite('gr-storage tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
index 9ede81c..b8828d7 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
@@ -23,16 +23,7 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-textarea.js"></script>
 
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-textarea.js';
-void(0);
-</script>
 <test-fixture id="basic">
   <template>
     <gr-textarea></gr-textarea>
@@ -52,7 +43,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-textarea.js';
 suite('gr-textarea tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.html b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.html
index 853f4c2..e2c2f60 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.html
@@ -22,16 +22,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-tooltip-content.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-tooltip-content.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-tooltip-content.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.html b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.html
index be5e26e..7d599b23 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.html
@@ -22,16 +22,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-tooltip.js"></script>
-
-<script type="module">
-import '../../../test/test-pre-setup.js';
-import '../../../test/common-test-setup.js';
-import './gr-tooltip.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -41,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './gr-tooltip.js';
 suite('gr-tooltip tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/revision-info/revision-info_test.html b/polygerrit-ui/app/elements/shared/revision-info/revision-info_test.html
index 4946e2e..d6804c0 100644
--- a/polygerrit-ui/app/elements/shared/revision-info/revision-info_test.html
+++ b/polygerrit-ui/app/elements/shared/revision-info/revision-info_test.html
@@ -23,12 +23,8 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../../test/test-pre-setup.js"></script>
-<script type="module" src="../../../test/common-test-setup.js"></script>
-<script type="module" src="./revision-info.js"></script>
 
 <script type="module">
-import '../../../test/test-pre-setup.js';
 import '../../../test/common-test-setup.js';
 import './revision-info.js';
 suite('revision-info tests', () => {
diff --git a/polygerrit-ui/app/rules.bzl b/polygerrit-ui/app/rules.bzl
index 8307fad..6e1f63a 100644
--- a/polygerrit-ui/app/rules.bzl
+++ b/polygerrit-ui/app/rules.bzl
@@ -1,5 +1,4 @@
 load("//tools/bzl:genrule2.bzl", "genrule2")
-load("//tools/node_tools/legacy:index.bzl", "polymer_bundler_tool")
 load("@npm_bazel_rollup//:index.bzl", "rollup_bundle")
 
 def polygerrit_bundle(name, srcs, outs, entry_point):
diff --git a/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils.js b/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils.js
index f0d0e7f..cefd254 100644
--- a/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils.js
+++ b/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils.js
@@ -24,16 +24,12 @@
   const ANONYMOUS_NAME = 'Anonymous';
 
   class GrDisplayNameUtils {
-    /**
-     * enableEmail when true enables to fallback to using email if
-     * the account name is not avilable.
-     */
-    static getUserName(config, account, enableEmail) {
+    static getUserName(config, account) {
       if (account && account.name) {
         return account.name;
       } else if (account && account.username) {
         return account.username;
-      } else if (enableEmail && account && account.email) {
+      } else if (account && account.email) {
         return account.email;
       } else if (config && config.user &&
           config.user.anonymous_coward_name !== 'Anonymous Coward') {
@@ -43,8 +39,8 @@
       return ANONYMOUS_NAME;
     }
 
-    static getAccountDisplayName(config, account, enableEmail) {
-      const reviewerName = this.getUserName(config, account, !!enableEmail);
+    static getAccountDisplayName(config, account) {
+      const reviewerName = this.getUserName(config, account);
       const reviewerEmail = this._accountEmail(account.email);
       const reviewerStatus = account.status ? '(' + account.status + ')' : '';
       return [reviewerName, reviewerEmail, reviewerStatus]
diff --git a/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils_test.html b/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils_test.html
index b9ddac62..262d53c 100644
--- a/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils_test.html
+++ b/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils_test.html
@@ -23,12 +23,7 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../test/test-pre-setup.js"></script>
-<script type="module" src="../../test/common-test-setup.js"></script>
-<script type="module" src="./gr-display-name-utils.js"></script>
-
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import './gr-display-name-utils.js';
 suite('gr-display-name-utils tests', () => {
@@ -43,7 +38,7 @@
     const account = {
       name: 'test-name',
     };
-    assert.deepEqual(GrDisplayNameUtils.getUserName(config, account, true),
+    assert.deepEqual(GrDisplayNameUtils.getUserName(config, account),
         'test-name');
   });
 
@@ -51,7 +46,7 @@
     const account = {
       username: 'test-user',
     };
-    assert.deepEqual(GrDisplayNameUtils.getUserName(config, account, true),
+    assert.deepEqual(GrDisplayNameUtils.getUserName(config, account),
         'test-user');
   });
 
@@ -59,12 +54,12 @@
     const account = {
       email: 'test-user@test-url.com',
     };
-    assert.deepEqual(GrDisplayNameUtils.getUserName(config, account, true),
+    assert.deepEqual(GrDisplayNameUtils.getUserName(config, account),
         'test-user@test-url.com');
   });
 
   test('getUserName returns not Anonymous Coward as the anon name', () => {
-    assert.deepEqual(GrDisplayNameUtils.getUserName(config, null, true),
+    assert.deepEqual(GrDisplayNameUtils.getUserName(config, null),
         'Anonymous');
   });
 
@@ -74,7 +69,7 @@
         anonymous_coward_name: 'Test Anon',
       },
     };
-    assert.deepEqual(GrDisplayNameUtils.getUserName(config, null, true),
+    assert.deepEqual(GrDisplayNameUtils.getUserName(config, null),
         'Test Anon');
   });
 
@@ -89,13 +84,6 @@
     assert.equal(
         GrDisplayNameUtils.getAccountDisplayName(config,
             {email: 'my@example.com'}),
-        'Anonymous <my@example.com>');
-  });
-
-  test('getAccountDisplayName - account with email only - allowEmail', () => {
-    assert.equal(
-        GrDisplayNameUtils.getAccountDisplayName(config,
-            {email: 'my@example.com'}, true),
         'my@example.com <my@example.com>');
   });
 
diff --git a/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider.js b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider.js
index 67001d2..a1fd94a 100644
--- a/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider.js
+++ b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider.js
@@ -36,7 +36,7 @@
 
     makeSuggestionItem(account) {
       return {
-        name: GrDisplayNameUtils.getAccountDisplayName(null, account, true),
+        name: GrDisplayNameUtils.getAccountDisplayName(null, account),
         value: {account, count: 1},
       };
     }
diff --git a/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html
index be47863..2f2cf0b 100644
--- a/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html
+++ b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html
@@ -23,21 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../test/test-pre-setup.js"></script>
-<script type="module" src="../../test/common-test-setup.js"></script>
-<script type="module" src="../../elements/shared/gr-rest-api-interface/gr-rest-api-interface.js"></script>
-<script type="module" src="../gr-display-name-utils/gr-display-name-utils.js"></script>
-<script type="module" src="./gr-email-suggestions-provider.js"></script>
-
-
-<script type="module">
-import '../../test/test-pre-setup.js';
-import '../../test/common-test-setup.js';
-import '../../elements/shared/gr-rest-api-interface/gr-rest-api-interface.js';
-import '../gr-display-name-utils/gr-display-name-utils.js';
-import './gr-email-suggestions-provider.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -46,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import '../../elements/shared/gr-rest-api-interface/gr-rest-api-interface.js';
 import '../gr-display-name-utils/gr-display-name-utils.js';
diff --git a/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.html b/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.html
index 21c5085..dc772db 100644
--- a/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.html
+++ b/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.html
@@ -23,20 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../test/test-pre-setup.js"></script>
-<script type="module" src="../../test/common-test-setup.js"></script>
-<script type="module" src="../../elements/shared/gr-rest-api-interface/gr-rest-api-interface.js"></script>
-<script type="module" src="../gr-display-name-utils/gr-display-name-utils.js"></script>
-<script type="module" src="./gr-group-suggestions-provider.js"></script>
-
-<script type="module">
-import '../../test/test-pre-setup.js';
-import '../../test/common-test-setup.js';
-import '../../elements/shared/gr-rest-api-interface/gr-rest-api-interface.js';
-import '../gr-display-name-utils/gr-display-name-utils.js';
-import './gr-group-suggestions-provider.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -45,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import '../../elements/shared/gr-rest-api-interface/gr-rest-api-interface.js';
 import '../gr-display-name-utils/gr-display-name-utils.js';
diff --git a/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js
index fecf75aa..a47eb72 100644
--- a/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js
+++ b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js
@@ -87,7 +87,7 @@
         // Reviewer is an account suggestion from getChangeSuggestedReviewers.
         return {
           name: GrDisplayNameUtils.getAccountDisplayName(this._config,
-              suggestion.account, false),
+              suggestion.account),
           value: suggestion,
         };
       }
@@ -104,7 +104,7 @@
         // Reviewer is an account suggestion from getSuggestedAccounts.
         return {
           name: GrDisplayNameUtils.getAccountDisplayName(this._config,
-              suggestion, false),
+              suggestion),
           value: {account: suggestion, count: 1},
         };
       }
diff --git a/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html
index 7821386..da2eef4 100644
--- a/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html
+++ b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html
@@ -23,20 +23,6 @@
 
 <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 <script src="/components/wct-browser-legacy/browser.js"></script>
-<script type="module" src="../../test/test-pre-setup.js"></script>
-<script type="module" src="../../test/common-test-setup.js"></script>
-<script type="module" src="../../elements/shared/gr-rest-api-interface/gr-rest-api-interface.js"></script>
-<script type="module" src="../gr-display-name-utils/gr-display-name-utils.js"></script>
-<script type="module" src="./gr-reviewer-suggestions-provider.js"></script>
-
-<script type="module">
-import '../../test/test-pre-setup.js';
-import '../../test/common-test-setup.js';
-import '../../elements/shared/gr-rest-api-interface/gr-rest-api-interface.js';
-import '../gr-display-name-utils/gr-display-name-utils.js';
-import './gr-reviewer-suggestions-provider.js';
-void(0);
-</script>
 
 <test-fixture id="basic">
   <template>
@@ -45,7 +31,6 @@
 </test-fixture>
 
 <script type="module">
-import '../../test/test-pre-setup.js';
 import '../../test/common-test-setup.js';
 import '../../elements/shared/gr-rest-api-interface/gr-rest-api-interface.js';
 import '../gr-display-name-utils/gr-display-name-utils.js';
diff --git a/polygerrit-ui/app/scripts/util.js b/polygerrit-ui/app/scripts/util.js
index 6a8a116..e8a1d21 100644
--- a/polygerrit-ui/app/scripts/util.js
+++ b/polygerrit-ui/app/scripts/util.js
@@ -170,5 +170,51 @@
     return [...results];
   };
 
+  function getPathFromNode(el) {
+    if (!el.tagName || el.tagName === 'GR-APP'
+      || el instanceof DocumentFragment
+      || el instanceof HTMLSlotElement) {
+      return '';
+    }
+    let path = el.tagName.toLowerCase();
+    if (el.id) path += `#${el.id}`;
+    if (el.className) path += `.${el.className.replace(/ /g, '.')}`;
+    return path;
+  }
+
+  /**
+   * Retrieves the dom path of the current event.
+   *
+   * If the event object contains a `path` property, then use it,
+   * otherwise, construct the dom path based on the event target.
+   *
+   * @param {!Event} e
+   * @return {string}
+   * @example
+   *
+   * domNode.onclick = e => {
+   *  getEventPath(e); // eg: div.class1>p#pid.class2
+   * }
+   */
+  util.getEventPath = e => {
+    if (!e) return '';
+
+    let path = e.path;
+    if (!path || !path.length) {
+      path = [];
+      let el = e.target;
+      while (el) {
+        path.push(el);
+        el = el.parentNode || el.host;
+      }
+    }
+
+    return path.reduce((domPath, curEl) => {
+      const pathForEl = getPathFromNode(curEl);
+      if (!pathForEl) return domPath;
+      return domPath ? `${pathForEl}>${domPath}` : pathForEl;
+    }, '');
+  };
+
   window.util = util;
 })(window);
diff --git a/polygerrit-ui/app/scripts/util_test.html b/polygerrit-ui/app/scripts/util_test.html
new file mode 100644
index 0000000..332707e
--- /dev/null
+++ b/polygerrit-ui/app/scripts/util_test.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2020 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/components/wct-browser-legacy/browser.js"></script>
+
+<test-fixture id="basic">
+  <template>
+    <div id="test" class="a b c">
+      <a class="testBtn"></a>
+    </div>
+  </template>
+</test-fixture>
+
+<script type="module">
+  import '../test/common-test-setup.js';
+  import './util.js';
+  suite('util tests', () => {
+    suite('getEventPath', () => {
+      test('empty event', () => {
+        assert.equal(util.getEventPath(), '');
+        assert.equal(util.getEventPath(null), '');
+        assert.equal(util.getEventPath(undefined), '');
+        assert.equal(util.getEventPath({}), '');
+      });
+
+      test('event with fake path', () => {
+        assert.equal(util.getEventPath({path: []}), '');
+        assert.equal(util.getEventPath({path: [
+          {tagName: 'dd'},
+        ]}), 'dd');
+      });
+
+      test('event with fake complicated path', () => {
+        assert.equal(util.getEventPath({path: [
+          {tagName: 'dd', id: 'test', className: 'a b'},
+          {tagName: 'DIV', id: 'test2', className: 'a b c'},
+        ]}), 'div#test2.a.b.c>dd#test.a.b');
+      });
+
+      test('event with fake target', () => {
+        const fakeTargetParent2 = {
+          tagName: 'DIV', id: 'test2', className: 'a b c',
+        };
+        const fakeTargetParent1 = {
+          parentNode: fakeTargetParent2,
+          tagName: 'dd',
+          id: 'test',
+          className: 'a b',
+        };
+        const fakeTarget = {tagName: 'SPAN', parentNode: fakeTargetParent1};
+        assert.equal(
+            util.getEventPath({target: fakeTarget}),
+            'div#test2.a.b.c>dd#test.a.b>span'
+        );
+      });
+
+      test('event with real click', () => {
+        const element = fixture('basic');
+        const aLink = element.querySelector('a');
+        let path;
+        aLink.onclick = e => path = util.getEventPath(e);
+        MockInteractions.click(aLink);
+        assert.equal(
+            path,
+            'html>body>test-fixture#basic>div#test.a.b.c>a.testBtn'
+        );
+      });
+    });
+  });
+</script>
\ No newline at end of file
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index 45e5af6..5b19340 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -189,6 +189,7 @@
     'shared/gr-editable-label/gr-editable-label_test.html',
     'shared/gr-formatted-text/gr-formatted-text_test.html',
     'shared/gr-hovercard/gr-hovercard_test.html',
+    'shared/gr-hovercard-account/gr-hovercard-account_test.html',
     'shared/gr-js-api-interface/gr-annotation-actions-context_test.html',
     'shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html',
     'shared/gr-js-api-interface/gr-change-actions-js-api_test.html',
@@ -262,6 +263,7 @@
     'gr-group-suggestions-provider/gr-group-suggestions-provider_test.html',
     'gr-display-name-utils/gr-display-name-utils_test.html',
     'gr-email-suggestions-provider/gr-email-suggestions-provider_test.html',
+    'util_test.html',
   ];
   /* eslint-enable max-len */
   for (let file of scripts) {
diff --git a/polygerrit-ui/app/test/test-pre-setup.js b/polygerrit-ui/app/test/test-pre-setup.js
deleted file mode 100644
index dd317cf..0000000
--- a/polygerrit-ui/app/test/test-pre-setup.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * After M80, htmlImports has been removed and the polyfill from
- * webcomponents can only support async html imports unlike
- * native htmlImports which loads htmls synchronously.
- */
-window.readyToTest = () => Promise.race([
-  new Promise(resolve =>
-    window.addEventListener('HTMLImportsLoaded', resolve)),
-  // timeout after 5s, the test timeout is 10s
-  new Promise(resolve => setTimeout(resolve, 5000)),
-]);
diff --git a/polygerrit-ui/app/wct.conf.js b/polygerrit-ui/app/wct.conf.js
index 0e53adb..1a9300e 100644
--- a/polygerrit-ui/app/wct.conf.js
+++ b/polygerrit-ui/app/wct.conf.js
@@ -27,7 +27,7 @@
 */
 
 const headless = 'WCT_HEADLESS_MODE' in process.env ?
-  process.env['WCT_HEADLESS_MODE'] !== '0' : false;
+  process.env['WCT_HEADLESS_MODE'] === '1' : false;
 
 const headlessBrowserOptions = {
   chrome: ['start-maximized', 'headless', 'disable-gpu', 'no-sandbox'],
diff --git a/polygerrit-ui/server.go b/polygerrit-ui/server.go
index fde2f42..339812b 100644
--- a/polygerrit-ui/server.go
+++ b/polygerrit-ui/server.go
@@ -115,16 +115,16 @@
 		return
 	}
 
-	requestPath := parsedUrl.Path
+	normalizedContentPath := parsedUrl.Path
 
-	if !strings.HasPrefix(requestPath, "/") {
-		requestPath = "/" + requestPath
+	if !strings.HasPrefix(normalizedContentPath, "/") {
+		normalizedContentPath = "/" + normalizedContentPath
 	}
 
-	isJsFile := strings.HasSuffix(requestPath, ".js") || strings.HasSuffix(requestPath, ".mjs")
-	data, err := readFile(parsedUrl.Path, requestPath)
+	isJsFile := strings.HasSuffix(normalizedContentPath, ".js") || strings.HasSuffix(normalizedContentPath, ".mjs")
+	data, err := getContent(normalizedContentPath)
 	if err != nil {
-		data, err = readFile(parsedUrl.Path + ".js", requestPath + ".js")
+		data, err = getContent(normalizedContentPath + ".js")
 		if err != nil {
 			writer.WriteHeader(404)
 			return
@@ -135,13 +135,13 @@
 		moduleImportRegexp := regexp.MustCompile("(?m)^(import.*)'([^/.].*)';$")
 		data = moduleImportRegexp.ReplaceAll(data, []byte("$1 '/node_modules/$2';"))
 		writer.Header().Set("Content-Type", "application/javascript")
-	} else if strings.HasSuffix(requestPath, ".css") {
+	} else if strings.HasSuffix(normalizedContentPath, ".css") {
 		writer.Header().Set("Content-Type", "text/css")
-	} else if strings.HasSuffix(requestPath, "_test.html") {
+	} else if strings.HasSuffix(normalizedContentPath, "_test.html") {
 		moduleImportRegexp := regexp.MustCompile("(?m)^(import.*)'([^/.].*)';$")
 		data = moduleImportRegexp.ReplaceAll(data, []byte("$1 '/node_modules/$2';"))
 		writer.Header().Set("Content-Type", "text/html")
-	} else if strings.HasSuffix(requestPath, ".html") {
+	} else if strings.HasSuffix(normalizedContentPath, ".html") {
 		writer.Header().Set("Content-Type", "text/html")
 	}
 	writer.WriteHeader(200)
@@ -149,23 +149,24 @@
 	writer.Write(data)
 }
 
-func readFile(originalPath string, redirectedPath string) ([]byte, error) {
-	pathsToTry := []string{"app" + redirectedPath}
+func getContent(normalizedContentPath string) ([]byte, error) {
+  //normalizedContentPath must always starts with '/'
+	pathsToTry := []string{"app" + normalizedContentPath}
 	bowerComponentsSuffix := "/bower_components/"
 	nodeModulesPrefix := "/node_modules/"
 	testComponentsPrefix := "/components/"
 
-	if strings.HasPrefix(originalPath, testComponentsPrefix) {
-		pathsToTry = append(pathsToTry, "node_modules/wct-browser-legacy/node_modules/"+originalPath[len(testComponentsPrefix):])
-		pathsToTry = append(pathsToTry, "node_modules/"+originalPath[len(testComponentsPrefix):])
+	if strings.HasPrefix(normalizedContentPath, testComponentsPrefix) {
+		pathsToTry = append(pathsToTry, "node_modules/wct-browser-legacy/node_modules/"+normalizedContentPath[len(testComponentsPrefix):])
+		pathsToTry = append(pathsToTry, "node_modules/"+normalizedContentPath[len(testComponentsPrefix):])
 	}
 
-	if strings.HasPrefix(originalPath, bowerComponentsSuffix) {
-		pathsToTry = append(pathsToTry, "node_modules/@webcomponents/"+originalPath[len(bowerComponentsSuffix):])
+	if strings.HasPrefix(normalizedContentPath, bowerComponentsSuffix) {
+		pathsToTry = append(pathsToTry, "node_modules/@webcomponents/"+normalizedContentPath[len(bowerComponentsSuffix):])
 	}
 
-	if strings.HasPrefix(originalPath, nodeModulesPrefix) {
-		pathsToTry = append(pathsToTry, "node_modules/"+originalPath[len(nodeModulesPrefix):])
+	if strings.HasPrefix(normalizedContentPath, nodeModulesPrefix) {
+		pathsToTry = append(pathsToTry, "node_modules/"+normalizedContentPath[len(nodeModulesPrefix):])
 	}
 
 	for _, path := range pathsToTry {
diff --git a/proto/cache.proto b/proto/cache.proto
index 5fc5e68..c80d51b 100644
--- a/proto/cache.proto
+++ b/proto/cache.proto
@@ -208,17 +208,17 @@
   }
   repeated AssigneeStatusUpdateProto assignee_update = 22;
 
-  // An update to the attention set of the change. See class AttentionStatus for
-  // context.
-  message AttentionStatusProto {
+  // An update to the attention set of the change. See class AttentionSetUpdate
+  // for context.
+  message AttentionSetUpdateProto {
     // Epoch millis.
     int64 timestamp_millis = 1;
     int32 account = 2;
-    // Maps to enum AttentionStatus.Operation
+    // Maps to enum AttentionSetUpdate.Operation
     string operation = 3;
     string reason = 4;
   }
-  repeated AttentionStatusProto attention_status = 23;
+  repeated AttentionSetUpdateProto attention_set_update = 23;
 }
 
 // Serialized form of com.google.gerrit.server.query.change.ConflictKey