diff --git a/frontend/test/__support__/integrated_tests.js b/frontend/test/__support__/integrated_tests.js
index b85f0f01b282a7ccaf4015f06f0d531c4996bb38..cfb5fd26b894cf27e877f5b18653495f767f2a72 100644
--- a/frontend/test/__support__/integrated_tests.js
+++ b/frontend/test/__support__/integrated_tests.js
@@ -20,23 +20,29 @@ import { Provider } from 'react-redux';
 
 import { createMemoryHistory } from 'history'
 import { getStore } from "metabase/store";
-import { createRoutes, Router, useRouterHistory } from "react-router";
+import { createRoutes, Link, Router, useRouterHistory } from "react-router";
 import _ from 'underscore';
+import chalk from "chalk";
 
 // Importing isomorphic-fetch sets the global `fetch` and `Headers` objects that are used here
 import fetch from 'isomorphic-fetch';
 
 import { refreshSiteSettings } from "metabase/redux/settings";
+
 import { getRoutes as getNormalRoutes } from "metabase/routes";
 import { getRoutes as getPublicRoutes } from "metabase/routes-public";
 import { getRoutes as getEmbedRoutes } from "metabase/routes-embed";
 
+import moment from "moment";
+import Button from "metabase/components/Button";
+
 let hasStartedCreatingStore = false;
 let hasFinishedCreatingStore = false
 let loginSession = null; // Stores the current login session
 let previousLoginSession = null;
 let simulateOfflineMode = false;
 
+
 /**
  * Login to the Metabase test instance with default credentials
  */
@@ -168,7 +174,7 @@ export const createTestStore = async ({ publicApp = false, embedApp = false } =
     const getRoutes = publicApp ? getPublicRoutes : (embedApp ? getEmbedRoutes : getNormalRoutes);
     const reducers = (publicApp || embedApp) ? publicReducers : normalReducers;
     const store = getStore(reducers, history, undefined, (createStore) => testStoreEnhancer(createStore, history, getRoutes));
-    store.setFinalStoreInstance(store);
+    store._setFinalStoreInstance(store);
 
     if (!publicApp) {
         await store.dispatch(refreshSiteSettings());
@@ -188,14 +194,15 @@ const testStoreEnhancer = (createStore, history, getRoutes) => {
             _onActionDispatched: null,
             _dispatchedActions: [],
             _finalStoreInstance: null,
+            _usingAppContainer: false,
 
-            setFinalStoreInstance: (finalStore) => {
-                store._finalStoreInstance = finalStore;
-            },
 
             dispatch: (action) => {
                 const result = store._originalDispatch(action);
-                store._dispatchedActions = store._dispatchedActions.concat([action]);
+                store._dispatchedActions = store._dispatchedActions.concat([{
+                    ...action,
+                    timestamp: Date.now()
+                }]);
                 if (store._onActionDispatched) store._onActionDispatched();
                 return result;
             },
@@ -211,6 +218,10 @@ const testStoreEnhancer = (createStore, history, getRoutes) => {
              * Convenient in tests for waiting specific actions to be executed after mounting a React container.
              */
             waitForActions: (actionTypes, {timeout = 8000} = {}) => {
+                if (store._onActionDispatched) {
+                    return Promise.reject(new Error("You have an earlier `store.waitForActions(...)` still in progress – have you forgotten to prepend `await` to the method call?"))
+                }
+
                 actionTypes = Array.isArray(actionTypes) ? actionTypes : [actionTypes]
 
                 const allActionsAreTriggered = () => _.every(actionTypes, actionType =>
@@ -223,7 +234,10 @@ const testStoreEnhancer = (createStore, history, getRoutes) => {
                 } else {
                     return new Promise((resolve, reject) => {
                         store._onActionDispatched = () => {
-                            if (allActionsAreTriggered()) resolve()
+                            if (allActionsAreTriggered()) {
+                                store._onActionDispatched = null;
+                                resolve()
+                            }
                         };
                         setTimeout(() => {
                             store._onActionDispatched = null;
@@ -234,8 +248,10 @@ const testStoreEnhancer = (createStore, history, getRoutes) => {
                             } else {
                                 return reject(
                                     new Error(
-                                        `Actions ${actionTypes.join(", ")} were not dispatched within ${timeout}ms. ` +
-                                        `Dispatched actions so far: ${store._dispatchedActions.map((a) => a.type).join(", ")}`
+                                        `These actions were not dispatched within ${timeout}ms:\n` +
+                                        chalk.cyan(actionTypes.join("\n")) +
+                                        "\n\nDispatched actions since initialization / last call of `store.resetDispatchedActions()`:\n" +
+                                        (store._dispatchedActions.map(store._formatDispatchedAction).join("\n") || "No dispatched actions")
                                     )
                                 )
                             }
@@ -246,7 +262,10 @@ const testStoreEnhancer = (createStore, history, getRoutes) => {
             },
 
             logDispatchedActions: () => {
-                console.log(`Dispatched actions so far: ${store._dispatchedActions.map((a) => a.type).join(", ")}`);
+                console.log(
+                    chalk.bold("\n\nDispatched actions since initialization / last call of `store.resetDispatchedActions()`:\n") +
+                    store._dispatchedActions.map(store._formatDispatchedAction).join("\n") || "No dispatched actions"
+                )
             },
 
             pushPath: (path) => history.push(path),
@@ -256,7 +275,7 @@ const testStoreEnhancer = (createStore, history, getRoutes) => {
             warnIfStoreCreationNotComplete: () => {
                 if (!hasFinishedCreatingStore) {
                     console.warn(
-                        "Seems that you don't wait until the store creation has completely finished. " +
+                        "Seems that you haven't waited until the store creation has completely finished. " +
                         "This means that site settings might not have been completely loaded. " +
                         "Please add `await` in front of createTestStore call.")
                 }
@@ -264,6 +283,7 @@ const testStoreEnhancer = (createStore, history, getRoutes) => {
 
             connectContainer: (reactContainer) => {
                 store.warnIfStoreCreationNotComplete();
+                store._usingAppContainer = false;
 
                 const routes = createRoutes(getRoutes(store._finalStoreInstance))
                 return store._connectWithStore(
@@ -277,6 +297,7 @@ const testStoreEnhancer = (createStore, history, getRoutes) => {
 
             getAppContainer: () => {
                 store.warnIfStoreCreationNotComplete();
+                store._usingAppContainer = true;
 
                 return store._connectWithStore(
                     <Router history={history}>
@@ -285,6 +306,14 @@ const testStoreEnhancer = (createStore, history, getRoutes) => {
                 )
             },
 
+            /** For having internally access to the store with all middlewares included **/
+            _setFinalStoreInstance: (finalStore) => {
+                store._finalStoreInstance = finalStore;
+            },
+
+            _formatDispatchedAction: (action) =>
+                moment(action.timestamp).format("hh:mm:ss.SSS") + " " + chalk.cyan(action.type),
+
             // eslint-disable-next-line react/display-name
             _connectWithStore: (reactContainer) =>
                 <Provider store={store._finalStoreInstance}>
@@ -297,19 +326,39 @@ const testStoreEnhancer = (createStore, history, getRoutes) => {
     }
 }
 
-export const clickRouterLink = (linkEnzymeWrapper) => {
-    // This hits an Enzyme bug so we should find some other way to warn the user :/
-    // https://github.com/airbnb/enzyme/pull/769
+export const click = (enzymeWrapper) => {
+    const nodeType = enzymeWrapper.type();
+    if (nodeType === Button || nodeType === "button") {
+        console.warn(
+            'You are calling `click` for a button; you would probably want to use `clickButton` instead as ' +
+            'it takes all button click scenarios into account.'
+        )
+    }
+    // Normal click event. Works for both `onClick` React event handlers and react-router <Link> objects.
+    // We simulate a left button click with `{ button: 0 }` because react-router requires that.
+    enzymeWrapper.simulate('click', { button: 0 });
+}
+
+// DEPRECATED
+export const clickRouterLink = click
+
+export const clickButton = (enzymeWrapper) => {
+    const closestButton = enzymeWrapper.closest("button");
+    // const childButton = enzymeWrapper.children("button");
 
-    // if (linkEnzymeWrapper.closest(Router).length === 0) {
-    //     console.warn(
-    //         "Trying to click a link with a component mounted with `store.connectContainer(container)`. Usually " +
-    //         "you want to use `store.getAppContainer()` instead because it has a complete support for react-router."
-    //     )
-    // }
+    if (closestButton.length === 1) {
+        closestButton.simulate("submit"); // for forms with onSubmit
+        closestButton.simulate("click"); // for lone buttons / forms without onSubmit
+    } else {
+        throw new Error('Couldn\'t find a button element to click in clickButton');
+    }
+}
 
-    linkEnzymeWrapper.simulate('click', {button: 0});
+export const setInputValue = (inputWrapper, value, { blur = true } = {}) => {
+    inputWrapper.simulate('change', { target: { value: value } });
+    if (blur) inputWrapper.simulate("blur")
 }
+
 // Commonly used question helpers that are temporarily here
 // TODO Atte Keinänen 6/27/17: Put all metabase-lib -related test helpers to one file
 export const createSavedQuestion = async (unsavedQuestion) => {
diff --git a/frontend/test/admin/databases/DatabaseListApp.integ.spec.js b/frontend/test/admin/databases/DatabaseListApp.integ.spec.js
index e2f8305db15781b6d00a986ae30e461ddb4c41b1..e22cc0b4b68a2fe2d685197a7951350c55b18a3a 100644
--- a/frontend/test/admin/databases/DatabaseListApp.integ.spec.js
+++ b/frontend/test/admin/databases/DatabaseListApp.integ.spec.js
@@ -1,7 +1,9 @@
 import {
     login,
     createTestStore,
-    clickRouterLink
+    click,
+    clickButton,
+    setInputValue
 } from "__support__/integrated_tests";
 
 import { mount } from "enzyme";
@@ -60,7 +62,7 @@ describe('dashboard list', () => {
             const listAppBeforeAdd = app.find(DatabaseListApp)
 
             const addDbButton = listAppBeforeAdd.find('.Button.Button--primary').first()
-            clickRouterLink(addDbButton)
+            click(addDbButton)
 
             const dbDetailsForm = app.find(DatabaseEditApp);
             expect(dbDetailsForm.length).toBe(1);
@@ -70,7 +72,7 @@ describe('dashboard list', () => {
             expect(dbDetailsForm.find('button[children="Save"]').props().disabled).toBe(true)
 
             const updateInputValue = (name, value) =>
-                dbDetailsForm.find(`input[name="${name}"]`).simulate('change', { target: { value } });
+                setInputValue(dbDetailsForm.find(`input[name="${name}"]`), value);
 
             updateInputValue("name", "Test db name");
             updateInputValue("dbname", "test_postgres_db");
@@ -79,7 +81,7 @@ describe('dashboard list', () => {
             const saveButton = dbDetailsForm.find('button[children="Save"]')
 
             expect(saveButton.props().disabled).toBe(false)
-            saveButton.simulate("submit");
+            clickButton(saveButton)
 
             // Now the submit button should be disabled so that you aren't able to trigger the db creation action twice
             await store.waitForActions([CREATE_DATABASE_STARTED])
@@ -111,7 +113,8 @@ describe('dashboard list', () => {
             const listAppBeforeAdd = app.find(DatabaseListApp)
 
             const addDbButton = listAppBeforeAdd.find('.Button.Button--primary').first()
-            clickRouterLink(addDbButton)
+
+            click(addDbButton) // ROUTER LINK
 
             const dbDetailsForm = app.find(DatabaseEditApp);
             expect(dbDetailsForm.length).toBe(1);
@@ -121,15 +124,17 @@ describe('dashboard list', () => {
             const saveButton = dbDetailsForm.find('button[children="Save"]')
             expect(saveButton.props().disabled).toBe(true)
 
+            // TODO: Apply change method here
             const updateInputValue = (name, value) =>
-                dbDetailsForm.find(`input[name="${name}"]`).simulate('change', { target: { value } });
+                setInputValue(dbDetailsForm.find(`input[name="${name}"]`), value);
 
             updateInputValue("name", "Test db name");
             updateInputValue("dbname", "test_postgres_db");
             updateInputValue("user", "uberadmin");
 
+            // TODO: Apply button submit thing here
             expect(saveButton.props().disabled).toBe(false)
-            saveButton.simulate("submit");
+            clickButton(saveButton)
 
             await store.waitForActions([CREATE_DATABASE_STARTED])
             expect(saveButton.text()).toBe("Saving...");
@@ -155,11 +160,11 @@ describe('dashboard list', () => {
 
             const deleteButton = wrapper.find('.Button.Button--danger').first()
 
-            deleteButton.simulate('click')
+            click(deleteButton);
 
             const deleteModal = wrapper.find('.test-modal')
-            deleteModal.find('.Form-input').simulate('change', { target: { value: "DELETE" }})
-            deleteModal.find('.Button.Button--danger').simulate('click')
+            setInputValue(deleteModal.find('.Form-input'), "DELETE")
+            click(deleteModal.find('.Button.Button--danger'));
 
             // test that the modal is gone
             expect(wrapper.find('.test-modal').length).toEqual(0)
@@ -197,12 +202,12 @@ describe('dashboard list', () => {
             const dbCount = wrapper.find('tr').length
 
             const deleteButton = wrapper.find('.Button.Button--danger').first()
-
-            deleteButton.simulate('click')
+            click(deleteButton)
 
             const deleteModal = wrapper.find('.test-modal')
-            deleteModal.find('.Form-input').simulate('change', { target: { value: "DELETE" }})
-            deleteModal.find('.Button.Button--danger').simulate('click')
+
+            setInputValue(deleteModal.find('.Form-input'), "DELETE");
+            click(deleteModal.find('.Button.Button--danger'))
 
             // test that the modal is gone
             expect(wrapper.find('.test-modal').length).toEqual(0)
@@ -235,7 +240,7 @@ describe('dashboard list', () => {
 
             const wrapper = app.find(DatabaseListApp)
             const sampleDatasetEditLink = wrapper.find('a[children="Sample Dataset"]').first()
-            clickRouterLink(sampleDatasetEditLink);
+            click(sampleDatasetEditLink); // ROUTER LINK
 
             expect(store.getPath()).toEqual("/admin/databases/1")
             await store.waitForActions([INITIALIZE_DATABASE]);
@@ -246,10 +251,10 @@ describe('dashboard list', () => {
             const nameField = dbDetailsForm.find(`input[name="name"]`);
             expect(nameField.props().value).toEqual("Sample Dataset")
 
-            nameField.simulate('change', { target: { value: newName } });
+            setInputValue(nameField, newName);
 
             const saveButton = dbDetailsForm.find('button[children="Save"]')
-            saveButton.simulate("submit");
+            clickButton(saveButton)
 
             await store.waitForActions([UPDATE_DATABASE_STARTED]);
             expect(saveButton.text()).toBe("Saving...");
@@ -286,10 +291,10 @@ describe('dashboard list', () => {
 
             const tooLongName = "too long name ".repeat(100);
             const nameField = dbDetailsForm.find(`input[name="name"]`);
-            nameField.simulate('change', { target: { value: tooLongName } });
+            setInputValue(nameField, tooLongName);
 
             const saveButton = dbDetailsForm.find('button[children="Save"]')
-            saveButton.simulate("submit");
+            clickButton(saveButton)
 
             await store.waitForActions([UPDATE_DATABASE_STARTED]);
             expect(saveButton.text()).toBe("Saving...");
diff --git a/frontend/test/admin/datamodel/FieldApp.integ.spec.js b/frontend/test/admin/datamodel/FieldApp.integ.spec.js
index b946b36338e4fad393c53e5f6cf45aaf8f998246..a1ef3a61220cafe97a957b96de2e7787e1b77d16 100644
--- a/frontend/test/admin/datamodel/FieldApp.integ.spec.js
+++ b/frontend/test/admin/datamodel/FieldApp.integ.spec.js
@@ -346,6 +346,7 @@ describe("FieldApp", () => {
             lastMapping.find(Input).simulate('change', {target: {value: "Extraordinarily awesome"}});
 
             const saveButton = valueRemappingsSection.find(ButtonWithStatus)
+            // TRY WITH clickButton !!!
             saveButton.simulate("click");
 
             store.waitForActions([UPDATE_FIELD_VALUES]);