Move flush from global to test util

Also rename to waitEventLoop() and remove polymer dependency. Cases
where callback or synchronous version were used were rewritten to
promises.

Release-Notes: skip
Change-Id: I3e480d3d91db73df509a6b2da2c9b22fb94fe07b
diff --git a/polygerrit-ui/app/.eslintrc.js b/polygerrit-ui/app/.eslintrc.js
index 62a681a..c519465 100644
--- a/polygerrit-ui/app/.eslintrc.js
+++ b/polygerrit-ui/app/.eslintrc.js
@@ -227,9 +227,6 @@
     'import/no-unused-modules': 2,
     // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-default-export.md
     'import/no-default-export': 2,
-    // Prevents certain identifiers being used.
-    // Prefer flush() over flushAsynchronousOperations().
-    'id-blacklist': ['error', 'flushAsynchronousOperations'],
     'regex/invalid': [
       'error', [{
         // eslint-disable-next-line regex/invalid
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.ts b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.ts
index a8c2d05..d693f80 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.ts
@@ -25,7 +25,7 @@
 } from '../../../types/common';
 import {GrButton} from '../../shared/gr-button/gr-button';
 import {GrAutocomplete} from '../../shared/gr-autocomplete/gr-autocomplete';
-import {EventType, PageErrorEvent} from '../../../types/events.js';
+import {EventType, PageErrorEvent} from '../../../types/events';
 import {getAccountSuggestions} from '../../../utils/account-util';
 import {getAppContext} from '../../../services/app-context';
 import {fixture, html, assert} from '@open-wc/testing';
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.ts b/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.ts
index e0b4d1d..36d0529 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.ts
@@ -13,7 +13,7 @@
   stubRestApi,
   waitUntil,
 } from '../../../test/test-utils';
-import {createGroupInfo} from '../../../test/test-data-generators.js';
+import {createGroupInfo} from '../../../test/test-data-generators';
 import {GroupId, GroupInfo, GroupName} from '../../../types/common';
 import {GrAutocomplete} from '../../shared/gr-autocomplete/gr-autocomplete';
 import {GrButton} from '../../shared/gr-button/gr-button';
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.ts b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.ts
index 16f830a..f3f85b8 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.ts
@@ -6,7 +6,7 @@
 import '../../../test/common-test-setup-karma';
 import './gr-permission';
 import {GrPermission} from './gr-permission';
-import {query, stubRestApi} from '../../../test/test-utils';
+import {query, stubRestApi, waitEventLoop} from '../../../test/test-utils';
 import {GitRef, GroupId, GroupName} from '../../../types/common';
 import {PermissionAction} from '../../../constants/constants';
 import {
@@ -313,7 +313,7 @@
       };
       element.setupValues();
       await element.updateComplete;
-      flush();
+      await waitEventLoop();
     });
 
     test('render', () => {
@@ -432,7 +432,7 @@
           bubbles: true,
         })
       );
-      await flush();
+      await waitEventLoop();
       assert.equal(element.rules!.length, 1);
     });
 
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.ts b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.ts
index 9ee1807..3c03ce1 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.ts
@@ -3,14 +3,14 @@
  * Copyright 2018 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import {ConfigParameterInfoType} from '../../../constants/constants.js';
+import {ConfigParameterInfoType} from '../../../constants/constants';
 import '../../../test/common-test-setup-karma';
 import './gr-plugin-config-array-editor';
 import {GrPluginConfigArrayEditor} from './gr-plugin-config-array-editor';
-import {queryAll, queryAndAssert, pressKey} from '../../../test/test-utils.js';
-import {GrButton} from '../../shared/gr-button/gr-button.js';
+import {queryAll, queryAndAssert, pressKey} from '../../../test/test-utils';
+import {GrButton} from '../../shared/gr-button/gr-button';
 import {fixture, html, assert} from '@open-wc/testing';
-import {Key} from '../../../utils/dom-util.js';
+import {Key} from '../../../utils/dom-util';
 
 suite('gr-plugin-config-array-editor tests', () => {
   let element: GrPluginConfigArrayEditor;
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.ts b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.ts
index 65b9cb5..9cbe7d60 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.ts
@@ -4,7 +4,7 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 import '../../../test/common-test-setup-karma';
-import './gr-repo-commands.js';
+import './gr-repo-commands';
 import {GrRepoCommands} from './gr-repo-commands';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation';
 import {
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 75f2c4d..5b2fdea 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
@@ -12,9 +12,10 @@
   mockPromise,
   queryAndAssert,
   stubRestApi,
+  waitEventLoop,
 } from '../../../test/test-utils';
 import {DashboardId, DashboardInfo, RepoName} from '../../../types/common';
-import {PageErrorEvent} from '../../../types/events.js';
+import {PageErrorEvent} from '../../../types/events';
 import {fixture, html, assert} from '@open-wc/testing';
 
 suite('gr-repo-dashboards tests', () => {
@@ -113,7 +114,7 @@
         'none'
       );
       element.repo = 'test' as RepoName;
-      await flush();
+      await waitEventLoop();
       assert.equal(
         getComputedStyle(queryAndAssert(element, '#loadingContainer')).display,
         'none'
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.ts b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.ts
index 3099a02..f9f0b57 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.ts
@@ -4,7 +4,7 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 import '../../../test/common-test-setup-karma';
-import './gr-repo-detail-list.js';
+import './gr-repo-detail-list';
 import {GrRepoDetailList} from './gr-repo-detail-list';
 import {page} from '../../../utils/page-wrapper-utils';
 import {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.ts b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.ts
index 4753e80..08a6a95 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.ts
@@ -39,7 +39,7 @@
   createConfig,
   createDownloadSchemes,
 } from '../../../test/test-data-generators';
-import {PageErrorEvent} from '../../../types/events.js';
+import {PageErrorEvent} from '../../../types/events';
 import {GrButton} from '../../shared/gr-button/gr-button';
 import {GrSelect} from '../../shared/gr-select/gr-select';
 import {GrTextarea} from '../../shared/gr-textarea/gr-textarea';
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-vote-flow/gr-change-list-bulk-vote-flow_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-vote-flow/gr-change-list-bulk-vote-flow_test.ts
index afbdcc3..e91b1c7 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-vote-flow/gr-change-list-bulk-vote-flow_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-vote-flow/gr-change-list-bulk-vote-flow_test.ts
@@ -18,6 +18,7 @@
   mockPromise,
   queryAll,
   stubReporting,
+  waitEventLoop,
 } from '../../../test/test-utils';
 import {ChangeInfo, NumericChangeId, LabelInfo} from '../../../api/rest-api';
 import {getAppContext} from '../../../services/app-context';
@@ -305,7 +306,7 @@
     );
     await selectChange(change1);
     await element.updateComplete;
-    await flush();
+    await waitEventLoop();
 
     assert.isNotOk(
       queryAndAssert<GrButton>(element, '#voteFlowButton').disabled
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-hashtag-flow/gr-change-list-hashtag-flow_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-hashtag-flow/gr-change-list-hashtag-flow_test.ts
index a1d0732..67901b0 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-hashtag-flow/gr-change-list-hashtag-flow_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-hashtag-flow/gr-change-list-hashtag-flow_test.ts
@@ -23,6 +23,7 @@
   queryAndAssert,
   stubReporting,
   stubRestApi,
+  waitEventLoop,
   waitUntil,
   waitUntilCalled,
   waitUntilObserved,
@@ -227,7 +228,7 @@
       // open flow
       queryAndAssert<GrButton>(element, 'gr-button#start-flow').click();
       await element.updateComplete;
-      await flush();
+      await waitEventLoop();
     });
 
     test('renders hashtags flow', () => {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-topic-flow/gr-change-list-topic-flow_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-topic-flow/gr-change-list-topic-flow_test.ts
index d7a4a2b..f00348b 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-topic-flow/gr-change-list-topic-flow_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-topic-flow/gr-change-list-topic-flow_test.ts
@@ -23,6 +23,7 @@
   queryAndAssert,
   stubReporting,
   stubRestApi,
+  waitEventLoop,
   waitUntil,
   waitUntilCalled,
   waitUntilObserved,
@@ -218,7 +219,7 @@
       // open flow
       queryAndAssert<GrButton>(element, 'gr-button#start-flow').click();
       await element.updateComplete;
-      await flush();
+      await waitEventLoop();
     });
 
     test('renders existing-topics flow', () => {
@@ -536,7 +537,7 @@
       // open flow
       queryAndAssert<GrButton>(element, 'gr-button#start-flow').click();
       await element.updateComplete;
-      await flush();
+      await waitEventLoop();
     });
 
     test('renders no-existing-topics flow', () => {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.ts
index a8d345f..45138a9 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.ts
@@ -3,7 +3,7 @@
  * Copyright 2016 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import '../../../test/common-test-setup-karma.js';
+import '../../../test/common-test-setup-karma';
 import './gr-change-list-view';
 import {GrChangeListView} from './gr-change-list-view';
 import {page} from '../../../utils/page-wrapper-utils';
@@ -15,15 +15,15 @@
   queryAndAssert,
   stubFlags,
 } from '../../../test/test-utils';
-import {createChange} from '../../../test/test-data-generators.js';
+import {createChange} from '../../../test/test-data-generators';
 import {
   ChangeInfo,
   EmailAddress,
   NumericChangeId,
   RepoName,
-} from '../../../api/rest-api.js';
+} from '../../../api/rest-api';
 import {fixture, html, waitUntil, assert} from '@open-wc/testing';
-import {GerritView} from '../../../services/router/router-model.js';
+import {GerritView} from '../../../services/router/router-model';
 
 const CHANGE_ID = 'IcA3dAB3edAB9f60B8dcdA6ef71A75980e4B7127';
 const COMMIT_HASH = '12345678';
diff --git a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.ts b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.ts
index 1fe4848..c20ce09 100644
--- a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.ts
@@ -7,7 +7,7 @@
 import {fixture, html, assert} from '@open-wc/testing';
 import './gr-user-header';
 import {GrUserHeader} from './gr-user-header';
-import {stubRestApi} from '../../../test/test-utils';
+import {stubRestApi, waitEventLoop} from '../../../test/test-utils';
 import {AccountId, EmailAddress, Timestamp} from '../../../types/common';
 
 suite('gr-user-header tests', () => {
@@ -61,13 +61,13 @@
     );
 
     element.userId = 10 as AccountId;
-    await flush();
+    await waitEventLoop();
 
     assert.isOk(element._accountDetails);
     assert.isOk(element._status);
 
     element.userId = undefined;
-    await flush();
+    await waitEventLoop();
 
     assert.isUndefined(element._accountDetails);
     assert.equal(element._status, '');
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 579c1a8..ef84af2 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
@@ -29,6 +29,7 @@
   stubFlags,
   stubRestApi,
   stubUsers,
+  waitEventLoop,
   waitQueryAndAssert,
   waitUntil,
 } from '../../../test/test-utils';
@@ -1366,7 +1367,7 @@
       await element.updateComplete;
       element.computeRevertSubmitted(element.change);
       // Wait for promises to settle.
-      await flush();
+      await waitEventLoop();
       await element.updateComplete;
       assert.isFalse(
         element.changeStatuses?.includes(ChangeStates.REVERT_SUBMITTED)
@@ -1402,7 +1403,7 @@
       await element.updateComplete;
       element.computeRevertSubmitted(element.change);
       // Wait for promises to settle.
-      await flush();
+      await waitEventLoop();
       await element.updateComplete;
       assert.isFalse(
         element.changeStatuses?.includes(ChangeStates.REVERT_CREATED)
@@ -1594,7 +1595,7 @@
     value.patchNum = 2 as RevisionPatchSetNum;
     element.params = {...value};
     await element.updateComplete;
-    await flush();
+    await waitEventLoop();
     assert.equal(element.fileList.selectedIndex, 0);
     assert.isFalse(reloadStub.calledTwice);
     assert.isTrue(reloadPatchDependentStub.calledOnce);
@@ -2526,7 +2527,7 @@
         },
       });
       await element.updateComplete;
-      await flush();
+      await waitEventLoop();
       assert.isTrue(changeDisplayStub.called);
       assert.isTrue(changeFullyLoadedStub.called);
     });
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.ts b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.ts
index 79baabf..3bb85aa 100644
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.ts
@@ -15,6 +15,7 @@
 import {CommitId, RepoName} from '../../../types/common';
 import {GrRouter} from '../../core/gr-router/gr-router';
 import {fixture, html, assert} from '@open-wc/testing';
+import {waitEventLoop} from '../../../test/test-utils';
 
 suite('gr-commit-info tests', () => {
   let element: GrCommitInfo;
@@ -47,7 +48,7 @@
     element.change = createChange();
     element.commitInfo = createCommit();
     element.serverConfig = createServerInfo();
-    await flush();
+    await waitEventLoop();
     assert.isTrue(weblinksStub.called);
   });
 
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.ts
index 53ed843..29d834f 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.ts
@@ -21,7 +21,7 @@
   TopicName,
 } from '../../../api/rest-api';
 import {createChange, createRevision} from '../../../test/test-data-generators';
-import {GrDialog} from '../../shared/gr-dialog/gr-dialog.js';
+import {GrDialog} from '../../shared/gr-dialog/gr-dialog';
 import {ProgressStatus} from '../../../constants/constants';
 import {fixture, html, assert} from '@open-wc/testing';
 
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts
index a12b08e..f48009b 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts
@@ -15,6 +15,7 @@
   waitUntil,
   pressKey,
   stubElement,
+  waitEventLoop,
 } from '../../../test/test-utils';
 import {
   BasePatchSetNum,
@@ -111,7 +112,7 @@
         .callsFake(() => Promise.resolve());
       await element.updateComplete;
       // Wait for expandedFilesChanged to complete.
-      await flush();
+      await waitEventLoop();
     });
 
     test('renders', () => {
@@ -343,7 +344,7 @@
       element.fileListIncrement = 300;
       element.files = createFiles(500);
       await element.updateComplete;
-      await flush();
+      await waitEventLoop();
 
       assert.equal(
         queryAll<HTMLDivElement>(element, '.file-row').length,
@@ -365,7 +366,7 @@
 
       queryAndAssert<GrButton>(element, '#showAllButton').click();
       await element.updateComplete;
-      await flush();
+      await waitEventLoop();
 
       assert.equal(element.numFilesShown, 500);
       assert.equal(element.shownFiles.length, 500);
@@ -930,7 +931,7 @@
         element.change = {_number: 42 as NumericChangeId} as ParsedChangeInfo;
         element.fileCursor.setCursorAtIndex(0);
         await element.updateComplete;
-        await flush();
+        await waitEventLoop();
       });
 
       test('toggle left diff via shortcut', () => {
@@ -1363,7 +1364,7 @@
       element.files = [normalize({}, path)];
       await element.updateComplete;
       // Wait for expandedFilesChanged to finish.
-      await flush();
+      await waitEventLoop();
 
       const renderSpy = sinon.spy(element, 'renderInOrder');
       const collapseStub = sinon.stub(element, 'clearCollapsedDiffs');
@@ -1376,7 +1377,7 @@
       element.toggleFileExpanded({path});
       await element.updateComplete;
       // Wait for expandedFilesChanged to finish.
-      await flush();
+      await waitEventLoop();
 
       assert.equal(collapseStub.lastCall.args[0].length, 0);
       assert.equal(
@@ -1389,7 +1390,7 @@
       element.toggleFileExpanded({path});
       await element.updateComplete;
       // Wait for expandedFilesChanged to finish.
-      await flush();
+      await waitEventLoop();
 
       assert.equal(
         queryAndAssert<GrIcon>(element, 'gr-icon').icon,
@@ -1409,11 +1410,11 @@
       element.files = [normalize({}, path)];
       // Wait for diffs to be computed.
       await element.updateComplete;
-      await flush();
+      await waitEventLoop();
       element.expandAllDiffs();
       await element.updateComplete;
       // Wait for expandedFilesChanged to finish.
-      await flush();
+      await waitEventLoop();
       assert.equal(element.filesExpanded, FilesExpandedState.ALL);
       assert.isTrue(reInitStub.calledTwice);
       assert.equal(collapseStub.lastCall.args[0].length, 0);
@@ -1421,7 +1422,7 @@
       element.collapseAllDiffs();
       await element.updateComplete;
       // Wait for expandedFilesChanged to finish.
-      await flush();
+      await waitEventLoop();
       assert.equal(element.expandedFiles.length, 0);
       assert.equal(element.filesExpanded, FilesExpandedState.NONE);
       assert.equal(collapseStub.lastCall.args[0].length, 1);
@@ -1456,7 +1457,7 @@
       sinon.stub(element, 'diffs').get(() => diffs);
       element.expandedFiles = element.expandedFiles.concat([{path}]);
       await element.updateComplete;
-      await flush();
+      await waitEventLoop();
       await promise;
     });
 
@@ -1593,7 +1594,7 @@
       element.renderInOrder([{path: 'p'}], diffs);
       await element.updateComplete;
       // Wait for renderInOrder to finish
-      await flush();
+      await waitEventLoop();
       assert.isTrue(reviewStub.called);
       assert.isTrue(reviewStub.calledWithExactly('p', true));
     });
@@ -1635,7 +1636,7 @@
           patchNum: 1 as RevisionPatchSetNum,
         };
         await element.updateComplete;
-        await flush();
+        await waitEventLoop();
       });
 
       test('displays cleanly merged file count', async () => {
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.ts b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.ts
index ef765b6..d01046e 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.ts
@@ -9,6 +9,7 @@
 import {AccountId} from '../../../api/rest-api';
 import {GrButton} from '../../shared/gr-button/gr-button';
 import {fixture, html, assert} from '@open-wc/testing';
+import {waitEventLoop} from '../../../test/test-utils';
 
 suite('gr-label-row-score tests', () => {
   let element: GrLabelScoreRow;
@@ -66,7 +67,7 @@
     };
 
     await element.updateComplete;
-    await flush();
+    await waitEventLoop();
   });
 
   function checkAriaCheckedValid() {
@@ -225,7 +226,7 @@
     element.label = {name: 'Verified', value: ' 0'};
     await element.updateComplete;
     // Wait for @selected-item-changed to fire
-    await flush();
+    await waitEventLoop();
 
     const selector = element.labelSelector;
     assert.strictEqual(selector!.selected, ' 0');
diff --git a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.ts b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.ts
index 9865e41..9935642 100644
--- a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.ts
@@ -5,7 +5,12 @@
  */
 import '../../../test/common-test-setup-karma';
 import './gr-label-scores';
-import {isHidden, queryAndAssert, stubRestApi} from '../../../test/test-utils';
+import {
+  isHidden,
+  queryAndAssert,
+  stubRestApi,
+  waitEventLoop,
+} from '../../../test/test-utils';
 import {GrLabelScores} from './gr-label-scores';
 import {AccountId} from '../../../types/common';
 import {GrLabelScoreRow} from '../gr-label-score-row/gr-label-score-row';
@@ -137,7 +142,7 @@
         ...createChange(),
         status: ChangeStatus.ABANDONED,
       };
-      await flush();
+      await waitEventLoop();
       assert.isFalse(isHidden(queryAndAssert(element, '.abandonedMessage')));
       assert.isTrue(isHidden(queryAndAssert(element, '.mergedMessage')));
     });
@@ -146,7 +151,7 @@
         ...createChange(),
         status: ChangeStatus.MERGED,
       };
-      await flush();
+      await waitEventLoop();
       assert.isFalse(isHidden(queryAndAssert(element, '.mergedMessage')));
       assert.isTrue(isHidden(queryAndAssert(element, '.abandonedMessage')));
     });
@@ -155,7 +160,7 @@
         ...createChange(),
         status: ChangeStatus.NEW,
       };
-      await flush();
+      await waitEventLoop();
       assert.isTrue(isHidden(queryAndAssert(element, '.mergedMessage')));
       assert.isTrue(isHidden(queryAndAssert(element, '.abandonedMessage')));
     });
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 f381ab8..c0a46aa 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
@@ -20,6 +20,7 @@
   query,
   queryAndAssert,
   stubRestApi,
+  waitEventLoop,
 } from '../../../test/test-utils';
 import {GrMessage} from './gr-message';
 import {
@@ -73,7 +74,7 @@
         assert.deepEqual(e.detail.message, element.message);
         promise.resolve();
       });
-      await flush();
+      await waitEventLoop();
       assert.isOk(query<HTMLElement>(element, '.replyActionContainer'));
       queryAndAssert<GrButton>(element, '.replyBtn').click();
       await promise;
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.ts
index 58893f5..000e74b 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.ts
@@ -23,6 +23,7 @@
   queryAndAssert,
   resetPlugins,
   stubRestApi,
+  waitEventLoop,
 } from '../../../test/test-utils';
 import {
   ChangeId,
@@ -675,7 +676,7 @@
         'http://some/plugins/url1.js'
       );
       getPluginLoader().loadPlugins([]);
-      await flush();
+      await waitEventLoop();
       assert.strictEqual(hookEl!.plugin, plugin!);
       assert.strictEqual(hookEl!.change, element.change);
     });
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.ts
index c495a6f..4beb068 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.ts
@@ -9,6 +9,7 @@
   queryAndAssert,
   resetPlugins,
   stubRestApi,
+  waitEventLoop,
 } from '../../../test/test-utils';
 import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
 import {GrReplyDialog} from './gr-reply-dialog';
@@ -84,14 +85,14 @@
     resetPlugins();
   });
 
-  test('submit blocked when invalid email is supplied to ccs', () => {
+  test('submit blocked when invalid email is supplied to ccs', async () => {
     const sendStub = sinon.stub(element, 'send').returns(Promise.resolve());
 
     element.ccsList!.entry!.setText('test');
     queryAndAssert<GrButton>(element, 'gr-button.send').click();
     assert.isFalse(element.ccsList!.submitEntryText());
     assert.isFalse(sendStub.called);
-    flush();
+    await waitEventLoop();
 
     element.ccsList!.entry!.setText('test@test.test');
     queryAndAssert<GrButton>(element, 'gr-button.send').click();
@@ -118,7 +119,7 @@
     setupElement(element);
     getPluginLoader().loadPlugins([]);
     await getPluginLoader().awaitPluginsLoaded();
-    await flush();
+    await waitEventLoop();
     const textarea = queryAndAssert<GrTextarea>(
       element,
       'gr-textarea'
@@ -127,7 +128,7 @@
     textarea.dispatchEvent(
       new CustomEvent('input', {bubbles: true, composed: true})
     );
-    await flush();
+    await waitEventLoop();
     const labelScoreRows = queryAndAssert(
       element.getLabelScores(),
       'gr-label-score-row[name="Code-Review"]'
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 b04288b..1eff46d 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
@@ -1673,10 +1673,9 @@
     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.
+    // Without wrapping this test in await element.updateComplete, 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<GrLabelScoreRow>(
       queryAndAssert(element, 'gr-label-scores'),
       'gr-label-score-row[name="Verified"]'
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 bb34d46..35562d0 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
@@ -10,7 +10,12 @@
   GrErrorManager,
   __testOnly_ErrorType,
 } from './gr-error-manager';
-import {stubAuth, stubReporting, stubRestApi} from '../../../test/test-utils';
+import {
+  stubAuth,
+  stubReporting,
+  stubRestApi,
+  waitEventLoop,
+} from '../../../test/test-utils';
 import {AppContext, getAppContext} from '../../../services/app-context';
 import {
   createAccountDetailWithId,
@@ -101,7 +106,7 @@
           bubbles: true,
         })
       );
-      await flush();
+      await waitEventLoop();
       assert.isFalse(showAuthErrorStub.calledOnce);
     });
 
@@ -123,7 +128,7 @@
           bubbles: true,
         })
       );
-      await flush();
+      await waitEventLoop();
       assert.isTrue(showAuthErrorStub.calledOnce);
     });
 
@@ -146,7 +151,7 @@
           bubbles: true,
         })
       );
-      await flush();
+      await waitEventLoop();
       assert.isTrue(getLoggedInStub.calledOnce);
     });
 
@@ -178,7 +183,7 @@
       );
 
       assert.isTrue(textSpy.called);
-      await flush();
+      await waitEventLoop();
       assert.isTrue(showErrorSpy.calledOnce);
       assert.isTrue(showErrorSpy.lastCall.calledWithExactly('Error 500: ZOMG'));
     });
@@ -237,7 +242,7 @@
           bubbles: true,
         })
       );
-      await flush();
+      await waitEventLoop();
       assert.equal(element.errorDialog.text, 'Error 500: 500\nTrace Id: xxxx');
     });
 
@@ -255,7 +260,7 @@
       );
 
       assert.isTrue(textSpy.called);
-      await flush();
+      await waitEventLoop();
       assert.isFalse(showAlertStub.called);
     });
 
@@ -268,7 +273,7 @@
           bubbles: true,
         })
       );
-      await flush();
+      await waitEventLoop();
       assert.isTrue(showAlertStub.calledOnce);
       assert.isTrue(
         showAlertStub.lastCall.calledWithExactly('Server unavailable')
@@ -331,15 +336,15 @@
         })
       );
       assert.equal(fetchStub.callCount, 1);
-      await flush();
+      await waitEventLoop();
 
-      // here needs two flush as there are two chained
-      // promises on server-error handler and flush only flushes one
+      // here needs two waitEventLoop() as there are two chained promises on
+      // server-error handler and waitEventLoop() only flushes one
       assert.equal(fetchStub.callCount, 2);
-      await flush();
+      await waitEventLoop();
       // Sometime overlay opens with delay, waiting while open is complete
       clock.tick(1000);
-      await flush();
+      await waitEventLoop();
       // auth-error fired
       assert.isTrue(toastSpy.called);
 
@@ -375,7 +380,7 @@
       clock.tick(1000);
       element.knownAccountId = 5 as AccountId;
       element.checkSignedIn();
-      await flush();
+      await waitEventLoop();
 
       assert.isTrue(refreshStub.called);
       assert.isTrue(hideToastSpy.called);
@@ -404,7 +409,7 @@
           bubbles: true,
         })
       );
-      await flush();
+      await waitEventLoop();
       let toast = toastSpy.lastCall.returnValue;
       assert.isOk(toast);
       assert.include(toast.shadowRoot.textContent, 'test reload');
@@ -425,15 +430,15 @@
           bubbles: true,
         })
       );
-      await flush();
-      await flush();
-      // here needs two flush as there are two chained
-      // promises on server-error handler and flush only flushes one
+      await waitEventLoop();
+      await waitEventLoop();
+      // here needs two waitEventLoop() as there are two chained promises on
+      // server-error handler and waitEventLoop() only flushes one
       assert.equal(fetchStub.callCount, 2);
-      await flush();
+      await waitEventLoop();
       // Sometime overlay opens with delay, waiting while open is complete
       clock.tick(1000);
-      await flush();
+      await waitEventLoop();
       // toast
       toast = toastSpy.lastCall.returnValue;
       assert.include(toast.shadowRoot.textContent, 'Credentials expired.');
@@ -452,7 +457,7 @@
           bubbles: true,
         })
       );
-      await flush();
+      await waitEventLoop();
       let toast = toastSpy.lastCall.returnValue;
       assert.isOk(toast);
       assert.include(toast.shadowRoot.textContent, 'test reload');
@@ -465,7 +470,7 @@
           bubbles: true,
         })
       );
-      await flush();
+      await waitEventLoop();
       toast = toastSpy.lastCall.returnValue;
       assert.include(toast.shadowRoot.textContent, 'second-test');
     });
@@ -492,12 +497,12 @@
         })
       );
       assert.equal(fetchStub.callCount, 1);
-      await flush();
+      await waitEventLoop();
 
-      // here needs two flush as there are two chained
-      // promises on server-error handler and flush only flushes one
+      // here needs two waitEventLoop() as there are two chained promises on
+      // server-error handler and waitEventLoop() only flushes one
       assert.equal(fetchStub.callCount, 2);
-      await flush();
+      await waitEventLoop();
       await waitUntil(() => toastSpy.calledOnce);
       let toast = toastSpy.lastCall.returnValue;
       assert.include(toast.shadowRoot.textContent, 'Credentials expired.');
@@ -515,7 +520,7 @@
         })
       );
 
-      await flush();
+      await waitEventLoop();
       assert.isTrue(toastSpy.calledOnce);
       toast = toastSpy.lastCall.returnValue;
       assert.isOk(toast);
@@ -574,7 +579,7 @@
       element.refreshingCredentials = true;
       element.checkSignedIn();
 
-      await flush();
+      await waitEventLoop();
       assert.isFalse(requestCheckStub.called);
       assert.isTrue(handleRefreshStub.called);
       assert.isFalse(reloadStub.called);
@@ -600,7 +605,7 @@
           bubbles: true,
         })
       );
-      await flush();
+      await waitEventLoop();
 
       assert.isTrue(openStub.called);
       assert.isTrue(reportStub.called);
@@ -612,7 +617,7 @@
           bubbles: true,
         })
       );
-      await flush();
+      await waitEventLoop();
 
       assert.isTrue(closeStub.called);
     });
@@ -633,7 +638,7 @@
       element.refreshingCredentials = true;
       element.checkSignedIn();
 
-      await flush();
+      await waitEventLoop();
 
       assert.isFalse(requestCheckStub.called);
       assert.isFalse(handleRefreshStub.called);
@@ -669,7 +674,7 @@
       element.refreshingCredentials = true;
       element.checkSignedIn();
 
-      await flush();
+      await waitEventLoop();
       assert.isTrue(requestCheckStub.called);
       assert.isFalse(handleRefreshStub.called);
       assert.isFalse(reloadStub.called);
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.ts b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.ts
index 7c1e394..d83f8a5 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.ts
@@ -11,6 +11,7 @@
   ShortcutSection,
 } from '../../../services/shortcuts/shortcuts-service';
 import {fixture, html, assert} from '@open-wc/testing';
+import {waitEventLoop} from '../../../test/test-utils';
 
 const x = ['x'];
 const ctrlX = ['Ctrl', 'x'];
@@ -23,12 +24,12 @@
     element = await fixture(
       html`<gr-keyboard-shortcuts-dialog></gr-keyboard-shortcuts-dialog>`
     );
-    await flush();
+    await waitEventLoop();
   });
 
-  function update(directory: Map<ShortcutSection, SectionView>) {
+  async function update(directory: Map<ShortcutSection, SectionView>) {
     element.onDirectoryUpdated(directory);
-    flush();
+    await waitEventLoop();
   }
 
   test('renders left and right contents', async () => {
@@ -42,7 +43,7 @@
         [{binding: [shiftMetaX], text: 'navigation shortcuts'}],
       ],
     ]);
-    update(directory);
+    await update(directory);
     await element.updateComplete;
 
     assert.shadowDom.equal(
@@ -117,9 +118,9 @@
       assert.isEmpty(element.right);
     });
 
-    test('everywhere goes on left', () => {
+    test('everywhere goes on left', async () => {
       const sectionView = [{binding: [], text: 'everywhere shortcuts'}];
-      update(new Map([[ShortcutSection.EVERYWHERE, sectionView]]));
+      await update(new Map([[ShortcutSection.EVERYWHERE, sectionView]]));
       assert.deepEqual(element.left, [
         {
           section: ShortcutSection.EVERYWHERE,
@@ -129,9 +130,9 @@
       assert.isEmpty(element.right);
     });
 
-    test('navigation goes on left', () => {
+    test('navigation goes on left', async () => {
       const sectionView = [{binding: [], text: 'navigation shortcuts'}];
-      update(new Map([[ShortcutSection.NAVIGATION, sectionView]]));
+      await update(new Map([[ShortcutSection.NAVIGATION, sectionView]]));
       assert.deepEqual(element.left, [
         {
           section: ShortcutSection.NAVIGATION,
@@ -141,9 +142,9 @@
       assert.isEmpty(element.right);
     });
 
-    test('actions go on right', () => {
+    test('actions go on right', async () => {
       const sectionView = [{binding: [], text: 'actions shortcuts'}];
-      update(new Map([[ShortcutSection.ACTIONS, sectionView]]));
+      await update(new Map([[ShortcutSection.ACTIONS, sectionView]]));
       assert.deepEqual(element.right, [
         {
           section: ShortcutSection.ACTIONS,
@@ -153,9 +154,9 @@
       assert.isEmpty(element.left);
     });
 
-    test('reply dialog goes on left', () => {
+    test('reply dialog goes on left', async () => {
       const sectionView = [{binding: [], text: 'reply dialog shortcuts'}];
-      update(new Map([[ShortcutSection.REPLY_DIALOG, sectionView]]));
+      await update(new Map([[ShortcutSection.REPLY_DIALOG, sectionView]]));
       assert.deepEqual(element.left, [
         {
           section: ShortcutSection.REPLY_DIALOG,
@@ -165,9 +166,9 @@
       assert.isEmpty(element.right);
     });
 
-    test('file list goes on left', () => {
+    test('file list goes on left', async () => {
       const sectionView = [{binding: [], text: 'file list shortcuts'}];
-      update(new Map([[ShortcutSection.FILE_LIST, sectionView]]));
+      await update(new Map([[ShortcutSection.FILE_LIST, sectionView]]));
       assert.deepEqual(element.left, [
         {
           section: ShortcutSection.FILE_LIST,
@@ -177,9 +178,9 @@
       assert.isEmpty(element.right);
     });
 
-    test('diffs go on right', () => {
+    test('diffs go on right', async () => {
       const sectionView = [{binding: [], text: 'diffs shortcuts'}];
-      update(new Map([[ShortcutSection.DIFFS, sectionView]]));
+      await update(new Map([[ShortcutSection.DIFFS, sectionView]]));
       assert.deepEqual(element.right, [
         {
           section: ShortcutSection.DIFFS,
@@ -189,7 +190,7 @@
       assert.isEmpty(element.left);
     });
 
-    test('multiple sections on each side', () => {
+    test('multiple sections on each side', async () => {
       const actionsSectionView = [{binding: [], text: 'actions shortcuts'}];
       const diffsSectionView = [{binding: [], text: 'diffs shortcuts'}];
       const everywhereSectionView = [
@@ -198,7 +199,7 @@
       const navigationSectionView = [
         {binding: [], text: 'navigation shortcuts'},
       ];
-      update(
+      await update(
         new Map([
           [ShortcutSection.ACTIONS, actionsSectionView],
           [ShortcutSection.DIFFS, diffsSectionView],
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
index 29b8e7a..2033eef 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
@@ -11,6 +11,7 @@
   stubBaseUrl,
   stubRestApi,
   addListenerForTest,
+  waitEventLoop,
 } from '../../../test/test-utils';
 import {
   GrRouter,
@@ -1172,7 +1173,7 @@
           params: {0: '1234', 1: 'comment/6789'},
         };
         router.handleChangeLegacyRoute(ctx);
-        await flush();
+        await waitEventLoop();
         assert.isTrue(
           redirectStub.calledWithExactly('/c/project/+/1234' + '/comment/6789')
         );
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts
index cd5eac1..e62dcbb 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts
@@ -21,6 +21,7 @@
   stubReporting,
   stubRestApi,
   stubUsers,
+  waitEventLoop,
   waitUntil,
 } from '../../../test/test-utils';
 import {ChangeComments} from '../gr-comment-api/gr-comment-api';
@@ -1301,34 +1302,31 @@
       );
     });
 
-    function isEditVisibile({
+    async function isEditVisibile({
       loggedIn,
       changeStatus,
     }: {
       loggedIn: boolean;
       changeStatus: ChangeStatus;
-    }) {
-      return new Promise(resolve => {
-        element.loggedIn = loggedIn;
-        element.path = 't.txt';
-        element.patchRange = {
-          basePatchNum: PARENT,
-          patchNum: 1 as RevisionPatchSetNum,
-        };
-        element.change = {
-          ...createParsedChange(),
-          _number: 42 as NumericChangeId,
-          status: changeStatus,
-          revisions: {
-            a: createRevision(1),
-            b: createRevision(2),
-          },
-        };
-        flush(() => {
-          const editBtn = query(element, '.editButton gr-button');
-          resolve(!!editBtn);
-        });
-      });
+    }): Promise<boolean> {
+      element.loggedIn = loggedIn;
+      element.path = 't.txt';
+      element.patchRange = {
+        basePatchNum: PARENT,
+        patchNum: 1 as RevisionPatchSetNum,
+      };
+      element.change = {
+        ...createParsedChange(),
+        _number: 42 as NumericChangeId,
+        status: changeStatus,
+        revisions: {
+          a: createRevision(1),
+          b: createRevision(2),
+        },
+      };
+      await element.updateComplete;
+      const editBtn = query(element, '.editButton gr-button');
+      return !!editBtn;
     }
 
     test('edit visible only when logged and status NEW', async () => {
@@ -1792,7 +1790,7 @@
       assert.equal(saveReviewedStub.callCount, callCount);
     });
 
-    test('file review status with edit loaded', () => {
+    test('file review status with edit loaded', async () => {
       const saveReviewedStub = sinon.stub(
         element.getChangeModel(),
         'setReviewedFilesStatus'
@@ -1802,7 +1800,7 @@
         basePatchNum: 1 as BasePatchSetNum,
         patchNum: EDIT,
       };
-      flush();
+      await waitEventLoop();
 
       assert.isTrue(element.computeEditMode());
       element.setReviewed(true);
@@ -1824,7 +1822,7 @@
       };
 
       await element.updateComplete;
-      await flush();
+      await waitEventLoop();
       assert.isTrue(initLineStub.calledOnce);
     });
 
@@ -1912,7 +1910,7 @@
         };
         element.change = change;
         await element.updateComplete;
-        await flush();
+        await waitEventLoop();
         assert.deepEqual(element.commitRange, {
           baseCommit: 'commit-sha-2' as CommitId,
           commit: 'commit-sha-4' as CommitId,
@@ -1928,7 +1926,7 @@
         };
         element.change = change;
         await element.updateComplete;
-        await flush();
+        await waitEventLoop();
         assert.deepEqual(element.commitRange, {
           commit: 'commit-sha-5' as CommitId,
           baseCommit: 'sha-5-parent' as CommitId,
@@ -2459,14 +2457,14 @@
       });
     });
 
-    test('shift+m navigates to next unreviewed file', () => {
+    test('shift+m navigates to next unreviewed file', async () => {
       element.files = getFilesFromFileList(['file1', 'file2', 'file3']);
       element.reviewedFiles = new Set(['file1', 'file2']);
       element.path = 'file1';
       const reviewedStub = sinon.stub(element, 'setReviewed');
       const navStub = sinon.stub(element, 'navToFile');
       pressKey(element, 'M');
-      flush();
+      await waitEventLoop();
 
       assert.isTrue(reviewedStub.lastCall.args[0]);
       assert.deepEqual(navStub.lastCall.args, [['file1', 'file3'], 1]);
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 721a280..5481729 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
@@ -6,7 +6,11 @@
 import '../../../test/common-test-setup-karma';
 import './gr-default-editor';
 import {GrDefaultEditor} from './gr-default-editor';
-import {mockPromise, queryAndAssert} from '../../../test/test-utils';
+import {
+  mockPromise,
+  queryAndAssert,
+  waitEventLoop,
+} from '../../../test/test-utils';
 import {fixture, html, assert} from '@open-wc/testing';
 
 suite('gr-default-editor tests', () => {
@@ -15,7 +19,7 @@
   setup(async () => {
     element = await fixture(html`<gr-default-editor></gr-default-editor>`);
     element.fileContent = '';
-    await flush();
+    await waitEventLoop();
   });
 
   test('render', () => {
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 f8247f4..02444fb 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
@@ -222,8 +222,8 @@
       assert.isTrue(hideDialogStub.called);
       assert.isTrue(element.openDialog!.disabled);
       assert.isFalse(queryStub.called);
-      // Setup focused manually - in headless mode Chrome sometimes don't
-      // setup focus. flush and/or flushAsynchronousOperations don't help
+      // Setup focused manually - in headless mode Chrome sometimes doesn't
+      // setup focus. waitEventLoop() doesn't help.
       openAutoComplete.focused = true;
       openAutoComplete.noDebounce = true;
       openAutoComplete.text = 'src/test.cpp';
@@ -283,8 +283,8 @@
       await showDialogSpy.lastCall.returnValue;
       assert.isTrue(element.deleteDialog!.disabled);
       assert.isFalse(queryStub.called);
-      // Setup focused manually - in headless mode Chrome sometimes don't
-      // setup focus. flush and/or flushAsynchronousOperations don't help
+      // Setup focused manually - in headless mode Chrome sometimes doesn't
+      // setup focus. waitEventLoop() doesn't help.
       deleteAutocomplete.focused = true;
       deleteAutocomplete.noDebounce = true;
       deleteAutocomplete.text = 'src/test.cpp';
@@ -310,8 +310,8 @@
       await showDialogSpy.lastCall.returnValue;
       assert.isTrue(element.deleteDialog!.disabled);
       assert.isFalse(queryStub.called);
-      // Setup focused manually - in headless mode Chrome sometimes don't
-      // setup focus. flush and/or flushAsynchronousOperations don't help
+      // Setup focused manually - in headless mode Chrome sometimes doesn't
+      // setup focus. waitEventLoop() doesn't help.
       deleteAutocomplete.focused = true;
       deleteAutocomplete.noDebounce = true;
       deleteAutocomplete.text = 'src/test.cpp';
@@ -370,8 +370,8 @@
       await showDialogSpy.lastCall.returnValue;
       assert.isTrue(element.renameDialog!.disabled);
       assert.isFalse(queryStub.called);
-      // Setup focused manually - in headless mode Chrome sometimes don't
-      // setup focus. flush and/or flushAsynchronousOperations don't help
+      // Setup focused manually - in headless mode Chrome sometimes doesn't
+      // setup focus. waitEventLoop() doesn't help.
       renameAutocomplete.focused = true;
       renameAutocomplete.noDebounce = true;
       renameAutocomplete.text = 'src/test.cpp';
@@ -402,8 +402,8 @@
       await showDialogSpy.lastCall.returnValue;
       assert.isTrue(element.renameDialog!.disabled);
       assert.isFalse(queryStub.called);
-      // Setup focused manually - in headless mode Chrome sometimes don't
-      // setup focus. flush and/or flushAsynchronousOperations don't help
+      // Setup focused manually - in headless mode Chrome sometimes doesn't
+      // setup focus. waitEventLoop() doesn't help.
       renameAutocomplete.focused = true;
       renameAutocomplete.noDebounce = true;
       renameAutocomplete.text = 'src/test.cpp';
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.ts b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.ts
index dae5e09..14f0b15 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.ts
@@ -8,7 +8,7 @@
 import {GrPopupInterface} from './gr-popup-interface';
 import {PluginApi} from '../../../api/plugin';
 import {HookApi, PluginElement} from '../../../api/hook';
-import {queryAndAssert} from '../../../test/test-utils';
+import {queryAndAssert, waitEventLoop} from '../../../test/test-utils';
 import {LitElement, html} from 'lit';
 import {customElement} from 'lit/decorators.js';
 import {fixture, assert} from '@open-wc/testing';
@@ -59,7 +59,7 @@
       const popup = instance._getElement();
       assert.isOk(popup);
       popup!.appendChild(manual);
-      await flush();
+      await waitEventLoop();
       assert.equal(
         queryAndAssert(container, '#foobar').textContent,
         'manual content'
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.ts b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.ts
index e9c016b..40b6250 100644
--- a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.ts
@@ -5,7 +5,7 @@
  */
 import '../../../test/common-test-setup-karma';
 import './gr-agreements-list';
-import {stubRestApi} from '../../../test/test-utils';
+import {stubRestApi, waitEventLoop} from '../../../test/test-utils';
 import {GrAgreementsList} from './gr-agreements-list';
 import {ContributorAgreementInfo} from '../../../types/common';
 import {fixture, html, assert} from '@open-wc/testing';
@@ -27,7 +27,7 @@
     element = await fixture(html`<gr-agreements-list></gr-agreements-list>`);
 
     await element.loadData();
-    await flush();
+    await waitEventLoop();
   });
 
   test('renders', () => {
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 db00211..2805aec 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
@@ -8,7 +8,7 @@
 import {GrGroupList} from './gr-group-list';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation';
 import {GroupId, GroupInfo, GroupName} from '../../../types/common';
-import {stubRestApi} from '../../../test/test-utils';
+import {stubRestApi, waitEventLoop} from '../../../test/test-utils';
 import {fixture, html, assert} from '@open-wc/testing';
 
 suite('gr-group-list tests', () => {
@@ -46,7 +46,7 @@
     element = await fixture(html`<gr-group-list></gr-group-list>`);
 
     await element.loadData();
-    await flush();
+    await waitEventLoop();
   });
 
   test('renders', () => {
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 d73e841..c4ed9fd 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
@@ -6,7 +6,7 @@
 import '../../../test/common-test-setup-karma';
 import './gr-http-password';
 import {GrHttpPassword} from './gr-http-password';
-import {stubRestApi} from '../../../test/test-utils';
+import {stubRestApi, waitEventLoop} from '../../../test/test-utils';
 import {
   createAccountDetailWithId,
   createServerInfo,
@@ -30,7 +30,7 @@
 
     element = await fixture(html`<gr-http-password></gr-http-password>`);
     await element.loadData();
-    await flush();
+    await waitEventLoop();
   });
 
   test('renders', () => {
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 10be124..d37e8bf 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
@@ -7,7 +7,7 @@
 import './gr-settings-view';
 import {GrSettingsView} from './gr-settings-view';
 import {GerritView} from '../../../services/router/router-model';
-import {queryAll, stubRestApi} from '../../../test/test-utils';
+import {queryAll, stubRestApi, waitEventLoop} from '../../../test/test-utils';
 import {
   AuthInfo,
   AccountDetailInfo,
@@ -522,7 +522,7 @@
     const div = await fixture(html`<div></div>`);
     div.appendChild(newElement);
 
-    flush();
+    await waitEventLoop();
 
     assert.isTrue(titleChangedStub.called);
     assert.equal(titleChangedStub.getCall(0).args[0].detail.title, 'Settings');
diff --git a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.ts b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.ts
index 9269293..2e69c70 100644
--- a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.ts
@@ -5,7 +5,12 @@
  */
 import '../../../test/common-test-setup-karma';
 import './gr-ssh-editor';
-import {mockPromise, query, stubRestApi} from '../../../test/test-utils';
+import {
+  mockPromise,
+  query,
+  stubRestApi,
+  waitEventLoop,
+} from '../../../test/test-utils';
 import {GrSshEditor} from './gr-ssh-editor';
 import {SshKeyInfo} from '../../../types/common';
 import {GrButton} from '../../shared/gr-button/gr-button';
@@ -40,7 +45,7 @@
     element = await fixture(html`<gr-ssh-editor></gr-ssh-editor>`);
 
     await element.loadData();
-    await flush();
+    await waitEventLoop();
   });
 
   test('renders', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.ts b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.ts
index 4216c11..ef1d4b3 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.ts
@@ -10,6 +10,7 @@
   queryAndAssert,
   spyRestApi,
   stubRestApi,
+  waitEventLoop,
 } from '../../../test/test-utils';
 import {GrAccountLabel} from './gr-account-label';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation';
@@ -174,7 +175,7 @@
         owner: kermit,
         reviewers: {},
       };
-      await flush();
+      await waitEventLoop();
     });
 
     test('show attention button', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.ts b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.ts
index 713afb1..fcf211f 100644
--- a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.ts
@@ -8,6 +8,7 @@
 import {GrAlert} from './gr-alert';
 import {assert} from '@open-wc/testing';
 import {GrButton} from '../gr-button/gr-button';
+import {waitEventLoop} from '../../../test/test-utils';
 
 suite('gr-alert tests', () => {
   let element: GrAlert;
@@ -51,7 +52,7 @@
     assert.isNull(element.parentNode);
     element.show('Alert text');
     // wait for element to be rendered after being attached to DOM
-    await flush();
+    await waitEventLoop();
     assert.equal(element.parentNode, document.body);
     element.style.setProperty('--gr-alert-transition-duration', '0ms');
     element.hide();
@@ -61,7 +62,7 @@
   test('action event', async () => {
     const spy = sinon.spy();
     element.show('Alert text');
-    await flush();
+    await waitEventLoop();
     element._actionCallback = spy;
     assert.isFalse(spy.called);
     element.shadowRoot!.querySelector<GrButton>('.action')!.click();
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.ts
index fbfa0d1..3c88291 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.ts
@@ -6,7 +6,12 @@
 import '../../../test/common-test-setup-karma';
 import './gr-autocomplete-dropdown';
 import {GrAutocompleteDropdown} from './gr-autocomplete-dropdown';
-import {pressKey, queryAll, queryAndAssert} from '../../../test/test-utils';
+import {
+  pressKey,
+  queryAll,
+  queryAndAssert,
+  waitEventLoop,
+} from '../../../test/test-utils';
 import {assertIsDefined} from '../../../utils/common-util';
 import {fixture, html, assert} from '@open-wc/testing';
 import {Key} from '../../../utils/dom-util';
@@ -25,7 +30,7 @@
       {dataValue: 'test value 1', name: 'test name 1', text: '1', label: 'hi'},
       {dataValue: 'test value 2', name: 'test name 2', text: '2'},
     ];
-    await flush();
+    await waitEventLoop();
   });
 
   teardown(() => {
@@ -80,10 +85,10 @@
     assert.equal(els[1].innerText.trim(), '2');
   });
 
-  test('escape key', () => {
+  test('escape key', async () => {
     const closeSpy = sinon.spy(element, 'close');
     pressKey(element, Key.ESC);
-    flush();
+    await waitEventLoop();
     assert.isTrue(closeSpy.called);
   });
 
@@ -140,26 +145,26 @@
     assert.equal(element.cursor.index, 0);
   });
 
-  test('tapping selects item', () => {
+  test('tapping selects item', async () => {
     const itemSelectedStub = sinon.stub();
     element.addEventListener('item-selected', itemSelectedStub);
 
     suggestionsEl().querySelectorAll('li')[1].click();
-    flush();
+    await waitEventLoop();
     assert.deepEqual(itemSelectedStub.lastCall.args[0].detail, {
       trigger: 'click',
       selected: suggestionsEl().querySelectorAll('li')[1],
     });
   });
 
-  test('tapping child still selects item', () => {
+  test('tapping child still selects item', async () => {
     const itemSelectedStub = sinon.stub();
     element.addEventListener('item-selected', itemSelectedStub);
     const lastElChild = queryAll<HTMLLIElement>(suggestionsEl(), 'li')[0]
       ?.lastElementChild;
     assertIsDefined(lastElChild);
     (lastElChild as HTMLSpanElement).click();
-    flush();
+    await waitEventLoop();
     assert.deepEqual(itemSelectedStub.lastCall.args[0].detail, {
       trigger: 'click',
       selected: queryAll<HTMLElement>(suggestionsEl(), 'li')[0],
diff --git a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.ts b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.ts
index bbbfd5c..1bab855 100644
--- a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.ts
@@ -6,7 +6,12 @@
 import '../../../test/common-test-setup-karma';
 import './gr-dialog';
 import {GrDialog} from './gr-dialog';
-import {isHidden, pressKey, queryAndAssert} from '../../../test/test-utils';
+import {
+  isHidden,
+  pressKey,
+  queryAndAssert,
+  waitEventLoop,
+} from '../../../test/test-utils';
 import {fixture, html, assert} from '@open-wc/testing';
 import {GrButton} from '../gr-button/gr-button';
 
@@ -121,7 +126,7 @@
     const handleConfirmStub = sinon.stub(element, '_handleConfirm');
     const handleKeydownSpy = sinon.spy(element, '_handleKeydown');
     pressKey(queryAndAssert(element, 'main'), 'Enter');
-    await flush();
+    await waitEventLoop();
 
     assert.isTrue(handleKeydownSpy.called);
     assert.isFalse(handleConfirmStub.called);
@@ -130,7 +135,7 @@
     await element.updateComplete;
 
     pressKey(queryAndAssert(element, 'main'), 'Enter');
-    await flush();
+    await waitEventLoop();
 
     assert.isTrue(handleConfirmStub.called);
   });
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.ts b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.ts
index 797570a..6026b05 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.ts
@@ -6,7 +6,12 @@
 import '../../../test/common-test-setup-karma';
 import './gr-dropdown-list';
 import {GrDropdownList} from './gr-dropdown-list';
-import {query, queryAll, queryAndAssert} from '../../../test/test-utils';
+import {
+  query,
+  queryAll,
+  queryAndAssert,
+  waitEventLoop,
+} from '../../../test/test-utils';
 import {PaperListboxElement} from '@polymer/paper-listbox';
 import {Timestamp} from '../../../types/common';
 import {assertIsDefined} from '../../../utils/common-util';
@@ -201,7 +206,7 @@
       },
     ];
     await element.updateComplete;
-    await flush();
+    await waitEventLoop();
 
     assert.equal(
       queryAndAssert<PaperListboxElement>(element, 'paper-listbox').selected,
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.ts b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.ts
index 9979fcb..bda6ff9 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.ts
@@ -16,7 +16,7 @@
   GrAutocomplete,
 } from '../gr-autocomplete/gr-autocomplete';
 import {Key} from '../../../utils/dom-util';
-import {pressKey, waitUntil} from '../../../test/test-utils';
+import {pressKey, waitEventLoop, waitUntil} from '../../../test/test-utils';
 import {IronInputElement} from '@polymer/iron-input';
 
 suite('gr-editable-label tests', () => {
@@ -120,7 +120,7 @@
     assert.equal(elementNoPlaceholder.title, '');
     element.value = 'value text';
 
-    await flush();
+    await waitEventLoop();
     assert.equal(element.title, 'value text');
   });
 
@@ -130,14 +130,14 @@
     assert.isFalse(element.editing);
 
     label.click();
-    await flush();
+    await waitEventLoop();
 
     assert.isTrue(element.editing);
     assert.isFalse(editedSpy.called);
 
     element.inputText = 'new text';
     pressKey(input, Key.ENTER);
-    await flush();
+    await waitEventLoop();
 
     assert.isTrue(editedSpy.called);
     assert.equal(input.value, 'new text');
@@ -150,14 +150,14 @@
     assert.isFalse(element.editing);
 
     label.click();
-    await flush();
+    await waitEventLoop();
 
     assert.isTrue(element.editing);
     assert.isFalse(editedSpy.called);
 
     element.inputText = 'new text';
     pressKey(queryAndAssert<GrButton>(element, '#saveBtn'), Key.ENTER);
-    await flush();
+    await waitEventLoop();
 
     assert.isTrue(editedSpy.called);
     assert.equal(input.value, 'new text');
@@ -170,14 +170,14 @@
     assert.isFalse(element.editing);
 
     label.click();
-    await flush();
+    await waitEventLoop();
 
     assert.isTrue(element.editing);
     assert.isFalse(editedSpy.called);
 
     element.inputText = 'new text';
     pressKey(input, Key.ESC);
-    await flush();
+    await waitEventLoop();
 
     assert.isFalse(editedSpy.called);
     // Text changes should be discarded.
@@ -191,7 +191,7 @@
     assert.isFalse(element.editing);
 
     label.click();
-    await flush();
+    await waitEventLoop();
 
     assert.isTrue(element.editing);
     assert.isFalse(editedSpy.called);
@@ -199,7 +199,7 @@
     element.inputText = 'new text';
     // Press escape:
     queryAndAssert<GrButton>(element, '#cancelBtn').click();
-    await flush();
+    await waitEventLoop();
 
     assert.isFalse(editedSpy.called);
     // Text changes should be discarded.
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_test.ts b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_test.ts
index bff7a7b..7d21146 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_test.ts
@@ -19,13 +19,13 @@
   AccountId,
   EmailAddress,
   ReviewerState,
-} from '../../../api/rest-api.js';
+} from '../../../api/rest-api';
 import {
   createAccountDetailWithId,
   createChange,
   createDetailedLabelInfo,
-} from '../../../test/test-data-generators.js';
-import {GrButton} from '../gr-button/gr-button.js';
+} from '../../../test/test-data-generators';
+import {GrButton} from '../gr-button/gr-button';
 import {EventType} from '../../../types/events';
 
 suite('gr-hovercard-account tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.js
index b8631f4..7dd413f 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.js
@@ -9,7 +9,11 @@
 import {EventType} from '../../../api/plugin';
 import {PLUGIN_LOADING_TIMEOUT_MS} from './gr-api-utils';
 import {getPluginLoader} from './gr-plugin-loader';
-import {stubRestApi, stubBaseUrl} from '../../../test/test-utils';
+import {
+  stubRestApi,
+  stubBaseUrl,
+  waitEventLoop,
+} from '../../../test/test-utils';
 import {getAppContext} from '../../../services/app-context';
 import {assert} from '@open-wc/testing';
 
@@ -133,7 +137,7 @@
     // Timeout on loading plugins
     clock.tick(PLUGIN_LOADING_TIMEOUT_MS * 2);
 
-    await flush();
+    await waitEventLoop();
     assert.isTrue(spy.called);
   });
 
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.js
index fdc44c0..2e46de8 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.js
@@ -6,7 +6,7 @@
 import '../../../test/common-test-setup-karma';
 import './gr-js-api-interface';
 import {GrPluginActionContext} from './gr-plugin-action-context';
-import {addListenerForTest} from '../../../test/test-utils';
+import {addListenerForTest, waitEventLoop} from '../../../test/test-utils';
 import {EventType} from '../../../types/events';
 import {assert} from '@open-wc/testing';
 
@@ -29,7 +29,7 @@
     sinon.stub(plugin, 'popup').returns(Promise.resolve(popupApiStub));
     const el = document.createElement('span');
     instance.popup(el);
-    await flush();
+    await waitEventLoop();
     assert.isTrue(popupApiStub._getElement.called);
     instance.hide();
     assert.isTrue(popupApiStub.close.called);
@@ -70,9 +70,9 @@
       document.body.appendChild(button);
     });
 
-    test('click', () => {
+    test('click', async () => {
       button.click();
-      flush();
+      await waitEventLoop();
       assert.isTrue(clickStub.called);
       assert.equal(button.textContent, 'foo');
     });
@@ -127,7 +127,7 @@
     const errorStub = sinon.stub();
     addListenerForTest(document, EventType.SHOW_ALERT, errorStub);
     instance.call();
-    await flush();
+    await waitEventLoop();
     assert.isTrue(errorStub.calledOnce);
     assert.equal(errorStub.args[0][0].detail.message,
         'Plugin network error: Error: boom');
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.ts
index cdac08e..91646f0 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.ts
@@ -6,7 +6,11 @@
 import '../../../test/common-test-setup-karma';
 import {PLUGIN_LOADING_TIMEOUT_MS} from './gr-api-utils';
 import {PluginLoader, _testOnly_resetPluginLoader} from './gr-plugin-loader';
-import {resetPlugins, stubBaseUrl} from '../../../test/test-utils';
+import {
+  resetPlugins,
+  stubBaseUrl,
+  waitEventLoop,
+} from '../../../test/test-utils';
 import {addListenerForTest, stubRestApi} from '../../../test/test-utils';
 import {PluginApi} from '../../../api/plugin';
 import {SinonFakeTimers} from 'sinon';
@@ -74,11 +78,11 @@
     );
     pluginsLoadedStub.reset();
     (window.Gerrit as any)._loadPlugins([]);
-    await flush();
+    await waitEventLoop();
     assert.isTrue(pluginsLoadedStub.called);
   });
 
-  test('arePluginsLoaded', () => {
+  test('arePluginsLoaded', async () => {
     assert.isFalse(pluginLoader.arePluginsLoaded());
     const plugins = [
       'http://test.com/plugins/foo/static/test.js',
@@ -90,7 +94,7 @@
     // Timeout on loading plugins
     clock.tick(PLUGIN_LOADING_TIMEOUT_MS * 2);
 
-    flush();
+    await waitEventLoop();
     assert.isTrue(pluginLoader.arePluginsLoaded());
   });
 
@@ -109,12 +113,12 @@
     ];
     pluginLoader.loadPlugins(plugins);
 
-    await flush();
+    await waitEventLoop();
     assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo', 'bar']));
     assert.isTrue(pluginLoader.arePluginsLoaded());
   });
 
-  test('isPluginEnabled and isPluginLoaded', () => {
+  test('isPluginEnabled and isPluginLoaded', async () => {
     sinon.stub(pluginLoader, '_loadJsPlugin').callsFake(url => {
       window.Gerrit.install(() => void 0, undefined, url);
     });
@@ -129,7 +133,7 @@
       plugins.every(plugin => pluginLoader.isPluginEnabled(plugin))
     );
 
-    flush();
+    await waitEventLoop();
     assert.isTrue(pluginLoader.arePluginsLoaded());
     assert.isTrue(plugins.every(plugin => pluginLoader.isPluginLoaded(plugin)));
   });
@@ -162,7 +166,7 @@
 
     pluginLoader.loadPlugins(plugins);
 
-    await flush();
+    await waitEventLoop();
     assert.isTrue(pluginsLoadedStub.calledWithExactly(['bar']));
     assert.isTrue(pluginLoader.arePluginsLoaded());
     assert.isTrue(alertStub.calledOnce);
@@ -199,7 +203,7 @@
       plugins.every(plugin => pluginLoader.isPluginEnabled(plugin))
     );
 
-    await flush();
+    await waitEventLoop();
     assert.isTrue(pluginsLoadedStub.calledWithExactly(['bar']));
     assert.isTrue(pluginLoader.arePluginsLoaded());
     assert.isTrue(alertStub.calledOnce);
@@ -233,7 +237,7 @@
 
     pluginLoader.loadPlugins(plugins);
 
-    await flush();
+    await waitEventLoop();
     assert.isTrue(pluginsLoadedStub.calledWithExactly([]));
     assert.isTrue(pluginLoader.arePluginsLoaded());
     assert.isTrue(alertStub.calledTwice);
@@ -259,7 +263,7 @@
 
     pluginLoader.loadPlugins(plugins);
 
-    await flush();
+    await waitEventLoop();
     assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo']));
     assert.isTrue(pluginLoader.arePluginsLoaded());
     assert.isTrue(alertStub.calledOnce);
@@ -281,7 +285,7 @@
     ];
     pluginLoader.loadPlugins(plugins);
 
-    await flush();
+    await waitEventLoop();
     assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo', 'bar']));
     assert.isTrue(pluginLoader.arePluginsLoaded());
   });
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-reporting-js-api_test.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-reporting-js-api_test.ts
index 98e04a3..d1c9f93 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-reporting-js-api_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-reporting-js-api_test.ts
@@ -3,13 +3,13 @@
  * Copyright 2020 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import '../../../test/common-test-setup-karma.js';
-import '../../change/gr-reply-dialog/gr-reply-dialog.js';
-import {getAppContext} from '../../../services/app-context.js';
-import {stubRestApi} from '../../../test/test-utils.js';
-import {PluginApi} from '../../../api/plugin.js';
-import {ReportingService} from '../../../services/gr-reporting/gr-reporting.js';
-import {ReportingPluginApi} from '../../../api/reporting.js';
+import '../../../test/common-test-setup-karma';
+import '../../change/gr-reply-dialog/gr-reply-dialog';
+import {getAppContext} from '../../../services/app-context';
+import {stubRestApi} from '../../../test/test-utils';
+import {PluginApi} from '../../../api/plugin';
+import {ReportingService} from '../../../services/gr-reporting/gr-reporting';
+import {ReportingPluginApi} from '../../../api/reporting';
 import {assert} from '@open-wc/testing';
 
 suite('gr-reporting-js-api tests', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.ts b/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.ts
index 8589214..12a40d8 100644
--- a/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.ts
@@ -5,6 +5,7 @@
  */
 import {assert} from '@open-wc/testing';
 import '../../../test/common-test-setup-karma';
+import {waitEventLoop} from '../../../test/test-utils';
 import './gr-lib-loader';
 import {GrLibLoader} from './gr-lib-loader';
 
@@ -36,12 +37,12 @@
     grLibLoader.getLibrary(libraryConfig).then(loaded2);
 
     resolveLoad();
-    await flush();
+    await waitEventLoop();
 
     const lateLoaded = sinon.stub();
     grLibLoader.getLibrary(libraryConfig).then(lateLoaded);
 
-    await flush();
+    await waitEventLoop();
 
     assert.isTrue(loaded1.calledOnce);
     assert.isTrue(loaded2.calledOnce);
@@ -58,12 +59,12 @@
     grLibLoader.getLibrary(libraryConfig).catch(failed2);
 
     rejectLoad();
-    await flush();
+    await waitEventLoop();
 
     const lateFailed = sinon.stub();
     grLibLoader.getLibrary(libraryConfig).catch(lateFailed);
 
-    await flush();
+    await waitEventLoop();
 
     assert.isTrue(failed1.calledOnce);
     assert.isTrue(failed2.calledOnce);
@@ -84,12 +85,12 @@
     grLibLoader.getLibrary(libraryConfig).then(loaded2);
 
     resolveLoad();
-    await flush();
+    await waitEventLoop();
 
     const lateLoaded = sinon.stub();
     grLibLoader.getLibrary(libraryConfig).then(lateLoaded);
 
-    await flush();
+    await waitEventLoop();
 
     assert.isTrue(configureCallback.calledOnce);
   });
@@ -110,7 +111,7 @@
 
     (window as any).library = library;
     resolveLoad();
-    await flush();
+    await waitEventLoop();
 
     assert.isTrue(loaded1.calledWith(library));
     assert.isTrue(loaded2.calledWith(library));
@@ -118,7 +119,7 @@
     const lateLoaded = sinon.stub();
     grLibLoader.getLibrary(libraryConfig).then(lateLoaded);
 
-    await flush();
+    await waitEventLoop();
 
     assert.isTrue(lateLoaded.calledWith(library));
   });
@@ -147,12 +148,12 @@
       grLibLoader.getLibrary(libraryConfig).then(loaded2);
 
       resolveLoad();
-      await flush();
+      await waitEventLoop();
 
       const lateLoaded = sinon.stub();
       grLibLoader.getLibrary(libraryConfig).then(lateLoaded);
 
-      await flush();
+      await waitEventLoop();
 
       assert.isFalse(loadStub.called);
       assert.isTrue(loaded1.called);
@@ -170,7 +171,7 @@
       grLibLoader.getLibrary(libraryConfig);
 
       resolveLoad();
-      await flush();
+      await waitEventLoop();
 
       assert.isTrue((window as any).library.initialize.calledOnce);
     });
@@ -185,7 +186,7 @@
       grLibLoader.getLibrary(libraryConfig);
 
       resolveLoad();
-      await flush();
+      await waitEventLoop();
 
       assert.isTrue(loadStub.called);
     });
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.ts b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.ts
index c79facc..bad44b5 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.ts
@@ -6,7 +6,7 @@
 import '../../../test/common-test-setup-karma';
 import './gr-linked-chip';
 import {GrLinkedChip} from './gr-linked-chip';
-import {queryAndAssert} from '../../../test/test-utils';
+import {queryAndAssert, waitEventLoop} from '../../../test/test-utils';
 import {fixture, html, assert} from '@open-wc/testing';
 import {GrButton} from '../gr-button/gr-button';
 
@@ -40,7 +40,7 @@
   test('remove fired', async () => {
     const spy = sinon.spy();
     element.addEventListener('remove', spy);
-    await flush();
+    await waitEventLoop();
     queryAndAssert<GrButton>(element, '#remove').click();
     assert.isTrue(spy.called);
   });
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.ts b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.ts
index b6e7098..18995df 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.ts
@@ -10,7 +10,7 @@
   GrRestApiHelper,
 } from './gr-rest-api-helper';
 import {getAppContext} from '../../../../services/app-context';
-import {stubAuth} from '../../../../test/test-utils';
+import {stubAuth, waitEventLoop} from '../../../../test/test-utils';
 import {FakeScheduler} from '../../../../services/scheduler/fake-scheduler';
 import {RetryScheduler} from '../../../../services/scheduler/retry-scheduler';
 import {ParsedJSON} from '../../../../types/common';
@@ -71,13 +71,13 @@
   async function assertReadRequest() {
     assert.equal(readScheduler.scheduled.length, 1);
     await readScheduler.resolve();
-    await flush();
+    await waitEventLoop();
   }
 
   async function assertWriteRequest() {
     assert.equal(writeScheduler.scheduled.length, 1);
     await writeScheduler.resolve();
-    await flush();
+    await waitEventLoop();
   }
 
   suite('send()', () => {
@@ -292,7 +292,7 @@
       );
       // Flush the retry scheduler
       clock.tick(50);
-      await flush();
+      await waitEventLoop();
       // We expect a retry.
       await assertReadRequest();
       const res: Response = await promise;
diff --git a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls_test.ts b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls_test.ts
index 4d28f0b..ee920d2 100644
--- a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls_test.ts
@@ -12,6 +12,7 @@
 import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
 import {DiffFileMetaInfo, DiffInfo, SyntaxBlock} from '../../../api/diff';
 import {fixture, html, assert} from '@open-wc/testing';
+import {waitEventLoop} from '../../../test/test-utils';
 
 suite('gr-context-control tests', () => {
   let element: GrContextControls;
@@ -22,7 +23,7 @@
     element.renderPreferences = {};
     const div = await fixture(html`<div></div>`);
     div.appendChild(element);
-    await flush();
+    await waitEventLoop();
   });
 
   function createContextGroup(options: {offset?: number; count?: number}) {
@@ -45,7 +46,7 @@
   test('no +10 buttons for 10 or less lines', async () => {
     element.group = createContextGroup({count: 10});
 
-    await flush();
+    await waitEventLoop();
 
     const buttons = element.shadowRoot!.querySelectorAll(
       'paper-button.showContext'
@@ -58,7 +59,7 @@
     element.group = createContextGroup({offset: 0, count: 20});
     element.showConfig = 'below';
 
-    await flush();
+    await waitEventLoop();
 
     const buttons = element.shadowRoot!.querySelectorAll(
       'paper-button.showContext'
@@ -76,7 +77,7 @@
     element.group = createContextGroup({offset: 10, count: 20});
     element.showConfig = 'both';
 
-    await flush();
+    await waitEventLoop();
 
     const buttons = element.shadowRoot!.querySelectorAll(
       'paper-button.showContext'
@@ -96,7 +97,7 @@
     element.group = createContextGroup({offset: 30, count: 20});
     element.showConfig = 'above';
 
-    await flush();
+    await waitEventLoop();
 
     const buttons = element.shadowRoot!.querySelectorAll(
       'paper-button.showContext'
@@ -122,7 +123,7 @@
     element.group = createContextGroup({offset: 0, count: 20});
     element.showConfig = 'below';
 
-    await flush();
+    await waitEventLoop();
 
     const fullExpansionButtons = element.shadowRoot!.querySelectorAll(
       '.fullExpansion paper-button'
@@ -151,7 +152,7 @@
     element.group = createContextGroup({offset: 10, count: 20});
     element.showConfig = 'both';
 
-    await flush();
+    await waitEventLoop();
 
     const fullExpansionButtons = element.shadowRoot!.querySelectorAll(
       '.fullExpansion paper-button'
@@ -188,7 +189,7 @@
     element.group = createContextGroup({offset: 30, count: 20});
     element.showConfig = 'above';
 
-    await flush();
+    await waitEventLoop();
 
     const fullExpansionButtons = element.shadowRoot!.querySelectorAll(
       '.fullExpansion paper-button'
@@ -228,7 +229,7 @@
     element.group = createContextGroup({offset: 10, count: 20});
     element.showConfig = 'both';
 
-    await flush();
+    await waitEventLoop();
 
     const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
       '.blockExpansion paper-button'
@@ -280,7 +281,7 @@
     element.group = createContextGroup({offset: 10, count: 20});
     element.showConfig = 'both';
 
-    await flush();
+    await waitEventLoop();
 
     const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
       '.blockExpansion paper-button'
@@ -325,7 +326,7 @@
     ]);
     element.group = createContextGroup({offset: 10, count: 20});
     element.showConfig = 'both';
-    await flush();
+    await waitEventLoop();
 
     const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
       '.blockExpansion paper-button'
@@ -343,7 +344,7 @@
 
     element.group = createContextGroup({offset: 10, count: 20});
     element.showConfig = 'both';
-    await flush();
+    await waitEventLoop();
 
     const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
       '.blockExpansion paper-button'
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer_test.ts
index c0812b7..9bcc7f4 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer_test.ts
@@ -5,7 +5,7 @@
  */
 import '../../../test/common-test-setup-karma';
 import {Side, TokenHighlightEventDetails} from '../../../api/diff';
-import {GrDiffLine, GrDiffLineType} from '../gr-diff/gr-diff-line.js';
+import {GrDiffLine, GrDiffLineType} from '../gr-diff/gr-diff-line';
 import {HOVER_DELAY_MS, TokenHighlightLayer} from './token-highlight-layer';
 import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
 import {html, render} from 'lit';
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts
index 72c52b6..ff3bc10 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts
@@ -24,6 +24,7 @@
   query,
   queryAll,
   queryAndAssert,
+  waitEventLoop,
   waitQueryAndAssert,
   waitUntil,
 } from '../../../test/test-utils';
@@ -1121,7 +1122,7 @@
         },
       ];
       await setupSampleDiff({content});
-      flush();
+      await waitEventLoop();
 
       const diffLine = queryAll<HTMLElement>(element, '.contentText')[2];
       assert.equal(getComputedStyle(diffLine).userSelect, 'none');
diff --git a/polygerrit-ui/app/embed/diff/gr-ranged-comment-hint/gr-ranged-comment-hint_test.ts b/polygerrit-ui/app/embed/diff/gr-ranged-comment-hint/gr-ranged-comment-hint_test.ts
index f0890c3..7beee0c 100644
--- a/polygerrit-ui/app/embed/diff/gr-ranged-comment-hint/gr-ranged-comment-hint_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-ranged-comment-hint/gr-ranged-comment-hint_test.ts
@@ -7,7 +7,7 @@
 import './gr-ranged-comment-hint';
 import {CommentRange} from '../../../types/common';
 import {GrRangedCommentHint} from './gr-ranged-comment-hint';
-import {queryAndAssert} from '../../../test/test-utils';
+import {queryAndAssert, waitEventLoop} from '../../../test/test-utils';
 import {GrRangeHeader} from '../gr-range-header/gr-range-header';
 import {fixture, html, assert} from '@open-wc/testing';
 
@@ -18,7 +18,7 @@
     element = await fixture(
       html`<gr-ranged-comment-hint></gr-ranged-comment-hint>`
     );
-    await flush();
+    await waitEventLoop();
   });
 
   test('shows line range', async () => {
@@ -28,7 +28,7 @@
       end_line: 5,
       end_character: 3,
     } as CommentRange;
-    await flush();
+    await waitEventLoop();
     const textDiv = queryAndAssert<GrRangeHeader>(element, 'gr-range-header');
     assert.equal(textDiv?.innerText.trim(), 'Long comment range 2 - 5');
   });
diff --git a/polygerrit-ui/app/mixins/hovercard-mixin/hovercard-mixin_test.ts b/polygerrit-ui/app/mixins/hovercard-mixin/hovercard-mixin_test.ts
index 239e2a2..706c427 100644
--- a/polygerrit-ui/app/mixins/hovercard-mixin/hovercard-mixin_test.ts
+++ b/polygerrit-ui/app/mixins/hovercard-mixin/hovercard-mixin_test.ts
@@ -7,7 +7,12 @@
 import {HovercardMixin} from './hovercard-mixin';
 import {LitElement} from 'lit';
 import {customElement} from 'lit/decorators.js';
-import {MockPromise, mockPromise, pressKey} from '../../test/test-utils';
+import {
+  MockPromise,
+  mockPromise,
+  pressKey,
+  waitEventLoop,
+} from '../../test/test-utils';
 import {findActiveElement, Key} from '../../utils/dom-util';
 import {fixture, html, assert} from '@open-wc/testing';
 
@@ -132,7 +137,7 @@
     button!.dispatchEvent(new CustomEvent('mousemove'));
 
     await enterPromise;
-    await flush();
+    await waitEventLoop();
     assert.isTrue(element.isScheduledToShow);
     element.showTask!.flush();
     assert.isTrue(element._isShowing);
@@ -160,7 +165,7 @@
     button!.dispatchEvent(new CustomEvent('mousemove'));
 
     await enterPromise;
-    await flush();
+    await waitEventLoop();
     assert.isTrue(element.isScheduledToShow);
     button!.click();
 
diff --git a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts
index b1bfa26..efdfded 100644
--- a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts
+++ b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts
@@ -24,7 +24,11 @@
 import {BulkActionsModel, LoadingState} from './bulk-actions-model';
 import {getAppContext} from '../../services/app-context';
 import '../../test/common-test-setup-karma';
-import {stubRestApi, waitUntilObserved} from '../../test/test-utils';
+import {
+  stubRestApi,
+  waitEventLoop,
+  waitUntilObserved,
+} from '../../test/test-utils';
 import {mockPromise} from '../../test/test-utils';
 import {SinonStubbedMember} from 'sinon';
 import {RestApiService} from '../../services/gr-rest-api/gr-rest-api';
@@ -587,7 +591,7 @@
       {...createChange(), _number: 1, subject: 'Subject 1-old'},
       {...createChange(), _number: 2, subject: 'Subject 2-old'},
     ] as ChangeInfo[]);
-    await flush();
+    await waitEventLoop();
     const model2 = bulkActionsModel.getState();
 
     // No change should happen.
diff --git a/polygerrit-ui/app/models/dependency_test.ts b/polygerrit-ui/app/models/dependency_test.ts
index fc6b13d..8f6b6e1 100644
--- a/polygerrit-ui/app/models/dependency_test.ts
+++ b/polygerrit-ui/app/models/dependency_test.ts
@@ -6,7 +6,7 @@
 import {define, provide, resolve} from './dependency';
 import {html, LitElement} from 'lit';
 import {customElement, property, query} from 'lit/decorators.js';
-import '../test/common-test-setup-karma.js';
+import '../test/common-test-setup-karma';
 import {fixture, assert} from '@open-wc/testing';
 
 interface FooService {
diff --git a/polygerrit-ui/app/models/di-provider-element_test.ts b/polygerrit-ui/app/models/di-provider-element_test.ts
index 501e45c..4e2eb31 100644
--- a/polygerrit-ui/app/models/di-provider-element_test.ts
+++ b/polygerrit-ui/app/models/di-provider-element_test.ts
@@ -6,7 +6,7 @@
 import {html, LitElement} from 'lit';
 import {customElement, state} from 'lit/decorators.js';
 import {define, resolve} from './dependency';
-import '../test/common-test-setup-karma.js';
+import '../test/common-test-setup-karma';
 import {fixture, assert} from '@open-wc/testing';
 import {DIProviderElement, wrapInProvider} from './di-provider-element';
 import {BehaviorSubject} from 'rxjs';
diff --git a/polygerrit-ui/app/services/app-context-init_test.ts b/polygerrit-ui/app/services/app-context-init_test.ts
index 24b559a..d29ee8e 100644
--- a/polygerrit-ui/app/services/app-context-init_test.ts
+++ b/polygerrit-ui/app/services/app-context-init_test.ts
@@ -3,10 +3,10 @@
  * Copyright 2020 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import '../test/common-test-setup-karma.js';
-import {AppContext} from './app-context.js';
+import '../test/common-test-setup-karma';
+import {AppContext} from './app-context';
 import {Finalizable} from './registry';
-import {createTestAppContext} from '../test/test-app-context-init.js';
+import {createTestAppContext} from '../test/test-app-context-init';
 import {assert} from '@open-wc/testing';
 
 suite('app context initializer tests', () => {
diff --git a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.js b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.js
index 80dccb0..f2e9e01 100644
--- a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.js
+++ b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.js
@@ -8,6 +8,7 @@
   addListenerForTest,
   mockPromise,
   stubAuth,
+  waitEventLoop,
 } from '../../test/test-utils';
 import {GrReviewerUpdatesParser} from '../../elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser';
 import {
@@ -1431,8 +1432,8 @@
     sinon.stub(element, '_changeBaseURL').returns(Promise.resolve(''));
     return element
         .getFileContent('1', 'tst/path', '1')
+        .then(() => waitEventLoop())
         .then(() => {
-          flush();
           assert.isFalse(spy.called);
 
           res.status = 500;
@@ -1479,7 +1480,7 @@
     });
   });
 
-  test('_logCall only reports requests with anonymized URLss', () => {
+  test('_logCall only reports requests with anonymized URLss', async () => {
     sinon.stub(Date, 'now').returns(200);
     const handler = sinon.stub();
     addListenerForTest(document, 'gr-rpc-log', handler);
@@ -1492,7 +1493,7 @@
         100,
         200
     );
-    flush();
+    await waitEventLoop();
     assert.isTrue(handler.calledOnce);
   });
 
diff --git a/polygerrit-ui/app/services/registry_test.ts b/polygerrit-ui/app/services/registry_test.ts
index 290855d..1a1ddcf 100644
--- a/polygerrit-ui/app/services/registry_test.ts
+++ b/polygerrit-ui/app/services/registry_test.ts
@@ -4,7 +4,7 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 import {create, Finalizable, Registry} from './registry';
-import '../test/common-test-setup-karma.js';
+import '../test/common-test-setup-karma';
 import {assert} from '@open-wc/testing';
 
 class Foo implements Finalizable {
diff --git a/polygerrit-ui/app/services/scheduler/fake-scheduler_test.ts b/polygerrit-ui/app/services/scheduler/fake-scheduler_test.ts
index 166f18f..5223fc7 100644
--- a/polygerrit-ui/app/services/scheduler/fake-scheduler_test.ts
+++ b/polygerrit-ui/app/services/scheduler/fake-scheduler_test.ts
@@ -4,8 +4,8 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 import {assert} from '@open-wc/testing';
-import '../../test/common-test-setup-karma.js';
-import {assertFails} from '../../test/test-utils.js';
+import '../../test/common-test-setup-karma';
+import {assertFails} from '../../test/test-utils';
 import {FakeScheduler} from './fake-scheduler';
 
 suite('fake scheduler', () => {
diff --git a/polygerrit-ui/app/services/scheduler/max-in-flight-scheduler_test.ts b/polygerrit-ui/app/services/scheduler/max-in-flight-scheduler_test.ts
index 53d92ca..0f24eda 100644
--- a/polygerrit-ui/app/services/scheduler/max-in-flight-scheduler_test.ts
+++ b/polygerrit-ui/app/services/scheduler/max-in-flight-scheduler_test.ts
@@ -3,8 +3,8 @@
  * Copyright 2022 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import '../../test/common-test-setup-karma.js';
-import {assertFails} from '../../test/test-utils.js';
+import '../../test/common-test-setup-karma';
+import {assertFails, waitEventLoop} from '../../test/test-utils';
 import {Scheduler} from './scheduler';
 import {MaxInFlightScheduler} from './max-in-flight-scheduler';
 import {FakeScheduler} from './fake-scheduler';
@@ -67,7 +67,7 @@
     assert.equal(fakeScheduler.scheduled.length, 2);
     fakeScheduler.resolve();
     assert.equal(fakeScheduler.scheduled.length, 1);
-    await flush();
+    await waitEventLoop();
     assert.equal(fakeScheduler.scheduled.length, 2);
   });
 
@@ -78,7 +78,7 @@
     assert.equal(fakeScheduler.scheduled.length, 2);
     fakeScheduler.reject(new Error('Fake Error'));
     assert.equal(fakeScheduler.scheduled.length, 1);
-    await flush();
+    await waitEventLoop();
     assert.equal(fakeScheduler.scheduled.length, 2);
   });
 
@@ -89,7 +89,7 @@
     }
     for (let i = 0; i < 3; ++i) {
       fakeScheduler.resolve();
-      await flush();
+      await waitEventLoop();
     }
     const res = await Promise.all(promises);
     assert.deepEqual(res, [0, 1, 2]);
diff --git a/polygerrit-ui/app/services/scheduler/retry-scheduler_test.ts b/polygerrit-ui/app/services/scheduler/retry-scheduler_test.ts
index d1877b6..22f7dd5 100644
--- a/polygerrit-ui/app/services/scheduler/retry-scheduler_test.ts
+++ b/polygerrit-ui/app/services/scheduler/retry-scheduler_test.ts
@@ -3,8 +3,8 @@
  * Copyright 2022 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import '../../test/common-test-setup-karma.js';
-import {assertFails} from '../../test/test-utils.js';
+import '../../test/common-test-setup-karma';
+import {assertFails, waitEventLoop} from '../../test/test-utils';
 import {Scheduler} from './scheduler';
 import {RetryScheduler, RetryError} from './retry-scheduler';
 import {FakeScheduler} from './fake-scheduler';
@@ -23,11 +23,11 @@
 
   async function waitForRetry(ms: number) {
     // Flush the promise so that we can reach untilTimeout
-    await flush();
+    await waitEventLoop();
     // Advance the clock.
     clock.tick(ms);
     // Flush the promise that waits for the clock.
-    await flush();
+    await waitEventLoop();
   }
 
   test('executes tasks', async () => {
diff --git a/polygerrit-ui/app/services/scheduler/scheduler_test.ts b/polygerrit-ui/app/services/scheduler/scheduler_test.ts
index 2ab75c1..7deb916 100644
--- a/polygerrit-ui/app/services/scheduler/scheduler_test.ts
+++ b/polygerrit-ui/app/services/scheduler/scheduler_test.ts
@@ -4,8 +4,8 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 import {assert} from '@open-wc/testing';
-import '../../test/common-test-setup-karma.js';
-import {assertFails} from '../../test/test-utils.js';
+import '../../test/common-test-setup-karma';
+import {assertFails} from '../../test/test-utils';
 import {BaseScheduler} from './scheduler';
 
 suite('naive scheduler', () => {
diff --git a/polygerrit-ui/app/test/common-test-setup-karma.ts b/polygerrit-ui/app/test/common-test-setup-karma.ts
index eff2610..321dd5b 100644
--- a/polygerrit-ui/app/test/common-test-setup-karma.ts
+++ b/polygerrit-ui/app/test/common-test-setup-karma.ts
@@ -4,14 +4,11 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 import {testResolver as testResolverImpl} from './common-test-setup';
-import {flush} from '@polymer/polymer/lib/utils/flush';
 
 declare global {
   interface Window {
-    flush: typeof flushImpl;
     testResolver: typeof testResolverImpl;
   }
-  let flush: typeof flushImpl;
   let testResolver: typeof testResolverImpl;
 }
 
@@ -60,34 +57,4 @@
   }
 });
 
-// Tests can use fake timers (sandbox.useFakeTimers)
-// Keep the original one for use in test utils methods.
-const nativeSetTimeout = window.setTimeout;
-
-function flushImpl(): Promise<void>;
-function flushImpl(callback: () => void): void;
-/**
- * Triggers a flush of any pending events, observations, etc and calls you back
- * after they have been processed if callback is passed; otherwise returns
- * promise.
- */
-function flushImpl(callback?: () => void): Promise<void> | void {
-  // Ideally, this function would be a call to Polymer.dom.flush, but that
-  // doesn't support a callback yet
-  // (https://github.com/Polymer/polymer-dev/issues/851)
-  // The type is used only in one place, disable eslint warning instead of
-  // creating an interface
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  flush();
-  if (callback) {
-    nativeSetTimeout(callback, 0);
-  } else {
-    return new Promise(resolve => {
-      nativeSetTimeout(resolve, 0);
-    });
-  }
-}
-
-self.flush = flushImpl;
-
 window.testResolver = testResolverImpl;
diff --git a/polygerrit-ui/app/test/test-utils.ts b/polygerrit-ui/app/test/test-utils.ts
index 47c21cc..f4c0091 100644
--- a/polygerrit-ui/app/test/test-utils.ts
+++ b/polygerrit-ui/app/test/test-utils.ts
@@ -246,6 +246,20 @@
 }
 
 /**
+ * sinon.useFakeTimers() overwrites window.setTimeout with a controlled,
+ * synchronous version for tests to use. Keep the original one for use in
+ * waitEventLoop
+ */
+const nativeSetTimeout = window.setTimeout;
+/**
+ * Wait for the current event loop's tasks to complete by scheduling a promise
+ * to resolve during the next loop. Prefer other wait methods over this one to
+ * wait for specific work to be done or for specific states to exist.
+ */
+export function waitEventLoop(): Promise<void> {
+  return new Promise(resolve => nativeSetTimeout(resolve, 0));
+}
+/**
  * Promisify an event callback to simplify async...await tests.
  *
  * Use like this:
diff --git a/polygerrit-ui/app/utils/async-util_test.ts b/polygerrit-ui/app/utils/async-util_test.ts
index aa4b5fc..356080c 100644
--- a/polygerrit-ui/app/utils/async-util_test.ts
+++ b/polygerrit-ui/app/utils/async-util_test.ts
@@ -6,6 +6,7 @@
 import {assert} from '@open-wc/testing';
 import {SinonFakeTimers} from 'sinon';
 import '../test/common-test-setup-karma';
+import {waitEventLoop} from '../test/test-utils';
 import {asyncForeach, debounceP} from './async-util';
 
 suite('async-util tests', () => {
@@ -56,18 +57,18 @@
       promise.catch((_reason?: any) => {
         assert.fail();
       });
-      await flush();
+      await waitEventLoop();
       assert.isFalse(hasResolved);
       clock.tick(99);
-      await flush();
+      await waitEventLoop();
       assert.isFalse(hasResolved);
       clock.tick(1);
-      await flush();
+      await waitEventLoop();
       assert.isTrue(hasResolved);
       await promise;
       // Shouldn't do anything.
       promise.cancel();
-      await flush();
+      await waitEventLoop();
     });
 
     test('It resolves immediately on flush and finalizes', async () => {
@@ -85,11 +86,11 @@
         assert.fail();
       });
       promise.flush();
-      await flush();
+      await waitEventLoop();
       assert.isTrue(hasResolved);
       // Shouldn't do anything.
       promise.cancel();
-      await flush();
+      await waitEventLoop();
     });
 
     test('It rejects on cancel', async () => {
@@ -106,14 +107,14 @@
         hasCanceled = true;
         assert.strictEqual(reason, 'because');
       });
-      await flush();
+      await waitEventLoop();
       assert.isFalse(hasCanceled);
       promise.cancel('because');
-      await flush();
+      await waitEventLoop();
       assert.isTrue(hasCanceled);
       // Shouldn't do anything.
       promise.flush();
-      await flush();
+      await waitEventLoop();
     });
 
     test('It delegates correctly', async () => {
@@ -130,10 +131,10 @@
       promise1.catch((_reason?: any) => {
         assert.fail();
       });
-      await flush();
+      await waitEventLoop();
       assert.isFalse(hasResolved1);
       clock.tick(99);
-      await flush();
+      await waitEventLoop();
       const promise2 = debounceP<number>(
         promise1,
         () => Promise.resolve(6),
@@ -148,16 +149,16 @@
         assert.fail();
       });
       clock.tick(99);
-      await flush();
+      await waitEventLoop();
       assert.isFalse(hasResolved1);
       assert.isFalse(hasResolved2);
       clock.tick(2);
-      await flush();
+      await waitEventLoop();
       assert.isTrue(hasResolved1);
       assert.isTrue(hasResolved2);
       // Shouldn't do anything.
       promise1.cancel();
-      await flush();
+      await waitEventLoop();
     });
 
     test('It does not delegate after timeout', async () => {
@@ -174,10 +175,10 @@
       promise1.catch((_reason?: any) => {
         assert.fail();
       });
-      await flush();
+      await waitEventLoop();
       assert.isFalse(hasResolved1);
       clock.tick(100);
-      await flush();
+      await waitEventLoop();
       assert.isTrue(hasResolved1);
 
       const promise2 = debounceP<number>(
@@ -194,14 +195,14 @@
         assert.fail();
       });
       clock.tick(99);
-      await flush();
+      await waitEventLoop();
       assert.isFalse(hasResolved2);
       clock.tick(1);
-      await flush();
+      await waitEventLoop();
       assert.isTrue(hasResolved2);
       // Shouldn't do anything.
       promise1.cancel();
-      await flush();
+      await waitEventLoop();
     });
   });
 });