diff --git a/enterprise/frontend/src/metabase-enterprise/auth/components/SettingsSAMLForm.jsx b/enterprise/frontend/src/metabase-enterprise/auth/components/SettingsSAMLForm.jsx
index bdd4f183af4587ee121bccbac6c21728425a18e1..1aa16b434a52e0129b853b13068586416861614f 100644
--- a/enterprise/frontend/src/metabase-enterprise/auth/components/SettingsSAMLForm.jsx
+++ b/enterprise/frontend/src/metabase-enterprise/auth/components/SettingsSAMLForm.jsx
@@ -12,7 +12,7 @@ import Form, {
   FormSubmit,
   FormMessage,
   FormSection,
-} from "metabase/containers/Form";
+} from "metabase/containers/FormikForm";
 
 import Breadcrumbs from "metabase/components/Breadcrumbs";
 import CopyWidget from "metabase/components/CopyWidget";
diff --git a/frontend/src/metabase/admin/settings/components/SettingsGoogleForm.jsx b/frontend/src/metabase/admin/settings/components/SettingsGoogleForm.jsx
index 25a681a45b0d5283c5c0b2460045b6e8009c2fa5..481e370ba346f4f77ed20892b07864ddbbb41853 100644
--- a/frontend/src/metabase/admin/settings/components/SettingsGoogleForm.jsx
+++ b/frontend/src/metabase/admin/settings/components/SettingsGoogleForm.jsx
@@ -8,7 +8,7 @@ import Form, {
   FormField,
   FormSubmit,
   FormMessage,
-} from "metabase/containers/Form";
+} from "metabase/containers/FormikForm";
 
 import { updateSettings } from "metabase/admin/settings/settings";
 import { settingToFormField } from "metabase/admin/settings/utils";
@@ -38,6 +38,7 @@ class SettingsGoogleForm extends Component {
         style={{ maxWidth: 520 }}
         initialValues={initialValues}
         onSubmit={updateSettings}
+        overwriteOnInitialValuesChange
       >
         <Breadcrumbs
           crumbs={[
diff --git a/frontend/src/metabase/admin/settings/slack/components/SlackSetupForm/SlackSetupForm.tsx b/frontend/src/metabase/admin/settings/slack/components/SlackSetupForm/SlackSetupForm.tsx
index c439a5317c2331a1afac50eaa9e9df1a691b6888..a8f7fd68750b2c2e5e8e3f71dacf8a683eee6df4 100644
--- a/frontend/src/metabase/admin/settings/slack/components/SlackSetupForm/SlackSetupForm.tsx
+++ b/frontend/src/metabase/admin/settings/slack/components/SlackSetupForm/SlackSetupForm.tsx
@@ -1,6 +1,6 @@
 import React, { useMemo } from "react";
 import { t } from "ttag";
-import Form from "metabase/containers/Form";
+import Form from "metabase/containers/FormikForm";
 import { SlackSettings } from "metabase-types/api";
 import { getSlackForm } from "../../forms";
 import { FormProps } from "./types";
diff --git a/frontend/src/metabase/admin/settings/slack/components/SlackStatusForm/SlackStatusForm.tsx b/frontend/src/metabase/admin/settings/slack/components/SlackStatusForm/SlackStatusForm.tsx
index eb1d2620b399a6421fe9a036ed3556fce114a4bc..5fc02da7573119846b89c92d6453dad36065b20d 100644
--- a/frontend/src/metabase/admin/settings/slack/components/SlackStatusForm/SlackStatusForm.tsx
+++ b/frontend/src/metabase/admin/settings/slack/components/SlackStatusForm/SlackStatusForm.tsx
@@ -1,5 +1,5 @@
 import React, { useCallback, useMemo } from "react";
-import Form from "metabase/containers/Form";
+import Form from "metabase/containers/FormikForm";
 import { SlackSettings } from "metabase-types/api";
 import { getSlackForm } from "../../forms";
 import { FormProps } from "./types";
@@ -13,7 +13,11 @@ const SlackStatusForm = ({ settings }: SlackStatusFormProps): JSX.Element => {
   const onSubmit = useCallback(() => undefined, []);
 
   return (
-    <Form form={form} initialValues={settings} onSubmit={onSubmit}>
+    <Form<SlackSettings>
+      form={form}
+      initialValues={settings}
+      onSubmit={onSubmit}
+    >
       {({ Form, FormField }: FormProps) => (
         <Form>
           <FormField name="slack-app-token" />
diff --git a/frontend/src/metabase/admin/settings/slack/forms.ts b/frontend/src/metabase/admin/settings/slack/forms.ts
index 35bab5bea5a9d2290b8b511906ef8ab624e3dc25..e4f83df2741728d10309bb632a5181d33df10779 100644
--- a/frontend/src/metabase/admin/settings/slack/forms.ts
+++ b/frontend/src/metabase/admin/settings/slack/forms.ts
@@ -1,6 +1,10 @@
 import { t } from "ttag";
+import { SlackSettings } from "metabase-types/api";
+import { FormObject } from "metabase-types/forms";
 
-export const getSlackForm = (readOnly?: boolean) => ({
+export const getSlackForm = (
+  readOnly?: boolean,
+): FormObject<SlackSettings> => ({
   fields: [
     {
       name: "slack-app-token",
diff --git a/frontend/src/metabase/containers/SaveQuestionModal.jsx b/frontend/src/metabase/containers/SaveQuestionModal.jsx
index 0d1eefceb143b97ed477ed2dcc59d6a5c9c1ab13..2bd19af3f8b46a8c85e016c94b97beade241ea15 100644
--- a/frontend/src/metabase/containers/SaveQuestionModal.jsx
+++ b/frontend/src/metabase/containers/SaveQuestionModal.jsx
@@ -4,7 +4,7 @@ import PropTypes from "prop-types";
 import { CSSTransitionGroup } from "react-transition-group";
 import { t } from "ttag";
 
-import Form, { FormField, FormFooter } from "metabase/containers/Form";
+import Form, { FormField, FormFooter } from "metabase/containers/FormikForm";
 import ModalContent from "metabase/components/ModalContent";
 import Radio from "metabase/core/components/Radio";
 import * as Q_DEPRECATED from "metabase/lib/query";
@@ -25,8 +25,8 @@ export default class SaveQuestionModal extends Component {
     multiStep: PropTypes.bool,
   };
 
-  validateName = (name, context) => {
-    if (context.form.saveType.value !== "overwrite") {
+  validateName = (name, { values }) => {
+    if (values.saveType !== "overwrite") {
       // We don't care if the form is valid when overwrite mode is enabled,
       // as original question's data will be submitted instead of the form values
       return validate.required()(name);
diff --git a/frontend/src/metabase/entities/questions/forms.js b/frontend/src/metabase/entities/questions/forms.js
index a147f00255d392450b9538a2c17f34c81de058c4..748952f403b57a1b22052b8b939f97ab71005eed 100644
--- a/frontend/src/metabase/entities/questions/forms.js
+++ b/frontend/src/metabase/entities/questions/forms.js
@@ -1,9 +1,12 @@
 import { t } from "ttag";
+
 import MetabaseSettings from "metabase/lib/settings";
+import validate from "metabase/lib/validate";
+
 import { PLUGIN_CACHING } from "metabase/plugins";
 
 const FORM_FIELDS = [
-  { name: "name", title: t`Name` },
+  { name: "name", title: t`Name`, validate: validate.required() },
   {
     name: "description",
     title: t`Description`,
diff --git a/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetFieldMetadataSidebar/DatasetFieldMetadataSidebar.jsx b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetFieldMetadataSidebar/DatasetFieldMetadataSidebar.jsx
index b6222c5c92df0727fcd71e104d7712de7bad949f..ad261db91904af8fc4ac0e2415bd7e3b2e51c001 100644
--- a/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetFieldMetadataSidebar/DatasetFieldMetadataSidebar.jsx
+++ b/frontend/src/metabase/query_builder/components/DatasetEditor/DatasetFieldMetadataSidebar/DatasetFieldMetadataSidebar.jsx
@@ -18,7 +18,7 @@ import {
 import { isLocalField, isSameField } from "metabase/lib/query/field_ref";
 import { isFK, getSemanticTypeIcon } from "metabase/lib/schema_metadata";
 
-import RootForm from "metabase/containers/Form";
+import RootForm from "metabase/containers/FormikForm";
 import { usePrevious } from "metabase/hooks/use-previous";
 
 import SidebarContent from "metabase/query_builder/components/SidebarContent";
@@ -76,7 +76,7 @@ function getFormFields({ dataset }) {
       value: type.id,
     }));
 
-  return fieldFormValues =>
+  return formFieldValues =>
     [
       { name: "display_name", title: t`Display name` },
       {
@@ -96,11 +96,11 @@ function getFormFields({ dataset }) {
         title: t`Column type`,
         widget: SemanticTypePicker,
         options: getSemanticTypeOptions(),
-        icon: getSemanticTypeIcon(fieldFormValues.semantic_type, "ellipsis"),
+        icon: getSemanticTypeIcon(formFieldValues?.semantic_type, "ellipsis"),
       },
       {
         name: "fk_target_field_id",
-        hidden: !isFK(fieldFormValues),
+        hidden: !isFK(formFieldValues),
         widget: FKTargetPicker,
         databaseId: dataset.databaseId(),
       },
diff --git a/frontend/src/metabase/setup/components/NewsletterForm/NewsletterForm.tsx b/frontend/src/metabase/setup/components/NewsletterForm/NewsletterForm.tsx
index 5f8ead6136ce5c0b74b832d7df1a5e810704d74d..6a8b52c8fb0951040d4fe450b01d20b88bd8437e 100644
--- a/frontend/src/metabase/setup/components/NewsletterForm/NewsletterForm.tsx
+++ b/frontend/src/metabase/setup/components/NewsletterForm/NewsletterForm.tsx
@@ -1,7 +1,7 @@
 import React, { useState } from "react";
 import { t } from "ttag";
 import Users from "metabase/entities/users";
-import Form from "metabase/containers/Form";
+import Form from "metabase/containers/FormikForm";
 import { SubscribeInfo } from "metabase-types/store";
 import {
   FormContainer,
@@ -47,7 +47,7 @@ const NewsletterForm = ({
         {t`Get infrequent emails about new releases and feature updates.`}
       </FormHeader>
       {!isSubscribed && (
-        <Form
+        <Form<{ email: string }>
           form={Users.forms.newsletter}
           initialValues={initialValues}
           submitTitle={t`Subscribe`}
diff --git a/frontend/src/metabase/setup/components/NewsletterForm/NewsletterForm.unit.spec.tsx b/frontend/src/metabase/setup/components/NewsletterForm/NewsletterForm.unit.spec.tsx
index 326786a1567ab4f65bbe1c26b506bf3e089cc464..72e3d88017e5360f71c63ebbd137aef9ce8f3337 100644
--- a/frontend/src/metabase/setup/components/NewsletterForm/NewsletterForm.unit.spec.tsx
+++ b/frontend/src/metabase/setup/components/NewsletterForm/NewsletterForm.unit.spec.tsx
@@ -9,7 +9,7 @@ const FormMock = (props: FormHTMLAttributes<HTMLFormElement>) => (
   </form>
 );
 
-jest.mock("metabase/containers/Form", () => FormMock);
+jest.mock("metabase/containers/FormikForm", () => FormMock);
 
 jest.mock("metabase/entities/users", () => ({
   forms: { newsletter: jest.fn() },
diff --git a/frontend/src/metabase/timelines/common/components/EditEventModal/EditEventModal.tsx b/frontend/src/metabase/timelines/common/components/EditEventModal/EditEventModal.tsx
index 6df46fadbe1136e1ada865905323cdd64d309fa6..160fccfebce532e7da9f91f37dde54b16706a403 100644
--- a/frontend/src/metabase/timelines/common/components/EditEventModal/EditEventModal.tsx
+++ b/frontend/src/metabase/timelines/common/components/EditEventModal/EditEventModal.tsx
@@ -1,6 +1,6 @@
 import React, { useCallback, useMemo } from "react";
 import { t } from "ttag";
-import Form from "metabase/containers/Form";
+import Form from "metabase/containers/FormikForm";
 import forms from "metabase/entities/timeline-events/forms";
 import { Timeline, TimelineEvent } from "metabase-types/api";
 import ModalBody from "../ModalBody";
@@ -47,7 +47,7 @@ const EditEventModal = ({
     <div>
       <ModalHeader title={t`Edit event`} onClose={onClose} />
       <ModalBody>
-        <Form
+        <Form<TimelineEvent>
           form={form}
           initialValues={event}
           isModal={true}
diff --git a/frontend/src/metabase/timelines/common/components/EditEventModal/EditEventModal.unit.spec.tsx b/frontend/src/metabase/timelines/common/components/EditEventModal/EditEventModal.unit.spec.tsx
index 8bad3d3ed005155474c5e86d23490ccf9c6f57ee..4bbc5d3cdd925c68c5d65c4f04d08f83ae63403d 100644
--- a/frontend/src/metabase/timelines/common/components/EditEventModal/EditEventModal.unit.spec.tsx
+++ b/frontend/src/metabase/timelines/common/components/EditEventModal/EditEventModal.unit.spec.tsx
@@ -13,7 +13,7 @@ const FormMock = (props: FormHTMLAttributes<HTMLFormElement>) => (
   </form>
 );
 
-jest.mock("metabase/containers/Form", () => FormMock);
+jest.mock("metabase/containers/FormikForm", () => FormMock);
 
 describe("EditEventModal", () => {
   it("should submit modal", () => {
diff --git a/frontend/src/metabase/timelines/common/components/EditTimelineModal/EditTimelineModal.tsx b/frontend/src/metabase/timelines/common/components/EditTimelineModal/EditTimelineModal.tsx
index ddf898d621e9eeb9e00aa515275157e2f9110d53..d2b667cb333553e8f0acb8aaf25519c02b478792 100644
--- a/frontend/src/metabase/timelines/common/components/EditTimelineModal/EditTimelineModal.tsx
+++ b/frontend/src/metabase/timelines/common/components/EditTimelineModal/EditTimelineModal.tsx
@@ -1,6 +1,6 @@
 import React, { useCallback, useMemo } from "react";
 import { t } from "ttag";
-import Form from "metabase/containers/Form";
+import Form from "metabase/containers/FormikForm";
 import forms from "metabase/entities/timelines/forms";
 import { Timeline } from "metabase-types/api";
 import ModalBody from "../ModalBody";
diff --git a/frontend/src/metabase/timelines/common/components/EditTimelineModal/EditTimelineModal.unit.spec.tsx b/frontend/src/metabase/timelines/common/components/EditTimelineModal/EditTimelineModal.unit.spec.tsx
index 6a79e56ea25bdbf8627bf8ea327f8c67d289ed0b..1922da2da09b806f3275eb76e48d4ca5c35e18e4 100644
--- a/frontend/src/metabase/timelines/common/components/EditTimelineModal/EditTimelineModal.unit.spec.tsx
+++ b/frontend/src/metabase/timelines/common/components/EditTimelineModal/EditTimelineModal.unit.spec.tsx
@@ -10,7 +10,7 @@ const FormMock = (props: FormHTMLAttributes<HTMLFormElement>) => (
   </form>
 );
 
-jest.mock("metabase/containers/Form", () => FormMock);
+jest.mock("metabase/containers/FormikForm", () => FormMock);
 
 describe("EditTimelineModal", () => {
   it("should submit modal", () => {
diff --git a/frontend/src/metabase/timelines/common/components/NewEventModal/NewEventModal.tsx b/frontend/src/metabase/timelines/common/components/NewEventModal/NewEventModal.tsx
index 41c8fb917085be02ca7d21d257c99d56517a6e6d..f431c5f2abb1f34feb2a1fe81ce47bd0508ec405 100644
--- a/frontend/src/metabase/timelines/common/components/NewEventModal/NewEventModal.tsx
+++ b/frontend/src/metabase/timelines/common/components/NewEventModal/NewEventModal.tsx
@@ -2,7 +2,7 @@ import React, { useCallback, useMemo } from "react";
 import { t } from "ttag";
 import { getDefaultTimezone } from "metabase/lib/time";
 import { getDefaultTimelineIcon } from "metabase/lib/timelines";
-import Form from "metabase/containers/Form";
+import Form from "metabase/containers/FormikForm";
 import forms from "metabase/entities/timeline-events/forms";
 import {
   Collection,
@@ -51,7 +51,7 @@ const NewEventModal = ({
     const hasOneTimeline = availableTimelines.length === 1;
 
     return {
-      timeline_id: defaultTimeline ? defaultTimeline.id : null,
+      timeline_id: defaultTimeline ? defaultTimeline.id : undefined,
       icon: hasOneTimeline ? defaultTimeline.icon : getDefaultTimelineIcon(),
       timezone: getDefaultTimezone(),
       source,
@@ -73,7 +73,7 @@ const NewEventModal = ({
     <div>
       <ModalHeader title={t`New event`} onClose={onClose} />
       <ModalBody>
-        <Form
+        <Form<Partial<TimelineEvent>>
           form={form}
           initialValues={initialValues}
           isModal={true}
diff --git a/frontend/src/metabase/timelines/common/components/NewTimelineModal/NewTimelineModal.tsx b/frontend/src/metabase/timelines/common/components/NewTimelineModal/NewTimelineModal.tsx
index a614269beb30a21a6afe0799e750b096ed872056..38ff14869839f01458241eda9cb641b2e745a314 100644
--- a/frontend/src/metabase/timelines/common/components/NewTimelineModal/NewTimelineModal.tsx
+++ b/frontend/src/metabase/timelines/common/components/NewTimelineModal/NewTimelineModal.tsx
@@ -1,6 +1,6 @@
 import React, { useCallback, useMemo } from "react";
 import { t } from "ttag";
-import Form from "metabase/containers/Form";
+import Form from "metabase/containers/FormikForm";
 import forms from "metabase/entities/timelines/forms";
 import { getDefaultTimelineIcon } from "metabase/lib/timelines";
 import { canonicalCollectionId } from "metabase/collections/utils";
diff --git a/frontend/src/metabase/timelines/common/components/NewTimelineModal/NewTimelineModal.unit.spec.tsx b/frontend/src/metabase/timelines/common/components/NewTimelineModal/NewTimelineModal.unit.spec.tsx
index 5cb844e46dcc434039810a3d44b5e6238052fabf..9213abc16b6011b12a61941f2d974ef969664ec1 100644
--- a/frontend/src/metabase/timelines/common/components/NewTimelineModal/NewTimelineModal.unit.spec.tsx
+++ b/frontend/src/metabase/timelines/common/components/NewTimelineModal/NewTimelineModal.unit.spec.tsx
@@ -10,7 +10,7 @@ const FormMock = (props: FormHTMLAttributes<HTMLFormElement>) => (
   </form>
 );
 
-jest.mock("metabase/containers/Form", () => FormMock);
+jest.mock("metabase/containers/FormikForm", () => FormMock);
 
 describe("NewTimelineModal", () => {
   it("should submit modal", () => {
diff --git a/frontend/test/metabase/containers/SaveQuestionModal.unit.spec.js b/frontend/test/metabase/containers/SaveQuestionModal.unit.spec.js
index b77615c8d7c71a1106306642206a88d806d95eac..c4014a42c29e3f8873bf2fd935dc2c20f157d573 100644
--- a/frontend/test/metabase/containers/SaveQuestionModal.unit.spec.js
+++ b/frontend/test/metabase/containers/SaveQuestionModal.unit.spec.js
@@ -1,5 +1,5 @@
 import React from "react";
-import { renderWithProviders, screen } from "__support__/ui";
+import { act, renderWithProviders, screen, waitFor } from "__support__/ui";
 import userEvent from "@testing-library/user-event";
 import mock from "xhr-mock";
 
@@ -37,7 +37,7 @@ function mockCachingEnabled(enabled = true) {
   });
 }
 
-const renderSaveQuestionModal = (question, originalQuestion) => {
+const setup = async (question, originalQuestion) => {
   const onCreateMock = jest.fn(() => Promise.resolve());
   const onSaveMock = jest.fn(() => Promise.resolve());
   const onCloseMock = jest.fn();
@@ -51,6 +51,7 @@ const renderSaveQuestionModal = (question, originalQuestion) => {
       onClose={onCloseMock}
     />,
   );
+  await waitFor(() => screen.getByRole("button", { name: "Save" }));
   return { onSaveMock, onCreateMock, onCloseMock };
 };
 
@@ -60,7 +61,7 @@ function getQuestion({
   isSaved,
   name = "Q1",
   description = "Example",
-  collection_id = 12,
+  collection_id = null,
   can_write = true,
 } = {}) {
   const extraCardParams = {};
@@ -106,20 +107,25 @@ function getDirtyQuestion(originalQuestion) {
   });
 }
 
-function fillForm({ name, description }) {
+async function fillForm({ name, description }) {
   if (name) {
     const input = screen.getByLabelText("Name");
-    userEvent.clear(input);
-    userEvent.type(input, name);
+    await userEvent.clear(input);
+    await userEvent.type(input, name);
   }
   if (description) {
     const input = screen.getByLabelText("Description");
-    userEvent.clear(input);
-    userEvent.type(input, description);
+    await userEvent.clear(input);
+    await userEvent.type(input, description);
   }
 }
 
 describe("SaveQuestionModal", () => {
+  beforeAll(() => {
+    console.error = jest.fn();
+    console.warn = jest.fn();
+  });
+
   const TEST_COLLECTIONS = [
     {
       can_write: false,
@@ -148,6 +154,9 @@ describe("SaveQuestionModal", () => {
     mock.get("/api/collection", {
       body: JSON.stringify(TEST_COLLECTIONS),
     });
+    mock.get("/api/collection/root", {
+      body: JSON.stringify(TEST_COLLECTIONS)[0],
+    });
   });
 
   afterEach(() => {
@@ -155,15 +164,15 @@ describe("SaveQuestionModal", () => {
   });
 
   describe("new question", () => {
-    it("should suggest a name for structured queries", () => {
-      renderSaveQuestionModal(getQuestion());
+    it("should suggest a name for structured queries", async () => {
+      await setup(getQuestion());
       expect(screen.getByLabelText("Name")).toHaveValue(
         EXPECTED_SUGGESTED_NAME,
       );
     });
 
-    it("should not suggest a name for native queries", () => {
-      renderSaveQuestionModal(
+    it("should not suggest a name for native queries", async () => {
+      await setup(
         new Question(
           {
             dataset_query: {
@@ -182,16 +191,18 @@ describe("SaveQuestionModal", () => {
       expect(screen.getByLabelText("Name")).toHaveValue("");
     });
 
-    it("should display empty description input", () => {
-      renderSaveQuestionModal(getQuestion());
+    it("should display empty description input", async () => {
+      await setup(getQuestion());
       expect(screen.getByLabelText("Description")).toHaveValue("");
     });
 
-    it("should call onCreate correctly with default form values", () => {
+    it("should call onCreate correctly with default form values", async () => {
       const question = getQuestion();
-      const { onCreateMock } = renderSaveQuestionModal(question);
+      const { onCreateMock } = await setup(question);
 
-      userEvent.click(screen.getByText("Save"));
+      await act(async () => {
+        await userEvent.click(screen.getByRole("button", { name: "Save" }));
+      });
 
       expect(onCreateMock).toHaveBeenCalledTimes(1);
       expect(onCreateMock).toHaveBeenCalledWith({
@@ -202,12 +213,17 @@ describe("SaveQuestionModal", () => {
       });
     });
 
-    it("should call onCreate correctly with edited form", () => {
+    it("should call onCreate correctly with edited form", async () => {
       const question = getQuestion();
-      const { onCreateMock } = renderSaveQuestionModal(question);
-
-      fillForm({ name: "My favorite orders", description: "So many of them" });
-      userEvent.click(screen.getByText("Save"));
+      const { onCreateMock } = await setup(question);
+
+      await act(async () => {
+        await fillForm({
+          name: "My favorite orders",
+          description: "So many of them",
+        });
+        await userEvent.click(screen.getByRole("button", { name: "Save" }));
+      });
 
       expect(onCreateMock).toHaveBeenCalledTimes(1);
       expect(onCreateMock).toHaveBeenCalledWith({
@@ -218,15 +234,17 @@ describe("SaveQuestionModal", () => {
       });
     });
 
-    it("should trim name and description", () => {
+    it("should trim name and description", async () => {
       const question = getQuestion();
-      const { onCreateMock } = renderSaveQuestionModal(question);
-
-      fillForm({
-        name: "    My favorite orders ",
-        description: "  So many of them   ",
+      const { onCreateMock } = await setup(question);
+
+      await act(async () => {
+        await fillForm({
+          name: "    My favorite orders ",
+          description: "  So many of them   ",
+        });
+        await userEvent.click(screen.getByRole("button", { name: "Save" }));
       });
-      userEvent.click(screen.getByText("Save"));
 
       expect(onCreateMock).toHaveBeenCalledTimes(1);
       expect(onCreateMock).toHaveBeenCalledWith({
@@ -237,14 +255,16 @@ describe("SaveQuestionModal", () => {
       });
     });
 
-    it('should correctly handle saving a question in the "root" collection', () => {
+    it('should correctly handle saving a question in the "root" collection', async () => {
       const question = getQuestion({
         collection_id: "root",
       });
-      const { onCreateMock } = renderSaveQuestionModal(question);
+      const { onCreateMock } = await setup(question);
 
-      fillForm({ name: "foo", description: "bar" });
-      userEvent.click(screen.getByText("Save"));
+      await act(async () => {
+        await fillForm({ name: "foo", description: "bar" });
+        await userEvent.click(screen.getByRole("button", { name: "Save" }));
+      });
 
       expect(onCreateMock).toHaveBeenCalledTimes(1);
       expect(onCreateMock).toHaveBeenCalledWith({
@@ -255,17 +275,19 @@ describe("SaveQuestionModal", () => {
       });
     });
 
-    it("shouldn't call onSave when form is submitted", () => {
+    it("shouldn't call onSave when form is submitted", async () => {
       const question = getQuestion();
-      const { onSaveMock } = renderSaveQuestionModal(question);
+      const { onSaveMock } = await setup(question);
 
-      userEvent.click(screen.getByText("Save"));
+      await act(async () => {
+        await userEvent.click(screen.getByRole("button", { name: "Save" }));
+      });
 
       expect(onSaveMock).not.toHaveBeenCalled();
     });
 
-    it("shouldn't show a control to overwrite a saved question", () => {
-      renderSaveQuestionModal(getQuestion());
+    it("shouldn't show a control to overwrite a saved question", async () => {
+      await setup(getQuestion());
       expect(
         screen.queryByText("Save as new question"),
       ).not.toBeInTheDocument();
@@ -276,12 +298,9 @@ describe("SaveQuestionModal", () => {
   });
 
   describe("saving as a new question", () => {
-    it("should offer to replace the original question by default", () => {
+    it("should offer to replace the original question by default", async () => {
       const originalQuestion = getQuestion({ isSaved: true });
-      renderSaveQuestionModal(
-        getDirtyQuestion(originalQuestion),
-        originalQuestion,
-      );
+      await setup(getDirtyQuestion(originalQuestion), originalQuestion);
 
       expect(
         screen.getByLabelText(/Replace original question, ".*"/),
@@ -289,7 +308,7 @@ describe("SaveQuestionModal", () => {
       expect(screen.getByText("Save as new question")).not.toBeChecked();
     });
 
-    it("should switch to the new question form", () => {
+    it("should switch to the new question form", async () => {
       const CARD = {
         name: "Q1",
         description: "Example description",
@@ -297,9 +316,11 @@ describe("SaveQuestionModal", () => {
       };
       const originalQuestion = getQuestion({ isSaved: true, ...CARD });
       const dirtyQuestion = getDirtyQuestion(originalQuestion);
-      renderSaveQuestionModal(dirtyQuestion, originalQuestion);
+      await setup(dirtyQuestion, originalQuestion);
 
-      userEvent.click(screen.getByText("Save as new question"));
+      await act(async () => {
+        await userEvent.click(screen.getByText("Save as new question"));
+      });
 
       expect(screen.getByLabelText("Name")).toHaveValue(
         EXPECTED_DIRTY_SUGGESTED_NAME,
@@ -310,16 +331,16 @@ describe("SaveQuestionModal", () => {
       expect(screen.queryByText("Our analytics")).toBeInTheDocument();
     });
 
-    it("should allow to save a question with default form values", () => {
+    // one
+    it("should allow to save a question with default form values", async () => {
       const originalQuestion = getQuestion({ isSaved: true });
       const dirtyQuestion = getDirtyQuestion(originalQuestion);
-      const { onCreateMock } = renderSaveQuestionModal(
-        dirtyQuestion,
-        originalQuestion,
-      );
+      const { onCreateMock } = await setup(dirtyQuestion, originalQuestion);
 
-      userEvent.click(screen.getByText("Save as new question"));
-      userEvent.click(screen.getByRole("button", { name: "Save" }));
+      await act(async () => {
+        await userEvent.click(screen.getByText("Save as new question"));
+        await userEvent.click(screen.getByRole("button", { name: "Save" }));
+      });
 
       expect(onCreateMock).toHaveBeenCalledTimes(1);
       expect(onCreateMock).toHaveBeenCalledWith({
@@ -328,17 +349,16 @@ describe("SaveQuestionModal", () => {
       });
     });
 
-    it("show allow to save a question with an edited form", () => {
+    it("show allow to save a question with an edited form", async () => {
       const originalQuestion = getQuestion({ isSaved: true });
       const dirtyQuestion = getDirtyQuestion(originalQuestion);
-      const { onCreateMock } = renderSaveQuestionModal(
-        dirtyQuestion,
-        originalQuestion,
-      );
+      const { onCreateMock } = await setup(dirtyQuestion, originalQuestion);
 
-      userEvent.click(screen.getByText("Save as new question"));
-      fillForm({ name: "My Q", description: "Sample" });
-      userEvent.click(screen.getByRole("button", { name: "Save" }));
+      await act(async () => {
+        await userEvent.click(screen.getByText("Save as new question"));
+        await fillForm({ name: "My Q", description: "Sample" });
+        await userEvent.click(screen.getByRole("button", { name: "Save" }));
+      });
 
       expect(onCreateMock).toHaveBeenCalledTimes(1);
       expect(onCreateMock).toHaveBeenCalledWith({
@@ -348,44 +368,42 @@ describe("SaveQuestionModal", () => {
       });
     });
 
-    it("shouldn't allow to save a question if form is invalid", () => {
+    it("shouldn't allow to save a question if form is invalid", async () => {
       const originalQuestion = getQuestion({ isSaved: true });
-      renderSaveQuestionModal(
-        getDirtyQuestion(originalQuestion),
-        originalQuestion,
-      );
+      await setup(getDirtyQuestion(originalQuestion), originalQuestion);
 
-      userEvent.click(screen.getByText("Save as new question"));
-      userEvent.clear(screen.getByLabelText("Name"));
-      userEvent.clear(screen.getByLabelText("Description"));
+      await act(async () => {
+        await userEvent.click(screen.getByText("Save as new question"));
+        await userEvent.clear(screen.getByLabelText("Name"));
+        await userEvent.clear(screen.getByLabelText("Description"));
+      });
 
       expect(screen.getByRole("button", { name: "Save" })).toBeDisabled();
     });
   });
 
   describe("overwriting a saved question", () => {
-    it("should display original question's name on save mode control", () => {
+    it("should display original question's name on save mode control", async () => {
       const originalQuestion = getQuestion({
         isSaved: true,
         name: "Beautiful Orders",
       });
       const dirtyQuestion = getDirtyQuestion(originalQuestion);
-      renderSaveQuestionModal(dirtyQuestion, originalQuestion);
+      await setup(dirtyQuestion, originalQuestion);
 
       expect(
         screen.queryByText('Replace original question, "Beautiful Orders"'),
       ).toBeInTheDocument();
     });
 
-    it("should call onSave correctly when form is submitted", () => {
+    it("should call onSave correctly when form is submitted", async () => {
       const originalQuestion = getQuestion({ isSaved: true });
       const dirtyQuestion = getDirtyQuestion(originalQuestion);
-      const { onSaveMock } = renderSaveQuestionModal(
-        dirtyQuestion,
-        originalQuestion,
-      );
+      const { onSaveMock } = await setup(dirtyQuestion, originalQuestion);
 
-      userEvent.click(screen.getByText("Save"));
+      await act(async () => {
+        await userEvent.click(screen.getByRole("button", { name: "Save" }));
+      });
 
       expect(onSaveMock).toHaveBeenCalledTimes(1);
       expect(onSaveMock).toHaveBeenCalledWith({
@@ -394,17 +412,18 @@ describe("SaveQuestionModal", () => {
       });
     });
 
-    it("should allow switching to 'save as new' and back", () => {
+    it("should allow switching to 'save as new' and back", async () => {
       const originalQuestion = getQuestion({ isSaved: true });
       const dirtyQuestion = getDirtyQuestion(originalQuestion);
-      const { onSaveMock } = renderSaveQuestionModal(
-        dirtyQuestion,
-        originalQuestion,
-      );
-
-      userEvent.click(screen.getByText("Save as new question"));
-      userEvent.click(screen.getByText(/Replace original question, ".*"/));
-      userEvent.click(screen.getByText("Save"));
+      const { onSaveMock } = await setup(dirtyQuestion, originalQuestion);
+
+      await act(async () => {
+        await userEvent.click(screen.getByText("Save as new question"));
+        await userEvent.click(
+          screen.getByText(/Replace original question, ".*"/),
+        );
+        await userEvent.click(screen.getByRole("button", { name: "Save" }));
+      });
 
       expect(onSaveMock).toHaveBeenCalledTimes(1);
       expect(onSaveMock).toHaveBeenCalledWith({
@@ -413,17 +432,19 @@ describe("SaveQuestionModal", () => {
       });
     });
 
-    it("should preserve original question's collection id", () => {
+    it("should preserve original question's collection id", async () => {
       const originalQuestion = getQuestion({
         isSaved: true,
         collection_id: 5,
       });
-      const { onSaveMock } = renderSaveQuestionModal(
+      const { onSaveMock } = await setup(
         getDirtyQuestion(originalQuestion),
         originalQuestion,
       );
 
-      userEvent.click(screen.getByText("Save"));
+      await act(async () => {
+        await userEvent.click(screen.getByRole("button", { name: "Save" }));
+      });
 
       expect(onSaveMock).toHaveBeenCalledWith(
         expect.objectContaining({
@@ -432,42 +453,44 @@ describe("SaveQuestionModal", () => {
       );
     });
 
-    it("shouldn't allow to save a question if form is invalid", () => {
-      renderSaveQuestionModal(getQuestion());
+    it("shouldn't allow to save a question if form is invalid", async () => {
+      await setup(getQuestion());
 
-      userEvent.clear(screen.getByLabelText("Name"));
-      userEvent.clear(screen.getByLabelText("Description"));
+      await act(async () => {
+        await userEvent.clear(screen.getByLabelText("Name"));
+        await userEvent.clear(screen.getByLabelText("Description"));
+      });
 
       expect(screen.getByRole("button", { name: "Save" })).toBeDisabled();
     });
 
-    it("shouldn't call onCreate when form is submitted", () => {
+    it("shouldn't call onCreate when form is submitted", async () => {
       const originalQuestion = getQuestion({ isSaved: true });
       const dirtyQuestion = getDirtyQuestion(originalQuestion);
-      const { onCreateMock } = renderSaveQuestionModal(
-        dirtyQuestion,
-        originalQuestion,
-      );
+      const { onCreateMock } = await setup(dirtyQuestion, originalQuestion);
 
-      userEvent.click(screen.getByText("Save"));
+      await act(async () => {
+        await userEvent.click(screen.getByRole("button", { name: "Save" }));
+      });
 
       expect(onCreateMock).not.toHaveBeenCalled();
     });
 
-    it("should keep 'save as new' form values while switching saving modes", () => {
+    it("should keep 'save as new' form values while switching saving modes", async () => {
       const originalQuestion = getQuestion({ isSaved: true });
-      renderSaveQuestionModal(
-        getDirtyQuestion(originalQuestion),
-        originalQuestion,
-      );
-
-      userEvent.click(screen.getByText("Save as new question"));
-      fillForm({
-        name: "Should not be erased",
-        description: "This should not be erased too",
+      await setup(getDirtyQuestion(originalQuestion), originalQuestion);
+
+      await act(async () => {
+        await userEvent.click(screen.getByText("Save as new question"));
+        await fillForm({
+          name: "Should not be erased",
+          description: "This should not be erased too",
+        });
+        await userEvent.click(
+          screen.getByText(/Replace original question, ".*"/),
+        );
+        await userEvent.click(screen.getByText("Save as new question"));
       });
-      userEvent.click(screen.getByText(/Replace original question, ".*"/));
-      userEvent.click(screen.getByText("Save as new question"));
 
       expect(screen.getByLabelText("Name")).toHaveValue("Should not be erased");
       expect(screen.getByLabelText("Description")).toHaveValue(
@@ -475,28 +498,29 @@ describe("SaveQuestionModal", () => {
       );
     });
 
-    it("should allow to replace the question if new question form is invalid (metabase#13817", () => {
+    it("should allow to replace the question if new question form is invalid (metabase#13817)", async () => {
       const originalQuestion = getQuestion({ isSaved: true });
-      renderSaveQuestionModal(
-        getDirtyQuestion(originalQuestion),
-        originalQuestion,
-      );
-
-      userEvent.click(screen.getByText("Save as new question"));
-      userEvent.clear(screen.getByLabelText("Name"));
-      userEvent.click(screen.getByText(/Replace original question, ".*"/));
+      await setup(getDirtyQuestion(originalQuestion), originalQuestion);
+
+      await act(async () => {
+        await userEvent.click(screen.getByText("Save as new question"));
+        await userEvent.clear(screen.getByLabelText("Name"));
+        await userEvent.click(
+          screen.getByText(/Replace original question, ".*"/),
+        );
+      });
 
-      expect(screen.getByRole("button", { name: "Save" })).toBeEnabled();
+      expect(await screen.getByRole("button", { name: "Save" })).toBeEnabled();
     });
 
-    it("should not allow overwriting when user does not have curate permission on collection (metabase#20717)", () => {
+    it("should not allow overwriting when user does not have curate permission on collection (metabase#20717)", async () => {
       const originalQuestion = getQuestion({
         isSaved: true,
         name: "Beautiful Orders",
         can_write: false,
       });
       const dirtyQuestion = getDirtyQuestion(originalQuestion);
-      renderSaveQuestionModal(dirtyQuestion, originalQuestion);
+      await setup(dirtyQuestion, originalQuestion);
 
       expect(
         screen.queryByText("Save as new question"),
@@ -507,15 +531,19 @@ describe("SaveQuestionModal", () => {
     });
   });
 
-  it("should call onClose when Cancel button is clicked", () => {
-    const { onCloseMock } = renderSaveQuestionModal(getQuestion());
-    userEvent.click(screen.getByRole("button", { name: "Cancel" }));
+  it("should call onClose when Cancel button is clicked", async () => {
+    const { onCloseMock } = await setup(getQuestion());
+    await act(async () => {
+      userEvent.click(screen.getByRole("button", { name: "Cancel" }));
+    });
     expect(onCloseMock).toHaveBeenCalledTimes(1);
   });
 
-  it("should call onClose when close icon is clicked", () => {
-    const { onCloseMock } = renderSaveQuestionModal(getQuestion());
-    userEvent.click(screen.getByLabelText("close icon"));
+  it("should call onClose when close icon is clicked", async () => {
+    const { onCloseMock } = await setup(getQuestion());
+    await act(async () => {
+      userEvent.click(screen.getByLabelText("close icon"));
+    });
     expect(onCloseMock).toHaveBeenCalledTimes(1);
   });
 
@@ -534,8 +562,8 @@ describe("SaveQuestionModal", () => {
       .question();
 
     describe("OSS", () => {
-      it("is not shown", () => {
-        renderSaveQuestionModal(question);
+      it("is not shown", async () => {
+        await setup(question);
         expect(screen.queryByText("More options")).not.toBeInTheDocument();
         expect(
           screen.queryByText("Cache all question results for"),
@@ -548,8 +576,8 @@ describe("SaveQuestionModal", () => {
         setupEnterpriseTest();
       });
 
-      it("is not shown", () => {
-        renderSaveQuestionModal(question);
+      it("is not shown", async () => {
+        await setup(question);
         expect(screen.queryByText("More options")).not.toBeInTheDocument();
         expect(
           screen.queryByText("Cache all question results for"),
diff --git a/frontend/test/metabase/scenarios/models/helpers/e2e-models-metadata-helpers.js b/frontend/test/metabase/scenarios/models/helpers/e2e-models-metadata-helpers.js
index 5ea638d58eb217e63370dc499b2feb8f33ffa696..6e68cb2fc7bcff1d686e13f34f06d75924d57ea0 100644
--- a/frontend/test/metabase/scenarios/models/helpers/e2e-models-metadata-helpers.js
+++ b/frontend/test/metabase/scenarios/models/helpers/e2e-models-metadata-helpers.js
@@ -19,7 +19,7 @@ export function mapColumnTo({ table, column } = {}) {
   cy.findByText("Database column this maps to")
     .closest(".Form-field")
     .findByTestId("select-button")
-    .click();
+    .click({ force: true });
 
   popover().contains(table).click();