Merge "Revert "Make ExternalIdCache public for plugins""
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 4d9213e..2c68bc8 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -83,6 +83,7 @@
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
@@ -216,6 +217,7 @@
   @Inject protected ProjectCache projectCache;
   @Inject protected Provider<InternalChangeQuery> queryProvider;
   @Inject protected PushOneCommit.Factory pushFactory;
+  @Inject protected PluginConfigFactory pluginConfig;
   @Inject protected Revisions revisions;
   @Inject protected SystemGroupBackend systemGroupBackend;
   @Inject protected TestNotesMigration notesMigration;
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java
index 48a166c..0aa56cf 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java
@@ -17,8 +17,11 @@
 import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
+import java.lang.annotation.Annotation;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 import org.eclipse.jgit.lib.Config;
 
 class ConfigAnnotationParser {
@@ -42,6 +45,67 @@
     return cfg;
   }
 
+  static class GlobalPluginConfigToGerritConfig implements GerritConfig {
+    private final GlobalPluginConfig delegate;
+
+    GlobalPluginConfigToGerritConfig(GlobalPluginConfig delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    public Class<? extends Annotation> annotationType() {
+      return delegate.annotationType();
+    }
+
+    @Override
+    public String name() {
+      return delegate.name();
+    }
+
+    @Override
+    public String value() {
+      return delegate.value();
+    }
+
+    @Override
+    public String[] values() {
+      return delegate.values();
+    }
+  }
+
+  static Map<String, Config> parse(GlobalPluginConfig annotation) {
+    if (annotation == null) {
+      return null;
+    }
+    Map<String, Config> result = new HashMap<>();
+    Config cfg = new Config();
+    parseAnnotation(cfg, new GlobalPluginConfigToGerritConfig(annotation));
+    result.put(annotation.pluginName(), cfg);
+    return result;
+  }
+
+  static Map<String, Config> parse(GlobalPluginConfigs annotation) {
+    if (annotation == null || annotation.value().length < 1) {
+      return null;
+    }
+
+    HashMap<String, Config> result = new HashMap<>();
+
+    for (GlobalPluginConfig c : annotation.value()) {
+      String pluginName = c.pluginName();
+      Config config;
+      if (result.containsKey(pluginName)) {
+        config = result.get(pluginName);
+      } else {
+        config = new Config();
+        result.put(pluginName, config);
+      }
+      parseAnnotation(config, new GlobalPluginConfigToGerritConfig(c));
+    }
+
+    return result;
+  }
+
   private static void parseAnnotation(Config cfg, GerritConfig c) {
     ArrayList<String> l = Lists.newArrayList(splitter.split(c.name()));
     if (l.size() == 2) {
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritConfig.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritConfig.java
index 54ff9ff..44323e4 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritConfig.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritConfig.java
@@ -25,9 +25,22 @@
 @Retention(RUNTIME)
 @Repeatable(GerritConfigs.class)
 public @interface GerritConfig {
+  /**
+   * Setting name in the form {@code "section.name"} or {@code "section.subsection.name"} where
+   * {@code section}, {@code subsection} and {@code name} correspond to the parameters of the same
+   * names in JGit's {@code Config#getString} method.
+   *
+   * @see org.eclipse.jgit.lib.Config#getString(String, String, String)
+   */
   String name();
 
+  /**
+   * Single value. Takes precedence over values specified in {@code values}.
+   */
   String value() default "";
 
+  /**
+   * Multiple values (list). Ignored if {@code value} is specified.
+   */
   String[] values() default "";
 }
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritServer.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritServer.java
index 1e741a8..b31b17b 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritServer.java
@@ -44,6 +44,8 @@
 import java.net.InetSocketAddress;
 import java.net.URI;
 import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.BrokenBarrierException;
 import java.util.concurrent.CyclicBarrier;
 import java.util.concurrent.ExecutorService;
@@ -68,7 +70,9 @@
           has(Sandboxed.class, testDesc.getTestClass()),
           has(UseSsh.class, testDesc.getTestClass()),
           null, // @GerritConfig is only valid on methods.
-          null); // @GerritConfigs is only valid on methods.
+          null, // @GerritConfigs is only valid on methods.
+          null, // @GlobalPluginConfig is only valid on methods.
+          null); // @GlobalPluginConfigs is only valid on methods.
     }
 
     static Description forTestMethod(org.junit.runner.Description testDesc, String configName) {
@@ -83,7 +87,9 @@
           testDesc.getAnnotation(UseSsh.class) != null
               || has(UseSsh.class, testDesc.getTestClass()),
           testDesc.getAnnotation(GerritConfig.class),
-          testDesc.getAnnotation(GerritConfigs.class));
+          testDesc.getAnnotation(GerritConfigs.class),
+          testDesc.getAnnotation(GlobalPluginConfig.class),
+          testDesc.getAnnotation(GlobalPluginConfigs.class));
     }
 
     private static boolean has(Class<? extends Annotation> annotation, Class<?> clazz) {
@@ -114,10 +120,26 @@
     @Nullable
     abstract GerritConfigs configs();
 
-    private Config buildConfig(Config baseConfig) {
+    @Nullable
+    abstract GlobalPluginConfig pluginConfig();
+
+    @Nullable
+    abstract GlobalPluginConfigs pluginConfigs();
+
+    private void checkValidAnnotations() {
       if (configs() != null && config() != null) {
         throw new IllegalStateException("Use either @GerritConfigs or @GerritConfig not both");
       }
+      if (pluginConfigs() != null && pluginConfig() != null) {
+        throw new IllegalStateException(
+            "Use either @GlobalPluginConfig or @GlobalPluginConfigs not both");
+      }
+      if ((pluginConfigs() != null || pluginConfig() != null) && memory()) {
+        throw new IllegalStateException("Must use @UseLocalDisk with @GlobalPluginConfig(s)");
+      }
+    }
+
+    private Config buildConfig(Config baseConfig) {
       if (configs() != null) {
         return ConfigAnnotationParser.parse(baseConfig, configs());
       } else if (config() != null) {
@@ -126,10 +148,20 @@
         return baseConfig;
       }
     }
+
+    private Map<String, Config> buildPluginConfigs() {
+      if (pluginConfigs() != null) {
+        return ConfigAnnotationParser.parse(pluginConfigs());
+      } else if (pluginConfig() != null) {
+        return ConfigAnnotationParser.parse(pluginConfig());
+      }
+      return new HashMap<>();
+    }
   }
 
   /** Returns fully started Gerrit server */
   static GerritServer start(Description desc, Config baseConfig) throws Exception {
+    desc.checkValidAnnotations();
     Config cfg = desc.buildConfig(baseConfig);
     Logger.getLogger("com.google.gerrit").setLevel(Level.DEBUG);
     CyclicBarrier serverStarted = new CyclicBarrier(2);
@@ -164,7 +196,7 @@
           ImmutableList.<Module>of(new InMemoryTestingDatabaseModule(cfg)));
       daemon.start();
     } else {
-      site = initSite(cfg);
+      site = initSite(cfg, desc.buildPluginConfigs());
       daemonService = Executors.newSingleThreadExecutor();
       @SuppressWarnings("unused")
       Future<?> possiblyIgnoredError =
@@ -189,7 +221,7 @@
     return new GerritServer(desc, i, daemon, daemonService);
   }
 
-  private static File initSite(Config base) throws Exception {
+  private static File initSite(Config base, Map<String, Config> pluginConfigs) throws Exception {
     File tmp = TempFileUtil.createTempDirectory();
     Init init = new Init();
     int rc =
@@ -207,6 +239,16 @@
     cfg.merge(base);
     mergeTestConfig(cfg);
     cfg.save();
+
+    for (String pluginName : pluginConfigs.keySet()) {
+      MergeableFileBasedConfig pluginCfg =
+          new MergeableFileBasedConfig(
+              new File(new File(tmp, "etc"), pluginName + ".config"), FS.DETECTED);
+      pluginCfg.load();
+      pluginCfg.merge(pluginConfigs.get(pluginName));
+      pluginCfg.save();
+    }
+
     return tmp;
   }
 
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GlobalPluginConfig.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GlobalPluginConfig.java
new file mode 100644
index 0000000..3337f68
--- /dev/null
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GlobalPluginConfig.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2017 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;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Target({METHOD})
+@Retention(RUNTIME)
+@Repeatable(GlobalPluginConfigs.class)
+public @interface GlobalPluginConfig {
+  /**
+   * Name of the plugin, corresponding to {@code $site/etc/@pluginName.comfig}.
+   */
+  String pluginName();
+
+  /**
+   * @see GerritConfig#name()
+   */
+  String name();
+
+  /**
+   * @see GerritConfig#value()
+   */
+  String value() default "";
+
+  /**
+   * @see GerritConfig#values()
+   */
+  String[] values() default "";
+}
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GlobalPluginConfigs.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GlobalPluginConfigs.java
new file mode 100644
index 0000000..dfcf955
--- /dev/null
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GlobalPluginConfigs.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2017 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;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Target({METHOD})
+@Retention(RUNTIME)
+public @interface GlobalPluginConfigs {
+  GlobalPluginConfig[] value();
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/annotation/UseGerritConfigAnnotationTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/annotation/UseGerritConfigAnnotationTest.java
index 12fa3cc..451af0f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/annotation/UseGerritConfigAnnotationTest.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/annotation/UseGerritConfigAnnotationTest.java
@@ -22,25 +22,60 @@
 
 public class UseGerritConfigAnnotationTest extends AbstractDaemonTest {
   @Test
-  @GerritConfig(name = "x.y", value = "z")
+  @GerritConfig(name = "section.name", value = "value")
   public void testOne() {
-    assertThat(cfg.getString("x", null, "y")).isEqualTo("z");
+    assertThat(cfg.getString("section", null, "name")).isEqualTo("value");
   }
 
   @Test
-  @GerritConfig(name = "x.y", value = "z")
-  @GerritConfig(name = "a.b", value = "c")
+  @GerritConfig(name = "section.subsection.name", value = "value")
+  public void testOneWithSubsection() {
+    assertThat(cfg.getString("section", "subsection", "name")).isEqualTo("value");
+  }
+
+  @Test
+  @GerritConfig(name = "section.name", value = "value")
+  @GerritConfig(name = "section1.name", value = "value1")
+  @GerritConfig(name = "section.subsection.name", value = "value")
+  @GerritConfig(name = "section.subsection1.name", value = "value1")
   public void testMultiple() {
-    assertThat(cfg.getString("x", null, "y")).isEqualTo("z");
-    assertThat(cfg.getString("a", null, "b")).isEqualTo("c");
+    assertThat(cfg.getString("section", null, "name")).isEqualTo("value");
+    assertThat(cfg.getString("section1", null, "name")).isEqualTo("value1");
+    assertThat(cfg.getString("section", "subsection", "name")).isEqualTo("value");
+    assertThat(cfg.getString("section", "subsection1", "name")).isEqualTo("value1");
   }
 
   @Test
   @GerritConfig(
-    name = "x.y",
-    values = {"a", "b"}
+    name = "section.name",
+    values = {"value-1", "value-2"}
   )
   public void testList() {
-    assertThat(cfg.getStringList("x", null, "y")).asList().containsExactly("a", "b");
+    assertThat(cfg.getStringList("section", null, "name"))
+        .asList()
+        .containsExactly("value-1", "value-2");
+  }
+
+  @Test
+  @GerritConfig(
+    name = "section.subsection.name",
+    values = {"value-1", "value-2"}
+  )
+  public void testListWithSubsection() {
+    assertThat(cfg.getStringList("section", "subsection", "name"))
+        .asList()
+        .containsExactly("value-1", "value-2");
+  }
+
+  @Test
+  @GerritConfig(
+    name = "section.name",
+    value = "value-1",
+    values = {"value-2", "value-3"}
+  )
+  public void valueHasPrecedenceOverValues() {
+    assertThat(cfg.getStringList("section", null, "name"))
+        .asList()
+        .containsExactly("value-1");
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/annotation/UseGlobalPluginConfigAnnotationTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/annotation/UseGlobalPluginConfigAnnotationTest.java
new file mode 100644
index 0000000..eaa0a95d
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/annotation/UseGlobalPluginConfigAnnotationTest.java
@@ -0,0 +1,94 @@
+// Copyright (C) 2017 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.annotation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.GlobalPluginConfig;
+import com.google.gerrit.acceptance.UseLocalDisk;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+
+public class UseGlobalPluginConfigAnnotationTest extends AbstractDaemonTest {
+  private Config cfg() {
+    return pluginConfig.getGlobalPluginConfig("test");
+  }
+
+  @Test
+  @UseLocalDisk
+  @GlobalPluginConfig(pluginName = "test", name = "section.name", value = "value")
+  public void testOne() {
+    assertThat(cfg().getString("section", null, "name")).isEqualTo("value");
+  }
+
+  @Test
+  @UseLocalDisk
+  @GlobalPluginConfig(pluginName = "test", name = "section.subsection.name", value = "value")
+  public void testOneWithSubsection() {
+    assertThat(cfg().getString("section", "subsection", "name")).isEqualTo("value");
+  }
+
+  @Test
+  @UseLocalDisk
+  @GlobalPluginConfig(pluginName = "test", name = "section.name", value = "value")
+  @GlobalPluginConfig(pluginName = "test", name = "section1.name", value = "value1")
+  @GlobalPluginConfig(pluginName = "test", name = "section.subsection.name", value = "value")
+  @GlobalPluginConfig(pluginName = "test", name = "section.subsection1.name", value = "value1")
+  public void testMultiple() {
+    assertThat(cfg().getString("section", null, "name")).isEqualTo("value");
+    assertThat(cfg().getString("section1", null, "name")).isEqualTo("value1");
+    assertThat(cfg().getString("section", "subsection", "name")).isEqualTo("value");
+    assertThat(cfg().getString("section", "subsection1", "name")).isEqualTo("value1");
+  }
+
+  @Test
+  @UseLocalDisk
+  @GlobalPluginConfig(
+    pluginName = "test",
+    name = "section.name",
+    values = {"value-1", "value-2"}
+  )
+  public void testList() {
+    assertThat(cfg().getStringList("section", null, "name"))
+        .asList()
+        .containsExactly("value-1", "value-2");
+  }
+
+  @Test
+  @UseLocalDisk
+  @GlobalPluginConfig(
+    pluginName = "test",
+    name = "section.subsection.name",
+    values = {"value-1", "value-2"}
+  )
+  public void testListWithSubsection() {
+    assertThat(cfg().getStringList("section", "subsection", "name"))
+        .asList()
+        .containsExactly("value-1", "value-2");
+  }
+
+  @Test
+  @UseLocalDisk
+  @GlobalPluginConfig(
+    pluginName = "test",
+    name = "section.name",
+    value = "value-1",
+    values = {"value-2", "value-3"}
+  )
+  public void valueHasPrecedenceOverValues() {
+    assertThat(cfg().getStringList("section", null, "name")).asList().containsExactly("value-1");
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
index 9160932..e51312b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
@@ -226,8 +226,6 @@
             });
         bu.execute();
       }
-
-      indexer.index(db.get(), inserter.getChange());
     }
   }
 
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index 79ede8f..b292477 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -157,6 +157,7 @@
     size = "large",
     srcs = ["wct_test.sh"],
     data = [
+        "test/common-test-setup.html",
         "test/index.html",
         ":pg_code.zip",
         ":test_components.zip",
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 a42d804..99efede 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
@@ -20,8 +20,7 @@
 
 <script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
 <script src="../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../test/common-test-setup.html"/>
 <script>
   /** @type {String} */
   window.CANONICAL_PATH = '/r';
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 65908f2..19837cc 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
@@ -20,8 +20,7 @@
 
 <script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
 <script src="../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../test/common-test-setup.html"/>
 <link rel="import" href="gr-change-table-behavior.html">
 
 <test-fixture id="basic">
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 7ebb17a..0cf7bb4 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
@@ -16,9 +16,9 @@
 <!-- Polymer included for the html import polyfill. -->
 <script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
 <script src="../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../test/common-test-setup.html"/>
 <title>gr-patch-set-behavior</title>
 
-<link rel="import" href="../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="gr-patch-set-behavior.html">
 
 <script>
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 848c744..37772d1 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
@@ -16,9 +16,9 @@
 <!-- Polymer included for the html import polyfill. -->
 <script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
 <script src="../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../test/common-test-setup.html"/>
 <title>gr-path-list-behavior</title>
 
-<link rel="import" href="../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="gr-path-list-behavior.html">
 
 <script>
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 5fc9d54..f442c43 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
@@ -19,8 +19,7 @@
 
 <script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
 <script src="../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../test/common-test-setup.html"/>
 <link rel="import" href="gr-tooltip-behavior.html">
 
 <script>void(0);</script>
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 da04c37..6906ea2 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
@@ -20,8 +20,7 @@
 
 <script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
 <script src="../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../test/common-test-setup.html"/>
 <link rel="import" href="keyboard-shortcut-behavior.html">
 
 <test-fixture id="basic">
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 7a8376e..87fe67b 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
@@ -20,12 +20,12 @@
 
 <script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
 <script src="../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../test/common-test-setup.html"/>
 <script>
   /** @type {String} */
   window.CANONICAL_PATH = '/r';
 </script>
 
-<link rel="import" href="../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="../base-url-behavior/base-url-behavior.html">
 <link rel="import" href="rest-client-behavior.html">
 
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.html b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.html
new file mode 100644
index 0000000..ea0136f
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.html
@@ -0,0 +1,56 @@
+<!--
+Copyright (C) 2017 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.
+-->
+
+<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
+<link rel="import" href="../../../behaviors/gr-url-encoding-behavior.html">
+<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-list-view/gr-list-view.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+
+<link rel="import" href="../../../styles/shared-styles.html">
+
+<dom-module id="gr-admin-group-list">
+  <template>
+    <style include="shared-styles"></style>
+    <gr-list-view
+        filter="[[_filter]]"
+        items="[[_groups]]"
+        items-per-page="[[_groupsPerPage]]"
+        loading="[[_loading]]"
+        offset="[[_offset]]"
+        path="/admin/groups">
+      <table id="list">
+        <tr class="headerRow">
+          <th class="name topHeader">Group Name</th>
+          <th class="description topHeader">Group Description</th>
+          <th class="visibleToAll topHeader">Visible To All</th>
+        </tr>
+        <template is="dom-repeat" items="[[_shownGroups]]">
+          <tr class="table">
+            <td class="name">
+              <a href$="[[_getUrl(item.group_id)]]">[[item.name]]</a>
+            </td>
+            <td class="description">[[item.description]]</td>
+            <td class="visibleToAll">[[_visibleToAll(item)]]</td>
+          </tr>
+        </template>
+      </table>
+    </gr-list-view>
+    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+  </template>
+  <script src="gr-admin-group-list.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js
new file mode 100644
index 0000000..295aae2
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js
@@ -0,0 +1,113 @@
+// Copyright (C) 2017 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.
+(function() {
+  'use strict';
+
+  Polymer({
+    is: 'gr-admin-group-list',
+
+    properties: {
+      /**
+       * URL params passed from the router.
+       */
+      params: {
+        type: Object,
+        observer: '_paramsChanged',
+      },
+
+      /**
+       * Offset of currently visible query results.
+       */
+      _offset: Number,
+
+      _groups: Array,
+
+      /**
+       * Because  we request one more than the groupsPerPage, _shownGroups
+       * may be one less than _groups.
+       * */
+      _shownGroups: {
+        type: Array,
+        computed: '_computeShownGroups(_groups)',
+      },
+
+      _groupsPerPage: {
+        type: Number,
+        value: 25,
+      },
+
+      _loading: {
+        type: Boolean,
+        value: true,
+      },
+      _filter: String,
+    },
+
+    behaviors: [
+      Gerrit.BaseUrlBehavior,
+      Gerrit.URLEncodingBehavior,
+    ],
+
+    listeners: {
+      'next-page': '_handleNextPage',
+      'previous-page': '_handlePreviousPage',
+    },
+
+    _paramsChanged(value) {
+      this._loading = true;
+
+      if (value) {
+        this._filter = value.filter || null;
+      }
+
+      if (value && value.offset) {
+        this._offset = value.offset;
+      } else {
+        this._offset = 0;
+      }
+      return this._getGroups(this._filter, this._groupsPerPage,
+          this._offset);
+    },
+
+    _getGroups(filter, groupsPerPage, offset) {
+      return this.$.restAPI.getGroups(filter, groupsPerPage, offset)
+          .then(groups => {
+            if (!groups) {
+              this._groups = [];
+              return;
+            }
+            this._groups = Object.keys(groups)
+             .map(key => {
+               const group = groups[key];
+               group.name = key;
+               return group;
+             });
+            this._loading = false;
+          });
+    },
+
+    _visibleToAll(item) {
+      return item.options.visible_to_all === true ? 'Y' : 'N';
+    },
+
+    _getUrl(item) {
+      return this.getBaseUrl() + '/admin/groups/' +
+          this.encodeURL(item, true);
+    },
+
+    _computeShownGroups(groups) {
+      return groups.slice(0, 25);
+    },
+  });
+})();
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
new file mode 100644
index 0000000..8b969f7
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html
@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2017 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-admin-group-list</title>
+
+<script src="../../../bower_components/page/page.js"></script>
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+
+<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+
+<link rel="import" href="gr-admin-group-list.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+  <template>
+    <gr-admin-group-list></gr-admin-group-list>
+  </template>
+</test-fixture>
+
+<script>
+  let counter = 0;
+  const groupGenerator = () => {
+    return {
+      name: `test${++counter}`,
+      id: '59b92f35489e62c80d1ab1bf0c2d17843038df8b',
+      url: '#/admin/groups/uuid-59b92f35489e62c80d1ab1bf0c2d17843038df8b',
+      options: {
+        visible_to_all: false,
+      },
+      description: 'Gerrit Site Administrators',
+      group_id: 1,
+      owner: 'Administrators',
+      owner_id: '7ca042f4d5847936fcb90ca91057673157fd06fc',
+    };
+  };
+
+  suite('gr-admin-group-list tests', () => {
+    let element;
+    let groups;
+    let sandbox;
+    let value;
+
+    setup(() => {
+      sandbox = sinon.sandbox.create();
+      element = fixture('basic');
+    });
+
+    teardown(() => {
+      sandbox.restore();
+    });
+
+    suite('list with groups', () => {
+      setup(done => {
+        groups = _.times(26, groupGenerator);
+
+        stub('gr-rest-api-interface', {
+          getGroups(num, offset) {
+            return Promise.resolve(groups);
+          },
+        });
+
+        element._paramsChanged(value).then(() => { flush(done); });
+      });
+
+      test('test for test group in the list', done => {
+        flush(() => {
+          assert.equal(element._groups[1].name, 'test2');
+          assert.equal(element._groups[1].options.visible_to_all, false);
+          done();
+        });
+      });
+
+      test('_shownGroups', () => {
+        assert.equal(element._shownGroups.length, 25);
+      });
+    });
+
+    suite('test with less then 25 groups', () => {
+      setup(done => {
+        groups = _.times(25, groupGenerator);
+
+        stub('gr-rest-api-interface', {
+          getGroups(num, offset) {
+            return Promise.resolve(groups);
+          },
+        });
+
+        element._paramsChanged(value).then(() => { flush(done); });
+      });
+
+      test('_shownGroups', () => {
+        assert.equal(element._shownGroups.length, 25);
+      });
+    });
+
+    suite('filter', () => {
+      test('_paramsChanged', done => {
+        sandbox.stub(element.$.restAPI, 'getGroups', () => {
+          return Promise.resolve(groups);
+        });
+        const value = {
+          filter: 'test',
+          offset: 25,
+        };
+        element._paramsChanged(value).then(() => {
+          assert.isTrue(element.$.restAPI.getGroups.lastCall
+              .calledWithExactly('test', 25, 25));
+          done();
+        });
+      });
+    });
+  });
+</script>
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.html b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.html
index 1adc739..0d3fe90 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.html
@@ -18,112 +18,49 @@
 <link rel="import" href="../../../behaviors/gr-url-encoding-behavior.html">
 <link rel="import" href="../../../bower_components/iron-input/iron-input.html">
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-list-view/gr-list-view.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 
-<link rel="import" href="../../../styles/gr-form-styles.html">
 <link rel="import" href="../../../styles/shared-styles.html">
 
 
 <dom-module id="gr-admin-project-list">
   <template>
-    <style include="shared-styles">
-      :host {
-        display: flex;
-        flex-direction: column;
-      }
-      tr.project-table {
-        border-bottom: 1px solid #eee;
-      }
-      #projectList {
-        border-collapse: collapse;
-        width: 100%;
-      }
-      #filterContainer {
-        margin: 1em;
-      }
-      #projectFilter {
-        font-size: 1em;
-        max-width: 25em;
-      }
-      td {
-        flex-shrink: 0;
-        padding: .3em .5em;
-      }
-      th {
-        background-color: #ddd;
-        border-bottom: 1px solid #eee;
-        font-weight: bold;
-        padding: .3em .5em;
-        text-align: left;
-      }
-      a {
-        color: var(--default-text-color);
-        text-decoration: none;
-      }
-      a:hover {
-        text-decoration: underline;
-      }
-      nav {
-        padding: .5em 0;
-        text-align: center;
-      }
-      nav a {
-        display: inline-block;
-      }
-      nav a:first-of-type {
-        margin-right: .5em;
-      }
-      .description {
-        width: 70%;
-      }
-      .loading {
-        color: #666;
-        padding: 1em var(--default-horizontal-margin);
-      }
-    </style>
-    <div id="filterContainer">
-      <label>Filter:</label>
-      <input is="iron-input"
-          type="text"
-          id="projectFilter"
-          bind-value="{{_filter}}"
-          on-input="_onValueChange">
-    </div>
-    <table id="projectList">
-      <tr class="headerRow">
-        <th class="name topHeader">Project Name</th>
-        <th class="description topHeader">Project Description</th>
-        <th class="repositoryBrowser topHeader">Repository Browser</th>
-        <th class="readOnly topHeader">Read only</th>
-      </tr>
-      <tr class="loading" hidden$="[[!_loading]]" hidden><td>Loading...</td></tr>
-      <template is="dom-repeat" items="[[_shownProjects]]" hidden$="[[_loading]]" hidden>
-        <tr class="project-table">
-          <td class="name">
-            <a href$="[[_getUrl(item.name)]]">[[item.name]]</a>
-          </td>
-          <td class="description">[[item.description]]</td>
-          <td class="repositoryBrowser">
-            <template is="dom-repeat"
-                items="[[_computeWeblink(item)]]" as="link">
-              <a href$="[[link.url]]" class="webLink" rel="noopener" target="_blank">
-                ([[link.name]])
-              </a>
-            </template>
-          </td>
-          <td class="readOnly">[[_readOnly(item)]]</td>
+    <style include="shared-styles"></style>
+    <gr-list-view
+        filter="[[_filter]]"
+        items-per-page="[[_projectsPerPage]]"
+        items="[[_projects]]"
+        loading="[[_loading]]"
+        offset="[[_offset]]"
+        path="/admin/projects">
+      <table id="list">
+        <tr class="headerRow">
+          <th class="name topHeader">Project Name</th>
+          <th class="description topHeader">Project Description</th>
+          <th class="repositoryBrowser topHeader">Repository Browser</th>
+          <th class="readOnly topHeader">Read only</th>
         </tr>
-      </template>
-    </table>
-    <nav>
-      <a id="prevArrow"
-          href$="[[_computeNavLink(_offset, -1, _projectsPerPage, _filter)]]"
-          hidden$="[[_hidePrevArrow(_offset)]]" hidden>&larr; Prev</a>
-      <a id="nextArrow"
-          href$="[[_computeNavLink(_offset, 1, _projectsPerPage, _filter)]]"
-          hidden$="[[_hideNextArrow(_loading, _projects)]]" hidden>
-        Next &rarr;</a>
-    </nav>
+        <tr class="loading" hidden$="[[!_loading]]" hidden><td>Loading...</td></tr>
+        <template is="dom-repeat" items="[[_shownProjects]]" hidden$="[[_loading]]" hidden>
+          <tr class="table">
+            <td class="name">
+              <a href$="[[_getUrl(item.name)]]">[[item.name]]</a>
+            </td>
+            <td class="description">[[item.description]]</td>
+            <td class="repositoryBrowser">
+              <template is="dom-repeat"
+                  items="[[_computeWeblink(item)]]" as="link">
+                <a href$="[[link.url]]" class="webLink" rel="noopener" target="_blank">
+                  ([[link.name]])
+                </a>
+              </template>
+            </td>
+            <td class="readOnly">[[_readOnly(item)]]</td>
+          </tr>
+        </template>
+      </table>
+    </gr-list-view>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
   </template>
   <script src="gr-admin-project-list.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.js b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.js
index 9aacccf..e7671d5 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.js
@@ -14,8 +14,6 @@
 (function() {
   'use strict';
 
-  const REQUEST_DEBOUNCE_INTERVAL_MS = 200;
-
   Polymer({
     is: 'gr-admin-project-list',
 
@@ -61,21 +59,6 @@
       Gerrit.URLEncodingBehavior,
     ],
 
-    listeners: {
-      'next-page': '_handleNextPage',
-      'previous-page': '_handlePreviousPage',
-    },
-
-    _onValueChange(e) {
-      this.debounce('reload', () => {
-        if (e.target.value) {
-          return page.show('/admin/projects/q/filter:' +
-              this.encodeURL(e.target.value, false));
-        }
-        page.show('/admin/projects');
-      }, REQUEST_DEBOUNCE_INTERVAL_MS);
-    },
-
     _paramsChanged(value) {
       this._loading = true;
 
@@ -119,7 +102,6 @@
           this.encodeURL(item, true);
     },
 
-
     _computeWeblink(project) {
       if (!project.web_links) {
         return '';
@@ -128,34 +110,8 @@
       return webLinks.length ? webLinks : null;
     },
 
-    _computeNavLink(offset, direction, projectsPerPage, filter) {
-      // Offset could be a string when passed from the router.
-      offset = +(offset || 0);
-      const newOffset = Math.max(0, offset + (projectsPerPage * direction));
-      let href = this.getBaseUrl() + '/admin/projects';
-      if (filter) {
-        href += '/q/filter:' + filter;
-      }
-      if (newOffset > 0) {
-        href += ',' + newOffset;
-      }
-      return href;
-    },
-
     _computeShownProjects(projects) {
       return projects.slice(0, 25);
     },
-
-    _hidePrevArrow(offset) {
-      return offset === 0;
-    },
-
-    _hideNextArrow(loading, projects) {
-      let lastPage = false;
-      if (projects.length < this._projectsPerPage + 1) {
-        lastPage = true;
-      }
-      return loading || lastPage || !projects || !projects.length;
-    },
   });
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list_test.html b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list_test.html
index 4b43b34..ff8571b 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list_test.html
@@ -21,9 +21,7 @@
 <script src="../../../bower_components/page/page.js"></script>
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
-
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-admin-project-list.html">
 
 <script>void(0);</script>
@@ -84,31 +82,6 @@
         });
       });
 
-      test('test next button', done => {
-        flush(() => {
-          let loading;
-          assert.isFalse(element._hideNextArrow(loading, projects));
-          loading = true;
-          assert.isTrue(element._hideNextArrow(loading, projects));
-          loading = false;
-          assert.isFalse(element._hideNextArrow(loading, projects));
-          element._projects = [];
-          assert.isTrue(element._hideNextArrow(loading, element._projects));
-          projects = _.times(4, projectGenerator);
-          assert.isTrue(element._hideNextArrow(loading, projects));
-          done();
-        });
-      });
-
-      test('test for prev button', () => {
-        flush(() => {
-          let offset = 0;
-          assert.isTrue(element._hidePrevArrow(offset));
-          offset = 5;
-          assert.isFalse(element._hidePrevArrow(offset));
-        });
-      });
-
       test('_shownProjects', () => {
         assert.equal(element._shownProjects.length, 25);
       });
@@ -127,33 +100,12 @@
         element._paramsChanged(value).then(() => { flush(done); });
       });
 
-      test('test next button', done => {
-        flush(() => {
-          let loading;
-          assert.isTrue(element._hideNextArrow(loading, projects));
-          projects = _.times(1, projectGenerator);
-          assert.isTrue(element._hideNextArrow(loading, projects));
-          projects = _.times(26, projectGenerator);
-          assert.isFalse(element._hideNextArrow(loading, projects));
-          done();
-        });
-      });
-
       test('_shownProjects', () => {
         assert.equal(element._shownProjects.length, 25);
       });
     });
 
     suite('filter', () => {
-      test('_onValueChange', done => {
-        sandbox.stub(page, 'show', url => {
-          assert.equal(url, '/admin/projects/q/filter:test');
-          done();
-        });
-        const e = {target: {value: 'test'}};
-        element._onValueChange(e);
-      });
-
       test('_paramsChanged', done => {
         sandbox.stub(element.$.restAPI, 'getProjects', () => {
           return Promise.resolve(projects);
@@ -168,30 +120,6 @@
           done();
         });
       });
-
-      test('_computeNavLink', () => {
-        const offset = 25;
-        const projectsPerPage = 25;
-        const filter = 'test';
-
-        sandbox.stub(element, 'getBaseUrl', () => '');
-
-        assert.equal(
-            element._computeNavLink(offset, 1, projectsPerPage, filter),
-            '/admin/projects/q/filter:test,50');
-
-        assert.equal(
-            element._computeNavLink(offset, -1, projectsPerPage, filter),
-            '/admin/projects/q/filter:test');
-
-        assert.equal(
-            element._computeNavLink(offset, 1, projectsPerPage, null),
-            '/admin/projects,50');
-
-        assert.equal(
-            element._computeNavLink(offset, -1, projectsPerPage, null),
-            '/admin/projects');
-      });
     });
   });
 </script>
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-project/gr-admin-project.js b/polygerrit-ui/app/elements/admin/gr-admin-project/gr-admin-project.js
index 3557aa9..7654215 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-project/gr-admin-project.js
+++ b/polygerrit-ui/app/elements/admin/gr-admin-project/gr-admin-project.js
@@ -48,6 +48,7 @@
 
     properties: {
       params: Object,
+      project: String,
 
       _configChanged: {
         type: Boolean,
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-project/gr-admin-project_test.html b/polygerrit-ui/app/elements/admin/gr-admin-project/gr-admin-project_test.html
index beb1e34..e19deb9 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-project/gr-admin-project_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-project/gr-admin-project_test.html
@@ -20,9 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
-
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-admin-project.html">
 
 <script>void(0);</script>
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 6036293..cf55c5c 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
@@ -20,9 +20,9 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../scripts/util.js"></script>
 
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="gr-change-list-item.html">
 
 <script>void(0);</script>
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 a6d798d..e60907b 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
@@ -21,8 +21,7 @@
 <script src="../../../bower_components/page/page.js"></script>
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-change-list-view.html">
 
 <script>void(0);</script>
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 62b09d6..aba32b03 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
@@ -20,9 +20,9 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../bower_components/page/page.js"></script>
 
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="gr-change-list.html">
 
 <script>void(0);</script>
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 8a3695b..c67a50b 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
@@ -20,7 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-dashboard-view.html">
 
 <script>void(0);</script>
diff --git a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html
index 54fde09..55213c1 100644
--- a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html
+++ b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html
@@ -20,9 +20,9 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../scripts/util.js"></script>
 
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="gr-account-entry.html">
 
 <script>void(0);</script>
diff --git a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html
index 5520254..a3a3c64 100644
--- a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-account-list.html">
 
 <script>void(0);</script>
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 8aa6bab..4e32116 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
@@ -20,9 +20,9 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../scripts/util.js"></script>
 
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="gr-change-actions.html">
 
 <script>void(0);</script>
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 c162794..b347890 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="../../plugins/gr-plugin-host/gr-plugin-host.html">
 <link rel="import" href="gr-change-metadata.html">
 
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 5003a68..0d7b037 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-change-metadata.html">
 
 <script>void(0);</script>
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 e45e514..358340d 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
@@ -396,7 +396,11 @@
     },
 
     _handleReplyOverlayOpen(e) {
-      this.$.replyDialog.focus();
+      // This is needed so that focus is not set on the reply overlay
+      // when the suggestion overaly from gr-autogrow-textarea opens.
+      if (e.target === this.$.replyOverlay) {
+        this.$.replyDialog.focus();
+      }
     },
 
     _handleReplySent(e) {
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 5847e73..e91f372 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
@@ -20,9 +20,9 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../bower_components/page/page.js"></script>
 
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="gr-change-view.html">
 
 <script>void(0);</script>
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 5275f48..4c8b443 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
@@ -20,6 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-comment-list.html">
 
 <script>void(0);</script>
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 fc45f2a..bd6fdcb 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-commit-info.html">
 
 <script>void(0);</script>
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 2d6defd..107fb23 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-confirm-cherrypick-dialog.html">
 
 <script>void(0);</script>
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 bb9651b..0ea7c49 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-confirm-rebase-dialog.html">
 
 <script>void(0);</script>
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 78b0dfc..09ba6bb 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-confirm-revert-dialog.html">
 
 <script>void(0);</script>
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 63d45c6..0bee5ba 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-download-dialog.html">
 
 <script>void(0);</script>
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 2558488..f0f5403 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
@@ -20,11 +20,11 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../bower_components/page/page.js"></script>
 <script src="../../../scripts/util.js"></script>
 
 <link rel="import" href="../../shared/gr-rest-api-interface/mock-diff-response_test.html">
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="gr-file-list.html">
 
 <script>void(0);</script>
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 8d91e75..3c46252 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-label-scores.html">
 
 <script>void(0);</script>
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 5f8b312..8638edd 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-message.html">
 
 <script>void(0);</script>
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 f6f2764..c47d63f 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-messages-list.html">
 
 <script>void(0);</script>
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 4a25405..6336433 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-related-changes-list.html">
 
 <script>void(0);</script>
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 b4a6171..12c08c6 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-reply-dialog.html">
 
 <script>void(0);</script>
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
index c77689d..eadf667 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
@@ -22,6 +22,7 @@
 <link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
 <link rel="import" href="../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
 <link rel="import" href="../../shared/gr-account-chip/gr-account-chip.html">
+<link rel="import" href="../../shared/gr-textarea/gr-textarea.html">
 <link rel="import" href="../../shared/gr-button/gr-button.html">
 <link rel="import" href="../../shared/gr-formatted-text/gr-formatted-text.html">
 <link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
@@ -92,28 +93,15 @@
         font-style: italic;
       }
       .textareaContainer {
-        display: flex;
-        flex: 1;
         min-height: 6em;
         position: relative;
       }
-      .textareaContainer > * {
-        flex: 1;
-      }
-      iron-autogrow-textarea {
-        padding: 0;
-        font-family: var(--monospace-font-family);
-      }
       .previewContainer gr-formatted-text {
         background: #f6f6f6;
         max-height: 20vh;
         overflow-y: scroll;
         padding: 1em;
       }
-      .message {
-        border: none;
-        width: 100%;
-      }
       .draftsContainer {
         flex: 1;
         overflow-y: auto;
@@ -208,17 +196,20 @@
       </section>
       <section class="textareaContainer">
         <gr-endpoint-decorator name="reply-text">
-          <iron-autogrow-textarea
+          <gr-textarea
               id="textarea"
               class="message"
               autocomplete="on"
               placeholder=[[_messagePlaceholder]]
+              fixed-position-dropdown
+              hide-border="true"
+              monospace="true"
               disabled="{{disabled}}"
               rows="4"
               max-rows="15"
-              bind-value="{{draft}}"
+              text="{{draft}}"
               on-bind-value-changed="_handleHeightChanged">
-          </iron-autogrow-textarea>
+          </gr-textarea>
         </gr-endpoint-decorator>
       </section>
       <section class="previewContainer">
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index 7f0e7d1..276e729 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -387,7 +387,8 @@
       }
       if (section === FocusTarget.BODY) {
         const textarea = this.$.textarea;
-        textarea.async(textarea.textarea.focus.bind(textarea.textarea));
+        textarea.async(textarea.getNativeTextarea()
+            .focus.bind(textarea.getNativeTextarea()));
       } else if (section === FocusTarget.REVIEWERS) {
         const reviewerEntry = this.$.reviewers.focusStart;
         reviewerEntry.async(reviewerEntry.focus);
@@ -561,6 +562,7 @@
 
     cancel() {
       this.fire('cancel', null, {bubbles: false});
+      this.$.textarea.closeDropdown();
       this._purgeReviewersPendingRemove(true);
       this._rebuildReviewerArrays(this.change.reviewers, this._owner,
           this.serverConfig);
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 fd21b5f..2b1b42c 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-reply-dialog.html">
 
 <script>void(0);</script>
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 c60916a..4e0541e6 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-reviewer-list.html">
 
 <script>void(0);</script>
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 46f3ca3..6bda66f 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-account-dropdown.html">
 
 <script>void(0);</script>
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 ba10f09..5d1c461 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-error-manager.html">
 
 <script>void(0);</script>
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
index c2273d1..390b028 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
@@ -27,6 +27,7 @@
     {
       url: '/admin/projects',
       name: 'Projects',
+      viewableToAll: true,
     },
     {
       url: '/admin/create-project',
@@ -178,11 +179,8 @@
           links: userLinks,
         });
       }
-      if (adminLinks && adminLinks.length > 0) {
-        links.push({
-          title: 'Admin',
-          links: adminLinks,
-        });
+      if (!adminLinks || !adminLinks.length) {
+        adminLinks = ADMIN_LINKS.filter(link => link.viewableToAll);
       }
       const docLinks = this._getDocLinks(docBaseUrl, DOCUMENTATION_LINKS);
       if (docLinks.length) {
@@ -191,6 +189,10 @@
           links: docLinks,
         });
       }
+      links.push({
+        title: 'More',
+        links: adminLinks,
+      });
       return links;
     },
 
@@ -212,7 +214,7 @@
     },
 
     _loadAccount() {
-      this.$.restAPI.getAccount().then(account => {
+      return this.$.restAPI.getAccount().then(account => {
         this._account = account;
         this.$.accountContainer.classList.toggle('loggedIn', account != null);
         this.$.accountContainer.classList.toggle('loggedOut', account == null);
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 f407ac5..08cf499 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-main-header.html">
 
 <script>void(0);</script>
@@ -118,16 +117,25 @@
         url: '/admin/groups',
         name: 'Groups',
       }];
+      const defaultAdminLink = {
+        title: 'More',
+        links: [{
+          url: '/admin/projects',
+          name: 'Projects',
+          viewableToAll: true,
+        }],
+      };
 
-      assert.deepEqual(
-          element._computeLinks(defaultLinks, [], []), defaultLinks);
+      // When no admin links are passed, it should use the default.
+      assert.deepEqual(element._computeLinks(defaultLinks, [], []),
+          defaultLinks.concat(defaultAdminLink));
       assert.deepEqual(
           element._computeLinks(defaultLinks, userLinks, adminLinks),
           defaultLinks.concat({
             title: 'Your',
             links: userLinks,
           }, {
-            title: 'Admin',
+            title: 'More',
             links: adminLinks,
           }));
     });
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 bda03b1..08f72b7 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
@@ -20,7 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-reporting.html">
 
 <script>void(0);</script>
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index c6b3ccf..4087e41 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -108,6 +108,48 @@
       });
     });
 
+    // Matches /admin/groups[,<offset>][/].
+    page(/^\/admin\/groups(,(\d+))?(\/)?$/, loadUser, data => {
+      restAPI.getLoggedIn().then(loggedIn => {
+        if (loggedIn) {
+          app.params = {
+            view: 'gr-admin-group-list',
+            offset: data.params[1] || 0,
+            filter: null,
+          };
+        } else {
+          page.redirect('/login/' + encodeURIComponent(data.canonicalPath));
+        }
+      });
+    });
+
+    page('/admin/groups/q/filter::filter,:offset', loadUser, data => {
+      restAPI.getLoggedIn().then(loggedIn => {
+        if (loggedIn) {
+          app.params = {
+            view: 'gr-admin-group-list',
+            offset: data.params.offset,
+            filter: data.params.filter,
+          };
+        } else {
+          page.redirect('/login/' + encodeURIComponent(data.canonicalPath));
+        }
+      });
+    });
+
+    page('/admin/groups/q/filter::filter', loadUser, data => {
+      restAPI.getLoggedIn().then(loggedIn => {
+        if (loggedIn) {
+          app.params = {
+            view: 'gr-admin-group-list',
+            filter: data.params.filter || null,
+          };
+        } else {
+          page.redirect('/login/' + encodeURIComponent(data.canonicalPath));
+        }
+      });
+    });
+
     // Matches /admin/projects[,<offset>][/].
     page(/^\/admin\/projects(,(\d+))?(\/)?$/, loadUser, data => {
       app.params = {
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 54ca399..059ea7d 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
@@ -20,9 +20,9 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../bower_components/page/page.js"></script>
 
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="gr-search-bar.html">
 <script src="../../../scripts/util.js"></script>
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
index e20aecc..b98959b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
@@ -128,9 +128,8 @@
           this._showTabs = !!prefs.show_tabs;
           this._showTrailingWhitespace = !!prefs.show_whitespace_errors;
 
-          // Stop the processor (if it's running).
-          this.$.processor.cancel();
-          this.$.syntaxLayer.cancel();
+          // Stop the processor and syntax layer (if they're running).
+          this.cancel();
 
           this._builder = this._getDiffBuilder(this.diff, comments, prefs);
 
@@ -245,6 +244,11 @@
           this.async(() => this.fire('render-content'), 1);
         },
 
+        cancel() {
+          this.$.processor.cancel();
+          this.$.syntaxLayer.cancel();
+        },
+
         _getDiffBuilder(diff, comments, prefs) {
           if (this.isImageDiff) {
             return new GrDiffBuilderImage(diff, comments, prefs,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
index 6c4a9d9..ee66b42 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
@@ -20,6 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../scripts/util.js"></script>
 <script src="../gr-diff/gr-diff-line.js"></script>
 <script src="../gr-diff/gr-diff-group.js"></script>
@@ -855,6 +856,14 @@
         };
         element.render({left: [], right: []}, prefs);
       });
+
+      test('cancel', () => {
+        const processorCancelStub = sandbox.stub(element.$.processor, 'cancel');
+        const syntaxCancelStub = sandbox.stub(element.$.syntaxLayer, 'cancel');
+        element.cancel();
+        assert.isTrue(processorCancelStub.called);
+        assert.isTrue(syntaxCancelStub.called);
+      });
     });
 
     suite('mock-diff', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group_test.html
index 056b74f..c2738460 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group_test.html
@@ -20,9 +20,9 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../scripts/util.js"></script>
 
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="gr-diff-comment-thread-group.html">
 
 <script>void(0);</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
index 85beca6..5a65f58 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
@@ -20,9 +20,9 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../scripts/util.js"></script>
 
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="gr-diff-comment-thread.html">
 
 <script>void(0);</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
index 110f09d..695e2b3 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
@@ -40,13 +40,13 @@
           padding: 2px;
         };
       }
-      :host[disabled] {
+      :host([disabled]) {
         pointer-events: none;
       }
-      :host[disabled] .container {
+      :host([disabled]) .container {
         opacity: .5;
       }
-      :host[is-robot-comment] {
+      :host([is-robot-comment]) {
         background-color: #cfe8fc;
       }
       .header {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
index 4a1df77..c4d1273 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
@@ -20,10 +20,10 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../bower_components/page/page.js"></script>
 <script src="../../../scripts/util.js"></script>
 
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="gr-diff-comment.html">
 
 <script>void(0);</script>
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 645093e..8be6e92 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
@@ -20,9 +20,9 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../scripts/util.js"></script>
 
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="../gr-diff/gr-diff.html">
 <link rel="import" href="./gr-diff-cursor.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/mock-diff-response_test.html">
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 b25b4fb..b237685 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
@@ -20,9 +20,9 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="gr-annotation.js"></script>
 
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 
 <script>void(0);</script>
 
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 c1254bd..f8e2a77 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-diff-highlight.html">
 
 <script>void(0);</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html
index b069cb4..99a7054 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html
@@ -28,7 +28,7 @@
       :host {
         display: block;
       }
-      :host[disabled] {
+      :host([disabled]) {
         opacity: .5;
         pointer-events: none;
       }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences_test.html b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences_test.html
index d201edf..f06cd3a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences_test.html
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-diff-preferences.html">
 
 <script>void(0);</script>
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 4785351..beb8ed2 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-diff-processor.html">
 
 <script>void(0);</script>
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 39555b4..2a35a39 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-diff-selection.html">
 
 <script>void(0);</script>
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 625af35..13d7140 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
@@ -20,10 +20,10 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../bower_components/page/page.js"></script>
 <script src="../../../scripts/util.js"></script>
 
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="gr-diff-view.html">
 
 <script>void(0);</script>
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 90e5d06..32405cf 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
@@ -19,6 +19,7 @@
 <title>gr-diff-group</title>
 
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="gr-diff-line.js"></script>
 <script src="gr-diff-group.js"></script>
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
index 687c462..d101fea 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -35,10 +35,10 @@
         --light-add-highlight-color: #efe;
         --dark-add-highlight-color: rgba(0, 255, 0, 0.15);
       }
-      :host.no-left .sideBySide ::content .left,
-      :host.no-left .sideBySide ::content .left + td,
-      :host.no-left .sideBySide ::content .right:not([data-value]),
-      :host.no-left .sideBySide ::content .right:not([data-value]) + td {
+      :host(.no-left) .sideBySide ::content .left,
+      :host(.no-left) .sideBySide ::content .left + td,
+      :host(.no-left) .sideBySide ::content .right:not([data-value]),
+      :host(.no-left) .sideBySide ::content .right:not([data-value]) + td {
         display: none;
       }
       .diffContainer {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index fbff941..386ac08 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -123,6 +123,7 @@
     },
 
     reload() {
+      this.$.diffBuilder.cancel();
       this._clearDiffContent();
 
       const promises = [];
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 bd7adfe..1c0f05e 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
@@ -20,9 +20,9 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../scripts/util.js"></script>
 
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/mock-diff-response_test.html">
 <link rel="import" href="gr-diff.html">
 
@@ -47,6 +47,19 @@
       sandbox.restore();
     });
 
+    test('reload cancels before network resolves', () => {
+      element = fixture('basic');
+      const cancelStub = sandbox.stub(element.$.diffBuilder, 'cancel');
+
+      // Stub the network calls into requests that never resolve.
+      sandbox.stub(element, '_getDiff', () => new Promise(() => {}));
+      sandbox.stub(element, '_getDiffCommentsAndDrafts',
+          () => new Promise(() => {}));
+
+      element.reload();
+      assert.isTrue(cancelStub.called);
+    });
+
     suite('not logged in', () => {
       setup(() => {
         stub('gr-rest-api-interface', {
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 0195eac..162936c 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
@@ -20,9 +20,9 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../bower_components/page/page.js"></script>
 
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="gr-patch-range-select.html">
 
 <script>void(0);</script>
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 1156ba0..afdf630 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
@@ -20,9 +20,9 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../gr-diff/gr-diff-line.js"></script>
 
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="gr-ranged-comment-layer.html">
 
 <script>void(0);</script>
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 2beb6bc..cfa8c5f 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-selection-action-box.html">
 
 <script>void(0);</script>
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 346a74a..98a6f8e 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
@@ -20,7 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="../../shared/gr-rest-api-interface/mock-diff-response_test.html">
 <link rel="import" href="gr-syntax-layer.html">
 
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html b/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html
index 0a916d0..b46910a 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html
@@ -20,7 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-syntax-lib-loader.html">
 
 <script>void(0);</script>
diff --git a/polygerrit-ui/app/elements/gr-app-it_test.html b/polygerrit-ui/app/elements/gr-app-it_test.html
index 67c5139..7b134c1 100644
--- a/polygerrit-ui/app/elements/gr-app-it_test.html
+++ b/polygerrit-ui/app/elements/gr-app-it_test.html
@@ -20,8 +20,7 @@
 
 <script src="../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../test/common-test-setup.html"/>
 <link rel="import" href="gr-app.html">
 
 <script>void(0);</script>
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index e0938a5..caf83bb 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -18,6 +18,7 @@
 <link rel="import" href="../behaviors/base-url-behavior/base-url-behavior.html">
 <link rel="import" href="../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
 <link rel="import" href="../styles/app-theme.html">
+<link rel="import" href="./admin/gr-admin-group-list/gr-admin-group-list.html">
 <link rel="import" href="./admin/gr-admin-project-list/gr-admin-project-list.html">
 <link rel="import" href="./admin/gr-admin-project/gr-admin-project.html">
 <link rel="import" href="./admin/gr-admin-view/gr-admin-view.html">
@@ -141,6 +142,11 @@
             on-account-detail-update="_handleAccountDetailUpdate">
         </gr-settings-view>
       </template>
+      <template is="dom-if" if="[[_showGroupListView]]" restamp="true">
+        <gr-admin-group-list
+            params="[[params]]"
+            id="groupList"></gr-admin-group-list>
+      </template>
       <template is="dom-if" if="[[_showProjectListView]]" restamp="true">
         <gr-admin-project-list
             params="[[params]]"
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index aa9b644..7f63330 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -77,6 +77,15 @@
       '?': '_showKeyboardShortcuts',
     },
 
+    created() {
+      // If shadow dom cookie is set, reload the page using shadow dom.
+      if (util.getCookie('USE_SHADOW_DOM') === 'true') {
+        if (!window.location.href.includes('?dom=shadow')) {
+          window.location.href = window.location.href + '?dom=shadow';
+        }
+      }
+    },
+
     ready() {
       this.$.router.start();
 
@@ -129,6 +138,7 @@
       this.set('_showChangeView', view === 'gr-change-view');
       this.set('_showDiffView', view === 'gr-diff-view');
       this.set('_showSettingsView', view === 'gr-settings-view');
+      this.set('_showGroupListView', view === 'gr-admin-group-list');
       this.set('_showProjectListView', view === 'gr-admin-project-list');
       this.set('_showAdminProject', view === 'gr-admin-project');
       this.set('_showAdminView', view === 'gr-admin-view');
diff --git a/polygerrit-ui/app/elements/gr-app_test.html b/polygerrit-ui/app/elements/gr-app_test.html
index e25869b..905c5c4 100644
--- a/polygerrit-ui/app/elements/gr-app_test.html
+++ b/polygerrit-ui/app/elements/gr-app_test.html
@@ -20,7 +20,7 @@
 
 <script src="../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../bower_components/web-component-tester/browser.js"></script>
-
+<link rel="import" href="../test/common-test-setup.html"/>
 <link rel="import" href="gr-app.html">
 
 <script>void(0);</script>
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 a64e1e5..52ece65 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-endpoint-decorator.html">
 
 <test-fixture id="basic">
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 d1b9e70..bc24c2b 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-external-style.html">
 
 <test-fixture id="basic">
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 17f4875..1e81092 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
@@ -20,7 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-plugin-host.html">
 
 <test-fixture id="basic">
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.html b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.html
index dfbf3038..494b9e8 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.html
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.html
@@ -20,9 +20,12 @@
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 
 <link rel="import" href="../../../styles/gr-form-styles.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+
 
 <dom-module id="gr-account-info">
   <template>
+    <style include="shared-styles"></style>
     <style include="gr-form-styles"></style>
     <div class="gr-form-styles">
       <section>
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 d354f07..84fbc09 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-account-info.html">
 
 <script>void(0);</script>
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 15edb94..61093b9 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-change-table-editor.html">
 
 <script>void(0);</script>
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 608179e..fdbafdb 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-email-editor.html">
 
 <script>void(0);</script>
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 c0cecdf..3c4eff2 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
@@ -20,7 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-group-list.html">
 
 <script>void(0);</script>
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 acc9f60..bbe1555 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-http-password.html">
 
 <script>void(0);</script>
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 f9e905d..7e12483 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-menu-editor.html">
 
 <script>void(0);</script>
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 1f8a34a..b3ea3fa 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-registration-dialog.html">
 
 <script>void(0);</script>
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 2d6c16d..517f208 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-settings-view.html">
 
 <script>void(0);</script>
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 09da19f..4273f7a 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-ssh-editor.html">
 
 <script>void(0);</script>
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 1e6ed7a..a93beb1 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-watched-projects-editor.html">
 
 <script>void(0);</script>
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 2c43879..3b1e1de 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
@@ -20,6 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../scripts/util.js"></script>
 
 <link rel="import" href="gr-account-label.html">
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 29d1580..7e2e97b 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
@@ -20,7 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-account-link.html">
 
 <script>void(0);</script>
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 6bbfcfb..b8dcb8d 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-alert.html">
 
 <script>
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 c071f0c..db9440c 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
@@ -20,9 +20,8 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-autocomplete-dropdown.html">
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 
 <script>void(0);</script>
 
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 eb8e305..fe21053 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-autocomplete.html">
 
 <script>void(0);</script>
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 0a1ac67..4a51142 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
@@ -20,7 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-avatar.html">
 
 <script>void(0);</script>
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 536b777..de8b5b1 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-button.html">
 
 <script>void(0);</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 6286efe..6c15a46 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-change-star.html">
 
 <script>void(0);</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-confirm-dialog/gr-confirm-dialog_test.html b/polygerrit-ui/app/elements/shared/gr-confirm-dialog/gr-confirm-dialog_test.html
index cb5d688..309e15c 100644
--- a/polygerrit-ui/app/elements/shared/gr-confirm-dialog/gr-confirm-dialog_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-confirm-dialog/gr-confirm-dialog_test.html
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-confirm-dialog.html">
 
 <script>void(0);</script>
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 7ab0088..a8102b5 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-cursor-manager.html">
 
 <script>void(0);</script>
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 8a3ea3a..2c15ef6 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
@@ -20,6 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../scripts/util.js"></script>
 
 <link rel="import" href="gr-date-formatter.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
index 390778d..eb5c3ed 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
@@ -41,7 +41,7 @@
         font: inherit;
         padding: .3em 0;
       }
-      :host[down-arrow] .dropdown-trigger {
+      :host([down-arrow]) .dropdown-trigger {
         padding-right: 1.4em;
       }
       gr-avatar {
@@ -85,7 +85,7 @@
         font-weight: bold;
       }
       :host:not([down-arrow]) .downArrow { display: none; }
-      :host[down-arrow] .downArrow {
+      :host([down-arrow]) .downArrow {
         border-left: .36em solid transparent;
         border-right: .36em solid transparent;
         border-top: .36em solid #ccc;
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 1815cca..c951cd4 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-dropdown.html">
 
 <script>void(0);</script>
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 df98d5a..d8e5b21 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
@@ -20,6 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../bower_components/iron-test-helpers/mock-interactions.js"></script>
 
 <link rel="import" href="gr-editable-content.html">
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 ffd1f81..9079f6f 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
@@ -20,6 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../bower_components/iron-test-helpers/mock-interactions.js"></script>
 
 <link rel="import" href="gr-editable-label.html">
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 ee05f7d..97c92b7 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
@@ -20,7 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-fixed-panel.html">
 
 <script>void(0);</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html
index 42ec18e..28a9e0e 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html
@@ -35,10 +35,10 @@
       blockquote {
         max-width: var(--gr-formatted-text-prose-max-width, none);
       }
-      :host.noTrailingMargin p:last-child,
-      :host.noTrailingMargin ul:last-child,
-      :host.noTrailingMargin blockquote:last-child,
-      :host.noTrailingMargin gr-linked-text.pre:last-child {
+      :host(.noTrailingMargin) p:last-child,
+      :host(.noTrailingMargin) ul:last-child,
+      :host(.noTrailingMargin) blockquote:last-child,
+      :host(.noTrailingMargin) gr-linked-text.pre:last-child {
         margin: 0;
       }
       blockquote {
@@ -46,6 +46,7 @@
         padding: 0 .7em;
       }
       li {
+        list-style-type: disc;
         margin-left: 1.4em;
       }
       gr-linked-text.pre {
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 0192ea9..fe5b92f 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
@@ -20,7 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-formatted-text.html">
 
 <script>void(0);</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 24fee39..53d7345 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <!--
 This must refer to the element this interface is wrapping around. Otherwise
 breaking changes to gr-change-actions won’t be noticed.
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 3080df1..2f67035 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
@@ -20,8 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <!--
 This must refer to the element this interface is wrapping around. Otherwise
 breaking changes to gr-reply-dialog won’t be noticed.
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 40810b1..761fe2a 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
@@ -20,6 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-js-api-interface.html">
 
 <script>void(0);</script>
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 3182653..d707d10 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
@@ -20,9 +20,9 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../bower_components/iron-test-helpers/mock-interactions.js"></script>
 
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="gr-linked-chip.html">
 
 <script>void(0);</script>
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 807278d..0042049 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
@@ -20,6 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../scripts/util.js"></script>
 
 <link rel="import" href="gr-linked-text.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.html b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.html
new file mode 100644
index 0000000..0e68899
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.html
@@ -0,0 +1,106 @@
+<!--
+Copyright (C) 2017 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.
+-->
+
+<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
+<link rel="import" href="../../../behaviors/gr-url-encoding-behavior.html">
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+
+<link rel="import" href="../../../styles/shared-styles.html">
+
+<dom-module id="gr-list-view">
+  <template>
+    <style include="shared-styles">
+      #filterContainer {
+        margin: 1em;
+      }
+      #filter {
+        font-size: 1em;
+        max-width: 25em;
+      }
+      a {
+        color: var(--default-text-color);
+        text-decoration: none;
+      }
+      a:hover {
+        text-decoration: underline;
+      }
+      nav {
+        padding: .5em 0;
+        text-align: center;
+      }
+      nav a {
+        display: inline-block;
+      }
+      nav a:first-of-type {
+        margin-right: .5em;
+      }
+      ::content {
+        display: flex;
+        flex-direction: column;
+      }
+      ::content tr.table {
+        border-bottom: 1px solid #eee;
+      }
+      ::content #list {
+        border-collapse: collapse;
+        width: 100%;
+      }
+      ::content td {
+        flex-shrink: 0;
+        padding: .3em .5em;
+      }
+      ::content th {
+        background-color: #ddd;
+        border-bottom: 1px solid #eee;
+        font-weight: bold;
+        padding: .3em .5em;
+        text-align: left;
+      }
+      ::content a {
+        color: var(--default-text-color);
+        text-decoration: none;
+      }
+      ::content a:hover {
+        text-decoration: underline;
+      }
+      ::content .description {
+        width: 70%;
+      }
+      ::content .loading {
+        color: #666;
+        padding: 1em var(--default-horizontal-margin);
+      }
+    </style>
+    <div id="filterContainer">
+      <label>Filter:</label>
+      <input is="iron-input"
+          type="text"
+          id="filter"
+          bind-value="{{_filter}}">
+    </div>
+    <content></content>
+    <nav>
+      <a id="prevArrow"
+          href$="[[_computeNavLink(offset, -1, itemsPerPage, filter)]]"
+          hidden$="[[_hidePrevArrow(offset)]]" hidden>&larr; Prev</a>
+      <a id="nextArrow"
+          href$="[[_computeNavLink(offset, 1, itemsPerPage, filter)]]"
+          hidden$="[[_hideNextArrow(loading, items)]]" hidden>
+        Next &rarr;</a>
+    </nav>
+  </template>
+  <script src="gr-list-view.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.js b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.js
new file mode 100644
index 0000000..1188534
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.js
@@ -0,0 +1,80 @@
+// Copyright (C) 2017 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.
+(function() {
+  'use strict';
+
+  const REQUEST_DEBOUNCE_INTERVAL_MS = 200;
+
+  Polymer({
+    is: 'gr-list-view',
+
+    properties: {
+      items: Array,
+      itemsPerPage: Number,
+      _filter: {
+        type: String,
+        observer: '_filterChanged',
+      },
+      offset: Number,
+      loading: Boolean,
+      path: String,
+    },
+
+    behaviors: [
+      Gerrit.BaseUrlBehavior,
+      Gerrit.URLEncodingBehavior,
+    ],
+
+    listeners: {
+      'next-page': '_handleNextPage',
+      'previous-page': '_handlePreviousPage',
+    },
+
+    _filterChanged(filter) {
+      this.debounce('reload', () => {
+        if (filter) {
+          return page.show(`${this.path}/q/filter:` +
+              this.encodeURL(filter, false));
+        }
+        page.show(this.path);
+      }, REQUEST_DEBOUNCE_INTERVAL_MS);
+    },
+
+    _computeNavLink(offset, direction, projectsPerPage, filter) {
+      // Offset could be a string when passed from the router.
+      offset = +(offset || 0);
+      const newOffset = Math.max(0, offset + (projectsPerPage * direction));
+      let href = this.getBaseUrl() + this.path;
+      if (filter) {
+        href += '/q/filter:' + filter;
+      }
+      if (newOffset > 0) {
+        href += ',' + newOffset;
+      }
+      return href;
+    },
+
+    _hidePrevArrow(offset) {
+      return offset === 0;
+    },
+
+    _hideNextArrow(loading, projects) {
+      let lastPage = false;
+      if (projects.length < this.itemsPerPage + 1) {
+        lastPage = true;
+      }
+      return loading || lastPage || !projects || !projects.length;
+    },
+  });
+})();
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
new file mode 100644
index 0000000..10ef4e1
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2017 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-list-view</title>
+<script src="../../../bower_components/page/page.js"></script>
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+
+<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="gr-list-view.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+  <template>
+    <gr-list-view></gr-list-view>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-list-view tests', () => {
+    let element;
+    let sandbox;
+
+    setup(() => {
+      sandbox = sinon.sandbox.create();
+      element = fixture('basic');
+    });
+
+    teardown(() => {
+      sandbox.restore();
+    });
+
+    test('_computeNavLink', () => {
+      const offset = 25;
+      const projectsPerPage = 25;
+      const filter = 'test';
+      element.path = '/admin/projects';
+
+      sandbox.stub(element, 'getBaseUrl', () => '');
+
+      assert.equal(
+          element._computeNavLink(offset, 1, projectsPerPage, filter),
+          '/admin/projects/q/filter:test,50');
+
+      assert.equal(
+          element._computeNavLink(offset, -1, projectsPerPage, filter),
+          '/admin/projects/q/filter:test');
+
+      assert.equal(
+          element._computeNavLink(offset, 1, projectsPerPage, null),
+          '/admin/projects,50');
+
+      assert.equal(
+          element._computeNavLink(offset, -1, projectsPerPage, null),
+          '/admin/projects');
+    });
+
+    test('_onValueChange', done => {
+      element.path = '/admin/projects';
+      sandbox.stub(page, 'show', url => {
+        assert.equal(url, '/admin/projects/q/filter:test');
+        done();
+      });
+      const e = {target: {value: 'test'}};
+      element._onValueChange(e);
+    });
+
+    test('next button', done => {
+      element.itemsPerPage = 25;
+      projects = new Array(26);
+
+      flush(() => {
+        let loading;
+        assert.isFalse(element._hideNextArrow(loading, projects));
+        loading = true;
+        assert.isTrue(element._hideNextArrow(loading, projects));
+        loading = false;
+        assert.isFalse(element._hideNextArrow(loading, projects));
+        element._projects = [];
+        assert.isTrue(element._hideNextArrow(loading, element._projects));
+        projects = new Array(4);
+        assert.isTrue(element._hideNextArrow(loading, projects));
+        done();
+      });
+    });
+
+    test('prev button', () => {
+      flush(() => {
+        let offset = 0;
+        assert.isTrue(element._hidePrevArrow(offset));
+        offset = 5;
+        assert.isFalse(element._hidePrevArrow(offset));
+      });
+    });
+  });
+</script>
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 c2352c5d..4f89535 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
@@ -497,6 +497,15 @@
       });
     },
 
+    getGroups(filter, groupsPerPage, opt_offset) {
+      const offset = opt_offset || 0;
+      filter = filter ? '&m=' + filter : '';
+
+      return this._fetchSharedCacheURL(
+          `/groups/?n=${groupsPerPage + 1}&S=${offset}${filter}`
+      );
+    },
+
     getProjects(filter, projectsPerPage, opt_offset) {
       const offset = opt_offset || 0;
       filter = filter ? '&m=' + filter : '';
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 1257529..1450a5a 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
@@ -20,9 +20,9 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../scripts/util.js"></script>
 
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="gr-rest-api-interface.html">
 
 <script>void(0);</script>
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 f27e068..ca1dc9f 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
@@ -20,9 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
-
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../scripts/util.js"></script>
 <script src="gr-reviewer-updates-parser.js"></script>
 
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/mock-diff-response_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/mock-diff-response_test.html
index 0385f64..add07ea 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/mock-diff-response_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/mock-diff-response_test.html
@@ -15,7 +15,7 @@
 -->
 
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
-
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <dom-module id="mock-diff-response">
   <template></template>
   <script>
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 83ae5af..fa8a54c 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
@@ -20,7 +20,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-select.html">
 
 <script>void(0);</script>
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 4171939..ce8ec20 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
@@ -19,7 +19,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-storage.html">
 
 <script>void(0);</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js
index 9158a57..add4b6f 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js
@@ -64,6 +64,7 @@
       text: {
         type: String,
         notify: true,
+        observer: '_handleTextChanged',
       },
       backgroundColor: {
         type: String,
@@ -316,5 +317,10 @@
       this._colonIndex = null;
       this.$.textarea.textarea.focus();
     },
+
+    _handleTextChanged(text) {
+      this.dispatchEvent(
+          new CustomEvent('value-changed', {detail: {value: text}}));
+    },
   });
 })();
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 1c5dea3..637fbbfc 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
@@ -20,9 +20,8 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-textarea.html">
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 
 <script>void(0);</script>
 
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 35c9a61..2fa02a3 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
@@ -19,7 +19,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-tooltip-content.html">
 
 <script>void(0);</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html
index 8d57be7..e79fb19 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html
@@ -28,11 +28,13 @@
         box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
         color: #fff;
         font-size: .75rem;
-        padding: .5em .85em;
         position: absolute;
         z-index: 1000;
         max-width: var(--tooltip-max-width);
       }
+      :host .tooltip {
+        padding: .5em .85em;
+      }
       .arrow {
         border-left: var(--gr-tooltip-arrow-size) solid transparent;
         border-right: var(--gr-tooltip-arrow-size) solid transparent;
@@ -45,8 +47,10 @@
         width: 0;
       }
     </style>
-    [[text]]
-    <i class="arrow"></i>
+    <div class="tooltip">
+      [[text]]
+      <i class="arrow"></i>
+    </div>
   </template>
   <script src="gr-tooltip.js"></script>
 </dom-module>
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 efc2a00..e1e6449 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
@@ -19,7 +19,7 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-
+<link rel="import" href="../../../test/common-test-setup.html"/>
 <link rel="import" href="gr-tooltip.html">
 
 <script>void(0);</script>
diff --git a/polygerrit-ui/app/test/common-test-setup.html b/polygerrit-ui/app/test/common-test-setup.html
new file mode 100644
index 0000000..00bd1e7
--- /dev/null
+++ b/polygerrit-ui/app/test/common-test-setup.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2017 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.
+-->
+
+<link rel="import"
+    href="../bower_components/iron-test-helpers/iron-test-helpers.html" />