Move to async test-functions

Change-Id: Id5392032a85063f89e5eca41ab33a3815948b4c9
diff --git a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.js b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.js
index af33691..321f069 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.js
@@ -31,41 +31,33 @@
     element = basicFixture.instantiate();
   });
 
-  test('name is updated correctly', done => {
+  test('name is updated correctly', async () => {
     assert.isFalse(element.hasNewGroupName);
 
     const inputEl = element.root.querySelector('iron-input');
     inputEl.bindValue = GROUP_NAME;
 
-    setTimeout(() => {
-      assert.isTrue(element.hasNewGroupName);
-      assert.deepEqual(element._name, GROUP_NAME);
-      done();
-    });
+    await new Promise(resolve => setTimeout(resolve));
+    assert.isTrue(element.hasNewGroupName);
+    assert.deepEqual(element._name, GROUP_NAME);
   });
 
-  test('test for redirecting to group on successful creation', done => {
+  test('test for redirecting to group on successful creation', async () => {
     stubRestApi('createGroup').returns(Promise.resolve({status: 201}));
     stubRestApi('getGroupConfig').returns(Promise.resolve({group_id: 551}));
 
     const showStub = sinon.stub(page, 'show');
-    element.handleCreateGroup()
-        .then(() => {
-          assert.isTrue(showStub.calledWith('/admin/groups/551'));
-          done();
-        });
+    await element.handleCreateGroup();
+    assert.isTrue(showStub.calledWith('/admin/groups/551'));
   });
 
-  test('test for unsuccessful group creation', done => {
+  test('test for unsuccessful group creation', async () => {
     stubRestApi('createGroup').returns(Promise.resolve({status: 409}));
     stubRestApi('getGroupConfig').returns(Promise.resolve({group_id: 551}));
 
     const showStub = sinon.stub(page, 'show');
-    element.handleCreateGroup()
-        .then(() => {
-          assert.isFalse(showStub.called);
-          done();
-        });
+    await element.handleCreateGroup();
+    assert.isFalse(showStub.called);
   });
 });
 
diff --git a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.ts b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.ts
index b6a08b87..ea0919c 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.ts
@@ -35,7 +35,7 @@
     element = basicFixture.instantiate();
   });
 
-  test('branch created', done => {
+  test('branch created', async () => {
     stubRestApi('createRepoBranch').returns(Promise.resolve(new Response()));
 
     assert.isFalse(element.hasNewItemName);
@@ -46,15 +46,14 @@
     ironInput(element.$.itemNameSection).bindValue = 'test-branch2';
     ironInput(element.$.itemRevisionSection).bindValue = 'HEAD';
 
-    setTimeout(() => {
-      assert.isTrue(element.hasNewItemName);
-      assert.equal(element._itemName, 'test-branch2' as BranchName);
-      assert.equal(element._itemRevision, 'HEAD');
-      done();
-    });
+    await flush();
+
+    assert.isTrue(element.hasNewItemName);
+    assert.equal(element._itemName, 'test-branch2' as BranchName);
+    assert.equal(element._itemRevision, 'HEAD');
   });
 
-  test('tag created', done => {
+  test('tag created', async () => {
     stubRestApi('createRepoTag').returns(Promise.resolve(new Response()));
 
     assert.isFalse(element.hasNewItemName);
@@ -65,15 +64,13 @@
     ironInput(element.$.itemNameSection).bindValue = 'test-tag2';
     ironInput(element.$.itemRevisionSection).bindValue = 'HEAD';
 
-    setTimeout(() => {
-      assert.isTrue(element.hasNewItemName);
-      assert.equal(element._itemName, 'test-tag2' as BranchName);
-      assert.equal(element._itemRevision, 'HEAD');
-      done();
-    });
+    await flush();
+    assert.isTrue(element.hasNewItemName);
+    assert.equal(element._itemName, 'test-tag2' as BranchName);
+    assert.equal(element._itemRevision, 'HEAD');
   });
 
-  test('tag created with annotations', done => {
+  test('tag created with annotations', async () => {
     stubRestApi('createRepoTag').returns(Promise.resolve(new Response()));
 
     assert.isFalse(element.hasNewItemName);
@@ -86,13 +83,11 @@
     ironInput(element.$.itemAnnotationSection).bindValue = 'test-message2';
     ironInput(element.$.itemRevisionSection).bindValue = 'HEAD';
 
-    setTimeout(() => {
-      assert.isTrue(element.hasNewItemName);
-      assert.equal(element._itemName, 'test-tag2' as BranchName);
-      assert.equal(element._itemAnnotation, 'test-message2');
-      assert.equal(element._itemRevision, 'HEAD');
-      done();
-    });
+    await flush();
+    assert.isTrue(element.hasNewItemName);
+    assert.equal(element._itemName, 'test-tag2' as BranchName);
+    assert.equal(element._itemAnnotation, 'test-message2');
+    assert.equal(element._itemRevision, 'HEAD');
   });
 
   test('_computeHideItemClass returns hideItem if type is branches', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.js b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.js
index 54c5099..d26d14c 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.js
@@ -18,7 +18,7 @@
 import '../../../test/common-test-setup-karma.js';
 import './gr-group-members.js';
 import {dom, flush} from '@polymer/polymer/lib/legacy/polymer.dom.js';
-import {addListenerForTest, stubBaseUrl, stubRestApi} from '../../../test/test-utils.js';
+import {addListenerForTest, mockPromise, stubBaseUrl, stubRestApi} from '../../../test/test-utils.js';
 import {ItemType} from './gr-group-members.js';
 
 const basicFixture = fixtureFromElement('gr-group-members');
@@ -235,42 +235,32 @@
     assert.isTrue(exceptionThrown);
   });
 
-  test('_getAccountSuggestions empty', done => {
-    element
-        ._getAccountSuggestions('nonexistent').then(accounts => {
-          assert.equal(accounts.length, 0);
-          done();
-        });
+  test('_getAccountSuggestions empty', async () => {
+    const accounts = await element._getAccountSuggestions('nonexistent');
+    assert.equal(accounts.length, 0);
   });
 
-  test('_getAccountSuggestions non-empty', done => {
-    element
-        ._getAccountSuggestions('test-').then(accounts => {
-          assert.equal(accounts.length, 3);
-          assert.equal(accounts[0].name,
-              'test-account <test.account@example.com>');
-          assert.equal(accounts[1].name, 'test-admin <test.admin@example.com>');
-          assert.equal(accounts[2].name, 'test-git');
-          done();
-        });
+  test('_getAccountSuggestions non-empty', async () => {
+    const accounts = await element._getAccountSuggestions('test-');
+    assert.equal(accounts.length, 3);
+    assert.equal(accounts[0].name,
+        'test-account <test.account@example.com>');
+    assert.equal(accounts[1].name, 'test-admin <test.admin@example.com>');
+    assert.equal(accounts[2].name, 'test-git');
   });
 
-  test('_getGroupSuggestions empty', done => {
-    element
-        ._getGroupSuggestions('nonexistent').then(groups => {
-          assert.equal(groups.length, 0);
-          done();
-        });
+  test('_getGroupSuggestions empty', async () => {
+    const groups = await element._getGroupSuggestions('nonexistent');
+
+    assert.equal(groups.length, 0);
   });
 
-  test('_getGroupSuggestions non-empty', done => {
-    element
-        ._getGroupSuggestions('test').then(groups => {
-          assert.equal(groups.length, 2);
-          assert.equal(groups[0].name, 'test-admin');
-          assert.equal(groups[1].name, 'test/Administrator (admin)');
-          done();
-        });
+  test('_getGroupSuggestions non-empty', async () => {
+    const groups = await element._getGroupSuggestions('test');
+
+    assert.equal(groups.length, 2);
+    assert.equal(groups[0].name, 'test-admin');
+    assert.equal(groups[1].name, 'test/Administrator (admin)');
   });
 
   test('_computeHideItemClass returns string for admin', () => {
@@ -343,22 +333,24 @@
     assert.equal(element._computeGroupUrl(url), url);
   });
 
-  test('fires page-error', done => {
+  test('fires page-error', async () => {
     groupStub.restore();
 
     element.groupId = 1;
 
     const response = {status: 404};
-    stubRestApi('getGroupConfig')
-        .callsFake((group, errFn) => {
-          errFn(response);
-        });
+    stubRestApi('getGroupConfig').callsFake((group, errFn) => {
+      errFn(response);
+      return Promise.resolve();
+    });
+    const promise = mockPromise();
     addListenerForTest(document, 'page-error', e => {
       assert.deepEqual(e.detail.response, response);
-      done();
+      promise.resolve();
     });
 
     element._loadGroupDetails();
+    await promise;
   });
 
   test('_computeItemName', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.js b/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.js
index 0668330..e492a15 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.js
@@ -17,7 +17,11 @@
 
 import '../../../test/common-test-setup-karma.js';
 import './gr-group.js';
-import {stubRestApi, addListenerForTest} from '../../../test/test-utils.js';
+import {
+  addListenerForTest,
+  mockPromise,
+  stubRestApi,
+} from '../../../test/test-utils.js';
 
 const basicFixture = fixtureFromElement('gr-group');
 
@@ -49,17 +53,15 @@
         .display === 'none');
   });
 
-  test('default values are populated with internal group', done => {
+  test('default values are populated with internal group', async () => {
     stubRestApi('getIsGroupOwner').returns(Promise.resolve(true));
     element.groupId = 1;
-    element._loadGroup().then(() => {
-      assert.isTrue(element._groupIsInternal);
-      assert.isFalse(element.$.visibleToAll.bindValue);
-      done();
-    });
+    await element._loadGroup();
+    assert.isTrue(element._groupIsInternal);
+    assert.isFalse(element.$.visibleToAll.bindValue);
   });
 
-  test('default values with external group', done => {
+  test('default values with external group', async () => {
     const groupExternal = {...group};
     groupExternal.id = 'external-group-id';
     groupStub.restore();
@@ -67,14 +69,12 @@
         Promise.resolve(groupExternal));
     stubRestApi('getIsGroupOwner').returns(Promise.resolve(true));
     element.groupId = 1;
-    element._loadGroup().then(() => {
-      assert.isFalse(element._groupIsInternal);
-      assert.isFalse(element.$.visibleToAll.bindValue);
-      done();
-    });
+    await element._loadGroup();
+    assert.isFalse(element._groupIsInternal);
+    assert.isFalse(element.$.visibleToAll.bindValue);
   });
 
-  test('rename group', done => {
+  test('rename group', async () => {
     const groupName = 'test-group';
     const groupName2 = 'test-group2';
     element.groupId = 1;
@@ -88,25 +88,22 @@
 
     const button = element.$.inputUpdateNameBtn;
 
-    element._loadGroup().then(() => {
-      assert.isTrue(button.hasAttribute('disabled'));
-      assert.isFalse(element.$.Title.classList.contains('edited'));
+    await element._loadGroup();
+    assert.isTrue(button.hasAttribute('disabled'));
+    assert.isFalse(element.$.Title.classList.contains('edited'));
 
-      element.$.groupNameInput.text = groupName2;
+    element.$.groupNameInput.text = groupName2;
 
-      assert.isFalse(button.hasAttribute('disabled'));
-      assert.isTrue(element.$.groupName.classList.contains('edited'));
+    assert.isFalse(button.hasAttribute('disabled'));
+    assert.isTrue(element.$.groupName.classList.contains('edited'));
 
-      element._handleSaveName().then(() => {
-        assert.isTrue(button.hasAttribute('disabled'));
-        assert.isFalse(element.$.Title.classList.contains('edited'));
-        assert.equal(element._groupName, groupName2);
-        done();
-      });
-    });
+    await element._handleSaveName();
+    assert.isTrue(button.hasAttribute('disabled'));
+    assert.isFalse(element.$.Title.classList.contains('edited'));
+    assert.equal(element._groupName, groupName2);
   });
 
-  test('rename group owner', done => {
+  test('rename group owner', async () => {
     const groupName = 'test-group';
     element.groupId = 1;
     element._groupConfig = {
@@ -119,24 +116,21 @@
 
     const button = element.$.inputUpdateOwnerBtn;
 
-    element._loadGroup().then(() => {
-      assert.isTrue(button.hasAttribute('disabled'));
-      assert.isFalse(element.$.Title.classList.contains('edited'));
+    await element._loadGroup();
+    assert.isTrue(button.hasAttribute('disabled'));
+    assert.isFalse(element.$.Title.classList.contains('edited'));
 
-      element.$.groupOwnerInput.text = 'testId2';
+    element.$.groupOwnerInput.text = 'testId2';
 
-      assert.isFalse(button.hasAttribute('disabled'));
-      assert.isTrue(element.$.groupOwner.classList.contains('edited'));
+    assert.isFalse(button.hasAttribute('disabled'));
+    assert.isTrue(element.$.groupOwner.classList.contains('edited'));
 
-      element._handleSaveOwner().then(() => {
-        assert.isTrue(button.hasAttribute('disabled'));
-        assert.isFalse(element.$.Title.classList.contains('edited'));
-        done();
-      });
-    });
+    await element._handleSaveOwner();
+    assert.isTrue(button.hasAttribute('disabled'));
+    assert.isFalse(element.$.Title.classList.contains('edited'));
   });
 
-  test('test for undefined group name', done => {
+  test('test for undefined group name', async () => {
     groupStub.restore();
 
     stubRestApi('getGroupConfig').returns(Promise.resolve({}));
@@ -149,16 +143,13 @@
 
     // Test that loading shows instead of filling
     // in group details
-    element._loadGroup().then(() => {
-      assert.isTrue(element.$.loading.classList.contains('loading'));
+    await element._loadGroup();
+    assert.isTrue(element.$.loading.classList.contains('loading'));
 
-      assert.isTrue(element._loading);
-
-      done();
-    });
+    assert.isTrue(element._loading);
   });
 
-  test('test fire event', done => {
+  test('test fire event', async () => {
     element._groupConfig = {
       name: 'test-group',
     };
@@ -166,11 +157,8 @@
     stubRestApi('saveGroupName').returns(Promise.resolve({status: 200}));
 
     const showStub = sinon.stub(element, 'dispatchEvent');
-    element._handleSaveName()
-        .then(() => {
-          assert.isTrue(showStub.called);
-          done();
-        });
+    await element._handleSaveName();
+    assert.isTrue(showStub.called);
   });
 
   test('_computeGroupDisabled', () => {
@@ -206,7 +194,7 @@
     assert.equal(element._computeLoadingClass(false), '');
   });
 
-  test('fires page-error', done => {
+  test('fires page-error', async () => {
     groupStub.restore();
 
     element.groupId = 1;
@@ -214,14 +202,17 @@
     const response = {status: 404};
     stubRestApi('getGroupConfig').callsFake((group, errFn) => {
       errFn(response);
+      return Promise.resolve(undefined);
     });
 
+    const promise = mockPromise();
     addListenerForTest(document, 'page-error', e => {
       assert.deepEqual(e.detail.response, response);
-      done();
+      promise.resolve();
     });
 
     element._loadGroup();
+    await promise;
   });
 
   test('uuid', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.js b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.js
index a9281e4..62c89b2 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.js
@@ -18,7 +18,10 @@
 import '../../../test/common-test-setup-karma.js';
 import './gr-plugin-list.js';
 import 'lodash/lodash.js';
-import {addListenerForTest, stubRestApi} from '../../../test/test-utils.js';
+import {
+  addListenerForTest,
+  mockPromise,
+  stubRestApi} from '../../../test/test-utils.js';
 
 const basicFixture = fixtureFromElement('gr-plugin-list');
 
@@ -52,52 +55,45 @@
     counter = 0;
   });
 
-  suite('list with plugins', () => {
-    setup(done => {
+  suite('list with plugins', async () => {
+    setup(async () => {
       plugins = _.times(26, pluginGenerator);
       stubRestApi('getPlugins').returns(Promise.resolve(plugins));
-      element._paramsChanged(value).then(() => { flush(done); });
+      await element._paramsChanged(value);
+      await flush();
     });
 
-    test('plugin in the list is formatted correctly', done => {
-      flush(() => {
-        assert.equal(element._plugins[4].id, 'test5');
-        assert.equal(element._plugins[4].index_url, 'plugins/test5/');
-        assert.equal(element._plugins[4].version, 'version-5');
-        assert.equal(element._plugins[4].api_version, 'api-version-5');
-        assert.equal(element._plugins[4].disabled, false);
-        done();
-      });
+    test('plugin in the list is formatted correctly', async () => {
+      await flush();
+      assert.equal(element._plugins[4].id, 'test5');
+      assert.equal(element._plugins[4].index_url, 'plugins/test5/');
+      assert.equal(element._plugins[4].version, 'version-5');
+      assert.equal(element._plugins[4].api_version, 'api-version-5');
+      assert.equal(element._plugins[4].disabled, false);
     });
 
-    test('with and without urls', done => {
-      flush(() => {
-        const names = element.root.querySelectorAll('.name');
-        assert.isOk(names[1].querySelector('a'));
-        assert.equal(names[1].querySelector('a').innerText, 'test1');
-        assert.isNotOk(names[2].querySelector('a'));
-        assert.equal(names[2].innerText, 'test2');
-        done();
-      });
+    test('with and without urls', async () => {
+      await flush();
+      const names = element.root.querySelectorAll('.name');
+      assert.isOk(names[1].querySelector('a'));
+      assert.equal(names[1].querySelector('a').innerText, 'test1');
+      assert.isNotOk(names[2].querySelector('a'));
+      assert.equal(names[2].innerText, 'test2');
     });
 
-    test('versions', done => {
-      flush(() => {
-        const versions = element.root.querySelectorAll('.version');
-        assert.equal(versions[2].innerText, 'version-2');
-        assert.equal(versions[3].innerText, '--');
-        done();
-      });
+    test('versions', async () => {
+      await flush();
+      const versions = element.root.querySelectorAll('.version');
+      assert.equal(versions[2].innerText, 'version-2');
+      assert.equal(versions[3].innerText, '--');
     });
 
-    test('api versions', done => {
-      flush(() => {
-        const apiVersions = element.root.querySelectorAll(
-            '.apiVersion');
-        assert.equal(apiVersions[3].innerText, 'api-version-3');
-        assert.equal(apiVersions[4].innerText, '--');
-        done();
-      });
+    test('api versions', async () => {
+      await flush();
+      const apiVersions = element.root.querySelectorAll(
+          '.apiVersion');
+      assert.equal(apiVersions[3].innerText, 'api-version-3');
+      assert.equal(apiVersions[4].innerText, '--');
     });
 
     test('_shownPlugins', () => {
@@ -106,10 +102,11 @@
   });
 
   suite('list with less then 26 plugins', () => {
-    setup(done => {
+    setup(async () => {
       plugins = _.times(25, pluginGenerator);
       stubRestApi('getPlugins').returns(Promise.resolve(plugins));
-      element._paramsChanged(value).then(() => { flush(done); });
+      await element._paramsChanged(value);
+      await flush();
     });
 
     test('_shownPlugins', () => {
@@ -133,7 +130,7 @@
   });
 
   suite('loading', () => {
-    test('correct contents are displayed', () => {
+    test('correct contents are displayed', async () => {
       assert.isTrue(element._loading);
       assert.equal(element.computeLoadingClass(element._loading), 'loading');
       assert.equal(getComputedStyle(element.$.loading).display, 'block');
@@ -141,30 +138,33 @@
       element._loading = false;
       element._plugins = _.times(25, pluginGenerator);
 
-      flush();
+      await flush();
       assert.equal(element.computeLoadingClass(element._loading), '');
       assert.equal(getComputedStyle(element.$.loading).display, 'none');
     });
   });
 
   suite('404', () => {
-    test('fires page-error', done => {
+    test('fires page-error', async () => {
       const response = {status: 404};
       stubRestApi('getPlugins').callsFake(
           (filter, pluginsPerPage, opt_offset, errFn) => {
             errFn(response);
+            return Promise.resolve(undefined);
           });
 
+      const promise = mockPromise();
       addListenerForTest(document, 'page-error', e => {
         assert.deepEqual(e.detail.response, response);
-        done();
+        promise.resolve();
       });
 
       const value = {
         filter: 'test',
         offset: 25,
       };
-      element._paramsChanged(value);
+      await element._paramsChanged(value);
+      await promise;
     });
   });
 });
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.js b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.js
index 2b7fdf5..1ccfd5e 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.js
@@ -20,7 +20,11 @@
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
 import {toSortedPermissionsArray} from '../../../utils/access-util.js';
-import {addListenerForTest, stubRestApi} from '../../../test/test-utils.js';
+import {
+  addListenerForTest,
+  mockPromise,
+  stubRestApi,
+} from '../../../test/test-utils.js';
 
 const basicFixture = fixtureFromElement('gr-repo-access');
 
@@ -238,19 +242,22 @@
     assert.equal(element._computeLoadingClass(false), '');
   });
 
-  test('fires page-error', done => {
+  test('fires page-error', async () => {
     const response = {status: 404};
 
     stubRestApi('getRepoAccessRights').callsFake((repoName, errFn) => {
       errFn(response);
+      return Promise.resolve(undefined);
     });
 
+    const promise = mockPromise();
     addListenerForTest(document, 'page-error', e => {
       assert.deepEqual(e.detail.response, response);
-      done();
+      promise.resolve();
     });
 
     element.repo = 'test';
+    await promise;
   });
 
   suite('with defined sections', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.js b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.js
index 893efe55..ac48484 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.js
@@ -18,7 +18,11 @@
 import '../../../test/common-test-setup-karma.js';
 import './gr-repo-commands.js';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
-import {addListenerForTest, stubRestApi} from '../../../test/test-utils.js';
+import {
+  addListenerForTest,
+  mockPromise,
+  stubRestApi,
+} from '../../../test/test-utils.js';
 
 const basicFixture = fixtureFromElement('gr-repo-commands');
 
@@ -112,7 +116,7 @@
   });
 
   suite('404', () => {
-    test('fires page-error', done => {
+    test('fires page-error', async () => {
       repoStub.restore();
 
       element.repo = 'test';
@@ -120,13 +124,18 @@
       const response = {status: 404};
       stubRestApi('getProjectConfig').callsFake((repo, errFn) => {
         errFn(response);
+        return Promise.resolve(undefined);
       });
+
+      await flush();
+      const promise = mockPromise();
       addListenerForTest(document, 'page-error', e => {
         assert.deepEqual(e.detail.response, response);
-        done();
+        promise.resolve();
       });
 
       element._loadRepo();
+      await promise;
     });
   });
 });
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.ts b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.ts
index ede2bb9..a54eafb 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.ts
@@ -21,6 +21,7 @@
 import {GerritNav} from '../../core/gr-navigation/gr-navigation';
 import {
   addListenerForTest,
+  mockPromise,
   queryAndAssert,
   stubRestApi,
 } from '../../../test/test-utils';
@@ -92,7 +93,7 @@
       );
     });
 
-    test('loading, sections, and ordering', done => {
+    test('loading, sections, and ordering', async () => {
       assert.isTrue(element._loading);
       assert.notEqual(
         getComputedStyle(queryAndAssert(element, '#loadingContainer')).display,
@@ -103,29 +104,25 @@
         'none'
       );
       element.repo = 'test' as RepoName;
-      flush(() => {
-        assert.equal(
-          getComputedStyle(queryAndAssert(element, '#loadingContainer'))
-            .display,
-          'none'
-        );
-        assert.notEqual(
-          getComputedStyle(queryAndAssert(element, '#dashboards')).display,
-          'none'
-        );
+      await flush();
+      assert.equal(
+        getComputedStyle(queryAndAssert(element, '#loadingContainer')).display,
+        'none'
+      );
+      assert.notEqual(
+        getComputedStyle(queryAndAssert(element, '#dashboards')).display,
+        'none'
+      );
 
-        const dashboard = element._dashboards!;
-        assert.equal(dashboard.length!, 2);
-        assert.equal(dashboard[0].section!, 'custom');
-        assert.equal(dashboard[1].section!, 'default');
+      const dashboard = element._dashboards!;
+      assert.equal(dashboard.length!, 2);
+      assert.equal(dashboard[0].section!, 'custom');
+      assert.equal(dashboard[1].section!, 'default');
 
-        const dashboards = dashboard[0].dashboards;
-        assert.equal(dashboards.length, 2);
-        assert.equal(dashboards[0].id, 'custom:custom1');
-        assert.equal(dashboards[1].id, 'custom:custom2');
-
-        done();
-      });
+      const dashboards = dashboard[0].dashboards;
+      assert.equal(dashboards.length, 2);
+      assert.equal(dashboards[0].id, 'custom:custom1');
+      assert.equal(dashboards[1].id, 'custom:custom2');
     });
   });
 
@@ -148,19 +145,21 @@
   });
 
   suite('404', () => {
-    test('fires page-error', done => {
+    test('fires page-error', async () => {
       const response = {status: 404} as Response;
       stubRestApi('getRepoDashboards').callsFake((_repo, errFn) => {
         errFn!(response);
         return Promise.resolve([]);
       });
 
+      const promise = mockPromise();
       addListenerForTest(document, 'page-error', e => {
         assert.deepEqual((e as PageErrorEvent).detail.response, response);
-        done();
+        promise.resolve();
       });
 
       element.repo = 'test' as RepoName;
+      await promise;
     });
   });
 });
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.js b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.js
index 1af9c02..d5eb5d5 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.js
@@ -20,7 +20,11 @@
 import 'lodash/lodash.js';
 import {page} from '../../../utils/page-wrapper-utils.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
-import {addListenerForTest, stubRestApi} from '../../../test/test-utils.js';
+import {
+  addListenerForTest,
+  mockPromise,
+  stubRestApi,
+} from '../../../test/test-utils.js';
 import {RepoDetailView} from '../../core/gr-navigation/gr-navigation.js';
 
 const basicFixture = fixtureFromElement('gr-repo-detail-list');
@@ -71,7 +75,7 @@
     });
 
     suite('list of repo branches', () => {
-      setup(done => {
+      setup(async () => {
         branches = [{
           ref: 'HEAD',
           revision: 'master',
@@ -82,53 +86,43 @@
           repo: 'test',
           detail: 'branches',
         };
-        element._paramsChanged(params).then(() => { flush(done); });
+        await element._paramsChanged(params);
+        await flush();
       });
 
-      test('test for branch in the list', done => {
-        flush(() => {
-          assert.equal(element._items[2].ref, 'refs/heads/test2');
-          done();
-        });
+      test('test for branch in the list', () => {
+        assert.equal(element._items[2].ref, 'refs/heads/test2');
       });
 
-      test('test for web links in the branches list', done => {
-        flush(() => {
-          assert.equal(element._items[2].web_links[0].url,
-              'https://git.example.org/branch/test;refs/heads/test2');
-          done();
-        });
+      test('test for web links in the branches list', () => {
+        assert.equal(element._items[2].web_links[0].url,
+            'https://git.example.org/branch/test;refs/heads/test2');
       });
 
-      test('test for refs/heads/ being striped from ref', done => {
-        flush(() => {
-          assert.equal(element._stripRefs(element._items[2].ref,
-              element.detailType), 'test2');
-          done();
-        });
+      test('test for refs/heads/ being striped from ref', () => {
+        assert.equal(element._stripRefs(element._items[2].ref,
+            element.detailType), 'test2');
       });
 
       test('_shownItems', () => {
         assert.equal(element._shownItems.length, 25);
       });
 
-      test('Edit HEAD button not admin', done => {
+      test('Edit HEAD button not admin', async () => {
         sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(true));
         stubRestApi('getRepoAccess').returns(
             Promise.resolve({
               test: {is_owner: false},
             }));
-        element._determineIfOwner('test').then(() => {
-          assert.equal(element._isOwner, false);
-          assert.equal(getComputedStyle(dom(element.root)
-              .querySelector('.revisionNoEditing')).display, 'inline');
-          assert.equal(getComputedStyle(dom(element.root)
-              .querySelector('.revisionEdit')).display, 'none');
-          done();
-        });
+        await element._determineIfOwner('test');
+        assert.equal(element._isOwner, false);
+        assert.equal(getComputedStyle(dom(element.root)
+            .querySelector('.revisionNoEditing')).display, 'inline');
+        assert.equal(getComputedStyle(dom(element.root)
+            .querySelector('.revisionEdit')).display, 'none');
       });
 
-      test('Edit HEAD button admin', done => {
+      test('Edit HEAD button admin', async () => {
         const saveBtn = element.root.querySelector('.saveBtn');
         const cancelBtn = element.root.querySelector('.cancelBtn');
         const editBtn = element.root.querySelector('.editBtn');
@@ -143,76 +137,74 @@
               test: {is_owner: true},
             }));
         sinon.stub(element, '_handleSaveRevision');
-        element._determineIfOwner('test').then(() => {
-          assert.equal(element._isOwner, true);
-          // The revision container for non-editing enabled row is not visible.
-          assert.equal(getComputedStyle(revisionNoEditing).display, 'none');
+        await element._determineIfOwner('test');
+        assert.equal(element._isOwner, true);
+        // The revision container for non-editing enabled row is not visible.
+        assert.equal(getComputedStyle(revisionNoEditing).display, 'none');
 
-          // The revision container for editing enabled row is visible.
-          assert.notEqual(getComputedStyle(dom(element.root)
-              .querySelector('.revisionEdit')).display, 'none');
+        // The revision container for editing enabled row is visible.
+        assert.notEqual(getComputedStyle(dom(element.root)
+            .querySelector('.revisionEdit')).display, 'none');
 
-          // The revision and edit button are visible.
-          assert.notEqual(getComputedStyle(revisionWithEditing).display,
-              'none');
-          assert.notEqual(getComputedStyle(editBtn).display, 'none');
+        // The revision and edit button are visible.
+        assert.notEqual(getComputedStyle(revisionWithEditing).display,
+            'none');
+        assert.notEqual(getComputedStyle(editBtn).display, 'none');
 
-          // The input, cancel, and save buttons are not visible.
-          const hiddenElements = dom(element.root)
-              .querySelectorAll('.canEdit .editItem');
+        // The input, cancel, and save buttons are not visible.
+        const hiddenElements = dom(element.root)
+            .querySelectorAll('.canEdit .editItem');
 
-          for (const item of hiddenElements) {
-            assert.equal(getComputedStyle(item).display, 'none');
-          }
+        for (const item of hiddenElements) {
+          assert.equal(getComputedStyle(item).display, 'none');
+        }
 
-          MockInteractions.tap(editBtn);
-          flush();
-          // The revision and edit button are not visible.
-          assert.equal(getComputedStyle(revisionWithEditing).display, 'none');
-          assert.equal(getComputedStyle(editBtn).display, 'none');
+        MockInteractions.tap(editBtn);
+        await flush();
+        // The revision and edit button are not visible.
+        assert.equal(getComputedStyle(revisionWithEditing).display, 'none');
+        assert.equal(getComputedStyle(editBtn).display, 'none');
 
-          // The input, cancel, and save buttons are not visible.
-          for (const item of hiddenElements) {
-            assert.notEqual(getComputedStyle(item).display, 'none');
-          }
+        // The input, cancel, and save buttons are not visible.
+        for (const item of hiddenElements) {
+          assert.notEqual(getComputedStyle(item).display, 'none');
+        }
 
-          // The revised ref was set correctly
-          assert.equal(element._revisedRef, 'master');
+        // The revised ref was set correctly
+        assert.equal(element._revisedRef, 'master');
 
-          assert.isFalse(saveBtn.disabled);
+        assert.isFalse(saveBtn.disabled);
 
-          // Delete the ref.
-          element._revisedRef = '';
-          assert.isTrue(saveBtn.disabled);
+        // Delete the ref.
+        element._revisedRef = '';
+        assert.isTrue(saveBtn.disabled);
 
-          // Change the ref to something else
-          element._revisedRef = 'newRef';
-          element._repo = 'test';
-          assert.isFalse(saveBtn.disabled);
+        // Change the ref to something else
+        element._revisedRef = 'newRef';
+        element._repo = 'test';
+        assert.isFalse(saveBtn.disabled);
 
-          // Save button calls handleSave. since this is stubbed, the edit
-          // section remains open.
-          MockInteractions.tap(saveBtn);
-          assert.isTrue(element._handleSaveRevision.called);
+        // Save button calls handleSave. since this is stubbed, the edit
+        // section remains open.
+        MockInteractions.tap(saveBtn);
+        assert.isTrue(element._handleSaveRevision.called);
 
-          // When cancel is tapped, the edit secion closes.
-          MockInteractions.tap(cancelBtn);
-          flush();
+        // When cancel is tapped, the edit secion closes.
+        MockInteractions.tap(cancelBtn);
+        await flush();
 
-          // The revision and edit button are visible.
-          assert.notEqual(getComputedStyle(revisionWithEditing).display,
-              'none');
-          assert.notEqual(getComputedStyle(editBtn).display, 'none');
+        // The revision and edit button are visible.
+        assert.notEqual(getComputedStyle(revisionWithEditing).display,
+            'none');
+        assert.notEqual(getComputedStyle(editBtn).display, 'none');
 
-          // The input, cancel, and save buttons are not visible.
-          for (const item of hiddenElements) {
-            assert.equal(getComputedStyle(item).display, 'none');
-          }
-          done();
-        });
+        // The input, cancel, and save buttons are not visible.
+        for (const item of hiddenElements) {
+          assert.equal(getComputedStyle(item).display, 'none');
+        }
       });
 
-      test('_handleSaveRevision with invalid rev', done => {
+      test('_handleSaveRevision with invalid rev', async () => {
         const event = {model: {set: sinon.stub()}};
         element._isEditing = true;
         stubRestApi('setRepoHead').returns(
@@ -221,14 +213,12 @@
             })
         );
 
-        element._setRepoHead('test', 'newRef', event).then(() => {
-          assert.isTrue(element._isEditing);
-          assert.isFalse(event.model.set.called);
-          done();
-        });
+        await element._setRepoHead('test', 'newRef', event);
+        assert.isTrue(element._isEditing);
+        assert.isFalse(event.model.set.called);
       });
 
-      test('_handleSaveRevision with valid rev', done => {
+      test('_handleSaveRevision with valid rev', async () => {
         const event = {model: {set: sinon.stub()}};
         element._isEditing = true;
         stubRestApi('setRepoHead').returns(
@@ -237,11 +227,9 @@
             })
         );
 
-        element._setRepoHead('test', 'newRef', event).then(() => {
-          assert.isFalse(element._isEditing);
-          assert.isTrue(event.model.set.called);
-          done();
-        });
+        await element._setRepoHead('test', 'newRef', event);
+        assert.isFalse(element._isEditing);
+        assert.isTrue(event.model.set.called);
       });
 
       test('test _computeItemName', () => {
@@ -251,7 +239,7 @@
     });
 
     suite('list with less then 25 branches', () => {
-      setup(done => {
+      setup(async () => {
         branches = _.times(25, branchGenerator);
         stubRestApi('getRepoBranches').returns(Promise.resolve(branches));
 
@@ -260,7 +248,8 @@
           detail: 'branches',
         };
 
-        element._paramsChanged(params).then(() => { flush(done); });
+        await element._paramsChanged(params);
+        await flush();
       });
 
       test('_shownItems', () => {
@@ -287,16 +276,18 @@
     });
 
     suite('404', () => {
-      test('fires page-error', done => {
+      test('fires page-error', async () => {
         const response = {status: 404};
         stubRestApi('getRepoBranches').callsFake(
             (filter, repo, reposBranchesPerPage, opt_offset, errFn) => {
               errFn(response);
+              return Promise.resolve();
             });
 
+        const promise = mockPromise();
         addListenerForTest(document, 'page-error', e => {
           assert.deepEqual(e.detail.response, response);
-          done();
+          promise.resolve();
         });
 
         const params = {
@@ -306,6 +297,7 @@
           offset: 25,
         };
         element._paramsChanged(params);
+        await promise;
       });
     });
   });
@@ -341,7 +333,7 @@
     });
 
     suite('list of repo tags', () => {
-      setup(done => {
+      setup(async () => {
         tags = _.times(26, tagGenerator);
         stubRestApi('getRepoTags').returns(Promise.resolve(tags));
 
@@ -350,50 +342,37 @@
           detail: 'tags',
         };
 
-        element._paramsChanged(params).then(() => { flush(done); });
+        await element._paramsChanged(params);
+        await flush();
       });
 
-      test('test for tag in the list', done => {
-        flush(() => {
-          assert.equal(element._items[1].ref, 'refs/tags/test2');
-          done();
-        });
+      test('test for tag in the list', async () => {
+        assert.equal(element._items[1].ref, 'refs/tags/test2');
       });
 
-      test('test for tag message in the list', done => {
-        flush(() => {
-          assert.equal(element._items[1].message, 'Annotated tag');
-          done();
-        });
+      test('test for tag message in the list', async () => {
+        assert.equal(element._items[1].message, 'Annotated tag');
       });
 
-      test('test for tagger in the tag list', done => {
+      test('test for tagger in the tag list', async () => {
         const tagger = {
           name: 'Test User',
           email: 'test.user@gmail.com',
           date: '2017-09-19 14:54:00.000000000',
           tz: 540,
         };
-        flush(() => {
-          assert.deepEqual(element._items[1].tagger, tagger);
-          done();
-        });
+
+        assert.deepEqual(element._items[1].tagger, tagger);
       });
 
-      test('test for web links in the tags list', done => {
-        flush(() => {
-          assert.equal(element._items[1].web_links[0].url,
-              'https://git.example.org/tag/test;refs/tags/test2');
-          done();
-        });
+      test('test for web links in the tags list', async () => {
+        assert.equal(element._items[1].web_links[0].url,
+            'https://git.example.org/tag/test;refs/tags/test2');
       });
 
-      test('test for refs/tags/ being striped from ref', done => {
-        flush(() => {
-          assert.equal(element._stripRefs(element._items[1].ref,
-              element.detailType), 'test2');
-          done();
-        });
+      test('test for refs/tags/ being striped from ref', async () => {
+        assert.equal(element._stripRefs(element._items[1].ref,
+            element.detailType), 'test2');
       });
 
       test('_shownItems', () => {
@@ -411,7 +390,7 @@
     });
 
     suite('list with less then 25 tags', () => {
-      setup(done => {
+      setup(async () => {
         tags = _.times(25, tagGenerator);
         stubRestApi('getRepoTags').returns(Promise.resolve(tags));
 
@@ -420,7 +399,8 @@
           detail: 'tags',
         };
 
-        element._paramsChanged(params).then(() => { flush(done); });
+        await element._paramsChanged(params);
+        await flush();
       });
 
       test('_shownItems', () => {
@@ -482,16 +462,18 @@
     });
 
     suite('404', () => {
-      test('fires page-error', done => {
+      test('fires page-error', async () => {
         const response = {status: 404};
         stubRestApi('getRepoTags').callsFake(
             (filter, repo, reposTagsPerPage, opt_offset, errFn) => {
               errFn(response);
+              return Promise.resolve();
             });
 
+        const promise = mockPromise();
         addListenerForTest(document, 'page-error', e => {
           assert.deepEqual(e.detail.response, response);
-          done();
+          promise.resolve();
         });
 
         const params = {
@@ -501,6 +483,7 @@
           offset: 25,
         };
         element._paramsChanged(params);
+        await promise;
       });
     });
 
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.js b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.js
index 4864af5..8fef4d0 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.js
@@ -53,17 +53,16 @@
   });
 
   suite('list with repos', () => {
-    setup(done => {
+    setup(async () => {
       repos = _.times(26, repoGenerator);
       stubRestApi('getRepos').returns(Promise.resolve(repos));
-      element._paramsChanged(value).then(() => { flush(done); });
+      await element._paramsChanged(value);
+      await flush();
     });
 
-    test('test for test repo in the list', done => {
-      flush(() => {
-        assert.equal(element._repos[1].id, 'test2');
-        done();
-      });
+    test('test for test repo in the list', async () => {
+      await flush();
+      assert.equal(element._repos[1].id, 'test2');
     });
 
     test('_shownRepos', () => {
@@ -84,10 +83,11 @@
   });
 
   suite('list with less then 25 repos', () => {
-    setup(done => {
+    setup(async () => {
       repos = _.times(25, repoGenerator);
       stubRestApi('getRepos').returns(Promise.resolve(repos));
-      element._paramsChanged(value).then(() => { flush(done); });
+      await element._paramsChanged(value);
+      await flush();
     });
 
     test('_shownRepos', () => {
@@ -113,17 +113,15 @@
       assert.isTrue(repoStub.lastCall.calledWithExactly('test', 25, 25));
     });
 
-    test('latest repos requested are always set', done => {
+    test('latest repos requested are always set', async () => {
       const repoStub = stubRestApi('getRepos');
       repoStub.withArgs('test').returns(Promise.resolve(repos));
       repoStub.withArgs('filter').returns(Promise.resolve(reposFiltered));
       element._filter = 'test';
 
       // Repos are not set because the element._filter differs.
-      element._getRepos('filter', 25, 0).then(() => {
-        assert.deepEqual(element._repos, []);
-        done();
-      });
+      await element._getRepos('filter', 25, 0);
+      assert.deepEqual(element._repos, []);
     });
 
     test('filter is case insensitive', async () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.js b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.js
index 9c3646a..f3df132 100644
--- a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.js
@@ -180,7 +180,7 @@
   });
 
   suite('already existing generic rule', () => {
-    setup(done => {
+    setup(async () => {
       element.group = 'Group Name';
       element.permission = 'submit';
       element.rule = {
@@ -195,11 +195,8 @@
       // Typically called on ready since elements will have properties defined
       // by the parent element.
       element._setupValues(element.rule);
-      flush();
-      flush(() => {
-        element.connectedCallback();
-        done();
-      });
+      await flush();
+      element.connectedCallback();
     });
 
     test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -295,7 +292,7 @@
   });
 
   suite('new edit rule', () => {
-    setup(done => {
+    setup(async () => {
       element.group = 'Group Name';
       element.permission = 'editTopicName';
       element.rule = {
@@ -303,12 +300,10 @@
       };
       element.section = 'refs/*';
       element._setupValues(element.rule);
-      flush();
+      await flush();
       element.rule.value.added = true;
-      flush(() => {
-        element.connectedCallback();
-        done();
-      });
+      await flush();
+      element.connectedCallback();
     });
 
     test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -348,7 +343,7 @@
   });
 
   suite('already existing rule with labels', () => {
-    setup(done => {
+    setup(async () => {
       element.label = {values: [
         {value: -2, text: 'This shall not be merged'},
         {value: -1, text: 'I would prefer this is not merged as is'},
@@ -369,11 +364,8 @@
       };
       element.section = 'refs/*';
       element._setupValues(element.rule);
-      flush();
-      flush(() => {
-        element.connectedCallback();
-        done();
-      });
+      await flush();
+      element.connectedCallback();
     });
 
     test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -406,7 +398,7 @@
   });
 
   suite('new rule with labels', () => {
-    setup(done => {
+    setup(async () => {
       sinon.spy(element, '_setDefaultRuleValues');
       element.label = {values: [
         {value: -2, text: 'This shall not be merged'},
@@ -422,12 +414,10 @@
       };
       element.section = 'refs/*';
       element._setupValues(element.rule);
-      flush();
+      await flush();
       element.rule.value.added = true;
-      flush(() => {
-        element.connectedCallback();
-        done();
-      });
+      await flush();
+      element.connectedCallback();
     });
 
     test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -468,7 +458,7 @@
   });
 
   suite('already existing push rule', () => {
-    setup(done => {
+    setup(async () => {
       element.group = 'Group Name';
       element.permission = 'push';
       element.rule = {
@@ -480,11 +470,8 @@
       };
       element.section = 'refs/*';
       element._setupValues(element.rule);
-      flush();
-      flush(() => {
-        element.connectedCallback();
-        done();
-      });
+      await flush();
+      element.connectedCallback();
     });
 
     test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -513,7 +500,7 @@
   });
 
   suite('new push rule', () => {
-    setup(done => {
+    setup(async () => {
       element.group = 'Group Name';
       element.permission = 'push';
       element.rule = {
@@ -521,12 +508,10 @@
       };
       element.section = 'refs/*';
       element._setupValues(element.rule);
-      flush();
+      await flush();
       element.rule.value.added = true;
-      flush(() => {
-        element.connectedCallback();
-        done();
-      });
+      await flush();
+      element.connectedCallback();
     });
 
     test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -557,7 +542,7 @@
   });
 
   suite('already existing edit rule', () => {
-    setup(done => {
+    setup(async () => {
       element.group = 'Group Name';
       element.permission = 'editTopicName';
       element.rule = {
@@ -569,11 +554,8 @@
       };
       element.section = 'refs/*';
       element._setupValues(element.rule);
-      flush();
-      flush(() => {
-        element.connectedCallback();
-        done();
-      });
+      await flush();
+      element.connectedCallback();
     });
 
     test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -590,10 +572,10 @@
       assert.isNotOk(element.root.querySelector('#labelMax'));
     });
 
-    test('modify value', () => {
+    test('modify value', async () => {
       assert.isNotOk(element.rule.value.modified);
       element.$.action.bindValue = false;
-      flush();
+      await flush();
       assert.isTrue(element.rule.value.modified);
 
       // The original value should now differ from the rule values.
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.js b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.js
index 445254a..86b2fd1 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.js
@@ -20,7 +20,7 @@
 import {page} from '../../../utils/page-wrapper-utils.js';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
 import 'lodash/lodash.js';
-import {stubRestApi} from '../../../test/test-utils.js';
+import {mockPromise, stubRestApi} from '../../../test/test-utils.js';
 
 const basicFixture = fixtureFromElement('gr-change-list-view');
 
@@ -38,10 +38,8 @@
     element = basicFixture.instantiate();
   });
 
-  teardown(done => {
-    flush(() => {
-      done();
-    });
+  teardown(async () => {
+    await flush();
   });
 
   test('_computePage', () => {
@@ -126,128 +124,123 @@
     assert.isTrue(showStub.called);
   });
 
-  test('_userId query', done => {
+  test('_userId query', async () => {
     assert.isNull(element._userId);
     element._query = 'owner: foo@bar';
     element._changes = [{owner: {email: 'foo@bar'}}];
-    flush(() => {
-      assert.equal(element._userId, 'foo@bar');
+    await flush();
+    assert.equal(element._userId, 'foo@bar');
 
-      element._query = 'foo bar baz';
-      element._changes = [{owner: {email: 'foo@bar'}}];
-      assert.isNull(element._userId);
-
-      done();
-    });
+    element._query = 'foo bar baz';
+    element._changes = [{owner: {email: 'foo@bar'}}];
+    assert.isNull(element._userId);
   });
 
-  test('_userId query without email', done => {
+  test('_userId query without email', async () => {
     assert.isNull(element._userId);
     element._query = 'owner: foo@bar';
     element._changes = [{owner: {}}];
-    flush(() => {
-      assert.isNull(element._userId);
-      done();
-    });
+    await flush();
+    assert.isNull(element._userId);
   });
 
-  test('_repo query', done => {
+  test('_repo query', async () => {
     assert.isNull(element._repo);
     element._query = 'project: test-repo';
     element._changes = [{owner: {email: 'foo@bar'}, project: 'test-repo'}];
-    flush(() => {
-      assert.equal(element._repo, 'test-repo');
-      element._query = 'foo bar baz';
-      element._changes = [{owner: {email: 'foo@bar'}}];
-      assert.isNull(element._repo);
-      done();
-    });
+    await flush();
+    assert.equal(element._repo, 'test-repo');
+    element._query = 'foo bar baz';
+    element._changes = [{owner: {email: 'foo@bar'}}];
+    assert.isNull(element._repo);
   });
 
-  test('_repo query with open status', done => {
+  test('_repo query with open status', async () => {
     assert.isNull(element._repo);
     element._query = 'project:test-repo status:open';
     element._changes = [{owner: {email: 'foo@bar'}, project: 'test-repo'}];
-    flush(() => {
-      assert.equal(element._repo, 'test-repo');
-      element._query = 'foo bar baz';
-      element._changes = [{owner: {email: 'foo@bar'}}];
-      assert.isNull(element._repo);
-      done();
-    });
+    await flush();
+    assert.equal(element._repo, 'test-repo');
+    element._query = 'foo bar baz';
+    element._changes = [{owner: {email: 'foo@bar'}}];
+    assert.isNull(element._repo);
   });
 
   suite('query based navigation', () => {
     setup(() => {
     });
 
-    teardown(done => {
-      flush(() => {
-        sinon.restore();
-        done();
-      });
+    teardown(async () => {
+      await flush();
+      sinon.restore();
     });
 
-    test('Searching for a change ID redirects to change', done => {
+    test('Searching for a change ID redirects to change', async () => {
       const change = {_number: 1};
       sinon.stub(element, '_getChanges')
           .returns(Promise.resolve([change]));
+      const promise = mockPromise();
       sinon.stub(GerritNav, 'navigateToChange').callsFake(
           (url, opt_patchNum, opt_basePatchNum, opt_isEdit, opt_redirect) => {
             assert.equal(url, change);
             assert.isTrue(opt_redirect);
-            done();
+            promise.resolve();
           });
 
       element.params = {view: GerritNav.View.SEARCH, query: CHANGE_ID};
+      await promise;
     });
 
-    test('Searching for a change num redirects to change', done => {
+    test('Searching for a change num redirects to change', async () => {
       const change = {_number: 1};
       sinon.stub(element, '_getChanges')
           .returns(Promise.resolve([change]));
+      const promise = mockPromise();
       sinon.stub(GerritNav, 'navigateToChange').callsFake(
           (url, opt_patchNum, opt_basePatchNum, opt_isEdit, opt_redirect) => {
             assert.equal(url, change);
             assert.isTrue(opt_redirect);
-            done();
+            promise.resolve();
           });
 
       element.params = {view: GerritNav.View.SEARCH, query: '1'};
+      await promise;
     });
 
-    test('Commit hash redirects to change', done => {
+    test('Commit hash redirects to change', async () => {
       const change = {_number: 1};
       sinon.stub(element, '_getChanges')
           .returns(Promise.resolve([change]));
+      const promise = mockPromise();
       sinon.stub(GerritNav, 'navigateToChange').callsFake(
           (url, opt_patchNum, opt_basePatchNum, opt_isEdit, opt_redirect) => {
             assert.equal(url, change);
             assert.isTrue(opt_redirect);
-            done();
+            promise.resolve();
           });
 
       element.params = {view: GerritNav.View.SEARCH, query: COMMIT_HASH};
+      await promise;
     });
 
-    test('Searching for an invalid change ID searches', () => {
+    test('Searching for an invalid change ID searches', async () => {
       sinon.stub(element, '_getChanges')
           .returns(Promise.resolve([]));
       const stub = sinon.stub(GerritNav, 'navigateToChange');
 
       element.params = {view: GerritNav.View.SEARCH, query: CHANGE_ID};
-      flush();
+      await flush();
 
       assert.isFalse(stub.called);
     });
 
-    test('Change ID with multiple search results searches', () => {
+    test('Change ID with multiple search results searches', async () => {
       sinon.stub(element, '_getChanges')
           .returns(Promise.resolve([{}, {}]));
       const stub = sinon.stub(GerritNav, 'navigateToChange');
 
       element.params = {view: GerritNav.View.SEARCH, query: CHANGE_ID};
-      flush();
+      await flush();
 
       assert.isFalse(stub.called);
     });
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.js b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.js
index 13490d7..0d2056a 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.js
@@ -19,7 +19,7 @@
 import './gr-change-list.js';
 import {afterNextRender} from '@polymer/polymer/lib/utils/render-status.js';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
-import {TestKeyboardShortcutBinder} from '../../../test/test-utils.js';
+import {mockPromise, TestKeyboardShortcutBinder} from '../../../test/test-utils.js';
 import {Shortcut} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.js';
 import {YOUR_TURN} from '../../core/gr-navigation/gr-navigation.js';
 
@@ -149,7 +149,7 @@
         {}, changeTableColumns, labelNames));
   });
 
-  test('keyboard shortcuts', done => {
+  test('keyboard shortcuts', async () => {
     sinon.stub(element, '_computeLabelNames');
     element.sections = [
       {results: new Array(1)},
@@ -161,7 +161,8 @@
       {_number: 1},
       {_number: 2},
     ];
-    flush();
+    await flush();
+    const promise = mockPromise();
     afterNextRender(element, () => {
       const elementItems = element.root.querySelectorAll(
           'gr-change-list-item');
@@ -192,8 +193,9 @@
       MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
       assert.equal(element.selectedIndex, 0);
 
-      done();
+      promise.resolve();
     });
+    await promise;
   });
 
   test('no changes', () => {
@@ -436,7 +438,7 @@
       element = basicFixture.instantiate();
     });
 
-    test('keyboard shortcuts', done => {
+    test('keyboard shortcuts', async () => {
       element.selectedIndex = 0;
       element.sections = [
         {
@@ -461,7 +463,8 @@
           ],
         },
       ];
-      flush();
+      await flush();
+      const promise = mockPromise();
       afterNextRender(element, () => {
         const elementItems = element.root.querySelectorAll(
             'gr-change-list-item');
@@ -499,8 +502,9 @@
         MockInteractions.pressAndReleaseKeyOn(element, 82); // 'r'
         assert.equal(change.reviewed, false,
             'Should mark change as unreviewed');
-        done();
+        promise.resolve();
       });
+      await promise;
     });
 
     test('_computeItemHighlight gives false for null account', () => {
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.ts b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.ts
index 1a359e5..e170a74 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.ts
@@ -18,7 +18,7 @@
 import '../../../test/common-test-setup-karma';
 import './gr-create-change-help';
 import {GrCreateChangeHelp} from './gr-create-change-help';
-import {queryAndAssert} from '../../../test/test-utils';
+import {mockPromise, queryAndAssert} from '../../../test/test-utils';
 import {GrButton} from '../../shared/gr-button/gr-button';
 import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
 
@@ -32,8 +32,10 @@
     await flush();
   });
 
-  test('Create change tap', done => {
-    element.addEventListener('create-tap', () => done());
+  test('Create change tap', async () => {
+    const promise = mockPromise();
+    element.addEventListener('create-tap', () => promise.resolve());
     MockInteractions.tap(queryAndAssert<GrButton>(element, 'gr-button'));
+    await promise;
   });
 });
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.js b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.js
index 473d2b9..96a19e0 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.js
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.js
@@ -22,7 +22,7 @@
 import {changeIsOpen} from '../../../utils/change-util.js';
 import {ChangeStatus} from '../../../constants/constants.js';
 import {createAccountWithId} from '../../../test/test-data-generators.js';
-import {addListenerForTest, stubRestApi, isHidden} from '../../../test/test-utils.js';
+import {addListenerForTest, stubRestApi, isHidden, mockPromise} from '../../../test/test-utils.js';
 
 const basicFixture = fixtureFromElement('gr-dashboard-view');
 
@@ -395,21 +395,23 @@
         'hide');
   });
 
-  test('404 page', done => {
+  test('404 page', async () => {
     const response = {status: 404};
     stubRestApi('getDashboard').callsFake(
         async (project, dashboard, errFn) => {
           errFn(response);
         });
+    const promise = mockPromise();
     addListenerForTest(document, 'page-error', e => {
       assert.strictEqual(e.detail.response, response);
-      paramsChangedPromise.then(done);
+      promise.resolve();
     });
     element.params = {
       view: GerritNav.View.DASHBOARD,
       project: 'project',
       dashboard: 'dashboard',
     };
+    await Promise.all([paramsChangedPromise, promise]);
   });
 
   test('params change triggers dashboardDisplayed()', async () => {
@@ -427,7 +429,7 @@
     assert.isTrue(element.reporting.dashboardDisplayed.calledOnce);
   });
 
-  test('selectedChangeIndex is derived from the params', () => {
+  test('selectedChangeIndex is derived from the params', async () => {
     stubRestApi('getDashboard').returns(Promise.resolve({
       title: 'title',
       sections: [],
@@ -443,9 +445,8 @@
     };
     flush();
     sinon.stub(element.reporting, 'dashboardDisplayed');
-    paramsChangedPromise.then(() => {
-      assert.equal(element._selectedChangeIndex, 23);
-    });
+    await paramsChangedPromise;
+    assert.equal(element._selectedChangeIndex, 23);
   });
 });
 
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
index 5508ee0..6b63000 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
@@ -36,6 +36,7 @@
 
 import 'lodash/lodash';
 import {
+  mockPromise,
   stubRestApi,
   TestKeyboardShortcutBinder,
 } from '../../../test/test-utils';
@@ -389,10 +390,8 @@
     );
   });
 
-  teardown(done => {
-    flush(() => {
-      done();
-    });
+  teardown(async () => {
+    await flush();
   });
 
   test('_handleMessageAnchorTap', () => {
@@ -507,11 +506,11 @@
   });
 
   suite('plugins adding to file tab', () => {
-    setup(done => {
+    setup(async () => {
       element._changeNum = TEST_NUMERIC_CHANGE_ID;
       // Resolving it here instead of during setup() as other tests depend
       // on flush() not being called during setup.
-      flush(() => done());
+      await flush();
     });
 
     test('plugin added tab shows up as a dynamic endpoint', () => {
@@ -527,19 +526,17 @@
       assert.equal(paperTabs[2].dataset.name, 'change-view-tab-header-url');
     });
 
-    test('_setActivePrimaryTab switched tab correctly', done => {
+    test('_setActivePrimaryTab switched tab correctly', async () => {
       element._setActivePrimaryTab(
         new CustomEvent('', {
           detail: {tab: 'change-view-tab-header-url'},
         })
       );
-      flush(() => {
-        assert.equal(element._activeTabs[0], 'change-view-tab-header-url');
-        done();
-      });
+      await flush();
+      assert.equal(element._activeTabs[0], 'change-view-tab-header-url');
     });
 
-    test('show-primary-tab switched primary tab correctly', done => {
+    test('show-primary-tab switched primary tab correctly', async () => {
       element.dispatchEvent(
         new CustomEvent('show-primary-tab', {
           composed: true,
@@ -549,13 +546,11 @@
           },
         })
       );
-      flush(() => {
-        assert.equal(element._activeTabs[0], 'change-view-tab-header-url');
-        done();
-      });
+      await flush();
+      assert.equal(element._activeTabs[0], 'change-view-tab-header-url');
     });
 
-    test('param change should switch primary tab correctly', done => {
+    test('param change should switch primary tab correctly', async () => {
       assert.equal(element._activeTabs[0], PrimaryTab.FILES);
       const queryMap = new Map<string, string>();
       queryMap.set('tab', PrimaryTab.FINDINGS);
@@ -565,13 +560,11 @@
         ...element.params,
         queryMap,
       };
-      flush(() => {
-        assert.equal(element._activeTabs[0], PrimaryTab.FINDINGS);
-        done();
-      });
+      await flush();
+      assert.equal(element._activeTabs[0], PrimaryTab.FINDINGS);
     });
 
-    test('invalid param change should not switch primary tab', done => {
+    test('invalid param change should not switch primary tab', async () => {
       assert.equal(element._activeTabs[0], PrimaryTab.FILES);
       const queryMap = new Map<string, string>();
       queryMap.set('tab', 'random');
@@ -581,22 +574,18 @@
         ...element.params,
         queryMap,
       };
-      flush(() => {
-        assert.equal(element._activeTabs[0], PrimaryTab.FILES);
-        done();
-      });
+      await flush();
+      assert.equal(element._activeTabs[0], PrimaryTab.FILES);
     });
 
-    test('switching tab sets _selectedTabPluginEndpoint', done => {
+    test('switching tab sets _selectedTabPluginEndpoint', async () => {
       const paperTabs = element.shadowRoot!.querySelector('#primaryTabs')!;
       tap(paperTabs.querySelectorAll('paper-tab')[2]);
-      flush(() => {
-        assert.equal(
-          element._selectedTabPluginEndpoint,
-          'change-view-tab-content-url'
-        );
-        done();
-      });
+      await flush();
+      assert.equal(
+        element._selectedTabPluginEndpoint,
+        'change-view-tab-content-url'
+      );
     });
   });
 
@@ -653,28 +642,24 @@
       );
     });
 
-    test('A fires an error event when not logged in', done => {
+    test('A fires an error event when not logged in', async () => {
       sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(false));
       const loggedInErrorSpy = sinon.spy();
       element.addEventListener('show-auth-required', loggedInErrorSpy);
       pressAndReleaseKeyOn(element, 65, null, 'a');
-      flush(() => {
-        assert.isFalse(element.$.replyOverlay.opened);
-        assert.isTrue(loggedInErrorSpy.called);
-        done();
-      });
+      await flush();
+      assert.isFalse(element.$.replyOverlay.opened);
+      assert.isTrue(loggedInErrorSpy.called);
     });
 
-    test('shift A does not open reply overlay', done => {
+    test('shift A does not open reply overlay', async () => {
       sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(true));
       pressAndReleaseKeyOn(element, 65, 'shift', 'a');
-      flush(() => {
-        assert.isFalse(element.$.replyOverlay.opened);
-        done();
-      });
+      await flush();
+      assert.isFalse(element.$.replyOverlay.opened);
     });
 
-    test('A toggles overlay when logged in', done => {
+    test('A toggles overlay when logged in', async () => {
       sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(true));
       element._change = {
         ...createChangeViewChange(),
@@ -695,19 +680,17 @@
       const openSpy = sinon.spy(element, '_openReplyDialog');
 
       pressAndReleaseKeyOn(element, 65, null, 'a');
-      flush(() => {
-        assert.isTrue(element.$.replyOverlay.opened);
-        element.$.replyOverlay.close();
-        assert.isFalse(element.$.replyOverlay.opened);
-        assert(
-          openSpy.lastCall.calledWithExactly(
-            element.$.replyDialog.FocusTarget.ANY
-          ),
-          '_openReplyDialog should have been passed ANY'
-        );
-        assert.equal(openSpy.callCount, 1);
-        done();
-      });
+      await flush();
+      assert.isTrue(element.$.replyOverlay.opened);
+      element.$.replyOverlay.close();
+      assert.isFalse(element.$.replyOverlay.opened);
+      assert(
+        openSpy.lastCall.calledWithExactly(
+          element.$.replyDialog.FocusTarget.ANY
+        ),
+        '_openReplyDialog should have been passed ANY'
+      );
+      assert.equal(openSpy.callCount, 1);
     });
 
     test('fullscreen-overlay-opened hides content', () => {
@@ -785,28 +768,24 @@
       assert.isTrue(handleCollapse.called);
     });
 
-    test('X should expand all messages', done => {
-      flush(() => {
-        const handleExpand = sinon.stub(
-          element.messagesList!,
-          'handleExpandCollapse'
-        );
-        pressAndReleaseKeyOn(element, 88, null, 'x');
-        assert(handleExpand.calledWith(true));
-        done();
-      });
+    test('X should expand all messages', async () => {
+      await flush();
+      const handleExpand = sinon.stub(
+        element.messagesList!,
+        'handleExpandCollapse'
+      );
+      pressAndReleaseKeyOn(element, 88, null, 'x');
+      assert(handleExpand.calledWith(true));
     });
 
-    test('Z should collapse all messages', done => {
-      flush(() => {
-        const handleExpand = sinon.stub(
-          element.messagesList!,
-          'handleExpandCollapse'
-        );
-        pressAndReleaseKeyOn(element, 90, null, 'z');
-        assert(handleExpand.calledWith(false));
-        done();
-      });
+    test('Z should collapse all messages', async () => {
+      await flush();
+      const handleExpand = sinon.stub(
+        element.messagesList!,
+        'handleExpandCollapse'
+      );
+      pressAndReleaseKeyOn(element, 90, null, 'z');
+      assert(handleExpand.calledWith(false));
     });
 
     test('d should open download overlay', () => {
@@ -946,12 +925,10 @@
       );
     });
 
-    test('changing patchsets resets robot comments', done => {
+    test('changing patchsets resets robot comments', async () => {
       element.set('_change.current_revision', 'rev3');
-      flush(() => {
-        assert.equal(element._robotCommentThreads!.length, 1);
-        done();
-      });
+      await flush();
+      assert.equal(element._robotCommentThreads!.length, 1);
     });
 
     test('Show more button is hidden', () => {
@@ -959,15 +936,13 @@
     });
 
     suite('robot comments show more button', () => {
-      setup(done => {
+      setup(async () => {
         const arr = [];
         for (let i = 0; i <= 30; i++) {
           arr.push(...THREADS);
         }
         element._commentThreads = arr;
-        flush(() => {
-          done();
-        });
+        await flush();
       });
 
       test('Show more button is rendered', () => {
@@ -978,12 +953,10 @@
         );
       });
 
-      test('Clicking show more button renders all comments', done => {
+      test('Clicking show more button renders all comments', async () => {
         tap(element.shadowRoot!.querySelector('.show-robot-comments')!);
-        flush(() => {
-          assert.equal(element._robotCommentThreads!.length, 62);
-          done();
-        });
+        await flush();
+        assert.equal(element._robotCommentThreads!.length, 62);
       });
     });
   });
@@ -1005,14 +978,12 @@
     assert.isTrue(openDialogStub.called);
   });
 
-  test('fetches the server config on attached', done => {
-    flush(() => {
-      assert.equal(
-        element._serverConfig!.user.anonymous_coward_name,
-        'test coward name'
-      );
-      done();
-    });
+  test('fetches the server config on attached', async () => {
+    await flush();
+    assert.equal(
+      element._serverConfig!.user.anonymous_coward_name,
+      'test coward name'
+    );
   });
 
   test('_changeStatuses', () => {
@@ -1047,7 +1018,7 @@
   });
 
   suite('ChangeStatus revert', () => {
-    test('do not show any chip if no revert created', done => {
+    test('do not show any chip if no revert created', async () => {
       const change = {
         ...createChange(),
         messages: createChangeMessages(2),
@@ -1066,20 +1037,18 @@
       element._change = change;
       element._mergeable = true;
       element._submitEnabled = true;
-      flush();
+      await flush();
       element.computeRevertSubmitted(element._change);
-      flush(() => {
-        assert.isFalse(
-          element._changeStatuses?.includes(ChangeStates.REVERT_SUBMITTED)
-        );
-        assert.isFalse(
-          element._changeStatuses?.includes(ChangeStates.REVERT_CREATED)
-        );
-        done();
-      });
+      await flush();
+      assert.isFalse(
+        element._changeStatuses?.includes(ChangeStates.REVERT_SUBMITTED)
+      );
+      assert.isFalse(
+        element._changeStatuses?.includes(ChangeStates.REVERT_CREATED)
+      );
     });
 
-    test('do not show any chip if all reverts are abandoned', done => {
+    test('do not show any chip if all reverts are abandoned', async () => {
       const change = {
         ...createChange(),
         messages: createChangeMessages(2),
@@ -1106,20 +1075,18 @@
       element._change = change;
       element._mergeable = true;
       element._submitEnabled = true;
-      flush();
+      await flush();
       element.computeRevertSubmitted(element._change);
-      flush(() => {
-        assert.isFalse(
-          element._changeStatuses?.includes(ChangeStates.REVERT_SUBMITTED)
-        );
-        assert.isFalse(
-          element._changeStatuses?.includes(ChangeStates.REVERT_CREATED)
-        );
-        done();
-      });
+      await flush();
+      assert.isFalse(
+        element._changeStatuses?.includes(ChangeStates.REVERT_SUBMITTED)
+      );
+      assert.isFalse(
+        element._changeStatuses?.includes(ChangeStates.REVERT_CREATED)
+      );
     });
 
-    test('show revert created if no revert is merged', done => {
+    test('show revert created if no revert is merged', async () => {
       const change = {
         ...createChange(),
         messages: createChangeMessages(2),
@@ -1144,20 +1111,18 @@
       element._change = change;
       element._mergeable = true;
       element._submitEnabled = true;
-      flush();
+      await flush();
       element.computeRevertSubmitted(element._change);
-      flush(() => {
-        assert.isFalse(
-          element._changeStatuses?.includes(ChangeStates.REVERT_SUBMITTED)
-        );
-        assert.isTrue(
-          element._changeStatuses?.includes(ChangeStates.REVERT_CREATED)
-        );
-        done();
-      });
+      await flush();
+      assert.isFalse(
+        element._changeStatuses?.includes(ChangeStates.REVERT_SUBMITTED)
+      );
+      assert.isTrue(
+        element._changeStatuses?.includes(ChangeStates.REVERT_CREATED)
+      );
     });
 
-    test('show revert submitted if revert is merged', done => {
+    test('show revert submitted if revert is merged', async () => {
       const change = {
         ...createChange(),
         messages: createChangeMessages(2),
@@ -1179,17 +1144,15 @@
       element._change = change;
       element._mergeable = true;
       element._submitEnabled = true;
-      flush();
+      await flush();
       element.computeRevertSubmitted(element._change);
-      flush(() => {
-        assert.isFalse(
-          element._changeStatuses?.includes(ChangeStates.REVERT_CREATED)
-        );
-        assert.isTrue(
-          element._changeStatuses?.includes(ChangeStates.REVERT_SUBMITTED)
-        );
-        done();
-      });
+      await flush();
+      assert.isFalse(
+        element._changeStatuses?.includes(ChangeStates.REVERT_CREATED)
+      );
+      assert.isTrue(
+        element._changeStatuses?.includes(ChangeStates.REVERT_SUBMITTED)
+      );
     });
   });
 
@@ -1343,17 +1306,15 @@
     assert.equal(element.viewState.diffMode, DiffViewMode.SIDE_BY_SIDE);
   });
 
-  test('diffMode defaults to side by side without preferences', done => {
+  test('diffMode defaults to side by side without preferences', async () => {
     stubRestApi('getPreferences').returns(Promise.resolve(createPreferences()));
     // No user prefs or diff view mode set.
 
-    element._setDiffViewMode()!.then(() => {
-      assert.equal(element.viewState.diffMode, DiffViewMode.SIDE_BY_SIDE);
-      done();
-    });
+    await element._setDiffViewMode()!;
+    assert.equal(element.viewState.diffMode, DiffViewMode.SIDE_BY_SIDE);
   });
 
-  test('diffMode defaults to preference when not already set', done => {
+  test('diffMode defaults to preference when not already set', async () => {
     stubRestApi('getPreferences').returns(
       Promise.resolve({
         ...createPreferences(),
@@ -1361,13 +1322,11 @@
       })
     );
 
-    element._setDiffViewMode()!.then(() => {
-      assert.equal(element.viewState.diffMode, DiffViewMode.UNIFIED);
-      done();
-    });
+    await element._setDiffViewMode()!;
+    assert.equal(element.viewState.diffMode, DiffViewMode.UNIFIED);
   });
 
-  test('existing diffMode overrides preference', done => {
+  test('existing diffMode overrides preference', async () => {
     element.viewState.diffMode = DiffViewMode.SIDE_BY_SIDE;
     stubRestApi('getPreferences').returns(
       Promise.resolve({
@@ -1375,10 +1334,8 @@
         default_diff_view: DiffViewMode.UNIFIED,
       })
     );
-    element._setDiffViewMode()!.then(() => {
-      assert.equal(element.viewState.diffMode, DiffViewMode.SIDE_BY_SIDE);
-      done();
-    });
+    await element._setDiffViewMode()!;
+    assert.equal(element.viewState.diffMode, DiffViewMode.SIDE_BY_SIDE);
   });
 
   test('don’t reload entire page when patchRange changes', async () => {
@@ -1486,17 +1443,15 @@
     assert.isTrue(recreateSpy.calledOnce);
   });
 
-  test('related changes are not updated after other action', done => {
+  test('related changes are not updated after other action', async () => {
     sinon.stub(element, 'loadData').callsFake(() => Promise.resolve());
-    flush();
+    await flush();
     const relatedChanges = element.shadowRoot!.querySelector(
       '#relatedChanges'
     ) as GrRelatedChangesList;
     sinon.stub(relatedChanges, 'reload');
-    element.loadData(true).then(() => {
-      assert.isFalse(navigateToChangeStub.called);
-      done();
-    });
+    await element.loadData(true);
+    assert.isFalse(navigateToChangeStub.called);
   });
 
   test('_computeCopyTextForTitle', () => {
@@ -1578,7 +1533,7 @@
     assert.equal(putStub.lastCall.args[1], '\n\n\n\n\n\n\n\n');
   });
 
-  test('topic is coalesced to null', done => {
+  test('topic is coalesced to null', async () => {
     sinon.stub(element, '_changeChanged');
     stubRestApi('getChangeDetail').returns(
       Promise.resolve({
@@ -1589,13 +1544,11 @@
       })
     );
 
-    element._getChangeDetail().then(() => {
-      assert.isNull(element._change!.topic);
-      done();
-    });
+    await element._getChangeDetail();
+    assert.isNull(element._change!.topic);
   });
 
-  test('commit sha is populated from getChangeDetail', done => {
+  test('commit sha is populated from getChangeDetail', async () => {
     sinon.stub(element, '_changeChanged');
     stubRestApi('getChangeDetail').callsFake(() =>
       Promise.resolve({
@@ -1606,10 +1559,8 @@
       })
     );
 
-    element._getChangeDetail().then(() => {
-      assert.equal('foo', element._commitInfo!.commit);
-      done();
-    });
+    await element._getChangeDetail();
+    assert.equal('foo', element._commitInfo!.commit);
   });
 
   test('edit is added to change', () => {
@@ -1693,43 +1644,39 @@
     assert.equal(element._getBasePatchNum(_change2, _patchRange), 'PARENT');
   });
 
-  test('_openReplyDialog called with `ANY` when coming from tap event', done => {
-    flush(() => {
-      const openStub = sinon.stub(element, '_openReplyDialog');
-      tap(element.$.replyBtn);
-      assert(
-        openStub.lastCall.calledWithExactly(
-          element.$.replyDialog.FocusTarget.ANY
-        ),
-        '_openReplyDialog should have been passed ANY'
-      );
-      assert.equal(openStub.callCount, 1);
-      done();
-    });
+  test('_openReplyDialog called with `ANY` when coming from tap event', async () => {
+    await flush();
+    const openStub = sinon.stub(element, '_openReplyDialog');
+    tap(element.$.replyBtn);
+    assert(
+      openStub.lastCall.calledWithExactly(
+        element.$.replyDialog.FocusTarget.ANY
+      ),
+      '_openReplyDialog should have been passed ANY'
+    );
+    assert.equal(openStub.callCount, 1);
   });
 
   test(
     '_openReplyDialog called with `BODY` when coming from message reply' +
       'event',
-    done => {
-      flush(() => {
-        const openStub = sinon.stub(element, '_openReplyDialog');
-        element.messagesList!.dispatchEvent(
-          new CustomEvent('reply', {
-            detail: {message: {message: 'text'}},
-            composed: true,
-            bubbles: true,
-          })
-        );
-        assert(
-          openStub.lastCall.calledWithExactly(
-            element.$.replyDialog.FocusTarget.BODY
-          ),
-          '_openReplyDialog should have been passed BODY'
-        );
-        assert.equal(openStub.callCount, 1);
-        done();
-      });
+    async () => {
+      await flush();
+      const openStub = sinon.stub(element, '_openReplyDialog');
+      element.messagesList!.dispatchEvent(
+        new CustomEvent('reply', {
+          detail: {message: {message: 'text'}},
+          composed: true,
+          bubbles: true,
+        })
+      );
+      assert(
+        openStub.lastCall.calledWithExactly(
+          element.$.replyDialog.FocusTarget.BODY
+        ),
+        '_openReplyDialog should have been passed BODY'
+      );
+      assert.equal(openStub.callCount, 1);
     }
   );
 
@@ -1771,7 +1718,7 @@
     assert.isNull(element._getUrlParameter('test'));
   });
 
-  test('revert dialog opened with revert param', done => {
+  test('revert dialog opened with revert param', async () => {
     const awaitPluginsLoadedStub = sinon
       .stub(getPluginLoader(), 'awaitPluginsLoaded')
       .callsFake(() => Promise.resolve());
@@ -1797,10 +1744,15 @@
       return param;
     });
 
-    sinon.stub(element.$.actions, 'showRevertDialog').callsFake(done);
+    const promise = mockPromise();
+
+    sinon
+      .stub(element.$.actions, 'showRevertDialog')
+      .callsFake(() => promise.resolve());
 
     element._maybeShowRevertDialog();
     assert.isTrue(awaitPluginsLoadedStub.called);
+    await promise;
   });
 
   suite('reply dialog tests', () => {
@@ -1823,7 +1775,7 @@
       );
     });
 
-    test('show reply dialog on open-reply-dialog event', done => {
+    test('show reply dialog on open-reply-dialog event', async () => {
       const openReplyDialogStub = sinon.stub(element, '_openReplyDialog');
       element.dispatchEvent(
         new CustomEvent('open-reply-dialog', {
@@ -1832,10 +1784,8 @@
           detail: {},
         })
       );
-      flush(() => {
-        assert.isTrue(openReplyDialogStub.calledOnce);
-        done();
-      });
+      await flush();
+      assert.isTrue(openReplyDialogStub.calledOnce);
     });
 
     test('reply from comment adds quote text', () => {
@@ -1887,13 +1837,11 @@
     });
   });
 
-  test('reply button is disabled until server config is loaded', done => {
+  test('reply button is disabled until server config is loaded', async () => {
     assert.isTrue(element._replyDisabled);
     // fetches the server config on attached
-    flush(() => {
-      assert.isFalse(element._replyDisabled);
-      done();
-    });
+    await flush();
+    assert.isFalse(element._replyDisabled);
   });
 
   test('header class computation', () => {
@@ -1901,19 +1849,17 @@
     assert.equal(element._computeHeaderClass(true), 'header editMode');
   });
 
-  test('_maybeScrollToMessage', done => {
-    flush(() => {
-      const scrollStub = sinon.stub(element.messagesList!, 'scrollToMessage');
+  test('_maybeScrollToMessage', async () => {
+    await flush();
+    const scrollStub = sinon.stub(element.messagesList!, 'scrollToMessage');
 
-      element._maybeScrollToMessage('');
-      assert.isFalse(scrollStub.called);
-      element._maybeScrollToMessage('message');
-      assert.isFalse(scrollStub.called);
-      element._maybeScrollToMessage('#message-TEST');
-      assert.isTrue(scrollStub.called);
-      assert.equal(scrollStub.lastCall.args[0], 'TEST');
-      done();
-    });
+    element._maybeScrollToMessage('');
+    assert.isFalse(scrollStub.called);
+    element._maybeScrollToMessage('message');
+    assert.isFalse(scrollStub.called);
+    element._maybeScrollToMessage('#message-TEST');
+    assert.isTrue(scrollStub.called);
+    assert.equal(scrollStub.lastCall.args[0], 'TEST');
   });
 
   test('topic update reloads related changes', () => {
@@ -2172,94 +2118,93 @@
       };
     });
 
-    test('edit exists in revisions', done => {
+    test('edit exists in revisions', async () => {
+      const promise = mockPromise();
       sinon.stub(GerritNav, 'navigateToChange').callsFake((...args) => {
         assert.equal(args.length, 2);
         assert.equal(args[1], EditPatchSetNum); // patchNum
-        done();
+        promise.resolve();
       });
 
       element.set('_change.revisions.rev2', {
         _number: EditPatchSetNum,
       });
-      flush();
+      await flush();
 
       fireEdit();
+      await promise;
     });
 
-    test('no edit exists in revisions, non-latest patchset', done => {
+    test('no edit exists in revisions, non-latest patchset', async () => {
+      const promise = mockPromise();
       sinon.stub(GerritNav, 'navigateToChange').callsFake((...args) => {
         assert.equal(args.length, 4);
         assert.equal(args[1], 1 as PatchSetNum); // patchNum
         assert.equal(args[3], true); // opt_isEdit
-        done();
+        promise.resolve();
       });
 
       element.set('_change.revisions.rev2', {_number: 2});
       element._patchRange = {patchNum: 1 as RevisionPatchSetNum};
-      flush();
+      await flush();
 
       fireEdit();
+      await promise;
     });
 
-    test('no edit exists in revisions, latest patchset', done => {
+    test('no edit exists in revisions, latest patchset', async () => {
+      const promise = mockPromise();
       sinon.stub(GerritNav, 'navigateToChange').callsFake((...args) => {
         assert.equal(args.length, 4);
         // No patch should be specified when patchNum == latest.
         assert.isNotOk(args[1]); // patchNum
         assert.equal(args[3], true); // opt_isEdit
-        done();
+        promise.resolve();
       });
 
       element.set('_change.revisions.rev2', {_number: 2});
       element._patchRange = {patchNum: 2 as RevisionPatchSetNum};
-      flush();
+      await flush();
 
       fireEdit();
+      await promise;
     });
   });
 
-  test('_handleStopEditTap', done => {
+  test('_handleStopEditTap', async () => {
     element._change = {
       ...createChangeViewChange(),
     };
     sinon.stub(element.$.metadata, '_computeLabelNames');
     navigateToChangeStub.restore();
+    const promise = mockPromise();
     sinon.stub(GerritNav, 'navigateToChange').callsFake((...args) => {
       assert.equal(args.length, 2);
       assert.equal(args[1], 1 as PatchSetNum); // patchNum
-      done();
+      promise.resolve();
     });
 
     element._patchRange = {patchNum: 1 as RevisionPatchSetNum};
     element.$.actions.dispatchEvent(
       new CustomEvent('stop-edit-tap', {bubbles: false})
     );
+    await promise;
   });
 
   suite('plugin endpoints', () => {
-    test('endpoint params', done => {
+    test('endpoint params', async () => {
       element._change = {...createChangeViewChange(), labels: {}};
       element._selectedRevision = createRevision();
-      let hookEl: HTMLElement;
-      let plugin: PluginApi;
-      pluginApi.install(
-        p => {
-          plugin = p;
-          plugin
-            .hook('change-view-integration')
-            .getLastAttached()
-            .then(el => (hookEl = el));
-        },
-        '0.1',
-        'http://some/plugins/url.js'
-      );
-      flush(() => {
-        assert.strictEqual((hookEl as any).plugin, plugin);
-        assert.strictEqual((hookEl as any).change, element._change);
-        assert.strictEqual((hookEl as any).revision, element._selectedRevision);
-        done();
-      });
+      const promise = mockPromise();
+      pluginApi.install(promise.resolve, '0.1', 'http://some/plugins/url.js');
+      await flush();
+      const plugin: PluginApi = (await promise) as PluginApi;
+      const hookEl = await plugin
+        .hook('change-view-integration')
+        .getLastAttached();
+      assert.strictEqual((hookEl as any).plugin, plugin);
+      assert.strictEqual((hookEl as any).change, element._change);
+      assert.strictEqual((hookEl as any).revision, element._selectedRevision);
     });
   });
 
@@ -2328,7 +2273,7 @@
         .returns(Promise.resolve([undefined, undefined, undefined]));
     });
 
-    test("don't report changeDisplayed on reply", done => {
+    test("don't report changeDisplayed on reply", async () => {
       const changeDisplayStub = sinon.stub(
         appContext.reportingService,
         'changeDisplayed'
@@ -2338,11 +2283,9 @@
         'changeFullyLoaded'
       );
       element._handleReplySent();
-      flush(() => {
-        assert.isFalse(changeDisplayStub.called);
-        assert.isFalse(changeFullyLoadedStub.called);
-        done();
-      });
+      await flush();
+      assert.isFalse(changeDisplayStub.called);
+      assert.isFalse(changeFullyLoadedStub.called);
     });
 
     test('report changeDisplayed on _paramsChanged', async () => {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.js b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.js
index 1034674..90fddaa 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.js
@@ -111,22 +111,20 @@
       flush();
     });
 
-    test('cherry pick topic submit', done => {
+    test('cherry pick topic submit', async () => {
       element.branch = 'master';
       const executeChangeActionStub = stubRestApi(
           'executeChangeAction').returns(Promise.resolve([]));
       MockInteractions.tap(element.shadowRoot.
           querySelector('gr-dialog').$.confirm);
-      flush(() => {
-        const args = executeChangeActionStub.args[0];
-        assert.equal(args[0], 1);
-        assert.equal(args[1], 'POST');
-        assert.equal(args[2], '/cherrypick');
-        assert.equal(args[4].destination, 'master');
-        assert.isTrue(args[4].allow_conflicts);
-        assert.isTrue(args[4].allow_empty);
-        done();
-      });
+      await flush();
+      const args = executeChangeActionStub.args[0];
+      assert.equal(args[0], 1);
+      assert.equal(args[1], 'POST');
+      assert.equal(args[2], '/cherrypick');
+      assert.equal(args[4].destination, 'master');
+      assert.isTrue(args[4].allow_conflicts);
+      assert.isTrue(args[4].allow_empty);
     });
 
     test('deselecting a change removes it from being cherry picked', () => {
@@ -171,18 +169,16 @@
       ), 'error');
     });
 
-    test('submit button is blocked while cherry picks is running', done => {
+    test('submit button is blocked while cherry picks is running', async () => {
       const confirmButton = element.shadowRoot.querySelector('gr-dialog').$
           .confirm;
       assert.isTrue(confirmButton.hasAttribute('disabled'));
       element.branch = 'b';
-      flush();
+      await flush();
       assert.isFalse(confirmButton.hasAttribute('disabled'));
       element.updateStatus(changes[0], {status: 'RUNNING'});
-      flush(() => {
-        assert.isTrue(confirmButton.hasAttribute('disabled'));
-        done();
-      });
+      await flush();
+      assert.isTrue(confirmButton.hasAttribute('disabled'));
     });
   });
 
@@ -192,12 +188,11 @@
     assert.isTrue(focusStub.called);
   });
 
-  test('_getProjectBranchesSuggestions non-empty', done => {
-    element._getProjectBranchesSuggestions('test-branch').then(branches => {
-      assert.equal(branches.length, 1);
-      assert.equal(branches[0].name, 'test-branch');
-      done();
-    });
+  test('_getProjectBranchesSuggestions non-empty', async () => {
+    const branches = await element._getProjectBranchesSuggestions(
+        'test-branch');
+    assert.equal(branches.length, 1);
+    assert.equal(branches[0].name, 'test-branch');
   });
 });
 
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.js b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.js
index 36a2ad3..10844f5 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.js
@@ -50,26 +50,22 @@
     assert.equal(element.message, myNewMessage);
   });
 
-  test('_getProjectBranchesSuggestions empty', done => {
-    element._getProjectBranchesSuggestions('nonexistent').then(branches => {
-      assert.equal(branches.length, 0);
-      done();
-    });
+  test('_getProjectBranchesSuggestions empty', async () => {
+    const branches = await element._getProjectBranchesSuggestions(
+        'nonexistent');
+    assert.equal(branches.length, 0);
   });
 
-  test('_getProjectBranchesSuggestions non-empty', done => {
-    element._getProjectBranchesSuggestions('test-branch').then(branches => {
-      assert.equal(branches.length, 1);
-      assert.equal(branches[0].name, 'test-branch');
-      done();
-    });
+  test('_getProjectBranchesSuggestions non-empty', async () => {
+    const branches = await element._getProjectBranchesSuggestions(
+        'test-branch');
+    assert.equal(branches.length, 1);
+    assert.equal(branches[0].name, 'test-branch');
   });
 
-  test('_getProjectBranchesSuggestions input empty string', done => {
-    element._getProjectBranchesSuggestions('').then(branches => {
-      assert.equal(branches.length, 0);
-      done();
-    });
+  test('_getProjectBranchesSuggestions input empty string', async () => {
+    const branches = await element._getProjectBranchesSuggestions('');
+    assert.equal(branches.length, 0);
   });
 });
 
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.js b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.js
index dd70678..08e5e3b 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.js
@@ -34,10 +34,8 @@
     element = basicFixture.instantiate();
   });
 
-  teardown(done => {
-    flush(() => {
-      done();
-    });
+  teardown(async () => {
+    await flush();
   });
 
   test('Diff preferences hidden when no prefs or diffPrefsDisabled', () => {
@@ -77,21 +75,19 @@
     assert.isTrue(element._collapseAllDiffs.called);
   });
 
-  test('show/hide diffs disabled for large amounts of files', done => {
+  test('show/hide diffs disabled for large amounts of files', async () => {
     const computeSpy = sinon.spy(element, '_fileListActionsVisible');
     element._files = [];
     element.changeNum = '42';
     element.basePatchNum = 'PARENT';
     element.patchNum = '2';
     element.shownFileCount = 1;
-    flush(() => {
-      assert.isTrue(computeSpy.lastCall.returnValue);
-      _.times(element._maxFilesForBulkActions + 1, () => {
-        element.shownFileCount = element.shownFileCount + 1;
-      });
-      assert.isFalse(computeSpy.lastCall.returnValue);
-      done();
+    await flush();
+    assert.isTrue(computeSpy.lastCall.returnValue);
+    _.times(element._maxFilesForBulkActions + 1, () => {
+      element.shownFileCount = element.shownFileCount + 1;
     });
+    assert.isFalse(computeSpy.lastCall.returnValue);
   });
 
   test('fileViewActions are properly hidden', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
index 79bc9f6..e11d3a3 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
@@ -93,7 +93,7 @@
   });
 
   suite('basic tests', () => {
-    setup(done => {
+    setup(async () => {
       stubRestApi('getDiffComments').returns(Promise.resolve({}));
       stubRestApi('getDiffRobotComments').returns(Promise.resolve({}));
       stubRestApi('getDiffDrafts').returns(Promise.resolve({}));
@@ -118,7 +118,6 @@
       };
       saveStub = sinon.stub(element, '_saveReviewedState').callsFake(
           () => Promise.resolve());
-      done();
     });
 
     test('correct number of files are shown', () => {
@@ -942,14 +941,15 @@
       assert.equal(collapseStub.lastCall.args[0].length, 1);
     });
 
-    test('_expandedFilesChanged', done => {
+    test('_expandedFilesChanged', async () => {
       sinon.stub(element, '_reviewFile');
       const path = 'path/to/my/file.txt';
+      const promise = mockPromise();
       const diffs = [{
         path,
         style: {},
         reload() {
-          done();
+          promise.resolve();
         },
         prefetchDiff() {},
         cancel() {},
@@ -963,6 +963,7 @@
       }];
       sinon.stub(element, 'diffs').get(() => diffs);
       element.push('_expandedFiles', {path});
+      await promise;
     });
 
     test('_clearCollapsedDiffs', () => {
@@ -1523,7 +1524,7 @@
       return diffs;
     }
 
-    setup(done => {
+    setup(async () => {
       stubRestApi('getPreferences').returns(Promise.resolve({}));
       stubRestApi('getDiffComments').returns(Promise.resolve({}));
       stubRestApi('getDiffRobotComments').returns(Promise.resolve({}));
@@ -1568,13 +1569,12 @@
         patchNum: 2,
       };
       sinon.stub(window, 'fetch').callsFake(() => Promise.resolve());
-      flush();
-      done();
+      await flush();
     });
 
     test('cursor with individually opened files', async () => {
       MockInteractions.keyUpOn(element, 73, null, 'i');
-      flush();
+      await flush();
       let diffs = await renderAndGetNewDiffs(0);
       const diffStops = diffs[0].getCursorStops();
 
@@ -1587,13 +1587,13 @@
       // Tapping content on a line selects the line number.
       MockInteractions.tap(dom(
           diffStops[10]).querySelectorAll('.contentText')[0]);
-      flush();
+      await flush();
       assert.isTrue(diffStops[10].classList.contains('target-row'));
 
       // Keyboard shortcuts are still moving the file cursor, not the diff
       // cursor.
       MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
-      flush();
+      await flush();
       assert.isTrue(diffStops[10].classList.contains('target-row'));
       assert.isFalse(diffStops[11].classList.contains('target-row'));
 
@@ -1601,7 +1601,7 @@
       assert.equal(element.fileCursor.index, 1);
 
       MockInteractions.keyUpOn(element, 73, null, 'i');
-      flush();
+      await flush();
       diffs = await renderAndGetNewDiffs(1);
 
       // Two diffs should be rendered.
@@ -1616,7 +1616,7 @@
 
     test('cursor with toggle all files', async () => {
       MockInteractions.keyUpOn(element, 73, 'shift', 'i');
-      flush();
+      await flush();
 
       const diffs = await renderAndGetNewDiffs(0);
       const diffStops = diffs[0].getCursorStops();
@@ -1630,13 +1630,13 @@
       // Tapping content on a line selects the line number.
       MockInteractions.tap(dom(
           diffStops[10]).querySelectorAll('.contentText')[0]);
-      flush();
+      await flush();
       assert.isTrue(diffStops[10].classList.contains('target-row'));
 
       // Keyboard shortcuts are still moving the file cursor, not the diff
       // cursor.
       MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
-      flush();
+      await flush();
       assert.isFalse(diffStops[10].classList.contains('target-row'));
       assert.isTrue(diffStops[11].classList.contains('target-row'));
 
@@ -1661,9 +1661,9 @@
             element.root.querySelectorAll('.row:not(.header-row)');
       });
 
-      test('n key with some files expanded and no shift key', () => {
+      test('n key with some files expanded and no shift key', async () => {
         MockInteractions.keyUpOn(fileRows[0], 73, null, 'i');
-        flush();
+        await flush();
 
         // Handle N key should return before calling diff cursor functions.
         MockInteractions.pressAndReleaseKeyOn(element, 78, null, 'n');
@@ -1675,9 +1675,9 @@
         assert.equal(element.filesExpanded, 'some');
       });
 
-      test('n key with some files expanded and shift key', () => {
+      test('n key with some files expanded and shift key', async () => {
         MockInteractions.keyUpOn(fileRows[0], 73, null, 'i');
-        flush();
+        await flush();
         assert.equal(nextChunkStub.callCount, 0);
 
         MockInteractions.pressAndReleaseKeyOn(element, 78, 'shift', 'n');
@@ -1689,9 +1689,9 @@
         assert.equal(element.filesExpanded, 'some');
       });
 
-      test('n key without all files expanded and shift key', () => {
+      test('n key without all files expanded and shift key', async () => {
         MockInteractions.keyUpOn(fileRows[0], 73, 'shift', 'i');
-        flush();
+        await flush();
 
         MockInteractions.pressAndReleaseKeyOn(element, 78, null, 'n');
         assert.isTrue(nKeySpy.called);
@@ -1702,9 +1702,9 @@
         assert.isTrue(element._showInlineDiffs);
       });
 
-      test('n key without all files expanded and no shift key', () => {
+      test('n key without all files expanded and no shift key', async () => {
         MockInteractions.keyUpOn(fileRows[0], 73, 'shift', 'i');
-        flush();
+        await flush();
 
         MockInteractions.pressAndReleaseKeyOn(element, 78, 'shift', 'n');
         assert.isTrue(nKeySpy.called);
@@ -1716,7 +1716,7 @@
       });
     });
 
-    test('_openSelectedFile behavior', () => {
+    test('_openSelectedFile behavior', async () => {
       const _filesByPath = element._filesByPath;
       element.set('_filesByPath', {});
       const navStub = sinon.stub(GerritNav, 'navigateToDiff');
@@ -1725,7 +1725,7 @@
       assert.isFalse(navStub.called);
 
       element.set('_filesByPath', _filesByPath);
-      flush();
+      await flush();
       // Navigates when a file is selected.
       element._openSelectedFile();
       assert.isTrue(navStub.called);
@@ -1753,7 +1753,7 @@
     });
 
     suite('editMode behavior', () => {
-      test('reviewed checkbox', () => {
+      test('reviewed checkbox', async () => {
         element._reviewFile.restore();
         const saveReviewStub = sinon.stub(element, '_saveReviewedState');
 
@@ -1762,7 +1762,7 @@
         assert.isTrue(saveReviewStub.calledOnce);
 
         element.editMode = true;
-        flush();
+        await flush();
 
         MockInteractions.pressAndReleaseKeyOn(element, 82, null, 'r');
         assert.isTrue(saveReviewStub.calledOnce);
@@ -1778,13 +1778,13 @@
       });
     });
 
-    test('editing actions', () => {
+    test('editing actions', async () => {
       // Edit controls are guarded behind a dom-if initially and not rendered.
       assert.isNotOk(dom(element.root)
           .querySelector('gr-edit-file-controls'));
 
       element.editMode = true;
-      flush();
+      await flush();
 
       // Commit message should not have edit controls.
       const editControls =
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.ts
index 283a133..4e02155 100644
--- a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.ts
@@ -76,7 +76,7 @@
     ]);
   });
 
-  test('_computeGroups with .bindValue', done => {
+  test('_computeGroups with .bindValue', async () => {
     queryAndAssert<IronInputElement>(element, '#filterInput')!.bindValue =
       'stable-3.2';
     const includedIn = {branches: [], tags: []} as IncludedInInfo;
@@ -84,14 +84,10 @@
       'master' as BranchName,
       'stable-3.2' as BranchName
     );
-
-    setTimeout(() => {
-      const filterText = element._filterText;
-      assert.deepEqual(element._computeGroups(includedIn, filterText), [
-        {title: 'Branches', items: ['stable-3.2']},
-      ]);
-
-      done();
-    });
+    await flush();
+    const filterText = element._filterText;
+    assert.deepEqual(element._computeGroups(includedIn, filterText), [
+      {title: 'Branches', items: ['stable-3.2']},
+    ]);
   });
 });
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.js b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.js
index 3c9b7c7..e7ff236 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.js
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.js
@@ -23,7 +23,7 @@
 suite('gr-label-row-score tests', () => {
   let element;
 
-  setup(done => {
+  setup(async () => {
     element = basicFixture.instantiate();
     element.labels = {
       'Code-Review': {
@@ -80,7 +80,7 @@
       value: '+1',
     };
 
-    flush(done);
+    await flush();
   });
 
   function checkAriaCheckedValid() {
@@ -241,7 +241,7 @@
     checkAriaCheckedValid();
   });
 
-  test('without permitted labels', () => {
+  test('without permitted labels', async () => {
     element.permittedLabels = {
       Verified: [
         '-1',
@@ -249,22 +249,22 @@
         '+1',
       ],
     };
-    flush();
+    await flush();
     assert.isOk(element.$.labelSelector);
     assert.isFalse(element.$.labelSelector.hidden);
 
     element.permittedLabels = {};
-    flush();
+    await flush();
     assert.isOk(element.$.labelSelector);
     assert.isTrue(element.$.labelSelector.hidden);
 
     element.permittedLabels = {Verified: []};
-    flush();
+    await flush();
     assert.isOk(element.$.labelSelector);
     assert.isTrue(element.$.labelSelector.hidden);
   });
 
-  test('asymmetrical labels', done => {
+  test('asymmetrical labels', async () => {
     element.permittedLabels = {
       'Code-Review': [
         '-2',
@@ -278,35 +278,32 @@
         '+1',
       ],
     };
-    flush(() => {
-      assert.strictEqual(element.$.labelSelector
-          .items.length, 2);
-      assert.strictEqual(
-          element.root.querySelectorAll('.placeholder').length,
-          3);
+    await flush();
+    assert.strictEqual(element.$.labelSelector
+        .items.length, 2);
+    assert.strictEqual(
+        element.root.querySelectorAll('.placeholder').length,
+        3);
 
-      element.permittedLabels = {
-        'Code-Review': [
-          ' 0',
-          '+1',
-        ],
-        'Verified': [
-          '-2',
-          '-1',
-          ' 0',
-          '+1',
-          '+2',
-        ],
-      };
-      flush(() => {
-        assert.strictEqual(element.$.labelSelector
-            .items.length, 5);
-        assert.strictEqual(
-            element.root.querySelectorAll('.placeholder').length,
-            0);
-        done();
-      });
-    });
+    element.permittedLabels = {
+      'Code-Review': [
+        ' 0',
+        '+1',
+      ],
+      'Verified': [
+        '-2',
+        '-1',
+        ' 0',
+        '+1',
+        '+2',
+      ],
+    };
+    await flush();
+    assert.strictEqual(element.$.labelSelector
+        .items.length, 5);
+    assert.strictEqual(
+        element.root.querySelectorAll('.placeholder').length,
+        0);
   });
 
   test('default_value', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts
index cae7a5a..90d2049 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts
@@ -26,6 +26,7 @@
   createRevisions,
 } from '../../../test/test-data-generators';
 import {
+  mockPromise,
   query,
   queryAll,
   queryAndAssert,
@@ -58,13 +59,13 @@
   let element: GrMessage;
 
   suite('when admin and logged in', () => {
-    setup(done => {
+    setup(async () => {
       stubRestApi('getIsAdmin').returns(Promise.resolve(true));
       element = basicFixture.instantiate();
-      flush(done);
+      await flush();
     });
 
-    test('reply event', done => {
+    test('reply event', async () => {
       element.message = {
         ...createChangeMessage(),
         id: '47c43261_55aa2c41' as ChangeMessageId,
@@ -79,18 +80,20 @@
         expanded: true,
       };
 
+      const promise = mockPromise();
       element.addEventListener('reply', (e: CustomEvent<ReplyEventDetail>) => {
         assert.deepEqual(e.detail.message, element.message);
-        done();
+        promise.resolve();
       });
-      flush();
+      await flush();
       assert.isFalse(
         queryAndAssert<HTMLElement>(element, '.replyActionContainer').hidden
       );
       tap(queryAndAssert(element, '.replyBtn'));
+      await promise;
     });
 
-    test('can see delete button', () => {
+    test('can see delete button', async () => {
       element.message = {
         ...createChangeMessage(),
         id: '47c43261_55aa2c41' as ChangeMessageId,
@@ -105,11 +108,11 @@
         expanded: true,
       };
 
-      flush();
+      await flush();
       assert.isFalse(queryAndAssert<HTMLElement>(element, '.deleteBtn').hidden);
     });
 
-    test('delete change message', done => {
+    test('delete change message', async () => {
       element.changeNum = 314159 as NumericChangeId;
       element.message = {
         ...createChangeMessage(),
@@ -125,6 +128,7 @@
         expanded: true,
       };
 
+      const promise = mockPromise();
       element.addEventListener(
         'change-message-deleted',
         (e: CustomEvent<ChangeMessageDeletedEventDetail>) => {
@@ -132,14 +136,15 @@
           assert.isFalse(
             (queryAndAssert(element, '.deleteBtn') as GrButton).disabled
           );
-          done();
+          promise.resolve();
         }
       );
-      flush();
+      await flush();
       tap(queryAndAssert(element, '.deleteBtn'));
       assert.isTrue(
         (queryAndAssert(element, '.deleteBtn') as GrButton).disabled
       );
+      await promise;
     });
 
     test('autogenerated prefix hiding', () => {
@@ -565,11 +570,11 @@
   });
 
   suite('when not logged in', () => {
-    setup(done => {
+    setup(async () => {
       stubRestApi('getLoggedIn').returns(Promise.resolve(false));
       stubRestApi('getIsAdmin').returns(Promise.resolve(false));
       element = basicFixture.instantiate();
-      flush(done);
+      await flush();
     });
 
     test('reply and delete button should be hidden', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
index d70c7ba..e3bbd9e 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
@@ -1037,45 +1037,39 @@
     });
   });
 
-  test('getlabelValue returns value', done => {
-    flush(() => {
-      const el = queryAndAssert(
-        queryAndAssert(element, 'gr-label-scores'),
-        'gr-label-score-row[name="Verified"]'
-      ) as GrLabelScoreRow;
-      el.setSelectedValue('-1');
-      assert.equal('-1', element.getLabelValue('Verified'));
-      done();
-    });
+  test('getlabelValue returns value', async () => {
+    await flush();
+    const el = queryAndAssert(
+      queryAndAssert(element, 'gr-label-scores'),
+      'gr-label-score-row[name="Verified"]'
+    ) as GrLabelScoreRow;
+    el.setSelectedValue('-1');
+    assert.equal('-1', element.getLabelValue('Verified'));
   });
 
-  test('getlabelValue when no score is selected', done => {
-    flush(() => {
-      const el = queryAndAssert(
-        queryAndAssert(element, 'gr-label-scores'),
-        'gr-label-score-row[name="Code-Review"]'
-      ) as GrLabelScoreRow;
-      el.setSelectedValue('-1');
-      assert.strictEqual(element.getLabelValue('Verified'), ' 0');
-      done();
-    });
+  test('getlabelValue when no score is selected', async () => {
+    await flush();
+    const el = queryAndAssert(
+      queryAndAssert(element, 'gr-label-scores'),
+      'gr-label-score-row[name="Code-Review"]'
+    ) as GrLabelScoreRow;
+    el.setSelectedValue('-1');
+    assert.strictEqual(element.getLabelValue('Verified'), ' 0');
   });
 
-  test('setlabelValue', done => {
+  test('setlabelValue', async () => {
     element._account = {_account_id: 1 as AccountId};
-    flush(() => {
-      const label = 'Verified';
-      const value = '+1';
-      element.setLabelValue(label, value);
+    await flush();
+    const label = 'Verified';
+    const value = '+1';
+    element.setLabelValue(label, value);
 
-      const labels = (
-        queryAndAssert(element, '#labelScores') as GrLabelScores
-      ).getLabelValues();
-      assert.deepEqual(labels, {
-        'Code-Review': 0,
-        Verified: 1,
-      });
-      done();
+    const labels = (
+      queryAndAssert(element, '#labelScores') as GrLabelScores
+    ).getLabelValues();
+    assert.deepEqual(labels, {
+      'Code-Review': 0,
+      Verified: 1,
     });
   });
 
@@ -1347,7 +1341,7 @@
     assert.isTrue(eraseDraftCommentStub.calledWith(location));
   });
 
-  test('400 converts to human-readable server-error', done => {
+  test('400 converts to human-readable server-error', async () => {
     stubRestApi('saveChangeReview').callsFake(
       (_changeNum, _patchNum, _review, errFn) => {
         errFn!(
@@ -1360,34 +1354,35 @@
       }
     );
 
+    const promise = mockPromise();
     const listener = (event: Event) => {
       if (event.target !== document) return;
       (event as CustomEvent).detail.response.text().then((body: string) => {
         if (body === 'human readable') {
-          done();
+          promise.resolve();
         }
       });
     };
     addListenerForTest(document, 'server-error', listener);
 
-    flush(() => {
-      element.send(false, false);
-    });
+    await flush();
+    element.send(false, false);
+    await promise;
   });
 
-  test('non-json 400 is treated as a normal server-error', done => {
+  test('non-json 400 is treated as a normal server-error', async () => {
     stubRestApi('saveChangeReview').callsFake(
       (_changeNum, _patchNum, _review, errFn) => {
         errFn!(cloneableResponse(400, 'Comment validation error!') as Response);
         return Promise.resolve(new Response());
       }
     );
-
+    const promise = mockPromise();
     const listener = (event: Event) => {
       if (event.target !== document) return;
       (event as CustomEvent).detail.response.text().then((body: string) => {
         if (body === 'Comment validation error!') {
-          done();
+          promise.resolve();
         }
       });
     };
@@ -1395,9 +1390,9 @@
 
     // Async tick is needed because iron-selector content is distributed and
     // distributed content requires an observer to be set up.
-    flush(() => {
-      element.send(false, false);
-    });
+    await flush();
+    element.send(false, false);
+    await promise;
   });
 
   test('filterReviewerSuggestion', () => {
@@ -1494,29 +1489,30 @@
     assert.strictEqual(element._chooseFocusTarget(), element.FocusTarget.BODY);
   });
 
-  test('only send labels that have changed', done => {
-    flush(() => {
-      stubSaveReview((review: ReviewInput) => {
-        assert.deepEqual(review?.labels, {
-          'Code-Review': 0,
-          Verified: -1,
-        });
+  test('only send labels that have changed', async () => {
+    await flush();
+    stubSaveReview((review: ReviewInput) => {
+      assert.deepEqual(review?.labels, {
+        'Code-Review': 0,
+        Verified: -1,
       });
-
-      element.addEventListener('send', () => {
-        done();
-      });
-      // Without wrapping this test in flush(), the below two calls to
-      // tap() cause a race in some situations in shadow DOM.
-      // The send button can be tapped before the others, causing the test to
-      // fail.
-      const el = queryAndAssert(
-        queryAndAssert(element, 'gr-label-scores'),
-        'gr-label-score-row[name="Verified"]'
-      ) as GrLabelScoreRow;
-      el.setSelectedValue('-1');
-      tap(queryAndAssert(element, '.send'));
     });
+
+    const promise = mockPromise();
+    element.addEventListener('send', () => {
+      promise.resolve();
+    });
+    // Without wrapping this test in flush(), the below two calls to
+    // tap() cause a race in some situations in shadow DOM.
+    // The send button can be tapped before the others, causing the test to
+    // fail.
+    const el = queryAndAssert(
+      queryAndAssert(element, 'gr-label-scores'),
+      'gr-label-score-row[name="Verified"]'
+    ) as GrLabelScoreRow;
+    el.setSelectedValue('-1');
+    tap(queryAndAssert(element, '.send'));
+    await promise;
   });
 
   test('moving from cc to reviewer', () => {
@@ -1811,14 +1807,14 @@
     stubSaveReview(() => undefined);
     element.addEventListener('send', () => assert.fail('wrongly called'));
     pressAndReleaseKeyOn(element, 13, null, 'enter');
-    flush();
   });
 
-  test('emit send on ctrl+enter key', done => {
+  test('emit send on ctrl+enter key', async () => {
     stubSaveReview(() => undefined);
-    element.addEventListener('send', () => done());
+    const promise = mockPromise();
+    element.addEventListener('send', () => promise.resolve());
     pressAndReleaseKeyOn(element, 13, 'ctrl', 'enter');
-    flush();
+    await promise;
   });
 
   test('_computeMessagePlaceholder', () => {
@@ -1840,7 +1836,7 @@
     );
   });
 
-  test('_handle400Error reviewers and CCs', done => {
+  test('_handle400Error reviewers and CCs', async () => {
     const error1 = 'error 1';
     const error2 = 'error 2';
     const error3 = 'error 3';
@@ -1862,49 +1858,48 @@
           },
         },
       });
+    const promise = mockPromise();
     const listener = (e: Event) => {
       (e as CustomEvent).detail.response.text().then((text: string) => {
         assert.equal(text, [error1, error2, error3].join(', '));
-        done();
+        promise.resolve();
       });
     };
     addListenerForTest(document, 'server-error', listener);
     element._handle400Error(cloneableResponse(400, text) as Response);
+    await promise;
   });
 
-  test('fires height change when the drafts comments load', done => {
+  test('fires height change when the drafts comments load', async () => {
     // Flush DOM operations before binding to the autogrow event so we don't
     // catch the events fired from the initial layout.
-    flush(() => {
-      const autoGrowHandler = sinon.stub();
-      element.addEventListener('autogrow', autoGrowHandler);
-      element.draftCommentThreads = [];
-      flush(() => {
-        assert.isTrue(autoGrowHandler.called);
-        done();
-      });
-    });
+    await flush();
+    const autoGrowHandler = sinon.stub();
+    element.addEventListener('autogrow', autoGrowHandler);
+    element.draftCommentThreads = [];
+    await flush();
+    assert.isTrue(autoGrowHandler.called);
   });
 
   suite('start review and save buttons', () => {
     let sendStub: sinon.SinonStub;
 
-    setup(() => {
+    setup(async () => {
       sendStub = sinon.stub(element, 'send').callsFake(() => Promise.resolve());
       element.canBeStarted = true;
       // Flush to make both Start/Save buttons appear in DOM.
-      flush();
+      await flush();
     });
 
-    test('start review sets ready', () => {
+    test('start review sets ready', async () => {
       tap(queryAndAssert(element, '.send'));
-      flush();
+      await flush();
       assert.isTrue(sendStub.calledWith(true, true));
     });
 
-    test("save review doesn't set ready", () => {
+    test("save review doesn't set ready", async () => {
       tap(queryAndAssert(element, '.save'));
-      flush();
+      await flush();
       assert.isTrue(sendStub.calledWith(true, false));
     });
   });
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.js b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.js
index fe99918..b478ef5 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.js
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.js
@@ -37,7 +37,7 @@
         .filter(e => e.style.display !== 'none');
   }
 
-  setup(done => {
+  setup(async () => {
     element = basicFixture.instantiate();
     element.changeNum = 123;
     element.change = {
@@ -267,9 +267,7 @@
     ];
 
     // use flush to render all (bypass initial-count set on dom-repeat)
-    flush(() => {
-      done();
-    });
+    await flush();
   });
 
   test('draft dropdown item only appears when logged in', () => {
@@ -289,14 +287,13 @@
     assert.equal(getVisibleThreads().length, element.threads.length);
   });
 
-  test('show unresolved threads if unresolvedOnly is set', done => {
+  test('show unresolved threads if unresolvedOnly is set', async () => {
     element.unresolvedOnly = true;
-    flush();
+    await flush();
     const unresolvedThreads = element.threads.filter(t => t.comments.some(
         c => c.unresolved
     ));
     assert.equal(getVisibleThreads().length, unresolvedThreads.length);
-    done();
   });
 
   test('showing file name takes visible threads into account', () => {
@@ -646,11 +643,9 @@
   });
 
   suite('hideDropdown', () => {
-    setup(done => {
+    setup(async () => {
       element.hideDropdown = true;
-      flush(() => {
-        done();
-      });
+      await flush();
     });
 
     test('toggle buttons are hidden', () => {
@@ -660,11 +655,9 @@
   });
 
   suite('empty thread', () => {
-    setup(done => {
+    setup(async () => {
       element.threads = [];
-      flush(() => {
-        done();
-      });
+      await flush();
     });
 
     test('default empty message should show', () => {
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.ts b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.ts
index e5e8b01..80ebf2d 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.ts
@@ -63,7 +63,7 @@
       });
     });
 
-    test('does not show auth error on 403 by default', done => {
+    test('does not show auth error on 403 by default', async () => {
       const showAuthErrorStub = sinon.stub(element, '_showAuthErrorAlert');
       const responseText = Promise.resolve('server says no.');
       element.dispatchEvent(
@@ -80,13 +80,11 @@
           bubbles: true,
         })
       );
-      flush(() => {
-        assert.isFalse(showAuthErrorStub.calledOnce);
-        done();
-      });
+      await flush();
+      assert.isFalse(showAuthErrorStub.calledOnce);
     });
 
-    test('show auth required for 403 with auth error and not authed before', done => {
+    test('show auth required for 403 with auth error and not authed before', async () => {
       const showAuthErrorStub = sinon.stub(element, '_showAuthErrorAlert');
       const responseText = Promise.resolve('Authentication required\n');
       getLoggedInStub.returns(Promise.resolve(true));
@@ -104,10 +102,8 @@
           bubbles: true,
         })
       );
-      flush(() => {
-        assert.isTrue(showAuthErrorStub.calledOnce);
-        done();
-      });
+      await flush();
+      assert.isTrue(showAuthErrorStub.calledOnce);
     });
 
     test('recheck auth for 403 with auth error if authed before', async () => {
@@ -149,7 +145,7 @@
       );
     });
 
-    test('show normal Error', done => {
+    test('show normal Error', async () => {
       const showErrorSpy = sinon.spy(element, '_showErrorDialog');
       const textSpy = sinon.spy(() => Promise.resolve('ZOMG'));
       element.dispatchEvent(
@@ -161,13 +157,9 @@
       );
 
       assert.isTrue(textSpy.called);
-      flush(() => {
-        assert.isTrue(showErrorSpy.calledOnce);
-        assert.isTrue(
-          showErrorSpy.lastCall.calledWithExactly('Error 500: ZOMG')
-        );
-        done();
-      });
+      await flush();
+      assert.isTrue(showErrorSpy.calledOnce);
+      assert.isTrue(showErrorSpy.lastCall.calledWithExactly('Error 500: ZOMG'));
     });
 
     test('constructServerErrorMsg', () => {
@@ -207,7 +199,7 @@
       );
     });
 
-    test('extract trace id from headers if exists', done => {
+    test('extract trace id from headers if exists', async () => {
       const textSpy = sinon.spy(() => Promise.resolve('500'));
       const headers = new Headers();
       headers.set('X-Gerrit-Trace', 'xxxx');
@@ -224,16 +216,14 @@
           bubbles: true,
         })
       );
-      flush(() => {
-        assert.equal(
-          element.$.errorDialog.text,
-          'Error 500: 500\nTrace Id: xxxx'
-        );
-        done();
-      });
+      await flush();
+      assert.equal(
+        element.$.errorDialog.text,
+        'Error 500: 500\nTrace Id: xxxx'
+      );
     });
 
-    test('suppress TOO_MANY_FILES error', done => {
+    test('suppress TOO_MANY_FILES error', async () => {
       const showAlertStub = sinon.stub(element, '_showAlert');
       const textSpy = sinon.spy(() =>
         Promise.resolve('too many files to find conflicts')
@@ -247,13 +237,11 @@
       );
 
       assert.isTrue(textSpy.called);
-      flush(() => {
-        assert.isFalse(showAlertStub.called);
-        done();
-      });
+      await flush();
+      assert.isFalse(showAlertStub.called);
     });
 
-    test('suppress CONFLICTS_OPERATOR_IS_NOT_SUPPORTED error', done => {
+    test('suppress CONFLICTS_OPERATOR_IS_NOT_SUPPORTED error', async () => {
       const showAlertStub = sinon.stub(element, '_showAlert');
       const textSpy = sinon.spy(() =>
         Promise.resolve("'conflicts:' operator is not supported by server")
@@ -267,13 +255,11 @@
       );
 
       assert.isTrue(textSpy.called);
-      flush(() => {
-        assert.isFalse(showAlertStub.called);
-        done();
-      });
+      await flush();
+      assert.isFalse(showAlertStub.called);
     });
 
-    test('show network error', done => {
+    test('show network error', async () => {
       const showAlertStub = sinon.stub(element, '_showAlert');
       element.dispatchEvent(
         new CustomEvent('network-error', {
@@ -282,13 +268,11 @@
           bubbles: true,
         })
       );
-      flush(() => {
-        assert.isTrue(showAlertStub.calledOnce);
-        assert.isTrue(
-          showAlertStub.lastCall.calledWithExactly('Server unavailable')
-        );
-        done();
-      });
+      await flush();
+      assert.isTrue(showAlertStub.calledOnce);
+      assert.isTrue(
+        showAlertStub.lastCall.calledWithExactly('Server unavailable')
+      );
     });
 
     test('_canOverride alerts', () => {
@@ -576,7 +560,7 @@
       assert.equal(element._lastCredentialCheck, 999999);
     });
 
-    test('refreshes with same credentials', done => {
+    test('refreshes with same credentials', async () => {
       const accountPromise = Promise.resolve({
         ...createAccountDetailWithId(1234),
       });
@@ -592,12 +576,10 @@
       element._refreshingCredentials = true;
       element._checkSignedIn();
 
-      flush(() => {
-        assert.isFalse(requestCheckStub.called);
-        assert.isTrue(handleRefreshStub.called);
-        assert.isFalse(reloadStub.called);
-        done();
-      });
+      await flush();
+      assert.isFalse(requestCheckStub.called);
+      assert.isTrue(handleRefreshStub.called);
+      assert.isFalse(reloadStub.called);
     });
 
     test('_showAlert hides existing alerts', () => {
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.ts b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.ts
index 3b22d1d..b5b0124 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.ts
@@ -22,6 +22,7 @@
 import {
   TestKeyboardShortcutBinder,
   stubRestApi,
+  mockPromise,
 } from '../../../test/test-utils';
 import {Shortcut} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
 import {_testOnly_clearDocsBaseUrlCache} from '../../../utils/url-util';
@@ -47,9 +48,9 @@
     TestKeyboardShortcutBinder.pop();
   });
 
-  setup(done => {
+  setup(async () => {
     element = basicFixture.instantiate();
-    flush(done);
+    await flush();
   });
 
   test('value is propagated to _inputVal', () => {
@@ -62,10 +63,11 @@
       ? document.activeElement!.shadowRoot.activeElement
       : document.activeElement;
 
-  test('enter in search input fires event', done => {
+  test('enter in search input fires event', async () => {
+    const promise = mockPromise();
     element.addEventListener('handle-search', () => {
       assert.notEqual(getActiveElement(), element.$.searchInput);
-      done();
+      promise.resolve();
     });
     element.value = 'test';
     MockInteractions.pressAndReleaseKeyOn(
@@ -74,6 +76,7 @@
       null,
       'enter'
     );
+    await promise;
   });
 
   test('input blurred after commit', () => {
@@ -176,7 +179,7 @@
       });
     });
 
-    test('Autocompletes groups', done => {
+    test('Autocompletes groups', async () => {
       sinon
         .stub(element, 'groupSuggestions')
         .callsFake(() =>
@@ -185,13 +188,11 @@
             {text: 'ownerin:gerrit'},
           ])
         );
-      element._getSearchSuggestions('ownerin:pol').then(s => {
-        assert.equal(s[0].value, 'ownerin:Polygerrit');
-        done();
-      });
+      const s = await element._getSearchSuggestions('ownerin:pol');
+      assert.equal(s[0].value, 'ownerin:Polygerrit');
     });
 
-    test('Autocompletes projects', done => {
+    test('Autocompletes projects', async () => {
       sinon
         .stub(element, 'projectSuggestions')
         .callsFake(() =>
@@ -201,27 +202,21 @@
             {text: 'project:gerrittest'},
           ])
         );
-      element._getSearchSuggestions('project:pol').then(s => {
-        assert.equal(s[0].value, 'project:Polygerrit');
-        done();
-      });
+      const s = await element._getSearchSuggestions('project:pol');
+      assert.equal(s[0].value, 'project:Polygerrit');
     });
 
-    test('Autocompletes simple searches', done => {
-      element._getSearchSuggestions('is:o').then(s => {
-        assert.equal(s[0].name, 'is:open');
-        assert.equal(s[0].value, 'is:open');
-        assert.equal(s[1].name, 'is:owner');
-        assert.equal(s[1].value, 'is:owner');
-        done();
-      });
+    test('Autocompletes simple searches', async () => {
+      const s = await element._getSearchSuggestions('is:o');
+      assert.equal(s[0].name, 'is:open');
+      assert.equal(s[0].value, 'is:open');
+      assert.equal(s[1].name, 'is:owner');
+      assert.equal(s[1].value, 'is:owner');
     });
 
-    test('Does not autocomplete with no match', done => {
-      element._getSearchSuggestions('asdasdasdasd').then(s => {
-        assert.equal(s.length, 0);
-        done();
-      });
+    test('Does not autocomplete with no match', async () => {
+      const s = await element._getSearchSuggestions('asdasdasdasd');
+      assert.equal(s.length, 0);
     });
 
     test('Autocompletes without is:mergable when disabled', async () => {
@@ -235,7 +230,7 @@
     'REF_UPDATED_AND_CHANGE_REINDEX',
   ].forEach(mergeability => {
     suite(`mergeability as ${mergeability}`, () => {
-      setup(done => {
+      setup(async () => {
         stubRestApi('getConfig').returns(
           Promise.resolve({
             ...createServerInfo(),
@@ -248,24 +243,22 @@
         );
 
         element = basicFixture.instantiate();
-        flush(done);
+        await flush();
       });
 
-      test('Autocompltes with is:mergable when enabled', done => {
-        element._getSearchSuggestions('is:mergeab').then(s => {
-          assert.equal(s.length, 2);
-          assert.equal(s[0].name, 'is:mergeable');
-          assert.equal(s[0].value, 'is:mergeable');
-          assert.equal(s[1].name, '-is:mergeable');
-          assert.equal(s[1].value, '-is:mergeable');
-          done();
-        });
+      test('Autocompltes with is:mergable when enabled', async () => {
+        const s = await element._getSearchSuggestions('is:mergeab');
+        assert.equal(s.length, 2);
+        assert.equal(s[0].name, 'is:mergeable');
+        assert.equal(s[0].value, 'is:mergeable');
+        assert.equal(s[1].name, '-is:mergeable');
+        assert.equal(s[1].value, '-is:mergeable');
       });
     });
   });
 
   suite('doc url', () => {
-    setup(done => {
+    setup(async () => {
       stubRestApi('getConfig').returns(
         Promise.resolve({
           ...createServerInfo(),
@@ -278,7 +271,7 @@
 
       _testOnly_clearDocsBaseUrlCache();
       element = basicFixture.instantiate();
-      flush(done);
+      await flush();
     });
 
     test('compute help doc url with correct path', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
index 196567e..44b0b8b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
@@ -751,7 +751,7 @@
     let outputEl;
     let keyLocations;
 
-    setup(done => {
+    setup(async () => {
       const prefs = {
         line_length: 10,
         show_tabs: true,
@@ -786,11 +786,11 @@
         return builder;
       });
       element.diff = {content};
-      element.render(keyLocations, prefs).then(done);
+      await element.render(keyLocations, prefs);
     });
 
-    test('addColumns is called', done => {
-      element.render(keyLocations, {}).then(done);
+    test('addColumns is called', async () => {
+      await element.render(keyLocations, {});
       assert.isTrue(element._builder.addColumns.called);
     });
 
@@ -812,15 +812,13 @@
       assert.strictEqual(sections[1], section[1]);
     });
 
-    test('render-start and render-content are fired', done => {
+    test('render-start and render-content are fired', async () => {
       const dispatchEventStub = sinon.stub(element, 'dispatchEvent');
-      element.render(keyLocations, {}).then(() => {
-        const firedEventTypes = dispatchEventStub.getCalls()
-            .map(c => c.args[0].type);
-        assert.include(firedEventTypes, 'render-start');
-        assert.include(firedEventTypes, 'render-content');
-        done();
-      });
+      await element.render(keyLocations, {});
+      const firedEventTypes = dispatchEventStub.getCalls()
+          .map(c => c.args[0].type);
+      assert.include(firedEventTypes, 'render-start');
+      assert.include(firedEventTypes, 'render-content');
     });
 
     test('cancel', () => {
@@ -837,7 +835,7 @@
     let prefs;
     let keyLocations;
 
-    setup(done => {
+    setup(async () => {
       element = mockDiffFixture.instantiate();
       diff = getMockDiffResponse();
       element.diff = diff;
@@ -849,10 +847,8 @@
       };
       keyLocations = {left: {}, right: {}};
 
-      element.render(keyLocations, prefs).then(() => {
-        builder = element._builder;
-        done();
-      });
+      await element.render(keyLocations, prefs);
+      builder = element._builder;
     });
 
     test('aria-labels on added line numbers', () => {
@@ -986,34 +982,30 @@
       assert.isTrue(lineNumberEl.classList.contains('right'));
     });
 
-    test('_getLineNumberEl unified left', done => {
+    test('_getLineNumberEl unified left', async () => {
       // Re-render as unified:
       element.viewMode = 'UNIFIED_DIFF';
-      element.render(keyLocations, prefs).then(() => {
-        builder = element._builder;
+      await element.render(keyLocations, prefs);
+      builder = element._builder;
 
-        const contentEl = builder.getContentByLine(5, 'left',
-            element.$.diffTable);
-        const lineNumberEl = builder._getLineNumberEl(contentEl, 'left');
-        assert.isTrue(lineNumberEl.classList.contains('lineNum'));
-        assert.isTrue(lineNumberEl.classList.contains('left'));
-        done();
-      });
+      const contentEl = builder.getContentByLine(5, 'left',
+          element.$.diffTable);
+      const lineNumberEl = builder._getLineNumberEl(contentEl, 'left');
+      assert.isTrue(lineNumberEl.classList.contains('lineNum'));
+      assert.isTrue(lineNumberEl.classList.contains('left'));
     });
 
-    test('_getLineNumberEl unified right', done => {
+    test('_getLineNumberEl unified right', async () => {
       // Re-render as unified:
       element.viewMode = 'UNIFIED_DIFF';
-      element.render(keyLocations, prefs).then(() => {
-        builder = element._builder;
+      await element.render(keyLocations, prefs);
+      builder = element._builder;
 
-        const contentEl = builder.getContentByLine(5, 'right',
-            element.$.diffTable);
-        const lineNumberEl = builder._getLineNumberEl(contentEl, 'right');
-        assert.isTrue(lineNumberEl.classList.contains('lineNum'));
-        assert.isTrue(lineNumberEl.classList.contains('right'));
-        done();
-      });
+      const contentEl = builder.getContentByLine(5, 'right',
+          element.$.diffTable);
+      const lineNumberEl = builder._getLineNumberEl(contentEl, 'right');
+      assert.isTrue(lineNumberEl.classList.contains('lineNum'));
+      assert.isTrue(lineNumberEl.classList.contains('right'));
     });
 
     test('_getNextContentOnSide side-by-side left', () => {
@@ -1040,44 +1032,38 @@
       assert.equal(nextElem.textContent, expectedNextString);
     });
 
-    test('_getNextContentOnSide unified left', done => {
+    test('_getNextContentOnSide unified left', async () => {
       // Re-render as unified:
       element.viewMode = 'UNIFIED_DIFF';
-      element.render(keyLocations, prefs).then(() => {
-        builder = element._builder;
+      await element.render(keyLocations, prefs);
+      builder = element._builder;
 
-        const startElem = builder.getContentByLine(5, 'left',
-            element.$.diffTable);
-        const expectedStartString = diff.content[2].ab[0];
-        const expectedNextString = diff.content[2].ab[1];
-        assert.equal(startElem.textContent, expectedStartString);
+      const startElem = builder.getContentByLine(5, 'left',
+          element.$.diffTable);
+      const expectedStartString = diff.content[2].ab[0];
+      const expectedNextString = diff.content[2].ab[1];
+      assert.equal(startElem.textContent, expectedStartString);
 
-        const nextElem = builder._getNextContentOnSide(startElem,
-            'left');
-        assert.equal(nextElem.textContent, expectedNextString);
-
-        done();
-      });
+      const nextElem = builder._getNextContentOnSide(startElem,
+          'left');
+      assert.equal(nextElem.textContent, expectedNextString);
     });
 
-    test('_getNextContentOnSide unified right', done => {
+    test('_getNextContentOnSide unified right', async () => {
       // Re-render as unified:
       element.viewMode = 'UNIFIED_DIFF';
-      element.render(keyLocations, prefs).then(() => {
-        builder = element._builder;
+      await element.render(keyLocations, prefs);
+      builder = element._builder;
 
-        const startElem = builder.getContentByLine(5, 'right',
-            element.$.diffTable);
-        const expectedStartString = diff.content[1].b[0];
-        const expectedNextString = diff.content[1].b[1];
-        assert.equal(startElem.textContent, expectedStartString);
+      const startElem = builder.getContentByLine(5, 'right',
+          element.$.diffTable);
+      const expectedStartString = diff.content[1].b[0];
+      const expectedNextString = diff.content[1].b[1];
+      assert.equal(startElem.textContent, expectedStartString);
 
-        const nextElem = builder._getNextContentOnSide(startElem,
-            'right');
-        assert.equal(nextElem.textContent, expectedNextString);
-
-        done();
-      });
+      const nextElem = builder._getNextContentOnSide(startElem,
+          'right');
+      assert.equal(nextElem.textContent, expectedNextString);
     });
 
     test('escaping HTML', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js
index 3e63b0a..3b604eb 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js
@@ -19,7 +19,7 @@
 import '../gr-diff/gr-diff.js';
 import './gr-diff-cursor.js';
 import {html} from '@polymer/polymer/lib/utils/html-tag.js';
-import {listenOnce} from '../../../test/test-utils.js';
+import {listenOnce, mockPromise} from '../../../test/test-utils.js';
 import {getMockDiffResponse} from '../../../test/mocks/diff-response.js';
 import {createDefaultDiffPrefs} from '../../../constants/constants.js';
 import {GrDiffCursor} from './gr-diff-cursor.js';
@@ -33,7 +33,7 @@
   let diffElement;
   let diff;
 
-  setup(done => {
+  setup(async () => {
     diffElement = basicFixture.instantiate();
     cursor = new GrDiffCursor();
 
@@ -48,17 +48,19 @@
       meta: {patchRange: undefined},
     };
     diffElement.path = 'some/path.ts';
+    const promise = mockPromise();
     const setupDone = () => {
       cursor._updateStops();
       cursor.moveToFirstChunk();
       diffElement.removeEventListener('render', setupDone);
-      done();
+      promise.resolve();
     };
     diffElement.addEventListener('render', setupDone);
 
     diff = getMockDiffResponse();
     diffElement.prefs = createDefaultDiffPrefs();
     diffElement.diff = diff;
+    await promise;
   });
 
   test('diff cursor functionality (side-by-side)', () => {
@@ -215,15 +217,17 @@
   });
 
   suite('unified diff', () => {
-    setup(done => {
+    setup(async () => {
+      const promise = mockPromise();
       // We must allow the diff to re-render after setting the viewMode.
       const renderHandler = function() {
         diffElement.removeEventListener('render', renderHandler);
         cursor.reInitCursor();
-        done();
+        promise.resolve();
       };
       diffElement.addEventListener('render', renderHandler);
       diffElement.viewMode = 'UNIFIED_DIFF';
+      await promise;
     });
 
     test('diff cursor functionality (unified)', () => {
@@ -312,11 +316,12 @@
   });
 
   suite('moved chunks without line range)', () => {
-    setup(done => {
+    setup(async () => {
+      const promise = mockPromise();
       const renderHandler = function() {
         diffElement.removeEventListener('render', renderHandler);
         cursor.reInitCursor();
-        done();
+        promise.resolve();
       };
       diffElement.addEventListener('render', renderHandler);
       diffElement.diff = {...diff, content: [
@@ -352,6 +357,7 @@
           ],
         },
       ]};
+      await promise;
     });
 
     test('renders moveControls with simple descriptions', () => {
@@ -363,11 +369,12 @@
   });
 
   suite('moved chunks (moveDetails)', () => {
-    setup(done => {
+    setup(async () => {
+      const promise = mockPromise();
       const renderHandler = function() {
         diffElement.removeEventListener('render', renderHandler);
         cursor.reInitCursor();
-        done();
+        promise.resolve();
       };
       diffElement.addEventListener('render', renderHandler);
       diffElement.diff = {...diff, content: [
@@ -403,6 +410,7 @@
           ],
         },
       ]};
+      await promise;
     });
 
     test('renders moveControls with simple descriptions', () => {
@@ -412,37 +420,41 @@
       assert.equal(movedOut.textContent, 'Moved to lines 2 - 4');
     });
 
-    test('startLineAnchor of movedIn chunk fires events', done => {
+    test('startLineAnchor of movedIn chunk fires events', async () => {
       const [movedIn] = diffElement.root
           .querySelectorAll('.dueToMove .moveControls');
       const [startLineAnchor] = movedIn.querySelectorAll('a');
 
+      const promise = mockPromise();
       const onMovedLinkClicked = e => {
         assert.deepEqual(e.detail, {lineNum: 4, side: 'left'});
-        done();
+        promise.resolve();
       };
       assert.equal(startLineAnchor.textContent, '4');
       startLineAnchor
           .addEventListener('moved-link-clicked', onMovedLinkClicked);
       MockInteractions.click(startLineAnchor);
+      await promise;
     });
 
-    test('endLineAnchor of movedOut fires events', done => {
+    test('endLineAnchor of movedOut fires events', async () => {
       const [, movedOut] = diffElement.root
           .querySelectorAll('.dueToMove .moveControls');
       const [, endLineAnchor] = movedOut.querySelectorAll('a');
 
+      const promise = mockPromise();
       const onMovedLinkClicked = e => {
         assert.deepEqual(e.detail, {lineNum: 4, side: 'right'});
-        done();
+        promise.resolve();
       };
       assert.equal(endLineAnchor.textContent, '4');
       endLineAnchor.addEventListener('moved-link-clicked', onMovedLinkClicked);
       MockInteractions.click(endLineAnchor);
+      await promise;
     });
   });
 
-  test('initialLineNumber not provided', done => {
+  test('initialLineNumber not provided', async () => {
     let scrollBehaviorDuringMove;
     const moveToNumStub = sinon.stub(cursor, 'moveToLineNumber');
     const moveToChunkStub = sinon.stub(cursor, 'moveToFirstChunk')
@@ -450,6 +462,7 @@
           scrollBehaviorDuringMove = cursor.cursorManager.scrollMode;
         });
 
+    const promise = mockPromise();
     function renderHandler() {
       diffElement.removeEventListener('render', renderHandler);
       cursor.reInitCursor();
@@ -457,19 +470,21 @@
       assert.isTrue(moveToChunkStub.called);
       assert.equal(scrollBehaviorDuringMove, 'never');
       assert.equal(cursor.cursorManager.scrollMode, 'keep-visible');
-      done();
+      promise.resolve();
     }
     diffElement.addEventListener('render', renderHandler);
     diffElement._diffChanged(getMockDiffResponse());
+    await promise;
   });
 
-  test('initialLineNumber provided', done => {
+  test('initialLineNumber provided', async () => {
     let scrollBehaviorDuringMove;
     const moveToNumStub = sinon.stub(cursor, 'moveToLineNumber')
         .callsFake(() => {
           scrollBehaviorDuringMove = cursor.cursorManager.scrollMode;
         });
     const moveToChunkStub = sinon.stub(cursor, 'moveToFirstChunk');
+    const promise = mockPromise();
     function renderHandler() {
       diffElement.removeEventListener('render', renderHandler);
       cursor.reInitCursor();
@@ -479,13 +494,14 @@
       assert.equal(moveToNumStub.lastCall.args[1], 'right');
       assert.equal(scrollBehaviorDuringMove, 'keep-visible');
       assert.equal(cursor.cursorManager.scrollMode, 'keep-visible');
-      done();
+      promise.resolve();
     }
     diffElement.addEventListener('render', renderHandler);
     cursor.initialLineNumber = 10;
     cursor.side = 'right';
 
     diffElement._diffChanged(getMockDiffResponse());
+    await promise;
   });
 
   test('getTargetDiffElement', () => {
@@ -502,31 +518,35 @@
       diffElement.loggedIn = true;
     });
 
-    test('adds new draft for selected line on the left', done => {
+    test('adds new draft for selected line on the left', async () => {
       cursor.moveToLineNumber(2, 'left');
+      const promise = mockPromise();
       diffElement.addEventListener('create-comment', e => {
         const {lineNum, range, side} = e.detail;
         assert.equal(lineNum, 2);
         assert.equal(range, undefined);
         assert.equal(side, 'left');
-        done();
+        promise.resolve();
       });
       cursor.createCommentInPlace();
+      await promise;
     });
 
-    test('adds draft for selected line on the right', done => {
+    test('adds draft for selected line on the right', async () => {
       cursor.moveToLineNumber(4, 'right');
+      const promise = mockPromise();
       diffElement.addEventListener('create-comment', e => {
         const {lineNum, range, side} = e.detail;
         assert.equal(lineNum, 4);
         assert.equal(range, undefined);
         assert.equal(side, 'right');
-        done();
+        promise.resolve();
       });
       cursor.createCommentInPlace();
+      await promise;
     });
 
-    test('creates comment for range if selected', done => {
+    test('creates comment for range if selected', async () => {
       const someRange = {
         start_line: 2,
         start_character: 3,
@@ -537,14 +557,16 @@
         side: 'right',
         range: someRange,
       };
+      const promise = mockPromise();
       diffElement.addEventListener('create-comment', e => {
         const {lineNum, range, side} = e.detail;
         assert.equal(lineNum, 6);
         assert.equal(range, someRange);
         assert.equal(side, 'right');
-        done();
+        promise.resolve();
       });
       cursor.createCommentInPlace();
+      await promise;
     });
 
     test('ignores call if nothing is selected', () => {
@@ -596,15 +618,13 @@
     assert.equal(cursor._findRowByNumberAndFile(5, 'left'), row);
   });
 
-  test('expand context updates stops', done => {
+  test('expand context updates stops', async () => {
     sinon.spy(cursor, '_updateStops');
     MockInteractions.tap(diffElement.shadowRoot
         .querySelector('gr-context-controls').shadowRoot
         .querySelector('.showContext'));
-    flush(() => {
-      assert.isTrue(cursor._updateStops.called);
-      done();
-    });
+    await flush();
+    assert.isTrue(cursor._updateStops.called);
   });
 
   test('updates stops when loading changes', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js
index 42aa160..344f9d8 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js
@@ -23,7 +23,11 @@
 import {Side, createDefaultDiffPrefs} from '../../../constants/constants.js';
 import {createChange} from '../../../test/test-data-generators.js';
 import {CoverageType} from '../../../types/types.js';
-import {addListenerForTest, stubRestApi} from '../../../test/test-utils.js';
+import {
+  addListenerForTest,
+  mockPromise,
+  stubRestApi,
+} from '../../../test/test-utils.js';
 import {EditPatchSetNum, ParentPatchSetNum} from '../../../types/common.js';
 import {_testOnly_resetState} from '../../../services/comments/comments-model.js';
 
@@ -64,14 +68,13 @@
   });
 
   suite('render reporting', () => {
-    test('starts total and content timer on render-start', done => {
+    test('starts total and content timer on render-start', () => {
       element.dispatchEvent(
           new CustomEvent('render-start', {bubbles: true, composed: true}));
       assert.isTrue(element.reporting.time.calledWithExactly(
           'Diff Total Render'));
       assert.isTrue(element.reporting.time.calledWithExactly(
           'Diff Content Render'));
-      done();
     });
 
     test('ends content timer on render-content', () => {
@@ -215,25 +218,23 @@
     });
   });
 
-  test('prefetch getDiff', done => {
+  test('prefetch getDiff', async () => {
     const diffRestApiStub = stubRestApi('getDiff')
         .returns(Promise.resolve({content: []}));
     element.changeNum = 123;
     element.patchRange = {basePatchNum: 1, patchNum: 2};
     element.path = 'file.txt';
     element.prefetchDiff();
-    element._getDiff().then(() =>{
-      assert.isTrue(diffRestApiStub.calledOnce);
-      done();
-    });
+    await element._getDiff();
+    assert.isTrue(diffRestApiStub.calledOnce);
   });
 
-  test('_getDiff handles null diff responses', done => {
+  test('_getDiff handles null diff responses', async () => {
     stubRestApi('getDiff').returns(Promise.resolve(null));
     element.changeNum = 123;
     element.patchRange = {basePatchNum: 1, patchNum: 2};
     element.path = 'file.txt';
-    element._getDiff().then(done);
+    await element._getDiff();
   });
 
   test('reload resolves on error', () => {
@@ -311,7 +312,7 @@
       };
     });
 
-    test('renders image diffs with same file name', done => {
+    test('renders image diffs with same file name', async () => {
       const mockDiff = {
         meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66},
         meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg',
@@ -342,6 +343,7 @@
         },
       }));
 
+      const promise = mockPromise();
       const rendered = () => {
         // Recognizes that it should be an image diff.
         assert.isTrue(element.isImageDiff);
@@ -377,7 +379,7 @@
           leftLoaded = true;
           if (rightLoaded) {
             element.removeEventListener('render', rendered);
-            done();
+            promise.resolve();
           }
         });
 
@@ -390,7 +392,7 @@
           rightLoaded = true;
           if (leftLoaded) {
             element.removeEventListener('render', rendered);
-            done();
+            promise.resolve();
           }
         });
       };
@@ -398,9 +400,10 @@
       element.addEventListener('render', rendered);
       element.prefs = createDefaultDiffPrefs();
       element.reload();
+      await promise;
     });
 
-    test('renders image diffs with a different file name', done => {
+    test('renders image diffs with a different file name', async () => {
       const mockDiff = {
         meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66},
         meta_b: {name: 'carrot2.jpg', content_type: 'image/jpeg',
@@ -431,6 +434,7 @@
         },
       }));
 
+      const promise = mockPromise();
       const rendered = () => {
         // Recognizes that it should be an image diff.
         assert.isTrue(element.isImageDiff);
@@ -468,7 +472,7 @@
           leftLoaded = true;
           if (rightLoaded) {
             element.removeEventListener('render', rendered);
-            done();
+            promise.resolve();
           }
         });
 
@@ -481,7 +485,7 @@
           rightLoaded = true;
           if (leftLoaded) {
             element.removeEventListener('render', rendered);
-            done();
+            promise.resolve();
           }
         });
       };
@@ -489,9 +493,10 @@
       element.addEventListener('render', rendered);
       element.prefs = createDefaultDiffPrefs();
       element.reload();
+      await promise;
     });
 
-    test('renders added image', done => {
+    test('renders added image', async () => {
       const mockDiff = {
         meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg',
           lines: 560},
@@ -517,6 +522,7 @@
         },
       }));
 
+      const promise = mockPromise();
       element.addEventListener('render', () => {
         // Recognizes that it should be an image diff.
         assert.isTrue(element.isImageDiff);
@@ -530,14 +536,15 @@
 
         assert.isNotOk(leftImage);
         assert.isOk(rightImage);
-        done();
+        promise.resolve();
       });
 
       element.prefs = createDefaultDiffPrefs();
       element.reload();
+      await promise;
     });
 
-    test('renders removed image', done => {
+    test('renders removed image', async () => {
       const mockDiff = {
         meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg',
           lines: 560},
@@ -563,6 +570,7 @@
         revisionImage: null,
       }));
 
+      const promise = mockPromise();
       element.addEventListener('render', () => {
         // Recognizes that it should be an image diff.
         assert.isTrue(element.isImageDiff);
@@ -576,14 +584,15 @@
 
         assert.isOk(leftImage);
         assert.isNotOk(rightImage);
-        done();
+        promise.resolve();
       });
 
       element.prefs = createDefaultDiffPrefs();
       element.reload();
+      await promise;
     });
 
-    test('does not render disallowed image type', done => {
+    test('does not render disallowed image type', async () => {
       const mockDiff = {
         meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg-evil',
           lines: 560},
@@ -611,6 +620,7 @@
         revisionImage: null,
       }));
 
+      const promise = mockPromise();
       element.addEventListener('render', () => {
         // Recognizes that it should be an image diff.
         assert.isTrue(element.isImageDiff);
@@ -619,11 +629,12 @@
         const leftImage =
             element.$.diff.$.diffTable.querySelector('td.left img');
         assert.isNotOk(leftImage);
-        done();
+        promise.resolve();
       });
 
       element.prefs = createDefaultDiffPrefs();
       element.reload();
+      await promise;
     });
   });
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js
index 033b886..d9c4ba2 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js
@@ -678,23 +678,21 @@
       assert.isNotOk(args[3]);
     });
 
-    test('A fires an error event when not logged in', done => {
+    test('A fires an error event when not logged in', async () => {
       const changeNavStub = sinon.stub(GerritNav, 'navigateToChange');
       sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(false));
       const loggedInErrorSpy = sinon.spy();
       element.addEventListener('show-auth-required', loggedInErrorSpy);
       MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
-      flush(() => {
-        assert.isTrue(changeNavStub.notCalled, 'The `a` keyboard shortcut ' +
-          'should only work when the user is logged in.');
-        assert.isNull(window.sessionStorage.getItem(
-            'changeView.showReplyDialog'));
-        assert.isTrue(loggedInErrorSpy.called);
-        done();
-      });
+      await flush();
+      assert.isTrue(changeNavStub.notCalled, 'The `a` keyboard shortcut ' +
+        'should only work when the user is logged in.');
+      assert.isNull(window.sessionStorage.getItem(
+          'changeView.showReplyDialog'));
+      assert.isTrue(loggedInErrorSpy.called);
     });
 
-    test('A navigates to change with logged in', done => {
+    test('A navigates to change with logged in', async () => {
       element._changeNum = '42';
       element._patchRange = {
         basePatchNum: 5,
@@ -712,41 +710,38 @@
       const loggedInErrorSpy = sinon.spy();
       element.addEventListener('show-auth-required', loggedInErrorSpy);
       MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
-      flush(() => {
-        assert.isTrue(element.changeViewState.showReplyDialog);
-        assert(changeNavStub.lastCall.calledWithExactly(element._change, 10,
-            5), 'Should navigate to /c/42/5..10');
-        assert.isFalse(loggedInErrorSpy.called);
-        done();
-      });
+      await flush();
+      assert.isTrue(element.changeViewState.showReplyDialog);
+      assert(changeNavStub.lastCall.calledWithExactly(element._change, 10,
+          5), 'Should navigate to /c/42/5..10');
+      assert.isFalse(loggedInErrorSpy.called);
     });
 
-    test('A navigates to change with old patch number with logged in', done => {
-      element._changeNum = '42';
-      element._patchRange = {
-        basePatchNum: PARENT,
-        patchNum: 1,
-      };
-      element._change = {
-        _number: 42,
-        revisions: {
-          a: {_number: 1, commit: {parents: []}},
-          b: {_number: 2, commit: {parents: []}},
-        },
-      };
-      const changeNavStub = sinon.stub(GerritNav, 'navigateToChange');
-      sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(true));
-      const loggedInErrorSpy = sinon.spy();
-      element.addEventListener('show-auth-required', loggedInErrorSpy);
-      MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
-      flush(() => {
-        assert.isTrue(element.changeViewState.showReplyDialog);
-        assert(changeNavStub.lastCall.calledWithExactly(element._change, 1,
-            PARENT), 'Should navigate to /c/42/1');
-        assert.isFalse(loggedInErrorSpy.called);
-        done();
-      });
-    });
+    test('A navigates to change with old patch number with logged in',
+        async () => {
+          element._changeNum = '42';
+          element._patchRange = {
+            basePatchNum: PARENT,
+            patchNum: 1,
+          };
+          element._change = {
+            _number: 42,
+            revisions: {
+              a: {_number: 1, commit: {parents: []}},
+              b: {_number: 2, commit: {parents: []}},
+            },
+          };
+          const changeNavStub = sinon.stub(GerritNav, 'navigateToChange');
+          sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(true));
+          const loggedInErrorSpy = sinon.spy();
+          element.addEventListener('show-auth-required', loggedInErrorSpy);
+          MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
+          await flush();
+          assert.isTrue(element.changeViewState.showReplyDialog);
+          assert(changeNavStub.lastCall.calledWithExactly(element._change, 1,
+              PARENT), 'Should navigate to /c/42/1');
+          assert.isFalse(loggedInErrorSpy.called);
+        });
 
     test('keyboard shortcuts with patch range', () => {
       element._changeNum = '42';
@@ -860,7 +855,7 @@
       assert.isTrue(changeNavStub.calledOnce);
     });
 
-    test('edit should redirect to edit page', done => {
+    test('edit should redirect to edit page', async () => {
       element._loggedIn = true;
       element._path = 't.txt';
       element._patchRange = {
@@ -877,23 +872,21 @@
         },
       };
       const redirectStub = sinon.stub(GerritNav, 'navigateToRelativeUrl');
-      flush(() => {
-        const editBtn = element.shadowRoot
-            .querySelector('.editButton gr-button');
-        assert.isTrue(!!editBtn);
-        MockInteractions.tap(editBtn);
-        assert.isTrue(redirectStub.called);
-        assert.isTrue(redirectStub.lastCall.calledWithExactly(
-            GerritNav.getEditUrlForDiff(
-                element._change,
-                element._path,
-                element._patchRange.patchNum
-            )));
-        done();
-      });
+      await flush();
+      const editBtn = element.shadowRoot
+          .querySelector('.editButton gr-button');
+      assert.isTrue(!!editBtn);
+      MockInteractions.tap(editBtn);
+      assert.isTrue(redirectStub.called);
+      assert.isTrue(redirectStub.lastCall.calledWithExactly(
+          GerritNav.getEditUrlForDiff(
+              element._change,
+              element._path,
+              element._patchRange.patchNum
+          )));
     });
 
-    test('edit should redirect to edit page with line number', done => {
+    test('edit should redirect to edit page with line number', async () => {
       const lineNumber = 42;
       element._loggedIn = true;
       element._path = 't.txt';
@@ -913,21 +906,19 @@
       sinon.stub(element.cursor, 'getAddress')
           .returns({number: lineNumber, isLeftSide: false});
       const redirectStub = sinon.stub(GerritNav, 'navigateToRelativeUrl');
-      flush(() => {
-        const editBtn = element.shadowRoot
-            .querySelector('.editButton gr-button');
-        assert.isTrue(!!editBtn);
-        MockInteractions.tap(editBtn);
-        assert.isTrue(redirectStub.called);
-        assert.isTrue(redirectStub.lastCall.calledWithExactly(
-            GerritNav.getEditUrlForDiff(
-                element._change,
-                element._path,
-                element._patchRange.patchNum,
-                lineNumber
-            )));
-        done();
-      });
+      await flush();
+      const editBtn = element.shadowRoot
+          .querySelector('.editButton gr-button');
+      assert.isTrue(!!editBtn);
+      MockInteractions.tap(editBtn);
+      assert.isTrue(redirectStub.called);
+      assert.isTrue(redirectStub.lastCall.calledWithExactly(
+          GerritNav.getEditUrlForDiff(
+              element._change,
+              element._path,
+              element._patchRange.patchNum,
+              lineNumber
+          )));
     });
 
     function isEditVisibile({loggedIn, changeStatus}) {
@@ -1294,7 +1285,7 @@
       assert.isFalse(saveReviewedStub.called);
     });
 
-    test('hash is determined from params', done => {
+    test('hash is determined from params', async () => {
       sinon.stub(element.$.diffHost, 'reload');
       sinon.stub(element, '_initLineOfInterestAndCursor');
 
@@ -1308,10 +1299,8 @@
         hash: 10,
       };
 
-      flush(() => {
-        assert.isTrue(element._initLineOfInterestAndCursor.calledOnce);
-        done();
-      });
+      await flush();
+      assert.isTrue(element._initLineOfInterestAndCursor.calledOnce);
     });
 
     test('diff mode selector correctly toggles the diff', () => {
@@ -1360,15 +1349,13 @@
       assert.equal(element._getDiffViewMode(), 'SIDE_BY_SIDE');
     });
 
-    test('diff mode selector should be hidden for binary', done => {
+    test('diff mode selector should be hidden for binary', async () => {
       element._diff = {binary: true, content: []};
 
-      flush(() => {
-        const diffModeSelector = element.shadowRoot
-            .querySelector('.diffModeSelector');
-        assert.isTrue(diffModeSelector.classList.contains('hide'));
-        done();
-      });
+      await flush();
+      const diffModeSelector = element.shadowRoot
+          .querySelector('.diffModeSelector');
+      assert.isTrue(diffModeSelector.classList.contains('hide'));
     });
 
     suite('_commitRange', () => {
@@ -1400,7 +1387,7 @@
             change));
       });
 
-      test('uses the patchNum and basePatchNum ', done => {
+      test('uses the patchNum and basePatchNum ', async () => {
         element.params = {
           view: GerritNav.View.DIFF,
           changeNum: '42',
@@ -1409,16 +1396,14 @@
           path: '/COMMIT_MSG',
         };
         element._change = change;
-        flush(() => {
-          assert.deepEqual(element._commitRange, {
-            baseCommit: 'commit-sha-2',
-            commit: 'commit-sha-4',
-          });
-          done();
+        await flush();
+        assert.deepEqual(element._commitRange, {
+          baseCommit: 'commit-sha-2',
+          commit: 'commit-sha-4',
         });
       });
 
-      test('uses the parent when there is no base patch num ', done => {
+      test('uses the parent when there is no base patch num ', async () => {
         element.params = {
           view: GerritNav.View.DIFF,
           changeNum: '42',
@@ -1426,12 +1411,10 @@
           path: '/COMMIT_MSG',
         };
         element._change = change;
-        flush(() => {
-          assert.deepEqual(element._commitRange, {
-            commit: 'commit-sha-5',
-            baseCommit: 'sha-5-parent',
-          });
-          done();
+        await flush();
+        assert.deepEqual(element._commitRange, {
+          commit: 'commit-sha-5',
+          baseCommit: 'sha-5-parent',
         });
       });
     });
@@ -1921,7 +1904,7 @@
       ]);
     });
 
-    test('File change should trigger navigateToDiff once', done => {
+    test('File change should trigger navigateToDiff once', async () => {
       element._files = getFilesFromFileList(['file1', 'file2', 'file3']);
       sinon.stub(element, '_initLineOfInterestAndCursor');
       sinon.stub(GerritNav, 'navigateToDiff');
@@ -1942,7 +1925,7 @@
         ...createChange(),
         revisions: createRevisions(1),
       };
-      flush();
+      await flush();
       assert.isTrue(GerritNav.navigateToDiff.notCalled);
 
       // Switch to file2
@@ -1964,7 +1947,6 @@
 
       // No extra call
       assert.isTrue(GerritNav.navigateToDiff.calledOnce);
-      done();
     });
 
     test('_computeDownloadDropdownLinks', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js
index c59ceb7..9ee779c 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js
@@ -23,7 +23,7 @@
 import {_setHiddenScroll} from '../../../scripts/hiddenscroll.js';
 import {runA11yAudit} from '../../../test/a11y-test-utils.js';
 import '@polymer/paper-button/paper-button.js';
-import {stubRestApi} from '../../../test/test-utils.js';
+import {mockPromise, stubRestApi} from '../../../test/test-utils.js';
 
 const basicFixture = fixtureFromElement('gr-diff');
 
@@ -232,7 +232,9 @@
         };
       });
 
-      test('renders image diffs with same file name', done => {
+      test('renders image diffs with same file name', async () => {
+        const leftRendered = mockPromise();
+        const rightRendered = mockPromise();
         const rendered = () => {
           // Recognizes that it should be an image diff.
           assert.isTrue(element.isImageDiff);
@@ -256,19 +258,12 @@
           assert.isNotOk(rightLabelName);
           assert.isNotOk(leftLabelName);
 
-          let leftLoaded = false;
-          let rightLoaded = false;
-
           leftImage.addEventListener('load', () => {
             assert.isOk(leftImage);
             assert.equal(leftImage.getAttribute('src'),
                 'data:image/bmp;base64,' + mockFile1.body);
             assert.equal(leftLabelContent.textContent, '1\u00d71 image/bmp');// \u00d7 - '×'
-            leftLoaded = true;
-            if (rightLoaded) {
-              element.removeEventListener('render', rendered);
-              done();
-            }
+            leftRendered.resolve();
           });
 
           rightImage.addEventListener('load', () => {
@@ -277,11 +272,7 @@
                 'data:image/bmp;base64,' + mockFile2.body);
             assert.equal(rightLabelContent.textContent, '1\u00d71 image/bmp');// \u00d7 - '×'
 
-            rightLoaded = true;
-            if (leftLoaded) {
-              element.removeEventListener('render', rendered);
-              done();
-            }
+            rightRendered.resolve();
           });
         };
 
@@ -305,9 +296,11 @@
           content: [{skip: 66}],
           binary: true,
         };
+        await Promise.all([leftRendered, rightRendered]);
+        element.removeEventListener('render', rendered);
       });
 
-      test('renders image diffs with a different file name', done => {
+      test('renders image diffs with a different file name', async () => {
         const mockDiff = {
           meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66},
           meta_b: {name: 'carrot2.jpg', content_type: 'image/jpeg',
@@ -324,7 +317,8 @@
           content: [{skip: 66}],
           binary: true,
         };
-
+        const leftRendered = mockPromise();
+        const rightRendered = mockPromise();
         const rendered = () => {
           // Recognizes that it should be an image diff.
           assert.isTrue(element.isImageDiff);
@@ -350,19 +344,12 @@
           assert.equal(leftLabelName.textContent, mockDiff.meta_a.name);
           assert.equal(rightLabelName.textContent, mockDiff.meta_b.name);
 
-          let leftLoaded = false;
-          let rightLoaded = false;
-
           leftImage.addEventListener('load', () => {
             assert.isOk(leftImage);
             assert.equal(leftImage.getAttribute('src'),
                 'data:image/bmp;base64,' + mockFile1.body);
             assert.equal(leftLabelContent.textContent, '1\u00d71 image/bmp');// \u00d7 - '×'
-            leftLoaded = true;
-            if (rightLoaded) {
-              element.removeEventListener('render', rendered);
-              done();
-            }
+            leftRendered.resolve();
           });
 
           rightImage.addEventListener('load', () => {
@@ -371,11 +358,7 @@
                 'data:image/bmp;base64,' + mockFile2.body);
             assert.equal(rightLabelContent.textContent, '1\u00d71 image/bmp');// \u00d7 - '×'
 
-            rightLoaded = true;
-            if (leftLoaded) {
-              element.removeEventListener('render', rendered);
-              done();
-            }
+            rightRendered.resolve();
           });
         };
 
@@ -386,9 +369,11 @@
         element.revisionImage = mockFile2;
         element.revisionImage._name = mockDiff.meta_b.name;
         element.diff = mockDiff;
+        await Promise.all([leftRendered, rightRendered]);
+        element.removeEventListener('render', rendered);
       });
 
-      test('renders added image', done => {
+      test('renders added image', async () => {
         const mockDiff = {
           meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg',
             lines: 560},
@@ -405,27 +390,27 @@
           binary: true,
         };
 
-        function rendered() {
-          // Recognizes that it should be an image diff.
-          assert.isTrue(element.isImageDiff);
-          assert.instanceOf(
-              element.$.diffBuilder._builder, GrDiffBuilderImage);
-
-          const leftImage = element.$.diffTable.querySelector('td.left img');
-          const rightImage = element.$.diffTable.querySelector('td.right img');
-
-          assert.isNotOk(leftImage);
-          assert.isOk(rightImage);
-          done();
-          element.removeEventListener('render', rendered);
-        }
+        const promise = mockPromise();
+        function rendered() { promise.resolve(); }
         element.addEventListener('render', rendered);
 
         element.revisionImage = mockFile2;
         element.diff = mockDiff;
+        await promise;
+        element.removeEventListener('render', rendered);
+        // Recognizes that it should be an image diff.
+        assert.isTrue(element.isImageDiff);
+        assert.instanceOf(
+            element.$.diffBuilder._builder, GrDiffBuilderImage);
+
+        const leftImage = element.$.diffTable.querySelector('td.left img');
+        const rightImage = element.$.diffTable.querySelector('td.right img');
+
+        assert.isNotOk(leftImage);
+        assert.isOk(rightImage);
       });
 
-      test('renders removed image', done => {
+      test('renders removed image', async () => {
         const mockDiff = {
           meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg',
             lines: 560},
@@ -441,28 +426,27 @@
           content: [{skip: 66}],
           binary: true,
         };
-
-        function rendered() {
-          // Recognizes that it should be an image diff.
-          assert.isTrue(element.isImageDiff);
-          assert.instanceOf(
-              element.$.diffBuilder._builder, GrDiffBuilderImage);
-
-          const leftImage = element.$.diffTable.querySelector('td.left img');
-          const rightImage = element.$.diffTable.querySelector('td.right img');
-
-          assert.isOk(leftImage);
-          assert.isNotOk(rightImage);
-          done();
-          element.removeEventListener('render', rendered);
-        }
+        const promise = mockPromise();
+        function rendered() { promise.resolve(); }
         element.addEventListener('render', rendered);
 
         element.baseImage = mockFile1;
         element.diff = mockDiff;
+        await promise;
+        element.removeEventListener('render', rendered);
+        // Recognizes that it should be an image diff.
+        assert.isTrue(element.isImageDiff);
+        assert.instanceOf(
+            element.$.diffBuilder._builder, GrDiffBuilderImage);
+
+        const leftImage = element.$.diffTable.querySelector('td.left img');
+        const rightImage = element.$.diffTable.querySelector('td.right img');
+
+        assert.isOk(leftImage);
+        assert.isNotOk(rightImage);
       });
 
-      test('does not render disallowed image type', done => {
+      test('does not render disallowed image type', async () => {
         const mockDiff = {
           meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg-evil',
             lines: 560},
@@ -480,50 +464,54 @@
         };
         mockFile1.type = 'image/jpeg-evil';
 
-        function rendered() {
-          // Recognizes that it should be an image diff.
-          assert.isTrue(element.isImageDiff);
-          assert.instanceOf(
-              element.$.diffBuilder._builder, GrDiffBuilderImage);
-          const leftImage = element.$.diffTable.querySelector('td.left img');
-          assert.isNotOk(leftImage);
-          done();
-          element.removeEventListener('render', rendered);
-        }
+        const promise = mockPromise();
+        function rendered() { promise.resolve(); }
         element.addEventListener('render', rendered);
 
         element.baseImage = mockFile1;
         element.diff = mockDiff;
+        await promise;
+        element.removeEventListener('render', rendered);
+        // Recognizes that it should be an image diff.
+        assert.isTrue(element.isImageDiff);
+        assert.instanceOf(
+            element.$.diffBuilder._builder, GrDiffBuilderImage);
+        const leftImage = element.$.diffTable.querySelector('td.left img');
+        assert.isNotOk(leftImage);
       });
     });
 
-    test('_handleTap lineNum', done => {
+    test('_handleTap lineNum', async () => {
       const addDraftStub = sinon.stub(element, 'addDraftAtLine');
       const el = document.createElement('div');
       el.className = 'lineNum';
+      const promise = mockPromise();
       el.addEventListener('click', e => {
         element._handleTap(e);
         assert.isTrue(addDraftStub.called);
         assert.equal(addDraftStub.lastCall.args[0], el);
-        done();
+        promise.resolve();
       });
       el.click();
+      await promise;
     });
 
-    test('_handleTap context', done => {
+    test('_handleTap context', async () => {
       const showContextStub =
           sinon.stub(element.$.diffBuilder, 'showContext');
       const el = document.createElement('div');
       el.className = 'showContext';
+      const promise = mockPromise();
       el.addEventListener('click', e => {
         element._handleDiffContextExpanded(e);
         assert.isTrue(showContextStub.called);
-        done();
+        promise.resolve();
       });
       el.click();
+      await promise;
     });
 
-    test('_handleTap content', done => {
+    test('_handleTap content', async () => {
       const content = document.createElement('div');
       const lineEl = document.createElement('div');
       lineEl.className = 'lineNum';
@@ -534,13 +522,15 @@
       const selectStub = sinon.stub(element, '_selectLine');
 
       content.className = 'content';
+      const promise = mockPromise();
       content.addEventListener('click', e => {
         element._handleTap(e);
         assert.isTrue(selectStub.called);
         assert.equal(selectStub.lastCall.args[0], lineEl);
-        done();
+        promise.resolve();
       });
       content.click();
+      await promise;
     });
 
     suite('getCursorStops', () => {
@@ -816,41 +806,47 @@
       element.noRenderOnPrefsChange = true;
     });
 
-    test('large render w/ context = 10', done => {
+    test('large render w/ context = 10', async () => {
       element.prefs = {...MINIMAL_PREFS, context: 10};
+      const promise = mockPromise();
       function rendered() {
         assert.isTrue(renderStub.called);
         assert.isFalse(element._showWarning);
-        done();
+        promise.resolve();
         element.removeEventListener('render', rendered);
       }
       element.addEventListener('render', rendered);
       element._renderDiffTable();
+      await promise;
     });
 
-    test('large render w/ whole file and bypass', done => {
+    test('large render w/ whole file and bypass', async () => {
       element.prefs = {...MINIMAL_PREFS, context: -1};
       element._safetyBypass = 10;
+      const promise = mockPromise();
       function rendered() {
         assert.isTrue(renderStub.called);
         assert.isFalse(element._showWarning);
-        done();
+        promise.resolve();
         element.removeEventListener('render', rendered);
       }
       element.addEventListener('render', rendered);
       element._renderDiffTable();
+      await promise;
     });
 
-    test('large render w/ whole file and no bypass', done => {
+    test('large render w/ whole file and no bypass', async () => {
       element.prefs = {...MINIMAL_PREFS, context: -1};
+      const promise = mockPromise();
       function rendered() {
         assert.isFalse(renderStub.called);
         assert.isTrue(element._showWarning);
-        done();
+        promise.resolve();
         element.removeEventListener('render', rendered);
       }
       element.addEventListener('render', rendered);
       element._renderDiffTable();
+      await promise;
     });
 
     test('toggles expand context using bypass', async () => {
@@ -1219,16 +1215,18 @@
     assert.equal(element.getDiffLength(diff), 52);
   });
 
-  test('`render` event has contentRendered field in detail', done => {
+  test('`render` event has contentRendered field in detail', async () => {
     element = basicFixture.instantiate();
     element.prefs = {};
     sinon.stub(element.$.diffBuilder, 'render')
         .returns(Promise.resolve());
+    const promise = mockPromise();
     element.addEventListener('render', event => {
       assert.isTrue(event.detail.contentRendered);
-      done();
+      promise.resolve();
     });
     element._renderDiffTable();
+    await promise;
   });
 
   test('_prefsEqual', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.js b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.js
index 89b8b4a..0fe1fe2 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.js
@@ -196,7 +196,7 @@
   });
 
   test('_computeBaseDropdownContent called when changeComments update',
-      done => {
+      async () => {
         element.revisions = [
           {commit: {parents: []}},
           {commit: {parents: []}},
@@ -212,15 +212,14 @@
         ];
         element.patchNum = 2;
         element.basePatchNum = 'PARENT';
-        flush();
+        await flush();
 
         // Should be recomputed for each available patch
         sinon.stub(element, '_computeBaseDropdownContent');
         assert.equal(element._computeBaseDropdownContent.callCount, 0);
         element.changeComments = new ChangeComments();
-        flush();
+        await flush();
         assert.equal(element._computeBaseDropdownContent.callCount, 1);
-        done();
       });
 
   test('_computePatchDropdownContent called when basePatchNum updates', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js
index b8c3c16..c907a80 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js
@@ -120,7 +120,7 @@
     assert.isFalse(annotationSpy.called);
   });
 
-  test('process on empty diff does nothing', done => {
+  test('process on empty diff does nothing', async () => {
     element.diff = {
       meta_a: {content_type: 'application/json'},
       meta_b: {content_type: 'application/json'},
@@ -128,17 +128,14 @@
     };
     const processNextSpy = sinon.spy(element, '_processNextLine');
 
-    const processPromise = element.process();
+    await element.process();
 
-    processPromise.then(() => {
-      assert.isFalse(processNextSpy.called);
-      assert.equal(element.baseRanges.length, 0);
-      assert.equal(element.revisionRanges.length, 0);
-      done();
-    });
+    assert.isFalse(processNextSpy.called);
+    assert.equal(element.baseRanges.length, 0);
+    assert.equal(element.revisionRanges.length, 0);
   });
 
-  test('process for unsupported languages does nothing', done => {
+  test('process for unsupported languages does nothing', async () => {
     element.diff = {
       meta_a: {content_type: 'text/x+objective-cobol-plus-plus'},
       meta_b: {content_type: 'application/not-a-real-language'},
@@ -146,33 +143,27 @@
     };
     const processNextSpy = sinon.spy(element, '_processNextLine');
 
-    const processPromise = element.process();
+    await element.process();
 
-    processPromise.then(() => {
-      assert.isFalse(processNextSpy.called);
-      assert.equal(element.baseRanges.length, 0);
-      assert.equal(element.revisionRanges.length, 0);
-      done();
-    });
+    assert.isFalse(processNextSpy.called);
+    assert.equal(element.baseRanges.length, 0);
+    assert.equal(element.revisionRanges.length, 0);
   });
 
-  test('process while disabled does nothing', done => {
+  test('process while disabled does nothing', async () => {
     const processNextSpy = sinon.spy(element, '_processNextLine');
     element.enabled = false;
     const loadHLJSSpy = sinon.spy(element, '_loadHLJS');
 
-    const processPromise = element.process();
+    await element.process();
 
-    processPromise.then(() => {
-      assert.isFalse(processNextSpy.called);
-      assert.equal(element.baseRanges.length, 0);
-      assert.equal(element.revisionRanges.length, 0);
-      assert.isFalse(loadHLJSSpy.called);
-      done();
-    });
+    assert.isFalse(processNextSpy.called);
+    assert.equal(element.baseRanges.length, 0);
+    assert.equal(element.revisionRanges.length, 0);
+    assert.isFalse(loadHLJSSpy.called);
   });
 
-  test('process highlight ipsum', done => {
+  test('process highlight ipsum', async () => {
     element.diff.meta_a.content_type = 'application/json';
     element.diff.meta_b.content_type = 'application/json';
 
@@ -180,65 +171,61 @@
     window.hljs = mockHLJS;
     const highlightSpy = sinon.spy(mockHLJS, 'highlight');
     const processNextSpy = sinon.spy(element, '_processNextLine');
-    const processPromise = element.process();
+    await element.process();
 
-    processPromise.then(() => {
-      const linesA = diff.meta_a.lines;
-      const linesB = diff.meta_b.lines;
+    const linesA = diff.meta_a.lines;
+    const linesB = diff.meta_b.lines;
 
-      assert.isTrue(processNextSpy.called);
-      assert.equal(element.baseRanges.length, linesA);
-      assert.equal(element.revisionRanges.length, linesB);
+    assert.isTrue(processNextSpy.called);
+    assert.equal(element.baseRanges.length, linesA);
+    assert.equal(element.revisionRanges.length, linesB);
 
-      assert.equal(highlightSpy.callCount, linesA + linesB);
+    assert.equal(highlightSpy.callCount, linesA + linesB);
 
-      // The first line of both sides have a range.
-      let ranges = [element.baseRanges[0], element.revisionRanges[0]];
-      for (const range of ranges) {
-        assert.equal(range.length, 1);
-        assert.equal(range[0].className,
-            'gr-diff gr-syntax gr-syntax-string');
-        assert.equal(range[0].start, 'lorem '.length);
-        assert.equal(range[0].length, 'ipsum'.length);
-      }
-
-      // There are no ranges from ll.1-12 on the left and ll.1-11 on the
-      // right.
-      ranges = element.baseRanges.slice(1, 12)
-          .concat(element.revisionRanges.slice(1, 11));
-
-      for (const range of ranges) {
-        assert.equal(range.length, 0);
-      }
-
-      // There should be another pair of ranges on l.13 for the left and
-      // l.12 for the right.
-      ranges = [element.baseRanges[13], element.revisionRanges[12]];
-
-      for (const range of ranges) {
-        assert.equal(range.length, 1);
-        assert.equal(range[0].className,
-            'gr-diff gr-syntax gr-syntax-string');
-        assert.equal(range[0].start, 32);
-        assert.equal(range[0].length, 'ipsum'.length);
-      }
-
-      // The next group should have a similar instance on either side.
-
-      let range = element.baseRanges[15];
+    // The first line of both sides have a range.
+    let ranges = [element.baseRanges[0], element.revisionRanges[0]];
+    for (const range of ranges) {
       assert.equal(range.length, 1);
-      assert.equal(range[0].className, 'gr-diff gr-syntax gr-syntax-string');
-      assert.equal(range[0].start, 34);
+      assert.equal(range[0].className,
+          'gr-diff gr-syntax gr-syntax-string');
+      assert.equal(range[0].start, 'lorem '.length);
       assert.equal(range[0].length, 'ipsum'.length);
+    }
 
-      range = element.revisionRanges[14];
+    // There are no ranges from ll.1-12 on the left and ll.1-11 on the
+    // right.
+    ranges = element.baseRanges.slice(1, 12)
+        .concat(element.revisionRanges.slice(1, 11));
+
+    for (const range of ranges) {
+      assert.equal(range.length, 0);
+    }
+
+    // There should be another pair of ranges on l.13 for the left and
+    // l.12 for the right.
+    ranges = [element.baseRanges[13], element.revisionRanges[12]];
+
+    for (const range of ranges) {
       assert.equal(range.length, 1);
-      assert.equal(range[0].className, 'gr-diff gr-syntax gr-syntax-string');
-      assert.equal(range[0].start, 35);
+      assert.equal(range[0].className,
+          'gr-diff gr-syntax gr-syntax-string');
+      assert.equal(range[0].start, 32);
       assert.equal(range[0].length, 'ipsum'.length);
+    }
 
-      done();
-    });
+    // The next group should have a similar instance on either side.
+
+    let range = element.baseRanges[15];
+    assert.equal(range.length, 1);
+    assert.equal(range[0].className, 'gr-diff gr-syntax gr-syntax-string');
+    assert.equal(range[0].start, 34);
+    assert.equal(range[0].length, 'ipsum'.length);
+
+    range = element.revisionRanges[14];
+    assert.equal(range.length, 1);
+    assert.equal(range[0].className, 'gr-diff gr-syntax gr-syntax-string');
+    assert.equal(range[0].start, 35);
+    assert.equal(range[0].length, 'ipsum'.length);
   });
 
   test('init calls cancel', () => {
diff --git a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.ts b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.ts
index 9c196c7..5267b2d 100644
--- a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.ts
+++ b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.ts
@@ -47,28 +47,24 @@
   });
 
   suite('list with searches for documentation', () => {
-    setup(done => {
+    setup(async () => {
       documentationSearches = _.times(26, documentationGenerator);
       stubRestApi('getDocumentationSearches').returns(
         Promise.resolve(documentationSearches)
       );
-      element._paramsChanged(value).then(() => {
-        flush(done);
-      });
+      await element._paramsChanged(value);
+      await flush();
     });
 
-    test('test for test repo in the list', done => {
-      flush(() => {
-        assert.equal(
-          element._documentationSearches![0].title,
-          'Gerrit Code Review - REST API Developers Notes1'
-        );
-        assert.equal(
-          element._documentationSearches![0].url,
-          'Documentation/dev-rest-api.html'
-        );
-        done();
-      });
+    test('test for test repo in the list', async () => {
+      assert.equal(
+        element._documentationSearches![0].title,
+        'Gerrit Code Review - REST API Developers Notes1'
+      );
+      assert.equal(
+        element._documentationSearches![0].url,
+        'Documentation/dev-rest-api.html'
+      );
     });
   });
 
@@ -88,14 +84,14 @@
   });
 
   suite('loading', () => {
-    test('correct contents are displayed', () => {
+    test('correct contents are displayed', async () => {
       assert.isTrue(element._loading);
       assert.equal(element.computeLoadingClass(element._loading), 'loading');
       assert.equal(getComputedStyle(element.$.loading).display, 'block');
 
       element._loading = false;
 
-      flush();
+      await flush();
       assert.equal(element.computeLoadingClass(element._loading), '');
       assert.equal(getComputedStyle(element.$.loading).display, 'none');
     });
diff --git a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.ts b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.ts
index 8a483fc..6b7ce34 100644
--- a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.ts
+++ b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.ts
@@ -18,7 +18,7 @@
 import '../../../test/common-test-setup-karma';
 import './gr-default-editor';
 import {GrDefaultEditor} from './gr-default-editor';
-import {queryAndAssert} from '../../../test/test-utils';
+import {mockPromise, queryAndAssert} from '../../../test/test-utils';
 
 const basicFixture = fixtureFromElement('gr-default-editor');
 
@@ -31,13 +31,15 @@
     await flush();
   });
 
-  test('fires content-change event', done => {
+  test('fires content-change event', async () => {
     const textarea = queryAndAssert<HTMLTextAreaElement>(element, '#textarea');
+    const promise = mockPromise();
     element.addEventListener('content-change', e => {
       assert.equal((e as CustomEvent).detail.value, 'test');
-      done();
+      promise.resolve();
     });
     textarea.value = 'test';
     textarea.dispatchEvent(new Event('input', {bubbles: true, composed: true}));
+    await promise;
   });
 });
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts
index 18e6e75..2e1fc21 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts
@@ -402,15 +402,13 @@
     });
   });
 
-  test('openOpenDialog', done => {
-    element.openOpenDialog('test/path.cpp').then(() => {
-      assert.isFalse(element.$.openDialog.hasAttribute('hidden'));
-      assert.equal(
-        element.$.openDialog!.querySelector('gr-autocomplete')!.text,
-        'test/path.cpp'
-      );
-      done();
-    });
+  test('openOpenDialog', async () => {
+    await element.openOpenDialog('test/path.cpp');
+    assert.isFalse(element.$.openDialog.hasAttribute('hidden'));
+    assert.equal(
+      element.$.openDialog!.querySelector('gr-autocomplete')!.text,
+      'test/path.cpp'
+    );
   });
 
   test('_getDialogFromEvent', () => {
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts
index 5b04a81..f591ab2 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts
@@ -19,7 +19,7 @@
 import {GrEditorView} from './gr-editor-view';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation';
 import {HttpMethod} from '../../../constants/constants';
-import {stubRestApi, stubStorage} from '../../../test/test-utils';
+import {mockPromise, stubRestApi, stubStorage} from '../../../test/test-utils';
 import {
   EditPatchSetNum,
   NumericChangeId,
@@ -348,14 +348,16 @@
     });
   });
 
-  test('_showAlert', done => {
+  test('_showAlert', async () => {
+    const promise = mockPromise();
     element.addEventListener('show-alert', e => {
       assert.deepEqual(e.detail, {message: 'test message'});
       assert.isTrue(e.bubbles);
-      done();
+      promise.resolve();
     });
 
     element._showAlert('test message');
+    await promise;
   });
 
   test('_viewEditInChangeView', () => {
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.js b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.js
index 8138ff0..1be5e82 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.js
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.js
@@ -51,7 +51,7 @@
   let decorationHookWithSlot;
   let replacementHook;
 
-  setup(done => {
+  setup(async () => {
     resetPlugins();
     container = basicFixture.instantiate();
     pluginApi.install(p => plugin = p, '0.1',
@@ -68,7 +68,7 @@
         'second', 'other-module', {replace: true});
     // Mimic all plugins loaded.
     getPluginLoader().loadPlugins([]);
-    flush(done);
+    await flush();
   });
 
   teardown(() => {
@@ -135,58 +135,52 @@
         });
   });
 
-  test('late registration', done => {
+  test('late registration', async () => {
     plugin.registerCustomComponent('banana', 'noob-noob');
-    flush(() => {
-      const element =
-          container.querySelector('gr-endpoint-decorator[name="banana"]');
-      const module = Array.from(element.root.children).find(
-          element => element.nodeName === 'NOOB-NOOB');
-      assert.isOk(module);
-      done();
-    });
+    await flush();
+    const element =
+        container.querySelector('gr-endpoint-decorator[name="banana"]');
+    const module = Array.from(element.root.children).find(
+        element => element.nodeName === 'NOOB-NOOB');
+    assert.isOk(module);
   });
 
-  test('two modules', done => {
+  test('two modules', async () => {
     plugin.registerCustomComponent('banana', 'mod-one');
     plugin.registerCustomComponent('banana', 'mod-two');
-    flush(() => {
-      const element =
-          container.querySelector('gr-endpoint-decorator[name="banana"]');
-      const module1 = Array.from(element.root.children).find(
-          element => element.nodeName === 'MOD-ONE');
-      assert.isOk(module1);
-      const module2 = Array.from(element.root.children).find(
-          element => element.nodeName === 'MOD-TWO');
-      assert.isOk(module2);
-      done();
-    });
+    await flush();
+    const element =
+        container.querySelector('gr-endpoint-decorator[name="banana"]');
+    const module1 = Array.from(element.root.children).find(
+        element => element.nodeName === 'MOD-ONE');
+    assert.isOk(module1);
+    const module2 = Array.from(element.root.children).find(
+        element => element.nodeName === 'MOD-TWO');
+    assert.isOk(module2);
   });
 
-  test('late param setup', done => {
+  test('late param setup', async () => {
     const element =
         container.querySelector('gr-endpoint-decorator[name="banana"]');
     const param = element.querySelector('gr-endpoint-param');
     param['value'] = undefined;
     plugin.registerCustomComponent('banana', 'noob-noob');
-    flush(() => {
-      let module = Array.from(element.root.children).find(
-          element => element.nodeName === 'NOOB-NOOB');
-      // Module waits for param to be defined.
-      assert.isNotOk(module);
-      const value = {abc: 'def'};
-      param.value = value;
-      flush(() => {
-        module = Array.from(element.root.children).find(
-            element => element.nodeName === 'NOOB-NOOB');
-        assert.isOk(module);
-        assert.strictEqual(module['someParam'], value);
-        done();
-      });
-    });
+    await flush();
+    let module = Array.from(element.root.children).find(
+        element => element.nodeName === 'NOOB-NOOB');
+    // Module waits for param to be defined.
+    assert.isNotOk(module);
+    const value = {abc: 'def'};
+    param.value = value;
+
+    await flush();
+    module = Array.from(element.root.children).find(
+        element => element.nodeName === 'NOOB-NOOB');
+    assert.isOk(module);
+    assert.strictEqual(module['someParam'], value);
   });
 
-  test('param is bound', done => {
+  test('param is bound', async () => {
     const element =
         container.querySelector('gr-endpoint-decorator[name="banana"]');
     const param = element.querySelector('gr-endpoint-param');
@@ -194,13 +188,11 @@
     const value2 = {def: 'abc'};
     param.value = value1;
     plugin.registerCustomComponent('banana', 'noob-noob');
-    flush(() => {
-      const module = Array.from(element.root.children).find(
-          element => element.nodeName === 'NOOB-NOOB');
-      assert.strictEqual(module['someParam'], value1);
-      param.value = value2;
-      assert.strictEqual(module['someParam'], value2);
-      done();
-    });
+    await flush();
+    const module = Array.from(element.root.children).find(
+        element => element.nodeName === 'NOOB-NOOB');
+    assert.strictEqual(module['someParam'], value1);
+    param.value = value2;
+    assert.strictEqual(module['someParam'], value2);
   });
 });
diff --git a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.js b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.js
index 6291e64..4e3d657 100644
--- a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.js
+++ b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.js
@@ -19,6 +19,7 @@
 import {addListener} from '@polymer/polymer/lib/utils/gestures.js';
 import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
 import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
+import {mockPromise} from '../../../test/test-utils.js';
 
 Polymer({
   is: 'gr-event-helper-some-element',
@@ -47,11 +48,13 @@
     instance = plugin.eventHelper(element);
   });
 
-  test('onTap()', done => {
+  test('onTap()', async () => {
+    const promise = mockPromise();
     instance.onTap(() => {
-      done();
+      promise.resolve();
     });
     MockInteractions.tap(element);
+    await promise;
   });
 
   test('onTap() cancel', () => {
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.ts b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.ts
index a186f70..342cf83 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.ts
@@ -36,11 +36,9 @@
     assert.isOk(element);
   });
 
-  test('open uses open() from gr-overlay', done => {
-    element.open().then(() => {
-      assert.isTrue(overlayOpen.called);
-      done();
-    });
+  test('open uses open() from gr-overlay', async () => {
+    await element.open();
+    assert.isTrue(overlayOpen.called);
   });
 
   test('close uses close() from gr-overlay', () => {
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.js b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.js
index 8a7788f..2889333 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.js
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.js
@@ -56,27 +56,23 @@
       instance = new GrPopupInterface(plugin);
     });
 
-    test('open', done => {
-      instance.open().then(api => {
-        assert.strictEqual(api, instance);
-        const manual = document.createElement('div');
-        manual.id = 'foobar';
-        manual.innerHTML = 'manual content';
-        api._getElement().appendChild(manual);
-        flush();
-        assert.equal(
-            container.querySelector('#foobar').textContent, 'manual content');
-        done();
-      });
+    test('open', async () => {
+      const api = await instance.open();
+      assert.strictEqual(api, instance);
+      const manual = document.createElement('div');
+      manual.id = 'foobar';
+      manual.innerHTML = 'manual content';
+      api._getElement().appendChild(manual);
+      await flush();
+      assert.equal(
+          container.querySelector('#foobar').textContent, 'manual content');
     });
 
-    test('close', done => {
-      instance.open().then(api => {
-        assert.isTrue(api._getElement().node.opened);
-        api.close();
-        assert.isFalse(api._getElement().node.opened);
-        done();
-      });
+    test('close', async () => {
+      const api = await instance.open();
+      assert.isTrue(api._getElement().node.opened);
+      api.close();
+      assert.isFalse(api._getElement().node.opened);
     });
   });
 
@@ -85,21 +81,16 @@
       instance = new GrPopupInterface(plugin, 'gr-user-test-popup');
     });
 
-    test('open', done => {
-      instance.open().then(api => {
-        assert.isNotNull(
-            container.querySelector('gr-user-test-popup'));
-        done();
-      });
+    test('open', async () => {
+      await instance.open();
+      assert.isNotNull(container.querySelector('gr-user-test-popup'));
     });
 
-    test('close', done => {
-      instance.open().then(api => {
-        assert.isTrue(api._getElement().node.opened);
-        api.close();
-        assert.isFalse(api._getElement().node.opened);
-        done();
-      });
+    test('close', async () => {
+      const api = await instance.open();
+      assert.isTrue(api._getElement().node.opened);
+      api.close();
+      assert.isFalse(api._getElement().node.opened);
     });
   });
 });
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
index 4a4862b..ab27b1e 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
@@ -157,7 +157,7 @@
       statusStub = stubRestApi('setAccountStatus').returns(Promise.resolve());
     });
 
-    test('name', done => {
+    test('name', async () => {
       assert.isTrue(element.nameMutable);
       assert.isFalse(element.hasUnsavedChanges);
 
@@ -167,18 +167,15 @@
       assert.isFalse(statusChangedSpy.called);
       assert.isTrue(element.hasUnsavedChanges);
 
-      element.save().then(() => {
-        assert.isFalse(usernameStub.called);
-        assert.isTrue(nameStub.called);
-        assert.isFalse(statusStub.called);
-        nameStub.lastCall.returnValue.then(() => {
-          assert.equal(nameStub.lastCall.args[0], 'new name');
-          done();
-        });
-      });
+      await element.save();
+      assert.isFalse(usernameStub.called);
+      assert.isTrue(nameStub.called);
+      assert.isFalse(statusStub.called);
+      await nameStub.lastCall.returnValue;
+      assert.equal(nameStub.lastCall.args[0], 'new name');
     });
 
-    test('username', done => {
+    test('username', async () => {
       element.set('_account.username', '');
       element._hasUsernameChange = false;
       assert.isTrue(element.usernameMutable);
@@ -189,18 +186,15 @@
       assert.isFalse(statusChangedSpy.called);
       assert.isTrue(element.hasUnsavedChanges);
 
-      element.save().then(() => {
-        assert.isTrue(usernameStub.called);
-        assert.isFalse(nameStub.called);
-        assert.isFalse(statusStub.called);
-        usernameStub.lastCall.returnValue.then(() => {
-          assert.equal(usernameStub.lastCall.args[0], 'new username');
-          done();
-        });
-      });
+      await element.save();
+      assert.isTrue(usernameStub.called);
+      assert.isFalse(nameStub.called);
+      assert.isFalse(statusStub.called);
+      await usernameStub.lastCall.returnValue;
+      assert.equal(usernameStub.lastCall.args[0], 'new username');
     });
 
-    test('status', done => {
+    test('status', async () => {
       assert.isFalse(element.hasUnsavedChanges);
 
       element.set('_account.status', 'new status');
@@ -209,15 +203,12 @@
       assert.isTrue(statusChangedSpy.called);
       assert.isTrue(element.hasUnsavedChanges);
 
-      element.save().then(() => {
-        assert.isFalse(usernameStub.called);
-        assert.isTrue(statusStub.called);
-        assert.isFalse(nameStub.called);
-        statusStub.lastCall.returnValue.then(() => {
-          assert.equal(statusStub.lastCall.args[0], 'new status');
-          done();
-        });
-      });
+      await element.save();
+      assert.isFalse(usernameStub.called);
+      assert.isTrue(statusStub.called);
+      assert.isFalse(nameStub.called);
+      await statusStub.lastCall.returnValue;
+      assert.equal(statusStub.lastCall.args[0], 'new status');
     });
   });
 
@@ -239,7 +230,7 @@
       stubRestApi('setAccountUsername').returns(Promise.resolve());
     });
 
-    test('set name and status', done => {
+    test('set name and status', async () => {
       assert.isTrue(element.nameMutable);
       assert.isFalse(element.hasUnsavedChanges);
 
@@ -253,16 +244,13 @@
 
       assert.isTrue(element.hasUnsavedChanges);
 
-      element.save().then(() => {
-        assert.isTrue(statusStub.called);
-        assert.isTrue(nameStub.called);
+      await element.save();
+      assert.isTrue(statusStub.called);
+      assert.isTrue(nameStub.called);
 
-        assert.equal(nameStub.lastCall.args[0], 'new name');
+      assert.equal(nameStub.lastCall.args[0], 'new name');
 
-        assert.equal(statusStub.lastCall.args[0], 'new status');
-
-        done();
-      });
+      assert.equal(statusStub.lastCall.args[0], 'new status');
     });
   });
 
@@ -277,7 +265,7 @@
       statusStub = stubRestApi('setAccountStatus').returns(Promise.resolve());
     });
 
-    test('read full name but set status', done => {
+    test('read full name but set status', async () => {
       const section = element.$.nameSection;
       const displaySpan = section.querySelectorAll('.value')[0];
       const inputSpan = section.querySelectorAll('.value')[1];
@@ -296,13 +284,10 @@
 
       assert.isTrue(element.hasUnsavedChanges);
 
-      element.save().then(() => {
-        assert.isTrue(statusStub.called);
-        statusStub.lastCall.returnValue.then(() => {
-          assert.equal(statusStub.lastCall.args[0], 'new status');
-          done();
-        });
-      });
+      await element.save();
+      assert.isTrue(statusStub.called);
+      await statusStub.lastCall.returnValue;
+      assert.equal(statusStub.lastCall.args[0], 'new status');
     });
   });
 
diff --git a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.js b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.js
index 8820748..ba736a2 100644
--- a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.js
+++ b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.js
@@ -17,7 +17,7 @@
 
 import '../../../test/common-test-setup-karma.js';
 import './gr-gpg-editor.js';
-import {stubRestApi} from '../../../test/test-utils.js';
+import {mockPromise, stubRestApi} from '../../../test/test-utils.js';
 
 const basicFixture = fixtureFromElement('gr-gpg-editor');
 
@@ -25,7 +25,7 @@
   let element;
   let keys;
 
-  setup(done => {
+  setup(async () => {
     const fingerprint1 = '0192 723D 42D1 0C5B 32A6  E1E0 9350 9E4B AFC8 A49B';
     const fingerprint2 = '0196 723D 42D1 0C5B 32A6  E1E0 9350 9E4B AFC8 A49B';
     keys = {
@@ -55,7 +55,8 @@
 
     element = basicFixture.instantiate();
 
-    element.loadData().then(() => { flush(done); });
+    await element.loadData();
+    await flush();
   });
 
   test('renders', () => {
@@ -70,7 +71,7 @@
     assert.equal(cells[0].textContent, 'AED9B59C');
   });
 
-  test('remove key', done => {
+  test('remove key', async () => {
     const lastKey = keys[Object.keys(keys)[1]];
 
     const saveStub = stubRestApi('deleteAccountGPGKey')
@@ -91,13 +92,11 @@
     assert.isTrue(element.hasUnsavedChanges);
     assert.isFalse(saveStub.called);
 
-    element.save().then(() => {
-      assert.isTrue(saveStub.called);
-      assert.equal(saveStub.lastCall.args[0], Object.keys(keys)[1]);
-      assert.equal(element._keysToRemove.length, 0);
-      assert.isFalse(element.hasUnsavedChanges);
-      done();
-    });
+    await element.save();
+    assert.isTrue(saveStub.called);
+    assert.equal(saveStub.lastCall.args[0], Object.keys(keys)[1]);
+    assert.equal(element._keysToRemove.length, 0);
+    assert.isFalse(element.hasUnsavedChanges);
   });
 
   test('show key', () => {
@@ -113,7 +112,7 @@
     assert.isTrue(openSpy.called);
   });
 
-  test('add key', done => {
+  test('add key', async () => {
     const newKeyString =
         '-----BEGIN PGP PUBLIC KEY BLOCK-----' +
         '\nVersion: BCPG v1.52\n\t<key 3>';
@@ -138,11 +137,12 @@
     assert.isFalse(element.$.addButton.disabled);
     assert.isFalse(element.$.newKey.disabled);
 
+    const promise = mockPromise();
     element._handleAddKey().then(() => {
       assert.isTrue(element.$.addButton.disabled);
       assert.isFalse(element.$.newKey.disabled);
       assert.equal(element._keys.length, 2);
-      done();
+      promise.resolve();
     });
 
     assert.isTrue(element.$.addButton.disabled);
@@ -150,9 +150,10 @@
 
     assert.isTrue(addStub.called);
     assert.deepEqual(addStub.lastCall.args[0], {add: [newKeyString]});
+    await promise;
   });
 
-  test('add invalid key', done => {
+  test('add invalid key', async () => {
     const newKeyString = 'not even close to valid';
 
     const addStub = stubRestApi(
@@ -164,11 +165,12 @@
     assert.isFalse(element.$.addButton.disabled);
     assert.isFalse(element.$.newKey.disabled);
 
+    const promise = mockPromise();
     element._handleAddKey().then(() => {
       assert.isFalse(element.$.addButton.disabled);
       assert.isFalse(element.$.newKey.disabled);
       assert.equal(element._keys.length, 2);
-      done();
+      promise.resolve();
     });
 
     assert.isTrue(element.$.addButton.disabled);
@@ -176,6 +178,7 @@
 
     assert.isTrue(addStub.called);
     assert.deepEqual(addStub.lastCall.args[0], {add: [newKeyString]});
+    await promise;
   });
 });
 
diff --git a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.ts b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.ts
index 3dce295..a1534af 100644
--- a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.ts
@@ -28,7 +28,7 @@
   let element: GrGroupList;
   let groups: GroupInfo[];
 
-  setup(done => {
+  setup(async () => {
     groups = [
       {
         url: 'some url',
@@ -58,9 +58,8 @@
 
     element = basicFixture.instantiate();
 
-    element.loadData().then(() => {
-      flush(done);
-    });
+    await element.loadData();
+    await flush();
   });
 
   test('renders', async () => {
diff --git a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.ts b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.ts
index 004f18f..eab8d2e 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.ts
@@ -35,7 +35,7 @@
   let account: AccountDetailInfo;
   let config: ServerInfo;
 
-  setup(done => {
+  setup(async () => {
     account = {...createAccountDetailWithId(), username: 'user name'};
     config = createServerInfo();
 
@@ -43,9 +43,8 @@
     stubRestApi('getConfig').returns(Promise.resolve(config));
 
     element = basicFixture.instantiate();
-    element.loadData().then(() => {
-      flush(done);
-    });
+    await element.loadData();
+    await flush();
   });
 
   test('generate password', () => {
@@ -76,12 +75,10 @@
     assert.isNull(element._passwordUrl);
   });
 
-  test('with http_password_url', done => {
+  test('with http_password_url', async () => {
     config.auth.http_password_url = 'http://example.com/';
-    element.loadData().then(() => {
-      assert.isNotNull(element._passwordUrl);
-      assert.equal(element._passwordUrl, config.auth.http_password_url);
-      done();
-    });
+    await element.loadData();
+    assert.isNotNull(element._passwordUrl);
+    assert.equal(element._passwordUrl, config.auth.http_password_url);
   });
 });
diff --git a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.ts b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.ts
index d1a4f8f..9d8dcc5 100644
--- a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.ts
@@ -88,13 +88,11 @@
     assert.isTrue(element.filterIdentities(ids[1]));
   });
 
-  test('delete id', done => {
+  test('delete id', async () => {
     element._idName = 'mailto:gerrit2@example.com';
     const loadDataStub = sinon.stub(element, 'loadData');
-    element._handleDeleteItemConfirm().then(() => {
-      assert.isTrue(loadDataStub.called);
-      done();
-    });
+    await element._handleDeleteItemConfirm();
+    assert.isTrue(loadDataStub.called);
   });
 
   test('_handleDeleteItem opens modal', () => {
diff --git a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.js b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.js
index 19852d9..1ca4852 100644
--- a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.js
+++ b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.js
@@ -46,7 +46,7 @@
     MockInteractions.tap(button);
   }
 
-  setup(done => {
+  setup(async () => {
     element = basicFixture.instantiate();
     menu = [
       {url: '/first/url', name: 'first name', target: '_blank'},
@@ -55,7 +55,7 @@
     ];
     element.set('menuItems', menu);
     flush$0();
-    flush(done);
+    await flush();
   });
 
   test('renders', () => {
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.ts b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.ts
index 6c21e58..07a2e51 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.ts
@@ -93,38 +93,34 @@
     return promise;
   }
 
-  test('fires the close event on close', done => {
-    close().then(done);
+  test('fires the close event on close', async () => {
+    await close();
   });
 
-  test('fires the close event on save', done => {
-    close(() =>
+  test('fires the close event on save', async () => {
+    await close(() =>
       MockInteractions.tap(queryAndAssert(element, '#saveButton'))
-    ).then(done);
+    );
   });
 
-  test('saves account details', done => {
-    flush(() => {
-      element.$.name.value = 'new name';
+  test('saves account details', async () => {
+    await flush();
+    element.$.name.value = 'new name';
 
-      element.set('_account.username', '');
-      element._hasUsernameChange = false;
-      assert.isTrue(element._usernameMutable);
+    element.set('_account.username', '');
+    element._hasUsernameChange = false;
+    assert.isTrue(element._usernameMutable);
 
-      element.set('_username', 'new username');
+    element.set('_username', 'new username');
 
-      // Nothing should be committed yet.
-      assert.equal(account.name, 'name');
-      assert.isNotOk(account.username);
+    // Nothing should be committed yet.
+    assert.equal(account.name, 'name');
+    assert.isNotOk(account.username);
 
-      // Save and verify new values are committed.
-      save()
-        .then(() => {
-          assert.equal(account.name, 'new name');
-          assert.equal(account.username, 'new username');
-        })
-        .then(done);
-    });
+    // Save and verify new values are committed.
+    await save();
+    assert.equal(account.name, 'new name');
+    assert.equal(account.username, 'new username');
   });
 
   test('save btn disabled', () => {
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.ts
index 19aeccb..26705ee 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.ts
@@ -93,7 +93,7 @@
     );
   }
 
-  setup(done => {
+  setup(async () => {
     account = {
       ...createAccountDetailWithId(123),
       name: 'user name',
@@ -126,7 +126,8 @@
     element = basicFixture.instantiate();
 
     // Allow the element to render.
-    element._testOnly_loadingPromise?.then(done);
+    if (element._testOnly_loadingPromise)
+      await element._testOnly_loadingPromise;
   });
 
   test('theme changing', () => {
@@ -164,7 +165,7 @@
     assert.equal(titleChangedStub.getCall(0).args[0].detail.title, 'Settings');
   });
 
-  test('user preferences', done => {
+  test('user preferences', async () => {
     // Rendered with the expected preferences selected.
     assert.equal(
       Number(
@@ -269,14 +270,12 @@
     });
 
     // Save the change.
-    element._handleSavePreferences().then(() => {
-      assert.isFalse(element._prefsChanged);
-      assert.isFalse(element._menuChanged);
-      done();
-    });
+    await element._handleSavePreferences();
+    assert.isFalse(element._prefsChanged);
+    assert.isFalse(element._menuChanged);
   });
 
-  test('publish comments on push', done => {
+  test('publish comments on push', async () => {
     const publishCommentsOnPush = valueOf(
       'Publish comments on push',
       'preferences'
@@ -292,14 +291,12 @@
     });
 
     // Save the change.
-    element._handleSavePreferences().then(() => {
-      assert.isFalse(element._prefsChanged);
-      assert.isFalse(element._menuChanged);
-      done();
-    });
+    await element._handleSavePreferences();
+    assert.isFalse(element._prefsChanged);
+    assert.isFalse(element._menuChanged);
   });
 
-  test('set new changes work-in-progress', done => {
+  test('set new changes work-in-progress', async () => {
     const newChangesWorkInProgress = valueOf(
       'Set new changes to "work in progress" by default',
       'preferences'
@@ -315,14 +312,12 @@
     });
 
     // Save the change.
-    element._handleSavePreferences().then(() => {
-      assert.isFalse(element._prefsChanged);
-      assert.isFalse(element._menuChanged);
-      done();
-    });
+    await element._handleSavePreferences();
+    assert.isFalse(element._prefsChanged);
+    assert.isFalse(element._menuChanged);
   });
 
-  test('menu', done => {
+  test('menu', async () => {
     assert.isFalse(element._menuChanged);
     assert.isFalse(element._prefsChanged);
 
@@ -349,12 +344,10 @@
       return Promise.resolve(new Response());
     });
 
-    element._handleSaveMenu().then(() => {
-      assert.isFalse(element._menuChanged);
-      assert.isFalse(element._prefsChanged);
-      assertMenusEqual(element.prefs.my, element._localMenu);
-      done();
-    });
+    await element._handleSaveMenu();
+    assert.isFalse(element._menuChanged);
+    assert.isFalse(element._prefsChanged);
+    assertMenusEqual(element.prefs.my, element._localMenu);
   });
 
   test('add email validation', () => {
@@ -388,7 +381,7 @@
     assert.isFalse(addEmailStub.called);
   });
 
-  test('add email does save valid', done => {
+  test('add email does save valid', async () => {
     const addEmailStub = stubAddAccountEmail(201);
 
     assert.isFalse(element._addingEmail);
@@ -401,13 +394,11 @@
     assert.isTrue(addEmailStub.called);
 
     assert.isTrue(addEmailStub.called);
-    addEmailStub.lastCall.returnValue.then(() => {
-      assert.isOk(element._lastSentVerificationEmail);
-      done();
-    });
+    await addEmailStub.lastCall.returnValue;
+    assert.isOk(element._lastSentVerificationEmail);
   });
 
-  test('add email does not set last-email if error', done => {
+  test('add email does not set last-email if error', async () => {
     const addEmailStub = stubAddAccountEmail(500);
 
     assert.isNotOk(element._lastSentVerificationEmail);
@@ -416,10 +407,8 @@
     element._handleAddEmailButton();
 
     assert.isTrue(addEmailStub.called);
-    addEmailStub.lastCall.returnValue.then(() => {
-      assert.isNotOk(element._lastSentVerificationEmail);
-      done();
-    });
+    await addEmailStub.lastCall.returnValue;
+    assert.isNotOk(element._lastSentVerificationEmail);
   });
 
   test('emails are loaded without emailToken', () => {
@@ -450,7 +439,7 @@
     assert.isTrue(element.prefs.legacycid_in_change_table);
   });
 
-  test('reset menu item back to default', done => {
+  test('reset menu item back to default', async () => {
     const originalMenu = {
       ...createDefaultPreferences(),
       my: [
@@ -471,10 +460,8 @@
 
     element.set('_localMenu', updatedMenu);
 
-    element._handleResetMenuButton().then(() => {
-      assertMenusEqual(element._localMenu, originalMenu.my);
-      done();
-    });
+    await element._handleResetMenuButton();
+    assertMenusEqual(element._localMenu, originalMenu.my);
   });
 
   test('test that reset button is called', () => {
diff --git a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.js b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.js
index 8f99baa..cd2c1df 100644
--- a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.js
+++ b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.js
@@ -17,7 +17,7 @@
 
 import '../../../test/common-test-setup-karma.js';
 import './gr-ssh-editor.js';
-import {stubRestApi} from '../../../test/test-utils.js';
+import {mockPromise, stubRestApi} from '../../../test/test-utils.js';
 
 const basicFixture = fixtureFromElement('gr-ssh-editor');
 
@@ -25,7 +25,7 @@
   let element;
   let keys;
 
-  setup(done => {
+  setup(async () => {
     keys = [{
       seq: 1,
       ssh_public_key: 'ssh-rsa <key 1> comment-one@machine-one',
@@ -46,7 +46,8 @@
 
     element = basicFixture.instantiate();
 
-    element.loadData().then(() => { flush(done); });
+    await element.loadData();
+    await flush();
   });
 
   test('renders', () => {
@@ -61,7 +62,7 @@
     assert.equal(cells[0].textContent, keys[1].comment);
   });
 
-  test('remove key', done => {
+  test('remove key', async () => {
     const lastKey = keys[1];
 
     const saveStub = stubRestApi('deleteAccountSSHKey')
@@ -82,13 +83,11 @@
     assert.isTrue(element.hasUnsavedChanges);
     assert.isFalse(saveStub.called);
 
-    element.save().then(() => {
-      assert.isTrue(saveStub.called);
-      assert.equal(saveStub.lastCall.args[0], lastKey.seq);
-      assert.equal(element._keysToRemove.length, 0);
-      assert.isFalse(element.hasUnsavedChanges);
-      done();
-    });
+    await element.save();
+    assert.isTrue(saveStub.called);
+    assert.equal(saveStub.lastCall.args[0], lastKey.seq);
+    assert.equal(element._keysToRemove.length, 0);
+    assert.isFalse(element.hasUnsavedChanges);
   });
 
   test('show key', () => {
@@ -104,7 +103,7 @@
     assert.isTrue(openSpy.called);
   });
 
-  test('add key', done => {
+  test('add key', async () => {
     const newKeyString = 'ssh-rsa <key 3> comment-three@machine-three';
     const newKeyObject = {
       seq: 3,
@@ -124,11 +123,12 @@
     assert.isFalse(element.$.addButton.disabled);
     assert.isFalse(element.$.newKey.disabled);
 
+    const promise = mockPromise();
     element._handleAddKey().then(() => {
       assert.isTrue(element.$.addButton.disabled);
       assert.isFalse(element.$.newKey.disabled);
       assert.equal(element._keys.length, 3);
-      done();
+      promise.resolve();
     });
 
     assert.isTrue(element.$.addButton.disabled);
@@ -136,9 +136,10 @@
 
     assert.isTrue(addStub.called);
     assert.equal(addStub.lastCall.args[0], newKeyString);
+    await promise;
   });
 
-  test('add invalid key', done => {
+  test('add invalid key', async () => {
     const newKeyString = 'not even close to valid';
 
     const addStub = stubRestApi(
@@ -150,11 +151,12 @@
     assert.isFalse(element.$.addButton.disabled);
     assert.isFalse(element.$.newKey.disabled);
 
+    const promise = mockPromise();
     element._handleAddKey().then(() => {
       assert.isFalse(element.$.addButton.disabled);
       assert.isFalse(element.$.newKey.disabled);
       assert.equal(element._keys.length, 2);
-      done();
+      promise.resolve();
     });
 
     assert.isTrue(element.$.addButton.disabled);
@@ -162,6 +164,7 @@
 
     assert.isTrue(addStub.called);
     assert.equal(addStub.lastCall.args[0], newKeyString);
+    await promise;
   });
 });
 
diff --git a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.ts b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.ts
index cb4b86d..c0580f6 100644
--- a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.ts
@@ -28,7 +28,7 @@
 suite('gr-watched-projects-editor tests', () => {
   let element: GrWatchedProjectsEditor;
 
-  setup(done => {
+  setup(async () => {
     const projects = [
       {
         project: 'project a',
@@ -69,9 +69,8 @@
 
     element = basicFixture.instantiate();
 
-    element.loadData().then(() => {
-      flush(done);
-    });
+    await element.loadData();
+    await flush();
   });
 
   test('renders', () => {
@@ -102,27 +101,21 @@
     assert.equal(checkedKeys[2], 'notify_all_comments');
   });
 
-  test('_getProjectSuggestions empty', done => {
-    element._getProjectSuggestions('nonexistent').then(projects => {
-      assert.equal(projects.length, 0);
-      done();
-    });
+  test('_getProjectSuggestions empty', async () => {
+    const projects = await element._getProjectSuggestions('nonexistent');
+    assert.equal(projects.length, 0);
   });
 
-  test('_getProjectSuggestions non-empty', done => {
-    element._getProjectSuggestions('the project').then(projects => {
-      assert.equal(projects.length, 1);
-      assert.equal(projects[0].name, 'the project');
-      done();
-    });
+  test('_getProjectSuggestions non-empty', async () => {
+    const projects = await element._getProjectSuggestions('the project');
+    assert.equal(projects.length, 1);
+    assert.equal(projects[0].name, 'the project');
   });
 
-  test('_getProjectSuggestions non-empty with two letter project', done => {
-    element._getProjectSuggestions('th').then(projects => {
-      assert.equal(projects.length, 1);
-      assert.equal(projects[0].name, 'the project');
-      done();
-    });
+  test('_getProjectSuggestions non-empty with two letter project', async () => {
+    const projects = await element._getProjectSuggestions('th');
+    assert.equal(projects.length, 1);
+    assert.equal(projects[0].name, 'the project');
   });
 
   test('_canAddProject', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.ts
index a4ff809..ecd9731 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.ts
@@ -45,6 +45,7 @@
 } from '@polymer/iron-test-helpers/mock-interactions';
 import {html} from '@polymer/polymer/lib/utils/html-tag';
 import {
+  mockPromise,
   stubComments,
   stubReporting,
   stubRestApi,
@@ -235,16 +236,14 @@
       assert.equal(element._hideActions(showActions, robotComment), true);
     });
 
-    test('setting project name loads the project config', done => {
+    test('setting project name loads the project config', async () => {
       const projectName = 'foo/bar/baz' as RepoName;
       const getProjectStub = stubRestApi('getProjectConfig').returns(
         Promise.resolve({} as ConfigInfo)
       );
       element.projectName = projectName;
-      flush(() => {
-        assert.isTrue(getProjectStub.calledWithExactly(projectName as never));
-        done();
-      });
+      await flush();
+      assert.isTrue(getProjectStub.calledWithExactly(projectName as never));
     });
 
     test('optionally show file path', () => {
@@ -436,7 +435,7 @@
     assert.isTrue(reportStub.calledOnce);
   });
 
-  test('ack', done => {
+  test('ack', async () => {
     const reportStub = stubReporting('recordDraftInteraction');
     element.changeNum = 42 as NumericChangeId;
     element.patchNum = 1 as PatchSetNum;
@@ -447,20 +446,15 @@
     const ackBtn = element.shadowRoot?.querySelector('#ackBtn');
     assert.isOk(ackBtn);
     tap(ackBtn!);
-    flush(() => {
-      const draft = addDraftServiceStub.firstCall.args[0];
-      assert.equal(draft.message, 'Ack');
-      assert.equal(
-        draft.in_reply_to,
-        'baf0414d_60047215' as UrlEncodedCommentId
-      );
-      assert.equal(draft.unresolved, false);
-      assert.isTrue(reportStub.calledOnce);
-      done();
-    });
+    await flush();
+    const draft = addDraftServiceStub.firstCall.args[0];
+    assert.equal(draft.message, 'Ack');
+    assert.equal(draft.in_reply_to, 'baf0414d_60047215' as UrlEncodedCommentId);
+    assert.equal(draft.unresolved, false);
+    assert.isTrue(reportStub.calledOnce);
   });
 
-  test('done', done => {
+  test('done', async () => {
     const reportStub = stubReporting('recordDraftInteraction');
     assert.isFalse(saveDiffDraftStub.called);
     element.changeNum = 42 as NumericChangeId;
@@ -471,21 +465,16 @@
     const doneBtn = element.shadowRoot?.querySelector('#doneBtn');
     assert.isOk(doneBtn);
     tap(doneBtn!);
-    flush(() => {
-      const draft = addDraftServiceStub.firstCall.args[0];
-      assert.equal(draft.message, 'Done');
-      assert.equal(
-        draft.in_reply_to,
-        'baf0414d_60047215' as UrlEncodedCommentId
-      );
-      assert.isFalse(draft.unresolved);
-      assert.isTrue(reportStub.calledOnce);
-      assert.isTrue(saveDiffDraftStub.called);
-      done();
-    });
+    await flush();
+    const draft = addDraftServiceStub.firstCall.args[0];
+    assert.equal(draft.message, 'Done');
+    assert.equal(draft.in_reply_to, 'baf0414d_60047215' as UrlEncodedCommentId);
+    assert.isFalse(draft.unresolved);
+    assert.isTrue(reportStub.calledOnce);
+    assert.isTrue(saveDiffDraftStub.called);
   });
 
-  test('save', done => {
+  test('save', async () => {
     element.changeNum = 42 as NumericChangeId;
     element.patchNum = 1 as PatchSetNum;
     element.path = '/path/to/file.txt';
@@ -494,17 +483,16 @@
 
     element.shadowRoot?.querySelector('gr-comment')?._fireSave();
 
-    flush(() => {
-      assert.equal(element.rootId, 'baf0414d_60047215' as UrlEncodedCommentId);
-      done();
-    });
+    await flush();
+    assert.equal(element.rootId, 'baf0414d_60047215' as UrlEncodedCommentId);
   });
 
-  test('please fix', done => {
+  test('please fix', async () => {
     element.changeNum = 42 as NumericChangeId;
     element.patchNum = 1 as PatchSetNum;
     const commentEl = element.shadowRoot?.querySelector('gr-comment');
     assert.ok(commentEl);
+    const promise = mockPromise();
     commentEl!.addEventListener('create-fix-comment', () => {
       const draft = addDraftServiceStub.firstCall.args[0];
       assert.equal(
@@ -516,7 +504,7 @@
         'baf0414d_60047215' as UrlEncodedCommentId
       );
       assert.isTrue(draft.unresolved);
-      done();
+      promise.resolve();
     });
     commentEl!.dispatchEvent(
       new CustomEvent('create-fix-comment', {
@@ -525,9 +513,10 @@
         bubbles: false,
       })
     );
+    await promise;
   });
 
-  test('discard', done => {
+  test('discard', async () => {
     element.changeNum = 42 as NumericChangeId;
     element.patchNum = 1 as PatchSetNum;
     element.path = '/path/to/file.txt';
@@ -540,20 +529,21 @@
         'it’s pronouced jiff, not giff'
       )
     );
-    flush();
+    await flush();
 
     const draftEl = element.root?.querySelectorAll('gr-comment')[1];
     assert.ok(draftEl);
     draftEl?._fireSave(); // tell the model about the draft
+    const promise = mockPromise();
     draftEl!.addEventListener('comment-discard', () => {
-      flush();
       assert.isTrue(deleteDraftStub.called);
-      done();
+      promise.resolve();
     });
     draftEl!._fireDiscard();
+    await promise;
   });
 
-  test('discard with a single comment still fires event with previous rootId', done => {
+  test('discard with a single comment still fires event with previous rootId', async () => {
     element.changeNum = 42 as NumericChangeId;
     element.patchNum = 1 as PatchSetNum;
     element.path = '/path/to/file.txt';
@@ -568,11 +558,14 @@
     const draftEl = element.root?.querySelectorAll('gr-comment')[0];
     assert.ok(draftEl);
     const deleteDraftStub = stubComments('deleteDraft');
+    const promise = mockPromise();
     draftEl!.addEventListener('comment-discard', () => {
       assert.isTrue(deleteDraftStub.called);
-      done();
+      promise.resolve();
     });
     draftEl!._fireDiscard();
+    await promise;
+    assert.isTrue(deleteDraftStub.called);
   });
 
   test('comment-update', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
index cb09125..0bf0fb7 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
@@ -28,6 +28,7 @@
   query,
   isVisible,
   stubReporting,
+  mockPromise,
 } from '../../../test/test-utils';
 import {
   AccountId,
@@ -155,7 +156,7 @@
       });
     });
 
-    test('message is not retrieved from storage when other edits', done => {
+    test('message is not retrieved from storage when other edits', async () => {
       const storageStub = stubStorage('getDraftComment');
       const loadSpy = sinon.spy(element, '_loadLocalDraft');
 
@@ -168,14 +169,12 @@
         },
         line: 5,
       };
-      flush(() => {
-        assert.isTrue(loadSpy.called);
-        assert.isFalse(storageStub.called);
-        done();
-      });
+      await flush();
+      assert.isTrue(loadSpy.called);
+      assert.isFalse(storageStub.called);
     });
 
-    test('message is retrieved from storage when no other edits', done => {
+    test('message is retrieved from storage when no other edits', async () => {
       const storageStub = stubStorage('getDraftComment');
       const loadSpy = sinon.spy(element, '_loadLocalDraft');
 
@@ -189,11 +188,9 @@
         line: 5,
         path: 'test',
       };
-      flush(() => {
-        assert.isTrue(loadSpy.called);
-        assert.isTrue(storageStub.called);
-        done();
-      });
+      await flush();
+      assert.isTrue(loadSpy.called);
+      assert.isTrue(storageStub.called);
     });
 
     test('_getPatchNum', () => {
@@ -315,7 +312,7 @@
       );
     });
 
-    test('delete comment', done => {
+    test('delete comment', async () => {
       const stub = stubRestApi('deleteComment').returns(
         Promise.resolve({
           id: '1' as UrlEncodedCommentId,
@@ -333,24 +330,21 @@
         )
       );
       tap(queryAndAssert(element, '.action.delete'));
-      flush(() => {
-        openSpy.lastCall.returnValue.then(() => {
-          const dialog = element.confirmDeleteOverlay?.querySelector(
-            '#confirmDeleteComment'
-          ) as GrConfirmDeleteCommentDialog;
-          dialog.message = 'removal reason';
-          element._handleConfirmDeleteComment();
-          assert.isTrue(
-            stub.calledWith(
-              42 as NumericChangeId,
-              1 as PatchSetNum,
-              'baf0414d_60047215' as UrlEncodedCommentId,
-              'removal reason'
-            )
-          );
-          done();
-        });
-      });
+      await flush();
+      await openSpy.lastCall.returnValue;
+      const dialog = element.confirmDeleteOverlay?.querySelector(
+        '#confirmDeleteComment'
+      ) as GrConfirmDeleteCommentDialog;
+      dialog.message = 'removal reason';
+      element._handleConfirmDeleteComment();
+      assert.isTrue(
+        stub.calledWith(
+          42 as NumericChangeId,
+          1 as PatchSetNum,
+          'baf0414d_60047215' as UrlEncodedCommentId,
+          'removal reason'
+        )
+      );
     });
 
     suite('draft update reporting', () => {
@@ -437,7 +431,7 @@
       assert.isTrue(reportStub.calledOnce);
     });
 
-    test('failed save draft request', done => {
+    test('failed save draft request', async () => {
       element.draft = true;
       element.changeNum = 1 as NumericChangeId;
       element.patchNum = 1 as PatchSetNum;
@@ -449,46 +443,42 @@
         ...createComment(),
         id: 'abc_123' as UrlEncodedCommentId,
       });
-      flush(() => {
-        let args = updateRequestStub.lastCall.args;
-        assert.deepEqual(args, [0, true]);
-        assert.equal(
-          element._getSavingMessage(...args),
-          __testOnly_UNSAVED_MESSAGE
-        );
-        assert.equal(
-          (queryAndAssert(element, '.draftLabel') as HTMLSpanElement).innerText,
-          'DRAFT(Failed to save)'
-        );
-        assert.isTrue(
-          isVisible(queryAndAssert(element, '.save')),
-          'save is visible'
-        );
-        diffDraftStub.returns(Promise.resolve({...new Response(), ok: true}));
-        element._saveDraft({
-          ...createComment(),
-          id: 'abc_123' as UrlEncodedCommentId,
-        });
-        flush(() => {
-          args = updateRequestStub.lastCall.args;
-          assert.deepEqual(args, [0]);
-          assert.equal(element._getSavingMessage(...args), 'All changes saved');
-          assert.equal(
-            (queryAndAssert(element, '.draftLabel') as HTMLSpanElement)
-              .innerText,
-            'DRAFT'
-          );
-          assert.isFalse(
-            isVisible(queryAndAssert(element, '.save')),
-            'save is not visible'
-          );
-          assert.isFalse(element._unableToSave);
-          done();
-        });
+      await flush();
+      let args = updateRequestStub.lastCall.args;
+      assert.deepEqual(args, [0, true]);
+      assert.equal(
+        element._getSavingMessage(...args),
+        __testOnly_UNSAVED_MESSAGE
+      );
+      assert.equal(
+        (queryAndAssert(element, '.draftLabel') as HTMLSpanElement).innerText,
+        'DRAFT(Failed to save)'
+      );
+      assert.isTrue(
+        isVisible(queryAndAssert(element, '.save')),
+        'save is visible'
+      );
+      diffDraftStub.returns(Promise.resolve({...new Response(), ok: true}));
+      element._saveDraft({
+        ...createComment(),
+        id: 'abc_123' as UrlEncodedCommentId,
       });
+      await flush();
+      args = updateRequestStub.lastCall.args;
+      assert.deepEqual(args, [0]);
+      assert.equal(element._getSavingMessage(...args), 'All changes saved');
+      assert.equal(
+        (queryAndAssert(element, '.draftLabel') as HTMLSpanElement).innerText,
+        'DRAFT'
+      );
+      assert.isFalse(
+        isVisible(queryAndAssert(element, '.save')),
+        'save is not visible'
+      );
+      assert.isFalse(element._unableToSave);
     });
 
-    test('failed save draft request with promise failure', done => {
+    test('failed save draft request with promise failure', async () => {
       element.draft = true;
       element.changeNum = 1 as NumericChangeId;
       element.patchNum = 1 as PatchSetNum;
@@ -500,43 +490,39 @@
         ...createComment(),
         id: 'abc_123' as UrlEncodedCommentId,
       });
-      flush(() => {
-        let args = updateRequestStub.lastCall.args;
-        assert.deepEqual(args, [0, true]);
-        assert.equal(
-          element._getSavingMessage(...args),
-          __testOnly_UNSAVED_MESSAGE
-        );
-        assert.equal(
-          (queryAndAssert(element, '.draftLabel') as HTMLSpanElement).innerText,
-          'DRAFT(Failed to save)'
-        );
-        assert.isTrue(
-          isVisible(queryAndAssert(element, '.save')),
-          'save is visible'
-        );
-        diffDraftStub.returns(Promise.resolve({...new Response(), ok: true}));
-        element._saveDraft({
-          ...createComment(),
-          id: 'abc_123' as UrlEncodedCommentId,
-        });
-        flush(() => {
-          args = updateRequestStub.lastCall.args;
-          assert.deepEqual(args, [0]);
-          assert.equal(element._getSavingMessage(...args), 'All changes saved');
-          assert.equal(
-            (queryAndAssert(element, '.draftLabel') as HTMLSpanElement)
-              .innerText,
-            'DRAFT'
-          );
-          assert.isFalse(
-            isVisible(queryAndAssert(element, '.save')),
-            'save is not visible'
-          );
-          assert.isFalse(element._unableToSave);
-          done();
-        });
+      await flush();
+      let args = updateRequestStub.lastCall.args;
+      assert.deepEqual(args, [0, true]);
+      assert.equal(
+        element._getSavingMessage(...args),
+        __testOnly_UNSAVED_MESSAGE
+      );
+      assert.equal(
+        (queryAndAssert(element, '.draftLabel') as HTMLSpanElement).innerText,
+        'DRAFT(Failed to save)'
+      );
+      assert.isTrue(
+        isVisible(queryAndAssert(element, '.save')),
+        'save is visible'
+      );
+      diffDraftStub.returns(Promise.resolve({...new Response(), ok: true}));
+      element._saveDraft({
+        ...createComment(),
+        id: 'abc_123' as UrlEncodedCommentId,
       });
+      await flush();
+      args = updateRequestStub.lastCall.args;
+      assert.deepEqual(args, [0]);
+      assert.equal(element._getSavingMessage(...args), 'All changes saved');
+      assert.equal(
+        (queryAndAssert(element, '.draftLabel') as HTMLSpanElement).innerText,
+        'DRAFT'
+      );
+      assert.isFalse(
+        isVisible(queryAndAssert(element, '.save')),
+        'save is not visible'
+      );
+      assert.isFalse(element._unableToSave);
     });
   });
 
@@ -837,7 +823,7 @@
       );
     });
 
-    test('robot comment layout', done => {
+    test('robot comment layout', async () => {
       const comment = {
         robot_id: 'happy_robot_id' as RobotId,
         url: '/robot/comment',
@@ -849,36 +835,32 @@
       };
       element.comment = comment;
       element.collapsed = false;
-      flush(() => {
-        let runIdMessage;
-        runIdMessage = queryAndAssert(element, '.runIdMessage') as HTMLElement;
-        assert.isFalse((runIdMessage as HTMLElement).hidden);
+      await flush;
+      let runIdMessage;
+      runIdMessage = queryAndAssert(element, '.runIdMessage') as HTMLElement;
+      assert.isFalse((runIdMessage as HTMLElement).hidden);
 
-        const runDetailsLink = queryAndAssert(
-          element,
-          '.robotRunLink'
-        ) as HTMLAnchorElement;
-        assert.isTrue(
-          runDetailsLink.href.indexOf((element.comment as UIRobot).url!) !== -1
-        );
+      const runDetailsLink = queryAndAssert(
+        element,
+        '.robotRunLink'
+      ) as HTMLAnchorElement;
+      assert.isTrue(
+        runDetailsLink.href.indexOf((element.comment as UIRobot).url!) !== -1
+      );
 
-        const robotServiceName = queryAndAssert(element, '.robotName');
-        assert.equal(robotServiceName.textContent?.trim(), 'happy_robot_id');
+      const robotServiceName = queryAndAssert(element, '.robotName');
+      assert.equal(robotServiceName.textContent?.trim(), 'happy_robot_id');
 
-        const authorName = queryAndAssert(element, '.robotId');
-        assert.isTrue(
-          (authorName as HTMLDivElement).innerText === 'Happy Robot'
-        );
+      const authorName = queryAndAssert(element, '.robotId');
+      assert.isTrue((authorName as HTMLDivElement).innerText === 'Happy Robot');
 
-        element.collapsed = true;
-        flush();
-        runIdMessage = queryAndAssert(element, '.runIdMessage');
-        assert.isTrue((runIdMessage as HTMLDivElement).hidden);
-        done();
-      });
+      element.collapsed = true;
+      await flush();
+      runIdMessage = queryAndAssert(element, '.runIdMessage');
+      assert.isTrue((runIdMessage as HTMLDivElement).hidden);
     });
 
-    test('author name fallback to email', done => {
+    test('author name fallback to email', async () => {
       const comment = {
         url: '/robot/comment',
         author: {
@@ -888,17 +870,15 @@
       };
       element.comment = comment;
       element.collapsed = false;
-      flush(() => {
-        const authorName = queryAndAssert(
-          queryAndAssert(element, 'gr-account-label'),
-          'span.name'
-        ) as HTMLSpanElement;
-        assert.equal(authorName.innerText.trim(), 'test@test.com');
-        done();
-      });
+      await flush();
+      const authorName = queryAndAssert(
+        queryAndAssert(element, 'gr-account-label'),
+        'span.name'
+      ) as HTMLSpanElement;
+      assert.equal(authorName.innerText.trim(), 'test@test.com');
     });
 
-    test('patchset level comment', done => {
+    test('patchset level comment', async () => {
       const comment = {
         ...element.comment,
         path: SpecialFilePath.PATCHSET_LEVEL_COMMENTS,
@@ -914,13 +894,11 @@
       const eraseMessageDraftSpy = spyStorage('eraseDraftComment');
       const mockEvent = {...new Event('click'), preventDefault: sinon.stub()};
       element._handleSave(mockEvent);
-      flush(() => {
-        assert.isTrue(eraseMessageDraftSpy.called);
-        done();
-      });
+      await flush();
+      assert.isTrue(eraseMessageDraftSpy.called);
     });
 
-    test('draft creation/cancellation', done => {
+    test('draft creation/cancellation', async () => {
       assert.isFalse(element.editing);
       element.draft = true;
       flush();
@@ -945,37 +923,41 @@
       element.addEventListener('comment-update', updateStub);
 
       let numDiscardEvents = 0;
+      const promise = mockPromise();
       element.addEventListener('comment-discard', () => {
         numDiscardEvents++;
         assert.isFalse(eraseMessageDraftSpy.called);
         if (numDiscardEvents === 2) {
           assert.isFalse(updateStub.called);
-          done();
+          promise.resolve();
         }
       });
       tap(queryAndAssert(element, '.cancel'));
-      flush();
+      await flush();
       element._messageText = '';
       element.editing = true;
-      flush();
+      await flush();
       pressAndReleaseKeyOn(element.textarea!, 27); // esc
+      await promise;
     });
 
-    test('draft discard removes message from storage', done => {
+    test('draft discard removes message from storage', async () => {
       element._messageText = '';
       const eraseMessageDraftSpy = sinon.spy(
         element,
         '_eraseDraftCommentFromStorage'
       );
 
+      const promise = mockPromise();
       element.addEventListener('comment-discard', () => {
         assert.isTrue(eraseMessageDraftSpy.called);
-        done();
+        promise.resolve();
       });
       element._handleDiscard({
         ...new Event('click'),
         preventDefault: sinon.stub(),
       });
+      await promise;
     });
 
     test('storage is cleared only after save success', () => {
@@ -1022,20 +1004,22 @@
       assert.equal(element._computeSaveDisabled('', comment, false), true);
     });
 
-    test('ctrl+s saves comment', done => {
+    test('ctrl+s saves comment', async () => {
+      const promise = mockPromise();
       const stub = sinon.stub(element, 'save').callsFake(() => {
         assert.isTrue(stub.called);
         stub.restore();
-        done();
+        promise.resolve();
         return Promise.resolve();
       });
       element._messageText = 'is that the horse from horsing around??';
       element.editing = true;
-      flush();
+      await flush();
       pressAndReleaseKeyOn(element.textarea!.$.textarea.textarea, 83, 'ctrl'); // 'ctrl + s'
+      await promise;
     });
 
-    test('draft saving/editing', done => {
+    test('draft saving/editing', async () => {
       const dispatchEventStub = sinon.stub(element, 'dispatchEvent');
 
       const clock: SinonFakeTimers = sinon.useFakeTimers();
@@ -1047,7 +1031,7 @@
       };
 
       element.draft = true;
-      flush();
+      await flush();
       tap(queryAndAssert(element, '.edit'));
       tickAndFlush(1);
       element._messageText = 'good news, everyone!';
@@ -1056,7 +1040,7 @@
       assert.isTrue(dispatchEventStub.calledTwice);
 
       element._messageText = 'good news, everyone!';
-      flush();
+      await flush();
       assert.isTrue(dispatchEventStub.calledTwice);
 
       tap(queryAndAssert(element, '.save'));
@@ -1066,57 +1050,50 @@
         'Element should be disabled when creating draft.'
       );
 
-      element
-        ._xhrPromise!.then(draft => {
-          const evt = dispatchEventStub.lastCall.args[0] as CustomEvent<{
-            comment: DraftInfo;
-          }>;
-          assert.equal(evt.type, 'comment-save');
+      let draft = await element._xhrPromise!;
+      const evt = dispatchEventStub.lastCall.args[0] as CustomEvent<{
+        comment: DraftInfo;
+      }>;
+      assert.equal(evt.type, 'comment-save');
 
-          const expectedDetail = {
-            comment: {
-              ...createComment(),
-              __draft: true,
-              __draftID: 'temp_draft_id',
-              id: 'baf0414d_40572e03' as UrlEncodedCommentId,
-              line: 5,
-              message: 'saved!',
-              path: '/path/to/file',
-              updated: '2015-12-08 21:52:36.177000000' as Timestamp,
-            },
-            patchNum: 1 as PatchSetNum,
-          };
+      const expectedDetail = {
+        comment: {
+          ...createComment(),
+          __draft: true,
+          __draftID: 'temp_draft_id',
+          id: 'baf0414d_40572e03' as UrlEncodedCommentId,
+          line: 5,
+          message: 'saved!',
+          path: '/path/to/file',
+          updated: '2015-12-08 21:52:36.177000000' as Timestamp,
+        },
+        patchNum: 1 as PatchSetNum,
+      };
 
-          assert.deepEqual(evt.detail, expectedDetail);
-          assert.isFalse(
-            element.disabled,
-            'Element should be enabled when done creating draft.'
-          );
-          assert.equal(draft.message, 'saved!');
-          assert.isFalse(element.editing);
-        })
-        .then(() => {
-          tap(queryAndAssert(element, '.edit'));
-          element._messageText =
-            'You’ll be delivering a package to Chapek 9, ' +
-            'a world where humans are killed on sight.';
-          tap(queryAndAssert(element, '.save'));
-          assert.isTrue(
-            element.disabled,
-            'Element should be disabled when updating draft.'
-          );
-
-          element._xhrPromise!.then(draft => {
-            assert.isFalse(
-              element.disabled,
-              'Element should be enabled when done updating draft.'
-            );
-            assert.equal(draft.message, 'saved!');
-            assert.isFalse(element.editing);
-            dispatchEventStub.restore();
-            done();
-          });
-        });
+      assert.deepEqual(evt.detail, expectedDetail);
+      assert.isFalse(
+        element.disabled,
+        'Element should be enabled when done creating draft.'
+      );
+      assert.equal(draft.message, 'saved!');
+      assert.isFalse(element.editing);
+      tap(queryAndAssert(element, '.edit'));
+      element._messageText =
+        'You’ll be delivering a package to Chapek 9, ' +
+        'a world where humans are killed on sight.';
+      tap(queryAndAssert(element, '.save'));
+      assert.isTrue(
+        element.disabled,
+        'Element should be disabled when updating draft.'
+      );
+      draft = await element._xhrPromise!;
+      assert.isFalse(
+        element.disabled,
+        'Element should be enabled when done updating draft.'
+      );
+      assert.equal(draft.message, 'saved!');
+      assert.isFalse(element.editing);
+      dispatchEventStub.restore();
     });
 
     test('draft prevent save when disabled', () => {
@@ -1138,14 +1115,16 @@
       assert.isTrue(saveStub.calledOnce);
     });
 
-    test('proper event fires on resolve, comment is not saved', done => {
+    test('proper event fires on resolve, comment is not saved', async () => {
       const save = sinon.stub(element, 'save');
+      const promise = mockPromise();
       element.addEventListener('comment-update', e => {
         assert.isTrue(e.detail.comment.unresolved);
         assert.isFalse(save.called);
-        done();
+        promise.resolve();
       });
       tap(queryAndAssert(element, '.resolve input'));
+      await promise;
     });
 
     test('resolved comment state indicated by checkbox', () => {
@@ -1261,19 +1240,21 @@
       assert.isTrue(discardStub.called);
     });
 
-    test('_handleFix fires create-fix event', done => {
+    test('_handleFix fires create-fix event', async () => {
+      const promise = mockPromise();
       element.addEventListener(
         'create-fix-comment',
         (e: CreateFixCommentEvent) => {
           assert.deepEqual(e.detail, element._getEventPayload());
-          done();
+          promise.resolve();
         }
       );
       element.isRobotComment = true;
       element.comments = [element.comment!];
-      flush();
+      await flush();
 
       tap(queryAndAssert(element, '.fix'));
+      await promise;
     });
 
     test('do not show Please Fix button if human reply exists', () => {
@@ -1419,19 +1400,21 @@
       queryAndAssert(element, '.robotActions gr-button');
     });
 
-    test('_handleShowFix fires open-fix-preview event', done => {
+    test('_handleShowFix fires open-fix-preview event', async () => {
+      const promise = mockPromise();
       element.addEventListener('open-fix-preview', e => {
         assert.deepEqual(e.detail, element._getEventPayload());
-        done();
+        promise.resolve();
       });
       element.comment = {
         ...createComment(),
         fix_suggestions: [{...createFixSuggestionInfo()}],
       };
       element.isRobotComment = true;
-      flush();
+      await flush();
 
       tap(queryAndAssert(element, '.show-fix'));
+      await promise;
     });
   });
 
@@ -1449,7 +1432,7 @@
       sinon.restore();
     });
 
-    test('show tip when no cached record', done => {
+    test('show tip when no cached record', async () => {
       element = draftFixture.instantiate() as GrComment;
       const respectfulGetStub = stubStorage('getRespectfulTipVisibility');
       const respectfulSetStub = stubStorage('setRespectfulTipVisibility');
@@ -1457,15 +1440,13 @@
       // fake random
       element.getRandomNum = () => 0;
       element.comment = {__editing: true, __draft: true};
-      flush(() => {
-        assert.isTrue(respectfulGetStub.called);
-        assert.isTrue(respectfulSetStub.called);
-        assert.isTrue(!!queryAndAssert(element, '.respectfulReviewTip'));
-        done();
-      });
+      await flush();
+      assert.isTrue(respectfulGetStub.called);
+      assert.isTrue(respectfulSetStub.called);
+      assert.isTrue(!!queryAndAssert(element, '.respectfulReviewTip'));
     });
 
-    test('add 14-day delays once dismissed', done => {
+    test('add 14-day delays once dismissed', async () => {
       element = draftFixture.instantiate() as GrComment;
       const respectfulGetStub = stubStorage('getRespectfulTipVisibility');
       const respectfulSetStub = stubStorage('setRespectfulTipVisibility');
@@ -1473,20 +1454,18 @@
       // fake random
       element.getRandomNum = () => 0;
       element.comment = {__editing: true, __draft: true};
-      flush(() => {
-        assert.isTrue(respectfulGetStub.called);
-        assert.isTrue(respectfulSetStub.called);
-        assert.isTrue(respectfulSetStub.lastCall.args[0] === undefined);
-        assert.isTrue(!!queryAndAssert(element, '.respectfulReviewTip'));
+      await flush();
+      assert.isTrue(respectfulGetStub.called);
+      assert.isTrue(respectfulSetStub.called);
+      assert.isTrue(respectfulSetStub.lastCall.args[0] === undefined);
+      assert.isTrue(!!queryAndAssert(element, '.respectfulReviewTip'));
 
-        tap(queryAndAssert(element, '.respectfulReviewTip .close'));
-        flush();
-        assert.isTrue(respectfulSetStub.lastCall.args[0] === 14);
-        done();
-      });
+      tap(queryAndAssert(element, '.respectfulReviewTip .close'));
+      flush();
+      assert.isTrue(respectfulSetStub.lastCall.args[0] === 14);
     });
 
-    test('do not show tip when fall out of probability', done => {
+    test('do not show tip when fall out of probability', async () => {
       element = draftFixture.instantiate() as GrComment;
       const respectfulGetStub = stubStorage('getRespectfulTipVisibility');
       const respectfulSetStub = stubStorage('setRespectfulTipVisibility');
@@ -1494,15 +1473,13 @@
       // fake random
       element.getRandomNum = () => 3;
       element.comment = {__editing: true, __draft: true};
-      flush(() => {
-        assert.isTrue(respectfulGetStub.called);
-        assert.isFalse(respectfulSetStub.called);
-        assert.isNotOk(query(element, '.respectfulReviewTip'));
-        done();
-      });
+      await flush();
+      assert.isTrue(respectfulGetStub.called);
+      assert.isFalse(respectfulSetStub.called);
+      assert.isNotOk(query(element, '.respectfulReviewTip'));
     });
 
-    test('show tip when editing changed to true', done => {
+    test('show tip when editing changed to true', async () => {
       element = draftFixture.instantiate() as GrComment;
       const respectfulGetStub = stubStorage('getRespectfulTipVisibility');
       const respectfulSetStub = stubStorage('setRespectfulTipVisibility');
@@ -1510,22 +1487,19 @@
       // fake random
       element.getRandomNum = () => 0;
       element.comment = {__editing: false};
-      flush(() => {
-        assert.isFalse(respectfulGetStub.called);
-        assert.isFalse(respectfulSetStub.called);
-        assert.isNotOk(query(element, '.respectfulReviewTip'));
+      await flush();
+      assert.isFalse(respectfulGetStub.called);
+      assert.isFalse(respectfulSetStub.called);
+      assert.isNotOk(query(element, '.respectfulReviewTip'));
 
-        element.editing = true;
-        flush(() => {
-          assert.isTrue(respectfulGetStub.called);
-          assert.isTrue(respectfulSetStub.called);
-          assert.isTrue(!!queryAndAssert(element, '.respectfulReviewTip'));
-          done();
-        });
-      });
+      element.editing = true;
+      await flush();
+      assert.isTrue(respectfulGetStub.called);
+      assert.isTrue(respectfulSetStub.called);
+      assert.isTrue(!!queryAndAssert(element, '.respectfulReviewTip'));
     });
 
-    test('no tip when cached record', done => {
+    test('no tip when cached record', async () => {
       element = draftFixture.instantiate() as GrComment;
       const respectfulGetStub = stubStorage('getRespectfulTipVisibility');
       const respectfulSetStub = stubStorage('setRespectfulTipVisibility');
@@ -1533,12 +1507,10 @@
       // fake random
       element.getRandomNum = () => 0;
       element.comment = {__editing: true, __draft: true};
-      flush(() => {
-        assert.isTrue(respectfulGetStub.called);
-        assert.isFalse(respectfulSetStub.called);
-        assert.isNotOk(query(element, '.respectfulReviewTip'));
-        done();
-      });
+      await flush();
+      assert.isTrue(respectfulGetStub.called);
+      assert.isFalse(respectfulSetStub.called);
+      assert.isNotOk(query(element, '.respectfulReviewTip'));
     });
   });
 });