diff --git a/frontend/test/__support__/e2e/helpers/e2e-notebook-helpers.js b/frontend/test/__support__/e2e/helpers/e2e-notebook-helpers.js
index 2509ab2342dfdb9e81be36e8c84c80cc585f044a..4dd14cb9d91e18c5735d427f2e1357af66f34ce8 100644
--- a/frontend/test/__support__/e2e/helpers/e2e-notebook-helpers.js
+++ b/frontend/test/__support__/e2e/helpers/e2e-notebook-helpers.js
@@ -11,3 +11,20 @@
 export function getNotebookStep(type, { stage = 0, index = 0 } = {}) {
   return cy.findByTestId(`step-${type}-${stage}-${index}`);
 }
+
+/**
+ * Visualize notebook query results.
+ *
+ * @param {function} callback
+ */
+export function visualize(callback) {
+  cy.intercept("POST", "/api/dataset").as("dataset");
+
+  cy.button("Visualize").click();
+
+  cy.wait("@dataset").then(({ response }) => {
+    if (callback) {
+      callback(response);
+    }
+  });
+}
diff --git a/frontend/test/metabase/scenarios/admin/datamodel/metrics.cy.spec.js b/frontend/test/metabase/scenarios/admin/datamodel/metrics.cy.spec.js
index 55363327b1782ecbf87eeb6201d47c7f55382632..7e7d46988eb6a02eb3f579990f1f31b8521d3706 100644
--- a/frontend/test/metabase/scenarios/admin/datamodel/metrics.cy.spec.js
+++ b/frontend/test/metabase/scenarios/admin/datamodel/metrics.cy.spec.js
@@ -3,6 +3,7 @@ import {
   popover,
   modal,
   openOrdersTable,
+  visualize,
 } from "__support__/e2e/cypress";
 import { SAMPLE_DATASET } from "__support__/e2e/cypress_sample_dataset";
 
@@ -49,9 +50,8 @@ describe("scenarios > admin > datamodel > metrics", () => {
       .parent()
       .contains("Revenue");
 
-    // Visualize results:
-    // It will render line chart by default. Switch to the table.
-    cy.button("Visualize").click();
+    visualize();
+    // Visualization will render line chart by default. Switch to the table.
     cy.icon("table2").click();
 
     cy.findAllByRole("grid").as("table");
diff --git a/frontend/test/metabase/scenarios/binning/binning-reproductions.cy.spec.js b/frontend/test/metabase/scenarios/binning/binning-reproductions.cy.spec.js
index 08d28c3f4b93b4c3fac79144e188e344d89a60ed..a2e982f5e372745be96ecc2f38c44302e9e96540 100644
--- a/frontend/test/metabase/scenarios/binning/binning-reproductions.cy.spec.js
+++ b/frontend/test/metabase/scenarios/binning/binning-reproductions.cy.spec.js
@@ -1,6 +1,7 @@
 import {
   restore,
   popover,
+  visualize,
   openOrdersTable,
   visitQuestionAdhoc,
 } from "__support__/e2e/cypress";
@@ -225,7 +226,7 @@ describe("binning related reproductions", () => {
           cy.findByText("10 bins").click();
         });
 
-      cy.button("Visualize").click();
+      visualize();
       cy.get(".bar");
     });
   });
diff --git a/frontend/test/metabase/scenarios/binning/qb-explicit-joins.cy.spec.js b/frontend/test/metabase/scenarios/binning/qb-explicit-joins.cy.spec.js
index cb829e1e1edaa565f2987e015b4fe528b4b157bd..265062b9dab4ba56de9d4483673d7e4c54dedd2f 100644
--- a/frontend/test/metabase/scenarios/binning/qb-explicit-joins.cy.spec.js
+++ b/frontend/test/metabase/scenarios/binning/qb-explicit-joins.cy.spec.js
@@ -1,4 +1,4 @@
-import { restore, popover } from "__support__/e2e/cypress";
+import { restore, popover, visualize } from "__support__/e2e/cypress";
 import { SAMPLE_DATASET } from "__support__/e2e/cypress_sample_dataset";
 
 const {
@@ -294,11 +294,7 @@ function chooseBucketAndAssert({
       cy.findByText(bucketSize).click();
     });
 
-  if (mode === "notebook") {
-    cy.button("Visualize").click();
-  }
-
-  waitAndAssertOnRequest("@dataset");
+  mode === "notebook" ? visualize() : waitAndAssertOnRequest("@dataset");
 
   const visualizaitonSelector = columnType === "time" ? "circle" : ".bar";
   cy.get(visualizaitonSelector);
diff --git a/frontend/test/metabase/scenarios/binning/qb-implicit-joins.cy.spec.js b/frontend/test/metabase/scenarios/binning/qb-implicit-joins.cy.spec.js
index 50dd351e4aed4d9c573e363048e864e05dcfbe90..04ef792969107a71ac0b18c1c334d40eb7cd9c78 100644
--- a/frontend/test/metabase/scenarios/binning/qb-implicit-joins.cy.spec.js
+++ b/frontend/test/metabase/scenarios/binning/qb-implicit-joins.cy.spec.js
@@ -1,4 +1,4 @@
-import { restore, popover } from "__support__/e2e/cypress";
+import { restore, popover, visualize } from "__support__/e2e/cypress";
 
 describe("scenarios > binning > from a saved QB question using implicit joins", () => {
   beforeEach(() => {
@@ -159,10 +159,7 @@ function chooseBucketAndAssert({
       cy.findByText(bucketSize).click();
     });
 
-  if (mode === "notebook") {
-    cy.button("Visualize").click();
-  }
-  waitAndAssertOnRequest("@dataset");
+  mode === "notebook" ? visualize() : waitAndAssertOnRequest("@dataset");
 
   cy.findByText(title);
   cy.get(".cellData")
diff --git a/frontend/test/metabase/scenarios/binning/qb-regular-table.cy.spec.js b/frontend/test/metabase/scenarios/binning/qb-regular-table.cy.spec.js
index b7e2e93af5ce48693a8a121912a8c6486fbcf63c..de4ffce867cdb7217f8478aabb8aac1ecbf75ec3 100644
--- a/frontend/test/metabase/scenarios/binning/qb-regular-table.cy.spec.js
+++ b/frontend/test/metabase/scenarios/binning/qb-regular-table.cy.spec.js
@@ -1,4 +1,4 @@
-import { restore, openTable } from "__support__/e2e/cypress";
+import { restore, openTable, visualize } from "__support__/e2e/cypress";
 import { SAMPLE_DATASET } from "__support__/e2e/cypress_sample_dataset";
 
 const { ORDERS_ID, PEOPLE_ID } = SAMPLE_DATASET;
@@ -161,7 +161,8 @@ function chooseInitialBinningOption({
       .click();
 
     cy.findByText(bucketSize).click();
-    cy.button("Visualize").click();
+
+    visualize();
   } else {
     cy.findByTestId("sidebar-right")
       .contains(column)
diff --git a/frontend/test/metabase/scenarios/binning/sql.cy.spec.js b/frontend/test/metabase/scenarios/binning/sql.cy.spec.js
index 755b3ed428b718e725581b3eb05df43851b1ff38..f78e20448ddd21aebd4625499c63aeb4280ad252 100644
--- a/frontend/test/metabase/scenarios/binning/sql.cy.spec.js
+++ b/frontend/test/metabase/scenarios/binning/sql.cy.spec.js
@@ -1,4 +1,4 @@
-import { restore, popover } from "__support__/e2e/cypress";
+import { restore, popover, visualize } from "__support__/e2e/cypress";
 
 const questionDetails = {
   name: "SQL Binning",
@@ -104,9 +104,11 @@ describe("scenarios > binning > from a saved sql question", () => {
       cy.findByText("Year").click();
 
       cy.findByText("Count by CREATED_AT: Year");
-      cy.button("Visualize").click();
 
-      waitAndAssertOnRequest("@dataset");
+      visualize(response => {
+        assertOnResponse(response);
+      });
+
       cy.get("circle");
     });
 
@@ -117,9 +119,11 @@ describe("scenarios > binning > from a saved sql question", () => {
       cy.findByText("50 bins").click();
 
       cy.findByText("Count by TOTAL: 50 bins");
-      cy.button("Visualize").click();
 
-      waitAndAssertOnRequest("@dataset");
+      visualize(response => {
+        assertOnResponse(response);
+      });
+
       cy.get(".bar");
     });
 
@@ -137,9 +141,11 @@ describe("scenarios > binning > from a saved sql question", () => {
       cy.findByText("10°").click();
 
       cy.findByText("Count by LONGITUDE: 10°");
-      cy.button("Visualize").click();
 
-      waitAndAssertOnRequest("@dataset");
+      visualize(response => {
+        assertOnResponse(response);
+      });
+
       cy.get(".bar");
     });
   });
@@ -215,7 +221,11 @@ function assertOnXYAxisLabels({ xLabel, yLabel } = {}) {
 }
 
 function waitAndAssertOnRequest(requestAlias) {
-  cy.wait(requestAlias).then(xhr => {
-    expect(xhr.response.body.error).to.not.exist;
+  cy.wait(requestAlias).then(({ response }) => {
+    assertOnResponse(response);
   });
 }
+
+function assertOnResponse(response) {
+  expect(response.body.error).to.not.exist;
+}
diff --git a/frontend/test/metabase/scenarios/custom-column/custom-column.cy.spec.js b/frontend/test/metabase/scenarios/custom-column/custom-column.cy.spec.js
index 34816ac76f8f14a1b3cc18e4968eb9e698084628..33db09b9c5a94b9f8d980e86260d970c293d8d0c 100644
--- a/frontend/test/metabase/scenarios/custom-column/custom-column.cy.spec.js
+++ b/frontend/test/metabase/scenarios/custom-column/custom-column.cy.spec.js
@@ -1,6 +1,7 @@
 import {
   restore,
   popover,
+  visualize,
   openOrdersTable,
   visitQuestionAdhoc,
   enterCustomColumnDetails,
@@ -25,8 +26,7 @@ describe("scenarios > question > custom column", () => {
     enterCustomColumnDetails({ formula: "1 + 1", name: "Math" });
     cy.button("Done").click();
 
-    cy.button("Visualize").click();
-    cy.wait("@dataset");
+    visualize();
 
     cy.findByText("There was a problem with your question").should("not.exist");
     cy.get(".Visualization").contains("Math");
@@ -51,8 +51,7 @@ describe("scenarios > question > custom column", () => {
       enterCustomColumnDetails({ formula, name });
       cy.button("Done").click();
 
-      cy.button("Visualize").click();
-      cy.wait("@dataset");
+      visualize();
 
       cy.get(".Visualization").contains(name);
     });
@@ -92,8 +91,7 @@ describe("scenarios > question > custom column", () => {
     });
     cy.button("Done").click();
 
-    cy.button("Visualize").click();
-    cy.wait("@dataset");
+    visualize();
 
     cy.findByText("There was a problem with your question").should("not.exist");
     // This is a pre-save state of the question but the column name should appear
@@ -112,11 +110,7 @@ describe("scenarios > question > custom column", () => {
     enterCustomColumnDetails({ formula: "1 + 1", name: "x" });
     cy.button("Done").click();
 
-    cy.button("Visualize").click();
-
-    // wait for results to load
-    cy.get(".LoadingSpinner").should("not.exist");
-    cy.button("Visualize").should("not.exist");
+    visualize();
 
     cy.log(
       "**Fails in 0.35.0, 0.35.1, 0.35.2, 0.35.4 and the latest master (2020-10-21)**",
@@ -328,11 +322,11 @@ describe("scenarios > question > custom column", () => {
     cy.get("[class*=NotebookCellItem]")
       .contains(CE_NAME)
       .should("not.exist");
-    cy.button("Visualize").click();
 
-    cy.wait("@dataset").then(xhr => {
-      expect(xhr.response.body.error).to.not.exist;
+    visualize(response => {
+      expect(response.body.error).to.not.exist;
     });
+
     cy.contains("37.65");
   });
 
@@ -417,10 +411,11 @@ describe("scenarios > question > custom column", () => {
       name: "No discount",
     });
     cy.button("Done").click();
-    cy.button("Visualize").click();
-    cy.wait("@dataset").then(xhr => {
-      expect(xhr.response.body.error).to.not.exist;
+
+    visualize(response => {
+      expect(response.body.error).to.not.exist;
     });
+
     cy.contains("37.65");
     cy.findByText("No discount");
   });
@@ -446,10 +441,11 @@ describe("scenarios > question > custom column", () => {
     cy.findByText("Days").click();
     cy.findByText("Years").click();
     cy.button("Add filter").click();
-    cy.button("Visualize").click();
-    cy.wait("@dataset").then(interception => {
-      expect(interception.response.body.error).not.to.exist;
+
+    visualize(response => {
+      expect(response.body.error).to.not.exist;
     });
+
     cy.findByText("MiscDate");
   });
 });
diff --git a/frontend/test/metabase/scenarios/custom-column/reproductions/13289-cc-post-aggregation-zoom-in.cy.spec.js b/frontend/test/metabase/scenarios/custom-column/reproductions/13289-cc-post-aggregation-zoom-in.cy.spec.js
index 5cc36e9a3560824fa135f0c77da4d807afeb548d..5e75a4b8a162ff01bcfa31402aecdb2d99a4d05c 100644
--- a/frontend/test/metabase/scenarios/custom-column/reproductions/13289-cc-post-aggregation-zoom-in.cy.spec.js
+++ b/frontend/test/metabase/scenarios/custom-column/reproductions/13289-cc-post-aggregation-zoom-in.cy.spec.js
@@ -3,6 +3,7 @@ import {
   openOrdersTable,
   popover,
   enterCustomColumnDetails,
+  visualize,
 } from "__support__/e2e/cypress";
 
 const CC_NAME = "Math";
@@ -40,8 +41,7 @@ describe("issue 13289", () => {
       cy.findByText("Created At").click();
     });
 
-    cy.button("Visualize").click();
-    cy.wait("@dataset");
+    visualize();
 
     cy.get(".Visualization").within(() => {
       cy.get("circle")
diff --git a/frontend/test/metabase/scenarios/custom-column/reproductions/13751-cc-allow-strings-in-filter.cy.spec.js b/frontend/test/metabase/scenarios/custom-column/reproductions/13751-cc-allow-strings-in-filter.cy.spec.js
index b33d2624f4a96c411e015fc939bffd185e04b82f..6f4a69b8bc12f008f943ecdbadc1eaea6bfffc94 100644
--- a/frontend/test/metabase/scenarios/custom-column/reproductions/13751-cc-allow-strings-in-filter.cy.spec.js
+++ b/frontend/test/metabase/scenarios/custom-column/reproductions/13751-cc-allow-strings-in-filter.cy.spec.js
@@ -1,4 +1,4 @@
-import { restore, popover } from "__support__/e2e/cypress";
+import { restore, popover, visualize } from "__support__/e2e/cypress";
 
 const CC_NAME = "C-States";
 const PG_DB_NAME = "QA Postgres12";
@@ -45,7 +45,8 @@ describe("issue 13751", () => {
         .click();
     });
 
-    cy.button("Visualize").click();
+    visualize();
+
     cy.findByText("Arnold Adams");
   });
 });
diff --git a/frontend/test/metabase/scenarios/custom-column/reproductions/14843-cc-apply-filter-not-equal-to.cy.spec.js b/frontend/test/metabase/scenarios/custom-column/reproductions/14843-cc-apply-filter-not-equal-to.cy.spec.js
index 4651361f06542ef6563249028f1d4bd233fca9b0..7e6140f336edb1e97daedfa09abd4794b0c2f5eb 100644
--- a/frontend/test/metabase/scenarios/custom-column/reproductions/14843-cc-apply-filter-not-equal-to.cy.spec.js
+++ b/frontend/test/metabase/scenarios/custom-column/reproductions/14843-cc-apply-filter-not-equal-to.cy.spec.js
@@ -1,4 +1,4 @@
-import { restore, popover } from "__support__/e2e/cypress";
+import { restore, popover, visualize } from "__support__/e2e/cypress";
 import { SAMPLE_DATASET } from "__support__/e2e/cypress_sample_dataset";
 
 const { PEOPLE, PEOPLE_ID } = SAMPLE_DATASET;
@@ -36,8 +36,7 @@ describe("issue 14843", () => {
     cy.findByPlaceholderText("Enter a number").type("3");
     cy.button("Add filter").click();
 
-    cy.button("Visualize").click();
-    cy.wait("@dataset");
+    visualize();
 
     cy.findByText(`${CC_NAME} is not equal to 3`);
     cy.findByText("Rye").should("not.exist");
diff --git a/frontend/test/metabase/scenarios/custom-column/reproductions/18069-cc-sum-aggregation-dimension-type.cy.spec.js b/frontend/test/metabase/scenarios/custom-column/reproductions/18069-cc-sum-aggregation-dimension-type.cy.spec.js
index e2b8f20b8c5d7227d777098e54f8f0a1bcf887a9..c13f09f50dcb2b56cd1d96bea98ff023b7127514 100644
--- a/frontend/test/metabase/scenarios/custom-column/reproductions/18069-cc-sum-aggregation-dimension-type.cy.spec.js
+++ b/frontend/test/metabase/scenarios/custom-column/reproductions/18069-cc-sum-aggregation-dimension-type.cy.spec.js
@@ -1,4 +1,4 @@
-import { restore, popover } from "__support__/e2e/cypress";
+import { restore, popover, visualize } from "__support__/e2e/cypress";
 
 import { SAMPLE_DATASET } from "__support__/e2e/cypress_sample_dataset";
 
@@ -20,8 +20,6 @@ const questionDetails = {
 
 describe("issue 18069", () => {
   beforeEach(() => {
-    cy.intercept("POST", "/api/dataset").as("dataset");
-
     restore();
     cy.signInAsAdmin();
 
@@ -49,8 +47,8 @@ describe("issue 18069", () => {
       cy.findByText("CC_ScaledRating").click();
     });
 
-    cy.button("Visualize").click();
-    cy.wait("@dataset");
+    visualize();
+
     cy.findByText("1,041.45");
   });
 });
diff --git a/frontend/test/metabase/scenarios/permissions/sandboxes.cy.spec.js b/frontend/test/metabase/scenarios/permissions/sandboxes.cy.spec.js
index 839264ae206e1412eae45a4e6628274d90727442..d74931bf3dfec695e9d616fe52896ee840ec2c69 100644
--- a/frontend/test/metabase/scenarios/permissions/sandboxes.cy.spec.js
+++ b/frontend/test/metabase/scenarios/permissions/sandboxes.cy.spec.js
@@ -8,6 +8,7 @@ import {
   restore,
   remapDisplayValueToFK,
   setupSMTP,
+  visualize,
 } from "__support__/e2e/cypress";
 import { USER_GROUPS } from "__support__/e2e/cypress_data";
 
@@ -146,7 +147,8 @@ describeWithToken("formatting > sandboxes", () => {
         cy.findByText("Greater than").click();
         cy.findByPlaceholderText("Enter a number").type("100");
         cy.findByText("Add filter").click();
-        cy.button("Visualize").click();
+
+        visualize();
 
         cy.log("Make sure user is still sandboxed");
         cy.get(".TableInteractive-cellWrapper--firstColumn").should(
@@ -219,7 +221,8 @@ describeWithToken("formatting > sandboxes", () => {
           .click();
       });
 
-      cy.button("Visualize").click();
+      visualize();
+
       cy.findByText("Count by User → ID");
       cy.findByText("11"); // Sum of orders for user with ID #1
     });
@@ -776,10 +779,9 @@ describeWithToken("formatting > sandboxes", () => {
       cy.findByText("Count of rows").click();
       cy.findByText("Pick a column to group by").click();
       cy.findByText(/Products? → ID/).click();
-      cy.button("Visualize").click();
 
-      cy.wait("@dataset").then(xhr => {
-        expect(xhr.response.body.error).not.to.exist;
+      visualize(response => {
+        expect(response.body.error).to.not.exist;
       });
 
       // Number of products with ID = 1 (and ID = 19)
diff --git a/frontend/test/metabase/scenarios/question/filter.cy.spec.js b/frontend/test/metabase/scenarios/question/filter.cy.spec.js
index 5e8a9de347755563425bcea8df45f1b42ee192d7..b6061919bb995abd59f08b91471f4af245c78a27 100644
--- a/frontend/test/metabase/scenarios/question/filter.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/filter.cy.spec.js
@@ -7,6 +7,7 @@ import {
   popover,
   filterWidget,
   visitQuestionAdhoc,
+  visualize,
 } from "__support__/e2e/cypress";
 
 import { SAMPLE_DATASET } from "__support__/e2e/cypress_sample_dataset";
@@ -214,9 +215,8 @@ describe("scenarios > question > filter", () => {
     cy.findByText("Add filter").click();
     cy.contains("Category is not Gizmo");
 
-    cy.button("Visualize").click();
-    // wait for results to load
-    cy.get(".LoadingSpinner").should("not.exist");
+    visualize();
+
     cy.log("The point of failure in 0.37.0-rc3");
     cy.contains("37.65");
     cy.findByText("There was a problem with your question").should("not.exist");
@@ -556,7 +556,8 @@ describe("scenarios > question > filter", () => {
       .click();
 
     // check that filter is applied and rows displayed
-    cy.button("Visualize").click();
+    visualize();
+
     cy.contains("Showing 1,112 rows");
   });
 
@@ -935,7 +936,8 @@ describe("scenarios > question > filter", () => {
         .parent()
         .find(".Icon-close")
         .click();
-      cy.button("Visualize");
+
+      visualize();
     });
   });
 
@@ -1012,7 +1014,8 @@ describe("scenarios > question > filter", () => {
           addBooleanFilter();
         });
 
-        cy.button("Visualize").click();
+        visualize();
+
         assertOnTheResult();
       });
 
diff --git a/frontend/test/metabase/scenarios/question/joins.cy.spec.js b/frontend/test/metabase/scenarios/question/joins.cy.spec.js
index 5cef9a53c574ff5c51498a278ceb3ead6277d785..36df63d88235a2810f0d28d38384ab6d9d9e192c 100644
--- a/frontend/test/metabase/scenarios/question/joins.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/joins.cy.spec.js
@@ -1,4 +1,9 @@
-import { restore, openOrdersTable, popover } from "__support__/e2e/cypress";
+import {
+  restore,
+  openOrdersTable,
+  popover,
+  visualize,
+} from "__support__/e2e/cypress";
 import { SAMPLE_DATASET } from "__support__/e2e/cypress_sample_dataset";
 
 const { ORDERS, ORDERS_ID, PRODUCTS } = SAMPLE_DATASET;
@@ -44,9 +49,8 @@ describe("scenarios > question > joined questions", () => {
       .findByText("Product ID")
       .click();
 
-    cy.button("Visualize").click();
-    cy.wait("@dataset").then(xhr => {
-      expect(xhr.response.body.error).not.to.exist;
+    visualize(response => {
+      expect(response.body.error).to.not.exist;
     });
   });
 
@@ -64,8 +68,7 @@ describe("scenarios > question > joined questions", () => {
     selectFromDropdown("Created At");
     selectFromDropdown("Created At");
 
-    cy.button("Visualize").click();
-    cy.wait("@dataset");
+    visualize();
 
     // 415 rows mean the join is done correctly,
     // (join on product's FK + join on the same "created_at" field)
@@ -73,7 +76,6 @@ describe("scenarios > question > joined questions", () => {
   });
 
   it("should allow joins on date-time fields", () => {
-    cy.intercept("POST", "/api/dataset").as("dataset");
     openOrdersTable({ mode: "notebook" });
 
     joinTable("Products");
@@ -103,8 +105,7 @@ describe("scenarios > question > joined questions", () => {
     cy.findByText("Summarize").click();
     selectFromDropdown("Count of rows");
 
-    cy.button("Visualize").click();
-    cy.wait("@dataset");
+    visualize();
 
     // 2087 rows mean the join is done correctly,
     // (orders joined with products on the same day-month-year)
@@ -112,7 +113,6 @@ describe("scenarios > question > joined questions", () => {
   });
 
   it("should show 'Previous results' instead of a table name for non-field dimensions", () => {
-    cy.intercept("POST", "/api/dataset").as("dataset");
     openOrdersTable({ mode: "notebook" });
 
     cy.findByText("Summarize").click();
diff --git a/frontend/test/metabase/scenarios/question/nested.cy.spec.js b/frontend/test/metabase/scenarios/question/nested.cy.spec.js
index a49bbccf00f7d6600e42f97fb878645922bc9b08..240256cec68a3a066528facedc5a7dbda8eb0a36 100644
--- a/frontend/test/metabase/scenarios/question/nested.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/nested.cy.spec.js
@@ -4,6 +4,7 @@ import {
   openOrdersTable,
   remapDisplayValueToFK,
   visitQuestionAdhoc,
+  visualize,
 } from "__support__/e2e/cypress";
 
 import { SAMPLE_DATASET } from "__support__/e2e/cypress_sample_dataset";
@@ -571,7 +572,8 @@ describe("scenarios > question > nested", () => {
       cy.findByText("Pick a column to group by").click();
       cy.findByText("CAT").click();
 
-      cy.button("Visualize").click();
+      visualize();
+
       cy.get("@consoleWarn").should(
         "not.be.calledWith",
         "Removing invalid MBQL clause",
@@ -583,8 +585,8 @@ describe("scenarios > question > nested", () => {
       cy.findByText("Pick a column to group by").click();
       cy.findByText("CAT").click();
 
-      cy.button("Visualize").click();
-      cy.wait("@dataset");
+      visualize();
+
       cy.findAllByRole("button")
         .contains("Summarize")
         .click();
diff --git a/frontend/test/metabase/scenarios/question/new.cy.spec.js b/frontend/test/metabase/scenarios/question/new.cy.spec.js
index e6503f12506a3fc54a1b4981fb057a9a431ef8b2..4fcd7a8c6182c09e956e850815bf4945938c8d4f 100644
--- a/frontend/test/metabase/scenarios/question/new.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/new.cy.spec.js
@@ -2,6 +2,7 @@ import {
   browse,
   restore,
   popover,
+  visualize,
   openOrdersTable,
   openReviewsTable,
 } from "__support__/e2e/cypress";
@@ -71,7 +72,9 @@ describe("scenarios > question > new", () => {
         .click();
       cy.findByText("Rating").click();
     });
-    cy.button("Visualize").click();
+
+    visualize();
+
     cy.get(".Visualization .bar").should("have.length", 6);
   });
 
@@ -131,7 +134,8 @@ describe("scenarios > question > new", () => {
 
       it("should allow to search saved questions", () => {
         cy.findByText("Orders, Count").click();
-        cy.findByText("Visualize").click();
+
+        visualize();
         cy.findByText("18,760");
       });
 
@@ -140,7 +144,9 @@ describe("scenarios > question > new", () => {
           .closest("li")
           .findByText("Table in")
           .click();
-        cy.findByText("Visualize").click();
+
+        visualize();
+
         cy.url().should("include", "question#");
         cy.findByText("Sample Dataset");
         cy.findByText("Orders");
@@ -200,7 +206,9 @@ describe("scenarios > question > new", () => {
         cy.findByText("Orders, Count, Grouped by Created At (year)");
         cy.findByText("Orders");
         cy.findByText("Orders, Count").click();
-        cy.button("Visualize").click();
+
+        visualize();
+
         cy.findByText("18,760");
       });
 
@@ -214,7 +222,8 @@ describe("scenarios > question > new", () => {
         cy.findByText("Orders");
         cy.findByText("Orders, Count, Grouped by Created At (year)").click();
 
-        cy.button("Visualize").click();
+        visualize();
+
         cy.findByText("2016");
         cy.findByText("5,834");
       });
@@ -222,13 +231,17 @@ describe("scenarios > question > new", () => {
       it("should perform a search scoped to saved questions", () => {
         cy.findByPlaceholderText("Search for a question").type("Grouped");
         cy.findByText("Orders, Count, Grouped by Created At (year)").click();
-        cy.button("Visualize").click();
+
+        visualize();
+
         cy.findByText("2018");
       });
 
       it("should reopen saved question picker after returning back to editor mode", () => {
         cy.findByText("Orders, Count, Grouped by Created At (year)").click();
-        cy.button("Visualize").click();
+
+        visualize();
+
         cy.icon("notebook").click();
         cy.findByTestId("data-step-cell").click();
 
@@ -404,7 +417,9 @@ describe("scenarios > question > new", () => {
       cy.contains("Custom question").click();
       cy.contains("Sample Dataset").click();
       cy.contains("Orders").click();
-      cy.contains("Visualize").click();
+
+      visualize();
+
       cy.contains("37.65");
     });
 
@@ -419,7 +434,9 @@ describe("scenarios > question > new", () => {
         cy.findByPlaceholderText("Name (required)").type("twice max total");
         cy.findByText("Done").click();
       });
-      cy.button("Visualize").click();
+
+      visualize();
+
       cy.findByText("318.7");
     });
 
diff --git a/frontend/test/metabase/scenarios/question/notebook.cy.spec.js b/frontend/test/metabase/scenarios/question/notebook.cy.spec.js
index 5e2dd27b0e33473e2d09126dbc8cdd54b00dc050..0bcb90cfadba30a573799f7f197ef7317b3d654d 100644
--- a/frontend/test/metabase/scenarios/question/notebook.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/notebook.cy.spec.js
@@ -4,6 +4,7 @@ import {
   openProductsTable,
   popover,
   modal,
+  visualize,
   visitQuestionAdhoc,
   interceptPromise,
   getNotebookStep,
@@ -38,6 +39,7 @@ describe("scenarios > question > notebook", () => {
     cy.findByText("Not now").click();
     // enter "notebook" and visualize without changing anything
     cy.icon("notebook").click();
+
     cy.button("Visualize").click();
 
     // there were no changes to the question, so we shouldn't have the option to "Save"
@@ -66,7 +68,9 @@ describe("scenarios > question > notebook", () => {
       cy.get("input").type("46");
       cy.contains("Add filter").click();
     });
-    cy.contains("Visualize").click();
+
+    visualize();
+
     cy.contains("2372"); // user's id in the table
     cy.contains("Showing 1 row"); // ensure only one user was returned
   });
@@ -130,8 +134,7 @@ describe("scenarios > question > notebook", () => {
       cy.findByText("EXPR (2)");
     });
 
-    cy.button("Visualize").click();
-    cy.wait("@dataset");
+    visualize();
 
     cy.findByText("EXPR");
     cy.findByText("EXPR (1)");
@@ -176,7 +179,8 @@ describe("scenarios > question > notebook", () => {
     cy.button("Done")
       .should("not.be.disabled")
       .click();
-    cy.contains("Visualize").click();
+
+    visualize();
 
     cy.contains("Showing 99 rows");
 
@@ -225,7 +229,9 @@ describe("scenarios > question > notebook", () => {
       popover()
         .contains("Rating")
         .click();
-      cy.contains("Visualize").click();
+
+      visualize();
+
       cy.contains("Orders + Reviews");
       cy.contains("3");
     });
@@ -241,7 +247,9 @@ describe("scenarios > question > notebook", () => {
       cy.icon("join_left_outer ").click();
       cy.contains("People").click();
       cy.contains("Orders + People");
-      cy.contains("Visualize").click();
+
+      visualize();
+
       cy.contains("Showing first 2,000");
 
       cy.log("Attempt to filter on the joined table");
@@ -294,8 +302,7 @@ describe("scenarios > question > notebook", () => {
       popover().within(() => cy.findByText("A_COLUMN").click());
       popover().within(() => cy.findByText("B_COLUMN").click());
 
-      cy.button("Visualize").click();
-      cy.queryByText("Visualize").then($el => cy.wrap($el).should("not.exist")); // wait for that screen to disappear to avoid "multiple elements" errors
+      visualize();
 
       // check that query worked
       cy.findByText("question a + question b");
@@ -331,11 +338,9 @@ describe("scenarios > question > notebook", () => {
           .should("not.be.disabled")
           .click();
       });
-      cy.button("Visualize").click();
 
-      cy.wait("@cardQuery").then(xhr => {
-        expect(xhr.response.body.error).not.to.exist;
-      });
+      visualize();
+
       cy.findByText("Sum Divide");
     });
 
@@ -431,33 +436,34 @@ describe("scenarios > question > notebook", () => {
         },
       });
 
-      cy.server();
-      cy.route("POST", "/api/dataset").as("dataset");
-
       // Join two previously saved questions
       cy.visit("/");
       cy.findByText("Ask a question").click();
       cy.findByText("Custom question").click();
       cy.findByText("Saved Questions").click();
+
       cy.findByText("12928_Q1").click();
+
       cy.icon("join_left_outer").click();
+
       popover().within(() => {
         cy.findByText("Sample Dataset").click();
         cy.findByText("Saved Questions").click();
       });
       cy.findByText("12928_Q2").click();
+
       cy.contains(/Products? → Category/).click();
+
       popover()
         .contains(/Products? → Category/)
         .click();
-      cy.button("Visualize").click();
 
-      cy.findByText("12928_Q1 + 12928_Q2");
+      visualize();
+
       cy.log("Reported failing in v1.35.4.1 and `master` on July, 16 2020");
-      // TODO: Add a positive assertion once this issue is fixed
-      cy.wait("@dataset").then(xhr => {
-        expect(xhr.response.body.error).not.to.exist;
-      });
+
+      cy.findByText("12928_Q1 + 12928_Q2");
+      cy.findAllByText(/Products? → Category/).should("have.length", 2);
     });
 
     it.skip("should join saved question with sorted metric (metabase#13744)", () => {
@@ -734,7 +740,8 @@ describe("scenarios > question > notebook", () => {
         cy.findByText("Add filter").click();
       });
 
-      cy.button("Visualize").click();
+      visualize();
+
       cy.findByText("Gadget").should("exist");
       cy.findByText("Gizmo").should("not.exist");
 
@@ -775,7 +782,8 @@ describe("scenarios > question > notebook", () => {
         .should("not.be.disabled")
         .click();
 
-      cy.button("Visualize").click();
+      visualize();
+
       cy.contains("Example");
       cy.contains("Big");
       cy.contains("Small");
@@ -796,7 +804,8 @@ describe("scenarios > question > notebook", () => {
         .should("not.be.disabled")
         .click();
 
-      cy.button("Visualize").click();
+      visualize();
+
       cy.contains("Showing 97 rows");
     });
 
@@ -827,7 +836,8 @@ describe("scenarios > question > notebook", () => {
           .should("not.be.disabled")
           .click();
 
-        cy.button("Visualize").click();
+        visualize();
+
         cy.contains(filter);
         cy.contains(result);
       });
@@ -853,7 +863,6 @@ function joinTwoSavedQuestions() {
         "source-table": PRODUCTS_ID,
       },
     }).then(() => {
-      cy.intercept("POST", "/api/dataset").as("cardQuery");
       cy.visit(`/question/new`);
       cy.findByText("Custom question").click();
 
@@ -876,8 +885,7 @@ function joinTwoSavedQuestions() {
         .findByText("ID")
         .click();
 
-      cy.button("Visualize").click();
-      cy.wait("@cardQuery");
+      visualize();
 
       cy.icon("notebook").click();
       cy.url().should("contain", "/notebook");
diff --git a/frontend/test/metabase/scenarios/question/reproductions/13097-mongo-apply-distinct-count-multiple-columns.cy.spec.js b/frontend/test/metabase/scenarios/question/reproductions/13097-mongo-apply-distinct-count-multiple-columns.cy.spec.js
index 3a793e41b339459d60af565248bb8d057281e2ab..5121dae582e01db3b0271b9fd3907967e716a050 100644
--- a/frontend/test/metabase/scenarios/question/reproductions/13097-mongo-apply-distinct-count-multiple-columns.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/reproductions/13097-mongo-apply-distinct-count-multiple-columns.cy.spec.js
@@ -1,4 +1,4 @@
-import { restore } from "__support__/e2e/cypress";
+import { restore, visualize } from "__support__/e2e/cypress";
 
 const MONGO_DB_NAME = "QA Mongo4";
 
@@ -27,8 +27,7 @@ describe.skip("issue 13097", () => {
 
     cy.intercept("POST", "/api/dataset").as("dataset");
 
-    cy.button("Visualize").click();
-    cy.wait("@dataset");
+    visualize();
 
     cy.log("Reported failing on stats ~v0.36.3");
     cy.findAllByText("1,966").should("have.length", 1); // City
diff --git a/frontend/test/metabase/scenarios/question/reproductions/13468.cy.spec.js b/frontend/test/metabase/scenarios/question/reproductions/13468.cy.spec.js
index c1fc1706cf741d09fc3728ec691683c064c2845b..421b76bbfdcab95543f6511efca06b5e282e55b4 100644
--- a/frontend/test/metabase/scenarios/question/reproductions/13468.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/reproductions/13468.cy.spec.js
@@ -1,4 +1,4 @@
-import { restore, openOrdersTable } from "__support__/e2e/cypress";
+import { restore, openOrdersTable, visualize } from "__support__/e2e/cypress";
 
 describe("issue 13468", () => {
   beforeEach(() => {
@@ -13,7 +13,7 @@ describe("issue 13468", () => {
     cy.findByText("Join data").click();
     cy.findByText("Products").click();
 
-    visualizeResults();
+    visualize();
 
     saveQuestion();
 
@@ -23,11 +23,6 @@ describe("issue 13468", () => {
   });
 });
 
-function visualizeResults() {
-  cy.button("Visualize").click();
-  cy.wait("@dataset");
-}
-
 function saveQuestion() {
   cy.findByText("Save").click();
   cy.button("Save").click();
diff --git a/frontend/test/metabase/scenarios/question/reproductions/15342-mysql-correct-joins-order.cy.spec.js b/frontend/test/metabase/scenarios/question/reproductions/15342-mysql-correct-joins-order.cy.spec.js
index 5f8487b5eefa1edf77e8873b7b820aaa2d27b7cd..6802a5a5a268fe8f4ef9ba567bf86c857df011f6 100644
--- a/frontend/test/metabase/scenarios/question/reproductions/15342-mysql-correct-joins-order.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/reproductions/15342-mysql-correct-joins-order.cy.spec.js
@@ -1,11 +1,9 @@
-import { restore, popover } from "__support__/e2e/cypress";
+import { restore, popover, visualize } from "__support__/e2e/cypress";
 
 const MYSQL_DB_NAME = "QA MySQL8";
 
 describe.skip("issue 15342", () => {
   beforeEach(() => {
-    cy.intercept("POST", "/api/dataset").as("query");
-
     restore("mysql-8");
     cy.signInAsAdmin();
 
@@ -32,8 +30,7 @@ describe.skip("issue 15342", () => {
       joinType: "inner",
     });
 
-    cy.button("Visualize").click();
-    cy.wait("@query");
+    visualize();
 
     cy.get(".Visualization").within(() => {
       cy.findByText("Email"); // from People table
diff --git a/frontend/test/metabase/scenarios/question/reproductions/17512.cy.spec.js b/frontend/test/metabase/scenarios/question/reproductions/17512.cy.spec.js
index 5e8e984e6f3c7d738111a5ff488b1007d6263152..1d903d10083d185a5be94a86d7b7c5ec66e31a29 100644
--- a/frontend/test/metabase/scenarios/question/reproductions/17512.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/reproductions/17512.cy.spec.js
@@ -1,4 +1,9 @@
-import { restore, openOrdersTable, popover } from "__support__/e2e/cypress";
+import {
+  restore,
+  openOrdersTable,
+  popover,
+  visualize,
+} from "__support__/e2e/cypress";
 
 describe.skip("issue 17512", () => {
   beforeEach(() => {
@@ -21,10 +26,8 @@ describe.skip("issue 17512", () => {
 
     addCustomColumn("1 + 1", "CC");
 
-    cy.button("Visualize").click();
-
-    cy.wait("@dataset").then(({ response }) => {
-      expect(response.body.error).not.to.exist;
+    visualize(response => {
+      expect(response.body.error).to.not.exist;
     });
 
     cy.findByText("CE");
diff --git a/frontend/test/metabase/scenarios/question/reproductions/17514-ui-overlay.cy.spec.js b/frontend/test/metabase/scenarios/question/reproductions/17514-ui-overlay.cy.spec.js
index 8a2ed6638908612439924398aaa108e98a94e144..51a255a8d697925962788c5836145605f1cbd9b5 100644
--- a/frontend/test/metabase/scenarios/question/reproductions/17514-ui-overlay.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/reproductions/17514-ui-overlay.cy.spec.js
@@ -4,6 +4,7 @@ import {
   filterWidget,
   saveDashboard,
   editDashboard,
+  visualize,
 } from "__support__/e2e/cypress";
 
 import { setAdHocFilter } from "../../native-filters/helpers/e2e-date-filter-helpers";
@@ -112,7 +113,7 @@ describe("issue 17514", () => {
 
       removeJoinedTable();
 
-      visualizeResults();
+      visualize();
 
       cy.findByText("Save").click();
 
@@ -127,7 +128,7 @@ describe("issue 17514", () => {
       cy.findByText("Join data").click();
       cy.findByText("Products").click();
 
-      visualizeResults();
+      visualize();
 
       // Cypress cannot click elements that are blocked by an overlay so this will immediately fail if the issue is not fixed
       cy.findByText("110.93").click();
@@ -155,11 +156,6 @@ function closeModal() {
   });
 }
 
-function visualizeResults() {
-  cy.button("Visualize").click();
-  cy.wait("@dataset");
-}
-
 function openNotebookMode() {
   cy.icon("notebook").click();
 }
diff --git a/frontend/test/metabase/scenarios/question/reproductions/17710-notebook-incomplete-joins-removed.cy.spec.js b/frontend/test/metabase/scenarios/question/reproductions/17710-notebook-incomplete-joins-removed.cy.spec.js
index e8e85f2959d8acc75a40a47f7ed6f072e2978ca5..baa7fa271f4528f33cb8cc147decdcfc0cebf484 100644
--- a/frontend/test/metabase/scenarios/question/reproductions/17710-notebook-incomplete-joins-removed.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/reproductions/17710-notebook-incomplete-joins-removed.cy.spec.js
@@ -1,4 +1,9 @@
-import { restore, popover, openOrdersTable } from "__support__/e2e/cypress";
+import {
+  restore,
+  popover,
+  openOrdersTable,
+  visualize,
+} from "__support__/e2e/cypress";
 
 describe("issue 17710", () => {
   beforeEach(() => {
@@ -19,7 +24,7 @@ describe("issue 17710", () => {
       cy.icon("add").click();
     });
 
-    visualizeResults();
+    visualize();
 
     cy.icon("notebook")
       .click()
@@ -31,8 +36,3 @@ describe("issue 17710", () => {
       });
   });
 });
-
-function visualizeResults() {
-  cy.button("Visualize").click();
-  cy.wait("@dataset");
-}
diff --git a/frontend/test/metabase/scenarios/question/reproductions/17767-cannot-join-on-aggregation-with-implicit-joins.cy.spec.js b/frontend/test/metabase/scenarios/question/reproductions/17767-cannot-join-on-aggregation-with-implicit-joins.cy.spec.js
index 92f27bf4a4cfd7f9125105a3d99a76a137e42bf6..fa5cbfad9882d097982ce7eee0502e4921e36465 100644
--- a/frontend/test/metabase/scenarios/question/reproductions/17767-cannot-join-on-aggregation-with-implicit-joins.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/reproductions/17767-cannot-join-on-aggregation-with-implicit-joins.cy.spec.js
@@ -1,4 +1,4 @@
-import { restore, popover } from "__support__/e2e/cypress";
+import { restore, popover, visualize } from "__support__/e2e/cypress";
 import { SAMPLE_DATASET } from "__support__/e2e/cypress_sample_dataset";
 
 const { ORDERS, ORDERS_ID, PRODUCTS } = SAMPLE_DATASET;
@@ -41,9 +41,8 @@ describe.skip("issue 17767", () => {
       .contains(/Products? ID/)
       .click();
 
-    cy.button("Visualize").click();
-    cy.wait("@dataset").then(({ response }) => {
-      expect(response.body.error).not.to.exist;
+    visualize(response => {
+      expect(response.body.error).to.not.exist;
     });
 
     cy.findByText("xavier");
diff --git a/frontend/test/metabase/scenarios/question/reproductions/17963-mongo-filter-expression-compare-two-fields.cy.spec.js b/frontend/test/metabase/scenarios/question/reproductions/17963-mongo-filter-expression-compare-two-fields.cy.spec.js
index 8b000d9f7fc5beeef7a96777ff79bccd3ed57673..96659e7841140b6ed4f557fc6d2e2ef653d472e4 100644
--- a/frontend/test/metabase/scenarios/question/reproductions/17963-mongo-filter-expression-compare-two-fields.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/reproductions/17963-mongo-filter-expression-compare-two-fields.cy.spec.js
@@ -1,4 +1,4 @@
-import { restore, popover } from "__support__/e2e/cypress";
+import { restore, popover, visualize } from "__support__/e2e/cypress";
 
 describe("issue 17963", () => {
   beforeEach(() => {
@@ -30,7 +30,7 @@ describe("issue 17963", () => {
     cy.findByText("Pick the metric you want to see").click();
     cy.findByText("Count of rows").click();
 
-    cy.button("Visualize").click();
+    visualize();
 
     cy.get(".ScalarValue").contains("1,337");
   });
diff --git a/frontend/test/metabase/scenarios/question/reproductions/18502-cannot-join-two-saved-questions-same-table.cy.spec.js b/frontend/test/metabase/scenarios/question/reproductions/18502-cannot-join-two-saved-questions-same-table.cy.spec.js
index 67e9e5d163912594ca13e083bf414adf126b0f82..83341d3ea6c66636722fc18bd65a0bdf337ebf33 100644
--- a/frontend/test/metabase/scenarios/question/reproductions/18502-cannot-join-two-saved-questions-same-table.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/reproductions/18502-cannot-join-two-saved-questions-same-table.cy.spec.js
@@ -1,4 +1,4 @@
-import { restore, popover } from "__support__/e2e/cypress";
+import { restore, popover, visualize } from "__support__/e2e/cypress";
 import { SAMPLE_DATASET } from "__support__/e2e/cypress_sample_dataset";
 
 const { PEOPLE, PEOPLE_ID } = SAMPLE_DATASET;
@@ -33,9 +33,7 @@ describe.skip("issue 18502", () => {
     cy.findByText("Created At").click();
     cy.findByText("Birth Date").click();
 
-    cy.button("Visualize").click();
-
-    cy.wait("@dataset").then(({ response }) => {
+    visualize(response => {
       expect(response.body.error).to.not.exist;
     });
 
diff --git a/frontend/test/metabase/scenarios/question/reproductions/18512-cannot-join-two-saved-questions-with-same-implicit-explicit-grouped-field.cy.spec.js b/frontend/test/metabase/scenarios/question/reproductions/18512-cannot-join-two-saved-questions-with-same-implicit-explicit-grouped-field.cy.spec.js
index 8b29abe98a56d6a1467fab4c4358ca50df000cba..2b3b73c77ce7ff057a2c9c21f49f63c6266b8165 100644
--- a/frontend/test/metabase/scenarios/question/reproductions/18512-cannot-join-two-saved-questions-with-same-implicit-explicit-grouped-field.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/reproductions/18512-cannot-join-two-saved-questions-with-same-implicit-explicit-grouped-field.cy.spec.js
@@ -1,4 +1,4 @@
-import { restore, popover } from "__support__/e2e/cypress";
+import { restore, popover, visualize } from "__support__/e2e/cypress";
 import { SAMPLE_DATASET } from "__support__/e2e/cypress_sample_dataset";
 
 const { PRODUCTS, PRODUCTS_ID, REVIEWS, REVIEWS_ID } = SAMPLE_DATASET;
@@ -38,9 +38,7 @@ describe.skip("issue 18512", () => {
       .findByText("Products → Created At")
       .click();
 
-    cy.button("Visualize").click();
-
-    cy.wait("@dataset").then(({ response }) => {
+    visualize(response => {
       expect(response.body.error).to.not.exist;
     });
 
diff --git a/frontend/test/metabase/scenarios/question/reproductions/4482-temporal-min-max.cy.spec.js b/frontend/test/metabase/scenarios/question/reproductions/4482-temporal-min-max.cy.spec.js
index 1e65dae92fa3df74f5e52bbc3df62427f83ad565..803d9a00aaaa9fee226321b62f187af8f96424e8 100644
--- a/frontend/test/metabase/scenarios/question/reproductions/4482-temporal-min-max.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/reproductions/4482-temporal-min-max.cy.spec.js
@@ -1,4 +1,4 @@
-import { restore } from "__support__/e2e/cypress";
+import { restore, visualize } from "__support__/e2e/cypress";
 
 describe("issue 4482", () => {
   beforeEach(() => {
@@ -21,8 +21,8 @@ describe("issue 4482", () => {
     cy.findByText("Rating");
     cy.contains("Created At").click();
 
-    cy.button("Visualize").click();
-    cy.wait("@dataset");
+    visualize();
+
     cy.findByText("April 1, 2016, 12:00 AM");
   });
 
@@ -39,8 +39,8 @@ describe("issue 4482", () => {
     cy.findByText("Rating");
     cy.contains("Created At").click();
 
-    cy.button("Visualize").click();
-    cy.wait("@dataset");
+    visualize();
+
     cy.findByText("April 1, 2019, 12:00 AM");
   });
 
diff --git a/frontend/test/metabase/scenarios/question/reproductions/6239.cy.spec.js b/frontend/test/metabase/scenarios/question/reproductions/6239.cy.spec.js
index a21b74d8edf3bf277edb84556201b95b7cd6213f..8ae07f467de2cf03b84010830650b3db464d3ea1 100644
--- a/frontend/test/metabase/scenarios/question/reproductions/6239.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/reproductions/6239.cy.spec.js
@@ -1,4 +1,9 @@
-import { restore, popover, openOrdersTable } from "__support__/e2e/cypress";
+import {
+  restore,
+  popover,
+  openOrdersTable,
+  visualize,
+} from "__support__/e2e/cypress";
 
 describe("issue 6239", () => {
   beforeEach(() => {
@@ -29,7 +34,7 @@ describe("issue 6239", () => {
       .contains(/^CE$/)
       .click();
 
-    cy.button("Visualize").click();
+    visualize();
 
     // Line chart renders initially. Switch to the table view.
     cy.icon("table2").click();
@@ -52,7 +57,7 @@ describe("issue 6239", () => {
     cy.icon("arrow_up").should("not.exist");
     cy.icon("arrow_down");
 
-    cy.button("Visualize").click();
+    visualize();
 
     cy.get(".cellData")
       .eq(1)
diff --git a/frontend/test/metabase/scenarios/sharing/alert/alert.cy.spec.js b/frontend/test/metabase/scenarios/sharing/alert/alert.cy.spec.js
index 72c2b43b0a7c0bfcb4d3285ecf496680a47b376e..f146c08bae699f18d58853642de7b6e4f4f3be88 100644
--- a/frontend/test/metabase/scenarios/sharing/alert/alert.cy.spec.js
+++ b/frontend/test/metabase/scenarios/sharing/alert/alert.cy.spec.js
@@ -4,6 +4,7 @@ import {
   createBasicAlert,
   popover,
   openPeopleTable,
+  visualize,
 } from "__support__/e2e/cypress";
 
 // Ported from alert.e2e.spec.js
@@ -176,7 +177,8 @@ describe("scenarios > alert", () => {
         cy.findByPlaceholderText("Find...").type("Cr");
         cy.findByText("Created At").click();
       });
-      cy.button("Visualize").click();
+
+      visualize();
 
       // Set a goal
       setGoal("35");
diff --git a/frontend/test/metabase/scenarios/smoketest/admin.cy.spec.js b/frontend/test/metabase/scenarios/smoketest/admin.cy.spec.js
index e3d8e8fae3ffc8aaa0557fd74d5777b5380092ee..9ad28be0af42f863d11340c12d2b1745f27945d0 100644
--- a/frontend/test/metabase/scenarios/smoketest/admin.cy.spec.js
+++ b/frontend/test/metabase/scenarios/smoketest/admin.cy.spec.js
@@ -1,4 +1,4 @@
-import { restore, sidebar } from "__support__/e2e/cypress";
+import { restore, sidebar, visualize } from "__support__/e2e/cypress";
 import { USERS } from "__support__/e2e/cypress_data";
 
 const { admin } = USERS;
@@ -165,7 +165,8 @@ describe("metabase-smoketest > admin", () => {
 
       cy.findByText("Join data").click();
       cy.findByText("People").click();
-      cy.button("Visualize").click();
+
+      visualize();
 
       // Summarize by State
       cy.findAllByText("Summarize")
diff --git a/frontend/test/metabase/scenarios/smoketest/admin_setup.cy.spec.js b/frontend/test/metabase/scenarios/smoketest/admin_setup.cy.spec.js
index 812b9ef4046423e21600e0cf620306704d3cc327..27586e5f31e5456507ca0e60848a2d3c62fd3cb0 100644
--- a/frontend/test/metabase/scenarios/smoketest/admin_setup.cy.spec.js
+++ b/frontend/test/metabase/scenarios/smoketest/admin_setup.cy.spec.js
@@ -5,6 +5,7 @@ import {
   setupLocalHostEmail,
   modal,
   openPeopleTable,
+  visualize,
 } from "__support__/e2e/cypress";
 import { USERS } from "__support__/e2e/cypress_data";
 
@@ -477,7 +478,8 @@ describe("smoketest > admin_setup", () => {
         .last()
         .click();
       cy.findByText("Add filter").click();
-      cy.button("Visualize").click();
+
+      visualize();
 
       cy.findAllByText("Awesome Concrete Shoes");
       cy.findByText("Mediocre Wooden Bench").should("not.exist");
diff --git a/frontend/test/metabase/scenarios/smoketest/user.cy.spec.js b/frontend/test/metabase/scenarios/smoketest/user.cy.spec.js
index 09e9af460ae3d4eafb515ecf9696b9c4e0298f44..6ea9860cab6969a5a36a07299964da2f2541de46 100644
--- a/frontend/test/metabase/scenarios/smoketest/user.cy.spec.js
+++ b/frontend/test/metabase/scenarios/smoketest/user.cy.spec.js
@@ -1,4 +1,4 @@
-import { restore, sidebar, popover } from "__support__/e2e/cypress";
+import { restore, sidebar, popover, visualize } from "__support__/e2e/cypress";
 
 describe("smoketest > user", () => {
   // Goal: user can use all the features of the simple question and notebook editor
@@ -24,7 +24,7 @@ describe("smoketest > user", () => {
 
     cy.findByText("Average of Rating");
 
-    cy.button("Visualize").click();
+    visualize();
 
     cy.icon("bar");
     cy.findAllByText("Vendor is not empty");
@@ -67,7 +67,8 @@ describe("smoketest > user", () => {
     popover().within(() => {
       cy.findAllByText("Title").click();
     });
-    cy.button("Visualize").click();
+
+    visualize();
 
     cy.get("@firstTableCell").contains("Aerodynamic Bronze Hat");
 
@@ -106,9 +107,9 @@ describe("smoketest > user", () => {
     cy.findByText("Greater than or equal to").click();
     cy.get("input[placeholder='Enter a number']").type("5");
     cy.findByText("Add filter").click();
-    cy.button("Visualize").click();
 
-    cy.button("Visualize").should("not.exist");
+    visualize();
+
     cy.get("svg");
     cy.findByText("Average of Rating is greater than or equal to 5");
 
@@ -180,7 +181,8 @@ describe("smoketest > user", () => {
     cy.findByText("Count of rows").click();
     cy.findByText("Pick a column to group by").click();
     cy.icon("calendar").click();
-    cy.button("Visualize").click();
+
+    visualize();
 
     cy.get("svg");
     cy.findAllByText("Created At");
@@ -193,7 +195,8 @@ describe("smoketest > user", () => {
 
     cy.icon("join_left_outer").click();
     cy.findByText("People").click(); // column selection happens automatcially
-    cy.button("Visualize").click();
+
+    visualize();
 
     cy.findByText("User → ID");
     cy.findByText("Created At");
@@ -205,7 +208,8 @@ describe("smoketest > user", () => {
 
     cy.findByText("Row limit").click();
     cy.get("input[type='number']").type("10");
-    cy.button("Visualize").click();
+
+    visualize();
 
     cy.get(".TableInteractive-cellWrapper--firstColumn").should(
       "have.length",
@@ -236,7 +240,7 @@ describe("smoketest > user", () => {
     // Distinctions
     // *** This test needs to be improved with variables that will change if the Sample data changes
 
-    cy.button("Visualize").click();
+    visualize();
 
     cy.findByText("Category").click();
     cy.findByText("Distincts").click();
@@ -330,7 +334,8 @@ describe("smoketest > user", () => {
       .last()
       .click();
     cy.findByText("People").click();
-    cy.button("Visualize").click();
+
+    visualize();
 
     cy.findByText("Longitude").click();
 
diff --git a/frontend/test/metabase/scenarios/visualizations/drillthroughs/chart_drill.cy.spec.js b/frontend/test/metabase/scenarios/visualizations/drillthroughs/chart_drill.cy.spec.js
index a1feb0b0822e8c8c082ea05a27c9d4719262c65e..8e02f3e4f74df31f760955112f5ce1acc688bc36 100644
--- a/frontend/test/metabase/scenarios/visualizations/drillthroughs/chart_drill.cy.spec.js
+++ b/frontend/test/metabase/scenarios/visualizations/drillthroughs/chart_drill.cy.spec.js
@@ -5,6 +5,7 @@ import {
   popover,
   sidebar,
   visitQuestionAdhoc,
+  visualize,
 } from "__support__/e2e/cypress";
 import { USER_GROUPS } from "__support__/e2e/cypress_data";
 import { SAMPLE_DATASET } from "__support__/e2e/cypress_sample_dataset";
@@ -322,8 +323,8 @@ describe("scenarios > visualizations > drillthroughs > chart drill", () => {
       .type("1");
     cy.findByText("Add filter").click();
 
-    // Visualize: line
-    cy.button("Visualize").click();
+    visualize();
+
     cy.findByText("Visualization").click();
     cy.icon("line").click();
     cy.findByText("Done").click();
diff --git a/frontend/test/metabase/scenarios/visualizations/trendline.cy.spec.js b/frontend/test/metabase/scenarios/visualizations/trendline.cy.spec.js
index b8cdc14eeae27e4677a0edcdee252e7da9914216..228e2f6207bf36777172b8d8db4765e1f686ca5d 100644
--- a/frontend/test/metabase/scenarios/visualizations/trendline.cy.spec.js
+++ b/frontend/test/metabase/scenarios/visualizations/trendline.cy.spec.js
@@ -1,4 +1,9 @@
-import { restore, openOrdersTable, sidebar } from "__support__/e2e/cypress";
+import {
+  restore,
+  openOrdersTable,
+  sidebar,
+  visualize,
+} from "__support__/e2e/cypress";
 
 describe("scenarios > question > trendline", () => {
   beforeEach(() => {
@@ -26,10 +31,8 @@ describe("scenarios > question > trendline", () => {
     cy.findByText("by month").click();
     cy.findByText("Year").click();
 
-    cy.button("Visualize").click();
+    visualize();
 
-    // Check graph is there
-    cy.button("Visualize").should("not.exist");
     cy.findByText("Visualization");
     cy.get("rect");
 
diff --git a/frontend/test/metabase/scenarios/visualizations/waterfall.cy.spec.js b/frontend/test/metabase/scenarios/visualizations/waterfall.cy.spec.js
index fd37cf61176d3750b62fbbb147b0eb4d24bd4cbd..37c0b891f747473b65909d85e3f9e1dcd83154a9 100644
--- a/frontend/test/metabase/scenarios/visualizations/waterfall.cy.spec.js
+++ b/frontend/test/metabase/scenarios/visualizations/waterfall.cy.spec.js
@@ -3,6 +3,7 @@ import {
   restore,
   visitQuestionAdhoc,
   openNativeEditor,
+  visualize,
 } from "__support__/e2e/cypress";
 import { SAMPLE_DATASET } from "__support__/e2e/cypress_sample_dataset";
 
@@ -112,7 +113,8 @@ describe("scenarios > visualizations > waterfall", () => {
       .blur();
     cy.button("Done").click();
 
-    cy.button("Visualize").click();
+    visualize();
+
     cy.contains("Visualization").click();
     cy.icon("waterfall").click();
 
@@ -126,7 +128,8 @@ describe("scenarios > visualizations > waterfall", () => {
     cy.findByText("Pick a column to group by").click();
     cy.findByText("Created At").click();
 
-    cy.button("Visualize").click();
+    visualize();
+
     cy.contains("Visualization").click();
     cy.icon("waterfall").click();