diff --git a/frontend/test/__support__/e2e/commands.js b/frontend/test/__support__/e2e/commands.js
index f31417d75969d5d20f73b147f86affee5e80bdde..273c895f34e0028ca5b341faf23a23d6c6522d90 100644
--- a/frontend/test/__support__/e2e/commands.js
+++ b/frontend/test/__support__/e2e/commands.js
@@ -39,6 +39,10 @@ Cypress.Commands.add("icon", icon_name => {
   cy.get(`.Icon-${icon_name}`);
 });
 
+Cypress.Commands.add("button", button_name => {
+  cy.findByRole("button", { name: button_name });
+});
+
 Cypress.Commands.add("createDashboard", name => {
   cy.log(`Create a dashboard: ${name}`);
   cy.request("POST", "/api/dashboard", { name });
diff --git a/frontend/test/metabase-db/mongo/query.cy.spec.js b/frontend/test/metabase-db/mongo/query.cy.spec.js
index aaa2bc1f5352825771fd3b34a98db3baa8b7fef5..8c15d2efe07115557f4384e2042911df2b6dd85c 100644
--- a/frontend/test/metabase-db/mongo/query.cy.spec.js
+++ b/frontend/test/metabase-db/mongo/query.cy.spec.js
@@ -67,7 +67,7 @@ describe("mongodb > user > query", () => {
       cy.server();
       cy.route("POST", "/api/dataset").as("dataset");
 
-      cy.findByText("Visualize").click();
+      cy.button("Visualize").click();
       cy.wait("@dataset");
 
       cy.log("Reported failing on stats ~v0.36.3");
diff --git a/frontend/test/metabase-db/postgres/custom-column.cy.spec.js b/frontend/test/metabase-db/postgres/custom-column.cy.spec.js
index 9e59ea1c6e39f3d811356551319ce0101148b27e..ba2249c3f402c77c543d9df6925f993bf926332e 100644
--- a/frontend/test/metabase-db/postgres/custom-column.cy.spec.js
+++ b/frontend/test/metabase-db/postgres/custom-column.cy.spec.js
@@ -48,7 +48,7 @@ describe("postgres > question > custom columns", () => {
         .click();
     });
 
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
     cy.findByText("Arnold Adams");
   });
 
@@ -90,7 +90,7 @@ describe("postgres > question > custom columns", () => {
       .click();
     cy.findByText("Function Percentile expects 1 argument").should("not.exist");
     cy.get("@description").type("A");
-    cy.findByRole("button", { name: "Done" })
+    cy.button("Done")
       .should("not.be.disabled")
       .click();
     // Todo: Add positive assertions once this is fixed
diff --git a/frontend/test/metabase-db/postgres/native.cy.spec.js b/frontend/test/metabase-db/postgres/native.cy.spec.js
index 887f902cd6efefb885f1c83ca2a9be4049e3a86e..ad6ff5dadf143144be8f2ddbb40cba9215e3eb62 100644
--- a/frontend/test/metabase-db/postgres/native.cy.spec.js
+++ b/frontend/test/metabase-db/postgres/native.cy.spec.js
@@ -16,7 +16,7 @@ describe("postgres > question > native", () => {
     cy.get(".ace_content").type("select pg_sleep(60)");
     cy.findByText("Save").click();
     cy.findByLabelText("Name").type("14957");
-    cy.findByRole("button", { name: "Save" }).click();
+    cy.button("Save").click();
     modal().should("not.exist");
   });
 });
diff --git a/frontend/test/metabase-smoketest/admin.cy.spec.js b/frontend/test/metabase-smoketest/admin.cy.spec.js
index 41ef464270f76f143b76f6d478b0d8b46ae13154..537e96268a9c51e6a9474f14cd78e221bcf60e26 100644
--- a/frontend/test/metabase-smoketest/admin.cy.spec.js
+++ b/frontend/test/metabase-smoketest/admin.cy.spec.js
@@ -168,7 +168,7 @@ describe("metabase-smoketest > admin", () => {
 
       cy.findByText("Join data").click();
       cy.findByText("People").click();
-      cy.findByText("Visualize").click();
+      cy.button("Visualize").click();
 
       // Summarize by State
       cy.findAllByText("Summarize")
diff --git a/frontend/test/metabase-smoketest/admin_setup.cy.spec.js b/frontend/test/metabase-smoketest/admin_setup.cy.spec.js
index 434c02998c87832e19dd26e29a7c9260d131078a..9a7da9fb36a54ac3599ed927d074d1dab070fe0a 100644
--- a/frontend/test/metabase-smoketest/admin_setup.cy.spec.js
+++ b/frontend/test/metabase-smoketest/admin_setup.cy.spec.js
@@ -478,7 +478,7 @@ describe("smoketest > admin_setup", () => {
         .last()
         .click();
       cy.findByText("Add filter").click();
-      cy.findByText("Visualize").click();
+      cy.button("Visualize").click();
 
       cy.findAllByText("Awesome Concrete Shoes");
       cy.findByText("Mediocre Wooden Bench").should("not.exist");
diff --git a/frontend/test/metabase-smoketest/user.cy.spec.js b/frontend/test/metabase-smoketest/user.cy.spec.js
index 4b93306c5d56a757b960e2094220a96d5d541db4..d134c0014261f53dced868db03cf567da09f70bc 100644
--- a/frontend/test/metabase-smoketest/user.cy.spec.js
+++ b/frontend/test/metabase-smoketest/user.cy.spec.js
@@ -24,7 +24,7 @@ describe("smoketest > user", () => {
 
     cy.findByText("Average of Rating");
 
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
 
     cy.icon("bar");
     cy.findAllByText("Vendor is not empty");
@@ -67,7 +67,7 @@ describe("smoketest > user", () => {
     popover().within(() => {
       cy.findAllByText("Title").click();
     });
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
 
     cy.get("@firstTableCell").contains("Aerodynamic Bronze Hat");
 
@@ -106,9 +106,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.findByText("Visualize").click();
+    cy.button("Visualize").click();
 
-    cy.findByText("Visualize").should("not.exist");
+    cy.button("Visualize").should("not.exist");
     cy.get("svg");
     cy.findByText("Average of Rating is greater than or equal to 5");
 
@@ -180,7 +180,7 @@ describe("smoketest > user", () => {
     cy.findByText("Count of rows").click();
     cy.findByText("Pick a column to group by").click();
     cy.icon("calendar").click();
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
 
     cy.get("svg");
     cy.findAllByText("Created At");
@@ -223,7 +223,7 @@ describe("smoketest > user", () => {
       "Demo Column",
     );
     cy.findByText("Done").click();
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
 
     cy.findByText("ID");
     cy.icon("table2");
@@ -238,7 +238,7 @@ describe("smoketest > user", () => {
 
     cy.icon("join_left_outer").click();
     cy.findByText("People").click(); // column selection happens automatcially
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
 
     cy.findByText("User → ID");
     cy.findByText("Created At");
@@ -250,7 +250,7 @@ describe("smoketest > user", () => {
 
     cy.findByText("Row limit").click();
     cy.get("input[type='number']").type("10");
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
 
     cy.get(".TableInteractive-cellWrapper--firstColumn").should(
       "have.length",
@@ -281,7 +281,7 @@ describe("smoketest > user", () => {
     // Distinctions
     // *** This test needs to be improved with variables that will change if the Sample data changes
 
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
 
     cy.findByText("Category").click();
     cy.findByText("Distincts").click();
@@ -375,7 +375,7 @@ describe("smoketest > user", () => {
       .last()
       .click();
     cy.findByText("People").click();
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
 
     cy.findByText("Longitude").click();
 
diff --git a/frontend/test/metabase/scenarios/admin/databases/add.cy.spec.js b/frontend/test/metabase/scenarios/admin/databases/add.cy.spec.js
index 8c79f80cc7e7b63a755834b4c8b6df4929ba8d98..56cc2428f73ce528c8c9cad71704cb9b5a4fd00a 100644
--- a/frontend/test/metabase/scenarios/admin/databases/add.cy.spec.js
+++ b/frontend/test/metabase/scenarios/admin/databases/add.cy.spec.js
@@ -44,7 +44,7 @@ describe("scenarios > admin > databases > add", () => {
     typeField("Database name", "test_postgres_db");
     typeField("Username", "uberadmin");
 
-    cy.findByRole("button", { name: "Save" })
+    cy.button("Save")
       .should("not.be.disabled")
       .click();
 
@@ -80,7 +80,7 @@ describe("scenarios > admin > databases > add", () => {
     typeField("Database name", "test_postgres_db");
     typeField("Username", "uberadmin");
 
-    cy.findByRole("button", { name: "Save" })
+    cy.button("Save")
       .should("not.be.disabled")
       .click();
 
@@ -88,7 +88,7 @@ describe("scenarios > admin > databases > add", () => {
 
     toggleFieldWithDisplayName("let me choose when Metabase syncs and scans");
 
-    cy.findByRole("button", { name: "Next" })
+    cy.button("Next")
       .should("not.be.disabled")
       .click();
 
@@ -107,17 +107,17 @@ describe("scenarios > admin > databases > add", () => {
     typeField("Database name", "test_postgres_db");
     typeField("Username", "uberadmin");
 
-    cy.findByRole("button", { name: "Save" }).should("not.be.disabled");
+    cy.button("Save").should("not.be.disabled");
 
     toggleFieldWithDisplayName("let me choose when Metabase syncs and scans");
 
-    cy.findByRole("button", { name: "Next" })
+    cy.button("Next")
       .should("not.be.disabled")
       .click();
 
     cy.findByText("Never, I'll do this manually if I need to").click();
 
-    cy.findByRole("button", { name: "Save" }).click();
+    cy.button("Save").click();
 
     cy.wait("@createDatabase").then(({ request }) => {
       expect(request.body.engine).to.equal("postgres");
@@ -144,7 +144,7 @@ describe("scenarios > admin > databases > add", () => {
     typeField("Database name", "test_postgres_db");
     typeField("Username", "uberadmin");
 
-    cy.findByRole("button", { name: "Save" }).click();
+    cy.button("Save").click();
 
     cy.wait("@createDatabase");
     cy.findByText("DATABASE CONNECTION ERROR").should("exist");
@@ -204,7 +204,7 @@ describe("scenarios > admin > databases > add", () => {
       }).as("createDatabase");
 
       // submit form and check that the file's body is included
-      cy.findByRole("button", { name: "Save" }).click();
+      cy.button("Save").click();
       cy.wait("@createDatabase").should(xhr => {
         expect(xhr.request.body.details["service-account-json"]).to.equal(
           '{"foo": 123}',
diff --git a/frontend/test/metabase/scenarios/admin/permissions/sandboxes.cy.spec.js b/frontend/test/metabase/scenarios/admin/permissions/sandboxes.cy.spec.js
index 075436782a4bec672eb945810eed392284fd183d..7487ada6ab8d67de31e7c322e3afcf5f80839249 100644
--- a/frontend/test/metabase/scenarios/admin/permissions/sandboxes.cy.spec.js
+++ b/frontend/test/metabase/scenarios/admin/permissions/sandboxes.cy.spec.js
@@ -146,7 +146,7 @@ describeWithToken("formatting > sandboxes", () => {
         cy.findByText("Greater than").click();
         cy.findByPlaceholderText("Enter a number").type("100");
         cy.findByText("Add filter").click();
-        cy.findByText("Visualize").click();
+        cy.button("Visualize").click();
 
         cy.log("Make sure user is still sandboxed");
         cy.get(".TableInteractive-cellWrapper--firstColumn").should(
@@ -219,7 +219,7 @@ describeWithToken("formatting > sandboxes", () => {
           .click();
       });
 
-      cy.findByText("Visualize").click();
+      cy.button("Visualize").click();
       cy.findByText("Count by User → ID");
       cy.findByText("11"); // Sum of orders for user with ID #1
     });
@@ -605,7 +605,7 @@ describeWithToken("formatting > sandboxes", () => {
             // Save the question
             cy.findByText("Save").click();
             modal().within(() => {
-              cy.findAllByRole("button", { name: "Save" }).click();
+              cy.button("Save").click();
             });
             // Wait for an update so the other queries don't accidentally cancel it
             cy.wait("@questionUpdate");
@@ -749,12 +749,12 @@ describeWithToken("formatting > sandboxes", () => {
         .eq(1) // No better way of doing this, undfortunately (see table above)
         .click();
       cy.findByText("Grant sandboxed access").click();
-      cy.findAllByRole("button", { name: "Change" }).click();
+      cy.button("Change").click();
       cy.findByText(
         "Use a saved question to create a custom view for this table",
       ).click();
       cy.findByText(QUESTION_NAME).click();
-      cy.findAllByRole("button", { name: "Save" }).click();
+      cy.button("Save").click();
 
       cy.wait("@sandboxTable").then(xhr => {
         expect(xhr.status).to.eq(400);
@@ -778,7 +778,7 @@ describeWithToken("formatting > sandboxes", () => {
       cy.findByText("Count of rows").click();
       cy.findByText("Pick a column to group by").click();
       cy.findByText(/Products? → ID/).click();
-      cy.findByText("Visualize").click();
+      cy.button("Visualize").click();
 
       cy.wait("@dataset").then(xhr => {
         expect(xhr.response.body.error).not.to.exist;
@@ -822,7 +822,7 @@ describeWithToken("formatting > sandboxes", () => {
             .find(".Icon-close")
             .click();
         });
-      cy.findAllByRole("button", { name: "Done" }).click();
+      cy.button("Done").click();
       // Rerun the query
       cy.icon("play")
         .last()
diff --git a/frontend/test/metabase/scenarios/admin/settings/settings.cy.spec.js b/frontend/test/metabase/scenarios/admin/settings/settings.cy.spec.js
index 5450c6a677622a5c8f016b29592444a7149e47ad..74e22fa6fdd5e44790576c1ade8a3aba87c5f864 100644
--- a/frontend/test/metabase/scenarios/admin/settings/settings.cy.spec.js
+++ b/frontend/test/metabase/scenarios/admin/settings/settings.cy.spec.js
@@ -406,7 +406,7 @@ describe("scenarios > admin > settings", () => {
       cy.visit("/admin/settings/email");
       cy.findByText("Send test email").scrollIntoView();
       // Needed to scroll the page down first to be able to use findByRole() - it fails otherwise
-      cy.findByRole("button", { name: "Save changes" }).should("be.disabled");
+      cy.button("Save changes").should("be.disabled");
     });
   });
 
diff --git a/frontend/test/metabase/scenarios/collections/permissions.cy.spec.js b/frontend/test/metabase/scenarios/collections/permissions.cy.spec.js
index 96e6ae936f4439a05b190f51fb5bfa190144e4d6..314d52d7f7dcfcc3ea6a47e2d35f89bd36521add 100644
--- a/frontend/test/metabase/scenarios/collections/permissions.cy.spec.js
+++ b/frontend/test/metabase/scenarios/collections/permissions.cy.spec.js
@@ -475,7 +475,7 @@ describe("collection permissions", () => {
                   cy.findByText("Create a new dashboard").click();
                   cy.get(".AdminSelect").findByText(personalCollection);
                   cy.findByLabelText("Name").type("Foo");
-                  cy.findByRole("button", { name: "Create" }).click();
+                  cy.button("Create").click();
                 });
                 cy.url().should("match", /\/dashboard\/\d+$/);
                 saveDashboard();
diff --git a/frontend/test/metabase/scenarios/collections/personal-collections.cy.spec.js b/frontend/test/metabase/scenarios/collections/personal-collections.cy.spec.js
index 02343631500ed901062300a3d20f27c28ce3d51a..73ac5f9976d347fb699740285481998af4a1ad91 100644
--- a/frontend/test/metabase/scenarios/collections/personal-collections.cy.spec.js
+++ b/frontend/test/metabase/scenarios/collections/personal-collections.cy.spec.js
@@ -106,7 +106,7 @@ describe("personal collections", () => {
           popover()
             .findByText("My personal collection") /* [3] */
             .click();
-          cy.findByRole("button", { name: "Update" }).click();
+          cy.button("Update").click();
           // Clicking on "Foo" would've closed it and would hide its sub-collections (if there were any).
           // By doing this, we're making sure "Bar" lives at the same level as "Foo"
           cy.get("@sidebar")
diff --git a/frontend/test/metabase/scenarios/dashboard/dashboard-drill.cy.spec.js b/frontend/test/metabase/scenarios/dashboard/dashboard-drill.cy.spec.js
index 107f015342dd5725d943f86c1998cfff906e57fc..bb0cc4a597418d5b42ceecd81e7447a701d51b21 100644
--- a/frontend/test/metabase/scenarios/dashboard/dashboard-drill.cy.spec.js
+++ b/frontend/test/metabase/scenarios/dashboard/dashboard-drill.cy.spec.js
@@ -585,10 +585,10 @@ describe("scenarios > dashboard > dashboard drill", () => {
         cy.icon("palette").click();
         cy.get(".Modal").within(() => {
           cy.findByText("Reset to defaults").click();
-          cy.findByRole("button", { name: "Done" }).click();
+          cy.button("Done").click();
         });
         // Save the whole dashboard
-        cy.findByRole("button", { name: "Save" }).click();
+        cy.button("Save").click();
         cy.findByText("You're editing this dashboard.").should("not.exist");
         cy.log("Reported failing on v0.38.0 - link gets dropped");
         cy.get(".DashCard").findAllByText(LINK_NAME);
diff --git a/frontend/test/metabase/scenarios/dashboard/dashboard.cy.spec.js b/frontend/test/metabase/scenarios/dashboard/dashboard.cy.spec.js
index 4ce8f098c9723a228cc7b509ecf755b0a32a578a..cb60e3e6ad4fe9a5d031a02278736d25eaa26e73 100644
--- a/frontend/test/metabase/scenarios/dashboard/dashboard.cy.spec.js
+++ b/frontend/test/metabase/scenarios/dashboard/dashboard.cy.spec.js
@@ -588,7 +588,7 @@ describe("scenarios > dashboard", () => {
         cy.findByPlaceholderText("Enter some text")
           .click()
           .type("Gizmo", { delay: 10 });
-        cy.findByRole("button", { name: "Add filter" })
+        cy.button("Add filter")
           .should("not.be.disabled")
           .click();
         cy.contains("Rustic Paper Wallet");
@@ -728,7 +728,7 @@ describe("scenarios > dashboard", () => {
       popover()
         .findByText("Organic")
         .click();
-      cy.findByRole("button", { name: "Add filter" }).click();
+      cy.button("Add filter").click();
       // Check that the search works
       cy.get("fieldset")
         .contains("Search")
diff --git a/frontend/test/metabase/scenarios/native/native.cy.spec.js b/frontend/test/metabase/scenarios/native/native.cy.spec.js
index 68799db12b1d979e8d41a38ed138fe21dd68e1ca..46fa2ba2965aa184fb17fddc876e7d5f0e3b8327 100644
--- a/frontend/test/metabase/scenarios/native/native.cy.spec.js
+++ b/frontend/test/metabase/scenarios/native/native.cy.spec.js
@@ -560,7 +560,7 @@ describe("scenarios > question > native", () => {
     popover()
       .findByText("Doohickey")
       .click();
-    cy.findByRole("button", { name: "Add filter" }).click();
+    cy.button("Add filter").click();
     // Rerun the query
     cy.get(".NativeQueryEditor .Icon-play").click();
     cy.wait("@dataset").wait("@dataset");
@@ -597,7 +597,7 @@ describe("scenarios > question > native", () => {
     popover()
       .findByText("Doohickey")
       .click();
-    cy.findByRole("button", { name: "Add filter" }).click();
+    cy.button("Add filter").click();
     cy.get(".NativeQueryEditor .Icon-play").click();
     cy.wait("@dataset").then(xhr => {
       expect(xhr.response.body.error).not.to.exist;
diff --git a/frontend/test/metabase/scenarios/onboarding/setup/user_settings.cy.spec.js b/frontend/test/metabase/scenarios/onboarding/setup/user_settings.cy.spec.js
index a8f94890c6c7ca1ac7078e3ad6d44d26e8015bd4..7a5afdacb1332295ecb6f90b23e2fde2d4cd8558 100644
--- a/frontend/test/metabase/scenarios/onboarding/setup/user_settings.cy.spec.js
+++ b/frontend/test/metabase/scenarios/onboarding/setup/user_settings.cy.spec.js
@@ -37,7 +37,7 @@ describe("user > settings", () => {
     cy.findByDisplayValue(first_name);
     cy.findByDisplayValue(last_name);
     cy.findByDisplayValue(email);
-    cy.findByRole("button", { name: "Update" }).should("be.disabled");
+    cy.button("Update").should("be.disabled");
   });
 
   it("should update the user without fetching memberships", () => {
diff --git a/frontend/test/metabase/scenarios/question/custom_column.cy.spec.js b/frontend/test/metabase/scenarios/question/custom_column.cy.spec.js
index e1f128aff1da0542ff724248bc544a488940425f..df5773994e4b9648f2c22291e9a5150822a4701a 100644
--- a/frontend/test/metabase/scenarios/question/custom_column.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/custom_column.cy.spec.js
@@ -42,7 +42,7 @@ describe("scenarios > question > custom columns", () => {
     cy.server();
     cy.route("POST", "/api/dataset").as("dataset");
 
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
     cy.wait("@dataset");
     cy.findByText("There was a problem with your question").should("not.exist");
     cy.get(".Visualization").contains(columnName);
@@ -63,7 +63,7 @@ describe("scenarios > question > custom columns", () => {
       cy.server();
       cy.route("POST", "/api/dataset").as("dataset");
 
-      cy.findByText("Visualize").click();
+      cy.button("Visualize").click();
       cy.wait("@dataset");
       cy.get(".Visualization").contains(columnName);
     });
@@ -109,7 +109,7 @@ describe("scenarios > question > custom columns", () => {
     cy.server();
     cy.route("POST", "/api/dataset").as("dataset");
 
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
     cy.wait("@dataset");
     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
@@ -151,7 +151,7 @@ describe("scenarios > question > custom columns", () => {
     cy.server();
     cy.route("POST", "/api/dataset").as("dataset");
 
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
     cy.wait("@dataset");
 
     cy.get(".Visualization").within(() => {
@@ -189,11 +189,11 @@ describe("scenarios > question > custom columns", () => {
       cy.findByText("Done").click();
     });
 
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
 
     // wait for results to load
     cy.get(".LoadingSpinner").should("not.exist");
-    cy.findByText("Visualize").should("not.exist");
+    cy.button("Visualize").should("not.exist");
 
     cy.log(
       "**Fails in 0.35.0, 0.35.1, 0.35.2, 0.35.4 and the latest master (2020-10-21)**",
@@ -408,7 +408,7 @@ describe("scenarios > question > custom columns", () => {
     cy.get("[class*=NotebookCellItem]")
       .contains(CE_NAME)
       .should("not.exist");
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
 
     cy.wait("@dataset").then(xhr => {
       expect(xhr.response.body.error).to.not.exist;
@@ -427,7 +427,7 @@ describe("scenarios > question > custom columns", () => {
         cy.findByPlaceholderText("Something nice and descriptive").type(
           "CategoryTitle",
         );
-        cy.findByRole("button", { name: "Done" }).click();
+        cy.button("Done").click();
       });
       cy.findByText("Filter").click();
       popover()
@@ -464,7 +464,7 @@ describe("scenarios > question > custom columns", () => {
         cy.findByPlaceholderText("Something nice and descriptive").type(
           "MiscDate",
         );
-        cy.findByRole("button", { name: "Done" }).click();
+        cy.button("Done").click();
       });
       cy.findByText("Filter").click();
       popover()
@@ -485,7 +485,7 @@ describe("scenarios > question > custom columns", () => {
         cy.findByPlaceholderText("Something nice and descriptive").type(
           "MiscDate",
         );
-        cy.findByRole("button", { name: "Done" }).click();
+        cy.button("Done").click();
       });
       cy.findByText("Filter").click();
       popover()
diff --git a/frontend/test/metabase/scenarios/question/filter.cy.spec.js b/frontend/test/metabase/scenarios/question/filter.cy.spec.js
index 4d18cf26b44dff77ecdfe5ab7cdd6724b7395b33..81a3a28c330c09400585b62693ec3b4895f96240 100644
--- a/frontend/test/metabase/scenarios/question/filter.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/filter.cy.spec.js
@@ -218,7 +218,7 @@ describe("scenarios > question > filter", () => {
     cy.findByText("Add filter").click();
     cy.contains("Category is not Gizmo");
 
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
     // wait for results to load
     cy.get(".LoadingSpinner").should("not.exist");
     cy.log("The point of failure in 0.37.0-rc3");
@@ -543,7 +543,7 @@ describe("scenarios > question > filter", () => {
       .click()
       .type("contains(");
     cy.findByText(/Checks to see if string1 contains string2 within it./i);
-    cy.findByRole("button", { name: "Done" }).should("not.be.disabled");
+    cy.button("Done").should("not.be.disabled");
     cy.get(".text-error").should("not.exist");
     cy.findAllByText(/Expected one of these possible Token sequences:/i).should(
       "not.exist",
@@ -701,8 +701,7 @@ describe("scenarios > question > filter", () => {
       .click()
       .clear()
       .type("NOT IsNull([Rating])", { delay: 50 });
-    cy.findAllByRole("button")
-      .contains("Done")
+    cy.button("Done")
       .should("not.be.disabled")
       .click();
 
@@ -713,13 +712,12 @@ describe("scenarios > question > filter", () => {
       .click()
       .clear()
       .type("NOT IsEmpty([Reviewer])", { delay: 50 });
-    cy.findAllByRole("button")
-      .contains("Done")
+    cy.button("Done")
       .should("not.be.disabled")
       .click();
 
     // check that filter is applied and rows displayed
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
     cy.contains("Showing 1,112 rows");
   });
 
@@ -848,7 +846,7 @@ describe("scenarios > question > filter", () => {
     cy.get("[contenteditable='true']").contains(
       'contains([Reviewer], "MULLER")',
     );
-    cy.findByRole("button", { name: "Done" }).click();
+    cy.button("Done").click();
     cy.wait("@dataset.2").then(xhr => {
       expect(xhr.response.body.data.rows).to.have.lengthOf(1);
     });
@@ -863,7 +861,7 @@ describe("scenarios > question > filter", () => {
     cy.get("[contenteditable='true']")
       .click()
       .type("3.14159");
-    cy.findAllByRole("button", { name: "Done" })
+    cy.button("Done")
       .should("not.be.disabled")
       .click();
     cy.findByText("Expecting boolean but found 3.14159");
@@ -877,7 +875,7 @@ describe("scenarios > question > filter", () => {
     cy.get("[contenteditable='true']")
       .click()
       .type('"TheAnswer"');
-    cy.findAllByRole("button", { name: "Done" })
+    cy.button("Done")
       .should("not.be.disabled")
       .click();
     cy.findByText('Expecting boolean but found "TheAnswer"');
@@ -902,7 +900,7 @@ describe("scenarios > question > filter", () => {
       .click();
     cy.findByText("Filter by this column").click();
     cy.findByPlaceholderText("Enter a number").type("42");
-    cy.findByRole("button", { name: "Update filter" })
+    cy.button("Update filter")
       .should("not.be.disabled")
       .click();
     cy.findByText("Doohickey");
diff --git a/frontend/test/metabase/scenarios/question/joins.cy.spec.js b/frontend/test/metabase/scenarios/question/joins.cy.spec.js
index 103bf53c921cb1be6547863c4bcc0ee08ab999c8..fec04792aa9bb2bf22df601e846e1bb854cc3a21 100644
--- a/frontend/test/metabase/scenarios/question/joins.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/joins.cy.spec.js
@@ -35,7 +35,7 @@ describe("scenarios > question > joined questions", () => {
     popover()
       .findByText("Product ID") // Implicit assertion - test will fail for multiple strings
       .click();
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
     cy.wait("@dataset").then(xhr => {
       expect(xhr.response.body.error).not.to.exist;
     });
diff --git a/frontend/test/metabase/scenarios/question/nested.cy.spec.js b/frontend/test/metabase/scenarios/question/nested.cy.spec.js
index fc5fb45362912c962743e0e3fe8a2ad8c96cb005..5b1aefae8484ab6aa3df8f17017f2350982d80d7 100644
--- a/frontend/test/metabase/scenarios/question/nested.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/nested.cy.spec.js
@@ -559,7 +559,7 @@ describe("scenarios > question > nested", () => {
       cy.findByText("Pick a column to group by").click();
       cy.findByText("CAT").click();
 
-      cy.findByText("Visualize").click();
+      cy.button("Visualize").click();
       cy.get("@consoleWarn").should(
         "not.be.calledWith",
         "Removing invalid MBQL clause",
@@ -571,7 +571,7 @@ describe("scenarios > question > nested", () => {
       cy.findByText("Pick a column to group by").click();
       cy.findByText("CAT").click();
 
-      cy.findByText("Visualize").click();
+      cy.button("Visualize").click();
       cy.wait("@dataset");
       cy.findAllByRole("button")
         .contains("Summarize")
diff --git a/frontend/test/metabase/scenarios/question/new.cy.spec.js b/frontend/test/metabase/scenarios/question/new.cy.spec.js
index c2581fd51ba894616f60f3a3b82364e61d3c839f..fe9437fbc028749897e339857a7bec038c5d3e3e 100644
--- a/frontend/test/metabase/scenarios/question/new.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/new.cy.spec.js
@@ -71,7 +71,7 @@ describe("scenarios > question > new", () => {
         .click();
       cy.findByText("Rating").click();
     });
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
     cy.get(".Visualization .bar").should("have.length", 6);
   });
 
@@ -322,7 +322,7 @@ describe("scenarios > question > new", () => {
         cy.findByPlaceholderText("Name (required)").type("twice max total");
         cy.findByText("Done").click();
       });
-      cy.findByText("Visualize").click();
+      cy.button("Visualize").click();
       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 f894df51107e08ad48366510fd0c87f5d71f1a54..1d58a6068958f3cb413f44318e680c1115713d05 100644
--- a/frontend/test/metabase/scenarios/question/notebook.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/notebook.cy.spec.js
@@ -37,7 +37,7 @@ describe("scenarios > question > notebook", () => {
     cy.findByText("Not now").click();
     // enter "notebook" and visualize without changing anything
     cy.icon("notebook").click();
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
 
     // there were no changes to the question, so we shouldn't have the option to "Save"
     cy.findByText("Save").should("not.exist");
@@ -99,7 +99,7 @@ describe("scenarios > question > notebook", () => {
       .click()
       .clear()
       .type("contains([Category])", { delay: 50 });
-    cy.findAllByRole("button", { name: "Done" })
+    cy.button("Done")
       .should("not.be.disabled")
       .click();
     cy.contains(/^Function contains expects 2 arguments/i);
@@ -125,7 +125,7 @@ describe("scenarios > question > notebook", () => {
       .click()
       .clear()
       .type("[Price] > 1");
-    cy.findAllByRole("button", { name: "Done" }).click();
+    cy.button("Done").click();
 
     // change the corresponding custom expression
     cy.findByText("Price is greater than 1").click();
@@ -204,7 +204,7 @@ describe("scenarios > question > notebook", () => {
           }
         });
       cy.findByText("wolf.dina@yahoo.com").click();
-      cy.findByRole("button", { name: "Add filter" }).click();
+      cy.button("Add filter").click();
       cy.contains("Showing 1 row");
     });
 
@@ -231,7 +231,7 @@ describe("scenarios > question > notebook", () => {
       popover().within(() => cy.findByText("A_COLUMN").click());
       popover().within(() => cy.findByText("B_COLUMN").click());
 
-      cy.findByText("Visualize").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
 
       // check that query worked
@@ -265,13 +265,12 @@ describe("scenarios > question > notebook", () => {
           .click()
           .type("Sum Divide");
 
-        cy.findAllByRole("button")
-          .contains("Done")
+        cy.button("Done")
           .should("not.be.disabled")
           .click();
       });
       cy.route("POST", "/api/dataset").as("visualization");
-      cy.findByText("Visualize").click();
+      cy.button("Visualize").click();
 
       cy.wait("@visualization").then(xhr => {
         expect(xhr.response.body.error).not.to.exist;
@@ -390,7 +389,7 @@ describe("scenarios > question > notebook", () => {
       popover()
         .contains(/Products? → Category/)
         .click();
-      cy.findByText("Visualize").click();
+      cy.button("Visualize").click();
 
       cy.findByText("12928_Q1 + 12928_Q2");
       cy.log("Reported failing in v1.35.4.1 and `master` on July, 16 2020");
@@ -661,7 +660,7 @@ describe("scenarios > question > notebook", () => {
         .click();
       cy.findByText("Greater than").click();
       cy.findByPlaceholderText("Enter a number").type(0);
-      cy.findByRole("button", { name: "Add filter" })
+      cy.button("Add filter")
         .should("not.be.disabled")
         .click();
     });
@@ -736,7 +735,7 @@ describe("scenarios > question > notebook", () => {
         cy.findByText("Add filter").click();
       });
 
-      cy.findByText("Visualize").click();
+      cy.button("Visualize").click();
       cy.findByText("Gadget").should("exist");
       cy.findByText("Gizmo").should("not.exist");
 
@@ -773,11 +772,11 @@ describe("scenarios > question > notebook", () => {
         .click()
         .type("Example", { delay: 100 });
 
-      cy.findAllByRole("button", { name: "Done" })
+      cy.button("Done")
         .should("not.be.disabled")
         .click();
 
-      cy.findAllByRole("button", { name: "Visualize" }).click();
+      cy.button("Visualize").click();
       cy.contains("Example");
       cy.contains("Big");
       cy.contains("Small");
@@ -794,11 +793,11 @@ describe("scenarios > question > notebook", () => {
 
       cy.contains(/^redundant input/i).should("not.exist");
 
-      cy.findAllByRole("button", { name: "Done" })
+      cy.button("Done")
         .should("not.be.disabled")
         .click();
 
-      cy.findAllByRole("button", { name: "Visualize" }).click();
+      cy.button("Visualize").click();
       cy.contains("Showing 97 rows");
     });
 
@@ -825,11 +824,11 @@ describe("scenarios > question > notebook", () => {
         cy.contains(/^expected closing parenthesis/i).should("not.exist");
         cy.contains(/^redundant input/i).should("not.exist");
 
-        cy.findAllByRole("button", { name: "Done" })
+        cy.button("Done")
           .should("not.be.disabled")
           .click();
 
-        cy.findAllByRole("button", { name: "Visualize" }).click();
+        cy.button("Visualize").click();
         cy.contains(filter);
         cy.contains(result);
       });
@@ -881,7 +880,7 @@ describe("scenarios > question > notebook", () => {
         .click()
         .clear()
         .type('[Category] = "widget', { delay: 50 });
-      cy.findAllByRole("button", { name: "Done" })
+      cy.button("Done")
         .should("not.be.disabled")
         .click();
       cy.findByText("Missing closing quotes");
@@ -1038,7 +1037,7 @@ function joinTwoSavedQuestions(ALIAS = "Joined Question") {
         cy.log("Reported in v0.36.0");
         cy.icon("notebook").click();
         cy.url().should("contain", "/notebook");
-        cy.findByText("Visualize").should("exist");
+        cy.button("Visualize").should("exist");
       });
     });
   });
diff --git a/frontend/test/metabase/scenarios/question/saved.cy.spec.js b/frontend/test/metabase/scenarios/question/saved.cy.spec.js
index ef63c93b64a07bd471f2476e2d87c752e42c52e8..a0fcfebe9276f8ce3c95b12b5ef1b2df50f0789e 100644
--- a/frontend/test/metabase/scenarios/question/saved.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/saved.cy.spec.js
@@ -38,7 +38,7 @@ describe("scenarios > question > saved", () => {
 
     modal().within(() => {
       cy.findByText("Save question");
-      cy.findByRole("button", { name: /save/i }).as("saveButton");
+      cy.button("Save").as("saveButton");
       cy.get("@saveButton").should("not.be.disabled");
 
       cy.log(
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 dde401f5860a3512b00216b1e8d8f241d5cec6bb..693b9aa4306f346984d36750b81933f31a1cd347 100644
--- a/frontend/test/metabase/scenarios/sharing/alert/alert.cy.spec.js
+++ b/frontend/test/metabase/scenarios/sharing/alert/alert.cy.spec.js
@@ -175,7 +175,7 @@ describe("scenarios > alert", () => {
         cy.findByPlaceholderText("Find...").type("Cr");
         cy.findByText("Created At").click();
       });
-      cy.findByText("Visualize").click();
+      cy.button("Visualize").click();
 
       // Set a goal
       setGoal("35");
diff --git a/frontend/test/metabase/scenarios/sharing/subscriptions.cy.spec.js b/frontend/test/metabase/scenarios/sharing/subscriptions.cy.spec.js
index b93de97b71de1993d1755546e6955af1361461b8..4e884da02388e5f1919d7faed778f72df462afba 100644
--- a/frontend/test/metabase/scenarios/sharing/subscriptions.cy.spec.js
+++ b/frontend/test/metabase/scenarios/sharing/subscriptions.cy.spec.js
@@ -26,24 +26,6 @@ describe("scenarios > dashboard > subscriptions", () => {
       .should("have.class", "cursor-default");
   });
 
-  it.skip("should allow sharing if dashboard contains only text cards (metabase#15077)", () => {
-    cy.createDashboard("15077D").then(({ body: { id: DASHBOARD_ID } }) => {
-      cy.visit(`/dashboard/${DASHBOARD_ID}`);
-    });
-    cy.icon("pencil").click();
-    cy.icon("string").click();
-    cy.findByPlaceholderText("Write here, and use Markdown if you'd like")
-      .click()
-      .type("Foo");
-    cy.findByRole("button", { name: "Save" }).click();
-    cy.findByText("You're editing this dashboard.").should("not.exist");
-    cy.icon("share")
-      .closest("a")
-      .should("have.class", "cursor-pointer")
-      .click();
-    cy.findByText("Dashboard subscriptions").click();
-  });
-
   describe("with no channels set up", () => {
     it("should instruct user to connect email or slack", () => {
       openDashboardSubscriptions();
@@ -196,6 +178,28 @@ describe("scenarios > dashboard > subscriptions", () => {
         );
       });
     });
+
+    it.skip("should include text cards (metabase#15744)", () => {
+      const TEXT_CARD = "FooBar";
+
+      cy.visit("/dashboard/1");
+      cy.icon("pencil").click();
+      cy.icon("string").click();
+      cy.findByPlaceholderText(
+        "Write here, and use Markdown if you'd like",
+      ).type(TEXT_CARD);
+      cy.button("Save").click();
+      cy.findByText("You're editing this dashboard.").should("not.exist");
+      assignRecipient();
+      // Click outside popover to close it and at the same time check that the text card content is shown as expected
+      cy.findByText(TEXT_CARD).click();
+      cy.findByText("Send email now").click();
+      cy.findByText(/^Sending/);
+      cy.findByText("Email sent");
+      cy.request("GET", "http://localhost:80/email").then(({ body }) => {
+        expect(body[0].html).to.include(TEXT_CARD);
+      });
+    });
   });
 
   describe("with Slack set up", () => {
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 2f3f688bf5ba0966c22e750cbcacfab9270ec543..91c9ac4628310c71b0a7e001d19b4f2131151694 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
@@ -240,7 +240,7 @@ describe("scenarios > visualizations > drillthroughs > chart drill", () => {
     cy.findByText("Add filter").click();
 
     // Visualize: line
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
     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 54a937a4f61a9ddffffade79248bbe741e8b20ce..b8cdc14eeae27e4677a0edcdee252e7da9914216 100644
--- a/frontend/test/metabase/scenarios/visualizations/trendline.cy.spec.js
+++ b/frontend/test/metabase/scenarios/visualizations/trendline.cy.spec.js
@@ -26,10 +26,10 @@ describe("scenarios > question > trendline", () => {
     cy.findByText("by month").click();
     cy.findByText("Year").click();
 
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
 
     // Check graph is there
-    cy.findByText("Visualize").should("not.exist");
+    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 2bccfdbb33e60a96acd36353833f943313cafbc9..ef55ac856f582b33e31f34a527025089f7367277 100644
--- a/frontend/test/metabase/scenarios/visualizations/waterfall.cy.spec.js
+++ b/frontend/test/metabase/scenarios/visualizations/waterfall.cy.spec.js
@@ -84,9 +84,9 @@ describe("scenarios > visualizations > waterfall", () => {
     cy.get("[contenteditable=true]")
       .type("between([Created At], '2016-01-01', '2016-08-01')")
       .blur();
-    cy.findByRole("button", { name: "Done" }).click();
+    cy.button("Done").click();
 
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
     cy.contains("Visualization").click();
     cy.icon("waterfall").click();
 
@@ -100,7 +100,7 @@ describe("scenarios > visualizations > waterfall", () => {
     cy.findByText("Pick a column to group by").click();
     cy.findByText("Created At").click();
 
-    cy.findByText("Visualize").click();
+    cy.button("Visualize").click();
     cy.contains("Visualization").click();
     cy.icon("waterfall").click();